github.com/cilium/cilium@v1.16.2/pkg/policy/api/egress.go (about)

     1  // SPDX-License-Identifier: Apache-2.0
     2  // Copyright Authors of Cilium
     3  
     4  package api
     5  
     6  import (
     7  	"context"
     8  
     9  	slim_metav1 "github.com/cilium/cilium/pkg/k8s/slim/k8s/apis/meta/v1"
    10  	"github.com/cilium/cilium/pkg/slices"
    11  )
    12  
    13  // EgressCommonRule is a rule that shares some of its fields across the
    14  // EgressRule and EgressDenyRule. It's publicly exported so the code generators
    15  // can generate code for this structure.
    16  //
    17  // +deepequal-gen:private-method=true
    18  type EgressCommonRule struct {
    19  	// ToEndpoints is a list of endpoints identified by an EndpointSelector to
    20  	// which the endpoints subject to the rule are allowed to communicate.
    21  	//
    22  	// Example:
    23  	// Any endpoint with the label "role=frontend" can communicate with any
    24  	// endpoint carrying the label "role=backend".
    25  	//
    26  	// +kubebuilder:validation:Optional
    27  	ToEndpoints []EndpointSelector `json:"toEndpoints,omitempty"`
    28  
    29  	// ToRequires is a list of additional constraints which must be met
    30  	// in order for the selected endpoints to be able to connect to other
    31  	// endpoints. These additional constraints do no by itself grant access
    32  	// privileges and must always be accompanied with at least one matching
    33  	// ToEndpoints.
    34  	//
    35  	// Example:
    36  	// Any Endpoint with the label "team=A" requires any endpoint to which it
    37  	// communicates to also carry the label "team=A".
    38  	//
    39  	// +kubebuilder:validation:Optional
    40  	ToRequires []EndpointSelector `json:"toRequires,omitempty"`
    41  
    42  	// ToCIDR is a list of IP blocks which the endpoint subject to the rule
    43  	// is allowed to initiate connections. Only connections destined for
    44  	// outside of the cluster and not targeting the host will be subject
    45  	// to CIDR rules.  This will match on the destination IP address of
    46  	// outgoing connections. Adding a prefix into ToCIDR or into ToCIDRSet
    47  	// with no ExcludeCIDRs is equivalent. Overlaps are allowed between
    48  	// ToCIDR and ToCIDRSet.
    49  	//
    50  	// Example:
    51  	// Any endpoint with the label "app=database-proxy" is allowed to
    52  	// initiate connections to 10.2.3.0/24
    53  	//
    54  	// +kubebuilder:validation:Optional
    55  	ToCIDR CIDRSlice `json:"toCIDR,omitempty"`
    56  
    57  	// ToCIDRSet is a list of IP blocks which the endpoint subject to the rule
    58  	// is allowed to initiate connections to in addition to connections
    59  	// which are allowed via ToEndpoints, along with a list of subnets contained
    60  	// within their corresponding IP block to which traffic should not be
    61  	// allowed. This will match on the destination IP address of outgoing
    62  	// connections. Adding a prefix into ToCIDR or into ToCIDRSet with no
    63  	// ExcludeCIDRs is equivalent. Overlaps are allowed between ToCIDR and
    64  	// ToCIDRSet.
    65  	//
    66  	// Example:
    67  	// Any endpoint with the label "app=database-proxy" is allowed to
    68  	// initiate connections to 10.2.3.0/24 except from IPs in subnet 10.2.3.0/28.
    69  	//
    70  	// +kubebuilder:validation:Optional
    71  	ToCIDRSet CIDRRuleSlice `json:"toCIDRSet,omitempty"`
    72  
    73  	// ToEntities is a list of special entities to which the endpoint subject
    74  	// to the rule is allowed to initiate connections. Supported entities are
    75  	// `world`, `cluster`,`host`,`remote-node`,`kube-apiserver`, `init`,
    76  	// `health`,`unmanaged` and `all`.
    77  	//
    78  	// +kubebuilder:validation:Optional
    79  	ToEntities EntitySlice `json:"toEntities,omitempty"`
    80  
    81  	// ToServices is a list of services to which the endpoint subject
    82  	// to the rule is allowed to initiate connections.
    83  	// Currently Cilium only supports toServices for K8s services without
    84  	// selectors.
    85  	//
    86  	// Example:
    87  	// Any endpoint with the label "app=backend-app" is allowed to
    88  	// initiate connections to all cidrs backing the "external-service" service
    89  	//
    90  	// +kubebuilder:validation:Optional
    91  	ToServices []Service `json:"toServices,omitempty"`
    92  
    93  	// ToGroups is a directive that allows the integration with multiple outside
    94  	// providers. Currently, only AWS is supported, and the rule can select by
    95  	// multiple sub directives:
    96  	//
    97  	// Example:
    98  	// toGroups:
    99  	// - aws:
   100  	//     securityGroupsIds:
   101  	//     - 'sg-XXXXXXXXXXXXX'
   102  	//
   103  	// +kubebuilder:validation:Optional
   104  	ToGroups []Groups `json:"toGroups,omitempty"`
   105  
   106  	// ToNodes is a list of nodes identified by an
   107  	// EndpointSelector to which endpoints subject to the rule is allowed to communicate.
   108  	//
   109  	// +kubebuilder:validation:Optional
   110  	ToNodes []EndpointSelector `json:"toNodes,omitempty"`
   111  
   112  	// TODO: Move this to the policy package
   113  	// (https://github.com/cilium/cilium/issues/8353)
   114  	aggregatedSelectors EndpointSelectorSlice `json:"-"`
   115  }
   116  
   117  // DeepEqual returns true if both EgressCommonRule are deep equal.
   118  // The semantic of a nil slice in one of its fields is different from the semantic
   119  // of an empty non-nil slice, thus it explicitly checks for that case before calling
   120  // the autogenerated method.
   121  func (in *EgressCommonRule) DeepEqual(other *EgressCommonRule) bool {
   122  	if slices.XorNil(in.ToEndpoints, other.ToEndpoints) {
   123  		return false
   124  	}
   125  	if slices.XorNil(in.ToCIDR, other.ToCIDR) {
   126  		return false
   127  	}
   128  	if slices.XorNil(in.ToCIDRSet, other.ToCIDRSet) {
   129  		return false
   130  	}
   131  	if slices.XorNil(in.ToEntities, other.ToEntities) {
   132  		return false
   133  	}
   134  
   135  	return in.deepEqual(other)
   136  }
   137  
   138  // EgressRule contains all rule types which can be applied at egress, i.e.
   139  // network traffic that originates inside the endpoint and exits the endpoint
   140  // selected by the endpointSelector.
   141  //
   142  //   - All members of this structure are optional. If omitted or empty, the
   143  //     member will have no effect on the rule.
   144  //
   145  //   - If multiple members of the structure are specified, then all members
   146  //     must match in order for the rule to take effect. The exception to this
   147  //     rule is the ToRequires member; the effects of any Requires field in any
   148  //     rule will apply to all other rules as well.
   149  //
   150  //   - ToEndpoints, ToCIDR, ToCIDRSet, ToEntities, ToServices and ToGroups are
   151  //     mutually exclusive. Only one of these members may be present within an
   152  //     individual rule.
   153  type EgressRule struct {
   154  	EgressCommonRule `json:",inline"`
   155  
   156  	// ToPorts is a list of destination ports identified by port number and
   157  	// protocol which the endpoint subject to the rule is allowed to
   158  	// connect to.
   159  	//
   160  	// Example:
   161  	// Any endpoint with the label "role=frontend" is allowed to initiate
   162  	// connections to destination port 8080/tcp
   163  	//
   164  	// +kubebuilder:validation:Optional
   165  	ToPorts PortRules `json:"toPorts,omitempty"`
   166  
   167  	// ToFQDN allows whitelisting DNS names in place of IPs. The IPs that result
   168  	// from DNS resolution of `ToFQDN.MatchName`s are added to the same
   169  	// EgressRule object as ToCIDRSet entries, and behave accordingly. Any L4 and
   170  	// L7 rules within this EgressRule will also apply to these IPs.
   171  	// The DNS -> IP mapping is re-resolved periodically from within the
   172  	// cilium-agent, and the IPs in the DNS response are effected in the policy
   173  	// for selected pods as-is (i.e. the list of IPs is not modified in any way).
   174  	// Note: An explicit rule to allow for DNS traffic is needed for the pods, as
   175  	// ToFQDN counts as an egress rule and will enforce egress policy when
   176  	// PolicyEnforcment=default.
   177  	// Note: If the resolved IPs are IPs within the kubernetes cluster, the
   178  	// ToFQDN rule will not apply to that IP.
   179  	// Note: ToFQDN cannot occur in the same policy as other To* rules.
   180  	//
   181  	// +kubebuilder:validation:Optional
   182  	ToFQDNs FQDNSelectorSlice `json:"toFQDNs,omitempty"`
   183  
   184  	// ICMPs is a list of ICMP rule identified by type number
   185  	// which the endpoint subject to the rule is allowed to connect to.
   186  	//
   187  	// Example:
   188  	// Any endpoint with the label "app=httpd" is allowed to initiate
   189  	// type 8 ICMP connections.
   190  	//
   191  	// +kubebuilder:validation:Optional
   192  	ICMPs ICMPRules `json:"icmps,omitempty"`
   193  
   194  	// Authentication is the required authentication type for the allowed traffic, if any.
   195  	//
   196  	// +kubebuilder:validation:Optional
   197  	Authentication *Authentication `json:"authentication,omitempty"`
   198  }
   199  
   200  // EgressDenyRule contains all rule types which can be applied at egress, i.e.
   201  // network traffic that originates inside the endpoint and exits the endpoint
   202  // selected by the endpointSelector.
   203  //
   204  //   - All members of this structure are optional. If omitted or empty, the
   205  //     member will have no effect on the rule.
   206  //
   207  //   - If multiple members of the structure are specified, then all members
   208  //     must match in order for the rule to take effect. The exception to this
   209  //     rule is the ToRequires member; the effects of any Requires field in any
   210  //     rule will apply to all other rules as well.
   211  //
   212  //   - ToEndpoints, ToCIDR, ToCIDRSet, ToEntities, ToServices and ToGroups are
   213  //     mutually exclusive. Only one of these members may be present within an
   214  //     individual rule.
   215  type EgressDenyRule struct {
   216  	EgressCommonRule `json:",inline"`
   217  
   218  	// ToPorts is a list of destination ports identified by port number and
   219  	// protocol which the endpoint subject to the rule is not allowed to connect
   220  	// to.
   221  	//
   222  	// Example:
   223  	// Any endpoint with the label "role=frontend" is not allowed to initiate
   224  	// connections to destination port 8080/tcp
   225  	//
   226  	// +kubebuilder:validation:Optional
   227  	ToPorts PortDenyRules `json:"toPorts,omitempty"`
   228  
   229  	// ICMPs is a list of ICMP rule identified by type number
   230  	// which the endpoint subject to the rule is not allowed to connect to.
   231  	//
   232  	// Example:
   233  	// Any endpoint with the label "app=httpd" is not allowed to initiate
   234  	// type 8 ICMP connections.
   235  	//
   236  	// +kubebuilder:validation:Optional
   237  	ICMPs ICMPRules `json:"icmps,omitempty"`
   238  }
   239  
   240  // SetAggregatedSelectors creates a single slice containing all of the following
   241  // fields within the EgressCommonRule, converted to EndpointSelector, to be
   242  // stored by the caller of the EgressCommonRule for easy lookup while performing
   243  // policy evaluation for the rule:
   244  // * ToEntities
   245  // * ToCIDR
   246  // * ToCIDRSet
   247  // * ToFQDNs
   248  //
   249  // ToEndpoints is not aggregated due to requirement folding in
   250  // GetDestinationEndpointSelectorsWithRequirements()
   251  func (e *EgressCommonRule) getAggregatedSelectors() EndpointSelectorSlice {
   252  	// explicitly check for empty non-nil slices, it should not result in any identity being selected.
   253  	if (e.ToEntities != nil && len(e.ToEntities) == 0) ||
   254  		(e.ToCIDR != nil && len(e.ToCIDR) == 0) ||
   255  		(e.ToCIDRSet != nil && len(e.ToCIDRSet) == 0) {
   256  		return nil
   257  	}
   258  
   259  	res := make(EndpointSelectorSlice, 0, len(e.ToEntities)+len(e.ToCIDR)+len(e.ToCIDRSet))
   260  	res = append(res, e.ToEntities.GetAsEndpointSelectors()...)
   261  	res = append(res, e.ToCIDR.GetAsEndpointSelectors()...)
   262  	res = append(res, e.ToCIDRSet.GetAsEndpointSelectors()...)
   263  	return res
   264  }
   265  
   266  // SetAggregatedSelectors creates a single slice containing all of the following
   267  // fields within the EgressRule, converted to EndpointSelector, to be stored
   268  // within the EgressRule for easy lookup while performing policy evaluation
   269  // for the rule:
   270  // * ToEntities
   271  // * ToCIDR
   272  // * ToCIDRSet
   273  // * ToFQDNs
   274  //
   275  // ToEndpoints is not aggregated due to requirement folding in
   276  // GetDestinationEndpointSelectorsWithRequirements()
   277  func (e *EgressRule) SetAggregatedSelectors() {
   278  	ess := e.getAggregatedSelectors()
   279  	ess = append(ess, e.ToFQDNs.GetAsEndpointSelectors()...)
   280  	e.aggregatedSelectors = ess
   281  }
   282  
   283  // SetAggregatedSelectors creates a single slice containing all of the following
   284  // fields within the EgressRule, converted to EndpointSelector, to be stored
   285  // within the EgressRule for easy lookup while performing policy evaluation
   286  // for the rule:
   287  // * ToEntities
   288  // * ToCIDR
   289  // * ToCIDRSet
   290  // * ToFQDNs
   291  //
   292  // ToEndpoints is not aggregated due to requirement folding in
   293  // GetDestinationEndpointSelectorsWithRequirements()
   294  func (e *EgressCommonRule) SetAggregatedSelectors() {
   295  	e.aggregatedSelectors = e.getAggregatedSelectors()
   296  }
   297  
   298  // GetDestinationEndpointSelectorsWithRequirements returns a slice of endpoints selectors covering
   299  // all L3 dst selectors of the egress rule
   300  func (e *EgressRule) GetDestinationEndpointSelectorsWithRequirements(requirements []slim_metav1.LabelSelectorRequirement) EndpointSelectorSlice {
   301  	if e.aggregatedSelectors == nil {
   302  		e.SetAggregatedSelectors()
   303  	}
   304  	return e.EgressCommonRule.getDestinationEndpointSelectorsWithRequirements(requirements)
   305  }
   306  
   307  // GetDestinationEndpointSelectorsWithRequirements returns a slice of endpoints selectors covering
   308  // all L3 source selectors of the ingress rule
   309  func (e *EgressDenyRule) GetDestinationEndpointSelectorsWithRequirements(requirements []slim_metav1.LabelSelectorRequirement) EndpointSelectorSlice {
   310  	if e.aggregatedSelectors == nil {
   311  		e.SetAggregatedSelectors()
   312  	}
   313  	return e.EgressCommonRule.getDestinationEndpointSelectorsWithRequirements(requirements)
   314  }
   315  
   316  // GetDestinationEndpointSelectorsWithRequirements returns a slice of endpoints selectors covering
   317  // all L3 source selectors of the ingress rule
   318  func (e *EgressCommonRule) getDestinationEndpointSelectorsWithRequirements(
   319  	requirements []slim_metav1.LabelSelectorRequirement,
   320  ) EndpointSelectorSlice {
   321  
   322  	// explicitly check for empty non-nil slices, it should not result in any identity being selected.
   323  	if e.aggregatedSelectors == nil || (e.ToEndpoints != nil && len(e.ToEndpoints) == 0) ||
   324  		(e.ToNodes != nil && len(e.ToNodes) == 0) {
   325  		return nil
   326  	}
   327  
   328  	res := make(EndpointSelectorSlice, 0, len(e.ToEndpoints)+len(e.aggregatedSelectors)+len(e.ToNodes))
   329  
   330  	if len(requirements) > 0 && len(e.ToEndpoints) > 0 {
   331  		for idx := range e.ToEndpoints {
   332  			sel := *e.ToEndpoints[idx].DeepCopy()
   333  			sel.MatchExpressions = append(sel.MatchExpressions, requirements...)
   334  			sel.SyncRequirementsWithLabelSelector()
   335  			// Even though this string is deep copied, we need to override it
   336  			// because we are updating the contents of the MatchExpressions.
   337  			sel.cachedLabelSelectorString = sel.LabelSelector.String()
   338  			res = append(res, sel)
   339  		}
   340  	} else {
   341  		res = append(res, e.ToEndpoints...)
   342  		res = append(res, e.ToNodes...)
   343  	}
   344  	return append(res, e.aggregatedSelectors...)
   345  }
   346  
   347  // AllowsWildcarding returns true if wildcarding should be performed upon
   348  // policy evaluation for the given rule.
   349  func (e *EgressRule) AllowsWildcarding() bool {
   350  	return e.EgressCommonRule.AllowsWildcarding() && len(e.ToFQDNs) == 0
   351  }
   352  
   353  // AllowsWildcarding returns true if wildcarding should be performed upon
   354  // policy evaluation for the given rule.
   355  func (e *EgressCommonRule) AllowsWildcarding() bool {
   356  	return len(e.ToRequires)+len(e.ToServices) == 0
   357  }
   358  
   359  // RequiresDerivative returns true when the EgressCommonRule contains sections
   360  // that need a derivative policy created in order to be enforced
   361  // (e.g. ToGroups).
   362  func (e *EgressCommonRule) RequiresDerivative() bool {
   363  	return len(e.ToGroups) > 0
   364  }
   365  
   366  // CreateDerivative will return a new rule based on the data gathered by the
   367  // rules that creates a new derivative policy.
   368  // In the case of ToGroups will call outside using the groups callback and this
   369  // function can take a bit of time.
   370  func (e *EgressRule) CreateDerivative(ctx context.Context) (*EgressRule, error) {
   371  	newRule := e.DeepCopy()
   372  	if !e.RequiresDerivative() {
   373  		return newRule, nil
   374  	}
   375  	newRule.ToCIDRSet = make(CIDRRuleSlice, 0, len(e.ToGroups))
   376  	cidrSet, err := ExtractCidrSet(ctx, e.ToGroups)
   377  	if err != nil {
   378  		return &EgressRule{}, err
   379  	}
   380  	newRule.ToCIDRSet = append(e.ToCIDRSet, cidrSet...)
   381  	newRule.ToGroups = nil
   382  	e.SetAggregatedSelectors()
   383  	return newRule, nil
   384  }
   385  
   386  // CreateDerivative will return a new rule based on the data gathered by the
   387  // rules that creates a new derivative policy.
   388  // In the case of ToGroups will call outside using the groups callback and this
   389  // function can take a bit of time.
   390  func (e *EgressDenyRule) CreateDerivative(ctx context.Context) (*EgressDenyRule, error) {
   391  	newRule := e.DeepCopy()
   392  	if !e.RequiresDerivative() {
   393  		return newRule, nil
   394  	}
   395  	newRule.ToCIDRSet = make(CIDRRuleSlice, 0, len(e.ToGroups))
   396  	cidrSet, err := ExtractCidrSet(ctx, e.ToGroups)
   397  	if err != nil {
   398  		return &EgressDenyRule{}, err
   399  	}
   400  	newRule.ToCIDRSet = append(e.ToCIDRSet, cidrSet...)
   401  	newRule.ToGroups = nil
   402  	e.SetAggregatedSelectors()
   403  	return newRule, nil
   404  }