...

Source file src/github.com/sigstore/cosign/v2/pkg/policy/eval_test.go

Documentation: github.com/sigstore/cosign/v2/pkg/policy

     1  //
     2  // Copyright 2022 The Sigstore Authors.
     3  //
     4  // Licensed under the Apache License, Version 2.0 (the "License");
     5  // you may not use this file except in compliance with the License.
     6  // You may obtain a copy of the License at
     7  //
     8  //     http://www.apache.org/licenses/LICENSE-2.0
     9  //
    10  // Unless required by applicable law or agreed to in writing, software
    11  // distributed under the License is distributed on an "AS IS" BASIS,
    12  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  // See the License for the specific language governing permissions and
    14  // limitations under the License.
    15  
    16  package policy
    17  
    18  import (
    19  	"context"
    20  	"strings"
    21  	"testing"
    22  )
    23  
    24  const (
    25  	customAttestation = `
    26  	{
    27  		"_type": "https://in-toto.io/Statement/v0.1",
    28  		"predicateType": "https://cosign.sigstore.dev/attestation/v1",
    29  		"subject": [
    30  		  {
    31  			"name": "registry.local:5000/policy-controller/demo",
    32  			"digest": {
    33  			  "sha256": "416cc82c76114b1744ea58bcbf2f411a0f2de4b0456703bf1bb83d33656951bc"
    34  			}
    35  		  }
    36  		],
    37  		"predicate": {
    38  		  "Data": "foobar e2e test",
    39  		  "Timestamp": "2022-04-20T18:17:19Z"
    40  		}
    41  	  }`
    42  
    43  	vulnAttestation = `
    44  	{
    45  		"_type": "https://in-toto.io/Statement/v0.1",
    46  		"predicateType": "https://cosign.sigstore.dev/attestation/vuln/v1",
    47  		"subject": [
    48  		  {
    49  			"name": "registry.local:5000/policy-controller/demo",
    50  			"digest": {
    51  			  "sha256": "416cc82c76114b1744ea58bcbf2f411a0f2de4b0456703bf1bb83d33656951bc"
    52  			}
    53  		  }
    54  		],
    55  		"predicate": {
    56  		  "invocation": {
    57  			"parameters": null,
    58  			"uri": "invocation.example.com/cosign-testing",
    59  			"event_id": "",
    60  			"builder.id": ""
    61  		  },
    62  		  "scanner": {
    63  			"uri": "fakescanner.example.com/cosign-testing",
    64  			"version": "",
    65  			"db": {
    66  			  "uri": "",
    67  			  "version": ""
    68  			},
    69  			"result": null
    70  		  },
    71  		  "metadata": {
    72  			"scanStartedOn": "2022-04-12T00:00:00Z",
    73  			"scanFinishedOn": "2022-04-12T00:10:00Z"
    74  		  }
    75  		}
    76  	  }`
    77  
    78  	cipAttestation = "{\"authorityMatches\":{\"keyatt\":{\"signatures\":null,\"attestations\":{\"vuln-key\":[{\"subject\":\"PLACEHOLDER\",\"issuer\":\"PLACEHOLDER\"}]}},\"keysignature\":{\"signatures\":[{\"subject\":\"PLACEHOLDER\",\"issuer\":\"PLACEHOLDER\"}],\"attestations\":null},\"keylessatt\":{\"signatures\":null,\"attestations\":{\"custom-keyless\":[{\"subject\":\"PLACEHOLDER\",\"issuer\":\"PLACEHOLDER\"}]}}}}"
    79  )
    80  
    81  func TestEvalPolicy(t *testing.T) {
    82  	// TODO(vaikas): Consider moving the attestations/cue files into testdata
    83  	// directory.
    84  	tests := []struct {
    85  		name        string
    86  		json        string
    87  		policyType  string
    88  		policyFile  string
    89  		wantErr     bool
    90  		wantErrSub  string
    91  		wantWarnSub string
    92  	}{{
    93  		name:       "custom attestation, mismatched predicateType",
    94  		json:       customAttestation,
    95  		policyType: "cue",
    96  		policyFile: `predicateType: "https://cosign.sigstore.dev/attestation/vuln/v1"`,
    97  		wantErr:    true,
    98  		wantErrSub: `conflicting values "https://cosign.sigstore.dev/attestation/v1" and "https://cosign.sigstore.dev/attestation/vuln/v1"`,
    99  	}, {
   100  		name:       "custom attestation, predicateType and data checks out",
   101  		json:       customAttestation,
   102  		policyType: "cue",
   103  		policyFile: `predicateType: "https://cosign.sigstore.dev/attestation/v1"
   104  		predicate: Data: "foobar e2e test"`,
   105  	}, {
   106  		name:       "custom attestation, data mismatch",
   107  		json:       customAttestation,
   108  		policyType: "cue",
   109  		policyFile: `predicateType: "https://cosign.sigstore.dev/attestation/v1"
   110  		predicate: Data: "invalid data here"`,
   111  		wantErr:    true,
   112  		wantErrSub: `predicate.Data: conflicting values "foobar e2e test" and "invalid data here"`,
   113  	}, {
   114  		name:       "vuln attestation, wrong invocation url",
   115  		json:       vulnAttestation,
   116  		policyType: "cue",
   117  		policyFile: `predicateType: "https://cosign.sigstore.dev/attestation/vuln/v1"
   118  		predicate: invocation: uri: "invocation.example.com/wrong-url-here"`,
   119  		wantErr:    true,
   120  		wantErrSub: `conflicting values "invocation.example.com/cosign-testing" and "invocation.example.com/wrong-url-here"`,
   121  	}, {
   122  		name:       "vuln attestation, checks out",
   123  		json:       vulnAttestation,
   124  		policyType: "cue",
   125  		policyFile: `predicateType: "https://cosign.sigstore.dev/attestation/vuln/v1"
   126  		predicate: invocation: uri: "invocation.example.com/cosign-testing"`,
   127  	}, {
   128  		name:       "cluster image policy main policy, checks out",
   129  		json:       cipAttestation,
   130  		policyType: "cue",
   131  		policyFile: `package sigstore
   132  		import "struct"
   133  		import "list"
   134  		authorityMatches: {
   135  		  keyatt: {
   136  			attestations: struct.MaxFields(1) & struct.MinFields(1)
   137  		  },
   138  		  keysignature: {
   139  			signatures: list.MaxItems(1) & list.MinItems(1)
   140  		  },
   141  		  keylessatt: {
   142  			attestations: struct.MaxFields(1) & struct.MinFields(1)
   143  		  },
   144  		  keylesssignature: {
   145  			signatures: list.MaxItems(1) & list.MinItems(1)
   146  		  }
   147  		}`,
   148  	}, {
   149  		name:       "cluster image policy main policy, fails",
   150  		json:       cipAttestation,
   151  		policyType: "cue",
   152  		wantErr:    true,
   153  		wantErrSub: `failed evaluating cue policy for cluster image policy main policy, fails: failed to evaluate the policy with error: authorityMatches.keylessattMinAttestations: conflicting values 2 and "Error" (mismatched types int and string)`,
   154  		policyFile: `package sigstore
   155  		import "struct"
   156  		import "list"
   157  		authorityMatches: {
   158  		  keyatt: {
   159  			attestations: struct.MaxFields(1) & struct.MinFields(1)
   160  		  },
   161  		  keysignature: {
   162  			signatures: list.MaxItems(1) & list.MinItems(1)
   163  		  },
   164  		  if( len(authorityMatches.keylessatt.attestations) < 2) {
   165  			keylessattMinAttestations: 2
   166  			keylessattMinAttestations: "Error"
   167  		  },
   168  		  keylesssignature: {
   169  			signatures: list.MaxItems(1) & list.MinItems(1)
   170  		  }
   171  		}`}, {
   172  		name:       "Rego cluster image policy main policy, checks out",
   173  		json:       cipAttestation,
   174  		policyType: "rego",
   175  		policyFile: `package sigstore
   176  			default isCompliant = false
   177  			isCompliant {
   178  				attestationsKeylessATT := input.authorityMatches.keylessatt.attestations
   179  				count(attestationsKeylessATT) == 1
   180  				attestationsKeyATT := input.authorityMatches.keyatt.attestations
   181  				count(attestationsKeyATT) == 1
   182  				keySignature := input.authorityMatches.keysignature.signatures
   183  				count(keySignature) == 1
   184  			}`,
   185  	},
   186  		{
   187  			name:       "Rego cluster image policy main policy, fails",
   188  			json:       cipAttestation,
   189  			policyType: "rego",
   190  			wantErr:    true,
   191  			wantErrSub: `failed evaluating rego policy for type Rego cluster image policy main policy, fails: policy is not compliant for query 'isCompliant = data.sigstore.isCompliant'`,
   192  			policyFile: `package sigstore
   193  			default isCompliant = false
   194  			isCompliant {
   195  			    attestationsKeylessATT := input.authorityMatches.keylessatt.attestations
   196  				count(attestationsKeylessATT) == 2
   197  				attestationsKeyATT := input.authorityMatches.keyatt.attestations
   198  				count(attestationsKeyATT) == 1
   199  				keySignature := input.authorityMatches.keysignature.signatures
   200  				count(keySignature) == 1
   201  			}`,
   202  		}, {
   203  			name:       "Rego cluster image policy main policy succeed with empty error msg",
   204  			json:       cipAttestation,
   205  			policyType: "rego",
   206  			policyFile: `package sigstore
   207  			isCompliant[response] {
   208  			    attestationsKeylessATT := input.authorityMatches.keylessatt.attestations
   209  				result = (count(attestationsKeylessATT) == 1)
   210  				attestationsKeyATT := input.authorityMatches.keyatt.attestations
   211  				result = (count(attestationsKeyATT) == 1)
   212  				keySignature := input.authorityMatches.keysignature.signatures
   213  				result = (count(keySignature) == 1)
   214  
   215  				errorMsg = ""
   216  				warnMsg = ""
   217  
   218  				response := {
   219  					"result" : result,
   220  					"error" : errorMsg,
   221  					"warning" : warnMsg
   222  				}
   223  			}`,
   224  		}, {
   225  			name:       "Rego cluster image policy main policy, fails with custom error msg",
   226  			json:       cipAttestation,
   227  			policyType: "rego",
   228  			wantErr:    true,
   229  			wantErrSub: `Not found expected list of attestations`,
   230  			policyFile: `package sigstore
   231  			isCompliant[response] {
   232  			    attestationsKeylessATT := input.authorityMatches.keylessatt.attestations
   233  				result = (count(attestationsKeylessATT) == 1000)
   234  
   235  				errorMsg = "Not found expected list of attestations"
   236  				warnMsg = ""
   237  
   238  				response := {
   239  					"result" : result,
   240  					"error" : errorMsg,
   241  					"warning" : warnMsg
   242  				}
   243  			}`,
   244  		}, {
   245  			name:        "Rego cluster image policy main policy, returns a custom warning msg",
   246  			json:        cipAttestation,
   247  			policyType:  "rego",
   248  			wantErr:     false,
   249  			wantWarnSub: `Throw warning error even if succeeded`,
   250  			policyFile: `package sigstore
   251  			isCompliant[response] {
   252  				attestationsKeylessATT := input.authorityMatches.keylessatt.attestations
   253  				result = (count(attestationsKeylessATT) == 1)
   254  				attestationsKeyATT := input.authorityMatches.keyatt.attestations
   255  				result = (count(attestationsKeyATT) == 1)
   256  				keySignature := input.authorityMatches.keysignature.signatures
   257  				result = (count(keySignature) == 1)
   258  
   259  				errorMsg = ""
   260  				warnMsg = "Throw warning error even if succeeded"
   261  
   262  				response := {
   263  					"result" : result,
   264  					"error" : errorMsg,
   265  					"warning" : warnMsg
   266  				}
   267  			}`,
   268  		}}
   269  	for _, tc := range tests {
   270  		ctx := context.Background()
   271  		warn, err := EvaluatePolicyAgainstJSON(ctx, tc.name, tc.policyType, tc.policyFile, []byte(tc.json))
   272  		if tc.wantErr {
   273  			if err == nil {
   274  				t.Errorf("Did not get an error, wanted %s", tc.wantErrSub)
   275  			} else if !strings.Contains(err.Error(), tc.wantErrSub) {
   276  				t.Errorf("Unexpected error, want: %s got: %s", tc.wantErrSub, err.Error())
   277  			}
   278  			if tc.wantWarnSub != "" && !strings.Contains(warn.Error(), tc.wantWarnSub) {
   279  				t.Errorf("Unexpected warning, want: %s got: %s", tc.wantErrSub, err.Error())
   280  			}
   281  		} else {
   282  			if !tc.wantErr && err != nil {
   283  				t.Errorf("Unexpected error, wanted none, got: %s", err.Error())
   284  			}
   285  			if tc.wantWarnSub != "" && !strings.Contains(warn.Error(), tc.wantWarnSub) {
   286  				t.Errorf("Unexpected warning, want: %s got: %s", tc.wantWarnSub, warn.Error())
   287  			}
   288  		}
   289  	}
   290  }
   291  

View as plain text