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

Substitute credentials authentication by token authentication with auto-renewal #743

Draft
wants to merge 3 commits into
base: 3.0
Choose a base branch
from

Conversation

farost
Copy link
Member

@farost farost commented Mar 13, 2025

Usage and product changes

Upgrade TypeDB protocol to use authentication tokens instead of passing credentials with every API call.
When sending gRPC requests, drivers include temporary tokens in the request headers, limiting password usage to connection_open and token renewal calls, providing better security for sensitive data.

The authentication and token renewal mechanisms are fully automatic and do not require any actions from TypeDB driver users. Thus, the API remains unchanged.

Implementation

Credential injectors start injecting tokens instead of credentials.
These tokens are acquired when:

  • user creates a new driver (connection_open);
  • a request fails because of the token expiration, and we automatically retry this request once with a prior sign_in call. This operation is expected to be insignificant in terms of performance effect since the users are not expected to set token TTLs to too low values.

@typedb-bot
Copy link
Member

PR Review Checklist

Do not edit the content of this comment. The PR reviewer should simply update this comment by ticking each review item below, as they get completed.


Trivial Change

  • This change is trivial and does not require a code or architecture review.

Code

  • Packages, classes, and methods have a single domain of responsibility.
  • Packages, classes, and methods are grouped into cohesive and consistent domain model.
  • The code is canonical and the minimum required to achieve the goal.
  • Modules, libraries, and APIs are easy to use, robust (foolproof and not errorprone), and tested.
  • Logic and naming has clear narrative that communicates the accurate intent and responsibility of each module (e.g. method, class, etc.).
  • The code is algorithmically efficient and scalable for the whole application.

Architecture

  • Any required refactoring is completed, and the architecture does not introduce technical debt incidentally.
  • Any required build and release automations are updated and/or implemented.
  • Any new components follows a consistent style with respect to the pre-existing codebase.
  • The architecture intuitively reflects the application domain, and is easy to understand.
  • The architecture has a well-defined hierarchy of encapsulated components.
  • The architecture is extensible and scalable.

@@ -275,6 +275,16 @@ impl Error {
}
}

fn try_extracting_connection_error(status: &Status, code: &str) -> Option<ConnectionError> {
// TODO: We should probably catch more connection errors instead of wrapping them into
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 really want to refactor error messaging here.

if let Some(token) = &*self.token.read().expect("Expected token read lock acquisition on inject") {
request.metadata_mut().insert(
"authorization",
format!("Bearer {}", token).try_into().expect("Expected authorization header formatting"),
Copy link
Member Author

Choose a reason for hiding this comment

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

It's more a question about the server, but I decided to follow the standard HTTP format of authorization: Bearer <TOKEN> metadata records. It requires manual parsing in tonic and is done better in axum (for http), but I haven't found any explicit recommendation to turn away from the HTTP standard in gRPC and drop the Bearer part. Although we used to just set token: ... in 2.x.

Copy link
Member

Choose a reason for hiding this comment

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

I don't have a strong knowledge/opinion on this, maybe @lolski has some?

@@ -45,15 +48,41 @@ impl<Channel: GRPCChannel> RPCStub<Channel> {
Self { grpc: GRPC::new(channel), call_credentials }
}

async fn call<F, R>(&mut self, call: F) -> Result<R>
async fn call_with_auto_renew_token<F, R>(&mut self, call: F) -> Result<R>
Copy link
Member Author

Choose a reason for hiding this comment

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

This part is actually a copypaste from 2.x.

where
for<'a> F: Fn(&'a mut Self) -> BoxFuture<'a, Result<R>>,
{
call(self).await
match call(self).await {
Err(Error::Connection(ConnectionError::TokenCredentialInvalid)) => {
Copy link
Member Author

Choose a reason for hiding this comment

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

If it failed specifically, renew the token and retry.

}

pub(super) async fn connection_open(&mut self, req: connection::open::Req) -> Result<connection::open::Res> {
self.single(|this| Box::pin(this.grpc.connection_open(req.clone()))).await
let result = self.single(|this| Box::pin(this.grpc.connection_open(req.clone()))).await;
Copy link
Member Author

Choose a reason for hiding this comment

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

Sneak into the response to get the token on the network level without asking the more business-like logic to work with it for us.

@@ -22,6 +22,7 @@ rm -rf typedb-all

bazel run //tool/test:typedb-extractor -- typedb-all
BAZEL_JAVA_HOME=$(bazel run //tool/test:echo-java-home)
# TODO: Can add `--server.authentication.token_ttl_seconds 15` to test token auto-renewal in BDDs!
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 have the artifacts (I just tested locally), so this feature can be good. We have small integration tests, some small behaviour tests, and some big behaviour tests. Everything will be covered!

@farost
Copy link
Member Author

farost commented Mar 13, 2025

It's expected to be red as I don't have the new server artifacts. It's green locally.

// ServerErrors. However, the most valuable information even for connection is inside
// stacktraces now.
match code {
"AUT3" => Some(ConnectionError::TokenCredentialInvalid {}),
Copy link
Member

Choose a reason for hiding this comment

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

Ouu ok make a comment in the server-side that we depend on those error messages client-side and not to change them randomly -- if we don't already have that warning!

Copy link
Member Author

@farost farost Mar 14, 2025

Choose a reason for hiding this comment

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

We did a similar thing (even for a bigger number of errors) in 2.x in other domains. But I will. And the BDDs with server running with shorter tokens will show if we don't actually renew them.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants