From 5d6a7cd20d6230a3d03e21795319603eeaf8480b Mon Sep 17 00:00:00 2001 From: laurentsimon <64505099+laurentsimon@users.noreply.github.com> Date: Wed, 22 Sep 2021 09:22:49 -0700 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20Add=20policy=20file=20(#1002)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * draft * draft 2 * add tests * check names * fixes * fix * comments * fix * test * remove risk * license * linter * comments --- cmd/root.go | 66 +++- policy/policy.go | 114 +++++++ policy/policy.pb.go | 308 ++++++++++++++++++ policy/policy.proto | 36 ++ policy/policy_test.go | 133 ++++++++ policy/testdata/policy-invalid-check.yaml | 19 ++ policy/testdata/policy-invalid-mode.yaml | 19 ++ policy/testdata/policy-invalid-score-0.yaml | 19 ++ policy/testdata/policy-invalid-score-10.yaml | 19 ++ policy/testdata/policy-multiple-defs.yaml | 29 ++ policy/testdata/policy-no-score-disabled.yaml | 25 ++ policy/testdata/policy-ok.yaml | 26 ++ 12 files changed, 806 insertions(+), 7 deletions(-) create mode 100644 policy/policy.go create mode 100644 policy/policy.pb.go create mode 100644 policy/policy.proto create mode 100644 policy/policy_test.go create mode 100644 policy/testdata/policy-invalid-check.yaml create mode 100644 policy/testdata/policy-invalid-mode.yaml create mode 100644 policy/testdata/policy-invalid-score-0.yaml create mode 100644 policy/testdata/policy-invalid-score-10.yaml create mode 100644 policy/testdata/policy-multiple-defs.yaml create mode 100644 policy/testdata/policy-no-score-disabled.yaml create mode 100644 policy/testdata/policy-ok.yaml diff --git a/cmd/root.go b/cmd/root.go index 9b50d9d2..05481911 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -36,6 +36,7 @@ import ( docs "github.com/ossf/scorecard/v2/docs/checks" sce "github.com/ossf/scorecard/v2/errors" "github.com/ossf/scorecard/v2/pkg" + spol "github.com/ossf/scorecard/v2/policy" "github.com/ossf/scorecard/v2/repos" ) @@ -50,6 +51,7 @@ var ( pypi string rubygems string showDetails bool + policyFile string ) const ( @@ -59,17 +61,61 @@ const ( formatDefault = "default" ) +const ( + scorecardLong = "A program that shows security scorecard for an open source software." + scorecardUse = `./scorecard --repo= [--checks=check1,...] [--show-details] [--policy=file] +or ./scorecard --{npm,pypi,rubgems}= [--checks=check1,...] [--show-details] [--policy=file]` + scorecardShort = "Security Scorecards" +) + +func readPolicy() (*spol.ScorecardPolicy, error) { + if policyFile != "" { + data, err := os.ReadFile(policyFile) + if err != nil { + return nil, fmt.Errorf("os.ReadFile: %w", err) + } + sp, err := spol.ParseFromYAML(data) + if err != nil { + return nil, fmt.Errorf("spol.ParseFromYAML: %w", err) + } + return sp, nil + } + return nil, nil +} + +func checksHavePolicies(sp *spol.ScorecardPolicy, enabledChecks checker.CheckNameToFnMap) bool { + for checkName := range enabledChecks { + _, exists := sp.Policies[checkName] + if !exists { + log.Printf("check %s has no policy declared", checkName) + return false + } + } + return true +} + var rootCmd = &cobra.Command{ - Use: `./scorecard --repo= [--checks=check1,...] [--show-details] -or ./scorecard --{npm,pypi,rubgems}= [--checks=check1,...] [--show-details]`, - Short: "Security Scorecards", - Long: "A program that shows security scorecard for an open source software.", + Use: scorecardUse, + Short: scorecardShort, + Long: scorecardLong, Run: func(cmd *cobra.Command, args []string) { // UPGRADEv3: remove. var v3 bool if _, v3 = os.LookupEnv("SCORECARD_V3"); v3 { fmt.Printf("**** Using SCORECARD_V3 code ***** \n\n") } + if format == formatSarif && !v3 { + log.Fatal("sarif not supported yet") + } + + if policyFile != "" && !v3 { + log.Fatal("policy not supported yet") + } + + policy, err := readPolicy() + if err != nil { + log.Fatalf("readPolicy: %v", err) + } if npm != "" { if git, err := fetchGitRepositoryFromNPM(npm); err != nil { @@ -120,6 +166,13 @@ or ./scorecard --{npm,pypi,rubgems}= [--checks=check1,...] [--show fmt.Fprintf(os.Stderr, "Starting [%s]\n", checkName) } } + + // If a policy was passed as argument, ensure all checks + // to run have a corresponding policy. + if policy != nil && !checksHavePolicies(policy, enabledChecks) { + log.Fatal("checks don't have policies") + } + ctx := context.Background() logger, err := githubrepo.NewLogger(*logLevel) @@ -151,6 +204,7 @@ or ./scorecard --{npm,pypi,rubgems}= [--checks=check1,...] [--show } // UPGRADEv2: support CSV/JSON. + // TODO: move the doc inside Scorecard structure. checkDocs, e := docs.Read() if e != nil { log.Fatalf("cannot read yaml file: %v", err) @@ -160,9 +214,6 @@ or ./scorecard --{npm,pypi,rubgems}= [--checks=check1,...] [--show case formatDefault: err = repoResult.AsString(showDetails, *logLevel, checkDocs, os.Stdout) case formatSarif: - if !v3 { - log.Fatalf("sarif not supported yet") - } // TODO: support config files and update checker.MaxResultScore. err = repoResult.AsSARIF(showDetails, *logLevel, os.Stdout, checkDocs, checker.MaxResultScore) case formatCSV: @@ -325,4 +376,5 @@ func init() { } rootCmd.Flags().StringSliceVar(&checksToRun, "checks", []string{}, fmt.Sprintf("Checks to run. Possible values are: %s", strings.Join(checkNames, ","))) + rootCmd.Flags().StringVar(&policyFile, "policy", "", "policy to enforce") } diff --git a/policy/policy.go b/policy/policy.go new file mode 100644 index 00000000..5cd15d7d --- /dev/null +++ b/policy/policy.go @@ -0,0 +1,114 @@ +// Copyright 2021 Security Scorecard Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package policy + +import ( + "errors" + "fmt" + + "gopkg.in/yaml.v3" + + "github.com/ossf/scorecard/v2/checks" + sce "github.com/ossf/scorecard/v2/errors" +) + +var ( + errInvalidVersion = errors.New("invalid version") + errInvalidCheck = errors.New("invalid check name") + errInvalidScore = errors.New("invalid score") + errInvalidMode = errors.New("invalid mode") + errRepeatingCheck = errors.New("check has multiple definitions") +) + +var allowedVersions = map[int]bool{1: true} + +var modes = map[string]bool{"enforced": true, "disabled": true} + +type checkPolicy struct { + Mode string `yaml:"mode"` + Score int `yaml:"score"` +} + +type scorecardPolicy struct { + Policies map[string]checkPolicy `yaml:"policies"` + Version int `yaml:"version"` +} + +func isAllowedVersion(v int) bool { + _, exists := allowedVersions[v] + return exists +} + +func modeToProto(m string) CheckPolicy_Mode { + switch m { + default: + panic("will never happen") + case "enforced": + return CheckPolicy_ENFORCED + case "disabled": + return CheckPolicy_DISABLED + } +} + +// ParseFromYAML parses a policy file and returns +// a scorecardPolicy. +func ParseFromYAML(b []byte) (*ScorecardPolicy, error) { + // Internal golang for unmarshalling the policy file. + sp := scorecardPolicy{} + // Protobuf-defined policy (policy.proto and policy.pb.go). + retPolicy := ScorecardPolicy{Policies: map[string]*CheckPolicy{}} + + err := yaml.Unmarshal(b, &sp) + if err != nil { + return &retPolicy, sce.WithMessage(sce.ErrScorecardInternal, err.Error()) + } + + if !isAllowedVersion(sp.Version) { + return &retPolicy, sce.WithMessage(sce.ErrScorecardInternal, errInvalidVersion.Error()) + } + + // Set version. + retPolicy.Version = int32(sp.Version) + + checksFound := make(map[string]bool) + for n, p := range sp.Policies { + if _, exists := checks.AllChecks[n]; !exists { + return &retPolicy, sce.WithMessage(sce.ErrScorecardInternal, fmt.Sprintf("%v: %v", errInvalidCheck.Error(), n)) + } + + _, exists := modes[p.Mode] + if !exists { + return &retPolicy, sce.WithMessage(sce.ErrScorecardInternal, fmt.Sprintf("%v: %v", errInvalidMode.Error(), p.Mode)) + } + + if p.Score < 0 || p.Score > 10 { + return &retPolicy, sce.WithMessage(sce.ErrScorecardInternal, fmt.Sprintf("%v: %v", errInvalidScore.Error(), p.Score)) + } + + _, exists = checksFound[n] + if exists { + return &retPolicy, sce.WithMessage(sce.ErrScorecardInternal, fmt.Sprintf("%v: %v", errRepeatingCheck.Error(), n)) + } + checksFound[n] = true + + // Add an entry to the policy. + retPolicy.Policies[n] = &CheckPolicy{ + Score: int32(p.Score), + Mode: modeToProto(p.Mode), + } + } + + return &retPolicy, nil +} diff --git a/policy/policy.pb.go b/policy/policy.pb.go new file mode 100644 index 00000000..4774ae3c --- /dev/null +++ b/policy/policy.pb.go @@ -0,0 +1,308 @@ +// Copyright 2021 Security Scorecard Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.27.1 +// protoc v3.12.4 +// source: policy.proto + +package policy + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +// Mode definition. +type CheckPolicy_Mode int32 + +const ( + CheckPolicy_DISABLED CheckPolicy_Mode = 0 + CheckPolicy_ENFORCED CheckPolicy_Mode = 1 +) + +// Enum value maps for CheckPolicy_Mode. +var ( + CheckPolicy_Mode_name = map[int32]string{ + 0: "DISABLED", + 1: "ENFORCED", + } + CheckPolicy_Mode_value = map[string]int32{ + "DISABLED": 0, + "ENFORCED": 1, + } +) + +func (x CheckPolicy_Mode) Enum() *CheckPolicy_Mode { + p := new(CheckPolicy_Mode) + *p = x + return p +} + +func (x CheckPolicy_Mode) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (CheckPolicy_Mode) Descriptor() protoreflect.EnumDescriptor { + return file_policy_proto_enumTypes[0].Descriptor() +} + +func (CheckPolicy_Mode) Type() protoreflect.EnumType { + return &file_policy_proto_enumTypes[0] +} + +func (x CheckPolicy_Mode) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use CheckPolicy_Mode.Descriptor instead. +func (CheckPolicy_Mode) EnumDescriptor() ([]byte, []int) { + return file_policy_proto_rawDescGZIP(), []int{0, 0} +} + +type CheckPolicy struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Mode CheckPolicy_Mode `protobuf:"varint,1,opt,name=mode,proto3,enum=ossf.scorecard.policy.CheckPolicy_Mode" json:"mode,omitempty"` + Score int32 `protobuf:"zigzag32,2,opt,name=score,proto3" json:"score,omitempty"` // TODO: add Risk. +} + +func (x *CheckPolicy) Reset() { + *x = CheckPolicy{} + if protoimpl.UnsafeEnabled { + mi := &file_policy_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *CheckPolicy) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CheckPolicy) ProtoMessage() {} + +func (x *CheckPolicy) ProtoReflect() protoreflect.Message { + mi := &file_policy_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CheckPolicy.ProtoReflect.Descriptor instead. +func (*CheckPolicy) Descriptor() ([]byte, []int) { + return file_policy_proto_rawDescGZIP(), []int{0} +} + +func (x *CheckPolicy) GetMode() CheckPolicy_Mode { + if x != nil { + return x.Mode + } + return CheckPolicy_DISABLED +} + +func (x *CheckPolicy) GetScore() int32 { + if x != nil { + return x.Score + } + return 0 +} + +type ScorecardPolicy struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Version int32 `protobuf:"varint,1,opt,name=version,proto3" json:"version,omitempty"` + Policies map[string]*CheckPolicy `protobuf:"bytes,2,rep,name=policies,proto3" json:"policies,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` +} + +func (x *ScorecardPolicy) Reset() { + *x = ScorecardPolicy{} + if protoimpl.UnsafeEnabled { + mi := &file_policy_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ScorecardPolicy) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ScorecardPolicy) ProtoMessage() {} + +func (x *ScorecardPolicy) ProtoReflect() protoreflect.Message { + mi := &file_policy_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ScorecardPolicy.ProtoReflect.Descriptor instead. +func (*ScorecardPolicy) Descriptor() ([]byte, []int) { + return file_policy_proto_rawDescGZIP(), []int{1} +} + +func (x *ScorecardPolicy) GetVersion() int32 { + if x != nil { + return x.Version + } + return 0 +} + +func (x *ScorecardPolicy) GetPolicies() map[string]*CheckPolicy { + if x != nil { + return x.Policies + } + return nil +} + +var File_policy_proto protoreflect.FileDescriptor + +var file_policy_proto_rawDesc = []byte{ + 0x0a, 0x0c, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x15, + 0x6f, 0x73, 0x73, 0x66, 0x2e, 0x73, 0x63, 0x6f, 0x72, 0x65, 0x63, 0x61, 0x72, 0x64, 0x2e, 0x70, + 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x22, 0x84, 0x01, 0x0a, 0x0b, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x50, + 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x12, 0x3b, 0x0a, 0x04, 0x6d, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x0e, 0x32, 0x27, 0x2e, 0x6f, 0x73, 0x73, 0x66, 0x2e, 0x73, 0x63, 0x6f, 0x72, 0x65, + 0x63, 0x61, 0x72, 0x64, 0x2e, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x2e, 0x43, 0x68, 0x65, 0x63, + 0x6b, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x2e, 0x4d, 0x6f, 0x64, 0x65, 0x52, 0x04, 0x6d, 0x6f, + 0x64, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x63, 0x6f, 0x72, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x11, 0x52, 0x05, 0x73, 0x63, 0x6f, 0x72, 0x65, 0x22, 0x22, 0x0a, 0x04, 0x4d, 0x6f, 0x64, 0x65, + 0x12, 0x0c, 0x0a, 0x08, 0x44, 0x49, 0x53, 0x41, 0x42, 0x4c, 0x45, 0x44, 0x10, 0x00, 0x12, 0x0c, + 0x0a, 0x08, 0x45, 0x4e, 0x46, 0x4f, 0x52, 0x43, 0x45, 0x44, 0x10, 0x01, 0x22, 0xde, 0x01, 0x0a, + 0x0f, 0x53, 0x63, 0x6f, 0x72, 0x65, 0x63, 0x61, 0x72, 0x64, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, + 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x05, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x50, 0x0a, 0x08, 0x70, 0x6f, + 0x6c, 0x69, 0x63, 0x69, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x34, 0x2e, 0x6f, + 0x73, 0x73, 0x66, 0x2e, 0x73, 0x63, 0x6f, 0x72, 0x65, 0x63, 0x61, 0x72, 0x64, 0x2e, 0x70, 0x6f, + 0x6c, 0x69, 0x63, 0x79, 0x2e, 0x53, 0x63, 0x6f, 0x72, 0x65, 0x63, 0x61, 0x72, 0x64, 0x50, 0x6f, + 0x6c, 0x69, 0x63, 0x79, 0x2e, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x69, 0x65, 0x73, 0x45, 0x6e, 0x74, + 0x72, 0x79, 0x52, 0x08, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x69, 0x65, 0x73, 0x1a, 0x5f, 0x0a, 0x0d, + 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x69, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, + 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, + 0x38, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x22, + 0x2e, 0x6f, 0x73, 0x73, 0x66, 0x2e, 0x73, 0x63, 0x6f, 0x72, 0x65, 0x63, 0x61, 0x72, 0x64, 0x2e, + 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x2e, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x50, 0x6f, 0x6c, 0x69, + 0x63, 0x79, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x42, 0x22, 0x5a, + 0x20, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6f, 0x73, 0x73, 0x66, + 0x2f, 0x73, 0x63, 0x6f, 0x72, 0x65, 0x63, 0x61, 0x72, 0x64, 0x2f, 0x70, 0x6f, 0x6c, 0x69, 0x63, + 0x79, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_policy_proto_rawDescOnce sync.Once + file_policy_proto_rawDescData = file_policy_proto_rawDesc +) + +func file_policy_proto_rawDescGZIP() []byte { + file_policy_proto_rawDescOnce.Do(func() { + file_policy_proto_rawDescData = protoimpl.X.CompressGZIP(file_policy_proto_rawDescData) + }) + return file_policy_proto_rawDescData +} + +var file_policy_proto_enumTypes = make([]protoimpl.EnumInfo, 1) +var file_policy_proto_msgTypes = make([]protoimpl.MessageInfo, 3) +var file_policy_proto_goTypes = []interface{}{ + (CheckPolicy_Mode)(0), // 0: ossf.scorecard.policy.CheckPolicy.Mode + (*CheckPolicy)(nil), // 1: ossf.scorecard.policy.CheckPolicy + (*ScorecardPolicy)(nil), // 2: ossf.scorecard.policy.ScorecardPolicy + nil, // 3: ossf.scorecard.policy.ScorecardPolicy.PoliciesEntry +} +var file_policy_proto_depIdxs = []int32{ + 0, // 0: ossf.scorecard.policy.CheckPolicy.mode:type_name -> ossf.scorecard.policy.CheckPolicy.Mode + 3, // 1: ossf.scorecard.policy.ScorecardPolicy.policies:type_name -> ossf.scorecard.policy.ScorecardPolicy.PoliciesEntry + 1, // 2: ossf.scorecard.policy.ScorecardPolicy.PoliciesEntry.value:type_name -> ossf.scorecard.policy.CheckPolicy + 3, // [3:3] is the sub-list for method output_type + 3, // [3:3] is the sub-list for method input_type + 3, // [3:3] is the sub-list for extension type_name + 3, // [3:3] is the sub-list for extension extendee + 0, // [0:3] is the sub-list for field type_name +} + +func init() { file_policy_proto_init() } +func file_policy_proto_init() { + if File_policy_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_policy_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*CheckPolicy); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_policy_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ScorecardPolicy); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_policy_proto_rawDesc, + NumEnums: 1, + NumMessages: 3, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_policy_proto_goTypes, + DependencyIndexes: file_policy_proto_depIdxs, + EnumInfos: file_policy_proto_enumTypes, + MessageInfos: file_policy_proto_msgTypes, + }.Build() + File_policy_proto = out.File + file_policy_proto_rawDesc = nil + file_policy_proto_goTypes = nil + file_policy_proto_depIdxs = nil +} diff --git a/policy/policy.proto b/policy/policy.proto new file mode 100644 index 00000000..cacdd022 --- /dev/null +++ b/policy/policy.proto @@ -0,0 +1,36 @@ +// Copyright 2021 Security Scorecard Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +package ossf.scorecard.policy; + +option go_package = "github.com/ossf/scorecard/policy"; + +message CheckPolicy { + + // Mode definition. + enum Mode { + DISABLED = 0; + ENFORCED = 1; + } + + Mode mode = 1; + sint32 score = 2; +} + +message ScorecardPolicy { + int32 version = 1; + map policies = 2; +} diff --git a/policy/policy_test.go b/policy/policy_test.go new file mode 100644 index 00000000..e3b3399e --- /dev/null +++ b/policy/policy_test.go @@ -0,0 +1,133 @@ +// Copyright 2021 Security Scorecard Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package policy + +import ( + "errors" + "io/ioutil" + "testing" + + sce "github.com/ossf/scorecard/v2/errors" +) + +func TestPolicyRead(t *testing.T) { + t.Parallel() + + // nolint + tests := []struct { + err error + name string + filename string + result ScorecardPolicy + }{ + { + name: "correct", + filename: "./testdata/policy-ok.yaml", + err: nil, + result: ScorecardPolicy{ + Version: 1, + Policies: map[string]*CheckPolicy{ + "Token-Permissions": &CheckPolicy{ + Score: 3, + Mode: CheckPolicy_DISABLED, + }, + "Branch-Protection": &CheckPolicy{ + Score: 5, + Mode: CheckPolicy_ENFORCED, + }, + "Vulnerabilities": &CheckPolicy{ + Score: 1, + Mode: CheckPolicy_ENFORCED, + }, + }, + }, + }, + { + name: "no score disabled", + filename: "./testdata/policy-no-score-disabled.yaml", + err: nil, + result: ScorecardPolicy{ + Version: 1, + Policies: map[string]*CheckPolicy{ + "Token-Permissions": &CheckPolicy{ + Score: 0, + Mode: CheckPolicy_DISABLED, + }, + "Branch-Protection": &CheckPolicy{ + Score: 5, + Mode: CheckPolicy_ENFORCED, + }, + "Vulnerabilities": &CheckPolicy{ + Score: 1, + Mode: CheckPolicy_ENFORCED, + }, + }, + }, + }, + { + name: "invalid score - 0", + filename: "./testdata/policy-invalid-score-0.yaml", + err: sce.ErrScorecardInternal, + }, + { + name: "invalid score + 10", + filename: "./testdata/policy-invalid-score-10.yaml", + err: sce.ErrScorecardInternal, + }, + { + name: "invalid mode", + filename: "./testdata/policy-invalid-mode.yaml", + err: sce.ErrScorecardInternal, + }, + { + name: "invalid check name", + filename: "./testdata/policy-invalid-check.yaml", + err: sce.ErrScorecardInternal, + }, + { + name: "multiple check definitions", + filename: "./testdata/policy-multiple-defs.yaml", + err: sce.ErrScorecardInternal, + }, + } + + for i := range tests { + tt := &tests[i] + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + var content []byte + var err error + content, err = ioutil.ReadFile(tt.filename) + if err != nil { + t.Fatalf("cannot read file: %v", err) + } + + p, err := ParseFromYAML(content) + + if !errors.Is(err, tt.err) { + t.Fatalf("%s: expected %v, got %v", tt.name, tt.err, err) + } + if err != nil { + return + } + + // Compare outputs only if the error is nil. + // TODO: compare objects. + if p.String() != tt.result.String() { + t.Fatalf("%s: invalid result", tt.name) + } + }) + } +} diff --git a/policy/testdata/policy-invalid-check.yaml b/policy/testdata/policy-invalid-check.yaml new file mode 100644 index 00000000..08d04e3e --- /dev/null +++ b/policy/testdata/policy-invalid-check.yaml @@ -0,0 +1,19 @@ +# Copyright 2021 Security Scorecard Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this exe except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +version: 1 +policies: + Unknown-Check: + score: 1 + mode: disabled diff --git a/policy/testdata/policy-invalid-mode.yaml b/policy/testdata/policy-invalid-mode.yaml new file mode 100644 index 00000000..14c57ff6 --- /dev/null +++ b/policy/testdata/policy-invalid-mode.yaml @@ -0,0 +1,19 @@ +# Copyright 2021 Security Scorecard Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this exe except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +version: 1 +policies: + Token-Permissions: + score: 1 + mode: unknown diff --git a/policy/testdata/policy-invalid-score-0.yaml b/policy/testdata/policy-invalid-score-0.yaml new file mode 100644 index 00000000..ee5c2aab --- /dev/null +++ b/policy/testdata/policy-invalid-score-0.yaml @@ -0,0 +1,19 @@ +# Copyright 2021 Security Scorecard Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this exe except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +version: 1 +policies: + Token-Permissions: + score: -1 + mode: disabled diff --git a/policy/testdata/policy-invalid-score-10.yaml b/policy/testdata/policy-invalid-score-10.yaml new file mode 100644 index 00000000..cfa5b58d --- /dev/null +++ b/policy/testdata/policy-invalid-score-10.yaml @@ -0,0 +1,19 @@ +# Copyright 2021 Security Scorecard Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this exe except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +version: 1 +policies: + Token-Permissions: + score: 11 + mode: disabled diff --git a/policy/testdata/policy-multiple-defs.yaml b/policy/testdata/policy-multiple-defs.yaml new file mode 100644 index 00000000..ed357996 --- /dev/null +++ b/policy/testdata/policy-multiple-defs.yaml @@ -0,0 +1,29 @@ +# Copyright 2021 Security Scorecard Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this exe except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +version: 1 +policies: + Token-Permissions: + score: 3 + mode: disabled + Branch-Protection: + score: 5 + mode: enforced + Vulnerabilities: + score: 1 + mode: disabled + Token-Permissions: + score: 6 + mode: enforced + diff --git a/policy/testdata/policy-no-score-disabled.yaml b/policy/testdata/policy-no-score-disabled.yaml new file mode 100644 index 00000000..f4ba4377 --- /dev/null +++ b/policy/testdata/policy-no-score-disabled.yaml @@ -0,0 +1,25 @@ +# Copyright 2021 Security Scorecard Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this exe except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +version: 1 +policies: + Token-Permissions: + mode: disabled + Branch-Protection: + score: 5 + mode: enforced + Vulnerabilities: + score: 1 + mode: enforced + diff --git a/policy/testdata/policy-ok.yaml b/policy/testdata/policy-ok.yaml new file mode 100644 index 00000000..c793e41d --- /dev/null +++ b/policy/testdata/policy-ok.yaml @@ -0,0 +1,26 @@ +# Copyright 2021 Security Scorecard Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this exe except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +version: 1 +policies: + Token-Permissions: + score: 3 + mode: disabled + Branch-Protection: + score: 5 + mode: enforced + Vulnerabilities: + score: 1 + mode: enforced +