docs: implement Phase 3 - user tutorials and guides
Create comprehensive tutorials and user guides for common workflows and core concepts. New tutorials: - tutorials/ci-cd-integration.md (8KB) - Complete CI/CD guide - GitHub Actions, GitLab CI, and Jenkins examples - Certificate management and kubeseal CLI usage - Bulk secret creation and environment-specific patterns - Troubleshooting and best practices New user guides: - user-guide/scopes-explained.md (12KB) - Deep dive into scopes - Detailed explanation of strict/namespace-wide/cluster-wide - Security implications and use cases - Decision tree for scope selection - Common mistakes and how to avoid them - Scope comparison table - user-guide/rbac-permissions.md (10KB) - RBAC configuration - Required permissions for different access levels - Example RBAC configurations (viewer, creator, admin) - Service account setup for CI/CD - Plugin UI behavior based on permissions - Troubleshooting permission issues - Security best practices Benefits: - Real-world examples for GitHub Actions, GitLab CI, Jenkins - Clear security guidance with decision trees - Copy-paste RBAC manifests for common scenarios - Troubleshooting sections for each guide - Cross-referenced with other documentation Phase 3 deliverables (3-4 days estimated, completed in 1 session): ✅ CI/CD integration tutorial with 3 platform examples ✅ Scopes explained with security best practices ✅ RBAC permissions guide with example manifests ✅ Decision trees and comparison tables ✅ Troubleshooting sections for each guide Total documentation: - 30KB of new tutorial/guide content - 3 comprehensive guides - 20+ code examples - Cross-referenced with API docs and other guides Next: Phase 4 - Troubleshooting guides and ADRs Generated with [Claude Code](https://claude.ai/code) via [Happy](https://happy.engineering) Co-Authored-By: Claude <noreply@anthropic.com> Co-Authored-By: Happy <yesreply@happy.engineering>
This commit is contained in:
@@ -0,0 +1,466 @@
|
|||||||
|
# CI/CD Integration Tutorial
|
||||||
|
|
||||||
|
Learn how to automate sealed secret creation in your CI/CD pipelines.
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
This tutorial shows you how to:
|
||||||
|
- Create sealed secrets in CI/CD pipelines
|
||||||
|
- Download sealing certificates for offline encryption
|
||||||
|
- Use `kubeseal` CLI with plugin-exported certificates
|
||||||
|
- Integrate with GitHub Actions, GitLab CI, and Jenkins
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
- Headlamp Sealed Secrets plugin installed
|
||||||
|
- Sealed Secrets controller running in your cluster
|
||||||
|
- Access to download sealing certificates
|
||||||
|
- CI/CD system (GitHub Actions, GitLab CI, or Jenkins)
|
||||||
|
|
||||||
|
## Step 1: Download the Sealing Certificate
|
||||||
|
|
||||||
|
The sealing certificate is the public key used to encrypt secrets. You can download it from Headlamp:
|
||||||
|
|
||||||
|
### Using Headlamp UI
|
||||||
|
|
||||||
|
1. Navigate to **Sealed Secrets → Sealing Keys**
|
||||||
|
2. Find the active certificate (no expiry warning)
|
||||||
|
3. Click **Download**
|
||||||
|
4. Save as `sealed-secrets-cert.pem`
|
||||||
|
|
||||||
|
### Using kubectl
|
||||||
|
|
||||||
|
Alternatively, fetch it directly from the controller:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
kubectl get secret -n kube-system \
|
||||||
|
-l sealedsecrets.bitnami.com/sealed-secrets-key=active \
|
||||||
|
-o jsonpath='{.items[0].data.tls\.crt}' | base64 -d > sealed-secrets-cert.pem
|
||||||
|
```
|
||||||
|
|
||||||
|
Or use the controller's certificate endpoint:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl http://sealed-secrets-controller.kube-system:8080/v1/cert.pem > sealed-secrets-cert.pem
|
||||||
|
```
|
||||||
|
|
||||||
|
## Step 2: Install kubeseal CLI
|
||||||
|
|
||||||
|
Install the `kubeseal` command-line tool:
|
||||||
|
|
||||||
|
**macOS (Homebrew):**
|
||||||
|
```bash
|
||||||
|
brew install kubeseal
|
||||||
|
```
|
||||||
|
|
||||||
|
**Linux:**
|
||||||
|
```bash
|
||||||
|
KUBESEAL_VERSION='0.24.0'
|
||||||
|
wget "https://github.com/bitnami-labs/sealed-secrets/releases/download/v${KUBESEAL_VERSION}/kubeseal-${KUBESEAL_VERSION}-linux-amd64.tar.gz"
|
||||||
|
tar -xvzf kubeseal-${KUBESEAL_VERSION}-linux-amd64.tar.gz kubeseal
|
||||||
|
sudo install -m 755 kubeseal /usr/local/bin/kubeseal
|
||||||
|
```
|
||||||
|
|
||||||
|
**Windows (Chocolatey):**
|
||||||
|
```powershell
|
||||||
|
choco install kubeseal
|
||||||
|
```
|
||||||
|
|
||||||
|
**Verify installation:**
|
||||||
|
```bash
|
||||||
|
kubeseal --version
|
||||||
|
# Output: kubeseal version: v0.24.0
|
||||||
|
```
|
||||||
|
|
||||||
|
## Step 3: Create Sealed Secrets in CI/CD
|
||||||
|
|
||||||
|
### GitHub Actions Example
|
||||||
|
|
||||||
|
Create `.github/workflows/sealed-secrets.yml`:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
name: Create Sealed Secrets
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
paths:
|
||||||
|
- 'secrets/**'
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
seal-secrets:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Install kubeseal
|
||||||
|
run: |
|
||||||
|
KUBESEAL_VERSION='0.24.0'
|
||||||
|
wget "https://github.com/bitnami-labs/sealed-secrets/releases/download/v${KUBESEAL_VERSION}/kubeseal-${KUBESEAL_VERSION}-linux-amd64.tar.gz"
|
||||||
|
tar -xvzf kubeseal-${KUBESEAL_VERSION}-linux-amd64.tar.gz kubeseal
|
||||||
|
sudo install -m 755 kubeseal /usr/local/bin/kubeseal
|
||||||
|
|
||||||
|
- name: Download sealing certificate
|
||||||
|
run: |
|
||||||
|
# Option 1: From repository secret
|
||||||
|
echo "${{ secrets.SEALED_SECRETS_CERT }}" > sealed-secrets-cert.pem
|
||||||
|
|
||||||
|
# Option 2: From cluster (requires kubectl access)
|
||||||
|
# kubectl get secret -n kube-system \
|
||||||
|
# -l sealedsecrets.bitnami.com/sealed-secrets-key=active \
|
||||||
|
# -o jsonpath='{.items[0].data.tls\.crt}' | base64 -d > sealed-secrets-cert.pem
|
||||||
|
|
||||||
|
- name: Create sealed secret
|
||||||
|
run: |
|
||||||
|
# Create a plain Kubernetes secret
|
||||||
|
kubectl create secret generic my-app-secret \
|
||||||
|
--from-literal=database-password=${{ secrets.DB_PASSWORD }} \
|
||||||
|
--from-literal=api-key=${{ secrets.API_KEY }} \
|
||||||
|
--dry-run=client \
|
||||||
|
-o yaml > secret.yaml
|
||||||
|
|
||||||
|
# Seal the secret
|
||||||
|
kubeseal --cert sealed-secrets-cert.pem \
|
||||||
|
--format=yaml < secret.yaml > sealed-secret.yaml
|
||||||
|
|
||||||
|
# Commit and push (optional)
|
||||||
|
git add sealed-secret.yaml
|
||||||
|
git commit -m "chore: update sealed secret"
|
||||||
|
git push
|
||||||
|
|
||||||
|
- name: Apply to cluster
|
||||||
|
run: |
|
||||||
|
kubectl apply -f sealed-secret.yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
**Store the certificate as a GitHub Secret:**
|
||||||
|
1. Go to repository Settings → Secrets and variables → Actions
|
||||||
|
2. Click "New repository secret"
|
||||||
|
3. Name: `SEALED_SECRETS_CERT`
|
||||||
|
4. Value: Paste contents of `sealed-secrets-cert.pem`
|
||||||
|
|
||||||
|
### GitLab CI Example
|
||||||
|
|
||||||
|
Create `.gitlab-ci.yml`:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
stages:
|
||||||
|
- seal
|
||||||
|
- deploy
|
||||||
|
|
||||||
|
variables:
|
||||||
|
KUBESEAL_VERSION: "0.24.0"
|
||||||
|
|
||||||
|
seal-secrets:
|
||||||
|
stage: seal
|
||||||
|
image: alpine:latest
|
||||||
|
before_script:
|
||||||
|
- apk add --no-cache curl tar
|
||||||
|
- curl -LO "https://github.com/bitnami-labs/sealed-secrets/releases/download/v${KUBESEAL_VERSION}/kubeseal-${KUBESEAL_VERSION}-linux-amd64.tar.gz"
|
||||||
|
- tar -xvzf kubeseal-${KUBESEAL_VERSION}-linux-amd64.tar.gz kubeseal
|
||||||
|
- mv kubeseal /usr/local/bin/
|
||||||
|
- chmod +x /usr/local/bin/kubeseal
|
||||||
|
script:
|
||||||
|
# Get certificate from GitLab CI variable
|
||||||
|
- echo "$SEALED_SECRETS_CERT" > sealed-secrets-cert.pem
|
||||||
|
|
||||||
|
# Create and seal secret
|
||||||
|
- |
|
||||||
|
cat <<EOF > secret.yaml
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Secret
|
||||||
|
metadata:
|
||||||
|
name: my-app-secret
|
||||||
|
namespace: production
|
||||||
|
stringData:
|
||||||
|
database-password: "${DB_PASSWORD}"
|
||||||
|
api-key: "${API_KEY}"
|
||||||
|
EOF
|
||||||
|
|
||||||
|
- kubeseal --cert sealed-secrets-cert.pem --format=yaml < secret.yaml > sealed-secret.yaml
|
||||||
|
artifacts:
|
||||||
|
paths:
|
||||||
|
- sealed-secret.yaml
|
||||||
|
only:
|
||||||
|
- main
|
||||||
|
|
||||||
|
deploy-sealed-secret:
|
||||||
|
stage: deploy
|
||||||
|
image: bitnami/kubectl:latest
|
||||||
|
script:
|
||||||
|
- kubectl apply -f sealed-secret.yaml
|
||||||
|
dependencies:
|
||||||
|
- seal-secrets
|
||||||
|
only:
|
||||||
|
- main
|
||||||
|
```
|
||||||
|
|
||||||
|
**Set GitLab CI Variables:**
|
||||||
|
1. Go to Settings → CI/CD → Variables
|
||||||
|
2. Add `SEALED_SECRETS_CERT` (type: File)
|
||||||
|
3. Add `DB_PASSWORD` and `API_KEY` (type: Masked)
|
||||||
|
|
||||||
|
### Jenkins Pipeline Example
|
||||||
|
|
||||||
|
Create `Jenkinsfile`:
|
||||||
|
|
||||||
|
```groovy
|
||||||
|
pipeline {
|
||||||
|
agent any
|
||||||
|
|
||||||
|
environment {
|
||||||
|
KUBESEAL_VERSION = '0.24.0'
|
||||||
|
NAMESPACE = 'production'
|
||||||
|
}
|
||||||
|
|
||||||
|
stages {
|
||||||
|
stage('Install kubeseal') {
|
||||||
|
steps {
|
||||||
|
sh '''
|
||||||
|
if ! command -v kubeseal &> /dev/null; then
|
||||||
|
wget "https://github.com/bitnami-labs/sealed-secrets/releases/download/v${KUBESEAL_VERSION}/kubeseal-${KUBESEAL_VERSION}-linux-amd64.tar.gz"
|
||||||
|
tar -xvzf kubeseal-${KUBESEAL_VERSION}-linux-amd64.tar.gz kubeseal
|
||||||
|
sudo install -m 755 kubeseal /usr/local/bin/kubeseal
|
||||||
|
fi
|
||||||
|
'''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
stage('Download Certificate') {
|
||||||
|
steps {
|
||||||
|
withCredentials([file(credentialsId: 'sealed-secrets-cert', variable: 'CERT_FILE')]) {
|
||||||
|
sh 'cp $CERT_FILE sealed-secrets-cert.pem'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
stage('Create Sealed Secret') {
|
||||||
|
steps {
|
||||||
|
withCredentials([
|
||||||
|
string(credentialsId: 'db-password', variable: 'DB_PASSWORD'),
|
||||||
|
string(credentialsId: 'api-key', variable: 'API_KEY')
|
||||||
|
]) {
|
||||||
|
sh '''
|
||||||
|
# Create secret manifest
|
||||||
|
kubectl create secret generic my-app-secret \
|
||||||
|
--namespace=${NAMESPACE} \
|
||||||
|
--from-literal=database-password=${DB_PASSWORD} \
|
||||||
|
--from-literal=api-key=${API_KEY} \
|
||||||
|
--dry-run=client \
|
||||||
|
-o yaml > secret.yaml
|
||||||
|
|
||||||
|
# Seal it
|
||||||
|
kubeseal --cert sealed-secrets-cert.pem \
|
||||||
|
--format=yaml < secret.yaml > sealed-secret.yaml
|
||||||
|
|
||||||
|
# Show sealed secret (safe to log)
|
||||||
|
cat sealed-secret.yaml
|
||||||
|
'''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
stage('Deploy') {
|
||||||
|
steps {
|
||||||
|
sh 'kubectl apply -f sealed-secret.yaml'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
post {
|
||||||
|
always {
|
||||||
|
cleanWs()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Step 4: Verify Sealed Secret
|
||||||
|
|
||||||
|
After creating the sealed secret, verify it was created and unsealed:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Check sealed secret exists
|
||||||
|
kubectl get sealedsecret my-app-secret -n production
|
||||||
|
|
||||||
|
# Check the unsealed secret was created
|
||||||
|
kubectl get secret my-app-secret -n production
|
||||||
|
|
||||||
|
# Verify secret contains correct keys
|
||||||
|
kubectl get secret my-app-secret -n production -o jsonpath='{.data}' | jq 'keys'
|
||||||
|
# Output: ["api-key", "database-password"]
|
||||||
|
```
|
||||||
|
|
||||||
|
## Advanced Patterns
|
||||||
|
|
||||||
|
### Pattern 1: Different Secrets per Environment
|
||||||
|
|
||||||
|
Create environment-specific sealed secrets:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Development
|
||||||
|
kubectl create secret generic my-app-secret \
|
||||||
|
--namespace=dev \
|
||||||
|
--from-literal=api-url=https://dev-api.example.com \
|
||||||
|
--dry-run=client -o yaml | \
|
||||||
|
kubeseal --cert sealed-secrets-cert.pem --format=yaml > dev-sealed-secret.yaml
|
||||||
|
|
||||||
|
# Production
|
||||||
|
kubectl create secret generic my-app-secret \
|
||||||
|
--namespace=production \
|
||||||
|
--from-literal=api-url=https://api.example.com \
|
||||||
|
--dry-run=client -o yaml | \
|
||||||
|
kubeseal --cert sealed-secrets-cert.pem --format=yaml > prod-sealed-secret.yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
### Pattern 2: Bulk Secret Creation
|
||||||
|
|
||||||
|
Create multiple secrets from a directory:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
#!/bin/bash
|
||||||
|
# seal-all-secrets.sh
|
||||||
|
|
||||||
|
CERT="sealed-secrets-cert.pem"
|
||||||
|
SECRETS_DIR="plain-secrets"
|
||||||
|
OUTPUT_DIR="sealed-secrets"
|
||||||
|
|
||||||
|
mkdir -p "$OUTPUT_DIR"
|
||||||
|
|
||||||
|
for secret_file in "$SECRETS_DIR"/*.yaml; do
|
||||||
|
filename=$(basename "$secret_file")
|
||||||
|
kubeseal --cert "$CERT" --format=yaml < "$secret_file" > "$OUTPUT_DIR/$filename"
|
||||||
|
echo "Sealed: $filename"
|
||||||
|
done
|
||||||
|
```
|
||||||
|
|
||||||
|
### Pattern 3: Update Existing Sealed Secret
|
||||||
|
|
||||||
|
To update a sealed secret, re-seal it completely:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Get current secret
|
||||||
|
kubectl get sealedsecret my-app-secret -o yaml > current-sealed.yaml
|
||||||
|
|
||||||
|
# Extract metadata
|
||||||
|
# ... modify as needed ...
|
||||||
|
|
||||||
|
# Create new version
|
||||||
|
kubectl create secret generic my-app-secret \
|
||||||
|
--from-literal=database-password=NEW_PASSWORD \
|
||||||
|
--from-literal=api-key=NEW_KEY \
|
||||||
|
--dry-run=client -o yaml | \
|
||||||
|
kubeseal --cert sealed-secrets-cert.pem --format=yaml > updated-sealed-secret.yaml
|
||||||
|
|
||||||
|
# Apply
|
||||||
|
kubectl apply -f updated-sealed-secret.yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
## Best Practices
|
||||||
|
|
||||||
|
### 1. Certificate Management
|
||||||
|
|
||||||
|
✅ **DO:**
|
||||||
|
- Store certificate in CI/CD secrets (encrypted at rest)
|
||||||
|
- Download fresh certificate periodically (before expiry)
|
||||||
|
- Use certificate from same cluster where secrets will be deployed
|
||||||
|
|
||||||
|
❌ **DON'T:**
|
||||||
|
- Commit certificate to Git (it's public, but still clutters repo)
|
||||||
|
- Use expired certificates
|
||||||
|
- Mix certificates from different clusters
|
||||||
|
|
||||||
|
### 2. Secret Rotation
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Check certificate expiry in Headlamp
|
||||||
|
# Sealed Secrets → Sealing Keys → Check "Valid Until"
|
||||||
|
|
||||||
|
# Download new certificate before expiry
|
||||||
|
# Re-seal all secrets with new certificate
|
||||||
|
# Deploy new sealed secrets
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Scope Selection
|
||||||
|
|
||||||
|
- **Use strict scope** for production secrets:
|
||||||
|
```bash
|
||||||
|
kubeseal --cert cert.pem --scope strict < secret.yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
- **Use namespace-wide** for shared secrets:
|
||||||
|
```bash
|
||||||
|
kubeseal --cert cert.pem --scope namespace-wide < secret.yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
- **Use cluster-wide** only for truly global secrets:
|
||||||
|
```bash
|
||||||
|
kubeseal --cert cert.pem --scope cluster-wide < secret.yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. GitOps Integration
|
||||||
|
|
||||||
|
Store sealed secrets in Git alongside manifests:
|
||||||
|
|
||||||
|
```
|
||||||
|
my-app/
|
||||||
|
├── deployment.yaml
|
||||||
|
├── service.yaml
|
||||||
|
└── sealed-secret.yaml # Safe to commit!
|
||||||
|
```
|
||||||
|
|
||||||
|
Apply with:
|
||||||
|
```bash
|
||||||
|
kubectl apply -f my-app/
|
||||||
|
```
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### "no key could decrypt secret"
|
||||||
|
|
||||||
|
**Cause**: Certificate mismatch or secret was sealed for different namespace/name.
|
||||||
|
|
||||||
|
**Solution**:
|
||||||
|
```bash
|
||||||
|
# Verify you're using the correct certificate
|
||||||
|
kubeseal --cert sealed-secrets-cert.pem --validate < sealed-secret.yaml
|
||||||
|
|
||||||
|
# Re-seal with correct scope and metadata
|
||||||
|
kubectl create secret generic EXACT_NAME \
|
||||||
|
--namespace EXACT_NAMESPACE \
|
||||||
|
--from-literal=key=value \
|
||||||
|
--dry-run=client -o yaml | \
|
||||||
|
kubeseal --cert sealed-secrets-cert.pem --scope strict --format=yaml > sealed-secret.yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
### "certificate has expired"
|
||||||
|
|
||||||
|
**Solution**: Download fresh certificate from Headlamp:
|
||||||
|
```bash
|
||||||
|
# Check expiry
|
||||||
|
openssl x509 -in sealed-secrets-cert.pem -noout -enddate
|
||||||
|
|
||||||
|
# Download new one from Headlamp UI or kubectl
|
||||||
|
```
|
||||||
|
|
||||||
|
### CI/CD pipeline fails to seal
|
||||||
|
|
||||||
|
**Check:**
|
||||||
|
1. `kubeseal` is installed: `kubeseal --version`
|
||||||
|
2. Certificate file exists and is valid
|
||||||
|
3. Input secret YAML is well-formed
|
||||||
|
4. Namespace exists in cluster
|
||||||
|
|
||||||
|
## Next Steps
|
||||||
|
|
||||||
|
- **[Multi-Cluster Setup](multi-cluster-setup.md)** - Manage secrets across multiple clusters
|
||||||
|
- **[Secret Rotation](secret-rotation.md)** - Rotate secrets and certificates
|
||||||
|
- **[RBAC Permissions](../user-guide/rbac-permissions.md)** - Configure access control
|
||||||
|
|
||||||
|
## Resources
|
||||||
|
|
||||||
|
- [kubeseal CLI Documentation](https://github.com/bitnami-labs/sealed-secrets#usage)
|
||||||
|
- [GitHub Actions Secrets](https://docs.github.com/en/actions/security-guides/encrypted-secrets)
|
||||||
|
- [GitLab CI Variables](https://docs.gitlab.com/ee/ci/variables/)
|
||||||
|
- [Jenkins Credentials](https://www.jenkins.io/doc/book/using/using-credentials/)
|
||||||
@@ -0,0 +1,475 @@
|
|||||||
|
# RBAC Permissions Guide
|
||||||
|
|
||||||
|
Configure Role-Based Access Control for Sealed Secrets operations.
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
The Headlamp Sealed Secrets plugin integrates with Kubernetes RBAC to control:
|
||||||
|
- Who can **view** SealedSecrets
|
||||||
|
- Who can **create** SealedSecrets
|
||||||
|
- Who can **delete** SealedSecrets
|
||||||
|
- Who can **view unsealed Secrets**
|
||||||
|
- Who can **download sealing certificates**
|
||||||
|
|
||||||
|
The plugin automatically checks permissions and hides/disables UI elements based on your RBAC roles.
|
||||||
|
|
||||||
|
## Required Permissions
|
||||||
|
|
||||||
|
### Minimum Read-Only Access
|
||||||
|
|
||||||
|
To **view** sealed secrets in Headlamp:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
kind: ClusterRole
|
||||||
|
metadata:
|
||||||
|
name: sealed-secrets-viewer
|
||||||
|
rules:
|
||||||
|
# View SealedSecrets
|
||||||
|
- apiGroups: ["bitnami.com"]
|
||||||
|
resources: ["sealedsecrets"]
|
||||||
|
verbs: ["get", "list"]
|
||||||
|
|
||||||
|
# View namespaces (for filtering)
|
||||||
|
- apiGroups: [""]
|
||||||
|
resources: ["namespaces"]
|
||||||
|
verbs: ["list"]
|
||||||
|
```
|
||||||
|
|
||||||
|
**What you can do:**
|
||||||
|
- ✅ List all sealed secrets
|
||||||
|
- ✅ View sealed secret details
|
||||||
|
- ✅ See encrypted data (safe to view)
|
||||||
|
- ❌ Create new sealed secrets
|
||||||
|
- ❌ Delete sealed secrets
|
||||||
|
- ❌ View unsealed secret values
|
||||||
|
|
||||||
|
### Creator Access
|
||||||
|
|
||||||
|
To **create** sealed secrets:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
kind: ClusterRole
|
||||||
|
metadata:
|
||||||
|
name: sealed-secrets-creator
|
||||||
|
rules:
|
||||||
|
# Create and view SealedSecrets
|
||||||
|
- apiGroups: ["bitnami.com"]
|
||||||
|
resources: ["sealedsecrets"]
|
||||||
|
verbs: ["get", "list", "create"]
|
||||||
|
|
||||||
|
# Download sealing certificates
|
||||||
|
- apiGroups: [""]
|
||||||
|
resources: ["services/proxy"]
|
||||||
|
verbs: ["get"]
|
||||||
|
resourceNames: ["sealed-secrets-controller"]
|
||||||
|
|
||||||
|
# Or direct service access
|
||||||
|
- apiGroups: [""]
|
||||||
|
resources: ["services"]
|
||||||
|
verbs: ["get"]
|
||||||
|
resourceNames: ["sealed-secrets-controller"]
|
||||||
|
|
||||||
|
# View namespaces
|
||||||
|
- apiGroups: [""]
|
||||||
|
resources: ["namespaces"]
|
||||||
|
verbs: ["list"]
|
||||||
|
```
|
||||||
|
|
||||||
|
**What you can do:**
|
||||||
|
- ✅ Everything from viewer role
|
||||||
|
- ✅ Create new sealed secrets
|
||||||
|
- ✅ Download sealing certificates
|
||||||
|
- ❌ Delete sealed secrets
|
||||||
|
- ❌ View unsealed secret values
|
||||||
|
|
||||||
|
### Full Access
|
||||||
|
|
||||||
|
To have **complete control** over sealed secrets:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
kind: ClusterRole
|
||||||
|
metadata:
|
||||||
|
name: sealed-secrets-admin
|
||||||
|
rules:
|
||||||
|
# Full SealedSecret access
|
||||||
|
- apiGroups: ["bitnami.com"]
|
||||||
|
resources: ["sealedsecrets"]
|
||||||
|
verbs: ["get", "list", "create", "update", "patch", "delete"]
|
||||||
|
|
||||||
|
# View unsealed Secrets
|
||||||
|
- apiGroups: [""]
|
||||||
|
resources: ["secrets"]
|
||||||
|
verbs: ["get", "list"]
|
||||||
|
|
||||||
|
# Access controller
|
||||||
|
- apiGroups: [""]
|
||||||
|
resources: ["services", "services/proxy"]
|
||||||
|
verbs: ["get"]
|
||||||
|
|
||||||
|
# View namespaces
|
||||||
|
- apiGroups: [""]
|
||||||
|
resources: ["namespaces"]
|
||||||
|
verbs: ["list"]
|
||||||
|
```
|
||||||
|
|
||||||
|
**What you can do:**
|
||||||
|
- ✅ Everything from creator role
|
||||||
|
- ✅ Delete sealed secrets
|
||||||
|
- ✅ View unsealed secret values (decrypt)
|
||||||
|
- ✅ Update sealed secrets
|
||||||
|
|
||||||
|
## Example RBAC Configurations
|
||||||
|
|
||||||
|
### Example 1: Development Team (Namespace-Scoped)
|
||||||
|
|
||||||
|
Allow developers to manage sealed secrets in their namespace:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
---
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
kind: Role
|
||||||
|
metadata:
|
||||||
|
name: dev-team-sealed-secrets
|
||||||
|
namespace: development
|
||||||
|
rules:
|
||||||
|
- apiGroups: ["bitnami.com"]
|
||||||
|
resources: ["sealedsecrets"]
|
||||||
|
verbs: ["get", "list", "create", "delete"]
|
||||||
|
|
||||||
|
- apiGroups: [""]
|
||||||
|
resources: ["services"]
|
||||||
|
verbs: ["get"]
|
||||||
|
resourceNames: ["sealed-secrets-controller"]
|
||||||
|
|
||||||
|
---
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
kind: RoleBinding
|
||||||
|
metadata:
|
||||||
|
name: dev-team-sealed-secrets
|
||||||
|
namespace: development
|
||||||
|
roleRef:
|
||||||
|
apiGroup: rbac.authorization.k8s.io
|
||||||
|
kind: Role
|
||||||
|
name: dev-team-sealed-secrets
|
||||||
|
subjects:
|
||||||
|
- kind: Group
|
||||||
|
name: developers
|
||||||
|
apiGroup: rbac.authorization.k8s.io
|
||||||
|
```
|
||||||
|
|
||||||
|
### Example 2: Platform Team (Cluster-Wide)
|
||||||
|
|
||||||
|
Allow platform team to manage all sealed secrets:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
---
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
kind: ClusterRole
|
||||||
|
metadata:
|
||||||
|
name: platform-sealed-secrets
|
||||||
|
rules:
|
||||||
|
- apiGroups: ["bitnami.com"]
|
||||||
|
resources: ["sealedsecrets"]
|
||||||
|
verbs: ["*"]
|
||||||
|
|
||||||
|
- apiGroups: [""]
|
||||||
|
resources: ["secrets"]
|
||||||
|
verbs: ["get", "list"]
|
||||||
|
|
||||||
|
- apiGroups: [""]
|
||||||
|
resources: ["services", "services/proxy"]
|
||||||
|
verbs: ["get"]
|
||||||
|
|
||||||
|
- apiGroups: [""]
|
||||||
|
resources: ["namespaces"]
|
||||||
|
verbs: ["list"]
|
||||||
|
|
||||||
|
---
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
kind: ClusterRoleBinding
|
||||||
|
metadata:
|
||||||
|
name: platform-sealed-secrets
|
||||||
|
roleRef:
|
||||||
|
apiGroup: rbac.authorization.k8s.io
|
||||||
|
kind: ClusterRole
|
||||||
|
name: platform-sealed-secrets
|
||||||
|
subjects:
|
||||||
|
- kind: Group
|
||||||
|
name: platform-team
|
||||||
|
apiGroup: rbac.authorization.k8s.io
|
||||||
|
```
|
||||||
|
|
||||||
|
### Example 3: Read-Only Auditor
|
||||||
|
|
||||||
|
Allow security team to audit sealed secrets without modification:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
---
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
kind: ClusterRole
|
||||||
|
metadata:
|
||||||
|
name: sealed-secrets-auditor
|
||||||
|
rules:
|
||||||
|
- apiGroups: ["bitnami.com"]
|
||||||
|
resources: ["sealedsecrets"]
|
||||||
|
verbs: ["get", "list"]
|
||||||
|
|
||||||
|
- apiGroups: [""]
|
||||||
|
resources: ["secrets"]
|
||||||
|
verbs: ["get", "list"]
|
||||||
|
|
||||||
|
- apiGroups: [""]
|
||||||
|
resources: ["namespaces"]
|
||||||
|
verbs: ["list"]
|
||||||
|
|
||||||
|
---
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
kind: ClusterRoleBinding
|
||||||
|
metadata:
|
||||||
|
name: security-team-auditor
|
||||||
|
roleRef:
|
||||||
|
apiGroup: rbac.authorization.k8s.io
|
||||||
|
kind: ClusterRole
|
||||||
|
name: sealed-secrets-auditor
|
||||||
|
subjects:
|
||||||
|
- kind: Group
|
||||||
|
name: security-auditors
|
||||||
|
apiGroup: rbac.authorization.k8s.io
|
||||||
|
```
|
||||||
|
|
||||||
|
### Example 4: CI/CD Service Account
|
||||||
|
|
||||||
|
Allow automated systems to create sealed secrets:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ServiceAccount
|
||||||
|
metadata:
|
||||||
|
name: ci-cd-sealed-secrets
|
||||||
|
namespace: ci-cd
|
||||||
|
|
||||||
|
---
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
kind: ClusterRole
|
||||||
|
metadata:
|
||||||
|
name: ci-cd-sealed-secrets-creator
|
||||||
|
rules:
|
||||||
|
- apiGroups: ["bitnami.com"]
|
||||||
|
resources: ["sealedsecrets"]
|
||||||
|
verbs: ["create", "get", "list"]
|
||||||
|
|
||||||
|
- apiGroups: [""]
|
||||||
|
resources: ["services"]
|
||||||
|
verbs: ["get"]
|
||||||
|
resourceNames: ["sealed-secrets-controller"]
|
||||||
|
|
||||||
|
---
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
kind: ClusterRoleBinding
|
||||||
|
metadata:
|
||||||
|
name: ci-cd-sealed-secrets
|
||||||
|
roleRef:
|
||||||
|
apiGroup: rbac.authorization.k8s.io
|
||||||
|
kind: ClusterRole
|
||||||
|
name: ci-cd-sealed-secrets-creator
|
||||||
|
subjects:
|
||||||
|
- kind: ServiceAccount
|
||||||
|
name: ci-cd-sealed-secrets
|
||||||
|
namespace: ci-cd
|
||||||
|
```
|
||||||
|
|
||||||
|
## Plugin UI Behavior
|
||||||
|
|
||||||
|
The plugin automatically adjusts the UI based on permissions:
|
||||||
|
|
||||||
|
### With Full Permissions
|
||||||
|
- ✅ "Create Sealed Secret" button visible
|
||||||
|
- ✅ "Delete" button visible on details page
|
||||||
|
- ✅ "Decrypt" button visible (if Secret access granted)
|
||||||
|
- ✅ Download sealing certificates
|
||||||
|
|
||||||
|
### With Read-Only Permissions
|
||||||
|
- ❌ "Create Sealed Secret" button hidden
|
||||||
|
- ❌ "Delete" button hidden/disabled
|
||||||
|
- ❌ "Decrypt" button hidden
|
||||||
|
- ❌ Download button hidden/disabled
|
||||||
|
|
||||||
|
### Testing Permissions
|
||||||
|
|
||||||
|
The plugin uses Kubernetes `SelfSubjectAccessReview` to check permissions in real-time.
|
||||||
|
|
||||||
|
You can test manually:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Check if you can create SealedSecrets
|
||||||
|
kubectl auth can-i create sealedsecrets.bitnami.com
|
||||||
|
|
||||||
|
# Check if you can list SealedSecrets
|
||||||
|
kubectl auth can-i list sealedsecrets.bitnami.com
|
||||||
|
|
||||||
|
# Check if you can view Secrets (for decryption)
|
||||||
|
kubectl auth can-i get secrets
|
||||||
|
|
||||||
|
# Check in specific namespace
|
||||||
|
kubectl auth can-i create sealedsecrets.bitnami.com -n production
|
||||||
|
```
|
||||||
|
|
||||||
|
## Common Permission Issues
|
||||||
|
|
||||||
|
### Issue 1: "Create Sealed Secret" Button Not Showing
|
||||||
|
|
||||||
|
**Cause**: Missing `create` permission for SealedSecrets.
|
||||||
|
|
||||||
|
**Solution**:
|
||||||
|
```yaml
|
||||||
|
rules:
|
||||||
|
- apiGroups: ["bitnami.com"]
|
||||||
|
resources: ["sealedsecrets"]
|
||||||
|
verbs: ["create"] # Add this verb
|
||||||
|
```
|
||||||
|
|
||||||
|
### Issue 2: Cannot Download Sealing Certificates
|
||||||
|
|
||||||
|
**Cause**: Missing service access permission.
|
||||||
|
|
||||||
|
**Solution**:
|
||||||
|
```yaml
|
||||||
|
rules:
|
||||||
|
- apiGroups: [""]
|
||||||
|
resources: ["services"]
|
||||||
|
verbs: ["get"]
|
||||||
|
resourceNames: ["sealed-secrets-controller"]
|
||||||
|
```
|
||||||
|
|
||||||
|
Or for proxy access:
|
||||||
|
```yaml
|
||||||
|
rules:
|
||||||
|
- apiGroups: [""]
|
||||||
|
resources: ["services/proxy"]
|
||||||
|
verbs: ["get", "create"]
|
||||||
|
```
|
||||||
|
|
||||||
|
### Issue 3: "Decrypt" Button Not Showing
|
||||||
|
|
||||||
|
**Cause**: Missing `get` permission for Secrets.
|
||||||
|
|
||||||
|
**Solution**:
|
||||||
|
```yaml
|
||||||
|
rules:
|
||||||
|
- apiGroups: [""]
|
||||||
|
resources: ["secrets"]
|
||||||
|
verbs: ["get"] # Required for decryption
|
||||||
|
```
|
||||||
|
|
||||||
|
### Issue 4: Cannot See SealedSecrets in Other Namespaces
|
||||||
|
|
||||||
|
**Cause**: Using `Role` instead of `ClusterRole`, or missing namespace list permission.
|
||||||
|
|
||||||
|
**Solution**: Use `ClusterRole` and `ClusterRoleBinding`:
|
||||||
|
```yaml
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
kind: ClusterRole # Not Role
|
||||||
|
metadata:
|
||||||
|
name: sealed-secrets-viewer
|
||||||
|
rules:
|
||||||
|
- apiGroups: ["bitnami.com"]
|
||||||
|
resources: ["sealedsecrets"]
|
||||||
|
verbs: ["get", "list"]
|
||||||
|
- apiGroups: [""]
|
||||||
|
resources: ["namespaces"]
|
||||||
|
verbs: ["list"] # Required
|
||||||
|
```
|
||||||
|
|
||||||
|
## Security Best Practices
|
||||||
|
|
||||||
|
### 1. Principle of Least Privilege
|
||||||
|
|
||||||
|
Only grant the minimum permissions needed:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# Good: Namespace-scoped for dev team
|
||||||
|
kind: Role
|
||||||
|
metadata:
|
||||||
|
namespace: dev-team-namespace
|
||||||
|
|
||||||
|
# Risky: Cluster-wide for dev team
|
||||||
|
kind: ClusterRole # Only if truly needed
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Separate Secret Access
|
||||||
|
|
||||||
|
Don't grant Secret access unless absolutely necessary:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# ✅ Good: Can create SealedSecrets but not view Secrets
|
||||||
|
- apiGroups: ["bitnami.com"]
|
||||||
|
resources: ["sealedsecrets"]
|
||||||
|
verbs: ["create"]
|
||||||
|
|
||||||
|
# ❌ Risky: Can view unsealed Secrets
|
||||||
|
- apiGroups: [""]
|
||||||
|
resources: ["secrets"]
|
||||||
|
verbs: ["get", "list"] # Only for authorized users
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Use Groups, Not Individual Users
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# ✅ Good: Use groups
|
||||||
|
subjects:
|
||||||
|
- kind: Group
|
||||||
|
name: developers
|
||||||
|
|
||||||
|
# ❌ Harder to maintain: Individual users
|
||||||
|
subjects:
|
||||||
|
- kind: User
|
||||||
|
name: alice
|
||||||
|
- kind: User
|
||||||
|
name: bob
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. Audit RBAC Changes
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# List all ClusterRoleBindings for SealedSecrets
|
||||||
|
kubectl get clusterrolebindings -o json | \
|
||||||
|
jq '.items[] | select(.roleRef.name | contains("sealed-secrets"))'
|
||||||
|
|
||||||
|
# List all RoleBindings in a namespace
|
||||||
|
kubectl get rolebindings -n production -o yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
## Troubleshooting Commands
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. Check your current permissions
|
||||||
|
kubectl auth can-i --list
|
||||||
|
|
||||||
|
# 2. Check specific permission
|
||||||
|
kubectl auth can-i create sealedsecrets.bitnami.com -n production
|
||||||
|
|
||||||
|
# 3. Check as another user (requires admin)
|
||||||
|
kubectl auth can-i create sealedsecrets --as alice --as-group developers
|
||||||
|
|
||||||
|
# 4. View your roles
|
||||||
|
kubectl get rolebindings,clusterrolebindings --all-namespaces -o wide | grep $(kubectl auth whoami)
|
||||||
|
|
||||||
|
# 5. Describe a role
|
||||||
|
kubectl describe clusterrole sealed-secrets-creator
|
||||||
|
```
|
||||||
|
|
||||||
|
## Next Steps
|
||||||
|
|
||||||
|
- **[Creating Secrets](creating-secrets.md)** - Create your first sealed secret
|
||||||
|
- **[Scopes Explained](scopes-explained.md)** - Understand security scopes
|
||||||
|
- **[Security Hardening](../deployment/security-hardening.md)** - Production security guide
|
||||||
|
|
||||||
|
## Resources
|
||||||
|
|
||||||
|
- [Kubernetes RBAC Documentation](https://kubernetes.io/docs/reference/access-authn-authz/rbac/)
|
||||||
|
- [SelfSubjectAccessReview API](https://kubernetes.io/docs/reference/access-authn-authz/authorization/#checking-api-access)
|
||||||
|
- [Best Practices for RBAC](https://kubernetes.io/docs/concepts/security/rbac-good-practices/)
|
||||||
@@ -0,0 +1,396 @@
|
|||||||
|
# Scopes Explained
|
||||||
|
|
||||||
|
Understanding SealedSecret scopes and when to use each one.
|
||||||
|
|
||||||
|
## What are Scopes?
|
||||||
|
|
||||||
|
Scopes determine **where** a SealedSecret can be unsealed. They control the binding between the encrypted data and its Kubernetes resource identity (namespace and/or name).
|
||||||
|
|
||||||
|
Think of scopes as **security levels** for your encrypted secrets:
|
||||||
|
- **Strict** = Locked to specific name + namespace (most secure)
|
||||||
|
- **Namespace-wide** = Locked to namespace (can rename)
|
||||||
|
- **Cluster-wide** = Can move anywhere (least secure)
|
||||||
|
|
||||||
|
## The Three Scopes
|
||||||
|
|
||||||
|
### Strict Scope (Recommended)
|
||||||
|
|
||||||
|
**Binding**: Namespace + Name + Key
|
||||||
|
|
||||||
|
The sealed secret can ONLY be unsealed if all three match:
|
||||||
|
- ✅ Same namespace
|
||||||
|
- ✅ Same secret name
|
||||||
|
- ✅ Same key name
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
```yaml
|
||||||
|
apiVersion: bitnami.com/v1alpha1
|
||||||
|
kind: SealedSecret
|
||||||
|
metadata:
|
||||||
|
name: db-credentials
|
||||||
|
namespace: production
|
||||||
|
spec:
|
||||||
|
encryptedData:
|
||||||
|
password: AgB... # Can ONLY be unsealed as:
|
||||||
|
# - name: db-credentials
|
||||||
|
# - namespace: production
|
||||||
|
# - key: password
|
||||||
|
```
|
||||||
|
|
||||||
|
**Use when:**
|
||||||
|
- Production secrets
|
||||||
|
- Database credentials
|
||||||
|
- API keys
|
||||||
|
- Any sensitive data
|
||||||
|
|
||||||
|
**Advantages:**
|
||||||
|
- ✅ Maximum security
|
||||||
|
- ✅ Prevents accidental leaks from renaming
|
||||||
|
- ✅ Prevents cross-namespace access
|
||||||
|
|
||||||
|
**Limitations:**
|
||||||
|
- ❌ Cannot rename the secret
|
||||||
|
- ❌ Cannot move to different namespace
|
||||||
|
- ❌ Must re-encrypt if name/namespace changes
|
||||||
|
|
||||||
|
**Creating with Headlamp:**
|
||||||
|
1. Click "Create Sealed Secret"
|
||||||
|
2. Select **"strict"** scope (default)
|
||||||
|
3. Fill in name, namespace, and secret data
|
||||||
|
4. Click "Create"
|
||||||
|
|
||||||
|
**Creating with kubeseal:**
|
||||||
|
```bash
|
||||||
|
kubectl create secret generic db-credentials \
|
||||||
|
--namespace production \
|
||||||
|
--from-literal=password=mysecret \
|
||||||
|
--dry-run=client -o yaml | \
|
||||||
|
kubeseal --cert cert.pem --scope strict --format yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
### Namespace-Wide Scope
|
||||||
|
|
||||||
|
**Binding**: Namespace + Key
|
||||||
|
|
||||||
|
The sealed secret can be unsealed if:
|
||||||
|
- ✅ Same namespace
|
||||||
|
- ✅ Same key name
|
||||||
|
- ⚠️ Any secret name (can rename)
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
```yaml
|
||||||
|
apiVersion: bitnami.com/v1alpha1
|
||||||
|
kind: SealedSecret
|
||||||
|
metadata:
|
||||||
|
name: app-config
|
||||||
|
namespace: staging
|
||||||
|
spec:
|
||||||
|
encryptedData:
|
||||||
|
api-url: AgB... # Can be unsealed as:
|
||||||
|
# - name: app-config (or ANY name)
|
||||||
|
# - namespace: staging (must match)
|
||||||
|
# - key: api-url (must match)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Use when:**
|
||||||
|
- Shared configuration across multiple apps in a namespace
|
||||||
|
- Secrets that might be renamed
|
||||||
|
- Non-critical secrets
|
||||||
|
|
||||||
|
**Advantages:**
|
||||||
|
- ✅ Flexible naming
|
||||||
|
- ✅ Can rename secret without re-encryption
|
||||||
|
- ✅ Still namespace-isolated
|
||||||
|
|
||||||
|
**Limitations:**
|
||||||
|
- ❌ Less secure than strict
|
||||||
|
- ❌ Cannot move to different namespace
|
||||||
|
- ❌ Anyone in namespace can unseal by creating correctly-named secret
|
||||||
|
|
||||||
|
**Creating with Headlamp:**
|
||||||
|
1. Click "Create Sealed Secret"
|
||||||
|
2. Select **"namespace-wide"** scope
|
||||||
|
3. Fill in namespace and secret data
|
||||||
|
4. Name can be changed later
|
||||||
|
|
||||||
|
**Creating with kubeseal:**
|
||||||
|
```bash
|
||||||
|
kubectl create secret generic temp-name \
|
||||||
|
--namespace staging \
|
||||||
|
--from-literal=api-url=https://api.staging.example.com \
|
||||||
|
--dry-run=client -o yaml | \
|
||||||
|
kubeseal --cert cert.pem --scope namespace-wide --format yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
### Cluster-Wide Scope
|
||||||
|
|
||||||
|
**Binding**: Key only
|
||||||
|
|
||||||
|
The sealed secret can be unsealed if:
|
||||||
|
- ⚠️ Any namespace
|
||||||
|
- ⚠️ Any secret name
|
||||||
|
- ✅ Same key name
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
```yaml
|
||||||
|
apiVersion: bitnami.com/v1alpha1
|
||||||
|
kind: SealedSecret
|
||||||
|
metadata:
|
||||||
|
name: global-config
|
||||||
|
namespace: default
|
||||||
|
spec:
|
||||||
|
encryptedData:
|
||||||
|
license-key: AgB... # Can be unsealed as:
|
||||||
|
# - name: ANY
|
||||||
|
# - namespace: ANY
|
||||||
|
# - key: license-key (must match)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Use when:**
|
||||||
|
- Truly global configuration
|
||||||
|
- License keys used across all namespaces
|
||||||
|
- Public URLs or non-sensitive config
|
||||||
|
|
||||||
|
**Advantages:**
|
||||||
|
- ✅ Maximum flexibility
|
||||||
|
- ✅ Can move anywhere in cluster
|
||||||
|
- ✅ Can rename freely
|
||||||
|
|
||||||
|
**Limitations:**
|
||||||
|
- ❌ Least secure
|
||||||
|
- ❌ Anyone in cluster can unseal
|
||||||
|
- ❌ Easy to accidentally expose
|
||||||
|
|
||||||
|
**Creating with Headlamp:**
|
||||||
|
1. Click "Create Sealed Secret"
|
||||||
|
2. Select **"cluster-wide"** scope
|
||||||
|
3. Fill in secret data
|
||||||
|
4. Can deploy to any namespace
|
||||||
|
|
||||||
|
**Creating with kubeseal:**
|
||||||
|
```bash
|
||||||
|
kubectl create secret generic temp \
|
||||||
|
--from-literal=license-key=ABC123 \
|
||||||
|
--dry-run=client -o yaml | \
|
||||||
|
kubeseal --cert cert.pem --scope cluster-wide --format yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
## Scope Comparison
|
||||||
|
|
||||||
|
| Feature | Strict | Namespace-Wide | Cluster-Wide |
|
||||||
|
|---------|--------|----------------|--------------|
|
||||||
|
| **Security** | 🔒🔒🔒 High | 🔒🔒 Medium | 🔒 Low |
|
||||||
|
| **Can rename** | ❌ No | ✅ Yes | ✅ Yes |
|
||||||
|
| **Can move namespace** | ❌ No | ❌ No | ✅ Yes |
|
||||||
|
| **Binding** | Name+NS+Key | NS+Key | Key only |
|
||||||
|
| **Use case** | Production secrets | Shared namespace config | Global config |
|
||||||
|
| **Recommended for** | Credentials, API keys | App config | Public URLs |
|
||||||
|
|
||||||
|
## Decision Tree
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────┐
|
||||||
|
│ Is this a sensitive credential? │
|
||||||
|
│ (password, API key, token, etc) │
|
||||||
|
└─────────┬──────────────┬────────┘
|
||||||
|
│ │
|
||||||
|
YES NO
|
||||||
|
│ │
|
||||||
|
v v
|
||||||
|
┌─────────┐ ┌──────────────────┐
|
||||||
|
│ strict │ │ Could this value │
|
||||||
|
│ scope │ │ be shared across │
|
||||||
|
└─────────┘ │ your cluster? │
|
||||||
|
└────┬────────┬────┘
|
||||||
|
│ │
|
||||||
|
YES NO
|
||||||
|
│ │
|
||||||
|
v v
|
||||||
|
┌──────────┐ ┌──────────────┐
|
||||||
|
│cluster- │ │namespace- │
|
||||||
|
│wide scope│ │wide scope │
|
||||||
|
└──────────┘ └──────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
## Examples by Use Case
|
||||||
|
|
||||||
|
### Database Credentials (Strict)
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
apiVersion: bitnami.com/v1alpha1
|
||||||
|
kind: SealedSecret
|
||||||
|
metadata:
|
||||||
|
name: postgres-credentials
|
||||||
|
namespace: production
|
||||||
|
spec:
|
||||||
|
encryptedData:
|
||||||
|
username: AgB...
|
||||||
|
password: AgB...
|
||||||
|
host: AgB...
|
||||||
|
```
|
||||||
|
|
||||||
|
**Why strict?** Credentials must not leak to other namespaces or be accessible via renaming.
|
||||||
|
|
||||||
|
### Application Config (Namespace-Wide)
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
apiVersion: bitnami.com/v1alpha1
|
||||||
|
kind: SealedSecret
|
||||||
|
metadata:
|
||||||
|
name: app-config
|
||||||
|
namespace: staging
|
||||||
|
spec:
|
||||||
|
encryptedData:
|
||||||
|
api-url: AgB...
|
||||||
|
timeout: AgB...
|
||||||
|
log-level: AgB...
|
||||||
|
```
|
||||||
|
|
||||||
|
**Why namespace-wide?** Config is specific to staging but might be used by multiple apps (with different names).
|
||||||
|
|
||||||
|
### License Key (Cluster-Wide)
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
apiVersion: bitnami.com/v1alpha1
|
||||||
|
kind: SealedSecret
|
||||||
|
metadata:
|
||||||
|
name: company-license
|
||||||
|
namespace: default
|
||||||
|
spec:
|
||||||
|
encryptedData:
|
||||||
|
license-key: AgB...
|
||||||
|
```
|
||||||
|
|
||||||
|
**Why cluster-wide?** The same license applies to all namespaces and environments.
|
||||||
|
|
||||||
|
## Changing Scopes
|
||||||
|
|
||||||
|
You **cannot** change a scope after encryption. To change scope:
|
||||||
|
|
||||||
|
1. **Delete the old sealed secret**:
|
||||||
|
```bash
|
||||||
|
kubectl delete sealedsecret old-secret -n production
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Re-encrypt with new scope**:
|
||||||
|
```bash
|
||||||
|
kubectl create secret generic old-secret \
|
||||||
|
--namespace production \
|
||||||
|
--from-literal=password=value \
|
||||||
|
--dry-run=client -o yaml | \
|
||||||
|
kubeseal --cert cert.pem --scope namespace-wide --format yaml > new-sealed-secret.yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Apply new sealed secret**:
|
||||||
|
```bash
|
||||||
|
kubectl apply -f new-sealed-secret.yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
## Security Best Practices
|
||||||
|
|
||||||
|
### 1. Default to Strict
|
||||||
|
|
||||||
|
Always use **strict scope** unless you have a specific reason not to:
|
||||||
|
|
||||||
|
✅ **Good:**
|
||||||
|
```bash
|
||||||
|
# Explicit strict scope
|
||||||
|
kubeseal --scope strict
|
||||||
|
```
|
||||||
|
|
||||||
|
❌ **Risky:**
|
||||||
|
```bash
|
||||||
|
# Using cluster-wide for credentials
|
||||||
|
kubeseal --scope cluster-wide # Don't do this for secrets!
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Audit Your Scopes
|
||||||
|
|
||||||
|
List all sealed secrets and their scopes:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
kubectl get sealedsecrets --all-namespaces -o json | \
|
||||||
|
jq -r '.items[] | "\(.metadata.namespace)/\(.metadata.name): \(.spec.template.type // "strict")"'
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Separate Sensitive from Non-Sensitive
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Sensitive → strict
|
||||||
|
kubectl create secret generic db-creds --from-literal=password=... | \
|
||||||
|
kubeseal --scope strict
|
||||||
|
|
||||||
|
# Non-sensitive → namespace-wide or cluster-wide
|
||||||
|
kubectl create secret generic app-urls --from-literal=api=https://... | \
|
||||||
|
kubeseal --scope namespace-wide
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. Document Scope Choices
|
||||||
|
|
||||||
|
Add annotations to explain why a scope was chosen:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
apiVersion: bitnami.com/v1alpha1
|
||||||
|
kind: SealedSecret
|
||||||
|
metadata:
|
||||||
|
name: db-credentials
|
||||||
|
namespace: production
|
||||||
|
annotations:
|
||||||
|
sealedsecrets.scope: "strict"
|
||||||
|
sealedsecrets.reason: "Database credentials must not be accessible outside production namespace"
|
||||||
|
spec:
|
||||||
|
encryptedData:
|
||||||
|
password: AgB...
|
||||||
|
```
|
||||||
|
|
||||||
|
## Common Mistakes
|
||||||
|
|
||||||
|
### ❌ Using Cluster-Wide for Everything
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# DON'T: Encrypting passwords with cluster-wide scope
|
||||||
|
kubeseal --scope cluster-wide < secret-with-passwords.yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
**Problem**: Anyone in any namespace can create a secret with the same key name and unseal it.
|
||||||
|
|
||||||
|
**Solution**: Use strict scope for credentials.
|
||||||
|
|
||||||
|
### ❌ Renaming Strict-Scoped Secrets
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Create strict-scoped secret
|
||||||
|
kubectl apply -f db-secret.yaml # name: db-credentials
|
||||||
|
|
||||||
|
# Later, try to rename
|
||||||
|
kubectl apply -f db-secret.yaml # name: database-creds (DIFFERENT NAME)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Problem**: Sealed secret won't unseal because name changed.
|
||||||
|
|
||||||
|
**Solution**: Re-encrypt with new name or use namespace-wide scope.
|
||||||
|
|
||||||
|
### ❌ Moving Secrets Between Namespaces
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Seal for production
|
||||||
|
kubectl create secret generic app-secret --namespace production | kubeseal
|
||||||
|
|
||||||
|
# Try to use in staging
|
||||||
|
kubectl apply -f sealed-secret.yaml --namespace staging
|
||||||
|
```
|
||||||
|
|
||||||
|
**Problem**: Won't unseal (namespace mismatch with strict or namespace-wide scope).
|
||||||
|
|
||||||
|
**Solution**: Re-encrypt for the target namespace or use cluster-wide (if appropriate).
|
||||||
|
|
||||||
|
## Next Steps
|
||||||
|
|
||||||
|
- **[RBAC Permissions](rbac-permissions.md)** - Control who can create/view sealed secrets
|
||||||
|
- **[Creating Secrets](creating-secrets.md)** - Complete guide to secret creation
|
||||||
|
- **[Secret Rotation](../tutorials/secret-rotation.md)** - Rotate secrets and keys
|
||||||
|
|
||||||
|
## Resources
|
||||||
|
|
||||||
|
- [Sealed Secrets Scopes Documentation](https://github.com/bitnami-labs/sealed-secrets#scopes)
|
||||||
|
- [Security Best Practices](../deployment/security-hardening.md)
|
||||||
Reference in New Issue
Block a user