package clustersecrets import ( "context" "database/sql/driver" "testing" "time" "github.com/DATA-DOG/go-sqlmock" "github.com/stretchr/testify/require" "edge-infra.dev/pkg/edge/api/bsl/types" "edge-infra.dev/pkg/edge/api/graph/model" "edge-infra.dev/pkg/edge/api/middleware" configType "edge-infra.dev/pkg/edge/api/types" "edge-infra.dev/pkg/sds/clustersecrets/common" ) var ( mockClusterSecretEdgeID = "4c235de1-6984-4c96-bae9-0fc35f94a020" mockTerminalEdgeID = "78de6c9a-5d30-4860-bd01-be751c849d7e" clusterSecretColumns = []string{"cluster_secret_edge_id", "cluster_secret_lease_edge_id", "secert_name", "secret_version", "secret_expire_at"} fetchSecretVersionQueryType = []string{"expire_at", "secret_version"} config = &configType.Config{ EdgeOptInSecurityCompliance: true, EdgeMaxLeaseValidityPeriod: "48h", } ) func TestAddClusterSecretValidityInDays(t *testing.T) { db, mock, err := sqlmock.New(sqlmock.QueryMatcherOption(sqlmock.QueryMatcherEqual)) require.NoError(t, err) defer db.Close() ctx := middleware.NewContext(context.Background(), &types.AuthUser{ Username: testuser, }) config.EdgeMaxSecretValidityPeriod = "60d" expirationTime := time.Now().UTC().AddDate(0, 0, 60).Format(time.RFC3339) clusterSecret := common.ClusterSecret{ SecretEdgeID: mockClusterSecretEdgeID, LeaseEdgeID: mockLeaseID, Name: "breakglass", Version: "1", Type: model.ClusterSecretTypeBreakglass, } mockAddClusterSecretQuery(mock, expirationTime, sqlmock.NewResult(1, 1)) clusterSecretService := NewClusterSecretService(db, nil, config) require.NoError(t, clusterSecretService.AddClusterSecret(ctx, clusterSecret)) require.NoError(t, mock.ExpectationsWereMet()) } func TestAddClusterSecretValidityInHours(t *testing.T) { //nolint db, mock, err := sqlmock.New(sqlmock.QueryMatcherOption(sqlmock.QueryMatcherEqual)) require.NoError(t, err) defer db.Close() ctx := middleware.NewContext(context.Background(), &types.AuthUser{ Username: testuser, }) config.EdgeMaxSecretValidityPeriod = "60h" expirationTime := time.Now().UTC().Add(time.Hour * 60).Format(time.RFC3339) clusterSecret := common.ClusterSecret{ SecretEdgeID: mockClusterSecretEdgeID, LeaseEdgeID: mockLeaseID, Name: "breakglass", Version: "1", Type: model.ClusterSecretTypeBreakglass, } mockAddClusterSecretQuery(mock, expirationTime, sqlmock.NewResult(1, 1)) clusterSecretService := NewClusterSecretService(db, nil, config) require.NoError(t, clusterSecretService.AddClusterSecret(ctx, clusterSecret)) require.NoError(t, mock.ExpectationsWereMet()) } func TestAddClusterSecretValidityInMins(t *testing.T) { //nolint db, mock, err := sqlmock.New(sqlmock.QueryMatcherOption(sqlmock.QueryMatcherEqual)) require.NoError(t, err) defer db.Close() ctx := middleware.NewContext(context.Background(), &types.AuthUser{ Username: testuser, }) config.EdgeMaxSecretValidityPeriod = "60m" expirationTime := time.Now().UTC().Add(time.Minute * 60).Format(time.RFC3339) clusterSecret := common.ClusterSecret{ SecretEdgeID: mockClusterSecretEdgeID, LeaseEdgeID: mockLeaseID, Name: "breakglass", Version: "1", Type: model.ClusterSecretTypeBreakglass, } mockAddClusterSecretQuery(mock, expirationTime, sqlmock.NewResult(1, 1)) clusterSecretService := NewClusterSecretService(db, nil, config) require.NoError(t, clusterSecretService.AddClusterSecret(ctx, clusterSecret)) require.NoError(t, mock.ExpectationsWereMet()) } func mockAddClusterSecretQuery(mock sqlmock.Sqlmock, expirationTime string, result driver.Result) { mock.ExpectExec(AddClusterSecretQuery).WithArgs(mockClusterSecretEdgeID, mockLeaseID, model.ClusterSecretTypeBreakglass.String(), "1", expirationTime, time.Now().UTC().Format(time.RFC3339), time.Now().UTC().Format(time.RFC3339), model.ClusterSecretTypeBreakglass.String()). WillReturnResult(result) } func TestFetchClusterSecret(t *testing.T) { db, mock, err := sqlmock.New(sqlmock.QueryMatcherOption(sqlmock.QueryMatcherEqual)) require.NoError(t, err) defer db.Close() ctx := middleware.NewContext(context.Background(), &types.AuthUser{ Username: testuser, }) expirationTime := time.Now().UTC().AddDate(0, 0, 60).Format(time.RFC3339) mockFetchClusterSecret(mock, expirationTime) clusterSecretService := NewClusterSecretService(db, nil, config) clusterSecret, err := clusterSecretService.FetchClusterSecret(ctx, mockClusterEdgeID, model.ClusterSecretTypeBreakglass) require.NoError(t, err) require.Equal(t, mockClusterSecretEdgeID, clusterSecret.SecretEdgeID) require.Equal(t, mockLeaseID, clusterSecret.LeaseEdgeID) require.Equal(t, "os-user-store", clusterSecret.Name) require.Equal(t, expirationTime, clusterSecret.ExpirationTime) require.Equal(t, model.ClusterSecretTypeBreakglass, clusterSecret.Type) require.Equal(t, "1", clusterSecret.Version) require.NoError(t, mock.ExpectationsWereMet()) } func mockFetchClusterSecret(mock sqlmock.Sqlmock, expirationTime string) { rows := mock.NewRows(clusterSecretColumns).AddRow(mockClusterSecretEdgeID, mockLeaseID, "os-user-store", "1", expirationTime) mock.ExpectQuery(FetchClusterSecretQuery).WithArgs(mockClusterEdgeID, model.ClusterSecretTypeBreakglass).WillReturnRows(rows) } func TestUpdateClusterSecret(t *testing.T) { db, mock, err := sqlmock.New(sqlmock.QueryMatcherOption(sqlmock.QueryMatcherEqual)) require.NoError(t, err) defer db.Close() ctx := middleware.NewContext(context.Background(), &types.AuthUser{ Username: testuser, }) config.EdgeMaxSecretValidityPeriod = "60d" expirationTime := time.Now().UTC().AddDate(0, 0, 60).Format(time.RFC3339) mockUpdateClusterSecret(mock, "1", expirationTime, sqlmock.NewResult(1, 1)) clusterSecretService := NewClusterSecretService(db, nil, config) require.NoError(t, clusterSecretService.UpdateClusterSecret(ctx, mockClusterSecretEdgeID, model.ClusterSecretTypeBreakglass, "1")) require.NoError(t, mock.ExpectationsWereMet()) } func mockUpdateClusterSecret(mock sqlmock.Sqlmock, version string, expirationTime string, result driver.Result) { mock.ExpectExec(UpdateClusterSecretQuery).WithArgs(expirationTime, version, mockClusterSecretEdgeID, model.ClusterSecretTypeBreakglass). WillReturnResult(result) } func TestFetchClusterSecretVersions(t *testing.T) { db, mock, err := sqlmock.New(sqlmock.QueryMatcherOption(sqlmock.QueryMatcherEqual)) require.NoError(t, err) defer db.Close() ctx := middleware.NewContext(context.Background(), &types.AuthUser{ Username: testuser, }) expirationTime := time.Now().UTC().AddDate(0, 0, 30).Format(time.RFC3339) mockFetchClusterSecretVersions(mock, expirationTime, "5") clusterSecretService := NewClusterSecretService(db, nil, config) versionInfo, err := clusterSecretService.FetchClusterSecretVersions(ctx, mockClusterEdgeID, model.ClusterSecretTypeBreakglass) require.NoError(t, err) require.Equal(t, versionInfo[0].Version, "5") require.Equal(t, versionInfo[0].ExpiresAt, expirationTime) require.Equal(t, versionInfo[1].Version, "2") require.Equal(t, versionInfo[1].ExpiresAt, "expires once latest version syncs to terminal(s) 78de6c9a-5d30-4860-bd01-be751c849d7e") require.NoError(t, mock.ExpectationsWereMet()) } func mockFetchClusterSecretVersions(mock sqlmock.Sqlmock, expirationTime string, version string) { rows := mock.NewRows(fetchSecretVersionQueryType).AddRow(expirationTime, version) mock.ExpectQuery(FetchSecretVersionInfoQuery).WithArgs(model.ClusterSecretTypeBreakglass, mockClusterEdgeID).WillReturnRows(rows) rows = mock.NewRows([]string{"value"}).AddRow(`["os-user-store","2","78de6c9a-5d30-4860-bd01-be751c849d7e", "grub2store","1","78de6c9a-5d30-4860-bd01-be751c849d7e"]`) mock.ExpectQuery(FetchLatestTerminalClusterSecretsQuery).WithArgs(mockClusterEdgeID).WillReturnRows(rows) } func TestFetchLatestTerminalClusterSecrets(t *testing.T) { db, mock, err := sqlmock.New(sqlmock.QueryMatcherOption(sqlmock.QueryMatcherEqual)) require.NoError(t, err) defer db.Close() ctx := middleware.NewContext(context.Background(), &types.AuthUser{ Username: testuser, }) mockFetchLatestTerminalClusterSecrets(mock) clusterSecretService := NewClusterSecretService(db, nil, config) terminalClusterSecrets, err := clusterSecretService.FetchLatestTerminalClusterSecrets(ctx, mockClusterEdgeID) require.NoError(t, err) require.Equal(t, 2, len(terminalClusterSecrets)) require.Equal(t, mockTerminalEdgeID, terminalClusterSecrets[0].TerminalEdgeID) require.Equal(t, "os-user-store", terminalClusterSecrets[0].SecretType) require.Equal(t, "2", terminalClusterSecrets[0].Version) require.NoError(t, mock.ExpectationsWereMet()) } func mockFetchLatestTerminalClusterSecrets(mock sqlmock.Sqlmock) { rows := mock.NewRows([]string{"value"}).AddRow(`["os-user-store","2","78de6c9a-5d30-4860-bd01-be751c849d7e", "grub2store","2","78de6c9a-5d30-4860-bd01-be751c849d7e"]`) mock.ExpectQuery(FetchLatestTerminalClusterSecretsQuery).WithArgs(mockClusterEdgeID).WillReturnRows(rows) } func TestExpireClusterSecret(t *testing.T) { db, mock, err := sqlmock.New(sqlmock.QueryMatcherOption(sqlmock.QueryMatcherEqual)) require.NoError(t, err) defer db.Close() ctx := middleware.NewContext(context.Background(), &types.AuthUser{ Username: testuser, }) expirationTime := time.Now().Format(time.RFC3339) mockExpireClusterSecret(mock, expirationTime, sqlmock.NewResult(2, 2)) clusterSecretService := NewClusterSecretService(db, nil, config) require.NoError(t, clusterSecretService.ExpireClusterSecrets(ctx, mockLeaseID)) require.NoError(t, mock.ExpectationsWereMet()) } func mockExpireClusterSecret(mock sqlmock.Sqlmock, expirationTime string, result driver.Result) { mock.ExpectExec(ExpireClusterSecretsQuery).WithArgs(expirationTime, mockLeaseID). WillReturnResult(result) }