From 47b792371c79621828916d72a5185179fb99bffc Mon Sep 17 00:00:00 2001 From: Firas Ghanmi Date: Mon, 17 Jun 2024 17:46:01 +0200 Subject: [PATCH] inject certificates if proxy is enabled --- api/v1alpha1/fulcio_types.go | 9 + api/v1alpha1/fulcio_types_test.go | 5 + api/v1alpha1/zz_generated.deepcopy.go | 16 ++ .../crd/bases/rhtas.redhat.com_fulcios.yaml | 15 ++ .../bases/rhtas.redhat.com_securesigns.yaml | 15 ++ config/samples/rhtas_v1alpha1_fulcio.yaml | 2 + config/samples/rhtas_v1alpha1_securesign.yaml | 2 + .../controller/fulcio/actions/configMap.go | 71 ++++++++ .../controller/fulcio/fulcio_controller.go | 1 + .../fulcio/fulcio_controller_test.go | 9 + .../fulcio/utils/fulcio_configMap.go | 18 ++ .../fulcio/utils/fulcio_deployment.go | 165 ++++++++++-------- 12 files changed, 257 insertions(+), 71 deletions(-) create mode 100644 internal/controller/fulcio/actions/configMap.go create mode 100644 internal/controller/fulcio/utils/fulcio_configMap.go diff --git a/api/v1alpha1/fulcio_types.go b/api/v1alpha1/fulcio_types.go index 3dc0ddbbe..429943af4 100644 --- a/api/v1alpha1/fulcio_types.go +++ b/api/v1alpha1/fulcio_types.go @@ -55,6 +55,9 @@ type FulcioConfig struct { // +optional OIDCIssuers []OIDCIssuer `json:"OIDCIssuers,omitempty"` + // Define whether you want to use cluster-wide proxy or not + Proxy Proxy `json:"proxy,omitempty"` + // A meta issuer has a templated URL of the form: // https://oidc.eks.*.amazonaws.com/id/* // Where * can match a single hostname or URI path parts @@ -65,6 +68,12 @@ type FulcioConfig struct { // +optional MetaIssuers []OIDCIssuer `json:"MetaIssuers,omitempty"` } +type Proxy struct { + // If set to true, the Operator will create a configMap containing certificates bundle. + //+kubebuilder:validation:XValidation:rule=(self || !oldSelf),message=Feature cannot be disabled + //+kubebuilder:default:=false + Enabled bool `json:"enabled"` +} type OIDCIssuer struct { // The expected issuer of an OIDC token diff --git a/api/v1alpha1/fulcio_types_test.go b/api/v1alpha1/fulcio_types_test.go index df0275ecc..87db6ad6b 100644 --- a/api/v1alpha1/fulcio_types_test.go +++ b/api/v1alpha1/fulcio_types_test.go @@ -120,6 +120,7 @@ var _ = Describe("Fulcio", func() { It("config is not empty", func() { invalidObject := generateFulcioObject("config-invalid") invalidObject.Spec.Config.OIDCIssuers = []OIDCIssuer{} + invalidObject.Spec.Config.Proxy = Proxy{Enabled: false} invalidObject.Spec.Config.MetaIssuers = []OIDCIssuer{} Expect(apierrors.IsInvalid(k8sClient.Create(context.Background(), invalidObject))).To(BeTrue()) @@ -130,6 +131,7 @@ var _ = Describe("Fulcio", func() { It("only MetaIssuer is set", func() { validObject := generateFulcioObject("config-metaissuer") validObject.Spec.Config.OIDCIssuers = []OIDCIssuer{} + validObject.Spec.Config.Proxy = Proxy{Enabled: false} validObject.Spec.Config.MetaIssuers = []OIDCIssuer{ { ClientID: "client", @@ -237,6 +239,9 @@ func generateFulcioObject(name string) *Fulcio { Issuer: "url", }, }, + Proxy: Proxy{ + Enabled: false, + }, MetaIssuers: []OIDCIssuer{ { ClientID: "client", diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index 90e5fbe28..5b6f73a03 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -277,6 +277,7 @@ func (in *FulcioConfig) DeepCopyInto(out *FulcioConfig) { *out = make([]OIDCIssuer, len(*in)) copy(*out, *in) } + out.Proxy = in.Proxy if in.MetaIssuers != nil { in, out := &in.MetaIssuers, &out.MetaIssuers *out = make([]OIDCIssuer, len(*in)) @@ -427,6 +428,21 @@ func (in *OIDCIssuer) DeepCopy() *OIDCIssuer { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Proxy) DeepCopyInto(out *Proxy) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Proxy. +func (in *Proxy) DeepCopy() *Proxy { + if in == nil { + return nil + } + out := new(Proxy) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Pvc) DeepCopyInto(out *Pvc) { *out = *in diff --git a/config/crd/bases/rhtas.redhat.com_fulcios.yaml b/config/crd/bases/rhtas.redhat.com_fulcios.yaml index d8c31fe0b..6c887a5f7 100644 --- a/config/crd/bases/rhtas.redhat.com_fulcios.yaml +++ b/config/crd/bases/rhtas.redhat.com_fulcios.yaml @@ -216,6 +216,21 @@ spec: - Type type: object type: array + proxy: + description: Define whether you want to use cluster-wide proxy + or not + properties: + enabled: + default: false + description: If set to true, the Operator will create a configMap + containing certificates bundle. + type: boolean + x-kubernetes-validations: + - message: Feature cannot be disabled + rule: (self || !oldSelf) + required: + - enabled + type: object type: object x-kubernetes-validations: - message: At least one of OIDCIssuers or MetaIssuers must be defined diff --git a/config/crd/bases/rhtas.redhat.com_securesigns.yaml b/config/crd/bases/rhtas.redhat.com_securesigns.yaml index d8b1df23e..98c52ead4 100644 --- a/config/crd/bases/rhtas.redhat.com_securesigns.yaml +++ b/config/crd/bases/rhtas.redhat.com_securesigns.yaml @@ -336,6 +336,21 @@ spec: - Type type: object type: array + proxy: + description: Define whether you want to use cluster-wide proxy + or not + properties: + enabled: + default: false + description: If set to true, the Operator will create + a configMap containing certificates bundle. + type: boolean + x-kubernetes-validations: + - message: Feature cannot be disabled + rule: (self || !oldSelf) + required: + - enabled + type: object type: object x-kubernetes-validations: - message: At least one of OIDCIssuers or MetaIssuers must be diff --git a/config/samples/rhtas_v1alpha1_fulcio.yaml b/config/samples/rhtas_v1alpha1_fulcio.yaml index aa8f63dff..95678d60c 100644 --- a/config/samples/rhtas_v1alpha1_fulcio.yaml +++ b/config/samples/rhtas_v1alpha1_fulcio.yaml @@ -10,6 +10,8 @@ spec: externalAccess: enabled: true config: + proxy: + enabled: false OIDCIssuers: - ClientID: "trusted-artifact-signer" IssuerURL: "https://your-oidc-issuer-url" diff --git a/config/samples/rhtas_v1alpha1_securesign.yaml b/config/samples/rhtas_v1alpha1_securesign.yaml index 4b75b4fb4..cd9ffbd70 100644 --- a/config/samples/rhtas_v1alpha1_securesign.yaml +++ b/config/samples/rhtas_v1alpha1_securesign.yaml @@ -21,6 +21,8 @@ spec: externalAccess: enabled: true config: + proxy: + enabled: false OIDCIssuers: - ClientID: "trusted-artifact-signer" IssuerURL: "https://your-oidc-issuer-url" diff --git a/internal/controller/fulcio/actions/configMap.go b/internal/controller/fulcio/actions/configMap.go new file mode 100644 index 000000000..ee1da4f52 --- /dev/null +++ b/internal/controller/fulcio/actions/configMap.go @@ -0,0 +1,71 @@ +package actions + +import ( + "context" + "fmt" + + rhtasv1alpha1 "github.com/securesign/operator/api/v1alpha1" + "github.com/securesign/operator/internal/controller/common/action" + "github.com/securesign/operator/internal/controller/constants" + futils "github.com/securesign/operator/internal/controller/fulcio/utils" + "k8s.io/apimachinery/pkg/api/meta" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" +) + +func NewConfigMapAction() action.Action[rhtasv1alpha1.Fulcio] { + return &configMapAction{} +} + +type configMapAction struct { + action.BaseAction +} + +func (i configMapAction) Name() string { + return "configmap" +} + +func (i configMapAction) CanHandle(_ context.Context, instance *rhtasv1alpha1.Fulcio) bool { + c := meta.FindStatusCondition(instance.Status.Conditions, constants.Ready) + return (c.Reason == constants.Creating || c.Reason == constants.Ready) && instance.Spec.Config.Proxy.Enabled +} + +func (i configMapAction) Handle(ctx context.Context, instance *rhtasv1alpha1.Fulcio) *action.Result { + var ( + updated bool + err error + ) + + cm, err := futils.CreateConfigMap(instance, "ca-inject") + if err != nil { + meta.SetStatusCondition(&instance.Status.Conditions, metav1.Condition{ + Type: constants.Ready, + Status: metav1.ConditionFalse, + Reason: constants.Failure, + Message: err.Error(), + }) + return i.FailedWithStatusUpdate(ctx, fmt.Errorf("could create ConfigMap: %w", err), instance) + } + + if err = controllerutil.SetControllerReference(instance, cm, i.Client.Scheme()); err != nil { + return i.Failed(fmt.Errorf("could not set controller reference for ConfigMap: %w", err)) + } + + if updated, err = i.Ensure(ctx, cm); err != nil { + meta.SetStatusCondition(&instance.Status.Conditions, metav1.Condition{ + Type: constants.Ready, + Status: metav1.ConditionFalse, + Reason: constants.Failure, + Message: err.Error(), + }) + return i.FailedWithStatusUpdate(ctx, fmt.Errorf("could not create Fulcio ConfigMap: %w", err), instance) + } + + if updated { + meta.SetStatusCondition(&instance.Status.Conditions, metav1.Condition{Type: constants.Ready, + Status: metav1.ConditionFalse, Reason: constants.Creating, Message: "ConfigMap created"}) + return i.StatusUpdate(ctx, instance) + } else { + return i.Continue() + } +} diff --git a/internal/controller/fulcio/fulcio_controller.go b/internal/controller/fulcio/fulcio_controller.go index 1649ab7d0..71c6c9319 100644 --- a/internal/controller/fulcio/fulcio_controller.go +++ b/internal/controller/fulcio/fulcio_controller.go @@ -92,6 +92,7 @@ func (r *FulcioReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctr actions.NewHandleCertAction(), actions.NewRBACAction(), actions.NewServerConfigAction(), + actions.NewConfigMapAction(), actions.NewDeployAction(), actions.NewCreateMonitorAction(), actions.NewServiceAction(), diff --git a/internal/controller/fulcio/fulcio_controller_test.go b/internal/controller/fulcio/fulcio_controller_test.go index a04ad62c6..7b9da2ddd 100644 --- a/internal/controller/fulcio/fulcio_controller_test.go +++ b/internal/controller/fulcio/fulcio_controller_test.go @@ -104,6 +104,9 @@ var _ = Describe("Fulcio controller", func() { Type: "email", }, }, + Proxy: v1alpha1.Proxy{ + Enabled: true, + }, }, Certificate: v1alpha1.FulcioCert{ OrganizationName: "MyOrg", @@ -219,6 +222,12 @@ var _ = Describe("Fulcio controller", func() { Expect(ingress.Spec.Rules[0].IngressRuleValue.HTTP.Paths[0].Backend.Service.Name).Should(Equal(service.Name)) Expect(ingress.Spec.Rules[0].IngressRuleValue.HTTP.Paths[0].Backend.Service.Port.Name).Should(Equal("80-tcp")) + By("Checking if ConfigMap was successfully created in the reconciliation") + configMap := &corev1.ConfigMap{} + Eventually(func() error { + return k8sClient.Get(ctx, types.NamespacedName{Name: "ca-inject", Namespace: Namespace}, configMap) + }).Should(Succeed()) + By("Checking if controller will return deployment to desired state") deployment = &appsv1.Deployment{} Eventually(func() error { diff --git a/internal/controller/fulcio/utils/fulcio_configMap.go b/internal/controller/fulcio/utils/fulcio_configMap.go new file mode 100644 index 000000000..dc0887e58 --- /dev/null +++ b/internal/controller/fulcio/utils/fulcio_configMap.go @@ -0,0 +1,18 @@ +package utils + +import ( + "github.com/securesign/operator/api/v1alpha1" + corev1 "k8s.io/api/core/v1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +func CreateConfigMap(instance *v1alpha1.Fulcio, configMapName string) (*corev1.ConfigMap, error) { + + return &corev1.ConfigMap{ + ObjectMeta: v1.ObjectMeta{ + Name: configMapName, + Namespace: instance.Namespace, + Labels: map[string]string{"config.openshift.io/inject-trusted-cabundle": "true"}, + }, + }, nil +} diff --git a/internal/controller/fulcio/utils/fulcio_deployment.go b/internal/controller/fulcio/utils/fulcio_deployment.go index ee4838881..cc94a6629 100644 --- a/internal/controller/fulcio/utils/fulcio_deployment.go +++ b/internal/controller/fulcio/utils/fulcio_deployment.go @@ -88,6 +88,98 @@ func CreateDeployment(instance *v1alpha1.Fulcio, deploymentName string, sa strin }) } + volumes := []corev1.Volume{ + { + Name: "fulcio-config", + VolumeSource: corev1.VolumeSource{ + ConfigMap: &corev1.ConfigMapVolumeSource{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: instance.Status.ServerConfigRef.Name, + }, + }, + }, + }, + { + Name: "oidc-info", + VolumeSource: corev1.VolumeSource{ + Projected: &corev1.ProjectedVolumeSource{ + Sources: oidcInfo, + }, + }, + }, + { + Name: "fulcio-cert", + VolumeSource: corev1.VolumeSource{ + Projected: &corev1.ProjectedVolumeSource{ + Sources: []corev1.VolumeProjection{ + { + Secret: &corev1.SecretProjection{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: instance.Status.Certificate.PrivateKeyRef.Name, + }, + Items: []corev1.KeyToPath{ + { + Key: instance.Status.Certificate.PrivateKeyRef.Key, + Path: "key.pem", + }, + }, + }, + }, + { + Secret: &corev1.SecretProjection{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: instance.Status.Certificate.CARef.Name, + }, + Items: []corev1.KeyToPath{ + { + Key: instance.Status.Certificate.CARef.Key, + Path: "cert.pem", + }, + }, + }, + }, + }, + }, + }, + }, + } + + volumeMounts := []corev1.VolumeMount{ + { + Name: "fulcio-config", + MountPath: "/etc/fulcio-config", + }, + { + Name: "oidc-info", + MountPath: "/var/run/fulcio", + ReadOnly: true, + }, + { + Name: "fulcio-cert", + MountPath: "/var/run/fulcio-secrets", + ReadOnly: true, + }, + } + + if instance.Spec.Config.Proxy.Enabled { + volumes = append(volumes, corev1.Volume{ + Name: "trusted-ca", + VolumeSource: corev1.VolumeSource{ + ConfigMap: &corev1.ConfigMapVolumeSource{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: "ca-inject", + }, + }, + }, + }) + + volumeMounts = append(volumeMounts, corev1.VolumeMount{ + Name: "trusted-ca", + MountPath: "/etc/ssl/certs", + ReadOnly: true, + }) + } + return &appsv1.Deployment{ ObjectMeta: metav1.ObjectMeta{ Name: deploymentName, @@ -152,80 +244,11 @@ func CreateDeployment(instance *v1alpha1.Fulcio, deploymentName string, sa strin SuccessThreshold: 1, FailureThreshold: 3, }, - VolumeMounts: []corev1.VolumeMount{ - { - Name: "fulcio-config", - MountPath: "/etc/fulcio-config", - }, - { - Name: "oidc-info", - MountPath: "/var/run/fulcio", - ReadOnly: true, - }, - { - Name: "fulcio-cert", - MountPath: "/var/run/fulcio-secrets", - ReadOnly: true, - }, - }, + VolumeMounts: volumeMounts, }, }, AutomountServiceAccountToken: &[]bool{true}[0], - Volumes: []corev1.Volume{ - { - Name: "fulcio-config", - VolumeSource: corev1.VolumeSource{ - ConfigMap: &corev1.ConfigMapVolumeSource{ - LocalObjectReference: corev1.LocalObjectReference{ - Name: instance.Status.ServerConfigRef.Name, - }, - }, - }, - }, - { - Name: "oidc-info", - VolumeSource: corev1.VolumeSource{ - Projected: &corev1.ProjectedVolumeSource{ - Sources: oidcInfo, - }, - }, - }, - { - Name: "fulcio-cert", - VolumeSource: corev1.VolumeSource{ - Projected: &corev1.ProjectedVolumeSource{ - Sources: []corev1.VolumeProjection{ - { - Secret: &corev1.SecretProjection{ - LocalObjectReference: corev1.LocalObjectReference{ - Name: instance.Status.Certificate.PrivateKeyRef.Name, - }, - Items: []corev1.KeyToPath{ - { - Key: instance.Status.Certificate.PrivateKeyRef.Key, - Path: "key.pem", - }, - }, - }, - }, - { - Secret: &corev1.SecretProjection{ - LocalObjectReference: corev1.LocalObjectReference{ - Name: instance.Status.Certificate.CARef.Name, - }, - Items: []corev1.KeyToPath{ - { - Key: instance.Status.Certificate.CARef.Key, - Path: "cert.pem", - }, - }, - }, - }, - }, - }, - }, - }, - }, + Volumes: volumes, }, }, },