Skip to content

Commit

Permalink
feat: add admission validation for grpcroute resources
Browse files Browse the repository at this point in the history
Signed-off-by: Mark S <the@wondersmith.dev>
  • Loading branch information
the-wondersmith committed May 1, 2024
1 parent 0bf9f06 commit 1b16e99
Show file tree
Hide file tree
Showing 2 changed files with 79 additions and 0 deletions.
4 changes: 4 additions & 0 deletions charts/linkerd-control-plane/templates/destination-rbac.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,7 @@ webhooks:
apiVersions: ["*"]
resources:
- httproutes
- grpcroutes
sideEffects: None
---
apiVersion: rbac.authorization.k8s.io/v1
Expand Down Expand Up @@ -233,6 +234,7 @@ rules:
- gateway.networking.k8s.io
resources:
- httproutes
- grpcroutes
verbs:
- get
- list
Expand All @@ -247,8 +249,10 @@ rules:
- gateway.networking.k8s.io
resources:
- httproutes/status
- grpcroutes/status
verbs:
- patch
- create
- apiGroups:
- workload.linkerd.io
resources:
Expand Down
75 changes: 75 additions & 0 deletions policy-controller/src/admission.rs
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,10 @@ impl Admission {
return self.admit_spec::<k8s_gateway_api::HttpRouteSpec>(req).await;
}

if is_kind::<k8s_gateway_api::GrpcRoute>(&req) {
return self.admit_spec::<k8s_gateway_api::GrpcRouteSpec>(req).await;
}

AdmissionResponse::invalid(format_args!(
"unsupported resource type: {}.{}.{}",
req.kind.group, req.kind.version, req.kind.kind
Expand Down Expand Up @@ -441,6 +445,7 @@ impl Validate<ServerAuthorizationSpec> for Admission {
}

use index::http_route;

fn validate_match(
httproute::HttpRouteMatch {
path,
Expand Down Expand Up @@ -579,3 +584,73 @@ impl Validate<k8s_gateway_api::HttpRouteSpec> for Admission {
Ok(())
}
}

#[async_trait::async_trait]
impl Validate<k8s_gateway_api::GrpcRouteSpec> for Admission {
async fn validate(
self,
_ns: &str,
_name: &str,
spec: k8s_gateway_api::GrpcRouteSpec,
) -> Result<()> {
fn validate_filter(filter: k8s_gateway_api::GrpcRouteFilter) -> Result<()> {
match filter {
k8s_gateway_api::GrpcRouteFilter::RequestHeaderModifier {
request_header_modifier,
} => http_route::header_modifier(request_header_modifier).map(|_| ()),
k8s_gateway_api::GrpcRouteFilter::ResponseHeaderModifier {
response_header_modifier,
} => http_route::header_modifier(response_header_modifier).map(|_| ()),
k8s_gateway_api::GrpcRouteFilter::RequestMirror { .. } => Ok(()),
k8s_gateway_api::GrpcRouteFilter::ExtensionRef { .. } => Ok(()),
}
}

fn validate_match_rule(
k8s_gateway_api::GrpcRouteMatch { method, headers }: k8s_gateway_api::GrpcRouteMatch,
) -> Result<()> {
if let Some(method_match) = method {
let (method_name, service_name) = match method_match {
k8s_gateway_api::GrpcMethodMatch::Exact { method, service } => {
(method, service)
}
k8s_gateway_api::GrpcMethodMatch::RegularExpression { method, service } => {
(method, service)
}
};

if method_name.as_deref().map(str::is_empty).unwrap_or(true)
&& service_name.as_deref().map(str::is_empty).unwrap_or(true)
{
bail!("at least one of GrpcMethodMatch.Service and GrpcMethodMatch.Method MUST be a non-empty string")
}
}

for rule in headers.into_iter().flatten() {
http_route::header_match(rule)?;
}

Ok(())
}

// Validate the rules in this spec.
// This is essentially just a check to ensure that none
// of the rules are improperly constructed (e.g. include
// a `GrpcMethodMatch` rule where neither `method.method`
// nor `method.service` actually contain a value)
for k8s_gateway_api::GrpcRouteRule {
filters, matches, ..
} in spec.rules.into_iter().flatten()
{
for rule in matches.into_iter().flatten() {
validate_match_rule(rule)?;
}

for filter in filters.into_iter().flatten() {
validate_filter(filter)?;
}
}

Ok(())
}
}

0 comments on commit 1b16e99

Please sign in to comment.