...

Source file src/github.com/linkerd/linkerd2/test/integration/external/externalissuer/external_issuer_test.go

Documentation: github.com/linkerd/linkerd2/test/integration/external/externalissuer

     1  package externalissuer
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"os"
     7  	"testing"
     8  	"time"
     9  
    10  	"github.com/linkerd/linkerd2/pkg/k8s"
    11  	"github.com/linkerd/linkerd2/testutil"
    12  	corev1 "k8s.io/api/core/v1"
    13  )
    14  
    15  var TestHelper *testutil.TestHelper
    16  
    17  const (
    18  	TestAppBackendDeploymentName = "backend"
    19  	TestAppNamespaceSuffix       = "external-issuer-app-test"
    20  )
    21  
    22  func TestMain(m *testing.M) {
    23  	TestHelper = testutil.NewTestHelper()
    24  	// Block test execution until viz extension pods are running
    25  	TestHelper.WaitUntilDeployReady(testutil.ExternalVizDeployReplicas)
    26  	os.Exit(m.Run())
    27  }
    28  
    29  func TestExternalIssuer(t *testing.T) {
    30  	ctx := context.Background()
    31  	TestHelper.WithDataPlaneNamespace(ctx, TestAppNamespaceSuffix, map[string]string{}, t, func(t *testing.T, testNamespace string) {
    32  		verifyInstallApp(ctx, t)
    33  		verifyAppWorksBeforeCertRotation(t)
    34  		verifyRotateExternalCerts(ctx, t)
    35  		verifyIdentityServiceReloadsIssuerCert(t)
    36  		ensureNewCSRSAreServed()
    37  		verifyAppWorksAfterCertRotation(t)
    38  	})
    39  }
    40  
    41  func verifyInstallApp(ctx context.Context, t *testing.T) {
    42  	out, err := TestHelper.LinkerdRun("inject", "--manual", "testdata/external_issuer_application.yaml")
    43  	if err != nil {
    44  		testutil.AnnotatedFatal(t, "'linkerd inject' command failed", err)
    45  	}
    46  
    47  	prefixedNs := TestHelper.GetTestNamespace(TestAppNamespaceSuffix)
    48  	out, err = TestHelper.KubectlApply(out, prefixedNs)
    49  	if err != nil {
    50  		testutil.AnnotatedFatalf(t, "'kubectl apply' command failed", "'kubectl apply' command failed\n%s", out)
    51  	}
    52  
    53  	if err := TestHelper.CheckPods(ctx, prefixedNs, TestAppBackendDeploymentName, 1); err != nil {
    54  		//nolint:errorlint
    55  		if rce, ok := err.(*testutil.RestartCountError); ok {
    56  			testutil.AnnotatedWarn(t, "CheckPods timed-out", rce)
    57  		} else {
    58  			testutil.AnnotatedError(t, "CheckPods timed-out", err)
    59  		}
    60  	}
    61  
    62  	if err := TestHelper.CheckPods(ctx, prefixedNs, "slow-cooker", 1); err != nil {
    63  		//nolint:errorlint
    64  		if rce, ok := err.(*testutil.RestartCountError); ok {
    65  			testutil.AnnotatedWarn(t, "CheckPods timed-out", rce)
    66  		} else {
    67  			testutil.AnnotatedError(t, "CheckPods timed-out", err)
    68  		}
    69  	}
    70  }
    71  
    72  func checkAppWoks(t *testing.T, timeout time.Duration) error {
    73  	return testutil.RetryFor(timeout, func() error {
    74  		args := []string{"viz", "stat", "deploy", "-n", TestHelper.GetTestNamespace(TestAppNamespaceSuffix), "--from", "deploy/slow-cooker", "-t", "1m"}
    75  		out, err := TestHelper.LinkerdRun(args...)
    76  		if err != nil {
    77  			return err
    78  		}
    79  		rowStats, err := testutil.ParseRows(out, 1, 8)
    80  		if err != nil {
    81  			return err
    82  		}
    83  
    84  		stat := rowStats[TestAppBackendDeploymentName]
    85  		if stat.Success != "100.00%" {
    86  			t.Fatalf("Expected no errors in test app but got [%s] success rate", stat.Success)
    87  		}
    88  		return nil
    89  	})
    90  
    91  }
    92  
    93  func verifyAppWorksBeforeCertRotation(t *testing.T) {
    94  	timeout := 40 * time.Second
    95  	err := checkAppWoks(t, timeout)
    96  	if err != nil {
    97  		testutil.AnnotatedFatal(t, fmt.Sprintf("timed-out while ensuring test app works (before cert rotation) (%s)", timeout), err)
    98  	}
    99  }
   100  
   101  func verifyRotateExternalCerts(ctx context.Context, t *testing.T) {
   102  	// We rotate the certificates here by simply grabbing
   103  	// the key and cert values from the temporary secret we have
   104  	// created
   105  	secretWithUpdatedData, err := TestHelper.KubernetesHelper.GetSecret(ctx, TestHelper.GetLinkerdNamespace(), k8s.IdentityIssuerSecretName+"-new")
   106  	if err != nil {
   107  		testutil.AnnotatedFatalf(t, "failed to fetch new secret data resource", "failed to fetch new secret data resource: %s", err)
   108  	}
   109  
   110  	roots := secretWithUpdatedData.Data[k8s.IdentityIssuerTrustAnchorsNameExternal]
   111  	crt := secretWithUpdatedData.Data[corev1.TLSCertKey]
   112  	key := secretWithUpdatedData.Data[corev1.TLSPrivateKeyKey]
   113  
   114  	if err = TestHelper.CreateTLSSecret(k8s.IdentityIssuerSecretName, string(roots), string(crt), string(key)); err != nil {
   115  		testutil.AnnotatedFatalf(t, "failed to update linkerd-identity-issuer resource", "failed to update linkerd-identity-issuer resource: %s", err)
   116  	}
   117  }
   118  
   119  func verifyIdentityServiceReloadsIssuerCert(t *testing.T) {
   120  	// check that the identity service has received an IssuerUpdated event
   121  	timeout := 90 * time.Second
   122  	err := testutil.RetryFor(timeout, func() error {
   123  		out, err := TestHelper.Kubectl("",
   124  			"--namespace", TestHelper.GetLinkerdNamespace(),
   125  			"get", "events", "--field-selector", "reason=IssuerUpdated", "-ojson",
   126  		)
   127  		if err != nil {
   128  			testutil.AnnotatedErrorf(t, "'kubectl get events' command failed", "'kubectl get events' command failed with %s\n%s", err, out)
   129  		}
   130  
   131  		events, err := testutil.ParseEvents(out)
   132  		if err != nil {
   133  			return err
   134  		}
   135  
   136  		if len(events) != 1 {
   137  			return fmt.Errorf("expected just one event but got %d", len(events))
   138  		}
   139  
   140  		expectedEventMessage := "Updated identity issuer"
   141  		issuerUpdatedEvent := events[0]
   142  
   143  		if issuerUpdatedEvent.Message != expectedEventMessage {
   144  			return fmt.Errorf("expected event message [%s] but got [%s]", expectedEventMessage, issuerUpdatedEvent.Message)
   145  
   146  		}
   147  		return nil
   148  	})
   149  	if err != nil {
   150  		testutil.AnnotatedFatal(t, fmt.Sprintf("timed-out verifying identity svc reloads issuer cert (%s)", timeout), err)
   151  	}
   152  
   153  }
   154  
   155  func ensureNewCSRSAreServed() {
   156  	// this is to ensure new certs have been issued by the identity service.
   157  	// we know that this will happen because the issuance lifetime is set to 15s.
   158  	// Possible improvement is to provide a more deterministic way of checking that.
   159  	// Perhaps we can emit k8s events when the identity service processed a CSR.
   160  	time.Sleep(20 * time.Second)
   161  }
   162  
   163  func verifyAppWorksAfterCertRotation(t *testing.T) {
   164  	timeout := 40 * time.Second
   165  	err := checkAppWoks(t, timeout)
   166  	if err != nil {
   167  		testutil.AnnotatedFatal(t, fmt.Sprintf("timed-out ensuring test app works (after cert rotation) (%s)", timeout), err)
   168  	}
   169  }
   170  

View as plain text