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

feat: support sops decryption #1180

Merged
merged 8 commits into from
May 14, 2020
Merged
Show file tree
Hide file tree
Changes from all 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 .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,10 @@ jobs:
# Make GCP Service Account creds available as a file
- run: echo $GCLOUD_SERVICE_KEY > ${HOME}/gcloud-service-key.json
- run: echo 'export GOOGLE_APPLICATION_CREDENTIALS=${HOME}/gcloud-service-key.json' >> $BASH_ENV
# Import test / dev key for SOPS
- run:
command: |
gpg --import --no-tty --batch --yes ./test/fixture-sops/test_pgp_key.asc
mkdir -p logs
run-go-tests --packages "$(go list ./... | grep /test | tr '\n' ' ')" | tee logs/integration.log
- run:
Expand Down
47 changes: 47 additions & 0 deletions config/config_helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,18 @@ package config

import (
"fmt"
"path"
"path/filepath"
"regexp"
"strings"
"unicode/utf8"

"github.com/hashicorp/go-getter"
"github.com/hashicorp/hcl/v2"
tflang "github.com/hashicorp/terraform/lang"
"github.com/zclconf/go-cty/cty"
"github.com/zclconf/go-cty/cty/function"
"go.mozilla.org/sops/v3/decrypt"

"github.com/gruntwork-io/terragrunt/aws_helper"
"github.com/gruntwork-io/terragrunt/errors"
Expand Down Expand Up @@ -109,6 +112,7 @@ func CreateTerragruntEvalContext(
"get_terraform_commands_that_need_locking": wrapStaticValueToStringSliceAsFuncImpl(TERRAFORM_COMMANDS_NEED_LOCKING),
"get_terraform_commands_that_need_input": wrapStaticValueToStringSliceAsFuncImpl(TERRAFORM_COMMANDS_NEED_INPUT),
"get_terraform_commands_that_need_parallelism": wrapStaticValueToStringSliceAsFuncImpl(TERRAFORM_COMMANDS_NEED_PARALLELISM),
"sops_decrypt_file": wrapStringSliceToStringAsFuncImpl(sopsDecryptFile, extensions.Include, terragruntOptions),
}

functions := map[string]function.Function{}
Expand Down Expand Up @@ -490,6 +494,41 @@ func getModulePathFromSourceUrl(sourceUrl string) (string, error) {
return matches[1], nil
}

// decrypts and returns sops encrypted utf-8 yaml or json data as a string
func sopsDecryptFile(params []string, include *IncludeConfig, terragruntOptions *options.TerragruntOptions) (string, error) {
numParams := len(params)

var sourceFile string

if numParams > 0 {
sourceFile = params[0]
}
if numParams != 1 {
return "", errors.WithStackTrace(WrongNumberOfParams{Func: "sops_decrypt_file", Expected: "1", Actual: numParams})
}

var format string
switch ext := path.Ext(sourceFile); ext {
case ".json":
format = "json"
case ".yaml", ".yml":
format = "yaml"
default:
return "", errors.WithStackTrace(InvalidSopsFormat{SourceFilePath: sourceFile})
}

rawData, err := decrypt.File(sourceFile, format)
if err != nil {
return "", errors.WithStackTrace(err)
}

if utf8.Valid(rawData) {
return string(rawData), nil
}

return "", errors.WithStackTrace(InvalidSopsFormat{SourceFilePath: sourceFile})
}

// Custom error types
type WrongNumberOfParams struct {
Func string
Expand Down Expand Up @@ -576,3 +615,11 @@ type ErrorParsingModulePath struct {
func (err ErrorParsingModulePath) Error() string {
return fmt.Sprintf("Unable to obtain the module path from the source URL '%s'. Ensure that the URL is in a supported format.", err.ModuleSourceUrl)
}

type InvalidSopsFormat struct {
SourceFilePath string
}

func (err InvalidSopsFormat) Error() string {
return fmt.Sprintf("File %s is not a valid format or encoding. Terragrunt will only decrypt yaml or json files in UTF-8 encoding.", err.SourceFilePath)
}
49 changes: 47 additions & 2 deletions docs/_docs/04_reference/built-in-functions.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ Terragrunt allows you to use built-in functions anywhere in `terragrunt.hcl`, ju
- [get\_terraform\_commands\_that\_need\_locking()](#get_terraform_commands_that_need_locking)

- [get\_terraform\_commands\_that\_need\_parallelism()](#get_terraform_commands_that_need_parallelism)

- [get\_terraform\_command()](#get_terraform_command)

- [get\_terraform\_cli\_args()](#get_terraform_cli_args)
Expand All @@ -48,6 +48,8 @@ Terragrunt allows you to use built-in functions anywhere in `terragrunt.hcl`, ju

- [read\_terragrunt\_config()](#read_terragrunt_config)

- [sops\_decrypt\_file()](#sops_decrypt_file)

## Terraform built-in functions

All [Terraform built-in functions](https://www.terraform.io/docs/configuration/functions.html) are supported in Terragrunt config files:
Expand Down Expand Up @@ -216,7 +218,7 @@ remote_state {
}
}
```

`get_env(NAME, DEFAULT)` returns the value of the environment variable named `NAME` or `DEFAULT` if that environment variable is not set. Example:

``` hcl
Expand Down Expand Up @@ -538,3 +540,46 @@ inputs = {
vpc_id = local.common_deps.dependency.vpc.outputs.vpc_id
}
```

# sops\_decrypt\_file

`sops_decrypt_file(file_path)` decrypts a yaml or json file encrypted with `sops`.

[sops](https://github.com/mozilla/sops) is an editor of encrypted files that supports YAML, JSON, ENV, INI and
BINARY formats and encrypts with AWS KMS, GCP KMS, Azure Key Vault and PGP.

This allows static secrets to be stored encrypted within your Terragrunt repository.

Only YAML and JSON formats are supported by `sops_decrypt_file`

For example, suppose you have some static secrets required to bootstrap your
infrastructure in `secrets.yaml`, you can decrypt and merge them into the inputs
by using `sops_decrypt_file`:

```hcl
locals {
secret_vars = yamldecode(sops_decrypt_file(find_in_parent_folders("secrets.yaml")))
}

inputs = merge(
local.secret_vars,
{
# additional inputs
}
)
```

If you absolutely need to fallback to a default value you can make use of the Terraform `try` function:

```hcl
locals {
secret_vars = try(jsondecode(sops_decrypt_file(find_in_parent_folders("no-secrets-here.json"))), {})
}

inputs = merge(
local.secret_vars, # This will be {}
{
# additional inputs
}
)
```
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ require (
github.com/stretchr/testify v1.5.1
github.com/urfave/cli v1.22.3
github.com/zclconf/go-cty v1.3.1
go.mozilla.org/sops/v3 v3.5.0
golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e
Expand Down
Loading