Signing & Security

7 minute read

Security Overview

Security is critical for provider distribution. Users must trust that provider binaries are authentic and haven’t been tampered with.

GPG Signing

GPG (GNU Privacy Guard) provides cryptographic signing and verification for releases.

Why Sign Releases?

Trust: Users verify provider authenticity Integrity: Detect tampering or corruption Registry Requirement: Terraform Registry requires signed releases Best Practice: Industry standard for software distribution

GPG Key Generation

# Generate new GPG key
gpg --full-generate-key

# Interactive prompts:
# 1. Key type: RSA and RSA (default)
# 2. Key size: 4096 bits
# 3. Expiration: 2 years (recommended)
# 4. Real name: Your Name
# 5. Email: your.email@example.com
# 6. Comment: Terraform Provider Signing Key
# 7. Passphrase: (strong passphrase)

Key Configuration

# List keys
gpg --list-secret-keys --keyid-format=long

# Output:
# /Users/you/.gnupg/secring.gpg
# -----------------------------
# sec   rsa4096/ABCD1234EFGH5678 2026-01-01 [SC] [expires: 2028-01-01]
#       Key fingerprint = 1234 5678 90AB CDEF 1234  5678 90AB CDEF 1234 5678
# uid                 [ultimate] Your Name (Terraform Provider) <your.email@example.com>
# ssb   rsa4096/IJKL9012MNOP3456 2026-01-01 [E]

# Extract key ID
KEY_ID="ABCD1234EFGH5678"

# Configure Git
git config --global user.signingkey $KEY_ID
git config --global commit.gpgsign true
git config --global tag.gpgsign true

Exporting Keys

# Export public key (for registry)
gpg --armor --export $KEY_ID > terraform-provider-gpg-public.asc

# Export private key (for CI/CD)
gpg --armor --export-secret-keys $KEY_ID > terraform-provider-gpg-private.asc

# Get fingerprint
gpg --fingerprint $KEY_ID

Signing Commits and Tags

Sign Individual Commits

# Sign single commit
git commit -S -m "Add new feature"

# Verify signed commit
git log --show-signature -1

Sign Tags

# Create signed tag
git tag -s v1.0.0 -m "Release v1.0.0"

# Verify signed tag
git tag -v v1.0.0

# Output:
# object 1234567890abcdef1234567890abcdef12345678
# type commit
# tag v1.0.0
# tagger Your Name <your.email@example.com> 1234567890 +0000
#
# Release v1.0.0
# gpg: Signature made Mon Jan 1 12:00:00 2026 UTC
# gpg:                using RSA key ABCD1234EFGH5678
# gpg: Good signature from "Your Name <your.email@example.com>" [ultimate]

Automatic Signing

# Enable automatic commit signing
git config --global commit.gpgsign true

# Enable automatic tag signing
git config --global tag.gpgsign true

# Now all commits and tags are signed by default
git commit -m "Feature update"  # Automatically signed
git tag v1.0.1 -m "Patch release"  # Automatically signed

CI/CD Security

Storing GPG Keys in GitHub

# Export private key for GitHub Secrets
gpg --armor --export-secret-keys $KEY_ID | base64 > gpg-private-base64.txt

# In GitHub: Settings → Secrets → New repository secret
# Name: GPG_PRIVATE_KEY
# Value: (contents of gpg-private-base64.txt or the raw key)

# Add passphrase
# Name: PASSPHRASE
# Value: (your GPG passphrase)

GitHub Actions Setup

# .github/workflows/release.yml
name: Release

on:
  push:
    tags:
      - 'v*'

permissions:
  contents: write

jobs:
  goreleaser:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v4
        with:
          fetch-depth: 0
      
      - name: Set up Go
        uses: actions/setup-go@v5
        with:
          go-version-file: 'go.mod'
          cache: true
      
      - name: Import GPG key
        id: import_gpg
        uses: crazy-max/ghaction-import-gpg@v6
        with:
          gpg_private_key: $
          passphrase: $
      
      - name: Verify GPG
        run: |
          echo "GPG key imported successfully"
          gpg --list-secret-keys
      
      - name: Run GoReleaser
        uses: goreleaser/goreleaser-action@v5
        with:
          version: latest
          args: release --clean
        env:
          GPG_FINGERPRINT: $
          GITHUB_TOKEN: $

Checksum Verification

Generating Checksums

# .goreleaser.yml
checksum:
  name_template: '{{ .ProjectName }}_{{ .Version }}_SHA256SUMS'
  algorithm: sha256
  extra_files:
    - glob: 'terraform-registry-manifest.json'
      name_template: '{{ .ProjectName }}_{{ .Version }}_manifest.json'

Generated file:

# terraform-provider-cachecloud_1.0.0_SHA256SUMS
a1b2c3d4e5f6... terraform-provider-cachecloud_1.0.0_darwin_amd64.zip
e5f6g7h8i9j0... terraform-provider-cachecloud_1.0.0_linux_amd64.zip
i9j0k1l2m3n4... terraform-provider-cachecloud_1.0.0_windows_amd64.zip

Signing Checksums

# .goreleaser.yml
signs:
  - artifacts: checksum
    args:
      - "--batch"
      - "--local-user"
      - "{{ .Env.GPG_FINGERPRINT }}"
      - "--output"
      - "${signature}"
      - "--detach-sign"
      - "${artifact}"

Generates:

terraform-provider-cachecloud_1.0.0_SHA256SUMS.sig

Manual Verification

# Download release assets
curl -LO https://github.com/example/terraform-provider-cachecloud/releases/download/v1.0.0/terraform-provider-cachecloud_1.0.0_SHA256SUMS
curl -LO https://github.com/example/terraform-provider-cachecloud/releases/download/v1.0.0/terraform-provider-cachecloud_1.0.0_SHA256SUMS.sig
curl -LO https://github.com/example/terraform-provider-cachecloud/releases/download/v1.0.0/terraform-provider-cachecloud_1.0.0_linux_amd64.zip

# Import public key
curl https://keybase.io/yourname/pgp_keys.asc | gpg --import

# Verify signature
gpg --verify terraform-provider-cachecloud_1.0.0_SHA256SUMS.sig \
              terraform-provider-cachecloud_1.0.0_SHA256SUMS

# Verify checksum
sha256sum -c terraform-provider-cachecloud_1.0.0_SHA256SUMS 2>&1 | grep OK

Dependency Security

Go Module Checksums

# Verify module checksums
go mod verify

# Output:
# all modules verified

Update Dependencies

# Check for updates
go list -m -u all

# Update specific dependency
go get -u github.com/hashicorp/terraform-plugin-framework@latest

# Update all dependencies
go get -u ./...

# Tidy modules
go mod tidy

Dependabot Configuration

# .github/dependabot.yml
version: 2
updates:
  # Go modules
  - package-ecosystem: "gomod"
    directory: "/"
    schedule:
      interval: "weekly"
    open-pull-requests-limit: 10
    labels:
      - "dependencies"
      - "go"
    commit-message:
      prefix: "deps"
  
  # GitHub Actions
  - package-ecosystem: "github-actions"
    directory: "/"
    schedule:
      interval: "weekly"
    labels:
      - "dependencies"
      - "github-actions"
    commit-message:
      prefix: "ci"

Security Scanning

Static Analysis

# .github/workflows/security.yml
name: Security

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]
  schedule:
    - cron: '0 0 * * 0'  # Weekly

jobs:
  gosec:
    name: Security Scan
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v4
      
      - name: Run Gosec
        uses: securego/gosec@master
        with:
          args: '-no-fail -fmt sarif -out results.sarif ./...'
      
      - name: Upload SARIF file
        uses: github/codeql-action/upload-sarif@v2
        with:
          sarif_file: results.sarif
  
  codeql:
    name: CodeQL Analysis
    runs-on: ubuntu-latest
    permissions:
      security-events: write
    steps:
      - name: Checkout
        uses: actions/checkout@v4
      
      - name: Initialize CodeQL
        uses: github/codeql-action/init@v2
        with:
          languages: go
      
      - name: Autobuild
        uses: github/codeql-action/autobuild@v2
      
      - name: Perform CodeQL Analysis
        uses: github/codeql-action/analyze@v2

Vulnerability Scanning

# Install govulncheck
go install golang.org/x/vuln/cmd/govulncheck@latest

# Scan for vulnerabilities
govulncheck ./...

# Output:
# Scanning your code and 123 packages across 45 dependent modules for known vulnerabilities...
# No vulnerabilities found.

Add to CI/CD

# .github/workflows/security.yml (additional job)
  vulnerability-scan:
    name: Vulnerability Scan
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      
      - uses: actions/setup-go@v5
        with:
          go-version: '1.21'
      
      - name: Install govulncheck
        run: go install golang.org/x/vuln/cmd/govulncheck@latest
      
      - name: Scan for vulnerabilities
        run: govulncheck ./...

Secret Management

Preventing Secret Commits

# Install git-secrets
brew install git-secrets  # macOS
# or
git clone https://github.com/awslabs/git-secrets.git && cd git-secrets && make install

# Initialize in repository
cd terraform-provider-cachecloud
git secrets --install
git secrets --register-aws

# Add custom patterns
git secrets --add 'api[_-]?key.*=.*["\x27][0-9a-zA-Z]{32,}["\x27]'
git secrets --add 'password.*=.*["\x27].+["\x27]'

# Scan repository
git secrets --scan

Pre-commit Hooks

# .pre-commit-config.yaml
repos:
  - repo: https://github.com/pre-commit/pre-commit-hooks
    rev: v4.5.0
    hooks:
      - id: detect-private-key
      - id: detect-aws-credentials
  
  - repo: https://github.com/Yelp/detect-secrets
    rev: v1.4.0
    hooks:
      - id: detect-secrets
        args: ['--baseline', '.secrets.baseline']

Install and run:

pip install pre-commit
pre-commit install
pre-commit run --all-files

Supply Chain Security

SLSA Compliance

# .github/workflows/slsa.yml
name: SLSA Provenance

on:
  release:
    types: [created]

permissions:
  contents: write

jobs:
  provenance:
    name: Generate Provenance
    runs-on: ubuntu-latest
    steps:
      - name: Generate provenance
        uses: slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@v1.9.0
        with:
          base64-subjects: "$"
          upload-assets: true

Software Bill of Materials (SBOM)

# Install syft
brew install syft

# Generate SBOM
syft packages . -o spdx-json > sbom.json

# Upload to release
gh release upload v1.0.0 sbom.json

Add to CI/CD:

# .github/workflows/release.yml (additional step)
      - name: Generate SBOM
        run: |
          curl -sSfL https://raw.githubusercontent.com/anchore/syft/main/install.sh | sh -s -- -b /usr/local/bin
          syft packages . -o spdx-json > sbom.json
      
      - name: Upload SBOM
        uses: actions/upload-release-asset@v1
        with:
          upload_url: $
          asset_path: ./sbom.json
          asset_name: sbom.json
          asset_content_type: application/json

Security Best Practices

1. Key Management

# ✅ Good: Separate keys per purpose
Development Key:  dev-signing@example.com
Production Key:   prod-signing@example.com

# ✅ Good: Set expiration
Key expires: 2 years

# ✅ Good: Strong passphrase
20+ characters, mixed case, numbers, symbols

# ❌ Bad: No expiration
Key never expires

# ❌ Bad: Shared key
Same key for multiple projects

2. Secret Storage

# ✅ Good: GitHub Secrets for CI/CD
GPG_PRIVATE_KEY: (encrypted in GitHub)
PASSPHRASE: (encrypted in GitHub)

# ✅ Good: Environment variables
export GPG_PASSPHRASE="..."

# ❌ Bad: Hardcoded in code
passphrase := "mysecretpass123"

# ❌ Bad: Committed to repository
echo "secret" > .env
git add .env

3. Dependency Management

# ✅ Good: Pin versions
github.com/hashicorp/terraform-plugin-framework v1.4.2

# ✅ Good: Regular updates
go get -u ./...

# ✅ Good: Vulnerability scanning
govulncheck ./...

# ❌ Bad: Latest tags
github.com/some/package latest

# ❌ Bad: Never update
Dependencies from 2020

4. Code Review

# ✅ Good: Require reviews
branch protection rules:
  - Require pull request reviews
  - Require status checks
  - Require signed commits

# ✅ Good: CODEOWNERS file
# .github/CODEOWNERS
* @security-team
/internal/provider/ @provider-team

Security Checklist

Pre-release Security

  • ✅ All commits signed
  • ✅ Release tag signed
  • ✅ Dependencies updated
  • ✅ Vulnerabilities scanned
  • ✅ No secrets in code
  • ✅ Static analysis passed
  • ✅ SBOM generated

Release Security

  • ✅ Checksums generated
  • ✅ Checksums signed
  • ✅ GPG key registered in registry
  • ✅ Release artifacts verified
  • ✅ Documentation includes verification steps

Post-release Security

  • ✅ Monitor for vulnerabilities
  • ✅ Update dependencies regularly
  • ✅ Rotate keys as scheduled
  • ✅ Review access controls
  • ✅ Audit releases

Incident Response

Compromised Key

# 1. Revoke compromised key
gpg --edit-key $KEY_ID
gpg> revkey
gpg> save

# 2. Generate new key
gpg --full-generate-key

# 3. Update GitHub Secrets
# Replace GPG_PRIVATE_KEY with new key

# 4. Re-sign previous releases
git tag -sf v1.0.0 v1.0.0 -m "Re-sign with new key"
git push --force origin v1.0.0

# 5. Notify users
# Create security advisory on GitHub

Vulnerability Discovered

# 1. Assess severity
govulncheck ./...

# 2. Update dependencies
go get -u dependency/path@fixed-version

# 3. Test thoroughly
make test
make testacc

# 4. Create patch release
git tag -s v1.2.1 -m "Security fix: CVE-2026-12345"

# 5. Publish advisory
# GitHub Security → Advisories → New draft

Key Takeaways

  • ✅ Sign all commits and tags with GPG
  • ✅ Use 4096-bit RSA keys with expiration
  • ✅ Store private keys securely in CI/CD
  • ✅ Generate and sign checksums
  • ✅ Scan for vulnerabilities regularly
  • ✅ Keep dependencies updated
  • ✅ Prevent secrets in commits
  • ✅ Generate SBOM for transparency
  • ✅ Follow security best practices
  • ✅ Have incident response plan

What’s Next?

Learn how to publish your provider to the Terraform Registry.

Next: Registry Publication →


Previous: ← Release Automation
Next: Registry Publication →

Last updated: June 30, 2026

Updated: