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 }