Skip to content

Commit

Permalink
use gwapiv1a2.PolicyStatus for SecurityPolicy Status
Browse files Browse the repository at this point in the history
Signed-off-by: huabing zhao <[email protected]>
  • Loading branch information
zhaohuabing committed Mar 12, 2024
1 parent 5fba1a2 commit 4940171
Show file tree
Hide file tree
Showing 26 changed files with 617 additions and 604 deletions.
2 changes: 1 addition & 1 deletion api/v1alpha1/securitypolicy_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ type SecurityPolicy struct {
Spec SecurityPolicySpec `json:"spec"`

// Status defines the current status of SecurityPolicy.
Status SecurityPolicyStatus `json:"status,omitempty"`
Status gwapiv1a2.PolicyStatus `json:"status,omitempty"`
}

// SecurityPolicySpec defines the desired state of SecurityPolicy.
Expand Down

Large diffs are not rendered by default.

216 changes: 127 additions & 89 deletions internal/gatewayapi/securitypolicy.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,89 +80,134 @@ func (t *Translator) ProcessSecurityPolicies(securityPolicies []*egv1a1.Security
// Process the policies targeting xRoutes
for _, policy := range securityPolicies {
if policy.Spec.TargetRef.Kind != KindGateway {
policy := policy.DeepCopy()
var (
policy = policy.DeepCopy()
targetedRoute RouteContext
parentGateways []gwv1a2.ParentReference
resolveErr *status.PolicyResolveError
)

res = append(res, policy)

// Negative statuses have already been assigned so its safe to skip
route := resolveSecurityPolicyRouteTargetRef(policy, routeMap)
if route == nil {
targetedRoute, resolveErr = resolveSecurityPolicyRouteTargetRef(policy, routeMap)
if targetedRoute == nil {
continue
}

// Find the Gateway that the route belongs to and add it to the
// gatewayRouteMap, which will be used to check policy overrides
for _, p := range GetParentReferences(route) {
// Find the parent Gateways for the route and add it to the
// gatewayRouteMap, which will be used to check policy override.
// The parent gateways are also used to set the status of the policy.
parentRefs := GetParentReferences(targetedRoute)
for _, p := range parentRefs {
if p.Kind == nil || *p.Kind == KindGateway {
namespace := route.GetNamespace()
namespace := targetedRoute.GetNamespace()
if p.Namespace != nil {
namespace = string(*p.Namespace)
}
gw := types.NamespacedName{
gwNN := types.NamespacedName{
Namespace: namespace,
Name: string(p.Name),
}.String()
}

if _, ok := gatewayRouteMap[gw]; !ok {
gatewayRouteMap[gw] = make(sets.Set[string])
key := gwNN.String()
if _, ok := gatewayRouteMap[key]; !ok {
gatewayRouteMap[key] = make(sets.Set[string])
}
gatewayRouteMap[gw].Insert(utils.NamespacedName(route).String())
gatewayRouteMap[key].Insert(utils.NamespacedName(targetedRoute).String())
parentGateways = append(parentGateways, getAncestorRefForPolicy(gwNN, p.SectionName))
}
}

err := t.translateSecurityPolicyForRoute(policy, route, resources, xdsIR)
// Set conditions for resolve error, then skip current xroute
if resolveErr != nil {
status.SetResolveErrorForPolicyAncestors(&policy.Status,
parentGateways,
t.GatewayControllerName,
policy.Generation,
resolveErr,
)

continue
}

err := t.translateSecurityPolicyForRoute(policy, targetedRoute, resources, xdsIR)
if err != nil {
status.SetSecurityPolicyCondition(policy,
gwv1a2.PolicyConditionAccepted,
metav1.ConditionFalse,
gwv1a2.PolicyReasonInvalid,
status.SetTranslationErrorForPolicyAncestors(&policy.Status,
parentGateways,
t.GatewayControllerName,
policy.Generation,
status.Error2ConditionMsg(err),
)
} else {
message := "SecurityPolicy has been accepted."
status.SetSecurityPolicyAccepted(&policy.Status, message)
}

// Set Accepted condition if it is unset
status.SetAcceptedForPolicyAncestors(&policy.Status, parentGateways, t.GatewayControllerName)
}
}

// Process the policies targeting Gateways
for _, policy := range securityPolicies {
if policy.Spec.TargetRef.Kind == KindGateway {
policy := policy.DeepCopy()
var (
policy = policy.DeepCopy()
targetedGateway *GatewayContext
resolveErr *status.PolicyResolveError
)

res = append(res, policy)

// Negative statuses have already been assigned so its safe to skip
gateway := resolveSecurityPolicyGatewayTargetRef(policy, gatewayMap)
if gateway == nil {
targetedGateway, resolveErr = resolveSecurityPolicyGatewayTargetRef(policy, gatewayMap)
if resolveErr == nil {
continue
}

err := t.translateSecurityPolicyForGateway(policy, gateway, resources, xdsIR)
if err != nil {
status.SetSecurityPolicyCondition(policy,
gwv1a2.PolicyConditionAccepted,
metav1.ConditionFalse,
gwv1a2.PolicyReasonInvalid,
// Find its ancestor reference by resolved gateway, even with resolve error
gatewayNN := utils.NamespacedName(targetedGateway)
parentGateways := []gwv1a2.ParentReference{
getAncestorRefForPolicy(gatewayNN, nil),
}

// Set conditions for resolve error, then skip current gateway
if resolveErr != nil {
status.SetResolveErrorForPolicyAncestors(&policy.Status,
parentGateways,
t.GatewayControllerName,
policy.Generation,
resolveErr,
)

continue
}

if err := t.translateSecurityPolicyForGateway(policy, targetedGateway, resources, xdsIR); err != nil {
status.SetTranslationErrorForPolicyAncestors(&policy.Status,
parentGateways,
t.GatewayControllerName,
policy.Generation,

Check warning on line 186 in internal/gatewayapi/securitypolicy.go

View check run for this annotation

Codecov / codecov/patch

internal/gatewayapi/securitypolicy.go#L182-L186

Added lines #L182 - L186 were not covered by tests
status.Error2ConditionMsg(err),
)
} else {
message := "SecurityPolicy has been accepted."
status.SetSecurityPolicyAccepted(&policy.Status, message)
}

// Set Accepted condition if it is unset
status.SetAcceptedForPolicyAncestors(&policy.Status, parentGateways, t.GatewayControllerName)

Check warning on line 193 in internal/gatewayapi/securitypolicy.go

View check run for this annotation

Codecov / codecov/patch

internal/gatewayapi/securitypolicy.go#L192-L193

Added lines #L192 - L193 were not covered by tests
// Check if this policy is overridden by other policies targeting
// at route level
gw := utils.NamespacedName(gateway).String()
if r, ok := gatewayRouteMap[gw]; ok {
if r, ok := gatewayRouteMap[gatewayNN.String()]; ok {

Check warning on line 196 in internal/gatewayapi/securitypolicy.go

View check run for this annotation

Codecov / codecov/patch

internal/gatewayapi/securitypolicy.go#L196

Added line #L196 was not covered by tests
// Maintain order here to ensure status/string does not change with the same data
routes := r.UnsortedList()
sort.Strings(routes)
message := fmt.Sprintf(
"This policy is being overridden by other securityPolicies for these routes: %v",
routes)
status.SetSecurityPolicyCondition(policy,
status.SetConditionForPolicyAncestors(&policy.Status,
parentGateways,
t.GatewayControllerName,

Check warning on line 205 in internal/gatewayapi/securitypolicy.go

View check run for this annotation

Codecov / codecov/patch

internal/gatewayapi/securitypolicy.go#L203-L205

Added lines #L203 - L205 were not covered by tests
egv1a1.PolicyConditionOverridden,
metav1.ConditionTrue,
egv1a1.PolicyReasonOverridden,
message,
policy.Generation,

Check warning on line 210 in internal/gatewayapi/securitypolicy.go

View check run for this annotation

Codecov / codecov/patch

internal/gatewayapi/securitypolicy.go#L210

Added line #L210 was not covered by tests
)
}
}
Expand All @@ -173,28 +218,13 @@ func (t *Translator) ProcessSecurityPolicies(securityPolicies []*egv1a1.Security

func resolveSecurityPolicyGatewayTargetRef(
policy *egv1a1.SecurityPolicy,
gateways map[types.NamespacedName]*policyGatewayTargetContext) *GatewayContext {
gateways map[types.NamespacedName]*policyGatewayTargetContext) (*GatewayContext, *status.PolicyResolveError) {
targetNs := policy.Spec.TargetRef.Namespace
// If empty, default to namespace of policy
if targetNs == nil {
targetNs = ptr.To(gwv1b1.Namespace(policy.Namespace))
}

// Ensure Policy and target are in the same namespace
if policy.Namespace != string(*targetNs) {
message := fmt.Sprintf(
"Namespace:%s TargetRef.Namespace:%s, SecurityPolicy can only target a resource in the same namespace.",
policy.Namespace, *targetNs)

status.SetSecurityPolicyCondition(policy,
gwv1a2.PolicyConditionAccepted,
metav1.ConditionFalse,
gwv1a2.PolicyReasonInvalid,
message,
)
return nil
}

// Find the Gateway
key := types.NamespacedName{
Name: string(policy.Spec.TargetRef.Name),
Expand All @@ -203,54 +233,51 @@ func resolveSecurityPolicyGatewayTargetRef(
gateway, ok := gateways[key]

// Gateway not found
// It's not an error if the gateway is not found because the SecurityPolicy
// may be reconciled by multiple controllers, and the gateway may not be managed
// by this controller.
if !ok {
return nil
return nil, nil
}

// Ensure Policy and target are in the same namespace
if policy.Namespace != string(*targetNs) {
// TODO zhaohuabing use CEL to validate cross-namespace reference
message := fmt.Sprintf("Namespace:%s TargetRef.Namespace:%s, SecurityPolicy can only target a resource in the same namespace.",
policy.Namespace, *targetNs)

return gateway.GatewayContext, &status.PolicyResolveError{
Reason: gwv1a2.PolicyReasonInvalid,
Message: message,
}

Check warning on line 252 in internal/gatewayapi/securitypolicy.go

View check run for this annotation

Codecov / codecov/patch

internal/gatewayapi/securitypolicy.go#L245-L252

Added lines #L245 - L252 were not covered by tests
}

// Check if another policy targeting the same Gateway exists
if gateway.attached {
message := "Unable to target Gateway, another SecurityPolicy has already attached to it"

status.SetSecurityPolicyCondition(policy,
gwv1a2.PolicyConditionAccepted,
metav1.ConditionFalse,
gwv1a2.PolicyReasonConflicted,
message,
)
return nil
return gateway.GatewayContext, &status.PolicyResolveError{
Reason: gwv1a2.PolicyReasonConflicted,
Message: message,
}
}

// Set context and save
gateway.attached = true
gateways[key] = gateway

return gateway.GatewayContext
return gateway.GatewayContext, nil
}

func resolveSecurityPolicyRouteTargetRef(
policy *egv1a1.SecurityPolicy,
routes map[policyTargetRouteKey]*policyRouteTargetContext) RouteContext {
routes map[policyTargetRouteKey]*policyRouteTargetContext) (RouteContext, *status.PolicyResolveError) {
targetNs := policy.Spec.TargetRef.Namespace
// If empty, default to namespace of policy
if targetNs == nil {
targetNs = ptr.To(gwv1b1.Namespace(policy.Namespace))
}

// Ensure Policy and target are in the same namespace
if policy.Namespace != string(*targetNs) {
message := fmt.Sprintf(
"Namespace:%s TargetRef.Namespace:%s, SecurityPolicy can only target a resource in the same namespace.",
policy.Namespace, *targetNs)

status.SetSecurityPolicyCondition(policy,
gwv1a2.PolicyConditionAccepted,
metav1.ConditionFalse,
gwv1a2.PolicyReasonInvalid,
message,
)
return nil
}

// Check if the route exists
key := policyTargetRouteKey{
Kind: string(policy.Spec.TargetRef.Kind),
Expand All @@ -260,30 +287,41 @@ func resolveSecurityPolicyRouteTargetRef(
route, ok := routes[key]

// Route not found
// It's not an error if the gateway is not found because the SecurityPolicy
// may be reconciled by multiple controllers, and the gateway may not be managed
// by this controller.
if !ok {
return nil
return nil, nil
}

// Ensure Policy and target are in the same namespace
// TODO zhaohuabing use CEL to validate cross-namespace reference
if policy.Namespace != string(*targetNs) {
message := fmt.Sprintf("Namespace:%s TargetRef.Namespace:%s, SecurityPolicy can only target a resource in the same namespace.",
policy.Namespace, *targetNs)

return route.RouteContext, &status.PolicyResolveError{
Reason: gwv1a2.PolicyReasonInvalid,
Message: message,
}

Check warning on line 306 in internal/gatewayapi/securitypolicy.go

View check run for this annotation

Codecov / codecov/patch

internal/gatewayapi/securitypolicy.go#L300-L306

Added lines #L300 - L306 were not covered by tests
}

// Check if another policy targeting the same xRoute exists
if route.attached {
message := fmt.Sprintf(
"Unable to target %s, another SecurityPolicy has already attached to it",
message := fmt.Sprintf("Unable to target %s, another BackendTrafficPolicy has already attached to it",
string(policy.Spec.TargetRef.Kind))

status.SetSecurityPolicyCondition(policy,
gwv1a2.PolicyConditionAccepted,
metav1.ConditionFalse,
gwv1a2.PolicyReasonConflicted,
message,
)
return nil
return route.RouteContext, &status.PolicyResolveError{
Reason: gwv1a2.PolicyReasonConflicted,
Message: message,
}
}

// Set context and save
route.attached = true
routes[key] = route

return route.RouteContext
return route.RouteContext, nil
}

func (t *Translator) translateSecurityPolicyForRoute(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -270,12 +270,7 @@ securityPolicies:
name: gateway-1
namespace: default
status:
conditions:
- lastTransitionTime: null
message: SecurityPolicy has been accepted.
reason: Accepted
status: "True"
type: Accepted
ancestors: null
xdsIR:
envoy-gateway-class:
accessLog:
Expand All @@ -295,21 +290,6 @@ xdsIR:
- backendWeights:
invalid: 0
valid: 0
cors:
allowHeaders:
- x-header-5
- x-header-6
allowMethods:
- GET
- POST
allowOrigins:
- distinct: false
name: ""
safeRegex: .*
exposeHeaders:
- x-header-7
- x-header-8
maxAge: 33m20s
destination:
name: httproute/default/httproute-1/rule/0
settings:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ securityPolicies:
kind: HTTPRoute
name: unknown-httproute
namespace: envoy-gateway
status: {}
status:
ancestors: null
- apiVersion: gateway.envoyproxy.io/v1alpha1
kind: SecurityPolicy
metadata:
Expand All @@ -25,5 +26,6 @@ securityPolicies:
kind: Gateway
name: unknown-gateway
namespace: envoy-gateway
status: {}
status:
ancestors: null
xdsIR: {}
Loading

0 comments on commit 4940171

Please sign in to comment.