istio.io/istio@v0.0.0-20240520182934-d79c90f27776/tests/integration/security/ca_custom_root/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  // cacustomroot creates cluster with custom plugin root CA (samples/cert/ca-cert.pem)
    19  // instead of using the auto-generated self-signed root CA.
    20  package cacustomroot
    21  
    22  import (
    23  	"fmt"
    24  	"os"
    25  	"os/exec"
    26  	"path"
    27  	"testing"
    28  
    29  	"istio.io/api/annotation"
    30  	"istio.io/istio/pkg/config/protocol"
    31  	"istio.io/istio/pkg/test/echo/common"
    32  	"istio.io/istio/pkg/test/env"
    33  	"istio.io/istio/pkg/test/framework"
    34  	"istio.io/istio/pkg/test/framework/components/echo"
    35  	"istio.io/istio/pkg/test/framework/components/echo/common/deployment"
    36  	"istio.io/istio/pkg/test/framework/components/istio"
    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/tmpl"
    41  	"istio.io/istio/tests/integration/security/util/cert"
    42  )
    43  
    44  var (
    45  	inst              istio.Instance
    46  	apps              deployment.SingleNamespaceView
    47  	client            echo.Instances
    48  	server            echo.Instances
    49  	serverNakedFoo    echo.Instances
    50  	serverNakedBar    echo.Instances
    51  	serverNakedFooAlt echo.Instances
    52  	customConfig      []echo.Config
    53  	echo1NS           namespace.Instance
    54  	config            deployment.Config
    55  )
    56  
    57  func TestMain(m *testing.M) {
    58  	framework.
    59  		NewSuite(m).
    60  		// k8s is required because the plugin CA key and certificate are stored in a k8s secret.
    61  		Label(label.CustomSetup).
    62  		Setup(istio.Setup(&inst, setupConfig, cert.CreateCASecret)).
    63  		Setup(namespace.Setup(&echo1NS, namespace.Config{Prefix: "echo1", Inject: true})).
    64  		Setup(func(ctx resource.Context) error {
    65  			err := SetupApps(ctx, namespace.Future(&echo1NS), &customConfig)
    66  			if err != nil {
    67  				return err
    68  			}
    69  			return nil
    70  		}).
    71  		Setup(func(ctx resource.Context) error {
    72  			config = deployment.Config{
    73  				Namespaces: []namespace.Getter{
    74  					namespace.Future(&echo1NS),
    75  				},
    76  				Configs: echo.ConfigFuture(&customConfig),
    77  			}
    78  			err := addDefaultConfig(ctx, config, &customConfig)
    79  			if err != nil {
    80  				return err
    81  			}
    82  			return nil
    83  		}).
    84  		Setup(deployment.SetupSingleNamespace(&apps, deployment.Config{
    85  			Namespaces: []namespace.Getter{
    86  				namespace.Future(&echo1NS),
    87  			},
    88  			Configs: echo.ConfigFuture(&customConfig),
    89  		})).
    90  		Setup(func(ctx resource.Context) error {
    91  			return createCustomInstances(&apps)
    92  		}).
    93  		Run()
    94  }
    95  
    96  func setupConfig(_ resource.Context, cfg *istio.Config) {
    97  	if cfg == nil {
    98  		return
    99  	}
   100  	// Add alternate root certificate to list of trusted anchors
   101  	script := path.Join(env.IstioSrc, "samples/certs", "root-cert-alt.pem")
   102  	rootPEM, err := cert.LoadCert(script)
   103  	if err != nil {
   104  		return
   105  	}
   106  
   107  	cfgYaml := tmpl.MustEvaluate(`
   108  values:
   109    pilot:
   110      env:
   111        ISTIO_MULTIROOT_MESH: true
   112    meshConfig:
   113      defaultConfig:
   114        proxyMetadata:
   115          PROXY_CONFIG_XDS_AGENT: "true"
   116      trustDomainAliases: [some-other, trust-domain-foo]
   117      caCertificates:
   118      - pem: |
   119  {{.pem | indent 8}}
   120  `, map[string]string{"pem": rootPEM})
   121  	cfg.ControlPlaneValues = cfgYaml
   122  }
   123  
   124  func createCustomInstances(apps *deployment.SingleNamespaceView) error {
   125  	for index, namespacedName := range apps.EchoNamespace.All.NamespacedNames() {
   126  		switch {
   127  		case namespacedName.Name == "client":
   128  			client = apps.EchoNamespace.All[index]
   129  		case namespacedName.Name == "server":
   130  			server = apps.EchoNamespace.All[index]
   131  		case namespacedName.Name == "server-naked-foo":
   132  			serverNakedFoo = apps.EchoNamespace.All[index]
   133  		case namespacedName.Name == "server-naked-bar":
   134  			serverNakedBar = apps.EchoNamespace.All[index]
   135  		case namespacedName.Name == "server-naked-foo-alt":
   136  			serverNakedFooAlt = apps.EchoNamespace.All[index]
   137  		}
   138  	}
   139  	return nil
   140  }
   141  
   142  func SetupApps(ctx resource.Context, customNs namespace.Getter, customCfg *[]echo.Config) error {
   143  	tmpdir, err := ctx.CreateTmpDirectory("ca-custom-root")
   144  	if err != nil {
   145  		return err
   146  	}
   147  
   148  	// Create testing certs using runtime namespace.
   149  	err = generateCerts(tmpdir, customNs.Get().Name())
   150  	if err != nil {
   151  		return err
   152  	}
   153  	rootCert, err := cert.LoadCert(path.Join(tmpdir, "root-cert.pem"))
   154  	if err != nil {
   155  		return err
   156  	}
   157  	clientCert, err := cert.LoadCert(path.Join(tmpdir, "workload-server-naked-foo-cert.pem"))
   158  	if err != nil {
   159  		return err
   160  	}
   161  	Key, err := cert.LoadCert(path.Join(tmpdir, "workload-server-naked-foo-key.pem"))
   162  	if err != nil {
   163  		return err
   164  	}
   165  
   166  	rootCertAlt, err := cert.LoadCert(path.Join(tmpdir, "root-cert-alt.pem"))
   167  	if err != nil {
   168  		return err
   169  	}
   170  	clientCertAlt, err := cert.LoadCert(path.Join(tmpdir, "workload-server-naked-foo-alt-cert.pem"))
   171  	if err != nil {
   172  		return err
   173  	}
   174  	keyAlt, err := cert.LoadCert(path.Join(tmpdir, "workload-server-naked-foo-alt-key.pem"))
   175  	if err != nil {
   176  		return err
   177  	}
   178  
   179  	var customConfig []echo.Config
   180  
   181  	clientConfig := echo.Config{
   182  		Namespace: customNs.Get(),
   183  		Service:   "client",
   184  	}
   185  	serverNakedFooConfig := echo.Config{
   186  		Namespace: customNs.Get(),
   187  		Service:   "server-naked-foo",
   188  		Subsets: []echo.SubsetConfig{
   189  			{
   190  				Annotations: map[string]string{annotation.SidecarInject.Name: "false"},
   191  			},
   192  		},
   193  		ServiceAccount: true,
   194  		Ports: []echo.Port{
   195  			{
   196  				Name:         "https",
   197  				Protocol:     protocol.HTTPS,
   198  				ServicePort:  443,
   199  				WorkloadPort: 8443,
   200  				TLS:          true,
   201  			},
   202  		},
   203  		TLSSettings: &common.TLSSettings{
   204  			RootCert:      rootCert,
   205  			ClientCert:    clientCert,
   206  			Key:           Key,
   207  			AcceptAnyALPN: true,
   208  		},
   209  	}
   210  
   211  	serverNakedBarConfig := echo.Config{
   212  		Namespace: customNs.Get(),
   213  		Service:   "server-naked-bar",
   214  		Subsets: []echo.SubsetConfig{
   215  			{
   216  				Annotations: map[string]string{annotation.SidecarInject.Name: "false"},
   217  			},
   218  		},
   219  		ServiceAccount: true,
   220  		Ports: []echo.Port{
   221  			{
   222  				Name:         "https",
   223  				Protocol:     protocol.HTTPS,
   224  				ServicePort:  443,
   225  				WorkloadPort: 8443,
   226  				TLS:          true,
   227  			},
   228  		},
   229  		TLSSettings: &common.TLSSettings{
   230  			RootCert:      rootCert,
   231  			ClientCert:    clientCert,
   232  			Key:           Key,
   233  			AcceptAnyALPN: true,
   234  		},
   235  	}
   236  
   237  	serverNakedFooAltConfig := echo.Config{
   238  		// Adding echo server for multi-root tests
   239  		Namespace: customNs.Get(),
   240  		Service:   "server-naked-foo-alt",
   241  		Subsets: []echo.SubsetConfig{
   242  			{
   243  				Annotations: map[string]string{annotation.SidecarInject.Name: "false"},
   244  			},
   245  		},
   246  		ServiceAccount: true,
   247  		Ports: []echo.Port{
   248  			{
   249  				Name:         "https",
   250  				Protocol:     protocol.HTTPS,
   251  				ServicePort:  443,
   252  				WorkloadPort: 8443,
   253  				TLS:          true,
   254  			},
   255  		},
   256  		TLSSettings: &common.TLSSettings{
   257  			RootCert:      rootCertAlt,
   258  			ClientCert:    clientCertAlt,
   259  			Key:           keyAlt,
   260  			AcceptAnyALPN: true,
   261  		},
   262  	}
   263  
   264  	serverConfig := echo.Config{
   265  		Subsets:        []echo.SubsetConfig{{}},
   266  		Namespace:      customNs.Get(),
   267  		Service:        "server",
   268  		ServiceAccount: true,
   269  		Ports: []echo.Port{
   270  			{
   271  				Name:         httpPlaintext,
   272  				Protocol:     protocol.HTTP,
   273  				ServicePort:  8090,
   274  				WorkloadPort: 8090,
   275  			},
   276  			{
   277  				Name:         httpMTLS,
   278  				Protocol:     protocol.HTTP,
   279  				ServicePort:  8091,
   280  				WorkloadPort: 8091,
   281  			},
   282  			{
   283  				Name:         tcpPlaintext,
   284  				Protocol:     protocol.TCP,
   285  				ServicePort:  8092,
   286  				WorkloadPort: 8092,
   287  			},
   288  			{
   289  				Name:         tcpMTLS,
   290  				Protocol:     protocol.TCP,
   291  				ServicePort:  8093,
   292  				WorkloadPort: 8093,
   293  			},
   294  			{
   295  				Name:         tcpWL,
   296  				WorkloadPort: 9000,
   297  				Protocol:     protocol.TCP,
   298  			},
   299  		},
   300  	}
   301  
   302  	customConfig = append(customConfig, clientConfig, serverNakedFooConfig, serverNakedBarConfig,
   303  		serverNakedFooAltConfig, serverConfig)
   304  
   305  	*customCfg = customConfig
   306  	return nil
   307  }
   308  
   309  func generateCerts(tmpdir, ns string) error {
   310  	workDir := path.Join(env.IstioSrc, "samples/certs")
   311  	script := path.Join(workDir, "generate-workload.sh")
   312  
   313  	// Create certificates signed by the same plugin CA that signs Istiod certificates
   314  	crts := []struct {
   315  		td string
   316  		sa string
   317  	}{
   318  		{
   319  			td: "foo",
   320  			sa: "server-naked-foo",
   321  		},
   322  		{
   323  			td: "bar",
   324  			sa: "server-naked-bar",
   325  		},
   326  	}
   327  	for _, crt := range crts {
   328  		command := exec.Cmd{
   329  			Path:   script,
   330  			Args:   []string{script, crt.td, ns, crt.sa, tmpdir},
   331  			Stdout: os.Stdout,
   332  			Stderr: os.Stdout,
   333  		}
   334  		if err := command.Run(); err != nil {
   335  			return fmt.Errorf("failed to create testing certificates: %s", err)
   336  		}
   337  	}
   338  
   339  	// Create certificates signed by a Different ca with a different root
   340  	command := exec.Cmd{
   341  		Path:   script,
   342  		Args:   []string{script, "foo", ns, "server-naked-foo-alt", tmpdir, "use-alternative-root"},
   343  		Stdout: os.Stdout,
   344  		Stderr: os.Stdout,
   345  	}
   346  	if err := command.Run(); err != nil {
   347  		return fmt.Errorf("failed to create testing certificates: %s", err)
   348  	}
   349  	return nil
   350  }
   351  
   352  func addDefaultConfig(ctx resource.Context, cfg deployment.Config, customCfg *[]echo.Config) error {
   353  	defaultConfigs := cfg.DefaultEchoConfigs(ctx)
   354  
   355  	if defaultConfigs == nil {
   356  		return fmt.Errorf("unable to create default config")
   357  	}
   358  
   359  	*customCfg = append(*customCfg, defaultConfigs...)
   360  	return nil
   361  }