sigs.k8s.io/prow@v0.0.0-20240503223140-c5e374dc7eb1/pkg/config/secret/agent.go (about)

     1  /*
     2  Copyright 2018 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 secret implements an agent to read and reload the secrets.
    18  package secret
    19  
    20  import (
    21  	"fmt"
    22  	"sync"
    23  
    24  	"github.com/sirupsen/logrus"
    25  	"k8s.io/apimachinery/pkg/util/sets"
    26  
    27  	"sigs.k8s.io/prow/pkg/logrusutil"
    28  	"sigs.k8s.io/prow/pkg/secretutil"
    29  )
    30  
    31  // secretAgent is the singleton that loads secrets for us
    32  var secretAgent *agent
    33  
    34  func init() {
    35  	secretAgent = &agent{
    36  		secretsMap:        map[string]secretReloader{},
    37  		ReloadingCensorer: secretutil.NewCensorer(),
    38  	}
    39  	logrus.SetFormatter(logrusutil.NewFormatterWithCensor(logrus.StandardLogger().Formatter, secretAgent.ReloadingCensorer))
    40  }
    41  
    42  // Start creates goroutines to monitor the files that contain the secret value.
    43  // Additionally, Start wraps the current standard logger formatter with a
    44  // censoring formatter that removes secret occurrences from the logs.
    45  func (a *agent) Start(paths []string) error {
    46  	a.secretsMap = make(map[string]secretReloader, len(paths))
    47  	a.ReloadingCensorer = secretutil.NewCensorer()
    48  
    49  	for _, path := range paths {
    50  		if err := a.Add(path); err != nil {
    51  			return fmt.Errorf("failed to load secret at %s: %w", path, err)
    52  		}
    53  	}
    54  
    55  	logrus.SetFormatter(logrusutil.NewFormatterWithCensor(logrus.StandardLogger().Formatter, a.ReloadingCensorer))
    56  
    57  	return nil
    58  }
    59  
    60  // Add registers a new path to the agent.
    61  func Add(paths ...string) error {
    62  	for _, path := range paths {
    63  		if err := secretAgent.Add(path); err != nil {
    64  			return err
    65  		}
    66  	}
    67  	return nil
    68  }
    69  
    70  // AddWithParser registers a new path to the agent. The secret will only be updated if it can
    71  // be successfully parsed. The returned getter must be kept, as it is the only way of accessing
    72  // the typed secret.
    73  func AddWithParser[T any](path string, parsingFN func([]byte) (T, error)) (func() T, error) {
    74  	loader := &parsingSecretReloader[T]{
    75  		path:      path,
    76  		parsingFN: parsingFN,
    77  	}
    78  	return loader.get, secretAgent.add(path, loader)
    79  }
    80  
    81  // GetSecret returns the value of a secret stored in a map.
    82  func GetSecret(secretPath string) []byte {
    83  	return secretAgent.GetSecret(secretPath)
    84  }
    85  
    86  // GetTokenGenerator returns a function that gets the value of a given secret.
    87  func GetTokenGenerator(secretPath string) func() []byte {
    88  	return func() []byte {
    89  		return GetSecret(secretPath)
    90  	}
    91  }
    92  
    93  func Censor(content []byte) []byte {
    94  	return secretAgent.Censor(content)
    95  }
    96  
    97  // agent watches a path and automatically loads the secrets stored.
    98  type agent struct {
    99  	sync.RWMutex
   100  	secretsMap map[string]secretReloader
   101  	*secretutil.ReloadingCensorer
   102  }
   103  
   104  type secretReloader interface {
   105  	getRaw() []byte
   106  	start(reloadCensor func()) error
   107  }
   108  
   109  // Add registers a new path to the agent.
   110  func (a *agent) Add(path string) error {
   111  	return a.add(path, &parsingSecretReloader[[]byte]{
   112  		path:      path,
   113  		parsingFN: func(b []byte) ([]byte, error) { return b, nil },
   114  	})
   115  }
   116  
   117  func (a *agent) add(path string, loader secretReloader) error {
   118  	if err := loader.start(a.refreshCensorer); err != nil {
   119  		return err
   120  	}
   121  
   122  	a.setSecret(path, loader)
   123  
   124  	return nil
   125  }
   126  
   127  // GetSecret returns the value of a secret stored in a map.
   128  func (a *agent) GetSecret(secretPath string) []byte {
   129  	a.RLock()
   130  	defer a.RUnlock()
   131  	if val, set := a.secretsMap[secretPath]; set {
   132  		return val.getRaw()
   133  	}
   134  	return nil
   135  }
   136  
   137  // setSecret sets a value in a map of secrets.
   138  func (a *agent) setSecret(secretPath string, secretValue secretReloader) {
   139  	a.Lock()
   140  	a.secretsMap[secretPath] = secretValue
   141  	a.Unlock()
   142  	a.refreshCensorer()
   143  }
   144  
   145  // refreshCensorer should be called when the secrets map changes
   146  func (a *agent) refreshCensorer() {
   147  	var secrets [][]byte
   148  	a.RLock()
   149  	for _, value := range a.secretsMap {
   150  		secrets = append(secrets, value.getRaw())
   151  	}
   152  	a.RUnlock()
   153  	a.ReloadingCensorer.RefreshBytes(secrets...)
   154  }
   155  
   156  // GetTokenGenerator returns a function that gets the value of a given secret.
   157  func (a *agent) GetTokenGenerator(secretPath string) func() []byte {
   158  	return func() []byte {
   159  		return a.GetSecret(secretPath)
   160  	}
   161  }
   162  
   163  // Censor replaces sensitive parts of the content with a placeholder.
   164  func (a *agent) Censor(content []byte) []byte {
   165  	a.RLock()
   166  	defer a.RUnlock()
   167  	if a.ReloadingCensorer == nil {
   168  		// there's no constructor for an agent so we can't ensure that everyone is
   169  		// trying to censor *after* actually loading a secret ...
   170  		return content
   171  	}
   172  	return secretutil.AdaptCensorer(a.ReloadingCensorer)(content)
   173  }
   174  
   175  func (a *agent) getSecrets() sets.Set[string] {
   176  	a.RLock()
   177  	defer a.RUnlock()
   178  	secrets := sets.New[string]()
   179  	for _, v := range a.secretsMap {
   180  		secrets.Insert(string(v.getRaw()))
   181  	}
   182  	return secrets
   183  }