Skip to content

Commit 5017148

Browse files
committed
Tests for YC KMS provider
- Tests for MasterKey public methods. gRPC calls `Encrypt` and `Decrypt` are mocked with dummy responses. - Minor adjustments for MasterKey constructors: the constructors return an error
1 parent f320eeb commit 5017148

File tree

4 files changed

+245
-33
lines changed

4 files changed

+245
-33
lines changed

cmd/sops/main.go

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -432,7 +432,12 @@ func main() {
432432
group = append(group, gcpkms.NewMasterKeyFromResourceID(kms))
433433
}
434434
for _, kms := range ycKmses {
435-
group = append(group, yckms.NewMasterKeyFromKeyID(kms))
435+
k, err := yckms.NewMasterKeyFromKeyID(kms)
436+
if err != nil {
437+
log.WithError(err).Error("Failed to add key")
438+
continue
439+
}
440+
group = append(group, k)
436441
}
437442
for _, uri := range vaultURIs {
438443
k, err := hcvault.NewMasterKeyFromURI(uri)
@@ -1105,7 +1110,11 @@ func keyGroups(c *cli.Context, file string) ([]sops.KeyGroup, error) {
11051110
}
11061111
}
11071112
if c.String("yc-kms") != "" {
1108-
for _, k := range yckms.NewMasterKeyFromKeyIDString(c.String("yc-kms")) {
1113+
ycKeys, err := yckms.NewMasterKeyFromKeyIDString(c.String("yc-kms"))
1114+
if err != nil {
1115+
return nil, err
1116+
}
1117+
for _, k := range ycKeys {
11091118
ycKmsKeys = append(ycKmsKeys, k)
11101119
}
11111120
}

config/config.go

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -178,7 +178,11 @@ func getKeyGroupsFromCreationRule(cRule *creationRule, kmsEncryptionContext map[
178178
keyGroup = append(keyGroup, gcpkms.NewMasterKeyFromResourceID(k.ResourceID))
179179
}
180180
for _, k := range group.YCKMS {
181-
keyGroup = append(keyGroup, yckms.NewMasterKeyFromKeyID(k.KeyID))
181+
if masterKey, err := yckms.NewMasterKeyFromKeyID(k.KeyID); err == nil {
182+
keyGroup = append(keyGroup, masterKey)
183+
} else {
184+
return nil, err
185+
}
182186
}
183187
for _, k := range group.AzureKV {
184188
keyGroup = append(keyGroup, azkv.NewMasterKey(k.VaultURL, k.Key, k.Version))
@@ -213,7 +217,11 @@ func getKeyGroupsFromCreationRule(cRule *creationRule, kmsEncryptionContext map[
213217
for _, k := range gcpkms.MasterKeysFromResourceIDString(cRule.GCPKMS) {
214218
keyGroup = append(keyGroup, k)
215219
}
216-
for _, k := range yckms.NewMasterKeyFromKeyIDString(cRule.YCKMS) {
220+
ycKeys, err := yckms.NewMasterKeyFromKeyIDString(cRule.YCKMS)
221+
if err != nil {
222+
return nil, err
223+
}
224+
for _, k := range ycKeys {
217225
keyGroup = append(keyGroup, k)
218226
}
219227
azureKeys, err := azkv.MasterKeysFromURLs(cRule.AzureKeyVault)

yckms/keysource.go

Lines changed: 32 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -39,33 +39,42 @@ type MasterKey struct {
3939
// CreationDate is the creation timestamp of the MasterKey. Used
4040
// for NeedsRotation.
4141
CreationDate time.Time
42+
43+
client *kms.SymmetricCryptoServiceClient
4244
}
4345

44-
func NewMasterKeyFromKeyID(keyID string) *MasterKey {
45-
k := &MasterKey{}
46-
k.KeyID = keyID
47-
k.CreationDate = time.Now().UTC()
48-
return k
46+
func NewMasterKeyFromKeyID(keyID string) (k *MasterKey, err error) {
47+
k = &MasterKey{
48+
KeyID: keyID,
49+
CreationDate: time.Now().UTC(),
50+
}
51+
52+
k.client, err = NewKMSClient()
53+
if err != nil {
54+
log.WithError(err).WithField("keyID", keyID).Error("Encryption failed")
55+
return nil, fmt.Errorf("cannot create YC KMS service: %w", err)
56+
}
57+
58+
return k, nil
4959
}
5060

51-
func NewMasterKeyFromKeyIDString(keyID string) []*MasterKey {
61+
func NewMasterKeyFromKeyIDString(keyID string) ([]*MasterKey, error) {
5262
var keys []*MasterKey
5363
if keyID == "" {
54-
return keys
64+
return keys, nil
5565
}
5666
for _, s := range strings.Split(keyID, ",") {
57-
keys = append(keys, NewMasterKeyFromKeyID(s))
67+
key, err := NewMasterKeyFromKeyID(strings.TrimSpace(s))
68+
if err != nil {
69+
return nil, err
70+
}
71+
keys = append(keys, key)
5872
}
59-
return keys
73+
return keys, nil
6074
}
6175

6276
func (key *MasterKey) Encrypt(dataKey []byte) error {
63-
client, ctx, err := key.newKMSClient()
64-
if err != nil {
65-
log.WithError(err).WithField("keyID", key.KeyID).Error("Encryption failed")
66-
return fmt.Errorf("cannot create YC KMS service: %w", err)
67-
}
68-
ciphertextResponse, err := client.Encrypt(ctx, &yckms.SymmetricEncryptRequest{
77+
ciphertextResponse, err := key.client.Encrypt(context.Background(), &yckms.SymmetricEncryptRequest{
6978
KeyId: key.KeyID,
7079
Plaintext: dataKey,
7180
})
@@ -100,13 +109,8 @@ func (key *MasterKey) EncryptIfNeeded(dataKey []byte) error {
100109
// Decrypt decrypts the EncryptedKey field with YC KMS and returns
101110
// the result.
102111
func (key *MasterKey) Decrypt() ([]byte, error) {
103-
client, ctx, err := key.newKMSClient()
104-
if err != nil {
105-
log.WithError(err).WithField("keyID", key.KeyID).Error("Decryption failed")
106-
return nil, fmt.Errorf("cannot create YC KMS service: %w", err)
107-
}
108112
decodedCipher, err := base64.StdEncoding.DecodeString(string(key.EncryptedDataKey()))
109-
plaintextResponse, err := client.Decrypt(ctx, &yckms.SymmetricDecryptRequest{
113+
plaintextResponse, err := key.client.Decrypt(context.Background(), &yckms.SymmetricDecryptRequest{
110114
KeyId: key.KeyID,
111115
Ciphertext: decodedCipher,
112116
})
@@ -129,33 +133,32 @@ func (key *MasterKey) ToString() string {
129133
}
130134

131135
// ToMap converts the MasterKey to a map for serialization purposes.
132-
func (key MasterKey) ToMap() map[string]interface{} {
136+
func (key *MasterKey) ToMap() map[string]interface{} {
133137
out := make(map[string]interface{})
134138
out["key_id"] = key.KeyID
135139
out["created_at"] = key.CreationDate.UTC().Format(time.RFC3339)
136140
out["enc"] = key.EncryptedKey
137141
return out
138142
}
139143

140-
// newKMSClient returns a YC KMS client configured with the credentialsStore
144+
// NewKMSClient returns a YC KMS client configured with the credentialsStore
141145
// and/or grpcConn, falling back to environmental defaults.
142146
// It returns an error if the ResourceID is invalid, or if the setup of the
143147
// client fails.
144-
func (key *MasterKey) newKMSClient() (*kms.SymmetricCryptoServiceClient, context.Context, error) {
145-
ctx := context.Background()
148+
func NewKMSClient() (*kms.SymmetricCryptoServiceClient, error) {
146149
cred, err := getYandexCloudCredentials()
147150
if err != nil {
148-
return nil, nil, err
151+
return nil, err
149152
}
150153

151-
client, err := ycsdk.Build(ctx, ycsdk.Config{
154+
client, err := ycsdk.Build(context.Background(), ycsdk.Config{
152155
Credentials: cred,
153156
})
154157
if err != nil {
155-
return nil, nil, err
158+
return nil, err
156159
}
157160

158-
return client.KMSCrypto().SymmetricCrypto(), ctx, nil
161+
return client.KMSCrypto().SymmetricCrypto(), nil
159162
}
160163

161164
// getYandexCloudCredentials trying to locate credentials in the following order

yckms/keysource_test.go

Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
1+
package yckms
2+
3+
import (
4+
"context"
5+
"encoding/base64"
6+
yckms "github.com/yandex-cloud/go-genproto/yandex/cloud/kms/v1"
7+
kms "github.com/yandex-cloud/go-sdk/gen/kmscrypto"
8+
"google.golang.org/grpc"
9+
"google.golang.org/grpc/codes"
10+
"google.golang.org/grpc/credentials/insecure"
11+
"google.golang.org/grpc/status"
12+
"google.golang.org/grpc/test/bufconn"
13+
"net"
14+
"testing"
15+
"time"
16+
17+
"github.com/stretchr/testify/assert"
18+
)
19+
20+
const (
21+
dummyKey = "my:dummy:ru-east-1:12345678:key/920aff2e-c5f1-4040-943a-047fa387b27e+my:dummy:iam::927034868273:role/sops-dev"
22+
anotherDummyKey = "my:dummy:ru-east-1:12345678:key/920aff2e-c5f1-4040-943a-047fa387b27e+my:dummy:iam::927034868273"
23+
dummyKeys = dummyKey + ", " + anotherDummyKey
24+
decodedKey = "I want to be a DJ"
25+
)
26+
27+
const bufSize = 1024 * 1024
28+
29+
var lis *bufconn.Listener
30+
31+
func init() {
32+
lis = bufconn.Listen(bufSize)
33+
s := grpc.NewServer()
34+
yckms.RegisterSymmetricCryptoServiceServer(s, mockSymmetricCryptoServiceServer{})
35+
go func() {
36+
if err := s.Serve(lis); err != nil {
37+
log.Fatalf("Server exited with error: %v", err)
38+
}
39+
}()
40+
}
41+
42+
func bufDialer(context.Context, string) (net.Conn, error) {
43+
return lis.Dial()
44+
}
45+
46+
type mockSymmetricCryptoServiceServer struct {
47+
}
48+
49+
func (mockSymmetricCryptoServiceServer) Encrypt(context.Context, *yckms.SymmetricEncryptRequest) (*yckms.SymmetricEncryptResponse, error) {
50+
key, _ := base64.StdEncoding.DecodeString("test")
51+
return &yckms.SymmetricEncryptResponse{
52+
Ciphertext: key,
53+
}, nil
54+
}
55+
func (mockSymmetricCryptoServiceServer) Decrypt(context.Context, *yckms.SymmetricDecryptRequest) (*yckms.SymmetricDecryptResponse, error) {
56+
return &yckms.SymmetricDecryptResponse{
57+
Plaintext: []byte(decodedKey),
58+
}, nil
59+
}
60+
func (mockSymmetricCryptoServiceServer) ReEncrypt(context.Context, *yckms.SymmetricReEncryptRequest) (*yckms.SymmetricReEncryptResponse, error) {
61+
return nil, status.Errorf(codes.Unimplemented, "method ReEncrypt not implemented")
62+
}
63+
func (mockSymmetricCryptoServiceServer) GenerateDataKey(context.Context, *yckms.GenerateDataKeyRequest) (*yckms.GenerateDataKeyResponse, error) {
64+
return nil, status.Errorf(codes.Unimplemented, "method GenerateDataKey not implemented")
65+
}
66+
67+
func TestNewMasterKeyFromKeyID(t *testing.T) {
68+
key, err := NewMasterKeyFromKeyID(dummyKey)
69+
assert.NoError(t, err)
70+
assert.Equal(t, dummyKey, key.KeyID)
71+
assert.NotNil(t, key.CreationDate)
72+
}
73+
74+
func TestNewMasterKeyFromKeyIDString(t *testing.T) {
75+
keys, err := NewMasterKeyFromKeyIDString(dummyKeys)
76+
assert.NoError(t, err)
77+
assert.Len(t, keys, 2)
78+
79+
k1 := keys[0]
80+
k2 := keys[1]
81+
82+
assert.Equal(t, dummyKey, k1.KeyID)
83+
assert.Equal(t, anotherDummyKey, k2.KeyID)
84+
}
85+
86+
func TestMasterKey_Encrypt(t *testing.T) {
87+
t.Run("encrypt", func(t *testing.T) {
88+
kmsClient, err := createTestKMSClient()
89+
assert.NoError(t, err)
90+
91+
key := &MasterKey{
92+
client: kmsClient,
93+
}
94+
95+
dataKey := []byte(decodedKey)
96+
assert.NoError(t, key.Encrypt(dataKey))
97+
98+
decodedCipher, err := base64.StdEncoding.DecodeString(key.EncryptedKey)
99+
assert.NoError(t, err)
100+
101+
decrypted, err := kmsClient.Decrypt(context.TODO(), &yckms.SymmetricDecryptRequest{
102+
KeyId: key.KeyID,
103+
Ciphertext: decodedCipher,
104+
})
105+
if assert.NoError(t, err) {
106+
assert.NotNil(t, decrypted)
107+
assert.Equal(t, dataKey, decrypted.Plaintext)
108+
}
109+
})
110+
}
111+
112+
func TestMasterKey_Decrypt(t *testing.T) {
113+
t.Run("decrypt", func(t *testing.T) {
114+
kmsClient, err := createTestKMSClient()
115+
assert.NoError(t, err)
116+
117+
key := &MasterKey{
118+
client: kmsClient,
119+
}
120+
121+
dataKey := []byte(decodedKey)
122+
out, err := kmsClient.Encrypt(context.TODO(), &yckms.SymmetricEncryptRequest{
123+
KeyId: dummyKey,
124+
Plaintext: dataKey,
125+
})
126+
assert.NoError(t, err)
127+
128+
key.EncryptedKey = base64.StdEncoding.EncodeToString(out.Ciphertext)
129+
got, err := key.Decrypt()
130+
assert.NoError(t, err)
131+
assert.Equal(t, dataKey, got)
132+
})
133+
}
134+
135+
func TestMasterKey_EncryptedDataKey(t *testing.T) {
136+
key := &MasterKey{EncryptedKey: "some key"}
137+
assert.EqualValues(t, key.EncryptedKey, key.EncryptedDataKey())
138+
}
139+
140+
func TestMasterKey_SetEncryptedDataKey(t *testing.T) {
141+
key := &MasterKey{}
142+
data := []byte("some data")
143+
key.SetEncryptedDataKey(data)
144+
assert.EqualValues(t, data, key.EncryptedKey)
145+
}
146+
147+
func TestMasterKey_NeedsRotation(t *testing.T) {
148+
t.Run("false", func(t *testing.T) {
149+
k := &MasterKey{}
150+
k.CreationDate = time.Now().UTC()
151+
152+
assert.False(t, k.NeedsRotation())
153+
})
154+
155+
t.Run("true", func(t *testing.T) {
156+
k := &MasterKey{}
157+
k.CreationDate = time.Now().UTC().Add(-kmsTTL - 1)
158+
159+
assert.True(t, k.NeedsRotation())
160+
})
161+
}
162+
163+
func TestMasterKey_ToString(t *testing.T) {
164+
key, err := NewMasterKeyFromKeyID(dummyKey)
165+
assert.NoError(t, err)
166+
167+
assert.Equal(t, dummyKey, key.ToString())
168+
}
169+
170+
func TestMasterKey_ToMap(t *testing.T) {
171+
key, err := NewMasterKeyFromKeyID(dummyKey)
172+
assert.NoError(t, err)
173+
174+
data := []byte("some data")
175+
key.SetEncryptedDataKey(data)
176+
177+
res := key.ToMap()
178+
assert.Equal(t, dummyKey, res["key_id"])
179+
assert.Equal(t, key.CreationDate.UTC().Format(time.RFC3339), res["created_at"])
180+
assert.Equal(t, "some data", res["enc"])
181+
}
182+
183+
func createTestKMSClient() (*kms.SymmetricCryptoServiceClient, error) {
184+
return kms.NewKMSCrypto(func(ctx context.Context) (*grpc.ClientConn, error) {
185+
return grpc.DialContext(
186+
ctx,
187+
"bufnet",
188+
grpc.WithContextDialer(bufDialer),
189+
grpc.WithTransportCredentials(insecure.NewCredentials()),
190+
)
191+
}).SymmetricCrypto(), nil
192+
}

0 commit comments

Comments
 (0)