From b3806340210bb78ed9e5dd334bd2524e094a44f9 Mon Sep 17 00:00:00 2001 From: Szepesi Tibor Date: Sun, 8 Dec 2024 02:53:26 +0100 Subject: [PATCH] Use jwt secret from env var (#100) --- .github/workflows/ci.yml | 2 +- Cargo.lock | 4 +++- Cargo.toml | 1 + iam-cli/Cargo.toml | 2 ++ iam-cli/commands/setup.rs | 45 +++++++++++++++++++++++++++++++++++++- iam-common/Cargo.toml | 2 +- iam-common/id.rs | 2 +- iam-common/keys/key.rs | 20 +++++++++++++++-- iam-common/keys/manager.rs | 2 +- 9 files changed, 72 insertions(+), 8 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a5d1b25..95a5d28 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -98,6 +98,6 @@ jobs: - bans licenses sources steps: - uses: actions/checkout@v2 - - uses: EmbarkStudios/cargo-deny-action@v1 + - uses: EmbarkStudios/cargo-deny-action@v2 with: command: check ${{ matrix.checks }} diff --git a/Cargo.lock b/Cargo.lock index aaf8630..2e33320 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,6 +1,6 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 [[package]] name = "addr2line" @@ -1260,8 +1260,10 @@ name = "iam-cli" version = "0.1.0" dependencies = [ "anyhow", + "base64 0.22.1", "clap", "dotenvy", + "ed25519-dalek", "iam-common", "iam-entity", "k8s-openapi", diff --git a/Cargo.toml b/Cargo.toml index 94a60a6..20368cf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -41,3 +41,4 @@ serde_json = "1.0.120" tokio = { version = "1.38.0", features = ["macros", "rt-multi-thread", "signal"] } tracing = { version = "0.1.40", default-features = false } uuid = { version = "1.10.0", default-features = false, features = ["v4"] } +ed25519-dalek = { version = "2.1.1", features = ["pkcs8", "rand_core"] } diff --git a/iam-cli/Cargo.toml b/iam-cli/Cargo.toml index a9c64b5..45508b2 100644 --- a/iam-cli/Cargo.toml +++ b/iam-cli/Cargo.toml @@ -10,8 +10,10 @@ path = "main.rs" [dependencies] anyhow.workspace = true +base64.workspace = true clap = { version = "4.5.11", features = ["cargo", "env"] } dotenvy.workspace = true +ed25519-dalek.workspace = true iam-common.workspace = true iam-entity.workspace = true k8s-openapi = { version = "0.22.0", features = ["earliest"] } diff --git a/iam-cli/commands/setup.rs b/iam-cli/commands/setup.rs index 567a4f5..b5cf224 100644 --- a/iam-cli/commands/setup.rs +++ b/iam-cli/commands/setup.rs @@ -1,5 +1,7 @@ use anyhow::Context; +use base64::{prelude::BASE64_STANDARD, Engine}; use clap::{Arg, ArgAction, ArgMatches, Command}; +use ed25519_dalek::SigningKey; use k8s_openapi::api::core::v1::Secret; use kube::{ api::{ObjectMeta, PostParams}, @@ -39,7 +41,8 @@ pub fn command() -> Command { pub async fn run(matches: &ArgMatches) -> anyhow::Result<()> { let client = Client::try_default().await?; - create_admin_user(matches, client).await?; + create_jwt_secret_key(client.clone()).await?; + create_admin_user(matches, client.clone()).await?; Ok(()) } @@ -93,3 +96,43 @@ async fn create_admin_user(matches: &ArgMatches, client: Client) -> anyhow::Resu Ok(()) } + +async fn create_jwt_secret_key(client: Client) -> anyhow::Result<()> { + const SECRET_NAME: &str = "iam-jwt"; + + let key = SigningKey::generate(&mut OsRng); + let bytes = BASE64_STANDARD.encode(key.to_bytes()); + + let secrets: Api = Api::default_namespaced(client); + + if secrets + .get_opt(SECRET_NAME) + .await + .context("Failed to query iam-jwt secret")? + .is_some() + { + println!("iam-jwt secret already exists."); + return Ok(()); + } + + secrets + .create( + &PostParams::default(), + &Secret { + metadata: ObjectMeta { + name: Some(SECRET_NAME.to_owned()), + ..Default::default() + }, + string_data: Some({ + let mut map = BTreeMap::new(); + map.insert("IAM_JWT_SECRET_KEY".to_owned(), bytes); + map + }), + ..Default::default() + }, + ) + .await + .context("Failed to create secret")?; + + Ok(()) +} diff --git a/iam-common/Cargo.toml b/iam-common/Cargo.toml index 16ccd07..f23bfd0 100644 --- a/iam-common/Cargo.toml +++ b/iam-common/Cargo.toml @@ -24,6 +24,6 @@ bytes = "1.6.1" serde_json.workspace = true mime.workspace = true base64.workspace = true -ed25519-dalek = { version = "2.1.1", features = ["pkcs8", "rand_core"] } +ed25519-dalek.workspace = true jose-jwk = { version = "0.1.2", default-features = false } anyhow.workspace = true diff --git a/iam-common/id.rs b/iam-common/id.rs index 8b526d8..21fc60f 100644 --- a/iam-common/id.rs +++ b/iam-common/id.rs @@ -129,7 +129,7 @@ impl<'de> Deserialize<'de> for Id { struct Visitor; - impl<'de> de::Visitor<'de> for Visitor { + impl de::Visitor<'_> for Visitor { type Value = Id; fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { diff --git a/iam-common/keys/key.rs b/iam-common/keys/key.rs index 681ed5f..f691cd0 100644 --- a/iam-common/keys/key.rs +++ b/iam-common/keys/key.rs @@ -1,7 +1,9 @@ -use ed25519_dalek::{pkcs8::EncodePrivateKey, SigningKey}; +use base64::{prelude::BASE64_STANDARD, Engine}; +use ed25519_dalek::{pkcs8::EncodePrivateKey, SecretKey, SigningKey, SECRET_KEY_LENGTH}; use jose_jwk::{Jwk, Okp, OkpCurves, Parameters}; use jsonwebtoken::{Algorithm, DecodingKey, EncodingKey}; use rand::rngs::OsRng; +use std::env; pub struct Key { pub(super) jwk: Jwk, @@ -10,8 +12,14 @@ pub struct Key { } impl Key { - pub(super) fn generate() -> Key { + #[allow(unused)] + pub(super) fn generate() -> Self { let private_key = SigningKey::generate(&mut OsRng); + Self::from_private_key(private_key.as_bytes()) + } + + pub(super) fn from_private_key(secret_key: &SecretKey) -> Self { + let private_key = SigningKey::from_bytes(secret_key); let public_key = private_key.verifying_key(); let bytes = Box::new(public_key.to_bytes()) as Box<[u8]>; @@ -38,6 +46,14 @@ impl Key { } } + // TODO: this is a temporary solution + pub(super) fn from_env() -> Self { + let key = env::var("IAM_JWT_SECRET_KEY").unwrap(); + let key = BASE64_STANDARD.decode(key).unwrap(); + assert_eq!(key.len(), SECRET_KEY_LENGTH); + Self::from_private_key(&key.try_into().unwrap()) + } + pub fn get_alg(&self) -> Algorithm { match self.jwk.key { jose_jwk::Key::Okp(Okp { diff --git a/iam-common/keys/manager.rs b/iam-common/keys/manager.rs index 427f886..22f2b55 100644 --- a/iam-common/keys/manager.rs +++ b/iam-common/keys/manager.rs @@ -10,7 +10,7 @@ pub struct KeyManager { impl KeyManager { pub fn new() -> Self { Self { - jwt_key: Key::generate(), + jwt_key: Key::from_env(), } }