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.