Skip to content

Keyfactor/command-cert-manager-issuer

Folders and files

NameName
Last commit message
Last commit date

Latest commit

7642fad · Apr 22, 2025
Jan 15, 2025
Mar 12, 2025
Jan 9, 2025
Mar 12, 2025
Apr 10, 2025
Apr 14, 2025
Mar 18, 2025
Jan 9, 2025
Sep 22, 2023
Jan 9, 2025
Apr 14, 2025
Jan 9, 2025
Mar 28, 2023
Jan 9, 2025
Jan 9, 2025
Apr 14, 2025
Mar 12, 2025
Mar 12, 2025
Jan 9, 2025

Repository files navigation

Command Issuer

Integration Status: production Release Issues GitHub Downloads (all assets, all releases)

Support · License · Related Integrations

Support

The Command Issuer is open source and community supported, meaning that there is no SLA applicable.

To report a problem or suggest a new feature, use the Issues tab. If you want to contribute actual bug fixes or proposed enhancements, use the Pull requests tab.

Overview

The Command Issuer for cert-manager is a CertificateRequest controller that issues certificates using Keyfactor Command.

Requirements

Before continuing, ensure that the following requirements are met:

Getting Started

Configuring Command

Command Issuer enrolls certificates by submitting a POST request to the Command CSR Enrollment endpoint. Before using Command Issuer, you must create or identify a Certificate Authority and Certificate Template suitable for your usecase. Additionally, you should ensure that the identity used by the Issuer/ClusterIssuer has the appropriate permissions in Command.

  1. Create or identify a Certificate Authority

    A certificate authority (CA) is an entity that issues digital certificates. Within Keyfactor Command, a CA may be a Microsoft CA, EJBCA, or a Keyfactor gateway to a cloud-based or remote CA.

    • If you haven't created a Certificate Authority before, refer to the Command documentation to learn how, or reach out to your Keyfactor support representative.

    The CA that you choose must be configured to allow CSR Enrollment.

  2. Identify a Certificate Template

    Certificate Templates in Command define properties and constraints of the certificates being issued. This includes settings like key usage, extended key usage, validity period, allowed key algorithms, and signature algorithms. They also control the type of information that end entities must provide and how that information is validated before issuing certificates.

    • If you don't have any suitable Certificate Templates, refer to the Command documentation or reach out to your Keyfactor support representative to learn more.

    The Certificate Template that you choose must be configured to allow CSR Enrollment.

    You should make careful note of the allowed Key Types and Key Sizes on the Certificate Template. When creating cert-manager Certificates, you must make sure that the key algorithm and size are allowed by your Certificate Template in Command.

    The same goes for Enrollment RegExes and Policies defined on your Certificate Template. When creating cert-manager Certificates, you must make sure that the subject, commonName, dnsNames, etc. are allowed and/or configured correctly by your Certificate Template in Command.

  3. Configure Command Security Roles and Claims

    In Command, Security Roles define groups of users or administrators with specific permissions. Users and subjects are identified by Claims. By adding a Claim to a Security Role, you can define what actions the user or subject can perform and what parts of the system it can interact with.

    The security role will need to be added as an Allowed Requester Security Role on the Certificate Authority and Certificate Template configured in the previous two steps.

    • If you haven't created Roles and Access rules before, this guide provides a primer on these concepts in Command.

    If your security policy requires fine-grain access control, Command Issuer requires the following Access Rules:

    Global Permissions Permission Model (Version Two) Permission Model (Version One)
    Metadata > Types > Read /metadata/types/read/ CertificateMetadataTypes:Read
    Certificates > Enrollment > Csr /certificates/enrollment/csr/ CertificateEnrollment:EnrollCSR

    Documentation for Version Two Permission Model and Version One Permission Model

Permission Metadata Read

Permission Certificate CSR Enrollment

Certificate Authority Allowed Requester

Certificate Template Allowed Requester

Installing Command Issuer

Command Issuer is installed using a Helm chart. The chart is available in the Command cert-manager Helm repository.

  1. Verify that at least one Kubernetes node is running:

    kubectl get nodes
  2. Add the Helm repository:

    helm repo add command-issuer https://keyfactor.github.io/command-cert-manager-issuer
    helm repo update
  3. Then, install the chart:

    helm install command-cert-manager-issuer command-issuer/command-cert-manager-issuer \
        --namespace command-issuer-system \
        --create-namespace 

The Helm chart installs the Command Issuer CRDs by default. The CRDs can be installed manually with the make install target.

Authentication

Command Issuer supports authentication to Command using one of the following methods:

  • Basic Authentication (username and password)
  • OAuth 2.0 "client credentials" token flow (sometimes called two-legged OAuth 2.0)

These credentials must be configured using a Kubernetes Secret. By default, the secret is expected to exist in the same namespace as the issuer controller (command-issuer-system by default).

Command Issuer can read secrets in the Issuer namespace if --set "secretConfig.useClusterRoleForSecretAccess=true" flag is set when installing the Helm chart.

Command Issuer also supports ambient authentication, where a token is fetched from an Authorization Server using a cloud provider's auth infrastructure and passed to Command directly. The following methods are supported:

  • Managed Identity Using Azure Entra ID Workload Identity (if running in AKS)

Basic Auth

Create a kubernetes.io/basic-auth secret with the Keyfactor Command username and password:

cat <<EOF | kubectl -n command-issuer-system apply -f -
apiVersion: v1
kind: Secret
metadata:
  name: command-secret
type: kubernetes.io/basic-auth
data:
  username: <base64 encoded (domain\\)username>
  password: <base64 encoded password>
EOF

OAuth

Create an Opaque secret containing the client ID and client secret to authenticate with Command:

token_url="<token url>"
client_id="<client id>"
client_secret="<client secret>"
audience="<audience>"
scopes="<scopes>" # comma separated list of scopes

kubectl -n command-issuer-system create secret generic command-secret \
    "--from-literal=tokenUrl=$token_url" \
    "--from-literal=clientId=$client_id" \
    "--from-literal=clientSecret=$client_secret" \
    "--from-literal=audience=$audience" \
    "--from-literal=scopes=$scopes"

Audience and Scopes are optional

Managed Identity Using Azure Entra ID Workload Identity (AKS)

Azure Entra ID workload identity in Azure Kubernetes Service (AKS) allows Command Issuer to exchange a Kubernetes ServiceAccount Token for an Azure Entra ID access token, which is then used to authenticate to Command.

At this time, Azure Kuberentes Services workload identity federation is best supported by User Assigned Managed Identities. Other identity solutions such as Azure AD Service Principals are not supported.

Here is a guide on how to use Azure User Assigned Managed Identities to authenticate your AKS workload with your Keyfactor Command instance.

  1. Reconfigure the AKS cluster to enable workload identity federation.

    export CLUSTER_NAME=<cluster-name>
    export RESOURCE_GROUP=<resource-group>
    az aks update \
        --name ${CLUSTER_NAME} \
        --resource-group ${RESOURCE_GROUP} \
        --enable-oidc-issuer \
        --enable-workload-identity

    The Azure Workload Identity extension can be installed on non-AKS or self-managed clusters if you're not using AKS.

    Refer to the AKS documentation for more information on the --enable-workload-identity feature.

  2. Create a User Assigned Managed Identity in Azure.

    export IDENTITY_NAME=command-issuer
    az identity create --name "${IDENTITY_NAME}" --resource-group "${RESOURCE_GROUP}"

    Read more about the az identity command.

  3. Reconfigure or deploy Command Issuer with extra labels for the Azure Workload Identity webhook, which will result in the Command Issuer controller Pod having an extra volume containing a Kubernetes ServiceAccount token which it will exchange for a token from Azure.

    export UAMI_CLIENT_ID=$(az identity show --name $IDENTITY_NAME --resource-group $RESOURCE_GROUP --query clientId --output tsv)
    
    echo "Identity Client ID: ${UAMI_CLIENT_ID}"
    
    helm install command-cert-manager-issuer command-issuer/command-cert-manager-issuer \
        --namespace command-issuer-system \
        --create-namespace \
        --set "fullnameOverride=command-cert-manager-issuer" \
        --set-string "podLabels.azure\.workload\.identity/use=true" \
        --set-string "serviceAccount.labels.azure\.workload\.identity/use=true" \
        --set-string "serviceAccount.annotations.azure\.workload\.identity/client-id=${UAMI_CLIENT_ID}"

    If successful, the Command Issuer Pod will have new environment variables and the Azure WI ServiceAccount token as a projected volume:

    kubectl -n command-issuer-system describe pod
    Containers:
      command-cert-manager-issuer:
        ...
        Environment:
          AZURE_CLIENT_ID:             <UAMI_CLIENT_ID>
          AZURE_TENANT_ID:             <GUID>
          AZURE_FEDERATED_TOKEN_FILE:  /var/run/secrets/azure/tokens/azure-identity-token
          AZURE_AUTHORITY_HOST:        https://login.microsoftonline.com/
        Mounts:
          /var/run/secrets/azure/tokens from azure-identity-token (ro)
          /var/run/secrets/kubernetes.io/serviceaccount from kube-api-access-6rmzz (ro)
    ...
    Volumes:
      ...
      azure-identity-token:
        Type:                    Projected (a volume that contains injected data from multiple sources)
        TokenExpirationSeconds:  3600

    Refer to Azure Workload Identity docs more information on the role of the Mutating Admission Webhook.

  4. Associate a Federated Identity Credential (FIC) with the User Assigned Managed Identity. The FIC allows Command Issuer to act on behalf of the Managed Identity by telling Azure to expect:

    • The iss claim of the ServiceAccount token to match the cluster's OIDC Issuer. Azure will also use the Issuer URL to download the JWT signing certificate.
    • The sub claim of the ServiceAccount token to match the ServiceAccount's name and namespace.
    export SERVICE_ACCOUNT_NAME=command-cert-manager-issuer # This is the default Kubernetes ServiceAccount used by the Command Issuer controller.
    export SERVICE_ACCOUNT_NAMESPACE=command-issuer-system # This is the default namespace for Command Issuer used in this doc.
    
    export SERVICE_ACCOUNT_ISSUER=$(az aks show --resource-group $RESOURCE_GROUP --name $CLUSTER_NAME --query "oidcIssuerProfile.issuerUrl" -o tsv)
    az identity federated-credential create \
        --name "${IDENTITY_NAME}-federated-credentials" \
        --identity-name "${IDENTITY_NAME}" \
        --resource-group "${RESOURCE_GROUP}" \
        --issuer "${SERVICE_ACCOUNT_ISSUER}" \
        --subject "system:serviceaccount:${SERVICE_ACCOUNT_NAMESPACE}:${SERVICE_ACCOUNT_NAME}" \
        --audiences "api://AzureADTokenExchange"

    Read more about Workload Identity federation in the Entra ID documentation.

    Read more about the az identity federated-credential command.

  5. Get the Managed Identity's Principal ID and Entra Identity Provider Information

export UAMI_PRINCIPAL_ID=$(az identity show --name $IDENTITY_NAME --resource-group $RESOURCE_GROUP --query principalId --output tsv)
export CURRENT_TENANT=$(az account show --query tenantId --output tsv)
echo "UAMI Principal ID: ${UAMI_PRINCIPAL_ID}"

echo "View then OIDC configuration for the Entra OIDC token issuer: https://login.microsoftonline.com/$CURRENT_TENANT/v2.0/.well-known/openid-configuration"

echo "Authority: https://login.microsoftonline.com/$CURRENT_TENANT/v2.0"

IMPORTANT NOTE: The Microsoft Entra Identity Provider is associated with your Azure tenant ID. Multi-tenant Azure workloads will require a Command Identity Provider for each tenant.

  1. Add the Microsoft Entra ID as an Identity Provider in Command using the identity provider information from the previous step, and add the Managed Identity's Principal ID as an OAuth Subject claim to the Security Role created/identified earlier.

Google Kubernetes Engine (GKE) Workload Identity

Google Kuberentes Engine (GKE) supports the ability to authenticate your GKE workloads using workload identity.

By default, GKE clusters are assigned the default service account for your Google project. This service account is used to generate an ID token for your workload. However, you may opt to use Workload Identity Federation to your GKE cluster.

  1. Get the OAuth Client and Identity Provider for your GKE Cluster

Regardless if you are using the default service account or a custom service account, the following script will help you derive your GKE cluster's OAuth Client:

export CLUSTER_NAME=<cluster-name>
export GCLOUD_REGION=<region>
export GCLOUD_PROJECT_ID=$(gcloud config get-value project) # populate with the current PROJECT_ID context
export GCLOUD_PROJECT_NUMBER=$(gcloud projects describe $GCLOUD_PROJECT_ID --format="value(projectNumber)")
  
export GCLOUD_SERVICE_ACCOUNT=$(gcloud container clusters describe $CLUSTER_NAME \
--zone $GCLOUD_REGION \
--format="value(nodeConfig.serviceAccount)")

if [[ "$GCLOUD_SERVICE_ACCOUNT" == "default" ]]; then
  # Override service account with default compute service account
  GCLOUD_SERVICE_ACCOUNT="$GCLOUD_PROJECT_NUMBER[email protected]"
fi

echo "Service account: $GCLOUD_SERVICE_ACCOUNT"

# Get OAuth2 Client ID of service account
export GCLOUD_SERVICE_ACCOUNT_CLIENT_ID=$(gcloud iam service-accounts describe $GCLOUD_SERVICE_ACCOUNT \
--format="value(oauth2ClientId)")

echo "Service account OAuth2 client ID: $GCLOUD_SERVICE_ACCOUNT_CLIENT_ID"

echo "View the OIDC configuration for Google's OIDC token issuer: https://accounts.google.com/.well-known/openid-configuration"

echo "Authority: https://accounts.google.com"
  1. Add Google as an Identity Provider in Command using the identity provider information from the previous step, and add the Service Account's OAuth Client ID as an OAuth Subject claim to the Security Role created/identified earlier.

CA Bundle

If the Command API is configured to use a self-signed certificate or with a certificate whose issuer isn't widely trusted, the CA certificate must be provided as a Kubernetes secret.

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 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.

  1. Prepare the spec

    export HOSTNAME="<hostname>"
    export COMMAND_CA_HOSTNAME="<certificateAuthorityName>" # Only required for non-HTTPS CA types
    export COMMAND_CA_LOGICAL_NAME="<certificateAuthorityName>"
    export CERTIFICATE_TEMPLATE_SHORT_NAME="<certificateTemplateShortName>"

    The spec field of both the Issuer and ClusterIssuer resources use the following fields:

    Field Name Description
    hostname The hostname of the Command API Server.
    apiPath (optional) The base path of the Command REST API. Defaults to KeyfactorAPI.
    commandSecretName The name of the Kubernetes secret containing basic auth credentials or OAuth 2.0 credentials
    caSecretName (optional) The name of the Kubernetes secret containing the CA certificate. Required if the Command API uses a self-signed certificate or it was signed by a CA that is not widely trusted.
    certificateAuthorityLogicalName The logical name of the Certificate Authority to use in Command. For example, Sub-CA
    certificateAuthorityHostname (optional) The hostname of the Certificate Authority specified by certificateAuthorityLogicalName. This field is usually only required if the CA in Command is a DCOM (MSCA-like) CA.
    certificateTemplate The Short Name of the Certificate Template to use when this Issuer/ClusterIssuer enrolls CSRs.
    scopes (Optional) If using ambient credentials, these scopes will be put on the access token generated by the ambient credentials' token provider, if applicable.
    audience (Optional) If using ambient credentials, this audience will be put on the access token generated by the ambient credentials' token provider, if applicable. Google's ambient credential token provider generates an OIDC ID Token. If this value is not provided, it will default to command.

    If a different combination of hostname/certificate authority/certificate template is required, a new Issuer or ClusterIssuer resource must be created. Each resource instantiation represents a single configuration.

  2. Create an Issuer or ClusterIssuer

    • Issuer

      Create an Issuer resource using the environment variables prepared in step 1.

      cat <<EOF > ./issuer.yaml
      apiVersion: command-issuer.keyfactor.com/v1alpha1
      kind: Issuer
      metadata:
        name: issuer-sample
        namespace: default
      spec:
        hostname: "$HOSTNAME"
        apiPath: "/KeyfactorAPI" # Preceding & trailing slashes are handled automatically
        commandSecretName: "command-secret" # references the secret created above
        caSecretName: "command-ca-secret" # references the secret created above
      
        # certificateAuthorityHostname: "$COMMAND_CA_HOSTNAME" # Uncomment if required
        certificateAuthorityLogicalName: "$COMMAND_CA_LOGICAL_NAME"
        certificateTemplate: "$CERTIFICATE_TEMPLATE_SHORT_NAME"
        # scopes: "openid email https://example.com/.default" # Uncomment if desired
        # audience: "https://your-command-url.com" # Uncomment if desired
      EOF
      
      kubectl -n default apply -f issuer.yaml
    • ClusterIssuer

      Create a ClusterIssuer resource using the environment variables prepared in step 1.

      cat <<EOF > ./clusterissuer.yaml
      apiVersion: command-issuer.keyfactor.com/v1alpha1
      kind: ClusterIssuer
      metadata:
        name: clusterissuer-sample
      spec:
        hostname: "$HOSTNAME"
        apiPath: "/KeyfactorAPI" # Preceding & trailing slashes are handled automatically 
        commandSecretName: "command-secret" # references the secret created above
        caSecretName: "command-ca-secret" # references the secret created above
      
        # certificateAuthorityHostname: "$COMMAND_CA_HOSTNAME" # Uncomment if required
        certificateAuthorityLogicalName: "$COMMAND_CA_LOGICAL_NAME"
        certificateTemplate: "$CERTIFICATE_TEMPLATE_SHORT_NAME"
        # scopes: "openid email https://example.com/.default" # Uncomment if desired
        # audience: "https://your-command-url.com" # Uncomment if desired
      EOF
      
      kubectl apply -f clusterissuer.yaml

Creating a Certificate

Once an Issuer or ClusterIssuer resource is created, they can be used to issue certificates using cert-manager. The two most important concepts are Certificate and CertificateRequest resources.

  1. Certificate resources represent a single X.509 certificate and its associated attributes. cert-manager maintains the corresponding certificate, including renewal when appropriate.
  2. When Certificate resources are created, cert-manager creates a corresponding CertificateRequest that targets a specific Issuer or ClusterIssuer to actually issue the certificate.

To learn more about cert-manager, see the cert-manager documentation.

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.

apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: command-certificate
spec:
  issuerRef:
    name: issuer-sample
    group: command-issuer.keyfactor.com
    kind: Issuer
  commonName: example.com
  secretName: command-certificate

Certificate resources support many more fields than the above example. See the Certificate resource documentation for more information.

Similarly, a CertificateRequest resource can be created directly. The following is an example of a CertificateRequest resource.

apiVersion: cert-manager.io/v1
kind: CertificateRequest
metadata:
  name: command-certificate
spec:
  issuerRef:
    name: issuer-sample
    group: command-issuer.keyfactor.com
    kind: Issuer
  request: <csr>

All fields in Command Issuer and ClusterIssuer spec can be overridden by applying Kubernetes Annotations to Certificates and CertificateRequests. See runtime customization for more

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. The following is an example of approving a CertificateRequest resource named command-certificate.

cmctl approve command-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.

kubectl get secret command-certificate -o jsonpath='{.data.tls\.crt}' | base64 -d

To learn more about certificate approval and RBAC configuration, see the cert-manager documentation.

Overriding the Issuer/ClusterIssuer spec using Kubernetes Annotations on CertificateRequest Resources

Command Issuer allows you to override the certificateAuthorityHostname, certificateAuthorityLogicalName, and certificateTemplate by setting Kubernetes Annotations on CertificateRequest resources. This may be useful if certain enrollment scenarios require a different Certificate Authority or Certificate Template, but you don't want to create a new Issuer/ClusterIssuer.

  • command-issuer.keyfactor.com/certificateAuthorityHostname overrides certificateAuthorityHostname
  • command-issuer.keyfactor.com/certificateAuthorityLogicalName overrides certificateAuthorityLogicalName
  • command-issuer.keyfactor.com/certificateTemplate overrides certificateTemplate

cert-manager copies Annotations set on Certificate resources to the corresponding CertificateRequest.

How to Apply Annotations

Notes

To apply these annotations, include them in the metadata section of your Certificate/CertificateRequest resource:

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: "[email protected]"
    # ... other annotations
spec:
# ... the rest of the spec

Certificate Metadata

Keyfactor Command allows users to attach custom metadata to certificates that can be used to tag certificates with additional information. Command Issuer can attach Certificate Metadata upon enrollment.

  • Pre-defined Certificate Metadata

    If all of the following metadata fields are defined in Command, Command Issuer will populate the fields upon certificate enrollment. All of the metadata fields are String types. Please refer to the Command docs to define these metadata fields in Command if you would like Command Issuer to populate these fields on certificates upon enrollment.

    Field Name Description
    Issuer-Namespace The namespace that the Issuer resource was created in. Is always empty for ClusterIssuers.
    Controller-Reconcile-Id The GUID of the reconciliation run that corresponded to the issuance of this certificate.
    Certificate-Signing-Request-Namespace The namespace that the CertificateRequest resource was created in.
    Controller-Namespace The namespace that the controller container is running in.
    Controller-Kind The issuer type - Issuer or ClusterIssuer.
    Controller-Resource-Group-Name The group name of the Command Issuer CRD. Is always command-issuer.keyfactor.com.
    Issuer-Name The name of the K8s Issuer/ClusterIssuer resource.

    You don't need to re-create the Issuer/ClusterIssuer when metadata fields are added/removed in Command. Command Issuer automatically detects the presence of these fields and tracks the state in the SupportsMetadata resource condition.

  • Custom Certificate Metadata

    You can also configure Command Issuer to attach Certificate Metadata by annotating Certificate/CertificateRequest resources. Command Issuer does not check for the presence of custom metadata fields configured in Annotations, and you should take special care that fields defined in annotations exist in Command prior to use. Certificate issuance will fail if any of the metadata fields specified aren't configured in Command. The syntax for specifying metadata is as follows:

    metadata.command-issuer.keyfactor.com/<metadata-field-name>: <metadata-value>

License

Apache License 2.0, see LICENSE.

Related Integrations

See all Keyfactor integrations.