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:
|
||||
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";
|
||||
|
||||
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.
|
||||
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
|
||||
|
||||
- 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.
|
||||
|
||||
## Licensing
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
use std::collections::HashMap;
|
||||
use std::env;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
|
||||
use clap::{Arg, App, SubCommand, ArgMatches};
|
||||
|
@ -131,6 +133,9 @@ pub async fn run(_global_args: &ArgMatches<'_>, local_args: &ArgMatches<'_>) {
|
|||
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
|
||||
let goal_arg = local_args.value_of("goal").unwrap();
|
||||
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 host = config.to_ssh_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(
|
||||
node.clone(),
|
||||
Target::new(host, config.clone()),
|
||||
Target::new(host.upcast(), config.clone()),
|
||||
);
|
||||
}
|
||||
None => {
|
||||
|
|
|
@ -41,6 +41,16 @@ let
|
|||
type = types.nullOr types.str;
|
||||
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 {
|
||||
description = ''
|
||||
The user to use to log into the remote node.
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
use std::collections::{HashMap, HashSet};
|
||||
use std::convert::TryInto;
|
||||
use std::path::PathBuf;
|
||||
use std::process::Stdio;
|
||||
|
||||
use async_trait::async_trait;
|
||||
|
@ -23,6 +24,12 @@ pub struct Ssh {
|
|||
/// The hostname or IP address to connect to.
|
||||
host: String,
|
||||
|
||||
/// The port to connect to.
|
||||
port: Option<u16>,
|
||||
|
||||
/// Local path to a ssh_config file.
|
||||
ssh_config: Option<PathBuf>,
|
||||
|
||||
friendly_name: String,
|
||||
path_cache: HashSet<StorePath>,
|
||||
progress_bar: ProcessProgress,
|
||||
|
@ -81,6 +88,8 @@ impl Ssh {
|
|||
Self {
|
||||
user,
|
||||
host,
|
||||
port: None,
|
||||
ssh_config: None,
|
||||
friendly_name,
|
||||
path_cache: HashSet::new(),
|
||||
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<()> {
|
||||
let mut execution = CommandExecution::new(command);
|
||||
|
||||
|
@ -136,20 +157,41 @@ impl Ssh {
|
|||
command
|
||||
}
|
||||
|
||||
fn ssh(&self, command: &[&str]) -> Command {
|
||||
fn ssh_options(&self) -> Vec<String> {
|
||||
// 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");
|
||||
cmd.arg(self.ssh_target())
|
||||
.args(&["-o", "StrictHostKeyChecking=accept-new", "-T"])
|
||||
|
||||
cmd
|
||||
.arg(self.ssh_target())
|
||||
.args(&options)
|
||||
.arg("--")
|
||||
.args(command);
|
||||
.args(command)
|
||||
.env("NIX_SSHOPTS", options_str);
|
||||
|
||||
cmd
|
||||
}
|
||||
}
|
||||
|
||||
impl Ssh {
|
||||
/// Uploads a single key.
|
||||
async fn upload_key(&mut self, name: &str, key: &Key) -> NixResult<()> {
|
||||
self.progress_bar.log(&format!("Deploying key {}", name));
|
||||
|
|
|
@ -86,6 +86,9 @@ pub struct NodeConfig {
|
|||
#[serde(rename = "targetUser")]
|
||||
target_user: String,
|
||||
|
||||
#[serde(rename = "targetPort")]
|
||||
target_port: Option<u16>,
|
||||
|
||||
#[serde(rename = "allowLocalDeployment")]
|
||||
allow_local_deployment: bool,
|
||||
tags: Vec<String>,
|
||||
|
@ -98,10 +101,14 @@ impl NodeConfig {
|
|||
pub fn tags(&self) -> &[String] { &self.tags }
|
||||
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| {
|
||||
let host = Ssh::new(self.target_user.clone(), target_host.clone());
|
||||
let host: Box<dyn Host> = Box::new(host);
|
||||
let mut host = Ssh::new(self.target_user.clone(), target_host.clone());
|
||||
|
||||
if let Some(target_port) = self.target_port {
|
||||
host.set_port(target_port);
|
||||
}
|
||||
|
||||
host
|
||||
})
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue