Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import (
"strconv"
"strings"

"github.com/WirelessCar/nauth/internal/domain"
"github.com/WirelessCar/nauth/internal/nats"
"sigs.k8s.io/controller-runtime/pkg/cache"

Expand Down Expand Up @@ -265,7 +266,7 @@ func main() {
namespace = string(controllerNamespace)
}

accountConfig, err := account.NewConfig(operatorNatsCluster, namespace, defaultNatsURL)
accountConfig, err := account.NewConfig(operatorNatsCluster, domain.Namespace(namespace), defaultNatsURL)
if err != nil {
setupLog.Error(err, "invalid configuration")
os.Exit(1)
Expand Down
33 changes: 27 additions & 6 deletions internal/account/account.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (

"github.com/WirelessCar/nauth/api/v1alpha1"
"github.com/WirelessCar/nauth/internal/controller"
"github.com/WirelessCar/nauth/internal/domain"
"github.com/WirelessCar/nauth/internal/k8s"
"github.com/WirelessCar/nauth/internal/ports"
"github.com/nats-io/jwt/v2"
Expand Down Expand Up @@ -75,6 +76,11 @@ func (a *Manager) validate() error {
}

func (a *Manager) Create(ctx context.Context, state *v1alpha1.Account) (*controller.AccountResult, error) {
accountRef := domain.NewNamespacedName(state.GetNamespace(), state.GetName())
if err := accountRef.Validate(); err != nil {
return nil, fmt.Errorf("invalid account reference %q: %w", accountRef, err)
}

var accountPublicKey string
var accountSigningPublicKey string
var accountKeyPair nkeys.KeyPair
Expand All @@ -84,7 +90,7 @@ func (a *Manager) Create(ctx context.Context, state *v1alpha1.Account) (*control
if err != nil {
return nil, fmt.Errorf("failed to resolve cluster target: %w", err)
}
accountSecrets, err := a.secretManager.GetSecrets(ctx, state.GetNamespace(), state.GetName(), "")
accountSecrets, err := a.secretManager.GetSecrets(ctx, accountRef, "")
if err == nil {
accountKeyPair = accountSecrets.Root
accountPublicKey, err = accountKeyPair.PublicKey()
Expand Down Expand Up @@ -117,12 +123,12 @@ func (a *Manager) Create(ctx context.Context, state *v1alpha1.Account) (*control
}
}

err = a.secretManager.ApplyRootSecret(ctx, state.GetNamespace(), state.GetName(), accountKeyPair)
err = a.secretManager.ApplyRootSecret(ctx, accountRef, accountKeyPair)
if err != nil {
return nil, fmt.Errorf("failed to apply account root secret during creation: %w", err)
}

err = a.secretManager.ApplySignSecret(ctx, state.GetNamespace(), state.GetName(), accountPublicKey, accountSigningKeyPair)
err = a.secretManager.ApplySignSecret(ctx, accountRef, accountPublicKey, accountSigningKeyPair)
if err != nil {
return nil, fmt.Errorf("failed to apply account signing secret during creation: %w", err)
}
Expand Down Expand Up @@ -166,13 +172,18 @@ func (a *Manager) Create(ctx context.Context, state *v1alpha1.Account) (*control
}

func (a *Manager) Update(ctx context.Context, state *v1alpha1.Account) (*controller.AccountResult, error) {
accountRef := domain.NewNamespacedName(state.GetNamespace(), state.GetName())
if err := accountRef.Validate(); err != nil {
return nil, fmt.Errorf("invalid account reference %q: %w", accountRef, err)
}

cluster, err := a.resolveClusterTarget(ctx, state)
if err != nil {
return nil, fmt.Errorf("failed to resolve cluster target: %w", err)
}

accountID := state.GetLabels()[k8s.LabelAccountID]
secrets, err := a.secretManager.GetSecrets(ctx, state.GetNamespace(), state.GetName(), accountID)
secrets, err := a.secretManager.GetSecrets(ctx, accountRef, accountID)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -232,6 +243,11 @@ func (a *Manager) Update(ctx context.Context, state *v1alpha1.Account) (*control
}

func (a *Manager) Import(ctx context.Context, state *v1alpha1.Account) (*controller.AccountResult, error) {
accountRef := domain.NewNamespacedName(state.GetNamespace(), state.GetName())
if err := accountRef.Validate(); err != nil {
return nil, fmt.Errorf("invalid account reference %q: %w", accountRef, err)
}

cluster, err := a.resolveClusterTarget(ctx, state)
if err != nil {
return nil, fmt.Errorf("failed to resolve cluster target: %w", err)
Expand All @@ -247,7 +263,7 @@ func (a *Manager) Import(ctx context.Context, state *v1alpha1.Account) (*control
return nil, fmt.Errorf("account ID is missing for account %s during import", state.GetName())
}

secrets, err := a.secretManager.GetSecrets(ctx, state.GetNamespace(), state.GetName(), accountID)
secrets, err := a.secretManager.GetSecrets(ctx, accountRef, accountID)
if err != nil {
return nil, fmt.Errorf("failed to get secrets for account %s during import: %w", accountID, err)
}
Expand Down Expand Up @@ -287,6 +303,11 @@ func (a *Manager) Import(ctx context.Context, state *v1alpha1.Account) (*control
}

func (a *Manager) Delete(ctx context.Context, state *v1alpha1.Account) error {
accountRef := domain.NewNamespacedName(state.GetNamespace(), state.GetName())
if err := accountRef.Validate(); err != nil {
return fmt.Errorf("invalid account reference %q: %w", accountRef, err)
}

cluster, err := a.resolveClusterTarget(ctx, state)
if err != nil {
return fmt.Errorf("failed to resolve cluster target: %w", err)
Expand Down Expand Up @@ -320,7 +341,7 @@ func (a *Manager) Delete(ctx context.Context, state *v1alpha1.Account) error {
return fmt.Errorf("failed to delete account: %w", err)
}

err = a.secretManager.DeleteAll(ctx, state.GetNamespace(), state.GetName(), accountID)
err = a.secretManager.DeleteAll(ctx, accountRef, accountID)
if err != nil {
return fmt.Errorf("failed to delete account secrets: %w", err)
}
Expand Down
79 changes: 43 additions & 36 deletions internal/account/account_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (

"github.com/WirelessCar/nauth/api/v1alpha1"
"github.com/WirelessCar/nauth/internal/controller"
"github.com/WirelessCar/nauth/internal/domain"
"github.com/WirelessCar/nauth/internal/k8s"
"github.com/WirelessCar/nauth/internal/ports"
"github.com/nats-io/jwt/v2"
Expand Down Expand Up @@ -87,14 +88,15 @@ func (t *ManagerTestSuite) Test_Create_ShouldSucceed() {
caughtSignAccountID string
caughtSignKeyPair nkeys.KeyPair
)
accountRef := domain.NewNamespacedName("account-namespace", "account-name")
var natsLimitsSubs int64 = 100

t.clusterTargetResolverMock.mockGetClusterTarget(t.ctx, nil, &t.clusterTarget)
t.secretManagerMock.mockGetSecretsError(t.ctx, "account-namespace", "account-name", "", fmt.Errorf("no secrets found"))
t.secretManagerMock.mockApplyRootSecretUnknown(t.ctx, "account-namespace", "account-name", func(rootKeyPair nkeys.KeyPair) {
t.secretManagerMock.mockGetSecretsError(t.ctx, accountRef, "", fmt.Errorf("no secrets found"))
t.secretManagerMock.mockApplyRootSecretUnknown(t.ctx, accountRef, func(rootKeyPair nkeys.KeyPair) {
caughtRootKeyPair = rootKeyPair
})
t.secretManagerMock.mockApplySignSecretUnknown(t.ctx, "account-namespace", "account-name", func(accountID string, signKeyPair nkeys.KeyPair) {
t.secretManagerMock.mockApplySignSecretUnknown(t.ctx, accountRef, func(accountID string, signKeyPair nkeys.KeyPair) {
caughtSignAccountID = accountID
caughtSignKeyPair = signKeyPair
})
Expand Down Expand Up @@ -134,17 +136,18 @@ func (t *ManagerTestSuite) Test_Create_ShouldSucceed_WhenAccountExplicitCluster(
caughtSignKeyPair nkeys.KeyPair
)

accountRef := domain.NewNamespacedName("account-namespace", "account-name")
natsLimitsSubs := int64(100)

t.clusterTargetResolverMock.mockGetClusterTarget(t.ctx, &v1alpha1.NatsClusterRef{
Namespace: "account-namespace",
Name: "account-namespace-cluster",
}, &t.clusterTarget)
t.secretManagerMock.mockGetSecretsError(t.ctx, "account-namespace", "account-name", "", fmt.Errorf("no secrets found"))
t.secretManagerMock.mockApplyRootSecretUnknown(t.ctx, "account-namespace", "account-name", func(rootKeyPair nkeys.KeyPair) {
t.secretManagerMock.mockGetSecretsError(t.ctx, accountRef, "", fmt.Errorf("no secrets found"))
t.secretManagerMock.mockApplyRootSecretUnknown(t.ctx, accountRef, func(rootKeyPair nkeys.KeyPair) {
caughtRootKeyPair = rootKeyPair
})
t.secretManagerMock.mockApplySignSecretUnknown(t.ctx, "account-namespace", "account-name", func(accountID string, signKeyPair nkeys.KeyPair) {
t.secretManagerMock.mockApplySignSecretUnknown(t.ctx, accountRef, func(accountID string, signKeyPair nkeys.KeyPair) {
caughtSignAccountID = accountID
caughtSignKeyPair = signKeyPair
})
Expand Down Expand Up @@ -183,18 +186,19 @@ func (t *ManagerTestSuite) Test_Create_ShouldSucceed_WhenSecretsAlreadyExist() {
var (
caughtAccountJWT string
)
accountRef := domain.NewNamespacedName("account-namespace", "account-name")
accountRootKey, _ := nkeys.CreateAccount()
accountID, _ := accountRootKey.PublicKey()
accountSignKey, _ := nkeys.CreateAccount()
var natsLimitsSubs int64 = 100

t.clusterTargetResolverMock.mockGetClusterTarget(t.ctx, nil, &t.clusterTarget)
t.secretManagerMock.mockGetSecrets(t.ctx, "account-namespace", "account-name", "", &Secrets{
t.secretManagerMock.mockGetSecrets(t.ctx, accountRef, "", &Secrets{
Root: accountRootKey,
Sign: accountSignKey,
})
t.secretManagerMock.mockApplyRootSecret(t.ctx, "account-namespace", "account-name", accountRootKey)
t.secretManagerMock.mockApplySignSecret(t.ctx, "account-namespace", "account-name", accountID, accountSignKey)
t.secretManagerMock.mockApplyRootSecret(t.ctx, accountRef, accountRootKey)
t.secretManagerMock.mockApplySignSecret(t.ctx, accountRef, accountID, accountSignKey)
t.natsClientMock.mockConnect(t.natsURL, t.sauCreds, t.natsConnMock)
t.natsConnMock.mockUploadAccountJWTCatch(func(jwt string) { caughtAccountJWT = jwt })
t.natsConnMock.mockDisconnect()
Expand Down Expand Up @@ -244,12 +248,13 @@ func (t *ManagerTestSuite) Test_Update_ShouldSucceed() {
var (
caughtAccountJWT string
)
accountRef := domain.NewNamespacedName("account-namespace", "account-name")
accountRootKey, _ := nkeys.CreateAccount()
accountID, _ := accountRootKey.PublicKey()
accountSignKey, _ := nkeys.CreateAccount()

t.clusterTargetResolverMock.mockGetClusterTarget(t.ctx, nil, &t.clusterTarget)
t.secretManagerMock.mockGetSecrets(t.ctx, "account-namespace", "account-name", accountID, &Secrets{
t.secretManagerMock.mockGetSecrets(t.ctx, accountRef, accountID, &Secrets{
Root: accountRootKey,
Sign: accountSignKey,
})
Expand Down Expand Up @@ -278,6 +283,7 @@ func (t *ManagerTestSuite) Test_Update_ShouldSucceed() {

func (t *ManagerTestSuite) Test_Import_ShouldSucceed() {
// Given
accountRef := domain.NewNamespacedName("account-namespace", "account-name")
accountRootKey, _ := nkeys.CreateAccount()
accountID, _ := accountRootKey.PublicKey()
accountSignKey, _ := nkeys.CreateAccount()
Expand All @@ -297,7 +303,7 @@ func (t *ManagerTestSuite) Test_Import_ShouldSucceed() {
t.NoError(err, "failed to encode existing account JWT")

t.clusterTargetResolverMock.mockGetClusterTarget(t.ctx, nil, &t.clusterTarget)
t.secretManagerMock.mockGetSecrets(t.ctx, "account-namespace", "account-name", accountID, &Secrets{
t.secretManagerMock.mockGetSecrets(t.ctx, accountRef, accountID, &Secrets{
Root: accountRootKey,
Sign: accountSignKey,
})
Expand Down Expand Up @@ -328,14 +334,15 @@ func (t *ManagerTestSuite) Test_Delete_ShouldSucceed() {
var (
caughtDeleteJWT string
)
accountRef := domain.NewNamespacedName("account-namespace", "account-name")
accountRootKey, _ := nkeys.CreateAccount()
accountID, _ := accountRootKey.PublicKey()

t.clusterTargetResolverMock.mockGetClusterTarget(t.ctx, nil, &t.clusterTarget)
t.natsClientMock.mockConnect(t.natsURL, t.sauCreds, t.natsConnMock)
t.natsConnMock.mockDeleteAccountJWTCatch(func(jwt string) { caughtDeleteJWT = jwt })
t.natsConnMock.mockDisconnect()
t.secretManagerMock.mockDeleteAll(t.ctx, "account-namespace", "account-name", accountID)
t.secretManagerMock.mockDeleteAll(t.ctx, accountRef, accountID)

// When
err := t.unitUnderTest.Delete(t.ctx, &v1alpha1.Account{
Expand Down Expand Up @@ -421,67 +428,67 @@ func newSecretManagerMock() *secretManagerMock {
return &secretManagerMock{}
}

func (m *secretManagerMock) ApplyRootSecret(ctx context.Context, namespace, accountName string, rootKeyPair nkeys.KeyPair) error {
args := m.Called(ctx, namespace, accountName, rootKeyPair)
func (m *secretManagerMock) ApplyRootSecret(ctx context.Context, accountRef domain.NamespacedName, rootKeyPair nkeys.KeyPair) error {
args := m.Called(ctx, accountRef, rootKeyPair)
return args.Error(0)
}

func (m *secretManagerMock) mockApplyRootSecret(ctx context.Context, namespace, accountName string, rootKeyPair nkeys.KeyPair) {
m.On("ApplyRootSecret", ctx, namespace, accountName, rootKeyPair).Return(nil)
func (m *secretManagerMock) mockApplyRootSecret(ctx context.Context, accountRef domain.NamespacedName, rootKeyPair nkeys.KeyPair) {
m.On("ApplyRootSecret", ctx, accountRef, rootKeyPair).Return(nil)
}

func (m *secretManagerMock) mockApplyRootSecretUnknown(ctx context.Context, namespace, accountName string, catch func(rootKeyPair nkeys.KeyPair)) {
m.On("ApplyRootSecret", ctx, namespace, accountName, mock.Anything).
func (m *secretManagerMock) mockApplyRootSecretUnknown(ctx context.Context, accountRef domain.NamespacedName, catch func(rootKeyPair nkeys.KeyPair)) {
m.On("ApplyRootSecret", ctx, accountRef, mock.Anything).
Return(nil).
Run(func(args mock.Arguments) {
if catch != nil {
catch(args.Get(3).(nkeys.KeyPair))
catch(args.Get(2).(nkeys.KeyPair))
}
})
}

func (m *secretManagerMock) ApplySignSecret(ctx context.Context, namespace, accountName, accountID string, signKeyPair nkeys.KeyPair) error {
args := m.Called(ctx, namespace, accountName, accountID, signKeyPair)
func (m *secretManagerMock) ApplySignSecret(ctx context.Context, accountRef domain.NamespacedName, accountID string, signKeyPair nkeys.KeyPair) error {
args := m.Called(ctx, accountRef, accountID, signKeyPair)
return args.Error(0)
}

func (m *secretManagerMock) mockApplySignSecret(ctx context.Context, namespace, accountName, accountID string, signKeyPair nkeys.KeyPair) {
m.On("ApplySignSecret", ctx, namespace, accountName, accountID, signKeyPair).Return(nil)
func (m *secretManagerMock) mockApplySignSecret(ctx context.Context, accountRef domain.NamespacedName, accountID string, signKeyPair nkeys.KeyPair) {
m.On("ApplySignSecret", ctx, accountRef, accountID, signKeyPair).Return(nil)
}

func (m *secretManagerMock) mockApplySignSecretUnknown(ctx context.Context, namespace, accountName string, catch func(accountID string, signKeyPair nkeys.KeyPair)) {
m.On("ApplySignSecret", ctx, namespace, accountName, mock.Anything, mock.Anything).
func (m *secretManagerMock) mockApplySignSecretUnknown(ctx context.Context, accountRef domain.NamespacedName, catch func(accountID string, signKeyPair nkeys.KeyPair)) {
m.On("ApplySignSecret", ctx, accountRef, mock.Anything, mock.Anything).
Return(nil).
Run(func(args mock.Arguments) {
if catch != nil {
catch(args.String(3), args.Get(4).(nkeys.KeyPair))
catch(args.String(2), args.Get(3).(nkeys.KeyPair))
}
})
}

func (m *secretManagerMock) DeleteAll(ctx context.Context, namespace, accountName, accountID string) error {
args := m.Called(ctx, namespace, accountName, accountID)
func (m *secretManagerMock) DeleteAll(ctx context.Context, accountRef domain.NamespacedName, accountID string) error {
args := m.Called(ctx, accountRef, accountID)
return args.Error(0)
}

func (m *secretManagerMock) mockDeleteAll(ctx context.Context, namespace, accountName, accountID string) {
m.On("DeleteAll", ctx, namespace, accountName, accountID).Return(nil)
func (m *secretManagerMock) mockDeleteAll(ctx context.Context, accountRef domain.NamespacedName, accountID string) {
m.On("DeleteAll", ctx, accountRef, accountID).Return(nil)
}

func (m *secretManagerMock) GetSecrets(ctx context.Context, namespace, accountName, accountID string) (*Secrets, error) {
args := m.Called(ctx, namespace, accountName, accountID)
func (m *secretManagerMock) GetSecrets(ctx context.Context, accountRef domain.NamespacedName, accountID string) (*Secrets, error) {
args := m.Called(ctx, accountRef, accountID)
if args.Get(0) == nil {
return nil, args.Error(1)
}
return args.Get(0).(*Secrets), args.Error(1)
}

func (m *secretManagerMock) mockGetSecrets(ctx context.Context, namespace, accountName, accountID string, result *Secrets) {
m.On("GetSecrets", ctx, namespace, accountName, accountID).Return(result, nil)
func (m *secretManagerMock) mockGetSecrets(ctx context.Context, accountRef domain.NamespacedName, accountID string, result *Secrets) {
m.On("GetSecrets", ctx, accountRef, accountID).Return(result, nil)
}

func (m *secretManagerMock) mockGetSecretsError(ctx context.Context, namespace, accountName, accountID string, err error) {
m.On("GetSecrets", ctx, namespace, accountName, accountID).Return(nil, err)
func (m *secretManagerMock) mockGetSecretsError(ctx context.Context, accountRef domain.NamespacedName, accountID string, err error) {
m.On("GetSecrets", ctx, accountRef, accountID).Return(nil, err)
}

var _ secretManager = (*secretManagerMock)(nil)
9 changes: 5 additions & 4 deletions internal/account/claims.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"fmt"

"github.com/WirelessCar/nauth/api/v1alpha1"
"github.com/WirelessCar/nauth/internal/domain"
"github.com/WirelessCar/nauth/internal/k8s"
"github.com/WirelessCar/nauth/internal/ports"
"github.com/nats-io/jwt/v2"
Expand Down Expand Up @@ -164,12 +165,12 @@ func newClaimsBuilder(
imports := jwt.Imports{}

for _, importClaim := range spec.Imports {
importAccount, err := accountReader.Get(ctx, importClaim.AccountRef.Name, importClaim.AccountRef.Namespace)
accountRef := domain.NewNamespacedName(importClaim.AccountRef.Namespace, importClaim.AccountRef.Name)
importAccount, err := accountReader.Get(ctx, accountRef)
if err != nil {
errs = append(errs, fmt.Errorf("failed to get account for import %q (namespace: %q, account: %q): %w",
errs = append(errs, fmt.Errorf("failed to get account for import %q (account: %q): %w",
importClaim.Name,
importClaim.AccountRef.Namespace,
importClaim.AccountRef.Name,
accountRef,
err))
} else {
account := importAccount.Labels[k8s.LabelAccountID]
Expand Down
9 changes: 5 additions & 4 deletions internal/account/claims_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"testing"

"github.com/WirelessCar/nauth/api/v1alpha1"
"github.com/WirelessCar/nauth/internal/domain"
"github.com/WirelessCar/nauth/internal/k8s"
approvals "github.com/approvals/go-approval-tests"
"github.com/nats-io/jwt/v2"
Expand Down Expand Up @@ -49,9 +50,9 @@ func TestClaims(t *testing.T) {

ctx := context.Background()
accountReaderMock := NewAccountReaderMock()
getAccountCall := accountReaderMock.On("Get", mock.Anything, mock.Anything, mock.Anything)
getAccountCall := accountReaderMock.On("Get", mock.Anything, mock.Anything)
getAccountCall.RunFn = func(args mock.Arguments) {
accountID := fakeAccountId(args.String(1), args.String(2))
accountID := fakeAccountId(args.Get(1).(domain.NamespacedName))
account := &v1alpha1.Account{}
account.Labels = map[string]string{
k8s.LabelAccountID: accountID,
Expand Down Expand Up @@ -158,8 +159,8 @@ func discoverTestCases(pattern string) []TestCaseInputFile {
return testCases
}

func fakeAccountId(accountNameRef string, namespace string) string {
return fmt.Sprintf("A%055s", strings.ToUpper(strings.ReplaceAll(accountNameRef+namespace, "-", "")))
func fakeAccountId(accountRef domain.NamespacedName) string {
return fmt.Sprintf("A%055s", strings.ToUpper(strings.ReplaceAll(accountRef.Name+accountRef.Namespace, "-", "")))
}

func loadAccountSpec(filePath string) (*v1alpha1.AccountSpec, error) {
Expand Down
Loading
Loading