mirror of
https://github.com/hasura/graphql-engine.git
synced 2024-12-15 09:22:43 +03:00
introduce AuthConfig v2, which removes role emulation (#891)
<!-- The PR description should answer 2 (maybe 3) important questions: --> ### What We have decided to remove the role emulation feature from engine altogether. More details in the RFC - https://docs.google.com/document/d/1tlS9pqRzLEotLXN_dhjFOeIgbH6zmejOdZTbkkPD-aM/edit ### How <!-- How is it trying to accomplish it (what are the implementation steps)? --> V3_GIT_ORIGIN_REV_ID: e7cb765df5afac6c6d6a05a572a832ce9910cc0b
This commit is contained in:
parent
a8d1002175
commit
5b23ed53bc
@ -12,6 +12,9 @@
|
||||
|
||||
### Changed
|
||||
|
||||
- Introduced `AuthConfig` `v2`. This new version removes role emulation in
|
||||
engine (`allowRoleEmulationBy`) field.
|
||||
|
||||
## [v2024.07.25]
|
||||
|
||||
### Fixed
|
||||
|
@ -36,8 +36,8 @@ fn build_allowed_roles(
|
||||
pub async fn authenticate_request(
|
||||
http_client: &reqwest::Client,
|
||||
jwt_config: JWTConfig,
|
||||
allow_role_emulation_for: Option<&Role>,
|
||||
headers: &HeaderMap,
|
||||
allow_role_emulation_for: Option<&Role>,
|
||||
) -> Result<Identity, Error> {
|
||||
let tracer = tracing_util::global_tracer();
|
||||
tracer
|
||||
@ -213,8 +213,8 @@ mod tests {
|
||||
let authenticated_identity = authenticate_request(
|
||||
&http_client,
|
||||
jwt_config,
|
||||
Some(&Role::new("admin")),
|
||||
&header_map,
|
||||
Some(&Role::new("admin")),
|
||||
)
|
||||
.await?;
|
||||
|
||||
@ -310,8 +310,8 @@ mod tests {
|
||||
let authenticated_identity = authenticate_request(
|
||||
&http_client,
|
||||
jwt_config,
|
||||
Some(&Role::new("admin")),
|
||||
&header_map,
|
||||
Some(&Role::new("admin")),
|
||||
)
|
||||
.await?;
|
||||
|
||||
|
@ -1,4 +1,6 @@
|
||||
use std::fmt::Display;
|
||||
use std::hash;
|
||||
use std::hash::{Hash, Hasher};
|
||||
use std::net;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
@ -12,29 +14,18 @@ use axum::{
|
||||
routing::{get, post},
|
||||
Extension, Json, Router,
|
||||
};
|
||||
|
||||
use base64::engine::Engine;
|
||||
use clap::Parser;
|
||||
use pre_execution_plugin::{
|
||||
configuration::PrePluginConfig, execute::pre_execution_plugins_handler,
|
||||
};
|
||||
use reqwest::header::CONTENT_TYPE;
|
||||
use serde::Serialize;
|
||||
use tower_http::cors::CorsLayer;
|
||||
use tower_http::trace::TraceLayer;
|
||||
use tracing_util::{
|
||||
add_event_on_active_span, set_attribute_on_active_span, set_status_on_current_span,
|
||||
ErrorVisibility, SpanVisibility, TraceableError, TraceableHttpResponse,
|
||||
};
|
||||
|
||||
use base64::engine::Engine;
|
||||
use engine::internal_flags::{resolve_unstable_features, UnstableFeature};
|
||||
use engine::VERSION;
|
||||
use engine::{
|
||||
authentication::{
|
||||
AuthConfig::{self, V1 as V1AuthConfig},
|
||||
AuthModeConfig,
|
||||
},
|
||||
authentication::{resolve_auth_config, AuthConfig, AuthModeConfig},
|
||||
internal_flags::{resolve_unstable_features, UnstableFeature},
|
||||
plugins::read_pre_execution_plugins_config,
|
||||
VERSION,
|
||||
};
|
||||
use execute::HttpContext;
|
||||
use hasura_authn_core::Session;
|
||||
@ -43,9 +34,14 @@ use hasura_authn_jwt::jwt;
|
||||
use hasura_authn_noauth as noauth;
|
||||
use hasura_authn_webhook::webhook;
|
||||
use lang_graphql as gql;
|
||||
use pre_execution_plugin::{
|
||||
configuration::PrePluginConfig, execute::pre_execution_plugins_handler,
|
||||
};
|
||||
use schema::GDS;
|
||||
use std::hash;
|
||||
use std::hash::{Hash, Hasher};
|
||||
use tracing_util::{
|
||||
add_event_on_active_span, set_attribute_on_active_span, set_status_on_current_span,
|
||||
ErrorVisibility, SpanVisibility, TraceableError, TraceableHttpResponse,
|
||||
};
|
||||
|
||||
mod cors;
|
||||
|
||||
@ -547,33 +543,36 @@ where
|
||||
SpanVisibility::Internal,
|
||||
|| {
|
||||
Box::pin(async {
|
||||
match &engine_state.auth_config {
|
||||
V1AuthConfig(auth_config) => match &auth_config.mode {
|
||||
// We are still supporting AuthConfig::V1, hence we need to
|
||||
// support role emulation
|
||||
let (auth_mode, allow_role_emulation_by) = match &engine_state.auth_config {
|
||||
AuthConfig::V1(auth_config) => (
|
||||
&auth_config.mode,
|
||||
auth_config.allow_role_emulation_by.as_ref(),
|
||||
),
|
||||
// There is no role emulation in AuthConfig::V2
|
||||
AuthConfig::V2(auth_config) => (&auth_config.mode, None),
|
||||
};
|
||||
match auth_mode {
|
||||
AuthModeConfig::NoAuth(no_auth_config) => {
|
||||
Ok(noauth::identity_from_config(no_auth_config))
|
||||
}
|
||||
|
||||
AuthModeConfig::Webhook(webhook_config) => {
|
||||
webhook::authenticate_request(
|
||||
AuthModeConfig::Webhook(webhook_config) => webhook::authenticate_request(
|
||||
&engine_state.http_context.client,
|
||||
webhook_config,
|
||||
&headers_map,
|
||||
auth_config.allow_role_emulation_by.as_ref(),
|
||||
allow_role_emulation_by,
|
||||
)
|
||||
.await
|
||||
.map_err(AuthError::from)
|
||||
}
|
||||
AuthModeConfig::Jwt(jwt_secret_config) => {
|
||||
jwt_auth::authenticate_request(
|
||||
.map_err(AuthError::from),
|
||||
AuthModeConfig::Jwt(jwt_secret_config) => jwt_auth::authenticate_request(
|
||||
&engine_state.http_context.client,
|
||||
*jwt_secret_config.clone(),
|
||||
auth_config.allow_role_emulation_by.as_ref(),
|
||||
&headers_map,
|
||||
allow_role_emulation_by,
|
||||
)
|
||||
.await
|
||||
.map_err(AuthError::from)
|
||||
}
|
||||
},
|
||||
.map_err(AuthError::from),
|
||||
}
|
||||
})
|
||||
},
|
||||
@ -728,7 +727,7 @@ async fn handle_sql_request(
|
||||
|
||||
#[allow(clippy::print_stdout)]
|
||||
/// Print any build warnings to stdout
|
||||
fn print_warnings(warnings: Vec<metadata_resolve::Warning>) {
|
||||
fn print_warnings<T: Display>(warnings: Vec<T>) {
|
||||
for warning in warnings {
|
||||
println!("Warning: {warning}");
|
||||
}
|
||||
@ -742,15 +741,23 @@ fn build_state(
|
||||
pre_execution_plugins_path: &Option<PathBuf>,
|
||||
metadata_resolve_configuration: metadata_resolve::configuration::Configuration,
|
||||
) -> Result<Arc<EngineState>, anyhow::Error> {
|
||||
let auth_config = read_auth_config(authn_config_path).map_err(StartupError::ReadAuth)?;
|
||||
// Auth Config
|
||||
let raw_auth_config = std::fs::read_to_string(authn_config_path)?;
|
||||
let (auth_config, auth_warnings) =
|
||||
resolve_auth_config(&raw_auth_config).map_err(StartupError::ReadAuth)?;
|
||||
|
||||
// Plugins
|
||||
let pre_execution_plugins_config =
|
||||
read_pre_execution_plugins_config(pre_execution_plugins_path)
|
||||
.map_err(StartupError::ReadPrePlugin)?;
|
||||
|
||||
// Metadata
|
||||
let raw_metadata = std::fs::read_to_string(metadata_path)?;
|
||||
let metadata = open_dds::Metadata::from_json_str(&raw_metadata)?;
|
||||
let (resolved_metadata, warnings) =
|
||||
metadata_resolve::resolve(metadata, metadata_resolve_configuration)?;
|
||||
|
||||
print_warnings(auth_warnings);
|
||||
print_warnings(warnings);
|
||||
|
||||
let http_context = HttpContext {
|
||||
@ -772,10 +779,3 @@ fn build_state(
|
||||
});
|
||||
Ok(state)
|
||||
}
|
||||
|
||||
fn read_auth_config(path: &PathBuf) -> Result<AuthConfig, anyhow::Error> {
|
||||
let raw_auth_config = std::fs::read_to_string(path)?;
|
||||
Ok(open_dds::traits::OpenDd::deserialize(
|
||||
serde_json::from_str(&raw_auth_config)?,
|
||||
)?)
|
||||
}
|
||||
|
@ -25,6 +25,42 @@ pub enum AuthModeConfig {
|
||||
/// Definition of the authentication configuration used by the API server.
|
||||
pub enum AuthConfig {
|
||||
V1(AuthConfigV1),
|
||||
V2(AuthConfigV2),
|
||||
}
|
||||
|
||||
impl AuthConfig {
|
||||
pub fn upgrade(self) -> AuthConfigV2 {
|
||||
match self {
|
||||
AuthConfig::V1(v1) => AuthConfigV2 { mode: v1.mode },
|
||||
AuthConfig::V2(v2) => v2,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Debug, Clone, JsonSchema, PartialEq, opendds_derive::OpenDd, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[serde(deny_unknown_fields)]
|
||||
#[schemars(title = "AuthConfigV2")]
|
||||
#[schemars(example = "AuthConfigV2::example")]
|
||||
/// Definition of the authentication configuration used by the API server.
|
||||
pub struct AuthConfigV2 {
|
||||
pub mode: AuthModeConfig,
|
||||
}
|
||||
|
||||
impl AuthConfigV2 {
|
||||
fn example() -> Self {
|
||||
open_dds::traits::OpenDd::deserialize(serde_json::json!(
|
||||
{
|
||||
"mode": {
|
||||
"webhook": {
|
||||
"url": "http://auth_hook:3050/validate-request",
|
||||
"method": "Post"
|
||||
}
|
||||
}
|
||||
}
|
||||
))
|
||||
.unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Debug, Clone, JsonSchema, PartialEq, opendds_derive::OpenDd, Deserialize)]
|
||||
@ -55,6 +91,29 @@ impl AuthConfigV1 {
|
||||
}
|
||||
}
|
||||
|
||||
/// Warnings for the user raised during auth config generation
|
||||
/// These are things that don't break the build, but may do so in future
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum Warning {
|
||||
#[error("AuthConfig v1 is deprecated. `allowRoleEmulationBy` has been removed. Please consider upgrading to AuthConfig v2.")]
|
||||
PleaseUpgradeToV2,
|
||||
}
|
||||
|
||||
/// Resolve `AuthConfig` which is not part of metadata. Hence we resolve/build
|
||||
/// it separately. This also emits warnings.
|
||||
pub fn resolve_auth_config(
|
||||
raw_auth_config: &str,
|
||||
) -> Result<(AuthConfig, Vec<Warning>), anyhow::Error> {
|
||||
let mut warnings = vec![];
|
||||
let auth_config: AuthConfig =
|
||||
open_dds::traits::OpenDd::deserialize(serde_json::from_str(raw_auth_config)?)?;
|
||||
match &auth_config {
|
||||
AuthConfig::V1(_) => warnings.push(Warning::PleaseUpgradeToV2),
|
||||
AuthConfig::V2(_) => (),
|
||||
}
|
||||
Ok((auth_config, warnings))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use goldenfile::Mint;
|
||||
|
@ -22,6 +22,25 @@
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"definition",
|
||||
"version"
|
||||
],
|
||||
"properties": {
|
||||
"version": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"v2"
|
||||
]
|
||||
},
|
||||
"definition": {
|
||||
"$ref": "#/definitions/AuthConfigV2"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
],
|
||||
"definitions": {
|
||||
@ -319,39 +338,17 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"NoAuthConfig": {
|
||||
"$id": "https://hasura.io/jsonschemas/metadata/NoAuthConfig",
|
||||
"title": "NoAuthConfig",
|
||||
"description": "Configuration used when running engine without authentication",
|
||||
"examples": [
|
||||
{
|
||||
"role": "admin",
|
||||
"sessionVariables": {
|
||||
"x-hasura-user-id": "100"
|
||||
}
|
||||
}
|
||||
],
|
||||
"AuthConfigV2": {
|
||||
"$id": "https://hasura.io/jsonschemas/metadata/AuthConfigV2",
|
||||
"title": "AuthConfigV2",
|
||||
"description": "Definition of the authentication configuration used by the API server.",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"role",
|
||||
"sessionVariables"
|
||||
"mode"
|
||||
],
|
||||
"properties": {
|
||||
"role": {
|
||||
"description": "role to assume whilst running the engine",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/Role"
|
||||
}
|
||||
]
|
||||
},
|
||||
"sessionVariables": {
|
||||
"title": "SessionVariables",
|
||||
"description": "static session variables to use whilst running the engine",
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"$ref": "#/definitions/SessionVariableValue"
|
||||
}
|
||||
"mode": {
|
||||
"$ref": "#/definitions/AuthModeConfig"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
@ -803,6 +800,43 @@
|
||||
"additionalProperties": false
|
||||
}
|
||||
]
|
||||
},
|
||||
"NoAuthConfig": {
|
||||
"$id": "https://hasura.io/jsonschemas/metadata/NoAuthConfig",
|
||||
"title": "NoAuthConfig",
|
||||
"description": "Configuration used when running engine without authentication",
|
||||
"examples": [
|
||||
{
|
||||
"role": "admin",
|
||||
"sessionVariables": {
|
||||
"x-hasura-user-id": "100"
|
||||
}
|
||||
}
|
||||
],
|
||||
"type": "object",
|
||||
"required": [
|
||||
"role",
|
||||
"sessionVariables"
|
||||
],
|
||||
"properties": {
|
||||
"role": {
|
||||
"description": "role to assume whilst running the engine",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/Role"
|
||||
}
|
||||
]
|
||||
},
|
||||
"sessionVariables": {
|
||||
"title": "SessionVariables",
|
||||
"description": "static session variables to use whilst running the engine",
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"$ref": "#/definitions/SessionVariableValue"
|
||||
}
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user