From af28aa2497041d65628eedef87cb4d597a0b3583 Mon Sep 17 00:00:00 2001 From: Christoph Herzog Date: Thu, 7 Aug 2025 15:03:40 +0200 Subject: [PATCH] feat(config): Add CapabilitySshServerV1 to app config Allows configuration of the SSH server behaviour for an app. --- .../jsonschema/types/AppConfigV1.schema.json | 108 ++++++++++++++++++ lib/config/src/app/mod.rs | 5 +- lib/config/src/app/ssh.rs | 47 ++++++++ 3 files changed, 159 insertions(+), 1 deletion(-) create mode 100644 lib/config/src/app/ssh.rs diff --git a/docs/schema/generated/jsonschema/types/AppConfigV1.schema.json b/docs/schema/generated/jsonschema/types/AppConfigV1.schema.json index 0272f17fe74..6c9fe724982 100644 --- a/docs/schema/generated/jsonschema/types/AppConfigV1.schema.json +++ b/docs/schema/generated/jsonschema/types/AppConfigV1.schema.json @@ -218,6 +218,16 @@ "type": "null" } ] + }, + "ssh": { + "anyOf": [ + { + "$ref": "#/definitions/CapabilitySshServerV1" + }, + { + "type": "null" + } + ] } }, "additionalProperties": true @@ -302,6 +312,29 @@ } } }, + "CapabilitySshServerV1": { + "description": "Configure SSH server credentials and settings.", + "type": "object", + "properties": { + "enabled": { + "description": "Enable an SSH server.", + "type": [ + "boolean", + "null" + ] + }, + "users": { + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/SshUserV1" + } + } + }, + "additionalProperties": true + }, "ExecutableJob": { "type": "object", "properties": { @@ -731,6 +764,48 @@ "PackageSource": { "type": "string" }, + "PasswordV1": { + "oneOf": [ + { + "description": "Plain text password.", + "type": "object", + "required": [ + "password", + "type" + ], + "properties": { + "password": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "plain" + ] + } + } + }, + { + "description": "Bcrypt password hash.", + "type": "object", + "required": [ + "hash", + "type" + ], + "properties": { + "hash": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "bcrypt" + ] + } + } + } + ] + }, "PrettyDuration": { "type": "string" }, @@ -749,6 +824,39 @@ }, "SnapshotTrigger": { "type": "string" + }, + "SshUserV1": { + "type": "object", + "required": [ + "username" + ], + "properties": { + "authorized_keys": { + "description": "SSH public keys for this user.", + "type": [ + "array", + "null" + ], + "items": { + "type": "string" + } + }, + "passwords": { + "description": "Passwords for this user.", + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/PasswordV1" + } + }, + "username": { + "description": "The username used for SSH login.", + "type": "string" + } + }, + "additionalProperties": true } } } \ No newline at end of file diff --git a/lib/config/src/app/mod.rs b/lib/config/src/app/mod.rs index a01b5d9eee5..27f5e2e8f55 100644 --- a/lib/config/src/app/mod.rs +++ b/lib/config/src/app/mod.rs @@ -5,8 +5,9 @@ mod http; mod job; mod pretty_duration; mod snapshot_trigger; +mod ssh; -pub use self::{healthcheck::*, http::*, job::*, pretty_duration::*, snapshot_trigger::*}; +pub use self::{healthcheck::*, http::*, job::*, pretty_duration::*, snapshot_trigger::*, ssh::*}; use anyhow::{bail, Context}; use bytesize::ByteSize; @@ -206,6 +207,8 @@ pub struct AppConfigCapabilityMapV1 { #[serde(skip_serializing_if = "Option::is_none")] pub instaboot: Option, + pub ssh: Option, + /// Additional unknown capabilities. /// /// This provides a small bit of forwards compatibility for newly added diff --git a/lib/config/src/app/ssh.rs b/lib/config/src/app/ssh.rs new file mode 100644 index 00000000000..5f639ce96d4 --- /dev/null +++ b/lib/config/src/app/ssh.rs @@ -0,0 +1,47 @@ +use indexmap::IndexMap; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +/// Configure SSH server credentials and settings. +#[derive(Serialize, Deserialize, JsonSchema, Clone, Debug, PartialEq, Eq)] +pub struct CapabilitySshServerV1 { + /// Enable an SSH server. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub enabled: Option, + + #[serde(skip_serializing_if = "Option::is_none")] + pub users: Option>, + + /// Additional unknown fields. + /// This provides a small bit of forwards compatibility. + #[serde(flatten)] + pub other: IndexMap, +} + +#[derive(Serialize, Deserialize, JsonSchema, PartialEq, Eq, Clone, Debug)] +pub struct SshUserV1 { + /// The username used for SSH login. + pub username: String, + + /// Passwords for this user. + #[serde(skip_serializing_if = "Option::is_none")] + pub passwords: Option>, + + /// SSH public keys for this user. + #[serde(skip_serializing_if = "Option::is_none")] + pub authorized_keys: Option>, + + /// Additional unknown fields. + /// This provides a small bit of forwards compatibility. + #[serde(flatten)] + pub other: IndexMap, +} + +#[derive(Serialize, Deserialize, JsonSchema, PartialEq, Eq, Clone, Debug)] +#[serde(rename_all = "snake_case", tag = "type")] +pub enum PasswordV1 { + /// Plain text password. + Plain { password: String }, + /// Bcrypt password hash. + Bcrypt { hash: String }, +}