|
|
@@ -18,6 +18,9 @@ package kubernetes
|
|
|
|
|
|
import (
|
|
|
"context"
|
|
|
+ "fmt"
|
|
|
+ "strings"
|
|
|
+ "time"
|
|
|
|
|
|
. "github.com/onsi/ginkgo/v2"
|
|
|
. "github.com/onsi/gomega"
|
|
|
@@ -28,6 +31,7 @@ import (
|
|
|
|
|
|
"github.com/external-secrets/external-secrets-e2e/framework"
|
|
|
frameworkv2 "github.com/external-secrets/external-secrets-e2e/framework/v2"
|
|
|
+ esv1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1"
|
|
|
esv1alpha1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1alpha1"
|
|
|
)
|
|
|
|
|
|
@@ -167,8 +171,116 @@ var _ = Describe("[kubernetes] v2 push secret", Label("kubernetes", "v2", "push-
|
|
|
return apierrors.IsNotFound(err)
|
|
|
}, defaultV2WaitTimeout, defaultV2PollInterval).Should(BeTrue())
|
|
|
})
|
|
|
+
|
|
|
+ It("pushes through a ClusterProvider when authenticationScope=ManifestNamespace", func() {
|
|
|
+ s := newClusterProviderV2Scenario(f, "push-manifest")
|
|
|
+ s.allowRemoteAccessFrom(s.workloadNamespace, "push-manifest")
|
|
|
+
|
|
|
+ sourceSecretName := fmt.Sprintf("%s-source", s.namePrefix)
|
|
|
+ remoteSecretName := fmt.Sprintf("%s-remote", s.namePrefix)
|
|
|
+ Expect(f.CRClient.Create(context.Background(), &corev1.Secret{
|
|
|
+ ObjectMeta: metav1.ObjectMeta{
|
|
|
+ Name: sourceSecretName,
|
|
|
+ Namespace: s.workloadNamespace,
|
|
|
+ },
|
|
|
+ Data: map[string][]byte{
|
|
|
+ "value": []byte("manifest-push-value"),
|
|
|
+ },
|
|
|
+ })).To(Succeed())
|
|
|
+
|
|
|
+ clusterProviderName := s.createClusterProvider("push-manifest", esv1.AuthenticationScopeManifestNamespace, nil)
|
|
|
+ frameworkv2.WaitForClusterProviderReady(f, clusterProviderName, defaultV2WaitTimeout)
|
|
|
+
|
|
|
+ pushSecretName := createClusterProviderPushSecret(f, s.workloadNamespace, clusterProviderName, sourceSecretName, remoteSecretName)
|
|
|
+ waitForPushSecretReady(f, pushSecretName)
|
|
|
+ waitForSecretValueInNamespace(f, s.remoteNamespace, remoteSecretName, "value", "manifest-push-value")
|
|
|
+ })
|
|
|
+
|
|
|
+ It("pushes through a ClusterProvider when authenticationScope=ProviderNamespace", func() {
|
|
|
+ s := newClusterProviderV2Scenario(f, "push-provider")
|
|
|
+ s.allowRemoteAccessFrom(s.providerNamespace, "push-provider")
|
|
|
+
|
|
|
+ sourceSecretName := fmt.Sprintf("%s-source", s.namePrefix)
|
|
|
+ remoteSecretName := fmt.Sprintf("%s-remote", s.namePrefix)
|
|
|
+ Expect(f.CRClient.Create(context.Background(), &corev1.Secret{
|
|
|
+ ObjectMeta: metav1.ObjectMeta{
|
|
|
+ Name: sourceSecretName,
|
|
|
+ Namespace: s.workloadNamespace,
|
|
|
+ },
|
|
|
+ Data: map[string][]byte{
|
|
|
+ "value": []byte("provider-push-value"),
|
|
|
+ },
|
|
|
+ })).To(Succeed())
|
|
|
+
|
|
|
+ clusterProviderName := s.createClusterProvider("push-provider", esv1.AuthenticationScopeProviderNamespace, nil)
|
|
|
+ frameworkv2.WaitForClusterProviderReady(f, clusterProviderName, defaultV2WaitTimeout)
|
|
|
+
|
|
|
+ pushSecretName := createClusterProviderPushSecret(f, s.workloadNamespace, clusterProviderName, sourceSecretName, remoteSecretName)
|
|
|
+ waitForPushSecretReady(f, pushSecretName)
|
|
|
+ waitForSecretValueInNamespace(f, s.remoteNamespace, remoteSecretName, "value", "provider-push-value")
|
|
|
+ })
|
|
|
+
|
|
|
+ It("denies PushSecrets from namespaces that do not match ClusterProvider conditions", func() {
|
|
|
+ s := newClusterProviderV2Scenario(f, "push-deny")
|
|
|
+ s.allowRemoteAccessFrom(s.workloadNamespace, "push-deny")
|
|
|
+
|
|
|
+ sourceSecretName := fmt.Sprintf("%s-source", s.namePrefix)
|
|
|
+ remoteSecretName := fmt.Sprintf("%s-remote", s.namePrefix)
|
|
|
+ Expect(f.CRClient.Create(context.Background(), &corev1.Secret{
|
|
|
+ ObjectMeta: metav1.ObjectMeta{
|
|
|
+ Name: sourceSecretName,
|
|
|
+ Namespace: s.workloadNamespace,
|
|
|
+ },
|
|
|
+ Data: map[string][]byte{
|
|
|
+ "value": []byte("should-not-push"),
|
|
|
+ },
|
|
|
+ })).To(Succeed())
|
|
|
+
|
|
|
+ clusterProviderName := s.createClusterProvider("push-deny", esv1.AuthenticationScopeManifestNamespace, []esv1.ClusterSecretStoreCondition{{
|
|
|
+ Namespaces: []string{"not-" + s.workloadNamespace},
|
|
|
+ }})
|
|
|
+ frameworkv2.WaitForClusterProviderReady(f, clusterProviderName, defaultV2WaitTimeout)
|
|
|
+
|
|
|
+ pushSecretName := createClusterProviderPushSecret(f, s.workloadNamespace, clusterProviderName, sourceSecretName, remoteSecretName)
|
|
|
+ waitForPushSecretErrored(f, pushSecretName)
|
|
|
+ expectNoSecretInNamespace(f, s.remoteNamespace, remoteSecretName)
|
|
|
+ expectPushSecretEvent(f, s.workloadNamespace, pushSecretName, fmt.Sprintf("using ClusterProvider %q is not allowed from namespace %q: denied by spec.conditions", clusterProviderName, s.workloadNamespace))
|
|
|
+ })
|
|
|
})
|
|
|
|
|
|
+func createClusterProviderPushSecret(f *framework.Framework, namespace, clusterProviderName, sourceSecretName, remoteSecretName string) string {
|
|
|
+ pushSecretName := fmt.Sprintf("%s-push-secret", remoteSecretName)
|
|
|
+ Expect(f.CRClient.Create(context.Background(), &esv1alpha1.PushSecret{
|
|
|
+ ObjectMeta: metav1.ObjectMeta{
|
|
|
+ Name: pushSecretName,
|
|
|
+ Namespace: namespace,
|
|
|
+ },
|
|
|
+ Spec: esv1alpha1.PushSecretSpec{
|
|
|
+ RefreshInterval: &metav1.Duration{Duration: defaultV2RefreshInterval},
|
|
|
+ SecretStoreRefs: []esv1alpha1.PushSecretStoreRef{{
|
|
|
+ Name: clusterProviderName,
|
|
|
+ Kind: esv1.ClusterProviderKindStr,
|
|
|
+ APIVersion: esv1.SchemeGroupVersion.String(),
|
|
|
+ }},
|
|
|
+ Selector: esv1alpha1.PushSecretSelector{
|
|
|
+ Secret: &esv1alpha1.PushSecretSecret{
|
|
|
+ Name: sourceSecretName,
|
|
|
+ },
|
|
|
+ },
|
|
|
+ Data: []esv1alpha1.PushSecretData{{
|
|
|
+ Match: esv1alpha1.PushSecretMatch{
|
|
|
+ SecretKey: "value",
|
|
|
+ RemoteRef: esv1alpha1.PushSecretRemoteRef{
|
|
|
+ RemoteKey: remoteSecretName,
|
|
|
+ Property: "value",
|
|
|
+ },
|
|
|
+ },
|
|
|
+ }},
|
|
|
+ },
|
|
|
+ })).To(Succeed())
|
|
|
+ return pushSecretName
|
|
|
+}
|
|
|
+
|
|
|
func waitForPushSecretReady(f *framework.Framework, name string) {
|
|
|
Eventually(func(g Gomega) {
|
|
|
var ps esv1alpha1.PushSecret
|
|
|
@@ -183,3 +295,50 @@ func waitForPushSecretReady(f *framework.Framework, name string) {
|
|
|
g.Expect(ready).To(BeTrue())
|
|
|
}, defaultV2WaitTimeout, defaultV2PollInterval).Should(Succeed())
|
|
|
}
|
|
|
+
|
|
|
+func waitForPushSecretErrored(f *framework.Framework, name string) {
|
|
|
+ Eventually(func(g Gomega) {
|
|
|
+ var ps esv1alpha1.PushSecret
|
|
|
+ g.Expect(f.CRClient.Get(context.Background(), types.NamespacedName{Name: name, Namespace: f.Namespace.Name}, &ps)).To(Succeed())
|
|
|
+ g.Expect(ps.Status.Conditions).NotTo(BeEmpty())
|
|
|
+ errored := false
|
|
|
+ for _, condition := range ps.Status.Conditions {
|
|
|
+ if condition.Type == esv1alpha1.PushSecretReady && condition.Status == corev1.ConditionFalse {
|
|
|
+ errored = true
|
|
|
+ }
|
|
|
+ }
|
|
|
+ g.Expect(errored).To(BeTrue())
|
|
|
+ }, defaultV2WaitTimeout, defaultV2PollInterval).Should(Succeed())
|
|
|
+}
|
|
|
+
|
|
|
+func waitForSecretValueInNamespace(f *framework.Framework, namespace, name, key, expectedValue string) {
|
|
|
+ Eventually(func(g Gomega) {
|
|
|
+ var secret corev1.Secret
|
|
|
+ g.Expect(f.CRClient.Get(context.Background(), types.NamespacedName{Name: name, Namespace: namespace}, &secret)).To(Succeed())
|
|
|
+ g.Expect(secret.Data).To(HaveKeyWithValue(key, []byte(expectedValue)))
|
|
|
+ }, defaultV2WaitTimeout, defaultV2PollInterval).Should(Succeed())
|
|
|
+}
|
|
|
+
|
|
|
+func expectNoSecretInNamespace(f *framework.Framework, namespace, name string) {
|
|
|
+ Consistently(func() bool {
|
|
|
+ var secret corev1.Secret
|
|
|
+ err := f.CRClient.Get(context.Background(), types.NamespacedName{Name: name, Namespace: namespace}, &secret)
|
|
|
+ return apierrors.IsNotFound(err)
|
|
|
+ }, 10*time.Second, defaultV2PollInterval).Should(BeTrue())
|
|
|
+}
|
|
|
+
|
|
|
+func expectPushSecretEvent(f *framework.Framework, namespace, pushSecretName, expectedMessage string) {
|
|
|
+ Eventually(func() string {
|
|
|
+ events, err := f.KubeClientSet.CoreV1().Events(namespace).List(context.Background(), metav1.ListOptions{
|
|
|
+ FieldSelector: "involvedObject.name=" + pushSecretName + ",involvedObject.kind=PushSecret",
|
|
|
+ })
|
|
|
+ Expect(err).NotTo(HaveOccurred())
|
|
|
+ messages := make([]string, 0, len(events.Items))
|
|
|
+ for _, event := range events.Items {
|
|
|
+ if event.Message != "" {
|
|
|
+ messages = append(messages, event.Message)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return strings.Join(messages, "\n")
|
|
|
+ }, defaultV2WaitTimeout, defaultV2PollInterval).Should(ContainSubstring(expectedMessage))
|
|
|
+}
|