diff --git a/.github/workflows/test-e2e.yml b/.github/workflows/test-e2e.yml index ccd3926..93dde12 100644 --- a/.github/workflows/test-e2e.yml +++ b/.github/workflows/test-e2e.yml @@ -38,16 +38,16 @@ jobs: with: go-version-file: go.mod - - name: Install KUTTL - run: | - curl -L https://github.com/kudobuilder/kuttl/releases/download/v0.15.0/kubectl-kuttl_0.15.0_linux_x86_64 -o /usr/local/bin/kubectl-kuttl - chmod +x /usr/local/bin/kubectl-kuttl + - name: Setup mise tools + uses: jdx/mise-action@v3 + with: + install: true + install_args: "kubectl-kuttl kind jq" + experimental: true - - name: Install the latest version of KIND + - name: Verify test tools run: | - curl -Lo ./kind https://kind.sigs.k8s.io/dl/latest/kind-linux-amd64 - chmod +x ./kind - sudo mv ./kind /usr/local/bin/kind + kubectl kuttl version kind version - name: Running E2E Tests diff --git a/api/v1alpha1/account_types.go b/api/v1alpha1/account_types.go index 7714eac..d7c0a74 100644 --- a/api/v1alpha1/account_types.go +++ b/api/v1alpha1/account_types.go @@ -58,6 +58,8 @@ type AccountClaims struct { // +optional DisplayName string `json:"displayName,omitempty"` // +optional + SigningKeys SigningKeys `json:"signingKeys,omitempty"` + // +optional Exports Exports `json:"exports,omitempty"` // +optional Imports Imports `json:"imports,omitempty"` @@ -82,17 +84,9 @@ type AccountStatus struct { // +optional ReconcileTimestamp metav1.Time `json:"reconcileTimestamp,omitempty"` // +optional - SigningKey KeyInfo `json:"signingKey"` - // +optional OperatorVersion string `json:"operatorVersion,omitempty"` } -type KeyInfo struct { - Name string `json:"name,omitempty"` - CreationDate metav1.Time `json:"creationDate,omitempty"` - ExpirationDate metav1.Time `json:"expirationDate,omitempty"` -} - // +kubebuilder:object:root=true // +kubebuilder:subresource:status // +kubebuilder:printcolumn:name="Ready",type=string,JSONPath=`.status.conditions[?(@.type=="Ready")].status` @@ -171,6 +165,13 @@ func init() { SchemeBuilder.Register(&Account{}, &AccountList{}) } +type SigningKeys []*SigningKey + +type SigningKey struct { + Key string `json:"key,omitempty"` + // TODO: [https://github.com/WirelessCar/nauth/issues/140] Support optional *UserScope +} + type Exports []*Export type Export struct { Name string `json:"name,omitempty"` diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index b0a56e7..67fcdfc 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -60,6 +60,17 @@ func (in *AccountClaims) DeepCopyInto(out *AccountClaims) { *out = new(AccountLimits) (*in).DeepCopyInto(*out) } + if in.SigningKeys != nil { + in, out := &in.SigningKeys, &out.SigningKeys + *out = make(SigningKeys, len(*in)) + for i := range *in { + if (*in)[i] != nil { + in, out := &(*in)[i], &(*out)[i] + *out = new(SigningKey) + **out = **in + } + } + } if in.Exports != nil { in, out := &in.Exports, &out.Exports *out = make(Exports, len(*in)) @@ -260,7 +271,6 @@ func (in *AccountStatus) DeepCopyInto(out *AccountStatus) { } } in.ReconcileTimestamp.DeepCopyInto(&out.ReconcileTimestamp) - in.SigningKey.DeepCopyInto(&out.SigningKey) } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AccountStatus. @@ -450,23 +460,6 @@ func (in *JetStreamLimits) DeepCopy() *JetStreamLimits { return out } -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *KeyInfo) DeepCopyInto(out *KeyInfo) { - *out = *in - in.CreationDate.DeepCopyInto(&out.CreationDate) - in.ExpirationDate.DeepCopyInto(&out.ExpirationDate) -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new KeyInfo. -func (in *KeyInfo) DeepCopy() *KeyInfo { - if in == nil { - return nil - } - out := new(KeyInfo) - in.DeepCopyInto(out) - return out -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *NatsCluster) DeepCopyInto(out *NatsCluster) { *out = *in @@ -705,6 +698,46 @@ func (in *ServiceLatency) DeepCopy() *ServiceLatency { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SigningKey) DeepCopyInto(out *SigningKey) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SigningKey. +func (in *SigningKey) DeepCopy() *SigningKey { + if in == nil { + return nil + } + out := new(SigningKey) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in SigningKeys) DeepCopyInto(out *SigningKeys) { + { + in := &in + *out = make(SigningKeys, len(*in)) + for i := range *in { + if (*in)[i] != nil { + in, out := &(*in)[i], &(*out)[i] + *out = new(SigningKey) + **out = **in + } + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SigningKeys. +func (in SigningKeys) DeepCopy() SigningKeys { + if in == nil { + return nil + } + out := new(SigningKeys) + in.DeepCopyInto(out) + return *out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in StringList) DeepCopyInto(out *StringList) { { diff --git a/charts/nauth-crds/crds/nauth.io_accounts.yaml b/charts/nauth-crds/crds/nauth.io_accounts.yaml index 4b99f59..05e472f 100644 --- a/charts/nauth-crds/crds/nauth.io_accounts.yaml +++ b/charts/nauth-crds/crds/nauth.io_accounts.yaml @@ -423,6 +423,13 @@ spec: format: int64 type: integer type: object + signingKeys: + items: + properties: + key: + type: string + type: object + type: array type: object conditions: items: @@ -491,17 +498,6 @@ spec: reconcileTimestamp: format: date-time type: string - signingKey: - properties: - creationDate: - format: date-time - type: string - expirationDate: - format: date-time - type: string - name: - type: string - type: object type: object type: object served: true diff --git a/charts/nauth/resources/crds/nauth.io_accounts.yaml b/charts/nauth/resources/crds/nauth.io_accounts.yaml index 4b99f59..05e472f 100644 --- a/charts/nauth/resources/crds/nauth.io_accounts.yaml +++ b/charts/nauth/resources/crds/nauth.io_accounts.yaml @@ -423,6 +423,13 @@ spec: format: int64 type: integer type: object + signingKeys: + items: + properties: + key: + type: string + type: object + type: array type: object conditions: items: @@ -491,17 +498,6 @@ spec: reconcileTimestamp: format: date-time type: string - signingKey: - properties: - creationDate: - format: date-time - type: string - expirationDate: - format: date-time - type: string - name: - type: string - type: object type: object type: object served: true diff --git a/internal/account/approvals/claims_test.TestClaims.account-limits.output.nauth.approved.yaml b/internal/account/approvals/claims_test.TestClaims.account-limits.output.nauth.approved.yaml index ef12553..d904a0d 100644 --- a/internal/account/approvals/claims_test.TestClaims.account-limits.output.nauth.approved.yaml +++ b/internal/account/approvals/claims_test.TestClaims.account-limits.output.nauth.approved.yaml @@ -17,3 +17,6 @@ natsLimits: data: -1 payload: -1 subs: -1 +signingKeys: +- key: ACI73NE4LXWVHSYSFXY73WTZVKIKE54PQUMRDYA4EUFYFGEGHKTPCOI4 +- key: ADCECGT44IBBMSNGOEZTVK2QUQSVTJW6FABW7JBFFTITDBHMP6TXM4XG diff --git a/internal/account/approvals/claims_test.TestClaims.default-limits.output.nauth.approved.yaml b/internal/account/approvals/claims_test.TestClaims.default-limits.output.nauth.approved.yaml index 849f066..29dc6bd 100644 --- a/internal/account/approvals/claims_test.TestClaims.default-limits.output.nauth.approved.yaml +++ b/internal/account/approvals/claims_test.TestClaims.default-limits.output.nauth.approved.yaml @@ -17,3 +17,6 @@ natsLimits: data: -1 payload: -1 subs: -1 +signingKeys: +- key: ACI73NE4LXWVHSYSFXY73WTZVKIKE54PQUMRDYA4EUFYFGEGHKTPCOI4 +- key: ADCECGT44IBBMSNGOEZTVK2QUQSVTJW6FABW7JBFFTITDBHMP6TXM4XG diff --git a/internal/account/approvals/claims_test.TestClaims.exports.output.nauth.approved.yaml b/internal/account/approvals/claims_test.TestClaims.exports.output.nauth.approved.yaml index 124f680..615dc80 100644 --- a/internal/account/approvals/claims_test.TestClaims.exports.output.nauth.approved.yaml +++ b/internal/account/approvals/claims_test.TestClaims.exports.output.nauth.approved.yaml @@ -48,3 +48,6 @@ natsLimits: data: -1 payload: -1 subs: -1 +signingKeys: +- key: ACI73NE4LXWVHSYSFXY73WTZVKIKE54PQUMRDYA4EUFYFGEGHKTPCOI4 +- key: ADCECGT44IBBMSNGOEZTVK2QUQSVTJW6FABW7JBFFTITDBHMP6TXM4XG diff --git a/internal/account/approvals/claims_test.TestClaims.imports.output.nauth.approved.yaml b/internal/account/approvals/claims_test.TestClaims.imports.output.nauth.approved.yaml index b00fc82..5438124 100644 --- a/internal/account/approvals/claims_test.TestClaims.imports.output.nauth.approved.yaml +++ b/internal/account/approvals/claims_test.TestClaims.imports.output.nauth.approved.yaml @@ -42,3 +42,6 @@ natsLimits: data: -1 payload: -1 subs: -1 +signingKeys: +- key: ACI73NE4LXWVHSYSFXY73WTZVKIKE54PQUMRDYA4EUFYFGEGHKTPCOI4 +- key: ADCECGT44IBBMSNGOEZTVK2QUQSVTJW6FABW7JBFFTITDBHMP6TXM4XG diff --git a/internal/account/approvals/claims_test.TestClaims.jetstream-limits.output.nauth.approved.yaml b/internal/account/approvals/claims_test.TestClaims.jetstream-limits.output.nauth.approved.yaml index 219b122..8d4e6b6 100644 --- a/internal/account/approvals/claims_test.TestClaims.jetstream-limits.output.nauth.approved.yaml +++ b/internal/account/approvals/claims_test.TestClaims.jetstream-limits.output.nauth.approved.yaml @@ -18,3 +18,6 @@ natsLimits: data: -1 payload: -1 subs: -1 +signingKeys: +- key: ACI73NE4LXWVHSYSFXY73WTZVKIKE54PQUMRDYA4EUFYFGEGHKTPCOI4 +- key: ADCECGT44IBBMSNGOEZTVK2QUQSVTJW6FABW7JBFFTITDBHMP6TXM4XG diff --git a/internal/account/approvals/claims_test.TestClaims.nats-limits.output.nauth.approved.yaml b/internal/account/approvals/claims_test.TestClaims.nats-limits.output.nauth.approved.yaml index 37c6cb1..8deb2a8 100644 --- a/internal/account/approvals/claims_test.TestClaims.nats-limits.output.nauth.approved.yaml +++ b/internal/account/approvals/claims_test.TestClaims.nats-limits.output.nauth.approved.yaml @@ -17,3 +17,6 @@ natsLimits: data: 1048576 payload: 1024 subs: 1000 +signingKeys: +- key: ACI73NE4LXWVHSYSFXY73WTZVKIKE54PQUMRDYA4EUFYFGEGHKTPCOI4 +- key: ADCECGT44IBBMSNGOEZTVK2QUQSVTJW6FABW7JBFFTITDBHMP6TXM4XG diff --git a/internal/account/claims.go b/internal/account/claims.go index 27b37be..0fd0097 100644 --- a/internal/account/claims.go +++ b/internal/account/claims.go @@ -4,6 +4,7 @@ import ( "context" "errors" "fmt" + "sort" "github.com/WirelessCar/nauth/api/v1alpha1" "github.com/WirelessCar/nauth/internal/domain" @@ -287,6 +288,23 @@ func convertNatsAccountClaims(claims *jwt.AccountClaims) v1alpha1.AccountClaims } } + // Signing Keys + if len(claims.SigningKeys) > 0 { + signingKeys := make(v1alpha1.SigningKeys, 0, len(claims.SigningKeys)) + for key := range claims.SigningKeys { + signingKey := v1alpha1.SigningKey{ + Key: key, + } + signingKeys = append(signingKeys, &signingKey) + // TODO: [https://github.com/WirelessCar/nauth/issues/140] Populate optional *UserScope + } + // Sort by key to ensure predictable, and human searchable, order. + sort.Slice(signingKeys, func(i, j int) bool { + return signingKeys[i].Key < signingKeys[j].Key + }) + out.SigningKeys = signingKeys + } + // Exports if len(claims.Exports) > 0 { exports := make(v1alpha1.Exports, 0, len(claims.Exports)) diff --git a/internal/account/claims_test.go b/internal/account/claims_test.go index 02800a8..f344d95 100644 --- a/internal/account/claims_test.go +++ b/internal/account/claims_test.go @@ -124,6 +124,43 @@ func TestClaims(t *testing.T) { } } +func TestClaims_convertNatsAccountClaims_ShouldSucceed_WhenMinimal(t *testing.T) { + // Given + claims := jwt.NewAccountClaims(testClaimsFakeAccountID) + + // When + result := convertNatsAccountClaims(claims) + + // Then + var ptrNoLimit int64 = -1 + var ptrDisabled int64 = 0 + var ptrTrue = true + require.Equal(t, v1alpha1.AccountClaims{ + AccountLimits: &v1alpha1.AccountLimits{ + Imports: &ptrNoLimit, + Exports: &ptrNoLimit, + WildcardExports: &ptrTrue, + Conn: &ptrNoLimit, + LeafNodeConn: &ptrNoLimit, + }, + JetStreamLimits: &v1alpha1.JetStreamLimits{ + MemoryStorage: &ptrDisabled, + DiskStorage: &ptrDisabled, + Streams: &ptrDisabled, + Consumer: &ptrDisabled, + MaxAckPending: &ptrDisabled, + MemoryMaxStreamBytes: &ptrDisabled, + DiskMaxStreamBytes: &ptrDisabled, + MaxBytesRequired: false, + }, + NatsLimits: &v1alpha1.NatsLimits{ + Subs: &ptrNoLimit, + Data: &ptrNoLimit, + Payload: &ptrNoLimit, + }, + }, result) +} + type TestCaseInputFile struct { TestName string InputFile string diff --git a/mise.toml b/mise.toml index 0864ba4..81d8ac7 100644 --- a/mise.toml +++ b/mise.toml @@ -16,7 +16,7 @@ helm-docs = "latest" golangci-lint = "latest" "ubi:elastic/crd-ref-docs" = "latest" "go:github.com/nats-io/natscli/nats" = "latest" -kubectl-kuttl = "latest" +kubectl-kuttl = "0.24.0" jq = "latest" [settings] @@ -37,4 +37,4 @@ run = "kubectl kuttl test" silent = true alias = "kk9s" description = "Run K9s in the test environment (kind)" -run = "KUBECONFIG=kubeconfig k9s" \ No newline at end of file +run = "KUBECONFIG=kubeconfig k9s" diff --git a/test/e2e/basic-test/01-assert-account.yaml b/test/e2e/basic-test/01-assert-account.yaml index de73610..a4ad3cd 100644 --- a/test/e2e/basic-test/01-assert-account.yaml +++ b/test/e2e/basic-test/01-assert-account.yaml @@ -47,20 +47,15 @@ resourceRefs: assertAll: - celExpr: matches(example_account.metadata.labels["account.nauth.io/id"], "^A.{55}$") - celExpr: example_account.metadata.labels["account.nauth.io/signed-by"] == "OCRTCOTZAWYINN4U4XVNEM6TDJNOBLZQMGDIZ765WFA5ZLYMRB32HCG4" - - celExpr: matches(example_account.status.signingKey.name, "^.{56}$") - ---- -apiVersion: kuttl.dev/v1beta1 -kind: TestAssert -timeout: 20 + - celExpr: matches(example_account.status.claims.signingKeys[0].key, "^A.{55}$") commands: - script: | set -eu - + # read account id from account aid="$(kubectl get accounts.nauth.io example-account -n "$NAMESPACE" -o jsonpath='{.metadata.labels.account\.nauth\.io/id}')" test -n "$aid" - + # verify account root secret exists sec="$(kubectl get secret -n "$NAMESPACE" -l account.nauth.io/id="$aid",nauth.io/secret-type=account-root -o jsonpath='{.items[0].data.default}')" test -n "$sec" diff --git a/test/e2e/basic-test/02-assert-user.yaml b/test/e2e/basic-test/02-assert-user.yaml index 729ff82..113b5b8 100644 --- a/test/e2e/basic-test/02-assert-user.yaml +++ b/test/e2e/basic-test/02-assert-user.yaml @@ -45,14 +45,10 @@ resourceRefs: name: example-user ref: example_user assertAll: + - celExpr: matches(example_account.status.claims.signingKeys[0].key, "^A.{55}$") - celExpr: matches(example_user.metadata.labels["user.nauth.io/id"], "^U.{55}$") - celExpr: example_user.metadata.labels["user.nauth.io/account-id"] == example_account.metadata.labels["account.nauth.io/id"] - - celExpr: example_user.metadata.labels["user.nauth.io/signed-by"] == example_account.status.signingKey.name - ---- -apiVersion: kuttl.dev/v1beta1 -kind: TestAssert -timeout: 20 + - celExpr: example_user.metadata.labels["user.nauth.io/signed-by"] == example_account.status.claims.signingKeys[0].key commands: - script: | set -eu diff --git a/test/e2e/cluster-ref-test/02-assert.yaml b/test/e2e/cluster-ref-test/02-assert.yaml index 187f75e..432a377 100644 --- a/test/e2e/cluster-ref-test/02-assert.yaml +++ b/test/e2e/cluster-ref-test/02-assert.yaml @@ -39,10 +39,6 @@ resourceRefs: assertAll: # Verify account ID label is set (starts with A) - celExpr: matches(account.metadata.labels["account.nauth.io/id"], "^A.{55}$") ---- -apiVersion: kuttl.dev/v1beta1 -kind: TestAssert -timeout: 30 commands: - script: | set -eu