aws-privateca-issuer icon indicating copy to clipboard operation
aws-privateca-issuer copied to clipboard

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

Open find-arka opened this issue 2 years ago • 11 comments

Describe the expected outcome

  • I created a Root CA in AWS Private CA with the template: RootCACertificate/V1
  • Created a Subordinate CA with the help of the same Root CA. Template used for Subordinate: SubordinateCACertificate_PathLen3/V1
  • Verified that the certificate of Subordinate CA has the pathlen:3 constraint i.e.
            X509v3 Basic Constraints: critical
                CA:TRUE, pathlen:3
  • Next, I created a CA certificate (using Certificate of cert-manager.io/v1). The generated CA certificate has the following constraints-
            X509v3 Basic Constraints: critical
                CA:TRUE, pathlen:0

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

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}"
  1. Setup a k8s Service Account and an associated IAM role to access the subordinate CA.
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
  1. Create a Certificate object
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?

  • [X] Check the Troubleshooting section
  • [X] Search open issues

Category

Supported Workflow Broken

Severity

Severity 3

find-arka avatar Nov 01 '22 18:11 find-arka

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 avatar Nov 01 '22 18:11 divyansh-gupta

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

divyansh-gupta avatar Nov 01 '22 18:11 divyansh-gupta

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

find-arka avatar Nov 01 '22 18:11 find-arka

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?

divyansh-gupta avatar Nov 03 '22 21:11 divyansh-gupta

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 avatar Feb 10 '23 15:02 find-arka

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

find-arka avatar Mar 25 '23 01:03 find-arka

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!

bmsiegel avatar Mar 25 '23 01:03 bmsiegel

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?

dcamzn avatar Mar 30 '23 14:03 dcamzn

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

kind: Certificate
  metadata:
    annotations:
      acm-pca.template-arn: templateArn

divyansh-gupta avatar Mar 30 '23 15:03 divyansh-gupta

@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:

  • Subordinate CA gets created in AWS with template: SubordinateCACertificate_PathLen2/V1
  • While creating Certificate custom resource for a CA certificate, and using an Issuer to use the above created Subordinate CA, end user decides to put annotation acm-pca.template-arn: SubordinateCACertificate_PathLen3/V1
  • From pathlen: 2 Issuing cert, can we create a CA cert with pathlen: 3? Assuming that the answer is no- We would have to put a validation corresponding to the annotation value: acm-pca.template-arn: SubordinateCACertificate_PathLen3/V1 so that accidentally users don't pass a template value which has higher path len than the Issuing cert path len.

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?

  • We fetch the path len of the Issuing cert. Let's call this value "n"
  • If the annotation acm-pca.template-arn is not present, and if the Certificate.spec.isCA is set to true, we generate the CA cert with a pathlen "n-1"
  • If the annotation is present, we check if the requested path len is less than the Issuing path len or not. If its less, then we set the path len based on the requested path len.
  • If the annotation is present, and the requested path len is greater than the issuing cert path len, we don't generate a cert and print the error that the requested len must be <= "n-1"

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

find-arka avatar Mar 30 '23 16:03 find-arka

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

dcamzn avatar Mar 31 '23 20:03 dcamzn