Feat: Auth methods (draft 1)

This commit is contained in:
Daniel Hougaard
2024-06-14 07:20:42 +02:00
parent f41ec46a35
commit e19b67f9a2
+93 -56
View File
@@ -20,6 +20,7 @@ import (
"text/template" "text/template"
"time" "time"
infisicalSdk "github.com/infisical/go-sdk"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
"gopkg.in/yaml.v2" "gopkg.in/yaml.v2"
@@ -87,15 +88,6 @@ type Template struct {
} `yaml:"config"` } `yaml:"config"`
} }
func newAgentTemplateChannels(templates []Template) map[string]chan bool {
// we keep each destination as an identifier for various channel
templateChannel := make(map[string]chan bool)
for _, template := range templates {
templateChannel[template.DestinationPath] = make(chan bool)
}
return templateChannel
}
type DynamicSecretLease struct { type DynamicSecretLease struct {
LeaseID string LeaseID string
ExpireAt time.Time ExpireAt time.Time
@@ -296,7 +288,7 @@ func ParseAgentConfig(configFile []byte) (*Config, error) {
} }
switch rawConfig.Auth.Type { switch rawConfig.Auth.Type {
case "universal-auth": case string(util.AuthStrategy.UNIVERSAL_AUTH):
var tokenConfig UniversalAuth var tokenConfig UniversalAuth
if err := yaml.Unmarshal(configBytes, &tokenConfig); err != nil { if err := yaml.Unmarshal(configBytes, &tokenConfig); err != nil {
return nil, err return nil, err
@@ -337,7 +329,7 @@ func dynamicSecretTemplateFunction(accessToken string, dynamicSecretManager *Dyn
return func(args ...string) (map[string]interface{}, error) { return func(args ...string) (map[string]interface{}, error) {
argLength := len(args) argLength := len(args)
if argLength != 4 && argLength != 5 { if argLength != 4 && argLength != 5 {
return nil, fmt.Errorf("Invalid arguments found for dynamic-secret function. Check template %i", templateId) return nil, fmt.Errorf("invalid arguments found for dynamic-secret function. Check template %d", templateId)
} }
projectSlug, envSlug, secretPath, slug, ttl := args[0], args[1], args[2], args[3], "" projectSlug, envSlug, secretPath, slug, ttl := args[0], args[1], args[2], args[3], ""
@@ -421,32 +413,56 @@ func ProcessBase64Template(templateId int, encodedTemplate string, data interfac
} }
type AgentManager struct { type AgentManager struct {
accessToken string accessToken string
accessTokenTTL time.Duration accessTokenTTL time.Duration
accessTokenMaxTTL time.Duration accessTokenMaxTTL time.Duration
accessTokenFetchedTime time.Time accessTokenFetchedTime time.Time
accessTokenRefreshedTime time.Time accessTokenRefreshedTime time.Time
mutex sync.Mutex mutex sync.Mutex
filePaths []Sink // Store file paths if needed filePaths []Sink // Store file paths if needed
templates []Template templates []Template
dynamicSecretLeases *DynamicSecretLeaseManager dynamicSecretLeases *DynamicSecretLeaseManager
clientIdPath string
clientSecretPath string universalAuthConfig UniversalAuth
newAccessTokenNotificationChan chan bool authStrategy util.AuthStrategyType
removeClientSecretOnRead bool
cachedClientSecret string newAccessTokenNotificationChan chan bool
exitAfterAuth bool removeUniversalAuthClientSecretOnRead bool
cachedUniversalAuthClientSecret string
exitAfterAuth bool
infisicalClient infisicalSdk.InfisicalClientInterface
} }
func NewAgentManager(fileDeposits []Sink, templates []Template, clientIdPath string, clientSecretPath string, newAccessTokenNotificationChan chan bool, removeClientSecretOnRead bool, exitAfterAuth bool) *AgentManager { type NewAgentMangerOptions struct {
FileDeposits []Sink
Templates []Template
UniversalAuthConfig UniversalAuth
AuthStrategy util.AuthStrategyType
NewAccessTokenNotificationChan chan bool
ExitAfterAuth bool
}
func NewAgentManager(options NewAgentMangerOptions) *AgentManager {
return &AgentManager{ return &AgentManager{
filePaths: fileDeposits, filePaths: options.FileDeposits,
templates: templates, templates: options.Templates,
clientIdPath: clientIdPath,
clientSecretPath: clientSecretPath, universalAuthConfig: options.UniversalAuthConfig,
newAccessTokenNotificationChan: newAccessTokenNotificationChan, authStrategy: options.AuthStrategy,
removeClientSecretOnRead: removeClientSecretOnRead,
exitAfterAuth: exitAfterAuth, newAccessTokenNotificationChan: options.NewAccessTokenNotificationChan,
exitAfterAuth: options.ExitAfterAuth,
infisicalClient: infisicalSdk.NewInfisicalClient(infisicalSdk.Config{
SiteUrl: config.INFISICAL_URL,
UserAgent: "infisical-agent",
}),
// clientIdPath: clientIdPath,
// clientSecretPath: clientSecretPath,
} }
} }
@@ -469,52 +485,61 @@ func (tm *AgentManager) GetToken() string {
return tm.accessToken return tm.accessToken
} }
// Fetches a new access token using client credentials func fetchUniversalAuthAccessToken(tm *AgentManager) (credential infisicalSdk.MachineIdentityCredential, e error) {
func (tm *AgentManager) FetchNewAccessToken() error {
clientID := os.Getenv(util.INFISICAL_UNIVERSAL_AUTH_CLIENT_ID_NAME) clientID := os.Getenv(util.INFISICAL_UNIVERSAL_AUTH_CLIENT_ID_NAME)
if clientID == "" { if clientID == "" {
clientIDAsByte, err := ReadFile(tm.clientIdPath) clientIDAsByte, err := ReadFile(tm.universalAuthConfig.ClientIDPath)
if err != nil { if err != nil {
return fmt.Errorf("unable to read client id from file path '%s' due to error: %v", tm.clientIdPath, err) return infisicalSdk.MachineIdentityCredential{}, fmt.Errorf("unable to read client id from file path '%s' due to error: %v", tm.universalAuthConfig.ClientIDPath, err)
} }
clientID = string(clientIDAsByte) clientID = string(clientIDAsByte)
} }
clientSecret := os.Getenv("INFISICAL_UNIVERSAL_CLIENT_SECRET") clientSecret := os.Getenv("INFISICAL_UNIVERSAL_CLIENT_SECRET")
if clientSecret == "" { if clientSecret == "" {
clientSecretAsByte, err := ReadFile(tm.clientSecretPath) clientSecretAsByte, err := ReadFile(tm.universalAuthConfig.ClientSecretPath)
if err != nil { if err != nil {
if len(tm.cachedClientSecret) == 0 { if len(tm.cachedUniversalAuthClientSecret) == 0 {
return fmt.Errorf("unable to read client secret from file and no cached client secret found: %v", err) return infisicalSdk.MachineIdentityCredential{}, fmt.Errorf("unable to read client secret from file and no cached client secret found: %v", err)
} else { } else {
clientSecretAsByte = []byte(tm.cachedClientSecret) clientSecretAsByte = []byte(tm.cachedUniversalAuthClientSecret)
} }
} }
clientSecret = string(clientSecretAsByte) clientSecret = string(clientSecretAsByte)
} }
// remove client secret after first read tm.cachedUniversalAuthClientSecret = clientSecret
if tm.removeClientSecretOnRead { if tm.removeUniversalAuthClientSecretOnRead {
os.Remove(tm.clientSecretPath) defer os.Remove(tm.universalAuthConfig.ClientSecretPath)
} }
// save as cache in memory return tm.infisicalClient.Auth().UniversalAuthLogin(clientID, clientSecret)
tm.cachedClientSecret = clientSecret
}
// Fetches a new access token using client credentials
func (tm *AgentManager) FetchNewAccessToken() error {
authStrategies := map[util.AuthStrategyType]func(agentManager *AgentManager) (credential infisicalSdk.MachineIdentityCredential, e error){
util.AuthStrategy.UNIVERSAL_AUTH: fetchUniversalAuthAccessToken,
}
credential, err := authStrategies[tm.authStrategy](tm)
loginResponse, err := util.UniversalAuthLogin(clientID, clientSecret)
if err != nil { if err != nil {
return err return err
} }
accessTokenTTL := time.Duration(loginResponse.AccessTokenTTL * int(time.Second)) accessTokenTTL := time.Duration(credential.ExpiresIn * int64(time.Second))
accessTokenMaxTTL := time.Duration(loginResponse.AccessTokenMaxTTL * int(time.Second)) accessTokenMaxTTL := time.Duration(credential.AccessTokenMaxTTL * int64(time.Second))
if accessTokenTTL <= time.Duration(5)*time.Second { if accessTokenTTL <= time.Duration(5)*time.Second {
util.PrintErrorMessageAndExit("At this this, agent does not support refresh of tokens with 5 seconds or less ttl. Please increase access token ttl and try again") util.PrintErrorMessageAndExit("At this time, agent does not support refresh of tokens with 5 seconds or less ttl. Please increase access token ttl and try again")
} }
tm.accessTokenFetchedTime = time.Now() tm.accessTokenFetchedTime = time.Now()
tm.SetToken(loginResponse.AccessToken, accessTokenTTL, accessTokenMaxTTL) tm.SetToken(credential.AccessToken, accessTokenTTL, accessTokenMaxTTL)
return nil return nil
} }
@@ -527,7 +552,7 @@ func (tm *AgentManager) RefreshAccessToken() error {
SetRetryWaitTime(5 * time.Second) SetRetryWaitTime(5 * time.Second)
accessToken := tm.GetToken() accessToken := tm.GetToken()
response, err := api.CallUniversalAuthRefreshAccessToken(httpClient, api.UniversalAuthRefreshRequest{AccessToken: accessToken}) response, err := api.CallMachineIdentityRefreshAccessToken(httpClient, api.UniversalAuthRefreshRequest{AccessToken: accessToken})
if err != nil { if err != nil {
return err return err
} }
@@ -564,6 +589,7 @@ func (tm *AgentManager) ManageTokenLifecycle() {
continue continue
} }
} else if time.Now().After(accessTokenMaxTTLExpiresInTime) { } else if time.Now().After(accessTokenMaxTTLExpiresInTime) {
// case: token has reached max ttl and we should re-authenticate entirely (cannot refresh)
log.Info().Msgf("token has reached max ttl, attempting to re authenticate...") log.Info().Msgf("token has reached max ttl, attempting to re authenticate...")
err := tm.FetchNewAccessToken() err := tm.FetchNewAccessToken()
if err != nil { if err != nil {
@@ -574,6 +600,7 @@ func (tm *AgentManager) ManageTokenLifecycle() {
continue continue
} }
} else { } else {
// case: token ttl has expired, but the token is still within max ttl, so we can refresh
log.Info().Msgf("attempting to refresh existing token...") log.Info().Msgf("attempting to refresh existing token...")
err := tm.RefreshAccessToken() err := tm.RefreshAccessToken()
if err != nil { if err != nil {
@@ -770,8 +797,10 @@ var agentCmd = &cobra.Command{
return return
} }
if agentConfig.Auth.Type != "universal-auth" { authMethodValid, authStrategy := util.IsAuthMethodValid(agentConfig.Auth.Type)
util.PrintErrorMessageAndExit("Only auth type of 'universal-auth' is supported at this time")
if !authMethodValid {
util.PrintErrorMessageAndExit("Invalid auth method provided. Please provide a valid auth method")
} }
configUniversalAuthType := agentConfig.Auth.Config.(UniversalAuth) configUniversalAuthType := agentConfig.Auth.Config.(UniversalAuth)
@@ -781,7 +810,15 @@ var agentCmd = &cobra.Command{
signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM) signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
filePaths := agentConfig.Sinks filePaths := agentConfig.Sinks
tm := NewAgentManager(filePaths, agentConfig.Templates, configUniversalAuthType.ClientIDPath, configUniversalAuthType.ClientSecretPath, tokenRefreshNotifier, configUniversalAuthType.RemoveClientSecretOnRead, agentConfig.Infisical.ExitAfterAuth) tm := NewAgentManager(NewAgentMangerOptions{
FileDeposits: filePaths,
Templates: agentConfig.Templates,
UniversalAuthConfig: configUniversalAuthType,
NewAccessTokenNotificationChan: tokenRefreshNotifier,
ExitAfterAuth: agentConfig.Infisical.ExitAfterAuth,
AuthStrategy: authStrategy,
})
tm.dynamicSecretLeases = NewDynamicSecretLeaseManager(sigChan) tm.dynamicSecretLeases = NewDynamicSecretLeaseManager(sigChan)
go tm.ManageTokenLifecycle() go tm.ManageTokenLifecycle()