docs: add a how-to guide for securing a charm - #2721
Conversation
The security explanation ends with a list of good practices, but there was nowhere task-oriented to send someone who has been asked to harden a charm. Turn those practices into a how-to they can work through, and link the two pages to each other.
james-garner-canonical
left a comment
There was a problem hiding this comment.
Looks good. My review is mostly editorial, but two actual comments:
- The point about the workload surfacing information through hook tools and events is confusing to me -- I guess the meaning is that Juju may surface output directly from the workload? (May it? Is it not all charm mediated?)
codespellseems like an odd recommendation for a security guide -- perhaps drop it or expand on the rationale.
| - {external+juju:ref}`Juju | Harden your deployment <harden-your-deployment>` | ||
| - {external+pebble:ref}`Pebble | Security <security>` | ||
|
|
||
| This guide walks through the actions that a charm author takes to harden a charm that uses Ops, and points to the parts of the [security explanation](#security) that describe why each action matters. Work through the steps that apply to your charm; not every workload needs every step. |
There was a problem hiding this comment.
| This guide walks through the actions that a charm author takes to harden a charm that uses Ops, and points to the parts of the [security explanation](#security) that describe why each action matters. Work through the steps that apply to your charm; not every workload needs every step. |
or
| This guide walks through the actions that a charm author takes to harden a charm that uses Ops, and points to the parts of the [security explanation](#security) that describe why each action matters. Work through the steps that apply to your charm; not every workload needs every step. | |
| This guide covers how to to harden a charm that uses Ops. Not every step will apply to every charm. Read the security explanation for Ops itself at [](#security). |
There was a problem hiding this comment.
I've done a mix of this, making it smaller in particular. I don't think we need the link because it's just above this in the see first link.
|
|
||
| ## Keep sensitive data out of the observable surface | ||
|
|
||
| Ops forwards charm logs to Juju through the `juju-log` hook command, buffers trace data locally, and lets the workload surface information back through hook commands and events. Any of these can end up in operator-visible places -- `juju debug-log`, `juju status`, trace receivers, crash reports, or the state database on disk. |
There was a problem hiding this comment.
| Ops forwards charm logs to Juju through the `juju-log` hook command, buffers trace data locally, and lets the workload surface information back through hook commands and events. Any of these can end up in operator-visible places -- `juju debug-log`, `juju status`, trace receivers, crash reports, or the state database on disk. | |
| Ops calls the `juju-log` hook command to send charm logs to Juju. Trace data is buffered locally. The workload surfaces information back through hook commands and events. Any of these can end up in places visible to Juju users, like `juju debug-log`, `juju status`, trace receivers, crash reports, or the state database on disk. |
I'm not actually sure what it means that the workload surfaces information back through hook commands and events though. Workloads don't call hook commands (though I guess a Juju aware workload in a machine charm could, if it got the timing right?), nor do they trigger events directly.
|
|
||
| ## Store and share sensitive data with Juju secrets | ||
|
|
||
| Use {external+juju:ref}`Juju secrets <secret>` for anything that a charm needs to keep confidential -- credentials, tokens, TLS material, and so on. Juju stores the value, controls which units can read it, and rotates access when relations change. |
There was a problem hiding this comment.
| Use {external+juju:ref}`Juju secrets <secret>` for anything that a charm needs to keep confidential -- credentials, tokens, TLS material, and so on. Juju stores the value, controls which units can read it, and rotates access when relations change. | |
| Use {external+juju:ref}`Juju secrets <secret>` for anything that a charm needs to keep confidential, like credentials, tokens, or TLS material. Juju stores the value, controls which units can read it, and rotates access when relations change. |
|
|
||
| - **`ruff`** for Python lint rules, including [`ruff`'s Bandit-derived security rules](https://docs.astral.sh/ruff/rules/#flake8-bandit-s). Enable the `S` rule set in `pyproject.toml`. | ||
| - **`zizmor`** for GitHub Actions workflow audits. Configure it to run on every push against the workflow files in `.github/workflows/`. | ||
| - **`codespell`** or an equivalent to catch typos in log messages and comments that would otherwise reach operators. |
There was a problem hiding this comment.
IMO this doesn't really fit as a security practice.
| - **`codespell`** or an equivalent to catch typos in log messages and comments that would otherwise reach operators. |
|
|
||
| ## Restrict what the charm can do on its host | ||
|
|
||
| Machine charms and Kubernetes charms have different levers here: |
There was a problem hiding this comment.
| Machine charms and Kubernetes charms have different levers here: | |
| Machine charms and Kubernetes charms manage permissions in different ways. |
|
|
||
| Machine charms and Kubernetes charms have different levers here: | ||
|
|
||
| **Machine charms.** Set an explicit `os.umask()` before creating files or directories the workload will use, so that group- and world-permissions are not inherited from whatever the calling context happened to be. Set ownership on files and directories the charm creates for the workload user. |
There was a problem hiding this comment.
| **Machine charms.** Set an explicit `os.umask()` before creating files or directories the workload will use, so that group- and world-permissions are not inherited from whatever the calling context happened to be. Set ownership on files and directories the charm creates for the workload user. | |
| **Machine charms.** Set an explicit `os.umask()` before creating files or directories the workload will use, so that permissions are not inherited from whatever the calling context happened to be. Set ownership on files and directories the charm creates for the workload user. |
|
|
||
| **Machine charms.** Set an explicit `os.umask()` before creating files or directories the workload will use, so that group- and world-permissions are not inherited from whatever the calling context happened to be. Set ownership on files and directories the charm creates for the workload user. | ||
|
|
||
| **Kubernetes charms.** Prefer running the charm and its sidecar containers as a non-root user. Set the {external+charmcraft:ref}`charm-user key in charmcraft.yaml <charmcraft-yaml-key-charm-user>` to `non-root`, and set an explicit `uid` and `gid` on each container in `charmcraft.yaml`. |
There was a problem hiding this comment.
Can we flag this somehow as a linking off point when we add broader docs for rootless charms? Maybe unnecessary.
|
|
||
| The security explanation for Ops itself lives at [](#security); mirror that structure in your charm if it helps operators reason about the deployment. |
There was a problem hiding this comment.
| The security explanation for Ops itself lives at [](#security); mirror that structure in your charm if it helps operators reason about the deployment. |
or
| The security explanation for Ops itself lives at [](#security); mirror that structure in your charm if it helps operators reason about the deployment. | |
| The security explanation for Ops itself lives at [](#security). It may be helpful to follow a similar structure for your charm's security documentation. |
| --- | ||
| myst: | ||
| html_meta: | ||
| description: Concrete steps for securing a charm that uses Ops -- handling sensitive data, restricting access, hardening dependencies, and reporting vulnerabilities. |
There was a problem hiding this comment.
Is "hardening dependencies" accurate?
Correct the claim that the workload surfaces information through hook commands and events: it is the charm that does the surfacing, including anything it has read from the workload. Drop the codespell recommendation, which is a documentation-quality practice rather than a security one, and trim wording that repeated the See first list or the section lead-in. Mention release cooldowns and name stable as the destination for rebuilt charms. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014RHPGNMVAVc1duuTnfJwEJ
Vale's US spellcheck flags "cooldown", which the dependency-patching section uses twice, so add it to the custom wordlist. Also shorten the opening sentence, following review feedback. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01U9L3maZSswQsHoPCZP57ch
The security explanation finishes with a list of good practices, but there's nowhere task-oriented to send someone who has been asked to harden a charm, so this adds a how-to that turns those practices into sections you can work through: keeping sensitive data out of logs and traces, Juju secrets, HTTPS for tracing, static checks, keeping dependencies patched, restricting what the charm can do on its host, hardening the workload, checking the deployed
opsversion, documenting the posture, and reporting vulnerabilities. The explanation index now points at it, so you can go from why to what.Preview.
Fixes #2656