Skip to content

Commit

Permalink
chore: port over infrastructure stuff
Browse files Browse the repository at this point in the history
  • Loading branch information
ctron committed Mar 12, 2024
1 parent 86a736f commit 988f5be
Show file tree
Hide file tree
Showing 46 changed files with 4,099 additions and 19 deletions.
2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ members = [
"graph",
"trustd",
"common",
"common/auth",
"common/infrastructure",
"entity",
"importer",
"migration",
Expand Down
15 changes: 8 additions & 7 deletions common/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,19 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
#huevos-graph = { path = "../graph"}
anyhow = "1.0.72"
clap = { version = "4", features = ["derive", "env"] }
cpe = "0.1.3"
lenient_semver = "0.4.2"
log = "0.4.19"
env_logger = "0.10.0"
serde_json = "1.0.104"
thiserror = "1"
serde = { version = "1.0.183", features = ["derive"] }
native-tls = "0.2"
packageurl = "0.3.0"
lenient_semver = "0.4.2"
pem = "3"
reqwest = "0.11"
sea-orm = { version = "0.12", features = ["sea-query-binder", "sqlx-postgres", "runtime-tokio-rustls", "macros"] }
cpe = "0.1.3"
serde = { version = "1.0.183", features = ["derive"] }
serde_json = "1.0.104"
thiserror = "1"

[dev-dependencies]
serde_json = "1.0.104"
44 changes: 44 additions & 0 deletions common/auth/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
[package]
name = "trustify-auth"
version = "0.1.0"
edition = "2021"
license = "Apache-2.0"
description = "Authentication and authorization functionality"

[dependencies]
anyhow = "1"
async-trait = "0.1"
base64 = "0.22"
biscuit = "0.6"
chrono = { version = "0.4.26", default-features = false }
clap = { version = "4", features = ["derive", "env"] }
futures-util = "0.3"
humantime = "2"
jsonpath-rust = "0.5"
log = "0.4"
openid = "0.12"
reqwest = "0.11"
schemars = "0.8"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
serde_yaml = "0.9"
thiserror = "1"
tokio = "1"
tracing = "0.1"
url = "2"

trustify-common = { path = ".." }

# feature: actix
actix-web = { version = "4.3.1", optional = true }
actix-http = { version = "3.3.1", optional = true }
actix-web-httpauth = { version = "0.8", optional = true }
actix-web-extras = { version = "0.1", optional = true }

# feature: swagger
utoipa = { version = "4", features = ["actix_extras"], optional = true }
utoipa-swagger-ui = { version = "6", features = ["actix-web"], optional = true }

[features]
actix = ["actix-web", "actix-http", "actix-web-httpauth", "actix-web-extras"]
swagger = ["utoipa", "utoipa-swagger-ui", "actix"]
13 changes: 13 additions & 0 deletions common/auth/examples/generate_auth_schema.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
use trustification_auth::auth::AuthConfig;

fn main() -> anyhow::Result<()> {
let schema = schemars::schema_for!(AuthConfig);
let path = "auth/schema/auth.json";
{
let file = std::fs::File::create(path)?;
serde_json::to_writer_pretty(file, &schema)?;
}
println!("Wrote schema to: {path}");

Ok(())
}
117 changes: 117 additions & 0 deletions common/auth/schema/auth.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "AuthConfig",
"type": "object",
"required": [
"authentication"
],
"properties": {
"authentication": {
"$ref": "#/definitions/AuthenticatorConfig"
},
"authorization": {
"default": {},
"allOf": [
{
"$ref": "#/definitions/AuthorizerConfig"
}
]
},
"disabled": {
"type": "boolean"
}
},
"definitions": {
"AuthenticatorClientConfig": {
"description": "Configuration for OIDC client used to authenticate on the server side",
"type": "object",
"required": [
"clientId",
"issuerUrl"
],
"properties": {
"additionalPermissions": {
"description": "Additional scopes which get added for client\n\nThis can be useful if a client is considered to only provide identities which are supposed to have certain scopes, but don't provide them.",
"type": "array",
"items": {
"type": "string"
}
},
"clientId": {
"description": "The ID of the client",
"type": "string"
},
"groupMappings": {
"description": "Mapping table for groups returned found through the `groups_selector` to permissions.",
"type": "object",
"additionalProperties": {
"type": "array",
"items": {
"type": "string"
}
}
},
"groupSelector": {
"description": "JSON path extracting a list of groups from the access token",
"default": null,
"type": [
"string",
"null"
]
},
"issuerUrl": {
"description": "The issuer URL",
"type": "string"
},
"requiredAudience": {
"description": "Enforce an audience claim (`aud`) for tokens.\n\nIf present, the token must have one matching `aud` claim.",
"default": null,
"type": [
"string",
"null"
]
},
"scopeMappings": {
"description": "Mapping table for scopes returned by the issuer to permissions.",
"type": "object",
"additionalProperties": {
"type": "array",
"items": {
"type": "string"
}
}
},
"tlsCaCertificates": {
"description": "Add additional certificates as trust anchor for contacting the issuer",
"default": [],
"type": "array",
"items": {
"type": "string"
}
},
"tlsInsecure": {
"description": "Ignore TLS checks when contacting the issuer",
"default": false,
"type": "boolean"
}
}
},
"AuthenticatorConfig": {
"type": "object",
"required": [
"clients"
],
"properties": {
"clients": {
"type": "array",
"items": {
"$ref": "#/definitions/AuthenticatorClientConfig"
}
}
}
},
"AuthorizerConfig": {
"type": "object"
}
}
}
91 changes: 91 additions & 0 deletions common/auth/src/auth.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
//! Both authentication and authorization

use crate::{
authenticator::config::{AuthenticatorConfig, SingleAuthenticatorClientConfig},
authorizer::AuthorizerConfig,
};
use std::path::PathBuf;

#[derive(Clone, Debug, Default, clap::Args)]
#[command(
rename_all_env = "SCREAMING_SNAKE_CASE",
next_help_heading = "Authentication & authorization"
)]
pub struct AuthConfigArguments {
/// Flag to disable authentication and authorization, default is on.
#[arg(
id = "auth-disabled",
default_value_t = false,
long = "auth-disabled",
env = "AUTH_DISABLED"
)]
pub disabled: bool,

/// Location of the AuthNZ configuration file
#[arg(
id = "auth-configuration",
long = "auth-configuration",
env = "AUTH_CONFIGURATION",
conflicts_with = "SingleAuthenticatorClientConfig"
)]
pub config: Option<PathBuf>,

#[command(flatten)]
pub clients: SingleAuthenticatorClientConfig,
}

#[derive(Clone, Debug, serde::Deserialize, serde::Serialize, schemars::JsonSchema)]
pub struct AuthConfig {
#[serde(default, skip_serializing_if = "is_default")]
pub disabled: bool,

pub authentication: AuthenticatorConfig,

#[serde(default)]
pub authorization: AuthorizerConfig,
}

pub fn is_default<D: Default + PartialEq>(d: &D) -> bool {
d == &D::default()
}

impl AuthConfigArguments {
pub fn split(
self,
devmode: bool,
) -> Result<Option<(AuthenticatorConfig, AuthorizerConfig)>, anyhow::Error> {
// disabled overrides devmode
if self.disabled {
return Ok(None);
}

// check for devmode
if devmode {
log::warn!("Running in developer mode");
return Ok(Some((AuthenticatorConfig::devmode(), Default::default())));
}

Ok(Some(match self.config {
Some(config) => {
let AuthConfig {
disabled,
authentication,
authorization,
} = serde_yaml::from_reader(std::fs::File::open(config)?)?;

if disabled {
return Ok(None);
}

(authentication, authorization)
}
None => {
let authn = AuthenticatorConfig {
clients: self.clients.expand().collect(),
};

(authn, Default::default())
}
}))
}
}
22 changes: 22 additions & 0 deletions common/auth/src/authenticator/actix.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
use super::user::UserInformation;
use super::Authenticator;
use actix_http::HttpMessage;
use actix_web::dev::ServiceRequest;
use actix_web_httpauth::extractors::bearer::BearerAuth;
use std::sync::Arc;

pub async fn openid_validator(
req: ServiceRequest,
auth: BearerAuth,
authenticator: Arc<Authenticator>,
) -> Result<ServiceRequest, (actix_web::Error, ServiceRequest)> {
match authenticator.validate_token(auth.token()).await {
Ok(payload) => {
req.extensions_mut()
.insert(UserInformation::Authenticated(payload.into()));
Ok(req)
}

Err(err) => Err((err.into(), req)),
}
}
48 changes: 48 additions & 0 deletions common/auth/src/authenticator/claims.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
//! OpenID Connect tools

use super::user::UserDetails;
use biscuit::SingleOrMultiple;
use openid::CompactJson;
use serde::{Deserialize, Serialize};
use serde_json::Value;
use url::Url;

/// An OIDC access token, containing the claims that we need.
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct AccessTokenClaims {
#[serde(default)]
pub azp: Option<String>,
pub sub: String,
pub iss: Url,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub aud: Option<SingleOrMultiple<String>>,

pub exp: i64,
pub iat: i64,
#[serde(default)]
pub auth_time: Option<i64>,

#[serde(flatten)]
pub extended_claims: Value,

#[serde(default, skip_serializing_if = "String::is_empty")]
pub scope: String,
}

impl CompactJson for AccessTokenClaims {}

/// A validated access token, including post-processing according to our configuration.
#[derive(Clone, Debug)]
pub struct ValidatedAccessToken {
pub access_token: AccessTokenClaims,
pub permissions: Vec<String>,
}

impl From<ValidatedAccessToken> for UserDetails {
fn from(token: ValidatedAccessToken) -> Self {
Self {
id: token.access_token.sub,
permissions: token.permissions,
}
}
}
Loading

0 comments on commit 988f5be

Please sign in to comment.