github.com/cilium/cilium@v1.16.2/pkg/k8s/apis/cilium.io/v2/validator/validator_test.go (about)

     1  // SPDX-License-Identifier: Apache-2.0
     2  // Copyright Authors of Cilium
     3  
     4  package validator
     5  
     6  import (
     7  	"encoding/json"
     8  	"testing"
     9  
    10  	"github.com/stretchr/testify/require"
    11  	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
    12  	"sigs.k8s.io/yaml"
    13  )
    14  
    15  func Test_GH10643(t *testing.T) {
    16  	cnp := []byte(`apiVersion: cilium.io/v2
    17  kind: CiliumNetworkPolicy
    18  metadata:
    19    name: exampleapp
    20    namespace: examplens
    21  spec:
    22    egress:
    23    - toFQDNs:
    24      - matchPattern: prefix*.tier.location.example.com
    25      toPorts:
    26      - ports:
    27        - port "8050"
    28        - protocol TCP
    29        - port "8051"
    30        - protocol TCP
    31        - port "8052"
    32        - protocol TCP
    33        - port "8053"
    34        - protocol TCP
    35        - port "8054"
    36        - protocol TCP
    37        - port "8055"
    38        - protocol TCP
    39        - port "8056"
    40        - protocol TCP
    41        - port "8057"
    42        - protocol TCP
    43        - port "8058"
    44        - protocol TCP
    45        - port "8059"
    46        - protocol TCP
    47    endpointSelector:
    48      matchExpressions:
    49      - key: app
    50        operator: In
    51        values:
    52        - example-app-0-spark-worker
    53        - example-app-0-spark-driver
    54        - example-app-0-spark-worker-qe3
    55        - example-app-0-spark-driver-qe3
    56        - example-app-1-spark-worker
    57        - example-app-1-spark-driver
    58        - example-app-1-spark-worker-qe3
    59        - example-app-1-spark-driver-qe3
    60  `)
    61  	jsnByte, err := yaml.YAMLToJSON(cnp)
    62  	require.NoError(t, err)
    63  
    64  	us := unstructured.Unstructured{}
    65  	err = json.Unmarshal(jsnByte, &us)
    66  	require.NoError(t, err)
    67  
    68  	validator, err := NewNPValidator()
    69  	require.NoError(t, err)
    70  	err = validator.ValidateCNP(&us)
    71  	// Err can't be nil since validation should detect the policy is not correct.
    72  	require.Error(t, err)
    73  }
    74  
    75  func Test_BadMatchLabels(t *testing.T) {
    76  	cnp := []byte(`apiVersion: cilium.io/v2
    77  kind: CiliumNetworkPolicy
    78  metadata:
    79    name: cnp-test-1
    80    namespace: ns-2
    81  spec:
    82    egress:
    83    - toServices:
    84      - k8sService:
    85          namespace: default
    86          serviceName: kubernetes
    87    endpointSelector:
    88      matchLabels:
    89        key: app
    90        operator: In
    91        values:
    92        - prometheus
    93        - kube-state-metrics
    94  `)
    95  	jsnByte, err := yaml.YAMLToJSON(cnp)
    96  	require.NoError(t, err)
    97  
    98  	us := unstructured.Unstructured{}
    99  	err = json.Unmarshal(jsnByte, &us)
   100  	require.NoError(t, err)
   101  
   102  	validator, err := NewNPValidator()
   103  	require.NoError(t, err)
   104  	err = validator.ValidateCNP(&us)
   105  	// Err can't be nil since validation should detect the policy is not correct.
   106  	require.Error(t, err)
   107  }
   108  
   109  func Test_GoodCNP(t *testing.T) {
   110  	cnp := []byte(`apiVersion: cilium.io/v2
   111  kind: CiliumNetworkPolicy
   112  metadata:
   113    name: cnp-test-1
   114    namespace: ns-2
   115  spec:
   116    egress:
   117    - toServices:
   118      - k8sService:
   119          namespace: default
   120          serviceName: kubernetes
   121    endpointSelector:
   122      matchLabels:
   123        key: app
   124        operator: In
   125  `)
   126  	jsnByte, err := yaml.YAMLToJSON(cnp)
   127  	require.NoError(t, err)
   128  
   129  	us := unstructured.Unstructured{}
   130  	err = json.Unmarshal(jsnByte, &us)
   131  	require.NoError(t, err)
   132  
   133  	validator, err := NewNPValidator()
   134  	require.NoError(t, err)
   135  	err = validator.ValidateCNP(&us)
   136  	require.NoError(t, err)
   137  }
   138  
   139  func Test_GoodCCNP(t *testing.T) {
   140  	ccnp := []byte(`apiVersion: cilium.io/v2
   141  kind: CiliumClusterwideNetworkPolicy
   142  metadata:
   143    name: cnp-test-1
   144  spec:
   145    egress:
   146    - toServices:
   147      - k8sService:
   148          namespace: default
   149          serviceName: kubernetes
   150    endpointSelector:
   151      matchLabels:
   152        key: app
   153        operator: In
   154  `)
   155  	jsnByte, err := yaml.YAMLToJSON(ccnp)
   156  	require.NoError(t, err)
   157  
   158  	us := unstructured.Unstructured{}
   159  	err = json.Unmarshal(jsnByte, &us)
   160  	require.NoError(t, err)
   161  
   162  	validator, err := NewNPValidator()
   163  	require.NoError(t, err)
   164  	err = validator.ValidateCCNP(&us)
   165  	require.NoError(t, err)
   166  }
   167  
   168  func Test_BadCCNP(t *testing.T) {
   169  	// Bad CCNP with endpointSelector and nodeSelector
   170  	ccnp := []byte(`apiVersion: cilium.io/v2
   171  kind: CiliumClusterwideNetworkPolicy
   172  metadata:
   173    name: cnp-test-1
   174  spec:
   175    egress:
   176    - toServices:
   177      - k8sService:
   178          namespace: default
   179          serviceName: kubernetes
   180    endpointSelector:
   181      matchLabels:
   182        key: app
   183        operator: In
   184    nodeSelector:
   185      matchLabels:
   186        key: app
   187        operator: In
   188  `)
   189  	jsnByte, err := yaml.YAMLToJSON(ccnp)
   190  	require.NoError(t, err)
   191  
   192  	us := unstructured.Unstructured{}
   193  	err = json.Unmarshal(jsnByte, &us)
   194  	require.NoError(t, err)
   195  
   196  	validator, err := NewNPValidator()
   197  	require.NoError(t, err)
   198  	err = validator.ValidateCCNP(&us)
   199  	// Err can't be nil since validation should detect the policy is not correct.
   200  	require.Error(t, err)
   201  }
   202  
   203  func Test_UnknownFieldDetection(t *testing.T) {
   204  	tests := []struct {
   205  		name        string
   206  		policy      []byte
   207  		clusterwide bool
   208  		err         error
   209  	}{
   210  		{
   211  			name: "ccnp GH-14526",
   212  			policy: []byte(`
   213  apiVersion: cilium.io/v2
   214  kind: CiliumClusterwideNetworkPolicy
   215  metadata:
   216    annotations:
   217      kubectl.kubernetes.io/last-applied-configuration: |
   218        {"apiVersion":"cilium.io/v2","kind":"CiliumClusterwideNetworkPolicy","metadata":{"annotations":{},"name":"ccnp-default-deny-egress"},"spec":{"egress":[{}],"endpointSelector":{}}}
   219    creationTimestamp: "2021-01-07T00:26:34Z"
   220    generation: 1
   221    managedFields:
   222    - apiVersion: cilium.io/v2
   223      fieldsType: FieldsV1
   224      fieldsV1:
   225        f:metadata:
   226          f:annotations:
   227            .: {}
   228            f:kubectl.kubernetes.io/last-applied-configuration: {}
   229        f:spec:
   230          .: {}
   231          f:egress: {}
   232          f:endpointSelector: {}
   233      manager: kubectl-client-side-apply
   234      operation: Update
   235      time: "2021-01-07T00:26:34Z"
   236    name: ccnp-default-deny-egress
   237    resourceVersion: "7849"
   238    selfLink: /apis/cilium.io/v2/ciliumclusterwidenetworkpolicies/ccnp-default-deny-egress
   239    uid: f776ca84-86dc-4589-ab91-64fccdec468a
   240  spec:
   241    egress:
   242    - {}
   243    endpointSelector: {}
   244  `),
   245  			clusterwide: true,
   246  			err:         nil,
   247  		},
   248  		{
   249  			name: "cnp GH-14526",
   250  			policy: []byte(`
   251  apiVersion: cilium.io/v2
   252  kind: CiliumNetworkPolicy
   253  metadata:
   254    annotations:
   255      kubectl.kubernetes.io/last-applied-configuration: |
   256        {"apiVersion":"cilium.io/v2","kind":"CiliumNetworkPolicy","metadata":{"annotations":{},"name":"ccnp-default-deny-egress"},"spec":{"egress":[{}],"endpointSelector":{}}}
   257    creationTimestamp: "2021-01-07T00:26:34Z"
   258    generation: 1
   259    managedFields:
   260    - apiVersion: cilium.io/v2
   261      fieldsType: FieldsV1
   262      fieldsV1:
   263        f:metadata:
   264          f:annotations:
   265            .: {}
   266            f:kubectl.kubernetes.io/last-applied-configuration: {}
   267        f:spec:
   268          .: {}
   269          f:egress: {}
   270          f:endpointSelector: {}
   271      manager: kubectl-client-side-apply
   272      operation: Update
   273      time: "2021-01-07T00:26:34Z"
   274    name: ccnp-default-deny-egress
   275    resourceVersion: "7849"
   276    selfLink: /apis/cilium.io/v2/ciliumnetworkpolicies/ccnp-default-deny-egress
   277    uid: f776ca84-86dc-4589-ab91-64fccdec468a
   278  spec:
   279    egress:
   280    - {}
   281    endpointSelector: {}
   282  `),
   283  			clusterwide: false,
   284  			err:         nil,
   285  		},
   286  		{
   287  			name: "neither a cnp or ccnp",
   288  			policy: []byte(`
   289  kind: ServiceAccount
   290  apiVersion: v1
   291  metadata:
   292    name: app1-account
   293  `),
   294  			clusterwide: false,
   295  			err: ErrUnknownKind{
   296  				kind: "ServiceAccount",
   297  			},
   298  		},
   299  
   300  		{
   301  			name: "cnp top-level description exists",
   302  			policy: []byte(`
   303  apiVersion: cilium.io/v2
   304  kind: CiliumNetworkPolicy
   305  description: "default deny policy"
   306  metadata:
   307    name: cnp-test-1
   308  spec:
   309    endpointSelector: {}
   310  `),
   311  			clusterwide: false,
   312  			err:         ErrTopLevelDescriptionFound,
   313  		},
   314  		{
   315  			name: "cnp top-level description does not exist",
   316  			policy: []byte(`
   317  apiVersion: cilium.io/v2
   318  kind: CiliumNetworkPolicy
   319  metadata:
   320    name: cnp-test-1
   321  spec:
   322    endpointSelector: {}
   323  `),
   324  			clusterwide: false,
   325  			err:         nil,
   326  		},
   327  		{
   328  			name: "ccnp top-level description exists",
   329  			policy: []byte(`
   330  apiVersion: cilium.io/v2
   331  kind: CiliumClusterwideNetworkPolicy
   332  description: "default deny policy"
   333  metadata:
   334    name: ccnp-test-1
   335  spec:
   336    nodeSelector: {}
   337  `),
   338  			clusterwide: true,
   339  			err:         ErrTopLevelDescriptionFound,
   340  		},
   341  		{
   342  			name: "ccnp top-level description does not exist",
   343  			policy: []byte(`
   344  apiVersion: cilium.io/v2
   345  kind: CiliumClusterwideNetworkPolicy
   346  metadata:
   347    name: ccnp-test-1
   348  spec:
   349    nodeSelector: {}
   350  `),
   351  			clusterwide: true,
   352  			err:         nil,
   353  		},
   354  
   355  		{
   356  			name: "cnp extra unknown fields",
   357  			policy: []byte(`
   358  apiVersion: cilium.io/v2
   359  kind: CiliumNetworkPolicy
   360  foo: bar
   361  metadata:
   362    name: ccnp-test-1
   363  spec:
   364    endpointSelector: {}
   365    bar: baz
   366  `),
   367  			clusterwide: false,
   368  			err: ErrUnknownFields{
   369  				extras: []string{"foo", "spec.bar"},
   370  			},
   371  		},
   372  		{
   373  			name: "ccnp extra unknown fields",
   374  			policy: []byte(`
   375  apiVersion: cilium.io/v2
   376  kind: CiliumClusterwideNetworkPolicy
   377  foo: bar
   378  metadata:
   379    name: ccnp-test-1
   380  spec:
   381    nodeSelector: {}
   382    bar: baz
   383  `),
   384  			clusterwide: true,
   385  			err: ErrUnknownFields{
   386  				extras: []string{"foo", "spec.bar"},
   387  			},
   388  		},
   389  		{
   390  			name: "cnp specs",
   391  			policy: []byte(`
   392  apiVersion: "cilium.io/v2"
   393  kind: CiliumNetworkPolicy
   394  metadata:
   395    name: "cnp-specs"
   396  specs:
   397    - description: "Policy to test multiple rules in a single file"
   398      endpointSelector:
   399        matchLabels:
   400          app: ratings
   401          version: v1
   402      ingress:
   403      - fromEndpoints:
   404        - matchLabels:
   405            app: reviews
   406            track: stable
   407            version: v1
   408        toPorts:
   409        - ports:
   410          - port: "9080"
   411            protocol: TCP
   412          rules:
   413            http:
   414            - method: "GET"
   415              path: "/health"
   416    - endpointSelector:
   417        matchLabels:
   418          app: details
   419          track: stable
   420          version: v1
   421      ingress:
   422      - fromEndpoints:
   423        - matchLabels:
   424            app: productpage
   425            track: stable
   426            version: v1
   427        toPorts:
   428        - ports:
   429          - port: "9080"
   430            protocol: TCP
   431          rules:
   432            http:
   433            - method: "GET"
   434              path: "/.*"
   435  `),
   436  			clusterwide: false,
   437  			err:         nil,
   438  		},
   439  	}
   440  	for _, tt := range tests {
   441  		t.Log(tt.name)
   442  
   443  		jsnByte, err := yaml.YAMLToJSON(tt.policy)
   444  		require.NoError(t, err)
   445  
   446  		us := unstructured.Unstructured{}
   447  		err = json.Unmarshal(jsnByte, &us)
   448  		require.NoError(t, err)
   449  
   450  		validator, err := NewNPValidator()
   451  		require.NoError(t, err)
   452  
   453  		if tt.clusterwide {
   454  			require.EqualValues(t, tt.err, validator.ValidateCCNP(&us))
   455  		} else {
   456  			require.EqualValues(t, tt.err, validator.ValidateCNP(&us))
   457  		}
   458  	}
   459  }
   460  
   461  func Test_GH28007(t *testing.T) {
   462  	cnp := []byte(`apiVersion: cilium.io/v2
   463  kind: CiliumNetworkPolicy
   464  metadata:
   465    name: exampleapp
   466    namespace: examplens
   467  spec:
   468    egress:
   469    - toEntities:
   470      - world
   471    endpointSelector:
   472      matchExpressions:
   473      - key: reserved:init
   474        operator: DoesNotExist
   475  `)
   476  	jsnByte, err := yaml.YAMLToJSON(cnp)
   477  	require.NoError(t, err)
   478  
   479  	us := unstructured.Unstructured{}
   480  	err = json.Unmarshal(jsnByte, &us)
   481  	require.NoError(t, err)
   482  
   483  	validator, err := NewNPValidator()
   484  	require.NoError(t, err)
   485  	err = validator.ValidateCNP(&us)
   486  	// Err can't be nil since validation should detect the policy is not correct.
   487  	require.Equal(t, errInitPolicyCNP, err)
   488  }