# Mozilla SOPS: age, KMS, and GitOps-Friendly Secret Workflows

> **Intro:** SOPS is one of the cleanest ways to keep encrypted configuration in Git without normalizing long-lived plaintext secrets in the repository. The important design question is not “can we encrypt a YAML file?” but **who decrypts, where, with what root of trust, and for how long**.
>
> **What this page includes**
>
> * where SOPS fits relative to Vault, CI variables, and Kubernetes-native secret patterns;
> * installation, bootstrap, and day-one usage with `age`;
> * examples for `.sops.yaml`, file encryption, key rotation, and CI/GitOps usage;
> * sample outputs and review notes for teams adopting SOPS.

## What SOPS is good at

SOPS is strongest when a team wants to:

* keep encrypted YAML/JSON/ENV/INI files in Git;
* let operators edit secrets without inventing a custom crypto workflow;
* use **age** or cloud KMS as the root of trust;
* preserve structure while encrypting leaf values rather than storing one giant opaque blob;
* support GitOps or config-in-repo flows where plaintext should never be committed.

## What SOPS is not

SOPS is not a full secret lifecycle platform. It does not replace:

* dynamic short-lived secrets from Vault or cloud secret managers;
* rotation orchestration for database credentials or cloud identities;
* access governance, approvals, or audit flows by itself;
* workload-identity design.

A practical mental model is:

* use **SOPS** when encrypted config must live in Git;
* use **Vault / cloud secret managers** when a runtime needs dynamic secret issuance, rotation, revocation, or brokered access.

## Preferred keying choice

When possible, prefer **age** for simple modern file encryption. Use cloud KMS or Vault transit where the organization already has strong control ownership and audit around those services.

## Installation patterns

### Linux amd64 example

```bash
export SOPS_VERSION=v3.11.0
curl -LO "https://github.com/getsops/sops/releases/download/${SOPS_VERSION}/sops-${SOPS_VERSION}.linux.amd64"
sudo mv "sops-${SOPS_VERSION}.linux.amd64" /usr/local/bin/sops
sudo chmod +x /usr/local/bin/sops
sops --version
```

### macOS with Homebrew

```bash
brew install sops age
sops --version
age --version
```

### Windows notes

Use the official release artifact or package manager the team standardizes on, then verify `sops --version` from PowerShell or a terminal. Keep the private age key in the standard user config path or a specifically controlled override path.

## Day-one bootstrap with age

### 1. Generate an age key pair

```bash
mkdir -p ~/.config/sops/age
age-keygen -o ~/.config/sops/age/keys.txt
chmod 600 ~/.config/sops/age/keys.txt
```

Print the recipient (public key):

```bash
grep '^# public key:' ~/.config/sops/age/keys.txt
```

### 2. Create a `.sops.yaml` policy

```yaml
creation_rules:
  - path_regex: secrets/.*\.ya?ml$
    age:
      - age1examplepublickeyreplacewithrealrecipient
```

### 3. Encrypt a file in place

```bash
sops encrypt -i secrets/app.enc.yaml
```

### 4. View or edit it safely

```bash
sops decrypt secrets/app.enc.yaml
sops edit secrets/app.enc.yaml
```

## Example encrypted file shape

SOPS preserves document structure and adds metadata. A typical encrypted YAML looks like this:

```yaml
apiVersion: v1
kind: Secret
metadata:
  name: app-config
stringData:
  DB_PASSWORD: ENC[AES256_GCM,data:...,iv:...,tag:...,type:str]
  API_TOKEN: ENC[AES256_GCM,data:...,iv:...,tag:...,type:str]
sops:
  age:
    - recipient: age1examplepublickeyreplacewithrealrecipient
      enc: |
        -----BEGIN AGE ENCRYPTED FILE-----
        ...
        -----END AGE ENCRYPTED FILE-----
  lastmodified: "2026-03-29T12:00:00Z"
  mac: ENC[AES256_GCM,data:...,iv:...,tag:...,type:str]
  version: 3.11.0
```

## Useful commands

### Encrypt to stdout

```bash
sops encrypt --age age1examplepublickeyreplacewithrealrecipient secrets/app.yaml > secrets/app.enc.yaml
```

### Decrypt to stdout

```bash
sops decrypt secrets/app.enc.yaml
```

### Decrypt from stdin with explicit type

```bash
cat secrets/app.enc.yaml | sops decrypt --input-type yaml --output-type yaml
```

### Extract one value

```bash
sops decrypt --extract '["stringData"]["DB_PASSWORD"]' secrets/app.enc.yaml
```

### Rotate data key metadata after changing recipients or policy

```bash
sops updatekeys -y secrets/app.enc.yaml
```

### In-place edit using the configured editor

```bash
EDITOR=vim sops edit secrets/app.enc.yaml
```

## Example `.sops.yaml` patterns

### Simple age-based repo policy

```yaml
creation_rules:
  - path_regex: secrets/.*\.ya?ml$
    age:
      - age1teamrecipientaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
```

### Different rules by path

```yaml
creation_rules:
  - path_regex: env/dev/.*\.ya?ml$
    age:
      - age1devrecipientaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa

  - path_regex: env/prod/.*\.ya?ml$
    age:
      - age1prodrecipientbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
```

### KMS-backed example

```yaml
creation_rules:
  - path_regex: prod/.*\.ya?ml$
    kms:
      - arn:aws:kms:us-east-1:111122223333:key/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee
```

## Where the age private key lives

By default, SOPS looks for `keys.txt` in the SOPS config directory for the user profile. Keep that file **out of Git**, restrict file permissions, and prefer workstation storage or secret injection patterns that match the team’s trust model.

Useful overrides:

```bash
export SOPS_AGE_KEY_FILE=/secure/location/keys.txt
export SOPS_AGE_RECIPIENTS=age1examplepublickeyreplacewithrealrecipient
```

## CI and GitOps usage notes

### Safer CI pattern

1. CI receives decryption authority from a **workload identity** or tightly-scoped cloud principal.
2. The pipeline decrypts only in the stage that truly needs plaintext.
3. Plaintext is written to ephemeral workspace only.
4. Logs, artifacts, and debug output never include decrypted secret values.
5. The job destroys workspace and short-lived credentials after use.

### What not to do

* do not store the private age key as a long-lived plaintext blob in the repository or base image;
* do not decrypt in a wide early stage if only one later deploy step needs plaintext;
* do not assume “encrypted in Git” means runtime exposure is solved;
* do not give every developer production recipients if the team has real environment separation.

## Sample output you should expect

### Version check

```
$ sops --version
sops 3.11.0
```

### Extracting a field

```
$ sops decrypt --extract '["stringData"]["DB_PASSWORD"]' secrets/app.enc.yaml
super-secret-password-value
```

### Missing key example

```
$ sops decrypt secrets/app.enc.yaml
Failed to get the data key required to decrypt the SOPS file.
```

## Review checklist

* [ ] `.sops.yaml` exists and matches repo layout intentionally
* [ ] recipients differ by environment where that distinction matters
* [ ] private keys or KMS auth are not baked into the repo or base image
* [ ] CI decrypts only at the stage that needs plaintext
* [ ] decrypted values do not land in artifacts, logs, shell history, or cache layers
* [ ] operational ownership exists for recipient changes and `updatekeys`

## Use this page with

* [Secret Management on HashiCorp Vault](/cloud-kubernetes-and-infrastructure-security/index/vault-secret-management-on-hashicorp-vault.md)
* [Workload Federation and Non-Human Identities](/architecture-api-crypto-and-identity/index-2/workload-federation-and-non-human-identities.md)
* [GitHub, GitLab, and Cloud Trust Patterns](/architecture-api-crypto-and-identity/index-2/github-gitlab-oidc-and-cloud-trust-patterns.md)
* [snippets/secrets/.sops.yaml.example](https://github.com/D3One/Product-Security-Gitbook/blob/main/snippets/secrets/.sops.yaml.example)
* [snippets/secrets/sops-age-bootstrap.sh](https://github.com/D3One/Product-Security-Gitbook/blob/main/snippets/secrets/sops-age-bootstrap.sh)
* [snippets/secrets/sops-output-sample.txt](https://github.com/D3One/Product-Security-Gitbook/blob/main/snippets/secrets/sops-output-sample.txt)

***

*Author attribution: Ivan Piskunov, 2026 - Educational and defensive-engineering use.*


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.product-security.expert/cloud-kubernetes-and-infrastructure-security/index/mozilla-sops-age-kms-and-gitops-secrets.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
