From 76a2388d2f9fc65f0bf6eda27932e601e99bfd11 Mon Sep 17 00:00:00 2001 From: Oscar Beaumont Date: Fri, 22 Mar 2024 00:43:17 +0800 Subject: [PATCH] `mttx pull` --- .cargo/config.toml | 4 + Cargo.lock | 98 +++- apps/mttx/Cargo.toml | 7 +- apps/mttx/src/cli/mod.rs | 12 +- apps/mttx/src/cli/pull.rs | 85 +++ apps/mttx/src/cli/push.rs | 3 +- apps/mttx/src/cli/validate.rs | 27 +- apps/mttx/src/main.rs | 34 +- .../[tenantSlug]/policies/[policyId]/edit.tsx | 543 ++++++++++-------- .../web/src/app/api/cli/policy/[policyId].tsx | 46 ++ packages/policies/package.json | 16 - packages/policies/src/definition.ts | 45 -- packages/policies/src/index.ts | 10 - packages/policies/src/policies/chrome.ts | 25 - .../policies/src/policies/restrictions.ts | 32 -- packages/policies/src/policies/script.ts | 1 - packages/policies/src/policies/slack.ts | 38 -- packages/policies/tsconfig.json | 8 - packages/policy/package.json | 21 - .../policy/src/apple/enrollmentProfile.ts | 29 - packages/policy/src/apple/index.ts | 1 - packages/policy/src/buildApple.ts | 38 -- packages/policy/src/configurationSchema.ts | 7 - packages/policy/src/implementations/index.ts | 67 --- packages/policy/src/implementations/slack.ts | 28 - packages/policy/src/index.ts | 20 - packages/policy/tsconfig.json | 8 - packages/ui/src/auto-imports.d.ts | 94 +-- 28 files changed, 634 insertions(+), 713 deletions(-) create mode 100644 .cargo/config.toml create mode 100644 apps/mttx/src/cli/pull.rs create mode 100644 apps/web/src/app/api/cli/policy/[policyId].tsx delete mode 100644 packages/policies/package.json delete mode 100644 packages/policies/src/definition.ts delete mode 100644 packages/policies/src/index.ts delete mode 100644 packages/policies/src/policies/chrome.ts delete mode 100644 packages/policies/src/policies/restrictions.ts delete mode 100644 packages/policies/src/policies/script.ts delete mode 100644 packages/policies/src/policies/slack.ts delete mode 100644 packages/policies/tsconfig.json delete mode 100644 packages/policy/package.json delete mode 100644 packages/policy/src/apple/enrollmentProfile.ts delete mode 100644 packages/policy/src/apple/index.ts delete mode 100644 packages/policy/src/buildApple.ts delete mode 100644 packages/policy/src/configurationSchema.ts delete mode 100644 packages/policy/src/implementations/index.ts delete mode 100644 packages/policy/src/implementations/slack.ts delete mode 100644 packages/policy/src/index.ts delete mode 100644 packages/policy/tsconfig.json diff --git a/.cargo/config.toml b/.cargo/config.toml new file mode 100644 index 00000000..d6ff40ee --- /dev/null +++ b/.cargo/config.toml @@ -0,0 +1,4 @@ +[alias] +mattrax = "run -p mattrax --" +mttx = "run -p mttx -- --server http://localhost:3000 " +mattraxd = "run -p mattraxd --" diff --git a/Cargo.lock b/Cargo.lock index 24b54ae5..1b78ddd7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1970,7 +1970,7 @@ dependencies = [ "parking_lot", "percent-encoding", "remain", - "reqwest", + "reqwest 0.11.27", "ring 0.16.20", "serde", "serde_json", @@ -1992,7 +1992,7 @@ dependencies = [ "http 0.2.12", "http-serde", "jsonwebtoken", - "reqwest", + "reqwest 0.11.27", "ring 0.16.20", "serde", "serde_json", @@ -2018,7 +2018,7 @@ dependencies = [ "handlebars", "http 0.2.12", "percent-encoding", - "reqwest", + "reqwest 0.11.27", "serde", "serde_json", "serde_urlencoded", @@ -2043,7 +2043,7 @@ dependencies = [ "http 0.2.12", "jsonwebtoken", "lazy_static", - "reqwest", + "reqwest 0.11.27", "serde", "serde-aux", "serde_json", @@ -2070,7 +2070,7 @@ dependencies = [ "graph-oauth", "handlebars", "lazy_static", - "reqwest", + "reqwest 0.11.27", "serde", "serde_json", "url", @@ -2397,6 +2397,7 @@ dependencies = [ "itoa 1.0.10", "pin-project-lite", "tokio", + "want", ] [[package]] @@ -2412,6 +2413,22 @@ dependencies = [ "tokio-native-tls", ] +[[package]] +name = "hyper-tls" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" +dependencies = [ + "bytes", + "http-body-util", + "hyper 1.1.0", + "hyper-util", + "native-tls", + "tokio", + "tokio-native-tls", + "tower-service", +] + [[package]] name = "hyper-util" version = "0.1.3" @@ -2419,6 +2436,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ca38ef113da30126bbff9cd1705f9273e15d45498615d138b0c20279ac7a76aa" dependencies = [ "bytes", + "futures-channel", "futures-util", "http 1.0.0", "http-body 1.0.0", @@ -2426,6 +2444,9 @@ dependencies = [ "pin-project-lite", "socket2", "tokio", + "tower", + "tower-service", + "tracing", ] [[package]] @@ -3026,12 +3047,16 @@ version = "0.0.1" dependencies = [ "clap", "graph-rs-sdk", + "mattrax-policy", "mattrax-utils", + "reqwest 0.12.0", "serde", "serde_json", + "serde_yaml", "tokio", "tracing", "tracing-subscriber", + "urlencoding", ] [[package]] @@ -4144,7 +4169,7 @@ dependencies = [ "http 0.2.12", "http-body 0.4.6", "hyper 0.14.28", - "hyper-tls", + "hyper-tls 0.5.0", "ipnet", "js-sys", "log", @@ -4171,6 +4196,48 @@ dependencies = [ "winreg 0.50.0", ] +[[package]] +name = "reqwest" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58b48d98d932f4ee75e541614d32a7f44c889b72bd9c2e04d95edd135989df88" +dependencies = [ + "base64 0.21.7", + "bytes", + "encoding_rs", + "futures-core", + "futures-util", + "h2 0.4.2", + "http 1.0.0", + "http-body 1.0.0", + "http-body-util", + "hyper 1.1.0", + "hyper-tls 0.6.0", + "hyper-util", + "ipnet", + "js-sys", + "log", + "mime", + "native-tls", + "once_cell", + "percent-encoding", + "pin-project-lite", + "rustls-pemfile", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "system-configuration", + "tokio", + "tokio-native-tls", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "winreg 0.50.0", +] + [[package]] name = "rfd" version = "0.10.0" @@ -4638,6 +4705,19 @@ dependencies = [ "syn 2.0.48", ] +[[package]] +name = "serde_yaml" +version = "0.9.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0623d197252096520c6f2a5e1171ee436e5af99a5d7caa2891e55e61950e6d9" +dependencies = [ + "indexmap 2.2.3", + "itoa 1.0.10", + "ryu", + "serde", + "unsafe-libyaml", +] + [[package]] name = "serialize-to-javascript" version = "0.1.1" @@ -5788,6 +5868,12 @@ version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" +[[package]] +name = "unsafe-libyaml" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861" + [[package]] name = "untrusted" version = "0.7.1" diff --git a/apps/mttx/Cargo.toml b/apps/mttx/Cargo.toml index 71a59856..820a24b9 100644 --- a/apps/mttx/Cargo.toml +++ b/apps/mttx/Cargo.toml @@ -7,12 +7,15 @@ publish = false [dependencies] mattrax-utils = { path = "../../crates/utils" } +mattrax-policy = { path = "../../crates/policy" } clap = { version = "4.5.0", features = ["derive"] } tracing-subscriber = "0.3.18" tracing = "0.1.40" - -graph-rs-sdk = "2.0.0-beta.0" serde = { version = "1.0.197", features = ["derive"] } serde_json = "1.0.114" +serde_yaml = "0.9.33" tokio = { version = "1.36.0", features = ["macros", "rt-multi-thread"] } +graph-rs-sdk = "2.0.0-beta.0" +reqwest = { version = "0.12.0", features = ["json"] } +urlencoding = "2.1.3" diff --git a/apps/mttx/src/cli/mod.rs b/apps/mttx/src/cli/mod.rs index 7ef311e8..d1d0b6ac 100644 --- a/apps/mttx/src/cli/mod.rs +++ b/apps/mttx/src/cli/mod.rs @@ -2,6 +2,7 @@ use std::path::PathBuf; use clap::{Parser, Subcommand}; +mod pull; mod push; mod validate; @@ -16,8 +17,14 @@ pub struct Cli { #[arg(short = 'v', short_alias = 'V', long, action = clap::builder::ArgAction::Version)] version: (), - #[arg(short, long)] - pub config: PathBuf, + // The backend API server + #[arg( + short, + long, + help = "The Mattrax API server to connect to", + default_value = "https://cloud.mattrax.app" + )] + pub server: String, #[command(subcommand)] pub command: Commands, @@ -27,4 +34,5 @@ pub struct Cli { pub enum Commands { Validate(validate::Command), Push(push::Command), + Pull(pull::Command), } diff --git a/apps/mttx/src/cli/pull.rs b/apps/mttx/src/cli/pull.rs new file mode 100644 index 00000000..710d907c --- /dev/null +++ b/apps/mttx/src/cli/pull.rs @@ -0,0 +1,85 @@ +use std::{fs, path::PathBuf}; + +use mattrax_policy::Policy; +use reqwest::{Client, Url}; +use tracing::{error, info}; + +#[derive(clap::Args)] +#[command(about = "Pull a policy from Mattrax to a local file")] +pub struct Command { + #[arg(help = "The ID of the policy to pull")] + policy_id: String, + + #[arg(help = "The file to write the policy to")] + path: PathBuf, + + #[arg(long, short, action, help = "Overwrite the file if it already exists")] + force: bool, +} + +impl Command { + pub async fn run(&self, base_url: Url, client: Client) { + if !self.force && self.path.exists() { + error!("File already exists at {:?}", self.path); + return; + } + + let Ok(url) = base_url + .join(&format!( + "/api/cli/policy/{}", + urlencoding::encode(&self.policy_id) + )) + .map_err(|err| error!("Error constructing url to Mattrax API: {err}")) + else { + return; + }; + + let Ok(response) = client + .get(url) + .send() + .await + .map_err(|err| error!("Error doing HTTP request to Mattrax API: {err}")) + else { + return; + }; + if !response.status().is_success() { + error!( + "Error fetching policy from Mattrax: {:?}", + response.status() + ); + return; + } + + // TODO: use a proper struct for the return type + let Ok(body) = response + .json::() + .await + .map_err(|err| error!("Error decoding response from Mattrax API: {err}")) + else { + return; + }; + + let policy = Policy { + name: body + .as_object() + .unwrap() + .get("name") + .unwrap() + .as_str() + .unwrap() + .to_string(), + }; + + let Ok(yaml) = serde_yaml::to_string(&policy) + .map_err(|err| error!("Error serializing policy to YAML: {err}")) + else { + return; + }; + + fs::write(&self.path, yaml) + .map_err(|err| error!("Error writing policy to file: {err}")) + .ok(); + + info!("Successfully pulled '{}' from Mattrax!", policy.name); + } +} diff --git a/apps/mttx/src/cli/push.rs b/apps/mttx/src/cli/push.rs index 0827127f..59bc4f93 100644 --- a/apps/mttx/src/cli/push.rs +++ b/apps/mttx/src/cli/push.rs @@ -1,3 +1,4 @@ +use reqwest::{Client, Url}; use tracing::info; #[derive(clap::Args)] @@ -5,7 +6,7 @@ use tracing::info; pub struct Command {} impl Command { - pub fn run(&self) { + pub async fn run(&self, base_url: Url, client: Client) { info!("Hello World"); // TODO: Push the policy up into Mattrax. diff --git a/apps/mttx/src/cli/validate.rs b/apps/mttx/src/cli/validate.rs index f05fcdce..d6cbc131 100644 --- a/apps/mttx/src/cli/validate.rs +++ b/apps/mttx/src/cli/validate.rs @@ -1,13 +1,32 @@ -use tracing::info; +use std::path::PathBuf; + +use tracing::{error, info}; #[derive(clap::Args)] #[command(about = "Validate a policy file is valid offline")] pub struct Command {} impl Command { - pub fn run(&self) { - info!("Hello World"); + pub fn run(&self, config_path: PathBuf) { + if !config_path.exists() { + error!("Config file was not found at {config_path:?}"); + return; + } + + let Ok(config_raw) = std::fs::read_to_string(&config_path) + .map_err(|err| error!("Failed to read config file: {err}")) + else { + return; + }; + + let Ok(file) = serde_yaml::from_str::(&config_raw) + .map_err(|err| error!("Failed to parse config file: {err}")) + else { + return; + }; + + info!("{file:#?}"); - // TODO: Validate the policy file against the schema. + // TODO: Validate the policy file against the schema (can Serde do this for us?) } } diff --git a/apps/mttx/src/main.rs b/apps/mttx/src/main.rs index 692e730f..93a9592f 100644 --- a/apps/mttx/src/main.rs +++ b/apps/mttx/src/main.rs @@ -1,15 +1,41 @@ use clap::Parser; +use reqwest::{Client, Url}; +use tracing::error; mod cli; -fn main() { +#[tokio::main] +async fn main() { let cli = cli::Cli::parse(); - tracing_subscriber::fmt().init(); + tracing_subscriber::fmt() + .without_time() + .with_target(false) + .init(); std::panic::set_hook(Box::new(move |panic| tracing::error!("{panic}"))); + let Ok(base_uri) = + Url::parse(&cli.server).map_err(|e| error!("`--server` flag is not a valid url: {e}")) + else { + return; + }; + + let Ok(client) = Client::builder() + .user_agent(format!(concat!( + "Mattrax ", + env!("CARGO_PKG_VERSION"), + "/", + env!("GIT_HASH") + ))) + .build() + .map_err(|e| error!("Error constructing HTTP client: {e}")) + else { + return; + }; + match cli.command { - cli::Commands::Validate(cmd) => cmd.run(), - cli::Commands::Push(cmd) => cmd.run(), + cli::Commands::Validate(cmd) => todo!(), // cmd.run(base_uri), + cli::Commands::Pull(cmd) => cmd.run(base_uri, client).await, + cli::Commands::Push(cmd) => cmd.run(base_uri, client).await, } } diff --git a/apps/web/src/app/(dash)/[tenantSlug]/policies/[policyId]/edit.tsx b/apps/web/src/app/(dash)/[tenantSlug]/policies/[policyId]/edit.tsx index 4ff9fddc..c32b218f 100644 --- a/apps/web/src/app/(dash)/[tenantSlug]/policies/[policyId]/edit.tsx +++ b/apps/web/src/app/(dash)/[tenantSlug]/policies/[policyId]/edit.tsx @@ -16,6 +16,10 @@ import { CardHeader, CardTitle, Input, + Tabs, + TabsContent, + TabsList, + TabsTrigger, } from "@mattrax/ui"; import { PageLayout, PageLayoutHeading } from "../../PageLayout"; import { usePolicy } from "./Context"; @@ -29,16 +33,48 @@ export default function Page() { onSuccess: () => policy.query.refetch(), })); + // TODO: Probs replace tabs with proper routes so it's in the URL (when we add the visual editor). + return ( - - Edit - - - } - > - + +
+ Edit + + + CLI + Visual + +
+ + {/* */} + + } + > + +

Get started editing this policy:

+

mttx pull {policy().id} ./policy.yaml

+ +

Deploy your policy:

+

mttx push ./policy.yaml

+ +

+ + Learn more about mttx CLI + +

+ + {/*
{JSON.stringify(policy().data, null, 2)}
*/} +
+ +

+ Visual editor coming soon... +

+
+ + {/* updatePolicy.mutate({ @@ -47,266 +83,267 @@ export default function Page() { }) } disabled={updatePolicy.isPending} - /> -
+ /> */} +
+ ); } -function DeployButton() { - const policy = usePolicy(); - const trpcCtx = trpc.useContext(); +// function DeployButton() { +// const policy = usePolicy(); +// const trpcCtx = trpc.useContext(); - return ( - - - { - trpcCtx.policy.getDeploySummary.ensureData({ - policyId: policy().id, - }); - }} - > - Deploy - - - - - - - ); -} +// return ( +// +// +// { +// trpcCtx.policy.getDeploySummary.ensureData({ +// policyId: policy().id, +// }); +// }} +// > +// Deploy +// +// +// +// +// +// +// ); +// } -function DeployDialog() { - const policy = usePolicy(); +// function DeployDialog() { +// const policy = usePolicy(); - const [page, setPage] = createSignal(0); - const controller = useController(); - const [comment, setComment] = createSignal(""); +// const [page, setPage] = createSignal(0); +// const controller = useController(); +// const [comment, setComment] = createSignal(""); - const getDeploySummary = trpc.policy.getDeploySummary.useQuery(() => ({ - policyId: policy().id, - })); +// const getDeploySummary = trpc.policy.getDeploySummary.useQuery(() => ({ +// policyId: policy().id, +// })); - const deployVersion = trpc.policy.deploy.useMutation(() => ({ - onSuccess: async () => { - await policy.query.refetch(); - controller.setOpen(false); - }, - })); +// const deployVersion = trpc.policy.deploy.useMutation(() => ({ +// onSuccess: async () => { +// await policy.query.refetch(); +// controller.setOpen(false); +// }, +// })); - return ( - - Deploy changes - - Would you like to deploy the following changes to{" "} - - {getDeploySummary.data?.numScoped} - {" "} - entities? - - {page() === 0 && ( - <> -
    - - - {([key, value]) => ( -
  • -

    {`${key} changed to ${value}`}

    -
  • - )} -
    -
    -
- - - )} - {page() === 1 && ( - <> -