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

Add JWT support for users, accounts and import activations #804

Merged
merged 4 commits into from Nov 24, 2018

Conversation

derekcollison
Copy link
Member

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

@coveralls
Copy link

coveralls commented Nov 21, 2018

Coverage Status

Coverage decreased (-0.2%) to 90.937% when pulling 6b23e40 on trust into fcb15da on master.

Copy link
Member

@kozlovic kozlovic left a 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...

server/const.go Outdated Show resolved Hide resolved
server/server.go Outdated Show resolved Hide resolved
server/server.go Show resolved Hide resolved
server/server.go Outdated Show resolved Hide resolved
server/server.go Outdated Show resolved Hide resolved
server/trust_test.go Show resolved Hide resolved
server/trust_test.go Show resolved Hide resolved
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>
Copy link
Member

@kozlovic kozlovic left a 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

for _, a := range accounts {
ma[a.Name] = a
ea := a.exports.services[subject]
if accounts != nil && ea == nil {
Copy link
Member

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)

Copy link
Member

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.

Copy link
Member Author

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.

Copy link
Member

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?

Copy link
Member Author

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

Copy link
Member

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.

Copy link
Member Author

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?

Copy link
Member

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.

server/accounts.go Outdated Show resolved Hide resolved
server/accounts.go Show resolved Hide resolved
server/accounts.go Show resolved Hide resolved
server/accounts.go Outdated Show resolved Hide resolved
}

// Sets the expiration timer for an account JWT that has it set.
func (a *Account) setExpirationTimer(d time.Duration) {
Copy link
Member

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.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Server lock is held.

Copy link
Member

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...

Copy link
Member Author

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.

server/accounts.go Show resolved Hide resolved
server/accounts.go Show resolved Hide resolved
server/accounts.go Show resolved Hide resolved
server/accounts.go Outdated Show resolved Hide resolved
c.Debugf("Account JWT not signed by trusted operator")
return false
}
if acc.IsExpired() {
Copy link

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

Copy link

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

Copy link
Member Author

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) {
Copy link

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.

Copy link
Member Author

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) {
Copy link

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

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok

server/client.go Outdated Show resolved Hide resolved
server/server.go Show resolved Hide resolved
server/errors.go Show resolved Hide resolved
Signed-off-by: Derek Collison <derek@nats.io>
Copy link

@sasbury sasbury left a 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>
Copy link
Member

@kozlovic kozlovic left a 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.

for _, a := range accounts {
ma[a.Name] = a
ea := a.exports.services[subject]
if accounts != nil && ea == nil {
Copy link
Member

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) {
Copy link
Member

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...

server/accounts.go Show resolved Hide resolved
server/accounts.go Show resolved Hide resolved
server/errors.go Show resolved Hide resolved
server/server.go Show resolved Hide resolved
@derekcollison
Copy link
Member Author

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>
Copy link
Member

@kozlovic kozlovic left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@derekcollison derekcollison merged commit 7aa5d21 into master Nov 24, 2018
@derekcollison derekcollison deleted the trust branch November 24, 2018 00:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants