diff --git a/deploy/charts/ejbca-cert-manager-issuer/README.md b/deploy/charts/ejbca-cert-manager-issuer/README.md index d05c309..2644c79 100644 --- a/deploy/charts/ejbca-cert-manager-issuer/README.md +++ b/deploy/charts/ejbca-cert-manager-issuer/README.md @@ -25,25 +25,44 @@ helm repo update ### Install Chart -```bash -helm install ejbca-cert-manager-issuer ejbca-issuer/ejbca-cert-manager-issuer +```shell +helm install ejbca-cert-manager-issuer ejbca-issuer/ejbca-cert-manager-issuer \ + --namespace ejbca-issuer-system \ + --create-namespace \ + --set image.repository=/keyfactor/ejbca-cert-manager-issuer \ + --set image.tag= + # --set image.pullPolicy=Never # Only required if using a local image ``` -Modifications can be made by overriding the default values in the `values.yaml` file with the `--set` flag. For example, to override the `replicaCount` value, run the following ejbca: -```bash +Modifications can be made by overriding the default values in the `values.yaml` file with the `--set` flag. For example, to override the `secretConfig.useClusterRoleForSecretAccess` to configure the chart to use a cluster role for secret access, run the following command: + +```shell helm install ejbca-cert-manager-issuer ejbca-issuer/ejbca-cert-manager-issuer \ + --namespace ejbca-issuer-system \ + --create-namespace \ + --set image.repository=/keyfactor/ejbca-cert-manager-issuer \ + --set image.tag= --set replicaCount=2 ``` -Modifications can also be made by modifying the `values.yaml` file directly. For example, to override the `replicaCount` value, modify the `replicaCount` value in the `values.yaml` file: +Modifications can also be made by modifying the `values.yaml` file directly. For example, to override the `secretConfig.useClusterRoleForSecretAccess` value to configure the chart to use a cluster role for secret access, modify the `secretConfig.useClusterRoleForSecretAccess` value in the `values.yaml` file by creating an override file: + ```yaml cat < override.yaml -replicaCount: 2 +image: + repository: /keyfactor/ejbca-cert-manager-issuer + pullPolicy: Never + tag: "latest" +secretConfig: + useClusterRoleForSecretAccess: true EOF ``` + Then, use the `-f` flag to specify the `values.yaml` file: -```bash + +```shell helm install ejbca-cert-manager-issuer ejbca-issuer/ejbca-cert-manager-issuer \ + --namespace command-issuer-system \ -f override.yaml ``` @@ -51,24 +70,25 @@ helm install ejbca-cert-manager-issuer ejbca-issuer/ejbca-cert-manager-issuer \ The following table lists the configurable parameters of the `ejbca-cert-manager-issuer` chart and their default values. -| Parameter | Description | Default | -|-----------------------------------|-----------------------------------------------------|--------------------------------------------------------------| -| `replicaCount` | Number of replica ejbca-cert-manager-issuers to run | `1` | -| `image.repository` | Image repository | `m8rmclarenkf/ejbca-cert-manager-external-issuer-controller` | -| `image.pullPolicy` | Image pull policy | `IfNotPresent` | -| `image.tag` | Image tag | `v1.3.1` | -| `imagePullSecrets` | Image pull secrets | `[]` | -| `nameOverride` | Name override | `""` | -| `fullnameOverride` | Full name override | `""` | -| `crd.create` | Specifies if CRDs will be created | `true` | -| `crd.annotations` | Annotations to add to the CRD | `{}` | -| `serviceAccount.create` | Specifies if a service account should be created | `true` | -| `serviceAccount.annotations` | Annotations to add to the service account | `{}` | -| `serviceAccount.name` | Name of the service account to use | `""` (uses the fullname template if `create` is true) | -| `podAnnotations` | Annotations for the pod | `{}` | -| `podSecurityContext.runAsNonRoot` | Run pod as non-root | `true` | -| `securityContext` | Security context for the pod | `{}` (with commented out options) | -| `secureMetrics.enabled` | Enable secure metrics via the Kube RBAC Proy | `false` | -| `resources` | CPU/Memory resource requests/limits | `{}` (with commented out options) | -| `nodeSelector` | Node labels for pod assignment | `{}` | -| `tolerations` | Tolerations for pod assignment | `[]` | +| Parameter | Description | Default | +|----------------------------------------------|-----------------------------------------------------------------------------------------------------|--------------------------------------------------------------| +| `replicaCount` | Number of replica ejbca-cert-manager-issuers to run | `1` | +| `image.repository` | Image repository | `m8rmclarenkf/ejbca-cert-manager-external-issuer-controller` | +| `image.pullPolicy` | Image pull policy | `IfNotPresent` | +| `image.tag` | Image tag | `v1.3.1` | +| `imagePullSecrets` | Image pull secrets | `[]` | +| `nameOverride` | Name override | `""` | +| `fullnameOverride` | Full name override | `""` | +| `crd.create` | Specifies if CRDs will be created | `true` | +| `crd.annotations` | Annotations to add to the CRD | `{}` | +| `serviceAccount.create` | Specifies if a service account should be created | `true` | +| `serviceAccount.annotations` | Annotations to add to the service account | `{}` | +| `serviceAccount.name` | Name of the service account to use | `""` (uses the fullname template if `create` is true) | +| `podAnnotations` | Annotations for the pod | `{}` | +| `podSecurityContext.runAsNonRoot` | Run pod as non-root | `true` | +| `securityContext` | Security context for the pod | `{}` (with commented out options) | +| `secureMetrics.enabled` | Enable secure metrics via the Kube RBAC Proy | `false` | +| `resources` | CPU/Memory resource requests/limits | `{}` (with commented out options) | +| `nodeSelector` | Node labels for pod assignment | `{}` | +| `tolerations` | Tolerations for pod assignment | `[]` | +| `secretConfig.useClusterRoleForSecretAccess` | Specifies if the ServiceAccount should be granted access to the Secret resource using a ClusterRole | `false` | \ No newline at end of file diff --git a/deploy/charts/ejbca-cert-manager-issuer/templates/deployment.yaml b/deploy/charts/ejbca-cert-manager-issuer/templates/deployment.yaml index 2707d04..18284e9 100644 --- a/deploy/charts/ejbca-cert-manager-issuer/templates/deployment.yaml +++ b/deploy/charts/ejbca-cert-manager-issuer/templates/deployment.yaml @@ -55,6 +55,9 @@ spec: - --health-probe-bind-address=:8081 - --metrics-bind-address=127.0.0.1:8080 - --leader-elect + {{- if .Values.secretConfig.useClusterRoleForSecretAccess}} + - --secret-access-granted-at-cluster-level + {{- end}} command: - /manager image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" diff --git a/deploy/charts/ejbca-cert-manager-issuer/values.yaml b/deploy/charts/ejbca-cert-manager-issuer/values.yaml index 881005b..ba06a35 100644 --- a/deploy/charts/ejbca-cert-manager-issuer/values.yaml +++ b/deploy/charts/ejbca-cert-manager-issuer/values.yaml @@ -18,6 +18,16 @@ fullnameOverride: "" secureMetrics: enabled: false +secretConfig: + # If true, when using Issuer resources, the credential secret must be created in the same namespace as the + # Issuer resource. This access is facilitated by granting the ServiceAccount [get, list, watch] for the secret + # API at the cluster level. + # + # If false, both Issuer and ClusterIssuer must reference a secret in the same namespace as the chart/reconciler. + # This access is facilitated by granting the ServiceAccount [get, list, watch] for the secret API only for the + # namespace the chart is deployed in. + useClusterRoleForSecretAccess: false + crd: # Specifies whether CRDs will be created create: true diff --git a/docs/config_usage.md b/docs/config_usage.md index 8194f54..cd28c0f 100644 --- a/docs/config_usage.md +++ b/docs/config_usage.md @@ -10,7 +10,7 @@ The cert-manager external issuer for Keyfactor EJBCA can be used to issue certificates from Keyfactor EJBCA using cert-manager. ### Authentication -Authentication to the EJBCA platform is done using a client certificate and key. The client certificate and key must be provided as a Kubernetes secret. +Authentication to the EJBCA platform is done using a client certificate and key. The client certificate and key must be provided as a Kubernetes secret. If the Helm chart was deployed with the `--set "secretConfig.useClusterRoleForSecretAccess=true"` flag, the secret must be created in the same namespace as any Issuer resources deployed. Otherwise, the secret must be created in the same namespace as the controller. Create a K8s TLS secret containing the client certificate and key to authenticate with EJBCA: ```shell diff --git a/docs/install.md b/docs/install.md index 100c9de..8380361 100644 --- a/docs/install.md +++ b/docs/install.md @@ -53,55 +53,53 @@ The cert-manager external issuer for Keyfactor EJBCA can also be installed using 1. Add the Helm repository: - ```bash + ```shell helm repo add ejbca-issuer https://keyfactor.github.io/ejbca-cert-manager-issuer helm repo update ``` 2. Then, install the chart: - ```bash - helm install ejbca-cert-manager-issuer ejbca-issuer/ejbca-cert-manager-issuer \ - --namespace ejbca-issuer-system \ - --create-namespace \ - --set image.repository=/keyfactor/ejbca-cert-manager-issuer \ - --set image.tag= - # --set image.pullPolicy=Never # Only required if using a local image - ``` - - a. Modifications can be made by overriding the default values in the `values.yaml` file with the `--set` flag. For example, to override the `replicaCount` value, run the following command: - ```shell helm install ejbca-cert-manager-issuer ejbca-issuer/ejbca-cert-manager-issuer \ --namespace ejbca-issuer-system \ --create-namespace \ --set image.repository=/keyfactor/ejbca-cert-manager-issuer \ --set image.tag= - --set replicaCount=2 - ``` - - b. Modifications can also be made by modifying the `values.yaml` file directly. For example, to override the - `replicaCount` value, modify the `replicaCount` value in the `values.yaml` file: - - ```yaml - cat < override.yaml - image: - repository: /keyfactor/ejbca-cert-manager-issuer - pullPolicy: Never - tag: "latest" - replicaCount: 2 - EOF + # --set image.pullPolicy=Never # Only required if using a local image ``` - Then, use the `-f` flag to specify the `values.yaml` file: - - ```yaml - helm install ejbca-cert-manager-issuer ejbca-issuer/ejbca-cert-manager-issuer \ - -f override.yaml - ``` + 1. Modifications can be made by overriding the default values in the `values.yaml` file with the `--set` flag. For example, to override the `secretConfig.useClusterRoleForSecretAccess` to configure the chart to use a cluster role for secret access, run the following command: + + ```shell + helm install ejbca-cert-manager-issuer ejbca-issuer/ejbca-cert-manager-issuer \ + --namespace ejbca-issuer-system \ + --create-namespace \ + --set image.repository=/keyfactor/ejbca-cert-manager-issuer \ + --set image.tag= + --set replicaCount=2 + ``` + + 2. Modifications can also be made by modifying the `values.yaml` file directly. For example, to override the `secretConfig.useClusterRoleForSecretAccess` value to configure the chart to use a cluster role for secret access, modify the `secretConfig.useClusterRoleForSecretAccess` value in the `values.yaml` file by creating an override file: + + ```yaml + cat < override.yaml + image: + repository: /keyfactor/ejbca-cert-manager-issuer + pullPolicy: Never + tag: "latest" + secretConfig: + useClusterRoleForSecretAccess: true + EOF + ``` + + Then, use the `-f` flag to specify the `values.yaml` file: + + ```shell + helm install ejbca-cert-manager-issuer ejbca-issuer/ejbca-cert-manager-issuer \ + --namespace command-issuer-system \ + -f override.yaml + ``` Next, complete the [Usage](config_usage.md) steps to configure the cert-manager external issuer for Keyfactor EJBCA. - - - diff --git a/internal/controllers/certificaterequest_controller.go b/internal/controllers/certificaterequest_controller.go index f48e760..060fd95 100644 --- a/internal/controllers/certificaterequest_controller.go +++ b/internal/controllers/certificaterequest_controller.go @@ -51,8 +51,9 @@ type CertificateRequestReconciler struct { SignerBuilder signer.EjbcaSignerBuilder ClusterResourceNamespace string - Clock clock.Clock - CheckApprovedCondition bool + Clock clock.Clock + CheckApprovedCondition bool + SecretAccessGrantedAtClusterLevel bool } // +kubebuilder:rbac:groups=cert-manager.io,resources=certificaterequests,verbs=get;list;watch @@ -177,6 +178,11 @@ func (r *CertificateRequestReconciler) Reconcile(ctx context.Context, req ctrl.R return ctrl.Result{}, nil } + // If SecretAccessGrantedAtClusterLevel is false, we always look for the Secret in the same namespace as the Issuer + if !r.SecretAccessGrantedAtClusterLevel { + secretNamespace = r.ClusterResourceNamespace + } + // Get the Issuer or ClusterIssuer if err := r.Get(ctx, issuerName, issuer); err != nil { return ctrl.Result{}, fmt.Errorf("%w: %v", errGetIssuer, err) diff --git a/internal/controllers/certificaterequest_controller_test.go b/internal/controllers/certificaterequest_controller_test.go index 1687556..56c1cd6 100644 --- a/internal/controllers/certificaterequest_controller_test.go +++ b/internal/controllers/certificaterequest_controller_test.go @@ -605,12 +605,13 @@ func TestCertificateRequestReconcile(t *testing.T) { WithObjects(tc.objects...). Build() controller := CertificateRequestReconciler{ - Client: fakeClient, - Scheme: scheme, - ClusterResourceNamespace: tc.clusterResourceNamespace, - SignerBuilder: tc.Builder, - CheckApprovedCondition: true, - Clock: fixedClock, + Client: fakeClient, + Scheme: scheme, + ClusterResourceNamespace: tc.clusterResourceNamespace, + SignerBuilder: tc.Builder, + CheckApprovedCondition: true, + Clock: fixedClock, + SecretAccessGrantedAtClusterLevel: true, } result, err := controller.Reconcile( ctrl.LoggerInto(context.TODO(), logrtesting.NewTestLogger(t)), diff --git a/internal/controllers/issuer_controller.go b/internal/controllers/issuer_controller.go index de64cd6..e5dab0c 100644 --- a/internal/controllers/issuer_controller.go +++ b/internal/controllers/issuer_controller.go @@ -48,10 +48,11 @@ var ( // IssuerReconciler reconciles a Issuer object type IssuerReconciler struct { client.Client - Kind string - Scheme *runtime.Scheme - ClusterResourceNamespace string - HealthCheckerBuilder signer.HealthCheckerBuilder + Kind string + Scheme *runtime.Scheme + ClusterResourceNamespace string + HealthCheckerBuilder signer.HealthCheckerBuilder + SecretAccessGrantedAtClusterLevel bool } //+kubebuilder:rbac:groups=ejbca-issuer.keyfactor.com,resources=issuers;clusterissuers,verbs=get;list;watch @@ -124,6 +125,11 @@ func (r *IssuerReconciler) Reconcile(ctx context.Context, req ctrl.Request) (res return ctrl.Result{}, nil } + // If SecretAccessGrantedAtClusterLevel is false, we always look for the Secret in the same namespace as the Issuer + if !r.SecretAccessGrantedAtClusterLevel { + secretName.Namespace = r.ClusterResourceNamespace + } + var authSecret corev1.Secret if err := r.Get(ctx, secretName, &authSecret); err != nil { return ctrl.Result{}, fmt.Errorf("%w, secret name: %s, reason: %v", errGetAuthSecret, secretName, err) diff --git a/internal/controllers/issuer_controller_test.go b/internal/controllers/issuer_controller_test.go index 30ee66d..9c95b9d 100644 --- a/internal/controllers/issuer_controller_test.go +++ b/internal/controllers/issuer_controller_test.go @@ -251,11 +251,12 @@ func TestIssuerReconcile(t *testing.T) { tc.kind = "Issuer" } controller := IssuerReconciler{ - Kind: tc.kind, - Client: fakeClient, - Scheme: scheme, - HealthCheckerBuilder: tc.healthCheckerBuilder, - ClusterResourceNamespace: tc.clusterResourceNamespace, + Kind: tc.kind, + Client: fakeClient, + Scheme: scheme, + HealthCheckerBuilder: tc.healthCheckerBuilder, + ClusterResourceNamespace: tc.clusterResourceNamespace, + SecretAccessGrantedAtClusterLevel: true, } result, err := controller.Reconcile( ctrl.LoggerInto(context.TODO(), logrtesting.NewTestLogger(t)), diff --git a/main.go b/main.go index 3518f78..a0401e4 100644 --- a/main.go +++ b/main.go @@ -64,6 +64,7 @@ func main() { var clusterResourceNamespace string var printVersion bool var disableApprovedCheck bool + var secretAccessGrantedAtClusterLevel bool flag.StringVar(&metricsAddr, "metrics-bind-address", ":8080", "The address the metric endpoint binds to.") flag.StringVar(&probeAddr, "health-probe-bind-address", ":8081", "The address the probe endpoint binds to.") @@ -74,6 +75,8 @@ func main() { flag.BoolVar(&printVersion, "version", false, "Print version to stdout and exit") flag.BoolVar(&disableApprovedCheck, "disable-approved-check", false, "Disables waiting for CertificateRequests to have an approved condition before signing.") + flag.BoolVar(&secretAccessGrantedAtClusterLevel, "secret-access-granted-at-cluster-level", false, + "Set this flag to true if the secret access is granted at cluster level. This will allow the controller to access secrets in any namespace. ") opts := zap.Options{ Development: true, @@ -96,6 +99,12 @@ func main() { } } + if secretAccessGrantedAtClusterLevel { + setupLog.Info("expecting secret access at cluster level") + } else { + setupLog.Info(fmt.Sprintf("expecting secret access at namespace level (%s)", clusterResourceNamespace)) + } + mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{ Scheme: scheme, MetricsBindAddress: metricsAddr, @@ -121,32 +130,35 @@ func main() { } if err = (&controllers.IssuerReconciler{ - Kind: "Issuer", - Client: mgr.GetClient(), - Scheme: mgr.GetScheme(), - ClusterResourceNamespace: clusterResourceNamespace, - HealthCheckerBuilder: signer.EjbcaHealthCheckerFromIssuerAndSecretData, + Kind: "Issuer", + Client: mgr.GetClient(), + Scheme: mgr.GetScheme(), + ClusterResourceNamespace: clusterResourceNamespace, + HealthCheckerBuilder: signer.EjbcaHealthCheckerFromIssuerAndSecretData, + SecretAccessGrantedAtClusterLevel: secretAccessGrantedAtClusterLevel, }).SetupWithManager(mgr); err != nil { setupLog.Error(err, "unable to create controller", "controller", "Issuer") os.Exit(1) } if err = (&controllers.IssuerReconciler{ - Kind: "ClusterIssuer", - Client: mgr.GetClient(), - Scheme: mgr.GetScheme(), - ClusterResourceNamespace: clusterResourceNamespace, - HealthCheckerBuilder: signer.EjbcaHealthCheckerFromIssuerAndSecretData, + Kind: "ClusterIssuer", + Client: mgr.GetClient(), + Scheme: mgr.GetScheme(), + ClusterResourceNamespace: clusterResourceNamespace, + HealthCheckerBuilder: signer.EjbcaHealthCheckerFromIssuerAndSecretData, + SecretAccessGrantedAtClusterLevel: secretAccessGrantedAtClusterLevel, }).SetupWithManager(mgr); err != nil { setupLog.Error(err, "unable to create controller", "controller", "ClusterIssuer") os.Exit(1) } if err = (&controllers.CertificateRequestReconciler{ - Client: mgr.GetClient(), - Scheme: mgr.GetScheme(), - ClusterResourceNamespace: clusterResourceNamespace, - SignerBuilder: signer.EjbcaSignerFromIssuerAndSecretData, - CheckApprovedCondition: !disableApprovedCheck, - Clock: clock.RealClock{}, + Client: mgr.GetClient(), + Scheme: mgr.GetScheme(), + ClusterResourceNamespace: clusterResourceNamespace, + SignerBuilder: signer.EjbcaSignerFromIssuerAndSecretData, + CheckApprovedCondition: !disableApprovedCheck, + Clock: clock.RealClock{}, + SecretAccessGrantedAtClusterLevel: secretAccessGrantedAtClusterLevel, }).SetupWithManager(mgr); err != nil { setupLog.Error(err, "unable to create controller", "controller", "CertificateRequest") os.Exit(1)