1
0
mirror of https://github.com/matrix-org/matrix-authentication-service.git synced 2025-07-31 09:24:31 +03:00

Do not embed the WASM-compiled policies in the binary

This commit is contained in:
Quentin Gliech
2022-11-18 19:28:16 +01:00
parent a86798d2b3
commit 834214bcac
20 changed files with 124 additions and 92 deletions

View File

@ -32,24 +32,21 @@ jobs:
version: 0.45.0 version: 0.45.0
- name: Lint policies - name: Lint policies
run: | working-directory: ./policies
cd crates/policy/policies run: make lint
make lint
- name: Run OPA tests - name: Run OPA tests
run: | working-directory: ./policies
cd crates/policy/policies run: make test
make test
- name: Run OPA tests with coverage - name: Run OPA tests with coverage
run: | working-directory: ./policies
cd crates/policy/policies run: make coverage
make coverage
- name: Upload to codecov.io - name: Upload to codecov.io
uses: codecov/codecov-action@v3 uses: codecov/codecov-action@v3
with: with:
files: crates/policy/policies/coverage.json files: policies/coverage.json
flags: policies flags: policies
frontend-lint: frontend-lint:
@ -198,9 +195,8 @@ jobs:
version: 0.45.0 version: 0.45.0
- name: Compile OPA policies - name: Compile OPA policies
run: | working-directory: ./policies
cd crates/policy/policies run: make
make
- name: Setup Rust cache - name: Setup Rust cache
uses: Swatinem/rust-cache@v2 uses: Swatinem/rust-cache@v2
@ -259,9 +255,8 @@ jobs:
version: 0.45.0 version: 0.45.0
- name: Compile OPA policies - name: Compile OPA policies
run: | working-directory: ./policies
cd crates/policy/policies run: make
make
- name: Setup Rust cache - name: Setup Rust cache
uses: Swatinem/rust-cache@v2 uses: Swatinem/rust-cache@v2
@ -325,9 +320,8 @@ jobs:
version: 0.45.0 version: 0.45.0
- name: Compile OPA policies - name: Compile OPA policies
run: | working-directory: ./policies
cd crates/policy/policies run: make
make
- name: Setup Rust cache - name: Setup Rust cache
uses: Swatinem/rust-cache@v2 uses: Swatinem/rust-cache@v2

View File

@ -74,8 +74,8 @@ ARG OPA_VERSION
# Download Open Policy Agent # Download Open Policy Agent
ADD --chmod=755 https://github.com/open-policy-agent/opa/releases/download/v${OPA_VERSION}/opa_${BUILDOS}_${BUILDARCH}_static /usr/local/bin/opa ADD --chmod=755 https://github.com/open-policy-agent/opa/releases/download/v${OPA_VERSION}/opa_${BUILDOS}_${BUILDARCH}_static /usr/local/bin/opa
WORKDIR /app/crates/policy/policies WORKDIR /app/policies
COPY ./crates/policy/policies/ /app/crates/policy/policies COPY ./policies /app/policies
RUN make -B RUN make -B
# Change the timestamp of built files for better caching # Change the timestamp of built files for better caching
@ -148,7 +148,6 @@ RUN cargo chef cook \
COPY ./Cargo.toml ./Cargo.lock /app/ COPY ./Cargo.toml ./Cargo.lock /app/
COPY ./crates /app/crates COPY ./crates /app/crates
COPY --from=static-files /app/crates/static-files/public /app/crates/static-files/public COPY --from=static-files /app/crates/static-files/public /app/crates/static-files/public
COPY --from=policy /app/crates/policy/policies/policy.wasm /app/crates/policy/policies/policy.wasm
ENV SQLX_OFFLINE=true ENV SQLX_OFFLINE=true
RUN cargo auditable zigbuild \ RUN cargo auditable zigbuild \
--locked \ --locked \
@ -168,6 +167,8 @@ FROM --platform=${TARGETPLATFORM} gcr.io/distroless/cc-debian${DEBIAN_VERSION}:d
COPY --from=builder /usr/local/bin/mas-cli /usr/local/bin/mas-cli COPY --from=builder /usr/local/bin/mas-cli /usr/local/bin/mas-cli
COPY --from=frontend /usr/local/share/mas-cli /usr/local/share/mas-cli COPY --from=frontend /usr/local/share/mas-cli /usr/local/share/mas-cli
COPY --from=policy /app/policies/policy.wasm /usr/local/share/mas-cli/policy.wasm
WORKDIR / WORKDIR /
ENTRYPOINT ["/usr/local/bin/mas-cli"] ENTRYPOINT ["/usr/local/bin/mas-cli"]
@ -178,5 +179,7 @@ FROM --platform=${TARGETPLATFORM} gcr.io/distroless/cc-debian${DEBIAN_VERSION}:n
COPY --from=builder /usr/local/bin/mas-cli /usr/local/bin/mas-cli COPY --from=builder /usr/local/bin/mas-cli /usr/local/bin/mas-cli
COPY --from=frontend /usr/local/share/mas-cli /usr/local/share/mas-cli COPY --from=frontend /usr/local/share/mas-cli /usr/local/share/mas-cli
COPY --from=policy /app/policies/policy.wasm /usr/local/share/mas-cli/policy.wasm
WORKDIR / WORKDIR /
ENTRYPOINT ["/usr/local/bin/mas-cli"] ENTRYPOINT ["/usr/local/bin/mas-cli"]

View File

@ -11,16 +11,23 @@ See the [Documentation](https://matrix-org.github.io/matrix-authentication-servi
- [Install Node.js and npm](https://nodejs.org/) - [Install Node.js and npm](https://nodejs.org/)
- [Install Open Policy Agent](https://www.openpolicyagent.org/docs/latest/#1-download-opa) - [Install Open Policy Agent](https://www.openpolicyagent.org/docs/latest/#1-download-opa)
- Clone this repository - Clone this repository
- Generate the frontend: - Generate the static-files:
```sh ```sh
cd crates/static-files cd crates/static-files
npm ci npm ci
npm run build npm run build
cd ../.. cd ../..
``` ```
- Build the frontend
```sh
cd frontend
npm ci
npm run build
cd ../..
```
- Build the Open Policy Agent policies - Build the Open Policy Agent policies
```sh ```sh
cd crates/policy/policies cd policies
make make
# OR, if you don't have `opa` installed and want to build through the OPA docker image # OR, if you don't have `opa` installed and want to build through the OPA docker image
make DOCKER=1 make DOCKER=1

View File

@ -18,7 +18,7 @@ use hyper::{Response, Uri};
use mas_config::PolicyConfig; use mas_config::PolicyConfig;
use mas_http::HttpServiceExt; use mas_http::HttpServiceExt;
use mas_policy::PolicyFactory; use mas_policy::PolicyFactory;
use tokio::io::{AsyncRead, AsyncWriteExt}; use tokio::io::AsyncWriteExt;
use tower::{Service, ServiceExt}; use tower::{Service, ServiceExt};
use tracing::info; use tracing::info;
@ -121,19 +121,12 @@ impl Options {
SC::Policy => { SC::Policy => {
let config: PolicyConfig = root.load_config()?; let config: PolicyConfig = root.load_config()?;
info!("Loading and compiling the policy module"); info!("Loading and compiling the policy module");
let mut policy: Box<dyn AsyncRead + std::marker::Unpin> = let policy_file = tokio::fs::File::open(&config.wasm_module)
if let Some(path) = &config.wasm_module { .await
Box::new( .context("failed to open OPA WASM policy file")?;
tokio::fs::File::open(path)
.await
.context("failed to open OPA WASM policy file")?,
)
} else {
Box::new(mas_policy::default_wasm_policy())
};
let policy_factory = PolicyFactory::load( let policy_factory = PolicyFactory::load(
&mut policy, policy_file,
config.data.clone().unwrap_or_default(), config.data.clone().unwrap_or_default(),
config.register_entrypoint.clone(), config.register_entrypoint.clone(),
config.client_registration_entrypoint.clone(), config.client_registration_entrypoint.clone(),

View File

@ -28,7 +28,7 @@ use mas_router::UrlBuilder;
use mas_storage::MIGRATOR; use mas_storage::MIGRATOR;
use mas_tasks::TaskQueue; use mas_tasks::TaskQueue;
use mas_templates::Templates; use mas_templates::Templates;
use tokio::{io::AsyncRead, signal::unix::SignalKind}; use tokio::signal::unix::SignalKind;
use tracing::{error, info, log::warn}; use tracing::{error, info, log::warn};
#[derive(Parser, Debug, Default)] #[derive(Parser, Debug, Default)]
@ -144,19 +144,12 @@ impl Options {
// Load and compile the WASM policies (and fallback to the default embedded one) // Load and compile the WASM policies (and fallback to the default embedded one)
info!("Loading and compiling the policy module"); info!("Loading and compiling the policy module");
let mut policy: Box<dyn AsyncRead + std::marker::Unpin> = let policy_file = tokio::fs::File::open(&config.policy.wasm_module)
if let Some(path) = &config.policy.wasm_module { .await
Box::new( .context("failed to open OPA WASM policy file")?;
tokio::fs::File::open(path)
.await
.context("failed to open OPA WASM policy file")?,
)
} else {
Box::new(mas_policy::default_wasm_policy())
};
let policy_factory = PolicyFactory::load( let policy_factory = PolicyFactory::load(
&mut policy, policy_file,
config.policy.data.clone().unwrap_or_default(), config.policy.data.clone().unwrap_or_default(),
config.policy.register_entrypoint.clone(), config.policy.register_entrypoint.clone(),
config.policy.client_registration_entrypoint.clone(), config.policy.client_registration_entrypoint.clone(),

View File

@ -43,6 +43,26 @@ fn http_address_example_4() -> &'static str {
"0.0.0.0:8080" "0.0.0.0:8080"
} }
#[cfg(not(feature = "docker"))]
fn http_listener_spa_manifest_default() -> Utf8PathBuf {
"./frontend/dist/manifest.json".into()
}
#[cfg(not(feature = "docker"))]
fn http_listener_spa_assets_default() -> Utf8PathBuf {
"./frontend/dist/".into()
}
#[cfg(feature = "docker")]
fn http_listener_spa_manifest_default() -> Utf8PathBuf {
"/usr/local/share/mas-cli/frontend-manifest.json".into()
}
#[cfg(feature = "docker")]
fn http_listener_spa_assets_default() -> Utf8PathBuf {
"/usr/local/share/mas-cli/frontend-assets/".into()
}
/// Kind of socket /// Kind of socket
#[derive(Debug, Serialize, Deserialize, JsonSchema, Clone, Copy)] #[derive(Debug, Serialize, Deserialize, JsonSchema, Clone, Copy)]
#[serde(rename_all = "lowercase")] #[serde(rename_all = "lowercase")]
@ -267,11 +287,13 @@ pub enum Resource {
/// Mount the single page app /// Mount the single page app
Spa { Spa {
/// Path to the vite manifest /// Path to the vite mamanifest.jsonnifest
#[serde(default = "http_listener_spa_manifest_default")]
#[schemars(with = "String")] #[schemars(with = "String")]
manifest: Utf8PathBuf, manifest: Utf8PathBuf,
/// Path to the assets to server /// Path to the assets to server
#[serde(default = "http_listener_spa_assets_default")]
#[schemars(with = "String")] #[schemars(with = "String")]
assets: Utf8PathBuf, assets: Utf8PathBuf,
}, },
@ -325,17 +347,9 @@ impl Default for HttpConfig {
Resource::Compat, Resource::Compat,
Resource::GraphQL { playground: true }, Resource::GraphQL { playground: true },
Resource::Static { web_root: None }, Resource::Static { web_root: None },
#[cfg(not(feature = "docker"))]
Resource::Spa { Resource::Spa {
manifest: "./frontend/dist/manifest.json".into(), manifest: http_listener_spa_manifest_default(),
assets: "./frontend/dist/".into(), assets: http_listener_spa_assets_default(),
},
#[cfg(feature = "docker")]
Resource::Spa {
// This is where the frontend files are mounted in the docker image by
// default
manifest: "/usr/local/share/mas-cli/frontend-manifest.json".into(),
assets: "/usr/local/share/mas-cli/frontend-assets/".into(),
}, },
], ],
tls: None, tls: None,

View File

@ -21,6 +21,16 @@ use serde_with::serde_as;
use super::ConfigurationSection; use super::ConfigurationSection;
#[cfg(not(feature = "docker"))]
fn default_policy_path() -> Utf8PathBuf {
"./policies/policy.wasm".into()
}
#[cfg(feature = "docker")]
fn default_policy_path() -> Utf8PathBuf {
"/usr/local/share/mas-cli/policy.wasm".into()
}
fn default_client_registration_endpoint() -> String { fn default_client_registration_endpoint() -> String {
"client_registration/violation".to_owned() "client_registration/violation".to_owned()
} }
@ -38,9 +48,9 @@ fn default_authorization_grant_endpoint() -> String {
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
pub struct PolicyConfig { pub struct PolicyConfig {
/// Path to the WASM module /// Path to the WASM module
#[serde(default)] #[serde(default = "default_policy_path")]
#[schemars(with = "Option<String>")] #[schemars(with = "String")]
pub wasm_module: Option<Utf8PathBuf>, pub wasm_module: Utf8PathBuf,
/// Entrypoint to use when evaluating client registrations /// Entrypoint to use when evaluating client registrations
#[serde(default = "default_client_registration_endpoint")] #[serde(default = "default_client_registration_endpoint")]
@ -62,7 +72,7 @@ pub struct PolicyConfig {
impl Default for PolicyConfig { impl Default for PolicyConfig {
fn default() -> Self { fn default() -> Self {
Self { Self {
wasm_module: None, wasm_module: default_policy_path(),
client_registration_entrypoint: default_client_registration_endpoint(), client_registration_entrypoint: default_client_registration_endpoint(),
register_entrypoint: default_register_endpoint(), register_entrypoint: default_register_endpoint(),
authorization_grant_entrypoint: default_authorization_grant_endpoint(), authorization_grant_entrypoint: default_authorization_grant_endpoint(),

View File

@ -376,7 +376,25 @@ async fn test_state(pool: PgPool) -> Result<AppState, anyhow::Error> {
let mailer = Mailer::new(&templates, &transport, &mailbox, &mailbox); let mailer = Mailer::new(&templates, &transport, &mailbox, &mailbox);
let homeserver = MatrixHomeserver::new("example.com".to_owned()); let homeserver = MatrixHomeserver::new("example.com".to_owned());
let policy_factory = PolicyFactory::load_default(serde_json::json!({})).await?;
#[allow(clippy::disallowed_types)]
let path = std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
.join("..")
.join("..")
.join("policies")
.join("policy.wasm");
let file = tokio::fs::File::open(path).await?;
let policy_factory = PolicyFactory::load(
file,
serde_json::json!({}),
"register/violation".to_owned(),
"client_registration/violation".to_owned(),
"authorization_grant/violation".to_owned(),
)
.await?;
let policy_factory = Arc::new(policy_factory); let policy_factory = Arc::new(policy_factory);
let graphql_schema = graphql_schema(&pool); let graphql_schema = graphql_schema(&pool);

View File

@ -11,12 +11,15 @@ opa-wasm = { git = "https://github.com/matrix-org/rust-opa-wasm.git" }
serde = { version = "1.0.147", features = ["derive"] } serde = { version = "1.0.147", features = ["derive"] }
serde_json = "1.0.88" serde_json = "1.0.88"
thiserror = "1.0.37" thiserror = "1.0.37"
tokio = { version = "1.21.2", features = ["io-util", "rt"] } tokio = { version = "1.21.2", features = ["io-util"] }
tracing = "0.1.37" tracing = "0.1.37"
wasmtime = { version = "2.0.2", default-features = false, features = ["async", "cranelift"] } wasmtime = { version = "2.0.2", default-features = false, features = ["async", "cranelift"] }
mas-data-model = { path = "../data-model" } mas-data-model = { path = "../data-model" }
oauth2-types = { path = "../oauth2-types" } oauth2-types = { path = "../oauth2-types" }
[dev-dependencies]
tokio = { version = "1.21.2", features = ["fs", "rt", "macros"] }
[features] [features]
cache = ["wasmtime/cache"] cache = ["wasmtime/cache"]

View File

@ -17,8 +17,6 @@
#![warn(clippy::pedantic)] #![warn(clippy::pedantic)]
#![allow(clippy::missing_errors_doc)] #![allow(clippy::missing_errors_doc)]
use std::io::Cursor;
use anyhow::bail; use anyhow::bail;
use mas_data_model::{AuthorizationGrant, StorageBackend, User}; use mas_data_model::{AuthorizationGrant, StorageBackend, User};
use oauth2_types::registration::VerifiedClientMetadata; use oauth2_types::registration::VerifiedClientMetadata;
@ -28,13 +26,6 @@ use thiserror::Error;
use tokio::io::{AsyncRead, AsyncReadExt}; use tokio::io::{AsyncRead, AsyncReadExt};
use wasmtime::{Config, Engine, Module, Store}; use wasmtime::{Config, Engine, Module, Store};
const DEFAULT_POLICY: &[u8] = include_bytes!("../policies/policy.wasm");
#[must_use]
pub fn default_wasm_policy() -> impl AsyncRead + std::marker::Unpin {
Cursor::new(DEFAULT_POLICY)
}
#[derive(Debug, Error)] #[derive(Debug, Error)]
pub enum LoadError { pub enum LoadError {
#[error("failed to read module")] #[error("failed to read module")]
@ -115,17 +106,6 @@ impl PolicyFactory {
Ok(factory) Ok(factory)
} }
pub async fn load_default(data: serde_json::Value) -> Result<Self, LoadError> {
Self::load(
default_wasm_policy(),
data,
"register/violation".to_owned(),
"client_registration/violation".to_owned(),
"authorization_grant/violation".to_owned(),
)
.await
}
#[tracing::instrument(skip(self), err)] #[tracing::instrument(skip(self), err)]
pub async fn instantiate(&self) -> Result<Policy, anyhow::Error> { pub async fn instantiate(&self) -> Result<Policy, anyhow::Error> {
let mut store = Store::new(&self.engine, ()); let mut store = Store::new(&self.engine, ());
@ -257,10 +237,27 @@ mod tests {
#[tokio::test] #[tokio::test]
async fn test_register() { async fn test_register() {
let factory = PolicyFactory::load_default(serde_json::json!({ let data = serde_json::json!({
"allowed_domains": ["element.io", "*.element.io"], "allowed_domains": ["element.io", "*.element.io"],
"banned_domains": ["staging.element.io"], "banned_domains": ["staging.element.io"],
})) });
#[allow(clippy::disallowed_types)]
let path = std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
.join("..")
.join("..")
.join("policies")
.join("policy.wasm");
let file = tokio::fs::File::open(path).await.unwrap();
let factory = PolicyFactory::load(
file,
data,
"register/violation".to_owned(),
"client_registration/violation".to_owned(),
"authorization_grant/violation".to_owned(),
)
.await .await
.unwrap(); .unwrap();

View File

@ -135,7 +135,7 @@
"client_registration_entrypoint": "client_registration/violation", "client_registration_entrypoint": "client_registration/violation",
"data": null, "data": null,
"register_entrypoint": "register/violation", "register_entrypoint": "register/violation",
"wasm_module": null "wasm_module": "./policies/policy.wasm"
}, },
"allOf": [ "allOf": [
{ {
@ -1234,7 +1234,7 @@
}, },
"wasm_module": { "wasm_module": {
"description": "Path to the WASM module", "description": "Path to the WASM module",
"default": null, "default": "./policies/policy.wasm",
"type": "string" "type": "string"
} }
} }
@ -1430,17 +1430,17 @@
"description": "Mount the single page app", "description": "Mount the single page app",
"type": "object", "type": "object",
"required": [ "required": [
"assets",
"manifest",
"name" "name"
], ],
"properties": { "properties": {
"assets": { "assets": {
"description": "Path to the assets to server", "description": "Path to the assets to server",
"default": "./frontend/dist/",
"type": "string" "type": "string"
}, },
"manifest": { "manifest": {
"description": "Path to the vite manifest", "description": "Path to the vite mamanifest.jsonnifest",
"default": "./frontend/dist/manifest.json",
"type": "string" "type": "string"
}, },
"name": { "name": {