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:
Anon Ray 2024-07-29 14:34:21 +05:30 committed by hasura-bot
parent a8d1002175
commit 5b23ed53bc
5 changed files with 181 additions and 85 deletions

View File

@ -12,6 +12,9 @@
### Changed
- Introduced `AuthConfig` `v2`. This new version removes role emulation in
engine (`allowRoleEmulationBy`) field.
## [v2024.07.25]
### Fixed

View File

@ -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?;

View File

@ -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)?,
)?)
}

View File

@ -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;

View File

@ -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
}
}
}