Enforce SSH workflow capabilities locally
This commit is contained in:
parent
c9803ea7f2
commit
f7e85960f7
8 changed files with 328 additions and 62 deletions
|
|
@ -2965,7 +2965,15 @@ pub fn handle_request(
|
|||
requested_validity,
|
||||
renewal_of,
|
||||
reason,
|
||||
subject,
|
||||
} => {
|
||||
ensure_subject_authorized(
|
||||
&store,
|
||||
node,
|
||||
subject.as_deref(),
|
||||
"resource:ssh:certs",
|
||||
"ssh_cert.request",
|
||||
)?;
|
||||
if principals.is_empty() {
|
||||
return Err(NodeError::MissingSshCertPrincipal);
|
||||
}
|
||||
|
|
@ -2995,20 +3003,37 @@ pub fn handle_request(
|
|||
store.insert_ssh_cert_request(&stored_from_ssh_cert_request(&request))?;
|
||||
Ok(ControlResponse::SshCertRequested { request })
|
||||
}
|
||||
ControlRequest::SshCertRequests => Ok(ControlResponse::SshCertRequests {
|
||||
requests: store
|
||||
.list_ssh_cert_requests()?
|
||||
.into_iter()
|
||||
.map(ssh_cert_request_from_stored)
|
||||
.collect::<Result<Vec<_>, _>>()?,
|
||||
}),
|
||||
ControlRequest::SshCertRequests { subject } => {
|
||||
ensure_subject_authorized(
|
||||
&store,
|
||||
node,
|
||||
subject.as_deref(),
|
||||
"resource:ssh:certs",
|
||||
"ssh_cert.read",
|
||||
)?;
|
||||
Ok(ControlResponse::SshCertRequests {
|
||||
requests: store
|
||||
.list_ssh_cert_requests()?
|
||||
.into_iter()
|
||||
.map(ssh_cert_request_from_stored)
|
||||
.collect::<Result<Vec<_>, _>>()?,
|
||||
})
|
||||
}
|
||||
ControlRequest::SshCertApprove {
|
||||
request_id,
|
||||
ca_key_path,
|
||||
valid_for,
|
||||
serial,
|
||||
out,
|
||||
subject,
|
||||
} => {
|
||||
ensure_subject_authorized(
|
||||
&store,
|
||||
node,
|
||||
subject.as_deref(),
|
||||
"resource:ssh:certs",
|
||||
"ssh_cert.approve",
|
||||
)?;
|
||||
let stored = store
|
||||
.get_ssh_cert_request(&request_id)?
|
||||
.ok_or_else(|| NodeError::SshCertRequestNotFound(request_id.clone()))?;
|
||||
|
|
@ -3051,7 +3076,15 @@ pub fn handle_request(
|
|||
ControlRequest::SshCertImport {
|
||||
request_id,
|
||||
cert_path,
|
||||
subject,
|
||||
} => {
|
||||
ensure_subject_authorized(
|
||||
&store,
|
||||
node,
|
||||
subject.as_deref(),
|
||||
"resource:ssh:certs",
|
||||
"ssh_cert.import",
|
||||
)?;
|
||||
let certificate = std::fs::read_to_string(&cert_path)?;
|
||||
let record = SshCertificateRecord {
|
||||
id: certificate_id(&certificate),
|
||||
|
|
@ -3069,23 +3102,40 @@ pub fn handle_request(
|
|||
certificate: record,
|
||||
})
|
||||
}
|
||||
ControlRequest::SshCertList => Ok(ControlResponse::SshCertList {
|
||||
requests: store
|
||||
.list_ssh_cert_requests()?
|
||||
.into_iter()
|
||||
.map(ssh_cert_request_from_stored)
|
||||
.collect::<Result<Vec<_>, _>>()?,
|
||||
certificates: store
|
||||
.list_ssh_certificates()?
|
||||
.into_iter()
|
||||
.map(ssh_certificate_from_stored)
|
||||
.collect(),
|
||||
}),
|
||||
ControlRequest::SshCertList { subject } => {
|
||||
ensure_subject_authorized(
|
||||
&store,
|
||||
node,
|
||||
subject.as_deref(),
|
||||
"resource:ssh:certs",
|
||||
"ssh_cert.read",
|
||||
)?;
|
||||
Ok(ControlResponse::SshCertList {
|
||||
requests: store
|
||||
.list_ssh_cert_requests()?
|
||||
.into_iter()
|
||||
.map(ssh_cert_request_from_stored)
|
||||
.collect::<Result<Vec<_>, _>>()?,
|
||||
certificates: store
|
||||
.list_ssh_certificates()?
|
||||
.into_iter()
|
||||
.map(ssh_certificate_from_stored)
|
||||
.collect(),
|
||||
})
|
||||
}
|
||||
ControlRequest::SshRevocationAdd {
|
||||
kind,
|
||||
target,
|
||||
reason,
|
||||
subject,
|
||||
} => {
|
||||
ensure_subject_authorized(
|
||||
&store,
|
||||
node,
|
||||
subject.as_deref(),
|
||||
"resource:ssh:revocations",
|
||||
"ssh_revocation.publish",
|
||||
)?;
|
||||
let kind = kind
|
||||
.parse::<SshRevocationKind>()
|
||||
.map_err(|_| NodeError::InvalidSshRevocationKind(kind.clone()))?;
|
||||
|
|
@ -3101,18 +3151,35 @@ pub fn handle_request(
|
|||
store.insert_ssh_revocation(&stored_from_ssh_revocation(&revocation))?;
|
||||
Ok(ControlResponse::SshRevocationAdded { revocation })
|
||||
}
|
||||
ControlRequest::SshRevocationList => Ok(ControlResponse::SshRevocationList {
|
||||
revocations: store
|
||||
.list_ssh_revocations()?
|
||||
.into_iter()
|
||||
.map(ssh_revocation_from_stored)
|
||||
.collect::<Result<Vec<_>, _>>()?,
|
||||
}),
|
||||
ControlRequest::SshRevocationList { subject } => {
|
||||
ensure_subject_authorized(
|
||||
&store,
|
||||
node,
|
||||
subject.as_deref(),
|
||||
"resource:ssh:revocations",
|
||||
"ssh_revocation.read",
|
||||
)?;
|
||||
Ok(ControlResponse::SshRevocationList {
|
||||
revocations: store
|
||||
.list_ssh_revocations()?
|
||||
.into_iter()
|
||||
.map(ssh_revocation_from_stored)
|
||||
.collect::<Result<Vec<_>, _>>()?,
|
||||
})
|
||||
}
|
||||
ControlRequest::SshRevocationExport {
|
||||
out,
|
||||
format,
|
||||
ca_public,
|
||||
subject,
|
||||
} => {
|
||||
ensure_subject_authorized(
|
||||
&store,
|
||||
node,
|
||||
subject.as_deref(),
|
||||
"resource:ssh:revocations",
|
||||
"ssh_revocation.read",
|
||||
)?;
|
||||
let revocations = store
|
||||
.list_ssh_revocations()?
|
||||
.into_iter()
|
||||
|
|
@ -3158,7 +3225,18 @@ pub fn handle_request(
|
|||
note,
|
||||
})
|
||||
}
|
||||
ControlRequest::SshRevocationImport { path, format } => {
|
||||
ControlRequest::SshRevocationImport {
|
||||
path,
|
||||
format,
|
||||
subject,
|
||||
} => {
|
||||
ensure_subject_authorized(
|
||||
&store,
|
||||
node,
|
||||
subject.as_deref(),
|
||||
"resource:ssh:revocations",
|
||||
"ssh_revocation.import",
|
||||
)?;
|
||||
let body = std::fs::read_to_string(&path)?;
|
||||
let created_at = UnixMillis(geth_store::now_ms());
|
||||
let revocations = match format.as_str() {
|
||||
|
|
@ -3672,6 +3750,20 @@ fn ensure_kv_write_authorized(
|
|||
subject: Option<String>,
|
||||
resource_id: &str,
|
||||
key: &str,
|
||||
) -> Result<(), NodeError> {
|
||||
let Some(subject) = subject else {
|
||||
return Ok(());
|
||||
};
|
||||
let capability = format!("kv.write_key:{key}");
|
||||
ensure_subject_authorized(store, node, Some(&subject), resource_id, &capability)
|
||||
}
|
||||
|
||||
fn ensure_subject_authorized(
|
||||
store: &Store,
|
||||
node: &LocalNode,
|
||||
subject: Option<&str>,
|
||||
resource_id: &str,
|
||||
capability: &str,
|
||||
) -> Result<(), NodeError> {
|
||||
let Some(subject) = subject else {
|
||||
return Ok(());
|
||||
|
|
@ -3680,13 +3772,12 @@ fn ensure_kv_write_authorized(
|
|||
return Ok(());
|
||||
}
|
||||
|
||||
let capability = format!("kv.write_key:{key}");
|
||||
let ops = load_auth_ops_for_resource(store, resource_id)?;
|
||||
let explanation = geth_auth::explain_auth_ops(
|
||||
&ops,
|
||||
PrincipalId::new(subject.clone()),
|
||||
PrincipalId::new(subject.to_owned()),
|
||||
ResourceId::new(resource_id.to_owned()),
|
||||
Capability::new(capability.clone()),
|
||||
Capability::new(capability.to_owned()),
|
||||
);
|
||||
if explanation.allowed {
|
||||
Ok(())
|
||||
|
|
@ -4241,6 +4332,7 @@ mod tests {
|
|||
requested_validity: Some("+52w".to_owned()),
|
||||
renewal_of: None,
|
||||
reason: Some("test sync".to_owned()),
|
||||
subject: None,
|
||||
},
|
||||
)
|
||||
.expect("right ssh cert request");
|
||||
|
|
@ -4254,6 +4346,7 @@ mod tests {
|
|||
kind: "key-id".to_owned(),
|
||||
target: "old-node-key".to_owned(),
|
||||
reason: Some("test sync".to_owned()),
|
||||
subject: None,
|
||||
},
|
||||
)
|
||||
.expect("right ssh revocation");
|
||||
|
|
@ -5004,6 +5097,7 @@ mod tests {
|
|||
requested_validity: Some("+4w".to_owned()),
|
||||
renewal_of: None,
|
||||
reason: Some("background sync test".to_owned()),
|
||||
subject: None,
|
||||
},
|
||||
)
|
||||
.expect("right second ssh cert request");
|
||||
|
|
@ -5017,6 +5111,7 @@ mod tests {
|
|||
kind: "key-id".to_owned(),
|
||||
target: "newly-revoked-key".to_owned(),
|
||||
reason: Some("background sync test".to_owned()),
|
||||
subject: None,
|
||||
},
|
||||
)
|
||||
.expect("right second ssh revocation");
|
||||
|
|
|
|||
Loading…
Reference in a new issue