summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLars Wirzenius <liw@liw.fi>2022-04-07 07:27:55 +0000
committerLars Wirzenius <liw@liw.fi>2022-04-07 07:27:55 +0000
commit99b4c180e7932cfe180323ba35956b1184212f51 (patch)
tree0c145eef6210cc711f054c77f127a8bf9877dc87
parentf5b7ee0ce079e22f37e42c23277ed32aebb41919 (diff)
parent7fb4d37e19469b1bf567dd57cb86ae9f9f9d44c0 (diff)
downloadvmadm-99b4c180e7932cfe180323ba35956b1184212f51.tar.gz
Merge branch 'liw/user-ca' into 'main'
feat: add a user_ca_pubkey field to config, spec See merge request larswirzenius/vmadm!58
-rw-r--r--src/cloudinit.rs25
-rw-r--r--src/config.rs4
-rw-r--r--src/spec.rs51
-rw-r--r--vmadm.md11
4 files changed, 78 insertions, 13 deletions
diff --git a/src/cloudinit.rs b/src/cloudinit.rs
index 4fdf9d1..f5db9bf 100644
--- a/src/cloudinit.rs
+++ b/src/cloudinit.rs
@@ -44,7 +44,7 @@ log(f"loading user-data from {filename}")
obj = yaml.safe_load(open(filename))
ssh_keys = obj.get("ssh_keys", {})
-# log(f"ssh_keys: {json.dumps(ssh_keys)}")
+user_ca_pubkey = obj.get("user_ca_pubkey", {})
keys = []
certs = []
@@ -77,6 +77,11 @@ for key_type in key_types:
with open(filename, "w") as f:
f.write(cert)
+user_ca_filename = os.path.join(etc, "user-ca-keys")
+if user_ca_pubkey:
+ with open(user_ca_filename, "w") as f:
+ f.write(user_ca_pubkey)
+
config = os.path.join(etc, "sshd_config")
data = ""
if os.path.exists(config):
@@ -93,6 +98,9 @@ with open(config, "w") as f:
for filename in certs:
log(f"hostcert {filename}")
f.write(f"hostcertificate {filename}\n")
+ if user_ca_pubkey:
+ log(f"trustedusercakeys {user_ca_filename}")
+ f.write(f"trustedusercakeys {user_ca_filename}\n")
f.write(data)
log("vmadm cloud-init script ending")
@@ -118,6 +126,10 @@ pub enum CloudInitError {
#[error(transparent)]
KeyError(#[from] KeyError),
+ /// Something went wrong reading a file.
+ #[error("could not read {0}: {1}")]
+ ReadError(PathBuf, #[source] std::io::Error),
+
/// Something went wrong doing I/O.
#[error("could not write to {0}: {1}")]
WriteError(PathBuf, #[source] std::io::Error),
@@ -172,14 +184,25 @@ struct Userdata {
#[serde(skip_serializing_if = "Option::is_none")]
ssh_keys: Option<Hostkeys>,
+ #[serde(skip_serializing_if = "Option::is_none")]
+ user_ca_pubkey: Option<String>,
+
runcmd: Vec<String>,
}
impl Userdata {
fn from(spec: &Specification) -> Result<Self, CloudInitError> {
+ let user_ca_pubkey = if let Some(filename) = &spec.user_ca_pubkey {
+ let data = std::fs::read(&filename)
+ .map_err(|err| CloudInitError::ReadError(filename.to_path_buf(), err))?;
+ Some(String::from_utf8(data)?)
+ } else {
+ None
+ };
Ok(Self {
ssh_authorized_keys: spec.ssh_keys.clone(),
ssh_keys: Hostkeys::from(spec)?,
+ user_ca_pubkey,
runcmd: vec![
format!("python3 -c {}", quote(SCRIPT)),
"systemctl reload ssh".to_string(),
diff --git a/src/config.rs b/src/config.rs
index 034150b..37cd98e 100644
--- a/src/config.rs
+++ b/src/config.rs
@@ -41,6 +41,9 @@ pub struct Configuration {
/// Path name to SSH CA key for creating SSH host certificates.
pub ca_key: Option<PathBuf>,
+
+ /// Path name to SSH CA public key for verifying SSH user certificates.
+ pub user_ca_pubkey: Option<PathBuf>,
}
/// Errors from this module.
@@ -94,6 +97,7 @@ impl Configuration {
expand_optional_pathbuf(&mut self.image_directory)?;
expand_optional_pathbuf(&mut self.image_directory)?;
expand_optional_pathbuf(&mut self.ca_key)?;
+ expand_optional_pathbuf(&mut self.user_ca_pubkey)?;
expand_optional_pathbufs(&mut self.authorized_keys)?;
Ok(())
}
diff --git a/src/spec.rs b/src/spec.rs
index 150f404..58d7550 100644
--- a/src/spec.rs
+++ b/src/spec.rs
@@ -33,19 +33,32 @@ struct OneVmInputSpecification {
pub autostart: Option<bool>,
pub networks: Option<Vec<String>>,
pub ca_key: Option<PathBuf>,
+ pub user_ca_pubkey: Option<PathBuf>,
}
impl OneVmInputSpecification {
- fn ssh_key_files(
- &self,
- config: &Configuration,
- name: &str,
- ) -> Result<Vec<PathBuf>, SpecificationError> {
- get(
+ fn ssh_key_files(&self, config: &Configuration, name: &str) -> Option<Vec<PathBuf>> {
+ if let Ok(x) = get(
&self.ssh_key_files,
&config.authorized_keys,
SpecificationError::NoAuthorizedKeys(name.to_string()),
- )
+ ) {
+ Some(x)
+ } else {
+ None
+ }
+ }
+
+ fn user_ca_pubkey(&self, config: &Configuration, name: &str) -> Option<PathBuf> {
+ if let Ok(x) = get(
+ &self.user_ca_pubkey,
+ &config.user_ca_pubkey,
+ SpecificationError::NoAuthorizedKeys(name.to_string()),
+ ) {
+ Some(x)
+ } else {
+ None
+ }
}
fn base_image(
@@ -199,6 +212,9 @@ pub struct Specification {
/// Path to CA key for creating host certificate.
pub ca_key: Option<PathBuf>,
+ /// Path to CA publicv key for verifying user certificates.
+ pub user_ca_pubkey: Option<PathBuf>,
+
/// List of networks to which host should be added.
pub networks: Vec<String>,
}
@@ -226,8 +242,8 @@ pub enum SpecificationError {
#[error("No CPU count specified for {0} and no default configured")]
NoCpuCount(String),
- /// No SSH authorized keys specified.
- #[error("No SSH authorized keys specified for {0} and no default configured")]
+ /// No SSH authorized keys or user CA specified.
+ #[error("No SSH authorized keys nor user CA specified for {0} and no default configured")]
NoAuthorizedKeys(String),
/// Error reading specification file.
@@ -289,8 +305,20 @@ impl Specification {
name: &str,
input: &OneVmInputSpecification,
) -> Result<Specification, SpecificationError> {
- let key_filenames = input.ssh_key_files(config, name)?;
- let ssh_keys = ssh_keys(&key_filenames)?;
+ let ssh_keys = if let Some(key_filenames) = input.ssh_key_files(config, name) {
+ ssh_keys(&key_filenames)?
+ } else {
+ vec![]
+ };
+ let user_ca_pubkey = input.user_ca_pubkey(config, name);
+ if ssh_keys.is_empty() && user_ca_pubkey.is_none() {
+ return Err(SpecificationError::NoAuthorizedKeys(name.to_string()));
+ }
+ let user_ca_pubkey = if let Some(filename) = user_ca_pubkey {
+ Some(expand_tilde(&filename)?)
+ } else {
+ None
+ };
let ca_key = if let Some(filename) = &input.ca_key {
Some(expand_tilde(filename)?)
} else {
@@ -326,6 +354,7 @@ impl Specification {
generate_host_certificate: gen_cert,
autostart: input.autostart(config),
ca_key,
+ user_ca_pubkey,
networks,
};
diff --git a/vmadm.md b/vmadm.md
index 71a49d1..9c6d3aa 100644
--- a/vmadm.md
+++ b/vmadm.md
@@ -71,6 +71,7 @@ default_cpus: 1
default_generate_host_certificate: true
default_autostart: true
ca_key: ~/ca_key
+user_ca_pubkey: ~/user_ca_pubkey
authorized_keys:
- ~/.ssh/id_rsa.pub
~~~
@@ -88,6 +89,7 @@ authorized_keys:
"network=default"
],
"ca_key": "~/ca_key",
+ "user_ca_pubkey": "~/user_ca_pubkey",
"authorized_keys": [
"~/.ssh/id_rsa.pub"
]
@@ -122,7 +124,8 @@ foo:
"cpus": 1,
"generate_host_certificate": true,
"autostart": true,
- "ca_key": "~/other_ca"
+ "ca_key": "~/other_ca",
+ "user_ca_pubkey": "~/user_ca_pubkey"
}
]
~~~
@@ -164,6 +167,7 @@ given an installed vmadm
given file init.yaml
given file config.yaml
given file .ssh/id_rsa.pub from init_ssh_key_pub
+given file user_ca_pubkey from ssh_key_pub
given file expected/init-test/meta-data from init-metadata
given file expected/init-test/user-data from init-userdata
when I run vmadm cloud-init --config config.yaml init.yaml actual
@@ -211,6 +215,8 @@ ssh_keys:
ecdsa_certificate: ecdsa-certificate
ed25519_private: ed25519-private
ed25519_certificate: ed25519-certificate
+user_ca_pubkey: >
+ ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQChZ6mVuGLBpW7SarFU/Tu6TemquNxatbMUZuTk8RqVtbkvTKeWFZ5h5tntWPHgST8ykYFaIrr8eYuKQkKdBxHW7H8kejTNwRu/rDbRYX5wxTn4jw4RVopGTpxMlGrWeu5CkWPoLAhQtIzzUAnrDGp9sqG6P1G4ohI61wZMFQta9R2uNxXnnes+e2r4Y78GxmlQH/o0ouI8fBnsxRK0IoSfFs2LutO6wjyzR59FdC9TT7wufd5kXMRzxsmPGeXzNcaqvHGxBvRucGFclCkqSRwk3GNEpXZQhlCIoTIoRu0IPAp/430tlx9zJMhhwDlZsOOXRrFYpdWVMSTAAKECLSYx liw@exolobe1
~~~
# Create a virtual machine
@@ -228,6 +234,7 @@ given a Debian 10 OpenStack cloud image
given file smoke.yaml
given file config.yaml
given file ca_key
+given file user_ca_pubkey from ssh_key_pub
given file .ssh/id_rsa from ssh_key
given file .ssh/id_rsa.pub from ssh_key_pub
given file .ssh/config from ssh_config
@@ -290,6 +297,7 @@ given file smoke.yaml
given file other.yaml
given file config.yaml
given file ca_key
+given file user_ca_pubkey from ssh_key_pub
given file .ssh/id_rsa from ssh_key
given file .ssh/id_rsa.pub from ssh_key_pub
given file .ssh/config from ssh_config
@@ -351,6 +359,7 @@ given a Debian 10 OpenStack cloud image
given file smoke.yaml
given file config.yaml
given file ca_key
+given file user_ca_pubkey from ssh_key_pub
given file .ssh/id_rsa from ssh_key
given file .ssh/id_rsa.pub from ssh_key_pub
given file .ssh/config from ssh_config