Skip to content

Commit 83e788b

Browse files
kdacosta0osmman
authored andcommitted
feat(rekor): Add AWS KMS signer support
Signed-off-by: Kristian Da Costa Menezes <kdacosta@redhat.com>
1 parent 66dee54 commit 83e788b

File tree

6 files changed

+322
-49
lines changed

6 files changed

+322
-49
lines changed

api/v1alpha1/rekor_types.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,7 @@ type RekorSigner struct {
105105
// - azurekms://keyname
106106
// - gcpkms://keyname
107107
// - hashivault://keyname
108+
// +kubebuilder:validation:XValidation:rule="self == '' || self == 'secret' || self == 'memory' || self.matches('^awskms://.+$') || self.matches('^gcpkms://.+$') || self.matches('^azurekms://.+$') || self.matches('^hashivault://.+$')",message="KMS must be '', 'secret', 'memory', or a valid URI with a key path (e.g., awskms:///key-id)"
108109
// +kubebuilder:default:=secret
109110
KMS string `json:"kms,omitempty"`
110111

api/v1alpha1/rekor_types_test.go

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -347,6 +347,96 @@ var _ = Describe("Rekor", func() {
347347
})
348348
})
349349

350+
Context("signer validation", func() {
351+
When("using valid KMS values", func() {
352+
It("should allow empty string", func() {
353+
validObject := generateRekorObject("rekor-kms-valid-empty")
354+
validObject.Spec.Signer.KMS = ""
355+
Expect(k8sClient.Create(context.Background(), validObject)).To(Succeed())
356+
})
357+
358+
It("should allow 'secret'", func() {
359+
validObject := generateRekorObject("rekor-kms-valid-secret")
360+
validObject.Spec.Signer.KMS = "secret"
361+
Expect(k8sClient.Create(context.Background(), validObject)).To(Succeed())
362+
})
363+
364+
It("should allow 'memory'", func() {
365+
validObject := generateRekorObject("rekor-kms-valid-memory")
366+
validObject.Spec.Signer.KMS = "memory"
367+
Expect(k8sClient.Create(context.Background(), validObject)).To(Succeed())
368+
})
369+
370+
It("should allow 'awskms://' URI", func() {
371+
validObject := generateRekorObject("rekor-kms-valid-aws")
372+
validObject.Spec.Signer.KMS = "awskms://key/arn"
373+
Expect(k8sClient.Create(context.Background(), validObject)).To(Succeed())
374+
})
375+
376+
It("should allow 'awskms:///' URI (triple slash)", func() {
377+
validObject := generateRekorObject("rekor-kms-valid-aws-tripleslash")
378+
validObject.Spec.Signer.KMS = "awskms:///key-id-or-arn"
379+
Expect(k8sClient.Create(context.Background(), validObject)).To(Succeed())
380+
})
381+
382+
It("should allow 'gcpkms://' URI", func() {
383+
validObject := generateRekorObject("rekor-kms-valid-gcp")
384+
validObject.Spec.Signer.KMS = "gcpkms://key/path"
385+
Expect(k8sClient.Create(context.Background(), validObject)).To(Succeed())
386+
})
387+
388+
It("should allow 'azurekms://' URI", func() {
389+
validObject := generateRekorObject("rekor-kms-valid-azure")
390+
validObject.Spec.Signer.KMS = "azurekms://key/path"
391+
Expect(k8sClient.Create(context.Background(), validObject)).To(Succeed())
392+
})
393+
394+
It("should allow 'hashivault://' URI", func() {
395+
validObject := generateRekorObject("rekor-kms-valid-vault")
396+
validObject.Spec.Signer.KMS = "hashivault://key/path"
397+
Expect(k8sClient.Create(context.Background(), validObject)).To(Succeed())
398+
})
399+
})
400+
401+
When("using invalid KMS values", func() {
402+
It("should reject a random string", func() {
403+
invalidObject := generateRekorObject("rekor-kms-invalid-random")
404+
invalidObject.Spec.Signer.KMS = "unsupported"
405+
406+
Expect(apierrors.IsInvalid(k8sClient.Create(context.Background(), invalidObject))).To(BeTrue())
407+
Expect(k8sClient.Create(context.Background(), invalidObject)).
408+
To(MatchError(ContainSubstring("KMS must be '', 'secret', 'memory', or a valid URI")))
409+
})
410+
411+
It("should reject an incomplete URI", func() {
412+
invalidObject := generateRekorObject("rekor-kms-invalid-incomplete")
413+
invalidObject.Spec.Signer.KMS = "awskms://"
414+
415+
Expect(apierrors.IsInvalid(k8sClient.Create(context.Background(), invalidObject))).To(BeTrue())
416+
Expect(k8sClient.Create(context.Background(), invalidObject)).
417+
To(MatchError(ContainSubstring("KMS must be '', 'secret', 'memory', or a valid URI")))
418+
})
419+
420+
It("should reject a typo URI", func() {
421+
invalidObject := generateRekorObject("rekor-kms-invalid-typo")
422+
invalidObject.Spec.Signer.KMS = "aws-kms://key"
423+
424+
Expect(apierrors.IsInvalid(k8sClient.Create(context.Background(), invalidObject))).To(BeTrue())
425+
Expect(k8sClient.Create(context.Background(), invalidObject)).
426+
To(MatchError(ContainSubstring("KMS must be '', 'secret', 'memory', or a valid URI")))
427+
})
428+
429+
It("should reject unsupported protocol", func() {
430+
invalidObject := generateRekorObject("rekor-kms-invalid-other")
431+
invalidObject.Spec.Signer.KMS = "s3://my-bucket"
432+
433+
Expect(apierrors.IsInvalid(k8sClient.Create(context.Background(), invalidObject))).To(BeTrue())
434+
Expect(k8sClient.Create(context.Background(), invalidObject)).
435+
To(MatchError(ContainSubstring("KMS must be '', 'secret', 'memory', or a valid URI")))
436+
})
437+
})
438+
})
439+
350440
Context("attestations", func() {
351441
When("file url", func() {
352442
It("default", func() {

config/crd/bases/rhtas.redhat.com_rekors.yaml

Lines changed: 58 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -617,8 +617,8 @@ spec:
617617
most preferred is the one with the greatest sum of weights, i.e.
618618
for each node that meets all of the scheduling requirements (resource
619619
request, requiredDuringScheduling anti-affinity expressions, etc.),
620-
compute a sum by iterating through the elements of this field and adding
621-
"weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the
620+
compute a sum by iterating through the elements of this field and subtracting
621+
"weight" from the sum if the node has pods which matches the corresponding podAffinityTerm; the
622622
node(s) with the highest sum are the most preferred.
623623
items:
624624
description: The weights of all of the matched WeightedPodAffinityTerm
@@ -1026,8 +1026,9 @@ spec:
10261026
in a Container.
10271027
properties:
10281028
name:
1029-
description: Name of the environment variable. Must be a
1030-
C_IDENTIFIER.
1029+
description: |-
1030+
Name of the environment variable.
1031+
May consist of any printable ASCII characters except '='.
10311032
type: string
10321033
value:
10331034
description: |-
@@ -1085,6 +1086,43 @@ spec:
10851086
- fieldPath
10861087
type: object
10871088
x-kubernetes-map-type: atomic
1089+
fileKeyRef:
1090+
description: |-
1091+
FileKeyRef selects a key of the env file.
1092+
Requires the EnvFiles feature gate to be enabled.
1093+
properties:
1094+
key:
1095+
description: |-
1096+
The key within the env file. An invalid key will prevent the pod from starting.
1097+
The keys defined within a source may consist of any printable ASCII characters except '='.
1098+
During Alpha stage of the EnvFiles feature gate, the key size is limited to 128 characters.
1099+
type: string
1100+
optional:
1101+
default: false
1102+
description: |-
1103+
Specify whether the file or its key must be defined. If the file or key
1104+
does not exist, then the env var is not published.
1105+
If optional is set to true and the specified key does not exist,
1106+
the environment variable will not be set in the Pod's containers.
1107+
1108+
If optional is set to false and the specified key does not exist,
1109+
an error will be returned during Pod creation.
1110+
type: boolean
1111+
path:
1112+
description: |-
1113+
The path within the volume from which to select the file.
1114+
Must be relative and may not contain the '..' path or start with '..'.
1115+
type: string
1116+
volumeName:
1117+
description: The name of the volume mount containing
1118+
the env file.
1119+
type: string
1120+
required:
1121+
- key
1122+
- path
1123+
- volumeName
1124+
type: object
1125+
x-kubernetes-map-type: atomic
10881126
resourceFieldRef:
10891127
description: |-
10901128
Selects a resource of the container: only resources limits and requests
@@ -1883,8 +1921,8 @@ spec:
18831921
most preferred is the one with the greatest sum of weights, i.e.
18841922
for each node that meets all of the scheduling requirements (resource
18851923
request, requiredDuringScheduling anti-affinity expressions, etc.),
1886-
compute a sum by iterating through the elements of this field and adding
1887-
"weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the
1924+
compute a sum by iterating through the elements of this field and subtracting
1925+
"weight" from the sum if the node has pods which matches the corresponding podAffinityTerm; the
18881926
node(s) with the highest sum are the most preferred.
18891927
items:
18901928
description: The weights of all of the matched WeightedPodAffinityTerm
@@ -2255,7 +2293,7 @@ spec:
22552293
Claims lists the names of resources, defined in spec.resourceClaims,
22562294
that are used by this container.
22572295
2258-
This is an alpha field and requires enabling the
2296+
This field depends on the
22592297
DynamicResourceAllocation feature gate.
22602298
22612299
This field is immutable. It can only be set for containers.
@@ -2366,7 +2404,7 @@ spec:
23662404
Claims lists the names of resources, defined in spec.resourceClaims,
23672405
that are used by this container.
23682406
2369-
This is an alpha field and requires enabling the
2407+
This field depends on the
23702408
DynamicResourceAllocation feature gate.
23712409
23722410
This field is immutable. It can only be set for containers.
@@ -2566,6 +2604,12 @@ spec:
25662604
- gcpkms://keyname
25672605
- hashivault://keyname
25682606
type: string
2607+
x-kubernetes-validations:
2608+
- message: KMS must be '', 'secret', 'memory', or a valid URI
2609+
with a key path (e.g., awskms:///key-id)
2610+
rule: self == '' || self == 'secret' || self == 'memory' ||
2611+
self.matches('^awskms://.+$') || self.matches('^gcpkms://.+$')
2612+
|| self.matches('^azurekms://.+$') || self.matches('^hashivault://.+$')
25692613
passwordRef:
25702614
description: |-
25712615
Password to decrypt the signer private key.
@@ -2868,6 +2912,12 @@ spec:
28682912
- gcpkms://keyname
28692913
- hashivault://keyname
28702914
type: string
2915+
x-kubernetes-validations:
2916+
- message: KMS must be '', 'secret', 'memory', or a valid URI
2917+
with a key path (e.g., awskms:///key-id)
2918+
rule: self == '' || self == 'secret' || self == 'memory' ||
2919+
self.matches('^awskms://.+$') || self.matches('^gcpkms://.+$')
2920+
|| self.matches('^azurekms://.+$') || self.matches('^hashivault://.+$')
28712921
passwordRef:
28722922
description: |-
28732923
Password to decrypt the signer private key.

0 commit comments

Comments
 (0)