diff --git a/Cargo.toml b/Cargo.toml index 8f827cd..a879e80 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,7 +5,7 @@ edition = "2024" [dependencies] bytes = "1.10.1" -clap = { version = "4.5.40", features = ["derive"] } +clap = { version = "4.5.40", features = ["derive", "env"] } clap_complete = { version = "4.5.54", features = ["unstable-dynamic"] } env_logger = "0.11.8" eyre = "0.6.12" diff --git a/src/bin/dls.rs b/src/bin/dls.rs index 2a2f84a..70eef8e 100644 --- a/src/bin/dls.rs +++ b/src/bin/dls.rs @@ -45,11 +45,20 @@ enum ClusterCommand { user_public_key: String, #[arg(long, default_value = "root")] principal: String, - #[arg(long, default_value = "+1d")] + #[arg(long, default_value = "1d")] validity: String, #[arg(long)] options: Vec, }, + KubeSign { + csr: String, + #[arg(long, default_value = "anonymous", env = "USER")] + user: String, + #[arg(long)] + group: Option, + #[arg(long, default_value = "1d")] + validity: String, + }, } #[tokio::main(flavor = "current_thread")] @@ -87,7 +96,7 @@ async fn main() -> Result<()> { }) => { let pub_key = tokio::fs::read_to_string(user_public_key).await?; let cert = cluster - .sign_ssh_user_pubkey(&dls::SshSignReq { + .ssh_userca_sign(&dls::SshSignReq { pub_key, principal, validity: Some(validity).filter(|s| s != ""), @@ -96,6 +105,23 @@ async fn main() -> Result<()> { .await?; write_raw(&cert); } + Some(CC::KubeSign { + csr, + user, + group, + validity, + }) => { + let csr = tokio::fs::read_to_string(csr).await?; + let cert = cluster + .kube_sign(&dls::KubeSignReq { + csr, + user, + group, + validity: Some(validity).filter(|s| s != ""), + }) + .await?; + write_raw(&cert); + } } } C::Hosts => write_json(&dls.hosts().await?), diff --git a/src/dls.rs b/src/dls.rs index 38584ee..44e41f2 100644 --- a/src/dls.rs +++ b/src/dls.rs @@ -96,7 +96,7 @@ impl<'t> Cluster<'t> { .await } - pub async fn sign_ssh_user_pubkey(&self, sign_req: &SshSignReq) -> Result> { + pub async fn ssh_userca_sign(&self, sign_req: &SshSignReq) -> Result> { let req = self.dls.req( Method::POST, format!("clusters/{}/ssh/user-ca/sign", self.name), @@ -106,6 +106,14 @@ impl<'t> Cluster<'t> { let resp = do_req(req, &self.dls.token).await?; Ok(resp.bytes().await.map_err(Error::Read)?.to_vec()) } + + pub async fn kube_sign(&self, sign_req: &KubeSignReq) -> Result> { + let req = (self.dls).req(Method::POST, format!("clusters/{}/kube/sign", self.name))?; + let req = req.json(sign_req); + + let resp = do_req(req, &self.dls.token).await?; + Ok(resp.bytes().await.map_err(Error::Read)?.to_vec()) + } } pub struct Host<'t> { @@ -164,8 +172,21 @@ pub struct SshSignReq { pub options: Vec, } +#[derive(serde::Deserialize, serde::Serialize)] +#[serde(rename_all = "PascalCase")] +pub struct KubeSignReq { + #[serde(rename = "CSR")] + pub csr: String, + pub user: String, + #[serde(skip_serializing_if = "Option::is_none")] + pub group: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub validity: Option, +} + #[derive(Debug, serde::Deserialize, serde::Serialize)] struct ServerError { + #[serde(default)] code: u16, message: String, #[serde(skip_serializing_if = "Option::is_none")]