Feat: Agent improvements, Secrets state manager

This commit is contained in:
Daniel Hougaard
2024-02-29 07:12:10 +01:00
parent c91456838e
commit 1ad916a784
+52 -16
View File
@@ -73,6 +73,19 @@ type Template struct {
DestinationPath string `yaml:"destination-path"` DestinationPath string `yaml:"destination-path"`
} }
type SecretsStateManager struct {
// etags should be stored in memory, and the key should be env-secretPath-projectID, and the value should be the actual etag
etags map[string]string
secretMutationChannel chan bool
}
func NewSecretsStateManager(secretMutationChannel chan bool) *SecretsStateManager {
return &SecretsStateManager{
etags: make(map[string]string),
secretMutationChannel: secretMutationChannel,
}
}
func ReadFile(filePath string) ([]byte, error) { func ReadFile(filePath string) ([]byte, error) {
return ioutil.ReadFile(filePath) return ioutil.ReadFile(filePath)
} }
@@ -170,20 +183,29 @@ func ParseAgentConfig(configFile []byte) (*Config, error) {
return config, nil return config, nil
} }
func secretTemplateFunction(accessToken string) func(string, string, string) ([]models.SingleEnvironmentVariable, error) { func secretTemplateFunction(accessToken string, secretStateManager *SecretsStateManager) func(string, string, string) ([]models.SingleEnvironmentVariable, error) {
return func(projectID, envSlug, secretPath string) ([]models.SingleEnvironmentVariable, error) { return func(projectID, envSlug, secretPath string) ([]models.SingleEnvironmentVariable, error) {
secrets, err := util.GetPlainTextSecretsViaMachineIdentity(accessToken, projectID, envSlug, secretPath, false) res, err := util.GetPlainTextSecretsViaMachineIdentity(accessToken, projectID, envSlug, secretPath, false)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return secrets, nil if secretStateManager != nil {
key := fmt.Sprintf("%s-%s-%s", envSlug, secretPath, projectID)
oldEtag, ok := secretStateManager.etags[key] // if there's no etag, it means it's the first time we are fetching this secret. we should only notify the secretMutationChannel if the etag has changed, not if it's the first time we are fetching the secret
if ok && oldEtag != res.Hash {
secretStateManager.secretMutationChannel <- true
}
secretStateManager.etags[key] = res.Hash
}
return res.Secrets, nil
} }
} }
func ProcessTemplate(templatePath string, data interface{}, accessToken string) (*bytes.Buffer, error) { func ProcessTemplate(templatePath string, data interface{}, accessToken string, secretStateManager *SecretsStateManager) (*bytes.Buffer, error) {
// custom template function to fetch secrets from Infisical // custom template function to fetch secrets from Infisical
secretFunction := secretTemplateFunction(accessToken) secretFunction := secretTemplateFunction(accessToken, secretStateManager)
funcs := template.FuncMap{ funcs := template.FuncMap{
"secret": secretFunction, "secret": secretFunction,
} }
@@ -203,7 +225,7 @@ func ProcessTemplate(templatePath string, data interface{}, accessToken string)
return &buf, nil return &buf, nil
} }
func ProcessBase64Template(encodedTemplate string, data interface{}, accessToken string) (*bytes.Buffer, error) { func ProcessBase64Template(encodedTemplate string, data interface{}, accessToken string, secretStateManager *SecretsStateManager) (*bytes.Buffer, error) {
// custom template function to fetch secrets from Infisical // custom template function to fetch secrets from Infisical
decoded, err := base64.StdEncoding.DecodeString(encodedTemplate) decoded, err := base64.StdEncoding.DecodeString(encodedTemplate)
if err != nil { if err != nil {
@@ -212,7 +234,7 @@ func ProcessBase64Template(encodedTemplate string, data interface{}, accessToken
templateString := string(decoded) templateString := string(decoded)
secretFunction := secretTemplateFunction(accessToken) secretFunction := secretTemplateFunction(accessToken, secretStateManager) // TODO: Fix this
funcs := template.FuncMap{ funcs := template.FuncMap{
"secret": secretFunction, "secret": secretFunction,
} }
@@ -244,13 +266,22 @@ type TokenManager struct {
clientIdPath string clientIdPath string
clientSecretPath string clientSecretPath string
newAccessTokenNotificationChan chan bool newAccessTokenNotificationChan chan bool
removeClientSecretOnRead bool
cachedClientSecret string removeClientSecretOnRead bool
exitAfterAuth bool cachedClientSecret string
exitAfterAuth bool
} }
func NewTokenManager(fileDeposits []Sink, templates []Template, clientIdPath string, clientSecretPath string, newAccessTokenNotificationChan chan bool, removeClientSecretOnRead bool, exitAfterAuth bool) *TokenManager { func NewTokenManager(fileDeposits []Sink, templates []Template, clientIdPath string, clientSecretPath string, newAccessTokenNotificationChan chan bool, removeClientSecretOnRead bool, exitAfterAuth bool) *TokenManager {
return &TokenManager{filePaths: fileDeposits, templates: templates, clientIdPath: clientIdPath, clientSecretPath: clientSecretPath, newAccessTokenNotificationChan: newAccessTokenNotificationChan, removeClientSecretOnRead: removeClientSecretOnRead, exitAfterAuth: exitAfterAuth} return &TokenManager{
filePaths: fileDeposits,
templates: templates,
clientIdPath: clientIdPath,
clientSecretPath: clientSecretPath,
newAccessTokenNotificationChan: newAccessTokenNotificationChan,
removeClientSecretOnRead: removeClientSecretOnRead,
exitAfterAuth: exitAfterAuth,
}
} }
func (tm *TokenManager) SetToken(token string, accessTokenTTL time.Duration, accessTokenMaxTTL time.Duration) { func (tm *TokenManager) SetToken(token string, accessTokenTTL time.Duration, accessTokenMaxTTL time.Duration) {
@@ -428,7 +459,7 @@ func (tm *TokenManager) WriteTokenToFiles() {
} }
} }
func (tm *TokenManager) FetchSecrets() { func (tm *TokenManager) FetchSecrets(secretStateManager *SecretsStateManager) {
log.Info().Msgf("template engine started...") log.Info().Msgf("template engine started...")
for { for {
token := tm.GetToken() token := tm.GetToken()
@@ -437,9 +468,9 @@ func (tm *TokenManager) FetchSecrets() {
var processedTemplate *bytes.Buffer var processedTemplate *bytes.Buffer
var err error var err error
if secretTemplate.SourcePath != "" { if secretTemplate.SourcePath != "" {
processedTemplate, err = ProcessTemplate(secretTemplate.SourcePath, nil, token) processedTemplate, err = ProcessTemplate(secretTemplate.SourcePath, nil, token, secretStateManager)
} else { } else {
processedTemplate, err = ProcessBase64Template(secretTemplate.Base64TemplateContent, nil, token) processedTemplate, err = ProcessBase64Template(secretTemplate.Base64TemplateContent, nil, token, secretStateManager)
} }
if err != nil { if err != nil {
@@ -458,7 +489,7 @@ func (tm *TokenManager) FetchSecrets() {
} }
// fetch new secrets every 5 minutes (TODO: add PubSub in the future ) // fetch new secrets every 5 minutes (TODO: add PubSub in the future )
time.Sleep(5 * time.Minute) time.Sleep(5 * time.Second)
} }
} }
} }
@@ -537,19 +568,24 @@ var agentCmd = &cobra.Command{
configUniversalAuthType := agentConfig.Auth.Config.(UniversalAuth) configUniversalAuthType := agentConfig.Auth.Config.(UniversalAuth)
tokenRefreshNotifier := make(chan bool) tokenRefreshNotifier := make(chan bool)
secretMutationNotifier := make(chan bool)
sigChan := make(chan os.Signal, 1) sigChan := make(chan os.Signal, 1)
signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM) signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
filePaths := agentConfig.Sinks filePaths := agentConfig.Sinks
tm := NewTokenManager(filePaths, agentConfig.Templates, configUniversalAuthType.ClientIDPath, configUniversalAuthType.ClientSecretPath, tokenRefreshNotifier, configUniversalAuthType.RemoveClientSecretOnRead, agentConfig.Infisical.ExitAfterAuth) tm := NewTokenManager(filePaths, agentConfig.Templates, configUniversalAuthType.ClientIDPath, configUniversalAuthType.ClientSecretPath, tokenRefreshNotifier, configUniversalAuthType.RemoveClientSecretOnRead, agentConfig.Infisical.ExitAfterAuth)
ssm := NewSecretsStateManager(secretMutationNotifier)
go tm.ManageTokenLifecycle() go tm.ManageTokenLifecycle()
go tm.FetchSecrets() go tm.FetchSecrets(ssm)
for { for {
select { select {
case <-tokenRefreshNotifier: case <-tokenRefreshNotifier:
go tm.WriteTokenToFiles() go tm.WriteTokenToFiles()
case <-secretMutationNotifier:
log.Info().Msgf("Mashallah, a mutation has occurred")
case <-sigChan: case <-sigChan:
log.Info().Msg("agent is gracefully shutting...") log.Info().Msg("agent is gracefully shutting...")
// TODO: check if we are in the middle of writing files to disk // TODO: check if we are in the middle of writing files to disk