common_test.go 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315
  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 providerstore
  14. import (
  15. "context"
  16. "crypto/rand"
  17. "crypto/rsa"
  18. "crypto/tls"
  19. "crypto/x509"
  20. "crypto/x509/pkix"
  21. "encoding/pem"
  22. "math/big"
  23. "net"
  24. "testing"
  25. "time"
  26. "github.com/go-logr/logr/testr"
  27. "github.com/stretchr/testify/assert"
  28. "github.com/stretchr/testify/require"
  29. "google.golang.org/grpc"
  30. "google.golang.org/grpc/credentials"
  31. corev1 "k8s.io/api/core/v1"
  32. metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  33. "k8s.io/apimachinery/pkg/runtime"
  34. utilruntime "k8s.io/apimachinery/pkg/util/runtime"
  35. clientgoscheme "k8s.io/client-go/kubernetes/scheme"
  36. ctrl "sigs.k8s.io/controller-runtime"
  37. "sigs.k8s.io/controller-runtime/pkg/client"
  38. fakeclient "sigs.k8s.io/controller-runtime/pkg/client/fake"
  39. esv1alpha1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1alpha1"
  40. esv2alpha1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v2alpha1"
  41. pb "github.com/external-secrets/external-secrets/proto/provider"
  42. )
  43. func providerStoreScheme(t *testing.T) *runtime.Scheme {
  44. t.Helper()
  45. scheme := runtime.NewScheme()
  46. utilruntime.Must(clientgoscheme.AddToScheme(scheme))
  47. utilruntime.Must(esv1alpha1.AddToScheme(scheme))
  48. utilruntime.Must(esv2alpha1.AddToScheme(scheme))
  49. return scheme
  50. }
  51. func newProviderStoreReconciler(t *testing.T, objects ...client.Object) *StoreReconciler {
  52. t.Helper()
  53. builder := fakeclient.NewClientBuilder().
  54. WithScheme(providerStoreScheme(t)).
  55. WithStatusSubresource(&esv2alpha1.ProviderStore{}, &esv2alpha1.ClusterProviderStore{}, &esv1alpha1.ClusterProviderClass{}).
  56. WithObjects(objects...)
  57. return &StoreReconciler{
  58. Client: builder.Build(),
  59. Log: testr.New(t),
  60. Scheme: providerStoreScheme(t),
  61. RequeueInterval: time.Minute,
  62. }
  63. }
  64. func newClusterProviderStoreReconciler(t *testing.T, objects ...client.Object) *ClusterStoreReconciler {
  65. t.Helper()
  66. builder := fakeclient.NewClientBuilder().
  67. WithScheme(providerStoreScheme(t)).
  68. WithStatusSubresource(&esv2alpha1.ProviderStore{}, &esv2alpha1.ClusterProviderStore{}, &esv1alpha1.ClusterProviderClass{}).
  69. WithObjects(objects...)
  70. return &ClusterStoreReconciler{
  71. Client: builder.Build(),
  72. Log: testr.New(t),
  73. Scheme: providerStoreScheme(t),
  74. RequeueInterval: time.Minute,
  75. }
  76. }
  77. func providerStoreReady(status esv2alpha1.ProviderStoreStatus) bool {
  78. for _, condition := range status.Conditions {
  79. if condition.Type == esv2alpha1.ProviderStoreReady && condition.Status == corev1.ConditionTrue {
  80. return true
  81. }
  82. }
  83. return false
  84. }
  85. type recordingProviderServer struct {
  86. pb.UnimplementedSecretStoreProviderServer
  87. }
  88. func newProviderStoreGRPCServer(t *testing.T) (*grpc.Server, string, map[string][]byte) {
  89. t.Helper()
  90. serverCert, serverKey, clientCert, clientKey, caCert := newMutualTLSArtifacts(t, "127.0.0.1")
  91. caPool := x509.NewCertPool()
  92. require.True(t, caPool.AppendCertsFromPEM(caCert))
  93. tlsCert, err := tls.X509KeyPair(serverCert, serverKey)
  94. require.NoError(t, err)
  95. lis, err := net.Listen("tcp", "127.0.0.1:0")
  96. require.NoError(t, err)
  97. grpcServer := grpc.NewServer(grpc.Creds(credentials.NewTLS(&tls.Config{
  98. MinVersion: tls.VersionTLS12,
  99. Certificates: []tls.Certificate{tlsCert},
  100. ClientCAs: caPool,
  101. ClientAuth: tls.RequireAndVerifyClientCert,
  102. })))
  103. pb.RegisterSecretStoreProviderServer(grpcServer, &recordingProviderServer{})
  104. go func() {
  105. _ = grpcServer.Serve(lis)
  106. }()
  107. t.Cleanup(func() {
  108. grpcServer.Stop()
  109. _ = lis.Close()
  110. })
  111. return grpcServer, lis.Addr().String(), map[string][]byte{
  112. "ca.crt": caCert,
  113. "client.crt": clientCert,
  114. "client.key": clientKey,
  115. }
  116. }
  117. func (s *recordingProviderServer) Validate(_ context.Context, _ *pb.ValidateRequest) (*pb.ValidateResponse, error) {
  118. return &pb.ValidateResponse{Valid: true}, nil
  119. }
  120. func newMutualTLSArtifacts(t *testing.T, host string) (serverCertPEM, serverKeyPEM, clientCertPEM, clientKeyPEM, caCertPEM []byte) {
  121. t.Helper()
  122. caKey, err := rsa.GenerateKey(rand.Reader, 2048)
  123. require.NoError(t, err)
  124. caTemplate := &x509.Certificate{
  125. SerialNumber: big.NewInt(1),
  126. Subject: pkix.Name{
  127. CommonName: "test-ca",
  128. },
  129. NotBefore: time.Now().Add(-time.Hour),
  130. NotAfter: time.Now().Add(time.Hour),
  131. KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageCRLSign,
  132. BasicConstraintsValid: true,
  133. IsCA: true,
  134. }
  135. caDER, err := x509.CreateCertificate(rand.Reader, caTemplate, caTemplate, &caKey.PublicKey, caKey)
  136. require.NoError(t, err)
  137. caCertPEM = pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: caDER})
  138. caKeyPEM := pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(caKey)})
  139. serverCertPEM, serverKeyPEM = newSignedCert(t, caDER, caKeyPEM, host, true)
  140. clientCertPEM, clientKeyPEM = newSignedCert(t, caDER, caKeyPEM, "provider-client", false)
  141. return serverCertPEM, serverKeyPEM, clientCertPEM, clientKeyPEM, caCertPEM
  142. }
  143. func newSignedCert(t *testing.T, caDER, caKeyPEM []byte, commonName string, isServer bool) ([]byte, []byte) {
  144. t.Helper()
  145. certKey, err := rsa.GenerateKey(rand.Reader, 2048)
  146. require.NoError(t, err)
  147. serialLimit := new(big.Int).Lsh(big.NewInt(1), 128)
  148. serialNumber, err := rand.Int(rand.Reader, serialLimit)
  149. require.NoError(t, err)
  150. template := &x509.Certificate{
  151. SerialNumber: serialNumber,
  152. Subject: pkix.Name{
  153. CommonName: commonName,
  154. },
  155. NotBefore: time.Now().Add(-time.Hour),
  156. NotAfter: time.Now().Add(time.Hour),
  157. KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment,
  158. ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth},
  159. }
  160. if isServer {
  161. template.ExtKeyUsage = []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}
  162. template.IPAddresses = []net.IP{net.ParseIP(commonName)}
  163. }
  164. caCert, err := x509.ParseCertificate(caDER)
  165. require.NoError(t, err)
  166. caKeyBlock, _ := pem.Decode(caKeyPEM)
  167. require.NotNil(t, caKeyBlock)
  168. caKey, err := x509.ParsePKCS1PrivateKey(caKeyBlock.Bytes)
  169. require.NoError(t, err)
  170. certDER, err := x509.CreateCertificate(rand.Reader, template, caCert, &certKey.PublicKey, caKey)
  171. require.NoError(t, err)
  172. certPEM := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: certDER})
  173. keyPEM := pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(certKey)})
  174. return certPEM, keyPEM
  175. }
  176. func readyRuntimeClass(name string) *esv1alpha1.ClusterProviderClass {
  177. return &esv1alpha1.ClusterProviderClass{
  178. ObjectMeta: metav1.ObjectMeta{Name: name},
  179. Status: esv1alpha1.ClusterProviderClassStatus{
  180. Conditions: []metav1.Condition{{
  181. Type: "Ready",
  182. Status: metav1.ConditionTrue,
  183. Reason: "Healthy",
  184. }},
  185. },
  186. }
  187. }
  188. func reconcileRequest(obj client.Object) ctrl.Request {
  189. return ctrl.Request{NamespacedName: client.ObjectKeyFromObject(obj)}
  190. }
  191. func TestFindProviderStoresForRuntimeClass(t *testing.T) {
  192. runtimeClass := &esv1alpha1.ClusterProviderClass{
  193. ObjectMeta: metav1.ObjectMeta{Name: "shared-runtime"},
  194. }
  195. reconciler := newProviderStoreReconciler(
  196. t,
  197. runtimeClass,
  198. &esv2alpha1.ProviderStore{
  199. ObjectMeta: metav1.ObjectMeta{Name: "implicit-kind", Namespace: "team-a"},
  200. Spec: esv2alpha1.ProviderStoreSpec{
  201. RuntimeRef: esv2alpha1.StoreRuntimeRef{Name: "shared-runtime"},
  202. },
  203. },
  204. &esv2alpha1.ProviderStore{
  205. ObjectMeta: metav1.ObjectMeta{Name: "explicit-kind", Namespace: "team-b"},
  206. Spec: esv2alpha1.ProviderStoreSpec{
  207. RuntimeRef: esv2alpha1.StoreRuntimeRef{
  208. Name: "shared-runtime",
  209. Kind: "ClusterProviderClass",
  210. },
  211. },
  212. },
  213. &esv2alpha1.ProviderStore{
  214. ObjectMeta: metav1.ObjectMeta{Name: "other-runtime", Namespace: "team-c"},
  215. Spec: esv2alpha1.ProviderStoreSpec{
  216. RuntimeRef: esv2alpha1.StoreRuntimeRef{Name: "other-runtime"},
  217. },
  218. },
  219. )
  220. requests := findProviderStoresForRuntimeClass(context.Background(), reconciler.Client, runtimeClass)
  221. assert.ElementsMatch(t, []ctrl.Request{
  222. {NamespacedName: client.ObjectKey{Name: "implicit-kind", Namespace: "team-a"}},
  223. {NamespacedName: client.ObjectKey{Name: "explicit-kind", Namespace: "team-b"}},
  224. }, requests)
  225. }
  226. func TestFindClusterProviderStoresForRuntimeClass(t *testing.T) {
  227. runtimeClass := &esv1alpha1.ClusterProviderClass{
  228. ObjectMeta: metav1.ObjectMeta{Name: "shared-runtime"},
  229. }
  230. reconciler := newClusterProviderStoreReconciler(
  231. t,
  232. runtimeClass,
  233. &esv2alpha1.ClusterProviderStore{
  234. ObjectMeta: metav1.ObjectMeta{Name: "implicit-kind"},
  235. Spec: esv2alpha1.ClusterProviderStoreSpec{
  236. RuntimeRef: esv2alpha1.StoreRuntimeRef{Name: "shared-runtime"},
  237. },
  238. },
  239. &esv2alpha1.ClusterProviderStore{
  240. ObjectMeta: metav1.ObjectMeta{Name: "explicit-kind"},
  241. Spec: esv2alpha1.ClusterProviderStoreSpec{
  242. RuntimeRef: esv2alpha1.StoreRuntimeRef{
  243. Name: "shared-runtime",
  244. Kind: "ClusterProviderClass",
  245. },
  246. },
  247. },
  248. &esv2alpha1.ClusterProviderStore{
  249. ObjectMeta: metav1.ObjectMeta{Name: "other-runtime"},
  250. Spec: esv2alpha1.ClusterProviderStoreSpec{
  251. RuntimeRef: esv2alpha1.StoreRuntimeRef{Name: "other-runtime"},
  252. },
  253. },
  254. )
  255. requests := findClusterProviderStoresForRuntimeClass(context.Background(), reconciler.Client, runtimeClass)
  256. assert.ElementsMatch(t, []ctrl.Request{
  257. {NamespacedName: client.ObjectKey{Name: "implicit-kind"}},
  258. {NamespacedName: client.ObjectKey{Name: "explicit-kind"}},
  259. }, requests)
  260. }