crds_controller.go 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578
  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 implements controllers for handling Custom Resource Definitions.
  14. package crds
  15. import (
  16. "bytes"
  17. "context"
  18. "crypto/rand"
  19. "crypto/rsa"
  20. "crypto/tls"
  21. "crypto/x509"
  22. "crypto/x509/pkix"
  23. "encoding/pem"
  24. "errors"
  25. "fmt"
  26. "math/big"
  27. "net/http"
  28. "os"
  29. "path/filepath"
  30. "slices"
  31. "sync"
  32. "time"
  33. "github.com/external-secrets/external-secrets/runtime/esutils"
  34. "github.com/go-logr/logr"
  35. corev1 "k8s.io/api/core/v1"
  36. apiext "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
  37. "k8s.io/apimachinery/pkg/api/equality"
  38. "k8s.io/apimachinery/pkg/runtime"
  39. "k8s.io/apimachinery/pkg/types"
  40. "k8s.io/client-go/tools/record"
  41. ctrl "sigs.k8s.io/controller-runtime"
  42. "sigs.k8s.io/controller-runtime/pkg/client"
  43. "sigs.k8s.io/controller-runtime/pkg/controller"
  44. )
  45. const (
  46. certName = "tls.crt"
  47. keyName = "tls.key"
  48. caCertName = "ca.crt"
  49. caKeyName = "ca.key"
  50. certValidityDuration = 10 * 365 * 24 * time.Hour
  51. // LookaheadInterval defines the interval to look ahead for certificate expiration.
  52. LookaheadInterval = 90 * 24 * time.Hour
  53. errResNotReady = "resource not ready: %s"
  54. )
  55. // Reconciler implements a reconciliation handler for CRD controllers.
  56. type Reconciler struct {
  57. client.Client
  58. Log logr.Logger
  59. Scheme *runtime.Scheme
  60. recorder record.EventRecorder
  61. SvcName string
  62. SvcNamespace string
  63. SecretName string
  64. SecretNamespace string
  65. CrdResources []string
  66. dnsName string
  67. CAName string
  68. CAChainName string
  69. CAOrganization string
  70. RequeueInterval time.Duration
  71. // the controller is ready when all crds are injected
  72. // and the controller is elected as leader
  73. leaderChan <-chan struct{}
  74. leaderElected bool
  75. readyStatusMapMu *sync.Mutex
  76. readyStatusMap map[string]bool
  77. }
  78. // Opts defines configuration options for the CRD controller.
  79. type Opts struct {
  80. SvcName string
  81. SvcNamespace string
  82. SecretName string
  83. SecretNamespace string
  84. Resources []string
  85. }
  86. // New returns a new CRD controller instance.
  87. func New(k8sClient client.Client, scheme *runtime.Scheme, leaderChan <-chan struct{}, logger logr.Logger,
  88. interval time.Duration, opts Opts) *Reconciler {
  89. return &Reconciler{
  90. Client: k8sClient,
  91. Log: logger,
  92. Scheme: scheme,
  93. SvcName: opts.SvcName,
  94. SvcNamespace: opts.SvcNamespace,
  95. SecretName: opts.SecretName,
  96. SecretNamespace: opts.SecretNamespace,
  97. RequeueInterval: interval,
  98. CrdResources: opts.Resources,
  99. CAName: "external-secrets",
  100. CAOrganization: "external-secrets",
  101. leaderChan: leaderChan,
  102. readyStatusMapMu: &sync.Mutex{},
  103. readyStatusMap: map[string]bool{},
  104. }
  105. }
  106. // CertInfo holds certificate data information.
  107. type CertInfo struct {
  108. CertDir string
  109. CertName string
  110. KeyName string
  111. CAName string
  112. }
  113. // Reconcile handles the reconciliation logic for CRDs.
  114. func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
  115. log := r.Log.WithValues("CustomResourceDefinition", req.NamespacedName)
  116. if slices.Contains(r.CrdResources, req.NamespacedName.Name) {
  117. err := r.updateCRD(logr.NewContext(ctx, log), req)
  118. if err != nil {
  119. log.Error(err, "failed to inject conversion webhook")
  120. r.readyStatusMapMu.Lock()
  121. r.readyStatusMap[req.NamespacedName.Name] = false
  122. r.readyStatusMapMu.Unlock()
  123. return ctrl.Result{}, err
  124. }
  125. r.readyStatusMapMu.Lock()
  126. r.readyStatusMap[req.NamespacedName.Name] = true
  127. r.readyStatusMapMu.Unlock()
  128. }
  129. return ctrl.Result{RequeueAfter: r.RequeueInterval}, nil
  130. }
  131. // ReadyCheck reviews if all webhook configs have been injected into the CRDs
  132. // and if the referenced webhook service is ready.
  133. func (r *Reconciler) ReadyCheck(_ *http.Request) error {
  134. // skip readiness check if we're not leader
  135. // as we depend on caches and being able to reconcile Webhooks
  136. if !r.leaderElected {
  137. select {
  138. case <-r.leaderChan:
  139. r.leaderElected = true
  140. default:
  141. return nil
  142. }
  143. }
  144. if err := r.checkCRDs(); err != nil {
  145. return err
  146. }
  147. return esutils.CheckEndpointSlicesReady(context.TODO(), r.Client, r.SvcName, r.SvcNamespace)
  148. }
  149. func (r *Reconciler) checkCRDs() error {
  150. for _, res := range r.CrdResources {
  151. r.readyStatusMapMu.Lock()
  152. rdy := r.readyStatusMap[res]
  153. r.readyStatusMapMu.Unlock()
  154. if !rdy {
  155. return fmt.Errorf(errResNotReady, res)
  156. }
  157. }
  158. return nil
  159. }
  160. // SetupWithManager sets up the controller with the Manager.
  161. func (r *Reconciler) SetupWithManager(mgr ctrl.Manager, opts controller.Options) error {
  162. r.recorder = mgr.GetEventRecorderFor("custom-resource-definition")
  163. return ctrl.NewControllerManagedBy(mgr).
  164. WithOptions(opts).
  165. For(&apiext.CustomResourceDefinition{}).
  166. Complete(r)
  167. }
  168. func (r *Reconciler) updateCRD(ctx context.Context, req ctrl.Request) error {
  169. log := logr.FromContextOrDiscard(ctx)
  170. secret := corev1.Secret{}
  171. secretName := types.NamespacedName{
  172. Name: r.SecretName,
  173. Namespace: r.SecretNamespace,
  174. }
  175. err := r.Get(context.Background(), secretName, &secret)
  176. if err != nil {
  177. return err
  178. }
  179. var updatedResource apiext.CustomResourceDefinition
  180. if err := r.Get(ctx, req.NamespacedName, &updatedResource); err != nil {
  181. return err
  182. }
  183. before := updatedResource.DeepCopyObject()
  184. svc := types.NamespacedName{
  185. Name: r.SvcName,
  186. Namespace: r.SvcNamespace,
  187. }
  188. if err := injectService(&updatedResource, svc); err != nil {
  189. return err
  190. }
  191. r.dnsName = fmt.Sprintf("%v.%v.svc", r.SvcName, r.SvcNamespace)
  192. need, err := r.refreshCertIfNeeded(&secret)
  193. if err != nil {
  194. return err
  195. }
  196. if need {
  197. artifacts, err := buildArtifactsFromSecret(&secret)
  198. if err != nil {
  199. return err
  200. }
  201. if err := injectCert(&updatedResource, artifacts.CertPEM); err != nil {
  202. return err
  203. }
  204. }
  205. if !equality.Semantic.DeepEqual(before, &updatedResource) {
  206. if err := r.Update(ctx, &updatedResource); err != nil {
  207. return err
  208. }
  209. log.Info("updated crd")
  210. return nil
  211. }
  212. log.V(1).Info("crd is unchanged")
  213. return nil
  214. }
  215. func injectService(crd *apiext.CustomResourceDefinition, svc types.NamespacedName) error {
  216. if crd.Spec.Conversion != nil && crd.Spec.Conversion.Strategy == apiext.NoneConverter {
  217. return nil
  218. }
  219. if crd.Spec.Conversion == nil ||
  220. crd.Spec.Conversion.Webhook == nil ||
  221. crd.Spec.Conversion.Webhook.ClientConfig == nil ||
  222. crd.Spec.Conversion.Webhook.ClientConfig.Service == nil {
  223. return errors.New("unexpected crd conversion webhook config")
  224. }
  225. crd.Spec.Conversion.Webhook.ClientConfig.Service.Namespace = svc.Namespace
  226. crd.Spec.Conversion.Webhook.ClientConfig.Service.Name = svc.Name
  227. return nil
  228. }
  229. func injectCert(crd *apiext.CustomResourceDefinition, certPem []byte) error {
  230. if crd.Spec.Conversion != nil && crd.Spec.Conversion.Strategy == apiext.NoneConverter {
  231. return nil
  232. }
  233. if crd.Spec.Conversion == nil ||
  234. crd.Spec.Conversion.Webhook == nil ||
  235. crd.Spec.Conversion.Webhook.ClientConfig == nil {
  236. return errors.New("unexpected crd conversion webhook config")
  237. }
  238. crd.Spec.Conversion.Webhook.ClientConfig.CABundle = certPem
  239. return nil
  240. }
  241. // KeyPairArtifacts stores certificate key pair data.
  242. type KeyPairArtifacts struct {
  243. Cert *x509.Certificate
  244. Key *rsa.PrivateKey
  245. CertPEM []byte
  246. KeyPEM []byte
  247. }
  248. // populateSecret populates the secret with the given certificate and key data.
  249. func populateSecret(cert, key []byte, caArtifacts *KeyPairArtifacts, secret *corev1.Secret) {
  250. if secret.Data == nil {
  251. secret.Data = make(map[string][]byte)
  252. }
  253. secret.Data[caCertName] = caArtifacts.CertPEM
  254. secret.Data[caKeyName] = caArtifacts.KeyPEM
  255. secret.Data[certName] = cert
  256. secret.Data[keyName] = key
  257. }
  258. // ValidCert checks if the provided certificate is valid for the given DNS name.
  259. func ValidCert(caCert, cert, key []byte, dnsName string, at time.Time) (bool, error) {
  260. if len(caCert) == 0 || len(cert) == 0 || len(key) == 0 {
  261. return false, errors.New("empty cert")
  262. }
  263. pool := x509.NewCertPool()
  264. caDer, _ := pem.Decode(caCert)
  265. if caDer == nil {
  266. return false, errors.New("bad CA cert")
  267. }
  268. cac, err := x509.ParseCertificate(caDer.Bytes)
  269. if err != nil {
  270. return false, err
  271. }
  272. pool.AddCert(cac)
  273. _, err = tls.X509KeyPair(cert, key)
  274. if err != nil {
  275. return false, err
  276. }
  277. b, rest := pem.Decode(cert)
  278. if b == nil {
  279. return false, err
  280. }
  281. if len(rest) > 0 {
  282. intermediate, _ := pem.Decode(rest)
  283. inter, err := x509.ParseCertificate(intermediate.Bytes)
  284. if err != nil {
  285. return false, err
  286. }
  287. pool.AddCert(inter)
  288. }
  289. crt, err := x509.ParseCertificate(b.Bytes)
  290. if err != nil {
  291. return false, err
  292. }
  293. _, err = crt.Verify(x509.VerifyOptions{
  294. DNSName: dnsName,
  295. Roots: pool,
  296. CurrentTime: at,
  297. })
  298. if err != nil {
  299. return false, err
  300. }
  301. return true, nil
  302. }
  303. func lookaheadTime() time.Time {
  304. return time.Now().Add(LookaheadInterval)
  305. }
  306. func (r *Reconciler) validServerCert(caCert, cert, key []byte) bool {
  307. valid, err := ValidCert(caCert, cert, key, r.dnsName, lookaheadTime())
  308. if err != nil {
  309. return false
  310. }
  311. return valid
  312. }
  313. func (r *Reconciler) validCACert(cert, key []byte) bool {
  314. valid, err := ValidCert(cert, cert, key, r.CAName, lookaheadTime())
  315. if err != nil {
  316. return false
  317. }
  318. return valid
  319. }
  320. func (r *Reconciler) refreshCertIfNeeded(secret *corev1.Secret) (bool, error) {
  321. if secret.Data == nil || !r.validCACert(secret.Data[caCertName], secret.Data[caKeyName]) {
  322. if err := r.refreshCerts(true, secret); err != nil {
  323. return false, err
  324. }
  325. return true, nil
  326. }
  327. if !r.validServerCert(secret.Data[caCertName], secret.Data[certName], secret.Data[keyName]) {
  328. if err := r.refreshCerts(false, secret); err != nil {
  329. return false, err
  330. }
  331. return true, nil
  332. }
  333. return true, nil
  334. }
  335. func (r *Reconciler) refreshCerts(refreshCA bool, secret *corev1.Secret) error {
  336. var caArtifacts *KeyPairArtifacts
  337. now := time.Now()
  338. begin := now.Add(-1 * time.Hour)
  339. end := now.Add(certValidityDuration)
  340. if refreshCA {
  341. var err error
  342. caArtifacts, err = r.CreateCACert(begin, end)
  343. if err != nil {
  344. return err
  345. }
  346. } else {
  347. var err error
  348. caArtifacts, err = buildArtifactsFromSecret(secret)
  349. if err != nil {
  350. return err
  351. }
  352. }
  353. cert, key, err := r.CreateCertPEM(caArtifacts, begin, end)
  354. if err != nil {
  355. return err
  356. }
  357. return r.writeSecret(cert, key, caArtifacts, secret)
  358. }
  359. func buildArtifactsFromSecret(secret *corev1.Secret) (*KeyPairArtifacts, error) {
  360. caPem, ok := secret.Data[caCertName]
  361. if !ok {
  362. return nil, fmt.Errorf("cert secret is not well-formed, missing %s", caCertName)
  363. }
  364. keyPem, ok := secret.Data[caKeyName]
  365. if !ok {
  366. return nil, fmt.Errorf("cert secret is not well-formed, missing %s", caKeyName)
  367. }
  368. caDer, _ := pem.Decode(caPem)
  369. if caDer == nil {
  370. return nil, errors.New("bad CA cert")
  371. }
  372. caCert, err := x509.ParseCertificate(caDer.Bytes)
  373. if err != nil {
  374. return nil, err
  375. }
  376. keyDer, _ := pem.Decode(keyPem)
  377. if keyDer == nil {
  378. return nil, err
  379. }
  380. key, err := x509.ParsePKCS1PrivateKey(keyDer.Bytes)
  381. if err != nil {
  382. return nil, err
  383. }
  384. return &KeyPairArtifacts{
  385. Cert: caCert,
  386. CertPEM: caPem,
  387. KeyPEM: keyPem,
  388. Key: key,
  389. }, nil
  390. }
  391. // CreateCACert creates a new CA certificate.
  392. func (r *Reconciler) CreateCACert(begin, end time.Time) (*KeyPairArtifacts, error) {
  393. templ := &x509.Certificate{
  394. SerialNumber: big.NewInt(0),
  395. Subject: pkix.Name{
  396. CommonName: r.CAName,
  397. Organization: []string{r.CAOrganization},
  398. },
  399. DNSNames: []string{
  400. r.CAName,
  401. },
  402. NotBefore: begin,
  403. NotAfter: end,
  404. KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment | x509.KeyUsageCertSign,
  405. BasicConstraintsValid: true,
  406. IsCA: true,
  407. }
  408. key, err := rsa.GenerateKey(rand.Reader, 2048)
  409. if err != nil {
  410. return nil, err
  411. }
  412. der, err := x509.CreateCertificate(rand.Reader, templ, templ, key.Public(), key)
  413. if err != nil {
  414. return nil, err
  415. }
  416. certPEM, keyPEM, err := pemEncode(der, key)
  417. if err != nil {
  418. return nil, err
  419. }
  420. cert, err := x509.ParseCertificate(der)
  421. if err != nil {
  422. return nil, err
  423. }
  424. return &KeyPairArtifacts{Cert: cert, Key: key, CertPEM: certPEM, KeyPEM: keyPEM}, nil
  425. }
  426. // CreateCAChain creates a certificate chain using the provided CA.
  427. func (r *Reconciler) CreateCAChain(ca *KeyPairArtifacts, begin, end time.Time) (*KeyPairArtifacts, error) {
  428. templ := &x509.Certificate{
  429. SerialNumber: big.NewInt(2),
  430. Subject: pkix.Name{
  431. CommonName: r.CAChainName,
  432. Organization: []string{r.CAOrganization},
  433. },
  434. DNSNames: []string{
  435. r.CAChainName,
  436. },
  437. NotBefore: begin,
  438. NotAfter: end,
  439. KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment | x509.KeyUsageCertSign,
  440. BasicConstraintsValid: true,
  441. IsCA: true,
  442. }
  443. key, err := rsa.GenerateKey(rand.Reader, 2048)
  444. if err != nil {
  445. return nil, err
  446. }
  447. der, err := x509.CreateCertificate(rand.Reader, templ, ca.Cert, key.Public(), ca.Key)
  448. if err != nil {
  449. return nil, err
  450. }
  451. certPEM, keyPEM, err := pemEncode(der, key)
  452. if err != nil {
  453. return nil, err
  454. }
  455. cert, err := x509.ParseCertificate(der)
  456. if err != nil {
  457. return nil, err
  458. }
  459. return &KeyPairArtifacts{Cert: cert, Key: key, CertPEM: certPEM, KeyPEM: keyPEM}, nil
  460. }
  461. // CreateCertPEM creates a new certificate in PEM format.
  462. func (r *Reconciler) CreateCertPEM(ca *KeyPairArtifacts, begin, end time.Time) ([]byte, []byte, error) {
  463. templ := &x509.Certificate{
  464. SerialNumber: big.NewInt(1),
  465. Subject: pkix.Name{
  466. CommonName: r.dnsName,
  467. },
  468. DNSNames: []string{
  469. r.dnsName,
  470. },
  471. NotBefore: begin,
  472. NotAfter: end,
  473. KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment,
  474. ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
  475. BasicConstraintsValid: true,
  476. }
  477. key, err := rsa.GenerateKey(rand.Reader, 2048)
  478. if err != nil {
  479. return nil, nil, err
  480. }
  481. der, err := x509.CreateCertificate(rand.Reader, templ, ca.Cert, key.Public(), ca.Key)
  482. if err != nil {
  483. return nil, nil, err
  484. }
  485. certPEM, keyPEM, err := pemEncode(der, key)
  486. if err != nil {
  487. return nil, nil, err
  488. }
  489. return certPEM, keyPEM, nil
  490. }
  491. func pemEncode(certificateDER []byte, key *rsa.PrivateKey) ([]byte, []byte, error) {
  492. certBuf := &bytes.Buffer{}
  493. if err := pem.Encode(certBuf, &pem.Block{Type: "CERTIFICATE", Bytes: certificateDER}); err != nil {
  494. return nil, nil, err
  495. }
  496. keyBuf := &bytes.Buffer{}
  497. if err := pem.Encode(keyBuf, &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(key)}); err != nil {
  498. return nil, nil, err
  499. }
  500. return certBuf.Bytes(), keyBuf.Bytes(), nil
  501. }
  502. func (r *Reconciler) writeSecret(cert, key []byte, caArtifacts *KeyPairArtifacts, secret *corev1.Secret) error {
  503. populateSecret(cert, key, caArtifacts, secret)
  504. return r.Update(context.Background(), secret)
  505. }
  506. // CheckCerts verifies that certificates exist in a given fs location
  507. // and if they're valid.
  508. func CheckCerts(c CertInfo, dnsName string, at time.Time) error {
  509. certFile := filepath.Join(c.CertDir, c.CertName)
  510. _, err := os.Stat(certFile)
  511. if err != nil {
  512. return err
  513. }
  514. ca, err := os.ReadFile(filepath.Join(c.CertDir, c.CAName))
  515. if err != nil {
  516. return err
  517. }
  518. cert, err := os.ReadFile(filepath.Join(c.CertDir, c.CertName))
  519. if err != nil {
  520. return err
  521. }
  522. key, err := os.ReadFile(filepath.Join(c.CertDir, c.KeyName))
  523. if err != nil {
  524. return err
  525. }
  526. ok, err := ValidCert(ca, cert, key, dnsName, at)
  527. if err != nil {
  528. return err
  529. }
  530. if !ok {
  531. return errors.New("certificate is not valid")
  532. }
  533. return nil
  534. }