...

Source file src/edge-infra.dev/pkg/f8n/devinfra/repo/owners/policybot/policy/predicate/signature.go

Documentation: edge-infra.dev/pkg/f8n/devinfra/repo/owners/policybot/policy/predicate

     1  // Copyright 2021 Palantir Technologies, Inc.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package predicate
    16  
    17  import (
    18  	"context"
    19  	"fmt"
    20  
    21  	"github.com/pkg/errors"
    22  
    23  	"edge-infra.dev/pkg/f8n/devinfra/repo/owners/policybot/pull"
    24  
    25  	"edge-infra.dev/pkg/f8n/devinfra/repo/owners/policybot/policy/common"
    26  )
    27  
    28  type HasValidSignatures bool
    29  
    30  var _ Predicate = HasValidSignatures(false)
    31  
    32  func (pred HasValidSignatures) Evaluate(ctx context.Context, prctx pull.Context) (*common.PredicateResult, error) {
    33  	commits, err := prctx.Commits()
    34  
    35  	predicateResult := common.PredicateResult{
    36  		ConditionPhrase: "have",
    37  		ConditionValues: []string{"valid signatures"},
    38  	}
    39  
    40  	if err != nil {
    41  		return nil, errors.Wrap(err, "failed to get commits")
    42  	}
    43  
    44  	var commitHashes []string
    45  
    46  	for _, c := range commits {
    47  		valid, desc := hasValidSignature(ctx, c)
    48  		commitHashes = append(commitHashes, c.SHA)
    49  		if !valid {
    50  			predicateResult.Values = []string{c.SHA}
    51  			if pred {
    52  				predicateResult.Description = desc
    53  				predicateResult.Satisfied = false
    54  				return &predicateResult, nil
    55  			}
    56  			predicateResult.Satisfied = true
    57  			return &predicateResult, nil
    58  		}
    59  	}
    60  	predicateResult.Values = commitHashes
    61  	if pred {
    62  		predicateResult.Satisfied = true
    63  		return &predicateResult, nil
    64  	}
    65  	predicateResult.Satisfied = false
    66  	predicateResult.Description = "All commits are signed and have valid signatures"
    67  	return &predicateResult, nil
    68  }
    69  
    70  func (pred HasValidSignatures) Trigger() common.Trigger {
    71  	return common.TriggerCommit
    72  }
    73  
    74  type HasValidSignaturesBy struct {
    75  	common.Actors `yaml:",inline"`
    76  }
    77  
    78  var _ Predicate = &HasValidSignaturesBy{}
    79  
    80  func (pred *HasValidSignaturesBy) Evaluate(ctx context.Context, prctx pull.Context) (*common.PredicateResult, error) {
    81  	commits, err := prctx.Commits()
    82  
    83  	predicateResult := common.PredicateResult{
    84  		ConditionsMap: map[string][]string{
    85  			"Organizations": pred.Organizations,
    86  			"Teams":         pred.Teams,
    87  			"Users":         pred.Users,
    88  		},
    89  	}
    90  
    91  	if err != nil {
    92  		return nil, errors.Wrap(err, "failed to get commits")
    93  	}
    94  
    95  	signers := make(map[string]string)
    96  	var commitHashes []string
    97  
    98  	for _, c := range commits {
    99  		valid, desc := hasValidSignature(ctx, c)
   100  		if !valid {
   101  			predicateResult.ConditionPhrase = "have valid signatures by members of"
   102  			predicateResult.ValuePhrase = "commits"
   103  			predicateResult.Values = []string{c.SHA}
   104  			predicateResult.Description = desc
   105  			predicateResult.Satisfied = false
   106  			return &predicateResult, nil
   107  		}
   108  		signers[c.Signature.Signer] = c.SHA
   109  		commitHashes = append(commitHashes, c.SHA)
   110  	}
   111  
   112  	var signerList []string
   113  
   114  	for signer := range signers {
   115  		signerList = append(signerList, signer)
   116  		member, err := pred.IsActor(ctx, prctx, signer)
   117  		if err != nil {
   118  			return nil, err
   119  		}
   120  		if !member {
   121  			predicateResult.ConditionPhrase = "satisfy the required membership conditions"
   122  			predicateResult.Values = []string{signer}
   123  			predicateResult.ValuePhrase = "signers"
   124  			predicateResult.Description = fmt.Sprintf("Contributor %q does not meet the required membership conditions for signing", signer)
   125  			predicateResult.Satisfied = false
   126  			return &predicateResult, nil
   127  		}
   128  	}
   129  	predicateResult.ConditionPhrase = "have valid signatures by members of"
   130  	predicateResult.Values = commitHashes
   131  	predicateResult.ValuePhrase = "commits"
   132  	predicateResult.Satisfied = true
   133  	return &predicateResult, nil
   134  }
   135  
   136  func (pred *HasValidSignaturesBy) Trigger() common.Trigger {
   137  	return common.TriggerCommit
   138  }
   139  
   140  type HasValidSignaturesByKeys struct {
   141  	KeyIDs []string `yaml:"key_ids"`
   142  }
   143  
   144  var _ Predicate = &HasValidSignaturesByKeys{}
   145  
   146  func (pred *HasValidSignaturesByKeys) Evaluate(ctx context.Context, prctx pull.Context) (*common.PredicateResult, error) {
   147  	commits, err := prctx.Commits()
   148  
   149  	predicateResult := common.PredicateResult{
   150  		ConditionPhrase: "have valid signatures by keys",
   151  		ConditionValues: pred.KeyIDs,
   152  	}
   153  
   154  	if err != nil {
   155  		return nil, errors.Wrap(err, "failed to get commits")
   156  	}
   157  
   158  	keys := make(map[string][]string)
   159  
   160  	var commitHashes []string
   161  
   162  	for _, c := range commits {
   163  		valid, desc := hasValidSignature(ctx, c)
   164  		if !valid {
   165  			predicateResult.Values = []string{c.SHA}
   166  			predicateResult.Description = desc
   167  			predicateResult.ValuePhrase = "commits"
   168  			predicateResult.Satisfied = false
   169  			return &predicateResult, nil
   170  		}
   171  		commitHashes = append(commitHashes, c.SHA)
   172  		// Only GPG signatures are valid for this predicate
   173  		switch c.Signature.Type {
   174  		case pull.SignatureGpg:
   175  			commitSHAs, ok := keys[c.Signature.KeyID]
   176  			if ok {
   177  				keys[c.Signature.KeyID] = append(commitSHAs, c.SHA)
   178  			} else {
   179  				keys[c.Signature.KeyID] = []string{c.SHA}
   180  			}
   181  		default:
   182  			predicateResult.Values = []string{c.SHA}
   183  			predicateResult.ValuePhrase = "commits"
   184  			predicateResult.ConditionPhrase = "have GPG signatures"
   185  			predicateResult.Description = fmt.Sprintf("Commit %.10s signature is not a GPG signature", c.SHA)
   186  			predicateResult.Satisfied = false
   187  			return &predicateResult, nil
   188  		}
   189  	}
   190  
   191  	for key := range keys {
   192  		isValidKey := false
   193  		for _, acceptedKey := range pred.KeyIDs {
   194  			if key == acceptedKey {
   195  				isValidKey = true
   196  				break
   197  			}
   198  		}
   199  		if !isValidKey {
   200  			predicateResult.ConditionPhrase = "exist in the set of allowed keys"
   201  			predicateResult.Values = []string{key}
   202  			predicateResult.ValuePhrase = "keys"
   203  			predicateResult.Description = fmt.Sprintf("Key %q does not meet the required key conditions for signing", key)
   204  			predicateResult.Satisfied = false
   205  			return &predicateResult, nil
   206  		}
   207  	}
   208  	predicateResult.Values = commitHashes
   209  	predicateResult.ValuePhrase = "commits"
   210  	predicateResult.Satisfied = true
   211  
   212  	return &predicateResult, nil
   213  }
   214  
   215  func (pred *HasValidSignaturesByKeys) Trigger() common.Trigger {
   216  	return common.TriggerCommit
   217  }
   218  
   219  func hasValidSignature(ctx context.Context, commit *pull.Commit) (bool, string) {
   220  	if commit.Signature == nil {
   221  		return false, fmt.Sprintf("Commit %.10s has no signature", commit.SHA)
   222  	}
   223  	if !commit.Signature.IsValid {
   224  		reason := commit.Signature.State
   225  		return false, fmt.Sprintf("Commit %.10s has an invalid signature due to %s", commit.SHA, reason)
   226  	}
   227  	return true, ""
   228  }
   229  

View as plain text