-
Notifications
You must be signed in to change notification settings - Fork 137
docs: add a how-to guide for securing a charm #2721
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
tonyandrewmeyer
wants to merge
6
commits into
canonical:main
Choose a base branch
from
tonyandrewmeyer:docs/2656-secure-your-charm
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
6 commits
Select commit
Hold shift + click to select a range
ec5aa64
docs: add a how-to guide for securing a charm
tonyandrewmeyer 55512ed
Apply suggestion from @tonyandrewmeyer
tonyandrewmeyer f744f89
Merge branch 'main' into docs/2656-secure-your-charm
tonyandrewmeyer d461697
Merge branch 'main' into docs/2656-secure-your-charm
tonyandrewmeyer 5450c57
docs: address review feedback on the secure-your-charm how-to
tonyandrewmeyer 515c40d
docs: fix the spelling check and tighten the guide intro
tonyandrewmeyer File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -8,6 +8,7 @@ callables | |
| Canonical's | ||
| Charmlibs | ||
| configurator | ||
| cooldown | ||
| cosl | ||
| databag | ||
| databags | ||
|
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,105 @@ | ||
| --- | ||
| myst: | ||
| html_meta: | ||
| description: Concrete steps for securing a charm that uses Ops -- handling sensitive data, restricting access, keeping dependencies patched, and reporting vulnerabilities. | ||
| --- | ||
|
|
||
| (secure-your-charm)= | ||
| # How to secure your charm | ||
|
|
||
| See first: | ||
|
|
||
| - [](#security) | ||
| - {external+juju:ref}`Juju | Harden your deployment <harden-your-deployment>` | ||
| - {external+pebble:ref}`Pebble | Security <security>` | ||
|
|
||
| This guide covers how to harden a charm that uses Ops. Not every charm needs every step. | ||
|
|
||
| ## Keep sensitive data out of the observable surface | ||
|
|
||
| Ops calls the `juju-log` hook command to send charm logs to Juju, and buffers trace data locally. The charm also surfaces information through hook commands and events -- statuses, relation data, and action results -- including anything it has read from the workload. 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. | ||
|
|
||
| To avoid leaking sensitive data: | ||
|
|
||
| - Do not include secrets, tokens, or other sensitive values in log messages, exception messages, or trace attributes. | ||
| - Do not pass sensitive values on the command line of processes you run from the charm; they typically end up in logs, traces, or exceptions. Pass them through the environment, a file, or standard input instead. | ||
| - Do not put sensitive values into `ops.StoredState`. The state database is not encrypted at rest (see [](#ops-charm-unit-databases)). | ||
|
|
||
| Ops does not mask sensitive values for you. | ||
|
|
||
| ## Store and share sensitive data with Juju secrets | ||
|
|
||
| Use {external+juju:ref}`Juju secrets <secret>` for anything that a charm needs to keep confidential, such as credentials, tokens, or TLS material. Juju stores the value, controls which units can read it, and rotates access when relations change. | ||
|
|
||
| See more: {ref}`manage-secrets` | ||
|
|
||
| If your charm accepts a user-provided secret through configuration, define the config option with `type: secret` in `charmcraft.yaml` rather than a plain string. | ||
|
|
||
| ## Send trace data over HTTPS | ||
|
|
||
| When a charm has the `ops[tracing]` extra installed and is integrated with a trace receiver, Ops sends buffered trace data over the network. This is the only outbound network connection Ops makes on the charm's behalf. | ||
|
|
||
| To avoid traces being intercepted, ensure that Juju users who integrate your charm with a trace receiver also integrate it with a certificate authority provider so that the traffic is TLS-protected. Document this expectation in your charm's own docs. | ||
|
|
||
| See more: [](#ops-cryptographic-technology) | ||
|
|
||
| ## Add static security checks to your project | ||
|
|
||
| Configure the checks that your charm project runs before every merge: | ||
|
|
||
| - **`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/`. | ||
|
|
||
| See more: [](#set-up-ci-integration) | ||
|
|
||
| ## Keep dependencies patched | ||
|
|
||
| Charms pick up security fixes for their dependencies (including Ops itself) at rebuild time, so the release pipeline needs to see new versions promptly. To make that happen: | ||
|
|
||
| 1. Restrict the version of `ops` in `pyproject.toml` in a way that allows compatible releases to be picked up on the next re-lock, for example `ops~=3.0` (or `ops~=2.23` if you support Ubuntu 20.04). See [](#ops-supported-versions) for the current list of supported releases. | ||
| 2. Commit a lock file (`uv.lock`, `poetry.lock`, or equivalent) so every rebuild produces a reproducible dependency set. | ||
| 3. Enable automated dependency updates -- for example, [Dependabot](https://docs.github.com/en/code-security/dependabot/dependabot-security-updates/about-dependabot-security-updates) or [Renovate](https://www.mend.io/renovate/) -- for both Python dependencies and any workflow actions your charm uses. Consider configuring a short cooldown, so that a compromised release has time to be withdrawn before your charm picks it up. Keep the cooldown short enough that security fixes are not held back for long. | ||
| 4. Rebuild and release the charm through your risk channels to `stable` on a regular cadence, so that picked-up fixes actually reach deployed units. | ||
|
|
||
| Keep the list of runtime dependencies small. Every dependency you add is a dependency you take on responsibility for updating. | ||
|
|
||
| ## Restrict what the charm can do on its host | ||
|
|
||
| Machine charms and Kubernetes charms manage permissions in different ways. | ||
|
|
||
| **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. | ||
|
|
||
| **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`. | ||
|
|
||
| ## Harden the workload | ||
|
|
||
| The workload is separate from Ops and typically has its own security hardening story. Follow the guidance for your workload upstream; if there is no upstream hardening guide, produce one and link to it from the charm's documentation. Existing charm-side examples to model on include: | ||
|
|
||
| - [Charmed PostgreSQL on Kubernetes](https://canonical-charmed-postgresql-k8s.readthedocs-hosted.com/14/explanation/security/) | ||
| - [Charmed Kubeflow](https://discourse.charmhub.io/t/security/15935) | ||
| - [Wordpress Hardening](https://developer.wordpress.org/advanced-administration/security/hardening/) (upstream) | ||
|
|
||
| ## Verify the version deployed in a unit | ||
|
|
||
| To confirm that a running unit has picked up the version of Ops you expect (for example, after a security release): | ||
|
|
||
| ```text | ||
| juju exec --unit <unit> -- bash -c '/var/lib/juju/agents/unit-*/charm/venv/bin/python -c "import ops; print(ops.__version__)"' | ||
| ``` | ||
|
|
||
| Compare the result to the [version on PyPI](https://pypi.org/project/ops/). See [](#ops-verifying-update) for background. | ||
|
|
||
| ## Document the security posture | ||
|
|
||
| Include a security section in your charm's own documentation that covers, at a minimum: | ||
|
|
||
| - Which workload the charm manages and where its upstream hardening guide lives. | ||
| - Which relations the charm requires for a secure deployment (for example, a certificate authority provider for TLS). | ||
| - Any configuration options that materially change the security posture (for example, opening extra ports, or relaxing authentication). | ||
| - How to report vulnerabilities to you. If your charm repository has a `SECURITY.md`, link to it. | ||
|
|
||
| The security explanation for Ops itself lives at [](#security). Following a similar structure for your charm's security documentation may help users reason about the deployment. | ||
|
|
||
| ## Report vulnerabilities in Ops | ||
|
|
||
| If you find a vulnerability in Ops, do not open a public issue. Follow the instructions in [SECURITY.md](https://github.com/canonical/operator/blob/main/SECURITY.md) in the `canonical/operator` repository, which routes reports through the [Ubuntu Security disclosure and embargo policy](https://ubuntu.com/security/disclosure-policy). | ||
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we flag this somehow as a linking off point when we add broader docs for rootless charms? Maybe unnecessary.