forked from DGNum/colmena
Allow customization of SSH configurations
This commit is contained in:
parent
a2fa8f1da7
commit
1c9e7cdb83
5 changed files with 88 additions and 12 deletions
10
README.md
10
README.md
|
@ -66,6 +66,11 @@ Here is a sample `hive.nix` with two nodes, with some common configurations appl
|
||||||
# can override it like:
|
# can override it like:
|
||||||
deployment.targetHost = "host-b.mydomain.tld";
|
deployment.targetHost = "host-b.mydomain.tld";
|
||||||
|
|
||||||
|
# It's also possible to override the target SSH port.
|
||||||
|
# For further customization, use the SSH_CONFIG_FILE
|
||||||
|
# environment variable to specify a ssh_config file.
|
||||||
|
deployment.targetPort = 1234;
|
||||||
|
|
||||||
time.timeZone = "America/Los_Angeles";
|
time.timeZone = "America/Los_Angeles";
|
||||||
|
|
||||||
boot.loader.grub.device = "/dev/sda";
|
boot.loader.grub.device = "/dev/sda";
|
||||||
|
@ -179,10 +184,13 @@ For example, to deploy ACME credentials for use with `security.acme`:
|
||||||
Take note that if you use the default path (`/run/keys`), the secret files are only stored in-memory and will not survive reboots.
|
Take note that if you use the default path (`/run/keys`), the secret files are only stored in-memory and will not survive reboots.
|
||||||
To upload your secrets without performing a full deployment, use `colmena upload-keys`.
|
To upload your secrets without performing a full deployment, use `colmena upload-keys`.
|
||||||
|
|
||||||
|
## Environment variables
|
||||||
|
|
||||||
|
- `SSH_CONFIG_FILE`: Path to a `ssh_config` file
|
||||||
|
|
||||||
## Current limitations
|
## Current limitations
|
||||||
|
|
||||||
- It's required to use SSH keys to log into the remote hosts, and interactive authentication will not work.
|
- It's required to use SSH keys to log into the remote hosts, and interactive authentication will not work.
|
||||||
- There is no option to override SSH or `nix-copy-closure` options.
|
|
||||||
- Error reporting is lacking.
|
- Error reporting is lacking.
|
||||||
|
|
||||||
## Licensing
|
## Licensing
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
use std::env;
|
||||||
|
use std::path::PathBuf;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use clap::{Arg, App, SubCommand, ArgMatches};
|
use clap::{Arg, App, SubCommand, ArgMatches};
|
||||||
|
@ -131,6 +133,9 @@ pub async fn run(_global_args: &ArgMatches<'_>, local_args: &ArgMatches<'_>) {
|
||||||
quit::with_code(2);
|
quit::with_code(2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let ssh_config = env::var("SSH_CONFIG_FILE")
|
||||||
|
.ok().map(PathBuf::from);
|
||||||
|
|
||||||
// FIXME: This is ugly :/ Make an enum wrapper for this fake "keys" goal
|
// FIXME: This is ugly :/ Make an enum wrapper for this fake "keys" goal
|
||||||
let goal_arg = local_args.value_of("goal").unwrap();
|
let goal_arg = local_args.value_of("goal").unwrap();
|
||||||
let goal = if goal_arg == "keys" {
|
let goal = if goal_arg == "keys" {
|
||||||
|
@ -146,10 +151,14 @@ pub async fn run(_global_args: &ArgMatches<'_>, local_args: &ArgMatches<'_>) {
|
||||||
let config = all_nodes.get(node).unwrap();
|
let config = all_nodes.get(node).unwrap();
|
||||||
let host = config.to_ssh_host();
|
let host = config.to_ssh_host();
|
||||||
match host {
|
match host {
|
||||||
Some(host) => {
|
Some(mut host) => {
|
||||||
|
if let Some(ssh_config) = ssh_config.as_ref() {
|
||||||
|
host.set_ssh_config(ssh_config.clone());
|
||||||
|
}
|
||||||
|
|
||||||
targets.insert(
|
targets.insert(
|
||||||
node.clone(),
|
node.clone(),
|
||||||
Target::new(host, config.clone()),
|
Target::new(host.upcast(), config.clone()),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
|
|
|
@ -41,6 +41,16 @@ let
|
||||||
type = types.nullOr types.str;
|
type = types.nullOr types.str;
|
||||||
default = name;
|
default = name;
|
||||||
};
|
};
|
||||||
|
targetPort = lib.mkOption {
|
||||||
|
description = ''
|
||||||
|
The target SSH port for deployment.
|
||||||
|
|
||||||
|
By default, the port is the standard port (22) or taken
|
||||||
|
from your ssh_config.
|
||||||
|
'';
|
||||||
|
type = types.nullOr types.ints.unsigned;
|
||||||
|
default = null;
|
||||||
|
};
|
||||||
targetUser = lib.mkOption {
|
targetUser = lib.mkOption {
|
||||||
description = ''
|
description = ''
|
||||||
The user to use to log into the remote node.
|
The user to use to log into the remote node.
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
use std::collections::{HashMap, HashSet};
|
use std::collections::{HashMap, HashSet};
|
||||||
use std::convert::TryInto;
|
use std::convert::TryInto;
|
||||||
|
use std::path::PathBuf;
|
||||||
use std::process::Stdio;
|
use std::process::Stdio;
|
||||||
|
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
|
@ -23,6 +24,12 @@ pub struct Ssh {
|
||||||
/// The hostname or IP address to connect to.
|
/// The hostname or IP address to connect to.
|
||||||
host: String,
|
host: String,
|
||||||
|
|
||||||
|
/// The port to connect to.
|
||||||
|
port: Option<u16>,
|
||||||
|
|
||||||
|
/// Local path to a ssh_config file.
|
||||||
|
ssh_config: Option<PathBuf>,
|
||||||
|
|
||||||
friendly_name: String,
|
friendly_name: String,
|
||||||
path_cache: HashSet<StorePath>,
|
path_cache: HashSet<StorePath>,
|
||||||
progress_bar: ProcessProgress,
|
progress_bar: ProcessProgress,
|
||||||
|
@ -81,6 +88,8 @@ impl Ssh {
|
||||||
Self {
|
Self {
|
||||||
user,
|
user,
|
||||||
host,
|
host,
|
||||||
|
port: None,
|
||||||
|
ssh_config: None,
|
||||||
friendly_name,
|
friendly_name,
|
||||||
path_cache: HashSet::new(),
|
path_cache: HashSet::new(),
|
||||||
progress_bar: ProcessProgress::default(),
|
progress_bar: ProcessProgress::default(),
|
||||||
|
@ -88,6 +97,18 @@ impl Ssh {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn set_port(&mut self, port: u16) {
|
||||||
|
self.port = Some(port);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_ssh_config(&mut self, ssh_config: PathBuf) {
|
||||||
|
self.ssh_config = Some(ssh_config);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn upcast(self) -> Box<dyn Host> {
|
||||||
|
Box::new(self)
|
||||||
|
}
|
||||||
|
|
||||||
async fn run_command(&mut self, command: Command) -> NixResult<()> {
|
async fn run_command(&mut self, command: Command) -> NixResult<()> {
|
||||||
let mut execution = CommandExecution::new(command);
|
let mut execution = CommandExecution::new(command);
|
||||||
|
|
||||||
|
@ -136,20 +157,41 @@ impl Ssh {
|
||||||
command
|
command
|
||||||
}
|
}
|
||||||
|
|
||||||
fn ssh(&self, command: &[&str]) -> Command {
|
fn ssh_options(&self) -> Vec<String> {
|
||||||
// TODO: Allow configuation of SSH parameters
|
// TODO: Allow configuation of SSH parameters
|
||||||
|
|
||||||
|
let mut options: Vec<String> = ["-o", "StrictHostKeyChecking=accept-new", "-T"]
|
||||||
|
.iter().map(|s| s.to_string()).collect();
|
||||||
|
|
||||||
|
if let Some(port) = self.port {
|
||||||
|
options.push("-p".to_string());
|
||||||
|
options.push(port.to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(ssh_config) = self.ssh_config.as_ref() {
|
||||||
|
options.push("-F".to_string());
|
||||||
|
options.push(ssh_config.to_str().unwrap().to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
options
|
||||||
|
}
|
||||||
|
|
||||||
|
fn ssh(&self, command: &[&str]) -> Command {
|
||||||
|
let options = self.ssh_options();
|
||||||
|
let options_str = options.join(" ");
|
||||||
|
|
||||||
let mut cmd = Command::new("ssh");
|
let mut cmd = Command::new("ssh");
|
||||||
cmd.arg(self.ssh_target())
|
|
||||||
.args(&["-o", "StrictHostKeyChecking=accept-new", "-T"])
|
cmd
|
||||||
|
.arg(self.ssh_target())
|
||||||
|
.args(&options)
|
||||||
.arg("--")
|
.arg("--")
|
||||||
.args(command);
|
.args(command)
|
||||||
|
.env("NIX_SSHOPTS", options_str);
|
||||||
|
|
||||||
cmd
|
cmd
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
impl Ssh {
|
|
||||||
/// Uploads a single key.
|
/// Uploads a single key.
|
||||||
async fn upload_key(&mut self, name: &str, key: &Key) -> NixResult<()> {
|
async fn upload_key(&mut self, name: &str, key: &Key) -> NixResult<()> {
|
||||||
self.progress_bar.log(&format!("Deploying key {}", name));
|
self.progress_bar.log(&format!("Deploying key {}", name));
|
||||||
|
|
|
@ -86,6 +86,9 @@ pub struct NodeConfig {
|
||||||
#[serde(rename = "targetUser")]
|
#[serde(rename = "targetUser")]
|
||||||
target_user: String,
|
target_user: String,
|
||||||
|
|
||||||
|
#[serde(rename = "targetPort")]
|
||||||
|
target_port: Option<u16>,
|
||||||
|
|
||||||
#[serde(rename = "allowLocalDeployment")]
|
#[serde(rename = "allowLocalDeployment")]
|
||||||
allow_local_deployment: bool,
|
allow_local_deployment: bool,
|
||||||
tags: Vec<String>,
|
tags: Vec<String>,
|
||||||
|
@ -98,10 +101,14 @@ impl NodeConfig {
|
||||||
pub fn tags(&self) -> &[String] { &self.tags }
|
pub fn tags(&self) -> &[String] { &self.tags }
|
||||||
pub fn allows_local_deployment(&self) -> bool { self.allow_local_deployment }
|
pub fn allows_local_deployment(&self) -> bool { self.allow_local_deployment }
|
||||||
|
|
||||||
pub fn to_ssh_host(&self) -> Option<Box<dyn Host>> {
|
pub fn to_ssh_host(&self) -> Option<Ssh> {
|
||||||
self.target_host.as_ref().map(|target_host| {
|
self.target_host.as_ref().map(|target_host| {
|
||||||
let host = Ssh::new(self.target_user.clone(), target_host.clone());
|
let mut host = Ssh::new(self.target_user.clone(), target_host.clone());
|
||||||
let host: Box<dyn Host> = Box::new(host);
|
|
||||||
|
if let Some(target_port) = self.target_port {
|
||||||
|
host.set_port(target_port);
|
||||||
|
}
|
||||||
|
|
||||||
host
|
host
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue