Closed micahbeech closed 1 year ago
Hi @micahbeech, thanks for sharing about this.
I believe there are two parts to what you observed.
I need to maintain long-lasting tokens for CI builds, and therefore need a reliable way to revoke them should they be compromised.
The revocation docs are outdated and refer to the old behavior. I will update them.
You need to revoke the auth.github-oauth-ui.token
that you configured in the Verdaccio config, not the user tokens from the OAuth flow.
You can revoke or regenerate it at https://github.com/settings/tokens where you originally created it.
The plugin no longer uses nor requests any permissions from registry users. The only information used from the OAuth token is the GitHub user name which is used to identify the user. Consequentially, revoking user tokens does nothing besides resetting the OAuth screen (users need to re-confirm sharing their public information).
The only token used by the plugin to communicate with GitHub is the auth.github-oauth-ui.token
that you configured in the verdaccio config. This is what you need to protect and revoke.
In the past, the plugin used end-user tokens to request membership information from GitHub. However, this required users to grant access to their private orgs, private repos, and so on, to determine whether they had the required memberships to meet the configured package rules.
This meant that every registry user had to have a high level of trust in the registry and grant access to a lot of private data on GitHub. And there was no way for users to limit how much exactly was shared with the registry. For example, you could only share all your private repos, or none. (I believe this now changed since GitHub introduced more granular tokens, but back then it wasn't possible).
Therefore, I changed the behavior to rely on a single token configured by the registry owner. The registry owner has control over all aspects involved: the Verdaccio config, the package access rules, the orgs, repos, and teams used in the package config, the deployment and secrets management, the token, and the scope of the token. Registry owners therefore only need to trust themselves. Users need to trust no one since they don't need to share anything. This is a much more ideal situation.
Once I have revoked a token, I can continue to browse packages and their versions in the Web UI, pull packages via the command line, and publish new packages. It's only once I logout of the Verdaccio UI or clear my local token (i.e. I'm forced to generate a new one) that my requests are rejected and I have to re-authorize the GitHub app.
Based on your configuration, you set your JWTs to be valid for 365d. Hence, the tokens issued by Verdaccio remain valid for that period of time.
There is no real revocation mechanism for these tokens, which is why they should be relatively short-lived.
Verdaccio first authenticates the user, then uses the result of the authentication for the duration of the token's validity.
In more detail:
Does this sufficiently address your points?
Hey @n4bb12, I appreciate your quick and thorough response. Just a few follow up points to make sure I'm understanding the flow correctly.
The auth flow as I understand it:
WebFlow
(via the Web UI's login button) or CliFlow
(via npx verdaccio-github-oauth-ui --registry https://my.registry
).S
.S
, and upon success calls the plugin to check access for the requested resource with the already decrypted user. If the token is invalid, the call fails.Where I'm still confused:
S
? If I am able to change this, I have a way to invalidate compromised tokens should I need to.
Plugin.authenticate
for? The way it is written, it looks like it gets called with a GitHub OAuth token, however based on my understanding of the above Verdaccio never gets an OAuth token to call it with. So when is this flow initiated?Sure, I will just quote your points and add my thoughts.
If a user does not have a valid Verdaccio token, they can generate one by initiating the
WebFlow
(via the Web UI's login button) orCliFlow
(vianpx verdaccio-github-oauth-ui --registry https://my.registry
).
Yes. I'm not sure I would call it "Verdaccio token" since they are a bit different. One is for use with npm, one is for the UI.
The above flows first retrieve a GitHub OAuth token, either by generating a new one if the app has not already been authorized by the GitHub user, or retrieving the existing one if there is already an installed OAuth app for the plugin on the user's account.
Roughly that's what happens, although a new token is created per redirect flow, not per app installation. The app installation is only there to remember the user confirmation. You will still get a new token each time you sign in.
You can read more about how the flow works here or try out an interactive flow here.
The OAuth token is used to retrieve the username and groups the user belongs to. Once this has been done, the GitHub OAuth token is not needed until the user has to generate a new Verdaccio token.
The OAuth token is only used to determine the user name. If using JWTs, it is discarded. We never use it again. If using AES encryption, we store it in the AES payload and use it to re-identify the user on each API call.
GitHub memberships are determined using the auth.github-oauth-ui.token
that you configured in the verdaccio config. User tokens are never used for this purpose.
The plugin then creates a user with the retrieved name and groups, and gets Verdaccio to encrypt the user as a JWT using the sign options from the config and some secret
S
.
Yes.
On subsequent calls, Verdaccio verifies the JWT using
S
, and upon success calls the plugin to check access for the requested resource with the already decrypted user. If the token is invalid, the call fails.
Yes.
What is the secret
S
?
Verdaccio generates a random secret at first startup and stores it in storage/.verdaccio-db.json
.
If I am able to change this, I have a way to invalidate compromised tokens should I need to.
Yes, and good point.
What is
Plugin.authenticate
for?
In general, the when and how is up to Verdaccio and can change (and has changed in the past). The docs state "on each request" but this refers to the time before Verdaccio used JWTs and is now outdated.
When using JWTs, I would expect Verdaccio to just decode the JWT and there's your user. No need to re-authenticate or re-evaluate memberships. It looks like this is the relevant code.
When not using JWTs, it could be called when using Basic Auth or the "legacy" AES-encrypted tokens. Try disabling the JWT settings in your Verdaccio config to observe this.
For other plugins, the answers might be different. For this plugin, and when using JWTs, plugin.authenticate
is not used.
The way it is written, it looks like it gets called with a GitHub OAuth token, however, based on my understanding of the above Verdaccio never gets an OAuth token to call it with. So when is this flow initiated?
In general, Verdaccio passes the username and some sort of secret/token/password. What this is depends on the plugin and the token mechanism.
In our case, if JWTs are not used, we AES-encrypt the user token from the OAuth flow. See here. This token is then handed back to us in plugin.authenticate
after Verdaccio already decrypted and split it for us.
It is a bit confusing, especially due to old behaviors in both Verdaccio and this plugin. But I hope this helped shed some light.
Very helpful, thank you!
I'll leave this open since I think some docs still need to be updated, but no other questions from me at the moment.
Bug Report
Versions
Observed behavior
When revoking tokens following the instructions under https://github.com/n4bb12/verdaccio-github-oauth-ui/blob/master/docs/configuration.md#revoking-tokens, it appears as though the token remains valid despite it being revoked.
Once I have revoked a token, I can continue to browse packages and their versions in the Web UI, pull packages via the command line, and publish new packages. It's only once I logout of the Verdaccio UI or clear my local token (i.e. I'm forced to generate a new one) that my requests are rejected and I have to re-authorize the GitHub app.
I have tried invalidating tokens a few ways, namely
Revoke
.Revoke all user tokens
.Note: I am seeing requests getting rejected after a token expires. However, I need to maintain long-lasting tokens for CI builds, and therefore need a reliable way to revoke them should they be compromised.
Expected behavior
After revoking a token, it should no longer be able to access or publish packages in Verdaccio.
Steps to reproduce
This can be reproduced via the Web UI or via the command line.
Web UI:
Settings > Applications > Authorized OAuth Apps
and revoke Verdaccio.Command line:
npx verdaccio-github-oauth-ui --registry https://my.registry
and authorize Verdaccio.Settings > Applications > Authorized OAuth Apps
and revoke Verdaccio.Additional context
Dockerfile:
Config: