Przeglądaj źródła

feat(testing): OpenBao e2e test (#6396)

* feat(testing): OpenBao e2e test

Signed-off-by: Philipp Stehle <philipp.stehle@secretz.io>

* fix review comment

Co-authored-by: Gustavo Fernandes de Carvalho <gustavo.carvalho@container-solutions.com>
Signed-off-by: Philipp Stehle <philipp.stehle@secretz.io>

---------

Signed-off-by: Philipp Stehle <philipp.stehle@secretz.io>
Co-authored-by: Gustavo Fernandes de Carvalho <gustavo.carvalho@container-solutions.com>
Philipp Stehle 3 tygodni temu
rodzic
commit
2ca3a06b18

+ 124 - 0
e2e/framework/addon/openbao.go

@@ -0,0 +1,124 @@
+/*
+Copyright © The ESO Authors
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    https://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package addon
+
+import (
+	"crypto/rand"
+	"fmt"
+	"path/filepath"
+
+	. "github.com/onsi/ginkgo/v2"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+
+	"github.com/external-secrets/external-secrets-e2e/framework/util"
+)
+
+type OpenBao struct {
+	chart     *HelmChart
+	Namespace string
+
+	InClusterURL string
+	LocalURL     string
+	RootToken    string
+
+	portForwarder *PortForward
+}
+
+func NewOpenBao() *OpenBao {
+	rootToken := rand.Text()
+
+	repo := "openbao"
+	return &OpenBao{
+		chart: &HelmChart{
+			Namespace:    "openbao",
+			ReleaseName:  "openbao",
+			Chart:        fmt.Sprintf("%s/openbao", repo),
+			ChartVersion: "0.28.3",
+			Repo: ChartRepo{
+				Name: repo,
+				URL:  "https://openbao.github.io/openbao-helm",
+			},
+			Args: []string{
+				"--create-namespace",
+			},
+			Values: []string{filepath.Join(AssetDir(), "openbao.values.yaml")},
+			Vars: []StringTuple{{
+				Key:   "server.dev.devRootToken",
+				Value: rootToken,
+			}},
+		},
+		Namespace: "openbao",
+		RootToken: rootToken,
+	}
+}
+
+func (l *OpenBao) Install() error {
+	if err := l.chart.Install(); err != nil {
+		return err
+	}
+
+	if err := l.initBao(); err != nil {
+		return err
+	}
+
+	return nil
+}
+
+func (l *OpenBao) initBao() error {
+	err := util.WaitForPodsReady(l.chart.config.KubeClientSet, 1, l.Namespace, metav1.ListOptions{
+		LabelSelector: "app.kubernetes.io/name=openbao",
+	})
+	if err != nil {
+		return fmt.Errorf("error waiting for OpenBao to be ready: %w", err)
+	}
+
+	// This e2e test provider uses a local port-forwarded to talk to the OpenBao API instead
+	// of using the kubernetes service. This allows us to run the e2e test suite locally.
+	l.portForwarder, err = NewPortForward(l.chart.config.KubeClientSet, l.chart.config.KubeConfig, "openbao", l.chart.Namespace, 8200)
+	if err != nil {
+		return err
+	}
+	if err := l.portForwarder.Start(); err != nil {
+		return err
+	}
+
+	l.InClusterURL = fmt.Sprintf("http://%s.%s.svc.cluster.local:8200", l.chart.ReleaseName, l.Namespace)
+	l.LocalURL = fmt.Sprintf("http://localhost:%d", l.portForwarder.localPort)
+
+	return nil
+}
+
+func (l *OpenBao) Logs() error {
+	return l.chart.Logs()
+}
+
+func (l *OpenBao) Uninstall() error {
+	if l.portForwarder != nil {
+		l.portForwarder.Close()
+		l.portForwarder = nil
+	}
+
+	if err := l.chart.Uninstall(); err != nil {
+		return err
+	}
+
+	return l.chart.config.KubeClientSet.CoreV1().Namespaces().Delete(GinkgoT().Context(), l.chart.Namespace, metav1.DeleteOptions{})
+}
+
+func (l *OpenBao) Setup(cfg *Config) error {
+	return l.chart.Setup(cfg)
+}

+ 3 - 1
e2e/framework/addon/port_forward.go

@@ -134,7 +134,9 @@ func (pf *PortForward) Start() error {
 }
 
 func (pf *PortForward) Close() {
-	pf.fwd.Close()
+	if pf.fwd != nil {
+		pf.fwd.Close()
+	}
 }
 
 // findAvailablePort finds an available local port

+ 6 - 0
e2e/k8s/openbao.values.yaml

@@ -0,0 +1,6 @@
+injector:
+  enabled: false
+
+server:
+  dev:
+    enabled: true

+ 1 - 0
e2e/suites/provider/cases/import.go

@@ -27,6 +27,7 @@ import (
 	_ "github.com/external-secrets/external-secrets-e2e/suites/provider/cases/fake"
 	_ "github.com/external-secrets/external-secrets-e2e/suites/provider/cases/gcp"
 	_ "github.com/external-secrets/external-secrets-e2e/suites/provider/cases/kubernetes"
+	_ "github.com/external-secrets/external-secrets-e2e/suites/provider/cases/openbao"
 	_ "github.com/external-secrets/external-secrets-e2e/suites/provider/cases/scaleway"
 	_ "github.com/external-secrets/external-secrets-e2e/suites/provider/cases/secretserver"
 	_ "github.com/external-secrets/external-secrets-e2e/suites/provider/cases/template"

+ 61 - 0
e2e/suites/provider/cases/openbao/openbao.go

@@ -0,0 +1,61 @@
+/*
+Copyright © The ESO Authors
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    https://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package openbao
+
+import (
+	"github.com/external-secrets/external-secrets-e2e/framework"
+	"github.com/external-secrets/external-secrets-e2e/framework/addon"
+	"github.com/external-secrets/external-secrets-e2e/suites/provider/cases/common"
+	. "github.com/onsi/ginkgo/v2"
+)
+
+const (
+	withTokenAuth = "with token auth"
+)
+
+var _ = Describe("[OpenBao]", Label("openbao"), Ordered, func() {
+	f := framework.New("openbao")
+	openbao := addon.NewOpenBao()
+	prov := newOpenBaoProvider(f, openbao)
+
+	BeforeAll(func() {
+		addon.InstallGlobalAddon(openbao)
+	})
+
+	DescribeTable("sync secrets",
+		framework.TableFuncWithExternalSecret(f, prov),
+		// uses token auth
+		framework.Compose(withTokenAuth, f, common.FindByName, useTokenAuth(prov)),
+		framework.Compose(withTokenAuth, f, common.FindByNameAndRewrite, useTokenAuth(prov)),
+		framework.Compose(withTokenAuth, f, common.JSONDataFromSync, useTokenAuth(prov)),
+		framework.Compose(withTokenAuth, f, common.JSONDataFromRewrite, useTokenAuth(prov)),
+		framework.Compose(withTokenAuth, f, common.JSONDataWithProperty, useTokenAuth(prov)),
+		framework.Compose(withTokenAuth, f, common.JSONDataWithTemplate, useTokenAuth(prov)),
+		framework.Compose(withTokenAuth, f, common.DataPropertyDockerconfigJSON, useTokenAuth(prov)),
+		framework.Compose(withTokenAuth, f, common.JSONDataWithoutTargetName, useTokenAuth(prov)),
+		framework.Compose(withTokenAuth, f, common.DecodingPolicySync, useTokenAuth(prov)),
+		framework.Compose(withTokenAuth, f, common.JSONDataWithTemplateFromLiteral, useTokenAuth(prov)),
+		framework.Compose(withTokenAuth, f, common.TemplateFromConfigmaps, useTokenAuth(prov)),
+	)
+})
+
+func useTokenAuth(prov *openBaoProvider) func(*framework.TestCase) {
+	return func(tc *framework.TestCase) {
+		prov.CreateTokenStore()
+		tc.ExternalSecret.Spec.SecretStoreRef.Name = tc.Framework.Namespace.Name
+	}
+}

+ 117 - 0
e2e/suites/provider/cases/openbao/provider.go

@@ -0,0 +1,117 @@
+/*
+Copyright © The ESO Authors
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    https://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package openbao
+
+import (
+	"fmt"
+	"io"
+	"net/http"
+	"strings"
+
+	"github.com/external-secrets/external-secrets-e2e/framework"
+	"github.com/external-secrets/external-secrets-e2e/framework/addon"
+	esv1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1"
+	esmeta "github.com/external-secrets/external-secrets/apis/meta/v1"
+	. "github.com/onsi/ginkgo/v2"
+	. "github.com/onsi/gomega"
+	v1 "k8s.io/api/core/v1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+)
+
+type openBaoProvider struct {
+	addon     *addon.OpenBao
+	framework *framework.Framework
+}
+
+const (
+	secretStorePath = "secret"
+)
+
+func newOpenBaoProvider(f *framework.Framework, addon *addon.OpenBao) *openBaoProvider {
+	return &openBaoProvider{
+		addon:     addon,
+		framework: f,
+	}
+}
+
+func (s *openBaoProvider) CreateSecret(key string, val framework.SecretEntry) {
+	s.updateSecret(http.MethodPost, fmt.Sprintf("v1/secret/data/%s", key), strings.NewReader(fmt.Sprintf(`{"data": %s}`, val.Value)), http.StatusOK)
+}
+
+func (s *openBaoProvider) DeleteSecret(key string) {
+	s.updateSecret(http.MethodDelete, fmt.Sprintf("v1/secret/metadata/%s", key), nil, http.StatusNoContent)
+}
+
+func (s *openBaoProvider) updateSecret(method string, path string, body io.Reader, expectedStatus int) {
+	req, err := http.NewRequest(method, fmt.Sprintf("%s/%s", s.addon.LocalURL, path), body)
+	Expect(err).ToNot(HaveOccurred())
+	req.Header.Add("X-Vault-Token", s.addon.RootToken)
+
+	res, err := http.DefaultClient.Do(req)
+	Expect(err).ToNot(HaveOccurred())
+	defer res.Body.Close()
+
+	ExpectWithOffset(1, res.StatusCode).To(Equal(expectedStatus), func() string {
+		body, err := io.ReadAll(res.Body)
+		if err != nil {
+			return fmt.Sprintf("http request failed: could not read response body: %v", err)
+		}
+		return fmt.Sprintf("http request failed: %s", string(body))
+	})
+}
+
+func makeStore(name, ns string, v *addon.OpenBao) *esv1.SecretStore {
+	return &esv1.SecretStore{
+		ObjectMeta: metav1.ObjectMeta{
+			Name:      name,
+			Namespace: ns,
+		},
+		Spec: esv1.SecretStoreSpec{
+			Provider: &esv1.SecretStoreProvider{
+				Vault: &esv1.VaultProvider{
+					Version: esv1.VaultKVStoreV2,
+					Path:    new(secretStorePath),
+					Server:  v.InClusterURL,
+				},
+			},
+		},
+	}
+}
+
+func (s openBaoProvider) CreateTokenStore() {
+	secret := &v1.Secret{
+		ObjectMeta: metav1.ObjectMeta{
+			Name:      "token-provider",
+			Namespace: s.framework.Namespace.Name,
+		},
+		Data: map[string][]byte{
+			"token": []byte(s.addon.RootToken),
+		},
+	}
+	secretStore := makeStore(s.framework.Namespace.Name, s.framework.Namespace.Name, s.addon)
+	secretStore.Spec.Provider.Vault.Auth = &esv1.VaultAuth{
+		TokenSecretRef: &esmeta.SecretKeySelector{
+			Name: secret.Name,
+			Key:  "token",
+		},
+	}
+
+	err := s.framework.CRClient.Create(GinkgoT().Context(), secret)
+	Expect(err).ToNot(HaveOccurred())
+	err = s.framework.CRClient.Create(GinkgoT().Context(), secretStore)
+	Expect(err).ToNot(HaveOccurred())
+}