package controllers import ( "context" "encoding/base64" "fmt" "os" "testing" "time" "cloud.google.com/go/secretmanager/apiv1/secretmanagerpb" cmMeta "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" "google.golang.org/api/option" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "edge-infra.dev/pkg/edge/api/sql/plugin" "edge-infra.dev/pkg/edge/api/sql/utils" edgeissuerapi "edge-infra.dev/pkg/edge/edge-issuer/api/v1alpha1" "edge-infra.dev/test/f2" "edge-infra.dev/test/f2/x/postgres" ) const ( TestTenantID = "3396a52c-6a22-4049-9593-5a63b596a377" TestBannerID = "3396a52c-6a22-4049-9593-5a63b596a101" TestCAPoolEdgeID = "3396a52c-6a22-4049-9593-5a63b596a379" TestCACertEdgeID = "3396a52c-6a22-4049-9593-5a63b596a380" SelectBannerID = `SELECT banner_edge_id FROM banners;` SelectCAPoolID = `SELECT ca_pool_edge_id FROM ca_pools;` SelectCACert = `SELECT ca_cert_edge_id FROM ca_certificates;` caCert = ` -----BEGIN CERTIFICATE----- MIIBiTCCAS+gAwIBAgIUUzqzORE0Axa4SXpblqd7fPDdQlUwCgYIKoZIzj0EAwIw GjEYMBYGA1UEAwwPRWRnZUlzc3VlclBLSUNBMB4XDTI1MDIxNzE0NTEwM1oXDTM1 MDIxNTE0NTEwM1owGjEYMBYGA1UEAwwPRWRnZUlzc3VlclBLSUNBMFkwEwYHKoZI zj0CAQYIKoZIzj0DAQcDQgAE27Uu6BoAwmZwzKtpY4KKIus3g16lHZeZPnmHx3Jj 9uAraOr16GMdysirSCbt/6A/Q7EecIB8V3iEoAThER/1uqNTMFEwHQYDVR0OBBYE FPoGyYTcuzT6XYcrY5iskdk7whgGMB8GA1UdIwQYMBaAFPoGyYTcuzT6XYcrY5is kdk7whgGMA8GA1UdEwEB/wQFMAMBAf8wCgYIKoZIzj0EAwIDSAAwRQIgE3/PuD3H 5o10t6pMI1tflzgyYOYuZubnKNtFv79uZCoCIQCpQCbwC+nLRHO4hkSTCiIx/8h2 yQethOhg/kQ2y2xyWA== -----END CERTIFICATE-----` //nolint: gosec testKey = `-----BEGIN EC PRIVATE KEY----- MHcCAQEEIOHQktpCBGoAI5lhPNpyzLUqfNf1hFieabgD4W+UrwPQoAoGCCqGSM49 AwEHoUQDQgAE27Uu6BoAwmZwzKtpY4KKIus3g16lHZeZPnmHx3Jj9uAraOr16GMd ysirSCbt/6A/Q7EecIB8V3iEoAThER/1ug== -----END EC PRIVATE KEY-----` ) var TestTime = time.Now().Format(time.RFC3339) // Seed data for the tests var Seed = []plugin.Seed{ { Name: "seed-tenants.tmpl", Priority: 0, Data: utils.Tenants{ { EdgeID: TestTenantID, BSLID: "e65ac3c1-f46c-4b61-88cd-d6241e3a919d", Description: "test description", Name: "testOrganization", }, }, }, { Name: "seed-banners.tmpl", Priority: 1, Data: utils.Banners{ { EdgeID: TestBannerID, BannerType: "org", ProjectID: "d56bf564-90f7-4036-9eab-9efd435e68fe", TenantID: TestTenantID, BSLID: "bc1d5e11-6b1b-405b-a9c5-fec50090c31b", Description: "test description", Name: "testBanner1", MismatchInfo: nil, InfraStatus: "PROVISIONING", InfraStatusUpdatedAt: time.Now().Format(time.RFC3339), BslEntityTypes: "{}", }, }, }, { Name: "seed-pools.tmpl", Priority: 2, Data: utils.CAPools{ { BannerEdgeID: TestBannerID, CAPoolEdgeID: TestCAPoolEdgeID, }, }, }, { Name: "seed-certificates.tmpl", Priority: 3, Data: utils.CACertificates{ { CACertEdgeID: TestCACertEdgeID, CAPoolEdgeID: TestCAPoolEdgeID, Status: "active", CertRef: "test-cert-ref-1", PrivateKeyRef: "test-private-key-ref-1", Expiration: time.Now().Add(1 * time.Hour).Format("2006-01-02 15:04:05"), }, }, }, } var f f2.Framework func TestMain(m *testing.M) { // Set up test framework in TestMain f = f2.New(context.Background(), f2.WithExtensions( postgres.New(postgres.ApplySeedModel()), ), ) os.Exit(f.Run(m)) } func TestSeedValid(t *testing.T) { feat := f2.NewFeature("Test add seed data works"). Setup("Add Seed data", postgres.WithData(Seed)). Test("Test Data exists", func(ctx f2.Context, t *testing.T) f2.Context { var require = require.New(t) pg := postgres.FromContextT(ctx, t) db := pg.DB() // check ca pools contains the correct data rows, err := db.QueryContext(ctx, SelectCAPoolID) require.NoError(err, "Failed to query database.") var caPoolEdgeID string results := []string{} for rows.Next() { err = rows.Scan(&caPoolEdgeID) require.NoError(err) results = append(results, caPoolEdgeID) } // check the data contains a single ca pool and it matches the constant in data. require.Len(results, 1, "Wrong number of results returned from database query, Expected: %v, Actual %v", 1, len(results)) // require the result matches the constant in data require.Equal(results[0], TestCAPoolEdgeID, "ca_pool_edge_id returned from database did not match expected value: %q", TestCAPoolEdgeID) // test ca certificates contains the correct data rows, err = db.QueryContext(ctx, SelectCACert) require.NoError(err, "Failed to query database.") var caCertEdgeID string results = []string{} for rows.Next() { err = rows.Scan(&caCertEdgeID) require.NoError(err) results = append(results, caCertEdgeID) } // check the data contains a single ca cert and it matches the constant in data. require.Len(results, 1, "Wrong number of results returned from database query, Expected: %v, Actual %v", 1, len(results)) // require the result matches the constant in data require.Equal(results[0], TestCACertEdgeID, "ca_cert_edge_id returned from database did not match expected value: %q", TestCACertEdgeID) return ctx }).Feature() f.Test(t, feat) } func TestEdgeIssuer(t *testing.T) { feat := f2.NewFeature("Test Edge Issuer"). Setup("Load Data", postgres.WithData(Seed)). Setup("Add EdgeIssuer Data", func(ctx f2.Context, t *testing.T) f2.Context { var require = require.New(t) pg := postgres.FromContextT(ctx, t) db := pg.DB() sMgr := &mockSecretManager{clients: make(map[string]*mockSecretManagerClient)} c, err := sMgr.NewWithOptions(ctx, TestTenantID) require.NoError(err, "Failed to create secret manager client.") caCertBase64 := base64.StdEncoding.EncodeToString([]byte(caCert)) caPrivKeyBase64 := base64.StdEncoding.EncodeToString([]byte(testKey)) err = c.AddSecret(ctx, "test-cert-ref", []byte(caCertBase64), nil, false, nil, "1") require.NoError(err, "Failed to add ca cert secret to secret manager.") err = c.AddSecret(ctx, "test-private-key-ref", []byte(caPrivKeyBase64), nil, false, nil, "1") require.NoError(err, "Failed to add private key secret to secret manager.") // now for actual testing that issuer is working issuer := &Issuer{ Config: &Config{ DB: db, TopLevelProjectID: TestTenantID, BannerID: TestBannerID, }, SecretManager: sMgr, } issuerObj := &edgeissuerapi.EdgeIssuer{} err = issuer.Check(ctx, issuerObj) require.NoError(err, "Failed to check issuer.") // confirm issuer certs match that in database require.Equal(string(issuer.CACert), caCert, "CA cert in issuer does not match that in database.") require.Equal(string(issuer.CAPrivateKey), testKey, "CA key in issuer does not match that in database.") return ctx }).Feature() f.Test(t, feat) } type MockSigner struct { mock.Mock } type mockSecret struct { value []byte } type mockSecretManager struct { clients map[string]*mockSecretManagerClient } func (m *MockSigner) GetCreationTimestamp() metav1.Time { return metav1.Now() } func (m *MockSigner) GetConditions() []cmMeta.CertificateRequestCondition { return []cmMeta.CertificateRequestCondition{} } func (m *MockSigner) GetAnnotations() map[string]string { return map[string]string{} } func (sm *mockSecretManager) NewWithOptions(_ context.Context, projectID string, _ ...option.ClientOption) (secretManagerClient, error) { if sm.clients[projectID] == nil { sm.clients[projectID] = &mockSecretManagerClient{ secrets: make(map[string]*mockSecret), } } return sm.clients[projectID], nil } func (smc *mockSecretManagerClient) GetSecret(_ context.Context, secretID string) (*secretmanagerpb.Secret, error) { s := smc.secrets[secretID] if s == nil { return nil, fmt.Errorf("secret %s not found", secretID) } spb := &secretmanagerpb.Secret{ Name: secretID, } return spb, nil } func (smc *mockSecretManagerClient) AddSecret(_ context.Context, secretID string, secret []byte, _ map[string]string, _ bool, _ *time.Time, _ string) error { smc.secrets[secretID] = &mockSecret{value: secret} return nil } type mockSecretManagerClient struct { secrets map[string]*mockSecret } func (smc *mockSecretManagerClient) GetSecretVersionValue(_ context.Context, secretID string, _ string) ([]byte, error) { s := smc.secrets[secretID] if s == nil { return nil, fmt.Errorf("secret %s not found", secretID) } return s.value, nil } func (smc *mockSecretManagerClient) GetLatestSecretValue(_ context.Context, secretID string) ([]byte, error) { s := smc.secrets[secretID] if s == nil { return nil, fmt.Errorf("secret %s not found", secretID) } return s.value, nil }