mirror of
https://github.com/BagelHole/DevOps-Security-Agent-Skills.git
synced 2026-08-22 12:49:53 +02:00
.
This commit is contained in:
@@ -0,0 +1,85 @@
|
||||
---
|
||||
name: aws-secrets-manager
|
||||
description: Store and rotate secrets in AWS Secrets Manager. Configure automatic rotation, access policies, and application integration. Use when managing secrets in AWS environments or requiring automatic credential rotation.
|
||||
license: MIT
|
||||
metadata:
|
||||
author: devops-skills
|
||||
version: "1.0"
|
||||
---
|
||||
|
||||
# AWS Secrets Manager
|
||||
|
||||
Securely store, manage, and rotate secrets in AWS.
|
||||
|
||||
## When to Use This Skill
|
||||
|
||||
Use this skill when:
|
||||
- Storing database credentials
|
||||
- Managing API keys in AWS
|
||||
- Implementing automatic secret rotation
|
||||
- Integrating secrets with AWS services
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- AWS account
|
||||
- AWS CLI configured
|
||||
- IAM permissions for Secrets Manager
|
||||
|
||||
## Basic Operations
|
||||
|
||||
```bash
|
||||
# Create secret
|
||||
aws secretsmanager create-secret \
|
||||
--name myapp/database \
|
||||
--secret-string '{"username":"admin","password":"secret123"}'
|
||||
|
||||
# Get secret
|
||||
aws secretsmanager get-secret-value --secret-id myapp/database
|
||||
|
||||
# Update secret
|
||||
aws secretsmanager put-secret-value \
|
||||
--secret-id myapp/database \
|
||||
--secret-string '{"username":"admin","password":"newpassword"}'
|
||||
|
||||
# Delete secret
|
||||
aws secretsmanager delete-secret --secret-id myapp/database --recovery-window-in-days 7
|
||||
```
|
||||
|
||||
## Automatic Rotation
|
||||
|
||||
```bash
|
||||
# Enable rotation with Lambda
|
||||
aws secretsmanager rotate-secret \
|
||||
--secret-id myapp/database \
|
||||
--rotation-lambda-arn arn:aws:lambda:region:account:function:rotation-function \
|
||||
--rotation-rules AutomaticallyAfterDays=30
|
||||
```
|
||||
|
||||
## Application Integration
|
||||
|
||||
```python
|
||||
import boto3
|
||||
import json
|
||||
|
||||
def get_secret(secret_name):
|
||||
client = boto3.client('secretsmanager')
|
||||
response = client.get_secret_value(SecretId=secret_name)
|
||||
return json.loads(response['SecretString'])
|
||||
|
||||
# Usage
|
||||
creds = get_secret('myapp/database')
|
||||
db_connect(creds['username'], creds['password'])
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
- Enable automatic rotation
|
||||
- Use resource-based policies
|
||||
- Enable encryption with KMS
|
||||
- Implement least-privilege access
|
||||
- Use versioning for rollback
|
||||
|
||||
## Related Skills
|
||||
|
||||
- [hashicorp-vault](../hashicorp-vault/) - Multi-cloud secrets
|
||||
- [aws-iam](../../../infrastructure/cloud-aws/aws-iam/) - IAM policies
|
||||
@@ -0,0 +1,115 @@
|
||||
# AWS Secrets Manager Patterns
|
||||
|
||||
## Basic Operations
|
||||
|
||||
```bash
|
||||
# Create secret
|
||||
aws secretsmanager create-secret \
|
||||
--name myapp/database \
|
||||
--secret-string '{"username":"admin","password":"secret123"}'
|
||||
|
||||
# Get secret
|
||||
aws secretsmanager get-secret-value --secret-id myapp/database
|
||||
|
||||
# Update secret
|
||||
aws secretsmanager update-secret \
|
||||
--secret-id myapp/database \
|
||||
--secret-string '{"username":"admin","password":"newsecret"}'
|
||||
|
||||
# Delete secret
|
||||
aws secretsmanager delete-secret --secret-id myapp/database --force-delete-without-recovery
|
||||
```
|
||||
|
||||
## Automatic Rotation
|
||||
|
||||
```python
|
||||
# Lambda rotation function
|
||||
def lambda_handler(event, context):
|
||||
secret_id = event['SecretId']
|
||||
token = event['ClientRequestToken']
|
||||
step = event['Step']
|
||||
|
||||
if step == "createSecret":
|
||||
create_secret(secret_id, token)
|
||||
elif step == "setSecret":
|
||||
set_secret(secret_id, token)
|
||||
elif step == "testSecret":
|
||||
test_secret(secret_id, token)
|
||||
elif step == "finishSecret":
|
||||
finish_secret(secret_id, token)
|
||||
```
|
||||
|
||||
## Terraform
|
||||
|
||||
```hcl
|
||||
resource "aws_secretsmanager_secret" "db" {
|
||||
name = "myapp/database"
|
||||
|
||||
tags = {
|
||||
Environment = "production"
|
||||
}
|
||||
}
|
||||
|
||||
resource "aws_secretsmanager_secret_version" "db" {
|
||||
secret_id = aws_secretsmanager_secret.db.id
|
||||
secret_string = jsonencode({
|
||||
username = "admin"
|
||||
password = random_password.db.result
|
||||
})
|
||||
}
|
||||
|
||||
# Rotation
|
||||
resource "aws_secretsmanager_secret_rotation" "db" {
|
||||
secret_id = aws_secretsmanager_secret.db.id
|
||||
rotation_lambda_arn = aws_lambda_function.rotation.arn
|
||||
|
||||
rotation_rules {
|
||||
automatically_after_days = 30
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Application Integration
|
||||
|
||||
### Python (boto3)
|
||||
```python
|
||||
import boto3
|
||||
import json
|
||||
|
||||
def get_secret(secret_name):
|
||||
client = boto3.client('secretsmanager')
|
||||
response = client.get_secret_value(SecretId=secret_name)
|
||||
return json.loads(response['SecretString'])
|
||||
|
||||
# Usage
|
||||
creds = get_secret('myapp/database')
|
||||
connection = connect(
|
||||
host=creds['host'],
|
||||
user=creds['username'],
|
||||
password=creds['password']
|
||||
)
|
||||
```
|
||||
|
||||
### ECS Task Definition
|
||||
```json
|
||||
{
|
||||
"containerDefinitions": [{
|
||||
"secrets": [{
|
||||
"name": "DATABASE_PASSWORD",
|
||||
"valueFrom": "arn:aws:secretsmanager:us-east-1:123456789:secret:myapp/database:password::"
|
||||
}]
|
||||
}]
|
||||
}
|
||||
```
|
||||
|
||||
### Lambda
|
||||
```yaml
|
||||
# SAM template
|
||||
Environment:
|
||||
Variables:
|
||||
SECRET_ARN: !Ref DatabaseSecret
|
||||
|
||||
Policies:
|
||||
- AWSSecretsManagerGetSecretValuePolicy:
|
||||
SecretArn: !Ref DatabaseSecret
|
||||
```
|
||||
@@ -0,0 +1,88 @@
|
||||
---
|
||||
name: azure-keyvault
|
||||
description: Manage secrets and certificates in Azure Key Vault. Configure access policies, integrate with Azure services, and implement secure secret management. Use when managing secrets in Azure environments.
|
||||
license: MIT
|
||||
metadata:
|
||||
author: devops-skills
|
||||
version: "1.0"
|
||||
---
|
||||
|
||||
# Azure Key Vault
|
||||
|
||||
Securely store and manage secrets, keys, and certificates in Azure.
|
||||
|
||||
## When to Use This Skill
|
||||
|
||||
Use this skill when:
|
||||
- Managing secrets in Azure
|
||||
- Storing encryption keys
|
||||
- Managing SSL certificates
|
||||
- Integrating with Azure services
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Azure subscription
|
||||
- Azure CLI installed
|
||||
- Appropriate RBAC permissions
|
||||
|
||||
## Basic Operations
|
||||
|
||||
```bash
|
||||
# Create Key Vault
|
||||
az keyvault create --name mykeyvault --resource-group mygroup --location eastus
|
||||
|
||||
# Set secret
|
||||
az keyvault secret set --vault-name mykeyvault --name db-password --value "secret123"
|
||||
|
||||
# Get secret
|
||||
az keyvault secret show --vault-name mykeyvault --name db-password
|
||||
|
||||
# List secrets
|
||||
az keyvault secret list --vault-name mykeyvault
|
||||
```
|
||||
|
||||
## Application Integration
|
||||
|
||||
```python
|
||||
from azure.identity import DefaultAzureCredential
|
||||
from azure.keyvault.secrets import SecretClient
|
||||
|
||||
credential = DefaultAzureCredential()
|
||||
client = SecretClient(vault_url="https://mykeyvault.vault.azure.net/", credential=credential)
|
||||
|
||||
# Get secret
|
||||
secret = client.get_secret("db-password")
|
||||
print(secret.value)
|
||||
```
|
||||
|
||||
## Kubernetes Integration
|
||||
|
||||
```yaml
|
||||
apiVersion: secrets-store.csi.x-k8s.io/v1
|
||||
kind: SecretProviderClass
|
||||
metadata:
|
||||
name: azure-keyvault
|
||||
spec:
|
||||
provider: azure
|
||||
parameters:
|
||||
keyvaultName: "mykeyvault"
|
||||
objects: |
|
||||
array:
|
||||
- |
|
||||
objectName: db-password
|
||||
objectType: secret
|
||||
tenantId: "tenant-id"
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
- Use managed identities
|
||||
- Enable soft-delete and purge protection
|
||||
- Implement access policies carefully
|
||||
- Use private endpoints
|
||||
- Monitor with Azure Monitor
|
||||
|
||||
## Related Skills
|
||||
|
||||
- [hashicorp-vault](../hashicorp-vault/) - Multi-cloud secrets
|
||||
- [azure-networking](../../../infrastructure/cloud-azure/azure-networking/) - Network security
|
||||
@@ -0,0 +1,81 @@
|
||||
---
|
||||
name: gcp-secret-manager
|
||||
description: Secure secrets in Google Cloud Secret Manager. Configure IAM policies, integrate with GKE, and manage secret versions. Use when managing secrets in GCP environments.
|
||||
license: MIT
|
||||
metadata:
|
||||
author: devops-skills
|
||||
version: "1.0"
|
||||
---
|
||||
|
||||
# GCP Secret Manager
|
||||
|
||||
Store and manage secrets securely in Google Cloud Platform.
|
||||
|
||||
## When to Use This Skill
|
||||
|
||||
Use this skill when:
|
||||
- Managing secrets in GCP
|
||||
- Integrating with GKE workloads
|
||||
- Storing API keys and credentials
|
||||
- Implementing secret rotation
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- GCP project
|
||||
- gcloud CLI configured
|
||||
- Secret Manager API enabled
|
||||
|
||||
## Basic Operations
|
||||
|
||||
```bash
|
||||
# Create secret
|
||||
echo -n "secret123" | gcloud secrets create db-password --data-file=-
|
||||
|
||||
# Access secret
|
||||
gcloud secrets versions access latest --secret=db-password
|
||||
|
||||
# Add new version
|
||||
echo -n "newsecret" | gcloud secrets versions add db-password --data-file=-
|
||||
|
||||
# List secrets
|
||||
gcloud secrets list
|
||||
```
|
||||
|
||||
## Application Integration
|
||||
|
||||
```python
|
||||
from google.cloud import secretmanager
|
||||
|
||||
client = secretmanager.SecretManagerServiceClient()
|
||||
name = f"projects/my-project/secrets/db-password/versions/latest"
|
||||
response = client.access_secret_version(request={"name": name})
|
||||
secret = response.payload.data.decode("UTF-8")
|
||||
```
|
||||
|
||||
## GKE Integration
|
||||
|
||||
```yaml
|
||||
apiVersion: secrets-store.csi.x-k8s.io/v1
|
||||
kind: SecretProviderClass
|
||||
metadata:
|
||||
name: gcp-secrets
|
||||
spec:
|
||||
provider: gcp
|
||||
parameters:
|
||||
secrets: |
|
||||
- resourceName: "projects/my-project/secrets/db-password/versions/latest"
|
||||
path: "db-password"
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
- Use Workload Identity for GKE
|
||||
- Implement IAM least-privilege
|
||||
- Enable audit logging
|
||||
- Use secret versions for rollback
|
||||
- Integrate with Cloud KMS for encryption
|
||||
|
||||
## Related Skills
|
||||
|
||||
- [hashicorp-vault](../hashicorp-vault/) - Multi-cloud secrets
|
||||
- [gcp-gke](../../../infrastructure/cloud-gcp/gcp-gke/) - GKE integration
|
||||
@@ -0,0 +1,384 @@
|
||||
---
|
||||
name: hashicorp-vault
|
||||
description: Manage secrets and PKI with HashiCorp Vault. Configure secret engines, authentication methods, and policies. Use when implementing centralized secrets management, dynamic credentials, or certificate management.
|
||||
license: MIT
|
||||
metadata:
|
||||
author: devops-skills
|
||||
version: "1.0"
|
||||
---
|
||||
|
||||
# HashiCorp Vault
|
||||
|
||||
Centrally manage secrets, encryption, and access with HashiCorp Vault.
|
||||
|
||||
## When to Use This Skill
|
||||
|
||||
Use this skill when:
|
||||
- Centralizing secrets management
|
||||
- Implementing dynamic credentials
|
||||
- Managing PKI and certificates
|
||||
- Encrypting sensitive data
|
||||
- Meeting compliance requirements
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Vault server (dev or production)
|
||||
- Vault CLI installed
|
||||
- Network access to Vault
|
||||
|
||||
## Quick Start
|
||||
|
||||
### Development Server
|
||||
|
||||
```bash
|
||||
# Start dev server
|
||||
vault server -dev
|
||||
|
||||
# Set environment
|
||||
export VAULT_ADDR='http://127.0.0.1:8200'
|
||||
export VAULT_TOKEN='root'
|
||||
|
||||
# Verify connection
|
||||
vault status
|
||||
```
|
||||
|
||||
### Production Deployment
|
||||
|
||||
```hcl
|
||||
# config.hcl
|
||||
storage "raft" {
|
||||
path = "/opt/vault/data"
|
||||
node_id = "vault-1"
|
||||
}
|
||||
|
||||
listener "tcp" {
|
||||
address = "0.0.0.0:8200"
|
||||
tls_cert_file = "/opt/vault/tls/vault.crt"
|
||||
tls_key_file = "/opt/vault/tls/vault.key"
|
||||
}
|
||||
|
||||
api_addr = "https://vault.example.com:8200"
|
||||
cluster_addr = "https://vault.example.com:8201"
|
||||
|
||||
ui = true
|
||||
```
|
||||
|
||||
```bash
|
||||
# Initialize Vault
|
||||
vault operator init -key-shares=5 -key-threshold=3
|
||||
|
||||
# Unseal (run 3 times with different keys)
|
||||
vault operator unseal <key-1>
|
||||
vault operator unseal <key-2>
|
||||
vault operator unseal <key-3>
|
||||
|
||||
# Login
|
||||
vault login <root-token>
|
||||
```
|
||||
|
||||
## Secret Engines
|
||||
|
||||
### KV Secrets
|
||||
|
||||
```bash
|
||||
# Enable KV v2
|
||||
vault secrets enable -path=secret kv-v2
|
||||
|
||||
# Write secret
|
||||
vault kv put secret/myapp/config \
|
||||
username="admin" \
|
||||
password="s3cr3t"
|
||||
|
||||
# Read secret
|
||||
vault kv get secret/myapp/config
|
||||
vault kv get -field=password secret/myapp/config
|
||||
|
||||
# Update secret
|
||||
vault kv put secret/myapp/config \
|
||||
username="admin" \
|
||||
password="new-password"
|
||||
|
||||
# List secrets
|
||||
vault kv list secret/
|
||||
|
||||
# Delete secret
|
||||
vault kv delete secret/myapp/config
|
||||
|
||||
# Version history
|
||||
vault kv metadata get secret/myapp/config
|
||||
```
|
||||
|
||||
### Database Secrets
|
||||
|
||||
```bash
|
||||
# Enable database engine
|
||||
vault secrets enable database
|
||||
|
||||
# Configure PostgreSQL connection
|
||||
vault write database/config/postgresql \
|
||||
plugin_name=postgresql-database-plugin \
|
||||
connection_url="postgresql://{{username}}:{{password}}@localhost:5432/mydb" \
|
||||
allowed_roles="readonly,readwrite" \
|
||||
username="vault" \
|
||||
password="vault-password"
|
||||
|
||||
# Create role
|
||||
vault write database/roles/readonly \
|
||||
db_name=postgresql \
|
||||
creation_statements="CREATE ROLE \"{{name}}\" WITH LOGIN PASSWORD '{{password}}' VALID UNTIL '{{expiration}}'; \
|
||||
GRANT SELECT ON ALL TABLES IN SCHEMA public TO \"{{name}}\";" \
|
||||
default_ttl="1h" \
|
||||
max_ttl="24h"
|
||||
|
||||
# Get credentials
|
||||
vault read database/creds/readonly
|
||||
```
|
||||
|
||||
### AWS Secrets
|
||||
|
||||
```bash
|
||||
# Enable AWS engine
|
||||
vault secrets enable aws
|
||||
|
||||
# Configure root credentials
|
||||
vault write aws/config/root \
|
||||
access_key=AKIA... \
|
||||
secret_key=secret... \
|
||||
region=us-east-1
|
||||
|
||||
# Create role
|
||||
vault write aws/roles/deploy \
|
||||
credential_type=iam_user \
|
||||
policy_document=-<<EOF
|
||||
{
|
||||
"Version": "2012-10-17",
|
||||
"Statement": [
|
||||
{
|
||||
"Effect": "Allow",
|
||||
"Action": ["s3:*"],
|
||||
"Resource": ["arn:aws:s3:::my-bucket/*"]
|
||||
}
|
||||
]
|
||||
}
|
||||
EOF
|
||||
|
||||
# Get credentials
|
||||
vault read aws/creds/deploy
|
||||
```
|
||||
|
||||
### PKI Secrets
|
||||
|
||||
```bash
|
||||
# Enable PKI engine
|
||||
vault secrets enable pki
|
||||
vault secrets tune -max-lease-ttl=87600h pki
|
||||
|
||||
# Generate root CA
|
||||
vault write -field=certificate pki/root/generate/internal \
|
||||
common_name="example.com" \
|
||||
ttl=87600h > ca_cert.crt
|
||||
|
||||
# Configure URLs
|
||||
vault write pki/config/urls \
|
||||
issuing_certificates="https://vault.example.com:8200/v1/pki/ca" \
|
||||
crl_distribution_points="https://vault.example.com:8200/v1/pki/crl"
|
||||
|
||||
# Create role
|
||||
vault write pki/roles/web-server \
|
||||
allowed_domains="example.com" \
|
||||
allow_subdomains=true \
|
||||
max_ttl="720h"
|
||||
|
||||
# Issue certificate
|
||||
vault write pki/issue/web-server \
|
||||
common_name="web.example.com" \
|
||||
ttl="24h"
|
||||
```
|
||||
|
||||
## Authentication Methods
|
||||
|
||||
### AppRole
|
||||
|
||||
```bash
|
||||
# Enable AppRole
|
||||
vault auth enable approle
|
||||
|
||||
# Create role
|
||||
vault write auth/approle/role/myapp \
|
||||
token_policies="myapp-policy" \
|
||||
token_ttl=1h \
|
||||
token_max_ttl=4h \
|
||||
secret_id_ttl=10m
|
||||
|
||||
# Get role ID
|
||||
vault read auth/approle/role/myapp/role-id
|
||||
|
||||
# Generate secret ID
|
||||
vault write -f auth/approle/role/myapp/secret-id
|
||||
|
||||
# Login
|
||||
vault write auth/approle/login \
|
||||
role_id=<role-id> \
|
||||
secret_id=<secret-id>
|
||||
```
|
||||
|
||||
### Kubernetes
|
||||
|
||||
```bash
|
||||
# Enable Kubernetes auth
|
||||
vault auth enable kubernetes
|
||||
|
||||
# Configure
|
||||
vault write auth/kubernetes/config \
|
||||
kubernetes_host="https://kubernetes.default.svc" \
|
||||
kubernetes_ca_cert=@/var/run/secrets/kubernetes.io/serviceaccount/ca.crt
|
||||
|
||||
# Create role
|
||||
vault write auth/kubernetes/role/myapp \
|
||||
bound_service_account_names=myapp \
|
||||
bound_service_account_namespaces=default \
|
||||
policies=myapp-policy \
|
||||
ttl=1h
|
||||
```
|
||||
|
||||
### OIDC
|
||||
|
||||
```bash
|
||||
# Enable OIDC auth
|
||||
vault auth enable oidc
|
||||
|
||||
# Configure
|
||||
vault write auth/oidc/config \
|
||||
oidc_discovery_url="https://accounts.google.com" \
|
||||
oidc_client_id="your-client-id" \
|
||||
oidc_client_secret="your-client-secret" \
|
||||
default_role="default"
|
||||
|
||||
# Create role
|
||||
vault write auth/oidc/role/default \
|
||||
bound_audiences="your-client-id" \
|
||||
allowed_redirect_uris="http://localhost:8250/oidc/callback" \
|
||||
user_claim="sub" \
|
||||
policies="default"
|
||||
```
|
||||
|
||||
## Policies
|
||||
|
||||
### Policy Definition
|
||||
|
||||
```hcl
|
||||
# myapp-policy.hcl
|
||||
# Read secrets
|
||||
path "secret/data/myapp/*" {
|
||||
capabilities = ["read", "list"]
|
||||
}
|
||||
|
||||
# Database credentials
|
||||
path "database/creds/myapp-db" {
|
||||
capabilities = ["read"]
|
||||
}
|
||||
|
||||
# PKI certificates
|
||||
path "pki/issue/web-server" {
|
||||
capabilities = ["create", "update"]
|
||||
}
|
||||
|
||||
# Deny access to other secrets
|
||||
path "secret/data/other/*" {
|
||||
capabilities = ["deny"]
|
||||
}
|
||||
```
|
||||
|
||||
```bash
|
||||
# Create policy
|
||||
vault policy write myapp myapp-policy.hcl
|
||||
|
||||
# List policies
|
||||
vault policy list
|
||||
|
||||
# Read policy
|
||||
vault policy read myapp
|
||||
```
|
||||
|
||||
## Application Integration
|
||||
|
||||
### Python
|
||||
|
||||
```python
|
||||
import hvac
|
||||
|
||||
# Initialize client
|
||||
client = hvac.Client(url='http://localhost:8200')
|
||||
|
||||
# AppRole authentication
|
||||
client.auth.approle.login(
|
||||
role_id='role-id',
|
||||
secret_id='secret-id'
|
||||
)
|
||||
|
||||
# Read secret
|
||||
secret = client.secrets.kv.v2.read_secret_version(
|
||||
path='myapp/config',
|
||||
mount_point='secret'
|
||||
)
|
||||
password = secret['data']['data']['password']
|
||||
|
||||
# Get database credentials
|
||||
db_creds = client.secrets.database.generate_credentials(
|
||||
name='myapp-db'
|
||||
)
|
||||
```
|
||||
|
||||
### Kubernetes Sidecar
|
||||
|
||||
```yaml
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: myapp
|
||||
annotations:
|
||||
vault.hashicorp.com/agent-inject: "true"
|
||||
vault.hashicorp.com/role: "myapp"
|
||||
vault.hashicorp.com/agent-inject-secret-config: "secret/data/myapp/config"
|
||||
vault.hashicorp.com/agent-inject-template-config: |
|
||||
{{- with secret "secret/data/myapp/config" -}}
|
||||
export DB_PASSWORD="{{ .Data.data.password }}"
|
||||
{{- end }}
|
||||
spec:
|
||||
serviceAccountName: myapp
|
||||
containers:
|
||||
- name: myapp
|
||||
image: myapp:latest
|
||||
command: ["/bin/sh", "-c", "source /vault/secrets/config && ./start.sh"]
|
||||
```
|
||||
|
||||
## Common Issues
|
||||
|
||||
### Issue: Sealed Vault
|
||||
**Problem**: Vault is sealed after restart
|
||||
**Solution**: Implement auto-unseal with cloud KMS or HSM
|
||||
|
||||
### Issue: Token Expired
|
||||
**Problem**: Application token has expired
|
||||
**Solution**: Implement token renewal, use shorter-lived tokens
|
||||
|
||||
### Issue: Permission Denied
|
||||
**Problem**: Cannot access secrets
|
||||
**Solution**: Review policies, check token capabilities
|
||||
|
||||
## Best Practices
|
||||
|
||||
- Use short-lived tokens
|
||||
- Implement auto-unseal
|
||||
- Enable audit logging
|
||||
- Use namespaces for isolation
|
||||
- Rotate root tokens regularly
|
||||
- Implement least-privilege policies
|
||||
- Use dynamic secrets where possible
|
||||
- Regular backup and DR testing
|
||||
|
||||
## Related Skills
|
||||
|
||||
- [aws-secrets-manager](../aws-secrets-manager/) - AWS native secrets
|
||||
- [sops-encryption](../sops-encryption/) - File encryption
|
||||
- [kubernetes-hardening](../../hardening/kubernetes-hardening/) - K8s security
|
||||
@@ -0,0 +1,81 @@
|
||||
# Kubernetes Authentication for Vault
|
||||
# Enables pods to authenticate with Vault using service accounts
|
||||
|
||||
---
|
||||
# ServiceAccount for Vault auth
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: vault-auth
|
||||
namespace: vault
|
||||
|
||||
---
|
||||
# ClusterRoleBinding for token review
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRoleBinding
|
||||
metadata:
|
||||
name: vault-tokenreview-binding
|
||||
roleRef:
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
kind: ClusterRole
|
||||
name: system:auth-delegator
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: vault-auth
|
||||
namespace: vault
|
||||
|
||||
---
|
||||
# Secret for SA token (K8s 1.24+)
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: vault-auth-token
|
||||
namespace: vault
|
||||
annotations:
|
||||
kubernetes.io/service-account.name: vault-auth
|
||||
type: kubernetes.io/service-account-token
|
||||
|
||||
---
|
||||
# Example: Application ServiceAccount
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: myapp
|
||||
namespace: myapp
|
||||
|
||||
---
|
||||
# Example: Pod using Vault Agent Injector
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: myapp
|
||||
namespace: myapp
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: myapp
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: myapp
|
||||
annotations:
|
||||
# Vault Agent Injector annotations
|
||||
vault.hashicorp.com/agent-inject: "true"
|
||||
vault.hashicorp.com/role: "myapp"
|
||||
vault.hashicorp.com/agent-inject-secret-config: "secret/data/myapp/config"
|
||||
vault.hashicorp.com/agent-inject-template-config: |
|
||||
{{- with secret "secret/data/myapp/config" -}}
|
||||
DATABASE_URL={{ .Data.data.database_url }}
|
||||
API_KEY={{ .Data.data.api_key }}
|
||||
{{- end }}
|
||||
spec:
|
||||
serviceAccountName: myapp
|
||||
containers:
|
||||
- name: myapp
|
||||
image: myapp:latest
|
||||
# Secrets available at /vault/secrets/config
|
||||
volumeMounts:
|
||||
- name: secrets
|
||||
mountPath: /vault/secrets
|
||||
readOnly: true
|
||||
@@ -0,0 +1,62 @@
|
||||
# Vault Server Configuration
|
||||
# /etc/vault.d/vault.hcl
|
||||
|
||||
# Cluster name
|
||||
cluster_name = "production"
|
||||
|
||||
# Storage backend (Raft for HA)
|
||||
storage "raft" {
|
||||
path = "/opt/vault/data"
|
||||
node_id = "vault-1"
|
||||
|
||||
retry_join {
|
||||
leader_api_addr = "https://vault-2.example.com:8200"
|
||||
}
|
||||
retry_join {
|
||||
leader_api_addr = "https://vault-3.example.com:8200"
|
||||
}
|
||||
}
|
||||
|
||||
# Listener configuration
|
||||
listener "tcp" {
|
||||
address = "0.0.0.0:8200"
|
||||
cluster_address = "0.0.0.0:8201"
|
||||
tls_cert_file = "/opt/vault/tls/vault.crt"
|
||||
tls_key_file = "/opt/vault/tls/vault.key"
|
||||
|
||||
# TLS settings
|
||||
tls_min_version = "tls12"
|
||||
tls_cipher_suites = "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384"
|
||||
}
|
||||
|
||||
# API address
|
||||
api_addr = "https://vault.example.com:8200"
|
||||
cluster_addr = "https://vault-1.example.com:8201"
|
||||
|
||||
# UI
|
||||
ui = true
|
||||
|
||||
# Telemetry
|
||||
telemetry {
|
||||
prometheus_retention_time = "30s"
|
||||
disable_hostname = true
|
||||
}
|
||||
|
||||
# Audit logging
|
||||
# Enable via API after init:
|
||||
# vault audit enable file file_path=/var/log/vault/audit.log
|
||||
|
||||
# Seal configuration (Auto-unseal with AWS KMS)
|
||||
# seal "awskms" {
|
||||
# region = "us-east-1"
|
||||
# kms_key_id = "alias/vault-unseal-key"
|
||||
# }
|
||||
|
||||
# Performance settings
|
||||
max_lease_ttl = "768h"
|
||||
default_lease_ttl = "768h"
|
||||
disable_mlock = false
|
||||
disable_cache = false
|
||||
|
||||
# Plugin directory
|
||||
plugin_directory = "/opt/vault/plugins"
|
||||
@@ -0,0 +1,136 @@
|
||||
# Vault Secrets Engines Guide
|
||||
|
||||
## KV Secrets Engine (v2)
|
||||
|
||||
### Enable and Configure
|
||||
```bash
|
||||
# Enable KV v2
|
||||
vault secrets enable -path=secret kv-v2
|
||||
|
||||
# Write secret
|
||||
vault kv put secret/myapp/config \
|
||||
db_host="postgres.example.com" \
|
||||
db_user="myapp" \
|
||||
db_password="secret123"
|
||||
|
||||
# Read secret
|
||||
vault kv get secret/myapp/config
|
||||
vault kv get -field=db_password secret/myapp/config
|
||||
|
||||
# List secrets
|
||||
vault kv list secret/myapp/
|
||||
|
||||
# Delete secret
|
||||
vault kv delete secret/myapp/config
|
||||
|
||||
# Versioning
|
||||
vault kv get -version=1 secret/myapp/config
|
||||
vault kv rollback -version=1 secret/myapp/config
|
||||
```
|
||||
|
||||
## Database Secrets Engine
|
||||
|
||||
### Setup Dynamic Credentials
|
||||
```bash
|
||||
# Enable database engine
|
||||
vault secrets enable database
|
||||
|
||||
# Configure PostgreSQL
|
||||
vault write database/config/myapp-db \
|
||||
plugin_name=postgresql-database-plugin \
|
||||
allowed_roles="myapp-role" \
|
||||
connection_url="postgresql://{{username}}:{{password}}@postgres:5432/myapp?sslmode=disable" \
|
||||
username="vault_admin" \
|
||||
password="admin_password"
|
||||
|
||||
# Create role
|
||||
vault write database/roles/myapp-role \
|
||||
db_name=myapp-db \
|
||||
creation_statements="CREATE ROLE \"{{name}}\" WITH LOGIN PASSWORD '{{password}}' VALID UNTIL '{{expiration}}'; \
|
||||
GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO \"{{name}}\";" \
|
||||
default_ttl="1h" \
|
||||
max_ttl="24h"
|
||||
|
||||
# Generate credentials
|
||||
vault read database/creds/myapp-role
|
||||
```
|
||||
|
||||
## AWS Secrets Engine
|
||||
|
||||
### Setup Dynamic AWS Credentials
|
||||
```bash
|
||||
# Enable AWS engine
|
||||
vault secrets enable aws
|
||||
|
||||
# Configure root credentials
|
||||
vault write aws/config/root \
|
||||
access_key=AKIAXXXXXXXX \
|
||||
secret_key=xxxxxxxx \
|
||||
region=us-east-1
|
||||
|
||||
# Create role
|
||||
vault write aws/roles/deploy-role \
|
||||
credential_type=iam_user \
|
||||
policy_document=-<<EOF
|
||||
{
|
||||
"Version": "2012-10-17",
|
||||
"Statement": [
|
||||
{
|
||||
"Effect": "Allow",
|
||||
"Action": ["s3:*"],
|
||||
"Resource": ["arn:aws:s3:::my-bucket/*"]
|
||||
}
|
||||
]
|
||||
}
|
||||
EOF
|
||||
|
||||
# Generate credentials
|
||||
vault read aws/creds/deploy-role
|
||||
```
|
||||
|
||||
## PKI Secrets Engine
|
||||
|
||||
### Certificate Authority
|
||||
```bash
|
||||
# Enable PKI
|
||||
vault secrets enable pki
|
||||
vault secrets tune -max-lease-ttl=87600h pki
|
||||
|
||||
# Generate root CA
|
||||
vault write pki/root/generate/internal \
|
||||
common_name="Example Root CA" \
|
||||
ttl=87600h
|
||||
|
||||
# Create role
|
||||
vault write pki/roles/server-cert \
|
||||
allowed_domains="example.com" \
|
||||
allow_subdomains=true \
|
||||
max_ttl="720h"
|
||||
|
||||
# Issue certificate
|
||||
vault write pki/issue/server-cert \
|
||||
common_name="api.example.com" \
|
||||
ttl="24h"
|
||||
```
|
||||
|
||||
## Transit Secrets Engine
|
||||
|
||||
### Encryption as a Service
|
||||
```bash
|
||||
# Enable transit
|
||||
vault secrets enable transit
|
||||
|
||||
# Create encryption key
|
||||
vault write -f transit/keys/myapp-key
|
||||
|
||||
# Encrypt data
|
||||
vault write transit/encrypt/myapp-key \
|
||||
plaintext=$(echo "secret data" | base64)
|
||||
|
||||
# Decrypt data
|
||||
vault write transit/decrypt/myapp-key \
|
||||
ciphertext="vault:v1:xxxxx"
|
||||
|
||||
# Rotate key
|
||||
vault write -f transit/keys/myapp-key/rotate
|
||||
```
|
||||
@@ -0,0 +1,130 @@
|
||||
# Vault Policy Guide
|
||||
|
||||
## Policy Syntax
|
||||
|
||||
```hcl
|
||||
# Basic policy structure
|
||||
path "secret/data/myapp/*" {
|
||||
capabilities = ["create", "read", "update", "delete", "list"]
|
||||
}
|
||||
```
|
||||
|
||||
## Capabilities
|
||||
|
||||
| Capability | HTTP Verb | Description |
|
||||
|------------|-----------|-------------|
|
||||
| `create` | POST/PUT | Create new data |
|
||||
| `read` | GET | Read data |
|
||||
| `update` | POST/PUT | Update existing data |
|
||||
| `delete` | DELETE | Delete data |
|
||||
| `list` | LIST | List keys |
|
||||
| `sudo` | - | Root-protected paths |
|
||||
| `deny` | - | Explicitly deny access |
|
||||
|
||||
## Common Policies
|
||||
|
||||
### Application Read-Only
|
||||
```hcl
|
||||
# app-readonly.hcl
|
||||
path "secret/data/myapp/*" {
|
||||
capabilities = ["read", "list"]
|
||||
}
|
||||
|
||||
path "secret/metadata/myapp/*" {
|
||||
capabilities = ["read", "list"]
|
||||
}
|
||||
```
|
||||
|
||||
### Developer Policy
|
||||
```hcl
|
||||
# developer.hcl
|
||||
path "secret/data/dev/*" {
|
||||
capabilities = ["create", "read", "update", "delete", "list"]
|
||||
}
|
||||
|
||||
path "secret/data/staging/*" {
|
||||
capabilities = ["read", "list"]
|
||||
}
|
||||
|
||||
# Deny production access
|
||||
path "secret/data/prod/*" {
|
||||
capabilities = ["deny"]
|
||||
}
|
||||
```
|
||||
|
||||
### CI/CD Pipeline
|
||||
```hcl
|
||||
# cicd.hcl
|
||||
# Read deployment secrets
|
||||
path "secret/data/deploy/*" {
|
||||
capabilities = ["read"]
|
||||
}
|
||||
|
||||
# Generate dynamic database credentials
|
||||
path "database/creds/myapp-role" {
|
||||
capabilities = ["read"]
|
||||
}
|
||||
|
||||
# Sign SSH keys
|
||||
path "ssh-client-signer/sign/deploy-role" {
|
||||
capabilities = ["create", "update"]
|
||||
}
|
||||
```
|
||||
|
||||
### Admin Policy
|
||||
```hcl
|
||||
# admin.hcl
|
||||
# Manage secrets engines
|
||||
path "sys/mounts/*" {
|
||||
capabilities = ["create", "read", "update", "delete", "list"]
|
||||
}
|
||||
|
||||
# Manage policies
|
||||
path "sys/policies/acl/*" {
|
||||
capabilities = ["create", "read", "update", "delete", "list"]
|
||||
}
|
||||
|
||||
# Manage auth methods
|
||||
path "sys/auth/*" {
|
||||
capabilities = ["create", "read", "update", "delete", "list", "sudo"]
|
||||
}
|
||||
|
||||
# View audit logs
|
||||
path "sys/audit" {
|
||||
capabilities = ["read", "list"]
|
||||
}
|
||||
```
|
||||
|
||||
## Policy Templates
|
||||
|
||||
### Using Templating
|
||||
```hcl
|
||||
# Per-user secrets path
|
||||
path "secret/data/users/{{identity.entity.name}}/*" {
|
||||
capabilities = ["create", "read", "update", "delete", "list"]
|
||||
}
|
||||
|
||||
# Team-based access
|
||||
path "secret/data/teams/{{identity.groups.names}}/*" {
|
||||
capabilities = ["read", "list"]
|
||||
}
|
||||
```
|
||||
|
||||
## Policy Management
|
||||
|
||||
```bash
|
||||
# Write policy
|
||||
vault policy write myapp-policy myapp-policy.hcl
|
||||
|
||||
# List policies
|
||||
vault policy list
|
||||
|
||||
# Read policy
|
||||
vault policy read myapp-policy
|
||||
|
||||
# Delete policy
|
||||
vault policy delete myapp-policy
|
||||
|
||||
# Test policy (requires root)
|
||||
vault token create -policy=myapp-policy
|
||||
```
|
||||
@@ -0,0 +1,55 @@
|
||||
#!/bin/bash
|
||||
# Vault Backup Script (Raft Storage)
|
||||
# Usage: ./vault-backup.sh [output-dir]
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
OUTPUT_DIR="${1:-./vault-backups}"
|
||||
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
|
||||
BACKUP_FILE="$OUTPUT_DIR/vault-snapshot-$TIMESTAMP.snap"
|
||||
|
||||
mkdir -p "$OUTPUT_DIR"
|
||||
|
||||
echo "========================================="
|
||||
echo "Vault Raft Snapshot Backup"
|
||||
echo "Output: $BACKUP_FILE"
|
||||
echo "========================================="
|
||||
echo ""
|
||||
|
||||
# Check Vault status
|
||||
if ! vault status &>/dev/null; then
|
||||
echo "Error: Cannot connect to Vault or Vault is sealed"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Take snapshot
|
||||
echo "Creating snapshot..."
|
||||
vault operator raft snapshot save "$BACKUP_FILE"
|
||||
|
||||
if [ -f "$BACKUP_FILE" ]; then
|
||||
SIZE=$(du -h "$BACKUP_FILE" | cut -f1)
|
||||
echo "Snapshot created successfully!"
|
||||
echo "File: $BACKUP_FILE"
|
||||
echo "Size: $SIZE"
|
||||
else
|
||||
echo "Error: Snapshot creation failed"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Verify snapshot
|
||||
echo ""
|
||||
echo "Verifying snapshot..."
|
||||
vault operator raft snapshot inspect "$BACKUP_FILE" | head -20
|
||||
|
||||
# Cleanup old backups (keep last 7)
|
||||
echo ""
|
||||
echo "Cleaning up old backups (keeping last 7)..."
|
||||
ls -t "$OUTPUT_DIR"/vault-snapshot-*.snap 2>/dev/null | tail -n +8 | xargs -r rm -v
|
||||
|
||||
echo ""
|
||||
echo "========================================="
|
||||
echo "Backup complete"
|
||||
echo ""
|
||||
echo "To restore:"
|
||||
echo " vault operator raft snapshot restore $BACKUP_FILE"
|
||||
echo "========================================="
|
||||
@@ -0,0 +1,78 @@
|
||||
#!/bin/bash
|
||||
# Vault Initialization and Unseal Script
|
||||
# Usage: ./vault-init.sh [vault-addr]
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
export VAULT_ADDR="${1:-http://127.0.0.1:8200}"
|
||||
|
||||
echo "========================================="
|
||||
echo "Vault Initialization"
|
||||
echo "Address: $VAULT_ADDR"
|
||||
echo "========================================="
|
||||
echo ""
|
||||
|
||||
# Check if Vault is already initialized
|
||||
INIT_STATUS=$(vault status -format=json 2>/dev/null | jq -r '.initialized' || echo "error")
|
||||
|
||||
if [ "$INIT_STATUS" == "true" ]; then
|
||||
echo "Vault is already initialized"
|
||||
SEALED=$(vault status -format=json | jq -r '.sealed')
|
||||
if [ "$SEALED" == "true" ]; then
|
||||
echo "Vault is sealed. Use unseal keys to unseal."
|
||||
else
|
||||
echo "Vault is unsealed and ready."
|
||||
fi
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [ "$INIT_STATUS" == "error" ]; then
|
||||
echo "Error: Cannot connect to Vault at $VAULT_ADDR"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Initialize Vault
|
||||
echo "Initializing Vault..."
|
||||
echo ""
|
||||
|
||||
# Initialize with 5 key shares, 3 required to unseal
|
||||
INIT_OUTPUT=$(vault operator init \
|
||||
-key-shares=5 \
|
||||
-key-threshold=3 \
|
||||
-format=json)
|
||||
|
||||
# Save keys securely
|
||||
echo "$INIT_OUTPUT" > vault-init-keys.json
|
||||
chmod 600 vault-init-keys.json
|
||||
|
||||
echo "Vault initialized successfully!"
|
||||
echo ""
|
||||
echo "IMPORTANT: vault-init-keys.json contains your unseal keys and root token"
|
||||
echo "Store these securely and distribute unseal keys to different people"
|
||||
echo ""
|
||||
|
||||
# Extract keys
|
||||
UNSEAL_KEY_1=$(echo "$INIT_OUTPUT" | jq -r '.unseal_keys_b64[0]')
|
||||
UNSEAL_KEY_2=$(echo "$INIT_OUTPUT" | jq -r '.unseal_keys_b64[1]')
|
||||
UNSEAL_KEY_3=$(echo "$INIT_OUTPUT" | jq -r '.unseal_keys_b64[2]')
|
||||
ROOT_TOKEN=$(echo "$INIT_OUTPUT" | jq -r '.root_token')
|
||||
|
||||
# Unseal Vault
|
||||
echo "Unsealing Vault..."
|
||||
vault operator unseal "$UNSEAL_KEY_1" >/dev/null
|
||||
vault operator unseal "$UNSEAL_KEY_2" >/dev/null
|
||||
vault operator unseal "$UNSEAL_KEY_3" >/dev/null
|
||||
|
||||
echo "Vault unsealed successfully!"
|
||||
echo ""
|
||||
echo "Root Token: $ROOT_TOKEN"
|
||||
echo ""
|
||||
echo "Login with: vault login $ROOT_TOKEN"
|
||||
echo ""
|
||||
echo "========================================="
|
||||
echo "Next steps:"
|
||||
echo "1. Store unseal keys securely (different locations)"
|
||||
echo "2. Create AppRole or other auth methods"
|
||||
echo "3. Enable audit logging"
|
||||
echo "4. Configure secrets engines"
|
||||
echo "========================================="
|
||||
@@ -0,0 +1,100 @@
|
||||
---
|
||||
name: sops-encryption
|
||||
description: Encrypt files and configs with Mozilla SOPS. Integrate with AWS KMS, GCP KMS, or PGP for key management. Use when encrypting configuration files, Kubernetes secrets, or implementing GitOps with encrypted secrets.
|
||||
license: MIT
|
||||
metadata:
|
||||
author: devops-skills
|
||||
version: "1.0"
|
||||
---
|
||||
|
||||
# SOPS Encryption
|
||||
|
||||
Encrypt secrets in configuration files while keeping structure visible.
|
||||
|
||||
## When to Use This Skill
|
||||
|
||||
Use this skill when:
|
||||
- Encrypting secrets in Git
|
||||
- Implementing GitOps with secrets
|
||||
- Managing Kubernetes secrets as code
|
||||
- Encrypting configuration files
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- SOPS installed
|
||||
- KMS access (AWS, GCP, Azure) or PGP key
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
# macOS
|
||||
brew install sops
|
||||
|
||||
# Linux
|
||||
wget https://github.com/getsops/sops/releases/download/v3.8.0/sops-v3.8.0.linux.amd64
|
||||
chmod +x sops-v3.8.0.linux.amd64
|
||||
mv sops-v3.8.0.linux.amd64 /usr/local/bin/sops
|
||||
```
|
||||
|
||||
## Basic Usage
|
||||
|
||||
```bash
|
||||
# Encrypt with AWS KMS
|
||||
sops --encrypt --kms arn:aws:kms:region:account:key/key-id secrets.yaml > secrets.enc.yaml
|
||||
|
||||
# Decrypt
|
||||
sops --decrypt secrets.enc.yaml
|
||||
|
||||
# Edit encrypted file
|
||||
sops secrets.enc.yaml
|
||||
|
||||
# Encrypt in place
|
||||
sops --encrypt --in-place secrets.yaml
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
```yaml
|
||||
# .sops.yaml
|
||||
creation_rules:
|
||||
- path_regex: .*\.prod\.yaml$
|
||||
kms: arn:aws:kms:us-east-1:account:key/prod-key
|
||||
- path_regex: .*\.dev\.yaml$
|
||||
kms: arn:aws:kms:us-east-1:account:key/dev-key
|
||||
- path_regex: .*
|
||||
pgp: fingerprint
|
||||
```
|
||||
|
||||
## Kubernetes Integration
|
||||
|
||||
```yaml
|
||||
# encrypted secret
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: myapp-secrets
|
||||
type: Opaque
|
||||
stringData:
|
||||
password: ENC[AES256_GCM,data:encrypted...]
|
||||
sops:
|
||||
kms:
|
||||
- arn: arn:aws:kms:region:account:key/key-id
|
||||
```
|
||||
|
||||
```bash
|
||||
# With ArgoCD
|
||||
# Install ksops plugin for ArgoCD to decrypt secrets
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
- Store .sops.yaml in repository
|
||||
- Use different keys per environment
|
||||
- Rotate encryption keys regularly
|
||||
- Never commit unencrypted secrets
|
||||
- Use key aliases for readability
|
||||
|
||||
## Related Skills
|
||||
|
||||
- [hashicorp-vault](../hashicorp-vault/) - Centralized secrets
|
||||
- [argocd-gitops](../../../devops/orchestration/argocd-gitops/) - GitOps integration
|
||||
Reference in New Issue
Block a user