github.com/zppinho/prow@v0.0.0-20240510014325-1738badeb017/pkg/github/hmac.go (about)

     1  /*
     2  Copyright 2016 The Kubernetes 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  
    17  package github
    18  
    19  import (
    20  	"crypto/hmac"
    21  	"crypto/sha1"
    22  	"encoding/hex"
    23  	"encoding/json"
    24  	"fmt"
    25  	"strings"
    26  	"time"
    27  
    28  	"github.com/sirupsen/logrus"
    29  	"sigs.k8s.io/yaml"
    30  )
    31  
    32  // HMACToken contains a hmac token and the time when it's created.
    33  type HMACToken struct {
    34  	Value     string    `json:"value"`
    35  	CreatedAt time.Time `json:"created_at"`
    36  }
    37  
    38  // HMACsForRepo contains all hmac tokens configured for a repo, org or globally.
    39  type HMACsForRepo []HMACToken
    40  
    41  // ValidatePayload ensures that the request payload signature matches the key.
    42  func ValidatePayload(payload []byte, sig string, tokenGenerator func() []byte) bool {
    43  	var event GenericEvent
    44  	if err := json.Unmarshal(payload, &event); err != nil {
    45  		logrus.WithError(err).Info("validatePayload couldn't unmarshal the github event payload")
    46  		return false
    47  	}
    48  
    49  	if !strings.HasPrefix(sig, "sha1=") {
    50  		return false
    51  	}
    52  	sig = sig[5:]
    53  	sb, err := hex.DecodeString(sig)
    54  	if err != nil {
    55  		return false
    56  	}
    57  
    58  	orgRepo := event.Repo.FullName
    59  	// If orgRepo is empty, the event is probably org-level, so try getting org name from the Org info.
    60  	if orgRepo == "" {
    61  		orgRepo = event.Org.Login
    62  	}
    63  	hmacs, err := extractHMACs(orgRepo, tokenGenerator)
    64  	if err != nil {
    65  		logrus.WithError(err).Warning("failed to get an appropriate hmac secret")
    66  		return false
    67  	}
    68  
    69  	// If we have a match with any valid hmac, we can validate successfully.
    70  	for _, key := range hmacs {
    71  		mac := hmac.New(sha1.New, key)
    72  		mac.Write(payload)
    73  		expected := mac.Sum(nil)
    74  		if hmac.Equal(sb, expected) {
    75  			return true
    76  		}
    77  	}
    78  	return false
    79  }
    80  
    81  // PayloadSignature returns the signature that matches the payload.
    82  func PayloadSignature(payload []byte, key []byte) string {
    83  	mac := hmac.New(sha1.New, key)
    84  	mac.Write(payload)
    85  	sum := mac.Sum(nil)
    86  	return "sha1=" + hex.EncodeToString(sum)
    87  }
    88  
    89  // extractHMACs returns all *valid* HMAC tokens for given repository/organization.
    90  // It considers only the tokens at the most specific level configured for the given repo.
    91  // For example : if a token for repo is present and it doesn't match the repo, we will
    92  // not try to find a match with org level token. However if no token is present for repo,
    93  // we will try to match with org level.
    94  func extractHMACs(orgRepo string, tokenGenerator func() []byte) ([][]byte, error) {
    95  	t := tokenGenerator()
    96  	repoToTokenMap := map[string]HMACsForRepo{}
    97  
    98  	if err := yaml.Unmarshal(t, &repoToTokenMap); err != nil {
    99  		// To keep backward compatibility, we are going to assume that in case of error,
   100  		// whole file is a single line hmac token.
   101  		// TODO: Once this code has been released and file has been moved to new format,
   102  		// we should delete this code and return error.
   103  		logrus.WithError(err).Trace("Couldn't unmarshal the hmac secret as hierarchical file. Parsing as single token format")
   104  		return [][]byte{t}, nil
   105  	}
   106  
   107  	orgName := strings.Split(orgRepo, "/")[0]
   108  
   109  	if val, ok := repoToTokenMap[orgRepo]; ok {
   110  		return extractTokens(val), nil
   111  	}
   112  	if val, ok := repoToTokenMap[orgName]; ok {
   113  		return extractTokens(val), nil
   114  	}
   115  	if val, ok := repoToTokenMap["*"]; ok {
   116  		return extractTokens(val), nil
   117  	}
   118  	return nil, fmt.Errorf("no hmac is configured for the org/repo %q and no legacy global token is configured", orgRepo)
   119  }
   120  
   121  // extractTokens return tokens for any given level of tree.
   122  func extractTokens(allTokens HMACsForRepo) [][]byte {
   123  	validTokens := make([][]byte, len(allTokens))
   124  	for i := range allTokens {
   125  		validTokens[i] = []byte(allTokens[i].Value)
   126  	}
   127  	return validTokens
   128  }