Skip to content

Commit

Permalink
Merge pull request #102 from nirmata/tsa-cosign
Browse files Browse the repository at this point in the history
feat: add TSA cert chain support for cosign in verify image rule
  • Loading branch information
pns-nirmata committed Apr 1, 2024
2 parents fac8cb1 + 59f0274 commit dcc5669
Show file tree
Hide file tree
Showing 18 changed files with 3,425 additions and 3 deletions.
6 changes: 6 additions & 0 deletions api/kyverno/v1/image_verification_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,12 @@ type CTLog struct {
// PubKey, if set, is used to validate SCTs against a custom source.
// +kubebuilder:validation:Optional
CTLogPubKey string `json:"pubkey,omitempty" yaml:"pubkey,omitempty"`

// TSACertChain, if set, is the PEM-encoded certificate chain file for the RFC3161 timestamp authority. Must
// contain the root CA certificate. Optionally may contain intermediate CA certificates, and
// may contain the leaf TSA certificate if not present in the timestamurce.
// +kubebuilder:validation:Optional
TSACertChain string `json:"tsaCertChain,omitempty" yaml:"tsaCertChain,omitempty"`
}

// Attestation are checks for signed in-toto Statements that are used to verify the image.
Expand Down
792 changes: 792 additions & 0 deletions charts/kyverno/charts/crds/templates/crds.yaml

Large diffs are not rendered by default.

396 changes: 396 additions & 0 deletions cmd/cli/kubectl-kyverno/data/crds/kyverno.io_clusterpolicies.yaml

Large diffs are not rendered by default.

396 changes: 396 additions & 0 deletions cmd/cli/kubectl-kyverno/data/crds/kyverno.io_policies.yaml

Large diffs are not rendered by default.

396 changes: 396 additions & 0 deletions config/crds/kyverno.io_clusterpolicies.yaml

Large diffs are not rendered by default.

396 changes: 396 additions & 0 deletions config/crds/kyverno.io_policies.yaml

Large diffs are not rendered by default.

792 changes: 792 additions & 0 deletions config/install-latest-testing.yaml

Large diffs are not rendered by default.

13 changes: 13 additions & 0 deletions docs/user/crd/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -1124,6 +1124,19 @@ <h3 id="kyverno.io/v1.CTLog">CTLog
<p>PubKey, if set, is used to validate SCTs against a custom source.</p>
</td>
</tr>
<tr>
<td>
<code>tsaCertChain</code><br/>
<em>
string
</em>
</td>
<td>
<p>TSACertChain, if set, is the PEM-encoded certificate chain file for the RFC3161 timestamp authority. Must
contain the root CA certificate. Optionally may contain intermediate CA certificates, and
may contain the leaf TSA certificate if not present in the timestamurce.</p>
</td>
</tr>
</tbody>
</table>
<hr />
Expand Down
13 changes: 11 additions & 2 deletions pkg/client/applyconfigurations/kyverno/v1/ctlog.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

37 changes: 37 additions & 0 deletions pkg/cosign/cosign.go
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,21 @@ func buildCosignOptions(ctx context.Context, opts images.Options) (*cosign.Check
cosignOpts.RegistryClientOpts = append(cosignOpts.RegistryClientOpts, remote.WithTargetRepository(signatureRepo))
}

if opts.TSACertChain != "" {
leaves, intermediates, roots, err := splitPEMCertificateChain([]byte(opts.TSACertChain))
if err != nil {
return nil, fmt.Errorf("error splitting tsa certificates: %w", err)
}
if len(leaves) > 1 {
return nil, fmt.Errorf("certificate chain must contain at most one TSA certificate")
}
if len(leaves) == 1 {
cosignOpts.TSACertificate = leaves[0]
}
cosignOpts.TSAIntermediateCertificates = intermediates
cosignOpts.TSARootCertificates = roots
}

return cosignOpts, nil
}

Expand Down Expand Up @@ -610,3 +625,25 @@ func getCTLogPubs(ctx context.Context, ctlogPubKey string) (*cosign.TrustedTrans
}
return &publicKeys, nil
}

func splitPEMCertificateChain(pem []byte) (leaves, intermediates, roots []*x509.Certificate, err error) {
certs, err := cryptoutils.UnmarshalCertificatesFromPEM(pem)
if err != nil {
return nil, nil, nil, err
}

for _, cert := range certs {
if !cert.IsCA {
leaves = append(leaves, cert)
} else {
// root certificates are self-signed
if bytes.Equal(cert.RawSubject, cert.RawIssuer) {
roots = append(roots, cert)
} else {
intermediates = append(intermediates, cert)
}
}
}

return leaves, intermediates, roots, nil
}
74 changes: 73 additions & 1 deletion pkg/cosign/cosign_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -214,7 +214,7 @@ func TestCTLogsPubkeys(t *testing.T) {

verifier := &cosignVerifier{}
_, err = verifier.VerifySignature(context.TODO(), opts)
assert.ErrorContains(t, err, "no matching signatures: ctfe public key not found for payload.")
assert.ErrorContains(t, err, "ctfe public key not found for payload.")

opts.CTLogsPubKey = ""
_, err = verifier.VerifySignature(context.TODO(), opts)
Expand Down Expand Up @@ -252,6 +252,78 @@ func TestCosignMatchCertificateData(t *testing.T) {
assert.Error(t, matchErr, "extension mismatch: expected pull for key githubWorkflowTrigger, received push")
}

func TestTSACertChain(t *testing.T) {
key := `
-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEstG5Xl7UxkQsmLUxdmS85HLgYBFy
c/P/oQ22iazkKm8P0sNlaZiaZC4TSEea3oh2Pim0+wxSubhKoK+7jq9Egg==
-----END PUBLIC KEY-----`

tsaCertChain := `
-----BEGIN CERTIFICATE-----
MIIH/zCCBeegAwIBAgIJAMHphhYNqOmAMA0GCSqGSIb3DQEBDQUAMIGVMREwDwYD
VQQKEwhGcmVlIFRTQTEQMA4GA1UECxMHUm9vdCBDQTEYMBYGA1UEAxMPd3d3LmZy
ZWV0c2Eub3JnMSIwIAYJKoZIhvcNAQkBFhNidXNpbGV6YXNAZ21haWwuY29tMRIw
EAYDVQQHEwlXdWVyemJ1cmcxDzANBgNVBAgTBkJheWVybjELMAkGA1UEBhMCREUw
HhcNMTYwMzEzMDE1MjEzWhcNNDEwMzA3MDE1MjEzWjCBlTERMA8GA1UEChMIRnJl
ZSBUU0ExEDAOBgNVBAsTB1Jvb3QgQ0ExGDAWBgNVBAMTD3d3dy5mcmVldHNhLm9y
ZzEiMCAGCSqGSIb3DQEJARYTYnVzaWxlemFzQGdtYWlsLmNvbTESMBAGA1UEBxMJ
V3VlcnpidXJnMQ8wDQYDVQQIEwZCYXllcm4xCzAJBgNVBAYTAkRFMIICIjANBgkq
hkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAtgKODjAy8REQ2WTNqUudAnjhlCrpE6ql
mQfNppeTmVvZrH4zutn+NwTaHAGpjSGv4/WRpZ1wZ3BRZ5mPUBZyLgq0YrIfQ5Fx
0s/MRZPzc1r3lKWrMR9sAQx4mN4z11xFEO529L0dFJjPF9MD8Gpd2feWzGyptlel
b+PqT+++fOa2oY0+NaMM7l/xcNHPOaMz0/2olk0i22hbKeVhvokPCqhFhzsuhKsm
q4Of/o+t6dI7sx5h0nPMm4gGSRhfq+z6BTRgCrqQG2FOLoVFgt6iIm/BnNffUr7V
DYd3zZmIwFOj/H3DKHoGik/xK3E82YA2ZulVOFRW/zj4ApjPa5OFbpIkd0pmzxzd
EcL479hSA9dFiyVmSxPtY5ze1P+BE9bMU1PScpRzw8MHFXxyKqW13Qv7LWw4sbk3
SciB7GACbQiVGzgkvXG6y85HOuvWNvC5GLSiyP9GlPB0V68tbxz4JVTRdw/Xn/XT
FNzRBM3cq8lBOAVt/PAX5+uFcv1S9wFE8YjaBfWCP1jdBil+c4e+0tdywT2oJmYB
BF/kEt1wmGwMmHunNEuQNzh1FtJY54hbUfiWi38mASE7xMtMhfj/C4SvapiDN837
gYaPfs8x3KZxbX7C3YAsFnJinlwAUss1fdKar8Q/YVs7H/nU4c4Ixxxz4f67fcVq
M2ITKentbCMCAwEAAaOCAk4wggJKMAwGA1UdEwQFMAMBAf8wDgYDVR0PAQH/BAQD
AgHGMB0GA1UdDgQWBBT6VQ2MNGZRQ0z357OnbJWveuaklzCBygYDVR0jBIHCMIG/
gBT6VQ2MNGZRQ0z357OnbJWveuakl6GBm6SBmDCBlTERMA8GA1UEChMIRnJlZSBU
U0ExEDAOBgNVBAsTB1Jvb3QgQ0ExGDAWBgNVBAMTD3d3dy5mcmVldHNhLm9yZzEi
MCAGCSqGSIb3DQEJARYTYnVzaWxlemFzQGdtYWlsLmNvbTESMBAGA1UEBxMJV3Vl
cnpidXJnMQ8wDQYDVQQIEwZCYXllcm4xCzAJBgNVBAYTAkRFggkAwemGFg2o6YAw
MwYDVR0fBCwwKjAooCagJIYiaHR0cDovL3d3dy5mcmVldHNhLm9yZy9yb290X2Nh
LmNybDCBzwYDVR0gBIHHMIHEMIHBBgorBgEEAYHyJAEBMIGyMDMGCCsGAQUFBwIB
FidodHRwOi8vd3d3LmZyZWV0c2Eub3JnL2ZyZWV0c2FfY3BzLmh0bWwwMgYIKwYB
BQUHAgEWJmh0dHA6Ly93d3cuZnJlZXRzYS5vcmcvZnJlZXRzYV9jcHMucGRmMEcG
CCsGAQUFBwICMDsaOUZyZWVUU0EgdHJ1c3RlZCB0aW1lc3RhbXBpbmcgU29mdHdh
cmUgYXMgYSBTZXJ2aWNlIChTYWFTKTA3BggrBgEFBQcBAQQrMCkwJwYIKwYBBQUH
MAGGG2h0dHA6Ly93d3cuZnJlZXRzYS5vcmc6MjU2MDANBgkqhkiG9w0BAQ0FAAOC
AgEAaK9+v5OFYu9M6ztYC+L69sw1omdyli89lZAfpWMMh9CRmJhM6KBqM/ipwoLt
nxyxGsbCPhcQjuTvzm+ylN6VwTMmIlVyVSLKYZcdSjt/eCUN+41K7sD7GVmxZBAF
ILnBDmTGJmLkrU0KuuIpj8lI/E6Z6NnmuP2+RAQSHsfBQi6sssnXMo4HOW5gtPO7
gDrUpVXID++1P4XndkoKn7Svw5n0zS9fv1hxBcYIHPPQUze2u30bAQt0n0iIyRLz
aWuhtpAtd7ffwEbASgzB7E+NGF4tpV37e8KiA2xiGSRqT5ndu28fgpOY87gD3ArZ
DctZvvTCfHdAS5kEO3gnGGeZEVLDmfEsv8TGJa3AljVa5E40IQDsUXpQLi8G+UC4
1DWZu8EVT4rnYaCw1VX7ShOR1PNCCvjb8S8tfdudd9zhU3gEB0rxdeTy1tVbNLXW
99y90xcwr1ZIDUwM/xQ/noO8FRhm0LoPC73Ef+J4ZBdrvWwauF3zJe33d4ibxEcb
8/pz5WzFkeixYM2nsHhqHsBKw7JPouKNXRnl5IAE1eFmqDyC7G/VT7OF669xM6hb
Ut5G21JE4cNK6NNucS+fzg1JPX0+3VhsYZjj7D5uljRvQXrJ8iHgr/M6j2oLHvTA
I2MLdq2qjZFDOCXsxBxJpbmLGBx9ow6ZerlUxzws2AWv2pk=
-----END CERTIFICATE-----
`
opts := images.Options{
ImageRef: "ghcr.io/kyverno/test-verify-image:tsa",
Key: key,
}

rc, err := registryclient.New()
assert.NilError(t, err)
opts.Client = rc

verifier := &cosignVerifier{}
_, err = verifier.VerifySignature(context.TODO(), opts)
assert.ErrorContains(t, err, "unable to verify RFC3161 timestamp bundle: no TSA root certificate(s) provided to verify timestamp")

opts.TSACertChain = tsaCertChain
_, err = verifier.VerifySignature(context.TODO(), opts)
assert.NilError(t, err)
}

type testSignature struct {
cert *x509.Certificate
}
Expand Down
3 changes: 3 additions & 0 deletions pkg/engine/internal/imageverifier.go
Original file line number Diff line number Diff line change
Expand Up @@ -567,6 +567,7 @@ func (iv *ImageVerifier) buildCosignVerifier(
if attestor.Keys.CTLog != nil {
opts.IgnoreSCT = attestor.Keys.CTLog.IgnoreSCT
opts.CTLogsPubKey = attestor.Keys.CTLog.CTLogPubKey
opts.TSACertChain = attestor.Keys.CTLog.TSACertChain
} else {
opts.IgnoreSCT = false
}
Expand All @@ -588,6 +589,7 @@ func (iv *ImageVerifier) buildCosignVerifier(
if attestor.Certificates.CTLog != nil {
opts.IgnoreSCT = attestor.Certificates.CTLog.IgnoreSCT
opts.CTLogsPubKey = attestor.Certificates.CTLog.CTLogPubKey
opts.TSACertChain = attestor.Certificates.CTLog.TSACertChain
} else {
opts.IgnoreSCT = false
}
Expand All @@ -605,6 +607,7 @@ func (iv *ImageVerifier) buildCosignVerifier(
if attestor.Keyless.CTLog != nil {
opts.IgnoreSCT = attestor.Keyless.CTLog.IgnoreSCT
opts.CTLogsPubKey = attestor.Keyless.CTLog.CTLogPubKey
opts.TSACertChain = attestor.Keyless.CTLog.TSACertChain
} else {
opts.IgnoreSCT = false
}
Expand Down
1 change: 1 addition & 0 deletions pkg/images/verifier.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ type Options struct {
RekorURL string
RekorPubKey string
IgnoreSCT bool
TSACertChain string
CTLogsPubKey string
SignatureAlgorithm string
PredicateType string
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: keyed-tsa-policy
status:
conditions:
- reason: Succeeded
status: "True"
type: Ready
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
apiVersion: v1
kind: Namespace
metadata:
name: test-verify-images
---
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: keyed-tsa-policy
spec:
background: false
failurePolicy: Fail
rules:
- match:
any:
- resources:
kinds:
- Pod
name: keyed-tsa-rule
verifyImages:
- attestors:
- entries:
- keys:
publicKeys: |-
-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEstG5Xl7UxkQsmLUxdmS85HLgYBFy
c/P/oQ22iazkKm8P0sNlaZiaZC4TSEea3oh2Pim0+wxSubhKoK+7jq9Egg==
-----END PUBLIC KEY-----
ctlog:
tsaCertChain: |-
-----BEGIN CERTIFICATE-----
MIIH/zCCBeegAwIBAgIJAMHphhYNqOmAMA0GCSqGSIb3DQEBDQUAMIGVMREwDwYD
VQQKEwhGcmVlIFRTQTEQMA4GA1UECxMHUm9vdCBDQTEYMBYGA1UEAxMPd3d3LmZy
ZWV0c2Eub3JnMSIwIAYJKoZIhvcNAQkBFhNidXNpbGV6YXNAZ21haWwuY29tMRIw
EAYDVQQHEwlXdWVyemJ1cmcxDzANBgNVBAgTBkJheWVybjELMAkGA1UEBhMCREUw
HhcNMTYwMzEzMDE1MjEzWhcNNDEwMzA3MDE1MjEzWjCBlTERMA8GA1UEChMIRnJl
ZSBUU0ExEDAOBgNVBAsTB1Jvb3QgQ0ExGDAWBgNVBAMTD3d3dy5mcmVldHNhLm9y
ZzEiMCAGCSqGSIb3DQEJARYTYnVzaWxlemFzQGdtYWlsLmNvbTESMBAGA1UEBxMJ
V3VlcnpidXJnMQ8wDQYDVQQIEwZCYXllcm4xCzAJBgNVBAYTAkRFMIICIjANBgkq
hkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAtgKODjAy8REQ2WTNqUudAnjhlCrpE6ql
mQfNppeTmVvZrH4zutn+NwTaHAGpjSGv4/WRpZ1wZ3BRZ5mPUBZyLgq0YrIfQ5Fx
0s/MRZPzc1r3lKWrMR9sAQx4mN4z11xFEO529L0dFJjPF9MD8Gpd2feWzGyptlel
b+PqT+++fOa2oY0+NaMM7l/xcNHPOaMz0/2olk0i22hbKeVhvokPCqhFhzsuhKsm
q4Of/o+t6dI7sx5h0nPMm4gGSRhfq+z6BTRgCrqQG2FOLoVFgt6iIm/BnNffUr7V
DYd3zZmIwFOj/H3DKHoGik/xK3E82YA2ZulVOFRW/zj4ApjPa5OFbpIkd0pmzxzd
EcL479hSA9dFiyVmSxPtY5ze1P+BE9bMU1PScpRzw8MHFXxyKqW13Qv7LWw4sbk3
SciB7GACbQiVGzgkvXG6y85HOuvWNvC5GLSiyP9GlPB0V68tbxz4JVTRdw/Xn/XT
FNzRBM3cq8lBOAVt/PAX5+uFcv1S9wFE8YjaBfWCP1jdBil+c4e+0tdywT2oJmYB
BF/kEt1wmGwMmHunNEuQNzh1FtJY54hbUfiWi38mASE7xMtMhfj/C4SvapiDN837
gYaPfs8x3KZxbX7C3YAsFnJinlwAUss1fdKar8Q/YVs7H/nU4c4Ixxxz4f67fcVq
M2ITKentbCMCAwEAAaOCAk4wggJKMAwGA1UdEwQFMAMBAf8wDgYDVR0PAQH/BAQD
AgHGMB0GA1UdDgQWBBT6VQ2MNGZRQ0z357OnbJWveuaklzCBygYDVR0jBIHCMIG/
gBT6VQ2MNGZRQ0z357OnbJWveuakl6GBm6SBmDCBlTERMA8GA1UEChMIRnJlZSBU
U0ExEDAOBgNVBAsTB1Jvb3QgQ0ExGDAWBgNVBAMTD3d3dy5mcmVldHNhLm9yZzEi
MCAGCSqGSIb3DQEJARYTYnVzaWxlemFzQGdtYWlsLmNvbTESMBAGA1UEBxMJV3Vl
cnpidXJnMQ8wDQYDVQQIEwZCYXllcm4xCzAJBgNVBAYTAkRFggkAwemGFg2o6YAw
MwYDVR0fBCwwKjAooCagJIYiaHR0cDovL3d3dy5mcmVldHNhLm9yZy9yb290X2Nh
LmNybDCBzwYDVR0gBIHHMIHEMIHBBgorBgEEAYHyJAEBMIGyMDMGCCsGAQUFBwIB
FidodHRwOi8vd3d3LmZyZWV0c2Eub3JnL2ZyZWV0c2FfY3BzLmh0bWwwMgYIKwYB
BQUHAgEWJmh0dHA6Ly93d3cuZnJlZXRzYS5vcmcvZnJlZXRzYV9jcHMucGRmMEcG
CCsGAQUFBwICMDsaOUZyZWVUU0EgdHJ1c3RlZCB0aW1lc3RhbXBpbmcgU29mdHdh
cmUgYXMgYSBTZXJ2aWNlIChTYWFTKTA3BggrBgEFBQcBAQQrMCkwJwYIKwYBBQUH
MAGGG2h0dHA6Ly93d3cuZnJlZXRzYS5vcmc6MjU2MDANBgkqhkiG9w0BAQ0FAAOC
AgEAaK9+v5OFYu9M6ztYC+L69sw1omdyli89lZAfpWMMh9CRmJhM6KBqM/ipwoLt
nxyxGsbCPhcQjuTvzm+ylN6VwTMmIlVyVSLKYZcdSjt/eCUN+41K7sD7GVmxZBAF
ILnBDmTGJmLkrU0KuuIpj8lI/E6Z6NnmuP2+RAQSHsfBQi6sssnXMo4HOW5gtPO7
gDrUpVXID++1P4XndkoKn7Svw5n0zS9fv1hxBcYIHPPQUze2u30bAQt0n0iIyRLz
aWuhtpAtd7ffwEbASgzB7E+NGF4tpV37e8KiA2xiGSRqT5ndu28fgpOY87gD3ArZ
DctZvvTCfHdAS5kEO3gnGGeZEVLDmfEsv8TGJa3AljVa5E40IQDsUXpQLi8G+UC4
1DWZu8EVT4rnYaCw1VX7ShOR1PNCCvjb8S8tfdudd9zhU3gEB0rxdeTy1tVbNLXW
99y90xcwr1ZIDUwM/xQ/noO8FRhm0LoPC73Ef+J4ZBdrvWwauF3zJe33d4ibxEcb
8/pz5WzFkeixYM2nsHhqHsBKw7JPouKNXRnl5IAE1eFmqDyC7G/VT7OF669xM6hb
Ut5G21JE4cNK6NNucS+fzg1JPX0+3VhsYZjj7D5uljRvQXrJ8iHgr/M6j2oLHvTA
I2MLdq2qjZFDOCXsxBxJpbmLGBx9ow6ZerlUxzws2AWv2pk=
-----END CERTIFICATE-----
imageReferences:
- ghcr.io/kyverno/test-verify-image:*
validationFailureAction: Enforce
webhookTimeoutSeconds: 30
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
apiVersion: v1
kind: Pod
metadata:
name: test-tsa-pod
namespace: test-verify-images
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
apiVersion: v1
kind: Pod
metadata:
name: test-tsa-pod
namespace: test-verify-images
spec:
containers:
- image: ghcr.io/kyverno/test-verify-image:tsa
name: test-secret
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
## Description

This test performs a simple verification of an image using a public key and a tsa cert chain.

## Expected Behavior

Pod creation should pass as the image has been signed by the public key and timestamp created using the TSA provider specified in the policy.

## Reference Issue(s)

N/A

0 comments on commit dcc5669

Please sign in to comment.