Files
sencho/docs/features/git-secrets.mdx
T
Anso 26f77cac96 docs: v1 docs refresh (batch 9) (#2181)
* docs(gitops): complete the overview's source states, placement limits, and control locations

List every source state the app can show, with the order in which the most
serious one wins. Add the pin-driven and unreadable-evidence cases to what
the placement policy hands back to an operator, and name the controls where
each policy is set.

* docs(git-sources): document the polling controls, Manual behavior, and pending-update indicators

State the per-node polling interval and its range, that a Manual source is
never polled or retried but still stages a plan when a webhook delivers a
commit, and where the pending-update icon and button lead. Add common setups
and what Git sources do not do, and correct the create form's alt text.

* docs(git-authentication): cover the Git error cases and current Bitbucket credentials

Add the failures a Git source can report that were missing from
troubleshooting (embedded credentials, SSH without a deploy key, blocked
hosts, rate limits, certificate expiry and hostname mismatch, network
errors), state that the token is only given to the repository's host, and
replace the deprecated Bitbucket app password guidance.

* docs(git-webhooks): state the GitHub content type, remote-node failures, and limits

A form-encoded GitHub delivery fails verification and answers 404, so say to
set the content type to JSON. Add the 500 a delivery gets when the owning
node is unreachable, correct how the hub forwards to a remote node, and list
what provider hooks do not cover.

* docs(git-secrets): document linking an encrypted repository and where encrypted values work

A repository that is already encrypted cannot be linked, because the identity
belongs to a linked source. Encrypted values belong in env files and
file-backed secrets, not in image references or published ports, which
Sencho resolves before decrypting.

* docs(blueprints): correct permissions, remote deploys, and roadmap wording

Describe the permissions Blueprint actions need instead of an admin-only
rule, say that a remote node runs the deploy itself with its own policy and
scanner state, and list all three drift notifications. Remove the repeated
marker, policy gate, and snapshot explanations, the raw apply request
format, and statements about future features.

* docs(git-managed-blueprints): add who can take each step and the limits

List the permission each authority step and rollout control needs, say that
webhooks do not fetch a Blueprint's source so Pull source and polling bring in
new commits, and gather the constraints that were scattered through the page.

* docs(gitops-portfolio): name the row menu actions and state what the portfolio does not do

List the exact row menu items for Direct and Blueprint applications, and
add the limits: hub only, navigation from row menus, and how unreachable
nodes appear.

* docs(gitops-drift): describe the drift row as it appears and add a screenshot

The row shows the class, the deriving component, evidence age, reason, and
expected against observed; the resolving action lives on the application's
status. Name the retry-interval setting, drop rollout states Sencho never
reports, and replace a hedge about which classes are reported.

* docs(git-transport): refresh the live attestations against the 0.98.0 release

Re-run all 21 live attestations (hosted public repositories, the direct
proxy and Pilot node paths, and disposable Gitea and Forgejo instances over
HTTPS with a private CA and over SSH with a deploy key) against the 0.98.0
image, record the new date, commit, and image digest, and regenerate the
matrix page. Describe unlisted hosts without claiming more than was tested,
and add the procedure the attestation record refers to.
2026-10-23 12:51:36 -04:00

96 lines
8.2 KiB
Plaintext

---
title: Repository secrets with SOPS and age
sidebarTitle: Repository secrets
description: Keep sensitive env files in Git as SOPS-encrypted documents. Sencho decrypts them only for the moment a validation, apply, deploy, or rollback needs them.
---
Some teams commit sensitive env files to the repository as [SOPS](https://github.com/getsops/sops)-encrypted documents. That covers a sibling `.env` synced with the source and any env files or file-backed secrets the compose file names.
Sencho keeps ciphertext in the repository and on disk. Decryption happens only in a short-lived overlay while it validates a candidate, applies, deploys, or rolls back.
## Set it up
<Steps>
<Step title="Create an identity on the owning node">
Open **Git Source** on the linked stack and select the **Secrets** tab. Click **Generate identity**, or **Import identity** to use an age key you already hold. The key pair is created on the node that owns this Git source. Private key material never leaves the node and is never returned by the API.
An identity belongs to a linked source, and linking needs to open every encrypted file to validate it. A repository whose current commit is already encrypted therefore cannot be linked: Sencho stops with "Required age identity is not available on this node". Link the repository while the env file is still plaintext, generate the identity, then commit the encrypted file and pull.
</Step>
<Step title="Add the recipient to your SOPS files">
Sencho shows the public recipient (`age1…`). Add it to the SOPS configuration in your repository and encrypt the env files.
</Step>
<Step title="Choose the encrypted source policy">
**Encrypted source policy** defaults to **Allow plaintext secrets**. Switch to **Require SOPS encryption** when every managed secret-bearing input in the repository must be SOPS-encrypted with age.
</Step>
<Step title="Check readiness">
The readiness chip reports whether this node holds the recipients for the generation applied on it. Before the first apply, it reports the staged candidate.
</Step>
</Steps>
<Frame caption="The Secrets tab with a generated identity. The recipient is listed with copy, rotate, and delete controls, and Import identity takes an existing age key.">
<img src="/images/git-secrets/secrets-tab.png" alt="Secrets tab of the Git Source sheet showing a Ready readiness chip, the Allow plaintext secrets and Require SOPS encryption buttons, a recipient starting age1 with copy, rotate, and delete buttons, a Generate identity button, and a field with an Import identity button" />
</Frame>
## What is supported
| Format | Support |
|---|---|
| age (X25519) recipients in YAML SOPS documents (`sops.age`) | Supported |
| age recipients in dotenv or INI stores (`sops_age__list_*` keys) | Supported |
| PGP, cloud KMS, and other SOPS backends | Refused with an unsupported-backend message |
| Encrypted compose files | Not supported |
## Where encrypted values can be used
Encrypted values belong in inputs that a container reads at run time: a sibling `.env` used by `env_file:`, other env files, and file-backed secrets or configs.
Do not use an encrypted variable in an image reference or a published port, for example `image: nginx:${TAG}` or `ports: ["${PORT}:80"]`. Sencho resolves images and ports before it decrypts anything, so it sees ciphertext there. Validation and apply still succeed, but the runtime state stays at applied not deployed with "Private registry readiness could not be proven", and the next Git apply refuses with "Rollback capture failed before apply" because the files on disk no longer render. Keep image tags and ports as plain values in the compose file.
## How decrypted files reach the stack
While a candidate is validated or a deploy runs, Compose reads decrypted values from the overlay.
- Relative mounts keep the host path they would have if Compose ran in the stack directory, at any parent depth. The same holds for the host directory a named volume's local driver binds.
- A decrypted secret file that the compose file mounts directly is read from the overlay for that run, including a file whose name contains a literal `$`.
- A rollback checks the restored files the same way before it replaces any container.
Sencho refuses, before the stack starts, a mount that covers both a decrypted secret and persistent data, a named volume backed by the decrypt area, and a relative path it cannot place on the stack with confidence. The fix for the first is to move the secret file out of that mount. On rollback it also refuses a mount outside the compose path that the host maps 1:1, and an encrypted env file that no identity on this node can open. The name `.sencho-bind-remap.yml` is reserved for the storage layer Sencho writes into the overlay, so a compose file that already holds that name is refused rather than replaced.
A manual **Deploy** or image **Update** follows the files applied on this node, so those actions stay refused while the applied generation is encrypted, even when the last successful deploy recorded no age recipients. Apply also refuses a staged candidate that does not record the recipients for its encrypted files; pull the source again so the candidate records them, then apply.
## Compose output is confidential
Compose quotes the values it interpolated back in its own errors. Whenever Compose runs over decrypted files, Sencho therefore reports only that the command failed and withholds the diagnostic. Those values cannot reach the result, the activity log, or a stored outcome. To find the cause, read the running stack's container logs.
The missing-variable guard follows the same rule. It still refuses the deploy, but on a source with encrypted inputs it cannot name the variables, because naming them would report a value. Give every `${VAR:?message}` reference a value in the project's `.env` or `env_file` files, then deploy again. When a project env file is part of the candidate, or a context directory is set and the stack root `.env` is present, validation and a Git deploy read that copy.
## Back up your keys
Back up both the Sencho data directory's encryption key (`encryption.key`) and every age identity you generate here. Losing either makes the ciphertext in Git unrecoverable on that node.
## Troubleshooting
<AccordionGroup>
<Accordion title="Missing or rotated SOPS age key">
A Git apply, Git deploy, or rollback that needs decryption fails before Compose runs when this node holds no matching age identity. Open the **Secrets** tab, check the readiness chip, and generate or import the key whose recipient appears in your SOPS files.
After rotating a key, re-encrypt the repository files with the new recipient before deleting the old identity. Rollback stays blocked for a generation whose encrypted files name only recipients this node lacks, until you restore one of those identities or advance to a generation encrypted for a key you still hold. Manual Deploy and image Update stay blocked on SOPS-managed stacks; use Git apply instead.
</Accordion>
<Accordion title="A deploy fails and Sencho will not say why">
That is deliberate for sources with encrypted inputs. The Compose diagnostic can contain decrypted values, so Sencho withholds it. Check the container logs, and confirm every `${VAR:?message}` reference has a value in the project's `.env` or `env_file` files.
</Accordion>
<Accordion title="Apply refuses a staged candidate">
The candidate does not record the age recipients for its encrypted files. Pull the source again so the candidate records them, then apply.
</Accordion>
<Accordion title="Required age identity is not available on this node">
A file in the commit names recipients that no identity on this node opens. When you are linking the repository for the first time, link it from a commit whose env files are still plaintext, generate the identity, then commit the encrypted files. On a linked source, import or generate the identity whose recipient appears in the file.
</Accordion>
<Accordion title="The deploy stops at registry readiness after an apply">
Check whether an image reference or a published port interpolates an encrypted variable. See [Where encrypted values can be used](#where-encrypted-values-can-be-used).
</Accordion>
</AccordionGroup>