istio.io/istio@v0.0.0-20240520182934-d79c90f27776/tests/integration/security/file_mounted_certs/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 filemountedcerts 19 20 import ( 21 "context" 22 "fmt" 23 "os" 24 "path" 25 "strings" 26 "testing" 27 28 v1 "k8s.io/api/core/v1" 29 kerrors "k8s.io/apimachinery/pkg/api/errors" 30 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 31 32 "istio.io/api/annotation" 33 "istio.io/istio/pkg/config/protocol" 34 "istio.io/istio/pkg/test/env" 35 "istio.io/istio/pkg/test/framework" 36 "istio.io/istio/pkg/test/framework/components/echo" 37 "istio.io/istio/pkg/test/framework/components/echo/common/deployment" 38 "istio.io/istio/pkg/test/framework/components/istio" 39 "istio.io/istio/pkg/test/framework/components/namespace" 40 "istio.io/istio/pkg/test/framework/label" 41 "istio.io/istio/pkg/test/framework/resource" 42 ) 43 44 var ( 45 inst istio.Instance 46 apps deployment.SingleNamespaceView 47 client echo.Instances 48 server echo.Instances 49 echo1NS namespace.Instance 50 customConfig []echo.Config 51 ) 52 53 const ( 54 PilotCertsPath = "tests/testdata/certs/pilot" 55 PilotSecretName = "test-istiod-server-cred" 56 57 ProxyMetadataJSON = ` 58 { 59 "FILE_MOUNTED_CERTS": "true", 60 "ISTIO_META_TLS_CLIENT_CERT_CHAIN": "/client-certs/cert-chain.pem", 61 "ISTIO_META_TLS_CLIENT_KEY": "/client-certs/key.pem", 62 "ISTIO_META_TLS_CLIENT_ROOT_CERT": "/client-certs/root-cert.pem", 63 "ISTIO_META_TLS_SERVER_CERT_CHAIN": "/server-certs/cert-chain.pem", 64 "ISTIO_META_TLS_SERVER_KEY": "/server-certs/key.pem", 65 "ISTIO_META_TLS_SERVER_ROOT_CERT": "/server-certs/root-cert.pem", 66 } 67 ` 68 ) 69 70 func TestMain(m *testing.M) { 71 // nolint: staticcheck 72 framework. 73 NewSuite(m). 74 Label(label.CustomSetup). 75 RequireSingleCluster(). 76 RequireMultiPrimary(). 77 Setup(istio.Setup(&inst, setupConfig, CreateCustomIstiodSecret)). 78 Setup(namespace.Setup(&echo1NS, namespace.Config{Prefix: "echo1", Inject: true})). 79 Setup(func(ctx resource.Context) error { 80 err := setupApps(ctx, namespace.Future(&echo1NS), &customConfig) 81 if err != nil { 82 return err 83 } 84 return nil 85 }). 86 Setup(deployment.SetupSingleNamespace(&apps, deployment.Config{ 87 Namespaces: []namespace.Getter{ 88 namespace.Future(&echo1NS), 89 }, 90 Configs: echo.ConfigFuture(&customConfig), 91 })). 92 Setup(func(ctx resource.Context) error { 93 return createCustomInstances(&apps) 94 }). 95 Run() 96 } 97 98 func setupConfig(_ resource.Context, cfg *istio.Config) { 99 if cfg == nil { 100 return 101 } 102 103 cfg.ControlPlaneValues = ` 104 components: 105 egressGateways: 106 - enabled: true 107 name: istio-egressgateway 108 k8s: 109 overlays: 110 - kind: Deployment 111 name: istio-egressgateway 112 patches: 113 - path: spec.template.spec.volumes[100] 114 value: |- 115 name: server-certs 116 secret: 117 secretName: ` + PilotSecretName + ` 118 defaultMode: 420 119 - path: spec.template.spec.volumes[101] 120 value: |- 121 name: client-certs 122 secret: 123 secretName: ` + PilotSecretName + ` 124 defaultMode: 420 125 - path: spec.template.spec.containers[0].volumeMounts[100] 126 value: |- 127 name: server-certs 128 mountPath: /server-certs 129 - path: spec.template.spec.containers[0].volumeMounts[101] 130 value: |- 131 name: client-certs 132 mountPath: /client-certs 133 134 ingressGateways: 135 - enabled: true 136 name: istio-ingressgateway 137 k8s: 138 overlays: 139 - kind: Deployment 140 name: istio-ingressgateway 141 patches: 142 - path: spec.template.spec.volumes[100] 143 value: |- 144 name: server-certs 145 secret: 146 secretName: ` + PilotSecretName + ` 147 defaultMode: 420 148 - path: spec.template.spec.volumes[101] 149 value: |- 150 name: client-certs 151 secret: 152 secretName: ` + PilotSecretName + ` 153 defaultMode: 420 154 - path: spec.template.spec.containers[0].volumeMounts[100] 155 value: |- 156 name: server-certs 157 mountPath: /server-certs 158 - path: spec.template.spec.containers[0].volumeMounts[101] 159 value: |- 160 name: client-certs 161 mountPath: /client-certs 162 163 pilot: 164 enabled: true 165 k8s: 166 overlays: 167 - kind: Deployment 168 name: istiod 169 patches: 170 - path: spec.template.spec.containers.[name:discovery].args[1001] 171 value: "--caCertFile=/server-certs/root-cert.pem" 172 - path: spec.template.spec.containers.[name:discovery].args[1002] 173 value: "--tlsCertFile=/server-certs/cert-chain.pem" 174 - path: spec.template.spec.containers.[name:discovery].args[1003] 175 value: "--tlsKeyFile=/server-certs/key.pem" 176 - path: spec.template.spec.volumes[-1] 177 value: |- 178 name: server-certs 179 secret: 180 secretName: ` + PilotSecretName + ` 181 defaultMode: 420 182 - path: spec.template.spec.containers[name:discovery].volumeMounts[-1] 183 value: |- 184 name: server-certs 185 mountPath: /server-certs 186 187 meshConfig: 188 defaultConfig: 189 controlPlaneAuthPolicy: "MUTUAL_TLS" 190 proxyMetadata: ` + strings.Replace(ProxyMetadataJSON, "\n", "", -1) + 191 ` 192 values: 193 global: 194 pilotCertProvider: "mycopki" 195 pilot: 196 env: 197 # We need to turn off the XDS Auth because test certificates only have a fixed/hardcoded identity, but the identity of the actual 198 # deployed test services changes on each run due to a randomly generated namespace suffixes. 199 # Turning the XDS-Auth ON will result in the error messages like: 200 # Unauthorized XDS: 10.1.0.159:41960 with identity [spiffe://cluster.local/ns/mounted-certs/sa/client client.mounted-certs.svc]: 201 # no identities ([spiffe://cluster.local/ns/mounted-certs/sa/client client.mounted-certs.svc]) matched istio-fd-sds-1-4523/default 202 XDS_AUTH: "false" 203 ` 204 205 cfg.EastWestGatewayValues = ` 206 components: 207 ingressGateways: 208 - enabled: true 209 name: istio-eastwestgateway 210 label: 211 istio: eastwestgateway 212 app: istio-eastwestgateway 213 k8s: 214 overlays: 215 - kind: Deployment 216 name: istio-eastwestgateway 217 patches: 218 - path: spec.template.spec.volumes[100] 219 value: |- 220 name: server-certs 221 secret: 222 secretName: ` + PilotSecretName + ` 223 defaultMode: 420 224 - path: spec.template.spec.volumes[101] 225 value: |- 226 name: client-certs 227 secret: 228 secretName: ` + PilotSecretName + ` 229 defaultMode: 420 230 - path: spec.template.spec.containers[0].volumeMounts[100] 231 value: |- 232 name: server-certs 233 mountPath: /server-certs 234 - path: spec.template.spec.containers[0].volumeMounts[101] 235 value: |- 236 name: client-certs 237 mountPath: /client-certs 238 ` 239 } 240 241 func CreateCustomIstiodSecret(ctx resource.Context) error { 242 systemNs, err := istio.ClaimSystemNamespace(ctx) 243 if err != nil { 244 return err 245 } 246 247 err = CreateCustomSecret(ctx, PilotSecretName, systemNs, PilotCertsPath) 248 if err != nil { 249 return err 250 } 251 252 return nil 253 } 254 255 func CreateCustomSecret(ctx resource.Context, name string, namespace namespace.Instance, certsPath string) error { 256 var privateKey, clientCert, caCert []byte 257 var err error 258 if privateKey, err = ReadCustomCertFromFile(certsPath, "key.pem"); err != nil { 259 return err 260 } 261 if clientCert, err = ReadCustomCertFromFile(certsPath, "cert-chain.pem"); err != nil { 262 return err 263 } 264 if caCert, err = ReadCustomCertFromFile(certsPath, "root-cert.pem"); err != nil { 265 return err 266 } 267 268 kubeAccessor := ctx.Clusters().Default() 269 secret := &v1.Secret{ 270 ObjectMeta: metav1.ObjectMeta{ 271 Name: name, 272 Namespace: namespace.Name(), 273 }, 274 Data: map[string][]byte{ 275 "key.pem": privateKey, 276 "cert-chain.pem": clientCert, 277 "root-cert.pem": caCert, 278 }, 279 } 280 281 _, err = kubeAccessor.Kube().CoreV1().Secrets(namespace.Name()).Create(context.TODO(), secret, metav1.CreateOptions{}) 282 if err != nil { 283 if kerrors.IsAlreadyExists(err) { 284 if _, err := kubeAccessor.Kube().CoreV1().Secrets(namespace.Name()).Update(context.TODO(), secret, metav1.UpdateOptions{}); err != nil { 285 return fmt.Errorf("failed updating secret %s: %v", secret.Name, err) 286 } 287 } else { 288 return fmt.Errorf("failed creating secret %s: %v", secret.Name, err) 289 } 290 } 291 return nil 292 } 293 294 func ReadCustomCertFromFile(certsPath string, f string) ([]byte, error) { 295 b, err := os.ReadFile(path.Join(env.IstioSrc, certsPath, f)) 296 if err != nil { 297 return nil, err 298 } 299 return b, nil 300 } 301 302 func setupApps(ctx resource.Context, customNs namespace.Getter, customCfg *[]echo.Config) error { 303 appsNamespace := customNs.Get() 304 305 // Server certificate has "server.file-mounted.svc" in SANs; Same is expected in DestinationRule.subjectAltNames for the test Echo server 306 // This cert is going to be used as a server and "client" certificate on the "Echo Server"'s side 307 err := CreateCustomSecret(ctx, ServerSecretName, appsNamespace, ServerCertsPath) 308 if err != nil { 309 return fmt.Errorf("Unable to create server secret. %v", err) 310 } 311 312 // Pilot secret will be used for xds connections from echo-server & echo-client to the control plane. 313 err = CreateCustomSecret(ctx, PilotSecretName, appsNamespace, PilotCertsPath) 314 if err != nil { 315 return fmt.Errorf("Unable to create pilot secret. %v", err) 316 } 317 318 // Client secret will be used as a "server" and client certificate on the "Echo Client"'s side. 319 // ie. it is going to be used for connections from EchoClient to EchoServer 320 err = CreateCustomSecret(ctx, ClientSecretName, appsNamespace, ClientCertsPath) 321 if err != nil { 322 return fmt.Errorf("Unable to create client secret. %v", err) 323 } 324 325 clientSidecarVolumes := ` 326 { 327 "server-certs": {"secret": {"secretName":"` + ClientSecretName + `"}}, 328 "client-certs": {"secret": {"secretName":"` + ClientSecretName + `"}}, 329 "workload-certs": {"secret": {"secretName": "` + ClientSecretName + `"}} 330 } 331 ` 332 333 serverSidecarVolumes := ` 334 { 335 "server-certs": {"secret": {"secretName":"` + ServerSecretName + `"}}, 336 "client-certs": {"secret": {"secretName":"` + ServerSecretName + `"}}, 337 "workload-certs": {"secret": {"secretName":"` + ServerSecretName + `"}} 338 } 339 ` 340 341 // workload-certs are needed in order to load the "default" SDS resource, which 342 // will be used for the xds-grpc mTLS (tls_certificate_sds_secret_configs.name == "default") 343 sidecarVolumeMounts := ` 344 { 345 "server-certs": { 346 "mountPath": "/server-certs" 347 }, 348 "client-certs": { 349 "mountPath": "/client-certs" 350 }, 351 "workload-certs": { 352 "mountPath": "/etc/certs" 353 } 354 } 355 ` 356 357 var customConfig []echo.Config 358 359 client := echo.Config{ 360 Service: "client", 361 Namespace: appsNamespace, 362 Ports: []echo.Port{}, 363 Subsets: []echo.SubsetConfig{{ 364 Version: "v1", 365 // Set up custom annotations to mount the certs. 366 Annotations: map[string]string{ 367 annotation.SidecarUserVolume.Name: clientSidecarVolumes, 368 annotation.SidecarUserVolumeMount.Name: sidecarVolumeMounts, 369 // the default bootstrap template does not support reusing values from the `ISTIO_META_TLS_CLIENT_*` environment variables 370 // see security/pkg/nodeagent/cache/secretcache.go:generateFileSecret() for details 371 annotation.ProxyConfig.Name: `{"controlPlaneAuthPolicy":"MUTUAL_TLS","proxyMetadata":` + strings.Replace(ProxyMetadataJSON, "\n", "", -1) + `}`, 372 }, 373 }}, 374 } 375 376 server := echo.Config{ 377 Service: "server", 378 Namespace: appsNamespace, 379 Ports: []echo.Port{ 380 { 381 Name: "http", 382 Protocol: protocol.HTTP, 383 ServicePort: 8443, 384 WorkloadPort: 8443, 385 TLS: false, 386 }, 387 }, 388 Subsets: []echo.SubsetConfig{{ 389 Version: "v1", 390 // Set up custom annotations to mount the certs. 391 Annotations: map[string]string{ 392 annotation.SidecarUserVolume.Name: serverSidecarVolumes, 393 annotation.SidecarUserVolumeMount.Name: sidecarVolumeMounts, 394 // the default bootstrap template does not support reusing values from the `ISTIO_META_TLS_CLIENT_*` environment variables 395 // see security/pkg/nodeagent/cache/secretcache.go:generateFileSecret() for details 396 annotation.ProxyConfig.Name: `{"controlPlaneAuthPolicy":"MUTUAL_TLS","proxyMetadata":` + strings.Replace(ProxyMetadataJSON, "\n", "", -1) + `}`, 397 }, 398 }}, 399 } 400 customConfig = append(customConfig, client, server) 401 *customCfg = customConfig 402 return nil 403 } 404 405 func createCustomInstances(apps *deployment.SingleNamespaceView) error { 406 for index, namespacedName := range apps.EchoNamespace.All.NamespacedNames() { 407 switch { 408 case namespacedName.Name == "client": 409 client = apps.EchoNamespace.All[index] 410 case namespacedName.Name == "server": 411 server = apps.EchoNamespace.All[index] 412 } 413 } 414 return nil 415 }