summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLars Wirzenius <liw@liw.fi>2021-03-07 15:09:21 +0000
committerLars Wirzenius <liw@liw.fi>2021-03-07 15:09:21 +0000
commit90add4e568a581c3109fdbe1bf7b1c1a539406da (patch)
treedc4916152d7c1f488174a7297863e89bad8daa03
parenta70482822219dd9a64747b257d586d60679636c3 (diff)
parent1e8ba95def26de67f8fd618549d6b8f80a14ddd8 (diff)
downloadvmadm-90add4e568a581c3109fdbe1bf7b1c1a539406da.tar.gz
Merge branch 'cleanup' into 'main'
Cleanup and documentation Closes #9 and #13 See merge request larswirzenius/vmadm!13
-rw-r--r--README.md24
-rw-r--r--src/bin/vmadm.rs46
-rw-r--r--src/cloudinit.rs27
-rw-r--r--src/cmd/cloud_init.rs10
-rw-r--r--src/cmd/delete.rs8
-rw-r--r--src/cmd/list.rs8
-rw-r--r--src/cmd/mod.rs5
-rw-r--r--src/cmd/new.rs10
-rw-r--r--src/config.rs23
-rw-r--r--src/image.rs10
-rw-r--r--src/install.rs23
-rw-r--r--src/lib.rsbin111 -> 650 bytes
-rw-r--r--src/spec.rs57
-rw-r--r--src/sshkeys.rs37
14 files changed, 245 insertions, 43 deletions
diff --git a/README.md b/README.md
index 5525589..7c56cec 100644
--- a/README.md
+++ b/README.md
@@ -139,3 +139,27 @@ where `XXX` is the public key part of the CA key, as stored in
`~/.ssh/ca/vmadm_ca.pub` in the example above. This tells your client
that the CA key on the line should be accepted for all hosts (`*`).
You can restrict it to only some hosts if you prefer.
+
+# Setup of host
+
+The host where vmadm is run needs to have libvirt running and you must
+have access to the `qemu:///system` connection.
+The Debian wiki has some useful documentation:
+
+* <https://wiki.debian.org/libvirt>
+* <https://wiki.debian.org/KVM>
+
+I set up my own libvirt hosts using an Ansible role:
+<http://git.liw.fi/ansibleness/tree/ansible/roles/vmhost>. It works on
+Debian. The short version:
+
+* install
+ - `libvirt` (Debian packages `libvirt-daemon-system`,
+ `libvirt-daemon`, `libvirt-daemon`)
+ - `virt-install` (Debian package `virtinst`)
+ - `qemu-img` (Debian package `qemu-uttils`)
+ - NSS lookups for VMs (Debian package `libnss-libvirt`)
+ - SSH client (Debian package `openssh-client`)
+* make sure you are in the `libvirt` group
+* edit `/etc/nsswitch.conf` to have `libvirt libvirt_guest` in the
+ `hosts` line
diff --git a/src/bin/vmadm.rs b/src/bin/vmadm.rs
index 8fe5540..9b16966 100644
--- a/src/bin/vmadm.rs
+++ b/src/bin/vmadm.rs
@@ -1,18 +1,11 @@
use anyhow::Context;
use directories_next::ProjectDirs;
-use vmadm::cmd;
-use vmadm::config::Configuration;
-use vmadm::spec::Specification;
-//use bytesize::GIB;
use log::debug;
-//use std::fs;
use std::path::{Path, PathBuf};
-//use std::thread;
use structopt::StructOpt;
-//use vmadm::cloudinit::CloudInitConfig;
-//use vmadm::image::VirtualMachineImage;
-//use vmadm::install::{virt_install, VirtInstallArgs};
-//use vmadm::spec::Specification;
+use vmadm::cmd;
+use vmadm::config::Configuration;
+use vmadm::spec::Specification;
const QUALIFIER: &str = "";
const ORG: &str = "";
@@ -122,36 +115,3 @@ fn config_filename(common: &CommonOptions) -> PathBuf {
}
}
}
-
-// fn cloud_init(spec: &Path, dirname: &Path) -> anyhow::Result<()> {
-// info!("generating cloud-init configuration");
-
-// debug!("reading specification from {}", spec.display());
-// let spec = fs::read(spec)?;
-// let spec: Specification = serde_yaml::from_slice(&spec)?;
-// debug!("spec:\n{:#?}", spec);
-
-// info!("creating cloud-init config");
-// let init = CloudInitConfig::from(&spec)?;
-
-// debug!("creating directory {}", dirname.display());
-// std::fs::create_dir_all(dirname)?;
-// init.create_dir(dirname)?;
-
-// Ok(())
-// }
-
-// fn cloud_init_iso(spec: &Path, iso: &Path) -> anyhow::Result<()> {
-// info!("generating cloud-init ISO");
-
-// debug!("reading specification from {}", spec.display());
-// let spec = fs::read(spec)?;
-// let spec: Specification = serde_yaml::from_slice(&spec)?;
-// debug!("spec:\n{:#?}", spec);
-
-// info!("creating cloud-init config");
-// let init = CloudInitConfig::from(&spec)?;
-// init.create_iso(iso)?;
-
-// Ok(())
-// }
diff --git a/src/cloudinit.rs b/src/cloudinit.rs
index ccf98eb..ec4efd3 100644
--- a/src/cloudinit.rs
+++ b/src/cloudinit.rs
@@ -1,3 +1,10 @@
+//! Prepare [cloud-init][] configuration.
+//!
+//! The configuration is provided to cloud-init via the "NoCloud"
+//! route, using an ISO image file.
+//!
+//! [cloud-init]: https://cloud-init.io/
+
use crate::spec::{Specification, SpecificationError};
use crate::sshkeys::{CaKey, KeyError, KeyKind, KeyPair};
use log::debug;
@@ -92,26 +99,34 @@ log("vmadm cloud-init script ending")
logfile.close()
"#;
+/// Errors from this module.
#[derive(Debug, thiserror::Error)]
pub enum CloudInitError {
+ /// CA key is not specified.
#[error("Host certificate requested, but no CA key specified")]
NoCAKey,
+ /// Something went wrong creating ISO image with configuration.
#[error("failed to create ISO image with genisoimage: {0}")]
IsoFailed(String),
+ /// Error in the specification.
#[error(transparent)]
SpecificationError(#[from] SpecificationError),
+ /// Error in generating SSH host key or certificate.
#[error(transparent)]
KeyError(#[from] KeyError),
+ /// Something went wrong doing I/O.
#[error(transparent)]
IoError(#[from] std::io::Error),
+ /// Something went wrong parsing a string as UTF8.
#[error(transparent)]
StringError(#[from] std::string::FromUtf8Error),
+ /// Something went wrong parsing or serializing to YAML.
#[error(transparent)]
SerdeError(#[from] serde_yaml::Error),
}
@@ -252,6 +267,7 @@ impl Hostkeys {
}
}
+/// Full cloud-init configuration.
#[derive(Clone, Debug)]
pub struct CloudInitConfig {
metadata: Metadata,
@@ -259,12 +275,14 @@ pub struct CloudInitConfig {
}
impl CloudInitConfig {
+ /// Create from a specification.
pub fn from(spec: &Specification) -> Result<Self, CloudInitError> {
let metadata = Metadata::from(spec);
let userdata = Userdata::from(spec)?;
Ok(CloudInitConfig { metadata, userdata })
}
+ /// Debugging aid: dump cloud-init to stdout.
pub fn dump(&self) {
println!("==== meta-data:\n{}", self.metadata().unwrap());
println!("==== user-data:\n{}", self.userdata().unwrap());
@@ -278,6 +296,11 @@ impl CloudInitConfig {
Ok(self.userdata.as_yaml()?)
}
+ /// Put cloud-init configuration into a named directory.
+ ///
+ /// The files `meta-data` and `user-data` will be stored in the
+ /// directory. cloud-init reads them on first boot and makes
+ /// appropriate changes the host's configuration.
pub fn create_dir(&self, path: &Path) -> Result<(), CloudInitError> {
let metadata = path.join("meta-data");
debug!("writing metadata to {}", metadata.display());
@@ -300,6 +323,10 @@ impl CloudInitConfig {
Ok(())
}
+ /// Create an ISO disk image file with the cloud-init configuration.
+ ///
+ /// The image will be attached to the VM when it starts.
+ /// cloud-init finds it via the volume ID (file system label).
pub fn create_iso(&self, filename: &Path) -> Result<(), CloudInitError> {
let dir = tempdir()?;
self.create_dir(dir.path())?;
diff --git a/src/cmd/cloud_init.rs b/src/cmd/cloud_init.rs
index a9b3588..b266eb4 100644
--- a/src/cmd/cloud_init.rs
+++ b/src/cmd/cloud_init.rs
@@ -1,17 +1,27 @@
+//! The `cloud-init` sub-command.
+
use crate::cloudinit::{CloudInitConfig, CloudInitError};
use crate::spec::Specification;
use log::{debug, info};
use std::path::Path;
+/// Errors from this module.
#[derive(Debug, thiserror::Error)]
pub enum CloudInitCommandError {
+ /// Error in the cloud-init configuration.
#[error(transparent)]
CloudInitError(#[from] CloudInitError),
+ /// Error doing I/O.
#[error(transparent)]
IoError(#[from] std::io::Error),
}
+/// The `cloud-init` sub-command.
+///
+/// This sub-command generates the cloud-init configuration based on
+/// specifications provided by the caller and writes them to files in
+/// a directory named by the caller.
pub fn cloud_init(specs: &[Specification], dirname: &Path) -> Result<(), CloudInitCommandError> {
for spec in specs {
let dirname = dirname.join(&spec.name);
diff --git a/src/cmd/delete.rs b/src/cmd/delete.rs
index 3f60e27..a9a0660 100644
--- a/src/cmd/delete.rs
+++ b/src/cmd/delete.rs
@@ -1,18 +1,26 @@
+//! The `delete` sub-command.
+
use crate::spec::Specification;
use log::{debug, info};
use std::thread;
use std::time::Duration;
use virt::connect::Connect;
+/// Errors from this module.
#[derive(Debug, thiserror::Error)]
pub enum DeleteError {
+ /// Error creating virtual machine.
#[error(transparent)]
VirtError(#[from] virt::error::Error),
+ /// Error doing I/O.
#[error(transparent)]
IoError(#[from] std::io::Error),
}
+/// Delete VMs corresponding to specifications.
+///
+/// Delete the VM corresponding to each specification provided by the caller.
pub fn delete(specs: &[Specification]) -> Result<(), DeleteError> {
for spec in specs {
info!("deleting virtual machine {}", spec.name);
diff --git a/src/cmd/list.rs b/src/cmd/list.rs
index a67dfb7..c83be26 100644
--- a/src/cmd/list.rs
+++ b/src/cmd/list.rs
@@ -1,13 +1,21 @@
+//! The `list` sub-command.
+
use crate::config::Configuration;
use virt::connect::Connect;
+/// Errors returned from this module.
#[derive(Debug, thiserror::Error)]
pub enum ListError {
+ /// An error from libvirt.
#[error(transparent)]
VirtError(#[from] virt::error::Error),
}
+/// The `list` sub-command.
+///
+/// Return all the virtual machines existing on the libvirt instance,
+/// and their current state.
pub fn list(_config: &Configuration) -> Result<(), ListError> {
let conn = Connect::open("qemu:///system")?;
let domains = conn.list_all_domains(0)?;
diff --git a/src/cmd/mod.rs b/src/cmd/mod.rs
index f1e029c..606e326 100644
--- a/src/cmd/mod.rs
+++ b/src/cmd/mod.rs
@@ -1,3 +1,8 @@
+//! Implementations of command line sub-commands.
+//!
+//! This module has sub-modules with functions for all the
+//! sub-commands of the command line tool part of the crate.
+
pub mod new;
pub use new::new;
diff --git a/src/cmd/new.rs b/src/cmd/new.rs
index 0f7bc94..57f69ae 100644
--- a/src/cmd/new.rs
+++ b/src/cmd/new.rs
@@ -1,3 +1,5 @@
+//! The `new` sub-command.
+
use crate::cloudinit::{CloudInitConfig, CloudInitError};
use crate::image::{ImageError, VirtualMachineImage};
use crate::install::{virt_install, VirtInstallArgs, VirtInstallError};
@@ -9,18 +11,26 @@ use std::net::TcpStream;
const SSH_PORT: i32 = 22;
+/// Errors returned by this module.
#[derive(Debug, thiserror::Error)]
pub enum NewError {
+ /// Problem with cloud-init configuration.
#[error(transparent)]
CloudInitError(#[from] CloudInitError),
+ /// Problem creating VM image.
#[error(transparent)]
ImageError(#[from] ImageError),
+ /// Problem with libvirt.
#[error(transparent)]
VirtInstallError(#[from] VirtInstallError),
}
+/// The `new` sub-command.
+///
+/// Create all the new virtual machines specified by the caller. Wait
+/// until each VM's SSH port listens for connections.
pub fn new(specs: &[Specification]) -> Result<(), NewError> {
for spec in specs {
info!("creating new VM {}", spec.name);
diff --git a/src/config.rs b/src/config.rs
index 9f99655..9894f45 100644
--- a/src/config.rs
+++ b/src/config.rs
@@ -1,31 +1,54 @@
+//! Tool configuration.
+
use log::debug;
use serde::Deserialize;
use std::default::Default;
use std::fs;
use std::path::{Path, PathBuf};
+/// Configuration from configuration file.
#[derive(Default, Debug, Deserialize)]
pub struct Configuration {
+ /// Base image, if provided.
pub default_base_image: Option<PathBuf>,
+
+ /// Default size of new VM image, in GiB.
pub default_image_gib: Option<u64>,
+
+ /// Default memory to give to new VM, in MiB.
pub default_memory_mib: Option<u64>,
+
+ /// Default number of CPUs for a new VM.
pub default_cpus: Option<u64>,
+
+ /// Should host certificates be generated for new VMs?
pub default_generate_host_certificate: Option<bool>,
+
+ /// Directory where new VM images should be created, if given.
pub image_directory: Option<PathBuf>,
+
+ /// List of path names of SSH public keys to put into the default
+ /// user's `authorized_keys` file.
pub authorized_keys: Option<Vec<PathBuf>>,
+
+ /// Path name to SSH CA key for creating SSH host certificates.
pub ca_key: Option<PathBuf>,
}
+/// Errors from this module.
#[derive(Debug, thiserror::Error)]
pub enum ConfigurationError {
+ /// I/O error.
#[error(transparent)]
IoError(#[from] std::io::Error),
+ /// YAML parsing error.
#[error(transparent)]
YamlError(#[from] serde_yaml::Error),
}
impl Configuration {
+ /// Read configuration from named file.
pub fn from_file(filename: &Path) -> Result<Self, ConfigurationError> {
if filename.exists() {
debug!("reading configuration file {}", filename.display());
diff --git a/src/image.rs b/src/image.rs
index db6102f..62f80f7 100644
--- a/src/image.rs
+++ b/src/image.rs
@@ -1,26 +1,34 @@
+//! Virtual machine image handling.
+
use std::fs::copy;
use std::path::{Path, PathBuf};
use std::process::Command;
use std::result::Result;
+/// Errors from this module.
#[derive(Debug, thiserror::Error)]
pub enum ImageError {
+ /// Error resizing image.
#[error("qemu-img resize failed: {0}")]
ResizeError(String),
+ /// I/O error.
#[error(transparent)]
IoError(#[from] std::io::Error),
+ /// Error parsing a string as UTF8.
#[error(transparent)]
StringError(#[from] std::string::FromUtf8Error),
}
+/// A virtual machine image.
#[derive(Debug, Clone)]
pub struct VirtualMachineImage {
filename: PathBuf,
}
impl VirtualMachineImage {
+ /// Create new image from a base image.
pub fn new_from_base(base_image: &Path, image: &Path) -> Result<Self, ImageError> {
copy(base_image, image)?;
Ok(Self {
@@ -28,10 +36,12 @@ impl VirtualMachineImage {
})
}
+ /// Filename of the image.
pub fn filename(&self) -> &Path {
&self.filename
}
+ /// Change size of image.
pub fn resize(&self, new_size: u64) -> Result<(), ImageError> {
let r = Command::new("qemu-img")
.arg("resize")
diff --git a/src/install.rs b/src/install.rs
index 21b9f3f..7506e0d 100644
--- a/src/install.rs
+++ b/src/install.rs
@@ -1,24 +1,38 @@
+//! Create a new VM.
+//!
+//! This module runs the `virt-install` tool to create a new VM, using
+//! an existing image file. It attaches the cloud-init ISO
+//! configuration image to the VM.
+
use crate::cloudinit::{CloudInitConfig, CloudInitError};
use crate::image::VirtualMachineImage;
use std::process::Command;
use std::result::Result;
use tempfile::tempdir;
+/// Errors from this module
#[derive(Debug, thiserror::Error)]
pub enum VirtInstallError {
+ /// Failed to create VM.
#[error("virt-install failed: {0}")]
VirtInstallFailed(String),
+ /// I/O error.
#[error(transparent)]
IoError(#[from] std::io::Error),
+ /// Error parsing a string as UTF8.
#[error(transparent)]
StringError(#[from] std::string::FromUtf8Error),
+ /// Error from cloud-init configuration.
#[error(transparent)]
CloudInitError(#[from] CloudInitError),
}
+/// Arguments to virt-install.
+///
+/// These are the arguments we can adjust, for running virt-install.
#[derive(Debug)]
pub struct VirtInstallArgs {
name: String,
@@ -29,6 +43,7 @@ pub struct VirtInstallArgs {
}
impl VirtInstallArgs {
+ /// Create new set of arguments for virt-install.
pub fn new(name: &str, image: &VirtualMachineImage, init: &CloudInitConfig) -> Self {
Self {
name: name.to_string(),
@@ -39,35 +54,43 @@ impl VirtInstallArgs {
}
}
+ /// Name for new VM.
pub fn name(&self) -> &str {
&self.name
}
+ /// Memory for new VM, in MiB.
pub fn memory(&self) -> u64 {
self.memory
}
+ /// Change memory to give to new VM, in MiB.
pub fn set_memory(&mut self, memory: u64) {
self.memory = memory
}
+ /// Virtual CPUs for new VM.
pub fn vcpus(&self) -> u64 {
self.vcpus
}
+ /// Change virtual CPUs for new VM.
pub fn set_vcpus(&mut self, vcpus: u64) {
self.vcpus = vcpus
}
+ /// Image for new VM.
pub fn image(&self) -> &VirtualMachineImage {
&self.image
}
+ /// cloud-init configuration for new VM.
pub fn init(&self) -> &CloudInitConfig {
&self.init
}
}
+/// Create new VM with virt-install.
pub fn virt_install(args: &VirtInstallArgs) -> Result<(), VirtInstallError> {
let dir = tempdir()?;
let iso = dir.path().join("cloudinit.iso");
diff --git a/src/lib.rs b/src/lib.rs
index bddc1ad..e571e80 100644
--- a/src/lib.rs
+++ b/src/lib.rs
Binary files differ
diff --git a/src/spec.rs b/src/spec.rs
index 2c13af7..fcf6eab 100644
--- a/src/spec.rs
+++ b/src/spec.rs
@@ -1,3 +1,5 @@
+//! Virtual machine specification.
+
use crate::config::Configuration;
use log::debug;
@@ -115,62 +117,117 @@ where
}
}
+/// Effective virtual machine specification.
+///
+/// This is the specification as read from the input file, with the
+/// defaults from the configuration file already applied.
#[derive(Debug)]
pub struct Specification {
+ /// Name of new virtual machine to create.
pub name: String,
+
+ /// SSH public keys to install in the default user's `authorized_keys` file.
pub ssh_keys: Vec<String>,
+
+ /// RSA host key to install in new VM.
pub rsa_host_key: Option<String>,
+
+ /// RSA host certificate.
pub rsa_host_cert: Option<String>,
+
+ /// DSA host key to install in new VM.
pub dsa_host_key: Option<String>,
+
+ /// DSA host certificate.
pub dsa_host_cert: Option<String>,
+
+ /// ECDSA host key to install in new VM.
pub ecdsa_host_key: Option<String>,
+
+ /// ECDSA host certificate.
pub ecdsa_host_cert: Option<String>,
+
+ /// Ed25519 host key to install in new VM.
pub ed25519_host_key: Option<String>,
+
+ /// Ed25519 host certificate.
pub ed25519_host_cert: Option<String>,
+ /// Path to base image.
pub base: PathBuf,
+
+ /// Path to new VM image, to be created.
pub image: PathBuf,
+
+ /// Size of new image, in GiB.
pub image_size_gib: u64,
+
+ /// Size of memory for new VM, in MiB.
pub memory_mib: u64,
+
+ /// CPUs new VM should have.
pub cpus: u64,
+
+ /// Should a new host key and certificate be created for new VM?
pub generate_host_certificate: bool,
+
+ /// Path to CA key for creating host certificate.
pub ca_key: Option<PathBuf>,
}
+/// Errors from this module.
#[derive(Debug, thiserror::Error)]
pub enum SpecificationError {
+ /// No base image file specified.
#[error("No base image or default base image specified for {0}")]
NoBaseImage(String),
+ /// No image file specified.
#[error("No image filename specified for {0} and no image_directory in configuration")]
NoImage(String),
+ /// No image size specified.
#[error("No image size specified for {0} and no default configured")]
NoImageSize(String),
+ /// No memory size specified.
#[error("No memory size specified for {0} and no default configured")]
NoMemorySize(String),
+ /// No CPU count specified.
#[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")]
NoAuthorizedKeys(String),
+ /// Error reading SSH public key.
#[error("Failed to read SSH public key file {0}")]
SshKeyRead(PathBuf, #[source] std::io::Error),
+ /// I/O error.
#[error(transparent)]
IoError(#[from] std::io::Error),
+ /// Error parsing string as UTF8.
#[error(transparent)]
FromUtf8Error(#[from] std::string::FromUtf8Error),
+ /// Error parsing YAML.
#[error(transparent)]
YamlError(#[from] serde_yaml::Error),
}
impl Specification {
+ /// Read all specifications from a file.
+ ///
+ /// Apply values from the provided configuration so that the
+ /// returned specifications are *effective* and the caller doesn't
+ /// need to worry about the configuration anymore.
+ ///
+ /// Also, SSH public keys are read from the files named in the
+ /// input specification.
pub fn from_file(
config: &Configuration,
filename: &Path,
diff --git a/src/sshkeys.rs b/src/sshkeys.rs
index 207f6fe..813a26d 100644
--- a/src/sshkeys.rs
+++ b/src/sshkeys.rs
@@ -1,3 +1,5 @@
+//! Generate SSH host keys and certificates.
+
use std::fs::{read, File, Permissions};
use std::io::Write;
use std::os::unix::fs::PermissionsExt;
@@ -5,31 +7,49 @@ use std::path::Path;
use std::process::Command;
use tempfile::tempdir;
+/// Errors from this module.
#[derive(Debug, thiserror::Error)]
pub enum KeyError {
+ /// Could not generate a new key pair.
#[error("ssh-keygen failed to generate a key: {0}")]
KeyGen(String),
+ /// Error creating a certificate.
#[error("ssh-keygen failed to certify a key: {0}")]
CertError(String),
+ /// I/O error.
#[error(transparent)]
IoError(#[from] std::io::Error),
+ /// Error parsing a string as UTF8.
#[error(transparent)]
Utf8Error(#[from] std::string::FromUtf8Error),
}
+/// Type of SSH key.
pub enum KeyKind {
+ /// RSA key of desired length in bits.
RSA(u32),
+
+ /// DSA of fixed length.
DSA,
+
+ /// ECDSA key of 256 bits.
ECDSA256,
+
+ /// ECDSA key of 384 bits.
ECDSA384,
+
+ /// ECDSA key of 521 bits.
ECDSA521,
+
+ /// Ed25519 key of fixed length.
Ed25519,
}
impl KeyKind {
+ /// Type of key as string for ssh-keygen -t option.
pub fn as_str(&self) -> &str {
match self {
Self::RSA(_) => "rsa",
@@ -41,6 +61,9 @@ impl KeyKind {
}
}
+ /// Number of bits needed for the key.
+ ///
+ /// This is only really meaningful for RSA keys.
pub fn bits(&self) -> u32 {
match self {
Self::RSA(bits) => *bits,
@@ -53,12 +76,14 @@ impl KeyKind {
}
}
+/// A public/private key pair.
pub struct KeyPair {
public: String,
private: String,
}
impl KeyPair {
+ /// Create pair from string representation.
pub fn from_str(public: String, private: String) -> Self {
Self {
private: private,
@@ -66,6 +91,7 @@ impl KeyPair {
}
}
+ /// Generate a new key pair of the desired kind.
pub fn generate(kind: KeyKind) -> Result<Self, KeyError> {
let dirname = tempdir()?;
let private_key = dirname.path().join("key");
@@ -93,9 +119,12 @@ impl KeyPair {
))
}
+ /// Public key of the pair, as a string.
pub fn public(&self) -> &str {
&self.public
}
+
+ /// Private key of the pair, as a string.
pub fn private(&self) -> &str {
&self.private
}
@@ -106,22 +135,30 @@ fn read_string(filename: &Path) -> Result<String, KeyError> {
Ok(String::from_utf8(bytes)?)
}
+/// A key for SSH certificate authority.
+///
+/// This is used for creating host certificates.
pub struct CaKey {
private: String,
}
impl CaKey {
+ /// Create new CA key from a key pair.
pub fn from(pair: KeyPair) -> Self {
Self {
private: pair.private().to_string(),
}
}
+ /// Read CA key from a file.
pub fn from_file(filename: &Path) -> Result<Self, KeyError> {
let private = read_string(filename)?;
Ok(Self { private })
}
+ /// Create a host certificate.
+ ///
+ /// Return as a string.
pub fn certify_host(&self, host_key: &KeyPair, hostname: &str) -> Result<String, KeyError> {
let dirname = tempdir()?;
let ca_key = dirname.path().join("ca");