istio.io/istio@v0.0.0-20240520182934-d79c90f27776/tests/integration/security/cacert_rotation/main_test.go (about) 1 //go:build integ 2 // +build integ 3 4 // Copyright Istio Authors 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 cacertrotation 19 20 import ( 21 "errors" 22 "fmt" 23 "testing" 24 "time" 25 26 admin "github.com/envoyproxy/go-control-plane/envoy/admin/v3" 27 28 "istio.io/istio/pkg/security" 29 "istio.io/istio/pkg/test/framework" 30 "istio.io/istio/pkg/test/framework/components/echo" 31 "istio.io/istio/pkg/test/framework/components/echo/check" 32 "istio.io/istio/pkg/test/framework/components/echo/common/deployment" 33 "istio.io/istio/pkg/test/framework/components/echo/echotest" 34 "istio.io/istio/pkg/test/framework/components/echo/match" 35 "istio.io/istio/pkg/test/framework/components/istio" 36 "istio.io/istio/pkg/test/framework/components/istioctl" 37 "istio.io/istio/pkg/test/framework/components/namespace" 38 "istio.io/istio/pkg/test/framework/label" 39 "istio.io/istio/pkg/test/framework/resource" 40 "istio.io/istio/pkg/test/util/retry" 41 "istio.io/istio/pkg/util/protomarshal" 42 "istio.io/istio/tests/integration/security/util/cert" 43 "istio.io/istio/tests/integration/security/util/reachability" 44 ) 45 46 var apps deployment.SingleNamespaceView 47 48 func TestMain(m *testing.M) { 49 framework. 50 NewSuite(m). 51 Label(label.CustomSetup). 52 Setup(istio.Setup(nil, setupConfig, cert.CreateCASecret)). 53 Setup(deployment.SetupSingleNamespace(&apps, deployment.Config{})). 54 Setup(func(ctx resource.Context) error { 55 return reachability.CreateCustomInstances(&apps) 56 }). 57 Run() 58 } 59 60 func setupConfig(_ resource.Context, cfg *istio.Config) { 61 if cfg == nil { 62 return 63 } 64 cfgYaml := ` 65 values: 66 pilot: 67 env: 68 ISTIO_MULTIROOT_MESH: true 69 meshConfig: 70 defaultConfig: 71 proxyMetadata: 72 PROXY_CONFIG_XDS_AGENT: "true" 73 ` 74 cfg.ControlPlaneValues = cfgYaml 75 } 76 77 func TestReachability(t *testing.T) { 78 framework.NewTest(t). 79 Run(func(t framework.TestContext) { 80 istioCfg := istio.DefaultConfigOrFail(t, t) 81 istioCtl := istioctl.NewOrFail(t, t, istioctl.Config{}) 82 namespace.ClaimOrFail(t, t, istioCfg.SystemNamespace) 83 84 from := apps.EchoNamespace.A 85 to := apps.EchoNamespace.B 86 fromAndTo := from.Append(to) 87 88 lastUpdateTime, err := getWorkloadCertLastUpdateTime(t, from[0], istioCtl) 89 if err != nil { 90 t.Errorf("failed to get workload cert last update time: %v", err) 91 } 92 93 // Verify traffic works between a and b 94 echotest.New(t, fromAndTo). 95 WithDefaultFilters(1, 1). 96 FromMatch(match.ServiceName(from.NamespacedName())). 97 ToMatch(match.ServiceName(to.NamespacedName())). 98 Run(func(t framework.TestContext, from echo.Instance, to echo.Target) { 99 // Verify mTLS works between a and b 100 opts := echo.CallOptions{ 101 To: to, 102 Port: echo.Port{ 103 Name: "http", 104 }, 105 } 106 opts.Check = check.And(check.OK(), check.ReachedTargetClusters(t)) 107 108 from.CallOrFail(t, opts) 109 }) 110 111 // step 1: Update CA root cert with combined root 112 if err := cert.CreateCustomCASecret(t, 113 "ca-cert.pem", "ca-key.pem", 114 "cert-chain.pem", "root-cert-combined.pem"); err != nil { 115 t.Errorf("failed to update combined CA secret: %v", err) 116 } 117 118 lastUpdateTime = waitForWorkloadCertUpdate(t, from[0], istioCtl, lastUpdateTime) 119 120 // step 2: Update CA signing key/cert with cacert to trigger workload cert resigning 121 if err := cert.CreateCustomCASecret(t, 122 "ca-cert-alt.pem", "ca-key-alt.pem", 123 "cert-chain-alt.pem", "root-cert-combined-2.pem"); err != nil { 124 t.Errorf("failed to update CA secret: %v", err) 125 } 126 127 lastUpdateTime = waitForWorkloadCertUpdate(t, from[0], istioCtl, lastUpdateTime) 128 129 // step 3: Remove the old root cert 130 if err := cert.CreateCustomCASecret(t, 131 "ca-cert-alt.pem", "ca-key-alt.pem", 132 "cert-chain-alt.pem", "root-cert-alt.pem"); err != nil { 133 t.Errorf("failed to update CA secret: %v", err) 134 } 135 136 waitForWorkloadCertUpdate(t, from[0], istioCtl, lastUpdateTime) 137 138 // Verify traffic works between a and b after cert rotation 139 echotest.New(t, fromAndTo). 140 WithDefaultFilters(1, 1). 141 FromMatch(match.ServiceName(from.NamespacedName())). 142 ToMatch(match.ServiceName(to.NamespacedName())). 143 Run(func(t framework.TestContext, from echo.Instance, to echo.Target) { 144 // Verify mTLS works between a and b 145 opts := echo.CallOptions{ 146 To: to, 147 Port: echo.Port{ 148 Name: "http", 149 }, 150 } 151 opts.Check = check.And(check.OK(), check.ReachedTargetClusters(t)) 152 153 from.CallOrFail(t, opts) 154 }) 155 }) 156 } 157 158 func getWorkloadCertLastUpdateTime(t framework.TestContext, i echo.Instance, ctl istioctl.Instance) (time.Time, error) { 159 podID, err := getPodID(i) 160 if err != nil { 161 t.Fatalf("Could not get Pod ID: %v", err) 162 } 163 podName := fmt.Sprintf("%s.%s", podID, i.NamespaceName()) 164 out, errOut, err := ctl.Invoke([]string{"pc", "s", podName, "-o", "json"}) 165 if err != nil || errOut != "" { 166 t.Errorf("failed to retrieve pod secret from %s, err: %v errOut: %s", podName, err, errOut) 167 } 168 169 dump := &admin.SecretsConfigDump{} 170 if err := protomarshal.Unmarshal([]byte(out), dump); err != nil { 171 t.Errorf("failed to unmarshal secret dump: %v", err) 172 } 173 174 for _, s := range dump.DynamicActiveSecrets { 175 if s.Name == security.WorkloadKeyCertResourceName { 176 return s.LastUpdated.AsTime(), nil 177 } 178 } 179 180 return time.Now(), errors.New("failed to find workload cert") 181 } 182 183 // Abstracted function to wait for workload cert to be updated 184 func waitForWorkloadCertUpdate(t framework.TestContext, from echo.Instance, istioCtl istioctl.Instance, lastUpdateTime time.Time) time.Time { 185 retry.UntilOrFail(t, func() bool { 186 updateTime, err := getWorkloadCertLastUpdateTime(t, from, istioCtl) 187 if err != nil { 188 t.Logf("failed to get workload cert last update time: %v", err) 189 return false 190 } 191 192 // retry when workload cert is not updated 193 if updateTime.After(lastUpdateTime) { 194 lastUpdateTime = updateTime 195 t.Logf("workload cert is updated, last update time: %v", updateTime) 196 return true 197 } 198 199 return false 200 }, retry.Timeout(5*time.Minute), retry.Delay(1*time.Second)) 201 return lastUpdateTime 202 } 203 204 func getPodID(i echo.Instance) (string, error) { 205 wls, err := i.Workloads() 206 if err != nil { 207 return "", nil 208 } 209 210 for _, wl := range wls { 211 return wl.PodName(), nil 212 } 213 214 return "", fmt.Errorf("no workloads") 215 }