crds_controller_test.go 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378
  1. /*
  2. Copyright © 2025 ESO Maintainer Team
  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 crds
  14. import (
  15. "bytes"
  16. "context"
  17. "crypto/rsa"
  18. "crypto/x509"
  19. "os"
  20. "testing"
  21. "time"
  22. corev1 "k8s.io/api/core/v1"
  23. apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
  24. metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  25. "k8s.io/apimachinery/pkg/types"
  26. ctrl "sigs.k8s.io/controller-runtime"
  27. client "sigs.k8s.io/controller-runtime/pkg/client/fake"
  28. )
  29. const (
  30. failedCreateCaCerts = "could not create ca certificates:%v"
  31. failedCreateCaChain = "could not create the intermediate certificates:%v"
  32. failedCreateServerCerts = "could not create server certificates:%v"
  33. invalidCerts = "generated certificates are invalid:%v,%v"
  34. dnsName = "foobar"
  35. tlscrt = "/tmp/tls"
  36. tlskey = "/tmp/key"
  37. cacrt = "/tmp/ca"
  38. )
  39. func newReconciler() Reconciler {
  40. return Reconciler{
  41. CrdResources: []string{"one", "two", "three"},
  42. SvcName: "foo",
  43. SvcNamespace: "default",
  44. SecretName: "foo",
  45. SecretNamespace: "default",
  46. }
  47. }
  48. func newService() corev1.Service {
  49. return corev1.Service{
  50. ObjectMeta: metav1.ObjectMeta{
  51. Name: "foo",
  52. Namespace: "default",
  53. Labels: map[string]string{"foo": "bar"},
  54. },
  55. }
  56. }
  57. func newSecret() corev1.Secret {
  58. return corev1.Secret{
  59. ObjectMeta: metav1.ObjectMeta{
  60. Name: "foo",
  61. Namespace: "default",
  62. Labels: map[string]string{"foo": "bar"},
  63. },
  64. }
  65. }
  66. func newCRD() apiextensionsv1.CustomResourceDefinition {
  67. return apiextensionsv1.CustomResourceDefinition{
  68. ObjectMeta: metav1.ObjectMeta{
  69. Name: "one",
  70. },
  71. Spec: apiextensionsv1.CustomResourceDefinitionSpec{
  72. Conversion: &apiextensionsv1.CustomResourceConversion{
  73. Strategy: "Webhook",
  74. Webhook: &apiextensionsv1.WebhookConversion{
  75. ConversionReviewVersions: []string{"v1"},
  76. ClientConfig: &apiextensionsv1.WebhookClientConfig{
  77. CABundle: []byte("test"),
  78. Service: &apiextensionsv1.ServiceReference{
  79. Name: "wrong",
  80. Namespace: "wrong",
  81. },
  82. },
  83. },
  84. },
  85. },
  86. }
  87. }
  88. func TestUpdateCRD(t *testing.T) {
  89. svc := newService()
  90. secret := newSecret()
  91. crd := newCRD()
  92. c := client.NewClientBuilder().WithObjects(&svc, &secret, &crd).Build()
  93. rec := newReconciler()
  94. rec.Client = c
  95. ctx := context.Background()
  96. req := ctrl.Request{
  97. NamespacedName: types.NamespacedName{
  98. Name: "one",
  99. },
  100. }
  101. err := rec.updateCRD(ctx, req)
  102. if err != nil {
  103. t.Fatalf("Failed updating CRD: %v", err)
  104. }
  105. var updatedCRD apiextensionsv1.CustomResourceDefinition
  106. var updatedSecret corev1.Secret
  107. err = c.Get(ctx, req.NamespacedName, &updatedCRD)
  108. if err != nil {
  109. t.Fatalf("Failed getting updated CRD: %v", err)
  110. }
  111. err = c.Get(ctx, types.NamespacedName{Name: secret.Name, Namespace: secret.Namespace}, &updatedSecret)
  112. if err != nil {
  113. t.Fatalf("Failed getting updated secret: %v", err)
  114. }
  115. if updatedCRD.Spec.Conversion.Webhook.ClientConfig.Service.Name != svc.Name {
  116. t.Fatalf("Failed updating CRD webhook service name: expected %v, got %v", svc.Name, updatedCRD.Spec.Conversion.Webhook.ClientConfig.Service.Name)
  117. }
  118. if !bytes.Equal(updatedSecret.Data[caCertName], updatedCRD.Spec.Conversion.Webhook.ClientConfig.CABundle) {
  119. t.Fatalf("Failed updating CRD webhook ca bundle: expected %v, got %v", string(updatedSecret.Data[caCertName]), string(updatedCRD.Spec.Conversion.Webhook.ClientConfig.CABundle))
  120. }
  121. err = rec.updateCRD(ctx, req)
  122. if err != nil {
  123. t.Fatalf("Failed updating CRD: %v", err)
  124. }
  125. resourceVersion := updatedCRD.ResourceVersion
  126. err = c.Get(ctx, req.NamespacedName, &updatedCRD)
  127. if err != nil {
  128. t.Fatalf("Failed getting updated CRD: %v", err)
  129. }
  130. if updatedCRD.ResourceVersion != resourceVersion {
  131. t.Errorf("expected no change in resource version: %v, got %v", resourceVersion, updatedCRD.ResourceVersion)
  132. }
  133. }
  134. func TestInjectSvcToConversionWebhook(t *testing.T) {
  135. svc := newService()
  136. crd := newCRD()
  137. name := types.NamespacedName{
  138. Name: svc.Name,
  139. Namespace: svc.Namespace,
  140. }
  141. err := injectService(&crd, name)
  142. if err != nil {
  143. t.Errorf("Failed: error when injecting: %v", err)
  144. }
  145. val := crd.Spec.Conversion.Webhook.ClientConfig.Service.Name
  146. if val != "foo" {
  147. t.Errorf("Wrong service name injected: %v", val)
  148. }
  149. val = crd.Spec.Conversion.Webhook.ClientConfig.Service.Namespace
  150. if val != "default" {
  151. t.Errorf("Wrong service namespace injected: %v", val)
  152. }
  153. }
  154. func TestInjectCertToConversionWebhook(t *testing.T) {
  155. certPEM := []byte("foobar")
  156. crd := newCRD()
  157. err := injectCert(&crd, certPEM)
  158. if err != nil {
  159. t.Errorf("Failed: error when injecting: %v", err)
  160. }
  161. if string(crd.Spec.Conversion.Webhook.ClientConfig.CABundle) != "foobar" {
  162. t.Errorf("Wrong certificate name injected: %v", string(crd.Spec.Conversion.Webhook.ClientConfig.CABundle))
  163. }
  164. }
  165. func TestPopulateSecret(t *testing.T) {
  166. secret := newSecret()
  167. caArtifacts := KeyPairArtifacts{
  168. Cert: &x509.Certificate{},
  169. Key: &rsa.PrivateKey{},
  170. CertPEM: []byte("foobarca"),
  171. KeyPEM: []byte("foobarcakey"),
  172. }
  173. cert := []byte("foobarcert")
  174. key := []byte("foobarkey")
  175. populateSecret(cert, key, &caArtifacts, &secret)
  176. if !bytes.Equal(secret.Data["tls.crt"], cert) {
  177. t.Errorf("secret value for tls.crt is wrong:%v", cert)
  178. }
  179. if !bytes.Equal(secret.Data["tls.key"], key) {
  180. t.Errorf("secret value for tls.key is wrong:%v", cert)
  181. }
  182. if !bytes.Equal(secret.Data["ca.crt"], caArtifacts.CertPEM) {
  183. t.Errorf("secret value for ca.crt is wrong:%v", cert)
  184. }
  185. if !bytes.Equal(secret.Data["ca.key"], caArtifacts.KeyPEM) {
  186. t.Errorf("secret value for ca.key is wrong:%v", cert)
  187. }
  188. }
  189. func TestCreateCACert(t *testing.T) {
  190. rec := newReconciler()
  191. caArtifacts, err := rec.CreateCACert(time.Now(), time.Now().AddDate(1, 0, 0))
  192. if err != nil {
  193. t.Errorf(failedCreateCaCerts, err)
  194. }
  195. if !rec.validCACert(caArtifacts.CertPEM, caArtifacts.KeyPEM) {
  196. t.Errorf(invalidCerts, caArtifacts.CertPEM, caArtifacts.KeyPEM)
  197. }
  198. }
  199. func TestCreateCertPEM(t *testing.T) {
  200. rec := newReconciler()
  201. caArtifacts, err := rec.CreateCACert(time.Now(), time.Now().AddDate(1, 0, 0))
  202. if err != nil {
  203. t.Fatalf(failedCreateCaCerts, err)
  204. }
  205. certPEM, keyPEM, err := rec.CreateCertPEM(caArtifacts, time.Now(), time.Now().AddDate(1, 0, 0))
  206. if err != nil {
  207. t.Errorf(failedCreateServerCerts, err)
  208. }
  209. if !rec.validServerCert(caArtifacts.CertPEM, certPEM, keyPEM) {
  210. t.Errorf(invalidCerts, certPEM, keyPEM)
  211. }
  212. }
  213. func TestValidCert(t *testing.T) {
  214. rec := newReconciler()
  215. rec.dnsName = dnsName
  216. caArtifacts, err := rec.CreateCACert(time.Now(), time.Now().AddDate(1, 0, 0))
  217. if err != nil {
  218. t.Fatalf(failedCreateCaCerts, err)
  219. }
  220. certPEM, keyPEM, err := rec.CreateCertPEM(caArtifacts, time.Now(), time.Now().AddDate(1, 0, 0))
  221. if err != nil {
  222. t.Errorf(failedCreateServerCerts, err)
  223. }
  224. ok, err := ValidCert(caArtifacts.CertPEM, certPEM, keyPEM, dnsName, time.Now())
  225. if err != nil {
  226. t.Errorf("error validating cert: %v", err)
  227. }
  228. if !ok {
  229. t.Errorf("certificate is invalid")
  230. }
  231. }
  232. func TestRefreshCertIfNeeded(t *testing.T) {
  233. rec := newReconciler()
  234. secret := newSecret()
  235. c := client.NewClientBuilder().WithObjects(&secret).Build()
  236. rec.Client = c
  237. rec.dnsName = dnsName
  238. caArtifacts, err := rec.CreateCACert(time.Now().AddDate(-1, 0, 0), time.Now().AddDate(0, -1, 0))
  239. if err != nil {
  240. t.Fatalf(failedCreateCaCerts, err)
  241. }
  242. certPEM, keyPEM, err := rec.CreateCertPEM(caArtifacts, time.Now(), time.Now().AddDate(1, 0, 0))
  243. if err != nil {
  244. t.Errorf(failedCreateServerCerts, err)
  245. }
  246. populateSecret(certPEM, keyPEM, caArtifacts, &secret)
  247. ok, err := rec.refreshCertIfNeeded(&secret)
  248. if err != nil {
  249. t.Errorf("could not verify refresh need: %v", err)
  250. }
  251. if !ok {
  252. t.Error("expected refresh true. got false")
  253. }
  254. ok, err = rec.refreshCertIfNeeded(&secret)
  255. if err != nil {
  256. t.Errorf("could not verify refresh need: %v", err)
  257. }
  258. if !ok {
  259. t.Error("expected refresh false. got true")
  260. }
  261. }
  262. func TestCheckCerts(t *testing.T) {
  263. rec := newReconciler()
  264. rec.dnsName = dnsName
  265. caArtifacts, err := rec.CreateCACert(time.Now().AddDate(0, 0, -1), time.Now().AddDate(0, 0, 2))
  266. if err != nil {
  267. t.Fatalf(failedCreateCaCerts, err)
  268. }
  269. certPEM, keyPEM, err := rec.CreateCertPEM(caArtifacts, time.Now(), time.Now().AddDate(0, 0, 1))
  270. if err != nil {
  271. t.Errorf(failedCreateServerCerts, err)
  272. }
  273. os.WriteFile(cacrt, caArtifacts.CertPEM, 0644)
  274. os.WriteFile(tlscrt, certPEM, 0644)
  275. os.WriteFile(tlskey, keyPEM, 0644)
  276. cert := CertInfo{
  277. CertDir: "/tmp",
  278. CertName: "tls",
  279. CAName: "ca",
  280. KeyName: "key",
  281. }
  282. err = CheckCerts(cert, rec.dnsName, time.Now())
  283. if err != nil {
  284. t.Errorf("error checking valid cert: %v", err)
  285. }
  286. err = CheckCerts(cert, rec.dnsName, time.Now().AddDate(-1, 0, 0))
  287. if err == nil {
  288. t.Error("expected failure due to expired certificate, got success")
  289. }
  290. err = CheckCerts(cert, "wrong", time.Now())
  291. if err == nil {
  292. t.Error("expected failure due to dns name got, success")
  293. }
  294. cert.CAName = "wrong"
  295. err = CheckCerts(cert, rec.dnsName, time.Now())
  296. if err == nil {
  297. t.Error("expected failure due to wrong certificate name, got success")
  298. }
  299. }
  300. func TestCheckCertChain(t *testing.T) {
  301. rec := newReconciler()
  302. rec.dnsName = dnsName
  303. caArtifacts, err := rec.CreateCACert(time.Now().AddDate(0, 0, -1), time.Now().AddDate(0, 0, 2))
  304. if err != nil {
  305. t.Fatalf(failedCreateCaCerts, err)
  306. }
  307. chainArtifacts, err := rec.CreateCAChain(caArtifacts, time.Now().AddDate(0, 0, -1), time.Now().AddDate(0, 0, 2))
  308. if err != nil {
  309. t.Fatalf(failedCreateCaChain, err)
  310. }
  311. certPEM, keyPEM, err := rec.CreateCertPEM(chainArtifacts, time.Now(), time.Now().AddDate(0, 0, 1))
  312. if err != nil {
  313. t.Errorf(failedCreateServerCerts, err)
  314. }
  315. _ = os.WriteFile(cacrt, caArtifacts.CertPEM, 0644)
  316. _ = os.WriteFile(tlscrt, certPEM, 0644)
  317. f, _ := os.OpenFile(tlscrt, os.O_APPEND|os.O_WRONLY, 0644)
  318. defer func() {
  319. _ = f.Close()
  320. }()
  321. if _, err = f.Write(chainArtifacts.CertPEM); err != nil {
  322. t.Errorf(failedCreateCaChain, err)
  323. }
  324. _ = os.WriteFile(tlskey, keyPEM, 0644)
  325. cert := CertInfo{
  326. CertDir: "/tmp",
  327. CertName: "tls",
  328. CAName: "ca",
  329. KeyName: "key",
  330. }
  331. err = CheckCerts(cert, rec.dnsName, time.Now())
  332. if err != nil {
  333. t.Errorf("error checking valid cert: %v", err)
  334. }
  335. err = CheckCerts(cert, rec.dnsName, time.Now().AddDate(-1, 0, 0))
  336. if err == nil {
  337. t.Error("expected failure due to expired certificate, got success")
  338. }
  339. err = CheckCerts(cert, "wrong", time.Now())
  340. if err == nil {
  341. t.Error("expected failure due to dns name got, success")
  342. }
  343. cert.CAName = "wrong"
  344. err = CheckCerts(cert, rec.dnsName, time.Now())
  345. if err == nil {
  346. t.Error("expected failure due to wrong certificate name, got success")
  347. }
  348. }