github.com/IBM-Blockchain/fabric-operator@v1.0.4/integration/actions/ca/ca_test.go (about) 1 /* 2 * Copyright contributors to the Hyperledger Fabric Operator project 3 * 4 * SPDX-License-Identifier: Apache-2.0 5 * 6 * Licensed under the Apache License, Version 2.0 (the "License"); 7 * you may not use this file except in compliance with the License. 8 * You may obtain a copy of the License at: 9 * 10 * http://www.apache.org/licenses/LICENSE-2.0 11 * 12 * Unless required by applicable law or agreed to in writing, software 13 * distributed under the License is distributed on an "AS IS" BASIS, 14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 * See the License for the specific language governing permissions and 16 * limitations under the License. 17 */ 18 package ca_test 19 20 import ( 21 "bytes" 22 "context" 23 "crypto/ecdsa" 24 "crypto/elliptic" 25 "crypto/rand" 26 "crypto/x509" 27 "crypto/x509/pkix" 28 "encoding/base64" 29 "encoding/json" 30 "encoding/pem" 31 "fmt" 32 "math/big" 33 "time" 34 35 current "github.com/IBM-Blockchain/fabric-operator/api/v1beta1" 36 "github.com/IBM-Blockchain/fabric-operator/integration" 37 "github.com/IBM-Blockchain/fabric-operator/integration/helper" 38 v1 "github.com/IBM-Blockchain/fabric-operator/pkg/apis/ca/v1" 39 "github.com/IBM-Blockchain/fabric-operator/pkg/offering/common" 40 "github.com/IBM-Blockchain/fabric-operator/pkg/util" 41 "github.com/IBM-Blockchain/fabric-operator/pkg/util/pointer" 42 . "github.com/onsi/ginkgo/v2" 43 . "github.com/onsi/gomega" 44 "github.com/pkg/errors" 45 corev1 "k8s.io/api/core/v1" 46 "k8s.io/apimachinery/pkg/api/resource" 47 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 48 "k8s.io/apimachinery/pkg/runtime" 49 "sigs.k8s.io/controller-runtime/pkg/client" 50 ) 51 52 var _ = Describe("trigger CA actions", func() { 53 AfterEach(func() { 54 // Set flag if a test falls 55 if CurrentGinkgoTestDescription().Failed { 56 testFailed = true 57 } 58 }) 59 60 Context("renew TLS cert set to true", func() { 61 var ( 62 expiringCA *helper.CA 63 ibpca *current.IBPCA 64 ) 65 66 Context("TLS certificate", func() { 67 var ( 68 err error 69 cert, key []byte 70 ) 71 72 BeforeEach(func() { 73 key, cert, err = GenSelfSignedCert(time.Hour * 48) 74 Expect(err).NotTo(HaveOccurred()) 75 76 certB64 := util.BytesToBase64(cert) 77 keyB64 := util.BytesToBase64(key) 78 79 override := &v1.ServerConfig{ 80 TLS: v1.ServerTLSConfig{ 81 Enabled: pointer.True(), 82 CertFile: certB64, 83 KeyFile: keyB64, 84 }, 85 } 86 overrideBytes, err := json.Marshal(override) 87 Expect(err).NotTo(HaveOccurred()) 88 89 expiringCA = CAWithOverrides(json.RawMessage(overrideBytes)) 90 helper.CreateCA(ibpCRClient, expiringCA.CR) 91 92 Eventually(expiringCA.PodIsRunning).Should((Equal(true))) 93 }) 94 95 When("TLS cert renew action is set to false", func() { 96 BeforeEach(func() { 97 patch := func(o client.Object) { 98 ibpca = o.(*current.IBPCA) 99 ibpca.Spec.Action.Renew.TLSCert = true 100 } 101 102 err := integration.ResilientPatch(ibpCRClient, expiringCA.Name, namespace, IBPCAS, 3, ¤t.IBPCA{}, patch) 103 Expect(err).NotTo(HaveOccurred()) 104 105 Eventually(expiringCA.PodIsRunning).Should((Equal(true))) 106 }) 107 108 It("renews", func() { 109 By("backing up old crypto", func() { 110 Eventually(func() bool { 111 backup, err := GetBackup("tls", expiringCA.CR.Name) 112 if err != nil { 113 return false 114 } 115 116 if len(backup.List) > 0 { 117 return backup.List[len(backup.List)-1].SignCerts == base64.StdEncoding.EncodeToString(cert) 118 } 119 120 return false 121 }).Should(Equal(true)) 122 }) 123 124 By("updating crypto secret with new TLS Cert", func() { 125 Eventually(func() bool { 126 crypto, err := kclient.CoreV1().Secrets(namespace). 127 Get(context.TODO(), fmt.Sprintf("%s-ca-crypto", expiringCA.CR.Name), metav1.GetOptions{}) 128 Expect(err).NotTo(HaveOccurred()) 129 130 return bytes.Equal(cert, crypto.Data["tls-cert.pem"]) 131 }).Should(Equal(false)) 132 }) 133 134 By("updating operations cert to match new TLS cert", func() { 135 crypto, err := kclient.CoreV1().Secrets(namespace). 136 Get(context.TODO(), fmt.Sprintf("%s-ca-crypto", expiringCA.CR.Name), metav1.GetOptions{}) 137 Expect(err).NotTo(HaveOccurred()) 138 139 Expect(bytes.Equal( 140 crypto.Data["operations-cert.pem"], 141 crypto.Data["tls-cert.pem"], 142 )).To(Equal(true)) 143 }) 144 145 By("refreshing the TLS certificate with expiration value of plus 10 years", func() { 146 crypto, err := kclient.CoreV1().Secrets(namespace). 147 Get(context.TODO(), fmt.Sprintf("%s-ca-crypto", expiringCA.CR.Name), metav1.GetOptions{}) 148 Expect(err).NotTo(HaveOccurred()) 149 150 newTLSCert := crypto.Data["tls-cert.pem"] 151 newCert, err := util.GetCertificateFromPEMBytes(newTLSCert) 152 Expect(err).NotTo(HaveOccurred()) 153 Expect(newCert.NotAfter.Year()).To(Equal(time.Now().Add(time.Hour * 87600).Year())) 154 }) 155 156 By("updating crypto secret with new TLS Key", func() { 157 Eventually(func() bool { 158 crypto, err := kclient.CoreV1().Secrets(namespace). 159 Get(context.TODO(), fmt.Sprintf("%s-ca-crypto", expiringCA.CR.Name), metav1.GetOptions{}) 160 Expect(err).NotTo(HaveOccurred()) 161 162 return bytes.Equal(key, crypto.Data["tls-key.pem"]) 163 }).Should(Equal(false)) 164 }) 165 166 By("updating operations key to match new TLS Key", func() { 167 crypto, err := kclient.CoreV1().Secrets(namespace). 168 Get(context.TODO(), fmt.Sprintf("%s-ca-crypto", expiringCA.CR.Name), metav1.GetOptions{}) 169 Expect(err).NotTo(HaveOccurred()) 170 171 Expect(bytes.Equal( 172 crypto.Data["operations-key.pem"], 173 crypto.Data["tls-key.pem"], 174 )).To(Equal(true)) 175 }) 176 177 By("updating connection profile with new TLS cert", func() { 178 Eventually(func() bool { 179 cm, err := kclient.CoreV1(). 180 ConfigMaps(namespace). 181 Get(context.TODO(), 182 fmt.Sprintf("%s-connection-profile", expiringCA.CR.Name), 183 metav1.GetOptions{}, 184 ) 185 Expect(err).NotTo(HaveOccurred()) 186 187 profileBytes := cm.BinaryData["profile.json"] 188 connectionProfile := ¤t.CAConnectionProfile{} 189 err = json.Unmarshal(profileBytes, connectionProfile) 190 Expect(err).NotTo(HaveOccurred()) 191 192 crypto, err := kclient.CoreV1().Secrets(namespace). 193 Get(context.TODO(), fmt.Sprintf("%s-ca-crypto", expiringCA.CR.Name), metav1.GetOptions{}) 194 Expect(err).NotTo(HaveOccurred()) 195 196 return bytes.Equal([]byte(connectionProfile.TLS.Cert), crypto.Data["tls-key.pem"]) 197 }).Should(Equal(false)) 198 }) 199 200 By("setting restart flag back to false after restart", func() { 201 Eventually(func() bool { 202 result := ibpCRClient.Get().Namespace(namespace).Resource(IBPCAS).Name(expiringCA.Name).Do(context.TODO()) 203 ibpca := ¤t.IBPCA{} 204 result.Into(ibpca) 205 206 return ibpca.Spec.Action.Renew.TLSCert 207 }).Should(Equal(false)) 208 }) 209 }) 210 }) 211 }) 212 }) 213 214 Context("restart", func() { 215 var ( 216 podName string 217 ca *current.IBPCA 218 ) 219 220 BeforeEach(func() { 221 Eventually(func() int { 222 return len(org1ca.GetPods()) 223 }).Should(Equal(1)) 224 225 podName = org1ca.GetPods()[0].Name 226 227 result := ibpCRClient.Get().Namespace(namespace).Resource(IBPCAS).Name(org1ca.Name).Do(context.TODO()) 228 Expect(result.Error()).NotTo(HaveOccurred()) 229 230 ca = ¤t.IBPCA{} 231 result.Into(ca) 232 }) 233 234 When("spec has restart flag set to true", func() { 235 BeforeEach(func() { 236 ca.Spec.Action.Restart = true 237 }) 238 239 It("performs restart action", func() { 240 bytes, err := json.Marshal(ca) 241 Expect(err).NotTo(HaveOccurred()) 242 243 result := ibpCRClient.Put().Namespace(namespace).Resource(IBPCAS).Name(org1ca.Name).Body(bytes).Do(context.TODO()) 244 Expect(result.Error()).NotTo(HaveOccurred()) 245 246 Eventually(org1ca.PodIsRunning).Should((Equal(true))) 247 248 By("restarting ca pod", func() { 249 Eventually(func() bool { 250 pods := org1ca.GetPods() 251 if len(pods) == 0 { 252 return false 253 } 254 255 newPodName := pods[0].Name 256 if newPodName != podName { 257 return true 258 } 259 260 return false 261 }).Should(Equal(true)) 262 }) 263 264 By("setting restart flag back to false after restart", func() { 265 Eventually(func() bool { 266 result := ibpCRClient.Get().Namespace(namespace).Resource(IBPCAS).Name(org1ca.Name).Do(context.TODO()) 267 ca := ¤t.IBPCA{} 268 result.Into(ca) 269 270 return ca.Spec.Action.Restart 271 }).Should(Equal(false)) 272 }) 273 }) 274 }) 275 }) 276 277 }) 278 279 func CAWithOverrides(rawMessage json.RawMessage) *helper.CA { 280 cr := ¤t.IBPCA{ 281 ObjectMeta: metav1.ObjectMeta{ 282 Name: "org2ca", 283 Namespace: namespace, 284 }, 285 Spec: current.IBPCASpec{ 286 License: current.License{ 287 Accept: true, 288 }, 289 ImagePullSecrets: []string{"regcred"}, 290 Images: ¤t.CAImages{ 291 CAImage: integration.CaImage, 292 CATag: integration.CaTag, 293 CAInitImage: integration.InitImage, 294 CAInitTag: integration.InitTag, 295 }, 296 Resources: ¤t.CAResources{ 297 CA: &corev1.ResourceRequirements{ 298 Requests: corev1.ResourceList{ 299 corev1.ResourceCPU: resource.MustParse("50m"), 300 corev1.ResourceMemory: resource.MustParse("100M"), 301 corev1.ResourceEphemeralStorage: resource.MustParse("100M"), 302 }, 303 Limits: corev1.ResourceList{ 304 corev1.ResourceCPU: resource.MustParse("50m"), 305 corev1.ResourceMemory: resource.MustParse("100M"), 306 corev1.ResourceEphemeralStorage: resource.MustParse("1G"), 307 }, 308 }, 309 }, 310 Zone: "select", 311 Region: "select", 312 Domain: domain, 313 ConfigOverride: ¤t.ConfigOverride{ 314 CA: &runtime.RawExtension{Raw: rawMessage}, 315 }, 316 FabricVersion: integration.FabricCAVersion, 317 }, 318 } 319 320 return &helper.CA{ 321 Domain: domain, 322 Name: cr.Name, 323 Namespace: namespace, 324 WorkingDir: wd, 325 CR: cr, 326 CRClient: ibpCRClient, 327 KClient: kclient, 328 NativeResourcePoller: integration.NativeResourcePoller{ 329 Name: cr.Name, 330 Namespace: namespace, 331 Client: kclient, 332 }, 333 } 334 } 335 336 // Generate TLS cert that is expires in the x days 337 func GenSelfSignedCert(expiresIn time.Duration) ([]byte, []byte, error) { 338 priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) 339 if err != nil { 340 return nil, nil, errors.Wrap(err, "failed to generate key") 341 } 342 343 serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128) 344 serialNumber, err := rand.Int(rand.Reader, serialNumberLimit) 345 if err != nil { 346 return nil, nil, errors.Wrap(err, "failed to generate serial number") 347 } 348 349 notBefore := time.Now() 350 notAfter := notBefore.Add(expiresIn) 351 352 template := x509.Certificate{ 353 SerialNumber: serialNumber, 354 Issuer: pkix.Name{ 355 Country: []string{"US"}, 356 Province: []string{"North Carolina"}, 357 Locality: []string{"Durham"}, 358 Organization: []string{"IBM"}, 359 OrganizationalUnit: []string{"Blockchain"}, 360 }, 361 Subject: pkix.Name{ 362 Country: []string{"US"}, 363 Province: []string{"North Carolina"}, 364 Locality: []string{"Durham"}, 365 Organization: []string{"IBM"}, 366 OrganizationalUnit: []string{"Blockchain"}, 367 }, 368 NotBefore: notBefore, 369 NotAfter: notAfter, 370 } 371 372 derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, &priv.PublicKey, priv) 373 if err != nil { 374 return nil, nil, errors.Wrap(err, "failed to create certificate") 375 } 376 377 keyBytes, err := x509.MarshalECPrivateKey(priv) 378 if err != nil { 379 return nil, nil, errors.Wrap(err, "failed to marshal key") 380 } 381 382 certPEM := &pem.Block{Type: "CERTIFICATE", Bytes: derBytes} 383 keyPEM := &pem.Block{Type: "EC PRIVATE KEY", Bytes: keyBytes} 384 385 certBytes := pem.EncodeToMemory(certPEM) 386 keyBytes = pem.EncodeToMemory(keyPEM) 387 388 return keyBytes, certBytes, nil 389 } 390 391 func GetBackup(certType, name string) (*common.Backup, error) { 392 backupSecret, err := kclient.CoreV1().Secrets(namespace).Get(context.TODO(), fmt.Sprintf("%s-crypto-backup", name), metav1.GetOptions{}) 393 if err != nil { 394 return nil, err 395 } 396 397 backup := &common.Backup{} 398 key := fmt.Sprintf("%s-backup.json", certType) 399 err = json.Unmarshal(backupSecret.Data[key], backup) 400 if err != nil { 401 return nil, err 402 } 403 404 return backup, nil 405 }