Skip to content

Commit

Permalink
Add API validation rules for CTlog and Fulcio resources
Browse files Browse the repository at this point in the history
  • Loading branch information
osmman committed Mar 1, 2024
1 parent 3e74b09 commit 42672b8
Show file tree
Hide file tree
Showing 10 changed files with 482 additions and 8 deletions.
3 changes: 3 additions & 0 deletions api/v1alpha1/ctlog_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,11 @@ import (
// NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized.

// CTlogSpec defines the desired state of CTlog component
// +kubebuilder:validation:XValidation:rule=(!has(self.publicKeyRef) || has(self.privateKeyRef)),message=privateKeyRef cannot be empty
// +kubebuilder:validation:XValidation:rule=(!has(self.privateKeyPasswordRef) || has(self.privateKeyRef)),message=privateKeyRef cannot be empty
type CTlogSpec struct {
// The ID of a Trillian tree that stores the log data.
// If it is unset, the operator will create new Merkle tree in the Trillian backend
//+optional
TreeID *int64 `json:"treeID,omitempty"`

Expand Down
179 changes: 179 additions & 0 deletions api/v1alpha1/ctlog_types_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
package v1alpha1

import (
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"golang.org/x/net/context"
_ "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

var _ = Describe("CTlog", func() {

Context("CTlogSpec", func() {
It("can be created", func() {
created := generateCTlogObject("ctlog-create")
Expect(k8sClient.Create(context.Background(), created)).To(Succeed())

fetched := &CTlog{}
Expect(k8sClient.Get(context.Background(), getKey(created), fetched)).To(Succeed())
Expect(fetched).To(Equal(created))
})

It("can be updated", func() {
created := generateCTlogObject("ctlog-update")
Expect(k8sClient.Create(context.Background(), created)).To(Succeed())

fetched := &CTlog{}
Expect(k8sClient.Get(context.Background(), getKey(created), fetched)).To(Succeed())
Expect(fetched).To(Equal(created))

var id int64 = 1234567890123456789
fetched.Spec.TreeID = &id
Expect(k8sClient.Update(context.Background(), fetched)).To(Succeed())
})

It("can be deleted", func() {
created := generateCTlogObject("ctlog-delete")
Expect(k8sClient.Create(context.Background(), created)).To(Succeed())

Expect(k8sClient.Delete(context.Background(), created)).To(Succeed())
Expect(k8sClient.Get(context.Background(), getKey(created), created)).ToNot(Succeed())
})

Context("is validated", func() {
It("public key", func() {
invalidObject := generateCTlogObject("public-key-invalid")
invalidObject.Spec.PublicKeyRef = &SecretKeySelector{
Key: "key",
LocalObjectReference: corev1.LocalObjectReference{Name: "name"},
}

Expect(apierrors.IsInvalid(k8sClient.Create(context.Background(), invalidObject))).To(BeTrue())
Expect(k8sClient.Create(context.Background(), invalidObject)).
To(MatchError(ContainSubstring("privateKeyRef cannot be empty")))
})

It("private key password", func() {
invalidObject := generateCTlogObject("private-key-password-invalid")
invalidObject.Spec.PublicKeyRef = &SecretKeySelector{
Key: "key",
LocalObjectReference: corev1.LocalObjectReference{Name: "name"},
}

Expect(apierrors.IsInvalid(k8sClient.Create(context.Background(), invalidObject))).To(BeTrue())
Expect(k8sClient.Create(context.Background(), invalidObject)).
To(MatchError(ContainSubstring("privateKeyRef cannot be empty")))
})
})

Context("Default settings", func() {
var (
ctlogInstance CTlog
expectedCTlogInstance CTlog
)

BeforeEach(func() {
expectedCTlogInstance = *generateCTlogObject("foo")
})

When("CR spec is empty", func() {
It("creates CR with defaults", func() {
ctlogInstance = CTlog{
ObjectMeta: metav1.ObjectMeta{
Name: "ctlog-defaults",
Namespace: "default",
},
}

Expect(k8sClient.Create(context.Background(), &ctlogInstance)).To(Succeed())
fetched := &CTlog{}
Expect(k8sClient.Get(context.Background(), getKey(&ctlogInstance), fetched)).To(Succeed())
Expect(fetched.Spec).To(Equal(expectedCTlogInstance.Spec))
})
})

When("CR is fully populated", func() {
It("outputs the CR", func() {
tree := int64(1269875)

ctlogInstance = CTlog{
ObjectMeta: metav1.ObjectMeta{
Name: "ctlog-full-manifest",
Namespace: "default",
},
Spec: CTlogSpec{
TreeID: &tree,
PublicKeyRef: &SecretKeySelector{
Key: "key",
LocalObjectReference: corev1.LocalObjectReference{
Name: "name",
},
},
PrivateKeyRef: &SecretKeySelector{
Key: "key",
LocalObjectReference: corev1.LocalObjectReference{
Name: "name",
},
},
PrivateKeyPasswordRef: &SecretKeySelector{
Key: "key",
LocalObjectReference: corev1.LocalObjectReference{
Name: "name",
},
},
RootCertificates: []SecretKeySelector{
{
Key: "key",
LocalObjectReference: corev1.LocalObjectReference{
Name: "name",
},
},
},
},
}

Expect(k8sClient.Create(context.Background(), &ctlogInstance)).To(Succeed())
fetchedCTlog := &CTlog{}
Expect(k8sClient.Get(context.Background(), getKey(&ctlogInstance), fetchedCTlog)).To(Succeed())
Expect(fetchedCTlog.Spec).To(Equal(ctlogInstance.Spec))
})
})

When("CR is partially set", func() {

It("sets spec.pvc.storage if spec.pvc is partially set", func() {
tree := int64(1269875)
ctlogInstance = CTlog{
ObjectMeta: metav1.ObjectMeta{
Name: "ctlog-storage",
Namespace: "default",
},
Spec: CTlogSpec{
TreeID: &tree,
},
}

expectedCTlogInstance.Spec.TreeID = &tree

Expect(k8sClient.Create(context.Background(), &ctlogInstance)).To(Succeed())
fetchedCTlog := &CTlog{}
Expect(k8sClient.Get(context.Background(), getKey(&ctlogInstance), fetchedCTlog)).To(Succeed())
Expect(fetchedCTlog.Spec).To(Equal(expectedCTlogInstance.Spec))
})
})
})
})
})

func generateCTlogObject(name string) *CTlog {
return &CTlog{
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: "default",
},
Spec: CTlogSpec{},
}
}
12 changes: 9 additions & 3 deletions api/v1alpha1/fulcio_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,14 @@ type FulcioSpec struct {
//+required
Config FulcioConfig `json:"config"`
// Certificate configuration
Certificate FulcioCert `json:"certificate,omitempty"`
Certificate FulcioCert `json:"certificate"`
//Enable Service monitors for fulcio
Monitoring MonitoringConfig `json:"monitoring,omitempty"`
}

// FulcioCert defines fields for system-generated certificate
// +kubebuilder:validation:XValidation:rule=(has(self.caRef) || self.commonName != ""),message=commonName cannot be empty
// +kubebuilder:validation:XValidation:rule=(!has(self.caRef) || has(self.privateKeyRef)),message=privateKeyRef cannot be empty
type FulcioCert struct {
// Reference to CA private key
//+optional
Expand All @@ -42,7 +44,8 @@ type FulcioCert struct {
}

type FulcioConfig struct {
OIDCIssuers map[string]OIDCIssuer `json:"OIDCIssuers,omitempty"`
//+kubebuilder:validation:MinProperties:=1
OIDCIssuers map[string]OIDCIssuer `json:"OIDCIssuers"`

// A meta issuer has a templated URL of the form:
// https://oidc.eks.*.amazonaws.com/id/*
Expand All @@ -51,16 +54,19 @@ type FulcioConfig struct {
// other special characters) Some examples we want to match:
// * https://oidc.eks.us-west-2.amazonaws.com/id/B02C93B6A2D30341AD01E1B6D48164CB
// * https://container.googleapis.com/v1/projects/mattmoor-credit/locations/us-west1-b/clusters/tenant-cluster
// +optional
MetaIssuers map[string]OIDCIssuer `json:"MetaIssuers,omitempty"`
}

type OIDCIssuer struct {
// The expected issuer of an OIDC token
IssuerURL string `json:"IssuerURL,omitempty"`
// The expected client ID of the OIDC token
//+required
ClientID string `json:"ClientID"`
// Used to determine the subject of the certificate and if additional
// certificate values are needed
//+required
Type string `json:"Type"`
// Optional, if the issuer is in a different claim in the OIDC token
IssuerClaim string `json:"IssuerClaim,omitempty"`
Expand All @@ -79,7 +85,7 @@ type OIDCIssuer struct {
// FulcioStatus defines the observed state of Fulcio
type FulcioStatus struct {
ServerConfigRef *v1.LocalObjectReference `json:"serverConfigRef,omitempty"`
Certificate FulcioCert `json:"certificate,omitempty"`
Certificate *FulcioCert `json:"certificate,omitempty"`
Url string `json:"url,omitempty"`
// +listType=map
// +listMapKey=type
Expand Down
Loading

0 comments on commit 42672b8

Please sign in to comment.