cert-manager / aws-privateca-issuer

Addon for cert-manager that issues certificates using AWS ACM PCA.
Apache License 2.0
186 stars 77 forks source link

[Feature Request]: Unable to Issue a CA certificate with the desired pathlen constraint value #220

Open find-arka opened 1 year ago

find-arka commented 1 year ago

Describe the expected outcome

Expected a CA certificate with pathlen:2

Describe the actual outcome

The generated CA certificate had a pathlen:0 constraint, instead of the expected pathlen:2 constraint.

Is this happening due to this section in pca.go?

if spec.IsCA {
return prefix + "acm-pca:::template/SubordinateCACertificate_PathLen0/V1"
}

Steps to reproduce

  1. Create an EKS Cluster.

  2. Install Cert Manager v1.10.0

    
    # https://cert-manager.io/docs/installation/helm/
    CERT_MANAGER_VERSION=v1.10.0
    helm repo add jetstack https://charts.jetstack.io
    helm repo update

helm install cert-manager jetstack/cert-manager \ --namespace cert-manager \ --create-namespace \ --version ${CERT_MANAGER_VERSION} \ --set installCRDs=true \ --wait


3. Create a Root CA and a Subordinate CA in AWS Private CA
You could run this script to create a Root CA and a Subordinate CA- https://raw.githubusercontent.com/find-arka/k8s-misc/main/create-ca-hierarchy-aws-pca.sh

With the output from the script, save the Intermediate CA ARN in an environment variable-
```bash
export CA_ARN=arn:aws:acm-pca:REDACTED:REDACTED-AC:certificate-authority/REDACTED
  1. Setup an IAM Policy to access the CA
    
    cat <<EOF > AWSPCAIssuerPolicyTest.json
    {
    "Version": "2012-10-17",
    "Statement": [
    {
      "Sid": "awspcaissuer",
      "Action": [
        "acm-pca:DescribeCertificateAuthority",
        "acm-pca:GetCertificate",
        "acm-pca:IssueCertificate"
      ],
      "Effect": "Allow",
      "Resource": "${CA_ARN}"
    }
    ]
    }
    EOF

POLICY_ARN=$(aws iam create-policy \ --policy-name AWSPCAIssuerPolicyTest \ --policy-document file://AWSPCAIssuerPolicyTest.json \ --output json | jq -r '.Policy.Arn')

echo "POLICY_ARN = ${POLICY_ARN}"


5. Setup a k8s Service Account and an associated IAM role to access the subordinate CA.
```zsh
CURRENT_CLUSTER="Please put your cluster name here"
echo "${POLICY_ARN}"
echo "${REGION}"
echo "${CURRENT_CLUSTER}"

# Currently, we are installing the plugin in the same namespace as cert-manager
export PCA_NAMESPACE=cert-manager
# latest version https://github.com/cert-manager/aws-privateca-issuer/releases
export AWSPCA_ISSUER_TAG=v1.2.2

# Enable the IAM OIDC Provider for the cluster
eksctl utils associate-iam-oidc-provider \
    --cluster=${CURRENT_CLUSTER} \
    --region=${REGION} \
    --approve;

# Create IAM role bound to a service account
eksctl create iamserviceaccount --cluster=${CURRENT_CLUSTER} \
    --region=${REGION} \
    --namespace=${PCA_NAMESPACE} \
    --attach-policy-arn=${POLICY_ARN} \
    --override-existing-serviceaccounts \
    --tags "created-by=${USER},team=${TEAM},purpose=customer-support" \
    --name=aws-pca-issuer \
    --role-name "ServiceAccountRolePrivateCA-${CURRENT_CLUSTER}" \
    --approve;

# Install AWS Private CA Issuer Plugin 
# https://github.com/cert-manager/aws-privateca-issuer/#setup
helm repo add awspca https://cert-manager.github.io/aws-privateca-issuer
helm repo update
helm install aws-pca-issuer awspca/aws-privateca-issuer \
    --namespace ${PCA_NAMESPACE} \
    --set image.tag=${AWSPCA_ISSUER_TAG} \
    --set serviceAccount.create=false \
    --set serviceAccount.name=aws-pca-issuer \
    --kube-context ${CURRENT_CLUSTER} \
    --wait;

# Verify deployment status
kubectl --context ${CURRENT_CLUSTER} -n ${PCA_NAMESPACE} \
    rollout status deploy/aws-pca-issuer-aws-privateca-issuer;
  1. Create the Issuer-
    
    # edit the var if your CA is in a different region
    export CA_REGION="YOUR CA REGION"
    export CA_ARN="arn:aws:acm-pca:redacted:redacted:certificate-authority/redacted"

cat << EOF | kubectl apply -f - apiVersion: awspca.cert-manager.io/v1beta1 kind: AWSPCAClusterIssuer metadata: name: my-cluster-issuer spec: arn: ${CA_ARN} region: ${CA_REGION} EOF


7. Create a Certificate object
```zsh
cat << EOF | kubectl apply -f -
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: my-internal-ca
spec:
  commonName: my-internal-ca
  dnsNames:
    - "*.internal.my-org.ca"
  isCA: true
  duration: 2160h #90d
  secretName: my-internal-ca-cert-manager
  subject:
    organizations:
    - cluster.local
    - cert-manager
  issuerRef:
    group: awspca.cert-manager.io
    kind: AWSPCAClusterIssuer
    name: my-cluster-issuer
EOF

Verify:

kubectl get Certificate my-internal-ca

Expected output:

NAME             READY   SECRET                        AGE
my-internal-ca   True    my-internal-ca-cert-manager   4s
  1. extract the secret to read the value-
kubectl get secret my-internal-ca-cert-manager -o yaml | yq -r '.data."tls.crt"' | base64 -d > my-internal-ca-cert-manager-tls-crt.pem
kubectl get secret my-internal-ca-cert-manager -o yaml | yq -r '.data."ca.crt"' | base64 -d > my-internal-ca-cert-manager-ca-crt.pem

my-internal-ca-cert-manager-tls-crt.pem has the CA cert chained with the Issuer. Extract the top section from the pem file and copy it to a different file. I named it generated-ca-cert.pem

openssl x509 -in generated-ca-cert.pem -noout -text | grep -A3 Constraint
            X509v3 Basic Constraints: critical
                CA:TRUE, pathlen:0
            X509v3 Authority Key Identifier:
                5C:F9:5F:9D:CF:86:DD:56:94:64:36:C4:REDACTED
  1. Get the Subordinate cert and verify that the pathlen 3 constraint is present-
ROOT_CAARN="arn:aws:acm-pca:REDACTED:REDACTED:certificate-authority/REDACTED"
SUBORDINATE_CERTARN="arn:aws:acm-pca:REDACTED:REDACTED:certificate-authority/REDACTED/certificate/REDACTED"

aws acm-pca get-certificate \
    --certificate-authority-arn "${ROOT_CAARN}" \
    --certificate-arn "${SUBORDINATE_CERTARN}" \
    --output json | jq -r '.Certificate' > "intermediate-cert-common-purpose.pem"

openssl x509 -in intermediate-cert-common-purpose.pem -noout -text | grep -A3 Constraint
            X509v3 Basic Constraints: critical
                CA:TRUE, pathlen:3
            X509v3 Authority Key Identifier:
                0B:97:66:22:D3:3A:FF:7D:51:10:2F:46:D1:F8:E8:E9:1D:4E:64:CA

Subordinate CA cert has pathlen:3 but the generated CA cert from that CA cert doesn't have pathlen:2, instead it has pathlen:0

Relevant log output

N/A.
Have already attached the expected output along with the commands in the above section.

Version

Cert Manager -> v1.10.0 aws-privateca-issuer-> v1.2.2 Kubernetes -> 1.22 Amazon EKS platform version -> eks.6

Have you tried the following?

Category

Supported Workflow Broken

Severity

Severity 3

divyansh-gupta commented 1 year ago

Hi @find-arka, thanks for the issue. I wouldn't say this is a bug - more of a missing feature. The root of the problem here is that this issuer does not allow users to set the TemplateArn themselves. This is a feature that we have discussed having, but have not committed resources to determine the best method to do so. I will discuss with the team.

divyansh-gupta commented 1 year ago

Related: https://github.com/cert-manager/aws-privateca-issuer/issues/98

find-arka commented 1 year ago

Hello @divyansh-gupta, Thank you for the quick response! Looking forward to hearing more about the team-discussion outcomes on this.

divyansh-gupta commented 1 year ago

Hi @find-arka, discussed with the team, there are several things we can do here:

  1. We can ask cert-manager to include an optional pathLen in their Certificate Resource API (currently unsupported https://cert-manager.io/docs/usage/certificate/) and consume that in this plugin to issue the certificate with the appropriate Private CA certificate template.

  2. We can make this a more general feature to allow users to specifically name the Private CA template they would like to issue with. We can do this in two ways: a. We update the Issuer Resource to take in a new optional TemplateArn parameter. We can then apply this template to all issuances that happen through this Issuer. This has the disadvantage that users will have to make multiple Issuers if they want to issue with more than 1 type of Private CA template.

    b. We talk to cert-manager and see if there is a way we can include TemplateArn as a custom API parameter in the Certificate Resource API (unlikely).

We aren't sure which is the right path forward yet, but would love to get your thoughts?

find-arka commented 1 year ago

Hello @divyansh-gupta , Hope you are doing well! My sincere apologies for the late response on this. Thank you very much for your suggestions.

Had a discussion with @jmunozro , and we both felt that option 2.a sounds like the best one:

  1. We can make this a more general feature to allow users to specifically name the Private CA template they would like to issue with. We can do this in two ways: a. We update the Issuer Resource to take in a new optional TemplateArn parameter. We can then apply this template to all issuances that happen through this Issuer. This has the disadvantage that users will have to make multiple Issuers if they want to issue with more than 1 type of Private CA template.
find-arka commented 1 year ago

Hello @divyansh-gupta , Hope you are doing well! Any thoughts on how this could be taken forward?

bmsiegel commented 1 year ago

Hi @find-arka, we're having discussions as a team, and when we have a path forward we'll update you. If you have solutions we'll be happy to take a look at a pull request. Thanks for checking in!

dcamzn commented 1 year ago

Hi @find-arka, we will look into using Kubernetes annotations. With annotations, we think you can pass in the template ARN that you'd like to use. If we're right, then you can specify the a template with the path length that you need. Do you have any thoughts or feedback on our approach?

divyansh-gupta commented 1 year ago

Just to add to that, the annotation would be something like:

kind: Certificate
  metadata:
    annotations:
      acm-pca.template-arn: templateArn
find-arka commented 1 year ago

@dcamzn , @divyansh-gupta Thank you for taking the discussion ahead. The annotation approach should work, but we might have to put additional validation to ensure that the generated cert has the right pathlen constraint.

e.g. scenario:

If we have to put in a validation like that, we would have to get the path len of the Issuing cert and then compare with the requested path len of the new CA cert.

Can we do it this way?

I'm sure some other boundary conditions for this validation need to be present. What do you all think?

dcamzn commented 1 year ago

@find-arka thanks for the feedback and reviewing our approach! We agree with you that we need to put additional validations with this approach. We will review validation requirements and come back for your thoughts.