Search code examples
bashkubernetesgcloudkubectl

kubectl patch secret with bash gcloud identity-token, heredoc and printf


I want to create a simple k8s cronjob to keep a gcp identity token fresh in a secret.

A relatively simple problem that I am not able to solve

Given

kubectl patch secret token-test --type json -p=<< END
[
   {
     "op": "replace",
     "path": "/data/token.jwt",
     "value": "$(gcloud auth print-identity-token | base64 )"
   }
]
END

I want this to be applied with kubectl patch secret token-test --type json -p=$(printf "'%s'" "$json")

I have tried many variants, the weird thing is that if I paste in the result of the heredoc insted of printf it works. But all my efforts fails with (also tried a json doc on a single line)

$ kubectl patch secret token-test --type json -p=$(printf "'%s'" "$json")
error: unable to parse "'[{": yaml: found unexpected end of stream

Whereas this actually works:

printf "'%s'" "$json"|pbcopy 
kubectl patch secret sudo-token-test --type json -p '[{ "op": "replace","path": "/data/token.jwt","value": "ZX...Zwo="}]'
secret/token-test patched

I cannot understand what is different when it fails. I understand bash is a bit tricky when it comes to string handling, but I am not sure if this is a bash issue or an issue in kubectl.


Solution

  • $ json='[{ "op": "replace","path": "/data/token.jwt","value": "'"$(gcloud auth print-identity-token | base64 )"'"}]'
    $ kubectl patch secret token-test --type json -p="$json"
    secret/token-test patched
    

    By appending the string insted of interpolating in the heredoc this was solved. Still don't know why the other approach failed though.

    EDIT:

    The end result for this was to drop kubectl and use curl instead as it fit better inside the docker image.

    
    # Point to the internal API server hostname
    APISERVER=https://kubernetes.default.svc
    
    # Path to ServiceAccount token
    SERVICEACCOUNT=/var/run/secrets/kubernetes.io/serviceaccount
    
    # Read this Pod's namespace
    NAMESPACE=$(cat ${SERVICEACCOUNT}/namespace)
    
    # Read the ServiceAccount bearer token
    TOKEN=$(cat ${SERVICEACCOUNT}/token)
    
    # Reference the internal certificate authority (CA)
    CACERT=${SERVICEACCOUNT}/ca.crt
    SECRET_NAME=$(cat /etc/scripts/secret-name)
    
    # Explore the API with TOKEN
    curl --fail --cacert ${CACERT} --header "Authorization: Bearer ${TOKEN}" --request PATCH ${APISERVER}/api/v1/namespaces/${NAMESPACE}/secrets/${SECRET_NAME}  \
       -H 'Accept: application/json' \
       -H "Content-Type: application/json-patch+json"  \
       -d '[{ "op": "replace","path": "/data/token.jwt","value": "'"$(gcloud auth print-identity-token | base64 -w0 )"'"}]' \
       --output /dev/null
    

    As commente by me in DazWilkin answer as well, the user that calls gcloud auth print-identity-token is actually a serviceaccount on k8s authenticated via workload identity on GKE to a GCP ServiceAccount.

    I need the token to be able to call AWS without storing the actual credentials by using it to call AWS via Workload Identity Federation