From e26ec860477326bd0872c59872b8d3655bf14360 Mon Sep 17 00:00:00 2001 From: Ian Stanton Date: Thu, 26 Sep 2024 07:17:05 -0400 Subject: [PATCH 01/44] Add init_azure_storage_workload_identity Signed-off-by: Ian Stanton --- conductor/src/main.rs | 72 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 71 insertions(+), 1 deletion(-) diff --git a/conductor/src/main.rs b/conductor/src/main.rs index 8d0c453c6..541048ff8 100644 --- a/conductor/src/main.rs +++ b/conductor/src/main.rs @@ -13,7 +13,8 @@ use crate::metrics_reporter::run_metrics_reporter; use crate::status_reporter::run_status_reporter; use conductor::routes::health::background_threads_running; use controller::apis::coredb_types::{ - Backup, CoreDBSpec, GoogleCredentials, S3Credentials, ServiceAccountTemplate, VolumeSnapshot, + AzureCredentials, AzureCredentialsStorageAccount, AzureCredentialsStorageKey, Backup, + CoreDBSpec, GoogleCredentials, S3Credentials, ServiceAccountTemplate, VolumeSnapshot, }; use controller::apis::postgres_parameters::{ConfigValue, PgConfig}; use k8s_openapi::apimachinery::pkg::apis::meta::v1::ObjectMeta; @@ -84,6 +85,14 @@ async fn run(metrics: CustomMetrics) -> Result<(), ConductorError> { .unwrap_or_else(|_| "".to_owned()) .parse() .expect("error parsing GCP_PROJECT_NUMBER"); + let is_azure: bool = env::var("IS_AZURE") + .unwrap_or_else(|_| "false".to_owned()) + .parse() + .expect("error parsing IS_AZURE"); + let azure_storage_account: String = env::var("AZURE_STORAGE_ACCOUNT") + .unwrap_or_else(|_| "".to_owned()) + .parse() + .expect("error parsing AZURE_STORAGE_ACCOUNT"); // Error and exit if CF_TEMPLATE_BUCKET is not set when IS_CLOUD_FORMATION is enabled if is_cloud_formation && cf_template_bucket.is_empty() { @@ -333,6 +342,15 @@ async fn run(metrics: CustomMetrics) -> Result<(), ConductorError> { ) .await?; + init_azure_storage_workload_identity( + is_azure, + &read_msg, + &mut coredb_spec, + backup_archive_bucket.clone(), + azure_storage_account.clone(), + ) + .await?; + info!("{}: Creating namespace", read_msg.msg_id); // create Namespace create_namespace(client.clone(), &namespace, org_id, instance_id).await?; @@ -898,6 +916,7 @@ async fn init_gcp_storage_workload_identity( retentionPolicy: Some(String::from("30")), schedule: Some(generate_cron_expression(&read_msg.message.namespace)), s3_credentials: None, + azure_credentials: None, endpoint_url: None, google_credentials: Some(GoogleCredentials { gke_environment: Some(true), @@ -911,6 +930,57 @@ async fn init_gcp_storage_workload_identity( Ok(()) } +async fn init_azure_storage_workload_identity( + is_azure: bool, + read_msg: &Message, + coredb_spec: &mut CoreDBSpec, + backup_archive_bucket: String, + azure_storage_account: String, +) -> Result<(), ConductorError> { + if !is_azure { + return Ok(()); + } + + //TODO(ianstanton) Implement Azure Workload Identity + + // Generate Backup spec for CoreDB + let volume_snapshot = Some(VolumeSnapshot { + enabled: false, + snapshot_class: None, + }); + + let write_path = read_msg + .message + .backups_write_path + .clone() + .unwrap_or("v2".to_string()); + + let backup = Backup { + destinationPath: Some(format!( + "https://{}.blob.core.windows.net/{}/{}", + azure_storage_account, backup_archive_bucket, write_path + )), + encryption: Some(String::from("AES256")), + retentionPolicy: Some(String::from("30")), + schedule: Some(generate_cron_expression(&read_msg.message.namespace)), + s3_credentials: None, + azure_credentials: Some(AzureCredentials { + connection_string: None, + inherit_from_azure_ad: Some(true), + storage_account: None, + storage_key: None, + storage_sas_token: None, + }), + endpoint_url: None, + google_credentials: None, + volume_snapshot, + }; + + coredb_spec.backup = backup; + + Ok(()) +} + fn from_env_default(key: &str, default: &str) -> String { env::var(key).unwrap_or_else(|_| default.to_owned()) } From e54ea7531d284edacc7adb852943c84f1a7d25b7 Mon Sep 17 00:00:00 2001 From: Ian Stanton Date: Thu, 26 Sep 2024 09:55:19 -0400 Subject: [PATCH 02/44] Add scratch uami_builder Signed-off-by: Ian Stanton --- conductor/Cargo.lock | 470 ++++++++++++++++++++++++++-- conductor/Cargo.toml | 2 + conductor/src/azure/mod.rs | 1 + conductor/src/azure/uami_builder.rs | 68 ++++ conductor/src/lib.rs | 1 + 5 files changed, 515 insertions(+), 27 deletions(-) create mode 100644 conductor/src/azure/mod.rs create mode 100644 conductor/src/azure/uami_builder.rs diff --git a/conductor/Cargo.lock b/conductor/Cargo.lock index b4a3e48ad..e9ba58a31 100644 --- a/conductor/Cargo.lock +++ b/conductor/Cargo.lock @@ -49,7 +49,7 @@ dependencies = [ "mime", "percent-encoding", "pin-project-lite", - "rand", + "rand 0.8.5", "sha1", "smallvec", "tokio", @@ -223,7 +223,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" dependencies = [ "cfg-if", - "getrandom", + "getrandom 0.2.15", "once_cell", "version_check", "zerocopy", @@ -280,6 +280,96 @@ version = "1.0.87" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "10f00e1f6e58a40e807377c75c6a7f97bf9044fab57816f2414e6f5f4499d7b8" +[[package]] +name = "async-channel" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81953c529336010edd6d8e358f886d9581267795c61b19475b71314bffa46d35" +dependencies = [ + "concurrent-queue", + "event-listener 2.5.3", + "futures-core", +] + +[[package]] +name = "async-channel" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89b47800b0be77592da0afd425cc03468052844aff33b84e33cc696f64e77b6a" +dependencies = [ + "concurrent-queue", + "event-listener-strategy", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-io" +version = "2.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "444b0228950ee6501b3568d3c93bf1176a1fdbc3b758dcd9475046d30f4dc7e8" +dependencies = [ + "async-lock", + "cfg-if", + "concurrent-queue", + "futures-io", + "futures-lite 2.3.0", + "parking", + "polling", + "rustix", + "slab", + "tracing", + "windows-sys 0.59.0", +] + +[[package]] +name = "async-lock" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff6e472cdea888a4bd64f342f09b3f50e1886d32afe8df3d663c01140b811b18" +dependencies = [ + "event-listener 5.3.1", + "event-listener-strategy", + "pin-project-lite", +] + +[[package]] +name = "async-process" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63255f1dc2381611000436537bbedfe83183faa303a5a0edaf191edef06526bb" +dependencies = [ + "async-channel 2.3.1", + "async-io", + "async-lock", + "async-signal", + "async-task", + "blocking", + "cfg-if", + "event-listener 5.3.1", + "futures-lite 2.3.0", + "rustix", + "tracing", +] + +[[package]] +name = "async-signal" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "637e00349800c0bdf8bfc21ebbc0b6524abea702b0da4168ac00d070d0c0b9f3" +dependencies = [ + "async-io", + "async-lock", + "atomic-waker", + "cfg-if", + "futures-core", + "futures-io", + "rustix", + "signal-hook-registry", + "slab", + "windows-sys 0.59.0", +] + [[package]] name = "async-stream" version = "0.3.5" @@ -302,6 +392,12 @@ dependencies = [ "syn 2.0.77", ] +[[package]] +name = "async-task" +version = "4.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" + [[package]] name = "async-trait" version = "0.1.82" @@ -654,15 +750,63 @@ dependencies = [ "tracing", ] +[[package]] +name = "azure_core" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34ce3de4b65b1ee2667c81d1fc692949049502a4cf9c38118d811d6d79a7eaef" +dependencies = [ + "async-trait", + "base64 0.22.1", + "bytes", + "dyn-clone", + "futures", + "getrandom 0.2.15", + "http-types", + "once_cell", + "paste", + "pin-project", + "rand 0.8.5", + "reqwest 0.12.7", + "rustc_version", + "serde", + "serde_json", + "time", + "tracing", + "url", + "uuid", +] + +[[package]] +name = "azure_identity" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72c97790480791ec1ee9b76f5c6499b1d0aac0d4cd1e62010bfc19bb545544c5" +dependencies = [ + "async-lock", + "async-process", + "async-trait", + "azure_core", + "futures", + "oauth2", + "pin-project", + "serde", + "time", + "tracing", + "tz-rs", + "url", + "uuid", +] + [[package]] name = "backoff" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b62ddb9cb1ec0a098ad4bbf9344d0713fa193ae1a80af55febcff2627b6a00c1" dependencies = [ - "getrandom", + "getrandom 0.2.15", "instant", - "rand", + "rand 0.8.5", ] [[package]] @@ -744,6 +888,19 @@ dependencies = [ "generic-array", ] +[[package]] +name = "blocking" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "703f41c54fc768e63e091340b424302bb1c29ef4aa0c7f10fe849dfb114d29ea" +dependencies = [ + "async-channel 2.3.1", + "async-task", + "futures-io", + "futures-lite 2.3.0", + "piper", +] + [[package]] name = "brotli" version = "6.0.0" @@ -834,6 +991,15 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "concurrent-queue" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" +dependencies = [ + "crossbeam-utils", +] + [[package]] name = "conductor" version = "0.1.0" @@ -843,6 +1009,8 @@ dependencies = [ "anyhow", "aws-config", "aws-sdk-cloudformation", + "azure_core", + "azure_identity", "base64 0.21.7", "chrono", "controller", @@ -855,7 +1023,7 @@ dependencies = [ "opentelemetry 0.18.0", "opentelemetry-prometheus", "pgmq", - "rand", + "rand 0.8.5", "reqwest 0.12.7", "schemars", "serde", @@ -872,6 +1040,12 @@ version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" +[[package]] +name = "const_fn" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "373e9fafaa20882876db20562275ff58d50e0caa2590077fe7ce7bef90211d0d" + [[package]] name = "controller" version = "0.50.0" @@ -888,7 +1062,7 @@ dependencies = [ "opentelemetry 0.19.0", "passwords", "prometheus", - "rand", + "rand 0.8.5", "regex", "reqwest 0.11.27", "schemars", @@ -1225,6 +1399,27 @@ version = "2.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" +[[package]] +name = "event-listener" +version = "5.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6032be9bd27023a771701cc49f9f053c751055f71efb2e0ae5c15809093675ba" +dependencies = [ + "concurrent-queue", + "parking", + "pin-project-lite", +] + +[[package]] +name = "event-listener-strategy" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f214dc438f977e6d4e3500aaa277f5ad94ca83fbbd9b1a15713ce2344ccc5a1" +dependencies = [ + "event-listener 5.3.1", + "pin-project-lite", +] + [[package]] name = "fastrand" version = "1.9.0" @@ -1350,6 +1545,34 @@ version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" +[[package]] +name = "futures-lite" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49a9d51ce47660b1e808d3c990b4709f2f415d928835a17dfd16991515c46bce" +dependencies = [ + "fastrand 1.9.0", + "futures-core", + "futures-io", + "memchr", + "parking", + "pin-project-lite", + "waker-fn", +] + +[[package]] +name = "futures-lite" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52527eb5074e35e9339c6b4e8d12600c7128b68fb25dcb9fa9dec18f7c25f3a5" +dependencies = [ + "fastrand 2.1.1", + "futures-core", + "futures-io", + "parking", + "pin-project-lite", +] + [[package]] name = "futures-macro" version = "0.3.30" @@ -1401,6 +1624,17 @@ dependencies = [ "version_check", ] +[[package]] +name = "getrandom" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.9.0+wasi-snapshot-preview1", +] + [[package]] name = "getrandom" version = "0.2.15" @@ -1410,7 +1644,7 @@ dependencies = [ "cfg-if", "js-sys", "libc", - "wasi", + "wasi 0.11.0+wasi-snapshot-preview1", "wasm-bindgen", ] @@ -1607,7 +1841,7 @@ dependencies = [ "idna 0.4.0", "ipnet", "once_cell", - "rand", + "rand 0.8.5", "thiserror", "tinyvec", "tokio", @@ -1628,7 +1862,7 @@ dependencies = [ "lru-cache", "once_cell", "parking_lot", - "rand", + "rand 0.8.5", "resolv-conf", "smallvec", "thiserror", @@ -1736,6 +1970,26 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "add0ab9360ddbd88cfeb3bd9574a1d85cfdfa14db10b3e21d3700dbc4328758f" +[[package]] +name = "http-types" +version = "2.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e9b187a72d63adbfba487f48095306ac823049cb504ee195541e91c7775f5ad" +dependencies = [ + "anyhow", + "async-channel 1.9.0", + "base64 0.13.1", + "futures-lite 1.13.0", + "infer", + "pin-project-lite", + "rand 0.7.3", + "serde", + "serde_json", + "serde_qs", + "serde_urlencoded", + "url", +] + [[package]] name = "httparse" version = "1.9.4" @@ -1985,6 +2239,12 @@ dependencies = [ "serde", ] +[[package]] +name = "infer" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64e9829a50b42bb782c1df523f78d332fe371b10c661e78b7a3c34b0198e9fac" + [[package]] name = "instant" version = "0.1.13" @@ -2147,7 +2407,7 @@ dependencies = [ "openssl", "pem 1.1.1", "pin-project", - "rand", + "rand 0.8.5", "secrecy", "serde", "serde_json", @@ -2399,7 +2659,7 @@ dependencies = [ "hermit-abi 0.3.9", "libc", "log", - "wasi", + "wasi 0.11.0+wasi-snapshot-preview1", "windows-sys 0.52.0", ] @@ -2462,7 +2722,7 @@ dependencies = [ "num-integer", "num-iter", "num-traits", - "rand", + "rand 0.8.5", "smallvec", "zeroize", ] @@ -2503,6 +2763,34 @@ dependencies = [ "libm", ] +[[package]] +name = "num_threads" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c7398b9c8b70908f6371f47ed36737907c87c52af34c268fed0bf0ceb92ead9" +dependencies = [ + "libc", +] + +[[package]] +name = "oauth2" +version = "4.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c38841cdd844847e3e7c8d29cef9dcfed8877f8f56f9071f77843ecf3baf937f" +dependencies = [ + "base64 0.13.1", + "chrono", + "getrandom 0.2.15", + "http 0.2.12", + "rand 0.8.5", + "serde", + "serde_json", + "serde_path_to_error", + "sha2", + "thiserror", + "url", +] + [[package]] name = "object" version = "0.36.4" @@ -2650,7 +2938,7 @@ dependencies = [ "once_cell", "opentelemetry_api 0.18.0", "percent-encoding", - "rand", + "rand 0.8.5", "thiserror", "tokio", "tokio-stream", @@ -2672,7 +2960,7 @@ dependencies = [ "once_cell", "opentelemetry_api 0.19.0", "percent-encoding", - "rand", + "rand 0.8.5", "thiserror", "tokio", "tokio-stream", @@ -2699,6 +2987,12 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" +[[package]] +name = "parking" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" + [[package]] name = "parking_lot" version = "0.12.3" @@ -2819,6 +3113,17 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "piper" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96c8c490f422ef9a4efd2cb5b42b76c8613d7e7dfc1caf667b8a3350a5acc066" +dependencies = [ + "atomic-waker", + "fastrand 2.1.1", + "futures-io", +] + [[package]] name = "pkcs1" version = "0.7.5" @@ -2846,6 +3151,21 @@ version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" +[[package]] +name = "polling" +version = "3.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc2790cd301dec6cd3b7a025e4815cf825724a51c98dccfe6a3e55f05ffb6511" +dependencies = [ + "cfg-if", + "concurrent-queue", + "hermit-abi 0.4.0", + "pin-project-lite", + "rustix", + "tracing", + "windows-sys 0.59.0", +] + [[package]] name = "powerfmt" version = "0.2.0" @@ -2936,6 +3256,19 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "rand" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" +dependencies = [ + "getrandom 0.1.16", + "libc", + "rand_chacha 0.2.2", + "rand_core 0.5.1", + "rand_hc", +] + [[package]] name = "rand" version = "0.8.5" @@ -2943,8 +3276,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", - "rand_chacha", - "rand_core", + "rand_chacha 0.3.1", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_chacha" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" +dependencies = [ + "ppv-lite86", + "rand_core 0.5.1", ] [[package]] @@ -2954,7 +3297,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", - "rand_core", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_core" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" +dependencies = [ + "getrandom 0.1.16", ] [[package]] @@ -2963,7 +3315,16 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom", + "getrandom 0.2.15", +] + +[[package]] +name = "rand_hc" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" +dependencies = [ + "rand_core 0.5.1", ] [[package]] @@ -2973,7 +3334,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3a3da5cbb4c27c5150c03a54a7e4745437cd90f9e329ae657c0b889a144bb7be" dependencies = [ "proc-macro-hack", - "rand", + "rand 0.8.5", "random-number-macro-impl", ] @@ -3012,7 +3373,7 @@ version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" dependencies = [ - "getrandom", + "getrandom 0.2.15", "libredox", "thiserror", ] @@ -3202,7 +3563,7 @@ checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" dependencies = [ "cc", "cfg-if", - "getrandom", + "getrandom 0.2.15", "libc", "spin 0.9.8", "untrusted 0.9.0", @@ -3222,7 +3583,7 @@ dependencies = [ "num-traits", "pkcs1", "pkcs8", - "rand_core", + "rand_core 0.6.4", "signature", "spki", "subtle", @@ -3485,6 +3846,27 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_path_to_error" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af99884400da37c88f5e9146b7f1fd0fbcae8f6eec4e9da38b67d05486f814a6" +dependencies = [ + "itoa", + "serde", +] + +[[package]] +name = "serde_qs" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7715380eec75f029a4ef7de39a9200e0a63823176b759d055b613f5a87df6a6" +dependencies = [ + "percent-encoding", + "serde", + "thiserror", +] + [[package]] name = "serde_urlencoded" version = "0.7.1" @@ -3563,7 +3945,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" dependencies = [ "digest", - "rand_core", + "rand_core 0.6.4", ] [[package]] @@ -3665,7 +4047,7 @@ dependencies = [ "crc", "crossbeam-queue", "either", - "event-listener", + "event-listener 2.5.3", "futures-channel", "futures-core", "futures-intrusive", @@ -3761,7 +4143,7 @@ dependencies = [ "memchr", "once_cell", "percent-encoding", - "rand", + "rand 0.8.5", "rsa", "serde", "sha1", @@ -3801,7 +4183,7 @@ dependencies = [ "md-5", "memchr", "once_cell", - "rand", + "rand 0.8.5", "serde", "serde_json", "sha2", @@ -4021,7 +4403,10 @@ checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" dependencies = [ "deranged", "itoa", + "js-sys", + "libc", "num-conv", + "num_threads", "powerfmt", "serde", "time-core", @@ -4347,7 +4732,7 @@ dependencies = [ "http 0.2.12", "httparse", "log", - "rand", + "rand 0.8.5", "sha1", "thiserror", "url", @@ -4360,6 +4745,15 @@ version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" +[[package]] +name = "tz-rs" +version = "0.6.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33851b15c848fad2cf4b105c6bb66eb9512b6f6c44a4b13f57c53c73c707e2b4" +dependencies = [ + "const_fn", +] + [[package]] name = "unicase" version = "2.7.0" @@ -4435,6 +4829,7 @@ dependencies = [ "form_urlencoded", "idna 0.5.0", "percent-encoding", + "serde", ] [[package]] @@ -4473,6 +4868,15 @@ dependencies = [ "syn 2.0.77", ] +[[package]] +name = "uuid" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81dfa00651efa65069b0b6b651f4aaa31ba9e3c3ce0137aaad053604ee7e0314" +dependencies = [ + "getrandom 0.2.15", +] + [[package]] name = "valuable" version = "0.1.0" @@ -4497,6 +4901,12 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c3082ca00d5a5ef149bb8b555a72ae84c9c59f7250f013ac822ac2e49b19c64" +[[package]] +name = "waker-fn" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "317211a0dc0ceedd78fb2ca9a44aed3d7b9b26f81870d485c07122b4350673b7" + [[package]] name = "want" version = "0.3.1" @@ -4506,6 +4916,12 @@ dependencies = [ "try-lock", ] +[[package]] +name = "wasi" +version = "0.9.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" + [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" diff --git a/conductor/Cargo.toml b/conductor/Cargo.toml index eeef1e9d8..8c65c7070 100644 --- a/conductor/Cargo.toml +++ b/conductor/Cargo.toml @@ -36,6 +36,8 @@ anyhow = "1.0.82" serde_yaml = "0.9.34" reqwest = { version = "0.12.3", features = ["json"] } google-cloud-storage = "0.22.1" +azure_identity = "0.20.0" +azure_core = "0.20.0" [dependencies.kube] features = ["runtime", "client", "derive"] diff --git a/conductor/src/azure/mod.rs b/conductor/src/azure/mod.rs new file mode 100644 index 000000000..ad7adb4c2 --- /dev/null +++ b/conductor/src/azure/mod.rs @@ -0,0 +1 @@ +pub mod uami_builder; \ No newline at end of file diff --git a/conductor/src/azure/uami_builder.rs b/conductor/src/azure/uami_builder.rs new file mode 100644 index 000000000..5be0dd2e7 --- /dev/null +++ b/conductor/src/azure/uami_builder.rs @@ -0,0 +1,68 @@ +use azure_identity::ClientSecretCredential; +use azure_identity::oauth2; +use reqwest::Client; +use reqwest::Url; +use std::sync::Arc; + +#[tokio::main] +async fn main() -> Result<(), Box> { + // Replace with your Azure credentials + let tenant_id = "your_tenant_id"; + let client_id = oauth2::ClientId::new("your_client_id"); + let client_secret = Some(oauth2::ClientSecret::new("your_client_secret")); + let subscription_id = "your_subscription_id"; + let resource_group_name = "your_resource_group_name"; + + let http_client = Arc::new(reqwest::Client::new()); + let authority_host = Url::parse("https://login.microsoftonline.com/")?; + + let credential = ClientSecretCredential { + http_client, + authority_host, + tenant_id: tenant_id.to_string(), + client_id, + client_secret, + cache: None, // or provide a custom TokenCache implementation + }; + + let token = credential.get_token("https://management.azure.com/").await?.token.secret(); + + let client = Client::new(); + let base_url = format!("https://management.azure.com/subscriptions/{}/resourceGroups/{}/providers/Microsoft.ManagedIdentity/userAssignedIdentities", subscription_id, resource_group_name); + + // Create a new User Assigned Managed Identity + let identity_name = "my-new-identity"; + let url = format!("{}/{}", base_url, identity_name); + let body = json!({ + "location": "eastus", + }); + let headers = { + let mut headers = reqwest::header::HeaderMap::new(); + headers.insert("Authorization", format!("Bearer {}", token).parse().unwrap()); + headers.insert("Content-Type", "application/json".parse().unwrap()); + headers + }; + + client.put(url) + .headers(headers) + .json(&body) + .send() + .await? + .text() + .await?; + + println!("Created User Assigned Managed Identity: {}", identity_name); + + // Delete the User Assigned Managed Identity + let url = format!("{}/{}", base_url, identity_name); + client.delete(url) + .headers(headers) + .send() + .await? + .text() + .await?; + + println!("Deleted User Assigned Managed Identity: {}", identity_name); + + Ok(()) +} \ No newline at end of file diff --git a/conductor/src/lib.rs b/conductor/src/lib.rs index 65321f4cc..156b4317b 100644 --- a/conductor/src/lib.rs +++ b/conductor/src/lib.rs @@ -1,4 +1,5 @@ pub mod aws; +pub mod azure; pub mod errors; pub mod extensions; pub mod gcp; From ed3c480ca85823b294bbb2ae3012dd1f4ad0a855 Mon Sep 17 00:00:00 2001 From: Ian Stanton Date: Thu, 26 Sep 2024 15:25:40 -0400 Subject: [PATCH 03/44] Updatescratch uami_builder Signed-off-by: Ian Stanton --- conductor/src/azure/uami_builder.rs | 23 ++++++++--------------- 1 file changed, 8 insertions(+), 15 deletions(-) diff --git a/conductor/src/azure/uami_builder.rs b/conductor/src/azure/uami_builder.rs index 5be0dd2e7..238d57613 100644 --- a/conductor/src/azure/uami_builder.rs +++ b/conductor/src/azure/uami_builder.rs @@ -1,31 +1,24 @@ -use azure_identity::ClientSecretCredential; -use azure_identity::oauth2; +use azure_identity::WorkloadIdentityCredential; +use azure_core::auth::TokenCredential; use reqwest::Client; use reqwest::Url; use std::sync::Arc; #[tokio::main] async fn main() -> Result<(), Box> { - // Replace with your Azure credentials - let tenant_id = "your_tenant_id"; - let client_id = oauth2::ClientId::new("your_client_id"); - let client_secret = Some(oauth2::ClientSecret::new("your_client_secret")); + let tenant_id = "your_tenant_id".to_string(); + let client_id = "your_client_id".to_string(); let subscription_id = "your_subscription_id"; let resource_group_name = "your_resource_group_name"; let http_client = Arc::new(reqwest::Client::new()); let authority_host = Url::parse("https://login.microsoftonline.com/")?; - let credential = ClientSecretCredential { - http_client, - authority_host, - tenant_id: tenant_id.to_string(), - client_id, - client_secret, - cache: None, // or provide a custom TokenCache implementation - }; + let scopes = vec!["https://management.azure.com/.default"]; + + let credential = WorkloadIdentityCredential::new(http_client, authority_host, tenant_id, client_id, None); + let token = credential.get_token(&scopes).await?; - let token = credential.get_token("https://management.azure.com/").await?.token.secret(); let client = Client::new(); let base_url = format!("https://management.azure.com/subscriptions/{}/resourceGroups/{}/providers/Microsoft.ManagedIdentity/userAssignedIdentities", subscription_id, resource_group_name); From 357654dc3f73c2680d2955660042e0785f779930 Mon Sep 17 00:00:00 2001 From: Ian Stanton Date: Fri, 4 Oct 2024 20:19:40 -0400 Subject: [PATCH 04/44] Add UAMI create example Signed-off-by: Ian Stanton --- conductor/Cargo.lock | 18 ++++++- conductor/Cargo.toml | 2 +- conductor/src/azure/mod.rs | 2 +- conductor/src/azure/uami_builder.rs | 75 ++++++++--------------------- 4 files changed, 40 insertions(+), 57 deletions(-) diff --git a/conductor/Cargo.lock b/conductor/Cargo.lock index e9ba58a31..e703cffb2 100644 --- a/conductor/Cargo.lock +++ b/conductor/Cargo.lock @@ -798,6 +798,22 @@ dependencies = [ "uuid", ] +[[package]] +name = "azure_mgmt_msi" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e707e87a141765b64c8f473c926078ea6ea56a3feb98f1541d88fa8a886300e6" +dependencies = [ + "azure_core", + "bytes", + "futures", + "log", + "once_cell", + "serde", + "serde_json", + "time", +] + [[package]] name = "backoff" version = "0.4.0" @@ -1009,8 +1025,8 @@ dependencies = [ "anyhow", "aws-config", "aws-sdk-cloudformation", - "azure_core", "azure_identity", + "azure_mgmt_msi", "base64 0.21.7", "chrono", "controller", diff --git a/conductor/Cargo.toml b/conductor/Cargo.toml index 8c65c7070..722598719 100644 --- a/conductor/Cargo.toml +++ b/conductor/Cargo.toml @@ -37,7 +37,7 @@ serde_yaml = "0.9.34" reqwest = { version = "0.12.3", features = ["json"] } google-cloud-storage = "0.22.1" azure_identity = "0.20.0" -azure_core = "0.20.0" +azure_mgmt_msi = "0.20.0" [dependencies.kube] features = ["runtime", "client", "derive"] diff --git a/conductor/src/azure/mod.rs b/conductor/src/azure/mod.rs index ad7adb4c2..2c685ff41 100644 --- a/conductor/src/azure/mod.rs +++ b/conductor/src/azure/mod.rs @@ -1 +1 @@ -pub mod uami_builder; \ No newline at end of file +pub mod uami_builder; diff --git a/conductor/src/azure/uami_builder.rs b/conductor/src/azure/uami_builder.rs index 238d57613..ba38ae352 100644 --- a/conductor/src/azure/uami_builder.rs +++ b/conductor/src/azure/uami_builder.rs @@ -1,61 +1,28 @@ -use azure_identity::WorkloadIdentityCredential; -use azure_core::auth::TokenCredential; -use reqwest::Client; -use reqwest::Url; +use azure_identity::AzureCliCredential; +use azure_mgmt_msi::models::{Identity, TrackedResource}; use std::sync::Arc; #[tokio::main] -async fn main() -> Result<(), Box> { - let tenant_id = "your_tenant_id".to_string(); - let client_id = "your_client_id".to_string(); - let subscription_id = "your_subscription_id"; - let resource_group_name = "your_resource_group_name"; - - let http_client = Arc::new(reqwest::Client::new()); - let authority_host = Url::parse("https://login.microsoftonline.com/")?; - - let scopes = vec!["https://management.azure.com/.default"]; - - let credential = WorkloadIdentityCredential::new(http_client, authority_host, tenant_id, client_id, None); - let token = credential.get_token(&scopes).await?; - - - let client = Client::new(); - let base_url = format!("https://management.azure.com/subscriptions/{}/resourceGroups/{}/providers/Microsoft.ManagedIdentity/userAssignedIdentities", subscription_id, resource_group_name); - - // Create a new User Assigned Managed Identity - let identity_name = "my-new-identity"; - let url = format!("{}/{}", base_url, identity_name); - let body = json!({ - "location": "eastus", - }); - let headers = { - let mut headers = reqwest::header::HeaderMap::new(); - headers.insert("Authorization", format!("Bearer {}", token).parse().unwrap()); - headers.insert("Content-Type", "application/json".parse().unwrap()); - headers +pub async fn create_uami() -> Result<(), Box> { + let credential = Arc::new(AzureCliCredential::new()); + let subscription_id = AzureCliCredential::get_subscription().await?; + let resource_group_name = "ian".to_string(); + let uami_name = "test-uami".to_string(); + let client = azure_mgmt_msi::Client::builder(credential).build()?; + + let uami_params = Identity { + tracked_resource: TrackedResource { + resource: Default::default(), + tags: None, + location: "eastus".to_string(), + }, + properties: None, }; - client.put(url) - .headers(headers) - .json(&body) - .send() - .await? - .text() + let uami_created = client + .user_assigned_identities_client() + .create_or_update(subscription_id, resource_group_name, uami_name, uami_params) .await?; - - println!("Created User Assigned Managed Identity: {}", identity_name); - - // Delete the User Assigned Managed Identity - let url = format!("{}/{}", base_url, identity_name); - client.delete(url) - .headers(headers) - .send() - .await? - .text() - .await?; - - println!("Deleted User Assigned Managed Identity: {}", identity_name); - + println!("UAMI created: {uami_created:#?}"); Ok(()) -} \ No newline at end of file +} From 8c7e9b8dc755f75ab3b524ba94033476e656546c Mon Sep 17 00:00:00 2001 From: Ian Stanton Date: Fri, 4 Oct 2024 21:14:17 -0400 Subject: [PATCH 05/44] Create role assignment Signed-off-by: Ian Stanton --- conductor/Cargo.lock | 19 +++++++++++- conductor/Cargo.toml | 1 + conductor/src/azure/uami_builder.rs | 46 +++++++++++++++++++++++++++-- 3 files changed, 62 insertions(+), 4 deletions(-) diff --git a/conductor/Cargo.lock b/conductor/Cargo.lock index e703cffb2..586029c8d 100644 --- a/conductor/Cargo.lock +++ b/conductor/Cargo.lock @@ -798,6 +798,22 @@ dependencies = [ "uuid", ] +[[package]] +name = "azure_mgmt_authorization" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0996028421d746deea5e44692c84dd79d6d1bd77b40f215cddb5b21a49b3d401" +dependencies = [ + "azure_core", + "bytes", + "futures", + "log", + "once_cell", + "serde", + "serde_json", + "time", +] + [[package]] name = "azure_mgmt_msi" version = "0.20.0" @@ -1026,6 +1042,7 @@ dependencies = [ "aws-config", "aws-sdk-cloudformation", "azure_identity", + "azure_mgmt_authorization", "azure_mgmt_msi", "base64 0.21.7", "chrono", @@ -1064,7 +1081,7 @@ checksum = "373e9fafaa20882876db20562275ff58d50e0caa2590077fe7ce7bef90211d0d" [[package]] name = "controller" -version = "0.50.0" +version = "0.50.1" dependencies = [ "actix-web", "anyhow", diff --git a/conductor/Cargo.toml b/conductor/Cargo.toml index 722598719..8902f51d3 100644 --- a/conductor/Cargo.toml +++ b/conductor/Cargo.toml @@ -38,6 +38,7 @@ reqwest = { version = "0.12.3", features = ["json"] } google-cloud-storage = "0.22.1" azure_identity = "0.20.0" azure_mgmt_msi = "0.20.0" +azure_mgmt_authorization = "0.20.0" [dependencies.kube] features = ["runtime", "client", "derive"] diff --git a/conductor/src/azure/uami_builder.rs b/conductor/src/azure/uami_builder.rs index ba38ae352..9207c07f1 100644 --- a/conductor/src/azure/uami_builder.rs +++ b/conductor/src/azure/uami_builder.rs @@ -1,4 +1,6 @@ use azure_identity::AzureCliCredential; +use azure_mgmt_authorization; +use azure_mgmt_authorization::models::RoleAssignmentProperties; use azure_mgmt_msi::models::{Identity, TrackedResource}; use std::sync::Arc; @@ -8,8 +10,9 @@ pub async fn create_uami() -> Result<(), Box> { let subscription_id = AzureCliCredential::get_subscription().await?; let resource_group_name = "ian".to_string(); let uami_name = "test-uami".to_string(); - let client = azure_mgmt_msi::Client::builder(credential).build()?; + let msi_client = azure_mgmt_msi::Client::builder(credential.clone()).build()?; + // Create User Assigned Managed Identity let uami_params = Identity { tracked_resource: TrackedResource { resource: Default::default(), @@ -19,10 +22,47 @@ pub async fn create_uami() -> Result<(), Box> { properties: None, }; - let uami_created = client + let uami_created = msi_client .user_assigned_identities_client() - .create_or_update(subscription_id, resource_group_name, uami_name, uami_params) + .create_or_update( + subscription_id.clone(), + resource_group_name, + uami_name, + uami_params, + ) .await?; println!("UAMI created: {uami_created:#?}"); + + let uami_id = uami_created.properties.unwrap().principal_id.unwrap(); + + // Create Role Assignment for the UAMI created above + let role_name = "Contributor"; + let role_assignment_name = "00000000-0000-0000-0000-000000000000".to_string(); + let role_assignment_params = azure_mgmt_authorization::models::RoleAssignmentCreateParameters { + properties: RoleAssignmentProperties { + scope: None, + role_definition_id: "ba92f5b4-2d11-453d-a403-e96b0029c9fe".to_string(), + principal_id: uami_id, + principal_type: None, + description: None, + condition: None, + condition_version: None, + created_on: None, + updated_on: None, + created_by: None, + updated_by: None, + delegated_managed_identity_resource_id: None, + }, + }; + + let role_assignment_client = azure_mgmt_authorization::Client::builder(credential).build()?; + let scope = format!("/subscriptions/{subscription_id}"); + + let role_assignment_created = role_assignment_client + .role_assignments_client() + .create(scope, role_assignment_name, role_assignment_params) + .await?; + println!("Role Assignment created: {role_assignment_created:#?}"); + Ok(()) } From 6b1666b3e47a4e74ecd808169ae869452f131421 Mon Sep 17 00:00:00 2001 From: Ian Stanton Date: Mon, 7 Oct 2024 11:53:34 -0400 Subject: [PATCH 06/44] Refactor uami and role assignment functions Signed-off-by: Ian Stanton --- conductor/src/azure/uami_builder.rs | 28 +++++++++++++++++----------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/conductor/src/azure/uami_builder.rs b/conductor/src/azure/uami_builder.rs index 9207c07f1..1b610cd87 100644 --- a/conductor/src/azure/uami_builder.rs +++ b/conductor/src/azure/uami_builder.rs @@ -1,18 +1,19 @@ use azure_identity::AzureCliCredential; +use azure_identity::WorkloadIdentityCredential; use azure_mgmt_authorization; -use azure_mgmt_authorization::models::RoleAssignmentProperties; +use azure_mgmt_authorization::models::{RoleAssignment, RoleAssignmentProperties}; use azure_mgmt_msi::models::{Identity, TrackedResource}; use std::sync::Arc; #[tokio::main] -pub async fn create_uami() -> Result<(), Box> { +pub async fn create_uami() -> Result> { let credential = Arc::new(AzureCliCredential::new()); let subscription_id = AzureCliCredential::get_subscription().await?; let resource_group_name = "ian".to_string(); let uami_name = "test-uami".to_string(); let msi_client = azure_mgmt_msi::Client::builder(credential.clone()).build()?; - // Create User Assigned Managed Identity + // Set parameters for User Assigned Managed Identity let uami_params = Identity { tracked_resource: TrackedResource { resource: Default::default(), @@ -22,6 +23,7 @@ pub async fn create_uami() -> Result<(), Box> { properties: None, }; + // Create User Assigned Managed Identity let uami_created = msi_client .user_assigned_identities_client() .create_or_update( @@ -31,13 +33,18 @@ pub async fn create_uami() -> Result<(), Box> { uami_params, ) .await?; - println!("UAMI created: {uami_created:#?}"); - - let uami_id = uami_created.properties.unwrap().principal_id.unwrap(); + Ok(uami_created) +} - // Create Role Assignment for the UAMI created above - let role_name = "Contributor"; +// Create Role Assignment for UAMI +pub async fn create_role_assignment( + subscription_id: &str, + uami_id: String, +) -> Result> { + let credential = AzureCliCredential::new(); let role_assignment_name = "00000000-0000-0000-0000-000000000000".to_string(); + + // Set parameters for Role Assignment let role_assignment_params = azure_mgmt_authorization::models::RoleAssignmentCreateParameters { properties: RoleAssignmentProperties { scope: None, @@ -58,11 +65,10 @@ pub async fn create_uami() -> Result<(), Box> { let role_assignment_client = azure_mgmt_authorization::Client::builder(credential).build()?; let scope = format!("/subscriptions/{subscription_id}"); + // Create Role Assignment let role_assignment_created = role_assignment_client .role_assignments_client() .create(scope, role_assignment_name, role_assignment_params) .await?; - println!("Role Assignment created: {role_assignment_created:#?}"); - - Ok(()) + Ok(role_assignment_created) } From 7f7ff0d15378e7b994bad7c2cdb98a2edab6bc1b Mon Sep 17 00:00:00 2001 From: Ian Stanton Date: Mon, 7 Oct 2024 12:10:32 -0400 Subject: [PATCH 07/44] Update parameters for create_uami Signed-off-by: Ian Stanton --- conductor/src/azure/uami_builder.rs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/conductor/src/azure/uami_builder.rs b/conductor/src/azure/uami_builder.rs index 1b610cd87..9c8667790 100644 --- a/conductor/src/azure/uami_builder.rs +++ b/conductor/src/azure/uami_builder.rs @@ -6,11 +6,15 @@ use azure_mgmt_msi::models::{Identity, TrackedResource}; use std::sync::Arc; #[tokio::main] -pub async fn create_uami() -> Result> { +pub async fn create_uami( + resource_group: String, + instance_name: String, + region: String, +) -> Result> { let credential = Arc::new(AzureCliCredential::new()); let subscription_id = AzureCliCredential::get_subscription().await?; - let resource_group_name = "ian".to_string(); - let uami_name = "test-uami".to_string(); + let resource_group_name = resource_group; + let uami_name = instance_name; let msi_client = azure_mgmt_msi::Client::builder(credential.clone()).build()?; // Set parameters for User Assigned Managed Identity @@ -18,7 +22,7 @@ pub async fn create_uami() -> Result> { tracked_resource: TrackedResource { resource: Default::default(), tags: None, - location: "eastus".to_string(), + location: region, }, properties: None, }; From 1dd3e2ef7dee7dcc17c5cddfd9b93d5393c615e0 Mon Sep 17 00:00:00 2001 From: Ian Stanton Date: Mon, 7 Oct 2024 12:14:01 -0400 Subject: [PATCH 08/44] Add get_credentials Signed-off-by: Ian Stanton --- conductor/src/azure/uami_builder.rs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/conductor/src/azure/uami_builder.rs b/conductor/src/azure/uami_builder.rs index 9c8667790..6b1853d2e 100644 --- a/conductor/src/azure/uami_builder.rs +++ b/conductor/src/azure/uami_builder.rs @@ -1,11 +1,18 @@ -use azure_identity::AzureCliCredential; use azure_identity::WorkloadIdentityCredential; +use azure_identity::{AzureCliCredential, TokenCredentialOptions}; use azure_mgmt_authorization; use azure_mgmt_authorization::models::{RoleAssignment, RoleAssignmentProperties}; use azure_mgmt_msi::models::{Identity, TrackedResource}; use std::sync::Arc; -#[tokio::main] +// Get credentials from workload identity +pub async fn get_credentials() -> Result> { + let options: TokenCredentialOptions = Default::default(); + let credential = WorkloadIdentityCredential::create(options)?; + Ok(credential) +} + +// Create User Assigned Managed Identity pub async fn create_uami( resource_group: String, instance_name: String, From 58c5540f3cc2756044851aab2a6de7e56ff668cb Mon Sep 17 00:00:00 2001 From: Ian Stanton Date: Mon, 7 Oct 2024 12:54:17 -0400 Subject: [PATCH 09/44] Use workload identity credentials Signed-off-by: Ian Stanton --- conductor/Cargo.lock | 1 + conductor/Cargo.toml | 1 + conductor/src/azure/uami_builder.rs | 21 ++++++++++----------- 3 files changed, 12 insertions(+), 11 deletions(-) diff --git a/conductor/Cargo.lock b/conductor/Cargo.lock index 586029c8d..0971802e1 100644 --- a/conductor/Cargo.lock +++ b/conductor/Cargo.lock @@ -1041,6 +1041,7 @@ dependencies = [ "anyhow", "aws-config", "aws-sdk-cloudformation", + "azure_core", "azure_identity", "azure_mgmt_authorization", "azure_mgmt_msi", diff --git a/conductor/Cargo.toml b/conductor/Cargo.toml index 8902f51d3..83ce2b3d7 100644 --- a/conductor/Cargo.toml +++ b/conductor/Cargo.toml @@ -39,6 +39,7 @@ google-cloud-storage = "0.22.1" azure_identity = "0.20.0" azure_mgmt_msi = "0.20.0" azure_mgmt_authorization = "0.20.0" +azure_core = "0.20.0" [dependencies.kube] features = ["runtime", "client", "derive"] diff --git a/conductor/src/azure/uami_builder.rs b/conductor/src/azure/uami_builder.rs index 6b1853d2e..d14e61de3 100644 --- a/conductor/src/azure/uami_builder.rs +++ b/conductor/src/azure/uami_builder.rs @@ -1,3 +1,4 @@ +use azure_core::auth::TokenCredential; use azure_identity::WorkloadIdentityCredential; use azure_identity::{AzureCliCredential, TokenCredentialOptions}; use azure_mgmt_authorization; @@ -6,23 +7,22 @@ use azure_mgmt_msi::models::{Identity, TrackedResource}; use std::sync::Arc; // Get credentials from workload identity -pub async fn get_credentials() -> Result> { +pub async fn get_credentials() -> Result, Box> { let options: TokenCredentialOptions = Default::default(); let credential = WorkloadIdentityCredential::create(options)?; - Ok(credential) + Ok(Arc::new(credential)) } // Create User Assigned Managed Identity pub async fn create_uami( resource_group: String, + subscription_id: String, instance_name: String, region: String, + credentials: Arc, ) -> Result> { - let credential = Arc::new(AzureCliCredential::new()); - let subscription_id = AzureCliCredential::get_subscription().await?; - let resource_group_name = resource_group; let uami_name = instance_name; - let msi_client = azure_mgmt_msi::Client::builder(credential.clone()).build()?; + let msi_client = azure_mgmt_msi::Client::builder(credentials).build()?; // Set parameters for User Assigned Managed Identity let uami_params = Identity { @@ -39,7 +39,7 @@ pub async fn create_uami( .user_assigned_identities_client() .create_or_update( subscription_id.clone(), - resource_group_name, + resource_group, uami_name, uami_params, ) @@ -51,9 +51,11 @@ pub async fn create_uami( pub async fn create_role_assignment( subscription_id: &str, uami_id: String, + credentials: Arc, ) -> Result> { - let credential = AzureCliCredential::new(); let role_assignment_name = "00000000-0000-0000-0000-000000000000".to_string(); + let role_assignment_client = azure_mgmt_authorization::Client::builder(credentials).build()?; + let scope = format!("/subscriptions/{subscription_id}"); // Set parameters for Role Assignment let role_assignment_params = azure_mgmt_authorization::models::RoleAssignmentCreateParameters { @@ -73,9 +75,6 @@ pub async fn create_role_assignment( }, }; - let role_assignment_client = azure_mgmt_authorization::Client::builder(credential).build()?; - let scope = format!("/subscriptions/{subscription_id}"); - // Create Role Assignment let role_assignment_created = role_assignment_client .role_assignments_client() From 37272f2ca64530b22e208cb7cac501943d82d020 Mon Sep 17 00:00:00 2001 From: Ian Stanton Date: Wed, 9 Oct 2024 12:02:40 -0400 Subject: [PATCH 10/44] Add create_federated_identity_credentials Signed-off-by: Ian Stanton --- conductor/src/azure/uami_builder.rs | 31 ++++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/conductor/src/azure/uami_builder.rs b/conductor/src/azure/uami_builder.rs index d14e61de3..b50c6bbe4 100644 --- a/conductor/src/azure/uami_builder.rs +++ b/conductor/src/azure/uami_builder.rs @@ -3,7 +3,7 @@ use azure_identity::WorkloadIdentityCredential; use azure_identity::{AzureCliCredential, TokenCredentialOptions}; use azure_mgmt_authorization; use azure_mgmt_authorization::models::{RoleAssignment, RoleAssignmentProperties}; -use azure_mgmt_msi::models::{Identity, TrackedResource}; +use azure_mgmt_msi::models::{FederatedIdentityCredential, Identity, TrackedResource}; use std::sync::Arc; // Get credentials from workload identity @@ -82,3 +82,32 @@ pub async fn create_role_assignment( .await?; Ok(role_assignment_created) } + +// Create Federated Identity Credentials for the UAMI +pub async fn create_federated_identity_credentials( + subscription_id: &str, + resource_group: String, + instance_name: String, + credentials: Arc, +) -> Result> { + let federated_identity_client = azure_mgmt_msi::Client::builder(credentials).build()?; + + // Set parameters for Federated Identity Credentials + let federated_identity_params = FederatedIdentityCredential { + proxy_resource: Default::default(), + properties: None, + }; + + // Create Federated Identity Credentials + let federated_identity_created = federated_identity_client + .federated_identity_credentials_client() + .create_or_update( + subscription_id, + resource_group, + &instance_name, + &instance_name, + federated_identity_params, + ) + .await?; + Ok(federated_identity_created) +} From 9c897c93b79350152624ea096044345f324ab641 Mon Sep 17 00:00:00 2001 From: Ian Stanton Date: Wed, 9 Oct 2024 12:37:22 -0400 Subject: [PATCH 11/44] Set federated identity properties Signed-off-by: Ian Stanton --- conductor/src/azure/uami_builder.rs | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/conductor/src/azure/uami_builder.rs b/conductor/src/azure/uami_builder.rs index b50c6bbe4..748bd74e8 100644 --- a/conductor/src/azure/uami_builder.rs +++ b/conductor/src/azure/uami_builder.rs @@ -3,7 +3,9 @@ use azure_identity::WorkloadIdentityCredential; use azure_identity::{AzureCliCredential, TokenCredentialOptions}; use azure_mgmt_authorization; use azure_mgmt_authorization::models::{RoleAssignment, RoleAssignmentProperties}; -use azure_mgmt_msi::models::{FederatedIdentityCredential, Identity, TrackedResource}; +use azure_mgmt_msi::models::{ + FederatedIdentityCredential, FederatedIdentityCredentialProperties, Identity, TrackedResource, +}; use std::sync::Arc; // Get credentials from workload identity @@ -89,13 +91,23 @@ pub async fn create_federated_identity_credentials( resource_group: String, instance_name: String, credentials: Arc, + region: String, ) -> Result> { let federated_identity_client = azure_mgmt_msi::Client::builder(credentials).build()?; + // TODO(ianstanton) + // Get cluster issuer with something similar to this az command: + // export AKS_OIDC_ISSUER="$(az aks show --name "${CLUSTER_NAME}" --resource-group "${RESOURCE_GROUP}" --query "oidcIssuerProfile.issuerUrl" --output tsv)" + let cluster_issuer = "https://.oic.prod-aks.azure.com///"; + // Set parameters for Federated Identity Credentials let federated_identity_params = FederatedIdentityCredential { proxy_resource: Default::default(), - properties: None, + properties: Some(FederatedIdentityCredentialProperties { + issuer: cluster_issuer.to_string(), + subject: format!("system:serviceaccount:{instance_name}:{instance_name}"), + audiences: vec!["api://AzureADTokenExchange".to_string()], + }), }; // Create Federated Identity Credentials From 13e88f30b0bb8c04980053e81392262cdab1018e Mon Sep 17 00:00:00 2001 From: Ian Stanton Date: Wed, 9 Oct 2024 13:00:36 -0400 Subject: [PATCH 12/44] Add todo Signed-off-by: Ian Stanton --- conductor/src/azure/uami_builder.rs | 10 ++++++++-- conductor/src/lib.rs | 3 +++ conductor/src/main.rs | 2 ++ 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/conductor/src/azure/uami_builder.rs b/conductor/src/azure/uami_builder.rs index 748bd74e8..80ed620c7 100644 --- a/conductor/src/azure/uami_builder.rs +++ b/conductor/src/azure/uami_builder.rs @@ -55,10 +55,14 @@ pub async fn create_role_assignment( uami_id: String, credentials: Arc, ) -> Result> { + // TODO(ianstanton) Determine what the role assignment name should be. This is a placeholder. let role_assignment_name = "00000000-0000-0000-0000-000000000000".to_string(); let role_assignment_client = azure_mgmt_authorization::Client::builder(credentials).build()?; let scope = format!("/subscriptions/{subscription_id}"); + // TODO(ianstanton) Set conditions for Role Assignment. These should allow for read / write + // to the instance's directory in the blob + // Set parameters for Role Assignment let role_assignment_params = azure_mgmt_authorization::models::RoleAssignmentCreateParameters { properties: RoleAssignmentProperties { @@ -96,8 +100,8 @@ pub async fn create_federated_identity_credentials( let federated_identity_client = azure_mgmt_msi::Client::builder(credentials).build()?; // TODO(ianstanton) - // Get cluster issuer with something similar to this az command: - // export AKS_OIDC_ISSUER="$(az aks show --name "${CLUSTER_NAME}" --resource-group "${RESOURCE_GROUP}" --query "oidcIssuerProfile.issuerUrl" --output tsv)" + // Get cluster issuer with something similar to this az command: + // export AKS_OIDC_ISSUER="$(az aks show --name "${CLUSTER_NAME}" --resource-group "${RESOURCE_GROUP}" --query "oidcIssuerProfile.issuerUrl" --output tsv)" let cluster_issuer = "https://.oic.prod-aks.azure.com///"; // Set parameters for Federated Identity Credentials @@ -123,3 +127,5 @@ pub async fn create_federated_identity_credentials( .await?; Ok(federated_identity_created) } + +// TODO(ianstanton) Delete UAMI diff --git a/conductor/src/lib.rs b/conductor/src/lib.rs index b48324e6b..b06078832 100644 --- a/conductor/src/lib.rs +++ b/conductor/src/lib.rs @@ -587,6 +587,9 @@ pub async fn delete_gcp_storage_workload_identity_binding( Ok(()) } +// TODO(ianstanton) Add function for creating Azure Workload Identity Binding +// TODO(ianstanton) Add function for deleting Azure Workload Identity Binding + #[cfg(test)] mod tests { const DECODER: base64::engine::GeneralPurpose = base64::engine::GeneralPurpose::new( diff --git a/conductor/src/main.rs b/conductor/src/main.rs index 0671e604f..41ce0712f 100644 --- a/conductor/src/main.rs +++ b/conductor/src/main.rs @@ -524,6 +524,8 @@ async fn run(metrics: CustomMetrics) -> Result<(), ConductorError> { .await?; } + // TODO(ianstanton) Delete Azure storage workload identity binding if Azure + let insert_query = sqlx::query!( "INSERT INTO deleted_instances (namespace) VALUES ($1) ON CONFLICT (namespace) DO NOTHING", namespace From 76ee7e165c476c3a8d49ca8b1cad06d79b38fa55 Mon Sep 17 00:00:00 2001 From: Ian Stanton Date: Wed, 9 Oct 2024 16:17:58 -0400 Subject: [PATCH 13/44] Delete UAMI Signed-off-by: Ian Stanton --- conductor/src/azure/uami_builder.rs | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/conductor/src/azure/uami_builder.rs b/conductor/src/azure/uami_builder.rs index 80ed620c7..7a111ec46 100644 --- a/conductor/src/azure/uami_builder.rs +++ b/conductor/src/azure/uami_builder.rs @@ -6,6 +6,7 @@ use azure_mgmt_authorization::models::{RoleAssignment, RoleAssignmentProperties} use azure_mgmt_msi::models::{ FederatedIdentityCredential, FederatedIdentityCredentialProperties, Identity, TrackedResource, }; +use azure_mgmt_msi::user_assigned_identities::delete::Response; use std::sync::Arc; // Get credentials from workload identity @@ -60,6 +61,8 @@ pub async fn create_role_assignment( let role_assignment_client = azure_mgmt_authorization::Client::builder(credentials).build()?; let scope = format!("/subscriptions/{subscription_id}"); + // TODO(ianstanton) Fetch role_definition_id + // TODO(ianstanton) Set conditions for Role Assignment. These should allow for read / write // to the instance's directory in the blob @@ -128,4 +131,18 @@ pub async fn create_federated_identity_credentials( Ok(federated_identity_created) } -// TODO(ianstanton) Delete UAMI +// Delete User Assigned Managed Identity +pub async fn delete_uami( + subscription_id: &str, + resource_group: String, + instance_name: String, + credentials: Arc, +) -> Result<(), Box> { + let msi_client = azure_mgmt_msi::Client::builder(credentials).build()?; + msi_client + .user_assigned_identities_client() + .delete(subscription_id, resource_group, instance_name) + .send() + .await?; + Ok(()) +} From 11b2ca57a564ba457af76a09f3f46ca15c0a223c Mon Sep 17 00:00:00 2001 From: Ian Stanton Date: Thu, 10 Oct 2024 12:16:43 -0400 Subject: [PATCH 14/44] Generate random UUID for Role Assignment Signed-off-by: Ian Stanton --- conductor/Cargo.lock | 1 + conductor/Cargo.toml | 1 + conductor/src/azure/uami_builder.rs | 2 +- 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/conductor/Cargo.lock b/conductor/Cargo.lock index 0971802e1..00acec8e7 100644 --- a/conductor/Cargo.lock +++ b/conductor/Cargo.lock @@ -1066,6 +1066,7 @@ dependencies = [ "sqlx", "thiserror", "tokio", + "uuid", ] [[package]] diff --git a/conductor/Cargo.toml b/conductor/Cargo.toml index 83ce2b3d7..c9329354f 100644 --- a/conductor/Cargo.toml +++ b/conductor/Cargo.toml @@ -40,6 +40,7 @@ azure_identity = "0.20.0" azure_mgmt_msi = "0.20.0" azure_mgmt_authorization = "0.20.0" azure_core = "0.20.0" +uuid = "1.10.0" [dependencies.kube] features = ["runtime", "client", "derive"] diff --git a/conductor/src/azure/uami_builder.rs b/conductor/src/azure/uami_builder.rs index 7a111ec46..fa653a3c7 100644 --- a/conductor/src/azure/uami_builder.rs +++ b/conductor/src/azure/uami_builder.rs @@ -57,7 +57,7 @@ pub async fn create_role_assignment( credentials: Arc, ) -> Result> { // TODO(ianstanton) Determine what the role assignment name should be. This is a placeholder. - let role_assignment_name = "00000000-0000-0000-0000-000000000000".to_string(); + let role_assignment_name = uuid::Uuid::new_v4().to_string(); let role_assignment_client = azure_mgmt_authorization::Client::builder(credentials).build()?; let scope = format!("/subscriptions/{subscription_id}"); From 56588b75ae14d9168fabfbd58965251b9a43ec67 Mon Sep 17 00:00:00 2001 From: Ian Stanton Date: Fri, 11 Oct 2024 11:36:40 -0400 Subject: [PATCH 15/44] Get role definition id Signed-off-by: Ian Stanton --- conductor/src/azure/uami_builder.rs | 43 +++++++++++++++++++++++++---- 1 file changed, 38 insertions(+), 5 deletions(-) diff --git a/conductor/src/azure/uami_builder.rs b/conductor/src/azure/uami_builder.rs index fa653a3c7..747b3ea9b 100644 --- a/conductor/src/azure/uami_builder.rs +++ b/conductor/src/azure/uami_builder.rs @@ -2,11 +2,13 @@ use azure_core::auth::TokenCredential; use azure_identity::WorkloadIdentityCredential; use azure_identity::{AzureCliCredential, TokenCredentialOptions}; use azure_mgmt_authorization; -use azure_mgmt_authorization::models::{RoleAssignment, RoleAssignmentProperties}; +use azure_mgmt_authorization::models::{RoleAssignment, RoleAssignmentProperties, RoleDefinition}; use azure_mgmt_msi::models::{ FederatedIdentityCredential, FederatedIdentityCredentialProperties, Identity, TrackedResource, }; use azure_mgmt_msi::user_assigned_identities::delete::Response; +use futures::StreamExt; +use schemars::_private::NoSerialize; use std::sync::Arc; // Get credentials from workload identity @@ -50,6 +52,29 @@ pub async fn create_uami( Ok(uami_created) } +// Get role definition ID +pub async fn get_role_definition_id( + subscription_id: &str, + role_name: &str, + credentials: Arc, +) -> Result> { + let role_definition_client = azure_mgmt_authorization::Client::builder(credentials).build()?; + // Get role definition for role name + let role_definition = role_definition_client + .role_definitions_client() + .list(subscription_id); + let mut role_definition_stream = role_definition.into_stream(); + while let Some(role_definition_page) = role_definition_stream.next().await { + let role_definition_page = role_definition_page?; + for item in role_definition_page.value { + if item.properties.unwrap().role_name == Some(role_name.to_string()) { + return Ok(item.id.unwrap()); + } + } + } + Err("Role definition not found".into()) +} + // Create Role Assignment for UAMI pub async fn create_role_assignment( subscription_id: &str, @@ -58,19 +83,27 @@ pub async fn create_role_assignment( ) -> Result> { // TODO(ianstanton) Determine what the role assignment name should be. This is a placeholder. let role_assignment_name = uuid::Uuid::new_v4().to_string(); - let role_assignment_client = azure_mgmt_authorization::Client::builder(credentials).build()?; + let role_assignment_client = + azure_mgmt_authorization::Client::builder(credentials.clone()).build()?; let scope = format!("/subscriptions/{subscription_id}"); - // TODO(ianstanton) Fetch role_definition_id + let role_definition = get_role_definition_id( + subscription_id, + "Storage Blob Data Contributor", + credentials, + ) + .await?; // TODO(ianstanton) Set conditions for Role Assignment. These should allow for read / write // to the instance's directory in the blob + // TODO(ianstanton) Fetch Storage Account ID + // Set parameters for Role Assignment let role_assignment_params = azure_mgmt_authorization::models::RoleAssignmentCreateParameters { properties: RoleAssignmentProperties { scope: None, - role_definition_id: "ba92f5b4-2d11-453d-a403-e96b0029c9fe".to_string(), + role_definition_id: role_definition, principal_id: uami_id, principal_type: None, description: None, @@ -87,7 +120,7 @@ pub async fn create_role_assignment( // Create Role Assignment let role_assignment_created = role_assignment_client .role_assignments_client() - .create(scope, role_assignment_name, role_assignment_params) + .create(scope, role_assignment_name, role_assignment_params) // Scope should be the Storage Account ID .await?; Ok(role_assignment_created) } From fd3b335259ea06238293e957a290596390a6106e Mon Sep 17 00:00:00 2001 From: Ian Stanton Date: Tue, 15 Oct 2024 05:58:42 -0400 Subject: [PATCH 16/44] Add Azure environment variables Signed-off-by: Ian Stanton --- conductor/src/main.rs | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/conductor/src/main.rs b/conductor/src/main.rs index 41ce0712f..e84373192 100644 --- a/conductor/src/main.rs +++ b/conductor/src/main.rs @@ -90,10 +90,22 @@ async fn run(metrics: CustomMetrics) -> Result<(), ConductorError> { .unwrap_or_else(|_| "false".to_owned()) .parse() .expect("error parsing IS_AZURE"); - let azure_storage_account: String = env::var("AZURE_STORAGE_ACCOUNT") + let azure_storage_account_name: String = env::var("AZURE_STORAGE_ACCOUNT_NAME") .unwrap_or_else(|_| "".to_owned()) .parse() - .expect("error parsing AZURE_STORAGE_ACCOUNT"); + .expect("error parsing AZURE_STORAGE_ACCOUNT_NAME"); + let azure_subscription_id: String = env::var("AZURE_SUBSCRIPTION_ID") + .unwrap_or_else(|_| "".to_owned()) + .parse() + .expect("error parsing AZURE_SUBSCRIPTION_ID"); + let azure_resource_group: String = env::var("AZURE_RESOURCE_GROUP") + .unwrap_or_else(|_| "".to_owned()) + .parse() + .expect("error parsing AZURE_RESOURCE_GROUP"); + let azure_region: String = env::var("AZURE_REGION") + .unwrap_or_else(|_| "".to_owned()) + .parse() + .expect("error parsing AZURE_REGION"); // Error and exit if CF_TEMPLATE_BUCKET is not set when IS_CLOUD_FORMATION is enabled if is_cloud_formation && cf_template_bucket.is_empty() { @@ -354,7 +366,7 @@ async fn run(metrics: CustomMetrics) -> Result<(), ConductorError> { &read_msg, &mut coredb_spec, backup_archive_bucket.clone(), - azure_storage_account.clone(), + azure_storage_account_name.clone(), ) .await?; From 5c50c292aa7c32b897cae7e6491141471ad70986 Mon Sep 17 00:00:00 2001 From: Ian Stanton Date: Tue, 15 Oct 2024 06:25:16 -0400 Subject: [PATCH 17/44] Set scope for get_role_definition_id Signed-off-by: Ian Stanton --- conductor/src/azure/uami_builder.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/conductor/src/azure/uami_builder.rs b/conductor/src/azure/uami_builder.rs index 747b3ea9b..6c5f55914 100644 --- a/conductor/src/azure/uami_builder.rs +++ b/conductor/src/azure/uami_builder.rs @@ -59,10 +59,9 @@ pub async fn get_role_definition_id( credentials: Arc, ) -> Result> { let role_definition_client = azure_mgmt_authorization::Client::builder(credentials).build()?; + let scope = format!("/subscriptions/{subscription_id}"); // Get role definition for role name - let role_definition = role_definition_client - .role_definitions_client() - .list(subscription_id); + let role_definition = role_definition_client.role_definitions_client().list(scope); let mut role_definition_stream = role_definition.into_stream(); while let Some(role_definition_page) = role_definition_stream.next().await { let role_definition_page = role_definition_page?; From d2109d02faecd6a657d5ca4e4d884175cd6cf167 Mon Sep 17 00:00:00 2001 From: Ian Stanton Date: Tue, 15 Oct 2024 07:46:55 -0400 Subject: [PATCH 18/44] Add get_storage_account_id Signed-off-by: Ian Stanton --- conductor/Cargo.lock | 17 +++++++++ conductor/Cargo.toml | 1 + conductor/src/azure/uami_builder.rs | 55 +++++++++++++++++++++++++---- 3 files changed, 67 insertions(+), 6 deletions(-) diff --git a/conductor/Cargo.lock b/conductor/Cargo.lock index 00acec8e7..fb046c2b2 100644 --- a/conductor/Cargo.lock +++ b/conductor/Cargo.lock @@ -830,6 +830,22 @@ dependencies = [ "time", ] +[[package]] +name = "azure_mgmt_storage" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5420e6a1e0434e075f2c8bdf98983ad5688563728ceca5a385af4ec0ded0367" +dependencies = [ + "azure_core", + "bytes", + "futures", + "log", + "once_cell", + "serde", + "serde_json", + "time", +] + [[package]] name = "backoff" version = "0.4.0" @@ -1045,6 +1061,7 @@ dependencies = [ "azure_identity", "azure_mgmt_authorization", "azure_mgmt_msi", + "azure_mgmt_storage", "base64 0.21.7", "chrono", "controller", diff --git a/conductor/Cargo.toml b/conductor/Cargo.toml index c9329354f..bf9b8a02f 100644 --- a/conductor/Cargo.toml +++ b/conductor/Cargo.toml @@ -39,6 +39,7 @@ google-cloud-storage = "0.22.1" azure_identity = "0.20.0" azure_mgmt_msi = "0.20.0" azure_mgmt_authorization = "0.20.0" +azure_mgmt_storage = "0.20.0" azure_core = "0.20.0" uuid = "1.10.0" diff --git a/conductor/src/azure/uami_builder.rs b/conductor/src/azure/uami_builder.rs index 6c5f55914..027214d54 100644 --- a/conductor/src/azure/uami_builder.rs +++ b/conductor/src/azure/uami_builder.rs @@ -74,29 +74,68 @@ pub async fn get_role_definition_id( Err("Role definition not found".into()) } +// Get storage account ID +pub async fn get_storage_account_id( + subscription_id: &str, + resource_group: &str, + storage_account_name: &str, + credentials: Arc, +) -> Result> { + let storage_client = azure_mgmt_storage::Client::builder(credentials).build()?; + let storage_account_list = storage_client + .storage_accounts_client() + .list_by_resource_group(resource_group, subscription_id); + let mut storage_account_stream = storage_account_list.into_stream(); + let mut storage_account = None; + while let Some(storage_account_page) = storage_account_stream.next().await { + let storage_account_page = storage_account_page?; + for item in storage_account_page.value { + if item.tracked_resource.resource.name == Some(storage_account_name.to_string()) { + storage_account = Some(item); + break; + } + } + if storage_account.is_some() { + break; + } + } + Ok(storage_account + .unwrap() + .tracked_resource + .resource + .id + .unwrap()) +} + // Create Role Assignment for UAMI pub async fn create_role_assignment( subscription_id: &str, + resource_group: &str, + storage_account_name: &str, uami_id: String, credentials: Arc, ) -> Result> { - // TODO(ianstanton) Determine what the role assignment name should be. This is a placeholder. let role_assignment_name = uuid::Uuid::new_v4().to_string(); let role_assignment_client = azure_mgmt_authorization::Client::builder(credentials.clone()).build()?; - let scope = format!("/subscriptions/{subscription_id}"); let role_definition = get_role_definition_id( subscription_id, "Storage Blob Data Contributor", - credentials, + credentials.clone(), ) .await?; // TODO(ianstanton) Set conditions for Role Assignment. These should allow for read / write // to the instance's directory in the blob - // TODO(ianstanton) Fetch Storage Account ID + let storage_account_id = get_storage_account_id( + subscription_id, + resource_group, + storage_account_name, + credentials, + ) + .await?; // Set parameters for Role Assignment let role_assignment_params = azure_mgmt_authorization::models::RoleAssignmentCreateParameters { @@ -116,10 +155,14 @@ pub async fn create_role_assignment( }, }; - // Create Role Assignment + // Create Role Assignment. Scope should be storage account ID let role_assignment_created = role_assignment_client .role_assignments_client() - .create(scope, role_assignment_name, role_assignment_params) // Scope should be the Storage Account ID + .create( + storage_account_id, + role_assignment_name, + role_assignment_params, + ) .await?; Ok(role_assignment_created) } From 569b4db0ce894329d7ad4cd16f74ea2c9ace2cc2 Mon Sep 17 00:00:00 2001 From: Ian Stanton Date: Tue, 15 Oct 2024 15:57:03 -0400 Subject: [PATCH 19/44] Return AzureError in relevant functions Signed-off-by: Ian Stanton --- conductor/src/azure/uami_builder.rs | 21 +++++++++++++-------- conductor/src/errors.rs | 4 ++++ 2 files changed, 17 insertions(+), 8 deletions(-) diff --git a/conductor/src/azure/uami_builder.rs b/conductor/src/azure/uami_builder.rs index 027214d54..8a0f74c04 100644 --- a/conductor/src/azure/uami_builder.rs +++ b/conductor/src/azure/uami_builder.rs @@ -1,4 +1,5 @@ use azure_core::auth::TokenCredential; +use azure_core::error::Error as AzureError; use azure_identity::WorkloadIdentityCredential; use azure_identity::{AzureCliCredential, TokenCredentialOptions}; use azure_mgmt_authorization; @@ -12,7 +13,7 @@ use schemars::_private::NoSerialize; use std::sync::Arc; // Get credentials from workload identity -pub async fn get_credentials() -> Result, Box> { +pub async fn get_credentials() -> Result, AzureError> { let options: TokenCredentialOptions = Default::default(); let credential = WorkloadIdentityCredential::create(options)?; Ok(Arc::new(credential)) @@ -25,7 +26,7 @@ pub async fn create_uami( instance_name: String, region: String, credentials: Arc, -) -> Result> { +) -> Result { let uami_name = instance_name; let msi_client = azure_mgmt_msi::Client::builder(credentials).build()?; @@ -57,7 +58,7 @@ pub async fn get_role_definition_id( subscription_id: &str, role_name: &str, credentials: Arc, -) -> Result> { +) -> Result { let role_definition_client = azure_mgmt_authorization::Client::builder(credentials).build()?; let scope = format!("/subscriptions/{subscription_id}"); // Get role definition for role name @@ -71,7 +72,11 @@ pub async fn get_role_definition_id( } } } - Err("Role definition not found".into()) + // Return error if not found + Err(AzureError::new( + azure_core::error::ErrorKind::Other, + format!("Role definition {} not found", role_name), + )) } // Get storage account ID @@ -80,7 +85,7 @@ pub async fn get_storage_account_id( resource_group: &str, storage_account_name: &str, credentials: Arc, -) -> Result> { +) -> Result { let storage_client = azure_mgmt_storage::Client::builder(credentials).build()?; let storage_account_list = storage_client .storage_accounts_client() @@ -114,7 +119,7 @@ pub async fn create_role_assignment( storage_account_name: &str, uami_id: String, credentials: Arc, -) -> Result> { +) -> Result { let role_assignment_name = uuid::Uuid::new_v4().to_string(); let role_assignment_client = azure_mgmt_authorization::Client::builder(credentials.clone()).build()?; @@ -174,7 +179,7 @@ pub async fn create_federated_identity_credentials( instance_name: String, credentials: Arc, region: String, -) -> Result> { +) -> Result { let federated_identity_client = azure_mgmt_msi::Client::builder(credentials).build()?; // TODO(ianstanton) @@ -212,7 +217,7 @@ pub async fn delete_uami( resource_group: String, instance_name: String, credentials: Arc, -) -> Result<(), Box> { +) -> Result<(), AzureError> { let msi_client = azure_mgmt_msi::Client::builder(credentials).build()?; msi_client .user_assigned_identities_client() diff --git a/conductor/src/errors.rs b/conductor/src/errors.rs index 9c5513c91..751067dcc 100644 --- a/conductor/src/errors.rs +++ b/conductor/src/errors.rs @@ -1,4 +1,5 @@ use aws_sdk_cloudformation::Error as CFError; +use azure_core::Error as AzureError; use google_cloud_storage::http::Error as GcsError; use kube; use pgmq::errors::PgmqError; @@ -57,4 +58,7 @@ pub enum ConductorError { /// Dataplane error #[error("Dataplane not found error: {0}")] DataplaneError(String), + + #[error("Error with Azure SDK {0}")] + AzureError(#[from] AzureError), } From a4b910761f2597d11a57ac01d1eb5d28632b830c Mon Sep 17 00:00:00 2001 From: Ian Stanton Date: Wed, 16 Oct 2024 07:03:23 -0400 Subject: [PATCH 20/44] Add get_cluster_issuer Signed-off-by: Ian Stanton --- conductor/Cargo.lock | 37 ++++++++++++++++++------- conductor/Cargo.toml | 11 ++++---- conductor/src/azure/uami_builder.rs | 43 +++++++++++++++++++++-------- 3 files changed, 65 insertions(+), 26 deletions(-) diff --git a/conductor/Cargo.lock b/conductor/Cargo.lock index fb046c2b2..7ead112fb 100644 --- a/conductor/Cargo.lock +++ b/conductor/Cargo.lock @@ -752,9 +752,9 @@ dependencies = [ [[package]] name = "azure_core" -version = "0.20.0" +version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34ce3de4b65b1ee2667c81d1fc692949049502a4cf9c38118d811d6d79a7eaef" +checksum = "7b552ad43a45a746461ec3d3a51dfb6466b4759209414b439c165eb6a6b7729e" dependencies = [ "async-trait", "base64 0.22.1", @@ -779,9 +779,9 @@ dependencies = [ [[package]] name = "azure_identity" -version = "0.20.0" +version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72c97790480791ec1ee9b76f5c6499b1d0aac0d4cd1e62010bfc19bb545544c5" +checksum = "88ddd80344317c40c04b603807b63a5cefa532f1b43522e72f480a988141f744" dependencies = [ "async-lock", "async-process", @@ -800,9 +800,25 @@ dependencies = [ [[package]] name = "azure_mgmt_authorization" -version = "0.20.0" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbb60abdc00edf3545c0a235fbd3aa26a8dc870676361c4064114de0c4c607b8" +dependencies = [ + "azure_core", + "bytes", + "futures", + "log", + "once_cell", + "serde", + "serde_json", + "time", +] + +[[package]] +name = "azure_mgmt_hybridkubernetes" +version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0996028421d746deea5e44692c84dd79d6d1bd77b40f215cddb5b21a49b3d401" +checksum = "9f340403dae2cd7aaf60dba87b2404b9bf13561fbadece912018b2f262d60d3f" dependencies = [ "azure_core", "bytes", @@ -816,9 +832,9 @@ dependencies = [ [[package]] name = "azure_mgmt_msi" -version = "0.20.0" +version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e707e87a141765b64c8f473c926078ea6ea56a3feb98f1541d88fa8a886300e6" +checksum = "c85adbe25b00edbbdd4034bc917f73d3eb647d5f6872185f6e1dcdf13950c91e" dependencies = [ "azure_core", "bytes", @@ -832,9 +848,9 @@ dependencies = [ [[package]] name = "azure_mgmt_storage" -version = "0.20.0" +version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5420e6a1e0434e075f2c8bdf98983ad5688563728ceca5a385af4ec0ded0367" +checksum = "e9943c26303482bb57afc0ab092d0638e7813ae30a0bb055dca52bf4952d6e4d" dependencies = [ "azure_core", "bytes", @@ -1060,6 +1076,7 @@ dependencies = [ "azure_core", "azure_identity", "azure_mgmt_authorization", + "azure_mgmt_hybridkubernetes", "azure_mgmt_msi", "azure_mgmt_storage", "base64 0.21.7", diff --git a/conductor/Cargo.toml b/conductor/Cargo.toml index bf9b8a02f..9bbd5ec31 100644 --- a/conductor/Cargo.toml +++ b/conductor/Cargo.toml @@ -36,11 +36,12 @@ anyhow = "1.0.82" serde_yaml = "0.9.34" reqwest = { version = "0.12.3", features = ["json"] } google-cloud-storage = "0.22.1" -azure_identity = "0.20.0" -azure_mgmt_msi = "0.20.0" -azure_mgmt_authorization = "0.20.0" -azure_mgmt_storage = "0.20.0" -azure_core = "0.20.0" +azure_identity = "0.21.0" +azure_mgmt_msi = "0.21.0" +azure_mgmt_authorization = "0.21.0" +azure_mgmt_hybridkubernetes = "0.21.0" +azure_mgmt_storage = "0.21.0" +azure_core = "0.21.0" uuid = "1.10.0" [dependencies.kube] diff --git a/conductor/src/azure/uami_builder.rs b/conductor/src/azure/uami_builder.rs index 8a0f74c04..3ea3e7b9d 100644 --- a/conductor/src/azure/uami_builder.rs +++ b/conductor/src/azure/uami_builder.rs @@ -1,15 +1,13 @@ use azure_core::auth::TokenCredential; use azure_core::error::Error as AzureError; +use azure_identity::TokenCredentialOptions; use azure_identity::WorkloadIdentityCredential; -use azure_identity::{AzureCliCredential, TokenCredentialOptions}; use azure_mgmt_authorization; -use azure_mgmt_authorization::models::{RoleAssignment, RoleAssignmentProperties, RoleDefinition}; +use azure_mgmt_authorization::models::{RoleAssignment, RoleAssignmentProperties}; use azure_mgmt_msi::models::{ FederatedIdentityCredential, FederatedIdentityCredentialProperties, Identity, TrackedResource, }; -use azure_mgmt_msi::user_assigned_identities::delete::Response; use futures::StreamExt; -use schemars::_private::NoSerialize; use std::sync::Arc; // Get credentials from workload identity @@ -172,6 +170,27 @@ pub async fn create_role_assignment( Ok(role_assignment_created) } +// Get OIDC Issuer URL from AKS cluster +pub async fn get_cluster_issuer( + subscription_id: &str, + resource_group: &str, + instance_name: &str, + credentials: Arc, +) -> Result { + let hybrid_kubernetes_client = + azure_mgmt_hybridkubernetes::Client::builder(credentials).build()?; + let cluster = hybrid_kubernetes_client + .connected_cluster_client() + .get(subscription_id, resource_group, instance_name) + .await?; + Ok(cluster + .properties + .oidc_issuer_profile + .unwrap() + .issuer_url + .unwrap()) +} + // Create Federated Identity Credentials for the UAMI pub async fn create_federated_identity_credentials( subscription_id: &str, @@ -180,18 +199,20 @@ pub async fn create_federated_identity_credentials( credentials: Arc, region: String, ) -> Result { - let federated_identity_client = azure_mgmt_msi::Client::builder(credentials).build()?; - - // TODO(ianstanton) - // Get cluster issuer with something similar to this az command: - // export AKS_OIDC_ISSUER="$(az aks show --name "${CLUSTER_NAME}" --resource-group "${RESOURCE_GROUP}" --query "oidcIssuerProfile.issuerUrl" --output tsv)" - let cluster_issuer = "https://.oic.prod-aks.azure.com///"; + let federated_identity_client = azure_mgmt_msi::Client::builder(credentials.clone()).build()?; + let cluster_issuer = get_cluster_issuer( + subscription_id, + &resource_group, + &instance_name, + credentials, + ) + .await?; // Set parameters for Federated Identity Credentials let federated_identity_params = FederatedIdentityCredential { proxy_resource: Default::default(), properties: Some(FederatedIdentityCredentialProperties { - issuer: cluster_issuer.to_string(), + issuer: cluster_issuer, subject: format!("system:serviceaccount:{instance_name}:{instance_name}"), audiences: vec!["api://AzureADTokenExchange".to_string()], }), From 1402002294691568b07d0d53110174990a871fcd Mon Sep 17 00:00:00 2001 From: Ian Stanton Date: Wed, 16 Oct 2024 14:42:12 -0400 Subject: [PATCH 21/44] Use REST API in get_cluster_issuer Signed-off-by: Ian Stanton --- conductor/src/azure/azure_error.rs | 12 +++++++ conductor/src/azure/mod.rs | 1 + conductor/src/azure/uami_builder.rs | 54 +++++++++++++++++++---------- conductor/src/errors.rs | 3 +- 4 files changed, 51 insertions(+), 19 deletions(-) create mode 100644 conductor/src/azure/azure_error.rs diff --git a/conductor/src/azure/azure_error.rs b/conductor/src/azure/azure_error.rs new file mode 100644 index 000000000..b5b96b513 --- /dev/null +++ b/conductor/src/azure/azure_error.rs @@ -0,0 +1,12 @@ +use azure_core::Error as AzureSDKError; +use reqwest::Error as ReqwestError; +use thiserror::Error; + +#[derive(Error, Debug)] +pub enum AzureError { + #[error("Error with Azure SDK {0}")] + AzureSDKError(#[from] AzureSDKError), + + #[error("Error with Azure REST API {0}")] + AzureRestAPIError(#[from] ReqwestError), +} diff --git a/conductor/src/azure/mod.rs b/conductor/src/azure/mod.rs index 2c685ff41..db350f0ea 100644 --- a/conductor/src/azure/mod.rs +++ b/conductor/src/azure/mod.rs @@ -1 +1,2 @@ +pub mod azure_error; pub mod uami_builder; diff --git a/conductor/src/azure/uami_builder.rs b/conductor/src/azure/uami_builder.rs index 3ea3e7b9d..9003f0294 100644 --- a/conductor/src/azure/uami_builder.rs +++ b/conductor/src/azure/uami_builder.rs @@ -1,5 +1,7 @@ +use crate::azure::azure_error; use azure_core::auth::TokenCredential; -use azure_core::error::Error as AzureError; +use azure_core::error::Error as AzureSDKError; +use azure_error::AzureError; use azure_identity::TokenCredentialOptions; use azure_identity::WorkloadIdentityCredential; use azure_mgmt_authorization; @@ -71,10 +73,10 @@ pub async fn get_role_definition_id( } } // Return error if not found - Err(AzureError::new( + Err(AzureError::from(AzureSDKError::new( azure_core::error::ErrorKind::Other, format!("Role definition {} not found", role_name), - )) + ))) } // Get storage account ID @@ -170,25 +172,42 @@ pub async fn create_role_assignment( Ok(role_assignment_created) } -// Get OIDC Issuer URL from AKS cluster +// Get OIDC Issuer URL from AKS cluster using rest API. This is necessary because the azure_mgmt_containerservice +// crate is no longer being built: https://github.com/Azure/azure-sdk-for-rust/pull/1243 pub async fn get_cluster_issuer( subscription_id: &str, resource_group: &str, - instance_name: &str, + cluster_name: &str, credentials: Arc, ) -> Result { - let hybrid_kubernetes_client = - azure_mgmt_hybridkubernetes::Client::builder(credentials).build()?; - let cluster = hybrid_kubernetes_client - .connected_cluster_client() - .get(subscription_id, resource_group, instance_name) + // Use REST API to get OIDC Issuer URL from AKS cluster + let client = reqwest::Client::new(); + let url = format!( + "https://management.azure.com/subscriptions/{subscription_id}/resourceGroups/{resource_group}/providers/Microsoft.ContainerService/managedClusters/{cluster_name}?api-version=2024-08-01", + subscription_id = subscription_id, + resource_group = resource_group, + cluster_name = cluster_name + ); + + let scopes: &[&str] = &["https://management.azure.com/.default"]; + + let response = client + .get(&url) + .header( + "Authorization", + format!( + "Bearer {}", + credentials.get_token(scopes).await?.token.secret() + ), + ) + .send() .await?; - Ok(cluster - .properties - .oidc_issuer_profile - .unwrap() - .issuer_url - .unwrap()) + + let response_json = response.json::().await?; + let issuer_url = response_json["properties"]["oidcIssuerProfile"]["issuerURL"] + .as_str() + .unwrap(); + Ok(issuer_url.to_string()) } // Create Federated Identity Credentials for the UAMI @@ -197,14 +216,13 @@ pub async fn create_federated_identity_credentials( resource_group: String, instance_name: String, credentials: Arc, - region: String, ) -> Result { let federated_identity_client = azure_mgmt_msi::Client::builder(credentials.clone()).build()?; let cluster_issuer = get_cluster_issuer( subscription_id, &resource_group, &instance_name, - credentials, + credentials.clone(), ) .await?; diff --git a/conductor/src/errors.rs b/conductor/src/errors.rs index 751067dcc..23fb7a9d3 100644 --- a/conductor/src/errors.rs +++ b/conductor/src/errors.rs @@ -1,5 +1,6 @@ +use crate::azure; use aws_sdk_cloudformation::Error as CFError; -use azure_core::Error as AzureError; +use azure::azure_error::AzureError; use google_cloud_storage::http::Error as GcsError; use kube; use pgmq::errors::PgmqError; From 6968dafac33742116e54f3684512e544c0289d1a Mon Sep 17 00:00:00 2001 From: Ian Stanton Date: Wed, 16 Oct 2024 14:44:08 -0400 Subject: [PATCH 22/44] Drop azure_mgmt_hybridkubernetes Signed-off-by: Ian Stanton --- conductor/Cargo.lock | 17 ----------------- conductor/Cargo.toml | 1 - 2 files changed, 18 deletions(-) diff --git a/conductor/Cargo.lock b/conductor/Cargo.lock index 7ead112fb..94a126229 100644 --- a/conductor/Cargo.lock +++ b/conductor/Cargo.lock @@ -814,22 +814,6 @@ dependencies = [ "time", ] -[[package]] -name = "azure_mgmt_hybridkubernetes" -version = "0.21.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f340403dae2cd7aaf60dba87b2404b9bf13561fbadece912018b2f262d60d3f" -dependencies = [ - "azure_core", - "bytes", - "futures", - "log", - "once_cell", - "serde", - "serde_json", - "time", -] - [[package]] name = "azure_mgmt_msi" version = "0.21.0" @@ -1076,7 +1060,6 @@ dependencies = [ "azure_core", "azure_identity", "azure_mgmt_authorization", - "azure_mgmt_hybridkubernetes", "azure_mgmt_msi", "azure_mgmt_storage", "base64 0.21.7", diff --git a/conductor/Cargo.toml b/conductor/Cargo.toml index 9bbd5ec31..2579bb345 100644 --- a/conductor/Cargo.toml +++ b/conductor/Cargo.toml @@ -39,7 +39,6 @@ google-cloud-storage = "0.22.1" azure_identity = "0.21.0" azure_mgmt_msi = "0.21.0" azure_mgmt_authorization = "0.21.0" -azure_mgmt_hybridkubernetes = "0.21.0" azure_mgmt_storage = "0.21.0" azure_core = "0.21.0" uuid = "1.10.0" From 89815e079d0c7322cf7523cb2b9852d31b1b172a Mon Sep 17 00:00:00 2001 From: Ian Stanton Date: Wed, 16 Oct 2024 16:31:10 -0400 Subject: [PATCH 23/44] Call from main Signed-off-by: Ian Stanton --- conductor/src/azure/uami_builder.rs | 33 ++++++------- conductor/src/lib.rs | 72 ++++++++++++++++++++++++++++- conductor/src/main.rs | 34 ++++++++++++-- 3 files changed, 115 insertions(+), 24 deletions(-) diff --git a/conductor/src/azure/uami_builder.rs b/conductor/src/azure/uami_builder.rs index 9003f0294..478c94f48 100644 --- a/conductor/src/azure/uami_builder.rs +++ b/conductor/src/azure/uami_builder.rs @@ -21,13 +21,12 @@ pub async fn get_credentials() -> Result, AzureError> { // Create User Assigned Managed Identity pub async fn create_uami( - resource_group: String, - subscription_id: String, - instance_name: String, - region: String, + resource_group: &str, + subscription_id: &str, + uami_name: &str, + region: &str, credentials: Arc, ) -> Result { - let uami_name = instance_name; let msi_client = azure_mgmt_msi::Client::builder(credentials).build()?; // Set parameters for User Assigned Managed Identity @@ -35,7 +34,7 @@ pub async fn create_uami( tracked_resource: TrackedResource { resource: Default::default(), tags: None, - location: region, + location: region.to_string(), }, properties: None, }; @@ -43,12 +42,7 @@ pub async fn create_uami( // Create User Assigned Managed Identity let uami_created = msi_client .user_assigned_identities_client() - .create_or_update( - subscription_id.clone(), - resource_group, - uami_name, - uami_params, - ) + .create_or_update(subscription_id, resource_group, uami_name, uami_params) .await?; Ok(uami_created) } @@ -213,10 +207,11 @@ pub async fn get_cluster_issuer( // Create Federated Identity Credentials for the UAMI pub async fn create_federated_identity_credentials( subscription_id: &str, - resource_group: String, - instance_name: String, + resource_group: &str, + instance_name: &str, credentials: Arc, ) -> Result { + let uami_name = instance_name; let federated_identity_client = azure_mgmt_msi::Client::builder(credentials.clone()).build()?; let cluster_issuer = get_cluster_issuer( subscription_id, @@ -242,8 +237,8 @@ pub async fn create_federated_identity_credentials( .create_or_update( subscription_id, resource_group, - &instance_name, - &instance_name, + uami_name, + instance_name, federated_identity_params, ) .await?; @@ -253,14 +248,14 @@ pub async fn create_federated_identity_credentials( // Delete User Assigned Managed Identity pub async fn delete_uami( subscription_id: &str, - resource_group: String, - instance_name: String, + resource_group: &str, + uami_name: &str, credentials: Arc, ) -> Result<(), AzureError> { let msi_client = azure_mgmt_msi::Client::builder(credentials).build()?; msi_client .user_assigned_identities_client() - .delete(subscription_id, resource_group, instance_name) + .delete(subscription_id, resource_group, uami_name) .send() .await?; Ok(()) diff --git a/conductor/src/lib.rs b/conductor/src/lib.rs index b06078832..93e9827cb 100644 --- a/conductor/src/lib.rs +++ b/conductor/src/lib.rs @@ -21,6 +21,11 @@ use k8s_openapi::api::core::v1::{Namespace, Secret}; use kube::api::{DeleteParams, ListParams, Patch, PatchParams}; +use crate::azure::uami_builder::{ + create_federated_identity_credentials, create_role_assignment, create_uami, delete_uami, + get_credentials, +}; + use chrono::{DateTime, SecondsFormat, Utc}; use kube::{Api, Client, ResourceExt}; use log::{debug, info, warn}; @@ -588,7 +593,72 @@ pub async fn delete_gcp_storage_workload_identity_binding( } // TODO(ianstanton) Add function for creating Azure Workload Identity Binding -// TODO(ianstanton) Add function for deleting Azure Workload Identity Binding +pub async fn create_azure_storage_workload_identity_binding( + azure_subscription_id: &str, + azure_resource_group: &str, + azure_region: &str, + backup_archive_bucket: &str, + storage_archive_bucket: &str, + namespace: &str, +) -> Result<(), ConductorError> { + let credentials = get_credentials().await?; + + // Create UAMI + let uami_name = namespace; + let uami = create_uami( + azure_resource_group, + azure_subscription_id, + azure_region, + uami_name, + credentials.clone(), + ) + .await?; + info!("Created UAMI: {:?}", uami); + + // Create Role Assignment for UAMI + let uami_id = uami.properties.unwrap().principal_id.unwrap(); + let role_assignment = create_role_assignment( + azure_subscription_id, + azure_resource_group, + storage_archive_bucket, + uami_id, + credentials.clone(), + ) + .await?; + info!("Created Role Assignment: {:?}", role_assignment); + + // Create Federated Credential for the UAMI + let federated_credential = create_federated_identity_credentials( + azure_subscription_id, + azure_resource_group, + namespace, + credentials.clone(), + ) + .await?; + info!("Created Federated Credential: {:?}", federated_credential); + + Ok(()) +} + +pub async fn delete_azure_storage_workload_identity_binding( + azure_subscription_id: &str, + azure_resource_group: &str, + namespace: &str, +) -> Result<(), ConductorError> { + let credentials = get_credentials().await?; + + // Delete UAMI + delete_uami( + azure_subscription_id, + azure_resource_group, + namespace, + credentials.clone(), + ) + .await?; + info!("Deleted UAMI"); + + Ok(()) +} #[cfg(test)] mod tests { diff --git a/conductor/src/main.rs b/conductor/src/main.rs index e84373192..c5898d611 100644 --- a/conductor/src/main.rs +++ b/conductor/src/main.rs @@ -3,8 +3,9 @@ use actix_web_opentelemetry::{PrometheusMetricsHandler, RequestTracing}; use conductor::errors::ConductorError; use conductor::monitoring::CustomMetrics; use conductor::{ - cloud::CloudProvider, create_cloudformation, create_gcp_storage_workload_identity_binding, - create_namespace, create_or_update, delete, delete_cloudformation, + cloud::CloudProvider, create_azure_storage_workload_identity_binding, create_cloudformation, + create_gcp_storage_workload_identity_binding, create_namespace, create_or_update, delete, + delete_azure_storage_workload_identity_binding, delete_cloudformation, delete_gcp_storage_workload_identity_binding, delete_namespace, generate_cron_expression, generate_spec, get_coredb_error_without_status, get_one, get_pg_conn, lookup_role_arn, restart_coredb, types, @@ -367,6 +368,9 @@ async fn run(metrics: CustomMetrics) -> Result<(), ConductorError> { &mut coredb_spec, backup_archive_bucket.clone(), azure_storage_account_name.clone(), + azure_subscription_id.clone(), + azure_resource_group.clone(), + azure_region.clone(), ) .await?; @@ -536,7 +540,18 @@ async fn run(metrics: CustomMetrics) -> Result<(), ConductorError> { .await?; } - // TODO(ianstanton) Delete Azure storage workload identity binding if Azure + if is_azure { + info!( + "{}: Deleting Azure storage workload identity binding", + read_msg.msg_id + ); + delete_azure_storage_workload_identity_binding( + &azure_subscription_id, + &azure_resource_group, + &namespace, + ) + .await?; + } let insert_query = sqlx::query!( "INSERT INTO deleted_instances (namespace) VALUES ($1) ON CONFLICT (namespace) DO NOTHING", @@ -958,12 +973,23 @@ async fn init_azure_storage_workload_identity( coredb_spec: &mut CoreDBSpec, backup_archive_bucket: String, azure_storage_account: String, + azure_subscription_id: String, + azure_resource_group: String, + azure_region: String, ) -> Result<(), ConductorError> { if !is_azure { return Ok(()); } - //TODO(ianstanton) Implement Azure Workload Identity + create_azure_storage_workload_identity_binding( + &azure_subscription_id, + &azure_resource_group, + &azure_region, + &azure_storage_account, + &read_msg.message.namespace, + &backup_archive_bucket, + ) + .await?; // Generate Backup spec for CoreDB let volume_snapshot = Some(VolumeSnapshot { From 778b0c7f2623f7967f853a459ca2f92837b46a67 Mon Sep 17 00:00:00 2001 From: Ian Stanton Date: Wed, 16 Oct 2024 20:15:19 -0400 Subject: [PATCH 24/44] Add echo Signed-off-by: Ian Stanton --- .github/actions/build-and-push-to-quay/action.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/actions/build-and-push-to-quay/action.yml b/.github/actions/build-and-push-to-quay/action.yml index a860e2442..8da5aa09e 100644 --- a/.github/actions/build-and-push-to-quay/action.yml +++ b/.github/actions/build-and-push-to-quay/action.yml @@ -58,6 +58,7 @@ runs: set -xe sudo apt-get update sudo apt-get install -y wget + eho "Downloading stoml" wget https://github.com/freshautomations/stoml/releases/download/v0.7.1/stoml_linux_amd64 mv stoml_linux_amd64 stoml chmod +x stoml From 187e56d1de4963fbdc86105d0a24cf0bec231473 Mon Sep 17 00:00:00 2001 From: Ian Stanton Date: Thu, 17 Oct 2024 15:44:46 -0400 Subject: [PATCH 25/44] Cleanup Signed-off-by: Ian Stanton --- .github/actions/build-and-push-to-quay/action.yml | 1 - conductor/src/azure/uami_builder.rs | 10 ++++------ conductor/src/lib.rs | 6 ++---- conductor/src/main.rs | 4 ++-- 4 files changed, 8 insertions(+), 13 deletions(-) diff --git a/.github/actions/build-and-push-to-quay/action.yml b/.github/actions/build-and-push-to-quay/action.yml index 8da5aa09e..a860e2442 100644 --- a/.github/actions/build-and-push-to-quay/action.yml +++ b/.github/actions/build-and-push-to-quay/action.yml @@ -58,7 +58,6 @@ runs: set -xe sudo apt-get update sudo apt-get install -y wget - eho "Downloading stoml" wget https://github.com/freshautomations/stoml/releases/download/v0.7.1/stoml_linux_amd64 mv stoml_linux_amd64 stoml chmod +x stoml diff --git a/conductor/src/azure/uami_builder.rs b/conductor/src/azure/uami_builder.rs index 478c94f48..431c6d083 100644 --- a/conductor/src/azure/uami_builder.rs +++ b/conductor/src/azure/uami_builder.rs @@ -111,7 +111,7 @@ pub async fn create_role_assignment( subscription_id: &str, resource_group: &str, storage_account_name: &str, - uami_id: String, + uami_id: &str, credentials: Arc, ) -> Result { let role_assignment_name = uuid::Uuid::new_v4().to_string(); @@ -141,7 +141,7 @@ pub async fn create_role_assignment( properties: RoleAssignmentProperties { scope: None, role_definition_id: role_definition, - principal_id: uami_id, + principal_id: uami_id.to_string(), principal_type: None, description: None, condition: None, @@ -174,7 +174,6 @@ pub async fn get_cluster_issuer( cluster_name: &str, credentials: Arc, ) -> Result { - // Use REST API to get OIDC Issuer URL from AKS cluster let client = reqwest::Client::new(); let url = format!( "https://management.azure.com/subscriptions/{subscription_id}/resourceGroups/{resource_group}/providers/Microsoft.ContainerService/managedClusters/{cluster_name}?api-version=2024-08-01", @@ -182,7 +181,6 @@ pub async fn get_cluster_issuer( resource_group = resource_group, cluster_name = cluster_name ); - let scopes: &[&str] = &["https://management.azure.com/.default"]; let response = client @@ -215,8 +213,8 @@ pub async fn create_federated_identity_credentials( let federated_identity_client = azure_mgmt_msi::Client::builder(credentials.clone()).build()?; let cluster_issuer = get_cluster_issuer( subscription_id, - &resource_group, - &instance_name, + resource_group, + instance_name, credentials.clone(), ) .await?; diff --git a/conductor/src/lib.rs b/conductor/src/lib.rs index 93e9827cb..f476c1531 100644 --- a/conductor/src/lib.rs +++ b/conductor/src/lib.rs @@ -592,13 +592,11 @@ pub async fn delete_gcp_storage_workload_identity_binding( Ok(()) } -// TODO(ianstanton) Add function for creating Azure Workload Identity Binding pub async fn create_azure_storage_workload_identity_binding( azure_subscription_id: &str, azure_resource_group: &str, azure_region: &str, backup_archive_bucket: &str, - storage_archive_bucket: &str, namespace: &str, ) -> Result<(), ConductorError> { let credentials = get_credentials().await?; @@ -620,8 +618,8 @@ pub async fn create_azure_storage_workload_identity_binding( let role_assignment = create_role_assignment( azure_subscription_id, azure_resource_group, - storage_archive_bucket, - uami_id, + backup_archive_bucket, + &uami_id, credentials.clone(), ) .await?; diff --git a/conductor/src/main.rs b/conductor/src/main.rs index ec161f505..ccc98f512 100644 --- a/conductor/src/main.rs +++ b/conductor/src/main.rs @@ -15,8 +15,8 @@ use crate::metrics_reporter::run_metrics_reporter; use crate::status_reporter::run_status_reporter; use conductor::routes::health::background_threads_running; use controller::apis::coredb_types::{ - AzureCredentials, AzureCredentialsStorageAccount, AzureCredentialsStorageKey, Backup, - CoreDBSpec, GoogleCredentials, S3Credentials, ServiceAccountTemplate, VolumeSnapshot, + AzureCredentials, Backup, CoreDBSpec, GoogleCredentials, S3Credentials, ServiceAccountTemplate, + VolumeSnapshot, }; use controller::apis::postgres_parameters::{ConfigValue, PgConfig}; use k8s_openapi::apimachinery::pkg::apis::meta::v1::ObjectMeta; From 442b0147247c47ae4f8511928e0c6afdf688712e Mon Sep 17 00:00:00 2001 From: Ian Stanton Date: Thu, 17 Oct 2024 15:53:11 -0400 Subject: [PATCH 26/44] Remove extra arg Signed-off-by: Ian Stanton --- conductor/src/main.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/conductor/src/main.rs b/conductor/src/main.rs index ccc98f512..6a204ceda 100644 --- a/conductor/src/main.rs +++ b/conductor/src/main.rs @@ -997,9 +997,8 @@ async fn init_azure_storage_workload_identity( &azure_subscription_id, &azure_resource_group, &azure_region, - &azure_storage_account, - &read_msg.message.namespace, &backup_archive_bucket, + &read_msg.message.namespace, ) .await?; From e81f4e1ef032254969501f91370fb0a59006f935 Mon Sep 17 00:00:00 2001 From: Ian Stanton Date: Fri, 18 Oct 2024 07:49:04 -0400 Subject: [PATCH 27/44] Fix parameter order Signed-off-by: Ian Stanton --- conductor/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/conductor/src/lib.rs b/conductor/src/lib.rs index f476c1531..e38530fd0 100644 --- a/conductor/src/lib.rs +++ b/conductor/src/lib.rs @@ -606,8 +606,8 @@ pub async fn create_azure_storage_workload_identity_binding( let uami = create_uami( azure_resource_group, azure_subscription_id, - azure_region, uami_name, + azure_region, credentials.clone(), ) .await?; From d859b469873b1b787ba9f08dc5e86f785c276e2c Mon Sep 17 00:00:00 2001 From: Ian Stanton Date: Fri, 18 Oct 2024 08:07:49 -0400 Subject: [PATCH 28/44] Temp hardcode storage rg Signed-off-by: Ian Stanton --- conductor/src/azure/uami_builder.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/conductor/src/azure/uami_builder.rs b/conductor/src/azure/uami_builder.rs index 431c6d083..489d2d8cd 100644 --- a/conductor/src/azure/uami_builder.rs +++ b/conductor/src/azure/uami_builder.rs @@ -76,10 +76,11 @@ pub async fn get_role_definition_id( // Get storage account ID pub async fn get_storage_account_id( subscription_id: &str, - resource_group: &str, + _resource_group: &str, storage_account_name: &str, credentials: Arc, ) -> Result { + let resource_group = "cdb-plat-eus-sandbox-storage-rg"; let storage_client = azure_mgmt_storage::Client::builder(credentials).build()?; let storage_account_list = storage_client .storage_accounts_client() From a70da0e27039599d8757a691c9c18a46cbbf4d9b Mon Sep 17 00:00:00 2001 From: Ian Stanton Date: Fri, 18 Oct 2024 08:19:36 -0400 Subject: [PATCH 29/44] Pass azure_storage_account Signed-off-by: Ian Stanton --- conductor/src/lib.rs | 5 +++-- conductor/src/main.rs | 1 + 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/conductor/src/lib.rs b/conductor/src/lib.rs index e38530fd0..d8ae5b3ff 100644 --- a/conductor/src/lib.rs +++ b/conductor/src/lib.rs @@ -596,7 +596,8 @@ pub async fn create_azure_storage_workload_identity_binding( azure_subscription_id: &str, azure_resource_group: &str, azure_region: &str, - backup_archive_bucket: &str, + _backup_archive_bucket: &str, + azure_storage_account: &str, namespace: &str, ) -> Result<(), ConductorError> { let credentials = get_credentials().await?; @@ -618,7 +619,7 @@ pub async fn create_azure_storage_workload_identity_binding( let role_assignment = create_role_assignment( azure_subscription_id, azure_resource_group, - backup_archive_bucket, + azure_storage_account, &uami_id, credentials.clone(), ) diff --git a/conductor/src/main.rs b/conductor/src/main.rs index 6a204ceda..a96441372 100644 --- a/conductor/src/main.rs +++ b/conductor/src/main.rs @@ -998,6 +998,7 @@ async fn init_azure_storage_workload_identity( &azure_resource_group, &azure_region, &backup_archive_bucket, + &azure_storage_account, &read_msg.message.namespace, ) .await?; From fd1e30410aafe7c52f9815ba5d02d5495d244899 Mon Sep 17 00:00:00 2001 From: Ian Stanton Date: Fri, 18 Oct 2024 08:35:56 -0400 Subject: [PATCH 30/44] Temp hardcode cluster name Signed-off-by: Ian Stanton --- conductor/src/azure/uami_builder.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/conductor/src/azure/uami_builder.rs b/conductor/src/azure/uami_builder.rs index 489d2d8cd..791b28e41 100644 --- a/conductor/src/azure/uami_builder.rs +++ b/conductor/src/azure/uami_builder.rs @@ -215,7 +215,7 @@ pub async fn create_federated_identity_credentials( let cluster_issuer = get_cluster_issuer( subscription_id, resource_group, - instance_name, + "aks-cdb-plat-eus2-sandbox-aks-data-1", credentials.clone(), ) .await?; From 54fdef7bc6dc9c6480f4318ee63dc45dd0b9ad8a Mon Sep 17 00:00:00 2001 From: Ian Stanton Date: Fri, 18 Oct 2024 09:36:55 -0400 Subject: [PATCH 31/44] Check if role assignment exists Signed-off-by: Ian Stanton --- conductor/src/azure/uami_builder.rs | 46 ++++++++++++++++++++++++++++- 1 file changed, 45 insertions(+), 1 deletion(-) diff --git a/conductor/src/azure/uami_builder.rs b/conductor/src/azure/uami_builder.rs index 791b28e41..9fa8e2a74 100644 --- a/conductor/src/azure/uami_builder.rs +++ b/conductor/src/azure/uami_builder.rs @@ -107,6 +107,40 @@ pub async fn get_storage_account_id( .unwrap()) } +// Check if role assignment exists +pub async fn role_assignment_exists( + subscription_id: &str, + storage_account_id: &str, + uami_id: &str, + credentials: Arc, +) -> Result { + let role_assignment_client = + azure_mgmt_authorization::Client::builder(credentials.clone()).build()?; + + let role_definition = get_role_definition_id( + subscription_id, + "Storage Blob Data Contributor", + credentials.clone(), + ) + .await?; + + let role_assignment_list = role_assignment_client + .role_assignments_client() + .list_for_scope(storage_account_id); + let mut role_assignment_stream = role_assignment_list.into_stream(); + while let Some(role_assignment_page) = role_assignment_stream.next().await { + let role_assignment_page = role_assignment_page?; + for item in role_assignment_page.value { + if item.properties.clone().unwrap().role_definition_id == role_definition + && item.properties.unwrap().principal_id == uami_id + { + return Ok(true); + } + } + } + Ok(false) +} + // Create Role Assignment for UAMI pub async fn create_role_assignment( subscription_id: &str, @@ -133,10 +167,20 @@ pub async fn create_role_assignment( subscription_id, resource_group, storage_account_name, - credentials, + credentials.clone(), ) .await?; + // Check if role assignment already exists + if role_assignment_exists(subscription_id, &storage_account_id, uami_id, credentials).await? { + return Ok(RoleAssignment { + id: None, + name: None, + type_: None, + properties: None, + }); + } + // Set parameters for Role Assignment let role_assignment_params = azure_mgmt_authorization::models::RoleAssignmentCreateParameters { properties: RoleAssignmentProperties { From a92fb61014df626151cebc01671060fdb07c9280 Mon Sep 17 00:00:00 2001 From: Ian Stanton Date: Fri, 18 Oct 2024 09:53:53 -0400 Subject: [PATCH 32/44] Print role assignment details Signed-off-by: Ian Stanton --- conductor/src/azure/uami_builder.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/conductor/src/azure/uami_builder.rs b/conductor/src/azure/uami_builder.rs index 9fa8e2a74..96784030f 100644 --- a/conductor/src/azure/uami_builder.rs +++ b/conductor/src/azure/uami_builder.rs @@ -11,6 +11,7 @@ use azure_mgmt_msi::models::{ }; use futures::StreamExt; use std::sync::Arc; +use log::info; // Get credentials from workload identity pub async fn get_credentials() -> Result, AzureError> { @@ -131,6 +132,7 @@ pub async fn role_assignment_exists( while let Some(role_assignment_page) = role_assignment_stream.next().await { let role_assignment_page = role_assignment_page?; for item in role_assignment_page.value { + println!("ROLE ASSIGNMENTS {:?}", item); if item.properties.clone().unwrap().role_definition_id == role_definition && item.properties.unwrap().principal_id == uami_id { @@ -172,7 +174,9 @@ pub async fn create_role_assignment( .await?; // Check if role assignment already exists + info!("Checking if role assignment exists"); if role_assignment_exists(subscription_id, &storage_account_id, uami_id, credentials).await? { + info!("Role assignment already exists, skipping creation"); return Ok(RoleAssignment { id: None, name: None, From 82c9412d620cfc0183da44453534094b56f6fdaf Mon Sep 17 00:00:00 2001 From: Ian Stanton Date: Fri, 18 Oct 2024 10:07:09 -0400 Subject: [PATCH 33/44] Update scope for role assignment list Signed-off-by: Ian Stanton --- conductor/src/azure/uami_builder.rs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/conductor/src/azure/uami_builder.rs b/conductor/src/azure/uami_builder.rs index 96784030f..32d0e52e2 100644 --- a/conductor/src/azure/uami_builder.rs +++ b/conductor/src/azure/uami_builder.rs @@ -10,8 +10,8 @@ use azure_mgmt_msi::models::{ FederatedIdentityCredential, FederatedIdentityCredentialProperties, Identity, TrackedResource, }; use futures::StreamExt; -use std::sync::Arc; use log::info; +use std::sync::Arc; // Get credentials from workload identity pub async fn get_credentials() -> Result, AzureError> { @@ -111,7 +111,7 @@ pub async fn get_storage_account_id( // Check if role assignment exists pub async fn role_assignment_exists( subscription_id: &str, - storage_account_id: &str, + _storage_account_id: &str, uami_id: &str, credentials: Arc, ) -> Result { @@ -125,14 +125,17 @@ pub async fn role_assignment_exists( ) .await?; + let scope = format!("/subscriptions/{subscription_id}"); + let role_assignment_list = role_assignment_client .role_assignments_client() - .list_for_scope(storage_account_id); + .list_for_scope(scope.clone()); + info!("Fetching role assignments for scope {}", scope); let mut role_assignment_stream = role_assignment_list.into_stream(); while let Some(role_assignment_page) = role_assignment_stream.next().await { let role_assignment_page = role_assignment_page?; for item in role_assignment_page.value { - println!("ROLE ASSIGNMENTS {:?}", item); + info!("ROLE ASSIGNMENTS {:?}", item); if item.properties.clone().unwrap().role_definition_id == role_definition && item.properties.unwrap().principal_id == uami_id { From c9759cb1ecf473683a7ba0fc98ee01dd36385eb0 Mon Sep 17 00:00:00 2001 From: Ian Stanton Date: Fri, 18 Oct 2024 10:19:04 -0400 Subject: [PATCH 34/44] List role assignments for subscription Signed-off-by: Ian Stanton --- conductor/src/azure/uami_builder.rs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/conductor/src/azure/uami_builder.rs b/conductor/src/azure/uami_builder.rs index 32d0e52e2..c80e82fe1 100644 --- a/conductor/src/azure/uami_builder.rs +++ b/conductor/src/azure/uami_builder.rs @@ -125,14 +125,13 @@ pub async fn role_assignment_exists( ) .await?; - let scope = format!("/subscriptions/{subscription_id}"); let role_assignment_list = role_assignment_client - .role_assignments_client() - .list_for_scope(scope.clone()); - info!("Fetching role assignments for scope {}", scope); + .role_assignments_client().list_for_subscription(subscription_id); + info!("Fetching role assignments for subscription {}", subscription_id); let mut role_assignment_stream = role_assignment_list.into_stream(); while let Some(role_assignment_page) = role_assignment_stream.next().await { + info!("GOT ROLE ASSIGNMENT LIST"); let role_assignment_page = role_assignment_page?; for item in role_assignment_page.value { info!("ROLE ASSIGNMENTS {:?}", item); From 8e7519323c66b4ecb017e0d5e746a8ce0f20f84f Mon Sep 17 00:00:00 2001 From: Ian Stanton Date: Fri, 18 Oct 2024 13:02:16 -0400 Subject: [PATCH 35/44] Pass uami principal id Signed-off-by: Ian Stanton --- conductor/src/azure/uami_builder.rs | 24 ++++++++++++++++++++---- conductor/src/lib.rs | 4 +++- 2 files changed, 23 insertions(+), 5 deletions(-) diff --git a/conductor/src/azure/uami_builder.rs b/conductor/src/azure/uami_builder.rs index c80e82fe1..1757ee894 100644 --- a/conductor/src/azure/uami_builder.rs +++ b/conductor/src/azure/uami_builder.rs @@ -125,14 +125,22 @@ pub async fn role_assignment_exists( ) .await?; - let role_assignment_list = role_assignment_client - .role_assignments_client().list_for_subscription(subscription_id); - info!("Fetching role assignments for subscription {}", subscription_id); + .role_assignments_client() + .list_for_subscription(subscription_id); + info!( + "Fetching role assignments for subscription {}", + subscription_id + ); let mut role_assignment_stream = role_assignment_list.into_stream(); while let Some(role_assignment_page) = role_assignment_stream.next().await { info!("GOT ROLE ASSIGNMENT LIST"); let role_assignment_page = role_assignment_page?; + info!( + "ROLE ASSIGNMENT LIST LENGTH: {}", + role_assignment_page.value.len() + ); + for item in role_assignment_page.value { info!("ROLE ASSIGNMENTS {:?}", item); if item.properties.clone().unwrap().role_definition_id == role_definition @@ -151,6 +159,7 @@ pub async fn create_role_assignment( resource_group: &str, storage_account_name: &str, uami_id: &str, + uami_principal_id: &str, credentials: Arc, ) -> Result { let role_assignment_name = uuid::Uuid::new_v4().to_string(); @@ -177,7 +186,14 @@ pub async fn create_role_assignment( // Check if role assignment already exists info!("Checking if role assignment exists"); - if role_assignment_exists(subscription_id, &storage_account_id, uami_id, credentials).await? { + if role_assignment_exists( + subscription_id, + &storage_account_id, + uami_principal_id, + credentials, + ) + .await? + { info!("Role assignment already exists, skipping creation"); return Ok(RoleAssignment { id: None, diff --git a/conductor/src/lib.rs b/conductor/src/lib.rs index d8ae5b3ff..2312beec5 100644 --- a/conductor/src/lib.rs +++ b/conductor/src/lib.rs @@ -615,12 +615,14 @@ pub async fn create_azure_storage_workload_identity_binding( info!("Created UAMI: {:?}", uami); // Create Role Assignment for UAMI - let uami_id = uami.properties.unwrap().principal_id.unwrap(); + let uami_id = uami.properties.clone().unwrap().principal_id.unwrap(); + let uami_principal_id = uami.properties.unwrap().principal_id.unwrap(); let role_assignment = create_role_assignment( azure_subscription_id, azure_resource_group, azure_storage_account, &uami_id, + &uami_principal_id, credentials.clone(), ) .await?; From c5a11d870e6c2c3fa19b93eeba1622bf6ab3e184 Mon Sep 17 00:00:00 2001 From: Ian Stanton Date: Fri, 18 Oct 2024 13:45:56 -0400 Subject: [PATCH 36/44] Clean up role assignment info Signed-off-by: Ian Stanton --- conductor/src/azure/uami_builder.rs | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/conductor/src/azure/uami_builder.rs b/conductor/src/azure/uami_builder.rs index 1757ee894..0d469dc9b 100644 --- a/conductor/src/azure/uami_builder.rs +++ b/conductor/src/azure/uami_builder.rs @@ -128,21 +128,10 @@ pub async fn role_assignment_exists( let role_assignment_list = role_assignment_client .role_assignments_client() .list_for_subscription(subscription_id); - info!( - "Fetching role assignments for subscription {}", - subscription_id - ); let mut role_assignment_stream = role_assignment_list.into_stream(); while let Some(role_assignment_page) = role_assignment_stream.next().await { - info!("GOT ROLE ASSIGNMENT LIST"); let role_assignment_page = role_assignment_page?; - info!( - "ROLE ASSIGNMENT LIST LENGTH: {}", - role_assignment_page.value.len() - ); - for item in role_assignment_page.value { - info!("ROLE ASSIGNMENTS {:?}", item); if item.properties.clone().unwrap().role_definition_id == role_definition && item.properties.unwrap().principal_id == uami_id { From 20605556f61c2bac30202fafb3c92b68b35b5236 Mon Sep 17 00:00:00 2001 From: Ian Stanton Date: Fri, 18 Oct 2024 14:31:34 -0400 Subject: [PATCH 37/44] Set ServiceAccountTemplate Signed-off-by: Ian Stanton --- conductor/src/lib.rs | 7 +++++-- conductor/src/main.rs | 17 ++++++++++++++++- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/conductor/src/lib.rs b/conductor/src/lib.rs index 2312beec5..eb7ecccf5 100644 --- a/conductor/src/lib.rs +++ b/conductor/src/lib.rs @@ -599,7 +599,7 @@ pub async fn create_azure_storage_workload_identity_binding( _backup_archive_bucket: &str, azure_storage_account: &str, namespace: &str, -) -> Result<(), ConductorError> { +) -> Result { let credentials = get_credentials().await?; // Create UAMI @@ -614,6 +614,9 @@ pub async fn create_azure_storage_workload_identity_binding( .await?; info!("Created UAMI: {:?}", uami); + // Get UAMI Client ID to return and pass to ServiceAccountTemplate + let uami_client_id = uami.properties.clone().unwrap().client_id.unwrap(); + // Create Role Assignment for UAMI let uami_id = uami.properties.clone().unwrap().principal_id.unwrap(); let uami_principal_id = uami.properties.unwrap().principal_id.unwrap(); @@ -638,7 +641,7 @@ pub async fn create_azure_storage_workload_identity_binding( .await?; info!("Created Federated Credential: {:?}", federated_credential); - Ok(()) + Ok(uami_client_id) } pub async fn delete_azure_storage_workload_identity_binding( diff --git a/conductor/src/main.rs b/conductor/src/main.rs index a96441372..5667987ed 100644 --- a/conductor/src/main.rs +++ b/conductor/src/main.rs @@ -993,7 +993,7 @@ async fn init_azure_storage_workload_identity( return Ok(()); } - create_azure_storage_workload_identity_binding( + let uami_client_id = create_azure_storage_workload_identity_binding( &azure_subscription_id, &azure_resource_group, &azure_region, @@ -1003,6 +1003,20 @@ async fn init_azure_storage_workload_identity( ) .await?; + // Format ServiceAccountTemplate spec in CoreDBSpec + use std::collections::BTreeMap; + let mut annotations: BTreeMap = BTreeMap::new(); + annotations.insert( + "azure.workload.identity/client-id".to_string(), + uami_client_id, + ); + let service_account_template = ServiceAccountTemplate { + metadata: Some(ObjectMeta { + annotations: Some(annotations), + ..ObjectMeta::default() + }), + }; + // Generate Backup spec for CoreDB let volume_snapshot = Some(VolumeSnapshot { enabled: false, @@ -1037,6 +1051,7 @@ async fn init_azure_storage_workload_identity( }; coredb_spec.backup = backup; + coredb_spec.serviceAccountTemplate = service_account_template; Ok(()) } From 4c064bb3bfa9371a5df43194e0e3959830fae32b Mon Sep 17 00:00:00 2001 From: Ian Stanton Date: Fri, 18 Oct 2024 17:07:31 -0400 Subject: [PATCH 38/44] Add CloudProvider Azure Signed-off-by: Ian Stanton --- conductor/src/cloud.rs | 12 ++++++++++++ conductor/src/lib.rs | 2 +- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/conductor/src/cloud.rs b/conductor/src/cloud.rs index c48c5eee3..97104ffaa 100644 --- a/conductor/src/cloud.rs +++ b/conductor/src/cloud.rs @@ -2,6 +2,7 @@ pub struct CloudProviderBuilder { gcp: bool, aws: bool, + azure: bool, } impl CloudProviderBuilder { @@ -9,6 +10,7 @@ impl CloudProviderBuilder { CloudProviderBuilder { gcp: false, aws: false, + azure: false, } } @@ -22,11 +24,18 @@ impl CloudProviderBuilder { self } + pub fn azure(mut self, value: bool) -> Self { + self.azure = value; + self + } + pub fn build(self) -> CloudProvider { if self.gcp { CloudProvider::GCP } else if self.aws { CloudProvider::AWS + } else if self.azure { + CloudProvider::Azure } else { CloudProvider::Unknown } @@ -35,6 +44,7 @@ impl CloudProviderBuilder { pub enum CloudProvider { AWS, + Azure, GCP, Unknown, } @@ -43,6 +53,7 @@ impl CloudProvider { pub fn as_str(&self) -> &'static str { match self { CloudProvider::AWS => "aws", + CloudProvider::Azure => "azure", CloudProvider::GCP => "gcp", CloudProvider::Unknown => "unknown", } @@ -51,6 +62,7 @@ impl CloudProvider { pub fn prefix(&self) -> &'static str { match self { CloudProvider::AWS => "s3://", + CloudProvider::Azure => "https://", CloudProvider::GCP => "gs://", CloudProvider::Unknown => "", } diff --git a/conductor/src/lib.rs b/conductor/src/lib.rs index eb7ecccf5..eb6c11fd9 100644 --- a/conductor/src/lib.rs +++ b/conductor/src/lib.rs @@ -51,7 +51,7 @@ pub async fn generate_spec( let mut spec = spec.clone(); match cloud_provider { - CloudProvider::AWS | CloudProvider::GCP => { + CloudProvider::AWS | CloudProvider::GCP | CloudProvider::Azure => { let prefix = cloud_provider.prefix(); // Format the backups_path with the correct prefix From a4b9cd5ef84393c7a6ba4a43f764afad8a0e1d08 Mon Sep 17 00:00:00 2001 From: Ian Stanton Date: Fri, 18 Oct 2024 19:03:26 -0400 Subject: [PATCH 39/44] Check only one cloud provider is true Signed-off-by: Ian Stanton --- conductor/src/main.rs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/conductor/src/main.rs b/conductor/src/main.rs index 5667987ed..4ecc14a63 100644 --- a/conductor/src/main.rs +++ b/conductor/src/main.rs @@ -117,9 +117,13 @@ async fn run(metrics: CustomMetrics) -> Result<(), ConductorError> { panic!("CF_TEMPLATE_BUCKET is required when IS_CLOUD_FORMATION is true"); } - // Error and exit if both IS_CLOUD_FORMATION and IS_GCP are set to true - if is_cloud_formation && is_gcp { - panic!("Cannot have both IS_CLOUD_FORMATION and IS_GCP set to true"); + // Only allow for setting one of IS_CLOUD_FORMATION, IS_GCP, or IS_AZURE to true + let cloud_providers = [is_cloud_formation, is_gcp, is_azure] + .iter() + .filter(|&&x| x) + .count(); + if cloud_providers > 1 { + panic!("Only one of IS_CLOUD_FORMATION, IS_GCP, or IS_AZURE can be set to true"); } // Error and exit if IS_GCP is true and GCP_PROJECT_ID or GCP_PROJECT_NUMBER are not set From 81a4c710f1416a7dd03fd336795c8ab17b516f59 Mon Sep 17 00:00:00 2001 From: Ian Stanton Date: Mon, 21 Oct 2024 14:00:35 -0400 Subject: [PATCH 40/44] Use prefix for working with multiple resource groups Signed-off-by: Ian Stanton --- conductor/src/azure/uami_builder.rs | 31 +++++++++++++++-------------- conductor/src/lib.rs | 8 ++++---- conductor/src/main.rs | 9 +++++---- 3 files changed, 25 insertions(+), 23 deletions(-) diff --git a/conductor/src/azure/uami_builder.rs b/conductor/src/azure/uami_builder.rs index 0d469dc9b..df97bdf6c 100644 --- a/conductor/src/azure/uami_builder.rs +++ b/conductor/src/azure/uami_builder.rs @@ -22,12 +22,13 @@ pub async fn get_credentials() -> Result, AzureError> { // Create User Assigned Managed Identity pub async fn create_uami( - resource_group: &str, + resource_group_prefix: &str, subscription_id: &str, uami_name: &str, region: &str, credentials: Arc, ) -> Result { + let resource_group = format!("{resource_group_prefix}-storage-rg"); let msi_client = azure_mgmt_msi::Client::builder(credentials).build()?; // Set parameters for User Assigned Managed Identity @@ -77,11 +78,11 @@ pub async fn get_role_definition_id( // Get storage account ID pub async fn get_storage_account_id( subscription_id: &str, - _resource_group: &str, + resource_group_prefix: &str, storage_account_name: &str, credentials: Arc, ) -> Result { - let resource_group = "cdb-plat-eus-sandbox-storage-rg"; + let resource_group = format!("{resource_group_prefix}-storage-rg"); let storage_client = azure_mgmt_storage::Client::builder(credentials).build()?; let storage_account_list = storage_client .storage_accounts_client() @@ -145,12 +146,13 @@ pub async fn role_assignment_exists( // Create Role Assignment for UAMI pub async fn create_role_assignment( subscription_id: &str, - resource_group: &str, + resource_group_prefix: &str, storage_account_name: &str, uami_id: &str, uami_principal_id: &str, credentials: Arc, ) -> Result { + let resource_group = format!("{resource_group_prefix}-storage-rg"); let role_assignment_name = uuid::Uuid::new_v4().to_string(); let role_assignment_client = azure_mgmt_authorization::Client::builder(credentials.clone()).build()?; @@ -167,7 +169,7 @@ pub async fn create_role_assignment( let storage_account_id = get_storage_account_id( subscription_id, - resource_group, + &resource_group, storage_account_name, credentials.clone(), ) @@ -226,17 +228,14 @@ pub async fn create_role_assignment( // crate is no longer being built: https://github.com/Azure/azure-sdk-for-rust/pull/1243 pub async fn get_cluster_issuer( subscription_id: &str, - resource_group: &str, + resource_group_prefix: &str, cluster_name: &str, credentials: Arc, ) -> Result { + let resource_group = format!("{resource_group_prefix}-aks-rg"); let client = reqwest::Client::new(); let url = format!( - "https://management.azure.com/subscriptions/{subscription_id}/resourceGroups/{resource_group}/providers/Microsoft.ContainerService/managedClusters/{cluster_name}?api-version=2024-08-01", - subscription_id = subscription_id, - resource_group = resource_group, - cluster_name = cluster_name - ); + "https://management.azure.com/subscriptions/{subscription_id}/resourceGroups/{resource_group}/providers/Microsoft.ContainerService/managedClusters/{cluster_name}?api-version=2024-08-01"); let scopes: &[&str] = &["https://management.azure.com/.default"]; let response = client @@ -261,16 +260,17 @@ pub async fn get_cluster_issuer( // Create Federated Identity Credentials for the UAMI pub async fn create_federated_identity_credentials( subscription_id: &str, - resource_group: &str, + resource_group_prefix: &str, instance_name: &str, credentials: Arc, ) -> Result { + let resource_group = format!("{resource_group_prefix}-storage-rg"); let uami_name = instance_name; let federated_identity_client = azure_mgmt_msi::Client::builder(credentials.clone()).build()?; let cluster_issuer = get_cluster_issuer( subscription_id, - resource_group, - "aks-cdb-plat-eus2-sandbox-aks-data-1", + &resource_group, + "aks-cdb-plat-eus2-sandbox-aks-data-1", // TODO(ianstanton) do not hard-code cluster_name credentials.clone(), ) .await?; @@ -302,10 +302,11 @@ pub async fn create_federated_identity_credentials( // Delete User Assigned Managed Identity pub async fn delete_uami( subscription_id: &str, - resource_group: &str, + resource_group_prefix: &str, uami_name: &str, credentials: Arc, ) -> Result<(), AzureError> { + let resource_group = format!("{resource_group_prefix}-storage-rg"); let msi_client = azure_mgmt_msi::Client::builder(credentials).build()?; msi_client .user_assigned_identities_client() diff --git a/conductor/src/lib.rs b/conductor/src/lib.rs index eb6c11fd9..19e43c4a6 100644 --- a/conductor/src/lib.rs +++ b/conductor/src/lib.rs @@ -594,7 +594,7 @@ pub async fn delete_gcp_storage_workload_identity_binding( pub async fn create_azure_storage_workload_identity_binding( azure_subscription_id: &str, - azure_resource_group: &str, + azure_resource_group_prefix: &str, azure_region: &str, _backup_archive_bucket: &str, azure_storage_account: &str, @@ -605,7 +605,7 @@ pub async fn create_azure_storage_workload_identity_binding( // Create UAMI let uami_name = namespace; let uami = create_uami( - azure_resource_group, + azure_resource_group_prefix, azure_subscription_id, uami_name, azure_region, @@ -622,7 +622,7 @@ pub async fn create_azure_storage_workload_identity_binding( let uami_principal_id = uami.properties.unwrap().principal_id.unwrap(); let role_assignment = create_role_assignment( azure_subscription_id, - azure_resource_group, + azure_resource_group_prefix, azure_storage_account, &uami_id, &uami_principal_id, @@ -634,7 +634,7 @@ pub async fn create_azure_storage_workload_identity_binding( // Create Federated Credential for the UAMI let federated_credential = create_federated_identity_credentials( azure_subscription_id, - azure_resource_group, + azure_resource_group_prefix, namespace, credentials.clone(), ) diff --git a/conductor/src/main.rs b/conductor/src/main.rs index 4ecc14a63..eb0dc42a7 100644 --- a/conductor/src/main.rs +++ b/conductor/src/main.rs @@ -99,10 +99,11 @@ async fn run(metrics: CustomMetrics) -> Result<(), ConductorError> { .unwrap_or_else(|_| "".to_owned()) .parse() .expect("error parsing AZURE_SUBSCRIPTION_ID"); - let azure_resource_group: String = env::var("AZURE_RESOURCE_GROUP") + // This is necessary for working with multiple resource groups. Example format: cdb-plat-eus-dev + let azure_resource_group_prefix: String = env::var("AZURE_RESOURCE_GROUP_PREFIX ") .unwrap_or_else(|_| "".to_owned()) .parse() - .expect("error parsing AZURE_RESOURCE_GROUP"); + .expect("error parsing AZURE_RESOURCE_GROUP_PREFIX"); let azure_region: String = env::var("AZURE_REGION") .unwrap_or_else(|_| "".to_owned()) .parse() @@ -378,7 +379,7 @@ async fn run(metrics: CustomMetrics) -> Result<(), ConductorError> { backup_archive_bucket.clone(), azure_storage_account_name.clone(), azure_subscription_id.clone(), - azure_resource_group.clone(), + azure_resource_group_prefix.clone(), azure_region.clone(), ) .await?; @@ -556,7 +557,7 @@ async fn run(metrics: CustomMetrics) -> Result<(), ConductorError> { ); delete_azure_storage_workload_identity_binding( &azure_subscription_id, - &azure_resource_group, + &azure_resource_group_prefix, &namespace, ) .await?; From 75160e6477b0b7968a1e6034061e19ce1e8a103a Mon Sep 17 00:00:00 2001 From: Ian Stanton Date: Mon, 21 Oct 2024 14:11:43 -0400 Subject: [PATCH 41/44] Format cluster name Signed-off-by: Ian Stanton --- conductor/src/azure/uami_builder.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/conductor/src/azure/uami_builder.rs b/conductor/src/azure/uami_builder.rs index df97bdf6c..5426fcfff 100644 --- a/conductor/src/azure/uami_builder.rs +++ b/conductor/src/azure/uami_builder.rs @@ -270,7 +270,7 @@ pub async fn create_federated_identity_credentials( let cluster_issuer = get_cluster_issuer( subscription_id, &resource_group, - "aks-cdb-plat-eus2-sandbox-aks-data-1", // TODO(ianstanton) do not hard-code cluster_name + &format!("aks-{resource_group_prefix}-aks-data-1"), credentials.clone(), ) .await?; From bb32a04bebb40d4ffcabcc3b94c01c16dc39ad4a Mon Sep 17 00:00:00 2001 From: Ian Stanton Date: Mon, 21 Oct 2024 14:31:07 -0400 Subject: [PATCH 42/44] Clean up returns and info Signed-off-by: Ian Stanton --- conductor/src/azure/uami_builder.rs | 23 +++++++++++------------ conductor/src/lib.rs | 8 +++----- 2 files changed, 14 insertions(+), 17 deletions(-) diff --git a/conductor/src/azure/uami_builder.rs b/conductor/src/azure/uami_builder.rs index 5426fcfff..78531061b 100644 --- a/conductor/src/azure/uami_builder.rs +++ b/conductor/src/azure/uami_builder.rs @@ -46,6 +46,7 @@ pub async fn create_uami( .user_assigned_identities_client() .create_or_update(subscription_id, resource_group, uami_name, uami_params) .await?; + info!("Created UAMI for {uami_name}"); Ok(uami_created) } @@ -148,10 +149,11 @@ pub async fn create_role_assignment( subscription_id: &str, resource_group_prefix: &str, storage_account_name: &str, + namespace: &str, uami_id: &str, uami_principal_id: &str, credentials: Arc, -) -> Result { +) -> Result<(), AzureError> { let resource_group = format!("{resource_group_prefix}-storage-rg"); let role_assignment_name = uuid::Uuid::new_v4().to_string(); let role_assignment_client = @@ -186,12 +188,7 @@ pub async fn create_role_assignment( .await? { info!("Role assignment already exists, skipping creation"); - return Ok(RoleAssignment { - id: None, - name: None, - type_: None, - properties: None, - }); + return Ok(()); } // Set parameters for Role Assignment @@ -213,7 +210,7 @@ pub async fn create_role_assignment( }; // Create Role Assignment. Scope should be storage account ID - let role_assignment_created = role_assignment_client + role_assignment_client .role_assignments_client() .create( storage_account_id, @@ -221,7 +218,8 @@ pub async fn create_role_assignment( role_assignment_params, ) .await?; - Ok(role_assignment_created) + info!("Created Role Assignment for {namespace}"); + Ok(()) } // Get OIDC Issuer URL from AKS cluster using rest API. This is necessary because the azure_mgmt_containerservice @@ -263,7 +261,7 @@ pub async fn create_federated_identity_credentials( resource_group_prefix: &str, instance_name: &str, credentials: Arc, -) -> Result { +) -> Result<(), AzureError> { let resource_group = format!("{resource_group_prefix}-storage-rg"); let uami_name = instance_name; let federated_identity_client = azure_mgmt_msi::Client::builder(credentials.clone()).build()?; @@ -286,7 +284,7 @@ pub async fn create_federated_identity_credentials( }; // Create Federated Identity Credentials - let federated_identity_created = federated_identity_client + federated_identity_client .federated_identity_credentials_client() .create_or_update( subscription_id, @@ -296,7 +294,8 @@ pub async fn create_federated_identity_credentials( federated_identity_params, ) .await?; - Ok(federated_identity_created) + info!("Created Federated Credential for {instance_name}"); + Ok(()) } // Delete User Assigned Managed Identity diff --git a/conductor/src/lib.rs b/conductor/src/lib.rs index 19e43c4a6..3e6a7aebe 100644 --- a/conductor/src/lib.rs +++ b/conductor/src/lib.rs @@ -612,7 +612,6 @@ pub async fn create_azure_storage_workload_identity_binding( credentials.clone(), ) .await?; - info!("Created UAMI: {:?}", uami); // Get UAMI Client ID to return and pass to ServiceAccountTemplate let uami_client_id = uami.properties.clone().unwrap().client_id.unwrap(); @@ -620,26 +619,25 @@ pub async fn create_azure_storage_workload_identity_binding( // Create Role Assignment for UAMI let uami_id = uami.properties.clone().unwrap().principal_id.unwrap(); let uami_principal_id = uami.properties.unwrap().principal_id.unwrap(); - let role_assignment = create_role_assignment( + create_role_assignment( azure_subscription_id, azure_resource_group_prefix, azure_storage_account, + &namespace, &uami_id, &uami_principal_id, credentials.clone(), ) .await?; - info!("Created Role Assignment: {:?}", role_assignment); // Create Federated Credential for the UAMI - let federated_credential = create_federated_identity_credentials( + create_federated_identity_credentials( azure_subscription_id, azure_resource_group_prefix, namespace, credentials.clone(), ) .await?; - info!("Created Federated Credential: {:?}", federated_credential); Ok(uami_client_id) } From 0b93513aaa640a907124c1a87b8601e2e7eeffef Mon Sep 17 00:00:00 2001 From: Ian Stanton Date: Mon, 21 Oct 2024 16:00:38 -0400 Subject: [PATCH 43/44] Check for required azure env vars Signed-off-by: Ian Stanton --- conductor/src/main.rs | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/conductor/src/main.rs b/conductor/src/main.rs index eb0dc42a7..e9019db23 100644 --- a/conductor/src/main.rs +++ b/conductor/src/main.rs @@ -91,10 +91,10 @@ async fn run(metrics: CustomMetrics) -> Result<(), ConductorError> { .unwrap_or_else(|_| "false".to_owned()) .parse() .expect("error parsing IS_AZURE"); - let azure_storage_account_name: String = env::var("AZURE_STORAGE_ACCOUNT_NAME") + let azure_storage_account: String = env::var("AZURE_STORAGE_ACCOUNT") .unwrap_or_else(|_| "".to_owned()) .parse() - .expect("error parsing AZURE_STORAGE_ACCOUNT_NAME"); + .expect("error parsing AZURE_STORAGE_ACCOUNT"); let azure_subscription_id: String = env::var("AZURE_SUBSCRIPTION_ID") .unwrap_or_else(|_| "".to_owned()) .parse() @@ -132,6 +132,16 @@ async fn run(metrics: CustomMetrics) -> Result<(), ConductorError> { panic!("GCP_PROJECT_ID and GCP_PROJECT_NUMBER must be set if IS_GCP is true"); } + // Error and exit if IS_AZURE is true and any of the required Azure environment variables are not set + if is_azure + && (azure_storage_account.is_empty() + || azure_subscription_id.is_empty() + || azure_resource_group_prefix.is_empty() + || azure_region.is_empty()) + { + panic!("AZURE_STORAGE_ACCOUNT, AZURE_SUBSCRIPTION_ID, AZURE_RESOURCE_GROUP_PREFIX, and AZURE_REGION must be set if IS_AZURE is true"); + } + // Connect to pgmq let queue = PGMQueueExt::new(pg_conn_url.clone(), 5).await?; queue.init().await?; @@ -377,7 +387,7 @@ async fn run(metrics: CustomMetrics) -> Result<(), ConductorError> { &read_msg, &mut coredb_spec, backup_archive_bucket.clone(), - azure_storage_account_name.clone(), + azure_storage_account.clone(), azure_subscription_id.clone(), azure_resource_group_prefix.clone(), azure_region.clone(), From 1895bdfa3290bbf97236135497694fa413c60374 Mon Sep 17 00:00:00 2001 From: Ian Stanton Date: Mon, 21 Oct 2024 16:36:29 -0400 Subject: [PATCH 44/44] Cleanup Signed-off-by: Ian Stanton --- conductor/src/azure/uami_builder.rs | 4 ++-- conductor/src/lib.rs | 8 +++----- conductor/src/main.rs | 1 - 3 files changed, 5 insertions(+), 8 deletions(-) diff --git a/conductor/src/azure/uami_builder.rs b/conductor/src/azure/uami_builder.rs index 78531061b..801f931ab 100644 --- a/conductor/src/azure/uami_builder.rs +++ b/conductor/src/azure/uami_builder.rs @@ -150,7 +150,6 @@ pub async fn create_role_assignment( resource_group_prefix: &str, storage_account_name: &str, namespace: &str, - uami_id: &str, uami_principal_id: &str, credentials: Arc, ) -> Result<(), AzureError> { @@ -196,7 +195,7 @@ pub async fn create_role_assignment( properties: RoleAssignmentProperties { scope: None, role_definition_id: role_definition, - principal_id: uami_id.to_string(), + principal_id: uami_principal_id.to_string(), principal_type: None, description: None, condition: None, @@ -312,5 +311,6 @@ pub async fn delete_uami( .delete(subscription_id, resource_group, uami_name) .send() .await?; + info!("Deleted UAMI for {uami_name}"); Ok(()) } diff --git a/conductor/src/lib.rs b/conductor/src/lib.rs index 3e6a7aebe..0faa7fd2c 100644 --- a/conductor/src/lib.rs +++ b/conductor/src/lib.rs @@ -596,18 +596,16 @@ pub async fn create_azure_storage_workload_identity_binding( azure_subscription_id: &str, azure_resource_group_prefix: &str, azure_region: &str, - _backup_archive_bucket: &str, azure_storage_account: &str, namespace: &str, ) -> Result { let credentials = get_credentials().await?; // Create UAMI - let uami_name = namespace; let uami = create_uami( azure_resource_group_prefix, azure_subscription_id, - uami_name, + namespace, azure_region, credentials.clone(), ) @@ -617,14 +615,12 @@ pub async fn create_azure_storage_workload_identity_binding( let uami_client_id = uami.properties.clone().unwrap().client_id.unwrap(); // Create Role Assignment for UAMI - let uami_id = uami.properties.clone().unwrap().principal_id.unwrap(); let uami_principal_id = uami.properties.unwrap().principal_id.unwrap(); create_role_assignment( azure_subscription_id, azure_resource_group_prefix, azure_storage_account, &namespace, - &uami_id, &uami_principal_id, credentials.clone(), ) @@ -642,6 +638,8 @@ pub async fn create_azure_storage_workload_identity_binding( Ok(uami_client_id) } +// TODO(ianstanton) Check to see whether we need to delete the role assignment and federated +// credentials pub async fn delete_azure_storage_workload_identity_binding( azure_subscription_id: &str, azure_resource_group: &str, diff --git a/conductor/src/main.rs b/conductor/src/main.rs index e9019db23..984168613 100644 --- a/conductor/src/main.rs +++ b/conductor/src/main.rs @@ -1012,7 +1012,6 @@ async fn init_azure_storage_workload_identity( &azure_subscription_id, &azure_resource_group, &azure_region, - &backup_archive_bucket, &azure_storage_account, &read_msg.message.namespace, )