Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[FIX] [authcallout] ensure authcallout in operator mode sends an auth error instead of timeout #5038

Merged
merged 1 commit into from
Feb 5, 2024
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
6 changes: 6 additions & 0 deletions server/auth_callout.go
Original file line number Diff line number Diff line change
Expand Up @@ -241,30 +241,35 @@ func (s *Server) processClientOrLeafCallout(c *client, opts *Options) (authorize

arc, err := decodeResponse(rc, rmsg, racc)
if err != nil {
c.authViolation()
respCh <- titleCase(err.Error())
return
}
vr := jwt.CreateValidationResults()
arc.Validate(vr)
if len(vr.Issues) > 0 {
c.authViolation()
respCh <- fmt.Sprintf("Error validating user JWT: %v", vr.Issues[0])
return
}

// Make sure that the user is what we requested.
if arc.Subject != pub {
c.authViolation()
respCh <- fmt.Sprintf("Expected authorized user of %q but got %q on account %q", pub, arc.Subject, racc.Name)
return
}

expiration, allowedConnTypes, err := getExpirationAndAllowedConnections(arc, racc.Name)
if err != nil {
c.authViolation()
respCh <- titleCase(err.Error())
return
}

targetAcc, err := assignAccountAndPermissions(arc, racc.Name)
if err != nil {
c.authViolation()
respCh <- titleCase(err.Error())
return
}
Expand All @@ -280,6 +285,7 @@ func (s *Server) processClientOrLeafCallout(c *client, opts *Options) (authorize
// Build internal user and bind to the targeted account.
nkuser := buildInternalNkeyUser(arc, allowedConnTypes, targetAcc)
if err := c.RegisterNkeyUser(nkuser); err != nil {
c.authViolation()
respCh <- fmt.Sprintf("Could not register auth callout user: %v", err)
return
}
Expand Down
116 changes: 115 additions & 1 deletion server/auth_callout_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -820,7 +820,6 @@ func testAuthCalloutScopedUser(t *testing.T, allowAnyAccount bool) {
// Send the signing key token. This should switch us to the test account, but the user
// is signed with the account signing key
nc := ac.Connect(nats.UserCredentials(creds), nats.Token(scopedToken))
require_NoError(t, err)

resp, err = nc.Request(userDirectInfoSubj, nil, time.Second)
require_NoError(t, err)
Expand Down Expand Up @@ -1713,3 +1712,118 @@ func TestAuthCalloutWSClientTLSCerts(t *testing.T) {
require_Equal(t, userInfo.UserID, "dlc")
require_Equal(t, userInfo.Account, "FOO")
}

func testConfClientClose(t *testing.T, respondNil bool) {
conf := `
listen: "127.0.0.1:-1"
server_name: ZZ
accounts {
AUTH { users [ {user: "auth", password: "pwd"} ] }
}
authorization {
timeout: 1s
auth_callout {
# Needs to be a public account nkey, will work for both server config and operator mode.
issuer: "ABJHLOVMPA4CI6R5KLNGOB4GSLNIY7IOUPAJC4YFNDLQVIOBYQGUWVLA"
account: AUTH
auth_users: [ auth ]
}
}
`
handler := func(m *nats.Msg) {
user, si, _, _, _ := decodeAuthRequest(t, m.Data)
if respondNil {
m.Respond(nil)
} else {
m.Respond(serviceResponse(t, user, si.ID, "", "not today", 0))
}
}

at := NewAuthTest(t, conf, handler, nats.UserInfo("auth", "pwd"))
defer at.Cleanup()

// This one will use callout since not defined in server config.
_, err := at.NewClient(nats.UserInfo("a", "x"))
require_Error(t, err)
require_True(t, strings.Contains(strings.ToLower(err.Error()), nats.AUTHORIZATION_ERR))
}

func TestAuthCallout_ClientAuthErrorConf(t *testing.T) {
testConfClientClose(t, true)
testConfClientClose(t, false)
}

func testAuthCall_ClientAuthErrorOperatorMode(t *testing.T, respondNil bool) {
_, spub := createKey(t)
sysClaim := jwt.NewAccountClaims(spub)
sysClaim.Name = "$SYS"
sysJwt, err := sysClaim.Encode(oKp)
require_NoError(t, err)

// AUTH service account.
akp, err := nkeys.FromSeed([]byte(authCalloutIssuerSeed))
require_NoError(t, err)

apub, err := akp.PublicKey()
require_NoError(t, err)

// The authorized user for the service.
upub, creds := createAuthServiceUser(t, akp)
defer removeFile(t, creds)

authClaim := jwt.NewAccountClaims(apub)
authClaim.Name = "AUTH"
authClaim.EnableExternalAuthorization(upub)
authClaim.Authorization.AllowedAccounts.Add("*")

// the scope for the bearer token which has no permissions
sentinelScope, authKP := newScopedRole(t, "sentinel", nil, nil, false)
sentinelScope.Template.Sub.Deny.Add(">")
sentinelScope.Template.Pub.Deny.Add(">")
sentinelScope.Template.Limits.Subs = 0
sentinelScope.Template.Payload = 0
authClaim.SigningKeys.AddScopedSigner(sentinelScope)

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
}
`, ojwt, spub, apub, authJwt, spub, sysJwt)

handler := func(m *nats.Msg) {
user, si, _, _, _ := decodeAuthRequest(t, m.Data)
if respondNil {
m.Respond(nil)
} else {
m.Respond(serviceResponse(t, user, si.ID, "", "not today", 0))
}
}

ac := NewAuthTest(t, conf, handler, nats.UserCredentials(creds))
defer ac.Cleanup()

// Bearer token - this has no permissions see sentinelScope
// 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 = createScopedUser(t, akp, authKP)
defer removeFile(t, creds)

// Send the signing key token. This should switch us to the test account, but the user
// is signed with the account signing key
_, err = ac.NewClient(nats.UserCredentials(creds))
require_Error(t, err)
require_True(t, strings.Contains(strings.ToLower(err.Error()), nats.AUTHORIZATION_ERR))
}

func TestAuthCallout_ClientAuthErrorOperatorMode(t *testing.T) {
testAuthCall_ClientAuthErrorOperatorMode(t, true)
testAuthCall_ClientAuthErrorOperatorMode(t, false)
}
Loading