From 30afb48c2a75c984096d2310aa3f076610f6ce29 Mon Sep 17 00:00:00 2001 From: Alberto Ricart Date: Thu, 23 Mar 2023 16:01:18 -0500 Subject: [PATCH] [FEAT] enable callout service to generate users for any account --- go.mod | 2 +- go.sum | 2 + server/accounts.go | 6 ++ server/auth_callout_test.go | 106 ++++++++++++++++++++++++++++++++++++ 4 files changed, 115 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 821bd2ccf9..f3aa7ec1e7 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.19 require ( github.com/klauspost/compress v1.16.3 github.com/minio/highwayhash v1.0.2 - github.com/nats-io/jwt/v2 v2.4.0 + github.com/nats-io/jwt/v2 v2.4.1-0.20230323205815-2d45a0eae4ff github.com/nats-io/nats.go v1.24.0 github.com/nats-io/nkeys v0.4.4 github.com/nats-io/nuid v1.0.1 diff --git a/go.sum b/go.sum index c8ffa64425..4f2d0b6635 100644 --- a/go.sum +++ b/go.sum @@ -17,6 +17,8 @@ github.com/minio/highwayhash v1.0.2 h1:Aak5U0nElisjDCfPSG79Tgzkn2gl66NxOMspRrKnA github.com/minio/highwayhash v1.0.2/go.mod h1:BQskDq+xkJ12lmlUUi7U0M5Swg3EWR+dLTk+kldvVxY= github.com/nats-io/jwt/v2 v2.4.0 h1:1woVcq37qhNwJOeZ4ZoRy5NJU5bvbtGsIammf2GpuJQ= github.com/nats-io/jwt/v2 v2.4.0/go.mod h1:24BeQtRwxRV8ruvC4CojXlx/WQ/VjuwlYiH+vu/+ibI= +github.com/nats-io/jwt/v2 v2.4.1-0.20230323205815-2d45a0eae4ff h1:JpvE+Nf39XwicxkUHUqOYbBoT7SFjloOF2okqizEhys= +github.com/nats-io/jwt/v2 v2.4.1-0.20230323205815-2d45a0eae4ff/go.mod h1:24BeQtRwxRV8ruvC4CojXlx/WQ/VjuwlYiH+vu/+ibI= github.com/nats-io/nats.go v1.24.0 h1:CRiD8L5GOQu/DcfkmgBcTTIQORMwizF+rPk6T0RaHVQ= github.com/nats-io/nats.go v1.24.0/go.mod h1:dVQF+BK3SzUZpwyzHedXsvH3EO38aVKuOPkkHlv5hXA= github.com/nats-io/nkeys v0.4.4 h1:xvBJ8d69TznjcQl9t6//Q5xXuVhyYiSos6RPtvQNTwA= diff --git a/server/accounts.go b/server/accounts.go index 399a5468be..c5d38ba8b7 100644 --- a/server/accounts.go +++ b/server/accounts.go @@ -2989,6 +2989,12 @@ func (a *Account) isAllowedAcount(acc string) bool { a.mu.RLock() defer a.mu.RUnlock() if a.extAuth != nil { + // if we have a single allowed account, and we have a wildcard + // we accept it + if len(a.extAuth.AllowedAccounts) == 1 && a.extAuth.AllowedAccounts[0] == "*" { + return true + } + // otherwise must match exactly for _, a := range a.extAuth.AllowedAccounts { if a == acc { return true diff --git a/server/auth_callout_test.go b/server/auth_callout_test.go index d715d2c937..ee218148a4 100644 --- a/server/auth_callout_test.go +++ b/server/auth_callout_test.go @@ -1314,3 +1314,109 @@ func TestAuthCalloutExpiredResponse(t *testing.T) { } checkAuthErrEvent("hello", "world", "claim is expired") } + +func TestAuthCalloutOperator_AnyAccount(t *testing.T) { + _, spub := createKey(t) + sysClaim := jwt.NewAccountClaims(spub) + sysClaim.Name = "$SYS" + sysJwt, err := sysClaim.Encode(oKp) + require_NoError(t, err) + + // A account. + akp, apk := createKey(t) + aClaim := jwt.NewAccountClaims(apk) + aClaim.Name = "A" + aJwt, err := aClaim.Encode(oKp) + require_NoError(t, err) + + // B account. + bkp, bpk := createKey(t) + bClaim := jwt.NewAccountClaims(bpk) + bClaim.Name = "B" + bJwt, err := bClaim.Encode(oKp) + require_NoError(t, err) + + // AUTH callout service account. + ckp, err := nkeys.FromSeed([]byte(authCalloutIssuerSeed)) + require_NoError(t, err) + + cpk, err := ckp.PublicKey() + require_NoError(t, err) + + // The authorized user for the service. + upub, creds := createAuthServiceUser(t, ckp) + defer removeFile(t, creds) + + authClaim := jwt.NewAccountClaims(cpk) + authClaim.Name = "AUTH" + authClaim.EnableExternalAuthorization(upub) + authClaim.Authorization.AllowedAccounts.Add("*") + authJwt, err := authClaim.Encode(oKp) + require_NoError(t, err) + + conf := fmt.Sprintf(` + listen: 127.0.0.1:-1 + operator: %s + system_account: %s + resolver: MEM + resolver_preload: { + %s: %s + %s: %s + %s: %s + %s: %s + } + `, ojwt, spub, cpk, authJwt, apk, aJwt, bpk, bJwt, spub, sysJwt) + + handler := func(m *nats.Msg) { + user, si, _, opts, _ := decodeAuthRequest(t, m.Data) + if opts.Token == "PutMeInA" { + ujwt := createAuthUser(t, user, "user_a", apk, "", akp, 0, nil) + m.Respond(serviceResponse(t, user, si.ID, ujwt, "", 0)) + } else if opts.Token == "PutMeInB" { + ujwt := createAuthUser(t, user, "user_b", bpk, "", bkp, 0, nil) + m.Respond(serviceResponse(t, user, si.ID, ujwt, "", 0)) + } else { + m.Respond(nil) + } + + } + + ac := NewAuthTest(t, conf, handler, nats.UserCredentials(creds)) + resp, err := ac.authClient.Request(userDirectInfoSubj, nil, time.Second) + require_NoError(t, err) + response := ServerAPIResponse{Data: &UserInfo{}} + err = json.Unmarshal(resp.Data, &response) + require_NoError(t, err) + + // Bearer token etc.. + // This is used by all users, and the customization will be in other connect args. + // This needs to also be bound to the authorization account. + creds = createBasicAccountUser(t, ckp) + defer removeFile(t, creds) + + // We require a token. + ac.RequireConnectError(nats.UserCredentials(creds)) + + // Send correct token. This should switch us to the A account. + nc := ac.Connect(nats.UserCredentials(creds), nats.Token("PutMeInA")) + require_NoError(t, err) + + resp, err = nc.Request(userDirectInfoSubj, nil, time.Second) + require_NoError(t, err) + response = ServerAPIResponse{Data: &UserInfo{}} + err = json.Unmarshal(resp.Data, &response) + require_NoError(t, err) + userInfo := response.Data.(*UserInfo) + require_Equal(t, userInfo.Account, apk) + + nc = ac.Connect(nats.UserCredentials(creds), nats.Token("PutMeInB")) + require_NoError(t, err) + + resp, err = nc.Request(userDirectInfoSubj, nil, time.Second) + require_NoError(t, err) + response = ServerAPIResponse{Data: &UserInfo{}} + err = json.Unmarshal(resp.Data, &response) + require_NoError(t, err) + userInfo = response.Data.(*UserInfo) + require_Equal(t, userInfo.Account, bpk) +}