mirror of
https://github.com/GreptimeTeam/greptimedb.git
synced 2026-10-29 07:06:20 +00:00
feat(auth): support HTTP bearer-token authentication (#8719)
* feat(auth): support HTTP bearer-token authentication (#8718) Adds an opt-in bearer-token (JWT / OAuth2) authentication path to the HTTP layer, so clients can authenticate with `Authorization: Bearer <token>` against any `/v1/` interface. Today such requests are rejected with `UnsupportedAuthScheme("bearer")` -> 401 before any handler runs. The token is treated as opaque by the server; validation and identity derivation stay in the UserProvider, so JWT/JWKS/OIDC policy remains pluggable and out of core. Changes: - `auth::UserProvider` gains `auth_token(token, catalog, schema) -> Result<UserInfoRef>` with a default that rejects (`Error::UnsupportedAuthMethod`), so password-only providers keep today's behavior. A provider that supports token auth overrides it to validate the token, resolve it to a user, and authorize the connection. - `auth::Error::UnsupportedAuthMethod` for the default-reject case. - `servers::http::authorize::inner_auth` extracts a bearer token (`extract_bearer_token`) and, when present, authenticates via `UserProvider::auth_token`; otherwise it falls through unchanged to the username/password path (Basic / influxdb / splunk). Basic and bearer coexist on the same server. Backward compatible: the default impl preserves existing behavior, and non-bearer requests take the exact same path as before. Tests: - `extract_bearer_token` recognizes `Bearer` (either header) and ignores Basic/Token/Splunk/empty. - `inner_auth` dispatches a bearer token to `auth_token` and populates the QueryContext user on success; rejects on failure. - A password-only provider (default `auth_token`) rejects bearer tokens. Refs: #8718 * chore: fmt * refactor(auth): address bearer-auth review feedback (#8719) Address the review comments on the HTTP bearer-token authentication PR: - Match the `Bearer` scheme case-insensitively (RFC 9110 §11.1) via `eq_ignore_ascii_case`. `extract_bearer_token` previously only accepted `Bearer`/`bearer`, so a valid `BEARER <token>` fell through to `UnsupportedAuthScheme` and never reached the provider. The opaque token itself is deliberately not lowercased. - Return `Option<&str>` (borrowing the request headers) instead of `Option<String>`, avoiding an allocation per bearer request. - Rename `UserProvider::auth_token` -> `auth_bearer_token` for clarity. - Route bearer-auth failures on Splunk HEC requests through `splunk_hec_err` (FORBIDDEN, code 4) instead of the generic 401 `ErrorResponse`, so HEC clients retain their `{"text":"Invalid token","code":4}` endpoint contract. Adds test coverage for case-insensitive scheme parsing (token preserved verbatim) and a regression test for the bearer/splunk routing path. Signed-off-by: Ning Sun <sunning@greptime.com> --------- Signed-off-by: Ning Sun <sunning@greptime.com>
This commit is contained in:
@@ -88,6 +88,13 @@ pub enum Error {
|
||||
#[snafu(implicit)]
|
||||
location: Location,
|
||||
},
|
||||
|
||||
#[snafu(display("Unsupported authentication method: {}", method))]
|
||||
UnsupportedAuthMethod {
|
||||
method: String,
|
||||
#[snafu(implicit)]
|
||||
location: Location,
|
||||
},
|
||||
}
|
||||
|
||||
impl ErrorExt for Error {
|
||||
@@ -105,6 +112,7 @@ impl ErrorExt for Error {
|
||||
Error::UserPasswordMismatch { .. } => StatusCode::UserPasswordMismatch,
|
||||
Error::AccessDenied { .. } => StatusCode::AccessDenied,
|
||||
Error::PermissionDenied { .. } => StatusCode::PermissionDenied,
|
||||
Error::UnsupportedAuthMethod { .. } => StatusCode::UserPasswordMismatch,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -34,8 +34,8 @@ use crate::common::{
|
||||
PgScramSha256Verifier, auth_mysql_with_hash_stage_2,
|
||||
};
|
||||
use crate::error::{
|
||||
IllegalParamSnafu, InvalidConfigSnafu, IoSnafu, Result, UnsupportedPasswordTypeSnafu,
|
||||
UserNotFoundSnafu, UserPasswordMismatchSnafu,
|
||||
IllegalParamSnafu, InvalidConfigSnafu, IoSnafu, Result, UnsupportedAuthMethodSnafu,
|
||||
UnsupportedPasswordTypeSnafu, UserNotFoundSnafu, UserPasswordMismatchSnafu,
|
||||
};
|
||||
use crate::user_info::{DefaultUserInfo, PermissionMode};
|
||||
use crate::{UserInfoRef, auth_mysql};
|
||||
@@ -66,6 +66,31 @@ pub trait UserProvider: Send + Sync {
|
||||
Ok(user_info)
|
||||
}
|
||||
|
||||
/// Authenticates and authorizes an opaque bearer token (e.g. a JWT or an
|
||||
/// OAuth2 access token).
|
||||
///
|
||||
/// Unlike [auth()](Self::auth), the caller has no `Identity`/`Password` —
|
||||
/// the provider validates the token and *derives* the identity from it.
|
||||
/// The token is opaque to the server, so JWT/JWKS/OIDC validation policy
|
||||
/// stays pluggable and out of core.
|
||||
///
|
||||
/// The default rejects token auth with
|
||||
/// [`Error::UnsupportedAuthMethod`], so password-only providers keep
|
||||
/// today's behavior. Providers that support token auth override this to
|
||||
/// validate the token, resolve it to a user, and
|
||||
/// [`authorize`](Self::authorize) the connection.
|
||||
async fn auth_bearer_token(
|
||||
&self,
|
||||
_token: &str,
|
||||
_catalog: &str,
|
||||
_schema: &str,
|
||||
) -> Result<UserInfoRef> {
|
||||
UnsupportedAuthMethodSnafu {
|
||||
method: "bearer token",
|
||||
}
|
||||
.fail()
|
||||
}
|
||||
|
||||
async fn postgres_auth_info(&self, _id: Identity<'_>) -> Result<PgAuthInfo> {
|
||||
Ok(PgAuthInfo::Cleartext)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user