github.com/verrazzano/verrazzano@v1.7.0/cluster-operator/internal/certificate/certificate.go (about) 1 // Copyright (c) 2022, 2023, Oracle and/or its affiliates. 2 // Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl. 3 4 package certificate 5 6 import ( 7 "bytes" 8 "context" 9 cryptorand "crypto/rand" 10 "crypto/rsa" 11 "crypto/x509" 12 "crypto/x509/pkix" 13 "encoding/pem" 14 "fmt" 15 "math/big" 16 "os" 17 "time" 18 19 "go.uber.org/zap" 20 v1 "k8s.io/api/core/v1" 21 "k8s.io/apimachinery/pkg/api/errors" 22 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 23 "k8s.io/client-go/kubernetes" 24 corev1 "k8s.io/client-go/kubernetes/typed/core/v1" 25 ) 26 27 const ( 28 // WebhookName is the resource name for the Verrazzano cluster operator webhook 29 WebhookName = "verrazzano-cluster-operator-webhook" 30 OperatorCA = "verrazzano-cluster-operator-ca" 31 OperatorTLS = "verrazzano-cluster-operator-tls" 32 33 // WebhookNamespace is the resource namespace for the Verrazzano cluster operator webhook 34 WebhookNamespace = "verrazzano-system" 35 36 CertKey = "tls.crt" 37 PrivKey = "tls.key" 38 39 certYearsValid = 3 40 ) 41 42 // CreateWebhookCertificates creates the needed certificates for the validating webhook 43 func CreateWebhookCertificates(log *zap.SugaredLogger, kubeClient kubernetes.Interface, certDir string) error { 44 commonName := fmt.Sprintf("%s.%s.svc", WebhookName, WebhookNamespace) 45 ca, caKey, err := createCACert(log, kubeClient, commonName) 46 if err != nil { 47 return err 48 } 49 50 serverPEM, serverKeyPEM, err := createTLSCert(log, kubeClient, commonName, ca, caKey) 51 if err != nil { 52 return err 53 } 54 55 log.Debugf("Creating certs dir %s", certDir) 56 if err := os.MkdirAll(certDir, 0666); err != nil { 57 log.Errorf("Mkdir error %v", err) 58 return err 59 } 60 61 if err := writeFile(log, fmt.Sprintf("%s/%s", certDir, CertKey), serverPEM); err != nil { 62 log.Errorf("Error writing cert file: %v", err) 63 return err 64 } 65 66 if err := writeFile(log, fmt.Sprintf("%s/%s", certDir, PrivKey), serverKeyPEM); err != nil { 67 log.Errorf("Error writing key file %v", err) 68 return err 69 } 70 71 return nil 72 } 73 74 func createTLSCert(log *zap.SugaredLogger, kubeClient kubernetes.Interface, commonName string, ca *x509.Certificate, caKey *rsa.PrivateKey) ([]byte, []byte, error) { 75 secretsClient := kubeClient.CoreV1().Secrets(WebhookNamespace) 76 existingSecret, err := secretsClient.Get(context.TODO(), OperatorTLS, metav1.GetOptions{}) 77 if err == nil { 78 log.Infof("Secret %s exists, using...", OperatorTLS) 79 return existingSecret.Data[CertKey], existingSecret.Data[PrivKey], nil 80 } 81 if !errors.IsNotFound(err) { 82 return []byte{}, []byte{}, err 83 } 84 85 serialNumber, err := newSerialNumber() 86 if err != nil { 87 return []byte{}, []byte{}, err 88 } 89 90 // server cert config 91 cert := &x509.Certificate{ 92 DNSNames: []string{commonName}, 93 SerialNumber: serialNumber, 94 Subject: pkix.Name{ 95 CommonName: commonName, 96 }, 97 NotBefore: time.Now(), 98 NotAfter: time.Now().AddDate(certYearsValid, 0, 0), 99 IsCA: false, 100 SubjectKeyId: []byte{1, 2, 3, 4, 6}, 101 ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth}, 102 KeyUsage: x509.KeyUsageDigitalSignature, 103 } 104 105 // server private key 106 serverPrivKey, err := rsa.GenerateKey(cryptorand.Reader, 4096) 107 if err != nil { 108 return []byte{}, []byte{}, err 109 } 110 111 // sign the server cert 112 serverCertBytes, err := x509.CreateCertificate(cryptorand.Reader, cert, ca, &serverPrivKey.PublicKey, caKey) 113 if err != nil { 114 return []byte{}, []byte{}, err 115 } 116 117 return createTLSCertSecretIfNecesary(log, secretsClient, serverCertBytes, serverPrivKey) 118 } 119 120 func createTLSCertSecretIfNecesary(log *zap.SugaredLogger, secretsClient corev1.SecretInterface, 121 serverCertBytes []byte, serverPrivKey *rsa.PrivateKey) ([]byte, []byte, error) { 122 // PEM encode Server cert 123 serverPEM := new(bytes.Buffer) 124 _ = pem.Encode(serverPEM, &pem.Block{ 125 Type: "CERTIFICATE", 126 Bytes: serverCertBytes, 127 }) 128 129 // PEM encode Server cert 130 serverKeyPEM := new(bytes.Buffer) 131 _ = pem.Encode(serverKeyPEM, &pem.Block{ 132 Type: "RSA PRIVATE KEY", 133 Bytes: x509.MarshalPKCS1PrivateKey(serverPrivKey), 134 }) 135 136 serverPEMBytes := serverPEM.Bytes() 137 138 serverKeyPEMBytes := serverKeyPEM.Bytes() 139 140 var webhookCrt v1.Secret 141 webhookCrt.Namespace = WebhookNamespace 142 webhookCrt.Name = OperatorTLS 143 webhookCrt.Type = v1.SecretTypeTLS 144 webhookCrt.Data = make(map[string][]byte) 145 webhookCrt.Data[CertKey] = serverPEMBytes 146 webhookCrt.Data[PrivKey] = serverKeyPEMBytes 147 148 _, createError := secretsClient.Create(context.TODO(), &webhookCrt, metav1.CreateOptions{}) 149 if createError != nil { 150 if errors.IsAlreadyExists(createError) { 151 log.Infof("Operator CA secret %s already exists, skipping", OperatorCA) 152 existingSecret, err := secretsClient.Get(context.TODO(), OperatorTLS, metav1.GetOptions{}) 153 if err != nil { 154 return []byte{}, []byte{}, err 155 } 156 log.Infof("Secret %s exists, using...", OperatorTLS) 157 return existingSecret.Data[CertKey], existingSecret.Data[PrivKey], nil 158 } 159 return []byte{}, []byte{}, createError 160 } 161 162 return serverPEMBytes, serverKeyPEMBytes, nil 163 } 164 165 func createCACert(log *zap.SugaredLogger, kubeClient kubernetes.Interface, commonName string) (*x509.Certificate, *rsa.PrivateKey, error) { 166 secretsClient := kubeClient.CoreV1().Secrets(WebhookNamespace) 167 existingSecret, err := secretsClient.Get(context.TODO(), OperatorCA, metav1.GetOptions{}) 168 if err == nil { 169 log.Infof("CA secret %s exists, using...", OperatorCA) 170 return decodeExistingSecretData(existingSecret) 171 } 172 if !errors.IsNotFound(err) { 173 return nil, nil, err 174 } 175 176 log.Infof("Creating CA secret %s", OperatorCA) 177 serialNumber, err := newSerialNumber() 178 if err != nil { 179 return nil, nil, err 180 } 181 182 // CA config 183 ca := &x509.Certificate{ 184 DNSNames: []string{commonName}, 185 SerialNumber: serialNumber, 186 Subject: pkix.Name{ 187 CommonName: commonName, 188 }, 189 NotBefore: time.Now(), 190 NotAfter: time.Now().AddDate(certYearsValid, 0, 0), 191 IsCA: true, 192 ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth}, 193 KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign, 194 BasicConstraintsValid: true, 195 } 196 197 // CA private key 198 caPrivKey, err := rsa.GenerateKey(cryptorand.Reader, 4096) 199 if err != nil { 200 return nil, nil, err 201 } 202 203 // Self signed CA certificate 204 caBytes, err := x509.CreateCertificate(cryptorand.Reader, ca, ca, &caPrivKey.PublicKey, caPrivKey) 205 if err != nil { 206 return nil, nil, err 207 } 208 209 return createCACertSecretIfNecessary(log, secretsClient, ca, caPrivKey, caBytes) 210 } 211 212 func createCACertSecretIfNecessary(log *zap.SugaredLogger, secretsClient corev1.SecretInterface, ca *x509.Certificate, 213 caPrivKey *rsa.PrivateKey, caBytes []byte) (*x509.Certificate, *rsa.PrivateKey, error) { 214 caPEMBytes, caKeyPEMBytes := encodeCABytes(caBytes, caPrivKey) 215 216 webhookCA := v1.Secret{} 217 webhookCA.Namespace = WebhookNamespace 218 webhookCA.Name = OperatorCA 219 webhookCA.Type = v1.SecretTypeTLS 220 221 webhookCA.Data = make(map[string][]byte) 222 webhookCA.Data[CertKey] = caPEMBytes 223 webhookCA.Data[PrivKey] = caKeyPEMBytes 224 225 _, createError := secretsClient.Create(context.TODO(), &webhookCA, metav1.CreateOptions{}) 226 if createError != nil { 227 if errors.IsAlreadyExists(createError) { 228 log.Infof("Operator CA secret %s already exists, using existing secret", OperatorCA) 229 existingSecret, err := secretsClient.Get(context.TODO(), OperatorCA, metav1.GetOptions{}) 230 if err != nil { 231 return nil, nil, err 232 } 233 return decodeExistingSecretData(existingSecret) 234 } 235 return nil, nil, createError 236 } 237 return ca, caPrivKey, nil 238 } 239 240 // encodeCABytes PEM-encode the certificate and Key data 241 func encodeCABytes(caBytes []byte, caPrivKey *rsa.PrivateKey) ([]byte, []byte) { 242 // PEM encode CA cert 243 caPEM := new(bytes.Buffer) 244 _ = pem.Encode(caPEM, &pem.Block{ 245 Type: "CERTIFICATE", 246 Bytes: caBytes, 247 }) 248 249 // PEM encode CA cert 250 caKeyPEM := new(bytes.Buffer) 251 _ = pem.Encode(caKeyPEM, &pem.Block{ 252 Type: "RSA PRIVATE KEY", 253 Bytes: x509.MarshalPKCS1PrivateKey(caPrivKey), 254 }) 255 256 return caPEM.Bytes(), caKeyPEM.Bytes() 257 } 258 259 // decodeExistingSecretData Decode existing secret data into their X509 and RSA objects 260 func decodeExistingSecretData(secret *v1.Secret) (*x509.Certificate, *rsa.PrivateKey, error) { 261 cert, err := decodeCertificate(secret.Data[CertKey]) 262 if err != nil { 263 return nil, nil, err 264 } 265 key, err := decodeKey(secret.Data[PrivKey]) 266 if err != nil { 267 return nil, nil, err 268 } 269 return cert, key, err 270 } 271 272 // decodeCertificate Decode certificate PEM data 273 func decodeCertificate(certBytes []byte) (*x509.Certificate, error) { 274 p, _ := pem.Decode(certBytes) 275 if p == nil { 276 return nil, fmt.Errorf("Unable to decode certificate") 277 } 278 certificate, err := x509.ParseCertificate(p.Bytes) 279 if err != nil { 280 return nil, err 281 } 282 return certificate, nil 283 } 284 285 // decodeKey - Decode private Key PEM data 286 func decodeKey(keyBytes []byte) (*rsa.PrivateKey, error) { 287 p, _ := pem.Decode(keyBytes) 288 if p == nil { 289 return nil, fmt.Errorf("Unable to decode certificate") 290 } 291 key, err := x509.ParsePKCS1PrivateKey(p.Bytes) 292 if err != nil { 293 return nil, err 294 } 295 return key, nil 296 } 297 298 // newSerialNumber returns a new random serial number suitable for use in a certificate. 299 func newSerialNumber() (*big.Int, error) { 300 // A serial number can be up to 20 octets in size. 301 return cryptorand.Int(cryptorand.Reader, new(big.Int).Lsh(big.NewInt(1), 8*20)) 302 } 303 304 // writeFile writes data in the file at the given path 305 func writeFile(log *zap.SugaredLogger, filepath string, pemData []byte) error { 306 log.Debugf("Writing file %s", filepath) 307 f, err := os.Create(filepath) 308 if err != nil { 309 return err 310 } 311 defer f.Close() 312 313 _, err = f.Write(pemData) 314 if err != nil { 315 return err 316 } 317 318 return nil 319 }