Browse Source

Introduce RetrySettings support for Hashicorp Vault (#2528)

* Ensure use of BuildKit in the Docker builds

The builds rely on `TARGETOS` and `TARGETARCH` being set, which is
automatically accomplished by the new builder.

Add the explicit envvar selector in the Makefile, until most users
update to docker 23+.

Signed-off-by: Andrea Stacchiotti <andreastacchiotti@gmail.com>

* Update docker build command in developer guide

Signed-off-by: Andrea Stacchiotti <andreastacchiotti@gmail.com>

* Introduce RetrySettings support for Hashicorp Vault

Leave default retries to 0 (not the default of the vault sdk of 2),
as this was decided in abec2a64cc236451c3233d58faaec0f1cc522572 .

Signed-off-by: Andrea Stacchiotti <andreastacchiotti@gmail.com>

---------

Signed-off-by: Andrea Stacchiotti <andreastacchiotti@gmail.com>
Andrea Stacchiotti 2 years ago
parent
commit
b50415edf0

+ 1 - 1
Makefile

@@ -225,7 +225,7 @@ docker.tag:  ## Emit IMAGE_TAG
 docker.build: $(addprefix build-,$(ARCH)) ## Build the docker image
 	@$(INFO) docker build
 	echo docker build -f $(DOCKERFILE) . $(DOCKER_BUILD_ARGS) -t $(IMAGE_NAME):$(IMAGE_TAG)
-	docker build -f $(DOCKERFILE) . $(DOCKER_BUILD_ARGS) -t $(IMAGE_NAME):$(IMAGE_TAG)
+	DOCKER_BUILDKIT=1 docker build -f $(DOCKERFILE) . $(DOCKER_BUILD_ARGS) -t $(IMAGE_NAME):$(IMAGE_TAG)
 	@$(OK) docker build
 
 .PHONY: docker.push

+ 1 - 1
docs/contributing/devguide.md

@@ -25,7 +25,7 @@ Building the operator binary and docker image:
 
 ```shell
 make build
-make docker.build IMG=external-secrets:latest
+make docker.build IMAGE_NAME=external-secrets IMAGE_TAG=latest
 ```
 
 Run tests and lint the code:

+ 3 - 2
docs/snippets/full-secret-store.yaml

@@ -14,7 +14,7 @@ spec:
   # You can specify retry settings for the http connection
   # these fields allow you to set a maxRetries before failure, and
   # an interval between the retries.
-  # Current supported providers: AWS, IBM
+  # Current supported providers: AWS, Hashicorp Vault, IBM
   retrySettings:
     maxRetries: 5
     retryInterval: "10s"
@@ -42,6 +42,7 @@ spec:
             name: awssm-secret
             key: secret-access-key
 
+    # (2) Hashicorp Vault
     vault:
       server: "https://vault.acme.org"
       # Path is the mount path of the Vault KV backend endpoint
@@ -89,7 +90,7 @@ spec:
             name: "my-secret"
             key: "vault"
 
-    # (2): GCP Secret Manager
+    # (3): GCP Secret Manager
     gcpsm:
       # Auth defines the information necessary to authenticate against GCP by getting
       # the credentials from an already created Kubernetes Secret.

+ 24 - 5
pkg/provider/vault/vault.go

@@ -27,6 +27,7 @@ import (
 	"reflect"
 	"strconv"
 	"strings"
+	"time"
 
 	"github.com/aws/aws-sdk-go/aws"
 	"github.com/aws/aws-sdk-go/aws/credentials"
@@ -233,7 +234,7 @@ func (c *Connector) newClient(ctx context.Context, store esv1beta1.GenericStore,
 	}
 	vaultSpec := storeSpec.Provider.Vault
 
-	vStore, cfg, err := c.prepareConfig(kube, corev1, vaultSpec, namespace, store.GetObjectKind().GroupVersionKind().Kind)
+	vStore, cfg, err := c.prepareConfig(kube, corev1, vaultSpec, storeSpec.RetrySettings, namespace, store.GetObjectKind().GroupVersionKind().Kind)
 	if err != nil {
 		return nil, err
 	}
@@ -247,7 +248,7 @@ func (c *Connector) newClient(ctx context.Context, store esv1beta1.GenericStore,
 }
 
 func (c *Connector) NewGeneratorClient(ctx context.Context, kube kclient.Client, corev1 typedcorev1.CoreV1Interface, vaultSpec *esv1beta1.VaultProvider, namespace string) (util.Client, error) {
-	vStore, cfg, err := c.prepareConfig(kube, corev1, vaultSpec, namespace, "Generator")
+	vStore, cfg, err := c.prepareConfig(kube, corev1, vaultSpec, nil, namespace, "Generator")
 	if err != nil {
 		return nil, err
 	}
@@ -265,7 +266,7 @@ func (c *Connector) NewGeneratorClient(ctx context.Context, kube kclient.Client,
 	return client, nil
 }
 
-func (c *Connector) prepareConfig(kube kclient.Client, corev1 typedcorev1.CoreV1Interface, vaultSpec *esv1beta1.VaultProvider, namespace, storeKind string) (*client, *vault.Config, error) {
+func (c *Connector) prepareConfig(kube kclient.Client, corev1 typedcorev1.CoreV1Interface, vaultSpec *esv1beta1.VaultProvider, retrySettings *esv1beta1.SecretStoreRetrySettings, namespace, storeKind string) (*client, *vault.Config, error) {
 	vStore := &client{
 		kube:      kube,
 		corev1:    corev1,
@@ -279,6 +280,26 @@ func (c *Connector) prepareConfig(kube kclient.Client, corev1 typedcorev1.CoreV1
 	if err != nil {
 		return nil, nil, err
 	}
+
+	// Setup retry options if present
+	if retrySettings != nil {
+		if retrySettings.MaxRetries != nil {
+			cfg.MaxRetries = int(*retrySettings.MaxRetries)
+		} else {
+			// By default we rely only on the reconciliation process for retrying
+			cfg.MaxRetries = 0
+		}
+
+		if retrySettings.RetryInterval != nil {
+			retryWait, err := time.ParseDuration(*retrySettings.RetryInterval)
+			if err != nil {
+				return nil, nil, err
+			}
+			cfg.MinRetryWait = retryWait
+			cfg.MaxRetryWait = retryWait
+		}
+	}
+
 	return vStore, cfg, nil
 }
 
@@ -998,8 +1019,6 @@ func (v *client) readSecret(ctx context.Context, path, version string) (map[stri
 func (v *client) newConfig() (*vault.Config, error) {
 	cfg := vault.DefaultConfig()
 	cfg.Address = v.store.Server
-	// In a controller-runtime context, we rely on the reconciliation process for retrying
-	cfg.MaxRetries = 0
 
 	if len(v.store.CABundle) == 0 && v.store.CAProvider == nil {
 		return cfg, nil

+ 32 - 0
pkg/provider/vault/vault_test.go

@@ -250,6 +250,38 @@ MIIFkTCCA3mgAwIBAgIUBEUg3m/WqAsWHG4Q/II3IePFfuowDQYJKoZIhvcNAQELBQAwWDELMAkGA1UE
 				err: errors.New(errVaultStore),
 			},
 		},
+		"InvalidRetrySettings": {
+			reason: "Should return error if given an invalid Retry Interval.",
+			args: args{
+				store: makeSecretStore(func(s *esv1beta1.SecretStore) {
+					s.Spec.RetrySettings = &esv1beta1.SecretStoreRetrySettings{
+						MaxRetries:    pointer.To(int32(3)),
+						RetryInterval: pointer.To("not-an-interval"),
+					}
+				}),
+			},
+			want: want{
+				err: errors.New("time: invalid duration \"not-an-interval\""),
+			},
+		},
+		"ValidRetrySettings": {
+			reason: "Should return a Vault provider with custom retry settings",
+			args: args{
+				store: makeSecretStore(func(s *esv1beta1.SecretStore) {
+					s.Spec.RetrySettings = &esv1beta1.SecretStoreRetrySettings{
+						MaxRetries:    pointer.To(int32(3)),
+						RetryInterval: pointer.To("10m"),
+					}
+				}),
+				ns:            "default",
+				kube:          clientfake.NewClientBuilder().Build(),
+				corev1:        utilfake.NewCreateTokenMock().WithToken("ok"),
+				newClientFunc: fake.ClientWithLoginMock,
+			},
+			want: want{
+				err: nil,
+			},
+		},
 		"AddVaultStoreCertsError": {
 			reason: "Should return error if given an invalid CA certificate.",
 			args: args{