|
15 | 15 | package tlsutil
|
16 | 16 |
|
17 | 17 | import (
|
| 18 | + "crypto/rsa" |
| 19 | + "crypto/x509" |
| 20 | + "errors" |
| 21 | + "fmt" |
| 22 | + "strings" |
| 23 | + |
| 24 | + "github.com/operator-framework/operator-sdk/pkg/sdk" |
| 25 | + |
18 | 26 | "k8s.io/api/core/v1"
|
| 27 | + apiErrors "k8s.io/apimachinery/pkg/api/errors" |
| 28 | + "k8s.io/apimachinery/pkg/api/meta" |
| 29 | + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" |
19 | 30 | "k8s.io/apimachinery/pkg/runtime"
|
20 | 31 | )
|
21 | 32 |
|
@@ -111,3 +122,207 @@ type CertGenerator interface {
|
111 | 122 | // ca.key: ..
|
112 | 123 | GenerateCert(cr runtime.Object, service *v1.Service, config *CertConfig) (*v1.Secret, *v1.ConfigMap, *v1.Secret, error)
|
113 | 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 | +type SDKCertGenerator struct { |
| 134 | +} |
| 135 | + |
| 136 | +type keyAndCert struct { |
| 137 | + key *rsa.PrivateKey |
| 138 | + cert *x509.Certificate |
| 139 | +} |
| 140 | + |
| 141 | +func (scg *SDKCertGenerator) GenerateCert(cr runtime.Object, service *v1.Service, config *CertConfig) (*v1.Secret, *v1.ConfigMap, *v1.Secret, error) { |
| 142 | + if err := verifyConfig(config); err != nil { |
| 143 | + return nil, nil, nil, err |
| 144 | + } |
| 145 | + |
| 146 | + k, n, ns, err := toKindNameNamespace(cr) |
| 147 | + if err != nil { |
| 148 | + return nil, nil, nil, err |
| 149 | + } |
| 150 | + appSecret, err := getAppSecretInCluster(toAppSecretName(k, n, config.CertName), ns) |
| 151 | + if err != nil { |
| 152 | + return nil, nil, nil, err |
| 153 | + } |
| 154 | + caSecret, caConfigMap, err := getCASecretAndConfigMapInCluster(toCASecretAndConfigMapName(k, n), ns) |
| 155 | + if err != nil { |
| 156 | + return nil, nil, nil, err |
| 157 | + } |
| 158 | + |
| 159 | + hasAppSecret := appSecret != nil |
| 160 | + hasCASecretAndConfigMap := caSecret != nil && caConfigMap != nil |
| 161 | + // TODO: handle passed in CA |
| 162 | + if hasAppSecret && hasCASecretAndConfigMap { |
| 163 | + return appSecret, caConfigMap, caSecret, nil |
| 164 | + } else if hasAppSecret && !hasCASecretAndConfigMap { |
| 165 | + // TODO |
| 166 | + } else if !hasAppSecret && hasCASecretAndConfigMap { |
| 167 | + // TODO |
| 168 | + } else { |
| 169 | + // TODO |
| 170 | + } |
| 171 | + return nil, nil, nil, nil |
| 172 | +} |
| 173 | + |
| 174 | +func verifyConfig(config *CertConfig) error { |
| 175 | + if config == nil { |
| 176 | + return errors.New("nil CertConfig not allowed") |
| 177 | + } |
| 178 | + if config.CertName == "" { |
| 179 | + return errors.New("empty CertConfig.CertName not allowed") |
| 180 | + } |
| 181 | + return nil |
| 182 | +} |
| 183 | + |
| 184 | +func toAppSecretName(kind, name, certName string) string { |
| 185 | + return strings.ToLower(kind) + "-" + name + "-" + certName |
| 186 | +} |
| 187 | + |
| 188 | +func toCASecretAndConfigMapName(kind, name string) string { |
| 189 | + return strings.ToLower(kind) + "-" + name + "-ca" |
| 190 | +} |
| 191 | + |
| 192 | +func getAppSecretInCluster(name, namespace string) (*v1.Secret, error) { |
| 193 | + se := &v1.Secret{ |
| 194 | + TypeMeta: metav1.TypeMeta{ |
| 195 | + Kind: "Secret", |
| 196 | + APIVersion: "v1", |
| 197 | + }, |
| 198 | + ObjectMeta: metav1.ObjectMeta{ |
| 199 | + Name: name, |
| 200 | + Namespace: namespace, |
| 201 | + }, |
| 202 | + } |
| 203 | + err := sdk.Get(se) |
| 204 | + if apiErrors.IsNotFound(err) { |
| 205 | + return nil, nil |
| 206 | + } |
| 207 | + if err != nil { |
| 208 | + return nil, err |
| 209 | + } |
| 210 | + return se, nil |
| 211 | +} |
| 212 | + |
| 213 | +// getCASecretAndConfigMapInCluster gets CA secret and configmap of the given name and namespace. |
| 214 | +// 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, then we error out because we expect either both CA secret and configmap exit or not. |
| 215 | +func getCASecretAndConfigMapInCluster(name, namespace string) (*v1.Secret, *v1.ConfigMap, error) { |
| 216 | + cm := &v1.ConfigMap{ |
| 217 | + TypeMeta: metav1.TypeMeta{ |
| 218 | + Kind: "ConfigMap", |
| 219 | + APIVersion: "v1", |
| 220 | + }, |
| 221 | + ObjectMeta: metav1.ObjectMeta{ |
| 222 | + Name: name, |
| 223 | + Namespace: namespace, |
| 224 | + }, |
| 225 | + } |
| 226 | + hasConfigMap := true |
| 227 | + err := sdk.Get(cm) |
| 228 | + if apiErrors.IsNotFound(err) { |
| 229 | + hasConfigMap = false |
| 230 | + } |
| 231 | + if err != nil { |
| 232 | + return nil, nil, err |
| 233 | + } |
| 234 | + |
| 235 | + se := &v1.Secret{ |
| 236 | + TypeMeta: metav1.TypeMeta{ |
| 237 | + Kind: "Secret", |
| 238 | + APIVersion: "v1", |
| 239 | + }, |
| 240 | + ObjectMeta: metav1.ObjectMeta{ |
| 241 | + Name: name, |
| 242 | + Namespace: namespace, |
| 243 | + }, |
| 244 | + } |
| 245 | + hasSecret := true |
| 246 | + err = sdk.Get(se) |
| 247 | + if apiErrors.IsNotFound(err) { |
| 248 | + hasSecret = false |
| 249 | + } |
| 250 | + if err != nil { |
| 251 | + return nil, nil, err |
| 252 | + } |
| 253 | + |
| 254 | + if hasConfigMap != hasSecret { |
| 255 | + // TODO: this case can happen if creating CA configmap succeeds and creating CA secret failed. We need to handle this case properly. |
| 256 | + 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) |
| 257 | + } |
| 258 | + if hasConfigMap == false { |
| 259 | + return nil, nil, nil |
| 260 | + } |
| 261 | + return se, cm, nil |
| 262 | +} |
| 263 | + |
| 264 | +func toKindNameNamespace(cr runtime.Object) (string, string, string, error) { |
| 265 | + a := meta.NewAccessor() |
| 266 | + k, err := a.Kind(cr) |
| 267 | + if err != nil { |
| 268 | + return "", "", "", err |
| 269 | + } |
| 270 | + n, err := a.Name(cr) |
| 271 | + if err != nil { |
| 272 | + return "", "", "", err |
| 273 | + } |
| 274 | + ns, err := a.Namespace(cr) |
| 275 | + if err != nil { |
| 276 | + return "", "", "", err |
| 277 | + } |
| 278 | + return k, n, ns, nil |
| 279 | +} |
| 280 | + |
| 281 | +// toTLSSecret returns a client/server "kubernetes.io/tls" secret. |
| 282 | +// TODO: add owner ref. |
| 283 | +func toTLSSecret(key *rsa.PrivateKey, cert *x509.Certificate, name, namespace string) *v1.Secret { |
| 284 | + return &v1.Secret{ |
| 285 | + TypeMeta: metav1.TypeMeta{ |
| 286 | + Kind: "Secret", |
| 287 | + APIVersion: "v1", |
| 288 | + }, |
| 289 | + ObjectMeta: metav1.ObjectMeta{ |
| 290 | + Name: name, |
| 291 | + Namespace: namespace, |
| 292 | + }, |
| 293 | + Data: map[string][]byte{ |
| 294 | + v1.TLSPrivateKeyKey: encodePrivateKeyPEM(key), |
| 295 | + v1.TLSCertKey: encodeCertificatePEM(cert), |
| 296 | + }, |
| 297 | + Type: v1.SecretTypeTLS, |
| 298 | + } |
| 299 | +} |
| 300 | + |
| 301 | +// TODO: add owner ref. |
| 302 | +func toCASecretAndConfigmap(key *rsa.PrivateKey, cert *x509.Certificate, name, namespace string) (*v1.ConfigMap, *v1.Secret) { |
| 303 | + return &v1.ConfigMap{ |
| 304 | + TypeMeta: metav1.TypeMeta{ |
| 305 | + Kind: "ConfigMap", |
| 306 | + APIVersion: "v1", |
| 307 | + }, |
| 308 | + ObjectMeta: metav1.ObjectMeta{ |
| 309 | + Name: name, |
| 310 | + Namespace: namespace, |
| 311 | + }, |
| 312 | + Data: map[string]string{ |
| 313 | + TLSCACertKey: string(encodeCertificatePEM(cert)), |
| 314 | + }, |
| 315 | + }, &v1.Secret{ |
| 316 | + TypeMeta: metav1.TypeMeta{ |
| 317 | + Kind: "Secret", |
| 318 | + APIVersion: "v1", |
| 319 | + }, |
| 320 | + ObjectMeta: metav1.ObjectMeta{ |
| 321 | + Name: name, |
| 322 | + Namespace: namespace, |
| 323 | + }, |
| 324 | + Data: map[string][]byte{ |
| 325 | + TLSPrivateCAKeyKey: encodePrivateKeyPEM(key), |
| 326 | + }, |
| 327 | + } |
| 328 | +} |
0 commit comments