This RFC proposes a solution for invalidating JWT (JSON Web Token) tokens for global registration within Satellite Server. This enhancement will help secure the registration process by providing mechanisms to invalidate tokens when necessary (e.g., after use or when compromised).
Currently, once a JWT is issued for global registration, there is no clear mechanism for invalidating the token if it becomes compromised or if it is no longer needed. This can potentially expose the system to security vulnerabilities and misuse.
Objectives
Implement a method for securely invalidating JWT tokens after use, expiration, or manual intervention.
Provide API endpoints or UI features to allow the administration of JWT tokens…
Proposed User Stories
As a user, I want to invalidate JWT tokens manually.
As a user or admin, I want to invalidate all JWTs for all users.
As a user or admin, I want to invalidate JWTs for a specific user.
Request for Feedback
Design Suggestions: Do you have alternative designs or workflows for handling token invalidation?
Additional User Stories: Are there other use cases, personas, or security concerns that should be addressed?
Token Management: Are there any preferred practices or tools you think should be considered for tracking, invalidating, or managing JWT tokens?
Please provide your feedback on this proposal, including any suggestions for improvements or additions.
Just to be clear, should this allow invalidating individual tokens or it’s more like “invalidating all my JWT manually at once”. I’d be fine with the later.
Fully agree to have a possibility to invalidate JWT Tokens.
Sometimes it would also be good to have a list of previous “registration commands” which are somewhere stored and can be re-used. If we would have this, then it would also be possible to remove such an entry and with this, the JWT Token.
@Bernhard_Suttner Thankyou for your feedback. I suppose another possible use case of this feature would be having all the commands listed for re-use. Sounds great !
@lstejska , @Marek_Hulan , yes I think we can begin with invalidating all tokens at once and then extend it in the future to doing it manually for each one
I disagree. This feels like unnecessary friction and frustration for the user. Assume you’ve created a bot account to handle creating registrations and handing them out to different groups in your organization. The user would be forced to go generate and hand out new ones to everyone.
Or another case. If a user accidentally creates a registration command and token but forgot to set the expiration. Or maybe they forgot to set a property on it. They have to now go remove all of theirs just to fix a mistake.
Before I respond, how are tokens validated today? AFAIK they’re signed and within them they contain an expiration. Right now they’re not stored so there’s no way to get an overview of issues tokens. The whole security model was that you would always issue short term tokens (in the range of minutes) so that security wise there wouldn’t be a concern.
This is a valid scenario, I agree, but there are a few key points that are not in the description of the RFC, so I’ll take the liberty to mention them here:
This is the first phase of the effort for managing JWTs, which we want to deliver for 3.13 or 3.14. So we are looking into a solution that would provide at least some way for the invalidation because right now, we have only “the console way” (see below)
Storing JWTs (and their management) has been discussed quite a lot, and we would like to plan and scope for the upcoming releases; we’ve already seen requests from the community, and I believe they will appreciate the feature
However, right now, we do not store JWTs anywhere. Creating all the logic for managing them would be quite a significant effort for the first phase. You know, the usuals: controllers, models, UI, permissions, API, testing, and so on.
The goal is to provide at least some way to invalidate the tokens, duable in release or two, and then plan the management of the tokens.
You have to go to the rails console and execute the following:
We need to clarify that it’s only possible to invalidate all of a user’s tokens, not individual ones, because the tokens themselves are not stored. Instead, we store a single secret for each user, which is used to encode and decode the tokens. Deleting this secret will invalidate all tokens associated with that user.
To check if a token is valid, run the following command in the Rails console: JWT.decode(token, jwt_secret)
Where jwt_secret can be retrieved with: User.find_by_login('user_login').jwt_secret
If the token is valid, this command will return an array with details such as user_id, iat, exp, scope, etc. If the token is invalid, it will raise an error, either JWT::VerificationError or JWT::ExpiredSignature.
I have a few questions regarding this feature:
How do we plan to implement this functionality? Will we invalidate tokens by deleting the user’s jwt_secret from the database? While this may be the simplest approach, it limits our control and ability to track deleted records.
Should we allow all users to invalidate both their own tokens and those of other users? I believe this action should be restricted and not available to all users.
Are jwt_secret values used for any purposes other than generating tokens for the registration command? I haven’t found other uses, but it’s important to verify this before making any changes.
Should it be changed, that for every token a separate secret is used so that its possible to invalidate a single token, too?
As I said, it would be very helpful to have a list of already created registration commands so that they can be changed and re-used. In this list, it would be then possible to delete the JWT token, too.
@lstejska@nofaralfasi that confirms what I suspected from the initial RFC: we can only invalidate them all or nothing because they aren’t stored individually.
I think it’s still a good first step
That makes sense to me. Imagine some user is on vacation and a token leaks. Then the admin should still be able to invalidate the token. I can even see a use case for resetting all the secrets in case you know a token leaked, but don’t know which user it belonged to.
A quick grep through Foreman doesn’t hint at other uses and I can’t think of any right now.
Why is that useful? In the design it was really intended for them to be short term. Essentially use them once and then move on. If that doesn’t work, it’s important to know why it doesn’t work.
Thank you all for your valuable responses and feedback on the RFC. They were very helpful in deciding the workflow. On further refinement, this is the proposed approach:
The users with the “Edit Users” permissions can invalidate ALL JWT token for self and other users.
Conventionally, Admin can invalidate self’s and other users tokens
For the first phase, it makes sense to only allow invalidating all of the tokens at once as the tokens are currently not stored anywhere
I’m open to any other modifications or points that I might have missed.
While the short term was the intent, we have feedback from users that they aren’t using them this way and they are deferring to either no-expiration or longer expiration. I have heard multiple users ask to see the list of current tokens and/or commands that have been generated and are available for use. They want to do this to be able to see an audit trail, and as this RFC indicates, invalidate one or more at a time. We gave them what they view as a secret and they want to manage them as such.
We see some users who each generate their own registration tokens, and in some use cases there will be a single admin generating tokens for individuals and teams and handing them out because they don’t give access to Foreman directly.
Some other things I have heard:
Users are concerned by knowledge of how many tokens exist and how many could be “out in the wild” and used by someone inadvertently
Users don’t understand the scope of the tokens generated. Is it per Foreman? Per organization? Per user?
Users don’t understand how many tokens they can have at one time. Is it one ever? Is it multiple?
Users don’t understand how to delete a registration token
Users might want to delete a single registration token and there is no ability to do that
Users want to see, for their user, the list of registration tokens that exist, the expiration date for the tokens
It wasn’t explicitly there, but I’d think that any user can invalidate their own token. Or would that be limited to users with the permissions to do host registrations since the tokens aren’t used anywhere else right now.
I also wonder how much auditing we have on this right now. I don’t think our audit log captures if the user authenticated registration with a token, but it may make sense.
I think this may introduce risk. I don’t know if the tokens could be used to gain admin permissions.
Short duration addresses this IMHO.
That sounds like a lack of documentation.
It was never designed this way, so it requires a significant redesign of the feature.
Maybe and “it depends” but it would solve a lot of issues in one step. As Eric said, user may expect other flows and they normally don’t have a look at the documentation - only after some things went wrong
If we discuss a change, than its maybe a good point in time to change the design and fix multiple issues in one step.
This would imply there is already mechanisms to address their needs. My understanding was there is not. Am I (and these users) missing something?
I get your thinking here. My point was to express that some users have to operate this way at an organizational level and need the feature to help them. In the current (partially deprecated) model, they point users at the consumer RPM and tell them the name of an activation key to use.
Define short duration. How long is a “safe” amount of time?
Do we expect users to think of registration tokens like one-time passwords or like API tokens (e.g. to Github) that have a 30 day lifespan?
I’m glad y’all are pointing this out as we do need to balance what can we achieve with how much effort it takes. We are finally getting more feedback from users on this feature and that feedback is pointing to unmet needs by the more security conscious folks.
I’m generally a fan of making the UI clear, so if more in-app help texts that explain the feature help I think that’s even better.
In a different discussion with the Anaconda team we talked about making the service discoverable. I think we shouldn’t try to push this feature in a direction to serve their needs.
Compare it to the Puppet workflow: you create a CSR, submit it to the Puppet CA. If that’s approved, you’re all set. I can imagine that with subscription-manager we can build a similar workflow. Then when the host is registered and facts are uploaded, you can manage the system. Though there is a big difference: Puppet has built in methods to manage a system where subman doesn’t. I think we should explore this further.
I think registration tokens should have a lifetime measured in minutes.
I think this would be very valuable. We definitely have audit record for the actual registration (aka host create) and we could add additional information about the token into the audit comment. Sounds like a user story identified.
As a user who registers the host using the GRT, I want the token to be captured in the respective audit record.
At the same time, the token itself is seen as a sensitive information, so we should redact part of it, so it can’t be easily read from audits and reused. I don’t think we have other good identifier than the actual JWT.
You basically give access to perform certain actions under the account, that generated the JWT. I believe in this case, it’s limited just for all things that are necessary for host registration, but still, they are executed under that user account.
If that’s a concern, service account should be created for that purpose.
I’d even say it goes against the principle of JWT. We could keep the list of tokens so we can invalidate individual tokens, but should the user use PAT (personal access token) instead of JWT in such scenario? JWT does not provide revocation option, we’d need to build that layer ourself. I wonder what would be the justification if we already have that for PAT.
This was intended to be more like OTP. Given the JWT has one big benefit over PAT, which is the limit authorization you can do with it, people may tend to use it instead of PAT and reuse it for multiple hosts. We gave them that option, so they are exploiting it.