How to secure your charm¶
See first:
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 that describe why each action matters. Work through the steps that apply to your charm; not every workload needs every step.
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.
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 Charm unit databases).
Ops does not mask sensitive values for you.
Send trace data over HTTPS¶
When a charm has the 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: Cryptographic technology
Add static security checks to your project¶
Configure the checks that your charm project runs before every merge:
rufffor Python lint rules, includingruff’s Bandit-derived security rules. Enable theSrule set inpyproject.toml.zizmorfor GitHub Actions workflow audits. Configure it to run on every push against the workflow files in.github/workflows/.codespellor an equivalent to catch typos in log messages and comments that would otherwise reach operators.
Run these checks in CI so they block merges rather than only running locally.
See more: Run integration tests in CI
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:
Restrict the version of
opsinpyproject.tomlin a way that allows compatible releases to be picked up on the next re-lock, for exampleops~=3.0(orops~=2.23if you support Ubuntu 20.04). See Supported versions for the current list of supported releases.Commit a lock file (
uv.lock,poetry.lock, or equivalent) so every rebuild produces a reproducible dependency set.Enable automated dependency updates – for example, Dependabot or Renovate – for both Python dependencies and any workflow actions your charm uses.
Rebuild and re-release the charm 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 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.
Kubernetes charms. Prefer running the charm and its sidecar containers as a non-root user. Set the charm-user key in charmcraft.yaml 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:
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):
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. See Verifying an update was applied 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; mirror that structure in your charm if it helps operators 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 in the canonical/operator repository, which routes reports through the Ubuntu Security disclosure and embargo policy.