Miloslav Trmač September 2016
By default, the policy is read from $HOME/.config/containers/policy.json,
if it exists, otherwise from /etc/containers/policy.json; applications
performing verification may allow using a different policy instead.
The purpose of the policy file is to define a set of policy requirements
for a container image, usually depending on its location (where it is being
pulled from) or otherwise defined identity.
Policy requirements can be defined for:
Usually, a scope can be defined to match a single image, and various prefixes of
such a most specific scope define namespaces of matching images. - A default policy for a single transport, expressed using an empty string as a scope - A global default policy.
If multiple policy requirements match a given image, only the requirements from
the most specific match apply, the more general policy requirements
definitions are ignored.
This is expressed in JSON using the top-level syntax
The global default set of policy requirements is mandatory; all of the
other fields ( transports itself, any specific transport, the
transport-specific default, etc.) are optional.
Supported scopes use the form
hostname[:port][/namespace[
/imagestream [ :tag]]], i.e. either specifying a
complete name of a tagged image, or prefix denoting a host/namespace/image
stream or a wildcarded expression for matching all subdomains. For wildcarded
subdomain matching, *.example.com is a valid case, but
example*.*.com is not.
Note: The hostname and port refer to the container registry
host and port (the one used e.g. for docker pull), not to the
OpenShift API host and port.
Supported scopes are paths of directories (either containing a single image or
subdirectories possibly containing images).
Note: The paths must be absolute and contain no symlinks. Paths violating
these requirements may be silently ignored.
The top-level scope "/" is forbidden; use the transport default
scope "", for consistency with other transports.
Scopes matching individual images are named Docker references in the fully
expanded form, either using a tag or digest. For example,
docker.io/library/busybox:latest ( not busybox:latest).
More general scopes are prefixes of individual-image scopes, and specify a
repository (by omitting the tag or digest), a repository namespace, or a
registry host (by only specifying the host name) or a wildcarded expression
for matching all subdomains. For wildcarded subdomain matching,
*.example.com is a valid case, but example*.*.com is not.
Supported scopes use the form directory:tag, and
directory referring to a directory containing one or more tags, or any
of the parent directories.
Note: See dir: above for semantics and restrictions on the
directory paths, they apply to oci: equivalently.
Scopes are ignored.
The policy requirements can also be used to decide whether an individual
signature is accepted (= is signed by a recognized key of a known author); in
that case some requirements may apply only to some signatures, but each
signature must be accepted by at least one requirement object.
The following requirement objects are supported:
This requirement accepts any image (but note that other requirements in the
array still apply).
When deciding to accept an individual signature, this requirement does not have
any effect; it does not cause the signature to be accepted, though.
This is useful primarily for policy scopes where no signature verification is
required; because the array of policy requirements must not be empty, this
requirement is used to represent the lack of requirements explicitly.
This requirement rejects every image, and every signature.
Exactly one of keyPath, keyPaths and keyData must be
present, containing a GPG keyring of one or more public keys. Only signatures
made by these keys are accepted.
The signedIdentity field, a JSON object, specifies what image identity
the signature claims about the image. One of the following alternatives are
supported:
(Note that with images identified using digest references, the digest from the
reference is validated even before signature verification starts.)
If the image identity matches the specified prefix, that prefix is replaced by
the specified “signed prefix”
(otherwise it is used as unchanged and no remapping takes place);
matching then follows the matchRepoDigestOrExact semantics documented above
(i.e. if the image identity carries a tag, the identity in the signature must exactly match,
if it uses a digest reference, the repository must match).
The prefix and signedPrefix values can be either host[:port]
values
(matching exactly the same host[:port], string),
repository namespaces, or repositories (i.e. they must not contain tags/digests),
and match as prefixes of the fully expanded form.
For example, docker.io/library/busybox (not busybox) to specify that single repository,
or docker.io/library (not an empty string) to specify the parent namespace of docker.io/library/busybox==busybox).
The prefix value is usually the same as the scope containing the parent
signedBy requirement.
If the signedIdentity field is missing, it is treated as
matchRepoDigestOrExact.
Note: matchExact, matchRepoDigestOrExact and
matchRepository can be only used if a Docker-like image identity is
provided by the transport. In particular, the dir: and oci:
transports can be only used with exactReference or
exactRepository.
Exactly one of keyPath and keyData must be present, containing a
sigstore public key. Only signatures made by this key is accepted.
The signedIdentity field has the same semantics as in the signedBy
requirement described above. Note that cosign-created signatures only
contain a repository, so only matchRepository and
exactRepository can be used to accept them (and that does not protect
against substitution of a signed image with an unexpected tag).
To use this with images hosted on image registries, the relevant registry or
repository must have the use-sigstore-attachments option enabled in
containers-registries.d(5).
September 2016, Originally compiled by Miloslav Trmač [email protected]
⟨mailto:[email protected]⟩
NAME
containers-policy.json - syntax for the signature verification policy fileDESCRIPTION
Signature verification policy files are used to specify policy, e.g. trusted keys, applicable when deciding whether to accept an image, or individual signatures of that image, as valid.FORMAT
The signature verification policy file, usually called policy.json, uses a JSON format. Unlike some other JSON files, its parsing is fairly strict: unrecognized, duplicated or otherwise invalid fields cause the entire file, and usually the entire operation, to be rejected.- •
- An individual scope in a transport. The transport values are the same as the transport prefixes when pushing/pulling images (e.g. docker:, atomic:), and scope values are defined by each transport; see below for more details.
such a most specific scope define namespaces of matching images. - A default policy for a single transport, expressed using an empty string as a scope - A global default policy.
{ "default": [/* policy requirements: global default */] "transports": { transport_name: { "": [/* policy requirements: default for transport $transport_name */], scope_1: [/* policy requirements: default for $scope_1 in $transport_name */], scope_2: [/*…*/] /*…*/ }, transport_name_2: {/*…*/} /*…*/ } }
Supported transports and their scopes
atomic:
The atomic: transport refers to images in an Atomic Registry.dir:
The dir: transport refers to images stored in local directories.docker:
The docker: transport refers to images in a registry implementing the "Docker Registry HTTP API V2".oci:
The oci: transport refers to images in directories compliant with "Open Container Image Layout Specification".tarball:
The tarball: transport refers to tarred up container root filesystems.Policy Requirements
Using the mechanisms above, a set of policy requirements is looked up. The policy requirements are represented as a JSON array of individual requirement objects. For an image to be accepted, all of the requirements must be satisfied simultaneously.insecureAcceptAnything
A simple requirement with the following syntax{"type":"insecureAcceptAnything"}
reject
A simple requirement with the following syntax:{"type":"reject"}
signedBy
This requirement requires an image to be signed using “simple signing” with an expected identity, or accepts a signature if it is using an expected identity and key.{ "type": "signedBy", "keyType": "GPGKeys", /* The only currently supported value */ "keyPath": "/path/to/local/keyring/file", "keyPaths": ["/path/to/local/keyring/file1","/path/to/local/keyring/file2"…], "keyData": "base64-encoded-keyring-data", "signedIdentity": identity_requirement }
- •
- The identity in the signature must exactly match the image identity. Note that with this, referencing an image by digest (with a signature claiming a repository:tag identity) will fail.
{"type":"matchExact"}
- •
- If the image identity carries a tag, the identity in the signature must exactly match; if the image identity uses a digest reference, the identity in the signature must be in the same repository as the image identity (using any tag).
{"type":"matchRepoDigestOrExact"}
- •
- The identity in the signature must be in the same repository as the image identity. This is useful e.g. to pull an image using the :latest tag when the image is signed with a tag specifying an exact image version.
{"type":"matchRepository"}
- •
- The identity in the signature must exactly match a specified identity. This is useful e.g. when locally mirroring images signed using their public identity.
{ "type": "exactReference", "dockerReference": docker_reference_value }
- •
- The identity in the signature must be in the same repository as a specified identity. This combines the properties of matchRepository and exactReference.
{ "type": "exactRepository", "dockerRepository": docker_repository_value }
- •
- Prefix remapping:
(otherwise it is used as unchanged and no remapping takes place);
matching then follows the matchRepoDigestOrExact semantics documented above
(i.e. if the image identity carries a tag, the identity in the signature must exactly match,
if it uses a digest reference, the repository must match).
(matching exactly the same host[:port], string),
repository namespaces, or repositories (i.e. they must not contain tags/digests),
and match as prefixes of the fully expanded form.
For example, docker.io/library/busybox (not busybox) to specify that single repository,
or docker.io/library (not an empty string) to specify the parent namespace of docker.io/library/busybox==busybox).
{ "type": "remapIdentity", "prefix": prefix, "signedPrefix": prefix, }
sigstoreSigned
This requirement requires an image to be signed using a sigstore signature with an expected identity and key.{ "type": "sigstoreSigned", "keyPath": "/path/to/local/keyring/file", "keyData": "base64-encoded-keyring-data", "signedIdentity": identity_requirement }
Examples
It is strongly recommended to set the default policy to reject, and then selectively allow individual transports and scopes as desired.A reasonably locked-down system
(Note that the /*…*/ comments are not valid in JSON, and must not be used in real policies.){ "default": [{"type": "reject"}], /* Reject anything not explicitly allowed */ "transports": { "docker": { /* Allow installing images from a specific repository namespace, without cryptographic verification. This namespace includes images like openshift/hello-openshift and openshift/origin. */ "docker.io/openshift": [{"type": "insecureAcceptAnything"}], /* Similarly, allow installing the “official” busybox images. Note how the fully expanded form, with the explicit /library/, must be used. */ "docker.io/library/busybox": [{"type": "insecureAcceptAnything"}], /* Allow installing images from all subdomains */ "*.temporary-project.example.com": [{"type": "insecureAcceptAnything"}], /* A sigstore-signed repository */ "hostname:5000/myns/sigstore-signed-with-full-references": [ { "type": "sigstoreSigned", "keyPath": "/path/to/sigstore-pubkey.pub" } ], /* A sigstore-signed repository, accepts signatures by /usr/bin/cosign */ "hostname:5000/myns/sigstore-signed-allows-malicious-tag-substitution": [ { "type": "sigstoreSigned", "keyPath": "/path/to/sigstore-pubkey.pub", "signedIdentity": {"type": "matchRepository"} } ] /* Other docker: images use the global default policy and are rejected */ }, "dir": { "": [{"type": "insecureAcceptAnything"}] /* Allow any images originating in local directories */ }, "atomic": { /* The common case: using a known key for a repository or set of repositories */ "hostname:5000/myns/official": [ { "type": "signedBy", "keyType": "GPGKeys", "keyPath": "/path/to/official-pubkey.gpg" } ], /* A more complex example, for a repository which contains a mirror of a third-party product, which must be signed-off by local IT */ "hostname:5000/vendor/product": [ { /* Require the image to be signed by the original vendor, using the vendor's repository location. */ "type": "signedBy", "keyType": "GPGKeys", "keyPath": "/path/to/vendor-pubkey.gpg", "signedIdentity": { "type": "exactRepository", "dockerRepository": "vendor-hostname/product/repository" } }, { /* Require the image to _also_ be signed by a local reviewer. */ "type": "signedBy", "keyType": "GPGKeys", "keyPath": "/path/to/reviewer-pubkey.gpg" } ], /* A way to mirror many repositories from a single vendor */ "private-mirror:5000/vendor-mirror": [ { /* Require the image to be signed by the original vendor, using the vendor's repository location. For example, private-mirror:5000/vendor-mirror/productA/image1:latest needs to be signed as vendor.example/productA/image1:latest . */ "type": "signedBy", "keyType": "GPGKeys", "keyPath": "/path/to/vendor-pubkey.gpg", "signedIdentity": { "type": "remapIdentity", "prefix": "private-mirror:5000/vendor-mirror", "signedPrefix": "vendor.example.com" } } ] } } }
Completely disable security, allow all images, do not trust any signatures
{ "default": [{"type": "insecureAcceptAnything"}] }
SEE ALSO
atomic(1)HISTORY
August 2018, Rename to by Valentin Rothberg [email protected] ⟨mailto:[email protected]⟩policy.json | Man |