From 5b61248bd444ad1b001ce9593337f3da43b341a7 Mon Sep 17 00:00:00 2001 From: Hayden Roszell Date: Tue, 3 Oct 2023 17:43:25 -0700 Subject: [PATCH 01/17] chore: Create release workflow, make rbac configurable in Chart, adjust for linting --- .github/workflows/test.yml | 42 +++++++++++++++++++ Makefile | 2 +- README.md | 8 ++-- .../templates/clusterrole.yaml | 4 +- .../templates/clusterrolebinding.yaml | 4 +- .../templates/deployment.yaml | 2 + .../templates/service.yaml | 4 +- .../command-cert-manager-issuer/values.yaml | 5 +++ .../certificaterequest_controller.go | 5 +-- .../certificaterequest_controller_test.go | 7 +--- internal/issuer/signer/signer.go | 21 ++-------- internal/issuer/signer/signer_test.go | 6 +-- 12 files changed, 74 insertions(+), 36 deletions(-) create mode 100644 .github/workflows/test.yml diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..3887025 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,42 @@ +name: test +on: [workflow_dispatch, push, pull_request] +jobs: + build: + name: Build and Lint + runs-on: ubuntu-latest + timeout-minutes: 5 + steps: + - uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0 + - uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe # v4.1.0 + with: + go-version-file: 'go.mod' + cache: true + - run: go mod download + - run: go build -v . + - name: Run linters + uses: golangci/golangci-lint-action@08e2f20817b15149a52b5b3ebe7de50aff2ba8c5 # v3.4.0 + with: + version: latest + test: + name: Go Test + needs: build + runs-on: ubuntu-latest + timeout-minutes: 5 + steps: + - name: Checkout + uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0 + - name: Set up Go 1.x + uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe # v4.1.0 + with: + go-version-file: 'go.mod' + cache: true + - run: go mod download + - env: + COMMAND_CERTIFICATE_AUTHORITY_HOSTNAME: ${{ secrets.COMMAND_CERTIFICATE_AUTHORITY_HOSTNAME }} + COMMAND_CERTIFICATE_AUTHORITY_LOGICAL_NAME: ${{ secrets.COMMAND_CERTIFICATE_AUTHORITY_LOGICAL_NAME }} + COMMAND_CERTIFICATE_TEMPLATE: ${{ secrets.COMMAND_CERTIFICATE_TEMPLATE }} + COMMAND_HOSTNAME: ${{ secrets.COMMAND_HOSTNAME }} + COMMAND_USERNAME: ${{ secrets.COMMAND_USERNAME }} + COMMAND_PASSWORD: ${{ secrets.COMMAND_PASSWORD }} + name: Run go test + run: go test -v ./... \ No newline at end of file diff --git a/Makefile b/Makefile index e7ebbd4..89f768e 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ # The version which will be reported by the --version argument of each binary # and which will be used as the Docker image tag -VERSION ?= 1.0.3 +VERSION ?= 1.0.4 # The Docker repository name, overridden in CI. DOCKER_REGISTRY ?= m8rmclarenkf DOCKER_IMAGE_NAME ?= command-cert-manager-external-issuer-controller diff --git a/README.md b/README.md index 91e5ee1..eda1a3e 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ The cert-manager external issuer for Keyfactor command is open source and commun ## Quick Start The quick start guide will walk you through the process of installing the cert-manager external issuer for Keyfactor Command. -The controller image is pulled from [Docker Hub](https://hub.docker.com/r/m8rmclarenkf/command-external-issuer). +The controller image is pulled from [Docker Hub](https://hub.docker.com/r/m8rmclarenkf/command-cert-manager-external-issuer-controller). ###### To build the container from sources, refer to the [Building Container Image from Source](#building-container-image-from-source) section. @@ -120,7 +120,7 @@ The `spec` field of both the Issuer and ClusterIssuer resources use the followin * `certificateTemplate` - The short name corresponding to a template in Command that will be used to issue certificates. * `certificateAuthorityLogicalName` - The logical name of the CA to use to sign the certificate request * `certificateAuthorityHostname` - The CAs hostname to use to sign the certificate request -* `caSecretName` - The name of the Kubernetes secret containing the CA certificate. This field is optional and only required if the Command server is configured to use a self-signed certificate or with a certificate signed by an untrusted root. +* `caBundleSecretName` - The name of the Kubernetes secret containing the CA certificate. This field is optional and only required if the Command server is configured to use a self-signed certificate or with a certificate signed by an untrusted root. ###### If a different combination of hostname/certificate authority/certificate profile/end entity profile is required, a new Issuer or ClusterIssuer resource must be created. Each resource instantiation represents a single configuration. @@ -142,7 +142,7 @@ spec: certificateTemplate: "" certificateAuthorityLogicalName: "" certificateAuthorityHostname: "" - caSecretName: "" + caBundleSecretName: "" EOF kubectl -n command-issuer-system apply -f command-issuer.yaml ``` @@ -167,7 +167,7 @@ spec: certificateTemplate: "" certificateAuthorityLogicalName: "" certificateAuthorityHostname: "" - caSecretName: "" + caBundleSecretName: "" EOF kubectl -n command-issuer-system apply -f command-clusterissuer.yaml ``` diff --git a/deploy/charts/command-cert-manager-issuer/templates/clusterrole.yaml b/deploy/charts/command-cert-manager-issuer/templates/clusterrole.yaml index 0a238b9..c65a41b 100644 --- a/deploy/charts/command-cert-manager-issuer/templates/clusterrole.yaml +++ b/deploy/charts/command-cert-manager-issuer/templates/clusterrole.yaml @@ -53,6 +53,7 @@ rules: - issuers/finalizers verbs: - update +{{- if .Values.secureMetrics.enabled }} --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole @@ -84,4 +85,5 @@ rules: - nonResourceURLs: - /metrics verbs: - - get \ No newline at end of file + - get +{{- end }} \ No newline at end of file diff --git a/deploy/charts/command-cert-manager-issuer/templates/clusterrolebinding.yaml b/deploy/charts/command-cert-manager-issuer/templates/clusterrolebinding.yaml index 391282b..8a9c2b6 100644 --- a/deploy/charts/command-cert-manager-issuer/templates/clusterrolebinding.yaml +++ b/deploy/charts/command-cert-manager-issuer/templates/clusterrolebinding.yaml @@ -12,6 +12,7 @@ subjects: - kind: ServiceAccount name: {{ include "command-cert-manager-issuer.serviceAccountName" . }} namespace: {{ .Release.Namespace }} +{{- if .Values.secureMetrics.enabled }} --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding @@ -26,4 +27,5 @@ roleRef: subjects: - kind: ServiceAccount name: {{ include "command-cert-manager-issuer.serviceAccountName" . }} - namespace: {{ .Release.Namespace }} \ No newline at end of file + namespace: {{ .Release.Namespace }} +{{- end }} \ No newline at end of file diff --git a/deploy/charts/command-cert-manager-issuer/templates/deployment.yaml b/deploy/charts/command-cert-manager-issuer/templates/deployment.yaml index cbc5763..4eb16ce 100644 --- a/deploy/charts/command-cert-manager-issuer/templates/deployment.yaml +++ b/deploy/charts/command-cert-manager-issuer/templates/deployment.yaml @@ -26,6 +26,7 @@ spec: securityContext: {{- toYaml .Values.podSecurityContext | nindent 8 }} containers: + {{- if .Values.secureMetrics.enabled }} - args: - --secure-listen-address=0.0.0.0:8443 - --upstream=http://127.0.0.1:8080/ @@ -49,6 +50,7 @@ spec: capabilities: drop: - ALL + {{- end }} - args: - --health-probe-bind-address=:8081 - --metrics-bind-address=127.0.0.1:8080 diff --git a/deploy/charts/command-cert-manager-issuer/templates/service.yaml b/deploy/charts/command-cert-manager-issuer/templates/service.yaml index 6f4f739..c07551c 100644 --- a/deploy/charts/command-cert-manager-issuer/templates/service.yaml +++ b/deploy/charts/command-cert-manager-issuer/templates/service.yaml @@ -1,3 +1,4 @@ +{{- if .Values.secureMetrics.enabled }} apiVersion: v1 kind: Service metadata: @@ -11,4 +12,5 @@ spec: protocol: TCP targetPort: https selector: - {{- include "command-cert-manager-issuer.selectorLabels" . | nindent 4 }} \ No newline at end of file + {{- include "command-cert-manager-issuer.selectorLabels" . | nindent 4 }} +{{- end}} \ No newline at end of file diff --git a/deploy/charts/command-cert-manager-issuer/values.yaml b/deploy/charts/command-cert-manager-issuer/values.yaml index 1130491..876ea0b 100644 --- a/deploy/charts/command-cert-manager-issuer/values.yaml +++ b/deploy/charts/command-cert-manager-issuer/values.yaml @@ -13,6 +13,11 @@ imagePullSecrets: [] nameOverride: "" fullnameOverride: "" +# Whether to enable and configure the kube-rbac-proxy sidecar for authorized and authenticated +# use of the /metrics endpoint by Prometheus. +secureMetrics: + enabled: false + crd: # Specifies whether CRDs will be created create: true diff --git a/internal/controllers/certificaterequest_controller.go b/internal/controllers/certificaterequest_controller.go index a8cd5bf..b07851d 100644 --- a/internal/controllers/certificaterequest_controller.go +++ b/internal/controllers/certificaterequest_controller.go @@ -20,6 +20,7 @@ import ( "context" "errors" "fmt" + commandissuer "github.com/Keyfactor/command-issuer/api/v1alpha1" "github.com/Keyfactor/command-issuer/internal/issuer/signer" issuerutil "github.com/Keyfactor/command-issuer/internal/issuer/util" cmutil "github.com/cert-manager/cert-manager/pkg/api/util" @@ -34,8 +35,6 @@ import ( ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/controller" - - commandissuer "github.com/Keyfactor/command-issuer/api/v1alpha1" ) var ( @@ -245,7 +244,7 @@ func (r *CertificateRequestReconciler) Reconcile(ctx context.Context, req ctrl.R meta.ControllerResourceGroupName = commandissuer.GroupVersion.Group meta.IssuerName = certificateRequest.Spec.IssuerRef.Name meta.IssuerNamespace = certificateRequest.Namespace - meta.ControllerReconcileId = fmt.Sprintf("%s", controller.ReconcileIDFromContext(ctx)) + meta.ControllerReconcileId = string(controller.ReconcileIDFromContext(ctx)) meta.CertificateSigningRequestNamespace = certificateRequest.Namespace signed, err := commandSigner.Sign(ctx, certificateRequest.Spec.Request, meta) diff --git a/internal/controllers/certificaterequest_controller_test.go b/internal/controllers/certificaterequest_controller_test.go index da5d0e5..718e609 100644 --- a/internal/controllers/certificaterequest_controller_test.go +++ b/internal/controllers/certificaterequest_controller_test.go @@ -19,9 +19,6 @@ package controllers import ( "context" "errors" - "testing" - "time" - cmutil "github.com/cert-manager/cert-manager/pkg/api/util" cmapi "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" cmmeta "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" @@ -40,14 +37,14 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client/fake" "sigs.k8s.io/controller-runtime/pkg/reconcile" + "testing" commandissuer "github.com/Keyfactor/command-issuer/api/v1alpha1" "github.com/Keyfactor/command-issuer/internal/issuer/signer" ) var ( - fixedClockStart = time.Date(2021, time.January, 1, 1, 0, 0, 0, time.UTC) - fixedClock = clock.RealClock{} + fixedClock = clock.RealClock{} ) type fakeSigner struct { diff --git a/internal/issuer/signer/signer.go b/internal/issuer/signer/signer.go index a6c4884..1f70b4e 100644 --- a/internal/issuer/signer/signer.go +++ b/internal/issuer/signer/signer.go @@ -127,7 +127,7 @@ func CommandSignerFromIssuerAndSecretData(ctx context.Context, spec *commandissu func (s *commandSigner) Check() error { endpoints, _, err := s.client.StatusApi.StatusGetEndpoints(context.Background()).Execute() if err != nil { - detail := fmt.Sprintf("failed to get endpoints from Keyfactor Command") + detail := "failed to get endpoints from Keyfactor Command" var bodyError *keyfactor.GenericOpenAPIError ok := errors.As(err, &bodyError) @@ -174,7 +174,7 @@ func (s *commandSigner) Sign(ctx context.Context, csrBytes []byte, k8sMeta K8sMe CommandMetaCertificateSigningRequestNamespace: k8sMeta.CertificateSigningRequestNamespace, }, Template: &s.certificateTemplate, - SANs: nil, // TODO figure out if the SANs from csr need to be copied here + SANs: nil, } var caBuilder strings.Builder @@ -244,7 +244,7 @@ func compileCertificatesToPemBytes(certificates []*x509.Certificate) ([]byte, er Bytes: certificate.Raw, }) if err != nil { - return make([]byte, 0, 0), err + return make([]byte, 0), err } } @@ -261,19 +261,6 @@ const ( CommandMetaCertificateSigningRequestNamespace = "Certificate-Signing-Request-Namespace" ) -var ( - // Map used to determine if Keyfactor Command has a metadata field with a given name in O(1) time. - commandMetadataMap = map[string]string{ - CommandMetaControllerNamespace: "The namespace that the controller container is running in.", - CommandMetaControllerKind: "The type of issuer that the controller used to issue this certificate.", - CommandMetaControllerResourceGroupName: "The group name of the resource that the Issuer or ClusterIssuer controller is managing.", - CommandMetaIssuerName: "The name of the K8s issuer resource", - CommandMetaIssuerNamespace: "The namespace that the issuer resource was created in.", - CommandMetaControllerReconcileId: "The certificate reconcile ID that the controller used to issue this certificate.", - CommandMetaCertificateSigningRequestNamespace: "The namespace that the CertificateSigningRequest resource was created in.", - } -) - func createCommandClientFromSecretData(ctx context.Context, spec *commandissuer.IssuerSpec, authSecretData map[string][]byte, caSecretData map[string][]byte) (*keyfactor.APIClient, error) { k8sLogger := log.FromContext(ctx) @@ -308,7 +295,7 @@ func createCommandClientFromSecretData(ctx context.Context, spec *commandissuer. config.UserAgent = "command-issuer" // If the CA certificate is provided, add it to the EJBCA configuration - if caSecretData != nil && len(caSecretData) > 0 { + if len(caSecretData) > 0 { // There is no requirement that the CA certificate is stored under a specific key in the secret, so we can just iterate over the map var caCertBytes []byte for _, caCertBytes = range caSecretData { diff --git a/internal/issuer/signer/signer_test.go b/internal/issuer/signer/signer_test.go index b2a1eff..d2fc2ee 100644 --- a/internal/issuer/signer/signer_test.go +++ b/internal/issuer/signer/signer_test.go @@ -158,7 +158,7 @@ func generateCSR(subject string) ([]byte, error) { subj, err := parseSubjectDN(subject, false) if err != nil { - return make([]byte, 0, 0), err + return make([]byte, 0), err } template := x509.CertificateRequest{ @@ -169,7 +169,7 @@ func generateCSR(subject string) ([]byte, error) { csrBytes, _ := x509.CreateCertificateRequest(rand.Reader, &template, keyBytes) err = pem.Encode(&csrBuf, &pem.Block{Type: "CERTIFICATE REQUEST", Bytes: csrBytes}) if err != nil { - return make([]byte, 0, 0), err + return make([]byte, 0), err } return csrBuf.Bytes(), nil @@ -208,7 +208,7 @@ func parseSubjectDN(subject string, randomizeCn bool) (pkix.Name, error) { name.OrganizationalUnit = []string{value} case "CN": if randomizeCn { - value = fmt.Sprintf("%s-%s", value, generateRandomString(5)) + name.CommonName = fmt.Sprintf("%s-%s", value, generateRandomString(5)) } else { name.CommonName = value } From 5fb90a4bbf9686fb3b8e73a373cc82294251bf48 Mon Sep 17 00:00:00 2001 From: Hayden Roszell Date: Wed, 4 Oct 2023 08:19:48 -0700 Subject: [PATCH 02/17] chore: CRD openAPIV3Schema bug - incorrect values for command-isuer --- .../templates/crds/clusterissuers.yaml | 28 ++++++++----------- .../templates/crds/issuers.yaml | 28 ++++++++----------- 2 files changed, 24 insertions(+), 32 deletions(-) diff --git a/deploy/charts/command-cert-manager-issuer/templates/crds/clusterissuers.yaml b/deploy/charts/command-cert-manager-issuer/templates/crds/clusterissuers.yaml index 5d08de0..5f4c61a 100644 --- a/deploy/charts/command-cert-manager-issuer/templates/crds/clusterissuers.yaml +++ b/deploy/charts/command-cert-manager-issuer/templates/crds/clusterissuers.yaml @@ -32,30 +32,26 @@ spec: spec: description: IssuerSpec defines the desired state of Issuer properties: - caBundleSecretName: - description: The name of the secret containing the CA bundle to use when verifying command's server certificate. If specified, the CA bundle will be added to the client trust roots for the command issuer. + caSecretName: + description: A reference to a Secret in the same namespace as the referent. If the referent is a ClusterIssuer, the reference instead refers to the resource with the given name in the configured 'cluster resource namespace', which is set as a flag on the controller component (and defaults to the namespace that the controller runs in). type: string - certificateAuthorityName: + certificateAuthorityHostname: + description: CertificateAuthorityHostname is the hostname associated with the Certificate Authority specified by CertificateAuthorityLogicalName E.g. "ca.example.com" type: string - certificateProfileName: + certificateAuthorityLogicalName: + description: CertificateAuthorityLogicalName is the logical name of the certificate authority to use E.g. "Keyfactor Root CA" or "Intermediate CA" type: string - commandSecretName: - description: A reference to a Secret in the same namespace as the referent. If the referent is a ClusterIssuer, the reference instead refers to the resource with the given name in the configured 'cluster resource namespace', which is set as a flag on the controller component (and defaults to the namespace that the controller runs in). + certificateTemplate: + description: CertificateTemplate is the name of the certificate template to use type: string - endEntityName: - description: 'Optional field that overrides the default for how the command issuer should determine the name of the end entity to reference or create when signing certificates. The options are: * cn: Use the CommonName from the CertificateRequest''s DN * dns: Use the first DNSName from the CertificateRequest''s DNSNames SANs * uri: Use the first URI from the CertificateRequest''s URI Sans * ip: Use the first IPAddress from the CertificateRequest''s IPAddresses SANs * certificateName: Use the value of the CertificateRequest''s certificateName annotation If none of the above options are used but endEntityName is populated, the value of endEntityName will be used as the end entity name. If endEntityName is not populated, the default tree listed in the command documentation will be used.' - type: string - endEntityProfileName: + commandSecretName: + description: A reference to a Secret in the same namespace as the referent. If the referent is a ClusterIssuer, the reference instead refers to the resource with the given name in the configured 'cluster resource namespace', which is set as a flag on the controller component (and defaults to the namespace that the controller runs in). The secret must be a K8s basic-auth secret of type kubernetes.io/basic-auth with the username and password fields set. type: string hostname: - description: Hostname is the hostname of the command server + description: Hostname is the hostname of the Keyfactor server type: string required: - - certificateAuthorityName - - certificateProfileName - - commandSecretName - - endEntityProfileName - - hostname + - caSecretName type: object status: description: IssuerStatus defines the observed state of Issuer diff --git a/deploy/charts/command-cert-manager-issuer/templates/crds/issuers.yaml b/deploy/charts/command-cert-manager-issuer/templates/crds/issuers.yaml index 73012cc..6dc0645 100644 --- a/deploy/charts/command-cert-manager-issuer/templates/crds/issuers.yaml +++ b/deploy/charts/command-cert-manager-issuer/templates/crds/issuers.yaml @@ -32,30 +32,26 @@ spec: spec: description: IssuerSpec defines the desired state of Issuer properties: - caBundleSecretName: - description: The name of the secret containing the CA bundle to use when verifying command's server certificate. If specified, the CA bundle will be added to the client trust roots for the command issuer. + caSecretName: + description: A reference to a Secret in the same namespace as the referent. If the referent is a ClusterIssuer, the reference instead refers to the resource with the given name in the configured 'cluster resource namespace', which is set as a flag on the controller component (and defaults to the namespace that the controller runs in). type: string - certificateAuthorityName: + certificateAuthorityHostname: + description: CertificateAuthorityHostname is the hostname associated with the Certificate Authority specified by CertificateAuthorityLogicalName E.g. "ca.example.com" type: string - certificateProfileName: + certificateAuthorityLogicalName: + description: CertificateAuthorityLogicalName is the logical name of the certificate authority to use E.g. "Keyfactor Root CA" or "Intermediate CA" type: string - commandSecretName: - description: A reference to a Secret in the same namespace as the referent. If the referent is a ClusterIssuer, the reference instead refers to the resource with the given name in the configured 'cluster resource namespace', which is set as a flag on the controller component (and defaults to the namespace that the controller runs in). + certificateTemplate: + description: CertificateTemplate is the name of the certificate template to use type: string - endEntityName: - description: 'Optional field that overrides the default for how the command issuer should determine the name of the end entity to reference or create when signing certificates. The options are: * cn: Use the CommonName from the CertificateRequest''s DN * dns: Use the first DNSName from the CertificateRequest''s DNSNames SANs * uri: Use the first URI from the CertificateRequest''s URI Sans * ip: Use the first IPAddress from the CertificateRequest''s IPAddresses SANs * certificateName: Use the value of the CertificateRequest''s certificateName annotation If none of the above options are used but endEntityName is populated, the value of endEntityName will be used as the end entity name. If endEntityName is not populated, the default tree listed in the command documentation will be used.' - type: string - endEntityProfileName: + commandSecretName: + description: A reference to a Secret in the same namespace as the referent. If the referent is a ClusterIssuer, the reference instead refers to the resource with the given name in the configured 'cluster resource namespace', which is set as a flag on the controller component (and defaults to the namespace that the controller runs in). The secret must be a K8s basic-auth secret of type kubernetes.io/basic-auth with the username and password fields set. type: string hostname: - description: Hostname is the hostname of the command server + description: Hostname is the hostname of the Keyfactor server type: string required: - - certificateAuthorityName - - certificateProfileName - - commandSecretName - - endEntityProfileName - - hostname + - caSecretName type: object status: description: IssuerStatus defines the observed state of Issuer From 8b68bb75b054acaa743e8ded4061f8d08ede00d6 Mon Sep 17 00:00:00 2001 From: Hayden Roszell Date: Wed, 4 Oct 2023 08:22:23 -0700 Subject: [PATCH 03/17] chore: Revert README.md from referencing caBundleSecretName to caSecretName --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index eda1a3e..100542a 100644 --- a/README.md +++ b/README.md @@ -120,7 +120,7 @@ The `spec` field of both the Issuer and ClusterIssuer resources use the followin * `certificateTemplate` - The short name corresponding to a template in Command that will be used to issue certificates. * `certificateAuthorityLogicalName` - The logical name of the CA to use to sign the certificate request * `certificateAuthorityHostname` - The CAs hostname to use to sign the certificate request -* `caBundleSecretName` - The name of the Kubernetes secret containing the CA certificate. This field is optional and only required if the Command server is configured to use a self-signed certificate or with a certificate signed by an untrusted root. +* `caSecretName` - The name of the Kubernetes secret containing the CA certificate. This field is optional and only required if the Command server is configured to use a self-signed certificate or with a certificate signed by an untrusted root. ###### If a different combination of hostname/certificate authority/certificate profile/end entity profile is required, a new Issuer or ClusterIssuer resource must be created. Each resource instantiation represents a single configuration. @@ -142,7 +142,7 @@ spec: certificateTemplate: "" certificateAuthorityLogicalName: "" certificateAuthorityHostname: "" - caBundleSecretName: "" + caSecretName: "" EOF kubectl -n command-issuer-system apply -f command-issuer.yaml ``` @@ -167,7 +167,7 @@ spec: certificateTemplate: "" certificateAuthorityLogicalName: "" certificateAuthorityHostname: "" - caBundleSecretName: "" + caSecretName: "" EOF kubectl -n command-issuer-system apply -f command-clusterissuer.yaml ``` From 4be4d38bba9395663c38ca85689887596834afe5 Mon Sep 17 00:00:00 2001 From: Hayden Roszell Date: Wed, 4 Oct 2023 09:42:26 -0700 Subject: [PATCH 04/17] chore: CRD openAPIV3Schema - Update descriptions to accurately reflect spec fields --- Makefile | 2 +- api/v1alpha1/issuer_types.go | 22 +++++++-------- ...d-issuer.keyfactor.com_clusterissuers.yaml | 28 ++++++++----------- .../command-issuer.keyfactor.com_issuers.yaml | 28 ++++++++----------- .../templates/crds/clusterissuers.yaml | 10 +++---- .../templates/crds/issuers.yaml | 10 +++---- 6 files changed, 44 insertions(+), 56 deletions(-) diff --git a/Makefile b/Makefile index 89f768e..e7ebbd4 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ # The version which will be reported by the --version argument of each binary # and which will be used as the Docker image tag -VERSION ?= 1.0.4 +VERSION ?= 1.0.3 # The Docker repository name, overridden in CI. DOCKER_REGISTRY ?= m8rmclarenkf DOCKER_IMAGE_NAME ?= command-cert-manager-external-issuer-controller diff --git a/api/v1alpha1/issuer_types.go b/api/v1alpha1/issuer_types.go index 1552b6f..4ed0944 100644 --- a/api/v1alpha1/issuer_types.go +++ b/api/v1alpha1/issuer_types.go @@ -22,9 +22,10 @@ import ( // IssuerSpec defines the desired state of Issuer type IssuerSpec struct { - // Hostname is the hostname of the Keyfactor server + // Hostname is the hostname of a Keyfactor Command instance. Hostname string `json:"hostname,omitempty"` - // CertificateTemplate is the name of the certificate template to use + // CertificateTemplate is the name of the certificate template to use. + // Refer to the Keyfactor Command documentation for more information. CertificateTemplate string `json:"certificateTemplate,omitempty"` // CertificateAuthorityLogicalName is the logical name of the certificate authority to use // E.g. "Keyfactor Root CA" or "Intermediate CA" @@ -33,20 +34,19 @@ type IssuerSpec struct { // CertificateAuthorityLogicalName E.g. "ca.example.com" CertificateAuthorityHostname string `json:"certificateAuthorityHostname,omitempty"` - // A reference to a Secret in the same namespace as the referent. If the + // A reference to a K8s kubernetes.io/basic-auth Secret containing basic auth + // credentials for the Command instance configured in Hostname. The secret must + // be in the same namespace as the referent. If the // referent is a ClusterIssuer, the reference instead refers to the resource // with the given name in the configured 'cluster resource namespace', which // is set as a flag on the controller component (and defaults to the - // namespace that the controller runs in). The secret must be a K8s basic-auth - // secret of type kubernetes.io/basic-auth with the username and password - // fields set. + // namespace that the controller runs in). SecretName string `json:"commandSecretName,omitempty"` - // A reference to a Secret in the same namespace as the referent. If the - // referent is a ClusterIssuer, the reference instead refers to the resource - // with the given name in the configured 'cluster resource namespace', which - // is set as a flag on the controller component (and defaults to the - // namespace that the controller runs in). + // The name of the secret containing the CA bundle to use when verifying + // Command's server certificate. If specified, the CA bundle will be added to + // the client trust roots for the Command issuer. + // +optional CaSecretName string `json:"caSecretName"` } diff --git a/config/crd/bases/command-issuer.keyfactor.com_clusterissuers.yaml b/config/crd/bases/command-issuer.keyfactor.com_clusterissuers.yaml index c2ee40b..217acfe 100644 --- a/config/crd/bases/command-issuer.keyfactor.com_clusterissuers.yaml +++ b/config/crd/bases/command-issuer.keyfactor.com_clusterissuers.yaml @@ -36,11 +36,9 @@ spec: description: IssuerSpec defines the desired state of Issuer properties: caSecretName: - description: A reference to a Secret in the same namespace as the - referent. If the referent is a ClusterIssuer, the reference instead - refers to the resource with the given name in the configured 'cluster - resource namespace', which is set as a flag on the controller component - (and defaults to the namespace that the controller runs in). + description: The name of the secret containing the CA bundle to use + when verifying Command's server certificate. If specified, the CA + bundle will be added to the client trust roots for the Command issuer. type: string certificateAuthorityHostname: description: CertificateAuthorityHostname is the hostname associated @@ -54,22 +52,20 @@ spec: type: string certificateTemplate: description: CertificateTemplate is the name of the certificate template - to use + to use. Refer to the Keyfactor Command documentation for more information. type: string commandSecretName: - description: A reference to a Secret in the same namespace as the - referent. If the referent is a ClusterIssuer, the reference instead - refers to the resource with the given name in the configured 'cluster - resource namespace', which is set as a flag on the controller component - (and defaults to the namespace that the controller runs in). The - secret must be a K8s basic-auth secret of type kubernetes.io/basic-auth - with the username and password fields set. + description: A reference to a K8s kubernetes.io/basic-auth Secret + containing basic auth credentials for the Command instance configured + in Hostname. The secret must be in the same namespace as the referent. + If the referent is a ClusterIssuer, the reference instead refers + to the resource with the given name in the configured 'cluster resource + namespace', which is set as a flag on the controller component (and + defaults to the namespace that the controller runs in). type: string hostname: - description: Hostname is the hostname of the Keyfactor server + description: Hostname is the hostname of a Keyfactor Command instance. type: string - required: - - caSecretName type: object status: description: IssuerStatus defines the observed state of Issuer diff --git a/config/crd/bases/command-issuer.keyfactor.com_issuers.yaml b/config/crd/bases/command-issuer.keyfactor.com_issuers.yaml index 8bd788b..ffcc231 100644 --- a/config/crd/bases/command-issuer.keyfactor.com_issuers.yaml +++ b/config/crd/bases/command-issuer.keyfactor.com_issuers.yaml @@ -36,11 +36,9 @@ spec: description: IssuerSpec defines the desired state of Issuer properties: caSecretName: - description: A reference to a Secret in the same namespace as the - referent. If the referent is a ClusterIssuer, the reference instead - refers to the resource with the given name in the configured 'cluster - resource namespace', which is set as a flag on the controller component - (and defaults to the namespace that the controller runs in). + description: The name of the secret containing the CA bundle to use + when verifying Command's server certificate. If specified, the CA + bundle will be added to the client trust roots for the Command issuer. type: string certificateAuthorityHostname: description: CertificateAuthorityHostname is the hostname associated @@ -54,22 +52,20 @@ spec: type: string certificateTemplate: description: CertificateTemplate is the name of the certificate template - to use + to use. Refer to the Keyfactor Command documentation for more information. type: string commandSecretName: - description: A reference to a Secret in the same namespace as the - referent. If the referent is a ClusterIssuer, the reference instead - refers to the resource with the given name in the configured 'cluster - resource namespace', which is set as a flag on the controller component - (and defaults to the namespace that the controller runs in). The - secret must be a K8s basic-auth secret of type kubernetes.io/basic-auth - with the username and password fields set. + description: A reference to a K8s kubernetes.io/basic-auth Secret + containing basic auth credentials for the Command instance configured + in Hostname. The secret must be in the same namespace as the referent. + If the referent is a ClusterIssuer, the reference instead refers + to the resource with the given name in the configured 'cluster resource + namespace', which is set as a flag on the controller component (and + defaults to the namespace that the controller runs in). type: string hostname: - description: Hostname is the hostname of the Keyfactor server + description: Hostname is the hostname of a Keyfactor Command instance. type: string - required: - - caSecretName type: object status: description: IssuerStatus defines the observed state of Issuer diff --git a/deploy/charts/command-cert-manager-issuer/templates/crds/clusterissuers.yaml b/deploy/charts/command-cert-manager-issuer/templates/crds/clusterissuers.yaml index 5f4c61a..f845fda 100644 --- a/deploy/charts/command-cert-manager-issuer/templates/crds/clusterissuers.yaml +++ b/deploy/charts/command-cert-manager-issuer/templates/crds/clusterissuers.yaml @@ -33,7 +33,7 @@ spec: description: IssuerSpec defines the desired state of Issuer properties: caSecretName: - description: A reference to a Secret in the same namespace as the referent. If the referent is a ClusterIssuer, the reference instead refers to the resource with the given name in the configured 'cluster resource namespace', which is set as a flag on the controller component (and defaults to the namespace that the controller runs in). + description: The name of the secret containing the CA bundle to use when verifying Command's server certificate. If specified, the CA bundle will be added to the client trust roots for the Command issuer. type: string certificateAuthorityHostname: description: CertificateAuthorityHostname is the hostname associated with the Certificate Authority specified by CertificateAuthorityLogicalName E.g. "ca.example.com" @@ -42,16 +42,14 @@ spec: description: CertificateAuthorityLogicalName is the logical name of the certificate authority to use E.g. "Keyfactor Root CA" or "Intermediate CA" type: string certificateTemplate: - description: CertificateTemplate is the name of the certificate template to use + description: CertificateTemplate is the name of the certificate template to use. Refer to the Keyfactor Command documentation for more information. type: string commandSecretName: - description: A reference to a Secret in the same namespace as the referent. If the referent is a ClusterIssuer, the reference instead refers to the resource with the given name in the configured 'cluster resource namespace', which is set as a flag on the controller component (and defaults to the namespace that the controller runs in). The secret must be a K8s basic-auth secret of type kubernetes.io/basic-auth with the username and password fields set. + description: A reference to a K8s kubernetes.io/basic-auth Secret containing basic auth credentials for the Command instance configured in Hostname. The secret must be in the same namespace as the referent. If the referent is a ClusterIssuer, the reference instead refers to the resource with the given name in the configured 'cluster resource namespace', which is set as a flag on the controller component (and defaults to the namespace that the controller runs in). type: string hostname: - description: Hostname is the hostname of the Keyfactor server + description: Hostname is the hostname of a Keyfactor Command instance. type: string - required: - - caSecretName type: object status: description: IssuerStatus defines the observed state of Issuer diff --git a/deploy/charts/command-cert-manager-issuer/templates/crds/issuers.yaml b/deploy/charts/command-cert-manager-issuer/templates/crds/issuers.yaml index 6dc0645..de8de0b 100644 --- a/deploy/charts/command-cert-manager-issuer/templates/crds/issuers.yaml +++ b/deploy/charts/command-cert-manager-issuer/templates/crds/issuers.yaml @@ -33,7 +33,7 @@ spec: description: IssuerSpec defines the desired state of Issuer properties: caSecretName: - description: A reference to a Secret in the same namespace as the referent. If the referent is a ClusterIssuer, the reference instead refers to the resource with the given name in the configured 'cluster resource namespace', which is set as a flag on the controller component (and defaults to the namespace that the controller runs in). + description: The name of the secret containing the CA bundle to use when verifying Command's server certificate. If specified, the CA bundle will be added to the client trust roots for the Command issuer. type: string certificateAuthorityHostname: description: CertificateAuthorityHostname is the hostname associated with the Certificate Authority specified by CertificateAuthorityLogicalName E.g. "ca.example.com" @@ -42,16 +42,14 @@ spec: description: CertificateAuthorityLogicalName is the logical name of the certificate authority to use E.g. "Keyfactor Root CA" or "Intermediate CA" type: string certificateTemplate: - description: CertificateTemplate is the name of the certificate template to use + description: CertificateTemplate is the name of the certificate template to use. Refer to the Keyfactor Command documentation for more information. type: string commandSecretName: - description: A reference to a Secret in the same namespace as the referent. If the referent is a ClusterIssuer, the reference instead refers to the resource with the given name in the configured 'cluster resource namespace', which is set as a flag on the controller component (and defaults to the namespace that the controller runs in). The secret must be a K8s basic-auth secret of type kubernetes.io/basic-auth with the username and password fields set. + description: A reference to a K8s kubernetes.io/basic-auth Secret containing basic auth credentials for the Command instance configured in Hostname. The secret must be in the same namespace as the referent. If the referent is a ClusterIssuer, the reference instead refers to the resource with the given name in the configured 'cluster resource namespace', which is set as a flag on the controller component (and defaults to the namespace that the controller runs in). type: string hostname: - description: Hostname is the hostname of the Keyfactor server + description: Hostname is the hostname of a Keyfactor Command instance. type: string - required: - - caSecretName type: object status: description: IssuerStatus defines the observed state of Issuer From 52e013ae28fc1365acbfbe79d01f3e3ece7b8f64 Mon Sep 17 00:00:00 2001 From: Hayden Roszell Date: Fri, 6 Oct 2023 12:04:23 -0700 Subject: [PATCH 05/17] feat(metadata): Signer recognizes annotation prefix for Command metadata --- .github/workflows/release.yml | 190 ++++++++- Makefile | 14 +- README.md | 12 +- config/manager/kustomization.yaml | 4 +- .../templates/role.yaml | 12 - .../command-cert-manager-issuer/values.yaml | 2 +- internal/issuer/signer/signer.go | 43 +- internal/issuer/signer/signer_test.go | 383 +++++++++++++++++- 8 files changed, 603 insertions(+), 57 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index cb6bcd6..6a337b0 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,43 +1,201 @@ -name: helm_release +name: Build and Release on: + push: + branches: + - '*' pull_request: branches: - 'v*' types: + # action should run when the pull request is closed + # (regardless of whether it was merged or just closed) - closed + # Make sure the action runs every time new commits are + # pushed to the pull request's branch + - synchronize + +env: + REGISTRY: ghcr.io + IMAGE_NAME: ${{ github.repository }} + jobs: + build: + name: Build Containers + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + platform: + - linux/arm64 + - linux/amd64 + - linux/s390x + - linux/ppc64le + + permissions: + contents: read + packages: write + + steps: + # Checkout code + # https://github.com/actions/checkout + - name: Checkout code + uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0 + + # Extract metadata (tags, labels) for Docker + # https://github.com/docker/metadata-action + - name: Extract Docker metadata + id: meta + uses: docker/metadata-action@96383f45573cb7f253c731d3b3ab81c87ef81934 # v5.0.0 + with: + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + + # Set up QEMU + # https://github.com/docker/setup-qemu-action + - name: Set up QEMU + uses: docker/setup-qemu-action@68827325e0b33c7199eb31dd4e31fbe9023e06e3 # v3.0.0 + + # Set up BuildKit Docker container builder to be able to build + # multi-platform images and export cache + # https://github.com/docker/setup-buildx-action + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@f95db51fddba0c2d1ec667646a06c2ce06100226 # v3.0.0 + + # Login to Docker registry + # https://github.com/docker/login-action + - name: Log into registry ${{ env.REGISTRY }} + uses: docker/login-action@343f7c4344506bcbf9b4de18042ae17996df046d # v3.0.0 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + # Build and push Docker image with Buildx + # https://github.com/docker/build-push-action + - name: Build and push Docker image + id: build + uses: docker/build-push-action@0565240e2d4ab88bba5387d719585280857ece09 # v5.0.0 + with: + context: . + platforms: ${{ matrix.platform }} + labels: ${{ steps.meta.outputs.labels }} + push: ${{ github.event.pull_request.merged == true }} + outputs: type=image,name=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }},push-by-digest=true,name-canonical=true + + # Export digest + - name: Export digest + if: github.event.pull_request.merged == true + run: | + mkdir -p /tmp/digests + digest="${{ steps.build.outputs.digest }}" + touch "/tmp/digests/${digest#sha256:}" + + # Upload digest + - name: Upload digest + if: github.event.pull_request.merged == true + uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3.1.3 + with: + name: digests + path: /tmp/digests/* + if-no-files-found: error + retention-days: 1 + + merge: + runs-on: ubuntu-latest + if: github.event.pull_request.merged == true + needs: + - build + steps: + # Download digests + # https://github.com/actions/download-artifact + - name: Download digests + uses: actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a # v3.0.2 + with: + name: digests + path: /tmp/digests + + # Set up BuildKit Docker container builder to be able to build + # multi-platform images and export cache + # https://github.com/docker/setup-buildx-action + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@f95db51fddba0c2d1ec667646a06c2ce06100226 # v3.0.0 + + # Extract metadata (tags, labels) for Docker + # https://github.com/docker/metadata-action + - name: Extract Docker metadata + id: meta + uses: docker/metadata-action@96383f45573cb7f253c731d3b3ab81c87ef81934 # v5.0.0 + with: + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + + # Login to Docker registry + # https://github.com/docker/login-action + - name: Log into registry ${{ env.REGISTRY }} + uses: docker/login-action@343f7c4344506bcbf9b4de18042ae17996df046d # v3.0.0 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + # Create manifest list and push + - name: Create manifest list and push + working-directory: /tmp/digests + run: | + docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \ + $(printf '${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}@sha256:%s ' *) + + - name: Inspect image + run: | + docker buildx imagetools inspect ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.meta.outputs.version }} + helm: runs-on: ubuntu-latest if: github.event.pull_request.merged == true + needs: + - merge steps: - - name: Extract Version Tag - id: extract_version - run: /bin/bash -c 'echo ::set-output name=VERSION::$(echo ${GITHUB_REF##*/} | cut -c2-)' + # Checkout code + # https://github.com/actions/checkout + - name: Checkout code + uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0 - - name: Checkout - uses: actions/checkout@v3 + # Extract metadata (tags, labels) to use in Helm chart + # https://github.com/docker/metadata-action + - name: Extract Docker metadata + id: meta + uses: docker/metadata-action@96383f45573cb7f253c731d3b3ab81c87ef81934 # v5.0.0 + with: + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + + # Set version from DOCKER_METADATA_OUTPUT_VERSION as environment variable + - name: Set Version + run: | + echo "VERSION=${DOCKER_METADATA_OUTPUT_VERSION:1}" >> $GITHUB_ENV # Change version and appVersion in Chart.yaml to the tag in the closed PR - name: Update Helm App/Chart Version shell: bash run: | - sed -i "s/^version: .*/version: ${{ steps.extract_version.outputs.VERSION }}/g" deploy/charts/command-cert-manager-issuer/Chart.yaml - sed -i "s/^appVersion: .*/appVersion: \"${{ steps.extract_version.outputs.VERSION }}\"/g" deploy/charts/command-cert-manager-issuer/Chart.yaml + sed -i "s/^version: .*/version: ${{ env.VERSION }}/g" deploy/charts/command-cert-manager-issuer/Chart.yaml + sed -i "s/^appVersion: .*/appVersion: \"${{ env.DOCKER_METADATA_OUTPUT_VERSION }}\"/g" deploy/charts/command-cert-manager-issuer/Chart.yaml + # Setup Helm + # https://github.com/Azure/setup-helm + - name: Install Helm + uses: azure/setup-helm@5119fcb9089d432beecbf79bb2c7915207344b78 # v3.5 + with: + token: ${{ secrets.GITHUB_TOKEN }} + + # Helm requires an ident name to be set for chart-releaser to work - name: Configure Git run: | git config user.name "$GITHUB_ACTOR" git config user.email "$GITHUB_ACTOR@users.noreply.github.com" - - name: Install Helm - uses: azure/setup-helm@v3 - + # Build and release Helm chart to GitHub Pages + # https://github.com/helm/chart-releaser-action - name: Run chart-releaser - uses: helm/chart-releaser-action@v1.5.0 + uses: helm/chart-releaser-action@be16258da8010256c6e82849661221415f031968 # v1.5.0 env: CR_TOKEN: "${{ secrets.GITHUB_TOKEN }}" with: - pages_branch: gh-pages - charts_dir: deploy/charts - mark_as_latest: true - packages_with_index: true + charts_dir: deploy/charts \ No newline at end of file diff --git a/Makefile b/Makefile index e7ebbd4..fba1c67 100644 --- a/Makefile +++ b/Makefile @@ -1,9 +1,9 @@ # The version which will be reported by the --version argument of each binary # and which will be used as the Docker image tag -VERSION ?= 1.0.3 +VERSION ?= v1.0.4 # The Docker repository name, overridden in CI. -DOCKER_REGISTRY ?= m8rmclarenkf -DOCKER_IMAGE_NAME ?= command-cert-manager-external-issuer-controller +DOCKER_REGISTRY ?= ghcr.io +DOCKER_IMAGE_NAME ?= keyfactor/command-cert-manager-issuer # Image URL to use all building/pushing image targets IMG ?= ${DOCKER_REGISTRY}/${DOCKER_IMAGE_NAME}:${VERSION} #IMG ?= command-issuer-dev:latest @@ -122,6 +122,14 @@ deploy: manifests kustomize ## Deploy controller to the K8s cluster specified in cd config/manager && $(KUSTOMIZE) edit set image controller=${IMG} $(KUSTOMIZE) build config/default | kubectl apply -f - +# Build the manager image for local development. This image is not intended to be used in production. +# Then, install it into the K8s cluster +.PHONY: deploy-local +deploy-local: manifests kustomize ## Build docker image with the manager. + docker build -t ejbca-issuer-dev:latest -f Dockerfile . + cd config/manager && $(KUSTOMIZE) edit set image controller=ejbca-issuer-dev:latest + $(KUSTOMIZE) build config/default | kubectl apply -f - + .PHONY: undeploy undeploy: ## Undeploy controller from the K8s cluster specified in ~/.kube/config. Call with ignore-not-found=true to ignore resource not found errors during deletion. $(KUSTOMIZE) build config/default | kubectl delete --ignore-not-found=$(ignore-not-found) -f - diff --git a/README.md b/README.md index 100542a..308428e 100644 --- a/README.md +++ b/README.md @@ -267,6 +267,15 @@ Here are the supported annotations that can override the default values: command-issuer.keyfactor.com/certificateAuthorityHostname: "example.com" ``` +### Metadata Annotations + +The Keyfactor Command external issuer for cert-manager also allows you to specify Command Metadata through the use of annotations. Metadata attached to a certificate request will be stored in Command and can be used for reporting and auditing purposes. The syntax for specifying metadata is as follows: +```yaml +metadata.command-issuer.keyfactor.com/: +``` + +###### :pushpin: The metadata field name must match a name of a metadata field in Command exactly. If the metadata field name does not match, the CSR enrollment will fail. + ### How to Apply Annotations To apply these annotations, include them in the metadata section of your CertificateRequest resource: @@ -278,9 +287,10 @@ metadata: annotations: command-issuer.keyfactor.com/certificateTemplate: "Ephemeral2day" command-issuer.keyfactor.com/certificateAuthorityLogicalName: "InternalIssuingCA1" + metadata.command-issuer.keyfactor.com/ResponsibleTeam: "theResponsibleTeam@example.com" # ... other annotations spec: -# ... rest of the spec +# ... the rest of the spec ``` ### Demo ClusterIssuer Usage with K8s Ingress diff --git a/config/manager/kustomization.yaml b/config/manager/kustomization.yaml index 6badba7..3bcb896 100644 --- a/config/manager/kustomization.yaml +++ b/config/manager/kustomization.yaml @@ -4,5 +4,5 @@ apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization images: - name: controller - newName: m8rmclarenkf/command-external-issuer - newTag: v1.0.2 + newName: ghcr.io/keyfactor/command-cert-manager-issuer + newTag: v1.0.4 diff --git a/deploy/charts/command-cert-manager-issuer/templates/role.yaml b/deploy/charts/command-cert-manager-issuer/templates/role.yaml index bd3d437..78b9b28 100644 --- a/deploy/charts/command-cert-manager-issuer/templates/role.yaml +++ b/deploy/charts/command-cert-manager-issuer/templates/role.yaml @@ -5,18 +5,6 @@ metadata: {{- include "command-cert-manager-issuer.labels" . | nindent 4 }} name: {{ include "command-cert-manager-issuer.name" . }}-leader-election-role rules: - - apiGroups: - - "" - resources: - - configmaps - verbs: - - get - - list - - watch - - create - - update - - patch - - delete - apiGroups: - coordination.k8s.io resources: diff --git a/deploy/charts/command-cert-manager-issuer/values.yaml b/deploy/charts/command-cert-manager-issuer/values.yaml index 876ea0b..9c14ccb 100644 --- a/deploy/charts/command-cert-manager-issuer/values.yaml +++ b/deploy/charts/command-cert-manager-issuer/values.yaml @@ -4,7 +4,7 @@ replicaCount: 1 image: - repository: m8rmclarenkf/command-cert-manager-external-issuer-controller + repository: ghcr.io/keyfactor/command-cert-manager-issuer pullPolicy: IfNotPresent # Overrides the image tag whose default is the chart appVersion. tag: "" diff --git a/internal/issuer/signer/signer.go b/internal/issuer/signer/signer.go index 1f70b4e..b926959 100644 --- a/internal/issuer/signer/signer.go +++ b/internal/issuer/signer/signer.go @@ -51,6 +51,7 @@ type commandSigner struct { certificateAuthorityLogicalName string certificateAuthorityHostname string certManagerCertificateName string + customMetadata map[string]interface{} } type HealthChecker interface { @@ -78,6 +79,10 @@ func CommandHealthCheckerFromIssuerAndSecretData(ctx context.Context, spec *comm } func CommandSignerFromIssuerAndSecretData(ctx context.Context, spec *commandissuer.IssuerSpec, annotations map[string]string, authSecretData map[string][]byte, caSecretData map[string][]byte) (Signer, error) { + return commandSignerFromIssuerAndSecretData(ctx, spec, annotations, authSecretData, caSecretData) +} + +func commandSignerFromIssuerAndSecretData(ctx context.Context, spec *commandissuer.IssuerSpec, annotations map[string]string, authSecretData map[string][]byte, caSecretData map[string][]byte) (*commandSigner, error) { k8sLog := log.FromContext(ctx) signer := commandSigner{} @@ -121,9 +126,23 @@ func CommandSignerFromIssuerAndSecretData(ctx context.Context, spec *commandissu k8sLog.Info(fmt.Sprintf("Using certificate template \"%s\" and certificate authority \"%s\" (%s)", signer.certificateTemplate, signer.certificateAuthorityLogicalName, signer.certificateAuthorityHostname)) + signer.customMetadata = extractMetadataFromAnnotations(annotations) + return &signer, nil } +func extractMetadataFromAnnotations(annotations map[string]string) map[string]interface{} { + metadata := make(map[string]interface{}) + + for key, value := range annotations { + if strings.HasPrefix(key, "metadata.command-issuer.keyfactor.com/") { + metadata[strings.TrimPrefix(key, "metadata.command-issuer.keyfactor.com/")] = value + } + } + + return metadata +} + func (s *commandSigner) Check() error { endpoints, _, err := s.client.StatusApi.StatusGetEndpoints(context.Background()).Execute() if err != nil { @@ -161,6 +180,19 @@ func (s *commandSigner) Sign(ctx context.Context, csrBytes []byte, k8sMeta K8sMe // Log the common metadata of the CSR k8sLog.Info(fmt.Sprintf("Found CSR wtih Common Name \"%s\" and %d DNS SANs, %d IP SANs, and %d URI SANs", csr.Subject.CommonName, len(csr.DNSNames), len(csr.IPAddresses), len(csr.URIs))) + // Print the SANs + for _, dnsName := range csr.DNSNames { + k8sLog.Info(fmt.Sprintf("DNS SAN: %s", dnsName)) + } + + for _, ipAddress := range csr.IPAddresses { + k8sLog.Info(fmt.Sprintf("IP SAN: %s", ipAddress.String())) + } + + for _, uri := range csr.URIs { + k8sLog.Info(fmt.Sprintf("URI SAN: %s", uri.String())) + } + modelRequest := keyfactor.ModelsEnrollmentCSREnrollmentRequest{ CSR: string(csrBytes), IncludeChain: ptr(true), @@ -177,6 +209,11 @@ func (s *commandSigner) Sign(ctx context.Context, csrBytes []byte, k8sMeta K8sMe SANs: nil, } + for metaName, value := range s.customMetadata { + k8sLog.Info(fmt.Sprintf("Adding metadata \"%s\" with value \"%s\"", metaName, value)) + modelRequest.Metadata[metaName] = value + } + var caBuilder strings.Builder if s.certificateAuthorityHostname != "" { caBuilder.WriteString(s.certificateAuthorityHostname) @@ -189,7 +226,11 @@ func (s *commandSigner) Sign(ctx context.Context, csrBytes []byte, k8sMeta K8sMe commandCsrResponseObject, _, err := s.client.EnrollmentApi.EnrollmentPostCSREnroll(context.Background()).Request(modelRequest).XCertificateformat(enrollmentPEMFormat).Execute() if err != nil { - detail := fmt.Sprintf("error enrolling certificate with Command. verify that the certificate template \"%s\" exists and that the certificate authority \"%s\" (%s) is configured correctly", s.certificateTemplate, s.certificateAuthorityLogicalName, s.certificateAuthorityHostname) + detail := fmt.Sprintf("error enrolling certificate with Command. Verify that the certificate template \"%s\" exists and that the certificate authority \"%s\" (%s) is configured correctly.", s.certificateTemplate, s.certificateAuthorityLogicalName, s.certificateAuthorityHostname) + + if len(s.customMetadata) > 0 { + detail += " Also verify that the metadata fields provided exist in Command." + } var bodyError *keyfactor.GenericOpenAPIError ok := errors.As(err, &bodyError) diff --git a/internal/issuer/signer/signer_test.go b/internal/issuer/signer/signer_test.go index d2fc2ee..d7b8c25 100644 --- a/internal/issuer/signer/signer_test.go +++ b/internal/issuer/signer/signer_test.go @@ -27,7 +27,11 @@ import ( "encoding/pem" "fmt" commandissuer "github.com/Keyfactor/command-issuer/api/v1alpha1" + "github.com/Keyfactor/keyfactor-go-client-sdk/api/keyfactor" + "github.com/stretchr/testify/assert" + "math/big" "os" + "reflect" "strings" "testing" "time" @@ -55,39 +59,348 @@ func TestCommandHealthCheckerFromIssuerAndSecretData(t *testing.T) { } func TestCommandSignerFromIssuerAndSecretData(t *testing.T) { - obj := testSigner{ - SignerBuilder: CommandSignerFromIssuerAndSecretData, + t.Run("ValidSigning", func(t *testing.T) { + obj := testSigner{ + SignerBuilder: CommandSignerFromIssuerAndSecretData, + } + + // Generate a test CSR to sign + csr, err := generateCSR("C=US,ST=California,L=San Francisco,O=Keyfactor,OU=Engineering,CN=example.com") + if err != nil { + t.Fatal(err) + } + + meta := K8sMetadata{ + ControllerNamespace: "test-namespace", + ControllerKind: "Issuer", + ControllerResourceGroupName: "test-issuer.example.com", + IssuerName: "test-issuer", + IssuerNamespace: "test-namespace", + ControllerReconcileId: "GUID", + CertificateSigningRequestNamespace: "test-namespace", + } + + start := time.Now() + signer, err := obj.SignerBuilder(getTestSignerConfigItems(t)) + if err != nil { + t.Fatal(err) + } + + signed, err := signer.Sign(context.Background(), csr, meta) + if err != nil { + t.Fatal(err) + } + t.Logf("Signing took %s", time.Since(start)) + + t.Logf("Signed certificate: %s", string(signed)) + }) + + // Set up test data + + spec := commandissuer.IssuerSpec{ + Hostname: "example-hostname.com", + CertificateTemplate: "example-template", + CertificateAuthorityLogicalName: "example-logical-name", + CertificateAuthorityHostname: "ca-hostname.com", + SecretName: "example-secret-name", + CaSecretName: "example-ca-secret-name", + } + + authSecretData := map[string][]byte{ + "username": []byte("username"), + "password": []byte("password"), } - // Generate a test CSR to sign - csr, err := generateCSR("C=US,ST=California,L=San Francisco,O=Keyfactor,OU=Engineering,CN=example.com") + caSecretData := map[string][]byte{ + "tls.crt": []byte("ca-cert"), + } + + t.Run("MissingCertTemplate", func(t *testing.T) { + templateCopy := spec.CertificateTemplate + spec.CertificateTemplate = "" + // Create the signer + _, err := commandSignerFromIssuerAndSecretData(context.Background(), &spec, make(map[string]string), authSecretData, caSecretData) + if err == nil { + t.Errorf("expected error, got nil") + } + + spec.CertificateTemplate = templateCopy + }) + + t.Run("MissingCaLogicalName", func(t *testing.T) { + logicalNameCopy := spec.CertificateAuthorityLogicalName + spec.CertificateAuthorityLogicalName = "" + // Create the signer + _, err := commandSignerFromIssuerAndSecretData(context.Background(), &spec, make(map[string]string), authSecretData, caSecretData) + if err == nil { + t.Errorf("expected error, got nil") + } + + spec.CertificateAuthorityLogicalName = logicalNameCopy + }) + + t.Run("NoAnnotations", func(t *testing.T) { + // Create the signer + signer, err := commandSignerFromIssuerAndSecretData(context.Background(), &spec, make(map[string]string), authSecretData, caSecretData) + if err != nil { + t.Fatal(err) + } + + // If there are no annotations, the customMetadata map should be empty + if len(signer.customMetadata) != 0 { + t.Errorf("expected customMetadata to be empty, got %v", signer.customMetadata) + } + }) + + t.Run("MetadataAnnotations", func(t *testing.T) { + annotations := map[string]string{ + "metadata.command-issuer.keyfactor.com/key1": "value1", + "metadata.command-issuer.keyfactor.com/key2": "value2", + } + + // Create the signer + signer, err := commandSignerFromIssuerAndSecretData(context.Background(), &spec, annotations, authSecretData, caSecretData) + if err != nil { + t.Fatal(err) + } + + // If there are no annotations, the customMetadata map should be empty + if len(signer.customMetadata) != 2 { + t.Errorf("expected customMetadata to have 2 entries, got %v", signer.customMetadata) + } + + if value, ok := signer.customMetadata["key1"].(string); ok && value == "value1" { + // They are equal + } else { + t.Errorf("expected customMetadata key1 to be value1, got %v", signer.customMetadata["key1"]) + } + + if value, ok := signer.customMetadata["key2"].(string); ok && value == "value2" { + // They are equal + } else { + t.Errorf("expected customMetadata key1 to be value1, got %v", signer.customMetadata["key1"]) + } + }) + + t.Run("AnnotationDefaultOverrides", func(t *testing.T) { + annotations := map[string]string{ + "command-issuer.keyfactor.com/certificateTemplate": "TestCertificateTemplate", + "command-issuer.keyfactor.com/certificateAuthorityLogicalName": "TestCertificateAuthorityLogicalName", + "command-issuer.keyfactor.com/certificateAuthorityHostname": "TestCertificateAuthorityHostname", + "command-manager.io/certificate-name": "TestCertificateName", + } + + // Create the signer + signer, err := commandSignerFromIssuerAndSecretData(context.Background(), &spec, annotations, authSecretData, caSecretData) + if err != nil { + t.Fatal(err) + } + + assert.Equal(t, "TestCertificateTemplate", signer.certificateTemplate) + assert.Equal(t, "TestCertificateAuthorityLogicalName", signer.certificateAuthorityLogicalName) + assert.Equal(t, "TestCertificateAuthorityHostname", signer.certificateAuthorityHostname) + assert.Equal(t, "TestCertificateName", signer.certManagerCertificateName) + }) +} + +func TestCompileCertificatesToPemBytes(t *testing.T) { + // Generate two certificates for testing + cert1, err := generateSelfSignedCertificate() if err != nil { - t.Fatal(err) + t.Fatalf("failed to generate mock certificate: %v", err) + } + cert2, err := generateSelfSignedCertificate() + if err != nil { + t.Fatalf("failed to generate mock certificate: %v", err) + } + + tests := []struct { + name string + certificates []*x509.Certificate + expectedError bool + }{ + { + name: "No certificates", + certificates: []*x509.Certificate{}, + expectedError: false, + }, + { + name: "Single certificate", + certificates: []*x509.Certificate{cert1}, + expectedError: false, + }, + { + name: "Multiple certificates", + certificates: []*x509.Certificate{cert1, cert2}, + expectedError: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + _, err := compileCertificatesToPemBytes(tt.certificates) + if (err != nil) != tt.expectedError { + t.Errorf("expected error = %v, got %v", tt.expectedError, err) + } + }) } +} - meta := K8sMetadata{ - ControllerNamespace: "test-namespace", - ControllerKind: "Issuer", - ControllerResourceGroupName: "test-issuer.example.com", - IssuerName: "test-issuer", - IssuerNamespace: "test-namespace", - ControllerReconcileId: "GUID", - CertificateSigningRequestNamespace: "test-namespace", +func Test_extractMetadataFromAnnotations(t *testing.T) { + tests := []struct { + name string + annotations map[string]string + expected map[string]interface{} + }{ + { + name: "empty annotations", + annotations: map[string]string{}, + expected: map[string]interface{}{}, + }, + { + name: "annotations without metadata prefix", + annotations: map[string]string{ + "key1": "value1", + "key2": "value2", + }, + expected: map[string]interface{}{}, + }, + { + name: "annotations with metadata prefix", + annotations: map[string]string{ + "metadata.command-issuer.keyfactor.com/key1": "value1", + "key2": "value2", + }, + expected: map[string]interface{}{ + "key1": "value1", + }, + }, + { + name: "mixed annotations", + annotations: map[string]string{ + "metadata.command-issuer.keyfactor.com/key1": "value1", + "metadata.command-issuer.keyfactor.com/key2": "value2", + "key3": "value3", + }, + expected: map[string]interface{}{ + "key1": "value1", + "key2": "value2", + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := extractMetadataFromAnnotations(tt.annotations) + if !reflect.DeepEqual(result, tt.expected) { + t.Errorf("expected %v, got %v", tt.expected, result) + } + }) } +} - start := time.Now() - signer, err := obj.SignerBuilder(getTestSignerConfigItems(t)) +func Test_createCommandClientFromSecretData(t *testing.T) { + cert1, err := generateSelfSignedCertificate() if err != nil { - t.Fatal(err) + t.Fatalf("failed to generate self-signed certificate: %v", err) } - signed, err := signer.Sign(context.Background(), csr, meta) + cert2, err := generateSelfSignedCertificate() if err != nil { - t.Fatal(err) + t.Fatalf("failed to generate self-signed certificate: %v", err) } - t.Logf("Signing took %s", time.Since(start)) - t.Logf("Signed certificate: %s", string(signed)) + certBytes, err := compileCertificatesToPemBytes([]*x509.Certificate{cert1, cert2}) + if err != nil { + return + } + + tests := []struct { + name string + spec commandissuer.IssuerSpec + authSecretData map[string][]byte + caSecretData map[string][]byte + verify func(*testing.T, *keyfactor.APIClient) error + expectedErr bool + }{ + { + name: "EmptySecretData", + authSecretData: map[string][]byte{ + "username": []byte(""), + "password": []byte(""), + }, + verify: func(t *testing.T, client *keyfactor.APIClient) error { + if client != nil { + return fmt.Errorf("expected client to be nil") + } + return nil + }, + expectedErr: true, + }, + { + name: "ValidAuthData", + spec: commandissuer.IssuerSpec{ + Hostname: "hostname", + }, + authSecretData: map[string][]byte{ + "username": []byte("username"), + "password": []byte("password"), + }, + verify: func(t *testing.T, client *keyfactor.APIClient) error { + if client == nil { + return fmt.Errorf("expected client to be non-nil") + } + + if client.GetConfig().Host != "hostname" { + return fmt.Errorf("expected hostname to be hostname, got %s", client.GetConfig().Host) + } + + if client.GetConfig().BasicAuth.UserName != "username" { + return fmt.Errorf("expected username to be username, got %s", client.GetConfig().BasicAuth.UserName) + } + + if client.GetConfig().BasicAuth.Password != "password" { + return fmt.Errorf("expected password to be password, got %s", client.GetConfig().BasicAuth.Password) + } + + return nil + }, + expectedErr: false, + }, + { + name: "InvalidCaData", + spec: commandissuer.IssuerSpec{ + Hostname: "hostname", + }, + authSecretData: map[string][]byte{ + "username": []byte("username"), + "password": []byte("password"), + }, + caSecretData: map[string][]byte{ + "tls.crt": certBytes, + }, + verify: func(t *testing.T, client *keyfactor.APIClient) error { + if client == nil { + return fmt.Errorf("expected client to be non-nil") + } + + return nil + }, + expectedErr: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result, err := createCommandClientFromSecretData(context.Background(), &tt.spec, tt.authSecretData, tt.caSecretData) + if (err != nil) != tt.expectedErr { + t.Errorf("expected error = %v, got %v", tt.expectedErr, err) + } + if err = tt.verify(t, result); err != nil { + t.Error(err) + } + }) + } } func getTestHealthCheckerConfigItems(t *testing.T) (context.Context, *commandissuer.IssuerSpec, map[string][]byte, map[string][]byte) { @@ -142,7 +455,7 @@ func getTestSignerConfigItems(t *testing.T) (context.Context, *commandissuer.Iss // Read the CA cert from the file system. caCertBytes, err := os.ReadFile(pathToCaCert) if err != nil { - t.Log("CA cert not found, assuming that EJBCA is using a trusted CA") + t.Log("CA cert not found, assuming that Command is using a trusted CA") } caSecretData := map[string][]byte{} @@ -219,3 +532,31 @@ func parseSubjectDN(subject string, randomizeCn bool) (pkix.Name, error) { return name, nil } + +func generateSelfSignedCertificate() (*x509.Certificate, error) { + priv, err := rsa.GenerateKey(rand.Reader, 2048) + if err != nil { + return nil, err + } + + template := x509.Certificate{ + SerialNumber: big.NewInt(1), + Subject: pkix.Name{CommonName: "test"}, + NotBefore: time.Now(), + NotAfter: time.Now().Add(time.Hour), + KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign, + BasicConstraintsValid: true, + } + + certDER, err := x509.CreateCertificate(rand.Reader, &template, &template, &priv.PublicKey, priv) + if err != nil { + return nil, err + } + + cert, err := x509.ParseCertificate(certDER) + if err != nil { + return nil, err + } + + return cert, nil +} From c849c6408bb8eb70c7d4547af2b004794b36e763 Mon Sep 17 00:00:00 2001 From: Hayden Roszell Date: Fri, 6 Oct 2023 12:10:23 -0700 Subject: [PATCH 06/17] chore: Replace string format with --- internal/issuer/signer/signer.go | 17 +++++++++-------- internal/issuer/signer/signer_test.go | 14 +++++++------- 2 files changed, 16 insertions(+), 15 deletions(-) diff --git a/internal/issuer/signer/signer.go b/internal/issuer/signer/signer.go index b926959..a1846d3 100644 --- a/internal/issuer/signer/signer.go +++ b/internal/issuer/signer/signer.go @@ -32,7 +32,8 @@ import ( const ( // Keyfactor enrollment PEM format - enrollmentPEMFormat = "PEM" + enrollmentPEMFormat = "PEM" + commandMetadataAnnotationPrefix = "metadata.command-issuer.keyfactor.com/" ) type K8sMetadata struct { @@ -124,7 +125,7 @@ func commandSignerFromIssuerAndSecretData(ctx context.Context, spec *commandissu signer.certManagerCertificateName = value } - k8sLog.Info(fmt.Sprintf("Using certificate template \"%s\" and certificate authority \"%s\" (%s)", signer.certificateTemplate, signer.certificateAuthorityLogicalName, signer.certificateAuthorityHostname)) + k8sLog.Info(fmt.Sprintf("Using certificate template %q and certificate authority %q (%s)", signer.certificateTemplate, signer.certificateAuthorityLogicalName, signer.certificateAuthorityHostname)) signer.customMetadata = extractMetadataFromAnnotations(annotations) @@ -135,8 +136,8 @@ func extractMetadataFromAnnotations(annotations map[string]string) map[string]in metadata := make(map[string]interface{}) for key, value := range annotations { - if strings.HasPrefix(key, "metadata.command-issuer.keyfactor.com/") { - metadata[strings.TrimPrefix(key, "metadata.command-issuer.keyfactor.com/")] = value + if strings.HasPrefix(key, commandMetadataAnnotationPrefix) { + metadata[strings.TrimPrefix(key, commandMetadataAnnotationPrefix)] = value } } @@ -178,7 +179,7 @@ func (s *commandSigner) Sign(ctx context.Context, csrBytes []byte, k8sMeta K8sMe } // Log the common metadata of the CSR - k8sLog.Info(fmt.Sprintf("Found CSR wtih Common Name \"%s\" and %d DNS SANs, %d IP SANs, and %d URI SANs", csr.Subject.CommonName, len(csr.DNSNames), len(csr.IPAddresses), len(csr.URIs))) + k8sLog.Info(fmt.Sprintf("Found CSR wtih Common Name %q and %d DNS SANs, %d IP SANs, and %d URI SANs", csr.Subject.CommonName, len(csr.DNSNames), len(csr.IPAddresses), len(csr.URIs))) // Print the SANs for _, dnsName := range csr.DNSNames { @@ -210,7 +211,7 @@ func (s *commandSigner) Sign(ctx context.Context, csrBytes []byte, k8sMeta K8sMe } for metaName, value := range s.customMetadata { - k8sLog.Info(fmt.Sprintf("Adding metadata \"%s\" with value \"%s\"", metaName, value)) + k8sLog.Info(fmt.Sprintf("Adding metadata %q with value %q", metaName, value)) modelRequest.Metadata[metaName] = value } @@ -226,7 +227,7 @@ func (s *commandSigner) Sign(ctx context.Context, csrBytes []byte, k8sMeta K8sMe commandCsrResponseObject, _, err := s.client.EnrollmentApi.EnrollmentPostCSREnroll(context.Background()).Request(modelRequest).XCertificateformat(enrollmentPEMFormat).Execute() if err != nil { - detail := fmt.Sprintf("error enrolling certificate with Command. Verify that the certificate template \"%s\" exists and that the certificate authority \"%s\" (%s) is configured correctly.", s.certificateTemplate, s.certificateAuthorityLogicalName, s.certificateAuthorityHostname) + detail := fmt.Sprintf("error enrolling certificate with Command. Verify that the certificate template %q exists and that the certificate authority %q (%s) is configured correctly.", s.certificateTemplate, s.certificateAuthorityLogicalName, s.certificateAuthorityHostname) if len(s.customMetadata) > 0 { detail += " Also verify that the metadata fields provided exist in Command." @@ -248,7 +249,7 @@ func (s *commandSigner) Sign(ctx context.Context, csrBytes []byte, k8sMeta K8sMe return nil, err } - k8sLog.Info(fmt.Sprintf("Successfully enrolled certificate with Command with subject \"%s\". Certificate has %d SANs", certAndChain[0].Subject, len(certAndChain[0].DNSNames)+len(certAndChain[0].IPAddresses)+len(certAndChain[0].URIs))) + k8sLog.Info(fmt.Sprintf("Successfully enrolled certificate with Command with subject %q. Certificate has %d SANs", certAndChain[0].Subject, len(certAndChain[0].DNSNames)+len(certAndChain[0].IPAddresses)+len(certAndChain[0].URIs))) // Return the certificate and chain in PEM format return compileCertificatesToPemBytes(certAndChain) diff --git a/internal/issuer/signer/signer_test.go b/internal/issuer/signer/signer_test.go index d7b8c25..232e506 100644 --- a/internal/issuer/signer/signer_test.go +++ b/internal/issuer/signer/signer_test.go @@ -154,8 +154,8 @@ func TestCommandSignerFromIssuerAndSecretData(t *testing.T) { t.Run("MetadataAnnotations", func(t *testing.T) { annotations := map[string]string{ - "metadata.command-issuer.keyfactor.com/key1": "value1", - "metadata.command-issuer.keyfactor.com/key2": "value2", + commandMetadataAnnotationPrefix + "key1": "value1", + commandMetadataAnnotationPrefix + "key2": "value2", } // Create the signer @@ -268,8 +268,8 @@ func Test_extractMetadataFromAnnotations(t *testing.T) { { name: "annotations with metadata prefix", annotations: map[string]string{ - "metadata.command-issuer.keyfactor.com/key1": "value1", - "key2": "value2", + commandMetadataAnnotationPrefix + "key1": "value1", + "key2": "value2", }, expected: map[string]interface{}{ "key1": "value1", @@ -278,9 +278,9 @@ func Test_extractMetadataFromAnnotations(t *testing.T) { { name: "mixed annotations", annotations: map[string]string{ - "metadata.command-issuer.keyfactor.com/key1": "value1", - "metadata.command-issuer.keyfactor.com/key2": "value2", - "key3": "value3", + commandMetadataAnnotationPrefix + "key1": "value1", + commandMetadataAnnotationPrefix + "key2": "value2", + "key3": "value3", }, expected: map[string]interface{}{ "key1": "value1", From ac412faf7732bf56a180c4bde7730769ab7d27b6 Mon Sep 17 00:00:00 2001 From: Hayden Roszell Date: Fri, 6 Oct 2023 12:25:55 -0700 Subject: [PATCH 07/17] bug(release): Force org/repository inside to lowercase --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 6a337b0..ebc2ad5 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -16,7 +16,7 @@ on: env: REGISTRY: ghcr.io - IMAGE_NAME: ${{ github.repository }} + IMAGE_NAME: ${{ github.repository }}.toLowerCase() jobs: build: From 5a8369d5c5e80057b07eb5f6ec40d6aa79980b9a Mon Sep 17 00:00:00 2001 From: Hayden Roszell Date: Fri, 6 Oct 2023 13:13:20 -0700 Subject: [PATCH 08/17] bug(release): Force org/repository inside to lowercase with parameter expansion --- .github/workflows/release.yml | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index ebc2ad5..65f622a 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -16,7 +16,6 @@ on: env: REGISTRY: ghcr.io - IMAGE_NAME: ${{ github.repository }}.toLowerCase() jobs: build: @@ -36,6 +35,11 @@ jobs: packages: write steps: + + - name: Set IMAGE_NAME + run: | + echo "IMAGE_NAME=${GITHUB_REPOSITORY,,}" >>${GITHUB_ENV} + # Checkout code # https://github.com/actions/checkout - name: Checkout code @@ -105,6 +109,10 @@ jobs: needs: - build steps: + - name: Set IMAGE_NAME + run: | + echo "IMAGE_NAME=${GITHUB_REPOSITORY,,}" >>${GITHUB_ENV} + # Download digests # https://github.com/actions/download-artifact - name: Download digests @@ -153,6 +161,10 @@ jobs: needs: - merge steps: + - name: Set IMAGE_NAME + run: | + echo "IMAGE_NAME=${GITHUB_REPOSITORY,,}" >>${GITHUB_ENV} + # Checkout code # https://github.com/actions/checkout - name: Checkout code From 5839f488147f8c7be21817d39c63f1c048f87b90 Mon Sep 17 00:00:00 2001 From: Hayden Roszell Date: Wed, 11 Oct 2023 15:34:57 -0700 Subject: [PATCH 09/17] chore: Create feature parity between cert-manager certificaterequest and command-issuer, update docs --- CHANGELOG.md | 9 ++++ README.md | 7 +-- config/manager/kustomization.yaml | 2 +- config/rbac/leader_election_role.yaml | 12 ------ .../certificaterequest_controller.go | 5 ++- .../certificaterequest_controller_test.go | 4 +- internal/issuer/signer/signer.go | 43 ++++++++++++------- internal/issuer/signer/signer_test.go | 16 +++---- 8 files changed, 49 insertions(+), 49 deletions(-) create mode 100644 CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..da350c9 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,9 @@ +# v1.0.4 + +## Features +* feat(signer): Signer recognizes `metadata.command-issuer.keyfactor.com/: ` annotations on the CertificateRequest resource and uses them to populate certificate metadata in Command. +* feat(release): Container build and release now uses GitHub Actions. +* fix(helm): CRDs now correspond to correct values for the `command-issuer`. +* fix(helm): Signer Helm Chart now includes a `secureMetrics` value to enable/disable sidecar RBAC container for further protection of the `/metrics` endpoint. +* fix(signer): Signer now returns CA chain bytes instead of appending to the leaf certificate. +* fix(role): Removed permissions for `configmaps` resource types for the `leader-election-role` role. \ No newline at end of file diff --git a/README.md b/README.md index 308428e..afdfd79 100644 --- a/README.md +++ b/README.md @@ -517,12 +517,6 @@ cat <> metadata.json "Description": "The group name of the resource that the Issuer or ClusterIssuer controller is managing.", "Name": "Controller-Resource-Group-Name" }, - { - "AllowAPI": true, - "DataType": 1, - "Description": "The name of the K8s issuer resource", - "Name": "Issuer-Name" - }, { "AllowAPI": true, "DataType": 1, @@ -540,6 +534,7 @@ cat <> metadata.json "CustomReports": [], "SecurityRoles": [] } +EOF kfutil import --metadata --file metadata.json ``` diff --git a/config/manager/kustomization.yaml b/config/manager/kustomization.yaml index 3bcb896..ace19ce 100644 --- a/config/manager/kustomization.yaml +++ b/config/manager/kustomization.yaml @@ -5,4 +5,4 @@ kind: Kustomization images: - name: controller newName: ghcr.io/keyfactor/command-cert-manager-issuer - newTag: v1.0.4 + newTag: latest diff --git a/config/rbac/leader_election_role.yaml b/config/rbac/leader_election_role.yaml index 9028e07..bbeff7a 100644 --- a/config/rbac/leader_election_role.yaml +++ b/config/rbac/leader_election_role.yaml @@ -11,18 +11,6 @@ metadata: app.kubernetes.io/managed-by: kustomize name: leader-election-role rules: -- apiGroups: - - "" - resources: - - configmaps - verbs: - - get - - list - - watch - - create - - update - - patch - - delete - apiGroups: - coordination.k8s.io resources: diff --git a/internal/controllers/certificaterequest_controller.go b/internal/controllers/certificaterequest_controller.go index b07851d..65c531a 100644 --- a/internal/controllers/certificaterequest_controller.go +++ b/internal/controllers/certificaterequest_controller.go @@ -247,11 +247,12 @@ func (r *CertificateRequestReconciler) Reconcile(ctx context.Context, req ctrl.R meta.ControllerReconcileId = string(controller.ReconcileIDFromContext(ctx)) meta.CertificateSigningRequestNamespace = certificateRequest.Namespace - signed, err := commandSigner.Sign(ctx, certificateRequest.Spec.Request, meta) + leaf, chain, err := commandSigner.Sign(ctx, certificateRequest.Spec.Request, meta) if err != nil { return ctrl.Result{}, fmt.Errorf("%w: %v", errSignerSign, err) } - certificateRequest.Status.Certificate = signed + certificateRequest.Status.Certificate = leaf + certificateRequest.Status.CA = chain setReadyCondition(cmmeta.ConditionTrue, cmapi.CertificateRequestReasonIssued, "Signed") return ctrl.Result{}, nil diff --git a/internal/controllers/certificaterequest_controller_test.go b/internal/controllers/certificaterequest_controller_test.go index 718e609..f113031 100644 --- a/internal/controllers/certificaterequest_controller_test.go +++ b/internal/controllers/certificaterequest_controller_test.go @@ -51,8 +51,8 @@ type fakeSigner struct { errSign error } -func (o *fakeSigner) Sign(context.Context, []byte, signer.K8sMetadata) ([]byte, error) { - return []byte("fake signed certificate"), o.errSign +func (o *fakeSigner) Sign(context.Context, []byte, signer.K8sMetadata) ([]byte, []byte, error) { + return []byte("fake signed certificate"), []byte("fake ca chain"), o.errSign } func TestCertificateRequestReconcile(t *testing.T) { diff --git a/internal/issuer/signer/signer.go b/internal/issuer/signer/signer.go index a1846d3..187f55d 100644 --- a/internal/issuer/signer/signer.go +++ b/internal/issuer/signer/signer.go @@ -63,7 +63,7 @@ type HealthCheckerBuilder func(context.Context, *commandissuer.IssuerSpec, map[s type CommandSignerBuilder func(context.Context, *commandissuer.IssuerSpec, map[string]string, map[string][]byte, map[string][]byte) (Signer, error) type Signer interface { - Sign(context.Context, []byte, K8sMetadata) ([]byte, error) + Sign(context.Context, []byte, K8sMetadata) ([]byte, []byte, error) } func CommandHealthCheckerFromIssuerAndSecretData(ctx context.Context, spec *commandissuer.IssuerSpec, authSecretData map[string][]byte, caSecretData map[string][]byte) (HealthChecker, error) { @@ -169,13 +169,13 @@ func (s *commandSigner) Check() error { return errors.New("missing \"POST /Enrollment/CSR\" endpoint") } -func (s *commandSigner) Sign(ctx context.Context, csrBytes []byte, k8sMeta K8sMetadata) ([]byte, error) { +func (s *commandSigner) Sign(ctx context.Context, csrBytes []byte, k8sMeta K8sMetadata) ([]byte, []byte, error) { k8sLog := log.FromContext(ctx) csr, err := parseCSR(csrBytes) if err != nil { k8sLog.Error(err, "failed to parse CSR") - return nil, err + return nil, nil, err } // Log the common metadata of the CSR @@ -241,12 +241,12 @@ func (s *commandSigner) Sign(ctx context.Context, csrBytes []byte, k8sMeta K8sMe k8sLog.Error(err, detail) - return nil, fmt.Errorf(detail) + return nil, nil, fmt.Errorf(detail) } certAndChain, err := getCertificatesFromCertificateInformation(commandCsrResponseObject.CertificateInformation) if err != nil { - return nil, err + return nil, nil, err } k8sLog.Info(fmt.Sprintf("Successfully enrolled certificate with Command with subject %q. Certificate has %d SANs", certAndChain[0].Subject, len(certAndChain[0].DNSNames)+len(certAndChain[0].IPAddresses)+len(certAndChain[0].URIs))) @@ -277,20 +277,31 @@ func getCertificatesFromCertificateInformation(commandResp *keyfactor.ModelsPkcs // compileCertificatesToPemString takes a slice of x509 certificates and returns a string containing the certificates in PEM format // If an error occurred, the function logs the error and continues to parse the remaining objects. -func compileCertificatesToPemBytes(certificates []*x509.Certificate) ([]byte, error) { - var pemBuilder strings.Builder - - for _, certificate := range certificates { - err := pem.Encode(&pemBuilder, &pem.Block{ - Type: "CERTIFICATE", - Bytes: certificate.Raw, - }) - if err != nil { - return make([]byte, 0), err +func compileCertificatesToPemBytes(certificates []*x509.Certificate) ([]byte, []byte, error) { + var leaf strings.Builder + var chain strings.Builder + + for i, certificate := range certificates { + if i == 0 { + err := pem.Encode(&leaf, &pem.Block{ + Type: "CERTIFICATE", + Bytes: certificate.Raw, + }) + if err != nil { + return make([]byte, 0), make([]byte, 0), err + } + } else { + err := pem.Encode(&chain, &pem.Block{ + Type: "CERTIFICATE", + Bytes: certificate.Raw, + }) + if err != nil { + return make([]byte, 0), make([]byte, 0), err + } } } - return []byte(pemBuilder.String()), nil + return []byte(leaf.String()), []byte(chain.String()), nil } const ( diff --git a/internal/issuer/signer/signer_test.go b/internal/issuer/signer/signer_test.go index 232e506..2362d14 100644 --- a/internal/issuer/signer/signer_test.go +++ b/internal/issuer/signer/signer_test.go @@ -86,13 +86,14 @@ func TestCommandSignerFromIssuerAndSecretData(t *testing.T) { t.Fatal(err) } - signed, err := signer.Sign(context.Background(), csr, meta) + leaf, chain, err := signer.Sign(context.Background(), csr, meta) if err != nil { t.Fatal(err) } t.Logf("Signing took %s", time.Since(start)) - t.Logf("Signed certificate: %s", string(signed)) + t.Logf("Signed certificate: %s", string(leaf)) + t.Logf("Chain: %s", string(chain)) }) // Set up test data @@ -238,7 +239,7 @@ func TestCompileCertificatesToPemBytes(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - _, err := compileCertificatesToPemBytes(tt.certificates) + _, _, err = compileCertificatesToPemBytes(tt.certificates) if (err != nil) != tt.expectedError { t.Errorf("expected error = %v, got %v", tt.expectedError, err) } @@ -305,12 +306,7 @@ func Test_createCommandClientFromSecretData(t *testing.T) { t.Fatalf("failed to generate self-signed certificate: %v", err) } - cert2, err := generateSelfSignedCertificate() - if err != nil { - t.Fatalf("failed to generate self-signed certificate: %v", err) - } - - certBytes, err := compileCertificatesToPemBytes([]*x509.Certificate{cert1, cert2}) + leafBytes, _, err := compileCertificatesToPemBytes([]*x509.Certificate{cert1}) if err != nil { return } @@ -377,7 +373,7 @@ func Test_createCommandClientFromSecretData(t *testing.T) { "password": []byte("password"), }, caSecretData: map[string][]byte{ - "tls.crt": certBytes, + "tls.crt": leafBytes, }, verify: func(t *testing.T, client *keyfactor.APIClient) error { if client == nil { From 050247ef469a1a29eceb814ca4c906c62b05cde6 Mon Sep 17 00:00:00 2001 From: Hayden Roszell Date: Wed, 11 Oct 2023 16:09:39 -0700 Subject: [PATCH 10/17] chore: Change action workflow to pull from vars instead of secrets for nonprotective fields --- .github/workflows/test.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 3887025..988a2a7 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -32,10 +32,10 @@ jobs: cache: true - run: go mod download - env: - COMMAND_CERTIFICATE_AUTHORITY_HOSTNAME: ${{ secrets.COMMAND_CERTIFICATE_AUTHORITY_HOSTNAME }} - COMMAND_CERTIFICATE_AUTHORITY_LOGICAL_NAME: ${{ secrets.COMMAND_CERTIFICATE_AUTHORITY_LOGICAL_NAME }} - COMMAND_CERTIFICATE_TEMPLATE: ${{ secrets.COMMAND_CERTIFICATE_TEMPLATE }} - COMMAND_HOSTNAME: ${{ secrets.COMMAND_HOSTNAME }} + COMMAND_CERTIFICATE_AUTHORITY_HOSTNAME: ${{ vars.COMMAND_CERTIFICATE_AUTHORITY_HOSTNAME }} + COMMAND_CERTIFICATE_AUTHORITY_LOGICAL_NAME: ${{ vars.COMMAND_CERTIFICATE_AUTHORITY_LOGICAL_NAME }} + COMMAND_CERTIFICATE_TEMPLATE: ${{ vars.COMMAND_CERTIFICATE_TEMPLATE }} + COMMAND_HOSTNAME: ${{ vars.COMMAND_HOSTNAME }} COMMAND_USERNAME: ${{ secrets.COMMAND_USERNAME }} COMMAND_PASSWORD: ${{ secrets.COMMAND_PASSWORD }} name: Run go test From 0d0a5e2bd6590beca733373f4b30ad396bf081af Mon Sep 17 00:00:00 2001 From: Hayden Roszell Date: Wed, 11 Oct 2023 16:14:23 -0700 Subject: [PATCH 11/17] fix(signer): Copy CN to DNS SAN for signer test --- internal/issuer/signer/signer_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/internal/issuer/signer/signer_test.go b/internal/issuer/signer/signer_test.go index 2362d14..375ec9a 100644 --- a/internal/issuer/signer/signer_test.go +++ b/internal/issuer/signer/signer_test.go @@ -473,6 +473,7 @@ func generateCSR(subject string) ([]byte, error) { template := x509.CertificateRequest{ Subject: subj, SignatureAlgorithm: x509.SHA256WithRSA, + DNSNames: []string{subj.CommonName}, } var csrBuf bytes.Buffer csrBytes, _ := x509.CreateCertificateRequest(rand.Reader, &template, keyBytes) From b76c246c562342ad46c0ca582a77e5af4484bc5c Mon Sep 17 00:00:00 2001 From: Hayden Roszell Date: Tue, 17 Oct 2023 10:03:06 -0700 Subject: [PATCH 12/17] chore: Clarify hostname requirements, refactor examples to include extended capabilities --- README.md | 2 +- config/samples/certificate.yaml | 4 ++++ config/samples/command-issuer_v1alpha1_clusterissuer.yaml | 1 + config/samples/command-issuer_v1alpha1_issuer.yaml | 8 ++++++-- 4 files changed, 12 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index afdfd79..495b6e4 100644 --- a/README.md +++ b/README.md @@ -115,7 +115,7 @@ The Issuer resource is namespaced, while the ClusterIssuer resource is cluster-s For example, ClusterIssuer resources can be used to issue certificates for resources in multiple namespaces, whereas Issuer resources can only be used to issue certificates for resources in the same namespace. The `spec` field of both the Issuer and ClusterIssuer resources use the following fields: -* `hostname` - The hostname of the Keyfactor Command server +* `hostname` - The hostname of the Keyfactor Command server - The signer sets the protocol to `https` and automatically trims the trailing path from the hostname. Additionally, the base Command API path is automatically set to `/KeyfactorAPI` and cannot be changed. * `commandSecretName` - The name of the Kubernetes `kubernetes.io/basic-auth` secret containing credentials to the Keyfactor instance * `certificateTemplate` - The short name corresponding to a template in Command that will be used to issue certificates. * `certificateAuthorityLogicalName` - The logical name of the CA to use to sign the certificate request diff --git a/config/samples/certificate.yaml b/config/samples/certificate.yaml index 23a389a..4a11be7 100644 --- a/config/samples/certificate.yaml +++ b/config/samples/certificate.yaml @@ -2,6 +2,10 @@ apiVersion: cert-manager.io/v1 kind: Certificate metadata: name: command-certificate + annotations: + command-issuer.keyfactor.com/certificateTemplate: "Ephemeral2day" + command-issuer.keyfactor.com/certificateAuthorityLogicalName: "InternalIssuingCA1" + metadata.command-issuer.keyfactor.com/ResponsibleTeam: "theResponsibleTeam@example.com" spec: commonName: command-issuer-sample secretName: command-certificate diff --git a/config/samples/command-issuer_v1alpha1_clusterissuer.yaml b/config/samples/command-issuer_v1alpha1_clusterissuer.yaml index ab2ff76..4cce43e 100644 --- a/config/samples/command-issuer_v1alpha1_clusterissuer.yaml +++ b/config/samples/command-issuer_v1alpha1_clusterissuer.yaml @@ -13,3 +13,4 @@ spec: certificateTemplate: "" certificateAuthorityLogicalName: "" certificateAuthorityHostname: "" + caSecretName: "" diff --git a/config/samples/command-issuer_v1alpha1_issuer.yaml b/config/samples/command-issuer_v1alpha1_issuer.yaml index c1307df..faa4d88 100644 --- a/config/samples/command-issuer_v1alpha1_issuer.yaml +++ b/config/samples/command-issuer_v1alpha1_issuer.yaml @@ -5,8 +5,12 @@ metadata: app.kubernetes.io/name: issuer app.kubernetes.io/instance: issuer-sample app.kubernetes.io/part-of: command-issuer - app.kubernetes.io/managed-by: kustomize app.kubernetes.io/created-by: command-issuer name: issuer-sample spec: - # TODO(user): Add fields here + hostname: "" + commandSecretName: "" + certificateTemplate: "" + certificateAuthorityLogicalName: "" + certificateAuthorityHostname: "" + caSecretName: "" From df34fe2d8d0d5c3b50727a0f2bb346abfe466ec1 Mon Sep 17 00:00:00 2001 From: Hayden Roszell Date: Tue, 17 Oct 2023 10:05:35 -0700 Subject: [PATCH 13/17] chore: Clarify hostname requirements, refactor examples to include extended capabilities --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 495b6e4..734f6a5 100644 --- a/README.md +++ b/README.md @@ -115,7 +115,7 @@ The Issuer resource is namespaced, while the ClusterIssuer resource is cluster-s For example, ClusterIssuer resources can be used to issue certificates for resources in multiple namespaces, whereas Issuer resources can only be used to issue certificates for resources in the same namespace. The `spec` field of both the Issuer and ClusterIssuer resources use the following fields: -* `hostname` - The hostname of the Keyfactor Command server - The signer sets the protocol to `https` and automatically trims the trailing path from the hostname. Additionally, the base Command API path is automatically set to `/KeyfactorAPI` and cannot be changed. +* `hostname` - The hostname of the Keyfactor Command server - The signer sets the protocol to `https` and automatically trims the trailing path from this field, if it exists. Additionally, the base Command API path is automatically set to `/KeyfactorAPI` and cannot be changed. * `commandSecretName` - The name of the Kubernetes `kubernetes.io/basic-auth` secret containing credentials to the Keyfactor instance * `certificateTemplate` - The short name corresponding to a template in Command that will be used to issue certificates. * `certificateAuthorityLogicalName` - The logical name of the CA to use to sign the certificate request From 3c134688478cfafc1281f9b584db5f92dbae98df Mon Sep 17 00:00:00 2001 From: Hayden Roszell Date: Tue, 17 Oct 2023 11:37:48 -0700 Subject: [PATCH 14/17] chore: Refactor Helm steps in readme and update makefile to download latest --- Makefile | 2 +- README.md | 66 +++++++++++-------- .../command-cert-manager-issuer/README.md | 55 ++++++++++------ 3 files changed, 75 insertions(+), 48 deletions(-) diff --git a/Makefile b/Makefile index fba1c67..ece267c 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ # The version which will be reported by the --version argument of each binary # and which will be used as the Docker image tag -VERSION ?= v1.0.4 +VERSION ?= latest # The Docker repository name, overridden in CI. DOCKER_REGISTRY ?= ghcr.io DOCKER_IMAGE_NAME ?= keyfactor/command-cert-manager-issuer diff --git a/README.md b/README.md index 734f6a5..a365f1f 100644 --- a/README.md +++ b/README.md @@ -45,42 +45,56 @@ kubectl get nodes ### Installation from Manifests -Once Kubernetes is running, a static installation of cert-manager can be installed with the following command: -```shell -kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.11.0/cert-manager.yaml -``` +1. Once Kubernetes is running, a static installation of cert-manager can be installed with the following command: + ```shell + kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.11.0/cert-manager.yaml + ``` -###### :pushpin: Running the static cert-manager configuration is not recommended for production use. For more information, see [Installing cert-manager](https://cert-manager.io/docs/installation/). + ###### :pushpin: Running the static cert-manager configuration is not recommended for production use. For more information, see [Installing cert-manager](https://cert-manager.io/docs/installation/). -Then, install the custom resource definitions (CRDs) for the cert-manager external issuer for Keyfactor Command: -```shell -make install -``` +2. Then, install the custom resource definitions (CRDs) for the cert-manager external issuer for Keyfactor Command: + ```shell + make install + ``` -Finally, deploy the controller to the cluster: -```shell -make deploy -``` +3. Finally, deploy the controller to the cluster: + ```shell + make deploy + ``` ### Installation from Helm Chart The cert-manager external issuer for Keyfactor Command can also be installed using a Helm chart. The chart is available in the [Command cert-manager Helm repository](https://keyfactor.github.io/command-cert-manager-issuer/). -First, add the Helm repository: -```bash -helm repo add command-issuer https://keyfactor.github.io/command-cert-manager-issuer -helm repo update -``` +1. Add the Helm repository: + ```bash + helm repo add command-issuer https://keyfactor.github.io/command-cert-manager-issuer + helm repo update + ``` -Then, install the chart: -```bash -helm install command-cert-manager-issuer command-issuer/command-cert-manager-issuer -``` +2. Then, install the chart: + ```bash + helm install command-cert-manager-issuer command-issuer/command-cert-manager-issuer + ``` -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: -```bash -helm install command-cert-manager-issuer command-issuer/command-cert-manager-issuer --set replicaCount=2 -``` + 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: + + helm install command-cert-manager-issuer command-issuer/command-cert-manager-issuer \ + --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: + + cat < override.yaml + replicaCount: 2 + EOF + + Then, use the `-f` flag to specify the `values.yaml` file: + + ```yaml + helm install command-cert-manager-issuer command-issuer/command-cert-manager-issuer \ + -f override.yaml + ``` ## Usage The cert-manager external issuer for Keyfactor Command can be used to issue certificates from Keyfactor Command using cert-manager. diff --git a/deploy/charts/command-cert-manager-issuer/README.md b/deploy/charts/command-cert-manager-issuer/README.md index 1ae5f8f..e992379 100644 --- a/deploy/charts/command-cert-manager-issuer/README.md +++ b/deploy/charts/command-cert-manager-issuer/README.md @@ -31,30 +31,43 @@ helm install command-cert-manager-issuer command-issuer/command-cert-manager-iss 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: ```bash -helm install command-cert-manager-issuer command-issuer/command-cert-manager-issuer --set replicaCount=2 +helm install command-cert-manager-issuer command-issuer/command-cert-manager-issuer \ + --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: +```yaml +cat < override.yaml +replicaCount: 2 +EOF +``` +Then, use the `-f` flag to specify the `values.yaml` file: +```bash +helm install command-cert-manager-issuer command-issuer/command-cert-manager-issuer \ + -f override.yaml ``` ## Configuration The following table lists the configurable parameters of the `command-cert-manager-issuer` chart and their default values. -| Parameter | Description | Default | -|-----------------------------------|-------------------------------------------------------|----------------------------------------------------------------| -| `replicaCount` | Number of replica command-cert-manager-issuers to run | `1` | -| `image.repository` | Image repository | `m8rmclarenkf/command-cert-manager-external-issuer-controller` | -| `image.pullPolicy` | Image pull policy | `IfNotPresent` | -| `image.tag` | Image tag | `1.0.3` | -| `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) | -| `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 command-cert-manager-issuers to run | `1` | +| `image.repository` | Image repository | `ghcr.io/keyfactor/command-cert-manager-issuer` | +| `image.pullPolicy` | Image pull policy | `IfNotPresent` | +| `image.tag` | Image tag | `""` | +| `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) | +| `resources` | CPU/Memory resource requests/limits | `{}` (with commented out options) | +| `nodeSelector` | Node labels for pod assignment | `{}` | +| `tolerations` | Tolerations for pod assignment | `[]` | From 0a55ed3640243f681d0b3ff58a6251eaa613131a Mon Sep 17 00:00:00 2001 From: Hayden Roszell Date: Mon, 23 Oct 2023 10:31:20 -0700 Subject: [PATCH 15/17] chore: Refactor Docs to use doc tree instead of lengthy readme --- Makefile | 15 +- README.md | 561 +----------------- .../command-cert-manager-issuer/values.yaml | 2 +- docs/annotations.markdown | 63 ++ docs/config_usage.markdown | 242 ++++++++ docs/example.markdown | 189 ++++++ docs/install.markdown | 125 ++++ docs/testing.markdown | 32 + 8 files changed, 667 insertions(+), 562 deletions(-) create mode 100644 docs/annotations.markdown create mode 100644 docs/config_usage.markdown create mode 100644 docs/example.markdown create mode 100644 docs/install.markdown create mode 100644 docs/testing.markdown diff --git a/Makefile b/Makefile index ece267c..3a50fea 100644 --- a/Makefile +++ b/Makefile @@ -2,8 +2,8 @@ # and which will be used as the Docker image tag VERSION ?= latest # The Docker repository name, overridden in CI. -DOCKER_REGISTRY ?= ghcr.io -DOCKER_IMAGE_NAME ?= keyfactor/command-cert-manager-issuer +DOCKER_REGISTRY ?= "" +DOCKER_IMAGE_NAME ?= "" # Image URL to use all building/pushing image targets IMG ?= ${DOCKER_REGISTRY}/${DOCKER_IMAGE_NAME}:${VERSION} #IMG ?= command-issuer-dev:latest @@ -67,6 +67,11 @@ test: manifests generate fmt vet envtest ## Run tests. ##@ Build +.PHONY: regcheck +regcheck: ## Check if the docker registry is set. + @test -n "$(DOCKER_REGISTRY)" || (echo "DOCKER_REGISTRY is not set" && exit 1) + @test -n "$(DOCKER_IMAGE_NAME)" || (echo "DOCKER_IMAGE_NAME is not set" && exit 1) + .PHONY: build build: manifests generate fmt vet ## Build manager binary. go build -o bin/manager main.go @@ -79,10 +84,10 @@ run: manifests generate fmt vet ## Run a controller from your host. # (i.e. docker build --platform linux/arm64 ). However, you must enable docker buildKit for it. # More info: https://docs.docker.com/develop/develop-images/build_enhancements/ .PHONY: docker-build -docker-build: test ## Build docker image with the manager. +docker-build: regcheck ## Build docker image with the manager. docker build -t ${IMG} . -.PHONY: docker-push +.PHONY: docker-push regcheck docker-push: ## Push docker image with the manager. docker push ${IMG} @@ -94,7 +99,7 @@ docker-push: ## Push docker image with the manager. # To properly provided solutions that supports more than one platform you should use this option. PLATFORMS ?= linux/arm64,linux/amd64,linux/s390x,linux/ppc64le .PHONY: docker-buildx -docker-buildx: test ## Build and push docker image for the manager for cross-platform support +docker-buildx: regcheck ## Build and push docker image for the manager for cross-platform support # copy existing Dockerfile and insert --platform=${BUILDPLATFORM} into Dockerfile.cross, and preserve the original Dockerfile sed -e '1 s/\(^FROM\)/FROM --platform=\$$\{BUILDPLATFORM\}/; t' -e ' 1,// s//FROM --platform=\$$\{BUILDPLATFORM\}/' Dockerfile > Dockerfile.cross - docker buildx create --name project-v3-builder diff --git a/README.md b/README.md index a365f1f..5b27c16 100644 --- a/README.md +++ b/README.md @@ -18,559 +18,8 @@ The cert-manager external issuer for Keyfactor command is open source and commun ###### To report a problem or suggest a new feature, use the **[Issues](../../issues)** tab. If you want to contribute actual bug fixes or proposed enhancements, see the [contribution guidelines](https://github.com/Keyfactor/command-k8s-csr-signer/blob/main/CONTRIBUTING.md) and use the **[Pull requests](../../pulls)** tab. -## Quick Start - -The quick start guide will walk you through the process of installing the cert-manager external issuer for Keyfactor Command. -The controller image is pulled from [Docker Hub](https://hub.docker.com/r/m8rmclarenkf/command-cert-manager-external-issuer-controller). - -###### To build the container from sources, refer to the [Building Container Image from Source](#building-container-image-from-source) section. - -### Requirements -* [Git](https://git-scm.com/) -* [Make](https://www.gnu.org/software/make/) -* [Docker](https://docs.docker.com/engine/install/) >= v20.10.0 -* [Kubectl](https://kubernetes.io/docs/tasks/tools/install-kubectl/) >= v1.11.3 -* Kubernetes >= v1.19 - * [Kubernetes](https://kubernetes.io/docs/tasks/tools/), [Minikube](https://minikube.sigs.k8s.io/docs/start/), or [Kind](https://kind.sigs.k8s.io/docs/user/quick-start/) -* [Keyfactor Command](https://www.keyfactor.com/products/command/) >= v10.1.0 -* [cert-manager](https://cert-manager.io/docs/installation/) >= v1.11.0 -* [cmctl](https://cert-manager.io/docs/reference/cmctl/) - -Before starting, ensure that all of the above requirements are met, and that Keyfactor Command is properly configured. Refer -to the [Keyfactor Configuration](#keyfactor-command-configuration) section for more information. -Additionally, verify that at least one Kubernetes node is running by running the following command: -```shell -kubectl get nodes -``` - -### Installation from Manifests - -1. Once Kubernetes is running, a static installation of cert-manager can be installed with the following command: - ```shell - kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.11.0/cert-manager.yaml - ``` - - ###### :pushpin: Running the static cert-manager configuration is not recommended for production use. For more information, see [Installing cert-manager](https://cert-manager.io/docs/installation/). - -2. Then, install the custom resource definitions (CRDs) for the cert-manager external issuer for Keyfactor Command: - ```shell - make install - ``` - -3. Finally, deploy the controller to the cluster: - ```shell - make deploy - ``` - -### Installation from Helm Chart - -The cert-manager external issuer for Keyfactor Command can also be installed using a Helm chart. The chart is available in the [Command cert-manager Helm repository](https://keyfactor.github.io/command-cert-manager-issuer/). - -1. Add the Helm repository: - ```bash - helm repo add command-issuer https://keyfactor.github.io/command-cert-manager-issuer - helm repo update - ``` - -2. Then, install the chart: - ```bash - helm install command-cert-manager-issuer command-issuer/command-cert-manager-issuer - ``` - - 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: - - helm install command-cert-manager-issuer command-issuer/command-cert-manager-issuer \ - --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: - - cat < override.yaml - replicaCount: 2 - EOF - - Then, use the `-f` flag to specify the `values.yaml` file: - - ```yaml - helm install command-cert-manager-issuer command-issuer/command-cert-manager-issuer \ - -f override.yaml - ``` - -## Usage -The cert-manager external issuer for Keyfactor Command can be used to issue certificates from Keyfactor Command using cert-manager. - -### Authentication -Authentication to the Command platform is done using basic authentication. The credentials must be provided as a Kubernetes `kubernetes.io/basic-auth` secret. These credentials should be for a user with "Certificate Enrollment: Enroll CSR" and "API: Read" permissions in Command. - -Create a `kubernetes.io/basic-auth` secret with the Keyfactor Command username and password: -```shell -cat < - password: -EOF -``` - -If the Command server is configured to use a self-signed certificate or with a certificate signed by an untrusted root, the CA certificate must be provided as a Kubernetes secret. -```shell -kubectl -n command-issuer-system create secret generic command-ca-secret --from-file=ca.crt -``` - -### Creating Issuer and ClusterIssuer resources -The `command-issuer.keyfactor.com/v1alpha1` API version supports Issuer and ClusterIssuer resources. -The Command controller will automatically detect and process resources of both types. - -The Issuer resource is namespaced, while the ClusterIssuer resource is cluster-scoped. -For example, ClusterIssuer resources can be used to issue certificates for resources in multiple namespaces, whereas Issuer resources can only be used to issue certificates for resources in the same namespace. - -The `spec` field of both the Issuer and ClusterIssuer resources use the following fields: -* `hostname` - The hostname of the Keyfactor Command server - The signer sets the protocol to `https` and automatically trims the trailing path from this field, if it exists. Additionally, the base Command API path is automatically set to `/KeyfactorAPI` and cannot be changed. -* `commandSecretName` - The name of the Kubernetes `kubernetes.io/basic-auth` secret containing credentials to the Keyfactor instance -* `certificateTemplate` - The short name corresponding to a template in Command that will be used to issue certificates. -* `certificateAuthorityLogicalName` - The logical name of the CA to use to sign the certificate request -* `certificateAuthorityHostname` - The CAs hostname to use to sign the certificate request -* `caSecretName` - The name of the Kubernetes secret containing the CA certificate. This field is optional and only required if the Command server is configured to use a self-signed certificate or with a certificate signed by an untrusted root. - -###### If a different combination of hostname/certificate authority/certificate profile/end entity profile is required, a new Issuer or ClusterIssuer resource must be created. Each resource instantiation represents a single configuration. - -The following is an example of an Issuer resource: -```shell -cat <> command-issuer.yaml -apiVersion: command-issuer.keyfactor.com/v1alpha1 -kind: Issuer -metadata: - labels: - app.kubernetes.io/name: issuer - app.kubernetes.io/instance: issuer-sample - app.kubernetes.io/part-of: command-issuer - app.kubernetes.io/created-by: command-issuer -name: issuer-sample -spec: - hostname: "" - commandSecretName: "" - certificateTemplate: "" - certificateAuthorityLogicalName: "" - certificateAuthorityHostname: "" - caSecretName: "" -EOF -kubectl -n command-issuer-system apply -f command-issuer.yaml -``` - -###### :pushpin: Issuers can only issue certificates in the same namespace as the issuer resource. To issue certificates in multiple namespaces, use a ClusterIssuer. - -The following is an example of a ClusterIssuer resource: -```shell -cat <> command-clusterissuer.yaml -apiVersion: command-issuer.keyfactor.com/v1alpha1 -kind: ClusterIssuer -metadata: - labels: - app.kubernetes.io/name: clusterissuer - app.kubernetes.io/instance: clusterissuer-sample - app.kubernetes.io/part-of: command-issuer - app.kubernetes.io/created-by: command-issuer - name: clusterissuer-sample -spec: - hostname: "" - commandSecretName: "" - certificateTemplate: "" - certificateAuthorityLogicalName: "" - certificateAuthorityHostname: "" - caSecretName: "" -EOF -kubectl -n command-issuer-system apply -f command-clusterissuer.yaml -``` - -###### :pushpin: ClusterIssuers can issue certificates in any namespace. To issue certificates in a single namespace, use an Issuer. - -To create new resources from the above examples, replace the empty strings with the appropriate values and apply the resources to the cluster: -```shell -kubectl -n command-issuer-system apply -f issuer.yaml -kubectl -n command-issuer-system apply -f clusterissuer.yaml -``` - -### Using Issuer and ClusterIssuer resources -Once the Issuer and ClusterIssuer resources are created, they can be used to issue certificates using cert-manager. -The two most important concepts are `Certificate` and `CertificateRequest` resources. `Certificate` -resources represent a single X.509 certificate and its associated attributes, and automatically renews the certificate -and keeps it up to date. When `Certificate` resources are created, they create `CertificateRequest` resources, which -use an Issuer or ClusterIssuer to actually issue the certificate. - -###### To learn more about cert-manager, see the [cert-manager documentation](https://cert-manager.io/docs/). - -The following is an example of a Certificate resource. This resource will create a corresponding CertificateRequest resource, -and will use the `issuer-sample` Issuer resource to issue the certificate. Once issued, the certificate will be stored in a -Kubernetes secret named `command-certificate`. -```yaml -apiVersion: cert-manager.io/v1 -kind: Certificate -metadata: - name: command-certificate -spec: - commonName: command-issuer-sample - secretName: command-certificate - issuerRef: - name: issuer-sample - group: command-issuer.keyfactor.com - kind: Issuer -``` - -###### :pushpin: Certificate resources support many more fields than the above example. See the [Certificate resource documentation](https://cert-manager.io/docs/usage/certificate/) for more information. - -###### :pushpin: Since this certificate request called `command-certificate` is configured to use `issuer-sample`, it must be deployed in the same namespace as `issuer-sample`. - -Similarly, a CertificateRequest resource can be created directly. The following is an example of a CertificateRequest resource. -```yaml -apiVersion: cert-manager.io/v1 -kind: CertificateRequest -metadata: - name: command-certificate -spec: - request: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURSBSRVFVRVNULS0tLS0KTUlJQ2REQ0NBVndDQVFBd0x6RUxNQWtHQTFVRUN4TUNTVlF4SURBZUJnTlZCQU1NRjJWcVltTmhYM1JsY25KaApabTl5YlY5MFpYTjBZV05qTUlJQklqQU5CZ2txaGtpRzl3MEJBUUVGQUFPQ0FROEFNSUlCQ2dLQ0FRRUF4blNSCklqZDZSN2NYdUNWRHZscXlFcUhKalhIazljN21pNTdFY3A1RXVnblBXa0YwTHBVc25PMld6WTE1bjV2MHBTdXMKMnpYSURhS3NtZU9ZQzlNOWtyRjFvOGZBelEreHJJWk5SWmg0cUZXRmpyNFV3a0EySTdUb05veitET2lWZzJkUgo1cnNmaFdHMmwrOVNPT3VscUJFcWVEcVROaWxyNS85OVpaemlBTnlnL2RiQXJibWRQQ1o5OGhQLzU0NDZhci9NCjdSd2ludjVCMnNRcWM0VFZwTTh3Nm5uUHJaQXA3RG16SktZbzVOQ3JyTmw4elhIRGEzc3hIQncrTU9DQUw0T00KTkJuZHpHSm5KenVyS0c3RU5UT3FjRlZ6Z3liamZLMktyMXRLS3pyVW5keTF1bTlmTWtWMEZCQnZ0SGt1ZG0xdwpMUzRleW1CemVtakZXQi9yRVFJREFRQUJvQUF3RFFZSktvWklodmNOQVFFTEJRQURnZ0VCQUJhdFpIVTdOczg2Cmgxc1h0d0tsSi95MG1peG1vRWJhUTNRYXAzQXVFQ2x1U09mdjFDZXBQZjF1N2dydEp5ZGRha1NLeUlNMVNzazAKcWNER2NncUsxVVZDR21vRkp2REZEaEUxMkVnM0ZBQ056UytFNFBoSko1N0JBSkxWNGZaeEpZQ3JyRDUxWnk3NgpPd01ORGRYTEVib0w0T3oxV3k5ZHQ3bngyd3IwWTNZVjAyL2c0dlBwaDVzTHl0NVZOWVd6eXJTMzJYckJwUWhPCnhGMmNNUkVEMUlaRHhuMjR2ZEtINjMzSFo1QXd0YzRYamdYQ3N5VW5mVUE0ZjR1cHBEZWJWYmxlRFlyTW1iUlcKWW1NTzdLTjlPb0MyZ1lVVVpZUVltdHlKZTJkYXlZSHVyUUlpK0ZsUU5zZjhna1hYeG45V2drTnV4ZTY3U0x5dApVNHF4amE4OCs1ST0KLS0tLS1FTkQgQ0VSVElGSUNBVEUgUkVRVUVTVC0tLS0t - issuerRef: - name: issuer-sample - group: command-issuer.keyfactor.com - kind: Issuer -``` - -### Approving Certificate Requests -Unless the cert-manager internal approver automatically approves the request, newly created CertificateRequest resources -will be in a `Pending` state until they are approved. CertificateRequest resources can be approved manually by using -[cmctl](https://cert-manager.io/docs/reference/cmctl/#approve-and-deny-certificaterequests). The following is an example -of approving a CertificateRequest resource named `command-certificate` in the `command-issuer-system` namespace. -```shell -cmctl -n command-issuer-system approve ejbca-certificate -``` - -Once a certificate request has been approved, the certificate will be issued and stored in the secret specified in the -CertificateRequest resource. The following is an example of retrieving the certificate from the secret. -```shell -kubectl get secret command-certificate -n command-issuer-system -o jsonpath='{.data.tls\.crt}' | base64 -d -``` - -###### To learn more about certificate approval and RBAC configuration, see the [cert-manager documentation](https://cert-manager.io/docs/concepts/certificaterequest/#approval). - -###### :pushpin: If the certificate was issued successfully, the Approved and Ready field will both be set to `True`. - -## Annotation Overrides for Issuer and ClusterIssuer Resources -The Keyfactor Command external issuer for cert-manager allows you to override default settings in the Issuer and ClusterIssuer resources through the use of annotations. This gives you more granular control on a per-Certificate/CertificateRequest basis. - -### Supported Annotations -Here are the supported annotations that can override the default values: - -- **`command-issuer.keyfactor.com/certificateTemplate`**: Overrides the `certificateTemplate` field from the resource spec. - - ```yaml - command-issuer.keyfactor.com/certificateTemplate: "Ephemeral2day" - ``` - -- **`command-issuer.keyfactor.com/certificateAuthorityLogicalName`**: Specifies the Certificate Authority (CA) logical name to use, overriding the default CA specified in the resource spec. - - ```yaml - command-issuer.keyfactor.com/certificateAuthorityLogicalName: "InternalIssuingCA1" - ``` - -- **`command-issuer.keyfactor.com/certificateAuthorityHostname`**: Specifies the Certificate Authority (CA) hostname to use, overriding the default CA specified in the resource spec. - - ```yaml - command-issuer.keyfactor.com/certificateAuthorityHostname: "example.com" - ``` - -### Metadata Annotations - -The Keyfactor Command external issuer for cert-manager also allows you to specify Command Metadata through the use of annotations. Metadata attached to a certificate request will be stored in Command and can be used for reporting and auditing purposes. The syntax for specifying metadata is as follows: -```yaml -metadata.command-issuer.keyfactor.com/: -``` - -###### :pushpin: The metadata field name must match a name of a metadata field in Command exactly. If the metadata field name does not match, the CSR enrollment will fail. - -### How to Apply Annotations - -To apply these annotations, include them in the metadata section of your CertificateRequest resource: - -```yaml -apiVersion: cert-manager.io/v1 -kind: Certificate -metadata: - annotations: - command-issuer.keyfactor.com/certificateTemplate: "Ephemeral2day" - command-issuer.keyfactor.com/certificateAuthorityLogicalName: "InternalIssuingCA1" - metadata.command-issuer.keyfactor.com/ResponsibleTeam: "theResponsibleTeam@example.com" - # ... other annotations -spec: -# ... the rest of the spec -``` - -### Demo ClusterIssuer Usage with K8s Ingress -This demo will show how to use a ClusterIssuer to issue a certificate for an Ingress resource. The demo uses the Kubernetes -`ingress-nginx` Ingress controller. If Minikube is being used, run the following command to enable the controller. -```shell -minikube addons enable ingress -kubectl get pods -n ingress-nginx -``` - -To manually deploy `ingress-nginx`, run the following command: -```shell -kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.7.0/deploy/static/provider/cloud/deploy.yaml -``` - -Create a namespace for the demo: -```shell -kubectl create ns command-clusterissuer-demo -``` - -Deploy two Pods running the `hashicorp/http-echo` image: -```shell -cat < -``` - -Validate that the certificate was created: -```shell -kubectl -n command-clusterissuer-demo describe ingress command-ingress-demo -``` - -Test it out -```shell -curl -k https://localhost/apple -curl -k https://localhost/banana -``` - -Clean up -```shell -kubectl -n command-clusterissuer-demo delete ingress command-ingress-demo -kubectl -n command-clusterissuer-demo delete service apple-service banana-service -kubectl -n command-clusterissuer-demo delete pod apple-app banana-app -kubectl delete ns command-clusterissuer-demo -kubectl delete -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.7.0/deploy/static/provider/cloud/deploy.yaml -``` - -## Cleanup -To list the certificates and certificate requests created, run the following commands: -```shell -kubectl get certificates -n command-issuer-system -kubectl get certificaterequests -n command-issuer-system -``` - -To remove the certificate and certificate request resources, run the following commands: -```shell -kubectl delete certificate command-certificate -n command-issuer-system -kubectl delete certificaterequest command-certificate -n command-issuer-system -``` - -To list the issuer and cluster issuer resources created, run the following commands: -```shell -kubectl -n command-issuer-system get issuers.command-issuer.keyfactor.com -kubectl -n command-issuer-system get clusterissuers.command-issuer.keyfactor.com -``` - -To remove the issuer and cluster issuer resources, run the following commands: -```shell -kubectl -n command-issuer-system delete issuers.command-issuer.keyfactor.com -kubectl -n command-issuer-system delete clusterissuers.command-issuer.keyfactor.com -``` - -To remove the controller from the cluster, run: -```shell -make undeploy -``` - -To remove the custom resource definitions (CRDs) for the cert-manager external issuer for Keyfactor Command, run: -```shell -make uninstall -``` - -## Keyfactor Command Configuration -The Command Issuer for cert-manager populates metadata fields in Command pertaining to the K8s cluster and cert-manager Issuer/ClusterIssuer. -Before configuring the issuer, create these metadata fields. These fields will be populated using the `kfutil` Keyfactor command line tool that offers convenient and powerful -command line access to the Keyfactor platform. Before proceeding, ensure that `kfutil` is installed and configured -by following the instructions here: [https://github.com/Keyfactor/kfutil](https://github.com/Keyfactor/kfutil) - -Use the `import` command to import the metadata fields into Command: -```shell -cat <> metadata.json -{ - "Collections": [], - "MetadataFields": [ - { - "AllowAPI": true, - "DataType": 1, - "Description": "The namespace that the issuer resource was created in.", - "Name": "Issuer-Namespace" - }, - { - "AllowAPI": true, - "DataType": 1, - "Description": "The certificate reconcile ID that the controller used to issue this certificate.", - "Name": "Controller-Reconcile-Id" - }, - { - "AllowAPI": true, - "DataType": 1, - "Description": "The namespace that the CertificateSigningRequest resource was created in.", - "Name": "Certificate-Signing-Request-Namespace" - }, - { - "AllowAPI": true, - "DataType": 1, - "Description": "The namespace that the controller container is running in.", - "Name": "Controller-Namespace" - }, - { - "AllowAPI": true, - "DataType": 1, - "Description": "The type of issuer that the controller used to issue this certificate.", - "Name": "Controller-Kind" - }, - { - "AllowAPI": true, - "DataType": 1, - "Description": "The group name of the resource that the Issuer or ClusterIssuer controller is managing.", - "Name": "Controller-Resource-Group-Name" - }, - { - "AllowAPI": true, - "DataType": 1, - "Description": "The name of the K8s issuer resource", - "Name": "Issuer-Name" - } - ], - "ExpirationAlerts": [], - "IssuedCertAlerts": [], - "DeniedCertAlerts": [], - "PendingCertAlerts": [], - "Networks": [], - "WorkflowDefinitions": [], - "BuiltInReports": [], - "CustomReports": [], - "SecurityRoles": [] -} -EOF -kfutil import --metadata --file metadata.json -``` - -## Building Container Image from Source - -### Requirements -* [Golang](https://golang.org/) >= v1.19 - -Building the container from source first runs appropriate test cases, which requires all requirements also listed in the -Quick Start section. As part of this testing is an enrollment of a certificate with Command, so a running instance of Command -is also required. - -The following environment variables must be exported before building the container image: -* `COMMAND_HOSTNAME` - The hostname of the Command server to use for testing. -* `COMMAND_USERNAME` - The username of an authorized Command user to use for testing. -* `COMMAND_PASSWORD` - The password of the authorized Command user to use for testing. -* `COMMAND_CERTIFICATE_TEMPLATE` - The name of the certificate template to use for testing. -* `COMMAND_CERTIFICATE_AUTHORITY_LOGICAL_NAME` - The logical name of the certificate authority to use for testing. -* `COMMAND_CERTIFICATE_AUTHORITY_HOSTNAME` - The hostname of the certificate authority to use for testing. -* `COMMAND_CA_CERT_PATH` - A relative or absolute path to the CA certificate that the Command server uses for TLS. The file must include the certificate in PEM format. - -To build the cert-manager external issuer for Keyfactor Command, run: -```shell -make docker-build -``` +* [Installation](docs/install.markdown) +* [Usage](docs/config_usage.markdown) +* [Example Usage](docs/example.markdown) +* [Customization](docs/annotations.markdown) +* [Testing the Source](docs/testing.markdown) diff --git a/deploy/charts/command-cert-manager-issuer/values.yaml b/deploy/charts/command-cert-manager-issuer/values.yaml index 9c14ccb..a521500 100644 --- a/deploy/charts/command-cert-manager-issuer/values.yaml +++ b/deploy/charts/command-cert-manager-issuer/values.yaml @@ -4,7 +4,7 @@ replicaCount: 1 image: - repository: ghcr.io/keyfactor/command-cert-manager-issuer + repository: "" pullPolicy: IfNotPresent # Overrides the image tag whose default is the chart appVersion. tag: "" diff --git a/docs/annotations.markdown b/docs/annotations.markdown new file mode 100644 index 0000000..afa9a63 --- /dev/null +++ b/docs/annotations.markdown @@ -0,0 +1,63 @@ + + Terraform logo + + +# Annotation Overrides for Issuer and ClusterIssuer Resources + +[![Go Report Card](https://goreportcard.com/badge/github.com/Keyfactor/command-cert-manager-issuer)](https://goreportcard.com/report/github.com/Keyfactor/command-cert-manager-issuer) +[![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://img.shields.io/badge/License-Apache%202.0-blue.svg) + +The Keyfactor Command external issuer for cert-manager allows you to override default settings in the Issuer and ClusterIssuer resources through the use of annotations. This gives you more granular control on a per-Certificate/CertificateRequest basis. + +### Documentation Tree +* [Installation](install.markdown) +* [Usage](config_usage.markdown) +* [Example Usage](example.markdown) +* [Testing the Source](testing.markdown) + +### Supported Annotations +Here are the supported annotations that can override the default values: + +- **`command-issuer.keyfactor.com/certificateTemplate`**: Overrides the `certificateTemplate` field from the resource spec. + + ```yaml + command-issuer.keyfactor.com/certificateTemplate: "Ephemeral2day" + ``` + +- **`command-issuer.keyfactor.com/certificateAuthorityLogicalName`**: Specifies the Certificate Authority (CA) logical name to use, overriding the default CA specified in the resource spec. + + ```yaml + command-issuer.keyfactor.com/certificateAuthorityLogicalName: "InternalIssuingCA1" + ``` + +- **`command-issuer.keyfactor.com/certificateAuthorityHostname`**: Specifies the Certificate Authority (CA) hostname to use, overriding the default CA specified in the resource spec. + + ```yaml + command-issuer.keyfactor.com/certificateAuthorityHostname: "example.com" + ``` + +### Metadata Annotations + +The Keyfactor Command external issuer for cert-manager also allows you to specify Command Metadata through the use of annotations. Metadata attached to a certificate request will be stored in Command and can be used for reporting and auditing purposes. The syntax for specifying metadata is as follows: +```yaml +metadata.command-issuer.keyfactor.com/: +``` + +###### :pushpin: The metadata field name must match a name of a metadata field in Command exactly. If the metadata field name does not match, the CSR enrollment will fail. + +### How to Apply Annotations + +To apply these annotations, include them in the metadata section of your CertificateRequest resource: + +```yaml +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + annotations: + command-issuer.keyfactor.com/certificateTemplate: "Ephemeral2day" + command-issuer.keyfactor.com/certificateAuthorityLogicalName: "InternalIssuingCA1" + metadata.command-issuer.keyfactor.com/ResponsibleTeam: "theResponsibleTeam@example.com" + # ... other annotations +spec: +# ... the rest of the spec +``` \ No newline at end of file diff --git a/docs/config_usage.markdown b/docs/config_usage.markdown new file mode 100644 index 0000000..d5fbc73 --- /dev/null +++ b/docs/config_usage.markdown @@ -0,0 +1,242 @@ + + Terraform logo + + +# Command Cert Manager Issuer Usage + +[![Go Report Card](https://goreportcard.com/badge/github.com/Keyfactor/command-cert-manager-issuer)](https://goreportcard.com/report/github.com/Keyfactor/command-cert-manager-issuer) +[![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://img.shields.io/badge/License-Apache%202.0-blue.svg) + +The cert-manager external issuer for Keyfactor Command can be used to issue certificates from Keyfactor Command using cert-manager. + +### Documentation Tree +* [Installation](install.markdown) +* [Example Usage](example.markdown) +* [Customization](annotations.markdown) +* [Testing the Source](testing.markdown) + +### Keyfactor Command Configuration +The Command Issuer for cert-manager populates metadata fields on issued certificates in Command pertaining to the K8s cluster and cert-manager Issuer/ClusterIssuer. Before deploying Issuers/ClusterIssuers, these metadata fields must be created in Command. To easily create these metadata fields, use the `kfutil` Keyfactor command line tool that offers convenient and powerful command line access to the Keyfactor platform. Before proceeding, ensure that `kfutil` is installed and configured by following the instructions here: [https://github.com/Keyfactor/kfutil](https://github.com/Keyfactor/kfutil). + +Then, use the `import` command to import the metadata fields into Command: +```shell +cat <> metadata.json +{ + "Collections": [], + "MetadataFields": [ + { + "AllowAPI": true, + "DataType": 1, + "Description": "The namespace that the issuer resource was created in.", + "Name": "Issuer-Namespace" + }, + { + "AllowAPI": true, + "DataType": 1, + "Description": "The certificate reconcile ID that the controller used to issue this certificate.", + "Name": "Controller-Reconcile-Id" + }, + { + "AllowAPI": true, + "DataType": 1, + "Description": "The namespace that the CertificateSigningRequest resource was created in.", + "Name": "Certificate-Signing-Request-Namespace" + }, + { + "AllowAPI": true, + "DataType": 1, + "Description": "The namespace that the controller container is running in.", + "Name": "Controller-Namespace" + }, + { + "AllowAPI": true, + "DataType": 1, + "Description": "The type of issuer that the controller used to issue this certificate.", + "Name": "Controller-Kind" + }, + { + "AllowAPI": true, + "DataType": 1, + "Description": "The group name of the resource that the Issuer or ClusterIssuer controller is managing.", + "Name": "Controller-Resource-Group-Name" + }, + { + "AllowAPI": true, + "DataType": 1, + "Description": "The name of the K8s issuer resource", + "Name": "Issuer-Name" + } + ], + "ExpirationAlerts": [], + "IssuedCertAlerts": [], + "DeniedCertAlerts": [], + "PendingCertAlerts": [], + "Networks": [], + "WorkflowDefinitions": [], + "BuiltInReports": [], + "CustomReports": [], + "SecurityRoles": [] +} +EOF +kfutil import --metadata --file metadata.json +``` + +### Authentication +Authentication to the Command platform is done using basic authentication. The credentials must be provided as a Kubernetes `kubernetes.io/basic-auth` secret. These credentials should be for a user with "Certificate Enrollment: Enroll CSR" and "API: Read" permissions in Command. + +Create a `kubernetes.io/basic-auth` secret with the Keyfactor Command username and password: +```shell +cat < + password: +EOF +``` + +If the Command server is configured to use a self-signed certificate or with a certificate signed by an untrusted root, the CA certificate must be provided as a Kubernetes secret. +```shell +kubectl -n command-issuer-system create secret generic command-ca-secret --from-file=ca.crt +``` + +### Creating Issuer and ClusterIssuer resources +The `command-issuer.keyfactor.com/v1alpha1` API version supports Issuer and ClusterIssuer resources. +The Command controller will automatically detect and process resources of both types. + +The Issuer resource is namespaced, while the ClusterIssuer resource is cluster-scoped. +For example, ClusterIssuer resources can be used to issue certificates for resources in multiple namespaces, whereas Issuer resources can only be used to issue certificates for resources in the same namespace. + +The `spec` field of both the Issuer and ClusterIssuer resources use the following fields: +* `hostname` - The hostname of the Keyfactor Command server - The signer sets the protocol to `https` and automatically trims the trailing path from this field, if it exists. Additionally, the base Command API path is automatically set to `/KeyfactorAPI` and cannot be changed. +* `commandSecretName` - The name of the Kubernetes `kubernetes.io/basic-auth` secret containing credentials to the Keyfactor instance +* `certificateTemplate` - The short name corresponding to a template in Command that will be used to issue certificates. +* `certificateAuthorityLogicalName` - The logical name of the CA to use to sign the certificate request +* `certificateAuthorityHostname` - The CAs hostname to use to sign the certificate request +* `caSecretName` - The name of the Kubernetes secret containing the CA certificate. This field is optional and only required if the Command server is configured to use a self-signed certificate or with a certificate signed by an untrusted root. + +###### If a different combination of hostname/certificate authority/certificate profile/end entity profile is required, a new Issuer or ClusterIssuer resource must be created. Each resource instantiation represents a single configuration. + +The following is an example of an Issuer resource: +```shell +cat <> command-issuer.yaml +apiVersion: command-issuer.keyfactor.com/v1alpha1 +kind: Issuer +metadata: + labels: + app.kubernetes.io/name: issuer + app.kubernetes.io/instance: issuer-sample + app.kubernetes.io/part-of: command-issuer + app.kubernetes.io/created-by: command-issuer +name: issuer-sample +spec: + hostname: "" + commandSecretName: "" + certificateTemplate: "" + certificateAuthorityLogicalName: "" + certificateAuthorityHostname: "" + caSecretName: "" +EOF +kubectl -n command-issuer-system apply -f command-issuer.yaml +``` + +###### :pushpin: Issuers can only issue certificates in the same namespace as the issuer resource. To issue certificates in multiple namespaces, use a ClusterIssuer. + +The following is an example of a ClusterIssuer resource: +```shell +cat <> command-clusterissuer.yaml +apiVersion: command-issuer.keyfactor.com/v1alpha1 +kind: ClusterIssuer +metadata: + labels: + app.kubernetes.io/name: clusterissuer + app.kubernetes.io/instance: clusterissuer-sample + app.kubernetes.io/part-of: command-issuer + app.kubernetes.io/created-by: command-issuer + name: clusterissuer-sample +spec: + hostname: "" + commandSecretName: "" + certificateTemplate: "" + certificateAuthorityLogicalName: "" + certificateAuthorityHostname: "" + caSecretName: "" +EOF +kubectl -n command-issuer-system apply -f command-clusterissuer.yaml +``` + +###### :pushpin: ClusterIssuers can issue certificates in any namespace. To issue certificates in a single namespace, use an Issuer. + +To create new resources from the above examples, replace the empty strings with the appropriate values and apply the resources to the cluster: +```shell +kubectl -n command-issuer-system apply -f issuer.yaml +kubectl -n command-issuer-system apply -f clusterissuer.yaml +``` + +### Using Issuer and ClusterIssuer resources +Once the Issuer and ClusterIssuer resources are created, they can be used to issue certificates using cert-manager. +The two most important concepts are `Certificate` and `CertificateRequest` resources. `Certificate` +resources represent a single X.509 certificate and its associated attributes, and automatically renews the certificate +and keeps it up to date. When `Certificate` resources are created, they create `CertificateRequest` resources, which +use an Issuer or ClusterIssuer to actually issue the certificate. + +###### To learn more about cert-manager, see the [cert-manager documentation](https://cert-manager.io/docs/). + +The following is an example of a Certificate resource. This resource will create a corresponding CertificateRequest resource, +and will use the `issuer-sample` Issuer resource to issue the certificate. Once issued, the certificate will be stored in a +Kubernetes secret named `command-certificate`. +```yaml +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + name: command-certificate +spec: + commonName: command-issuer-sample + secretName: command-certificate + issuerRef: + name: issuer-sample + group: command-issuer.keyfactor.com + kind: Issuer +``` + +###### :pushpin: Certificate resources support many more fields than the above example. See the [Certificate resource documentation](https://cert-manager.io/docs/usage/certificate/) for more information. + +###### :pushpin: Since this certificate request called `command-certificate` is configured to use `issuer-sample`, it must be deployed in the same namespace as `issuer-sample`. + +Similarly, a CertificateRequest resource can be created directly. The following is an example of a CertificateRequest resource. +```yaml +apiVersion: cert-manager.io/v1 +kind: CertificateRequest +metadata: + name: command-certificate +spec: + request: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURSBSRVFVRVNULS0tLS0KTUlJQ2REQ0NBVndDQVFBd0x6RUxNQWtHQTFVRUN4TUNTVlF4SURBZUJnTlZCQU1NRjJWcVltTmhYM1JsY25KaApabTl5YlY5MFpYTjBZV05qTUlJQklqQU5CZ2txaGtpRzl3MEJBUUVGQUFPQ0FROEFNSUlCQ2dLQ0FRRUF4blNSCklqZDZSN2NYdUNWRHZscXlFcUhKalhIazljN21pNTdFY3A1RXVnblBXa0YwTHBVc25PMld6WTE1bjV2MHBTdXMKMnpYSURhS3NtZU9ZQzlNOWtyRjFvOGZBelEreHJJWk5SWmg0cUZXRmpyNFV3a0EySTdUb05veitET2lWZzJkUgo1cnNmaFdHMmwrOVNPT3VscUJFcWVEcVROaWxyNS85OVpaemlBTnlnL2RiQXJibWRQQ1o5OGhQLzU0NDZhci9NCjdSd2ludjVCMnNRcWM0VFZwTTh3Nm5uUHJaQXA3RG16SktZbzVOQ3JyTmw4elhIRGEzc3hIQncrTU9DQUw0T00KTkJuZHpHSm5KenVyS0c3RU5UT3FjRlZ6Z3liamZLMktyMXRLS3pyVW5keTF1bTlmTWtWMEZCQnZ0SGt1ZG0xdwpMUzRleW1CemVtakZXQi9yRVFJREFRQUJvQUF3RFFZSktvWklodmNOQVFFTEJRQURnZ0VCQUJhdFpIVTdOczg2Cmgxc1h0d0tsSi95MG1peG1vRWJhUTNRYXAzQXVFQ2x1U09mdjFDZXBQZjF1N2dydEp5ZGRha1NLeUlNMVNzazAKcWNER2NncUsxVVZDR21vRkp2REZEaEUxMkVnM0ZBQ056UytFNFBoSko1N0JBSkxWNGZaeEpZQ3JyRDUxWnk3NgpPd01ORGRYTEVib0w0T3oxV3k5ZHQ3bngyd3IwWTNZVjAyL2c0dlBwaDVzTHl0NVZOWVd6eXJTMzJYckJwUWhPCnhGMmNNUkVEMUlaRHhuMjR2ZEtINjMzSFo1QXd0YzRYamdYQ3N5VW5mVUE0ZjR1cHBEZWJWYmxlRFlyTW1iUlcKWW1NTzdLTjlPb0MyZ1lVVVpZUVltdHlKZTJkYXlZSHVyUUlpK0ZsUU5zZjhna1hYeG45V2drTnV4ZTY3U0x5dApVNHF4amE4OCs1ST0KLS0tLS1FTkQgQ0VSVElGSUNBVEUgUkVRVUVTVC0tLS0t + issuerRef: + name: issuer-sample + group: command-issuer.keyfactor.com + kind: Issuer +``` + +### Approving Certificate Requests +Unless the cert-manager internal approver automatically approves the request, newly created CertificateRequest resources +will be in a `Pending` state until they are approved. CertificateRequest resources can be approved manually by using +[cmctl](https://cert-manager.io/docs/reference/cmctl/#approve-and-deny-certificaterequests). The following is an example +of approving a CertificateRequest resource named `command-certificate` in the `command-issuer-system` namespace. +```shell +cmctl -n command-issuer-system approve ejbca-certificate +``` + +Once a certificate request has been approved, the certificate will be issued and stored in the secret specified in the +CertificateRequest resource. The following is an example of retrieving the certificate from the secret. +```shell +kubectl get secret command-certificate -n command-issuer-system -o jsonpath='{.data.tls\.crt}' | base64 -d +``` + +###### To learn more about certificate approval and RBAC configuration, see the [cert-manager documentation](https://cert-manager.io/docs/concepts/certificaterequest/#approval). + +###### :pushpin: If the certificate was issued successfully, the Approved and Ready field will both be set to `True`. + +Next, see the [example usage](example.markdown) documentation for a complete example of using the Command Issuer for cert-manager. \ No newline at end of file diff --git a/docs/example.markdown b/docs/example.markdown new file mode 100644 index 0000000..63cacf5 --- /dev/null +++ b/docs/example.markdown @@ -0,0 +1,189 @@ + + Terraform logo + + +# Demo ClusterIssuer Usage with K8s Ingress + +[![Go Report Card](https://goreportcard.com/badge/github.com/Keyfactor/command-cert-manager-issuer)](https://goreportcard.com/report/github.com/Keyfactor/command-cert-manager-issuer) +[![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://img.shields.io/badge/License-Apache%202.0-blue.svg) + +### Documentation Tree +* [Installation](install.markdown) +* [Usage](config_usage.markdown) +* [Customization](annotations.markdown) +* [Testing the Source](testing.markdown) + +This demo will show how to use a ClusterIssuer to issue a certificate for an Ingress resource. The demo uses the Kubernetes +`ingress-nginx` Ingress controller. If Minikube is being used, run the following command to enable the controller. +```shell +minikube addons enable ingress +kubectl get pods -n ingress-nginx +``` + +To manually deploy `ingress-nginx`, run the following command: +```shell +kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.7.0/deploy/static/provider/cloud/deploy.yaml +``` + +Create a namespace for the demo: +```shell +kubectl create ns command-clusterissuer-demo +``` + +Deploy two Pods running the `hashicorp/http-echo` image: +```shell +cat < +``` + +Validate that the certificate was created: +```shell +kubectl -n command-clusterissuer-demo describe ingress command-ingress-demo +``` + +Test it out +```shell +curl -k https://localhost/apple +curl -k https://localhost/banana +``` + +Clean up +```shell +kubectl -n command-clusterissuer-demo delete ingress command-ingress-demo +kubectl -n command-clusterissuer-demo delete service apple-service banana-service +kubectl -n command-clusterissuer-demo delete pod apple-app banana-app +kubectl delete ns command-clusterissuer-demo +kubectl delete -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.7.0/deploy/static/provider/cloud/deploy.yaml +``` + +## Cleanup +To list the certificates and certificate requests created, run the following commands: +```shell +kubectl get certificates -n command-issuer-system +kubectl get certificaterequests -n command-issuer-system +``` + +To remove the certificate and certificate request resources, run the following commands: +```shell +kubectl delete certificate command-certificate -n command-issuer-system +kubectl delete certificaterequest command-certificate -n command-issuer-system +``` + +To list the issuer and cluster issuer resources created, run the following commands: +```shell +kubectl -n command-issuer-system get issuers.command-issuer.keyfactor.com +kubectl -n command-issuer-system get clusterissuers.command-issuer.keyfactor.com +``` + +To remove the issuer and cluster issuer resources, run the following commands: +```shell +kubectl -n command-issuer-system delete issuers.command-issuer.keyfactor.com +kubectl -n command-issuer-system delete clusterissuers.command-issuer.keyfactor.com +``` + +To remove the controller from the cluster, run: +```shell +make undeploy +``` + +To remove the custom resource definitions (CRDs) for the cert-manager external issuer for Keyfactor Command, run: +```shell +make uninstall +``` \ No newline at end of file diff --git a/docs/install.markdown b/docs/install.markdown new file mode 100644 index 0000000..7ddbf72 --- /dev/null +++ b/docs/install.markdown @@ -0,0 +1,125 @@ + + Terraform logo + + +# Installing the Keyfactor Command Issuer for cert-manager + +[![Go Report Card](https://goreportcard.com/badge/github.com/Keyfactor/command-cert-manager-issuer)](https://goreportcard.com/report/github.com/Keyfactor/command-cert-manager-issuer) +[![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://img.shields.io/badge/License-Apache%202.0-blue.svg) + +### Documentation Tree +* [Usage](config_usage.markdown) +* [Example Usage](example.markdown) +* [Customization](annotations.markdown) +* [Testing the Source](testing.markdown) + +### Requirements +* [Git](https://git-scm.com/) +* [Make](https://www.gnu.org/software/make/) +* [Docker](https://docs.docker.com/engine/install/) >= v20.10.0 +* [Kubectl](https://kubernetes.io/docs/tasks/tools/install-kubectl/) >= v1.11.3 +* Kubernetes >= v1.19 + * [Kubernetes](https://kubernetes.io/docs/tasks/tools/), [Minikube](https://minikube.sigs.k8s.io/docs/start/), or [Kind](https://kind.sigs.k8s.io/docs/user/quick-start/) +* [Keyfactor Command](https://www.keyfactor.com/products/command/) >= v10.1.0 +* [cert-manager](https://cert-manager.io/docs/installation/) >= v1.11.0 +* [cmctl](https://cert-manager.io/docs/reference/cmctl/) + +Before starting, ensure that all of the above requirements are met, and that Keyfactor Command is properly configured according to the [product docs](https://software.keyfactor.com/Content/MasterTopics/Home.htm). Additionally, verify that at least one Kubernetes node is running by running the following command: + +```shell +kubectl get nodes +``` + +A static installation of cert-manager can be installed with the following command: + +```shell +kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.11.0/cert-manager.yaml +``` + +###### :pushpin: Running the static cert-manager configuration is not recommended for production use. For more information, see [Installing cert-manager](https://cert-manager.io/docs/installation/). + +### Building the Container Image + +The cert-manager external issuer for Keyfactor Command is distributed as source code, and the container must be built manually. The container image can be built using the following command: +```shell +make docker-build DOCKER_REGISTRY= DOCKER_IMAGE_NAME=keyfactor/command-cert-manager-issuer +``` + +###### :pushpin: The container image can be built using Docker Buildx by running `make docker-buildx`. This will build the image for all supported platforms. + +To push the container image to a container registry, run the following command: +```shell +docker login +make docker-push DOCKER_REGISTRY= DOCKER_IMAGE_NAME=keyfactor/command-cert-manager-issuer +``` + +### Installation from Manifests + +The cert-manager external issuer for Keyfactor Command can be installed using the manifests in the `config/` directory. + +1. Install the custom resource definitions (CRDs) for the cert-manager external issuer for Keyfactor Command: + + ```shell + make install + ``` + +2. Finally, deploy the controller to the cluster: + + ```shell + make deploy DOCKER_REGISTRY= DOCKER_IMAGE_NAME=keyfactor/command-cert-manager-issuer + ``` + +### Installation from Helm Chart + +The cert-manager external issuer for Keyfactor Command can also be installed using a Helm chart. The chart is available in the [Command cert-manager Helm repository](https://keyfactor.github.io/command-cert-manager-issuer/). + +1. Add the Helm repository: + + ```bash + helm repo add command-issuer https://keyfactor.github.io/command-cert-manager-issuer + helm repo update + ``` + +2. Then, install the chart: + + ```bash + helm install command-cert-manager-issuer command-issuer/command-cert-manager-issuer \ + --namespace command-issuer-system \ + --create-namespace \ + --set image.repository=/keyfactor/command-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 command-cert-manager-issuer command-issuer/command-cert-manager-issuer \ + --namespace command-issuer-system \ + --create-namespace \ + --set image.repository=/keyfactor/command-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/command-cert-manager-issuer + pullPolicy: Never + tag: "latest" + replicaCount: 2 + EOF + ``` + + Then, use the `-f` flag to specify the `values.yaml` file: + + ```yaml + helm install command-cert-manager-issuer command-issuer/command-cert-manager-issuer \ + -f override.yaml + ``` + +Next, complete the [Usage](config_usage.markdown) steps to configure the cert-manager external issuer for Keyfactor Command. \ No newline at end of file diff --git a/docs/testing.markdown b/docs/testing.markdown new file mode 100644 index 0000000..e633da2 --- /dev/null +++ b/docs/testing.markdown @@ -0,0 +1,32 @@ + + Terraform logo + + +# Testing the Controller Source Code + +[![Go Report Card](https://goreportcard.com/badge/github.com/Keyfactor/command-cert-manager-issuer)](https://goreportcard.com/report/github.com/Keyfactor/command-cert-manager-issuer) +[![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://img.shields.io/badge/License-Apache%202.0-blue.svg) + + +### Documentation Tree +* [Installation](install.markdown) +* [Usage](config_usage.markdown) +* [Example Usage](example.markdown) +* [Customization](annotations.markdown) + +The test cases for the controller require a set of environment variables to be set. These variables are used to +authenticate to the Command server and to enroll a certificate. The test cases are run using the `make test` command. + +The following environment variables must be exported before testing the controller: +* `COMMAND_HOSTNAME` - The hostname of the Command server to use for testing. +* `COMMAND_USERNAME` - The username of an authorized Command user to use for testing. +* `COMMAND_PASSWORD` - The password of the authorized Command user to use for testing. +* `COMMAND_CERTIFICATE_TEMPLATE` - The name of the certificate template to use for testing. +* `COMMAND_CERTIFICATE_AUTHORITY_LOGICAL_NAME` - The logical name of the certificate authority to use for testing. +* `COMMAND_CERTIFICATE_AUTHORITY_HOSTNAME` - The hostname of the certificate authority to use for testing. +* `COMMAND_CA_CERT_PATH` - A relative or absolute path to the CA certificate that the Command server uses for TLS. The file must include the certificate in PEM format. + +To build the cert-manager external issuer for Keyfactor Command, run: +```shell +make test +``` \ No newline at end of file From 415a85663ca8715af812ee3d5b628a51160ee202 Mon Sep 17 00:00:00 2001 From: Hayden <49427552+m8rmclaren@users.noreply.github.com> Date: Wed, 25 Oct 2023 09:04:54 -0700 Subject: [PATCH 16/17] fix(docs): Fix formatting in install.markdown --- docs/install.markdown | 48 +++++++++++++++++++++---------------------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/docs/install.markdown b/docs/install.markdown index 7ddbf72..af1b0bc 100644 --- a/docs/install.markdown +++ b/docs/install.markdown @@ -93,33 +93,33 @@ The cert-manager external issuer for Keyfactor Command can also be installed usi 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 command-cert-manager-issuer command-issuer/command-cert-manager-issuer \ - --namespace command-issuer-system \ - --create-namespace \ - --set image.repository=/keyfactor/command-cert-manager-issuer \ - --set image.tag= - --set replicaCount=2 - ``` + ```shell + helm install command-cert-manager-issuer command-issuer/command-cert-manager-issuer \ + --namespace command-issuer-system \ + --create-namespace \ + --set image.repository=/keyfactor/command-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/command-cert-manager-issuer - pullPolicy: Never - tag: "latest" - replicaCount: 2 - EOF - ``` - - Then, use the `-f` flag to specify the `values.yaml` file: + ```yaml + cat < override.yaml + image: + repository: /keyfactor/command-cert-manager-issuer + pullPolicy: Never + tag: "latest" + replicaCount: 2 + EOF + ``` + + Then, use the `-f` flag to specify the `values.yaml` file: - ```yaml - helm install command-cert-manager-issuer command-issuer/command-cert-manager-issuer \ - -f override.yaml - ``` + ```yaml + helm install command-cert-manager-issuer command-issuer/command-cert-manager-issuer \ + -f override.yaml + ``` -Next, complete the [Usage](config_usage.markdown) steps to configure the cert-manager external issuer for Keyfactor Command. \ No newline at end of file +Next, complete the [Usage](config_usage.markdown) steps to configure the cert-manager external issuer for Keyfactor Command. From a0812c5a90743ae22f1247990679fa3e371fb6d6 Mon Sep 17 00:00:00 2001 From: Hayden Roszell Date: Mon, 30 Oct 2023 11:56:29 -0700 Subject: [PATCH 17/17] fix(makefile): Bug naming convention in deploy-local target --- CHANGELOG.md | 2 ++ Makefile | 5 ++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index da350c9..aac1554 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,8 @@ ## Features * feat(signer): Signer recognizes `metadata.command-issuer.keyfactor.com/: ` annotations on the CertificateRequest resource and uses them to populate certificate metadata in Command. * feat(release): Container build and release now uses GitHub Actions. + +## Fixes * fix(helm): CRDs now correspond to correct values for the `command-issuer`. * fix(helm): Signer Helm Chart now includes a `secureMetrics` value to enable/disable sidecar RBAC container for further protection of the `/metrics` endpoint. * fix(signer): Signer now returns CA chain bytes instead of appending to the leaf certificate. diff --git a/Makefile b/Makefile index 3a50fea..4f6b288 100644 --- a/Makefile +++ b/Makefile @@ -6,7 +6,6 @@ DOCKER_REGISTRY ?= "" DOCKER_IMAGE_NAME ?= "" # Image URL to use all building/pushing image targets IMG ?= ${DOCKER_REGISTRY}/${DOCKER_IMAGE_NAME}:${VERSION} -#IMG ?= command-issuer-dev:latest # ENVTEST_K8S_VERSION refers to the version of kubebuilder assets to be downloaded by envtest binary. ENVTEST_K8S_VERSION = 1.26.0 @@ -131,8 +130,8 @@ deploy: manifests kustomize ## Deploy controller to the K8s cluster specified in # Then, install it into the K8s cluster .PHONY: deploy-local deploy-local: manifests kustomize ## Build docker image with the manager. - docker build -t ejbca-issuer-dev:latest -f Dockerfile . - cd config/manager && $(KUSTOMIZE) edit set image controller=ejbca-issuer-dev:latest + docker build -t command-issuer-dev:latest -f Dockerfile . + cd config/manager && $(KUSTOMIZE) edit set image controller=command-issuer-dev:latest $(KUSTOMIZE) build config/default | kubectl apply -f - .PHONY: undeploy