ca/user: Add certificate extensions
dustin/sshca/pipeline/head This commit looks good
Details
dustin/sshca/pipeline/head This commit looks good
Details
According to the *sshd(8)* manual page: > Certificates may encode access restrictions similar to these key > options. If both certificate restrictions and key options are > present, the most restrictive union of the two is applied. This would seem to apply that if a certificate has no restrictions, all features are allowed unless restricted in the `authorized_keys` file. Unfortunately, this is not actually the case. A certificate with no extensions apparently trumps all other configuration. As such, certificates need to explicitly list the features users will need. The list of extensions to add to user certificates is configurable via the `ca.user.extensions` array. The default set should provide a good user experience without being overly permissive.master
parent
f38f9d9f6e
commit
f9ebbbcce9
38
src/ca.rs
38
src/ca.rs
|
@ -144,7 +144,20 @@ pub fn sign_host_cert(
|
||||||
privkey: &PrivateKey,
|
privkey: &PrivateKey,
|
||||||
alias: &[&str],
|
alias: &[&str],
|
||||||
) -> Result<Certificate, CertError> {
|
) -> Result<Certificate, CertError> {
|
||||||
sign_cert(hostname, pubkey, duration, privkey, alias, CertType::Host)
|
let now = SystemTime::now();
|
||||||
|
let not_before = now.duration_since(UNIX_EPOCH)?.as_secs();
|
||||||
|
let not_after = not_before + duration.as_secs();
|
||||||
|
|
||||||
|
let mut builder = Builder::new_with_random_nonce(
|
||||||
|
&mut OsRng, pubkey, not_before, not_after,
|
||||||
|
)?;
|
||||||
|
builder.cert_type(CertType::Host)?;
|
||||||
|
builder.valid_principal(hostname)?;
|
||||||
|
for a in alias {
|
||||||
|
builder.valid_principal(*a)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(builder.sign(privkey)?)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a signed SSH certificate for a user public key
|
/// Create a signed SSH certificate for a user public key
|
||||||
|
@ -158,17 +171,7 @@ pub fn sign_user_cert(
|
||||||
duration: Duration,
|
duration: Duration,
|
||||||
privkey: &PrivateKey,
|
privkey: &PrivateKey,
|
||||||
alias: &[&str],
|
alias: &[&str],
|
||||||
) -> Result<Certificate, CertError> {
|
extensions: &[impl AsRef<str>],
|
||||||
sign_cert(username, pubkey, duration, privkey, alias, CertType::User)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn sign_cert(
|
|
||||||
principal: &str,
|
|
||||||
pubkey: &PublicKey,
|
|
||||||
duration: Duration,
|
|
||||||
privkey: &PrivateKey,
|
|
||||||
alias: &[&str],
|
|
||||||
cert_type: CertType,
|
|
||||||
) -> Result<Certificate, CertError> {
|
) -> Result<Certificate, CertError> {
|
||||||
let now = SystemTime::now();
|
let now = SystemTime::now();
|
||||||
let not_before = now.duration_since(UNIX_EPOCH)?.as_secs();
|
let not_before = now.duration_since(UNIX_EPOCH)?.as_secs();
|
||||||
|
@ -177,11 +180,14 @@ fn sign_cert(
|
||||||
let mut builder = Builder::new_with_random_nonce(
|
let mut builder = Builder::new_with_random_nonce(
|
||||||
&mut OsRng, pubkey, not_before, not_after,
|
&mut OsRng, pubkey, not_before, not_after,
|
||||||
)?;
|
)?;
|
||||||
builder.cert_type(cert_type)?;
|
builder.cert_type(CertType::User)?;
|
||||||
builder.valid_principal(principal)?;
|
builder.valid_principal(username)?;
|
||||||
for a in alias {
|
for a in alias {
|
||||||
builder.valid_principal(*a)?;
|
builder.valid_principal(*a)?;
|
||||||
}
|
}
|
||||||
|
for e in extensions {
|
||||||
|
builder.extension(e.as_ref(), "")?;
|
||||||
|
}
|
||||||
|
|
||||||
Ok(builder.sign(privkey)?)
|
Ok(builder.sign(privkey)?)
|
||||||
}
|
}
|
||||||
|
@ -201,9 +207,9 @@ mod test {
|
||||||
let host_pub_key = host_key.public_key();
|
let host_pub_key = host_key.public_key();
|
||||||
let duration = Duration::from_secs(86400 * 30);
|
let duration = Duration::from_secs(86400 * 30);
|
||||||
let hostname = "cloud0.example.org";
|
let hostname = "cloud0.example.org";
|
||||||
let cert = sign_cert(
|
let cert = sign_host_cert(
|
||||||
hostname,
|
hostname,
|
||||||
&host_pub_key,
|
host_pub_key,
|
||||||
duration,
|
duration,
|
||||||
&ca_key,
|
&ca_key,
|
||||||
&["nextcloud.example.org"],
|
&["nextcloud.example.org"],
|
||||||
|
|
|
@ -75,6 +75,10 @@ pub struct UserCaConfig {
|
||||||
/// Duration of issued user certificates
|
/// Duration of issued user certificates
|
||||||
#[serde(default = "default_user_cert_duration")]
|
#[serde(default = "default_user_cert_duration")]
|
||||||
pub cert_duration: u64,
|
pub cert_duration: u64,
|
||||||
|
|
||||||
|
/// Certificate extensions
|
||||||
|
#[serde(default = "default_user_cert_extensions")]
|
||||||
|
pub extensions: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for UserCaConfig {
|
impl Default for UserCaConfig {
|
||||||
|
@ -83,6 +87,7 @@ impl Default for UserCaConfig {
|
||||||
private_key_file: default_user_ca_key(),
|
private_key_file: default_user_ca_key(),
|
||||||
private_key_passphrase_file: None,
|
private_key_passphrase_file: None,
|
||||||
cert_duration: default_user_cert_duration(),
|
cert_duration: default_user_cert_duration(),
|
||||||
|
extensions: default_user_cert_extensions(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -171,6 +176,16 @@ fn default_user_cert_duration() -> u64 {
|
||||||
3600
|
3600
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn default_user_cert_extensions() -> Vec<String> {
|
||||||
|
vec![
|
||||||
|
"permit-X11-forwarding".into(),
|
||||||
|
"permit-agent-forwarding".into(),
|
||||||
|
"permit-port-forwarding".into(),
|
||||||
|
"permit-pty".into(),
|
||||||
|
"permit-user-rc".into(),
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
/// Load configuration from a TOML file
|
/// Load configuration from a TOML file
|
||||||
///
|
///
|
||||||
/// If `path` is provided, the configuration will be loaded from the
|
/// If `path` is provided, the configuration will be loaded from the
|
||||||
|
|
|
@ -168,6 +168,7 @@ pub(super) async fn sign_user_cert(
|
||||||
|
|
||||||
let config = &ctx.config;
|
let config = &ctx.config;
|
||||||
let duration = Duration::from_secs(config.ca.user.cert_duration);
|
let duration = Duration::from_secs(config.ca.user.cert_duration);
|
||||||
|
let extensions = &config.ca.user.extensions;
|
||||||
let privkey = ca::load_private_key(
|
let privkey = ca::load_private_key(
|
||||||
&config.ca.user.private_key_file,
|
&config.ca.user.private_key_file,
|
||||||
config.ca.user.private_key_passphrase_file.as_ref(),
|
config.ca.user.private_key_passphrase_file.as_ref(),
|
||||||
|
@ -192,7 +193,14 @@ pub(super) async fn sign_user_cert(
|
||||||
pubkey.algorithm().as_str(),
|
pubkey.algorithm().as_str(),
|
||||||
username
|
username
|
||||||
);
|
);
|
||||||
let cert = ca::sign_user_cert(username, &pubkey, duration, &privkey, &alias)?;
|
let cert = ca::sign_user_cert(
|
||||||
|
username,
|
||||||
|
&pubkey,
|
||||||
|
duration,
|
||||||
|
&privkey,
|
||||||
|
&alias,
|
||||||
|
&extensions[..],
|
||||||
|
)?;
|
||||||
info!(
|
info!(
|
||||||
"Signed {} key for {}",
|
"Signed {} key for {}",
|
||||||
pubkey.algorithm().as_str(),
|
pubkey.algorithm().as_str(),
|
||||||
|
|
Loading…
Reference in New Issue