istio.io/istio@v0.0.0-20240520182934-d79c90f27776/tests/integration/security/ca_custom_root/secure_naming_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 cacustomroot 19 20 import ( 21 "context" 22 "fmt" 23 "strings" 24 "testing" 25 "time" 26 27 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 28 29 "istio.io/istio/pkg/config/constants" 30 "istio.io/istio/pkg/test/framework" 31 "istio.io/istio/pkg/test/framework/components/echo" 32 "istio.io/istio/pkg/test/framework/components/echo/check" 33 "istio.io/istio/pkg/test/framework/components/echo/match" 34 "istio.io/istio/pkg/test/framework/components/istio" 35 "istio.io/istio/pkg/test/framework/components/namespace" 36 "istio.io/istio/pkg/test/util/retry" 37 "istio.io/istio/tests/integration/security/util/cert" 38 ) 39 40 const ( 41 // The length of the example certificate chain. 42 exampleCertChainLength = 3 43 44 defaultIdentityDR = `apiVersion: networking.istio.io/v1alpha3 45 kind: DestinationRule 46 metadata: 47 name: "service-b-dr" 48 spec: 49 host: "b.NS.svc.cluster.local" 50 trafficPolicy: 51 tls: 52 mode: ISTIO_MUTUAL 53 subjectAltNames: 54 - "spiffe://cluster.local/ns/NS/sa/default" 55 ` 56 correctIdentityDR = `apiVersion: networking.istio.io/v1alpha3 57 kind: DestinationRule 58 metadata: 59 name: "service-b-dr" 60 spec: 61 host: "b.NS.svc.cluster.local" 62 trafficPolicy: 63 tls: 64 mode: ISTIO_MUTUAL 65 subjectAltNames: 66 - "spiffe://cluster.local/ns/NS/sa/b" 67 ` 68 nonExistIdentityDR = `apiVersion: networking.istio.io/v1alpha3 69 kind: DestinationRule 70 metadata: 71 name: "service-b-dr" 72 spec: 73 host: "b.NS.svc.cluster.local" 74 trafficPolicy: 75 tls: 76 mode: ISTIO_MUTUAL 77 subjectAltNames: 78 - "I-do-not-exist" 79 ` 80 identityListDR = `apiVersion: networking.istio.io/v1alpha3 81 kind: DestinationRule 82 metadata: 83 name: "service-b-dr" 84 spec: 85 host: "b.NS.svc.cluster.local" 86 trafficPolicy: 87 tls: 88 mode: ISTIO_MUTUAL 89 subjectAltNames: 90 - "spiffe://cluster.local/ns/NS/sa/a" 91 - "spiffe://cluster.local/ns/NS/sa/b" 92 - "spiffe://cluster.local/ns/default/sa/default" 93 - "I-do-not-exist" 94 ` 95 ) 96 97 // TestSecureNaming verifies: 98 // - The certificate issued by CA to the sidecar is as expected and that strict mTLS works as expected. 99 // - The plugin CA certs are correctly used in workload mTLS. 100 // - The CA certificate in the configmap of each namespace is as expected, which 101 // 102 // is used for data plane to control plane TLS authentication. 103 // 104 // - Secure naming information is respected in the mTLS handshake. 105 func TestSecureNaming(t *testing.T) { 106 framework.NewTest(t). 107 Run(func(t framework.TestContext) { 108 istioCfg := istio.DefaultConfigOrFail(t, t) 109 110 testNamespace := apps.EchoNamespace.Namespace 111 namespace.ClaimOrFail(t, t, istioCfg.SystemNamespace) 112 // Check that the CA certificate in the configmap of each namespace is as expected, which 113 // is used for data plane to control plane TLS authentication. 114 retry.UntilSuccessOrFail(t, func() error { 115 return checkCACert(t, testNamespace) 116 }, retry.Delay(time.Second), retry.Timeout(10*time.Second)) 117 to := match.Namespace(testNamespace).GetMatches(apps.EchoNamespace.B) 118 for _, cluster := range t.Clusters() { 119 t.NewSubTest(fmt.Sprintf("From %s", cluster.StableName())).Run(func(t framework.TestContext) { 120 a := match.And(match.Cluster(cluster), match.Namespace(testNamespace)).GetMatches(apps.EchoNamespace.A)[0] 121 b := match.And(match.Cluster(cluster), match.Namespace(testNamespace)).GetMatches(apps.EchoNamespace.B)[0] 122 t.NewSubTest("mTLS cert validation with plugin CA"). 123 Run(func(t framework.TestContext) { 124 // Verify that the certificate issued to the sidecar is as expected. 125 out := cert.DumpCertFromSidecar(t, a, b, "http") 126 verifyCertificatesWithPluginCA(t, out) 127 128 // Verify mTLS works between a and b 129 opts := echo.CallOptions{ 130 To: to, 131 Port: echo.Port{ 132 Name: "http", 133 }, 134 } 135 opts.Check = check.And(check.OK(), check.ReachedTargetClusters(t)) 136 a.CallOrFail(t, opts) 137 }) 138 139 secureNamingTestCases := []struct { 140 name string 141 destinationRule string 142 expectSuccess bool 143 }{ 144 { 145 name: "connection fails when DR doesn't match SA", 146 destinationRule: defaultIdentityDR, 147 expectSuccess: false, 148 }, 149 { 150 name: "connection succeeds when DR matches SA", 151 destinationRule: correctIdentityDR, 152 expectSuccess: true, 153 }, 154 { 155 name: "connection fails when DR contains non-matching, non-existing SA", 156 destinationRule: nonExistIdentityDR, 157 expectSuccess: false, 158 }, 159 { 160 name: "connection succeeds when SA is in the list of SANs", 161 destinationRule: identityListDR, 162 expectSuccess: true, 163 }, 164 } 165 for _, tc := range secureNamingTestCases { 166 t.NewSubTest(tc.name). 167 Run(func(t framework.TestContext) { 168 dr := strings.ReplaceAll(tc.destinationRule, "NS", testNamespace.Name()) 169 t.ConfigIstio().YAML(testNamespace.Name(), dr).ApplyOrFail(t) 170 // Verify mTLS works between a and b 171 opts := echo.CallOptions{ 172 To: to, 173 Port: echo.Port{ 174 Name: "http", 175 }, 176 } 177 if tc.expectSuccess { 178 opts.Check = check.And(check.OK(), check.ReachedTargetClusters(t)) 179 } else { 180 opts.Check = check.NotOK() 181 } 182 183 a.CallOrFail(t, opts) 184 }) 185 } 186 }) 187 } 188 }) 189 } 190 191 func verifyCertificatesWithPluginCA(t framework.TestContext, certs []string) { 192 // Verify that the certificate chain length is as expected 193 if len(certs) != exampleCertChainLength { 194 t.Errorf("expect %v certs in the cert chain but getting %v certs", 195 exampleCertChainLength, len(certs)) 196 return 197 } 198 var rootCert []byte 199 var err error 200 if rootCert, err = cert.ReadSampleCertFromFile("root-cert.pem"); err != nil { 201 t.Errorf("error when reading expected CA cert: %v", err) 202 return 203 } 204 // Verify that the CA certificate is as expected 205 if strings.TrimSpace(string(rootCert)) != strings.TrimSpace(certs[2]) { 206 t.Errorf("the actual CA cert is different from the expected. expected: %v, actual: %v", 207 strings.TrimSpace(string(rootCert)), strings.TrimSpace(certs[2])) 208 return 209 } 210 t.Log("the CA certificate is as expected") 211 } 212 213 func checkCACert(t framework.TestContext, testNamespace namespace.Instance) error { 214 configMapName := "istio-ca-root-cert" 215 cm, err := t.Clusters().Default().Kube().CoreV1().ConfigMaps(testNamespace.Name()).Get(context.TODO(), configMapName, 216 metav1.GetOptions{}) 217 if err != nil { 218 return err 219 } 220 var certData string 221 var pluginCert []byte 222 var ok bool 223 if certData, ok = cm.Data[constants.CACertNamespaceConfigMapDataName]; !ok { 224 return fmt.Errorf("CA certificate %v not found", constants.CACertNamespaceConfigMapDataName) 225 } 226 t.Logf("CA certificate %v found", constants.CACertNamespaceConfigMapDataName) 227 if pluginCert, err = cert.ReadSampleCertFromFile("root-cert.pem"); err != nil { 228 return err 229 } 230 if string(pluginCert) != certData { 231 return fmt.Errorf("CA certificate (%v) not matching plugin cert (%v)", certData, string(pluginCert)) 232 } 233 234 return nil 235 }