Skip to content

Commit 41e9e5d

Browse files
author
Silas Davis
authored
Merge pull request #86 from gregdhill/grant-version
increment hoard version in grant to preserve backwards compatability
2 parents effc361 + da892da commit 41e9e5d

14 files changed

+939
-682
lines changed

CHANGELOG.md

+6
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
11
# [Monax Hoard](https://github.com/monax/hoard) Changelog
2+
## [7.2.0] - 2019-12-17
3+
### Fixed
4+
- Symmetric grants are now versioned to preserve backwards compatability
5+
6+
27
## [7.1.0] - 2019-12-12
38
### Changed
49
- [CMD] Secret keys can now be loaded from the environment
@@ -153,6 +158,7 @@ This is the first Hoard open source release and includes:
153158
- Hoar-Daemon hoard
154159
- Hoar-Control hoarctl CLI
155160

161+
[7.2.0]: https://github.com/monax/hoard/compare/v7.1.0...v7.2.0
156162
[7.1.0]: https://github.com/monax/hoard/compare/v7.0.0...v7.1.0
157163
[7.0.0]: https://github.com/monax/hoard/compare/v6.0.0...v7.0.0
158164
[6.0.0]: https://github.com/monax/hoard/compare/v5.1.0...v6.0.0

NOTES.md

+1-4
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,3 @@
1-
### Changed
2-
- [CMD] Secret keys can now be loaded from the environment
3-
41
### Fixed
5-
- [ENCRYPTION] Secret keys are no longer derived at runtime due to high scrypt memory overhead
2+
- Symmetric grants are now versioned to preserve backwards compatability
63

cmd/hoard/config.go

+3-3
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import (
44
"fmt"
55
"strings"
66

7-
b64 "encoding/base64"
87
"github.com/cep21/xdgbasedir"
98
cli "github.com/jawher/mow.cli"
109
"github.com/monax/hoard/v7/config"
@@ -49,7 +48,7 @@ func Config(cmd *cli.Cmd) {
4948
conf.ChunkSize = *chunkSizeOpt
5049
if len(*secretsOpt) > 0 {
5150
conf.Secrets = &config.Secrets{
52-
Symmetric: make([]config.SymmetricSecret, len(*secretsOpt)),
51+
Symmetric: make([]*config.SymmetricSecret, len(*secretsOpt)),
5352
}
5453
for i, ss := range *secretsOpt {
5554
pair := strings.Split(ss, ":")
@@ -66,8 +65,9 @@ func Config(cmd *cli.Cmd) {
6665
fatalf("could not derive secret key for config: %v", err)
6766
}
6867

68+
conf.Secrets.Symmetric[i] = new(config.SymmetricSecret)
6969
conf.Secrets.Symmetric[i].PublicID = pair[0]
70-
conf.Secrets.Symmetric[i].SecretKey = b64.StdEncoding.EncodeToString(data)
70+
conf.Secrets.Symmetric[i].SecretKey = data
7171
}
7272
}
7373
}

config/secrets.go

+64-15
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,62 @@ import (
1111
// Symmetric secrets are those local to the running daemon
1212
// and OpenPGP identifies an entity in the given keyring
1313
type Secrets struct {
14-
Symmetric []SymmetricSecret
14+
Symmetric []*SymmetricSecret
1515
OpenPGP *OpenPGPSecret
1616
}
1717

1818
type SymmetricSecret struct {
1919
// An identifier for this secret that will be stored in the clear with the grant
20-
PublicID string
21-
SecretKey string
20+
PublicID string
21+
// We expect this to be base64 encoded
22+
SecretKey SecretKey
23+
// Needed for backwards compatability
24+
Passphrase string
25+
}
26+
27+
// SecretKey allows us to encode yaml and toml as base64
28+
type SecretKey []byte
29+
30+
// MarshalText should fulfil most serialization interfaces to ensure that the
31+
// secret key in the config is always base64 encoded
32+
func (sec SecretKey) MarshalText() ([]byte, error) {
33+
data := b64.StdEncoding.EncodeToString(sec)
34+
return []byte(data), nil
35+
}
36+
37+
func (sec *SymmetricSecret) UnmarshalTOML(in interface{}) error {
38+
if sec == nil {
39+
sec = new(SymmetricSecret)
40+
}
41+
42+
data, _ := in.(map[string]interface{})
43+
sec.PublicID, _ = data["PublicID"].(string)
44+
sec.Passphrase, _ = data["Passphrase"].(string)
45+
46+
secret, _ := data["SecretKey"].(string)
47+
key, err := b64.StdEncoding.DecodeString(secret)
48+
sec.SecretKey = key
49+
return err
50+
}
51+
52+
func (sec *SymmetricSecret) UnmarshalYAML(unmarshal func(interface{}) error) error {
53+
if sec == nil {
54+
sec = new(SymmetricSecret)
55+
}
56+
57+
secret := &struct {
58+
PublicID string
59+
SecretKey string
60+
Passphrase string
61+
}{}
62+
if err := unmarshal(secret); err != nil {
63+
return err
64+
}
65+
sec.PublicID = secret.PublicID
66+
sec.Passphrase = secret.Passphrase
67+
key, err := b64.StdEncoding.DecodeString(secret.SecretKey)
68+
sec.SecretKey = key
69+
return err
2270
}
2371

2472
type OpenPGPSecret struct {
@@ -34,7 +82,7 @@ type SecretsManager struct {
3482
OpenPGP *OpenPGPSecret
3583
}
3684

37-
type SymmetricProvider func(secretID string) ([]byte, error)
85+
type SymmetricProvider func(secretID string) (SymmetricSecret, error)
3886

3987
// NoopSecretManager is an empty secret manager
4088
var NoopSecretManager = SecretsManager{
@@ -43,35 +91,36 @@ var NoopSecretManager = SecretsManager{
4391
}
4492

4593
// NoopSymmetricProvider returns an empty provider
46-
func NoopSymmetricProvider(_ string) ([]byte, error) {
47-
return nil, fmt.Errorf("no secrets provided to hoard")
94+
func NoopSymmetricProvider(_ string) (SymmetricSecret, error) {
95+
return SymmetricSecret{}, fmt.Errorf("no secrets provided to hoard")
4896
}
4997

5098
// ProviderFromConfig creates a secret reader from a set of symmetric secrets
5199
func NewSymmetricProvider(conf *Secrets, fromEnv bool) (SymmetricProvider, error) {
52100
if conf == nil || len(conf.Symmetric) == 0 {
53101
return NoopSymmetricProvider, nil
54102
}
55-
secs := make(map[string][]byte, len(conf.Symmetric))
103+
secs := make(map[string]SymmetricSecret, len(conf.Symmetric))
56104
for _, s := range conf.Symmetric {
57105
if fromEnv {
58106
// sometimes we don't want to specify these in the config
59-
s.SecretKey = os.Getenv(s.PublicID)
107+
secret := os.Getenv(s.PublicID)
108+
s.SecretKey = []byte(secret)
109+
s.Passphrase = secret
60110
}
61-
secret, err := b64.StdEncoding.DecodeString(s.SecretKey)
62-
if err != nil {
63-
return nil, err
111+
secs[s.PublicID] = SymmetricSecret{
112+
Passphrase: s.Passphrase,
113+
SecretKey: s.SecretKey,
64114
}
65-
secs[s.PublicID] = secret
66115
}
67-
return func(id string) ([]byte, error) {
116+
return func(id string) (SymmetricSecret, error) {
68117
if id == "" {
69-
return nil, fmt.Errorf("empty secret ID passed to provider")
118+
return SymmetricSecret{}, fmt.Errorf("empty secret ID passed to provider")
70119
}
71120
if val, ok := secs[id]; ok {
72121
return val, nil
73122
}
74-
return nil, fmt.Errorf("could not find symmetric secret with ID '%s'", id)
123+
return SymmetricSecret{}, fmt.Errorf("could not find symmetric secret with ID '%s'", id)
75124
}, nil
76125
}
77126

config/secrets_test.go

+60
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
package config
2+
3+
import (
4+
"bytes"
5+
"encoding/json"
6+
"testing"
7+
8+
"github.com/BurntSushi/toml"
9+
"github.com/monax/hoard/v7/encryption"
10+
"github.com/stretchr/testify/assert"
11+
yaml "gopkg.in/yaml.v2"
12+
)
13+
14+
func TestSecretKeyMarshal(t *testing.T) {
15+
salt := make([]byte, encryption.NonceSize)
16+
key, err := encryption.DeriveSecretKey([]byte("hello"), salt)
17+
assert.NoError(t, err)
18+
19+
secret := SecretKey(key)
20+
data, err := secret.MarshalText()
21+
assert.NoError(t, err)
22+
expected := "bFQ+wRhNaOgC4fNcliGFaZ5Xr3wOywYJZP1eqj6SDCk="
23+
assert.Equal(t, expected, string(data))
24+
25+
inSecret := new(SymmetricSecret)
26+
inSecret.SecretKey = secret
27+
outSecret := new(SymmetricSecret)
28+
29+
data, err = json.Marshal(inSecret)
30+
assert.NoError(t, err)
31+
assert.Equal(t, "{\"PublicID\":\"\",\"SecretKey\":\"bFQ+wRhNaOgC4fNcliGFaZ5Xr3wOywYJZP1eqj6SDCk=\",\"Passphrase\":\"\"}", string(data))
32+
err = json.Unmarshal(data, outSecret)
33+
assert.NoError(t, err)
34+
assert.Equal(t, key, []byte(outSecret.SecretKey))
35+
36+
data, err = yaml.Marshal(inSecret)
37+
assert.NoError(t, err)
38+
assert.Equal(t, "publicid: \"\"\nsecretkey: bFQ+wRhNaOgC4fNcliGFaZ5Xr3wOywYJZP1eqj6SDCk=\npassphrase: \"\"\n", string(data))
39+
err = yaml.Unmarshal(data, outSecret)
40+
assert.NoError(t, err)
41+
assert.Equal(t, key, []byte(outSecret.SecretKey))
42+
43+
buf := new(bytes.Buffer)
44+
encoder := toml.NewEncoder(buf)
45+
err = encoder.Encode(inSecret)
46+
assert.NoError(t, err)
47+
assert.Equal(t, "PublicID = \"\"\nSecretKey = \"bFQ+wRhNaOgC4fNcliGFaZ5Xr3wOywYJZP1eqj6SDCk=\"\nPassphrase = \"\"\n", buf.String())
48+
err = toml.Unmarshal(buf.Bytes(), outSecret)
49+
assert.NoError(t, err)
50+
assert.Equal(t, key, []byte(outSecret.SecretKey))
51+
52+
err = toml.Unmarshal([]byte("SecretKey = \"bFQ+wRhNaOgC4fNcliGFaZ5Xr3wOywYJZP1eqj6SDCk=\"\n"), outSecret)
53+
assert.NoError(t, err)
54+
assert.Equal(t, key, []byte(outSecret.SecretKey))
55+
assert.Equal(t, "", outSecret.PublicID)
56+
assert.Equal(t, "", outSecret.Passphrase)
57+
58+
err = toml.Unmarshal([]byte("PublicID = \"\"\nSecretKey = \"badkey=\"\n"), outSecret)
59+
assert.Error(t, err)
60+
}

go.mod

+5
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,16 @@ require (
1919
github.com/h2non/filetype v1.0.10
2020
github.com/jawher/mow.cli v1.1.0
2121
github.com/monax/relic v2.0.0+incompatible
22+
github.com/naoina/go-stringutil v0.1.0 // indirect
23+
github.com/naoina/toml v0.1.1
24+
github.com/pelletier/go-toml v1.6.0
2225
github.com/stretchr/testify v1.4.0
26+
github.com/test-go/testify v1.1.4
2327
gocloud.dev v0.18.0
2428
golang.org/x/crypto v0.0.0-20191122220453-ac88ee75c92c
2529
golang.org/x/oauth2 v0.0.0-20191122200657-5d9234df094c
2630
google.golang.org/api v0.6.0
2731
google.golang.org/grpc v1.25.1
32+
gopkg.in/yaml.v1 v1.0.0-20140924161607-9f9df34309c0
2833
gopkg.in/yaml.v2 v2.2.7
2934
)

go.sum

+11
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,12 @@ github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrk
127127
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
128128
github.com/monax/relic v2.0.0+incompatible h1:5q+fw8Y7UJJuOBzGV5bZNlBk9k9ii6fzmdpwXsZKMdg=
129129
github.com/monax/relic v2.0.0+incompatible/go.mod h1:ZJcXg8m9tYkd2h6VeEZruhRUQPklFKbzFaTxyXrXxVk=
130+
github.com/naoina/go-stringutil v0.1.0 h1:rCUeRUHjBjGTSHl0VC00jUPLz8/F9dDzYI70Hzifhks=
131+
github.com/naoina/go-stringutil v0.1.0/go.mod h1:XJ2SJL9jCtBh+P9q5btrd/Ylo8XwT/h1USek5+NqSA0=
132+
github.com/naoina/toml v0.1.1 h1:PT/lllxVVN0gzzSqSlHEmP8MJB4MY2U7STGxiouV4X8=
133+
github.com/naoina/toml v0.1.1/go.mod h1:NBIhNtsFMo3G2szEBne+bO4gS192HuIYRqfvOWb4i1E=
134+
github.com/pelletier/go-toml v1.6.0 h1:aetoXYr0Tv7xRU/V4B4IZJ2QcbtMUFoNb3ORp7TzIK4=
135+
github.com/pelletier/go-toml v1.6.0/go.mod h1:5N711Q9dKgbdkxHL+MEfF31hpT7l0S0s/t2kKREewys=
130136
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
131137
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
132138
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
@@ -140,6 +146,8 @@ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXf
140146
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
141147
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
142148
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
149+
github.com/test-go/testify v1.1.4 h1:Tf9lntrKUMHiXQ07qBScBTSA0dhYQlu83hswqelv1iE=
150+
github.com/test-go/testify v1.1.4/go.mod h1:rH7cfJo/47vWGdi4GPj16x3/t1xGOj2YxzmNQzk2ghU=
143151
go.opencensus.io v0.15.0/go.mod h1:UffZAU+4sDEINUGP/B7UfBBkq4fqLu9zXAX7ke6CHW0=
144152
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
145153
go.opencensus.io v0.22.0 h1:C9hSCOW830chIVkdja34wa6Ky+IzWllkUinR+BtRZd4=
@@ -237,8 +245,11 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8
237245
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
238246
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
239247
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
248+
gopkg.in/yaml.v1 v1.0.0-20140924161607-9f9df34309c0 h1:POO/ycCATvegFmVuPpQzZFJ+pGZeX22Ufu6fibxDVjU=
249+
gopkg.in/yaml.v1 v1.0.0-20140924161607-9f9df34309c0/go.mod h1:WDnlLJ4WF5VGsH/HVa3CI79GS0ol3YnhVnKP89i0kNg=
240250
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
241251
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
252+
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
242253
gopkg.in/yaml.v2 v2.2.7 h1:VUgggvou5XRW9mHwD/yXxIYSMtY0zoKQf/v226p2nyo=
243254
gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
244255
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=

grant/grant.go

+10-3
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,11 @@ import (
77
"github.com/monax/hoard/v7/reference"
88
)
99

10+
const defaultGrantVersion = 1
11+
1012
// Seal this reference into a Grant as specified by Spec
1113
func Seal(secret config.SecretsManager, ref *reference.Ref, spec *Spec) (*Grant, error) {
12-
grt := &Grant{Spec: spec}
14+
grt := &Grant{Spec: spec, Version: defaultGrantVersion}
1315

1416
if s := spec.GetPlaintext(); s != nil {
1517
grt.EncryptedReference = PlaintextGrant(ref)
@@ -18,7 +20,7 @@ func Seal(secret config.SecretsManager, ref *reference.Ref, spec *Spec) (*Grant,
1820
if err != nil {
1921
return nil, err
2022
}
21-
encRef, err := SymmetricGrant(ref, secret)
23+
encRef, err := SymmetricGrant(ref, secret.SecretKey)
2224
if err != nil {
2325
return nil, err
2426
}
@@ -45,7 +47,12 @@ func Unseal(secret config.SecretsManager, grt *Grant) (*reference.Ref, error) {
4547
if err != nil {
4648
return nil, err
4749
}
48-
return SymmetricReference(grt.EncryptedReference, secret)
50+
switch grt.GetVersion() {
51+
case 0:
52+
return SymmetricReferenceV0(grt.EncryptedReference, []byte(secret.Passphrase))
53+
default:
54+
return SymmetricReferenceV1(grt.EncryptedReference, secret.SecretKey)
55+
}
4956
} else if s := grt.Spec.GetOpenPGP(); s != nil {
5057
return OpenPGPReference(grt.EncryptedReference, secret.OpenPGP)
5158
} else {

0 commit comments

Comments
 (0)