From 26da84b4b9a1414b2b61c0519414a2a82430b62f Mon Sep 17 00:00:00 2001 From: kingcdavid Date: Wed, 3 Jan 2024 09:43:12 +0000 Subject: [PATCH] Feature/return ca chain (#1) Updating to return CA chain and improve actions to be independent of repository owner --- .github/workflows/main.yml | 37 +++++++++++++- LICENSE | 2 +- Makefile | 5 +- README.md | 2 +- .../certificaterequest_controller.go | 3 +- .../certificaterequest_controller_test.go | 5 +- internal/issuer/signer/signer.go | 49 +++++++++++++------ 7 files changed, 79 insertions(+), 24 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index de38e64..40d8e0d 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -14,18 +14,23 @@ on: workflow_dispatch env: REGISTRY: ghcr.io IMAGE_NAME: ${{ github.repository }} + GITHUBOWNER: ${{ github.repository_owner }} jobs: - build-and-push-image: + docker_build_and_release: runs-on: ubuntu-latest permissions: - contents: read + contents: write packages: write steps: - name: Checkout repository uses: actions/checkout@v3 + - name: Get the version + id: get_version + run: echo ::set-output name=VERSION::$(echo $GITHUB_REF | cut -d / -f 3) + - name: Log in to the Container registry uses: docker/login-action@f054a8b539a109f9f41c372932f1ae047eff08c9 with: @@ -46,3 +51,31 @@ jobs: push: true tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} + + - name: Create install.yaml + shell: bash + run: make build/install.yaml + + - name: Create release + id: create_release + uses: actions/create-release@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + tag_name: ${{ github.ref }} + release_name: Release ${{ steps.get_version.outputs.VERSION }} + draft: true + prerelease: false + body: | + To install: `kubectl apply -f https://github.com/${{ github.repository_owner }}/atlas-cert-manager/releases/download/${{ steps.get_version.outputs.VERSION }}/install.yaml` + + - name: Upload Release Asset + id: upload-release-asset + uses: actions/upload-release-asset@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + upload_url: ${{ steps.create_release.outputs.upload_url }} # This pulls from the CREATE RELEASE step above, referencing it's ID to get its outputs object, which include a `upload_url`. See this blog post for more info: https://jasonet.co/posts/new-features-of-github-actions/#passing-data-to-future-steps + asset_path: build/install.yaml + asset_name: install.yaml + asset_content_type: application/x-yaml diff --git a/LICENSE b/LICENSE index 9f63039..e3a2ffd 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2023 nhgs64 +Copyright (c) 2023 GlobalSign Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/Makefile b/Makefile index e7d626f..338f654 100644 --- a/Makefile +++ b/Makefile @@ -7,10 +7,11 @@ SHELL := bash # The version which will be reported by the --version argument of each binary # and which will be used as the Docker image tag -VERSION ?= master +VERSION ?= latest # The Docker repository name, overridden in CI. DOCKER_REGISTRY ?= ghcr.io -DOCKER_IMAGE_NAME ?= nhgs64/atlas-cert-manager +GITHUBOWNER ?= globalsign +DOCKER_IMAGE_NAME ?= ${GITHUBOWNER}/atlas-cert-manager # Image URL to use all building/pushing image targets IMG ?= ${DOCKER_REGISTRY}/${DOCKER_IMAGE_NAME}:${VERSION} # Produce CRDs that work back to Kubernetes 1.11 (no version conversion) diff --git a/README.md b/README.md index da46ef0..e58842d 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ kubectl apply -f https://github.com/cert-manager/cert-manager/releases/latest/do ``` Next, install the Atlas controller and CRDs: ```console -kubectl apply -f https://github.com/nhgs64/atlas-cert-manager/releases/latest/download/install.yaml +kubectl apply -f https://github.com/globalsign/atlas-cert-manager/releases/latest/download/install.yaml ``` The controller is deployed and ready to handle Atlas requests. diff --git a/internal/controllers/certificaterequest_controller.go b/internal/controllers/certificaterequest_controller.go index 142def9..beae237 100644 --- a/internal/controllers/certificaterequest_controller.go +++ b/internal/controllers/certificaterequest_controller.go @@ -221,11 +221,12 @@ func (r *CertificateRequestReconciler) Reconcile(ctx context.Context, req ctrl.R return ctrl.Result{}, fmt.Errorf("%w: %v", errSignerBuilder, err) } - signed, err := signer.Sign(certificateRequest.Spec.Request) + signed, ca, err := signer.Sign(certificateRequest.Spec.Request) if err != nil { return ctrl.Result{}, fmt.Errorf("%w: %v", errSignerSign, err) } certificateRequest.Status.Certificate = signed + certificateRequest.Status.CA = ca 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 9adf09a..80842c4 100644 --- a/internal/controllers/certificaterequest_controller_test.go +++ b/internal/controllers/certificaterequest_controller_test.go @@ -38,8 +38,9 @@ type fakeSigner struct { errSign error } -func (o *fakeSigner) Sign([]byte) ([]byte, error) { - return []byte("fake signed certificate"), o.errSign +func (o *fakeSigner) Sign([]byte) ([]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 d050bbb..bcb048f 100644 --- a/internal/issuer/signer/signer.go +++ b/internal/issuer/signer/signer.go @@ -21,7 +21,7 @@ type HealthChecker interface { type HealthCheckerBuilder func(*sampleissuerapi.IssuerSpec, map[string][]byte) (HealthChecker, error) type Signer interface { - Sign([]byte) ([]byte, error) + Sign([]byte) ([]byte, []byte, error) } type SignerBuilder func(*sampleissuerapi.IssuerSpec, map[string][]byte) (Signer, error) @@ -67,19 +67,20 @@ func (o *hvcaSigner) Check() error { return nil } -func (o *hvcaSigner) Sign(csrBytes []byte) ([]byte, error) { +func (o *hvcaSigner) Sign(csrBytes []byte) ([]byte, []byte, error) { ctx, cancel := context.WithCancel(context.Background()) var clnt *hvclient.Client var serial *big.Int var info *hvclient.CertInfo + var caChainList []*x509.Certificate defer cancel() if clnt, err = hvclient.NewClient(ctx, o.config); err != nil { - return nil, err + return nil, nil, err } // Parse the csr csr, err := parseCSR(csrBytes) if err != nil { - return nil, err + return nil, nil, err } var req = hvclient.Request{ @@ -92,13 +93,13 @@ func (o *hvcaSigner) Sign(csrBytes []byte) ([]byte, error) { // Pull the validation policy and check it for required fields vp, err := clnt.Policy(ctx) if err != nil { - return nil, err + return nil, nil, err } // Subject validation // common name if vp.SubjectDN.CommonName.Presence == hvclient.Required { if csr.Subject.CommonName == "" { - return nil, errors.New("atlas validation policy requires subject common name, but CSR did not contain one") + return nil, nil, errors.New("atlas validation policy requires subject common name, but CSR did not contain one") } req.Subject.CommonName = csr.Subject.CommonName } @@ -109,7 +110,7 @@ func (o *hvcaSigner) Sign(csrBytes []byte) ([]byte, error) { // serial number if vp.SubjectDN.SerialNumber.Presence == hvclient.Required { if csr.Subject.SerialNumber == "" { - return nil, errors.New("atlas validation policy requires subject serial number, but CSR did not contain one") + return nil, nil, errors.New("atlas validation policy requires subject serial number, but CSR did not contain one") } req.Subject.SerialNumber = csr.Subject.SerialNumber } @@ -135,15 +136,15 @@ func (o *hvcaSigner) Sign(csrBytes []byte) ([]byte, error) { } // Validate number of SANs if vp.SAN.DNSNames.MinCount > len(req.SAN.DNSNames) || vp.SAN.IPAddresses.MinCount > len(req.SAN.IPAddresses) { - return nil, errors.New("atlas validation policy requires additional SANs not present in the provided CSR") + return nil, nil, errors.New("atlas validation policy requires additional SANs not present in the provided CSR") } // Check key type if vp.PublicKey.KeyType.String() != csr.PublicKeyAlgorithm.String() { - return nil, errors.New("csr public key type doesn't match Atlas account pubic key type: CSR - " + csr.PublicKeyAlgorithm.String() + "Atlas - " + vp.PublicKey.KeyType.String()) + return nil, nil, errors.New("csr public key type doesn't match Atlas account pubic key type: CSR - " + csr.PublicKeyAlgorithm.String() + "Atlas - " + vp.PublicKey.KeyType.String()) } // Check PKCS type if vp.PublicKey.KeyFormat != hvclient.PKCS10 { - return nil, errors.New("atlas account does not support pkcs10 key format, update atlas account") + return nil, nil, errors.New("atlas account does not support pkcs10 key format, update atlas account") } // Check signature hash algorithm requirement and set to the first approved one if vp.SignaturePolicy.HashAlgorithm.Presence == 2 { //Presence is required @@ -151,14 +152,32 @@ func (o *hvcaSigner) Sign(csrBytes []byte) ([]byte, error) { } // Request cert if serial, err = clnt.CertificateRequest(ctx, &req); err != nil { - return nil, err + return nil, nil, err } // Retrieve cert if info, err = clnt.CertificateRetrieve(ctx, serial); err != nil { - return nil, err + return nil, nil, err } + // Retrieve ca chain + if caChainList, err = clnt.TrustChain(ctx); err != nil { + return nil, nil, err + } + + // Convert CA Chain into PEM + var caChain []byte + for _, cert := range caChainList { + var certPEM = pem.EncodeToMemory(&pem.Block{ + Type: "CERTIFICATE", + Bytes: cert.Raw, + }) + + caChain = append(caChain, certPEM...) + } + return pem.EncodeToMemory(&pem.Block{ - Type: "CERTIFICATE", - Bytes: info.X509.Raw, - }), nil + Type: "CERTIFICATE", + Bytes: info.X509.Raw, + }), + caChain, + nil }