Add compute_ctl:external_api JWT scope

It allows access to the compute_ctl external HTTP server.

Signed-off-by: Tristan Partin <tristan@neon.tech>
This commit is contained in:
Tristan Partin
2025-05-30 15:19:03 -05:00
parent 8b75265231
commit a393967ba4
5 changed files with 53 additions and 35 deletions
+44 -32
View File
@@ -64,45 +64,57 @@ impl AsyncAuthorizeRequest<Body> for Authorize {
};
match data.claims.scope {
// TODO: We should validate audience for every token, but
// instead of this ad-hoc validation, we should turn
// [`Validation::validate_aud`] on. This is merely a stopgap
// while we roll out `aud` deployment. We return a 401
// Unauthorized because when we eventually do use
// [`Validation`], we will hit the above `Err` match arm which
// returns 401 Unauthorized.
Some(ref scope) if scope.contains(&ComputeClaimsScope::Admin) => {
let Some(ref audience) = data.claims.audience else {
return Err(JsonResponse::error(
StatusCode::UNAUTHORIZED,
"missing audience in authorization token claims",
));
};
// If the scope is not [`ComputeClaimsScope::Admin`], then we
// must validate the compute_id
Some(ref scope) => {
// TODO: We should validate audience for every token, but
// instead of this ad-hoc validation, we should turn
// [`Validation::validate_aud`] on. This is merely a stopgap
// while we roll out `aud` deployment. We return a 401
// Unauthorized because when we eventually do use
// [`Validation`], we will hit the above `Err` match arm which
// returns 401 Unauthorized.
if scope.contains(&ComputeClaimsScope::Admin) {
let Some(ref audience) = data.claims.audience else {
return Err(JsonResponse::error(
StatusCode::UNAUTHORIZED,
"missing audience in authorization token claims",
));
};
if !audience.iter().any(|a| a == COMPUTE_AUDIENCE) {
if !audience.iter().any(|a| a == COMPUTE_AUDIENCE) {
return Err(JsonResponse::error(
StatusCode::UNAUTHORIZED,
"invalid audience in authorization token claims",
));
}
} else if scope.contains(&ComputeClaimsScope::ComputeCtlExternalApi) {
let Some(ref claimed_compute_id) = data.claims.compute_id else {
return Err(JsonResponse::error(
StatusCode::FORBIDDEN,
"missing compute_id in authorization token claims",
));
};
if *claimed_compute_id != compute_id {
return Err(JsonResponse::error(
StatusCode::FORBIDDEN,
"invalid compute ID in authorization token claims",
));
}
} else {
return Err(JsonResponse::error(
StatusCode::UNAUTHORIZED,
"invalid audience in authorization token claims",
"compute_ctl:external_api scope not included",
));
}
}
// If the scope is not [`ComputeClaimsScope::Admin`], then we
// must validate the compute_id
_ => {
let Some(ref claimed_compute_id) = data.claims.compute_id else {
return Err(JsonResponse::error(
StatusCode::FORBIDDEN,
"missing compute_id in authorization token claims",
));
};
if *claimed_compute_id != compute_id {
return Err(JsonResponse::error(
StatusCode::FORBIDDEN,
"invalid compute ID in authorization token claims",
));
}
None => {
return Err(JsonResponse::error(
StatusCode::UNAUTHORIZED,
"compute_ctl:external_api scope not included",
));
}
}
+2 -2
View File
@@ -920,7 +920,7 @@ impl Endpoint {
self.external_http_address.port()
),
)
.bearer_auth(self.generate_jwt(None::<Vec<ComputeClaimsScope>>)?)
.bearer_auth(self.generate_jwt(Some(vec![ComputeClaimsScope::ComputeCtlExternalApi]))?)
.send()
.await?;
@@ -997,7 +997,7 @@ impl Endpoint {
self.external_http_address.port()
))
.header(CONTENT_TYPE.as_str(), "application/json")
.bearer_auth(self.generate_jwt(None::<Vec<ComputeClaimsScope>>)?)
.bearer_auth(self.generate_jwt(Some(vec![ComputeClaimsScope::ComputeCtlExternalApi]))?)
.body(
serde_json::to_string(&ConfigurationRequest {
spec,
+5
View File
@@ -17,6 +17,10 @@ pub enum ComputeClaimsScope {
/// An admin-scoped token allows access to all of `compute_ctl`'s authorized
/// facilities.
Admin,
/// Scope providing access to the `compute_ctl` external API.
#[serde(rename = "compute_ctl:external_api")]
ComputeCtlExternalApi,
}
impl FromStr for ComputeClaimsScope {
@@ -25,6 +29,7 @@ impl FromStr for ComputeClaimsScope {
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"admin" => Ok(ComputeClaimsScope::Admin),
"compute_ctl:external_api" => Ok(ComputeClaimsScope::ComputeCtlExternalApi),
_ => Err(anyhow::anyhow!("invalid compute claims scope \"{s}\"")),
}
}
+1
View File
@@ -25,6 +25,7 @@ The value to place in the `aud` claim.
@final
class ComputeClaimsScope(StrEnum):
ADMIN = "admin"
COMPUTE_CTL_EXTERNAL_API = "compute_ctl:external_api"
@final
+1 -1
View File
@@ -4282,7 +4282,7 @@ class Endpoint(PgProtocol, LogUtils):
self.config(config_lines)
self.__jwt = self.generate_jwt([])
self.__jwt = self.generate_jwt([ComputeClaimsScope.COMPUTE_CTL_EXTERNAL_API])
return self