github.com/yashbhutwala/operator-sdk@v0.8.1/pkg/tls/tls.go (about) 1 // Copyright 2018 The Operator-SDK Authors 2 // 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 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package tlsutil 16 17 import ( 18 "crypto/rsa" 19 "crypto/x509" 20 "errors" 21 "fmt" 22 "io/ioutil" 23 "strings" 24 25 "k8s.io/api/core/v1" 26 apiErrors "k8s.io/apimachinery/pkg/api/errors" 27 "k8s.io/apimachinery/pkg/api/meta" 28 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 29 "k8s.io/apimachinery/pkg/runtime" 30 "k8s.io/client-go/kubernetes" 31 ) 32 33 // CertType defines the type of the cert. 34 type CertType int 35 36 const ( 37 // ClientAndServingCert defines both client and serving cert. 38 ClientAndServingCert CertType = iota 39 // ServingCert defines a serving cert. 40 ServingCert 41 // ClientCert defines a client cert. 42 ClientCert 43 ) 44 45 // CertConfig configures how to generate the Cert. 46 type CertConfig struct { 47 // CertName is the name of the cert. 48 CertName string 49 // Optional CertType. Serving, client or both; defaults to both. 50 CertType CertType 51 // Optional CommonName is the common name of the cert; defaults to "". 52 CommonName string 53 // Optional Organization is Organization of the cert; defaults to "". 54 Organization []string 55 // Optional CA Key, if user wants to provide custom CA key via a file path. 56 CAKey string 57 // Optional CA Certificate, if user wants to provide custom CA cert via file path. 58 CACert string 59 // TODO: consider to add passed in SAN fields. 60 } 61 62 // CertGenerator is an operator specific TLS tool that generates TLS assets for the deploying a user's application. 63 type CertGenerator interface { 64 // GenerateCert generates a secret containing TLS encryption key and cert, a Secret 65 // containing the CA key, and a ConfigMap containing the CA Certificate given the Custom 66 // Resource(CR) "cr", the Kubernetes Service "Service", and the CertConfig "config". 67 // 68 // GenerateCert creates and manages TLS key and cert and CA with the following: 69 // CA creation and management: 70 // - If CA is not given: 71 // - A unique CA is generated for the CR. 72 // - CA's key is packaged into a Secret as shown below. 73 // - CA's cert is packaged in a ConfigMap as shown below. 74 // - The CA Secret and ConfigMap are created on the k8s cluster in the CR's namespace before 75 // returned to the user. The CertGenerator manages the CA Secret and ConfigMap to ensure it's 76 // unqiue per CR. 77 // - If CA is given: 78 // - CA's key is packaged into a Secret as shown below. 79 // - CA's cert is packaged in a ConfigMap as shown below. 80 // - The CA Secret and ConfigMap are returned but not created in the K8s cluster in the CR's 81 // namespace. The CertGenerator doesn't manage the CA because the user controls the lifecycle 82 // of the CA. 83 // 84 // TLS Key and Cert Creation and Management: 85 // - A unique TLS cert and key pair is generated per CR + CertConfig.CertName. 86 // - The CA is used to generate and sign the TLS cert. 87 // - The signing process uses the passed in "service" to set the Subject Alternative Names(SAN) 88 // for the certificate. We assume that the deployed applications are typically communicated 89 // with via a Kubernetes Service. The SAN is set to the FQDN of the service 90 // `<service-name>.<service-namespace>.svc.cluster.local`. 91 // - Once TLS key and cert are created, they are packaged into a secret as shown below. 92 // - Finally, the secret are created on the k8s cluster in the CR's namespace before returned to 93 // the user. The CertGenerator manages this secret to ensure that it is unique per CR + 94 // CertConfig.CertName. 95 // 96 // TLS encryption key and cert Secret format: 97 // kind: Secret 98 // apiVersion: v1 99 // metadata: 100 // name: <cr-kind>-<cr-name>-<CertConfig.CertName> 101 // namespace: <cr-namespace> 102 // data: 103 // tls.crt: ... 104 // tls.key: ... 105 // 106 // CA Certificate ConfigMap format: 107 // kind: ConfigMap 108 // apiVersion: v1 109 // metadata: 110 // name: <cr-kind>-<cr-name>-ca 111 // namespace: <cr-namespace> 112 // data: 113 // ca.crt: ... 114 // 115 // CA Key Secret format: 116 // kind: Secret 117 // apiVersion: v1 118 // metadata: 119 // name: <cr-kind>-<cr-name>-ca 120 // namespace: <cr-namespace> 121 // data: 122 // ca.key: .. 123 GenerateCert(cr runtime.Object, service *v1.Service, config *CertConfig) (*v1.Secret, *v1.ConfigMap, *v1.Secret, error) 124 } 125 126 const ( 127 // TLSPrivateCAKeyKey is the key for the private CA key field. 128 TLSPrivateCAKeyKey = "ca.key" 129 // TLSCertKey is the key for tls CA certificates. 130 TLSCACertKey = "ca.crt" 131 ) 132 133 // NewSDKCertGenerator constructs a new CertGenerator given the kubeClient. 134 func NewSDKCertGenerator(kubeClient kubernetes.Interface) CertGenerator { 135 return &SDKCertGenerator{KubeClient: kubeClient} 136 } 137 138 type SDKCertGenerator struct { 139 KubeClient kubernetes.Interface 140 } 141 142 // GenerateCert returns a secret containing the TLS encryption key and cert, 143 // a ConfigMap containing the CA Certificate and a Secret containing the CA key or it 144 // returns a error incase something goes wrong. 145 func (scg *SDKCertGenerator) GenerateCert(cr runtime.Object, service *v1.Service, config *CertConfig) (*v1.Secret, *v1.ConfigMap, *v1.Secret, error) { 146 if err := verifyConfig(config); err != nil { 147 return nil, nil, nil, err 148 } 149 150 k, n, ns, err := toKindNameNamespace(cr) 151 if err != nil { 152 return nil, nil, nil, err 153 } 154 appSecretName := ToAppSecretName(k, n, config.CertName) 155 appSecret, err := getAppSecretInCluster(scg.KubeClient, appSecretName, ns) 156 if err != nil { 157 return nil, nil, nil, err 158 } 159 caSecretAndConfigMapName := ToCASecretAndConfigMapName(k, n) 160 161 var ( 162 caSecret *v1.Secret 163 caConfigMap *v1.ConfigMap 164 ) 165 166 caSecret, caConfigMap, err = getCASecretAndConfigMapInCluster(scg.KubeClient, caSecretAndConfigMapName, ns) 167 if err != nil { 168 return nil, nil, nil, err 169 } 170 171 if config.CAKey != "" && config.CACert != "" { 172 // custom CA provided by the user. 173 customCAKeyData, err := ioutil.ReadFile(config.CAKey) 174 if err != nil { 175 return nil, nil, nil, fmt.Errorf("error reading CA Key from the given file name: %v", err) 176 } 177 178 customCACertData, err := ioutil.ReadFile(config.CACert) 179 if err != nil { 180 return nil, nil, nil, fmt.Errorf("error reading CA Cert from the given file name: %v", err) 181 } 182 183 customCAKey, err := parsePEMEncodedPrivateKey(customCAKeyData) 184 if err != nil { 185 return nil, nil, nil, fmt.Errorf("error parsing CA Key from the given file name: %v", err) 186 } 187 188 customCACert, err := parsePEMEncodedCert(customCACertData) 189 if err != nil { 190 return nil, nil, nil, fmt.Errorf("error parsing CA Cert from the given file name: %v", err) 191 } 192 caSecret, caConfigMap = toCASecretAndConfigmap(customCAKey, customCACert, caSecretAndConfigMapName) 193 } else if config.CAKey != "" || config.CACert != "" { 194 // if only one of the custom CA Key or Cert is provided 195 return nil, nil, nil, ErrCAKeyAndCACertReq 196 } 197 198 hasAppSecret := appSecret != nil 199 hasCASecretAndConfigMap := caSecret != nil && caConfigMap != nil 200 201 switch { 202 case hasAppSecret && hasCASecretAndConfigMap: 203 return appSecret, caConfigMap, caSecret, nil 204 205 case hasAppSecret && !hasCASecretAndConfigMap: 206 return nil, nil, nil, ErrCANotFound 207 208 case !hasAppSecret && hasCASecretAndConfigMap: 209 // Note: if a custom CA is passed in my the user it takes preference over an already 210 // generated CA secret and CA configmap that might exist in the cluster 211 caKey, err := parsePEMEncodedPrivateKey(caSecret.Data[TLSPrivateCAKeyKey]) 212 if err != nil { 213 return nil, nil, nil, err 214 } 215 caCert, err := parsePEMEncodedCert([]byte(caConfigMap.Data[TLSCACertKey])) 216 if err != nil { 217 return nil, nil, nil, err 218 } 219 key, err := newPrivateKey() 220 if err != nil { 221 return nil, nil, nil, err 222 } 223 cert, err := newSignedCertificate(config, service, key, caCert, caKey) 224 if err != nil { 225 return nil, nil, nil, err 226 } 227 appSecret, err := scg.KubeClient.CoreV1().Secrets(ns).Create(toTLSSecret(key, cert, appSecretName)) 228 if err != nil { 229 return nil, nil, nil, err 230 } 231 return appSecret, caConfigMap, caSecret, nil 232 233 case !hasAppSecret && !hasCASecretAndConfigMap: 234 // If no custom CAKey and CACert are provided we have to generate them 235 caKey, err := newPrivateKey() 236 if err != nil { 237 return nil, nil, nil, err 238 } 239 caCert, err := newSelfSignedCACertificate(caKey) 240 if err != nil { 241 return nil, nil, nil, err 242 } 243 244 caSecret, caConfigMap := toCASecretAndConfigmap(caKey, caCert, caSecretAndConfigMapName) 245 caSecret, err = scg.KubeClient.CoreV1().Secrets(ns).Create(caSecret) 246 if err != nil { 247 return nil, nil, nil, err 248 } 249 caConfigMap, err = scg.KubeClient.CoreV1().ConfigMaps(ns).Create(caConfigMap) 250 if err != nil { 251 return nil, nil, nil, err 252 } 253 key, err := newPrivateKey() 254 if err != nil { 255 return nil, nil, nil, err 256 } 257 cert, err := newSignedCertificate(config, service, key, caCert, caKey) 258 if err != nil { 259 return nil, nil, nil, err 260 } 261 appSecret, err := scg.KubeClient.CoreV1().Secrets(ns).Create(toTLSSecret(key, cert, appSecretName)) 262 if err != nil { 263 return nil, nil, nil, err 264 } 265 return appSecret, caConfigMap, caSecret, nil 266 default: 267 return nil, nil, nil, ErrInternal 268 } 269 } 270 271 func verifyConfig(config *CertConfig) error { 272 if config == nil { 273 return errors.New("nil CertConfig not allowed") 274 } 275 if config.CertName == "" { 276 return errors.New("empty CertConfig.CertName not allowed") 277 } 278 return nil 279 } 280 281 func ToAppSecretName(kind, name, certName string) string { 282 return strings.ToLower(kind) + "-" + name + "-" + certName 283 } 284 285 func ToCASecretAndConfigMapName(kind, name string) string { 286 return strings.ToLower(kind) + "-" + name + "-ca" 287 } 288 289 func getAppSecretInCluster(kubeClient kubernetes.Interface, name, namespace string) (*v1.Secret, error) { 290 se, err := kubeClient.CoreV1().Secrets(namespace).Get(name, metav1.GetOptions{}) 291 if err != nil && !apiErrors.IsNotFound(err) { 292 return nil, err 293 } 294 if apiErrors.IsNotFound(err) { 295 return nil, nil 296 } 297 return se, nil 298 } 299 300 // getCASecretAndConfigMapInCluster gets CA secret and configmap of the given name and namespace. 301 // it only returns both if they are found and nil if both are not found. In the case if only one of them is found, 302 // then we error out because we expect either both CA secret and configmap exit or not. 303 // 304 // NOTE: both the CA secret and configmap have the same name with template `<cr-kind>-<cr-name>-ca` which is what the 305 // input parameter `name` refers to. 306 func getCASecretAndConfigMapInCluster(kubeClient kubernetes.Interface, name, namespace string) (*v1.Secret, *v1.ConfigMap, error) { 307 hasConfigMap := true 308 cm, err := kubeClient.CoreV1().ConfigMaps(namespace).Get(name, metav1.GetOptions{}) 309 if err != nil && !apiErrors.IsNotFound(err) { 310 return nil, nil, err 311 } 312 if apiErrors.IsNotFound(err) { 313 hasConfigMap = false 314 } 315 316 hasSecret := true 317 se, err := kubeClient.CoreV1().Secrets(namespace).Get(name, metav1.GetOptions{}) 318 if err != nil && !apiErrors.IsNotFound(err) { 319 return nil, nil, err 320 } 321 if apiErrors.IsNotFound(err) { 322 hasSecret = false 323 } 324 325 if hasConfigMap != hasSecret { 326 // TODO: this case can happen if creating CA configmap succeeds and creating CA secret failed. We need to handle this case properly. 327 return nil, nil, fmt.Errorf("expect either both ca configmap and secret both exist or not exist, but got hasCAConfigmap==%v and hasCASecret==%v", hasConfigMap, hasSecret) 328 } 329 if hasConfigMap == false { 330 return nil, nil, nil 331 } 332 return se, cm, nil 333 } 334 335 func toKindNameNamespace(cr runtime.Object) (string, string, string, error) { 336 a := meta.NewAccessor() 337 k, err := a.Kind(cr) 338 if err != nil { 339 return "", "", "", err 340 } 341 n, err := a.Name(cr) 342 if err != nil { 343 return "", "", "", err 344 } 345 ns, err := a.Namespace(cr) 346 if err != nil { 347 return "", "", "", err 348 } 349 return k, n, ns, nil 350 } 351 352 // toTLSSecret returns a client/server "kubernetes.io/tls" secret. 353 // TODO: add owner ref. 354 func toTLSSecret(key *rsa.PrivateKey, cert *x509.Certificate, name string) *v1.Secret { 355 return &v1.Secret{ 356 ObjectMeta: metav1.ObjectMeta{ 357 Name: name, 358 }, 359 Data: map[string][]byte{ 360 v1.TLSPrivateKeyKey: encodePrivateKeyPEM(key), 361 v1.TLSCertKey: encodeCertificatePEM(cert), 362 }, 363 Type: v1.SecretTypeTLS, 364 } 365 } 366 367 // TODO: add owner ref. 368 func toCASecretAndConfigmap(key *rsa.PrivateKey, cert *x509.Certificate, name string) (*v1.Secret, *v1.ConfigMap) { 369 return &v1.Secret{ 370 ObjectMeta: metav1.ObjectMeta{ 371 Name: name, 372 }, 373 Data: map[string][]byte{ 374 TLSPrivateCAKeyKey: encodePrivateKeyPEM(key), 375 }, 376 }, &v1.ConfigMap{ 377 ObjectMeta: metav1.ObjectMeta{ 378 Name: name, 379 }, 380 Data: map[string]string{ 381 TLSCACertKey: string(encodeCertificatePEM(cert)), 382 }, 383 } 384 }