Moving paths in Vault with Go

In the past I used Vault to store secrets, this could be anything from credentials, VPN details to provider’s information. Besides the configuration management system, in our case Chef, required access during the provisioning phase.

In more than one occasion, I wound up having to replicate a path for a new environment. On a few occasions, a team would request secrets for their application but decided last minute to rename path. This how I came up with vault-mv.

This tool helps to copy or move secrets in Vault. I did not mention it, but it assumes you have a VAULT_TOKEN in your environment. The token should be created with the right policies otherwise you may encounter issues both reading and writing the path.

Vault testing environment

The easiest way was to create a vault server in development mode. This keeps everything in memory, therefore every time you restart the server, you have to repeat the steps below:

$ vault sever -dev
$ export VAULT_ADDR="http://localhost:8200"
$ vault operator unseal
$ vault login
$ vault secrets disable secret
$ vault secrets enable -path=/secret generic

Implementing a generic engine gives you a more friendly CLI interface when interacting with Vault.

$ vault write secret/newPath
$ vault read secret/newPath
$ vault delete secret/newPath

Copy secrets in Vault

When I first started working on this idea, I realised pretty quickly this would require recursion. I need to read a source path in Vault and copy its contents to a new location. This is the default behavior of vault-mv. On some occasions we might need to destroy the source path, for that reason a flag is provided.

The interesting part is to walk a Vault path and check if there are more secrets, in which case we have to call again the walkVaultPath function. In order to decide if the source path is kept or deleted a boolean is passed.

func walkVaultPath(vaultPath, destPath string, preserveDataSource bool, logical *api.Logical) {
	secret, _ := logical.List(vaultPath)
	if secret == nil {

		switch preserveDataSource {
		case false:
			fmt.Printf("moving %s to %s\n", vaultPath, destPath)
		default:
			fmt.Printf("copying %s to %s\n", vaultPath, destPath)
		}
		secret, err := logical.Read(vaultPath)
		if err != nil {
			logrus.Fatal(err)
		}

		secret, err = logical.Write(destPath, secret.Data)
		if err != nil {
			logrus.Fatal(err)
		}

		if !preserveDataSource {
			_, err := logical.Delete(vaultPath)
			if err != nil {
				log.Fatal(err)
			}
		}
		return
	}

	n := secret.Data["keys"]
	for _, key := range n.([]interface{}) {
		newPath := filepath.Join(vaultPath, key.(string))
		dPath := filepath.Join(destPath, key.(string))
		walkVaultPath(newPath, dPath, preserveDataSource, logical)
	}
}

If you want to read is available on github

Example

If the preserve data flag is set to false this will delete the soure path.

$ vault-mv  --p=false  secret/stg secret/testing
moving secret/stg/app/provider/aws to secret/testing/app/provider/aws
moving secret/stg/app/provider/gcp to secret/testing/app/provider/gcp
moving secret/stg/password to secret/testing/password
moving secret/stg/redis_url to secret/testing/redis_url
moving secret/stg/username to secret/testing/username

I’m glad that I finally found the time to sit and work on this. Now I can copy/move paths without the hassle that was involved before.