Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding Hashicorp vault support #655

Merged
merged 18 commits into from
May 4, 2020
Merged
Show file tree
Hide file tree
Changes from 19 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
target
Cargo.lock
vendor/
coverage.txt
profile.out
61 changes: 61 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,66 @@ And decrypt it using::
$ sops --decrypt test.enc.yaml


Encrypting using Hashicorp Vault
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

To understand how to deploy Vault securely this is not the place, we assume you have a instance (or more) of Vault running and you have privileged access to it.
vnzongzna marked this conversation as resolved.
Show resolved Hide resolved

To easily deploy Vault locally: (DO NOT DO THIS FOR PRODUCTION!!!)

.. code:: bash

$ docker run -d -p8200:8200 vault:1.2.0 server -dev -dev-root-token-id=toor


.. code:: bash

$ # Subsitute this with the address of the vault running
vnzongzna marked this conversation as resolved.
Show resolved Hide resolved
$ export VAULT_ADDR=http://127.0.0.1:8200

$ # this may not be necessary in case you previously used `vault login` for production use
$ export VAULT_TOKEN=toor

$ # to check if Vault started correctly and configured
vnzongzna marked this conversation as resolved.
Show resolved Hide resolved
$ vault status
Key Value
--- -----
Seal Type shamir
Initialized true
Sealed false
Total Shares 1
Threshold 1
Version 1.2.0
Cluster Name vault-cluster-618cc902
Cluster ID e532e461-e8f0-1352-8a41-fc7c11096908
HA Enabled false

$ # It is required to enable a transit engine if not already done (It is suggested to create a transit engine specifically for sops, in which it is possible to have multiple keys with various permission levels)
$ vault secrets enable -path=sops transit
Success! Enabled thˇe transit secrets engine at: sops/
vnzongzna marked this conversation as resolved.
Show resolved Hide resolved

$ # Then create one or more keys
$ vault write sops/keys/firstkey type=rsa-4096
Success! Data written to: sops/keys/firstkey

$ vault write sops/keys/secondkey type=rsa-2048
Success! Data written to: sops/keys/secondkey

$ vault write sops/keys/thirdkey type=chacha20-poly1305
Success! Data written to: sops/keys/thirdkey

$ sops --hc-vault-transit $VAULT_ADDR/v1/sops/keys/firstkey vault_example.yml

$ cat <<EOF >.sops.yaml
vnzongzna marked this conversation as resolved.
Show resolved Hide resolved
creation_rules:
- path_regex: \.dev\.yaml$
hc_vault_transit_uri: "$VAULT_ADDR/v1/sops/keys/secondkey"
- \.prod\.yaml$
vnzongzna marked this conversation as resolved.
Show resolved Hide resolved
hc_vault_transit_uri: "$VAULT_ADDR/v1/sops/keys/thirdkey"
EOF

$ sops --verbose -e prod/raw.yaml > prod/encrypted.yaml

Adding and removing keys
~~~~~~~~~~~~~~~~~~~~~~~~

Expand Down Expand Up @@ -546,6 +606,7 @@ can manage the three sets of configurations for the three types of files:
- path_regex: \.prod\.yaml$
kms: 'arn:aws:kms:us-west-2:361527076523:key/5052f06a-5d3f-489e-b86c-57201e06f31e+arn:aws:iam::361527076523:role/hiera-sops-prod,arn:aws:kms:eu-central-1:361527076523:key/cb1fab90-8d17-42a1-a9d8-334968904f94+arn:aws:iam::361527076523:role/hiera-sops-prod'
pgp: 'FBC7B9E2A4F9289AC0C1D4843D16CEE4A27381B4'
hc_vault_uris: "http://localhost:8200/v1/sops/keys/thirdkey"

# gcp files using GCP KMS
- path_regex: \.gcp\.yaml$
Expand Down
74 changes: 68 additions & 6 deletions cmd/sops/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import (
"go.mozilla.org/sops/v3/cmd/sops/subcommand/updatekeys"
"go.mozilla.org/sops/v3/config"
"go.mozilla.org/sops/v3/gcpkms"
"go.mozilla.org/sops/v3/hcvault"
"go.mozilla.org/sops/v3/keys"
"go.mozilla.org/sops/v3/keyservice"
"go.mozilla.org/sops/v3/kms"
Expand Down Expand Up @@ -78,6 +79,14 @@ func main() {
(you need to setup google application default credentials. See
https://developers.google.com/identity/protocols/application-default-credentials)


To encrypt or decrypt a document with HashiCorp Vault's Transit Secret Engine, specify the
Vault key URI name in the --hc-vault-transit flag or in the SOPS_VAULT_URIS (eg. https://vault.example.org:8200/v1/transit/keys/dev
vnzongzna marked this conversation as resolved.
Show resolved Hide resolved
where 'https://vault.example.org:8200' is the vault server, 'transit' the enginePath, and 'dev' is the name of the key )
environment variable.
(you need to enable the Transit Secrets Engine in Vault. See
https://www.vaultproject.io/docs/secrets/transit/index.html)

To encrypt or decrypt a document with Azure Key Vault, specify the
Azure Key Vault key URL in the --azure-kv flag or in the SOPS_AZURE_KEYVAULT_URL
environment variable.
Expand All @@ -91,11 +100,11 @@ func main() {
To use multiple KMS or PGP keys, separate them by commas. For example:
$ sops -p "10F2...0A, 85D...B3F21" file.yaml

The -p, -k, --gcp-kms and --azure-kv flags are only used to encrypt new documents. Editing
The -p, -k, --gcp-kms, --hc-vault-transit and --azure-kv flags are only used to encrypt new documents. Editing
or decrypting existing documents can be done with "sops file" or
"sops -d file" respectively. The KMS and PGP keys listed in the encrypted
documents are used then. To manage master keys in existing documents, use
the "add-{kms,pgp,gcp-kms,azure-kv}" and "rm-{kms,pgp,gcp-kms,azure-kv}" flags.
the "add-{kms,pgp,gcp-kms,azure-kv,hc-vault-transit}" and "rm-{kms,pgp,gcp-kms,azure-kv,hc-vault-transit}" flags.

To use a different GPG binary than the one in your PATH, set SOPS_GPG_EXEC.
To use a GPG key server other than gpg.mozilla.org, set SOPS_GPG_KEYSERVER.
Expand Down Expand Up @@ -224,7 +233,7 @@ func main() {
},
cli.BoolFlag{
Name: "recursive",
Usage: "If the source path is a directory, publish all its content recursively",
Usage: "If the source path is a directory, publish all its content recursively",
},
cli.BoolFlag{
Name: "verbose",
Expand Down Expand Up @@ -351,6 +360,10 @@ func main() {
Name: "azure-kv",
Usage: "the Azure Key Vault key URL the new group should contain. Can be specified more than once",
},
cli.StringSliceFlag{
Name: "hc-vault",
vnzongzna marked this conversation as resolved.
Show resolved Hide resolved
Usage: "the full vault path to the key used to encrypt/decrypt. Make you choose and configure a key with encrption/decryption enabled (e.g. 'https://vault.example.org:8200/v1/transit/keys/dev'). Can be specified more than once",
},
cli.BoolFlag{
Name: "in-place, i",
Usage: "write output back to the same file instead of stdout",
Expand All @@ -368,6 +381,7 @@ func main() {
pgpFps := c.StringSlice("pgp")
kmsArns := c.StringSlice("kms")
gcpKmses := c.StringSlice("gcp-kms")
vaultURIs := c.StringSlice("hc-vault")
azkvs := c.StringSlice("azure-kv")
var group sops.KeyGroup
for _, fp := range pgpFps {
Expand All @@ -379,6 +393,14 @@ func main() {
for _, kms := range gcpKmses {
group = append(group, gcpkms.NewMasterKeyFromResourceID(kms))
}
for _, uri := range vaultURIs {
k, err := hcvault.NewMasterKeyFromURI(uri)
if err != nil {
log.WithError(err).Error("Failed to add key")
continue
}
group = append(group, k)
}
for _, url := range azkvs {
k, err := azkv.NewMasterKeyFromURL(url)
if err != nil {
Expand Down Expand Up @@ -501,6 +523,11 @@ func main() {
Usage: "comma separated list of Azure Key Vault URLs",
EnvVar: "SOPS_AZURE_KEYVAULT_URLS",
},
cli.StringFlag{
Name: "hc-vault-transit",
Usage: "comma separated list of vault's key URI (e.g. 'https://vault.example.org:8200/v1/transit/keys/dev')",
EnvVar: "SOPS_VAULT_URIS",
},
cli.StringFlag{
Name: "pgp, p",
Usage: "comma separated list of PGP fingerprints",
Expand Down Expand Up @@ -550,6 +577,14 @@ func main() {
Name: "rm-kms",
Usage: "remove the provided comma-separated list of KMS ARNs from the list of master keys on the given file",
},
cli.StringFlag{
Name: "add-hc-vault-transit",
Usage: "add the provided comma-separated list of Vault's URI key to the list of master keys on the given file ( eg. https://vault.example.org:8200/v1/transit/keys/dev)",
},
cli.StringFlag{
Name: "rm-hc-vault-transit",
Usage: "remove the provided comma-separated list of Vault's URI key from the list of master keys on the given file ( eg. https://vault.example.org:8200/v1/transit/keys/dev)",
},
cli.StringFlag{
Name: "add-pgp",
Usage: "add the provided comma-separated list of PGP fingerprints to the list of master keys on the given file",
Expand Down Expand Up @@ -615,8 +650,8 @@ func main() {
return toExitError(err)
}
if _, err := os.Stat(fileName); os.IsNotExist(err) {
if c.String("add-kms") != "" || c.String("add-pgp") != "" || c.String("add-gcp-kms") != "" || c.String("add-azure-kv") != "" ||
c.String("rm-kms") != "" || c.String("rm-pgp") != "" || c.String("rm-gcp-kms") != "" || c.String("rm-azure-kv") != "" {
if c.String("add-kms") != "" || c.String("add-pgp") != "" || c.String("add-gcp-kms") != "" || c.String("add-hc-vault-transit") != "" || c.String("add-azure-kv") != "" ||
c.String("rm-kms") != "" || c.String("rm-pgp") != "" || c.String("rm-gcp-kms") != "" || c.String("rm-hc-vault-transit") != "" || c.String("rm-azure-kv") != "" {
return common.NewExitError("Error: cannot add or remove keys on non-existent files, use `--kms` and `--pgp` instead.", codes.CannotChangeKeysFromNonExistentFile)
}
if c.Bool("encrypt") || c.Bool("decrypt") || c.Bool("rotate") {
Expand Down Expand Up @@ -729,6 +764,13 @@ func main() {
for _, k := range azureKeys {
addMasterKeys = append(addMasterKeys, k)
}
hcVaultKeys, err := hcvault.NewMasterKeysFromURIs(c.String("add-hc-vault-transit"))
if err != nil {
return err
}
for _, k := range hcVaultKeys {
addMasterKeys = append(addMasterKeys, k)
}

var rmMasterKeys []keys.MasterKey
for _, k := range kms.MasterKeysFromArnString(c.String("rm-kms"), kmsEncryptionContext, c.String("aws-profile")) {
Expand All @@ -747,6 +789,14 @@ func main() {
for _, k := range azureKeys {
rmMasterKeys = append(rmMasterKeys, k)
}
hcVaultKeys, err = hcvault.NewMasterKeysFromURIs(c.String("rm-hc-vault-transit"))
if err != nil {
return err
}
for _, k := range hcVaultKeys {
rmMasterKeys = append(rmMasterKeys, k)
}

output, err = rotate(rotateOpts{
OutputStore: outputStore,
InputStore: inputStore,
Expand Down Expand Up @@ -938,6 +988,7 @@ func keyGroups(c *cli.Context, file string) ([]sops.KeyGroup, error) {
var pgpKeys []keys.MasterKey
var cloudKmsKeys []keys.MasterKey
var azkvKeys []keys.MasterKey
var hcVaultMkKeys []keys.MasterKey
kmsEncryptionContext := kms.ParseKMSContext(c.String("encryption-context"))
if c.String("encryption-context") != "" && kmsEncryptionContext == nil {
return nil, common.NewExitError("Invalid KMS encryption context format", codes.ErrorInvalidKMSEncryptionContextFormat)
Expand All @@ -961,12 +1012,21 @@ func keyGroups(c *cli.Context, file string) ([]sops.KeyGroup, error) {
azkvKeys = append(azkvKeys, k)
}
}
if c.String("hc-vault-transit") != "" {
hcVaultKeys, err := hcvault.NewMasterKeysFromURIs(c.String("hc-vault-transit"))
if err != nil {
return nil, err
}
for _, k := range hcVaultKeys {
hcVaultMkKeys = append(hcVaultMkKeys, k)
}
}
if c.String("pgp") != "" {
for _, k := range pgp.MasterKeysFromFingerprintString(c.String("pgp")) {
pgpKeys = append(pgpKeys, k)
}
}
if c.String("kms") == "" && c.String("pgp") == "" && c.String("gcp-kms") == "" && c.String("azure-kv") == "" {
if c.String("kms") == "" && c.String("pgp") == "" && c.String("gcp-kms") == "" && c.String("azure-kv") == "" && c.String("hc-vault-transit") == "" {
conf, err := loadConfig(c, file, kmsEncryptionContext)
// config file might just not be supplied, without any error
if conf == nil {
Expand All @@ -983,6 +1043,8 @@ func keyGroups(c *cli.Context, file string) ([]sops.KeyGroup, error) {
group = append(group, cloudKmsKeys...)
group = append(group, azkvKeys...)
group = append(group, pgpKeys...)
group = append(group, hcVaultMkKeys...)
log.Infof("Master keys available: %+v", group)
vnzongzna marked this conversation as resolved.
Show resolved Hide resolved
return []sops.KeyGroup{group}, nil
}

Expand Down
25 changes: 24 additions & 1 deletion config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (
"go.mozilla.org/sops/v3"
"go.mozilla.org/sops/v3/azkv"
"go.mozilla.org/sops/v3/gcpkms"
"go.mozilla.org/sops/v3/hcvault"
"go.mozilla.org/sops/v3/kms"
"go.mozilla.org/sops/v3/logging"
"go.mozilla.org/sops/v3/pgp"
Expand Down Expand Up @@ -69,6 +70,7 @@ type keyGroup struct {
KMS []kmsKey
GCPKMS []gcpKmsKey `yaml:"gcp_kms"`
AzureKV []azureKVKey `yaml:"azure_keyvault"`
Vault []vaultKey `yaml:"hc_vault"`
PGP []string
}

Expand All @@ -89,6 +91,12 @@ type azureKVKey struct {
Version string `yaml:"version"`
}

type vaultKey struct {
VaultAddress string `yaml:"vault_address"`
EnginePath string `yaml:"engine_path"`
KeyName string `yaml:"key_name"`
}

type destinationRule struct {
PathRegex string `yaml:"path_regex"`
S3Bucket string `yaml:"s3_bucket"`
Expand All @@ -110,6 +118,8 @@ type creationRule struct {
PGP string
GCPKMS string `yaml:"gcp_kms"`
AzureKeyVault string `yaml:"azure_keyvault"`
VaultURI string `yaml:"hc_vault_transit_uri"`
Vault []vaultKey `yaml:"hc_vault_transit"`
vnzongzna marked this conversation as resolved.
Show resolved Hide resolved
KeyGroups []keyGroup `yaml:"key_groups"`
ShamirThreshold int `yaml:"shamir_threshold"`
UnencryptedSuffix string `yaml:"unencrypted_suffix"`
Expand Down Expand Up @@ -154,6 +164,9 @@ func getKeyGroupsFromCreationRule(cRule *creationRule, kmsEncryptionContext map[
for _, k := range group.AzureKV {
keyGroup = append(keyGroup, azkv.NewMasterKey(k.VaultURL, k.Key, k.Version))
}
for _, k := range group.Vault {
keyGroup = append(keyGroup, hcvault.NewMasterKey(k.VaultAddress, k.EnginePath, k.KeyName))
}
groups = append(groups, keyGroup)
}
} else {
Expand All @@ -174,6 +187,16 @@ func getKeyGroupsFromCreationRule(cRule *creationRule, kmsEncryptionContext map[
for _, k := range azureKeys {
keyGroup = append(keyGroup, k)
}
vaultKeys, err := hcvault.NewMasterKeysFromURIs(cRule.VaultURI)
if err != nil {
return nil, err
}
for _, k := range vaultKeys {
keyGroup = append(keyGroup, k)
}
for _, k := range cRule.Vault {
keyGroup = append(keyGroup, hcvault.NewMasterKey(k.VaultAddress, k.EnginePath, k.KeyName))
}
groups = append(groups, keyGroup)
}
return groups, nil
Expand Down Expand Up @@ -250,7 +273,7 @@ func parseDestinationRuleForFile(conf *configFile, filePath string, kmsEncryptio
var dest publish.Destination
if dRule != nil {
if dRule.S3Bucket != "" && dRule.GCSBucket != "" && dRule.VaultPath != "" {
return nil, fmt.Errorf("error loading config: more than one destinations were found in a single destination rule, you can only use one per rule.")
return nil, fmt.Errorf("error loading config: more than one destinations were found in a single destination rule, you can only use one per rule")
}
if dRule.S3Bucket != "" {
dest = publish.NewS3Destination(dRule.S3Bucket, dRule.S3Prefix)
Expand Down
Loading