SOPS Integration - Team Sharing & CI/CD

Overview

SOPS (Secrets OPerationS) extends dsec with multi-recipient encryption capabilities, enabling:

  • Team Sharing - Share secrets with family, friends, collaborators using multiple age keys

  • CI/CD Integration - Use cloud KMS (AWS, GCP, Azure) for pipeline secrets

  • Backward Compatibility - Existing dsec workflow unchanged

Architecture

%%{init: {'theme': 'dark', 'themeVariables': { 'primaryColor': '#313244', 'primaryTextColor': '#cdd6f4', 'primaryBorderColor': '#89b4fa', 'lineColor': '#89b4fa', 'secondaryColor': '#45475a', 'tertiaryColor': '#181825', 'background': '#1e1e2e', 'mainBkg': '#313244', 'nodeBorder': '#89b4fa', 'clusterBkg': '#181825', 'clusterBorder': '#585b70', 'titleColor': '#cdd6f4', 'fontFamily': 'JetBrains Mono, monospace'}}}%%
flowchart TB
    subgraph Personal["Personal Secrets (dsec/age)"]
        D000["d000/"]
        D001["d001+"]
        MASTER["master.age.key"]
    end

    subgraph Shared["Team Secrets (dsec+sops)"]
        TEAMS["teams/"]
        REGISTRY["registry.yaml"]
        SOPS_YAML[".sops.yaml"]
    end

    subgraph Keys["Key Management"]
        AGE["Age Keys<br/>(recipients)"]
        AWS["AWS KMS"]
        GCP["GCP KMS"]
        AZURE["Azure KV"]
    end

    MASTER --> D000 & D001
    AGE --> TEAMS
    AWS & GCP & AZURE --> TEAMS
    REGISTRY --> TEAMS
    SOPS_YAML --> TEAMS

    style Personal fill:#a6e3a1,color:#1e1e2e
    style Shared fill:#89b4fa,color:#1e1e2e
    style Keys fill:#f9e2af,color:#1e1e2e

Directory Structure

~/.secrets/
├── [EXISTING - UNCHANGED]
│   ├── environments/domains/{d000,d001,...}
│   ├── .metadata/keys/master.age.{key,pub}
│   └── bin/dsec
│
└── shared/                          # NEW: SOPS-managed secrets
    ├── .sops.yaml                   # Global SOPS config
    ├── registry.yaml                # Team registry (metadata)
    ├── keys/
    │   ├── recipients/              # Team member public keys
    │   │   └── owner.txt
    │   └── kms/                     # Cloud KMS ARNs
    └── teams/
        ├── family/
        │   ├── .sops.yaml           # Team-specific config
        │   └── home-automation.yaml # SOPS-encrypted secrets
        ├── homelab/
        └── friends/

Key Management Tiers

Tier Keys Location Use Case

Personal

master.age.key

~/.secrets/.metadata/keys/

Your d000/d001+ domain secrets

Team

Multiple age public keys

~/.secrets/shared/teams/{team}/

Family, friends, collaborators

CI/CD

AWS/GCP/Azure KMS

Cloud provider

GitHub Actions, GitLab CI, pipelines

Commands Reference

Team Management

Command Description

dsec team list

List all teams with member/secret counts

dsec team init <id> [name]

Create a new team

dsec team add-member <team> <pubkey> [--name]

Add member to team

dsec team remove-member <team> <member-id>

Remove member from team

dsec team keys <team>

Display all team public keys

dsec team rotate <team>

Re-encrypt all team secrets with current keys

Shared Secrets

Command Description

dsec shared list [team]

List shared secrets

dsec shared source <team> <name>

Output export statements for eval

dsec shared unsource

Clear shared secrets from environment

dsec shared show <team> <name>

Display decrypted secrets

dsec shared edit <team> <name>

Edit secrets via SOPS (creates if new)

dsec shared add <team> <name> [file]

Add secrets from YAML file

CI/CD Integration

Command Description

dsec cicd init <path> [--provider]

Initialize SOPS config for project

dsec cicd add-kms <path> <arn>

Add KMS key to project config

dsec cicd validate <path>

Validate SOPS configuration

dsec cicd export <path> <name>

Export secrets as KEY=value for CI

Shell Wrappers

Add to ~/.zshrc (auto-included in shell-security.zsh):

# SOPS uses this for decryption
export SOPS_AGE_KEY_FILE="$HOME/.secrets/.metadata/keys/master.age.key"

# Team secrets loader
dsteam() { eval " $(dsec shared source "$@")"; }
dsteunsource() { eval " $(dsec shared unsource)"; }

# Short aliases
alias dst='dsteam'
alias dstu='dsteunsource'

Usage

# Load team secrets
dsteam family home-automation

# Clear when done
dsteunsource

# Or short form
dst family home-automation
dstu

Workflows

Creating a Team

# Initialize team
dsec team init family "Family Home Automation"

# View teams
dsec team list

Adding Team Members

# Member generates their key
age-keygen -o alice.key              # They keep this private
age-keygen -y alice.key > alice.pub  # They send you this

# You add them to the team
dsec team add-member family alice.pub --name "Alice"

# Re-encrypt existing secrets for new member
dsec team rotate family

Creating Shared Secrets

# Interactive edit (creates file if new)
dsec shared edit family home-automation

# Or from existing YAML
cat > /tmp/secrets.yaml << 'EOF'
MQTT_USER: homeassistant
MQTT_PASS: supersecret
ZIGBEE_KEY: 0x1234567890abcdef
EOF
dsec shared add family home-automation /tmp/secrets.yaml
shred -u /tmp/secrets.yaml

Team Member Decryption

Team members decrypt with their own age key:

# On Alice's machine
export SOPS_AGE_KEY_FILE=~/.age/alice.key
sops -d ~/.secrets/shared/teams/family/home-automation.yaml

# Or using dsec (if they have it)
dst family home-automation

CI/CD Pipeline Setup

# In your project directory
dsec cicd init . --github

# Add AWS KMS for production
dsec cicd add-kms . "arn:aws:kms:us-east-1:123456789:key/abc-123"

# Validate configuration
dsec cicd validate .

# Add secrets
sops secrets/prod.yaml

GitHub Actions Example

name: Deploy
on: push

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Install SOPS
        run: |
          curl -LO https://github.com/getsops/sops/releases/download/v3.8.1/sops-v3.8.1.linux.amd64
          chmod +x sops-v3.8.1.linux.amd64
          sudo mv sops-v3.8.1.linux.amd64 /usr/local/bin/sops

      - name: Decrypt secrets
        env:
          SOPS_AGE_KEY: ${{ secrets.AGE_SECRET_KEY }}
        run: |
          echo "$SOPS_AGE_KEY" > /tmp/age.key
          export SOPS_AGE_KEY_FILE=/tmp/age.key
          sops -d secrets/prod.yaml > .env
          source .env
          # ... deploy ...
          rm /tmp/age.key .env

Security Model

Isolation

Aspect Personal (dsec) Team (SOPS)

Directory

~/.secrets/environments/

~/.secrets/shared/

Encryption

Single age key

Multi-recipient age + KMS

Access Control

Domain lock (d000 vs d001+)

Team membership

Audit Log

~/.secrets/.metadata/audit.log

~/.secrets/.metadata/team-audit.log

Key Rotation

When a team member leaves:

# Remove member
dsec team remove-member family alice

# Re-encrypt all secrets (excludes removed member)
dsec team rotate family

After removing a member, you should also rotate any secrets they had access to (change passwords, API keys, etc.) as they may have copies.

Dependencies

Tool Purpose Installation

sops

Multi-recipient encryption

pacman -S sops

yq (v4+)

YAML processing

pacman -S go-yq

age

Encryption backend

pacman -S age

Troubleshooting

Issue Cause Solution

yq v4+ required

Python yq installed

sudo pacman -S go-yq

Failed to decrypt

Wrong age key

Check SOPS_AGE_KEY_FILE

Team not found

Team not initialized

dsec team init <name>

No creation_rules

Missing .sops.yaml

dsec cicd init .