crds_controller.go 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468
  1. /*
  2. Licensed under the Apache License, Version 2.0 (the "License");
  3. you may not use this file except in compliance with the License.
  4. You may obtain a copy of the License at
  5. http://www.apache.org/licenses/LICENSE-2.0
  6. Unless required by applicable law or agreed to in writing, software
  7. distributed under the License is distributed on an "AS IS" BASIS,
  8. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  9. See the License for the specific language governing permissions and
  10. limitations under the License.
  11. */
  12. package crds
  13. import (
  14. "bytes"
  15. "context"
  16. "crypto/rand"
  17. "crypto/rsa"
  18. "crypto/tls"
  19. "crypto/x509"
  20. "crypto/x509/pkix"
  21. "encoding/base64"
  22. "encoding/pem"
  23. "errors"
  24. "fmt"
  25. "math/big"
  26. "os"
  27. "time"
  28. "github.com/go-logr/logr"
  29. corev1 "k8s.io/api/core/v1"
  30. "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
  31. "k8s.io/apimachinery/pkg/runtime"
  32. "k8s.io/apimachinery/pkg/runtime/schema"
  33. "k8s.io/client-go/tools/record"
  34. ctrl "sigs.k8s.io/controller-runtime"
  35. "sigs.k8s.io/controller-runtime/pkg/client"
  36. "sigs.k8s.io/controller-runtime/pkg/controller"
  37. )
  38. const (
  39. certName = "tls.crt"
  40. keyName = "tls.key"
  41. caCertName = "ca.crt"
  42. caKeyName = "ca.key"
  43. rotationCheckFrequency = 12 * time.Hour
  44. certValidityDuration = 10 * 365 * 24 * time.Hour
  45. lookaheadInterval = 90 * 24 * time.Hour
  46. )
  47. type WebhookType int
  48. const (
  49. //ValidatingWebhook indicates the webhook is a ValidatingWebhook
  50. Validating WebhookType = iota
  51. //MutingWebhook indicates the webhook is a MutatingWebhook
  52. Mutating
  53. //CRDConversionWebhook indicates the webhook is a conversion webhook
  54. CRDConversion
  55. //APIServiceWebhook indicates the webhook is an extension API server
  56. APIService
  57. )
  58. type Reconciler struct {
  59. client.Client
  60. Log logr.Logger
  61. Scheme *runtime.Scheme
  62. recorder record.EventRecorder
  63. SvcLabels map[string]string
  64. SecretLabels map[string]string
  65. CrdResources []string
  66. CertDir string
  67. dnsName string
  68. CAName string
  69. CAOrganization string
  70. RestartOnSecretRefresh bool
  71. }
  72. type WebhookInfo struct {
  73. //Name is the name of the webhook for a validating or mutating webhook, or the CRD name in case of a CRD conversion webhook
  74. Name string
  75. Type WebhookType
  76. }
  77. func contains(s []string, e string) bool {
  78. for _, a := range s {
  79. if a == e {
  80. return true
  81. }
  82. }
  83. return false
  84. }
  85. func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
  86. log := r.Log.WithValues("CustomResourceDefinition", req.NamespacedName)
  87. if contains(r.CrdResources, req.NamespacedName.Name) {
  88. err := r.updateCRD(ctx, req)
  89. if err != nil {
  90. log.Error(err, "failed to inject conversion webhook")
  91. return ctrl.Result{}, err
  92. }
  93. }
  94. return ctrl.Result{}, nil
  95. }
  96. func (r *Reconciler) ConvertToWebhookInfo() []WebhookInfo {
  97. info := make([]WebhookInfo, len(r.CrdResources))
  98. for p, v := range r.CrdResources {
  99. r := WebhookInfo{
  100. Name: v,
  101. Type: CRDConversion,
  102. }
  103. info[p] = r
  104. }
  105. return info
  106. }
  107. func (r *Reconciler) SetupWithManager(mgr ctrl.Manager, opts controller.Options) error {
  108. crdGVK := schema.GroupVersionKind{Group: "apiextensions.k8s.io", Version: "v1", Kind: "CustomResourceDefinition"}
  109. res := &unstructured.Unstructured{}
  110. res.SetGroupVersionKind(crdGVK)
  111. r.recorder = mgr.GetEventRecorderFor("custom-resource-definition")
  112. return ctrl.NewControllerManagedBy(mgr).
  113. WithOptions(opts).
  114. For(res).
  115. Complete(r)
  116. }
  117. func (r *Reconciler) updateCRD(ctx context.Context, req ctrl.Request) error {
  118. crdGVK := schema.GroupVersionKind{Group: "apiextensions.k8s.io", Version: "v1", Kind: "CustomResourceDefinition"}
  119. svcList := corev1.ServiceList{}
  120. err := r.List(context.Background(), &svcList, client.MatchingLabels(r.SvcLabels))
  121. if err != nil {
  122. return err
  123. }
  124. if len(svcList.Items) != 1 {
  125. return errors.New("multiple services match labels")
  126. }
  127. secretList := corev1.SecretList{}
  128. err = r.List(context.Background(), &secretList, client.MatchingLabels(r.SecretLabels))
  129. if err != nil {
  130. return err
  131. }
  132. if len(secretList.Items) != 1 {
  133. return errors.New("multiple secrets match labels")
  134. }
  135. updatedResource := &unstructured.Unstructured{}
  136. updatedResource.SetGroupVersionKind(crdGVK)
  137. if err := r.Get(ctx, req.NamespacedName, updatedResource); err != nil {
  138. return err
  139. }
  140. if err := injectSvcToConversionWebhook(updatedResource, &svcList.Items[0]); err != nil {
  141. return err
  142. }
  143. r.dnsName = fmt.Sprintf("%v.%v.svc", svcList.Items[0].Name, svcList.Items[0].Namespace)
  144. need, err := r.refreshCertIfNeeded(&secretList.Items[0])
  145. if err != nil {
  146. return err
  147. }
  148. if need {
  149. artifacts, err := buildArtifactsFromSecret(&secretList.Items[0])
  150. if err != nil {
  151. return err
  152. }
  153. if err := injectCertToConversionWebhook(updatedResource, artifacts.CertPEM); err != nil {
  154. return err
  155. }
  156. }
  157. if err := r.Update(ctx, updatedResource); err != nil {
  158. return err
  159. }
  160. return nil
  161. }
  162. func (r *Reconciler) EnsureCertsMounted() bool {
  163. certFile := r.CertDir + "/" + certName
  164. _, err := os.Stat(certFile)
  165. return err == nil
  166. }
  167. func injectSvcToConversionWebhook(crd *unstructured.Unstructured, service *corev1.Service) error {
  168. _, found, err := unstructured.NestedMap(crd.Object, "spec", "conversion", "webhook", "clientConfig")
  169. if err != nil {
  170. return err
  171. }
  172. if !found {
  173. return errors.New("`conversion.webhook.clientConfig` field not found in CustomResourceDefinition")
  174. }
  175. if err := unstructured.SetNestedField(crd.Object, service.Name, "spec", "conversion", "webhook", "clientConfig", "service", "name"); err != nil {
  176. return err
  177. }
  178. if err := unstructured.SetNestedField(crd.Object, service.Namespace, "spec", "conversion", "webhook", "clientConfig", "service", "namespace"); err != nil {
  179. return err
  180. }
  181. return nil
  182. }
  183. func injectCertToConversionWebhook(crd *unstructured.Unstructured, certPem []byte) error {
  184. _, found, err := unstructured.NestedMap(crd.Object, "spec", "conversion", "webhook", "clientConfig")
  185. if err != nil {
  186. return err
  187. }
  188. if !found {
  189. return errors.New("`conversion.webhook.clientConfig` field not found in CustomResourceDefinition")
  190. }
  191. if err := unstructured.SetNestedField(crd.Object, base64.StdEncoding.EncodeToString(certPem), "spec", "conversion", "webhook", "clientConfig", "caBundle"); err != nil {
  192. return err
  193. }
  194. return nil
  195. }
  196. type KeyPairArtifacts struct {
  197. Cert *x509.Certificate
  198. Key *rsa.PrivateKey
  199. CertPEM []byte
  200. KeyPEM []byte
  201. }
  202. func populateSecret(cert, key []byte, caArtifacts *KeyPairArtifacts, secret *corev1.Secret) {
  203. if secret.Data == nil {
  204. secret.Data = make(map[string][]byte)
  205. }
  206. secret.Data[caCertName] = caArtifacts.CertPEM
  207. secret.Data[caKeyName] = caArtifacts.KeyPEM
  208. secret.Data[certName] = cert
  209. secret.Data[keyName] = key
  210. }
  211. func ValidCert(caCert, cert, key []byte, dnsName string, at time.Time) (bool, error) {
  212. if len(caCert) == 0 || len(cert) == 0 || len(key) == 0 {
  213. return false, errors.New("empty cert")
  214. }
  215. pool := x509.NewCertPool()
  216. caDer, _ := pem.Decode(caCert)
  217. if caDer == nil {
  218. return false, errors.New("bad CA cert")
  219. }
  220. cac, err := x509.ParseCertificate(caDer.Bytes)
  221. if err != nil {
  222. return false, err
  223. }
  224. pool.AddCert(cac)
  225. _, err = tls.X509KeyPair(cert, key)
  226. if err != nil {
  227. return false, err
  228. }
  229. b, _ := pem.Decode(cert)
  230. if b == nil {
  231. return false, err
  232. }
  233. crt, err := x509.ParseCertificate(b.Bytes)
  234. if err != nil {
  235. return false, err
  236. }
  237. _, err = crt.Verify(x509.VerifyOptions{
  238. DNSName: dnsName,
  239. Roots: pool,
  240. CurrentTime: at,
  241. })
  242. if err != nil {
  243. return false, err
  244. }
  245. return true, nil
  246. }
  247. func lookaheadTime() time.Time {
  248. return time.Now().Add(lookaheadInterval)
  249. }
  250. func (r *Reconciler) validServerCert(caCert, cert, key []byte) bool {
  251. valid, err := ValidCert(caCert, cert, key, r.dnsName, lookaheadTime())
  252. if err != nil {
  253. return false
  254. }
  255. return valid
  256. }
  257. func (r *Reconciler) validCACert(cert, key []byte) bool {
  258. valid, err := ValidCert(cert, cert, key, r.CAName, lookaheadTime())
  259. if err != nil {
  260. return false
  261. }
  262. return valid
  263. }
  264. func (r *Reconciler) refreshCertIfNeeded(secret *corev1.Secret) (bool, error) {
  265. if secret.Data == nil || !r.validCACert(secret.Data[caCertName], secret.Data[caKeyName]) {
  266. //crLog.Info("refreshing CA and server certs")
  267. if err := r.refreshCerts(true, secret); err != nil {
  268. //crLog.Error(err, "could not refresh CA and server certs")
  269. return false, nil
  270. }
  271. //crLog.Info("server certs refreshed")
  272. if r.RestartOnSecretRefresh {
  273. //crLog.Info("Secrets have been updated; exiting so pod can be restarted (This behaviour can be changed with the option RestartOnSecretRefresh)")
  274. os.Exit(0)
  275. }
  276. return true, nil
  277. }
  278. // make sure our reconciler is initialized on startup (either this or the above refreshCerts() will call this)
  279. if !r.validServerCert(secret.Data[caCertName], secret.Data[certName], secret.Data[keyName]) {
  280. //crLog.Info("refreshing server certs")
  281. if err := r.refreshCerts(false, secret); err != nil {
  282. //crLog.Error(err, "could not refresh server certs")
  283. return false, nil
  284. }
  285. //crLog.Info("server certs refreshed")
  286. if r.RestartOnSecretRefresh {
  287. //crLog.Info("Secrets have been updated; exiting so pod can be restarted (This behaviour can be changed with the option RestartOnSecretRefresh)")
  288. os.Exit(0)
  289. }
  290. return true, nil
  291. }
  292. //crLog.Info("no cert refresh needed")
  293. return true, nil
  294. }
  295. func (r *Reconciler) refreshCerts(refreshCA bool, secret *corev1.Secret) error {
  296. var caArtifacts *KeyPairArtifacts
  297. now := time.Now()
  298. begin := now.Add(-1 * time.Hour)
  299. end := now.Add(certValidityDuration)
  300. if refreshCA {
  301. var err error
  302. caArtifacts, err = r.CreateCACert(begin, end)
  303. if err != nil {
  304. return err
  305. }
  306. } else {
  307. var err error
  308. caArtifacts, err = buildArtifactsFromSecret(secret)
  309. if err != nil {
  310. return err
  311. }
  312. }
  313. cert, key, err := r.CreateCertPEM(caArtifacts, begin, end)
  314. if err != nil {
  315. return err
  316. }
  317. if err := r.writeSecret(cert, key, caArtifacts, secret); err != nil {
  318. return err
  319. }
  320. return nil
  321. }
  322. func buildArtifactsFromSecret(secret *corev1.Secret) (*KeyPairArtifacts, error) {
  323. caPem, ok := secret.Data[caCertName]
  324. if !ok {
  325. return nil, fmt.Errorf("cert secret is not well-formed, missing %s", caCertName)
  326. }
  327. keyPem, ok := secret.Data[caKeyName]
  328. if !ok {
  329. return nil, fmt.Errorf("cert secret is not well-formed, missing %s", caKeyName)
  330. }
  331. caDer, _ := pem.Decode(caPem)
  332. if caDer == nil {
  333. return nil, errors.New("bad CA cert")
  334. }
  335. caCert, err := x509.ParseCertificate(caDer.Bytes)
  336. if err != nil {
  337. return nil, err
  338. }
  339. keyDer, _ := pem.Decode(keyPem)
  340. if keyDer == nil {
  341. return nil, err
  342. }
  343. key, err := x509.ParsePKCS1PrivateKey(keyDer.Bytes)
  344. if err != nil {
  345. return nil, err
  346. }
  347. return &KeyPairArtifacts{
  348. Cert: caCert,
  349. CertPEM: caPem,
  350. KeyPEM: keyPem,
  351. Key: key,
  352. }, nil
  353. }
  354. func (r *Reconciler) CreateCACert(begin, end time.Time) (*KeyPairArtifacts, error) {
  355. templ := &x509.Certificate{
  356. SerialNumber: big.NewInt(0),
  357. Subject: pkix.Name{
  358. CommonName: r.CAName,
  359. Organization: []string{r.CAOrganization},
  360. },
  361. DNSNames: []string{
  362. r.CAName,
  363. },
  364. NotBefore: begin,
  365. NotAfter: end,
  366. KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment | x509.KeyUsageCertSign,
  367. BasicConstraintsValid: true,
  368. IsCA: true,
  369. }
  370. key, err := rsa.GenerateKey(rand.Reader, 2048)
  371. if err != nil {
  372. return nil, err
  373. }
  374. der, err := x509.CreateCertificate(rand.Reader, templ, templ, key.Public(), key)
  375. if err != nil {
  376. return nil, err
  377. }
  378. certPEM, keyPEM, err := pemEncode(der, key)
  379. if err != nil {
  380. return nil, err
  381. }
  382. cert, err := x509.ParseCertificate(der)
  383. if err != nil {
  384. return nil, err
  385. }
  386. return &KeyPairArtifacts{Cert: cert, Key: key, CertPEM: certPEM, KeyPEM: keyPEM}, nil
  387. }
  388. func (r *Reconciler) CreateCertPEM(ca *KeyPairArtifacts, begin, end time.Time) ([]byte, []byte, error) {
  389. templ := &x509.Certificate{
  390. SerialNumber: big.NewInt(1),
  391. Subject: pkix.Name{
  392. CommonName: r.dnsName,
  393. },
  394. DNSNames: []string{
  395. r.dnsName,
  396. },
  397. NotBefore: begin,
  398. NotAfter: end,
  399. KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment,
  400. ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
  401. BasicConstraintsValid: true,
  402. }
  403. key, err := rsa.GenerateKey(rand.Reader, 2048)
  404. if err != nil {
  405. return nil, nil, err
  406. }
  407. der, err := x509.CreateCertificate(rand.Reader, templ, ca.Cert, key.Public(), ca.Key)
  408. if err != nil {
  409. return nil, nil, err
  410. }
  411. certPEM, keyPEM, err := pemEncode(der, key)
  412. if err != nil {
  413. return nil, nil, err
  414. }
  415. return certPEM, keyPEM, nil
  416. }
  417. func pemEncode(certificateDER []byte, key *rsa.PrivateKey) ([]byte, []byte, error) {
  418. certBuf := &bytes.Buffer{}
  419. if err := pem.Encode(certBuf, &pem.Block{Type: "CERTIFICATE", Bytes: certificateDER}); err != nil {
  420. return nil, nil, err
  421. }
  422. keyBuf := &bytes.Buffer{}
  423. if err := pem.Encode(keyBuf, &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(key)}); err != nil {
  424. return nil, nil, err
  425. }
  426. return certBuf.Bytes(), keyBuf.Bytes(), nil
  427. }
  428. func (r *Reconciler) writeSecret(cert, key []byte, caArtifacts *KeyPairArtifacts, secret *corev1.Secret) error {
  429. populateSecret(cert, key, caArtifacts, secret)
  430. return r.Update(context.Background(), secret)
  431. }