Files
oikos/vendor/github.com/infisical/go-sdk/client_auth_helper.go
dtoro febc153b7f
Some checks failed
ci / build-test (push) Has been cancelled
ci / docker-build (push) Has been cancelled
ci / web (push) Has been cancelled
Desktop App / Build Linux (amd64) (push) Has been cancelled
Desktop App / Attach to Release (push) Has been cancelled
fix: add involves edge from task to agent:nomos at creation
Plus sync vendor directory for Docker build compatibility.
2026-08-11 22:03:12 +02:00

260 lines
9.7 KiB
Go

package infisical
import (
"fmt"
"net/http"
"strings"
"time"
"github.com/go-resty/resty/v2"
api "github.com/infisical/go-sdk/packages/api/auth"
"github.com/infisical/go-sdk/packages/models"
"github.com/infisical/go-sdk/packages/util"
)
const renewalBufferSeconds = 5
// isTokenExpiringSoon checks if the token will expire within the given buffer time.
func (c *InfisicalClient) isTokenExpiringSoon(bufferSeconds int64) bool {
c.mu.RLock()
defer c.mu.RUnlock()
// Skip if no auth method or using plain access token (no refresh capability)
if c.authMethod == "" || c.authMethod == util.ACCESS_TOKEN {
return false
}
// Skip if token details are not set
if c.tokenDetails.ExpiresIn == 0 {
return false
}
timeSinceLastFetch := time.Since(c.lastFetchedTime).Seconds()
return timeSinceLastFetch >= float64(c.tokenDetails.ExpiresIn-bufferSeconds)
}
// refreshTokenSynchronously performs a blocking token refresh. This gets called by the onbeforerequest hook and the token lifecycle goroutine.
func (c *InfisicalClient) refreshTokenSynchronously(manualTrigger bool) error {
c.logger.Debug().Msgf("Refreshing token synchronously. Manual trigger: %v", manualTrigger)
// Use TryLock to prevent deadlocks when the refresh operation itself triggers HTTP requests (renewal or re-auth)
// If we can't acquire the lock, another goroutine is already refreshing.
if !c.refreshMu.TryLock() {
c.logger.Debug().Msg("Another refresh is already in progress, skipping")
return nil
}
defer c.refreshMu.Unlock()
// Double-check if refresh is still needed after acquiring the lock
// (another goroutine might have already refreshed)
if !c.isTokenExpiringSoon(renewalBufferSeconds) {
return nil
}
c.mu.RLock()
authMethod := c.authMethod
credential := c.credential
tokenDetails := c.tokenDetails
firstFetchedTime := c.firstFetchedTime
config := c.config
c.mu.RUnlock()
// Check if we need re-auth (approaching max TTL) or can renew
timeSinceFirstFetch := time.Since(firstFetchedTime).Seconds()
timeUntilMaxTTL := float64(tokenDetails.AccessTokenMaxTTL) - timeSinceFirstFetch
// If time until max TTL is less than the token TTL, we need to re-auth
needsReAuth := timeUntilMaxTTL < float64(tokenDetails.ExpiresIn)
c.logger.Debug().Msgf("timeSinceFirstFetch: %f, timeUntilMaxTTL: %f, needsReAuth: %v", timeSinceFirstFetch, timeUntilMaxTTL, needsReAuth)
if needsReAuth {
c.logger.Debug().Msgf("Re-authentication needed. Attempting re-authentication")
err := c.doReAuthentication(authMethod, credential, config)
message := "Re-authentication successful"
if err != nil {
message = fmt.Sprintf("Re-authentication failed. Error: %v", err)
}
c.logger.Debug().Msg(message)
return err
}
// Try renewal first
c.logger.Debug().Msgf("Attempting token renewal")
err := c.doTokenRenewal(tokenDetails.AccessToken, credential, authMethod)
if err != nil {
c.logger.Debug().Msgf("Token renewal failed. Attempting re-authentication as fallback. Error: %v", err)
// Renewal failed, try re-authentication as fallback
if !config.SilentMode {
util.PrintWarning(c.logger, fmt.Sprintf("Token renewal failed during pre-request check: %s. Attempting re-authentication", err.Error()))
}
message := "Re-authentication successful as fallback"
err = c.doReAuthentication(authMethod, credential, config)
if err != nil {
message = fmt.Sprintf("Re-authentication failed as fallback. Error: %v", err)
}
c.logger.Debug().Msg(message)
return err
}
c.logger.Debug().Msgf("Token renewal successful")
return nil
}
// doTokenRenewal attempts to renew the access token.
func (c *InfisicalClient) doTokenRenewal(accessToken string, credential interface{}, authMethod util.AuthMethod) error {
renewedCredential, err := api.CallRenewAccessToken(c.httpClient, api.RenewAccessTokenRequest{AccessToken: accessToken})
if err != nil {
return err
}
c.setAccessToken(renewedCredential, credential, authMethod)
return nil
}
// doReAuthentication performs a full re-authentication using the stored credentials.
func (c *InfisicalClient) doReAuthentication(authMethod util.AuthMethod, credential interface{}, config Config) error {
authStrategies := c.getAuthStrategies()
strategy, exists := authStrategies[authMethod]
if !exists {
return fmt.Errorf("unknown auth method: %s", authMethod)
}
newToken, err := strategy(credential)
if err != nil {
if !config.SilentMode {
util.PrintWarning(c.logger, fmt.Sprintf("Re-authentication failed during pre-request check: %s", err.Error()))
}
return err
}
c.setAccessToken(newToken, credential, authMethod)
c.mu.Lock()
c.firstFetchedTime = time.Now()
c.mu.Unlock()
return nil
}
// calculateSleepTime determines how long to sleep before the next token refresh check
func (c *InfisicalClient) calculateSleepTime(tokenDetails MachineIdentityCredential, bufferSeconds int64) time.Duration {
if tokenDetails.ExpiresIn == 0 {
return 1 * time.Second
}
c.mu.RLock()
timeSinceLastFetch := time.Since(c.lastFetchedTime).Seconds()
c.mu.RUnlock()
timeUntilExpiry := float64(tokenDetails.ExpiresIn) - timeSinceLastFetch - float64(bufferSeconds)
if timeUntilExpiry <= 0 {
return 1 * time.Second
}
return time.Duration(timeUntilExpiry) * time.Second
}
// getAuthStrategies returns the map of authentication strategies
func (c *InfisicalClient) getAuthStrategies() map[util.AuthMethod]func(cred interface{}) (credential MachineIdentityCredential, err error) {
return map[util.AuthMethod]func(cred interface{}) (credential MachineIdentityCredential, err error){
util.UNIVERSAL_AUTH: func(cred interface{}) (credential MachineIdentityCredential, err error) {
if parsedCreds, ok := cred.(models.UniversalAuthCredential); ok {
return c.auth.UniversalAuthLogin(parsedCreds.ClientID, parsedCreds.ClientSecret)
}
return MachineIdentityCredential{}, fmt.Errorf("failed to parse UniversalAuthCredential")
},
util.KUBERNETES: func(cred interface{}) (credential MachineIdentityCredential, err error) {
if parsedCreds, ok := cred.(models.KubernetesCredential); ok {
return c.auth.KubernetesRawServiceAccountTokenLogin(parsedCreds.IdentityID, parsedCreds.ServiceAccountToken)
}
return MachineIdentityCredential{}, fmt.Errorf("failed to parse KubernetesAuthCredential")
},
util.AZURE: func(cred interface{}) (credential MachineIdentityCredential, err error) {
if parsedCreds, ok := cred.(models.AzureCredential); ok {
if parsedCreds.ClientID != "" {
return c.auth.WithAzureClientID(parsedCreds.ClientID).AzureAuthLogin(parsedCreds.IdentityID, parsedCreds.Resource)
}
return c.auth.AzureAuthLogin(parsedCreds.IdentityID, parsedCreds.Resource)
}
return MachineIdentityCredential{}, fmt.Errorf("failed to parse AzureAuthCredential")
},
util.GCP_ID_TOKEN: func(cred interface{}) (credential MachineIdentityCredential, err error) {
if parsedCreds, ok := cred.(models.GCPIDTokenCredential); ok {
return c.auth.GcpIdTokenAuthLogin(parsedCreds.IdentityID)
}
return MachineIdentityCredential{}, fmt.Errorf("failed to parse GCPIDTokenCredential")
},
util.GCP_IAM: func(cred interface{}) (credential MachineIdentityCredential, err error) {
if parsedCreds, ok := cred.(models.GCPIAMCredential); ok {
return c.auth.GcpIamAuthLogin(parsedCreds.IdentityID, parsedCreds.ServiceAccountKeyFilePath)
}
return MachineIdentityCredential{}, fmt.Errorf("failed to parse GCPIAMCredential")
},
util.AWS_IAM: func(cred interface{}) (credential MachineIdentityCredential, err error) {
if parsedCreds, ok := cred.(models.AWSIAMCredential); ok {
return c.auth.AwsIamAuthLogin(parsedCreds.IdentityID)
}
return MachineIdentityCredential{}, fmt.Errorf("failed to parse AWSIAMCredential")
},
util.JWT_AUTH: func(cred interface{}) (credential MachineIdentityCredential, err error) {
if parsedCreds, ok := cred.(models.JWTCredential); ok {
return c.auth.JwtAuthLogin(parsedCreds.IdentityID, parsedCreds.JWT)
}
return MachineIdentityCredential{}, fmt.Errorf("failed to parse JWTCredential")
},
util.LDAP_AUTH: func(cred interface{}) (credential MachineIdentityCredential, err error) {
if parsedCreds, ok := cred.(models.LDAPCredential); ok {
return c.auth.LdapAuthLogin(parsedCreds.IdentityID, parsedCreds.Username, parsedCreds.Password)
}
return MachineIdentityCredential{}, fmt.Errorf("failed to parse LDAPCredential")
},
util.OCI_AUTH: func(cred interface{}) (credential MachineIdentityCredential, err error) {
if parsedCreds, ok := cred.(models.OCICredential); ok {
return c.auth.OciAuthLogin(OciAuthLoginOptions{
IdentityID: parsedCreds.IdentityID,
PrivateKey: parsedCreds.PrivateKey,
Fingerprint: parsedCreds.Fingerprint,
UserID: parsedCreds.UserID,
TenancyID: parsedCreds.TenancyID,
Region: parsedCreds.Region,
Passphrase: parsedCreds.Passphrase,
})
}
return MachineIdentityCredential{}, fmt.Errorf("failed to parse OCICredential")
},
}
}
func (c *InfisicalClient) beforeRequestAuthInterceptor(client *resty.Client, req *resty.Request) error {
// skip auth endpoints to prevent infinite loops.
// note(daniel): req.URL contains just the path ("/v1/auth/..."), not the full URL with base.
// the base URL has /api appended, but that's not part of req.URL at this point.
if strings.Contains(req.URL, "/v1/auth/") && req.Method == http.MethodPost {
return nil
}
// Check if token is expired or will expire within 5 seconds
if c.isTokenExpiringSoon(renewalBufferSeconds) {
if err := c.refreshTokenSynchronously(true); err != nil {
// Don't fail the request on refresh error, we let the request fail with 401 as it normally would.
// logging is already done within refreshTokenSynchronously
return nil
}
c.mu.RLock()
newToken := c.tokenDetails.AccessToken
c.mu.RUnlock()
if newToken != "" {
req.SetAuthToken(newToken)
}
}
return nil
}