{"id":15283224,"url":"https://github.com/wikimedia/operations-software-cfssl-issuer","last_synced_at":"2026-02-13T10:13:11.739Z","repository":{"id":64306490,"uuid":"426605418","full_name":"wikimedia/operations-software-cfssl-issuer","owner":"wikimedia","description":"A cert-manager external issuer to request certificates signed by a CFSSL API. Mirror from https://gerrit.wikimedia.org/g/operations/software/cfssl-issuer - our actual code is hosted with Gerrit (please see https://www.mediawiki.org/wiki/Developer_access for contributing)","archived":false,"fork":false,"pushed_at":"2025-06-11T10:02:21.000Z","size":15291,"stargazers_count":3,"open_issues_count":0,"forks_count":1,"subscribers_count":20,"default_branch":"main","last_synced_at":"2025-08-27T21:55:05.354Z","etag":null,"topics":["cert-manager","cfssl","cfssl-api","kubernetes","pki"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":false,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/wikimedia.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2021-11-10T11:59:52.000Z","updated_at":"2025-06-11T10:02:24.000Z","dependencies_parsed_at":"2024-10-23T13:55:26.857Z","dependency_job_id":null,"html_url":"https://github.com/wikimedia/operations-software-cfssl-issuer","commit_stats":null,"previous_names":[],"tags_count":27,"template":false,"template_full_name":null,"purl":"pkg:github/wikimedia/operations-software-cfssl-issuer","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wikimedia%2Foperations-software-cfssl-issuer","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wikimedia%2Foperations-software-cfssl-issuer/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wikimedia%2Foperations-software-cfssl-issuer/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wikimedia%2Foperations-software-cfssl-issuer/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/wikimedia","download_url":"https://codeload.github.com/wikimedia/operations-software-cfssl-issuer/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wikimedia%2Foperations-software-cfssl-issuer/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29401344,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-13T06:24:03.484Z","status":"ssl_error","status_checked_at":"2026-02-13T06:23:12.830Z","response_time":78,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["cert-manager","cfssl","cfssl-api","kubernetes","pki"],"created_at":"2024-09-30T14:43:24.103Z","updated_at":"2026-02-13T10:13:11.705Z","avatar_url":"https://github.com/wikimedia.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# cfssl-issuer\n\nThis is an an [External Issuer] for cert-manager to be used with [CFSSL] `multirootca`.\nIt is based off of the [sample-external-issuer] provided by cert-manager.\n\nWhile it might work as well with `cfssl serve` instead of `multirootca` that has not been tested yet.\n\n## Install\nFor the installation of cert-manager, please see the documentation at https://cert-manager.io/docs/installation/.\n\nFor the cfssl-issuer, there are helm charts (_cfssl-issuer_ and _cfssl-issuer-cdrs_) available at https://helm-charts.wikimedia.org/stable (source: [cfssl-issuer](https://gerrit.wikimedia.org/r/plugins/gitiles/operations/deployment-charts/+/refs/heads/master/charts/cfssl-issuer/), [cfssl-issuer-cdrs](https://gerrit.wikimedia.org/r/plugins/gitiles/operations/deployment-charts/+/refs/heads/master/charts/cfssl-issuer-crds/)). The corresponding docker images can be found at: https://docker-registry.wikimedia.org/cfssl-issuer/tags/\n\nPlease see the helm charts `values.yaml` for examples on how to create Issuer/ClusterIssuer objects.\n\n## multirootca and bundles\nThe cfssl-issuer supports fetching bundles instead of certificates from the CFSSL endpoint `/api/v1/cfssl/authsign` (see [doc/api/endpoint_authsign.txt](https://github.com/cloudflare/cfssl/blob/master/doc/api/endpoint_authsign.txt)) which is currently only supported in a forked version of multirootca which can be fount at: https://github.com/wikimedia/cfssl/tree/wmf\n\nA corresponding upstream PR is at: https://github.com/cloudflare/cfssl/pull/1218\n\n## Root CA in kubernetes.io/tls Secret\nIn case the Issuer is configured with `bundle: true` (see node on multirootca support from above), the root CA is returned by the multirootca API and will be provided to the user in the resulting `kubernetes.io/tls` Secret.\n\nAs the multirootca API lacks the `/api/v1/cfssl/bundle` endpoint, this is unfortunately not possible with a `bundle: false` Issuer.\n\n# Development\n\nYou will need the following command line tools installed on your PATH:\n\n* [Git](https://git-scm.com/)\n* [Golang v1.20+](https://golang.org/)\n* [Docker v17.03+](https://docs.docker.com/install/)\n* [Kind v0.18.0+](https://kind.sigs.k8s.io/docs/user/quick-start/)\n* [Kubectl v1.26.3+](https://kubernetes.io/docs/tasks/tools/install-kubectl/)\n* [Kubebuilder v3.9.1+](https://book.kubebuilder.io/quick-start.html#installation)\n* [Kustomize v3.8.1+](https://kustomize.io/)\n\nYou may also want to read: the [Kubebuilder Book] and the [cert-manager Concepts Documentation].\n\n## The CertificateRequest\n\nThe `CertificateRequestReconciler` is triggered by changes to any `CertificateRequest` resource in the cluster.\nThe `Reconcile` function is called with the name of the object that changed, and\nthe first thing we need to do is to `GET` the complete object from the Kubernetes API server.\n\nThe `Reconcile` function may occasionally be triggered with the names of deleted resources,\nso we have to handle that case gracefully.\n\nIn the implementation we are careful to `return Result{}, nil` when the `CertificateRequest` is not found.\nThis tells controller-runtime *do not retry*.\nOther error types are assumed to be temporary errors and are returned.\n\nNOTE: If you return an `error`, controller-runtime will retry with an increasing backoff,\nso it is very important to distinguish between temporary and permanent errors.\n\n## Ignore foreign CertificateRequest\n\nWe only want to reconcile `CertificateRequest` resources that are configured for our issuer.\nSo the next piece of controller logic attempts to exit early if `CertificateRequest.Spec.IssuerRef` does not refer to our particular `Issuer` or `ClusterIssuer` types.\n\nAlso note how in the implementation we use the `Scheme.New`  method to verify the `Kind`.\nThis later will allow us to easily handle both `Issuer` and `ClusterIssuer` references.\n\nIf there is a mismatch in the `IssuerRef` we ignore the `CertificateRequest`.\n\n## Check that the CertificateRequest is Approved\n\nIssuers must only sign `Approved` `CertificateRequest` resources.\nIf the `CertificateRequest` has been `Denied`, then the Issuer should set a\n`Ready` condition to `False`, and set the `FailureTime`.\nIf the `CertificateRequest` has been `Approved`, then the Issuer should process\nthe request.\n\nIssuers are not responsible for approving `CertificateRequests`.\nYou can read more about the [CertificateRequest Approval API][] in the cert-manager documentation.\n\n[CertificateRequest Approval API]: https://cert-manager.io/docs/concepts/certificaterequest/#approval\n\nThe [cert-manager API utility package][] contains functions for checking the `Approved` and `Denied` conditions of a `CertificateRequest`.\n\n[cert-manager API utility package]: https://pkg.go.dev/github.com/jetstack/cert-manager@v1.3.0/pkg/api/util#CertificateRequestIsApproved\n\nIf using an older version of cert-manager (pre v1.3), you can disable this check\nby supplying the command line flag `-disable-approved-check` to the Deployment.\n\n## Set the CertificateRequest Ready condition\n\nThe [External Issuer] documentation says the following:\n\n It is important to update the condition status of the `CertificateRequest` to a ready state,\n as this is what is used to signal to higher order controllers, such as the Certificate controller, that the resource is ready to be consumed.\n Conversely, if the `CertificateRequest` fails, it is important to mark the resource as such, as this will also be used to signal to higher order controllers.\n\nSo now we need to ensure that our issuer always sets one of the [strongly defined conditions](https://cert-manager.io/docs/concepts/certificaterequest/#conditions)\non all the `CertificateRequest` referring to our `Group`.\n\nThe first thing to check is whether the `Ready` condition is already `true` in which case we can exit early.\n\n## The Issuer or ClusterIssuer\n\nThe `Issuer` or `ClusterIssuer` for the `CertificateRequest` contain configuration that you will need to connect to the CFSSL API (such as the `Label` and `Profile` to use).\nIt also contains a reference to a `Secret` containing credentials which you will use to authenticate with with the CFSSL API.\n\nAn `Issuer` has both a name and a namespace.\nA `ClusterIssuer` is [cluster scoped](https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/#not-all-objects-are-in-a-namespace) and does not have a namespace.\nSo we check which type we have in order to derive the correct name.\n\n## Get the Issuer or ClusterIssuer credentials from a Secret\n\nThe CFSSL API requires some configuration and credentials and the obvious place to store these is in a Kubernetes `Secret`.\n\nThe Secret for an Issuer MUST be in the same namespace as the Issuer.\nThe Secret for a ClusterIssuer MUST be in a namespace defined via command line argument. If no configuration is given, the namespace running the cfssl-issuer is used.\n\nNOTE: Ideally, we would `WATCH` for the particular `Secret` and trigger the reconciliation when it becomes available.\nAnd that may be a future enhancement to this project.\n\n\n## Issuer health checks\n\nAs the issuer connects to a CFSSL API it performs periodic health checks to ensure that the API server is responding and if not,\nto set update the `Ready` condition of the `Issuer` to false and log a meaningful error message with the condition.\nThis will give early warning of problems with the configuration or with the API,\nrather than waiting a for `CertificateRequest` to fail before being alerted to the problem.\n\nSince we want the health checks to be performed periodically,\nwe need to make controller-runtime retry reconciling regularly, even when the current reconcile succeeds.\nWe do this by setting the `Result.RequeueAfter` field of the returned result.\n\n\n## Sign the CertificateRequest\n\nNow we turn back to the `CertificateRequestReconciler` and think about how we want it to handle the certificate signing request (CSR).\n\nThe `signer` package contains a new simple `Interface` and a factory function definition:\n\n```\ntype Signer interface {\n    Sign(context.Context, []byte) ([]byte, []byte, error)\n}\n\ntype SignerBuilder func(*cfsslissuerapi.IssuerSpec, map[string][]byte) (Signer, error)\n```\n\nBoth are implemented by the `cfssl` signer in `internal/issuer/signer/cfssl.go`. The provided CSR is validated, transformed and finally send to the CFSSL API for signing (using the `Label` and `Profile` for the selected issuer).\n\n## End-to-end tests\n\nThose are implemented using [Kind] and a dummy CFSSL API container called simple-cfssl (which can be build from this source tree as well). End-to-end tests can be run via:\n```\nmake e2e-all\n```\n\nIf you already have a running Kubernetes cluster and want to test using the currently active context:\n```\nmake docker-build deploy-simple-cfssl e2e\n```\n\nIn the unit-tests, we can use a simple byte string for the certificate, but in E2E tests later we will use real certificate signing requests and real certificates.\n\n#### An example signer\n\nFor the purposes of this example external issuer,\nwe will implement an `exampleSigner` which implements both the `HealthChecker` and the `Signer` interfaces, and\nwhich signs the CSR using a static in-memory CA certificate.\n\nIn `internal/issuer/signer/signer.go` you will see that we:\ndecode the supplied CSR bytes,\nand then sign the certificate using some libraries that were copied from the Kubernetes project.\nThis simple implementation is just sufficient to allow us (later) to perform some E2E tests with cert-manager.\n\nIn your external issuer, this is where you will plug in your CA client library,\nor where you will instantiate an HTTP client and connect to your API.\n\nNotice also that we add two concrete factory functions which are supplied to the `IssuerReconciler` and `CertificateRequestReconciler` in `main.go`.\n\n#### What about the ClusterIssuerReconciler?\n\nWe have so far abandoned development of the `ClusterIssuerReconciler`, and that's because we want to re-use the `IssuerReconciler` rather than duplicating everything.\n\nSo here we delete the skaffolded `controllers/clusterissuer_controller.go` and update the `issuer_controller.go` to handle both types.\n\nAs well as juggling the code to handle both types, we:\naggregate the Kubebuilder RBAC annotations, and\nadd a new command line flag which allows us to set a `--cluster-resource-namespace`.\n\nThe `--cluster-resource-namespace` is the namespace where the issuer will look for `Secret` resources referred to by a `ClusterIssuer`,\nsince `ClusterIssuer` is cluster-scoped.\nThe default value of the flag is the namespace where the issuer is running in the cluster.\n\n### Logging and Events\n\nWe want to make it easy to debug problems with the issuer,\nso in addition to setting Conditions on the Issuer, ClusterIssuer and CertificateRequest,\nwe can provide more feedback to the user by logging Kubernetes Events.\nYou may want to read more about [Application Introspection and Debugging][] before continuing.\n\n[Application Introspection and Debugging]: https://kubernetes.io/docs/tasks/debug-application-cluster/debug-application-introspection/\n\nKubernetes Events are saved to the API server on a best-effort basis,\nthey are (usually) associated with some other Kubernetes resource,\nand they are temporary; old Events are periodically purged from the API server.\nThis allows tools such as `kubectl describe \u003cresource-kind\u003e \u003cresource-name\u003e` to show not only the resource details,\nbut also a table of the recent events associated with that resource.\n\nThe aim is to produce helpful debug output that looks like this:\n\n```\n$ kubectl describe clusterissuers.sample-issuer.example.com clusterissuer-sample\n...\n    Type:                  Ready\nEvents:\n  Type     Reason            Age                From                    Message\n  ----     ------            ----               ----                    -------\n  Normal   IssuerReconciler  13s                cfssl-issuer  First seen\n  Warning  IssuerReconciler  13s (x3 over 13s)  cfssl-issuer  Temporary error. Retrying: failed to get Secret containing Issuer credentials, secret name: cfssl-issuer-system/clusterissuer-sample-credentials, reason: Secret \"clusterissuer-sample-credentials\" not found\n  Normal   IssuerReconciler  13s (x3 over 13s)  cfssl-issuer  Success\n```\nAnd this:\n\n```\n$ kubectl describe certificaterequests.cert-manager.io issuer-sample\n...\nEvents:\n  Type     Reason                        Age   From                    Message\n  ----     ------                        ----  ----                    -------\n  Normal   CertificateRequestReconciler  23m   cfssl-issuer  Initialising Ready condition\n  Warning  CertificateRequestReconciler  23m   cfssl-issuer  Temporary error. Retrying: error getting issuer: Issuer.sample-issuer.example.com \"issuer-sample\" not found\n  Normal   CertificateRequestReconciler  23m   cfssl-issuer  Signed\n\n```\n\nFirst add [record.EventRecorder][] attributes to the `IssuerReconciler` and to the `CertificateRequestReconciler`.\nAnd then in the Reconciler code, you can then generate an event by executing `r.recorder.Eventf(...)` whenever a significant change is made to the resource.\n\n[record.EventRecorder]: https://pkg.go.dev/k8s.io/client-go/tools/record#EventRecorder\n\nYou can also write unit tests to verify the Reconciler events by using a [record.FakeRecorder][].\n\n[record.FakeRecorder]: https://pkg.go.dev/k8s.io/client-go/tools/record#FakeRecorder\n\nSee [PR 10: Generate Kubernetes Events](https://github.com/cert-manager/sample-external-issuer/pull/10) for an example of how you might generate events in your issuer.\n\n### End-to-end tests\n\nNow our issuer is almost feature complete and it should be possible to write an end-to-end test that\ndeploys a cert-manager `Certificate`\nreferring to an external `Issuer` and check that a signed `Certificate` is saved to the expected secret.\n\nWe can make such a test easier by tidying up the `Makefile` and adding some new targets\nwhich will help create a test cluster and to help install cert-manager.\n\nWe can write a simple end-to-end test which deploys a `Certificate` manifest and waits for it to be ready.\n\n```console\nkubectl apply --filename config/samples\nkubectl wait --for=condition=Ready --timeout=5s issuers.sample-issuer.example.com issuer-sample\nkubectl wait --for=condition=Ready --timeout=5s  certificates.cert-manager.io certificate-by-issuer\n```\n\nYou can of course write more complete tests than this,\nbut this is a good start and demonstrates that the issuer is doing what we hoped it would do.\n\nRun the tests as follows:\n\n```bash\n# Create a Kind cluster along with cert-manager.\nmake kind-cluster deploy-cert-manager\n\n# Wait for cert-manager to start...\n\n# Build and install cfssl-issuer and run the E2E tests.\n# This step can be run iteratively when ever you make changes to the code or to the installation manifests.\nmake docker-build kind-load deploy e2e\n```\n\n#### Continuous Integration\n\nYou should configure a CI system to automatically run the unit-tests when the code changes.\nSee the `.github/workflows/`  directory for some examples of using GitHub Actions\nwhich are triggered by changes to pull request branches and by any changes to the master branch.\n\nThe E2E tests can be executed with GitHub Actions too.\nThe GitHub Actions Ubuntu runner has Docker installed and is capable of running a Kind cluster for the E2E tests.\nThe Kind cluster logs can be saved in the event of an E2E test failure,\nand uploaded as a GitHub Actions artifact,\nto make it easier to diagnose E2E test failures.\n\n## Security considerations\n\nWe use a [Distroless Docker Image][] as our Docker base image,\nand we configure our `manager` process to run as `USER: nonroot:nonroot`.\nThis limits the privileges of the `manager` process in the cluster.\n\nThe [kube-rbac-proxy][] sidecar Docker image also uses a non-root user by default (since v0.7.0).\n\nAdditionally we [Configure a Security Context][] for the manager Pod.\nWe set `runAsNonRoot`, which ensure that the Kubelet will validate the image at runtime\nto ensure that it does not run as UID 0 (root) and fail to start the container if it does.\n\n## Links\n\n[External Issuer]: https://cert-manager.io/docs/contributing/external-issuers\n[CFSSL]: https://github.com/cloudflare/cfssl\n[cert-manager Concepts Documentation]: https://cert-manager.io/docs/concepts\n[Kubebuilder Book]: https://book.kubebuilder.io\n[kube-rbac-proxy]: https://github.com/brancz/kube-rbac-proxy\n[Kind]: (https://kind.sigs.k8s.io/)\n[sample-external-issuer]: https://github.com/cert-manager/sample-external-issuer\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwikimedia%2Foperations-software-cfssl-issuer","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fwikimedia%2Foperations-software-cfssl-issuer","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwikimedia%2Foperations-software-cfssl-issuer/lists"}