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
Add JWT support for users, accounts and import activations #804
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Review of first commit only...
Add in trusted keys options and binary stamp User JWT and Account fetch with AccountResolver Account and User expiration Account Imports/Exports w/ updates Import activation expiration Signed-off-by: Derek Collison <derek@nats.io>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Review of accounts.go
server/accounts.go
Outdated
for _, a := range accounts { | ||
ma[a.Name] = a | ||
ea := a.exports.services[subject] | ||
if accounts != nil && ea == nil { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Difficult to understand: the ea
map is only created and never updated (at least in this function). Is this the intent? In other words, for a given account, there will be always a single call to AddServiceExport() for a given subject? (no ability to add more accounts with additional calls to this function)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seeing the equivalent function for stream makes me wonder if the for _, acc
should not be moved out of this if
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Setting to nil ok since that indicates something, when we retrieve we check ok not for v != nil.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But what I meant is that we populate ea
only once, when it was not found and we create/populate it. But next time there is a lookup for subject and it is not nil, then nothing happens. Is that right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When we fetch ea it could be missing and nil, but we set it outside the loop regardless because if it was not found but we have no accounts[] then we need to set the entry to nil in the map. On authorization we check v, ok := a.exports.services[subject]; !ok
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Again, what I was asking is that: will there ever be an incremental call to AddServiceExport for the same account and same subject but possibly different list of accounts? If not, then that's fine, otherwise, I think there may be a problem.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Meaning you want the function to treat the authorized list as idempotent correct?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I want to know if there will be a case of calling AddServicesExport() with same account/subject and different list of accounts. If not, then code is fine. Otherwise, not sure.
I see that in one case this function is called from a place where we just create the account (buildInternal) so that's fine, but otherwise it looks like it is from an update of an account.
} | ||
|
||
// Sets the expiration timer for an account JWT that has it set. | ||
func (a *Account) setExpirationTimer(d time.Duration) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No locking used there... but it seems that we are calling this without account's lock.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Server lock is held.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I tracked it down up to UpdateAccount() and FetchAccount(), which are not holding the lock but these function are called by LookupAccount(), which is. But UpdateAccount() and FetchAccount() being exported, this is not safe...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ok let me take a look and resolve.. Good point on exports with lock assumed to be held.
c.Debugf("Account JWT not signed by trusted operator") | ||
return false | ||
} | ||
if acc.IsExpired() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is a second time check we should do for "nbf" (Not Before) Claim. This is the opposite of expired basically. We don't use it yet, but the server should probably check just in case. The isBlocking(true) checks this on jwt claims
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
note, this probably shows up in a number of places
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think we will make it available if it fails the IsBlocking(true)
since we process it that way when first handed the claims IIRC. The acc.IsExpired is for us when we fire the timer to mark the account. Also triggers us to refetch at some point.
@@ -479,6 +483,20 @@ func (c *client) setPermissions(perms *Permissions) { | |||
} | |||
} | |||
|
|||
// Check to see if we have an expiration for the user JWT via base claims. | |||
// FIXME(dlc) - Clear on connect with new JWT. | |||
func (c *client) checkExpiration(claims *jwt.ClaimsData) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can maybe check the nbf claim here since it is similar to expires.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For the user JWT (juc) I do a check before any of this for IsBlocking(true)
which I think covers us.
} | ||
} | ||
|
||
func TestJWTUserExpired(t *testing.T) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Worth adding a not before test
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ok
Signed-off-by: Derek Collison <derek@nats.io>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
New JWT changes look good.
Signed-off-by: Derek Collison <derek@nats.io>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Clarification needed about AddServiceExport() and big concern about LookupAccount() that ultimately releases then regrabs the server lock during processing.
server/accounts.go
Outdated
for _, a := range accounts { | ||
ma[a.Name] = a | ||
ea := a.exports.services[subject] | ||
if accounts != nil && ea == nil { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Again, what I was asking is that: will there ever be an incremental call to AddServiceExport for the same account and same subject but possibly different list of accounts? If not, then that's fine, otherwise, I think there may be a problem.
} | ||
|
||
// Sets the expiration timer for an account JWT that has it set. | ||
func (a *Account) setExpirationTimer(d time.Duration) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I tracked it down up to UpdateAccount() and FetchAccount(), which are not holding the lock but these function are called by LookupAccount(), which is. But UpdateAccount() and FetchAccount() being exported, this is not safe...
We need to release any locks when doing a fetch, since its expected to be a URL Get(). And in that case I believe worse that can happen is we update the account twice with the same result. I think this code may be reworked at some point, but I believe is safe as it is. Definitely do not want to hold a lock, especially the server lock which is what is protecting accounts, during the fetch. As we add in Limits to the Account JWTs and need to track usage, we may introduce a different locking mechanism for account lookup etc. If that is the case we will revisit this, IMO. |
Signed-off-by: Derek Collison <derek@nats.io>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
This PR adds support for trusted roots (operators) and JWT support for accounts, users, import and export claims and import activation tokens.
Supports resolver updates (memory based for now, will be URL) for accounts and URL or inline for activation tokens. Support JWT expiration.
/cc @nats-io/core