mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-06 22:27:22 +00:00
Feat: Auth methods (draft 1)
This commit is contained in:
+93
-56
@@ -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()
|
||||||
|
|||||||
Reference in New Issue
Block a user