Skip to content

Commit

Permalink
feat: Support cosign --k8s-keychain flag
Browse files Browse the repository at this point in the history
  • Loading branch information
marckn0x committed Feb 21, 2022
1 parent 09ddf75 commit ee14051
Show file tree
Hide file tree
Showing 4 changed files with 70 additions and 16 deletions.
28 changes: 21 additions & 7 deletions connaisseur/res/config_schema.json
Expand Up @@ -148,14 +148,28 @@
},
"auth": {
"type": "object",
"properties": {
"secret_name": {
"type": "string",
"pattern": "[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*"
"oneOf": [
{
"properties": {
"secret_name": {
"type": "string",
"pattern": "[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*"
}
},
"required": [
"secret_name"
]
},
{
"properties": {
"k8s_keychain": {
"type": "boolean"
}
},
"required": [
"k8s_keychain"
]
}
},
"required": [
"secret_name"
]
},
"cert": {
Expand Down
4 changes: 3 additions & 1 deletion connaisseur/validators/cosign/cosign_validator.py
Expand Up @@ -22,9 +22,10 @@ class CosignValidator(ValidatorInterface):
name: str
trust_roots: list

def __init__(self, name: str, trust_roots: list, **kwargs):
def __init__(self, name: str, trust_roots: list, auth: dict = None, **kwargs):
super().__init__(name, **kwargs)
self.trust_roots = trust_roots
self.k8s_keychain = False if auth is None else auth.get("k8s_keychain", False)

def __get_key(self, key_name: str = None):
key_name = key_name or "default"
Expand Down Expand Up @@ -137,6 +138,7 @@ def __invoke_cosign(self, image, key):
"--output",
"text",
*pubkey_config,
*(["--k8s-keychain"] if self.k8s_keychain else []),
image,
]

Expand Down
36 changes: 32 additions & 4 deletions docs/validators/sigstore_cosign.md
Expand Up @@ -94,8 +94,9 @@ kubectl run altsigned --image=docker.io/securesystemsengineering/testimage:co-si
| `trust_roots[*].name` | | :heavy_check_mark: | See [basics](../basics.md#validators). |
| `trust_roots[*].key` | | :heavy_check_mark: | See [basics](../basics.md#validators). ECDSA public key from `cosign.pub` file or [KMS URI](https://github.com/sigstore/cosign/blob/main/KMS.md). See additional notes [below](#kms-support). |
| `host` | | | Not yet implemented. |
| `auth.` | | | Authentication credentials for private registries. |
| `auth.secret_name` | | | Name of a Kubernetes secret in Connaisseur namespace that contains [dockerconfigjson](https://kubernetes.io/docs/concepts/configuration/secret/#docker-config-secrets) for registry authentication. See additional notes [below](#authentication). |
| `auth.` | | | Authentication credentials for private registries. See additional notes [below](#authentication). |
| `auth.secret_name` | | | Name of a Kubernetes secret in Connaisseur namespace that contains [dockerconfigjson](https://kubernetes.io/docs/concepts/configuration/secret/#docker-config-secrets) for registry authentication. See additional notes [below](#dockerconfigjson). |
| `auth.k8s_keychain` | false | | When true, pass `--k8s-keychain` argument to `cosign verify` in order to use workload identities for authentication. See additional notes [below](#k8s_keychain). |
| `cert` | | | A certificate in PEM format for private registries. |

### Example
Expand Down Expand Up @@ -123,8 +124,11 @@ policy:

### Authentication

When using a private registry for images and signature data, the credentials need to be provided to Connaisseur.
This is done by creating a [dockerconfigjson](https://kubernetes.io/docs/concepts/configuration/secret/#docker-config-secrets) Kubernetes secret in the Connaisseur namespace and passing the secret name to Connaisseur as `auth.secret_name`.
When using a private registry for images and signature data, the credentials need to be provided to Connaisseur. There are two ways to do this.

#### dockerconfigjson

Create a [dockerconfigjson](https://kubernetes.io/docs/concepts/configuration/secret/#docker-config-secrets) Kubernetes secret in the Connaisseur namespace and pass the secret name to Connaisseur as `auth.secret_name`.
The secret can for example be created directly from your local `config.json` (for docker this resides in `~/.docker/config.json`):

```bash
Expand All @@ -136,6 +140,30 @@ kubectl create secret generic my-secret \
In the above case, the secret name in Connaisseur configuration would be `secret_name: my-secret`.
It is possible to provide one Kubernetes secret with a `config.json` for authentication to multiple private registries and referencing this in multiple validators.

#### k8s_keychain

Specification of `auth.k8s_keychain: true` in the validator configuration passes the `--k8s-keychain` to `cosign` when performing image validation.
Thus, [k8schain](https://pkg.go.dev/github.com/google/go-containerregistry/pkg/authn/k8schain) is used by `cosign` to pick up ambient registry credentials from the environment and for example use workload identities in case of common cloud providers.

For example, when validating against an ECR private repository, the credentials of an IAM user allowed to perform actions
`ecr:GetAuthorizationToken`, `ecr:BatchGetImage`, and `ecr:GetDownloadUrlForLayer` could be added to the secret `connaisseur-env-secrets`:

```yaml
apiVersion: v1
kind: Secret
type: Opaque
metadata:
name: connaisseur-env-secrets
...
data:
AWS_ACCESS_KEY_ID: ***
AWS_SECRET_ACCESS_KEY: ***
...
```

If `k8s_keychain` is set to `true` in the validator configuration, `cosign` will log into ECR at time of validation.
See [this cosign pull request](https://github.com/sigstore/cosign/pull/972) for more details.

### KMS Support

> :warning: This is currently an experimental feature that might unstable over time. As such, it is not part of our semantic versioning guarantees and we take the liberty to adjust or remove it with any version at any time without incrementing MAJOR or MINOR.
Expand Down
18 changes: 14 additions & 4 deletions tests/validators/cosign/test_cosign_validator.py
Expand Up @@ -233,7 +233,7 @@ def test_get_cosign_validated_digests(


@pytest.mark.parametrize(
"image, process_input, input_type",
"image, process_input, input_type, auth",
[
(
"testimage:v1",
Expand All @@ -244,11 +244,14 @@ def test_get_cosign_validated_digests(
"-----END PUBLIC KEY-----\n"
),
"key",
{"secret_name": "thesecret"},
),
("testimage:v1", "k8s://connaisseur/test_key", "ref"),
("testimage:v1", "k8s://connaisseur/test_key", "ref", {"k8s_keychain": False}),
("testimage:v1", "k8s://connaisseur/test_key", "ref", {"k8s_keychain": True}),
("testimage:v1", "k8s://connaisseur/test_key", "ref", None),
],
)
def test_invoke_cosign(fake_process, image, process_input, input_type):
def test_invoke_cosign(fake_process, image, process_input, input_type, auth):
def stdin_function(input):
return {"stderr": input.decode(), "stdout": input}

Expand All @@ -264,6 +267,11 @@ def stdin_function(input):
"text",
"--key",
"/dev/stdin" if input_type == "key" else process_input,
*(
["--k8s-keychain"]
if auth is not None and auth.get("k8s_keychain", False)
else []
),
image,
]
fake_process.register_subprocess(
Expand All @@ -272,7 +280,9 @@ def stdin_function(input):
stdout=bytes(cosign_payload, "utf-8"),
stdin_callable=stdin_function,
)
val = co.CosignValidator(**static_cosigns[0])
config = static_cosigns[0].copy()
config["auth"] = auth
val = co.CosignValidator(**config)
returncode, stdout, stderr = val._CosignValidator__invoke_cosign(
"testimage:v1", process_input
)
Expand Down

0 comments on commit ee14051

Please sign in to comment.