Skip to content

Commit

Permalink
[FEAT] allow callout account configuration to generate users for any …
Browse files Browse the repository at this point in the history
…account (#3987)
  • Loading branch information
derekcollison committed Mar 24, 2023
2 parents 2eaede8 + 30afb48 commit 03c5475
Show file tree
Hide file tree
Showing 4 changed files with 115 additions and 1 deletion.
2 changes: 1 addition & 1 deletion go.mod
Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Expand Up @@ -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=
Expand Down
6 changes: 6 additions & 0 deletions server/accounts.go
Expand Up @@ -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
Expand Down
106 changes: 106 additions & 0 deletions server/auth_callout_test.go
Expand Up @@ -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)
}

0 comments on commit 03c5475

Please sign in to comment.