github.com/jmrodri/operator-sdk@v0.5.0/test/e2e/tls_util_test.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 e2e 16 17 import ( 18 "io/ioutil" 19 "reflect" 20 "testing" 21 22 framework "github.com/operator-framework/operator-sdk/pkg/test" 23 tlsutil "github.com/operator-framework/operator-sdk/pkg/tls" 24 25 "k8s.io/api/core/v1" 26 apiErrors "k8s.io/apimachinery/pkg/api/errors" 27 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 28 "k8s.io/apimachinery/pkg/runtime" 29 ) 30 31 var ( 32 // TLS test variables. 33 crKind = "Pod" 34 crName = "example-pod" 35 certName = "app-cert" 36 caConfigMapAndSecretName = tlsutil.ToCASecretAndConfigMapName(crKind, crName) 37 appSecretName = tlsutil.ToAppSecretName(crKind, crName, certName) 38 39 caConfigMap *v1.ConfigMap 40 caSecret *v1.Secret 41 appSecret *v1.Secret 42 43 ccfg *tlsutil.CertConfig 44 ) 45 46 // setup test variables. 47 func init() { 48 caCertBytes, err := ioutil.ReadFile("./testdata/ca.crt") 49 if err != nil { 50 panic(err) 51 } 52 caConfigMap = &v1.ConfigMap{ 53 ObjectMeta: metav1.ObjectMeta{ 54 Name: caConfigMapAndSecretName, 55 }, 56 Data: map[string]string{tlsutil.TLSCACertKey: string(caCertBytes)}, 57 } 58 59 caKeyBytes, err := ioutil.ReadFile("./testdata/ca.key") 60 if err != nil { 61 panic(err) 62 } 63 caSecret = &v1.Secret{ 64 ObjectMeta: metav1.ObjectMeta{ 65 Name: caConfigMapAndSecretName, 66 }, 67 Data: map[string][]byte{tlsutil.TLSPrivateCAKeyKey: caKeyBytes}, 68 } 69 70 appSecret = &v1.Secret{ 71 ObjectMeta: metav1.ObjectMeta{ 72 Name: appSecretName, 73 }, 74 } 75 76 ccfg = &tlsutil.CertConfig{ 77 CertName: certName, 78 } 79 } 80 81 // TestBothAppAndCATLSAssetsExist ensures that when both application 82 // and CA TLS assets exist in the k8s cluster for a given cr, 83 // the GenerateCert() simply returns those to the caller. 84 func TestBothAppAndCATLSAssetsExist(t *testing.T) { 85 ctx := framework.NewTestCtx(t) 86 defer ctx.Cleanup() 87 namespace, err := ctx.GetNamespace() 88 if err != nil { 89 t.Fatal(err) 90 } 91 92 f := framework.Global 93 appSecret, err := f.KubeClient.CoreV1().Secrets(namespace).Create(appSecret) 94 if err != nil { 95 t.Fatal(err) 96 } 97 98 caConfigMap, err := f.KubeClient.CoreV1().ConfigMaps(namespace).Create(caConfigMap) 99 if err != nil { 100 t.Fatal(err) 101 } 102 103 caSecret, err := f.KubeClient.CoreV1().Secrets(namespace).Create(caSecret) 104 if err != nil { 105 t.Fatal(err) 106 } 107 108 cg := tlsutil.NewSDKCertGenerator(f.KubeClient) 109 actualAppSecret, actualCaConfigMap, actualCaSecret, err := cg.GenerateCert(newDummyCR(namespace), nil, ccfg) 110 if err != nil { 111 t.Fatal(err) 112 } 113 114 if !reflect.DeepEqual(appSecret, actualAppSecret) { 115 t.Fatalf("Expect %+v, but got %+v", appSecret, actualAppSecret) 116 } 117 if !reflect.DeepEqual(caConfigMap, actualCaConfigMap) { 118 t.Fatalf("Expect %+v, but got %+v", caConfigMap, actualCaConfigMap) 119 } 120 if !reflect.DeepEqual(caSecret, actualCaSecret) { 121 t.Fatalf("Expect %+v, but got %+v", caSecret, actualCaSecret) 122 } 123 } 124 125 // TestOnlyAppSecretExist tests a case where the application TLS asset exists but its 126 // correspoding CA asset doesn't. In this case, CertGenerator can't genereate a new CA because 127 // it won't verify the existing application TLS cert. Therefore, CertGenerator can't proceed 128 // and returns an error to the caller. 129 func TestOnlyAppSecretExist(t *testing.T) { 130 ctx := framework.NewTestCtx(t) 131 defer ctx.Cleanup() 132 namespace, err := ctx.GetNamespace() 133 if err != nil { 134 t.Fatal(err) 135 } 136 137 f := framework.Global 138 _, err = f.KubeClient.CoreV1().Secrets(namespace).Create(appSecret) 139 if err != nil { 140 t.Fatal(err) 141 } 142 143 cg := tlsutil.NewSDKCertGenerator(f.KubeClient) 144 _, _, _, err = cg.GenerateCert(newDummyCR(namespace), nil, ccfg) 145 if err == nil { 146 t.Fatal("Expect error, but got none") 147 } 148 if err != tlsutil.ErrCANotFound { 149 t.Fatalf("Expect %v, but got %v", tlsutil.ErrCANotFound.Error(), err.Error()) 150 } 151 } 152 153 // TestOnlyCAExist tests the case where only the CA exists in the cluster; 154 // GenerateCert can retrieve the CA and uses it to create a new application secret. 155 func TestOnlyCAExist(t *testing.T) { 156 ctx := framework.NewTestCtx(t) 157 defer ctx.Cleanup() 158 namespace, err := ctx.GetNamespace() 159 if err != nil { 160 t.Fatal(err) 161 } 162 163 f := framework.Global 164 _, err = f.KubeClient.CoreV1().ConfigMaps(namespace).Create(caConfigMap) 165 if err != nil { 166 t.Fatal(err) 167 } 168 _, err = f.KubeClient.CoreV1().Secrets(namespace).Create(caSecret) 169 if err != nil { 170 t.Fatal(err) 171 } 172 173 cg := tlsutil.NewSDKCertGenerator(f.KubeClient) 174 appSecret, _, _, err := cg.GenerateCert(newDummyCR(namespace), newAppSvc(namespace), ccfg) 175 if err != nil { 176 t.Fatal(err) 177 } 178 179 verifyAppSecret(t, appSecret, namespace) 180 } 181 182 // TestNoneOfCaAndAppSecretExist ensures that when none of the CA and Application TLS assets 183 // exist, GenerateCert() creates both and put them into the k8s cluster. 184 func TestNoneOfCaAndAppSecretExist(t *testing.T) { 185 ctx := framework.NewTestCtx(t) 186 defer ctx.Cleanup() 187 namespace, err := ctx.GetNamespace() 188 if err != nil { 189 t.Fatal(err) 190 } 191 192 f := framework.Global 193 cg := tlsutil.NewSDKCertGenerator(f.KubeClient) 194 appSecret, caConfigMap, caSecret, err := cg.GenerateCert(newDummyCR(namespace), newAppSvc(namespace), ccfg) 195 if err != nil { 196 t.Fatal(err) 197 } 198 199 verifyAppSecret(t, appSecret, namespace) 200 verifyCaConfigMap(t, caConfigMap, namespace) 201 verifyCASecret(t, caSecret, namespace) 202 } 203 204 // TestCustomCA ensures that if a user provides a custom Key and Cert and the CA and Application TLS assets 205 // do not exist, the GenerateCert method can use the custom CA to generate the TLS assest. 206 func TestCustomCA(t *testing.T) { 207 ctx := framework.NewTestCtx(t) 208 defer ctx.Cleanup() 209 namespace, err := ctx.GetNamespace() 210 if err != nil { 211 t.Fatal(err) 212 } 213 214 f := framework.Global 215 cg := tlsutil.NewSDKCertGenerator(f.KubeClient) 216 217 customConfig := &tlsutil.CertConfig{ 218 CertName: certName, 219 CAKey: "testdata/ca.key", 220 CACert: "testdata/ca.crt", 221 } 222 appSecret, _, _, err := cg.GenerateCert(newDummyCR(namespace), newAppSvc(namespace), customConfig) 223 if err != nil { 224 t.Fatal(err) 225 } 226 227 verifyAppSecret(t, appSecret, namespace) 228 229 // ensure caConfigMap does not exist in k8s cluster. 230 _, err = framework.Global.KubeClient.CoreV1().Secrets(namespace).Get(caConfigMapAndSecretName, metav1.GetOptions{}) 231 if !apiErrors.IsNotFound(err) { 232 t.Fatal(err) 233 } 234 235 // ensure caConfigMap does not exist in k8s cluster. 236 _, err = framework.Global.KubeClient.CoreV1().Secrets(namespace).Get(caConfigMapAndSecretName, metav1.GetOptions{}) 237 if !apiErrors.IsNotFound(err) { 238 t.Fatal(err) 239 } 240 } 241 242 func verifyCASecret(t *testing.T, caSecret *v1.Secret, namespace string) { 243 // check if caConfigMap has the correct fields. 244 if caConfigMapAndSecretName != caSecret.Name { 245 t.Fatalf("Expect the ca config name %v, but got %v", caConfigMapAndSecretName, caConfigMap.Name) 246 } 247 if namespace != caSecret.Namespace { 248 t.Fatalf("Expect the ca config namespace %v, but got %v", namespace, appSecret.Namespace) 249 } 250 if _, ok := caSecret.Data[tlsutil.TLSPrivateCAKeyKey]; !ok { 251 t.Fatalf("Expect the ca config to have the data field %v, but got none", tlsutil.TLSPrivateCAKeyKey) 252 } 253 254 // check if caConfigMap exists in k8s cluster. 255 caSecretFromCluster, err := framework.Global.KubeClient.CoreV1().Secrets(namespace).Get(caConfigMapAndSecretName, metav1.GetOptions{}) 256 if err != nil { 257 t.Fatal(err) 258 } 259 // check if caSecret returned from GenerateCert is the same as the one that exists in the k8s. 260 if !reflect.DeepEqual(caSecret, caSecretFromCluster) { 261 t.Fatalf("Expect %+v, but got %+v", caSecret, caSecretFromCluster) 262 } 263 } 264 265 func verifyCaConfigMap(t *testing.T, caConfigMap *v1.ConfigMap, namespace string) { 266 // check if caConfigMap has the correct fields. 267 if caConfigMapAndSecretName != caConfigMap.Name { 268 t.Fatalf("Expect the ca config name %v, but got %v", caConfigMapAndSecretName, caConfigMap.Name) 269 } 270 if namespace != caConfigMap.Namespace { 271 t.Fatalf("Expect the ca config namespace %v, but got %v", namespace, appSecret.Namespace) 272 } 273 if _, ok := caConfigMap.Data[tlsutil.TLSCACertKey]; !ok { 274 t.Fatalf("Expect the ca config to have the data field %v, but got none", tlsutil.TLSCACertKey) 275 } 276 277 // check if caConfigMap exists in k8s cluster. 278 caConfigMapFromCluster, err := framework.Global.KubeClient.CoreV1().ConfigMaps(namespace).Get(caConfigMapAndSecretName, metav1.GetOptions{}) 279 if err != nil { 280 t.Fatal(err) 281 } 282 // check if caConfigMap returned from GenerateCert is the same as the one that exists in the k8s. 283 if !reflect.DeepEqual(caConfigMap, caConfigMapFromCluster) { 284 t.Fatalf("Expect %+v, but got %+v", caConfigMap, caConfigMapFromCluster) 285 } 286 } 287 288 func verifyAppSecret(t *testing.T, appSecret *v1.Secret, namespace string) { 289 // check if appSecret has the correct fields. 290 if appSecretName != appSecret.Name { 291 t.Fatalf("Expect the secret name %v, but got %v", appSecretName, appSecret.Name) 292 } 293 if namespace != appSecret.Namespace { 294 t.Fatalf("Expect the secret namespace %v, but got %v", namespace, appSecret.Namespace) 295 } 296 if v1.SecretTypeTLS != appSecret.Type { 297 t.Fatalf("Expect the secret type %v, but got %v", v1.SecretTypeTLS, appSecret.Type) 298 } 299 if _, ok := appSecret.Data[v1.TLSCertKey]; !ok { 300 t.Fatalf("Expect the secret to have the data field %v, but got none", v1.TLSCertKey) 301 } 302 if _, ok := appSecret.Data[v1.TLSPrivateKeyKey]; !ok { 303 t.Fatalf("Expect the secret to have the data field %v, but got none", v1.TLSPrivateKeyKey) 304 } 305 306 // check if appSecret exists in k8s cluster. 307 appSecretFromCluster, err := framework.Global.KubeClient.CoreV1().Secrets(namespace).Get(appSecretName, metav1.GetOptions{}) 308 if err != nil { 309 t.Fatal(err) 310 } 311 // check if appSecret returned from GenerateCert is the same as the one that exists in the k8s. 312 if !reflect.DeepEqual(appSecret, appSecretFromCluster) { 313 t.Fatalf("Expect %+v, but got %+v", appSecret, appSecretFromCluster) 314 } 315 } 316 317 // newDummyCR returns a dummy runtime object for the CR input of GenerateCert(). 318 func newDummyCR(namespace string) runtime.Object { 319 return &v1.Pod{ 320 TypeMeta: metav1.TypeMeta{ 321 Kind: crKind, 322 }, 323 ObjectMeta: metav1.ObjectMeta{ 324 Name: crName, 325 Namespace: namespace, 326 }, 327 } 328 } 329 330 func newAppSvc(namespace string) *v1.Service { 331 return &v1.Service{ 332 ObjectMeta: metav1.ObjectMeta{ 333 Name: "app-service", 334 Namespace: namespace, 335 }, 336 } 337 }