-
Notifications
You must be signed in to change notification settings - Fork 224
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
feat(gateway): add JWT authentication #6535
base: master
Are you sure you want to change the base?
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.
Thx for the PR, logic looks good, just code organization could be improved. Generally I'd recommend to think about how arguments to functions can be reduced, that way you typically get a good feeling for where a function should live (e.g. verification belongs into AuthManager
because it has access to the secret and that's all you need to verify).
cli.rpcpassword.clone(), | ||
cli.rpcjwt.clone(), |
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.
Now needing both is unfortunate, ideally the password would just be an implementation detail of some function/struct providing fetching the session token. What's the plan to evolve this?
I see, both are optional.
gateway/ln-gateway/src/lib.rs
Outdated
let gateway_id = Self::load_or_create_gateway_id(&gateway_db).await; | ||
|
||
// for JWT it is necessary to create an encoding secret that will be used each | ||
// time a new JWT token is generated. The encoding secret ensures token's | ||
// integrity and authenticity, so it is necessary to use a secure random | ||
// number generator to generate strong keys. | ||
let encoding_secret: [u8; 16] = rand::thread_rng().gen(); |
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.
The secret being genearted on the fly means a GW restart invalidates old tokens I assume? Not a problem imo, just want to point it out so everyone is aware.
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.
Yes, you are right. Where should I put a note about that? Would it be okay as an additional comment, or should I create an additional document for that?
} | ||
|
||
impl GatewayRpcClient { | ||
pub fn new(versioned_api: SafeUrl, password: Option<String>) -> Self { | ||
pub fn new(versioned_api: SafeUrl, password: Option<String>, jwt_code: Option<String>) -> Self { |
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.
What are the semantics of providing a password, a JWT or both? Please document. E.g. does providing the password lead to automatic requesting of a JWT whenever the previous 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.
Providing both a password and a JWT does not make sense.
Providing an initial password has the purpose of generating a JWT that has to be used to request private endpoints.
Maybe instead of providing optional parameters (JWT and password) would be better to provide an enum
for each case, WDYT?
} | ||
return Ok(next.run(request).await); | ||
} | ||
//TODO remove this fallback when all the callers support JWT auth |
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.
Should create an issue once merged
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.
yes, @elsirion it would be better to create additional issues in this repository, and also I think it would be necessary to create an issue in the ui
repository.
let auth_manager = gateway.auth_manager.lock().await; | ||
if let Ok(decoded_token_data) = jsonwebtoken::decode::<crate::auth_manager::Session>( | ||
&token, | ||
&jsonwebtoken::DecodingKey::from_secret(auth_manager.encoding_secret.as_ref()), | ||
&jsonwebtoken::Validation::default(), | ||
) { | ||
let now = fedimint_core::time::now() | ||
.duration_since(UNIX_EPOCH) | ||
.expect("Time went backwards") | ||
.as_secs(); | ||
if now > decoded_token_data.claims.exp { | ||
return Err(StatusCode::UNAUTHORIZED); | ||
} |
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.
This could be a separate function for checking the JWT, would make the code cleaner.
EDIT: should live inside AuthManager
after reading the full diff
gateway/ln-gateway/src/lib.rs
Outdated
@@ -197,6 +199,9 @@ pub struct Gateway { | |||
/// The gateway's federation manager. | |||
federation_manager: Arc<RwLock<FederationManager>>, | |||
|
|||
/// The gateway's authentication manager | |||
auth_manager: Arc<Mutex<AuthManager>>, |
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.
Why does the auth manager need to be mutated at all? Can we remove the Acr<Mutex<_>>
?
let session = auth_manager | ||
.generate_session() | ||
.map_err(|_| AdminGatewayError::Unauthorized)?; | ||
let token = session | ||
.encode_jwt(&auth_manager.encoding_secret) | ||
.map_err(|_| AdminGatewayError::Unauthorized)?; |
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.
Should be encapsulated as one function in the AuthManager
. There's no reason to generate a session without signing it imo. Just call the fn something that makes it clear that the session tokens are signed.
pub struct Session { | ||
/// the unique identifier of the session. | ||
pub id: PublicKey, | ||
/// the expire time of the session | ||
pub exp: u64, | ||
} |
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.
Member fields can be private afaik. Only the auth manager should access them to either create a session or check it.
pub fn new(id: PublicKey, expiry: u64) -> Self { | ||
let now = fedimint_core::time::now() | ||
.duration_since(UNIX_EPOCH) | ||
.expect("Time went backwards") | ||
.as_secs(); | ||
Self { | ||
id, | ||
exp: now + expiry, | ||
} | ||
} |
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.
This could become private too
pub fn encode_jwt(self, encoding_secret: &[u8; 16]) -> anyhow::Result<String> { | ||
let claim = self; | ||
encode( | ||
&Header::default(), | ||
&claim, | ||
&EncodingKey::from_secret(encoding_secret), | ||
) | ||
.map_err(|_| anyhow::anyhow!("Unable to generate jwt token session")) | ||
} |
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.
Can be moved to AuthManager
and used inside generate_and_sign_session
acdfd59
to
34a4808
Compare
closes #4128
Motivation
Add support for JWT gateway authentication
This PR adds one additional endpoint:
/auth/session
if the password is correct, this endpoint returns a JWT token that must be used in the other endpoints that require authentication.How to test it using the CLI:
gateway-cli --address=http://127.0.0.1:16265/v1 --rpcpassword=theresnosecondbest get-session-jwt-auth
gateway-cli --address=http://127.0.0.1:16265/v1 --rpcjwt=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpZCI6IjAzY2I1MTBjOWJiMzA5ODNkOTYzODk3ZWUyNjlkM2FiZmY3Nzg0YTVjMmVkYWM3YjQzMzg1NjIyMmMzZGIxYWQ1ZSIsImV4cCI6MTczMzg1ODUxN30.4FNxyn6Y-M_vfPRstxgrv4weK9MMRv31Inijw6JJo80 get-balances