diff --git a/pkg/apis/extensions/v1beta1/conversion.go b/pkg/apis/extensions/v1beta1/conversion.go index 62167b6f201..292c07c5b17 100644 --- a/pkg/apis/extensions/v1beta1/conversion.go +++ b/pkg/apis/extensions/v1beta1/conversion.go @@ -62,6 +62,8 @@ func addConversionFuncs(scheme *runtime.Scheme) error { Convert_extensions_PodSecurityPolicySpec_To_v1beta1_PodSecurityPolicySpec, Convert_v1beta1_IPBlock_To_networking_IPBlock, Convert_networking_IPBlock_To_v1beta1_IPBlock, + Convert_networking_NetworkPolicyEgressRule_To_v1beta1_NetworkPolicyEgressRule, + Convert_v1beta1_NetworkPolicyEgressRule_To_networking_NetworkPolicyEgressRule, ) if err != nil { return err @@ -283,6 +285,12 @@ func Convert_v1beta1_NetworkPolicySpec_To_networking_NetworkPolicySpec(in *exten return err } } + out.Egress = make([]networking.NetworkPolicyEgressRule, len(in.Egress)) + for i := range in.Egress { + if err := Convert_v1beta1_NetworkPolicyEgressRule_To_networking_NetworkPolicyEgressRule(&in.Egress[i], &out.Egress[i], s); err != nil { + return err + } + } return nil } @@ -296,6 +304,12 @@ func Convert_networking_NetworkPolicySpec_To_v1beta1_NetworkPolicySpec(in *netwo return err } } + out.Egress = make([]extensionsv1beta1.NetworkPolicyEgressRule, len(in.Egress)) + for i := range in.Egress { + if err := Convert_networking_NetworkPolicyEgressRule_To_v1beta1_NetworkPolicyEgressRule(&in.Egress[i], &out.Egress[i], s); err != nil { + return err + } + } return nil } @@ -331,6 +345,38 @@ func Convert_networking_NetworkPolicyIngressRule_To_v1beta1_NetworkPolicyIngress return nil } +func Convert_v1beta1_NetworkPolicyEgressRule_To_networking_NetworkPolicyEgressRule(in *extensionsv1beta1.NetworkPolicyEgressRule, out *networking.NetworkPolicyEgressRule, s conversion.Scope) error { + out.Ports = make([]networking.NetworkPolicyPort, len(in.Ports)) + for i := range in.Ports { + if err := Convert_v1beta1_NetworkPolicyPort_To_networking_NetworkPolicyPort(&in.Ports[i], &out.Ports[i], s); err != nil { + return err + } + } + out.To = make([]networking.NetworkPolicyPeer, len(in.To)) + for i := range in.To { + if err := Convert_v1beta1_NetworkPolicyPeer_To_networking_NetworkPolicyPeer(&in.To[i], &out.To[i], s); err != nil { + return err + } + } + return nil +} + +func Convert_networking_NetworkPolicyEgressRule_To_v1beta1_NetworkPolicyEgressRule(in *networking.NetworkPolicyEgressRule, out *extensionsv1beta1.NetworkPolicyEgressRule, s conversion.Scope) error { + out.Ports = make([]extensionsv1beta1.NetworkPolicyPort, len(in.Ports)) + for i := range in.Ports { + if err := Convert_networking_NetworkPolicyPort_To_v1beta1_NetworkPolicyPort(&in.Ports[i], &out.Ports[i], s); err != nil { + return err + } + } + out.To = make([]extensionsv1beta1.NetworkPolicyPeer, len(in.To)) + for i := range in.To { + if err := Convert_networking_NetworkPolicyPeer_To_v1beta1_NetworkPolicyPeer(&in.To[i], &out.To[i], s); err != nil { + return err + } + } + return nil +} + func Convert_v1beta1_NetworkPolicyPeer_To_networking_NetworkPolicyPeer(in *extensionsv1beta1.NetworkPolicyPeer, out *networking.NetworkPolicyPeer, s conversion.Scope) error { if in.PodSelector != nil { out.PodSelector = new(metav1.LabelSelector) diff --git a/pkg/apis/networking/types.go b/pkg/apis/networking/types.go index 74d818cd77f..840841fa696 100644 --- a/pkg/apis/networking/types.go +++ b/pkg/apis/networking/types.go @@ -55,6 +55,15 @@ type NetworkPolicySpec struct { // solely to ensure that the pods it selects are isolated by default) // +optional Ingress []NetworkPolicyIngressRule + + // List of egress rules to be applied to the selected pods. Outgoing traffic is + // allowed if there are no NetworkPolicies selecting the pod (and cluster policy + // otherwise allows the traffic), OR if the traffic matches at least one egress rule + // across all of the NetworkPolicy objects whose podSelector matches the pod. If + // this field is empty then this NetworkPolicy does not limit any outgoing traffic + // (and serves solely to ensure that the pods it selects does not allow any outgoing traffic.) + // +optional + Egress []NetworkPolicyEgressRule } // NetworkPolicyIngressRule describes a particular set of traffic that is allowed to the pods @@ -77,6 +86,26 @@ type NetworkPolicyIngressRule struct { From []NetworkPolicyPeer } +// NetworkPolicyEgressRule describes a particular set of traffic that is allowed out of pods +// matched by a NetworkPolicySpec's podSelector. The traffic must match both ports and to. +type NetworkPolicyEgressRule struct { + // List of destination ports for outgoing traffic. + // Each item in this list is combined using a logical OR. If this field is + // empty or missing, this rule matches all ports (traffic not restricted by port). + // If this field is present and contains at least one item, then this rule allows + // traffic only if the traffic matches at least one port in the list. + // +optional + Ports []NetworkPolicyPort + + // List of destinations for outgoing traffic of pods selected for this rule. + // Items in this list are combined using a logical OR operation. If this field is + // empty or missing, this rule matches all destinations (traffic not restricted by + // destination). If this field is present and contains at least one item, this rule + // allows traffic only if the traffic matches at least one item in the to list. + // +optional + To []NetworkPolicyPeer +} + // NetworkPolicyPort describes a port to allow traffic on type NetworkPolicyPort struct { // The protocol (TCP or UDP) which traffic must match. If not specified, this diff --git a/pkg/apis/networking/validation/validation.go b/pkg/apis/networking/validation/validation.go index 780cc652905..7541da6588a 100644 --- a/pkg/apis/networking/validation/validation.go +++ b/pkg/apis/networking/validation/validation.go @@ -81,6 +81,48 @@ func ValidateNetworkPolicySpec(spec *networking.NetworkPolicySpec, fldPath *fiel } } } + // Validate egress rules + for i, egress := range spec.Egress { + egressPath := fldPath.Child("egress").Index(i) + for i, port := range egress.Ports { + portPath := egressPath.Child("ports").Index(i) + if port.Protocol != nil && *port.Protocol != api.ProtocolTCP && *port.Protocol != api.ProtocolUDP { + allErrs = append(allErrs, field.NotSupported(portPath.Child("protocol"), *port.Protocol, []string{string(api.ProtocolTCP), string(api.ProtocolUDP)})) + } + if port.Port != nil { + if port.Port.Type == intstr.Int { + for _, msg := range validation.IsValidPortNum(int(port.Port.IntVal)) { + allErrs = append(allErrs, field.Invalid(portPath.Child("port"), port.Port.IntVal, msg)) + } + } else { + for _, msg := range validation.IsValidPortName(port.Port.StrVal) { + allErrs = append(allErrs, field.Invalid(portPath.Child("port"), port.Port.StrVal, msg)) + } + } + } + } + for i, to := range egress.To { + toPath := egressPath.Child("to").Index(i) + numTo := 0 + if to.PodSelector != nil { + numTo++ + allErrs = append(allErrs, unversionedvalidation.ValidateLabelSelector(to.PodSelector, toPath.Child("podSelector"))...) + } + if to.NamespaceSelector != nil { + numTo++ + allErrs = append(allErrs, unversionedvalidation.ValidateLabelSelector(to.NamespaceSelector, toPath.Child("namespaceSelector"))...) + } + if to.IPBlock != nil { + numTo++ + allErrs = append(allErrs, ValidateIPBlock(to.IPBlock, toPath.Child("ipBlock"))...) + } + if numTo == 0 { + allErrs = append(allErrs, field.Required(toPath, "must specify a to type")) + } else if numTo > 1 { + allErrs = append(allErrs, field.Forbidden(toPath, "may not specify more than 1 to type")) + } + } + } return allErrs } diff --git a/pkg/apis/networking/validation/validation_test.go b/pkg/apis/networking/validation/validation_test.go index 0be1f0998c5..242fd77dada 100644 --- a/pkg/apis/networking/validation/validation_test.go +++ b/pkg/apis/networking/validation/validation_test.go @@ -128,6 +128,17 @@ func TestValidateNetworkPolicy(t *testing.T) { PodSelector: metav1.LabelSelector{ MatchLabels: map[string]string{"a": "b"}, }, + Egress: []networking.NetworkPolicyEgressRule{ + { + To: []networking.NetworkPolicyPeer{ + { + NamespaceSelector: &metav1.LabelSelector{ + MatchLabels: map[string]string{"c": "d"}, + }, + }, + }, + }, + }, Ingress: []networking.NetworkPolicyIngressRule{ { From: []networking.NetworkPolicyPeer{ @@ -142,6 +153,46 @@ func TestValidateNetworkPolicy(t *testing.T) { }, }, }, + { + ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "bar"}, + Spec: networking.NetworkPolicySpec{ + PodSelector: metav1.LabelSelector{ + MatchLabels: map[string]string{"a": "b"}, + }, + Ingress: []networking.NetworkPolicyIngressRule{ + { + From: []networking.NetworkPolicyPeer{ + { + IPBlock: &networking.IPBlock{ + CIDR: "192.168.0.0/16", + Except: []string{"192.168.3.0/24", "192.168.4.0/24"}, + }, + }, + }, + }, + }, + }, + }, + { + ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "bar"}, + Spec: networking.NetworkPolicySpec{ + PodSelector: metav1.LabelSelector{ + MatchLabels: map[string]string{"a": "b"}, + }, + Egress: []networking.NetworkPolicyEgressRule{ + { + To: []networking.NetworkPolicyPeer{ + { + IPBlock: &networking.IPBlock{ + CIDR: "192.168.0.0/16", + Except: []string{"192.168.3.0/24", "192.168.4.0/24"}, + }, + }, + }, + }, + }, + }, + }, } // Success cases are expected to pass validation. @@ -259,6 +310,23 @@ func TestValidateNetworkPolicy(t *testing.T) { }, }, }, + "invalid egress.to.podSelector": { + ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "bar"}, + Spec: networking.NetworkPolicySpec{ + PodSelector: metav1.LabelSelector{}, + Egress: []networking.NetworkPolicyEgressRule{ + { + To: []networking.NetworkPolicyPeer{ + { + PodSelector: &metav1.LabelSelector{ + MatchLabels: invalidSelector, + }, + }, + }, + }, + }, + }, + }, "invalid ingress.from.namespaceSelector": { ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "bar"}, Spec: networking.NetworkPolicySpec{ diff --git a/staging/src/k8s.io/api/extensions/v1beta1/types.go b/staging/src/k8s.io/api/extensions/v1beta1/types.go index fdd342b2617..d372aa51aad 100644 --- a/staging/src/k8s.io/api/extensions/v1beta1/types.go +++ b/staging/src/k8s.io/api/extensions/v1beta1/types.go @@ -1174,6 +1174,15 @@ type NetworkPolicySpec struct { // (and serves solely to ensure that the pods it selects are isolated by default). // +optional Ingress []NetworkPolicyIngressRule `json:"ingress,omitempty" protobuf:"bytes,2,rep,name=ingress"` + + // List of egress rules to be applied to the selected pods. Outgoing traffic is + // allowed if there are no NetworkPolicies selecting the pod (and cluster policy + // otherwise allows the traffic), OR if the traffic matches at least one egress rule + // across all of the NetworkPolicy objects whose podSelector matches the pod. If + // this field is empty then this NetworkPolicy does not limit any outgoing traffic + // (and serves solely to ensure that the pods it selects does not allow any outgoing traffic.) + // +optional + Egress []NetworkPolicyEgressRule `json:"egress,omitempty" protobuf:"bytes,3,rep,name=egress"` } // This NetworkPolicyIngressRule matches traffic if and only if the traffic matches both ports AND from. @@ -1195,6 +1204,26 @@ type NetworkPolicyIngressRule struct { From []NetworkPolicyPeer `json:"from,omitempty" protobuf:"bytes,2,rep,name=from"` } +// NetworkPolicyEgressRule describes a particular set of traffic that is allowed out of pods +// matched by a NetworkPolicySpec's podSelector. The traffic must match both ports and to. +type NetworkPolicyEgressRule struct { + // List of destination ports for outgoing traffic. + // Each item in this list is combined using a logical OR. If this field is + // empty or missing, this rule matches all ports (traffic not restricted by port). + // If this field is present and contains at least one item, then this rule allows + // traffic only if the traffic matches at least one port in the list. + // +optional + Ports []NetworkPolicyPort `json:"ports,omitempty" protobuf:"bytes,1,rep,name=ports"` + + // List of destinations for outgoing traffic of pods selected for this rule. + // Items in this list are combined using a logical OR operation. If this field is + // empty or missing, this rule matches all destinations (traffic not restricted by + // destination). If this field is present and contains at least one item, this rule + // allows traffic only if the traffic matches at least one item in the to list. + // +optional + To []NetworkPolicyPeer `json:"to,omitempty" protobuf:"bytes,2,rep,name=to"` +} + type NetworkPolicyPort struct { // Optional. The protocol (TCP or UDP) which traffic must match. // If not specified, this field defaults to TCP. diff --git a/staging/src/k8s.io/api/networking/v1/types.go b/staging/src/k8s.io/api/networking/v1/types.go index a0c7721b2d6..d420c4d7297 100644 --- a/staging/src/k8s.io/api/networking/v1/types.go +++ b/staging/src/k8s.io/api/networking/v1/types.go @@ -57,6 +57,15 @@ type NetworkPolicySpec struct { // solely to ensure that the pods it selects are isolated by default) // +optional Ingress []NetworkPolicyIngressRule `json:"ingress,omitempty" protobuf:"bytes,2,rep,name=ingress"` + + // List of egress rules to be applied to the selected pods. Outgoing traffic is + // allowed if there are no NetworkPolicies selecting the pod (and cluster policy + // otherwise allows the traffic), OR if the traffic matches at least one egress rule + // across all of the NetworkPolicy objects whose podSelector matches the pod. If + // this field is empty then this NetworkPolicy does not limit any outgoing traffic + // (and serves solely to ensure that the pods it selects does not allow any outgoing traffic.) + // +optional + Egress []NetworkPolicyEgressRule `json:"egress,omitempty" protobuf:"bytes,3,rep,name=egress"` } // NetworkPolicyIngressRule describes a particular set of traffic that is allowed to the pods @@ -79,6 +88,26 @@ type NetworkPolicyIngressRule struct { From []NetworkPolicyPeer `json:"from,omitempty" protobuf:"bytes,2,rep,name=from"` } +// NetworkPolicyEgressRule describes a particular set of traffic that is allowed out of pods +// matched by a NetworkPolicySpec's podSelector. The traffic must match both ports and to. +type NetworkPolicyEgressRule struct { + // List of destination ports for outgoing traffic. + // Each item in this list is combined using a logical OR. If this field is + // empty or missing, this rule matches all ports (traffic not restricted by port). + // If this field is present and contains at least one item, then this rule allows + // traffic only if the traffic matches at least one port in the list. + // +optional + Ports []NetworkPolicyPort `json:"ports,omitempty" protobuf:"bytes,1,rep,name=ports"` + + // List of destinations for outgoing traffic of pods selected for this rule. + // Items in this list are combined using a logical OR operation. If this field is + // empty or missing, this rule matches all destinations (traffic not restricted by + // destination). If this field is present and contains at least one item, this rule + // allows traffic only if the traffic matches at least one item in the to list. + // +optional + To []NetworkPolicyPeer `json:"to,omitempty" protobuf:"bytes,2,rep,name=to"` +} + // NetworkPolicyPort describes a port to allow traffic on type NetworkPolicyPort struct { // The protocol (TCP or UDP) which traffic must match. If not specified, this