k8s_connection_validate_test.go 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254
  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 esutils
  14. import (
  15. "strings"
  16. "testing"
  17. metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  18. esv1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1"
  19. esmeta "github.com/external-secrets/external-secrets/apis/meta/v1"
  20. )
  21. // storeOfKind returns a minimal GenericStore whose GetKind() reports the
  22. // requested kind. TypeMeta is set as well so the selector validators, which
  23. // read GetObjectKind().GroupVersionKind().Kind, agree with GetKind().
  24. func storeOfKind(kind string) esv1.GenericStore {
  25. if kind == esv1.ClusterSecretStoreKind {
  26. return &esv1.ClusterSecretStore{TypeMeta: metav1.TypeMeta{Kind: kind}}
  27. }
  28. return &esv1.SecretStore{TypeMeta: metav1.TypeMeta{Kind: kind}}
  29. }
  30. func TestIsReferentKubernetesAuth(t *testing.T) {
  31. tests := []struct {
  32. name string
  33. auth *esv1.KubernetesAuth
  34. want bool
  35. }{
  36. {name: "nil auth", auth: nil, want: false},
  37. {name: "empty auth", auth: &esv1.KubernetesAuth{}, want: false},
  38. {
  39. name: "cert clientCert without namespace is referent",
  40. auth: &esv1.KubernetesAuth{Cert: &esv1.CertAuth{
  41. ClientCert: esmeta.SecretKeySelector{Name: "c", Key: "tls.crt"},
  42. ClientKey: esmeta.SecretKeySelector{Name: "c", Key: "tls.key", Namespace: new("ns")},
  43. }},
  44. want: true,
  45. },
  46. {
  47. name: "cert clientKey without namespace is referent",
  48. auth: &esv1.KubernetesAuth{Cert: &esv1.CertAuth{
  49. ClientCert: esmeta.SecretKeySelector{Name: "c", Key: "tls.crt", Namespace: new("ns")},
  50. ClientKey: esmeta.SecretKeySelector{Name: "c", Key: "tls.key"},
  51. }},
  52. want: true,
  53. },
  54. {
  55. name: "cert with both namespaces set is not referent",
  56. auth: &esv1.KubernetesAuth{Cert: &esv1.CertAuth{
  57. ClientCert: esmeta.SecretKeySelector{Name: "c", Key: "tls.crt", Namespace: new("ns")},
  58. ClientKey: esmeta.SecretKeySelector{Name: "c", Key: "tls.key", Namespace: new("ns")},
  59. }},
  60. want: false,
  61. },
  62. {
  63. name: "serviceAccount without namespace is referent",
  64. auth: &esv1.KubernetesAuth{ServiceAccount: &esmeta.ServiceAccountSelector{Name: "sa"}},
  65. want: true,
  66. },
  67. {
  68. name: "serviceAccount with namespace is not referent",
  69. auth: &esv1.KubernetesAuth{ServiceAccount: &esmeta.ServiceAccountSelector{Name: "sa", Namespace: new("ns")}},
  70. want: false,
  71. },
  72. {
  73. name: "token without namespace is referent",
  74. auth: &esv1.KubernetesAuth{Token: &esv1.TokenAuth{BearerToken: esmeta.SecretKeySelector{Name: "t", Key: "token"}}},
  75. want: true,
  76. },
  77. {
  78. name: "token with namespace is not referent",
  79. auth: &esv1.KubernetesAuth{Token: &esv1.TokenAuth{BearerToken: esmeta.SecretKeySelector{Name: "t", Key: "token", Namespace: new("ns")}}},
  80. want: false,
  81. },
  82. }
  83. for _, tt := range tests {
  84. t.Run(tt.name, func(t *testing.T) {
  85. if got := IsReferentKubernetesAuth(tt.auth); got != tt.want {
  86. t.Errorf("IsReferentKubernetesAuth() = %v, want %v", got, tt.want)
  87. }
  88. })
  89. }
  90. }
  91. func TestValidateKubernetesConnection(t *testing.T) {
  92. tests := []struct {
  93. name string
  94. kind string
  95. server esv1.KubernetesServer
  96. auth *esv1.KubernetesAuth
  97. authRef *esmeta.SecretKeySelector
  98. wantErr bool
  99. errContains string
  100. wantWarning bool
  101. }{
  102. {
  103. name: "no CA and no authRef warns",
  104. kind: esv1.SecretStoreKind,
  105. wantWarning: true,
  106. },
  107. {
  108. name: "authRef suppresses the no-CA warning",
  109. kind: esv1.SecretStoreKind,
  110. authRef: &esmeta.SecretKeySelector{Name: "kubeconfig", Key: "config"},
  111. },
  112. {
  113. name: "CABundle suppresses the no-CA warning",
  114. kind: esv1.SecretStoreKind,
  115. server: esv1.KubernetesServer{CABundle: []byte("ca")},
  116. },
  117. {
  118. name: "SecretStore with CAProvider (no namespace) is valid",
  119. kind: esv1.SecretStoreKind,
  120. server: esv1.KubernetesServer{CAProvider: &esv1.CAProvider{Name: "ca"}},
  121. },
  122. {
  123. name: "ClusterSecretStore CAProvider requires namespace",
  124. kind: esv1.ClusterSecretStoreKind,
  125. server: esv1.KubernetesServer{CAProvider: &esv1.CAProvider{Name: "ca"}},
  126. wantErr: true,
  127. errContains: "CAProvider.namespace must not be empty",
  128. },
  129. {
  130. name: "SecretStore rejects CAProvider namespace",
  131. kind: esv1.SecretStoreKind,
  132. server: esv1.KubernetesServer{CAProvider: &esv1.CAProvider{Name: "ca", Namespace: new("ns")}},
  133. wantErr: true,
  134. errContains: "CAProvider.namespace must be empty",
  135. },
  136. {
  137. name: "ClusterSecretStore CAProvider with namespace is valid",
  138. kind: esv1.ClusterSecretStoreKind,
  139. server: esv1.KubernetesServer{CAProvider: &esv1.CAProvider{Name: "ca", Namespace: new("ns")}},
  140. },
  141. {
  142. name: "cert missing clientCert name",
  143. kind: esv1.SecretStoreKind,
  144. server: esv1.KubernetesServer{CABundle: []byte("ca")},
  145. auth: &esv1.KubernetesAuth{Cert: &esv1.CertAuth{ClientCert: esmeta.SecretKeySelector{Key: "tls.crt"}, ClientKey: esmeta.SecretKeySelector{Name: "c", Key: "tls.key"}}},
  146. wantErr: true,
  147. errContains: "ClientCert.Name cannot be empty",
  148. },
  149. {
  150. name: "cert missing clientCert key",
  151. kind: esv1.SecretStoreKind,
  152. server: esv1.KubernetesServer{CABundle: []byte("ca")},
  153. auth: &esv1.KubernetesAuth{Cert: &esv1.CertAuth{ClientCert: esmeta.SecretKeySelector{Name: "c"}, ClientKey: esmeta.SecretKeySelector{Name: "c", Key: "tls.key"}}},
  154. wantErr: true,
  155. errContains: "ClientCert.Key cannot be empty",
  156. },
  157. {
  158. name: "cert valid",
  159. kind: esv1.SecretStoreKind,
  160. server: esv1.KubernetesServer{CABundle: []byte("ca")},
  161. auth: &esv1.KubernetesAuth{Cert: &esv1.CertAuth{ClientCert: esmeta.SecretKeySelector{Name: "c", Key: "tls.crt"}, ClientKey: esmeta.SecretKeySelector{Name: "c", Key: "tls.key"}}},
  162. },
  163. {
  164. name: "token missing name",
  165. kind: esv1.SecretStoreKind,
  166. server: esv1.KubernetesServer{CABundle: []byte("ca")},
  167. auth: &esv1.KubernetesAuth{Token: &esv1.TokenAuth{BearerToken: esmeta.SecretKeySelector{Key: "token"}}},
  168. wantErr: true,
  169. errContains: "BearerToken.Name cannot be empty",
  170. },
  171. {
  172. name: "token missing key",
  173. kind: esv1.SecretStoreKind,
  174. server: esv1.KubernetesServer{CABundle: []byte("ca")},
  175. auth: &esv1.KubernetesAuth{Token: &esv1.TokenAuth{BearerToken: esmeta.SecretKeySelector{Name: "t"}}},
  176. wantErr: true,
  177. errContains: "BearerToken.Key cannot be empty",
  178. },
  179. {
  180. name: "token valid",
  181. kind: esv1.SecretStoreKind,
  182. server: esv1.KubernetesServer{CABundle: []byte("ca")},
  183. auth: &esv1.KubernetesAuth{Token: &esv1.TokenAuth{BearerToken: esmeta.SecretKeySelector{Name: "t", Key: "token"}}},
  184. },
  185. {
  186. name: "referent serviceAccount on ClusterSecretStore is allowed",
  187. kind: esv1.ClusterSecretStoreKind,
  188. server: esv1.KubernetesServer{CABundle: []byte("ca")},
  189. auth: &esv1.KubernetesAuth{ServiceAccount: &esmeta.ServiceAccountSelector{Name: "sa"}},
  190. },
  191. {
  192. // ClientCert has a cross-namespace selector on a SecretStore, which
  193. // the delegated ValidateSecretSelector rejects. Exercises the cert
  194. // selector error-propagation path.
  195. name: "cert selector namespace mismatch is rejected",
  196. kind: esv1.SecretStoreKind,
  197. server: esv1.KubernetesServer{CABundle: []byte("ca")},
  198. auth: &esv1.KubernetesAuth{
  199. Cert: &esv1.CertAuth{ClientCert: esmeta.SecretKeySelector{Name: "c", Key: "tls.crt", Namespace: new("other")}, ClientKey: esmeta.SecretKeySelector{Name: "c", Key: "tls.key"}},
  200. },
  201. wantErr: true,
  202. },
  203. {
  204. name: "token selector namespace mismatch is rejected",
  205. kind: esv1.SecretStoreKind,
  206. server: esv1.KubernetesServer{CABundle: []byte("ca")},
  207. auth: &esv1.KubernetesAuth{Token: &esv1.TokenAuth{BearerToken: esmeta.SecretKeySelector{Name: "t", Key: "token", Namespace: new("other")}}},
  208. wantErr: true,
  209. },
  210. {
  211. name: "serviceAccount selector namespace mismatch is rejected",
  212. kind: esv1.SecretStoreKind,
  213. server: esv1.KubernetesServer{CABundle: []byte("ca")},
  214. auth: &esv1.KubernetesAuth{ServiceAccount: &esmeta.ServiceAccountSelector{Name: "sa", Namespace: new("other")}},
  215. wantErr: true,
  216. },
  217. {
  218. name: "valid config without CA warns only",
  219. kind: esv1.SecretStoreKind,
  220. auth: &esv1.KubernetesAuth{Token: &esv1.TokenAuth{BearerToken: esmeta.SecretKeySelector{Name: "t", Key: "token"}}},
  221. wantWarning: true,
  222. },
  223. }
  224. for _, tt := range tests {
  225. t.Run(tt.name, func(t *testing.T) {
  226. store := storeOfKind(tt.kind)
  227. warnings, err := ValidateKubernetesConnection(store, tt.server, tt.auth, tt.authRef)
  228. if (err != nil) != tt.wantErr {
  229. t.Fatalf("ValidateKubernetesConnection() error = %v, wantErr %v", err, tt.wantErr)
  230. }
  231. if tt.errContains != "" && (err == nil || !strings.Contains(err.Error(), tt.errContains)) {
  232. t.Errorf("ValidateKubernetesConnection() error = %v, want contains %q", err, tt.errContains)
  233. }
  234. if tt.wantWarning {
  235. if len(warnings) != 1 || warnings[0] != WarnNoCAConfigured {
  236. t.Errorf("ValidateKubernetesConnection() warnings = %v, want [%q]", warnings, WarnNoCAConfigured)
  237. }
  238. } else if len(warnings) > 0 {
  239. t.Errorf("ValidateKubernetesConnection() unexpected warnings: %v", warnings)
  240. }
  241. })
  242. }
  243. }