diff --git a/config/milo/activity/policies/httpproxy-policy.yaml b/config/milo/activity/policies/httpproxy-policy.yaml index b7c2a653..d929014d 100644 --- a/config/milo/activity/policies/httpproxy-policy.yaml +++ b/config/milo/activity/policies/httpproxy-policy.yaml @@ -37,15 +37,15 @@ spec: summary: "{{ actor }} created {{ link('a load balancer', audit.objectRef) }}" - name: delete-annotated - match: "!audit.user.username.startsWith('system:') && audit.verb == 'delete' && has(audit.responseObject.metadata.annotations) && 'networking.datumapis.com/display-name' in audit.responseObject.metadata.annotations" + match: "!audit.user.username.startsWith('system:') && audit.verb == 'delete' && has(audit.responseStatus) && audit.responseStatus.code >= 200 && audit.responseStatus.code < 300 && has(audit.responseObject) && type(audit.responseObject) == map && has(audit.responseObject.metadata) && has(audit.responseObject.metadata.annotations) && 'networking.datumapis.com/display-name' in audit.responseObject.metadata.annotations" summary: "{{ actor }} deleted load balancer {{ audit.responseObject.metadata.annotations['networking.datumapis.com/display-name'] }}" - name: delete-name - match: "!audit.user.username.startsWith('system:') && audit.verb == 'delete' && has(audit.objectRef.name)" + match: "!audit.user.username.startsWith('system:') && audit.verb == 'delete' && has(audit.responseStatus) && audit.responseStatus.code >= 200 && audit.responseStatus.code < 300 && has(audit.objectRef.name)" summary: "{{ actor }} deleted load balancer {{ audit.objectRef.name }}" - name: delete-fallback - match: "!audit.user.username.startsWith('system:') && audit.verb == 'delete'" + match: "!audit.user.username.startsWith('system:') && audit.verb == 'delete' && has(audit.responseStatus) && audit.responseStatus.code >= 200 && audit.responseStatus.code < 300" summary: "{{ actor }} deleted a load balancer" - name: update-hostname-added diff --git a/internal/activitypolicy/httpproxy_delete_test.go b/internal/activitypolicy/httpproxy_delete_test.go new file mode 100644 index 00000000..9f2e4a13 --- /dev/null +++ b/internal/activitypolicy/httpproxy_delete_test.go @@ -0,0 +1,41 @@ +// SPDX-License-Identifier: AGPL-3.0-only + +package activitypolicy_test + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestHTTPProxyDeleteOutcomes(t *testing.T) { + policy := loadPolicy(t, "config/milo/activity/policies/httpproxy-policy.yaml") + cases := []struct { + name string + code int + response any + want string + }{ + {"failed delete without response", 500, nil, ""}, + {"forbidden delete", 403, nil, ""}, + {"not found", 404, nil, ""}, + {"failed delete with annotations", 500, map[string]any{"metadata": map[string]any{"annotations": map[string]any{"networking.datumapis.com/display-name": "storefront"}}}, ""}, + {"successful delete without response", 200, nil, "delete-name"}, + {"accepted delete with status response", 202, map[string]any{"kind": "Status", "status": "Success"}, "delete-name"}, + {"successful delete without annotations", 200, map[string]any{"metadata": map[string]any{}}, "delete-name"}, + {"successful annotated delete", 200, map[string]any{"metadata": map[string]any{"annotations": map[string]any{"networking.datumapis.com/display-name": "storefront"}}}, "delete-annotated"}, + } + for _, tt := range cases { + t.Run(tt.name, func(t *testing.T) { + audit := map[string]any{"verb": "delete", "user": map[string]any{"username": "alice@example.com"}, "objectRef": map[string]any{"name": "proxy"}, "responseStatus": map[string]any{"code": tt.code}} + if tt.response != nil { + audit["responseObject"] = tt.response + } + rule := firstMatchingAuditRule(t, policy, audit) + require.Equal(t, tt.want, rule) + if rule != "" { + require.NotEmpty(t, auditSummary(t, policy, rule, audit)) + } + }) + } +}