Skip to main content

secrets-management

Secrets management patterns for infra-cd — 1Password Operator (OnePasswordItem/ExternalSecret), SOPS age encryption for FluxCD variable substitution and bootstrap secrets, and Terraform 1Password provider.

インストールへ移動

ソース情報

リポジトリ
dark-vex/infra-cd
ソースの最終更新活動
2026年7月26日 12:40
検出された SKILL.md の言語
英語
スター
8
フォーク
0

インストール方法

デフォルトでは、最初にソースを確認する Prompt が選択されています。直接コマンドに切り替えるか、ローカルコピーをダウンロードすることもできます。

ソースファイルを確認

インストールを決める前に、SKILL.md と SkillsMP に表示されている付属ファイルをお読みください。

SKILL.md を表示中

SKILL.md
ソースの指示 · 読み取り専用プレビュー
name
secrets-management
description
Secrets management patterns for infra-cd — 1Password Operator (OnePasswordItem/ExternalSecret), SOPS age encryption for FluxCD variable substitution and bootstrap secrets, and Terraform 1Password provider.
paths
clusters/**, terraform/**
# Secrets Management Skill Use this skill when adding, rotating, or troubleshooting secrets in Kubernetes, Terraform, or FluxCD variable substitution. ## When to use - Adding a new application that needs Kubernetes secrets - Setting up SOPS-encrypted cluster variables for FluxCD substitution - Adding SOPS-encrypted bootstrap secrets for apps that must exist before the 1Password Operator runs - Configuring the 1Password Terraform provider for a new environment - Troubleshooting missing or stale secrets in a cluster ## Pattern 1: 1Password Operator (Kubernetes secrets) Use `OnePasswordItem` for injecting 1Password items directly as Kubernetes `Secret` resources. ### OnePasswordItem CRD ```yaml # clusters/{cluster}/apps/{app}/secrets/my-secret.yml apiVersion: onepassword.com/v1 kind: OnePasswordItem metadata: name: my-secret namespace: {app-namespace} spec: itemPath: "vaults/{vault-name}/items/{item-title}" ``` The operator creates a matching `Secret` with the same name. Fields in the 1Password item become keys in the Secret. **Convention:** Store all K8s secrets under vault `k8s_secrets`. Item title matches the Kubernetes secret name. ### ExternalSecret (alternative) Used when you need to transform or select specific fields: ```yaml apiVersion: external-secrets.io/v1beta1 kind: ExternalSecret metadata: name: my-secret namespace: {app-namespace} spec: secretStoreRef: name: onepassword-connect kind: ClusterSecretStore target: name: my-secret data: - secretKey: username remoteRef: key: vaults/{vault}/items/{item} property: username - secretKey: password remoteRef: key: vaults/{vault}/items/{item} property: password ``` ## Pattern 2: SOPS-encrypted cluster variables FluxCD uses `cluster-vars` Secret for `${VAR}` substitution in manifests. The Secret is created from a SOPS-encrypted file. ### File location ``` clusters/{cluster}/vars/cluster-vars.sops.yaml ``` ### Adding a new variable 1. Decrypt the file: `sops clusters/{cluster}/vars/cluster-vars.sops.yaml` 2. Add the new key under `data:` (all values must be base64 or plain string) 3. Save (SOPS re-encrypts on save) 4. Reference in manifests as `${MY_VAR_NAME}` ### Age key management Each cluster has its own age key in `.sops.yaml`: ```yaml # .sops.yaml creation_rules: - path_regex: clusters/{cluster}/vars/.*\.sops\.yaml age: age1...{cluster-public-key}... ``` **Never commit age private keys (`.agekey` files) — they are gitignored.** The age private key must be available as a Kubernetes Secret named `sops-age` in `flux-system` namespace for FluxCD to decrypt during reconciliation. ## Pattern 3: SOPS-encrypted bootstrap secrets (K8s Secret) Use this pattern for apps that must be provisioned **before** the 1Password Operator is running — specifically, any app whose credentials are required to start the 1Password Connect operator itself (e.g. `1password-connect-credentials`). ### Why SOPS instead of OnePasswordItem here `OnePasswordItem` requires the 1Password Operator to already be running. For the operator's own credentials you need a chicken-and-egg workaround: encrypt the Secret with SOPS age and let a dedicated child Flux Kustomization (with `spec.decryption.provider: sops`) create it before the operator starts. ### Required file layout — TWO kustomization.yaml files are mandatory ``` clusters/{cluster}/apps/{app-name}/ ├── kustomization.yaml # app-level — lists ONLY deploy.yaml ├── deploy.yaml # defines {app}-secrets child Kustomization with decryption block └── secrets/ ├── kustomization.yaml # secrets-level — lists the .sops.yaml file └── my-credentials.sops.yaml ``` **Why both files are required:** The top-level `apps` Flux Kustomization auto-scans `clusters/{cluster}/apps/` recursively. Its behaviour: - Directory **with** `kustomization.yaml` → treated as a kustomize component (recursion stops here) - Directory **without** `kustomization.yaml` → recurse and include every YAML file directly Without `apps/{app}/kustomization.yaml`, Flux recurses into `{app}/`, then into `{app}/secrets/`, finds the SOPS-encrypted file, and fails immediately with `Object 'Kind' is missing` because `apiVersion` and `kind` are both encrypted fields. The `apps` Kustomization has no SOPS decryption and cannot recover. ### app-level kustomization.yaml ```yaml # clusters/{cluster}/apps/{app-name}/kustomization.yaml apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization resources: - deploy.yaml ``` This is intentionally minimal — `deploy.yaml` defines the child Kustomizations, which own `manifests/` and `secrets/` separately. ### secrets-level kustomization.yaml ```yaml # clusters/{cluster}/apps/{app-name}/secrets/kustomization.yaml apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization resources: - my-credentials.sops.yaml ``` This is used by the `{app}-secrets` child Kustomization (which has `spec.decryption.provider: sops`). Without it, Flux auto-generates the resource list and still finds the SOPS file — having the explicit file is better practice and matches the `falco/secrets/kustomization.yaml` pattern. ### deploy.yaml child Kustomization with SOPS decryption ```yaml # clusters/{cluster}/apps/{app-name}/deploy.yaml apiVersion: kustomize.toolkit.fluxcd.io/v1 kind: Kustomization metadata: name: {app-name}-secrets namespace: flux-system spec: targetNamespace: {app-namespace} interval: 15m sourceRef: kind: GitRepository name: flux-system path: ./clusters/{cluster}/apps/{app-name}/secrets prune: true decryption: provider: sops secretRef: name: sops-age # age private key pre-loaded in flux-system during bootstrap ``` ### SOPS configuration in .sops.yaml Add a creation rule for the new path so `sops` uses the correct age key: ```yaml # .sops.yaml creation_rules: # existing cluster-vars rule ... - path_regex: clusters/{cluster}/apps/{app-name}/secrets/.*\.sops\.yaml$ age: {cluster-age-public-key} ``` ### Encrypting the bootstrap secret ```bash # Create the plain Secret manifest first (NEVER commit this file) cat > /tmp/my-credentials.yaml <<EOF apiVersion: v1 kind: Secret metadata: name: my-credentials namespace: {app-namespace} stringData: token: "..." credentials: "..." EOF # Encrypt in-place using .sops.yaml path rules sops --encrypt /tmp/my-credentials.yaml > \ clusters/{cluster}/apps/{app-name}/secrets/my-credentials.sops.yaml rm /tmp/my-credentials.yaml ``` ### Verification ```bash # Confirm decryption works (requires SOPS_AGE_KEY env var or ~/.config/sops/age/keys.txt) sops -d clusters/{cluster}/apps/{app-name}/secrets/my-credentials.sops.yaml # Dry-run the kustomize build for the secrets path kustomize build clusters/{cluster}/apps/{app-name}/secrets # should fail (can't decrypt locally without key) flux build kustomization {app-name}-secrets --path clusters/{cluster}/apps/{app-name}/secrets # cluster-side ``` ## Pattern 4: Terraform 1Password provider Always source Terraform provider credentials from 1Password, never hardcode: ```hcl # variables.tf variable "onepassword_token" { type = string sensitive = true } variable "onepassword_endpoint" { type = string sensitive = true } # provider.tf provider "onepassword" { connect_url = var.onepassword_endpoint connect_token = var.onepassword_token } # data.tf data "onepassword_item" "my_credentials" { vault = "infra" title = "My Service" } # main.tf (usage) resource "some_resource" "example" { api_key = data.onepassword_item.my_credentials.credential } ``` In CI, `OP_TOKEN` and `OP_ENDPOINT` are passed as environment variables from GitHub Actions secrets. ## Never commit - Raw Kubernetes `Secret` manifests with `data:` base64 values - `.env` files - TLS certificates or SSH private keys - API tokens or passwords in plaintext - Age private keys (`.agekey`) ## Verification checklist - [ ] 1Password item created in correct vault (`k8s_secrets` for K8s, `infra` for Terraform) - [ ] `OnePasswordItem` itemPath matches the exact vault + item title - [ ] SOPS-encrypted file re-encrypted after adding new variables - [ ] FluxCD `postBuild.substituteFrom` references `cluster-vars` Secret - [ ] `sops -d clusters/{cluster}/vars/cluster-vars.sops.yaml` decrypts without error - [ ] No plaintext secrets in git history (`git log -p` check) - [ ] **SOPS bootstrap secrets only:** `apps/{app}/kustomization.yaml` exists and lists only `deploy.yaml` - [ ] **SOPS bootstrap secrets only:** `apps/{app}/secrets/kustomization.yaml` exists and lists the `.sops.yaml` file - [ ] **SOPS bootstrap secrets only:** child Kustomization in `deploy.yaml` has `spec.decryption.provider: sops` + `secretRef: sops-age` - [ ] **SOPS bootstrap secrets only:** `.sops.yaml` has a `path_regex` rule covering the new secrets path
GitHubで見る