Signing & Security
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.
Previous: ← Release Automation
Next: Registry Publication →
Last updated: June 30, 2026