linkerd2/test/externalissuer/external_issuer_test.go

159 lines
4.6 KiB
Go

package externalissuer
import (
"fmt"
"os"
"testing"
"time"
"github.com/linkerd/linkerd2/pkg/k8s"
"github.com/linkerd/linkerd2/testutil"
corev1 "k8s.io/api/core/v1"
)
var TestHelper *testutil.TestHelper
const (
TestAppBackendDeploymentName = "backend"
TestAppNamespaceSuffix = "external-issuer-app-test"
)
func TestMain(m *testing.M) {
TestHelper = testutil.NewTestHelper()
if !TestHelper.ExternalIssuer() {
fmt.Fprintln(os.Stdout, "Skiping as --external-issuer=false")
os.Exit(0)
}
os.Exit(m.Run())
}
func TestExternalIssuer(t *testing.T) {
verifyInstallApp(t)
verifyAppWorksBeforeCertRotation(t)
verifyRotateExternalCerts(t)
verifyIdentityServiceReloadsIssuerCert(t)
ensureNewCSRSAreServed()
verifyAppWorksAfterCertRotation(t)
}
func verifyInstallApp(t *testing.T) {
out, stderr, err := TestHelper.LinkerdRun("inject", "--manual", "testdata/external_issuer_application.yaml")
if err != nil {
t.Fatalf("linkerd inject command failed\n%s\n%s", out, stderr)
}
prefixedNs := TestHelper.GetTestNamespace(TestAppNamespaceSuffix)
err = TestHelper.CreateDataPlaneNamespaceIfNotExists(prefixedNs, nil)
if err != nil {
t.Fatalf("failed to create %s namespace: %s", prefixedNs, err)
}
out, err = TestHelper.KubectlApply(out, prefixedNs)
if err != nil {
t.Fatalf("kubectl apply command failed\n%s", out)
}
if err := TestHelper.CheckPods(prefixedNs, TestAppBackendDeploymentName, 1); err != nil {
t.Error(err)
}
if err := TestHelper.CheckPods(prefixedNs, "slow-cooker", 1); err != nil {
t.Error(err)
}
}
func checkAppWoks(t *testing.T) error {
return TestHelper.RetryFor(20*time.Second, func() error {
args := []string{"stat", "deploy", "-n", TestHelper.GetTestNamespace(TestAppNamespaceSuffix), "--from", "deploy/slow-cooker", "-t", "1m"}
out, stderr, err := TestHelper.LinkerdRun(args...)
if err != nil {
return fmt.Errorf("Unexpected stat error: %s\n%s\n%s", err, out, stderr)
}
rowStats, err := testutil.ParseRows(out, 1, 8)
if err != nil {
return err
}
stat := rowStats[TestAppBackendDeploymentName]
if stat.Success != "100.00%" {
t.Fatalf("Expected no errors in test app but got [%s] succes rate", stat.Success)
}
return nil
})
}
func verifyAppWorksBeforeCertRotation(t *testing.T) {
err := checkAppWoks(t)
if err != nil {
t.Fatalf("Received error while ensuring test app works (before cert rotation): %s", err)
}
}
func verifyRotateExternalCerts(t *testing.T) {
// We rotate the certificates here by simply grabbing
// the key and cert values from the temporary secret we have
// created
secretWithUpdatedData, err := TestHelper.KubernetesHelper.GetSecret(TestHelper.GetLinkerdNamespace(), k8s.IdentityIssuerSecretName+"-new")
if err != nil {
t.Fatalf("failed to fetch new secret data resource: %s", err)
}
roots := secretWithUpdatedData.Data[k8s.IdentityIssuerTrustAnchorsNameExternal]
crt := secretWithUpdatedData.Data[corev1.TLSCertKey]
key := secretWithUpdatedData.Data[corev1.TLSPrivateKeyKey]
if err = TestHelper.CreateTLSSecret(k8s.IdentityIssuerSecretName, string(roots), string(crt), string(key)); err != nil {
t.Fatalf("failed to update linkerd-identity-issuer resource: %s", err)
}
}
func verifyIdentityServiceReloadsIssuerCert(t *testing.T) {
// check that the identity service has received an IssuerUpdated event
err := TestHelper.RetryFor(90*time.Second, func() error {
out, err := TestHelper.Kubectl("",
"--namespace", TestHelper.GetLinkerdNamespace(),
"get", "events", "--field-selector", "reason=IssuerUpdated", "-ojson",
)
if err != nil {
t.Errorf("kubectl get events command failed with %s\n%s", err, out)
}
events, err := testutil.ParseEvents(out)
if err != nil {
return err
}
if len(events) != 1 {
return fmt.Errorf("expected just one event but got %d", len(events))
}
expectedEventMessage := "Updated identity issuer"
issuerUpdatedEvent := events[0]
if issuerUpdatedEvent.Message != expectedEventMessage {
return fmt.Errorf("expected event message [%s] but got [%s]", expectedEventMessage, issuerUpdatedEvent.Message)
}
return nil
})
if err != nil {
t.Fatal(err.Error())
}
}
func ensureNewCSRSAreServed() {
// this is to ensure new certs have been issued by the identity service.
// we know that this will happen because the issuance lifetime is set to 15s.
// Possible improvement is to provide a more deterministic way of checking that.
// Perhaps we can emit k8s events when the identity service processed a CSR.
time.Sleep(20 * time.Second)
}
func verifyAppWorksAfterCertRotation(t *testing.T) {
err := checkAppWoks(t)
if err != nil {
t.Fatalf("Received error while ensuring test app works (after cert rotation): %s", err)
}
}