...

Source file src/edge-infra.dev/pkg/edge/linkerd/certs/trustanchor/trustanchor_test.go

Documentation: edge-infra.dev/pkg/edge/linkerd/certs/trustanchor

     1  package trustanchor
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"os"
     7  	"testing"
     8  	"time"
     9  
    10  	"github.com/stretchr/testify/assert"
    11  	"github.com/stretchr/testify/require"
    12  	corev1 "k8s.io/api/core/v1"
    13  	"k8s.io/apimachinery/pkg/api/errors"
    14  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    15  	"k8s.io/apimachinery/pkg/types"
    16  	"sigs.k8s.io/controller-runtime/pkg/client"
    17  
    18  	"edge-infra.dev/pkg/edge/linkerd"
    19  	l5dv1alpha1 "edge-infra.dev/pkg/edge/linkerd/k8s/apis/linkerd/v1alpha1"
    20  	"edge-infra.dev/test/f2"
    21  	"edge-infra.dev/test/f2/x/ktest"
    22  )
    23  
    24  var f f2.Framework
    25  
    26  var linkerdNamespace = &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: linkerd.Namespace}}
    27  
    28  func TestMain(m *testing.M) {
    29  	f = f2.New(context.Background(),
    30  		f2.WithExtensions(
    31  			ktest.New(),
    32  		)).
    33  		Setup(func(ctx f2.Context) (f2.Context, error) {
    34  			k, err := ktest.FromContext(ctx)
    35  			if err != nil {
    36  				return ctx, err
    37  			}
    38  			// Override timeouts if we aren't using a live cluster
    39  			if !*k.Env.UseExistingCluster {
    40  				k.Timeout = 5 * time.Second
    41  				k.Tick = 10 * time.Millisecond
    42  			}
    43  			return ctx, nil
    44  		}).Teardown()
    45  	os.Exit(f.Run(m))
    46  }
    47  
    48  func TestCreateIfNotExists(t *testing.T) {
    49  	var (
    50  		k                      *ktest.K8s
    51  		l5d                    *l5dv1alpha1.Linkerd
    52  		emptyTrustAnchorSecret *corev1.Secret
    53  		trustAnchorSecret      *corev1.Secret
    54  	)
    55  	feature := f2.NewFeature("CreateIfNotExists").
    56  		Setup("create linkerd resources", func(ctx f2.Context, t *testing.T) f2.Context {
    57  			k = ktest.FromContextT(ctx, t)
    58  			l5d = createL5d()
    59  			emptyTrustAnchorSecret = buildSecret(l5d, []byte{}, []byte{})
    60  			trustAnchorSecret = buildSecret(l5d, []byte("fake-cert"), []byte{})
    61  			return ctx
    62  		}).
    63  		Test("unable to create trust anchor secret", func(ctx f2.Context, t *testing.T) f2.Context {
    64  			cert, err := CreateIfNotExists(ctx, k.Client, l5d)
    65  			assert.EqualError(t, err, "failed to create secret")
    66  			assert.Equal(t, "", cert)
    67  			assert.EqualValues(t, l5d.Status.Conditions[0].Status, corev1.ConditionFalse)
    68  			assert.Equal(t, l5d.Status.Conditions[0].Reason, l5dv1alpha1.TrustAnchorSecretSetupFailedReason)
    69  			return ctx
    70  		}).
    71  		Test("unable to determine if trust anchor exists", func(ctx f2.Context, t *testing.T) f2.Context {
    72  			require.NoError(t, k.Client.Create(ctx, linkerdNamespace))
    73  			require.NoError(t, k.Client.Create(ctx, emptyTrustAnchorSecret))
    74  			cert, err := CreateIfNotExists(ctx, k.Client, l5d)
    75  			assert.Error(t, err)
    76  			assert.Equal(t, "", cert)
    77  			assert.EqualValues(t, l5d.Status.Conditions[0].Status, corev1.ConditionFalse)
    78  			assert.Equal(t, l5d.Status.Conditions[0].Reason, l5dv1alpha1.TrustAnchorSecretSetupFailedReason)
    79  			return ctx
    80  		}).
    81  		Test("successfully create new certificate", func(ctx f2.Context, t *testing.T) f2.Context {
    82  			require.NoError(t, k.Client.Delete(ctx, emptyTrustAnchorSecret))
    83  			cert, err := CreateIfNotExists(ctx, k.Client, l5d)
    84  			assert.NoError(t, err)
    85  			assert.Contains(t, cert, "-----BEGIN CERTIFICATE-----")
    86  			assert.Contains(t, cert, "-----END CERTIFICATE-----")
    87  			assert.EqualValues(t, l5d.Status.Conditions[0].Status, corev1.ConditionTrue)
    88  			assert.Equal(t, l5d.Status.Conditions[0].Reason, l5dv1alpha1.SucceededReason)
    89  			return ctx
    90  		}).
    91  		Test("succesfullly return existing certificate", func(ctx f2.Context, t *testing.T) f2.Context {
    92  			cert, err := CreateIfNotExists(ctx, k.Client, l5d)
    93  			assert.NoError(t, err)
    94  			assert.Contains(t, cert, "-----BEGIN CERTIFICATE-----")
    95  			assert.Contains(t, cert, "-----END CERTIFICATE-----")
    96  			assert.EqualValues(t, l5d.Status.Conditions[0].Status, corev1.ConditionTrue)
    97  			assert.Equal(t, l5d.Status.Conditions[0].Reason, l5dv1alpha1.SucceededReason)
    98  			return ctx
    99  		}).
   100  		Teardown("delete trust anchor secret", func(ctx f2.Context, t *testing.T) f2.Context {
   101  			require.NoError(t, k.Client.Delete(ctx, trustAnchorSecret))
   102  			return ctx
   103  		}).Feature()
   104  	f.Test(t, feature)
   105  }
   106  
   107  func TestSecretExists(t *testing.T) {
   108  	var (
   109  		k                      *ktest.K8s
   110  		l5d                    *l5dv1alpha1.Linkerd
   111  		emptyTrustAnchorSecret *corev1.Secret
   112  		trustAnchorSecret      *corev1.Secret
   113  	)
   114  	feature := f2.NewFeature("secretExists").
   115  		Setup("create linkerd resources", func(ctx f2.Context, t *testing.T) f2.Context {
   116  			k = ktest.FromContextT(ctx, t)
   117  			l5d = createL5d()
   118  			emptyTrustAnchorSecret = buildSecret(l5d, []byte{}, []byte{})
   119  			trustAnchorSecret = buildSecret(l5d, []byte("fake-cert"), []byte{})
   120  			return ctx
   121  		}).
   122  		Test("return empty certificate if secret doesnt exist", func(ctx f2.Context, t *testing.T) f2.Context {
   123  			cert, err := SecretExists(ctx, k.Client)
   124  			assert.NoError(t, err)
   125  			assert.Equal(t, "", cert)
   126  			return ctx
   127  		}).
   128  		Test("error if certifcate key is empty for existing secret", func(ctx f2.Context, t *testing.T) f2.Context {
   129  			require.NoError(t, k.Client.Create(ctx, emptyTrustAnchorSecret))
   130  			cert, err := SecretExists(ctx, k.Client)
   131  			assert.Equal(t, "", cert)
   132  			assert.EqualError(t, err, fmt.Sprintf("secret was present, but %s was not present or empty", corev1.TLSCertKey))
   133  			return ctx
   134  		}).
   135  		Test("return certificate string if secret exists", func(ctx f2.Context, t *testing.T) f2.Context {
   136  			require.NoError(t, k.Client.Delete(ctx, emptyTrustAnchorSecret))
   137  			require.NoError(t, k.Client.Create(ctx, trustAnchorSecret))
   138  
   139  			cert, err := SecretExists(ctx, k.Client)
   140  			assert.Equal(t, "fake-cert", cert)
   141  			assert.NoError(t, err)
   142  			return ctx
   143  		}).
   144  		Teardown("delete trust anchor secret", func(ctx f2.Context, t *testing.T) f2.Context {
   145  			require.NoError(t, k.Client.Delete(ctx, trustAnchorSecret))
   146  			return ctx
   147  		}).Feature()
   148  	f.Test(t, feature)
   149  }
   150  
   151  func TestCreateSecret(t *testing.T) {
   152  	var (
   153  		k   *ktest.K8s
   154  		l5d *l5dv1alpha1.Linkerd
   155  	)
   156  	feature := f2.NewFeature("createSecret").
   157  		Setup("create linkerd CR", func(ctx f2.Context, t *testing.T) f2.Context {
   158  			k = ktest.FromContextT(ctx, t)
   159  			l5d = createL5d()
   160  			return ctx
   161  		}).
   162  		Test("create trust anchor seret if secret does not exist", func(ctx f2.Context, t *testing.T) f2.Context {
   163  			cert, err := createSecret(ctx, k.Client, l5d)
   164  			assert.NoError(t, err)
   165  			assert.NotEqual(t, "", cert)
   166  			assert.Contains(t, cert, "-----BEGIN CERTIFICATE-----")
   167  			assert.Contains(t, cert, "-----END CERTIFICATE-----")
   168  			return ctx
   169  		}).
   170  		Test("return certificate if secret already exists", func(ctx f2.Context, t *testing.T) f2.Context {
   171  			cert, err := createSecret(ctx, k.Client, l5d)
   172  			assert.NoError(t, err)
   173  			assert.NotEqual(t, "", cert)
   174  			assert.Contains(t, cert, "-----BEGIN CERTIFICATE-----")
   175  			assert.Contains(t, cert, "-----END CERTIFICATE-----")
   176  			return ctx
   177  		}).
   178  		Teardown("delete trust anchor secret", func(ctx f2.Context, t *testing.T) f2.Context {
   179  			trustAnchorSecret := buildSecret(l5d, []byte{}, []byte{})
   180  			require.NoError(t, k.Client.Delete(ctx, trustAnchorSecret))
   181  			return ctx
   182  		}).Feature()
   183  	f.Test(t, feature)
   184  }
   185  
   186  func TestRotate(t *testing.T) {
   187  	var (
   188  		k                 *ktest.K8s
   189  		l5d               *l5dv1alpha1.Linkerd
   190  		trustAnchorSecret *corev1.Secret
   191  		trustAnchorCert   string
   192  		trustRootsCM      *corev1.ConfigMap
   193  	)
   194  	feature := f2.NewFeature("rotate").
   195  		Setup("setup", func(ctx f2.Context, t *testing.T) f2.Context {
   196  			k = ktest.FromContextT(ctx, t)
   197  			l5d = createL5d()
   198  			trustAnchorCert = "fake-cert"
   199  			trustAnchorSecret = buildSecret(l5d, []byte(trustAnchorCert), []byte{})
   200  
   201  			// create identity trust roots configmap
   202  			trustRootsCM = &corev1.ConfigMap{
   203  				ObjectMeta: metav1.ObjectMeta{
   204  					Name:      linkerd.LinkerdIdentityConfigMap,
   205  					Namespace: linkerd.Namespace,
   206  				},
   207  			}
   208  			require.NoError(t, k.Client.Create(ctx, trustRootsCM))
   209  
   210  			return ctx
   211  		}).
   212  		Test("secret doesn't exist", func(ctx f2.Context, t *testing.T) f2.Context {
   213  			require.Error(t, k.Client.Get(ctx, linkerd.TrustAnchorKey(), trustAnchorSecret))
   214  			assert.NoError(t, Rotate(ctx, k.Client, l5d))
   215  			assert.EqualValues(t, l5d.Status.Conditions[0].Status, corev1.ConditionTrue)
   216  			assert.Equal(t, l5d.Status.Conditions[0].Reason, l5dv1alpha1.SucceededReason)
   217  			assert.NoError(t, k.Client.Get(ctx, linkerd.TrustAnchorKey(), trustAnchorSecret))
   218  
   219  			// assert trust roots configmap is correct
   220  			require.NoError(t, k.Client.Get(ctx, types.NamespacedName{Name: linkerd.LinkerdIdentityConfigMap, Namespace: linkerd.Namespace}, trustRootsCM))
   221  			caBundle, exists := trustRootsCM.Data[CaBundleKey]
   222  			assert.True(t, exists)
   223  			assert.NotEqual(t, "", caBundle)
   224  			assert.Contains(t, caBundle, "-----BEGIN CERTIFICATE-----")
   225  			assert.Contains(t, caBundle, "-----END CERTIFICATE-----")
   226  
   227  			return ctx
   228  		}).
   229  		Test("secret already exists", func(ctx f2.Context, t *testing.T) f2.Context {
   230  			require.NoError(t, k.Client.Get(ctx, linkerd.TrustAnchorKey(), trustAnchorSecret))
   231  			assert.NoError(t, Rotate(ctx, k.Client, l5d))
   232  			assert.EqualValues(t, l5d.Status.Conditions[0].Status, corev1.ConditionTrue)
   233  			assert.Equal(t, l5d.Status.Conditions[0].Reason, l5dv1alpha1.DualAnchor)
   234  
   235  			// assert trust roots configmap
   236  			require.NoError(t, k.Client.Get(ctx, types.NamespacedName{Name: linkerd.LinkerdIdentityConfigMap, Namespace: linkerd.Namespace}, trustRootsCM))
   237  			caBundle, exists := trustRootsCM.Data[CaBundleKey]
   238  			assert.True(t, exists)
   239  			assert.Contains(t, caBundle, "-----BEGIN CERTIFICATE-----")
   240  			assert.Contains(t, caBundle, "-----END CERTIFICATE-----")
   241  
   242  			return ctx
   243  		}).
   244  		Teardown("delete trust anchor secret", func(ctx f2.Context, t *testing.T) f2.Context {
   245  			require.NoError(t, k.Client.Delete(ctx, trustAnchorSecret))
   246  			require.NoError(t, k.Client.Delete(ctx, trustRootsCM))
   247  			return ctx
   248  		}).
   249  		Feature()
   250  	f.Test(t, feature)
   251  }
   252  
   253  func TestIsRotated(t *testing.T) {
   254  	var (
   255  		k                 *ktest.K8s
   256  		l5d               *l5dv1alpha1.Linkerd
   257  		trustAnchorSecret *corev1.Secret
   258  	)
   259  	feature := f2.NewFeature("IsRotated").
   260  		Setup("setup", func(ctx f2.Context, t *testing.T) f2.Context {
   261  			k = ktest.FromContextT(ctx, t)
   262  			l5d = createL5d()
   263  			trustAnchorSecret = buildSecret(l5d, []byte("fake-cert"), []byte{})
   264  			return ctx
   265  		}).
   266  		Test("nil secret", func(ctx f2.Context, t *testing.T) f2.Context {
   267  			// assert we get a nil secret
   268  			trustAnchorSecret, err := getSecret(ctx, k.Client)
   269  			assert.NoError(t, err)
   270  			assert.Nil(t, trustAnchorSecret)
   271  
   272  			// assert IsRotated returns false
   273  			assert.False(t, IsRotated(ctx, k.Client))
   274  
   275  			return ctx
   276  		}).
   277  		Test("annotation doesn't exist", func(ctx f2.Context, t *testing.T) f2.Context {
   278  			require.NoError(t, k.Client.Create(ctx, trustAnchorSecret))
   279  
   280  			assert.Empty(t, trustAnchorSecret.Annotations[trustAnchorRotated])
   281  			assert.False(t, IsRotated(ctx, k.Client))
   282  			return ctx
   283  		}).
   284  		Test("annotation set", func(ctx f2.Context, t *testing.T) f2.Context {
   285  			secret := trustAnchorSecret.DeepCopy()
   286  			secret.Annotations = map[string]string{trustAnchorRotated: "true"}
   287  			require.NoError(t, k.Client.Patch(ctx, secret, client.StrategicMergeFrom(trustAnchorSecret.DeepCopy())))
   288  			assert.True(t, IsRotated(ctx, k.Client))
   289  			return ctx
   290  		}).
   291  		Teardown("delete trust anchor secret", func(ctx f2.Context, t *testing.T) f2.Context {
   292  			require.NoError(t, k.Client.Delete(ctx, trustAnchorSecret))
   293  			return ctx
   294  		}).
   295  		Feature()
   296  	f.Test(t, feature)
   297  }
   298  
   299  func TestCaBundle(t *testing.T) {
   300  	var (
   301  		k          *ktest.K8s
   302  		l5d        *l5dv1alpha1.Linkerd
   303  		cmObj      = types.NamespacedName{Namespace: linkerd.Namespace, Name: linkerd.LinkerdIdentityConfigMap}
   304  		identityCM = &corev1.ConfigMap{
   305  			ObjectMeta: metav1.ObjectMeta{
   306  				Name:      linkerd.LinkerdIdentityConfigMap,
   307  				Namespace: linkerd.Namespace,
   308  			},
   309  		}
   310  		caSecret          = "secret"
   311  		caExtraSecret     = "extra-secret"
   312  		trustCert         = "test-cert"
   313  		trustAnchorSecret = &corev1.Secret{}
   314  	)
   315  
   316  	featureUpdateCaBundle := f2.NewFeature("updateCaBundle").
   317  		Setup("create identity configmap", func(ctx f2.Context, t *testing.T) f2.Context {
   318  			k = ktest.FromContextT(ctx, t)
   319  			require.NoError(t, k.Client.Create(ctx, identityCM.DeepCopy()))
   320  			return ctx
   321  		}).
   322  		Test("no data", func(ctx f2.Context, t *testing.T) f2.Context {
   323  			newCaBundle, err := updateCaBundle(ctx, k.Client, "", "fake-secret")
   324  			require.NoError(t, err)
   325  
   326  			cm := &corev1.ConfigMap{}
   327  			require.NoError(t, k.Client.Get(ctx, cmObj, cm))
   328  
   329  			assert.NotEmpty(t, cm.Data[CaBundleKey])
   330  			assert.Equal(t, newCaBundle, cm.Data[CaBundleKey])
   331  			return ctx
   332  		}).
   333  		Test("ca bundle updated", func(ctx f2.Context, t *testing.T) f2.Context {
   334  			newCaBundle, err := updateCaBundle(ctx, k.Client, caSecret, caExtraSecret)
   335  			require.NoError(t, err)
   336  
   337  			cm := &corev1.ConfigMap{}
   338  			require.NoError(t, k.Client.Get(ctx, cmObj, cm))
   339  
   340  			assert.NotEmpty(t, cm.Data[CaBundleKey])
   341  			assert.Equal(t, newCaBundle, cm.Data[CaBundleKey])
   342  			return ctx
   343  		}).Feature()
   344  
   345  	featureGetCaBundle := f2.NewFeature("getCaBundle").
   346  		Setup("setup trust anchor secret", func(ctx f2.Context, t *testing.T) f2.Context {
   347  			l5d = createL5d()
   348  			trustAnchorSecret := buildSecret(l5d, []byte(trustCert), []byte{})
   349  			require.NoError(t, k.Client.Create(ctx, trustAnchorSecret))
   350  			return ctx
   351  		}).
   352  		Test("ca bundle exists", func(ctx f2.Context, t *testing.T) f2.Context {
   353  			caBundle, err := GetCaBundle(ctx, k.Client)
   354  			assert.NoError(t, err)
   355  			assert.Equal(t, fmt.Sprintf("%s%s", caSecret, caExtraSecret), caBundle)
   356  			return ctx
   357  		}).
   358  		Test("configmap doesn't exist", func(ctx f2.Context, t *testing.T) f2.Context {
   359  			require.NoError(t, k.Client.Delete(ctx, identityCM))
   360  
   361  			caBundle, err := GetCaBundle(ctx, k.Client)
   362  			assert.NoError(t, err)
   363  			assert.True(t, errors.IsNotFound(k.Client.Get(ctx, cmObj, &corev1.ConfigMap{})))
   364  			assert.Equal(t, trustCert, caBundle)
   365  			return ctx
   366  		}).
   367  		Test("ca bundle doesn't exist", func(ctx f2.Context, t *testing.T) f2.Context {
   368  			require.NoError(t, k.Client.Create(ctx, identityCM))
   369  			_, err := GetCaBundle(ctx, k.Client)
   370  			assert.ErrorContains(t, err, "ca bundle not found in configmap")
   371  			return ctx
   372  		}).
   373  		Feature()
   374  
   375  	featureCheckCaBundle := f2.NewFeature("checkCaBundle").
   376  		Setup("setup check", func(ctx f2.Context, t *testing.T) f2.Context {
   377  			require.NoError(t, k.Client.Get(ctx, linkerd.TrustAnchorKey(), trustAnchorSecret))
   378  			return ctx
   379  		}).
   380  		Test("secret does not exist", func(ctx f2.Context, t *testing.T) f2.Context {
   381  			require.NoError(t, k.Client.Delete(ctx, trustAnchorSecret))
   382  
   383  			caBundleOK, err := CheckCaBundle(ctx, k.Client)
   384  			assert.ErrorContains(t, err, "trust anchor secret does not exist")
   385  			assert.False(t, caBundleOK)
   386  			return ctx
   387  		}).
   388  		Test("root ca not in bundle", func(ctx f2.Context, t *testing.T) f2.Context {
   389  			trustAnchorSecret = &corev1.Secret{ObjectMeta: metav1.ObjectMeta{Name: linkerd.TrustAnchorName, Namespace: linkerd.Namespace}}
   390  			require.NoError(t, k.Client.Create(ctx, trustAnchorSecret))
   391  
   392  			caBundleOK, err := CheckCaBundle(ctx, k.Client)
   393  			assert.ErrorContains(t, err, "secret was present, but tls.crt was not present or empty")
   394  			assert.False(t, caBundleOK)
   395  			return ctx
   396  		}).
   397  		Test("root ca in bundle", func(ctx f2.Context, t *testing.T) f2.Context {
   398  			require.NoError(t, k.Client.Delete(ctx, trustAnchorSecret))
   399  			cert, err := Create(ctx, k.Client, l5d)
   400  			require.NoError(t, err)
   401  			_, err = updateCaBundle(ctx, k.Client, "", cert)
   402  			require.NoError(t, err)
   403  
   404  			caBundleOK, err := CheckCaBundle(ctx, k.Client)
   405  			assert.NoError(t, err)
   406  			assert.True(t, caBundleOK)
   407  			return ctx
   408  		}).
   409  		Feature()
   410  
   411  	featureGetLastValidCa := f2.NewFeature("getLastValidCa").
   412  		Test("empty ca bundle", func(ctx f2.Context, t *testing.T) f2.Context {
   413  			lastCa, err := getLastValidCa("")
   414  			assert.NoError(t, err)
   415  			assert.Empty(t, lastCa)
   416  			return ctx
   417  		}).
   418  		Test("single cert in bundle", func(ctx f2.Context, t *testing.T) f2.Context {
   419  			cert, _, err := generateSecret(ctx, l5d)
   420  			require.NoError(t, err)
   421  
   422  			lastCa, err := getLastValidCa(cert)
   423  			assert.NoError(t, err)
   424  			assert.Equal(t, cert, lastCa)
   425  			return ctx
   426  		}).
   427  		Test("multiple certs in bundle", func(ctx f2.Context, t *testing.T) f2.Context {
   428  			certFirst, _, err := generateSecret(ctx, l5d)
   429  			require.NoError(t, err)
   430  			// ensure that certFirst and certLast are created with different times
   431  			time.Sleep(time.Second)
   432  			certLast, _, err := generateSecret(ctx, l5d)
   433  			require.NoError(t, err)
   434  
   435  			lastCa, err := getLastValidCa(fmt.Sprintf("%s%s", certLast, certFirst))
   436  			assert.NoError(t, err)
   437  			assert.Equal(t, certLast, lastCa)
   438  			return ctx
   439  		}).
   440  		Feature()
   441  
   442  	f.Test(t,
   443  		featureUpdateCaBundle,
   444  		featureGetCaBundle,
   445  		featureCheckCaBundle,
   446  		featureGetLastValidCa,
   447  	)
   448  }
   449  
   450  func createL5d() *l5dv1alpha1.Linkerd {
   451  	return &l5dv1alpha1.Linkerd{
   452  		ObjectMeta: metav1.ObjectMeta{
   453  			Name:      "linkerd",
   454  			Namespace: linkerd.Namespace,
   455  			UID:       "12345",
   456  		},
   457  	}
   458  }
   459  

View as plain text