cert-manager: Vault issuer with Kubernetes auth: claim "iss" is invalid due to the use of default service account tokens
🚧 Update 29 July 2022 🚧 If you are seeing the message “claim
iss
is invalid”, the workaround is to disableiss
validation in Vault as explained in Problem 3: Incohenrent iss.
In Kubernetes v1.20+, kube-apiserver requires the flag --service-account-issuer
to be an URI (see documentation).
When I deployed the Vault Issuer with Helm, I got the following error on vault login api:
claim "iss" is invalid
I found in (vault.go)[https://github.com/jetstack/cert-manager/blob/master/pkg/internal/vault/vault.go#L313-L315] that cert-manager sends the JWT token as is, but Vault expects a JWT with an issuer equal to the value of the flag --service-account-issuer
.
For example, when kube-apiserver is running with the flag:
--service-account-issuer=https://kubernetes.default.svc.cluster.local
then the JWT token used by cert-manager in the Vault issuer should have a payload that looks like this:
{
"iss": "https://kubernetes.default.svc.cluster.local"
}
Instead of this, the JWT token looks like this:
{
"iss": "kubernetes/serviceaccount"
}
My current workaround is to disable JWT Issuer Validation on my PKI config in Vault.
Steps to reproduce the bug: Use latest version of kubernetes Define service-account-issuer in kube-apiserver config Setup vault issuer with this doc use latest vault and create pki with JWT Issuer Validation enabled try to create vault issuer
Environment details::
- Kubernetes version: v1.21.0
- cert-manager version: v1.4.0
- Install method: e.g. helm
/kind bug
About this issue
- Original URL
- State: closed
- Created 3 years ago
- Reactions: 10
- Comments: 32 (13 by maintainers)
To recap the state of this issue, and possible workarounds: There is a clean and secure permanent bounded service account support that would solve this issue in Draft PR
Meanwwhile, we tested two possible workarounds:
Workaround 1 is a compromise in security but it’s very easy to do and understand, you be the judge of how it affects your threat model most likely not a good idea.
To test number 2 we followed the steps here: We start with on an EKS 1.21 with cert-manager and internal Vault (done following setup shown by @maelvls here).
The only difference is that we set the
iss
on the/v1/auth/kubernetes
engine tokubernetes/serviceaccount
🔥 ( and nothttps://kubernetes.default.svc.cluster.local
). In EKS 1.21 all static service accounts that are not bounded (meaning not tied to pod and permanent) will have that fixed iss.We then created the issuer with the same exact config in that comment and it showed itself ready (avoiding any iss error). If there is any trouble make sure all the policies and engine -> sa auth role config are binding the
vault-sa
in the last step in comment paying attention tobound_service_account_name
.This validates that we can use existing cert-manager mechanism for Vault issuers with static sa on EKS 1.21. What about all other workloads that might need to talk to vault that will use bounded service accounts?
For those, we tested added a different kubenetes auth engine at a different path
v1/auth/kubernetes-oidc-iss
. we set the iss to the correct cluster oidc endpoint. we then tied a different service accountvault-user
which we tied to a pod, in EKS 1.21 they will automatically be using a bounded service account, we then checked auth to vault works:// Add bounded sa engine:
// authorise a vault-user for a bounded pod sa:
kubectl exec vault-0 -i -- vault write auth/kubernetes-oidc-iss/role/vault-sa bound_service_account_names=vault-user 🔥 bound_service_account_namespaces=default policies=vault-sa ttl=20m
From vault-user pod, we check authentication works successfully using (bounded token):
We double checked the iss of the JWT on the pod has the correct oidc iss, and it was a bounded token with service account name and an expiry.
Of course for workaround 2 having to manage two auth engines is not ideal, and more importantly it would be better to have cert-manager issuers have the more secure bounded sa support through https://github.com/jetstack/cert-manager/pull/4524, this is how to do it until the feature lands and you are able to upgrade.
/retitle Make it possible to give a projected service account token to the Vault Issuer instead of a service account Secret
Hi!
I was able to reproduce the issue! Back in https://github.com/jetstack/cert-manager/issues/4144#issuecomment-884275324, I made a tiny mistake that was setting
iss
tokubernetes/serviceaccount
instead of the issuer I was giving. I get the same error message.For anyone looking at this thread, here is where we are at:
secretRef
field.Idea 1: let cert-manager request the token using the TokenRequest API, similarly as to how you can ask a Pod to mount a projected serviceAccountToken; we could imagine a new field
projectedServiceAccount
:The cert-manager Pod will thus need to be able to request tokens, which we can give permission to with the following role:
Idea 2: a simpler idea could be instead to mount a projected volume with a projection of the
vault-sa
service account into the cert-manager Pod and configure the Issuer with atokenFile
field. The downside is that would have to edit cert-manager’s Deployment spec anytime you want to use a Vault Issuer. It could look like this:FWIW @hawksight, that’s exactly what HashiCorp seem to be suggesting to get Kuberetes auth working - https://learn.hashicorp.com/tutorials/vault/agent-kubernetes?in=vault%2Fauth-methods#kubernetes-1-24-only=
@maelvls - the change you are referring to above is the following?: https://github.com/kubernetes/kubernetes/blob/master/CHANGELOG/CHANGELOG-1.24.md#urgent-upgrade-notes
Specifically:
So in the cert-manager docs we would need to update here to accommodate?
Instead of:
We would have:
kubernetes.io/service-account-token
eg.vault-issuer-token
in the above example.With that, cert-manager will still support vault with service account auth? Just the process for getting that static token is a little different?
tl;dr:
You are correct, with Vault 1.9.0, the OIDC auth method does not work with the “default” JWT token used by cert-manager using the OIDC Discovery URL. As per the ID Token Validation section of the OIDC spec, the “default” JWT token does not abide by the spec for the claims
iss
,sub
,aud
,exp
, andiat
.I wondered if you could still use the JWT auth method using static keys or with the JWKS bundle URI (Kubernetes 1.20+). For example, imagining that your “default” JWT token looks like this:
It would not work either since the following JWT auth configuration (imagining your cluster is on EKS) would require an audience to exist on the “default” JWT token, and Vault requires
bound_audiences
to be set with the role typejwt
:Until we can get bound tokens to work with the Vault issuer in cert-manager, the JWT and OIDC auth methods will not work. The alternative is to use the Kubernetes auth.
Based on Vault docs from here
as a result both
disable_iss_validation
andissuer
parameters will be deprecated, ref.@maelvls In our case, we’re using an OIDC provider to connect IAM users with serviceAccounts, so we can give specific permissions to every pod if necessary, described here.
I assume because we’re using this, EKS automatically configure the necessary parameters for service account volume projection, described here: https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/#service-account-token-volume-projection. It’s a managed solution, so we can’t change the kube-api server parameters, which means, the token volume projection and service account issuer discovery are enabled by default.
To use
iss: kubernetes/serviceaccount
, we should setautomountServiceAccountToken
to false and manually mount the serviceAccount token as asecret
volume to avoid projection. It would work, but require more manual maintenance, changing the mount if the serviceAccount secret name changes or adding manually a secret with a fixed name to an already existing serviceAccount. We don’t want to do that, because multiple pods calling Vault with serviceAccount token auth and this would become a maintenance nightmare quickly. Or we should use 2 separate Vault as @tafkam said, which is cumbersome, I agree.@maelvls other applications (e.g. kubernetes-external-secrets, any microservices you are running) are using the projected tokens by default to authenticate in Vault since k8s 1.21. Configuring iss: kubernetes/serviceaccount would require two different Vault Kubernetes authentication backends for a single cluster. One for legacy serviceaccount tokens, and one for projected tokens since iss: kubernetes/serviceaccount will break anything trying to connect to Vault with a projected token. This is rather cumbersome when managing alot of k8s clusters.
That said, cert-manager seems to be in the minority of apps still using legacy tokens, in regard of the Vault issuer use-case.
Hi,
I have the same issue, using AWS EKS.
EKS version: 1.21 Vault version: 1.5.4 Cert manager version: 1.5.3
First, here’s the relevant configs/tokens:
Vault:
Vault pod mounted (projected) token:
Cert manager pod mounted (projected) token:
An example Issuer:
The ServiceAccount token passed to the issuer (dliver-fluentd-aggregator-issuer-token-rbgp6):
The Issuer status:
So on the pods level, everything is correct, they’re using the projected token, which has the correct “iss”, which is the oidc URL. We have some other services that call Vault through its REST API and they are working correctly too, because they are using the projected token.
The issue is, that the cert-manager Issuer CRD is not able to use projected tokens, just a simple secret token, based on the API reference: https://cert-manager.io/docs/reference/api-docs/#cert-manager.io/v1.VaultKubernetesAuth. As @maelvls said, “By default, the kube-apiserver flag --service-account-issuer is not used to fill in the iss of service account tokens. The default “iss”: “kubernetes/serviceaccount” is always used when mounting the service account.”, so it will never match with the Vault issuer. I didn’t find any documentation for Kubernetes to change the “iss” field for the ServiceAccount token. Based on this, I think it should be handled by cert-manager somehow because currently it just sends the token as is, the related code linked in the first comment by @rumanzo.
@maelvls Can you reproduce the info based on this information? Or do you have any idea how to give a correct, projected token to the Issuer CRD?