1
0
Fork 0
mirror of https://github.com/external-secrets/external-secrets.git synced 2024-12-15 17:51:01 +00:00

Implement Doppler Secret Push and Delete functions (#3200)

* Implement Doppler Secret Push and Delete functions

Signed-off-by: Carter Cook <carter.cook@filedeploy.com>

* Better error formatting (PR review #3200)

Signed-off-by: Carter Cook <carter.cook@filedeploy.com>

---------

Signed-off-by: Carter Cook <carter.cook@filedeploy.com>
This commit is contained in:
filedeploy 2024-03-06 04:35:18 -05:00 committed by GitHub
parent 6edd8d38dd
commit 1fbd7a01e1
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 240 additions and 12 deletions

View file

@ -35,8 +35,10 @@ import (
const (
customBaseURLEnvVar = "DOPPLER_BASE_URL"
verifyTLSOverrideEnvVar = "DOPPLER_VERIFY_TLS"
errGetSecret = "could not get secret %s: %s"
errGetSecrets = "could not get secrets %s"
errGetSecret = "could not get secret %s: %w"
errGetSecrets = "could not get secrets %w"
errDeleteSecrets = "could not delete secrets %s: %w"
errPushSecrets = "could not push secrets %s: %w"
errUnmarshalSecretMap = "unable to unmarshal secret %s: %w"
secretsDownloadFileKey = "DOPPLER_SECRETS_FILE"
errDopplerTokenSecretName = "missing auth.secretRef.dopplerToken.name"
@ -63,6 +65,7 @@ type SecretsClientInterface interface {
Authenticate() error
GetSecret(request dClient.SecretRequest) (*dClient.SecretResponse, error)
GetSecrets(request dClient.SecretsRequest) (*dClient.SecretsResponse, error)
UpdateSecrets(request dClient.UpdateSecretsRequest) error
}
func (c *Client) setAuth(ctx context.Context) error {
@ -94,12 +97,44 @@ func (c *Client) Validate() (esv1beta1.ValidationResult, error) {
return esv1beta1.ValidationResultReady, nil
}
func (c *Client) DeleteSecret(_ context.Context, _ esv1beta1.PushSecretRemoteRef) error {
return fmt.Errorf("not implemented")
func (c *Client) DeleteSecret(_ context.Context, ref esv1beta1.PushSecretRemoteRef) error {
request := dClient.UpdateSecretsRequest{
ChangeRequests: []dClient.Change{
{
Name: ref.GetRemoteKey(),
OriginalName: ref.GetRemoteKey(),
ShouldDelete: true,
},
},
Project: c.project,
Config: c.config,
}
err := c.doppler.UpdateSecrets(request)
if err != nil {
return fmt.Errorf(errDeleteSecrets, ref.GetRemoteKey(), err)
}
return nil
}
func (c *Client) PushSecret(_ context.Context, _ *corev1.Secret, _ esv1beta1.PushSecretData) error {
return fmt.Errorf("not implemented")
func (c *Client) PushSecret(_ context.Context, secret *corev1.Secret, data esv1beta1.PushSecretData) error {
value := secret.Data[data.GetSecretKey()]
request := dClient.UpdateSecretsRequest{
Secrets: dClient.Secrets{
data.GetRemoteKey(): string(value),
},
Project: c.project,
Config: c.config,
}
err := c.doppler.UpdateSecrets(request)
if err != nil {
return fmt.Errorf(errPushSecrets, data.GetRemoteKey(), err)
}
return nil
}
func (c *Client) GetSecret(_ context.Context, ref esv1beta1.ExternalSecretDataRemoteRef) ([]byte, error) {

View file

@ -41,7 +41,12 @@ type httpRequestBody []byte
type Secrets map[string]string
type RawSecrets map[string]*interface{}
type Change struct {
Name string `json:"name"`
OriginalName string `json:"originalName"`
Value *string `json:"value"`
ShouldDelete bool `json:"shouldDelete,omitempty"`
}
type APIError struct {
Err error
@ -74,9 +79,10 @@ type SecretsRequest struct {
}
type UpdateSecretsRequest struct {
Secrets RawSecrets `json:"secrets,omitempty"`
Project string `json:"project,omitempty"`
Config string `json:"config,omitempty"`
Secrets Secrets `json:"secrets,omitempty"`
ChangeRequests []Change `json:"change_requests,omitempty"`
Project string `json:"project,omitempty"`
Config string `json:"config,omitempty"`
}
type secretResponseBody struct {

View file

@ -21,7 +21,9 @@ import (
"testing"
"github.com/google/go-cmp/cmp"
corev1 "k8s.io/api/core/v1"
esv1alpha1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1alpha1"
esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
v1 "github.com/external-secrets/external-secrets/apis/meta/v1"
"github.com/external-secrets/external-secrets/pkg/provider/doppler/client"
@ -31,11 +33,15 @@ import (
const (
validSecretName = "API_KEY"
validSecretValue = "3a3ea4f5"
validRemoteKey = "REMOTE_KEY"
dopplerProject = "DOPPLER_PROJECT"
dopplerProjectVal = "auth-api"
missingSecret = "INVALID_NAME"
invalidSecret = "doppler_project"
invalidRemoteKey = "INVALID_REMOTE_KEY"
missingSecretErr = "could not get secret"
missingDeleteErr = "could not delete secrets"
missingPushErr = "could not push secrets"
)
type dopplerTestCase struct {
@ -50,12 +56,43 @@ type dopplerTestCase struct {
expectedData map[string][]byte
}
type updateSecretCase struct {
label string
fakeClient *fake.DopplerClient
request client.UpdateSecretsRequest
remoteRef *esv1alpha1.PushSecretRemoteRef
secret corev1.Secret
secretData esv1beta1.PushSecretData
apiErr error
expectError string
}
func makeValidAPIRequest() client.SecretRequest {
return client.SecretRequest{
Name: validSecretName,
}
}
func makeValidPushRequest() client.UpdateSecretsRequest {
return client.UpdateSecretsRequest{
Secrets: client.Secrets{
validRemoteKey: validSecretValue,
},
}
}
func makeValidDeleteRequest() client.UpdateSecretsRequest {
return client.UpdateSecretsRequest{
ChangeRequests: []client.Change{
{
Name: validRemoteKey,
OriginalName: validRemoteKey,
ShouldDelete: true,
},
},
}
}
func makeValidAPIOutput() *client.SecretResponse {
return &client.SecretResponse{
Name: validSecretName,
@ -69,6 +106,33 @@ func makeValidRemoteRef() *esv1beta1.ExternalSecretDataRemoteRef {
}
}
func makeValidPushRemoteRef() *esv1alpha1.PushSecretRemoteRef {
return &esv1alpha1.PushSecretRemoteRef{
RemoteKey: validRemoteKey,
}
}
func makeValidSecret() corev1.Secret {
return corev1.Secret{
Data: map[string][]byte{
validSecretName: []byte(validSecretValue),
},
}
}
func makeValidSecretData() esv1alpha1.PushSecretData {
return makeSecretData(validSecretName, *makeValidPushRemoteRef())
}
func makeSecretData(key string, ref esv1alpha1.PushSecretRemoteRef) esv1alpha1.PushSecretData {
return esv1alpha1.PushSecretData{
Match: esv1alpha1.PushSecretMatch{
SecretKey: key,
RemoteRef: ref,
},
}
}
func makeValidDopplerTestCase() *dopplerTestCase {
return &dopplerTestCase{
fakeClient: &fake.DopplerClient{},
@ -82,6 +146,17 @@ func makeValidDopplerTestCase() *dopplerTestCase {
}
}
func makeValidUpdateSecretTestCase() *updateSecretCase {
return &updateSecretCase{
fakeClient: &fake.DopplerClient{},
remoteRef: makeValidPushRemoteRef(),
secret: makeValidSecret(),
secretData: makeValidSecretData(),
apiErr: nil,
expectError: "",
}
}
func makeValidDopplerTestCaseCustom(tweaks ...func(pstc *dopplerTestCase)) *dopplerTestCase {
pstc := makeValidDopplerTestCase()
for _, fn := range tweaks {
@ -91,6 +166,15 @@ func makeValidDopplerTestCaseCustom(tweaks ...func(pstc *dopplerTestCase)) *dopp
return pstc
}
func makeValidUpdateSecretCaseCustom(tweaks ...func(pstc *updateSecretCase)) *updateSecretCase {
pstc := makeValidUpdateSecretTestCase()
for _, fn := range tweaks {
fn(pstc)
}
pstc.fakeClient.WithUpdateValue(pstc.request, pstc.apiErr)
return pstc
}
func TestGetSecret(t *testing.T) {
setSecret := func(pstc *dopplerTestCase) {
pstc.label = "set secret"
@ -120,7 +204,7 @@ func TestGetSecret(t *testing.T) {
}
setClientError := func(pstc *dopplerTestCase) {
pstc.label = "invalid client error"
pstc.label = "invalid client error" //nolint:goconst
pstc.response = &client.SecretResponse{}
pstc.expectError = missingSecretErr
pstc.apiErr = fmt.Errorf("")
@ -205,6 +289,93 @@ func ErrorContains(out error, want string) bool {
return strings.Contains(out.Error(), want)
}
func TestDeleteSecret(t *testing.T) {
deleteSecret := func(pstc *updateSecretCase) {
pstc.label = "delete secret"
pstc.request = makeValidDeleteRequest()
}
deleteMissingSecret := func(pstc *updateSecretCase) {
pstc.label = "delete missing secret"
pstc.request = makeValidDeleteRequest()
pstc.remoteRef.RemoteKey = invalidRemoteKey
pstc.expectError = missingDeleteErr
pstc.apiErr = fmt.Errorf("")
}
setClientError := func(pstc *updateSecretCase) {
pstc.label = "invalid client error"
pstc.request = makeValidDeleteRequest()
pstc.expectError = missingDeleteErr
pstc.apiErr = fmt.Errorf("")
}
testCases := []*updateSecretCase{
makeValidUpdateSecretCaseCustom(deleteSecret),
makeValidUpdateSecretCaseCustom(deleteMissingSecret),
makeValidUpdateSecretCaseCustom(setClientError),
}
c := Client{}
for k, tc := range testCases {
c.doppler = tc.fakeClient
err := c.DeleteSecret(context.Background(), tc.remoteRef)
if !ErrorContains(err, tc.expectError) {
t.Errorf("[%d] unexpected error: %s, expected: '%s'", k, err.Error(), tc.expectError)
}
}
}
func TestPushSecret(t *testing.T) {
pushSecret := func(pstc *updateSecretCase) {
pstc.label = "push secret"
pstc.request = makeValidPushRequest()
}
pushMissingSecretKey := func(pstc *updateSecretCase) {
pstc.label = "push missing secret key"
pstc.secretData = makeSecretData(invalidSecret, *makeValidPushRemoteRef())
pstc.expectError = missingPushErr
pstc.apiErr = fmt.Errorf("")
}
pushMissingRemoteSecret := func(pstc *updateSecretCase) {
pstc.label = "push missing remote secret"
pstc.secretData = makeSecretData(
validSecretName,
esv1alpha1.PushSecretRemoteRef{
RemoteKey: invalidRemoteKey,
},
)
pstc.expectError = missingPushErr
pstc.apiErr = fmt.Errorf("")
}
setClientError := func(pstc *updateSecretCase) {
pstc.label = "invalid client error"
pstc.expectError = missingPushErr
pstc.apiErr = fmt.Errorf("")
}
testCases := []*updateSecretCase{
makeValidUpdateSecretCaseCustom(pushSecret),
makeValidUpdateSecretCaseCustom(pushMissingSecretKey),
makeValidUpdateSecretCaseCustom(pushMissingRemoteSecret),
makeValidUpdateSecretCaseCustom(setClientError),
}
c := Client{}
for k, tc := range testCases {
c.doppler = tc.fakeClient
err := c.PushSecret(context.Background(), &tc.secret, tc.secretData)
if !ErrorContains(err, tc.expectError) {
t.Errorf("[%d] unexpected error: %s, expected: '%s'", k, err.Error(), tc.expectError)
}
}
}
type storeModifier func(*esv1beta1.SecretStore) *esv1beta1.SecretStore
func makeSecretStore(fn ...storeModifier) *esv1beta1.SecretStore {

View file

@ -24,7 +24,8 @@ import (
)
type DopplerClient struct {
getSecret func(request client.SecretRequest) (*client.SecretResponse, error)
getSecret func(request client.SecretRequest) (*client.SecretResponse, error)
updateSecrets func(request client.UpdateSecretsRequest) error
}
func (dc *DopplerClient) BaseURL() *url.URL {
@ -44,6 +45,10 @@ func (dc *DopplerClient) GetSecrets(_ client.SecretsRequest) (*client.SecretsRes
return &client.SecretsResponse{}, nil
}
func (dc *DopplerClient) UpdateSecrets(request client.UpdateSecretsRequest) error {
return dc.updateSecrets(request)
}
func (dc *DopplerClient) WithValue(request client.SecretRequest, response *client.SecretResponse, err error) {
if dc != nil {
dc.getSecret = func(requestIn client.SecretRequest) (*client.SecretResponse, error) {
@ -54,3 +59,14 @@ func (dc *DopplerClient) WithValue(request client.SecretRequest, response *clien
}
}
}
func (dc *DopplerClient) WithUpdateValue(request client.UpdateSecretsRequest, err error) {
if dc != nil {
dc.updateSecrets = func(requestIn client.UpdateSecretsRequest) error {
if !cmp.Equal(requestIn, request) {
return fmt.Errorf("unexpected test argument")
}
return err
}
}
}