Port encrypted config shared bits to a separate PR (#9037)

* Port encrypted config shared bits to a separate PR

* Address feedback
This commit is contained in:
Jeff Mitchell
2020-05-19 18:15:30 -04:00
committed by GitHub
parent 57d18c95b0
commit 14615acda4
6 changed files with 259 additions and 11 deletions

2
go.mod
View File

@@ -45,7 +45,7 @@ require (
github.com/go-sql-driver/mysql v1.5.0
github.com/go-test/deep v1.0.2
github.com/gocql/gocql v0.0.0-20190402132108-0e1d5de854df
github.com/gogo/protobuf v1.3.1 // indirect
github.com/gogo/protobuf v1.3.1
github.com/golang/protobuf v1.4.1
github.com/google/go-github v17.0.0+incompatible
github.com/google/go-metrics-stackdriver v0.2.0

2
go.sum
View File

@@ -508,8 +508,6 @@ github.com/hashicorp/vault-plugin-secrets-mongodbatlas v0.1.2 h1:X9eK6NSb1qafvoE
github.com/hashicorp/vault-plugin-secrets-mongodbatlas v0.1.2/go.mod h1:YRW9zn9NZNitRlPYNAWRp/YEdKCF/X8aOg8IYSxFT5Y=
github.com/hashicorp/vault-plugin-secrets-openldap v0.1.0-beta1.0.20200515114020-e19ec0ccabc3 h1:7SLyjXdy1QwQgIb2XU39pr0J0W0fZxKq/pm8M3pv05Y=
github.com/hashicorp/vault-plugin-secrets-openldap v0.1.0-beta1.0.20200515114020-e19ec0ccabc3/go.mod h1:mfMeH+oOuVMgJVQahScA7ic+q8HfzHTocE3xJhmk4Co=
github.com/hashicorp/vault-plugin-secrets-openldap v0.1.2 h1:618nyNUHX2Oc7pcQh6r0Zm0kaMrhkfAyUyFmDFwyYnQ=
github.com/hashicorp/vault-plugin-secrets-openldap v0.1.2/go.mod h1:9Cy4Jp779BjuIOhYLjEfH3M3QCUxZgPnvJ3tAOOmof4=
github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM=
github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d h1:kJCB4vdITiW1eC1vq2e6IsrXKrZit1bv/TDYFGMp4BQ=
github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM=

View File

@@ -49,6 +49,15 @@ func LoadConfigFile(path string) (*SharedConfig, error) {
return ParseConfig(string(d))
}
func LoadConfigKMSes(path string) ([]*KMS, error) {
// Read the file
d, err := ioutil.ReadFile(path)
if err != nil {
return nil, err
}
return ParseKMSes(string(d))
}
func ParseConfig(d string) (*SharedConfig, error) {
// Parse!
obj, err := hcl.Parse(d)
@@ -82,19 +91,19 @@ func ParseConfig(d string) (*SharedConfig, error) {
}
if o := list.Filter("hsm"); len(o.Items) > 0 {
if err := parseKMS(&result, o, "hsm", 1); err != nil {
if err := parseKMS(&result.Seals, o, "hsm", 2); err != nil {
return nil, errwrap.Wrapf("error parsing 'hsm': {{err}}", err)
}
}
if o := list.Filter("seal"); len(o.Items) > 0 {
if err := parseKMS(&result, o, "seal", 2); err != nil {
if err := parseKMS(&result.Seals, o, "seal", 3); err != nil {
return nil, errwrap.Wrapf("error parsing 'seal': {{err}}", err)
}
}
if o := list.Filter("kms"); len(o.Items) > 0 {
if err := parseKMS(&result, o, "kms", 2); err != nil {
if err := parseKMS(&result.Seals, o, "kms", 3); err != nil {
return nil, errwrap.Wrapf("error parsing 'kms': {{err}}", err)
}
}

View File

@@ -0,0 +1,92 @@
package configutil
import (
"bytes"
"context"
"encoding/base64"
"errors"
"fmt"
"regexp"
wrapping "github.com/hashicorp/go-kms-wrapping"
"google.golang.org/protobuf/proto"
)
var (
encryptRegex = regexp.MustCompile(`{{encrypt\(.*\)}}`)
decryptRegex = regexp.MustCompile(`{{decrypt\(.*\)}}`)
)
func EncryptDecrypt(rawStr string, decrypt, strip bool, wrapper wrapping.Wrapper) (string, error) {
var locs [][]int
raw := []byte(rawStr)
searchVal := "{{encrypt("
replaceVal := "{{decrypt("
suffixVal := ")}}"
if decrypt {
searchVal = "{{decrypt("
replaceVal = "{{encrypt("
locs = decryptRegex.FindAllIndex(raw, -1)
} else {
locs = encryptRegex.FindAllIndex(raw, -1)
}
if strip {
replaceVal = ""
suffixVal = ""
}
out := make([]byte, 0, len(rawStr)*2)
var prevMaxLoc int
for _, match := range locs {
if len(match) != 2 {
return "", fmt.Errorf("expected two values for match, got %d", len(match))
}
// Append everything from the end of the last match to the beginning of this one
out = append(out, raw[prevMaxLoc:match[0]]...)
// Transform. First pull off the suffix/prefix
matchBytes := raw[match[0]:match[1]]
matchBytes = bytes.TrimSuffix(bytes.TrimPrefix(matchBytes, []byte(searchVal)), []byte(")}}"))
var finalVal string
// Now encrypt or decrypt
switch decrypt {
case false:
outBlob, err := wrapper.Encrypt(context.Background(), matchBytes, nil)
if err != nil {
return "", fmt.Errorf("error encrypting parameter: %w", err)
}
if outBlob == nil {
return "", errors.New("nil value returned from encrypting parameter")
}
outMsg, err := proto.Marshal(outBlob)
if err != nil {
return "", fmt.Errorf("error marshaling encrypted parameter: %w", err)
}
finalVal = base64.RawURLEncoding.EncodeToString(outMsg)
default:
inMsg, err := base64.RawURLEncoding.DecodeString(string(matchBytes))
if err != nil {
return "", fmt.Errorf("error decoding encrypted parameter: %w", err)
}
inBlob := new(wrapping.EncryptedBlobInfo)
if err := proto.Unmarshal(inMsg, inBlob); err != nil {
return "", fmt.Errorf("error unmarshaling encrypted parameter: %w", err)
}
dec, err := wrapper.Decrypt(context.Background(), inBlob, nil)
if err != nil {
return "", fmt.Errorf("error decrypting encrypted parameter: %w", err)
}
finalVal = string(dec)
}
// Append new value
out = append(out, []byte(fmt.Sprintf("%s%s%s", replaceVal, finalVal, suffixVal))...)
prevMaxLoc = match[1]
}
// At the end, append the rest
out = append(out, raw[prevMaxLoc:len(raw)]...)
return string(out), nil
}

View File

@@ -0,0 +1,111 @@
package configutil
import (
"bytes"
"context"
"encoding/base64"
"testing"
wrapping "github.com/hashicorp/go-kms-wrapping"
"google.golang.org/protobuf/proto"
)
func getAEADTestKMS(t *testing.T) {
}
func TestEncryptParams(t *testing.T) {
rawStr := `
storage "consul" {
api_key = "{{encrypt(foobar)}}"
}
telemetry {
some_param = "something"
circonus_api_key = "{{encrypt(barfoo)}}"
}
`
finalStr := `
storage "consul" {
api_key = "foobar"
}
telemetry {
some_param = "something"
circonus_api_key = "barfoo"
}
`
reverser := new(reversingWrapper)
out, err := EncryptDecrypt(rawStr, false, false, reverser)
if err != nil {
t.Fatal(err)
}
first := true
locs := decryptRegex.FindAllIndex([]byte(out), -1)
for _, match := range locs {
matchBytes := []byte(out)[match[0]:match[1]]
matchBytes = bytes.TrimSuffix(bytes.TrimPrefix(matchBytes, []byte("{{decrypt(")), []byte(")}}"))
inMsg, err := base64.RawURLEncoding.DecodeString(string(matchBytes))
if err != nil {
t.Fatal(err)
}
inBlob := new(wrapping.EncryptedBlobInfo)
if err := proto.Unmarshal(inMsg, inBlob); err != nil {
t.Fatal(err)
}
ct := string(inBlob.Ciphertext)
if first {
if ct != "raboof" {
t.Fatal(ct)
}
first = false
} else {
if ct != "oofrab" {
t.Fatal(ct)
}
}
}
decOut, err := EncryptDecrypt(out, true, false, reverser)
if err != nil {
t.Fatal(err)
}
if decOut != rawStr {
t.Fatal(decOut)
}
decOut, err = EncryptDecrypt(out, true, true, reverser)
if err != nil {
t.Fatal(err)
}
if decOut != finalStr {
t.Fatal(decOut)
}
}
type reversingWrapper struct{}
func (r *reversingWrapper) Type() string { return "reversing" }
func (r *reversingWrapper) KeyID() string { return "reverser" }
func (r *reversingWrapper) HMACKeyID() string { return "" }
func (r *reversingWrapper) Init(_ context.Context) error { return nil }
func (r *reversingWrapper) Finalize(_ context.Context) error { return nil }
func (r *reversingWrapper) Encrypt(_ context.Context, input []byte, _ []byte) (*wrapping.EncryptedBlobInfo, error) {
return &wrapping.EncryptedBlobInfo{
Ciphertext: r.reverse(input),
}, nil
}
func (r *reversingWrapper) Decrypt(_ context.Context, input *wrapping.EncryptedBlobInfo, _ []byte) ([]byte, error) {
return r.reverse(input.Ciphertext), nil
}
func (r *reversingWrapper) reverse(input []byte) []byte {
output := make([]byte, len(input))
for i, j := 0, len(input)-1; i < j; i, j = i+1, j-1 {
output[i], output[j] = input[j], input[i]
}
return output
}

View File

@@ -56,7 +56,7 @@ func (k *KMS) GoString() string {
return fmt.Sprintf("*%#v", *k)
}
func parseKMS(result *SharedConfig, list *ast.ObjectList, blockName string, maxKMS int) error {
func parseKMS(result *[]*KMS, list *ast.ObjectList, blockName string, maxKMS int) error {
if len(list.Items) > maxKMS {
return fmt.Errorf("only two or less %q blocks are permitted", blockName)
}
@@ -117,11 +117,47 @@ func parseKMS(result *SharedConfig, list *ast.ObjectList, blockName string, maxK
seals = append(seals, seal)
}
result.Seals = append(result.Seals, seals...)
*result = append(*result, seals...)
return nil
}
func ParseKMSes(d string) ([]*KMS, error) {
// Parse!
obj, err := hcl.Parse(d)
if err != nil {
return nil, err
}
// Start building the result
var result struct {
Seals []*KMS `hcl:"-"`
}
if err := hcl.DecodeObject(&result, obj); err != nil {
return nil, err
}
list, ok := obj.Node.(*ast.ObjectList)
if !ok {
return nil, fmt.Errorf("error parsing: file doesn't contain a root object")
}
if o := list.Filter("seal"); len(o.Items) > 0 {
if err := parseKMS(&result.Seals, o, "seal", 3); err != nil {
return nil, errwrap.Wrapf("error parsing 'seal': {{err}}", err)
}
}
if o := list.Filter("kms"); len(o.Items) > 0 {
if err := parseKMS(&result.Seals, o, "kms", 3); err != nil {
return nil, errwrap.Wrapf("error parsing 'kms': {{err}}", err)
}
}
return result.Seals, nil
}
func configureWrapper(configKMS *KMS, infoKeys *[]string, info *map[string]string, logger hclog.Logger) (wrapping.Wrapper, error) {
var wrapper wrapping.Wrapper
var kmsInfo map[string]string
@@ -167,9 +203,11 @@ func configureWrapper(configKMS *KMS, infoKeys *[]string, info *map[string]strin
return nil, err
}
for k, v := range kmsInfo {
*infoKeys = append(*infoKeys, k)
(*info)[k] = v
if infoKeys != nil && info != nil {
for k, v := range kmsInfo {
*infoKeys = append(*infoKeys, k)
(*info)[k] = v
}
}
return wrapper, nil