Using base64Decode in a Nomad jobspec template

I am trying to decode a base64 encoded string that Vault gives me in a Nomad jobspec template. I have seen the base64Decode function used in several different ways in different examples. What I have here seems to be the proper way to use it.

template {
  change_mode = "noop"
  env         = true
  destination = "${NOMAD_SECRETS_DIR}/gcp.env"
  data        = <<-EOT
    {{- $gcp_credentials  := secret "gcp/static-account/storage-role/key" -}}

    GCP_JSON_DATA={{ $gcp_credentials.Data.private_key_data | base64Decode }}

  EOT
}

However, when I run the job, Nomad complains with the following message:

Template failed: (dynamic): execute: template: :24:64: 
  executing "" at <base64Decode>: invalid value; expected string

If I try the function with this syntax:

GCS_SERVICE_ACCOUNT={{ base64Decode($gcp_credentials.Data.private_key_data) }}

It also does not like it:

Template failed: (dynamic): parse: template: :24: 
  unexpected "(" in operand

And this:

GCS_SERVICE_ACCOUNT={{ base64Decode $gcp_credentials.Data.private_key_data }}

Returns this:

Template failed: (dynamic): execute: template: :24:52: 
  executing "" at <$gcp_credentials.Data.private_key_data>: invalid value; expected string

And:

GCS_SERVICE_ACCOUNT={{ base64Decode "$gcp_credentials.Data.private_key_data" }}

Returns:

Template failed: (dynamic): execute: template: :24:23: 
  executing "" at <base64Decode "$gcp_credentials.Data.private_key_data">: 
  error calling base64Decode: base64Decode: 
    illegal base64 data at input byte 0

And:

GCS_SERVICE_ACCOUNT="{{ "$gcp_credentials.Data.private_key_data" | base64Decode }}"

Returns:

Template failed: (dynamic): execute: template: :24:67: 
  executing "" at <base64Decode>: 
  error calling base64Decode: base64Decode: 
    illegal base64 data at input byte 0

Obviously I am holding it wrong.

Can anyone tell me the correct way to use the base64Decode function in the context of a Nomad jobspec template?

Hi @SunSparc , the docs for base64Decode suggest one of your attempts should have worked - the one where it looks like the key itself may not be a valid base64 encoded string; can you double check that?

Yes, something must be invalid. But I take the raw value that is returned:

GCS_SERVICE_ACCOUNT="{{ $gcp_credentials.Data.private_key_data }}"

outputs base64 string, such as: aSdF1g2<snip-3170-characters>q1w2e3r4==

I run that string into base64 -d on my computer and it returns some lovely and valid json text.

As a test I did this:

{{- $gcp_creds := "aGVsbG8=" -}}
GCS_SERVICE_ACCOUNT="{{ base64Decode "$gcp_creds" }}"

This happily returned the same error:

Template failed: (dynamic): execute: template: :25:24: 
  executing "" at <base64Decode "$gcp_creds">: error calling base64Decode: 
    base64Decode: illegal base64 data at input byte 0

Hmm, well I can demonstrate the consul-template bits work as expected on the Nomad side with the job file below. Could you try rendering the credentials by pasting the base64 text into the meta.password field?

job "eg" {
  datacenters = ["dc1"]
  type = "batch"
  
  meta {
    password = "TXlQNHNzdzByZA=="
  }

  group "cache" {
    task "cat" {
      driver = "exec"

      config {
        command = "cat"
        args = ["local/out.txt"]
      }
      
      template {
        destination = "local/out.txt"
        change_mode = "noop"
        env = true
        data = <<EOH
        {{- $creds := env "NOMAD_META_password" -}}
        PASS={{- base64Decode $creds -}}
EOH
      }

    }
  }
}

@seth.hoenig, I copied your example into my template and it worked, of course.

Using your example gave me some ideas. I played around with them for a bit and finally came up with something that worked. Thank you for your help.

One of the things that was throwing me off is the way that env templates require the contents to be done in a very specific way. That trickiness coupled with the fact that GCP credential data is delivered as base64 and then unpacked becomes a JSON file filled with all sorts of characters that are well known for causing problems.

I decided to try putting the decoded contents into a separate non-env template file, and it worked a charm:

      template {
        change_mode = "noop"
        env = false
        destination = "${NOMAD_SECRETS_DIR}/gcp.env"
        data = <<-EOH
          {{- with secret "gcp/static-account/storage-role/key" -}}
          {{ base64Decode .Data.private_key_data }}
          {{- end }}
        EOH
      }

However, that still did not get the credentials into the environment the way I needed. As it turns out, I needed to also use the toJSON function:

template {
  change_mode = "noop"
  env         = true
  destination = "${NOMAD_SECRETS_DIR}/gcp.env"
  data        = <<-EOT
    {{- with secret "gcp/static-account/storage-role/key" -}}
    GCP_JSON_DATA={{ base64Decode Data.private_key_data | toJSON }}
    {{- end }}
  EOT
}

Now things are working. Thanks again!