mirror of https://github.com/grafana/grafana
SecretService: Add decrypt service (#104472)
* initial decrypt files * service tests * fix lint * move interface to secret service folderpull/105171/head
parent
1fa74d2427
commit
1d19e3616f
@ -0,0 +1,36 @@ |
||||
package decrypt |
||||
|
||||
import ( |
||||
"context" |
||||
|
||||
"github.com/grafana/grafana/pkg/registry/apis/secret/contracts" |
||||
"github.com/grafana/grafana/pkg/registry/apis/secret/service" |
||||
"github.com/grafana/grafana/pkg/registry/apis/secret/xkube" |
||||
) |
||||
|
||||
type OSSDecryptService struct { |
||||
decryptStore contracts.DecryptStorage |
||||
} |
||||
|
||||
var _ service.DecryptService = &OSSDecryptService{} |
||||
|
||||
func ProvideDecryptService(decryptStore contracts.DecryptStorage) *OSSDecryptService { |
||||
return &OSSDecryptService{ |
||||
decryptStore: decryptStore, |
||||
} |
||||
} |
||||
|
||||
func (d *OSSDecryptService) Decrypt(ctx context.Context, namespace string, names ...string) (map[string]service.DecryptResult, error) { |
||||
results := make(map[string]service.DecryptResult, len(names)) |
||||
|
||||
for _, name := range names { |
||||
exposedSecureValue, err := d.decryptStore.Decrypt(ctx, xkube.Namespace(namespace), name) |
||||
if err != nil { |
||||
results[name] = service.NewDecryptResultErr(err) |
||||
} else { |
||||
results[name] = service.NewDecryptResultValue(&exposedSecureValue) |
||||
} |
||||
} |
||||
|
||||
return results, nil |
||||
} |
@ -0,0 +1,101 @@ |
||||
package decrypt |
||||
|
||||
import ( |
||||
"context" |
||||
"errors" |
||||
"testing" |
||||
|
||||
secretv0alpha1 "github.com/grafana/grafana/pkg/apis/secret/v0alpha1" |
||||
"github.com/grafana/grafana/pkg/registry/apis/secret/service" |
||||
"github.com/grafana/grafana/pkg/registry/apis/secret/xkube" |
||||
"github.com/stretchr/testify/mock" |
||||
"github.com/stretchr/testify/require" |
||||
) |
||||
|
||||
func TestDecryptService(t *testing.T) { |
||||
t.Parallel() |
||||
|
||||
ctx := context.Background() |
||||
|
||||
t.Run("when there are only errors from the storage, the service returns them in the map", func(t *testing.T) { |
||||
t.Parallel() |
||||
|
||||
mockErr := errors.New("mock error") |
||||
mockStorage := &MockDecryptStorage{} |
||||
mockStorage.On("Decrypt", mock.Anything, mock.Anything, mock.Anything).Return(secretv0alpha1.ExposedSecureValue(""), mockErr) |
||||
decryptedValuesResp := map[string]service.DecryptResult{ |
||||
"secure-value-1": service.NewDecryptResultErr(mockErr), |
||||
} |
||||
|
||||
decryptService := &OSSDecryptService{ |
||||
decryptStore: mockStorage, |
||||
} |
||||
|
||||
resp, err := decryptService.Decrypt(ctx, "default", "secure-value-1") |
||||
require.NotNil(t, resp) |
||||
require.NoError(t, err) |
||||
require.EqualValues(t, decryptedValuesResp, resp) |
||||
}) |
||||
|
||||
t.Run("when there is no error from the storage, it returns a map of the decrypted values", func(t *testing.T) { |
||||
t.Parallel() |
||||
|
||||
mockStorage := &MockDecryptStorage{} |
||||
// Set up the mock to return a different value for each name in the test
|
||||
exposedSecureValue1 := secretv0alpha1.NewExposedSecureValue("value1") |
||||
exposedSecureValue2 := secretv0alpha1.NewExposedSecureValue("value2") |
||||
mockStorage.On("Decrypt", mock.Anything, xkube.Namespace("default"), "secure-value-1"). |
||||
Return(exposedSecureValue1, nil) |
||||
mockStorage.On("Decrypt", mock.Anything, xkube.Namespace("default"), "secure-value-2"). |
||||
Return(exposedSecureValue2, nil) |
||||
|
||||
decryptedValuesResp := map[string]service.DecryptResult{ |
||||
"secure-value-1": service.NewDecryptResultValue(&exposedSecureValue1), |
||||
"secure-value-2": service.NewDecryptResultValue(&exposedSecureValue2), |
||||
} |
||||
|
||||
decryptService := &OSSDecryptService{ |
||||
decryptStore: mockStorage, |
||||
} |
||||
|
||||
resp, err := decryptService.Decrypt(ctx, "default", "secure-value-1", "secure-value-2") |
||||
require.NotNil(t, resp) |
||||
require.NoError(t, err) |
||||
require.EqualValues(t, decryptedValuesResp, resp) |
||||
}) |
||||
|
||||
t.Run("when there is an error from the storage, the service returns a map of errors and decrypted values", func(t *testing.T) { |
||||
t.Parallel() |
||||
|
||||
mockErr := errors.New("mock error") |
||||
mockStorage := &MockDecryptStorage{} |
||||
exposedSecureValue := secretv0alpha1.NewExposedSecureValue("value") |
||||
mockStorage.On("Decrypt", mock.Anything, xkube.Namespace("default"), "secure-value-1"). |
||||
Return(exposedSecureValue, nil) |
||||
mockStorage.On("Decrypt", mock.Anything, xkube.Namespace("default"), "secure-value-2"). |
||||
Return(secretv0alpha1.ExposedSecureValue(""), mockErr) |
||||
|
||||
decryptedValuesResp := map[string]service.DecryptResult{ |
||||
"secure-value-1": service.NewDecryptResultValue(&exposedSecureValue), |
||||
"secure-value-2": service.NewDecryptResultErr(mockErr), |
||||
} |
||||
|
||||
decryptService := &OSSDecryptService{ |
||||
decryptStore: mockStorage, |
||||
} |
||||
|
||||
resp, err := decryptService.Decrypt(ctx, "default", "secure-value-1", "secure-value-2") |
||||
require.NotNil(t, resp) |
||||
require.NoError(t, err) |
||||
require.EqualValues(t, decryptedValuesResp, resp) |
||||
}) |
||||
} |
||||
|
||||
type MockDecryptStorage struct { |
||||
mock.Mock |
||||
} |
||||
|
||||
func (m *MockDecryptStorage) Decrypt(ctx context.Context, namespace xkube.Namespace, name string) (secretv0alpha1.ExposedSecureValue, error) { |
||||
args := m.Called(ctx, namespace, name) |
||||
return args.Get(0).(secretv0alpha1.ExposedSecureValue), args.Error(1) |
||||
} |
@ -0,0 +1,36 @@ |
||||
package service |
||||
|
||||
import ( |
||||
"context" |
||||
|
||||
secretv0alpha1 "github.com/grafana/grafana/pkg/apis/secret/v0alpha1" |
||||
) |
||||
|
||||
// DecryptResult is the (union) result of a decryption operation.
|
||||
// It contains the decrypted `value` when the decryption succeeds, and the `err` when it fails.
|
||||
// It is not possible to construct a `DecryptResult` where both `value` and `err` are set from another package.
|
||||
type DecryptResult struct { |
||||
value *secretv0alpha1.ExposedSecureValue |
||||
err error |
||||
} |
||||
|
||||
func (d DecryptResult) Error() error { |
||||
return d.err |
||||
} |
||||
|
||||
func (d DecryptResult) Value() *secretv0alpha1.ExposedSecureValue { |
||||
return d.value |
||||
} |
||||
|
||||
func NewDecryptResultErr(err error) DecryptResult { |
||||
return DecryptResult{err: err} |
||||
} |
||||
|
||||
func NewDecryptResultValue(value *secretv0alpha1.ExposedSecureValue) DecryptResult { |
||||
return DecryptResult{value: value} |
||||
} |
||||
|
||||
// DecryptService is the inferface for the decrypt service.
|
||||
type DecryptService interface { |
||||
Decrypt(ctx context.Context, namespace string, names ...string) (map[string]DecryptResult, error) |
||||
} |
Loading…
Reference in new issue