Skip to content

Commit 51a88df

Browse files
committed
fix: AdmissionPolicyGroup must not have context aware resources
Prior to this commit, it was possible to declare AdmissionPolicyGroup resources with policies that had context aware resources. Context awareness allows a policy to query the Kubernetes API with `list` and `get` verbs. The queries are run using the ServiceAccount of the Policy Server instance that hosts the policy. AdmissionPolicyGroup are namespaced CRDs which can be created by non-admin Kubernetes users. The ability to deploy AdmissionPolicyGroup with context aware resources exposes the cluster to potential leaks of data. For example, a non-admin user, might create an AdmissionPolicyGroup with one of its policies that has access to Kubernetes Secrets. The policy would then be able to `get` Secrets that are not available to the non-admin user (if the Policy Server is running with a ServiceAccount that has been explicitly configured to have `get` access to all the Secrets of the cluster). This commit fixes the by removing the `ContextAwareResources` field from the `AdmissionPolicyGroup`. == What happens after this commit is deployed The existing AdmissionPolicyGroup will be automatically updated and they will lose their `.spec.policies.[*].ContextAwareResources` field (if set). The kubewarden controller will also automatically reconcile the Policy Server configuration. Signed-off-by: Flavio Castelli <[email protected]>
1 parent 04225f4 commit 51a88df

13 files changed

+522
-237
lines changed

api/policies/v1/admissionpolicygroup_types.go

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -75,11 +75,7 @@ func (r *AdmissionPolicyGroup) SetPolicyModeStatus(policyMode PolicyModeStatus)
7575
}
7676

7777
func (r *AdmissionPolicyGroup) IsContextAware() bool {
78-
for _, policy := range r.Spec.Policies {
79-
if len(policy.ContextAwareResources) > 0 {
80-
return true
81-
}
82-
}
78+
// We return false here because AdmissionPolicyGroup have no access to context aware resources.
8379
return false
8480
}
8581

@@ -91,8 +87,17 @@ func (r *AdmissionPolicyGroup) GetModule() string {
9187
return ""
9288
}
9389

94-
func (r *AdmissionPolicyGroup) GetPolicyGroupMembers() PolicyGroupMembers {
95-
return r.Spec.Policies
90+
func (r *AdmissionPolicyGroup) GetPolicyGroupMembersWithContext() PolicyGroupMembersWithContext {
91+
policies := make(PolicyGroupMembersWithContext)
92+
for name, policy := range r.Spec.Policies {
93+
policies[name] = PolicyGroupMemberWithContext{
94+
PolicyGroupMember: policy,
95+
// AdmissionPolicyGroup does not have access to context aware resources.
96+
ContextAwareResources: []ContextAwareResource{},
97+
}
98+
}
99+
100+
return policies
96101
}
97102

98103
func (r *AdmissionPolicyGroup) GetSettings() runtime.RawExtension {

api/policies/v1/clusteradmissionpolicygroup_types.go

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,19 @@ import (
2222
"k8s.io/apimachinery/pkg/runtime"
2323
)
2424

25+
type ClusterPolicyGroupSpec struct {
26+
GroupSpec `json:""`
27+
28+
// Policies is a list of policies that are part of the group that will
29+
// be available to be called in the evaluation expression field.
30+
// Each policy in the group should be a Kubewarden policy.
31+
// +kubebuilder:validation:Required
32+
Policies PolicyGroupMembersWithContext `json:"policies"`
33+
}
34+
2535
// ClusterAdmissionPolicyGroupSpec defines the desired state of ClusterAdmissionPolicyGroup.
2636
type ClusterAdmissionPolicyGroupSpec struct {
27-
PolicyGroupSpec `json:""`
37+
ClusterPolicyGroupSpec `json:""`
2838

2939
// NamespaceSelector decides whether to run the webhook on an object based
3040
// on whether the namespace for that object matches the selector. If the
@@ -148,7 +158,7 @@ func (r *ClusterAdmissionPolicyGroup) GetStatus() *PolicyStatus {
148158
return &r.Status
149159
}
150160

151-
func (r *ClusterAdmissionPolicyGroup) GetPolicyGroupMembers() PolicyGroupMembers {
161+
func (r *ClusterAdmissionPolicyGroup) GetPolicyGroupMembersWithContext() PolicyGroupMembersWithContext {
152162
return r.Spec.Policies
153163
}
154164

api/policies/v1/factories.go

Lines changed: 26 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -314,12 +314,14 @@ func (f *AdmissionPolicyGroupFactory) Build() *AdmissionPolicyGroup {
314314
},
315315
Spec: AdmissionPolicyGroupSpec{
316316
PolicyGroupSpec: PolicyGroupSpec{
317-
PolicyServer: f.policyServer,
318-
Policies: f.policyMembers,
319-
Expression: f.expression,
320-
Rules: f.rules,
321-
MatchConditions: f.matchConds,
322-
Mode: f.mode,
317+
GroupSpec: GroupSpec{
318+
PolicyServer: f.policyServer,
319+
Expression: f.expression,
320+
Rules: f.rules,
321+
MatchConditions: f.matchConds,
322+
Mode: f.mode,
323+
},
324+
Policies: f.policyMembers,
323325
},
324326
},
325327
}
@@ -330,7 +332,7 @@ type ClusterAdmissionPolicyGroupFactory struct {
330332
policyServer string
331333
rules []admissionregistrationv1.RuleWithOperations
332334
expression string
333-
policyMembers PolicyGroupMembers
335+
policyMembers PolicyGroupMembersWithContext
334336
matchConds []admissionregistrationv1.MatchCondition
335337
mode PolicyMode
336338
}
@@ -353,12 +355,16 @@ func NewClusterAdmissionPolicyGroupFactory() *ClusterAdmissionPolicyGroupFactory
353355
},
354356
},
355357
expression: "pod_privileged() && user_group_psp()",
356-
policyMembers: PolicyGroupMembers{
358+
policyMembers: PolicyGroupMembersWithContext{
357359
"pod_privileged": {
358-
Module: "registry://ghcr.io/kubewarden/tests/pod-privileged:v0.2.5",
360+
PolicyGroupMember: PolicyGroupMember{
361+
Module: "registry://ghcr.io/kubewarden/tests/pod-privileged:v0.2.5",
362+
},
359363
},
360364
"user_group_psp": {
361-
Module: "registry://ghcr.io/kubewarden/tests/user-group-psp:v0.4.9",
365+
PolicyGroupMember: PolicyGroupMember{
366+
Module: "registry://ghcr.io/kubewarden/tests/user-group-psp:v0.4.9",
367+
},
362368
},
363369
},
364370
matchConds: []admissionregistrationv1.MatchCondition{
@@ -378,7 +384,7 @@ func (f *ClusterAdmissionPolicyGroupFactory) WithPolicyServer(policyServer strin
378384
return f
379385
}
380386

381-
func (f *ClusterAdmissionPolicyGroupFactory) WithMembers(members PolicyGroupMembers) *ClusterAdmissionPolicyGroupFactory {
387+
func (f *ClusterAdmissionPolicyGroupFactory) WithMembers(members PolicyGroupMembersWithContext) *ClusterAdmissionPolicyGroupFactory {
382388
f.policyMembers = members
383389
return f
384390
}
@@ -413,13 +419,15 @@ func (f *ClusterAdmissionPolicyGroupFactory) Build() *ClusterAdmissionPolicyGrou
413419
},
414420
},
415421
Spec: ClusterAdmissionPolicyGroupSpec{
416-
PolicyGroupSpec: PolicyGroupSpec{
417-
PolicyServer: f.policyServer,
418-
Policies: f.policyMembers,
419-
Expression: f.expression,
420-
Rules: f.rules,
421-
MatchConditions: f.matchConds,
422-
Mode: f.mode,
422+
ClusterPolicyGroupSpec: ClusterPolicyGroupSpec{
423+
GroupSpec: GroupSpec{
424+
PolicyServer: f.policyServer,
425+
Expression: f.expression,
426+
Rules: f.rules,
427+
MatchConditions: f.matchConds,
428+
Mode: f.mode,
429+
},
430+
Policies: f.policyMembers,
423431
},
424432
},
425433
}

api/policies/v1/policy.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,7 @@ type Policy interface {
151151
// +kubebuilder:object:generate:=false
152152
type PolicyGroup interface {
153153
Policy
154-
GetPolicyGroupMembers() PolicyGroupMembers
154+
GetPolicyGroupMembersWithContext() PolicyGroupMembersWithContext
155155
GetExpression() string
156156
GetMessage() string
157157
}

api/policies/v1/policy_types.go

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,12 @@ type PolicyGroupMember struct {
170170
// +kubebuilder:pruning:PreserveUnknownFields
171171
// x-kubernetes-embedded-resource: false
172172
Settings runtime.RawExtension `json:"settings,omitempty"`
173+
}
174+
175+
type PolicyGroupMembersWithContext map[string]PolicyGroupMemberWithContext
176+
177+
type PolicyGroupMemberWithContext struct {
178+
PolicyGroupMember `json:""`
173179

174180
// List of Kubernetes resources the policy is allowed to access at evaluation time.
175181
// Access to these resources is done using the `ServiceAccount` of the PolicyServer
@@ -178,7 +184,7 @@ type PolicyGroupMember struct {
178184
ContextAwareResources []ContextAwareResource `json:"contextAwareResources,omitempty"`
179185
}
180186

181-
type PolicyGroupSpec struct {
187+
type GroupSpec struct {
182188
// PolicyServer identifies an existing PolicyServer resource.
183189
// +kubebuilder:default:=default
184190
// +optional
@@ -302,6 +308,10 @@ type PolicyGroupSpec struct {
302308
// returned in the warning field of the response.
303309
// +kubebuilder:validation:Required
304310
Message string `json:"message"`
311+
}
312+
313+
type PolicyGroupSpec struct {
314+
GroupSpec `json:""`
305315

306316
// Policies is a list of policies that are part of the group that will
307317
// be available to be called in the evaluation expression field.

api/policies/v1/policygroup_validation.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -56,10 +56,10 @@ func validatePolicyGroupUpdate(oldPolicyGroup, newPolicyGroup PolicyGroup) field
5656
// validatePolicyGroupMembers validates that a policy group has at least one policy member.
5757
func validatePolicyGroupMembers(policyGroup PolicyGroup) field.ErrorList {
5858
var allErrors field.ErrorList
59-
if len(policyGroup.GetPolicyGroupMembers()) == 0 {
59+
if len(policyGroup.GetPolicyGroupMembersWithContext()) == 0 {
6060
allErrors = append(allErrors, field.Required(field.NewPath("spec").Child("policies"), "policy groups must have at least one policy member"))
6161
}
62-
for memberName := range policyGroup.GetPolicyGroupMembers() {
62+
for memberName := range policyGroup.GetPolicyGroupMembersWithContext() {
6363
_, matchReservedSymbol := celReservedSymbols[memberName]
6464
if len(memberName) == 0 || matchReservedSymbol || !idenRegex.MatchString(memberName) {
6565
allErrors = append(allErrors, field.Invalid(field.NewPath("spec").Child("policies"), memberName, "policy group member name is invalid"))
@@ -81,7 +81,7 @@ func validatePolicyGroupExpressionField(policyGroup PolicyGroup) *field.Error {
8181

8282
// Create a CEL environment with custom functions for each policy member
8383
var opts []cel.EnvOption
84-
for policyName := range policyGroup.GetPolicyGroupMembers() {
84+
for policyName := range policyGroup.GetPolicyGroupMembersWithContext() {
8585
fn := cel.Function(policyName, cel.Overload(policyName, []*cel.Type{}, types.BoolType))
8686
opts = append(opts, fn)
8787
}

0 commit comments

Comments
 (0)