sigs.k8s.io/prow@v0.0.0-20240503223140-c5e374dc7eb1/pkg/secretutil/censor.go (about) 1 /* 2 Copyright 2021 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 secretutil implements utilities to operate on secret data. 18 package secretutil 19 20 import ( 21 "encoding/base64" 22 "strings" 23 "sync" 24 25 "go4.org/bytereplacer" 26 ) 27 28 // Censorer knows how to replace sensitive data from input. 29 type Censorer interface { 30 // Censor will remove sensitive data previously registered with the Censorer 31 // from the input. This is thread-safe, will mutate the input and will never 32 // change the overall size of the input. 33 Censor(input *[]byte) 34 } 35 36 func NewCensorer() *ReloadingCensorer { 37 return &ReloadingCensorer{ 38 RWMutex: &sync.RWMutex{}, 39 Replacer: bytereplacer.New(), 40 } 41 } 42 43 type ReloadingCensorer struct { 44 *sync.RWMutex 45 *bytereplacer.Replacer 46 largestSecret int 47 } 48 49 var _ Censorer = &ReloadingCensorer{} 50 51 // Censor will remove sensitive data previously registered with the Censorer 52 // from the input. This is thread-safe, will mutate the input and will never 53 // change the overall size of the input. 54 // Censoring will attempt to be intelligent about how content is removed from 55 // the input - when the ReloadingCensorer is given secrets to censor, we: 56 // - handle the case where whitespace is needed to be trimmed 57 // - censor not only the plaintext representation of the secret but also 58 // the base64-encoded representation of it, as it's common for k8s 59 // Secrets to contain information in this way 60 func (c *ReloadingCensorer) Censor(input *[]byte) { 61 c.RLock() 62 // we know our replacer will never have to allocate, as our replacements 63 // are the same size as what they're replacing, so we can throw away 64 // the return value from Replace() 65 c.Replacer.Replace(*input) 66 c.RUnlock() 67 } 68 69 // LargestSecret returns the size of the largest secret we will censor. 70 func (c *ReloadingCensorer) LargestSecret() int { 71 c.RLock() 72 defer c.RUnlock() 73 return c.largestSecret 74 } 75 76 // RefreshBytes refreshes the set of secrets that we censor. 77 func (c *ReloadingCensorer) RefreshBytes(secrets ...[]byte) { 78 var asStrings []string 79 for _, secret := range secrets { 80 asStrings = append(asStrings, string(secret)) 81 } 82 c.Refresh(asStrings...) 83 } 84 85 // Refresh refreshes the set of secrets that we censor. 86 func (c *ReloadingCensorer) Refresh(secrets ...string) { 87 var largestSecret int 88 var replacements []string 89 addReplacement := func(s string) { 90 replacements = append(replacements, s, strings.Repeat(`X`, len(s))) 91 if len(s) > largestSecret { 92 largestSecret = len(s) 93 } 94 } 95 for _, secret := range secrets { 96 toEncode := []string{secret} 97 if trimmed := strings.TrimSpace(secret); trimmed != secret { 98 secret = trimmed 99 toEncode = append(toEncode, trimmed) 100 } 101 if secret == "" { 102 continue 103 } 104 if strings.EqualFold(secret, "true") || strings.EqualFold(secret, "false") { 105 // true and false appear in secrets when there is configuration that gets changed at the same time actual secrets. 106 // this happens when storing credentials to servers where capabilities are different. 107 // while every other type could reasonably be secret (through secret floats would be weird), it's difficult 108 // to justify secret booleans, since all values are known and replacing them carries a high price. 109 continue 110 } 111 112 addReplacement(secret) 113 for _, item := range toEncode { 114 encoded := base64.StdEncoding.EncodeToString([]byte(item)) 115 addReplacement(encoded) 116 } 117 } 118 c.Lock() 119 c.Replacer = bytereplacer.New(replacements...) 120 c.largestSecret = largestSecret 121 c.Unlock() 122 } 123 124 // AdaptCensorer returns a func that censors without touching the input, to 125 // be used in places where the previous behavior is required while migrations 126 // occur. 127 func AdaptCensorer(censorer Censorer) func(input []byte) []byte { 128 return func(input []byte) []byte { 129 output := make([]byte, len(input)) 130 copy(output, input) 131 censorer.Censor(&output) 132 return output 133 } 134 }