Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add API validation rules for CTlog and Fulcio resources #236

Merged
merged 1 commit into from
Mar 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading