clusterprovider.go 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251
  1. /*
  2. Copyright © The ESO Authors
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. https://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. package common
  14. import (
  15. "context"
  16. "fmt"
  17. "time"
  18. . "github.com/onsi/ginkgo/v2"
  19. . "github.com/onsi/gomega"
  20. corev1 "k8s.io/api/core/v1"
  21. apierrors "k8s.io/apimachinery/pkg/api/errors"
  22. metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  23. "k8s.io/apimachinery/pkg/types"
  24. "github.com/external-secrets/external-secrets-e2e/framework"
  25. esv1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1"
  26. )
  27. type ClusterProviderConfig struct {
  28. Name string
  29. AuthScope esv1.AuthenticationScope
  30. Conditions []esv1.ClusterSecretStoreCondition
  31. }
  32. type ClusterProviderExternalSecretHarness struct {
  33. Prepare func(tc *framework.TestCase, cfg ClusterProviderConfig) *ClusterProviderExternalSecretRuntime
  34. }
  35. type ClusterProviderExternalSecretRuntime struct {
  36. ClusterProviderName string
  37. Provider framework.SecretStoreProvider
  38. BreakAuth func()
  39. RepairAuth func()
  40. }
  41. func (r *ClusterProviderExternalSecretRuntime) SupportsAuthLifecycle() bool {
  42. return r != nil && r.BreakAuth != nil && r.RepairAuth != nil
  43. }
  44. func ClusterProviderManifestNamespace(f *framework.Framework, harness ClusterProviderExternalSecretHarness) (string, func(*framework.TestCase)) {
  45. return clusterProviderSyncCase(f, harness, "manifest", "manifest-value", esv1.AuthenticationScopeManifestNamespace)
  46. }
  47. func ClusterProviderProviderNamespace(f *framework.Framework, harness ClusterProviderExternalSecretHarness) (string, func(*framework.TestCase)) {
  48. return clusterProviderSyncCase(f, harness, "provider", "provider-value", esv1.AuthenticationScopeProviderNamespace)
  49. }
  50. func ClusterProviderManifestNamespaceRecovery(f *framework.Framework, harness ClusterProviderExternalSecretHarness) (string, func(*framework.TestCase)) {
  51. return clusterProviderRecoveryCase(f, harness, "manifest-recovery", "manifest-recovered", esv1.AuthenticationScopeManifestNamespace)
  52. }
  53. func ClusterProviderProviderNamespaceRecovery(f *framework.Framework, harness ClusterProviderExternalSecretHarness) (string, func(*framework.TestCase)) {
  54. return clusterProviderRecoveryCase(f, harness, "provider-recovery", "provider-recovered", esv1.AuthenticationScopeProviderNamespace)
  55. }
  56. func ClusterProviderDeniedByConditions(f *framework.Framework, harness ClusterProviderExternalSecretHarness) (string, func(*framework.TestCase)) {
  57. return "[common] should deny workload namespaces that do not match ClusterProvider conditions", func(tc *framework.TestCase) {
  58. targetSecretName := "denied-target"
  59. remoteSecretName := "denied-source"
  60. expectedMessage := "should-not-sync"
  61. tc.ExpectedSecret = nil
  62. tc.ExternalSecret.ObjectMeta.Name = "denied-external-secret"
  63. tc.ExternalSecret.Spec.Target.Name = targetSecretName
  64. tc.ExternalSecret.Spec.Data = []esv1.ExternalSecretData{{
  65. SecretKey: "value",
  66. RemoteRef: esv1.ExternalSecretDataRemoteRef{
  67. Key: remoteSecretName,
  68. Property: "value",
  69. },
  70. }}
  71. tc.Secrets = map[string]framework.SecretEntry{
  72. remoteSecretName: {Value: jsonSecretValue(expectedMessage)},
  73. }
  74. var runtime *ClusterProviderExternalSecretRuntime
  75. tc.Prepare = func(tc *framework.TestCase, _ framework.SecretStoreProvider) {
  76. runtime = harness.Prepare(tc, ClusterProviderConfig{
  77. Name: "deny",
  78. AuthScope: esv1.AuthenticationScopeManifestNamespace,
  79. Conditions: []esv1.ClusterSecretStoreCondition{{
  80. Namespaces: []string{"not-" + f.Namespace.Name},
  81. }},
  82. })
  83. applyClusterProviderExternalSecret(tc, runtime)
  84. }
  85. tc.AfterSync = func(_ framework.SecretStoreProvider, _ *corev1.Secret) {
  86. waitForExternalSecretStatus(tc.Framework, tc.ExternalSecret.Namespace, tc.ExternalSecret.Name, corev1.ConditionFalse)
  87. expectNoSecretInNamespace(tc.Framework, tc.ExternalSecret.Namespace, targetSecretName)
  88. expectEventMessage(
  89. tc.Framework,
  90. tc.ExternalSecret.Namespace,
  91. tc.ExternalSecret.Name,
  92. "ExternalSecret",
  93. fmt.Sprintf("using ClusterProvider %q is not allowed from namespace %q: denied by spec.conditions", runtime.ClusterProviderName, f.Namespace.Name),
  94. )
  95. }
  96. }
  97. }
  98. func clusterProviderSyncCase(f *framework.Framework, harness ClusterProviderExternalSecretHarness, name, expectedValue string, authScope esv1.AuthenticationScope) (string, func(*framework.TestCase)) {
  99. return fmt.Sprintf("[common] should use %s auth with ClusterProvider", authScope), func(tc *framework.TestCase) {
  100. targetSecretName := fmt.Sprintf("%s-target", name)
  101. remoteSecretName := fmt.Sprintf("%s-source", name)
  102. tc.ExternalSecret.ObjectMeta.Name = fmt.Sprintf("%s-external-secret", name)
  103. tc.ExternalSecret.Spec.Target.Name = targetSecretName
  104. tc.ExternalSecret.Spec.Data = []esv1.ExternalSecretData{{
  105. SecretKey: "value",
  106. RemoteRef: esv1.ExternalSecretDataRemoteRef{
  107. Key: remoteSecretName,
  108. Property: "value",
  109. },
  110. }}
  111. tc.Secrets = map[string]framework.SecretEntry{
  112. remoteSecretName: {Value: jsonSecretValue(expectedValue)},
  113. }
  114. tc.ExpectedSecret = &corev1.Secret{
  115. Type: corev1.SecretTypeOpaque,
  116. Data: map[string][]byte{
  117. "value": []byte(expectedValue),
  118. },
  119. }
  120. tc.Prepare = func(tc *framework.TestCase, _ framework.SecretStoreProvider) {
  121. runtime := harness.Prepare(tc, ClusterProviderConfig{
  122. Name: name,
  123. AuthScope: authScope,
  124. })
  125. applyClusterProviderExternalSecret(tc, runtime)
  126. }
  127. }
  128. }
  129. func clusterProviderRecoveryCase(f *framework.Framework, harness ClusterProviderExternalSecretHarness, name, expectedValue string, authScope esv1.AuthenticationScope) (string, func(*framework.TestCase)) {
  130. return fmt.Sprintf("[common] should recover after repairing ClusterProvider auth with %s scope", authScope), func(tc *framework.TestCase) {
  131. targetSecretName := fmt.Sprintf("%s-target", name)
  132. remoteSecretName := fmt.Sprintf("%s-source", name)
  133. tc.ExpectedSecret = nil
  134. tc.ExternalSecret.ObjectMeta.Name = fmt.Sprintf("%s-external-secret", name)
  135. tc.ExternalSecret.Spec.RefreshInterval = &metav1.Duration{Duration: time.Hour}
  136. tc.ExternalSecret.Spec.Target.Name = targetSecretName
  137. tc.ExternalSecret.Spec.Data = []esv1.ExternalSecretData{{
  138. SecretKey: "value",
  139. RemoteRef: esv1.ExternalSecretDataRemoteRef{
  140. Key: remoteSecretName,
  141. Property: "value",
  142. },
  143. }}
  144. tc.Secrets = map[string]framework.SecretEntry{
  145. remoteSecretName: {Value: jsonSecretValue(expectedValue)},
  146. }
  147. var runtime *ClusterProviderExternalSecretRuntime
  148. tc.Prepare = func(tc *framework.TestCase, _ framework.SecretStoreProvider) {
  149. runtime = harness.Prepare(tc, ClusterProviderConfig{
  150. Name: name,
  151. AuthScope: authScope,
  152. })
  153. Expect(runtime).NotTo(BeNil(), "cluster provider harness returned nil runtime")
  154. if !runtime.SupportsAuthLifecycle() {
  155. Skip(fmt.Sprintf("provider %q does not support auth lifecycle recovery hooks", runtime.ClusterProviderName))
  156. }
  157. applyClusterProviderExternalSecret(tc, runtime)
  158. runtime.BreakAuth()
  159. }
  160. tc.AfterSync = func(_ framework.SecretStoreProvider, _ *corev1.Secret) {
  161. waitForExternalSecretStatus(tc.Framework, tc.ExternalSecret.Namespace, tc.ExternalSecret.Name, corev1.ConditionFalse)
  162. expectNoSecretInNamespace(tc.Framework, tc.ExternalSecret.Namespace, targetSecretName)
  163. runtime.RepairAuth()
  164. waitForSecretData(tc.Framework, tc.ExternalSecret.Namespace, targetSecretName, map[string][]byte{
  165. "value": []byte(expectedValue),
  166. }, 30*time.Second)
  167. }
  168. }
  169. }
  170. func applyClusterProviderExternalSecret(tc *framework.TestCase, runtime *ClusterProviderExternalSecretRuntime) {
  171. tc.ProviderOverride = runtime.Provider
  172. tc.ExternalSecret.Spec.SecretStoreRef.Name = runtime.ClusterProviderName
  173. tc.ExternalSecret.Spec.SecretStoreRef.Kind = esv1.ClusterProviderKindStr
  174. }
  175. func waitForExternalSecretStatus(f *framework.Framework, namespace, name string, status corev1.ConditionStatus) {
  176. Eventually(func(g Gomega) {
  177. var externalSecret esv1.ExternalSecret
  178. g.Expect(f.CRClient.Get(context.Background(), types.NamespacedName{
  179. Name: name,
  180. Namespace: namespace,
  181. }, &externalSecret)).To(Succeed())
  182. condition := esv1.GetExternalSecretCondition(externalSecret.Status, esv1.ExternalSecretReady)
  183. g.Expect(condition).NotTo(BeNil())
  184. g.Expect(condition.Status).To(Equal(status))
  185. }, time.Minute, 5*time.Second).Should(Succeed())
  186. }
  187. func waitForSecretData(f *framework.Framework, namespace, name string, expected map[string][]byte, timeout time.Duration) {
  188. Eventually(func(g Gomega) {
  189. var syncedSecret corev1.Secret
  190. g.Expect(f.CRClient.Get(context.Background(), types.NamespacedName{
  191. Name: name,
  192. Namespace: namespace,
  193. }, &syncedSecret)).To(Succeed())
  194. g.Expect(syncedSecret.Type).To(Equal(corev1.SecretTypeOpaque))
  195. g.Expect(syncedSecret.Data).To(Equal(expected))
  196. }, timeout, 5*time.Second).Should(Succeed())
  197. }
  198. func expectNoSecretInNamespace(f *framework.Framework, namespace, name string) {
  199. Consistently(func() bool {
  200. var secret corev1.Secret
  201. err := f.CRClient.Get(context.Background(), types.NamespacedName{Name: name, Namespace: namespace}, &secret)
  202. return apierrors.IsNotFound(err)
  203. }, 10*time.Second, 5*time.Second).Should(BeTrue())
  204. }
  205. func expectEventMessage(f *framework.Framework, namespace, objectName, objectKind, expectedMessage string) {
  206. Eventually(func() string {
  207. events, err := f.KubeClientSet.CoreV1().Events(namespace).List(context.Background(), metav1.ListOptions{
  208. FieldSelector: "involvedObject.name=" + objectName + ",involvedObject.kind=" + objectKind,
  209. })
  210. Expect(err).NotTo(HaveOccurred())
  211. messages := make([]string, 0, len(events.Items))
  212. for _, event := range events.Items {
  213. if event.Message != "" {
  214. messages = append(messages, event.Message)
  215. }
  216. }
  217. return fmt.Sprintf("%v", messages)
  218. }, time.Minute, 5*time.Second).Should(ContainSubstring(expectedMessage))
  219. }
  220. func jsonSecretValue(value string) string {
  221. return fmt.Sprintf(`{"value":%q}`, value)
  222. }