feat(loopback): support dynamic credentials (#4982)

This commit is contained in:
Damian Debkowski authored and GitHub committed 2024-07-30 10:15:57 -07:00
1 parent 48038c3c33
commit 85b9adea81
3 files changed
+182 -23

No files matched your search

+9
View File
@@ -0,0 +1,9 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package loopback
const (
// ConstDynamicCredentials should be used as a key for an storage bucket attribute, to mock dynamic credentials.
ConstDynamicCredentials = "dynamic_credentials"
)
+33 -12
View File
@@ -145,11 +145,21 @@ func (l *LoopbackStorage) onCreateStorageBucket(ctx context.Context, req *plgpb.
return nil, status.Errorf(err.ErrCode, err.ErrMsg)
}
}
secrets := req.GetBucket().GetSecrets()
if secrets == nil {
secrets = &structpb.Struct{
Fields: make(map[string]*structpb.Value),
}
secrets := &structpb.Struct{
Fields: make(map[string]*structpb.Value),
}
var hasDynamicCreds bool
attrs := req.GetBucket().GetAttributes()
if attrs != nil {
_, hasDynamicCreds = attrs.Fields[ConstDynamicCredentials]
}
var hasStaticCreds bool
if req.GetBucket().GetSecrets() != nil && len(req.GetBucket().GetSecrets().AsMap()) > 0 {
hasStaticCreds = true
secrets = req.GetBucket().GetSecrets()
}
if hasDynamicCreds && hasStaticCreds {
return nil, status.Errorf(codes.InvalidArgument, "%s: cannot use both dynamic and static credentials", op)
}
return &plgpb.OnCreateStorageBucketResponse{
Persisted: &storagebuckets.StorageBucketPersisted{
@@ -179,14 +189,25 @@ func (l *LoopbackStorage) onUpdateStorageBucket(ctx context.Context, req *plgpb.
return nil, status.Errorf(err.ErrCode, "%s: %s", op, err.ErrMsg)
}
}
var secrets *structpb.Struct
if req.GetNewBucket().GetSecrets() != nil {
secrets := &structpb.Struct{
Fields: make(map[string]*structpb.Value),
}
var hasDynamicCreds bool
attrs := req.GetNewBucket().GetAttributes()
if attrs != nil {
_, hasDynamicCreds = attrs.Fields[ConstDynamicCredentials]
}
var hasStaticCreds bool
if req.GetNewBucket().GetSecrets() != nil && len(req.GetNewBucket().GetSecrets().AsMap()) > 0 {
hasStaticCreds = true
secrets = req.GetNewBucket().GetSecrets()
} else if req.GetPersisted().GetData() != nil {
secrets = req.GetPersisted().GetData()
} else {
secrets = &structpb.Struct{
Fields: make(map[string]*structpb.Value),
}
if hasDynamicCreds && hasStaticCreds {
return nil, status.Errorf(codes.InvalidArgument, "%s: cannot use both dynamic and static credentials", op)
}
if !hasDynamicCreds && len(secrets.AsMap()) == 0 {
if req.GetPersisted() != nil && req.GetPersisted().GetData() != nil && len(req.GetPersisted().GetData().AsMap()) > 0 {
secrets = req.GetPersisted().GetData()
}
}
return &plgpb.OnUpdateStorageBucketResponse{
+140 -11
View File
@@ -52,9 +52,11 @@ func TestLoopbackOnCreateStorageBucket(t *testing.T) {
}
tests := []struct {
name string
request *plgpb.OnCreateStorageBucketRequest
expectedErr codes.Code
name string
request *plgpb.OnCreateStorageBucketRequest
expectedErr codes.Code
expectedStaticCreds bool
expectedDynamicCreds bool
}{
{
name: "missing request",
@@ -104,6 +106,22 @@ func TestLoopbackOnCreateStorageBucket(t *testing.T) {
},
expectedErr: codes.PermissionDenied,
},
{
name: "error both credential types",
request: &plgpb.OnCreateStorageBucketRequest{
Bucket: &storagebuckets.StorageBucket{
BucketName: "aws_s3_mock",
Secrets: secrets,
Attributes: &structpb.Struct{
Fields: map[string]*structpb.Value{
"endpoint": structpb.NewStringValue("0.0.0.0"),
ConstDynamicCredentials: structpb.NewBoolValue(true),
},
},
},
},
expectedErr: codes.InvalidArgument,
},
{
name: "valid without credentials",
request: &plgpb.OnCreateStorageBucketRequest{
@@ -116,9 +134,25 @@ func TestLoopbackOnCreateStorageBucket(t *testing.T) {
},
},
},
expectedDynamicCreds: true,
},
{
name: "valid with credentials",
name: "valid with dynamic credentials",
request: &plgpb.OnCreateStorageBucketRequest{
Bucket: &storagebuckets.StorageBucket{
BucketName: "aws_s3_mock",
Attributes: &structpb.Struct{
Fields: map[string]*structpb.Value{
"endpoint": structpb.NewStringValue("0.0.0.0"),
ConstDynamicCredentials: structpb.NewBoolValue(true),
},
},
},
},
expectedDynamicCreds: true,
},
{
name: "valid with static credentials",
request: &plgpb.OnCreateStorageBucketRequest{
Bucket: &storagebuckets.StorageBucket{
BucketName: "aws_s3_mock",
@@ -130,6 +164,7 @@ func TestLoopbackOnCreateStorageBucket(t *testing.T) {
},
},
},
expectedStaticCreds: true,
},
}
for _, tt := range tests {
@@ -148,7 +183,17 @@ func TestLoopbackOnCreateStorageBucket(t *testing.T) {
require.NotNil(response)
assert.NotNil(response.GetPersisted())
assert.NotNil(response.GetPersisted().GetData())
assert.EqualValues(tt.request.GetBucket().GetSecrets().AsMap(), response.GetPersisted().GetData().AsMap())
if tt.expectedDynamicCreds {
assert.Empty(response.GetPersisted().GetData().AsMap())
return
}
if tt.expectedStaticCreds {
assert.NotEmpty(response.GetPersisted().GetData().AsMap())
assert.EqualValues(tt.request.GetBucket().GetSecrets().AsMap(), response.GetPersisted().GetData().AsMap())
return
}
})
}
}
@@ -181,9 +226,11 @@ func TestLoopbackOnUpdateStorageBucket(t *testing.T) {
}
tests := []struct {
name string
request *plgpb.OnUpdateStorageBucketRequest
expectedErr codes.Code
name string
request *plgpb.OnUpdateStorageBucketRequest
expectedErr codes.Code
expectedStaticCreds bool
expectedDynamicCreds bool
}{
{
name: "missing request",
@@ -236,7 +283,23 @@ func TestLoopbackOnUpdateStorageBucket(t *testing.T) {
expectedErr: codes.PermissionDenied,
},
{
name: "valid without credentials",
name: "error both credential types",
request: &plgpb.OnUpdateStorageBucketRequest{
NewBucket: &storagebuckets.StorageBucket{
BucketName: "aws_s3_mock",
Secrets: secrets,
Attributes: &structpb.Struct{
Fields: map[string]*structpb.Value{
"endpoint": structpb.NewStringValue("0.0.0.0"),
ConstDynamicCredentials: structpb.NewBoolValue(true),
},
},
},
},
expectedErr: codes.InvalidArgument,
},
{
name: "valid without static credentials",
request: &plgpb.OnUpdateStorageBucketRequest{
NewBucket: &storagebuckets.StorageBucket{
BucketName: "aws_s3_mock",
@@ -247,9 +310,25 @@ func TestLoopbackOnUpdateStorageBucket(t *testing.T) {
},
},
},
expectedDynamicCreds: true,
},
{
name: "valid with credentials",
name: "valid with dynamic credentials attribute",
request: &plgpb.OnUpdateStorageBucketRequest{
NewBucket: &storagebuckets.StorageBucket{
BucketName: "aws_s3_mock",
Attributes: &structpb.Struct{
Fields: map[string]*structpb.Value{
"endpoint": structpb.NewStringValue("0.0.0.0"),
ConstDynamicCredentials: structpb.NewBoolValue(true),
},
},
},
},
expectedDynamicCreds: true,
},
{
name: "valid with both persisted and static credentials",
request: &plgpb.OnUpdateStorageBucketRequest{
NewBucket: &storagebuckets.StorageBucket{
BucketName: "aws_s3_mock",
@@ -262,6 +341,37 @@ func TestLoopbackOnUpdateStorageBucket(t *testing.T) {
},
Persisted: persisted,
},
expectedStaticCreds: true,
},
{
name: "valid with only persisted static credentials",
request: &plgpb.OnUpdateStorageBucketRequest{
NewBucket: &storagebuckets.StorageBucket{
BucketName: "aws_s3_mock",
Attributes: &structpb.Struct{
Fields: map[string]*structpb.Value{
"endpoint": structpb.NewStringValue("0.0.0.0"),
},
},
},
Persisted: persisted,
},
expectedStaticCreds: true,
},
{
name: "valid with only static credentials",
request: &plgpb.OnUpdateStorageBucketRequest{
NewBucket: &storagebuckets.StorageBucket{
BucketName: "aws_s3_mock",
Secrets: secrets,
Attributes: &structpb.Struct{
Fields: map[string]*structpb.Value{
"endpoint": structpb.NewStringValue("0.0.0.0"),
},
},
},
},
expectedStaticCreds: true,
},
}
for _, tt := range tests {
@@ -280,7 +390,26 @@ func TestLoopbackOnUpdateStorageBucket(t *testing.T) {
require.NotNil(response)
assert.NotNil(response.GetPersisted())
assert.NotNil(response.GetPersisted().GetData())
assert.EqualValues(tt.request.GetNewBucket().GetSecrets().AsMap(), response.GetPersisted().GetData().AsMap())
if tt.expectedDynamicCreds {
assert.Empty(response.GetPersisted().GetData().AsMap())
return
}
if tt.expectedStaticCreds {
assert.NotEmpty(response.GetPersisted().GetData().AsMap())
newSecrets := tt.request.GetNewBucket().GetSecrets()
if newSecrets != nil && len(newSecrets.AsMap()) > 0 {
assert.EqualValues(newSecrets.AsMap(), response.GetPersisted().GetData().AsMap())
return
}
oldSecrets := tt.request.GetPersisted().GetData()
if oldSecrets != nil && len(oldSecrets.AsMap()) > 0 {
assert.EqualValues(oldSecrets.AsMap(), response.GetPersisted().GetData().AsMap())
return
}
return
}
})
}
}