Migrate sdk/recovery to api/ and sdk/wrapper to internal (#1507)

* `recovery` is only used by the API (and the main module, for decryption)
* `wrapper` is only used by the CLI right now

Moving these brings them closer to the code that uses them, and makes
the SDK package a bit more clear in terms of functionality.
This commit is contained in:
Jeff Mitchell authored and GitHub committed 2021-09-07 22:13:19 -04:00
1 parent 3fc2cf4df5
commit 081de3cf79
16 files changed
+17 -683

No files matched your search

+1 -1
View File
@@ -19,7 +19,7 @@ import (
"time"
"unicode"
"github.com/hashicorp/boundary/sdk/recovery"
"github.com/hashicorp/boundary/api/recovery"
cleanhttp "github.com/hashicorp/go-cleanhttp"
wrapping "github.com/hashicorp/go-kms-wrapping"
retryablehttp "github.com/hashicorp/go-retryablehttp"
+3 -1
View File
@@ -6,13 +6,15 @@ replace github.com/hashicorp/boundary/sdk => ../sdk
require (
github.com/fatih/structs v1.1.0
github.com/hashicorp/boundary/sdk v0.0.8
github.com/hashicorp/go-cleanhttp v0.5.2
github.com/hashicorp/go-kms-wrapping v0.6.5
github.com/hashicorp/go-retryablehttp v0.6.8
github.com/hashicorp/go-rootcerts v1.0.2
github.com/hashicorp/go-secure-stdlib/parseutil v0.1.1
github.com/hashicorp/go-uuid v1.0.2
github.com/mr-tron/base58 v1.2.0
github.com/stretchr/testify v1.7.0
golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e
google.golang.org/grpc v1.35.0
google.golang.org/protobuf v1.27.1
)
+1 -14
View File
@@ -45,9 +45,6 @@ github.com/Azure/go-autorest/tracing v0.5.0/go.mod h1:r/s2XiOKccPW3HrqB+W0TQzfbt
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ=
github.com/Masterminds/goutils v1.1.0/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU=
github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y=
github.com/Masterminds/sprig v2.22.0+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuNhlNS5hqE0NB0E6fgfo2Br3o=
github.com/Microsoft/go-winio v0.4.15-0.20190919025122-fc70bd9a86b5/go.mod h1:tTuCMEN+UleMWgg9dVx4Hu52b1bJo+59jBh3ajtinzw=
github.com/Microsoft/hcsshim v0.8.9/go.mod h1:5692vkUqntj1idxauYlpoINNKeqCiG6Sg38RRsjT5y8=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
@@ -210,14 +207,10 @@ github.com/hashicorp/go-rootcerts v1.0.1/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR3
github.com/hashicorp/go-rootcerts v1.0.2 h1:jzhAVGtqPKbwpyCPELlgNWhE1znq+qwJtW5Oi2viEzc=
github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8=
github.com/hashicorp/go-secure-stdlib/awsutil v0.1.2/go.mod h1:QRJZ7siKie+SZJB9jLbfKrs0Gd0yPWMtbneg0iU1PrY=
github.com/hashicorp/go-secure-stdlib/configutil v0.1.2/go.mod h1:EN1DJMjv9y5e/uRAuP2WPibAox5KHEiq2BjZl00aNPQ=
github.com/hashicorp/go-secure-stdlib/listenerutil v0.1.1/go.mod h1:irE9ILwhE8VooWdhHhBy0qXzOMRlHKoWoMlr31mAFCE=
github.com/hashicorp/go-secure-stdlib/parseutil v0.1.1 h1:78ki3QBevHwYrVxnyVeaEz+7WtifHhauYF23es/0KlI=
github.com/hashicorp/go-secure-stdlib/parseutil v0.1.1/go.mod h1:QmrqtbKuxxSWTN3ETMPuB+VtEiBJ/A9XhoYGv8E1uD8=
github.com/hashicorp/go-secure-stdlib/reloadutil v0.1.1/go.mod h1:Ch/bf00Qnx77MZd49JRgHYqHQjtEmTgGU2faufpVZb0=
github.com/hashicorp/go-secure-stdlib/strutil v0.1.1 h1:nd0HIW15E6FG1MsnArYaHfuw9C2zgzM8LxkG5Ty/788=
github.com/hashicorp/go-secure-stdlib/strutil v0.1.1/go.mod h1:gKOamz3EwoIoJq7mlMIRBpVTAUn8qPCrEclOKKWhD3U=
github.com/hashicorp/go-secure-stdlib/tlsutil v0.1.1/go.mod h1:l8slYwnJA26yBz+ErHpp2IRCLr0vuOMGBORIz4rRiAs=
github.com/hashicorp/go-sockaddr v1.0.2 h1:ztczhD1jLxIRjVejw8gFomI1BQZOe2WoVOu0SyteCQc=
github.com/hashicorp/go-sockaddr v1.0.2/go.mod h1:rB4wwRAUzs07qva3c5SdrY/NEtAUjGlgmH/UkBUC97A=
github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
@@ -235,12 +228,9 @@ github.com/hashicorp/vault/sdk v0.1.14-0.20200519221530-14615acda45f/go.mod h1:W
github.com/hashicorp/vault/sdk v0.2.1/go.mod h1:WfUiO1vYzfBkz1TmoE4ZGU7HD0T0Cl/rZwaxjBkgN4U=
github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/huandu/xstrings v1.3.2/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
github.com/huaweicloud/golangsdk v0.0.0-20200304081349-45ec0797f2a4/go.mod h1:WQBcHRNX9shz3928lWEvstQJtAtYI7ks6XlgtRT9Tcw=
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/jefferai/isbadcipher v0.0.0-20190226160619-51d2077c035f/go.mod h1:3J2qVK16Lq8V+wfiL2lPeDZ7UWMxk5LemerHa1p6N00=
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
github.com/jmespath/go-jmespath v0.3.0/go.mod h1:9QtRXoHjLGCJ5IBSaohpXITPlowMeeYCZ7fLUTSywik=
github.com/json-iterator/go v1.1.5/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
@@ -273,7 +263,6 @@ github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHX
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
github.com/mitchellh/cli v1.1.2/go.mod h1:6iaV0fGdElS6dPBx0EApTxHrcWvmJphyh2n8YBLPPZ4=
github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw=
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
@@ -374,9 +363,8 @@ golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8U
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9 h1:vEg9joUBmeBcK9iSJftGNf3coIG4HqZElCPehJsfAYM=
golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a h1:vclmkQCjlDX5OydZ9wv8rBCcS0QyQY66Mpf/7BZbInM=
golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@@ -607,7 +595,6 @@ gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw=
+131
View File
@@ -0,0 +1,131 @@
package recovery
import (
"context"
"encoding/base64"
"encoding/json"
"errors"
"fmt"
"strings"
"time"
wrapping "github.com/hashicorp/go-kms-wrapping"
"github.com/hashicorp/go-uuid"
"github.com/mr-tron/base58"
"google.golang.org/protobuf/proto"
)
const (
nonceLength = 32
v1String = "1"
)
// FutureLeeway indicates how far in the future we should allow the creation
// time of the token to be, in order to account for clock drift
var FutureLeeway = time.Minute
// Info is the struct required to be marshaled to be used as a token
// for the recovery workflow.
type Info struct {
Nonce string `json:"nonce"`
NonceBytes []byte `json:"-"`
CreationTime time.Time `json:"creation_time"`
}
func GenerateRecoveryToken(ctx context.Context, wrapper wrapping.Wrapper) (string, error) {
b, err := uuid.GenerateRandomBytes(nonceLength)
if err != nil {
return "", fmt.Errorf("error generating random bytes for recovery token nonce: %w", err)
}
info := &Info{
Nonce: base64.RawStdEncoding.EncodeToString(b),
CreationTime: time.Now(),
}
return formatToken(ctx, wrapper, info)
}
func formatToken(ctx context.Context, wrapper wrapping.Wrapper, info *Info) (string, error) {
marshaledInfo, err := json.Marshal(info)
if err != nil {
return "", fmt.Errorf("error marshaling recovery info: %w", err)
}
blobInfo, err := wrapper.Encrypt(ctx, marshaledInfo, nil)
if err != nil {
return "", fmt.Errorf("error encrypting recovery info: %w", err)
}
marshaledBlob, err := proto.Marshal(blobInfo)
if err != nil {
return "", fmt.Errorf("error marshaling encrypted blob: %w", err)
}
encodedMarshaledBlob := base58.FastBase58Encoding(marshaledBlob)
return fmt.Sprintf("r_%s%s", v1String, encodedMarshaledBlob), nil
}
func ParseRecoveryToken(ctx context.Context, wrapper wrapping.Wrapper, versionedToken string) (*Info, error) {
versionedToken = strings.TrimSpace(versionedToken)
if versionedToken == "" {
return nil, errors.New("empty token")
}
if !strings.HasPrefix(versionedToken, "r_") {
return nil, errors.New("token has wrong format")
}
versionedToken = strings.TrimPrefix(versionedToken, "r_")
ver, token := versionedToken[0:1], versionedToken[1:]
// Don't do anything yet, but if we ever need to adjust data based on
// version we'd do it here
switch ver {
case v1String:
default:
return nil, fmt.Errorf("unknown recovery token version %s", ver)
}
marshaledBlob, err := base58.FastBase58Decoding(token)
if err != nil {
return nil, fmt.Errorf("error base58-decoding token: %w", err)
}
if len(marshaledBlob) == 0 {
return nil, fmt.Errorf("length zero after base58-decoding token")
}
blobInfo := new(wrapping.EncryptedBlobInfo)
if err := proto.Unmarshal(marshaledBlob, blobInfo); err != nil {
return nil, fmt.Errorf("error decoding encrypted blob: %w", err)
}
marshaledInfo, err := wrapper.Decrypt(ctx, blobInfo, nil)
if err != nil {
return nil, fmt.Errorf("error decrypting recovery info: %w", err)
}
info := new(Info)
if err := json.Unmarshal(marshaledInfo, info); err != nil {
return nil, fmt.Errorf("error unmarshaling recovery info: %w", err)
}
info.NonceBytes, err = base64.RawStdEncoding.DecodeString(info.Nonce)
if err != nil {
return nil, fmt.Errorf("error decoding nonce bytes: %w", err)
}
if len(info.NonceBytes) != nonceLength {
return nil, errors.New("nonce has incorrect length, must be 32 bytes")
}
if info.CreationTime.IsZero() {
return nil, errors.New("recovery token creation time is zero")
}
// It must be before the current time. This means someone can't create
// one way in the future and keep using it. We fudge this by 1 minute to
// account for time discrepancies between systems.
if info.CreationTime.After(time.Now().Add(FutureLeeway)) {
return nil, errors.New("recovery token creation time is invalid")
}
return info, nil
}
+114
View File
@@ -0,0 +1,114 @@
package recovery
import (
"context"
"crypto/rand"
"encoding/base64"
"testing"
"time"
wrapping "github.com/hashicorp/go-kms-wrapping"
"github.com/hashicorp/go-kms-wrapping/wrappers/aead"
"github.com/hashicorp/go-uuid"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func testWrapper(t *testing.T) wrapping.Wrapper {
rootKey := make([]byte, 32)
n, err := rand.Read(rootKey)
if err != nil {
t.Fatal(err)
}
if n != 32 {
t.Fatal(n)
}
root := aead.NewWrapper(nil)
_, err = root.SetConfig(map[string]string{
"key_id": base64.StdEncoding.EncodeToString(rootKey),
})
if err != nil {
t.Fatal(err)
}
if err := root.SetAESGCMKeyBytes(rootKey); err != nil {
t.Fatal(err)
}
return root
}
func TestRecoveryTokens(t *testing.T) {
ctx := context.Background()
wrapper := testWrapper(t)
b, err := uuid.GenerateRandomBytes(nonceLength)
require.NoError(t, err)
assert.Len(t, b, 32)
b64Nonce := base64.RawStdEncoding.EncodeToString(b)
tests := []struct {
name string
withTime time.Time
withNonce string
wantErr bool
}{
{
name: "normal",
},
{
name: "zero time",
withNonce: b64Nonce,
wantErr: true,
},
{
name: "future time",
withNonce: b64Nonce,
withTime: time.Now().Add(5 * time.Minute),
wantErr: true,
},
{
name: "empty nonce",
withNonce: string(""),
withTime: time.Now(),
wantErr: true,
},
{
name: "wrong nonce length",
withNonce: base64.RawStdEncoding.EncodeToString(append(b, b...)),
withTime: time.Now(),
wantErr: true,
},
{
name: "bad nonce format",
withNonce: string(b),
withTime: time.Now(),
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
if tt.withTime.IsZero() && tt.withNonce == "" {
token, err := GenerateRecoveryToken(ctx, wrapper)
require.NoError(err)
assert.NotEmpty(token)
info, err := ParseRecoveryToken(ctx, wrapper, token)
require.NoError(err)
assert.NotNil(info)
return
}
token, err := formatToken(ctx, wrapper, &Info{
Nonce: tt.withNonce,
CreationTime: tt.withTime,
})
require.NoError(err)
assert.NotEmpty(token)
info, err := ParseRecoveryToken(ctx, wrapper, token)
if tt.wantErr {
require.Error(err)
return
}
require.NoError(err)
assert.NotNil(info)
})
}
}