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