summaryrefslogtreecommitdiff
path: root/containers-policy.json.5.md
blob: 909d04afd0db25dc3598667fc4f3fc53d7aec944 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
% CONTAINERS-POLICY.JSON 5 policy.json Man Page
% Miloslav Trmač
% September 2016

# NAME
containers-policy.json - syntax for the signature verification policy file

## DESCRIPTION

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.

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.

## 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.

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:

- 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.

  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
```js
{
    "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: {/*…*/}
        /*…*/
    }
}
```

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.

<!-- NOTE: Keep this in sync with transports/transports.go! -->
## Supported transports and their scopes

See containers-transports(5) for general documentation about the transports and their reference syntax.

### `atomic:`

The deprecated `atomic:` transport refers to images in an Atomic Registry.

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 starting with `*.` 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.

### `containers-storage:`

Supported scopes have the form `[`_storage-specifier_`]`_image-scope_.

`[`_storage-specifier_`]` is usually `[`_graph-driver-name_`@`_graph-root_`]`, e.g. `[overlay@/var/lib/containers/storage]`.

_image-scope_ matching the individual image is
- a named Docker reference *in the fully expanded form*, either using a tag or digest. For example, `docker.io/library/busybox:latest` (*not* `busybox:latest`)
- and/or (depending on which one the user’s input provides) `@`_image-id_

More general scopes are prefixes of individual-image scopes, and specify a less-precisely-specified image, or a repository
(by omitting first the image ID, if any; then the digest, if any; and finally a tag, if any),
a repository namespace, or a registry host (by only specifying the host name and possibly a port number).

Finally, two full-store specifiers matching all images in the store are valid scopes:
- `[`_graph-driver-name_`@`_graph-root_`]` and
- `[`_graph-root_`]`

Note that some tools like Podman and Buildah hard-code overrides of the signature verification policy for “push” operations,
allowing these operations regardless of configuration in `policy.json`.

### `dir:`

The `dir:` transport refers to images stored in local directories.

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.

### `docker:`

The `docker:` transport refers to images in a registry implementing the "Docker Registry HTTP API V2".

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 and possibly a port number)
or a wildcarded expression starting with `*.`, for matching all subdomains (not including a port number). For wildcarded subdomain
matching, `*.example.com` is a valid case, but `example*.*.com` is not.

### `docker-archive:`

Only the default `""` scope is supported.

### `docker-daemon:`

For references using the _algo:digest_ format (referring to an image ID), only the default `""` scope is used.

For images using a named reference, scopes matching individual images are *in the fully expanded form*, either
using a tag or digest. For example, `docker.io/library/busybox:latest` (*not* `busybox:latest`).

More general named 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 and possibly a port number)
or a wildcarded expression starting with `*.`, for matching all subdomains (not including a port number). For wildcarded subdomain
matching, `*.example.com` is a valid case, but `example*.*.com` is not.

### `oci:`

The `oci:` transport refers to images in directories compliant with "Open Container Image Layout Specification".

Supported scopes are paths to directories
(either containing an OCI layout, or subdirectories possibly containing OCI layout directories).
The _reference_ annotation value, if any, is not used.

*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.

### `oci-archive:`

Supported scopes are paths to OCI archives, and their parent directories
(either containing a single archive, or subdirectories possibly containing archives).
The _reference_ annotation value, if any, is not used.

*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.

### `ostree`:

Supported scopes have the form _repo-path_`:`_image-scope_; _repo_path_ is the path to the OSTree repository.

_image-scope_ is the _docker_reference_ part of the reference, with with a `:latest` tag implied if no tag is present,
and parent namespaces of the _docker_reference_ value (by omitting the tag, or a prefix specifying a higher-level namespace).

*Note:*
- The _repo_path_ must be absolute and contain no symlinks. Paths violating these requirements may be silently ignored.

### `sif:`

Supported scopes are paths to Singularity images, and their parent directories
(either containing images, 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.

### `tarball:`

The `tarball:` transport is an implementation detail of some import workflows. Only the default `""` scope is supported.

## 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.

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:

### `insecureAcceptAnything`

A simple requirement with the following syntax

```json
{"type":"insecureAcceptAnything"}
```

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.

### `reject`

A simple requirement with the following syntax:

```json
{"type":"reject"}
```

This requirement rejects every image, and every signature.

### `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.

```js
{
    "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
}
```
<!-- Later: other keyType values -->

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:

- 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.

  ```json
  {"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).

  (Note that with images identified using digest references, the digest from the reference is validated even before signature verification starts.)

  ```json
  {"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.

  ```json
  {"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.

  ```js
  {
      "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`.

  ```js
  {
      "type": "exactRepository",
      "dockerRepository": docker_repository_value
  }
  ```
- Prefix remapping:

  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.

  ```js
  {
      "type": "remapIdentity",
      "prefix": prefix,
      "signedPrefix": prefix,
  }
  ```

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`.

<!-- ### `signedBaseLayer` -->


### `sigstoreSigned`

This requirement requires an image to be signed using a sigstore signature with an expected identity and key.

```js
{
    "type":    "sigstoreSigned",
    "keyPath": "/path/to/local/public/key/file",
    "keyData": "base64-encoded-public-key-data",
    "fulcio": {
        "caPath": "/path/to/local/CA/file",
        "caData": "base64-encoded-CA-data",
        "oidcIssuer": "https://expected.OIDC.issuer/",
        "subjectEmail", "expected-signing-user@example.com",
    },
    "rekorPublicKeyPath": "/path/to/local/public/key/file",
    "rekorPublicKeyData": "base64-encoded-public-key-data",
    "signedIdentity": identity_requirement
}
```
Exactly one of `keyPath`, `keyData` and `fulcio` must be present.

If `keyPath` or `keyData` is present, it contains a sigstore public key.
Only signatures made by this key are accepted.

If `fulcio` is present, the signature must be based on a Fulcio-issued certificate.
One of `caPath` and `caData` must be specified, containing the public key of the Fulcio instance.
Both `oidcIssuer` and `subjectEmail` are mandatory,
exactly specifying the expected identity provider,
and the identity of the user obtaining the Fulcio certificate.

At most one of `rekorPublicKeyPath` and `rekorPublicKeyData` can be present;
it is mandatory if `fulcio` is specified.
If a Rekor public key is specified,
the signature must have been uploaded to a Rekor server
and the signature must contain an (offline-verifiable) “signed entry timestamp”
proving the existence of the Rekor log record,
signed by the provided public key.

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 `use-sigstore-attachments` option needs to be enabled for the relevant registry or repository in the client's containers-registries.d(5).

## 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.)

```js
{
    "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 using the community Fulcio+Rekor servers.

               The community servers’ public keys can be obtained from
               https://github.com/sigstore/sigstore/tree/main/pkg/tuf/repository/targets .  */
            "hostname:5000/myns/sigstore-signed-fulcio-rekor": [
                {
                    "type": "sigstoreSigned",
                    "fulcio": {
                        "caPath": "/path/to/fulcio_v1.crt.pem",
                        "oidcIssuer": "https://github.com/login/oauth",
                        "subjectEmail": "test-user@example.com"
                    },
                    "rekorPublicKeyPath": "/path/to/rekor.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"}
                }
            ],
            /* A sigstore-signed repository using the community Fulcio+Rekor servers,
               accepts signatures by /usr/bin/cosign.

               The community servers’ public keys can be obtained from
               https://github.com/sigstore/sigstore/tree/main/pkg/tuf/repository/targets .  */
            "hostname:5000/myns/sigstore-signed-fulcio-rekor- allows-malicious-tag-substitution": [
                {
                    "type": "sigstoreSigned",
                    "fulcio": {
                        "caPath": "/path/to/fulcio_v1.crt.pem",
                        "oidcIssuer": "https://github.com/login/oauth",
                        "subjectEmail": "test-user@example.com"
                    },
                    "rekorPublicKeyPath": "/path/to/rekor.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

```json
{
    "default": [{"type": "insecureAcceptAnything"}]
}
```
## SEE ALSO
  atomic(1)

## HISTORY
August 2018, Rename to containers-policy.json(5) by Valentin Rothberg <vrothberg@suse.com>

September 2016, Originally compiled by Miloslav Trmač <mitr@redhat.com>