This is an automated email from the ASF dual-hosted git repository.
github-actions[bot] pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/dubbo-kubernetes.git
The following commit(s) were added to refs/heads/master by this push:
new ca93af7a Refactor the security feature module (#1012)
ca93af7a is described below
commit ca93af7a926bf8a60137024715282351d58aa16a
Author: mfordjody <[email protected]>
AuthorDate: Tue Aug 11 17:27:46 2026 +0800
Refactor the security feature module (#1012)
* feat: implement mesh security policy pipeline
* fix: watch authorization policy CRD plural
* refactor: enforce project security architecture
---
.../pkg/bootstrap/inherent_grpc_controller.go | 97 ++++++++++++++++++++--
.../pkg/bootstrap/inherent_grpc_controller_test.go | 41 +++++++++
.../pkg/networking/grpcgen/security_filters.go | 1 +
.../networking/grpcgen/security_filters_test.go | 11 +++
go.mod | 6 +-
go.sum | 12 +--
manifests/charts/base/files/crd-all.gen.yaml | 10 ++-
pkg/config/validation/validators.go | 22 ++++-
pkg/config/validation/validators_test.go | 43 +++++++++-
tools/make/lint.mk | 8 ++
10 files changed, 228 insertions(+), 23 deletions(-)
diff --git a/dubbod/discovery/pkg/bootstrap/inherent_grpc_controller.go
b/dubbod/discovery/pkg/bootstrap/inherent_grpc_controller.go
index d7cb4fba..aa0970ba 100644
--- a/dubbod/discovery/pkg/bootstrap/inherent_grpc_controller.go
+++ b/dubbod/discovery/pkg/bootstrap/inherent_grpc_controller.go
@@ -48,6 +48,7 @@ import (
pkiutil
"github.com/apache/dubbo-kubernetes/dubbod/security/pkg/pki/util"
caserver
"github.com/apache/dubbo-kubernetes/dubbod/security/pkg/server/ca"
meshv1alpha1 "github.com/kdubbo/api/mesh/v1alpha1"
+ securityv1alpha3 "github.com/kdubbo/api/security/v1alpha3"
"google.golang.org/protobuf/proto"
corev1 "k8s.io/api/core/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
@@ -407,10 +408,25 @@ type inherentGRPCServiceRuntimeConfig struct {
}
type inherentGRPCPortRuntimeConfig struct {
- Name string `json:"name,omitempty"`
- Port int `json:"port"`
- MTLSMode string `json:"mtlsMode,omitempty"`
- Fault *inherentGRPCFaultRuntimeConfig `json:"fault,omitempty"`
+ Name string
`json:"name,omitempty"`
+ Port int
`json:"port"`
+ MTLSMode string
`json:"mtlsMode,omitempty"`
+ AuthorizationPolicies []inherentGRPCAuthorizationPolicyRuntimeConfig
`json:"authorizationPolicies,omitempty"`
+ Fault *inherentGRPCFaultRuntimeConfig
`json:"fault,omitempty"`
+}
+
+type inherentGRPCAuthorizationPolicyRuntimeConfig struct {
+ Name string `json:"name"`
+ Action string `json:"action"`
+ Rules []inherentGRPCAuthorizationRuleRuntimeConfig
`json:"rules,omitempty"`
+}
+
+type inherentGRPCAuthorizationRuleRuntimeConfig struct {
+ Sources []inherentGRPCAuthorizationSourceRuntimeConfig
`json:"sources,omitempty"`
+}
+
+type inherentGRPCAuthorizationSourceRuntimeConfig struct {
+ Principals []string `json:"principals,omitempty"`
}
type inherentGRPCFaultRuntimeConfig struct {
@@ -751,10 +767,11 @@ func buildRuntimeServiceConfig(push
*discoverymodel.PushContext, endpointIndex *
continue
}
cfg.Ports = append(cfg.Ports, inherentGRPCPortRuntimeConfig{
- Name: port.Name,
- Port: port.Port,
- MTLSMode: runtimeInboundMTLSMode(push,
svc.Attributes.Namespace, port.Port),
- Fault: runtimeFaultInjection(push,
svc.Attributes.Namespace, svc.Attributes.Name, port.Name),
+ Name: port.Name,
+ Port: port.Port,
+ MTLSMode: runtimeInboundMTLSMode(push,
svc.Attributes.Namespace, port.Port),
+ AuthorizationPolicies:
runtimeWorkloadAuthorizationPolicies(push, svc),
+ Fault: runtimeFaultInjection(push,
svc.Attributes.Namespace, svc.Attributes.Name, port.Name),
})
cfg.Endpoints = append(cfg.Endpoints,
runtimeEndpointsForService(endpointIndex, svc, port.Port, nil)...)
}
@@ -762,6 +779,70 @@ func buildRuntimeServiceConfig(push
*discoverymodel.PushContext, endpointIndex *
return cfg
}
+func runtimeWorkloadAuthorizationPolicies(
+ push *discoverymodel.PushContext,
+ svc *discoverymodel.Service,
+) []inherentGRPCAuthorizationPolicyRuntimeConfig {
+ if push == nil || svc == nil {
+ return nil
+ }
+ workloadLabels := svc.Attributes.LabelSelectors
+ if len(workloadLabels) == 0 {
+ workloadLabels = svc.Attributes.Labels
+ }
+ configs :=
push.AuthorizationPoliciesForWorkload(svc.Attributes.Namespace, workloadLabels)
+ out := make([]inherentGRPCAuthorizationPolicyRuntimeConfig, 0,
len(configs))
+ for _, cfg := range configs {
+ spec, ok := cfg.Spec.(*securityv1alpha3.AuthorizationPolicy)
+ if !ok || spec == nil {
+ continue
+ }
+ policy := inherentGRPCAuthorizationPolicyRuntimeConfig{
+ Name: cfg.Name,
+ Action: spec.GetAction().String(),
+ }
+ for _, rule := range spec.GetRules() {
+ projected, ok := runtimeWorkloadAuthorizationRule(rule)
+ if ok {
+ policy.Rules = append(policy.Rules, projected)
+ }
+ }
+ // JWT claim constraints are enforced by dxgate. Never weaken
them into
+ // an unconstrained L4 workload rule in dxproxy.
+ if len(spec.GetRules()) > 0 && len(policy.Rules) == 0 {
+ continue
+ }
+ out = append(out, policy)
+ }
+ return out
+}
+
+func runtimeWorkloadAuthorizationRule(
+ rule *securityv1alpha3.Rule,
+) (inherentGRPCAuthorizationRuleRuntimeConfig, bool) {
+ if rule == nil {
+ return inherentGRPCAuthorizationRuleRuntimeConfig{}, true
+ }
+ if len(rule.GetWhen()) > 0 {
+ return inherentGRPCAuthorizationRuleRuntimeConfig{}, false
+ }
+ projected := inherentGRPCAuthorizationRuleRuntimeConfig{}
+ for _, from := range rule.GetFrom() {
+ source := from.GetSource()
+ if source == nil {
+ projected.Sources = append(projected.Sources,
inherentGRPCAuthorizationSourceRuntimeConfig{})
+ continue
+ }
+ if len(source.GetRequestPrincipals()) > 0 {
+ return inherentGRPCAuthorizationRuleRuntimeConfig{},
false
+ }
+ projected.Sources = append(projected.Sources,
inherentGRPCAuthorizationSourceRuntimeConfig{
+ Principals: append([]string(nil),
source.GetPrincipals()...),
+ })
+ }
+ return projected, true
+}
+
func runtimeFaultInjection(push *discoverymodel.PushContext, namespace, name,
portName string) *inherentGRPCFaultRuntimeConfig {
settings, found := push.FaultInjectionForService(namespace, name,
portName)
if !found {
diff --git a/dubbod/discovery/pkg/bootstrap/inherent_grpc_controller_test.go
b/dubbod/discovery/pkg/bootstrap/inherent_grpc_controller_test.go
index f1257874..b3c994e5 100644
--- a/dubbod/discovery/pkg/bootstrap/inherent_grpc_controller_test.go
+++ b/dubbod/discovery/pkg/bootstrap/inherent_grpc_controller_test.go
@@ -293,6 +293,47 @@ func
TestBuildRuntimeTrafficConfigCapturesInherentSecurity(t *testing.T) {
}
}
+func TestBuildRuntimeTrafficConfigProjectsOnlyWorkloadPrincipalAuthorization(t
*testing.T) {
+ svc := newInherentRuntimeTestService("provider", "grpc-app",
"provider.grpc-app.svc.cluster.local", 17070)
+ workloadPolicy := config.Config{
+ Meta: config.Meta{
+ GroupVersionKind: gvk.AuthorizationPolicy,
+ Name: "allow-workload",
+ Namespace: "grpc-app",
+ },
+ Spec: &security.AuthorizationPolicy{
+ Action: security.AuthorizationPolicy_ALLOW,
+ Rules: []*security.Rule{{From:
[]*security.From{{Source: &security.Source{
+ Principals:
[]string{"cluster.local/ns/client/sa/caller"},
+ }}}}},
+ },
+ }
+ jwtPolicy := config.Config{
+ Meta: config.Meta{
+ GroupVersionKind: gvk.AuthorizationPolicy,
+ Name: "allow-jwt",
+ Namespace: "grpc-app",
+ },
+ Spec: &security.AuthorizationPolicy{
+ Action: security.AuthorizationPolicy_ALLOW,
+ Rules: []*security.Rule{{From:
[]*security.From{{Source: &security.Source{
+ RequestPrincipals:
[]string{"https://issuer.example/alice"},
+ }}}}},
+ },
+ }
+ push := newInherentRuntimeTestPushContext(t,
[]config.Config{workloadPolicy, jwtPolicy}, []*discoverymodel.Service{svc})
+
+ serviceConfig := buildRuntimeServiceConfig(push, nil, svc)
+ got := serviceConfig.Ports[0].AuthorizationPolicies
+ if len(got) != 1 || got[0].Name != "allow-workload" {
+ t.Fatalf("authorization policies = %+v, want only
allow-workload", got)
+ }
+ principals := got[0].Rules[0].Sources[0].Principals
+ if len(principals) != 1 || principals[0] !=
"cluster.local/ns/client/sa/caller" {
+ t.Fatalf("principals = %v, want caller SPIFFE identity",
principals)
+ }
+}
+
func TestBuildRuntimeTrafficConfigCapturesPermissivePeerAuthentication(t
*testing.T) {
hostname := host.Name("provider.grpc-app.svc.cluster.local")
svc := newInherentRuntimeTestService("provider", "grpc-app",
string(hostname), 17070)
diff --git a/dubbod/discovery/pkg/networking/grpcgen/security_filters.go
b/dubbod/discovery/pkg/networking/grpcgen/security_filters.go
index 54092ac5..3c0ca1f7 100644
--- a/dubbod/discovery/pkg/networking/grpcgen/security_filters.go
+++ b/dubbod/discovery/pkg/networking/grpcgen/security_filters.go
@@ -145,6 +145,7 @@ func authorizationRuleFromAPI(rule *security.Rule)
*rbacv1.Rule {
}
sources = append(sources, &rbacv1.Source{
RequestPrincipals: append([]string(nil),
from.GetSource().GetRequestPrincipals()...),
+ Principals: append([]string(nil),
from.GetSource().GetPrincipals()...),
})
}
when := make([]*rbacv1.Condition, 0, len(rule.GetWhen()))
diff --git a/dubbod/discovery/pkg/networking/grpcgen/security_filters_test.go
b/dubbod/discovery/pkg/networking/grpcgen/security_filters_test.go
index bf2a25f4..419e5510 100644
--- a/dubbod/discovery/pkg/networking/grpcgen/security_filters_test.go
+++ b/dubbod/discovery/pkg/networking/grpcgen/security_filters_test.go
@@ -98,6 +98,17 @@ func
TestBuildInboundHTTPFiltersAddsJWTAndAuthorizationBeforeRouter(t *testing.T
}
}
+func TestAuthorizationRuleProjectsWorkloadPrincipal(t *testing.T) {
+ rule := authorizationRuleFromAPI(&security.Rule{
+ From: []*security.From{{Source: &security.Source{
+ Principals:
[]string{"cluster.local/ns/orders/sa/client"},
+ }}},
+ })
+ if got := rule.GetSources()[0].GetPrincipals(); len(got) != 1 || got[0]
!= "cluster.local/ns/orders/sa/client" {
+ t.Fatalf("principals = %v, want workload principal", got)
+ }
+}
+
func newRequestAuthenticationConfig() config.Config {
return config.Config{
Meta: config.Meta{
diff --git a/go.mod b/go.mod
index cbef21a3..8089457e 100644
--- a/go.mod
+++ b/go.mod
@@ -41,9 +41,9 @@ require (
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0
github.com/hashicorp/go-multierror v1.1.1
github.com/hashicorp/golang-lru/v2 v2.0.7
- github.com/kdubbo/api v0.0.0-20260809042441-64b5ddb809d4
- github.com/kdubbo/client-go v0.0.0-20260809042806-04a31db14165
- github.com/kdubbo/xds-api v0.0.0-20260809042456-0d57cc43a21a
+ github.com/kdubbo/api v0.0.0-20260811085311-7752d1da2bcb
+ github.com/kdubbo/client-go v0.0.0-20260811085609-07dfedad5b33
+ github.com/kdubbo/xds-api v0.0.0-20260811085311-5a7b086ecdc6
github.com/prometheus/client_golang v1.23.2
github.com/prometheus/client_model v0.6.2
github.com/spf13/cobra v1.10.2
diff --git a/go.sum b/go.sum
index 5a952f7a..45722ad4 100644
--- a/go.sum
+++ b/go.sum
@@ -154,12 +154,12 @@ github.com/inconshreveable/mousetrap v1.1.0
h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2
github.com/inconshreveable/mousetrap v1.1.0/go.mod
h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/json-iterator/go v1.1.12
h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod
h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
-github.com/kdubbo/api v0.0.0-20260809042441-64b5ddb809d4
h1:lb/8FbFHyH30htWGL8i4BZWwNW4sFXOlC490Ir26/8A=
-github.com/kdubbo/api v0.0.0-20260809042441-64b5ddb809d4/go.mod
h1:8BtJiIovg7QCPsCxXcw3gDf922VcvYq5ihOSvj49Rq8=
-github.com/kdubbo/client-go v0.0.0-20260809042806-04a31db14165
h1:kB6sUmF2T70lrVg+XibLkthO3hKUL0YqF2J08FoHtVk=
-github.com/kdubbo/client-go v0.0.0-20260809042806-04a31db14165/go.mod
h1:8G0p4h/XaJ9V6vX1OrB+4Ys+U+q9AeXQdMPkyUd+sIA=
-github.com/kdubbo/xds-api v0.0.0-20260809042456-0d57cc43a21a
h1:stTvOOGy4r6DoxquO928mB9AGGoFBvjVHxqAGIGiSkI=
-github.com/kdubbo/xds-api v0.0.0-20260809042456-0d57cc43a21a/go.mod
h1:o2HDUgL1ntaDbWomZ4cD2tt8jBamuG2qRtjXOa1zZ0Q=
+github.com/kdubbo/api v0.0.0-20260811085311-7752d1da2bcb
h1:BYIqwRc/1D9xsHXonODshRwgEgMIip6nAWF1oMuWFhI=
+github.com/kdubbo/api v0.0.0-20260811085311-7752d1da2bcb/go.mod
h1:8BtJiIovg7QCPsCxXcw3gDf922VcvYq5ihOSvj49Rq8=
+github.com/kdubbo/client-go v0.0.0-20260811085609-07dfedad5b33
h1:Xtv/mwzQdww5uGk0t+En7X843chTKgFWen1DCDmSq8s=
+github.com/kdubbo/client-go v0.0.0-20260811085609-07dfedad5b33/go.mod
h1:J7zo0kp1Q+2JanfPHasOvasgsCJ+I5RZpd42DhcMMuw=
+github.com/kdubbo/xds-api v0.0.0-20260811085311-5a7b086ecdc6
h1:Oodawr6d7r8N6hXImlXkpMs9+dECFbRWCZZ0nIiG00k=
+github.com/kdubbo/xds-api v0.0.0-20260811085311-5a7b086ecdc6/go.mod
h1:o2HDUgL1ntaDbWomZ4cD2tt8jBamuG2qRtjXOa1zZ0Q=
github.com/kisielk/errcheck v1.5.0/go.mod
h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod
h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.18.6
h1:2jupLlAwFm95+YDR+NwD2MEfFO9d4z4Prjl1XXDjuao=
diff --git a/manifests/charts/base/files/crd-all.gen.yaml
b/manifests/charts/base/files/crd-all.gen.yaml
index 21f52ae6..f95f087b 100644
--- a/manifests/charts/base/files/crd-all.gen.yaml
+++ b/manifests/charts/base/files/crd-all.gen.yaml
@@ -1157,7 +1157,7 @@ metadata:
dubbo: security
heritage: Tiller
release: dubbo
- name: authorizationpolicys.security.dubbo.apache.org
+ name: authorizationpolicies.security.dubbo.apache.org
spec:
group: security.dubbo.apache.org
names:
@@ -1166,7 +1166,7 @@ spec:
- security
kind: AuthorizationPolicy
listKind: AuthorizationPolicyList
- plural: authorizationpolicys
+ plural: authorizationpolicies
shortNames:
- ap
singular: authorizationpolicy
@@ -1213,6 +1213,12 @@ spec:
source:
description: Source attributes.
properties:
+ principals:
+ description: Workload identities derived from
a verified
+ mutual TLS certificate.
+ items:
+ type: string
+ type: array
requestPrincipals:
description: Request identities derived from
validated
JWT issuer and subject.
diff --git a/pkg/config/validation/validators.go
b/pkg/config/validation/validators.go
index 8d5b050a..20831327 100644
--- a/pkg/config/validation/validators.go
+++ b/pkg/config/validation/validators.go
@@ -56,6 +56,8 @@ var ValidateAuthorizationPolicy = validateFunc(
v = appendValidation(v, fmt.Errorf("rule[%d]
must not be null", i))
continue
}
+ hasWorkloadPrincipal := false
+ hasRequestPrincipal := false
for j, from := range rule.GetFrom() {
if from == nil {
v = appendValidation(v,
fmt.Errorf("rule[%d].from[%d] must not be null", i, j))
@@ -65,15 +67,29 @@ var ValidateAuthorizationPolicy = validateFunc(
v = appendValidation(v,
fmt.Errorf("rule[%d].from[%d].source must be set", i, j))
continue
}
- if len(from.GetSource().GetRequestPrincipals())
== 0 {
- v = appendValidation(v,
fmt.Errorf("rule[%d].from[%d].source must specify requestPrincipals", i, j))
+ source := from.GetSource()
+ hasWorkloadPrincipal = hasWorkloadPrincipal ||
len(source.GetPrincipals()) > 0
+ hasRequestPrincipal = hasRequestPrincipal ||
len(source.GetRequestPrincipals()) > 0
+ if len(source.GetPrincipals()) == 0 &&
len(source.GetRequestPrincipals()) == 0 {
+ v = appendValidation(v,
fmt.Errorf("rule[%d].from[%d].source must specify principals or
requestPrincipals", i, j))
}
- for k, principal := range
from.GetSource().GetRequestPrincipals() {
+ if len(source.GetPrincipals()) > 0 &&
len(source.GetRequestPrincipals()) > 0 {
+ v = appendValidation(v,
fmt.Errorf("rule[%d].from[%d].source must not mix principals and
requestPrincipals", i, j))
+ }
+ for k, principal := range
source.GetPrincipals() {
+ if principal == "" {
+ v = appendValidation(v,
fmt.Errorf("rule[%d].from[%d].source.principals[%d] must not be empty", i, j,
k))
+ }
+ }
+ for k, principal := range
source.GetRequestPrincipals() {
if principal == "" {
v = appendValidation(v,
fmt.Errorf("rule[%d].from[%d].source.requestPrincipals[%d] must not be empty",
i, j, k))
}
}
}
+ if hasWorkloadPrincipal && (hasRequestPrincipal ||
len(rule.GetWhen()) > 0) {
+ v = appendValidation(v, fmt.Errorf("rule[%d]
must not mix workload principal and JWT constraints", i))
+ }
for j, when := range rule.GetWhen() {
if when == nil {
v = appendValidation(v,
fmt.Errorf("rule[%d].when[%d] must not be null", i, j))
diff --git a/pkg/config/validation/validators_test.go
b/pkg/config/validation/validators_test.go
index 34cdbd71..179903c9 100644
--- a/pkg/config/validation/validators_test.go
+++ b/pkg/config/validation/validators_test.go
@@ -50,7 +50,7 @@ func TestValidateAuthorizationPolicy(t *testing.T) {
wantErr: false,
},
{
- name: "valid rule",
+ name: "valid JWT rule",
spec: &security.AuthorizationPolicy{
Action: security.AuthorizationPolicy_ALLOW,
Rules: []*security.Rule{{
@@ -61,6 +61,18 @@ func TestValidateAuthorizationPolicy(t *testing.T) {
},
wantErr: false,
},
+ {
+ name: "valid workload principal rule",
+ spec: &security.AuthorizationPolicy{
+ Action: security.AuthorizationPolicy_ALLOW,
+ Rules: []*security.Rule{{
+ From: []*security.From{{
+ Source:
&security.Source{Principals: []string{"cluster.local/ns/default/sa/client"}},
+ }},
+ }},
+ },
+ wantErr: false,
+ },
{
name: "empty deny policy",
spec: &security.AuthorizationPolicy{
@@ -88,6 +100,35 @@ func TestValidateAuthorizationPolicy(t *testing.T) {
},
wantErr: true,
},
+ {
+ name: "mixed principal types",
+ spec: &security.AuthorizationPolicy{
+ Rules: []*security.Rule{{
+ From: []*security.From{{
+ Source: &security.Source{
+ Principals:
[]string{"cluster.local/ns/default/sa/client"},
+ RequestPrincipals:
[]string{"issuer/subject"},
+ },
+ }},
+ }},
+ },
+ wantErr: true,
+ },
+ {
+ name: "workload principal with JWT claim",
+ spec: &security.AuthorizationPolicy{
+ Rules: []*security.Rule{{
+ From: []*security.From{{
+ Source:
&security.Source{Principals: []string{"cluster.local/ns/default/sa/client"}},
+ }},
+ When: []*security.Condition{{
+ Key:
"request.auth.claims[groups]",
+ Values: []string{"orders"},
+ }},
+ }},
+ },
+ wantErr: true,
+ },
{
name: "when without values",
spec: &security.AuthorizationPolicy{
diff --git a/tools/make/lint.mk b/tools/make/lint.mk
index b689d4cf..c3502660 100644
--- a/tools/make/lint.mk
+++ b/tools/make/lint.mk
@@ -40,6 +40,14 @@ lint-helm: ## Lint and render the Helm charts.
helm lint $$chart || exit 1; \
helm template test-release $$chart >/dev/null || exit 1; \
done
+ @if grep -q 'authorizationpolicys.security.dubbo.apache.org'
manifests/charts/base/files/crd-all.gen.yaml; then \
+ echo "AuthorizationPolicy CRD uses invalid plural
authorizationpolicys"; \
+ exit 1; \
+ fi
+ @grep -q 'name: authorizationpolicies.security.dubbo.apache.org'
manifests/charts/base/files/crd-all.gen.yaml || { \
+ echo "AuthorizationPolicy CRD plural must be
authorizationpolicies"; \
+ exit 1; \
+ }
.PHONY: fmt
fmt: ## Format all Go sources with gofmt -s.