istio.io/istio@v0.0.0-20240520182934-d79c90f27776/tests/integration/security/util/cert/cert.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 cert
    19  
    20  import (
    21  	"context"
    22  	"encoding/json"
    23  	"os"
    24  	"path"
    25  
    26  	v1 "k8s.io/api/core/v1"
    27  	"k8s.io/apimachinery/pkg/api/errors"
    28  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    29  
    30  	"istio.io/istio/pkg/log"
    31  	"istio.io/istio/pkg/test"
    32  	"istio.io/istio/pkg/test/echo/common/scheme"
    33  	"istio.io/istio/pkg/test/env"
    34  	"istio.io/istio/pkg/test/framework/components/echo"
    35  	"istio.io/istio/pkg/test/framework/components/echo/check"
    36  	"istio.io/istio/pkg/test/framework/components/istio"
    37  	"istio.io/istio/pkg/test/framework/resource"
    38  	"istio.io/istio/security/pkg/pki/ca"
    39  )
    40  
    41  // DumpCertFromSidecar gets the certificates served by the destination.
    42  func DumpCertFromSidecar(t test.Failer, from echo.Instance, to echo.Target, port string) []string {
    43  	result := from.CallOrFail(t, echo.CallOptions{
    44  		To:    to,
    45  		Count: 1,
    46  		Port: echo.Port{
    47  			Name: port,
    48  		},
    49  		Scheme: scheme.TLS,
    50  		TLS: echo.TLS{
    51  			Alpn: []string{"istio"},
    52  		},
    53  		Check: check.NoError(),
    54  	})
    55  	if result.Responses.Len() != 1 {
    56  		t.Fatalf("dump cert failed, no responses")
    57  	}
    58  	var certs []string
    59  	for _, rr := range result.Responses[0].Body() {
    60  		var s string
    61  		if err := json.Unmarshal([]byte(rr), &s); err != nil {
    62  			t.Fatalf("failed to unmarshal: %v", err)
    63  		}
    64  		certs = append(certs, s)
    65  	}
    66  	return certs
    67  }
    68  
    69  // CreateCASecret creates a k8s secret "cacerts" to store the CA key and cert.
    70  func CreateCASecret(ctx resource.Context) error {
    71  	return CreateCustomCASecret(ctx,
    72  		"ca-cert.pem", "ca-key.pem",
    73  		"cert-chain.pem", "root-cert.pem")
    74  }
    75  
    76  // CreateCASecretAlt creates a k8s secret "cacerts" to store the CA key and cert using an alternative set of certs.
    77  func CreateCASecretAlt(ctx resource.Context) error {
    78  	return CreateCustomCASecret(ctx,
    79  		"ca-cert-alt.pem", "ca-key-alt.pem",
    80  		"cert-chain-alt.pem", "root-cert-alt.pem")
    81  }
    82  
    83  func CreateCustomCASecret(ctx resource.Context, caCertFile, caKeyFile, certChainFile, rootCertFile string) error {
    84  	name := "cacerts"
    85  	systemNs, err := istio.ClaimSystemNamespace(ctx)
    86  	if err != nil {
    87  		return err
    88  	}
    89  
    90  	var caCert, caKey, certChain, rootCert []byte
    91  	if caCert, err = ReadSampleCertFromFile(caCertFile); err != nil {
    92  		return err
    93  	}
    94  	if caKey, err = ReadSampleCertFromFile(caKeyFile); err != nil {
    95  		return err
    96  	}
    97  	if certChain, err = ReadSampleCertFromFile(certChainFile); err != nil {
    98  		return err
    99  	}
   100  	if rootCert, err = ReadSampleCertFromFile(rootCertFile); err != nil {
   101  		return err
   102  	}
   103  
   104  	for _, cluster := range ctx.AllClusters() {
   105  		secret := &v1.Secret{
   106  			ObjectMeta: metav1.ObjectMeta{
   107  				Name:      name,
   108  				Namespace: systemNs.Name(),
   109  			},
   110  			Data: map[string][]byte{
   111  				ca.CACertFile:       caCert,
   112  				ca.CAPrivateKeyFile: caKey,
   113  				ca.CertChainFile:    certChain,
   114  				ca.RootCertFile:     rootCert,
   115  			},
   116  		}
   117  
   118  		if _, err := cluster.Kube().CoreV1().Secrets(systemNs.Name()).Create(context.TODO(), secret, metav1.CreateOptions{}); err != nil {
   119  			if errors.IsAlreadyExists(err) {
   120  				if _, err := cluster.Kube().CoreV1().Secrets(systemNs.Name()).Update(context.TODO(), secret, metav1.UpdateOptions{}); err != nil {
   121  					return err
   122  				}
   123  			} else {
   124  				return err
   125  			}
   126  		}
   127  
   128  		// If there is a configmap storing the CA cert from a previous
   129  		// integration test, remove it. Ideally, CI should delete all
   130  		// resources from a previous integration test, but sometimes
   131  		// the resources from a previous integration test are not deleted.
   132  		configMapName := "istio-ca-root-cert"
   133  		err = cluster.Kube().CoreV1().ConfigMaps(systemNs.Name()).Delete(context.TODO(), configMapName,
   134  			metav1.DeleteOptions{})
   135  		if err == nil {
   136  			log.Infof("configmap %v is deleted", configMapName)
   137  		} else {
   138  			log.Infof("configmap %v may not exist and the deletion returns err (%v)",
   139  				configMapName, err)
   140  		}
   141  	}
   142  
   143  	return nil
   144  }
   145  
   146  func ReadSampleCertFromFile(f string) ([]byte, error) {
   147  	filename := f
   148  	if !path.IsAbs(filename) {
   149  		filename = path.Join(env.IstioSrc, "samples/certs", f)
   150  	}
   151  	b, err := os.ReadFile(filename)
   152  	if err != nil {
   153  		return nil, err
   154  	}
   155  	return b, nil
   156  }
   157  
   158  // CreateCustomEgressSecret creates a k8s secret "cacerts" to store egress gateways CA key and cert.
   159  func CreateCustomEgressSecret(ctx resource.Context) error {
   160  	name := "egress-gw-cacerts"
   161  	systemNs, err := istio.ClaimSystemNamespace(ctx)
   162  	if err != nil {
   163  		return err
   164  	}
   165  
   166  	var caKey, certChain, rootCert, fakeRootCert []byte
   167  	if caKey, err = ReadCustomCertFromFile("key.pem"); err != nil {
   168  		return err
   169  	}
   170  	if certChain, err = ReadCustomCertFromFile("cert-chain.pem"); err != nil {
   171  		return err
   172  	}
   173  	if rootCert, err = ReadCustomCertFromFile("root-cert.pem"); err != nil {
   174  		return err
   175  	}
   176  	if fakeRootCert, err = ReadCustomCertFromFile("fake-root-cert.pem"); err != nil {
   177  		return err
   178  	}
   179  
   180  	secret := &v1.Secret{
   181  		ObjectMeta: metav1.ObjectMeta{
   182  			Name:      name,
   183  			Namespace: systemNs.Name(),
   184  		},
   185  		Data: map[string][]byte{
   186  			"key.pem":            caKey,
   187  			"cert-chain.pem":     certChain,
   188  			"root-cert.pem":      rootCert,
   189  			"fake-root-cert.pem": fakeRootCert,
   190  		},
   191  	}
   192  	for _, cluster := range ctx.Clusters() {
   193  		_, err = cluster.Kube().CoreV1().Secrets(systemNs.Name()).Create(context.TODO(), secret, metav1.CreateOptions{})
   194  		if err != nil {
   195  			if errors.IsAlreadyExists(err) {
   196  				_, err = cluster.Kube().CoreV1().Secrets(systemNs.Name()).Update(context.TODO(), secret, metav1.UpdateOptions{})
   197  				return err
   198  			}
   199  			return err
   200  		}
   201  	}
   202  	return nil
   203  }
   204  
   205  func ReadCustomCertFromFile(f string) ([]byte, error) {
   206  	b, err := os.ReadFile(path.Join(env.IstioSrc, "tests/testdata/certs/dns", f))
   207  	if err != nil {
   208  		return nil, err
   209  	}
   210  	return b, nil
   211  }
   212  
   213  func LoadCert(filename string) (string, error) {
   214  	data, err := ReadSampleCertFromFile(filename)
   215  	if err != nil {
   216  		return "", err
   217  	}
   218  	return string(data), nil
   219  }