Extracting secrets from AWS-Vault

Recently I was in the situation in which I needed to do some automation involving AWS-Vault, which is a tool for securely managing AWS access keys on developer workstations. Until now I had been using the file backend, which relies on encrypted-at-rest files to store the secrets.

Since there is obviously no caching involved here, every time I wanted to use a secret I would need to type in the passphrase to unlock it, with no cached copy of the passphrase involved. Coupled with a bug that my team faces when applying Terraform (sessions cause remote state S3 bucket reads to fail, worked around with the --no-session option), there was no way that I could create a loop for it.

The solution for this was to move to a different backend. Since I run Linux on my workstation, that meant moving to the secret-service backend, which in my case is backed by gnome-keyring-daemon.

Researching this yielded no solution for how to migrate from one backend to another. There was nothing in a contrib/ directory or any blog posts, so instead I had a dig into the source code. This showed that the AWS-Vault key files (located in $HOME/.awsvault/keys were a base64-encoded blob, that when decoded revealed some JSON content from a standard called JOSE (Javascript Object Signing and Encryption). Specifically, I learned that they contained JWK (Javascript Web Key) attributes, although just trying to decoded it yielded encoding errors:

$ cat .awsvault/keys/mykey |base64 -d
{"alg":"PBES2-HS256+A128KW","created":"2019-12-04 09:33:34.461409668 -0800 PST m=+9.768812157","enc":"A256GCM","p2c":8192,"p2s":"FFFFFFFFFFF"}base64: invalid input code

Perfect, from here on out I can just find a good JOSE library in my language of choice, load it up, and print the payload" was what I thought. This didn’t end up working though. I wasn’t able to figure out a workable method using python-jose, try as I might. My next effort was simply replicate the libraries and methods from the original file in AWS-Vault’s keyring library. This actually ended up working. The final code for this is as follows:

import (
jose "github.com/dvsekhvalnov/jose2go"
"fmt"
"io/ioutil"
"os"
"encoding/json"
)

type Item struct {
Key         string
Data        ']byte
Label       string
Description string

// Backend specific config
KeychainNotTrustApplication bool
KeychainNotSynchronizable   bool
}

type Key struct {
AccessKeyID         string
SecretAccessKey     string
SessionToken        string
}

func main() {
if len(os.Args) != 3 {
 fmt.Println("Wrong input format, expected: ./aws-vault-decrypt <key_file> <password>")
 os.Exit(1)
}

bytes, err := ioutil.ReadFile(os.Args'1])
if os.IsNotExist(err) {
  fmt.Println("File not found")
}
if err != nil {
  fmt.Printf("Error: %s", err)
}

payload, _, err := jose.Decode(string(bytes), os.Args'2])
//fmt.Printf(payload)
var decoded = Item{}
err = json.Unmarshal(']byte(payload), &decoded)
var data = Key{}
err = json.Unmarshal(']byte(decoded.Data), &data)
fmt.Printf("AWS_ACCESS_KEY_ID: %s\n", string(data.AccessKeyID))
fmt.Printf("AWS_SECRET_ACCESS_KEY: %s\n", string(data.SecretAccessKey))
}

This resulted in good output:

$ ./aws-vault-decrypt /home/bkero/.awsvault/keys/mykey 'mypassword'
AWS_ACCESS_KEY_ID: AKIAXXXXXXXXXXXXXXXX
AWS_SECRET_ACCESS_KEY: XXXXXXX/XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

From here it was simply a matter of setting my new backend in AWS-Vault and running aws-vault add:

$ export AWS_VAULT_BACKEND=secret-service
$ aws-vault add mykey
Enter Access Key ID: AKIAXXXXXXXXXXXXXXXX
Enter Secret Access Key: XXXXXXX/XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
Added credentials to profile "mykey" in vault

Note that you’ll want to add that export AWS_VAULT_BACKEND=secret-service line to your $HOME/.bashrc if you want the change to be permanent.