mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-10 06:28:50 +00:00
Merge remote-tracking branch 'origin/main' into feat/cli-secret-plain-output
This commit is contained in:
+239
-108
@@ -15,12 +15,12 @@ import (
|
||||
"path"
|
||||
"runtime"
|
||||
"slices"
|
||||
"strings"
|
||||
"sync"
|
||||
"syscall"
|
||||
"text/template"
|
||||
"time"
|
||||
|
||||
infisicalSdk "github.com/infisical/go-sdk"
|
||||
"github.com/rs/zerolog/log"
|
||||
"gopkg.in/yaml.v2"
|
||||
|
||||
@@ -60,9 +60,26 @@ type UniversalAuth struct {
|
||||
RemoveClientSecretOnRead bool `yaml:"remove_client_secret_on_read"`
|
||||
}
|
||||
|
||||
type OAuthConfig struct {
|
||||
ClientID string `yaml:"client-id"`
|
||||
ClientSecret string `yaml:"client-secret"`
|
||||
type KubernetesAuth struct {
|
||||
IdentityID string `yaml:"identity-id"`
|
||||
ServiceAccountToken string `yaml:"service-account-token"`
|
||||
}
|
||||
|
||||
type AzureAuth struct {
|
||||
IdentityID string `yaml:"identity-id"`
|
||||
}
|
||||
|
||||
type GcpIdTokenAuth struct {
|
||||
IdentityID string `yaml:"identity-id"`
|
||||
}
|
||||
|
||||
type GcpIamAuth struct {
|
||||
IdentityID string `yaml:"identity-id"`
|
||||
ServiceAccountKey string `yaml:"service-account-key"`
|
||||
}
|
||||
|
||||
type AwsIamAuth struct {
|
||||
IdentityID string `yaml:"identity-id"`
|
||||
}
|
||||
|
||||
type Sink struct {
|
||||
@@ -88,15 +105,6 @@ type Template struct {
|
||||
} `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 {
|
||||
LeaseID string
|
||||
ExpireAt time.Time
|
||||
@@ -257,17 +265,12 @@ func WriteBytesToFile(data *bytes.Buffer, outputPath string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
func appendAPIEndpoint(address string) string {
|
||||
// Ensure the address does not already end with "/api"
|
||||
if strings.HasSuffix(address, "/api") {
|
||||
return address
|
||||
func ParseAuthConfig(authConfigFile []byte, destination interface{}) error {
|
||||
if err := yaml.Unmarshal(authConfigFile, destination); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Check if the address ends with a slash and append accordingly
|
||||
if address[len(address)-1] == '/' {
|
||||
return address + "api"
|
||||
}
|
||||
return address + "/api"
|
||||
return nil
|
||||
}
|
||||
|
||||
func ParseAgentConfig(configFile []byte) (*Config, error) {
|
||||
@@ -290,43 +293,20 @@ func ParseAgentConfig(configFile []byte) (*Config, error) {
|
||||
rawConfig.Infisical.Address = DEFAULT_INFISICAL_CLOUD_URL
|
||||
}
|
||||
|
||||
config.INFISICAL_URL = appendAPIEndpoint(rawConfig.Infisical.Address)
|
||||
config.INFISICAL_URL = util.AppendAPIEndpoint(rawConfig.Infisical.Address)
|
||||
|
||||
log.Info().Msgf("Infisical instance address set to %s", rawConfig.Infisical.Address)
|
||||
|
||||
config := &Config{
|
||||
Infisical: rawConfig.Infisical,
|
||||
Auth: AuthConfig{
|
||||
Type: rawConfig.Auth.Type,
|
||||
Type: rawConfig.Auth.Type,
|
||||
Config: rawConfig.Auth.Config,
|
||||
},
|
||||
Sinks: rawConfig.Sinks,
|
||||
Templates: rawConfig.Templates,
|
||||
}
|
||||
|
||||
// Marshal and then unmarshal the config based on the type
|
||||
configBytes, err := yaml.Marshal(rawConfig.Auth.Config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
switch rawConfig.Auth.Type {
|
||||
case "universal-auth":
|
||||
var tokenConfig UniversalAuth
|
||||
if err := yaml.Unmarshal(configBytes, &tokenConfig); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
config.Auth.Config = tokenConfig
|
||||
case "oauth": // aws, gcp, k8s service account, etc
|
||||
var oauthConfig OAuthConfig
|
||||
if err := yaml.Unmarshal(configBytes, &oauthConfig); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
config.Auth.Config = oauthConfig
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown auth type: %s", rawConfig.Auth.Type)
|
||||
}
|
||||
|
||||
return config, nil
|
||||
}
|
||||
|
||||
@@ -351,7 +331,7 @@ func dynamicSecretTemplateFunction(accessToken string, dynamicSecretManager *Dyn
|
||||
return func(args ...string) (map[string]interface{}, error) {
|
||||
argLength := len(args)
|
||||
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], ""
|
||||
@@ -435,32 +415,54 @@ func ProcessBase64Template(templateId int, encodedTemplate string, data interfac
|
||||
}
|
||||
|
||||
type AgentManager struct {
|
||||
accessToken string
|
||||
accessTokenTTL time.Duration
|
||||
accessTokenMaxTTL time.Duration
|
||||
accessTokenFetchedTime time.Time
|
||||
accessTokenRefreshedTime time.Time
|
||||
mutex sync.Mutex
|
||||
filePaths []Sink // Store file paths if needed
|
||||
templates []Template
|
||||
dynamicSecretLeases *DynamicSecretLeaseManager
|
||||
clientIdPath string
|
||||
clientSecretPath string
|
||||
newAccessTokenNotificationChan chan bool
|
||||
removeClientSecretOnRead bool
|
||||
cachedClientSecret string
|
||||
exitAfterAuth bool
|
||||
accessToken string
|
||||
accessTokenTTL time.Duration
|
||||
accessTokenMaxTTL time.Duration
|
||||
accessTokenFetchedTime time.Time
|
||||
accessTokenRefreshedTime time.Time
|
||||
mutex sync.Mutex
|
||||
filePaths []Sink // Store file paths if needed
|
||||
templates []Template
|
||||
dynamicSecretLeases *DynamicSecretLeaseManager
|
||||
|
||||
authConfigBytes []byte
|
||||
authStrategy util.AuthStrategyType
|
||||
|
||||
newAccessTokenNotificationChan chan 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
|
||||
|
||||
AuthConfigBytes []byte
|
||||
AuthStrategy util.AuthStrategyType
|
||||
|
||||
NewAccessTokenNotificationChan chan bool
|
||||
ExitAfterAuth bool
|
||||
}
|
||||
|
||||
func NewAgentManager(options NewAgentMangerOptions) *AgentManager {
|
||||
|
||||
return &AgentManager{
|
||||
filePaths: fileDeposits,
|
||||
templates: templates,
|
||||
clientIdPath: clientIdPath,
|
||||
clientSecretPath: clientSecretPath,
|
||||
newAccessTokenNotificationChan: newAccessTokenNotificationChan,
|
||||
removeClientSecretOnRead: removeClientSecretOnRead,
|
||||
exitAfterAuth: exitAfterAuth,
|
||||
filePaths: options.FileDeposits,
|
||||
templates: options.Templates,
|
||||
|
||||
authConfigBytes: options.AuthConfigBytes,
|
||||
authStrategy: options.AuthStrategy,
|
||||
|
||||
newAccessTokenNotificationChan: options.NewAccessTokenNotificationChan,
|
||||
exitAfterAuth: options.ExitAfterAuth,
|
||||
|
||||
infisicalClient: infisicalSdk.NewInfisicalClient(infisicalSdk.Config{
|
||||
SiteUrl: config.INFISICAL_URL,
|
||||
UserAgent: api.USER_AGENT, // ? Should we perhaps use a different user agent for the Agent for better analytics?
|
||||
}),
|
||||
}
|
||||
|
||||
}
|
||||
@@ -483,52 +485,164 @@ func (tm *AgentManager) GetToken() string {
|
||||
return tm.accessToken
|
||||
}
|
||||
|
||||
func (tm *AgentManager) FetchUniversalAuthAccessToken() (credential infisicalSdk.MachineIdentityCredential, e error) {
|
||||
|
||||
var universalAuthConfig UniversalAuth
|
||||
if err := ParseAuthConfig(tm.authConfigBytes, &universalAuthConfig); err != nil {
|
||||
return infisicalSdk.MachineIdentityCredential{}, fmt.Errorf("unable to parse auth config due to error: %v", err)
|
||||
}
|
||||
|
||||
clientID, err := util.GetEnvVarOrFileContent(util.INFISICAL_UNIVERSAL_AUTH_CLIENT_ID_NAME, universalAuthConfig.ClientIDPath)
|
||||
if err != nil {
|
||||
return infisicalSdk.MachineIdentityCredential{}, fmt.Errorf("unable to get client id: %v", err)
|
||||
}
|
||||
|
||||
clientSecret, err := util.GetEnvVarOrFileContent("INFISICAL_UNIVERSAL_CLIENT_SECRET", universalAuthConfig.ClientSecretPath)
|
||||
if err != nil {
|
||||
if len(tm.cachedUniversalAuthClientSecret) == 0 {
|
||||
return infisicalSdk.MachineIdentityCredential{}, fmt.Errorf("unable to get client secret: %v", err)
|
||||
}
|
||||
clientSecret = tm.cachedUniversalAuthClientSecret
|
||||
}
|
||||
|
||||
tm.cachedUniversalAuthClientSecret = clientSecret
|
||||
if tm.removeUniversalAuthClientSecretOnRead {
|
||||
defer os.Remove(universalAuthConfig.ClientSecretPath)
|
||||
}
|
||||
|
||||
return tm.infisicalClient.Auth().UniversalAuthLogin(clientID, clientSecret)
|
||||
|
||||
}
|
||||
|
||||
func (tm *AgentManager) FetchKubernetesAuthAccessToken() (credential infisicalSdk.MachineIdentityCredential, err error) {
|
||||
|
||||
var kubernetesAuthConfig KubernetesAuth
|
||||
if err := ParseAuthConfig(tm.authConfigBytes, &kubernetesAuthConfig); err != nil {
|
||||
return infisicalSdk.MachineIdentityCredential{}, fmt.Errorf("unable to parse auth config due to error: %v", err)
|
||||
}
|
||||
|
||||
identityId, err := util.GetEnvVarOrFileContent(util.INFISICAL_MACHINE_IDENTITY_ID_NAME, kubernetesAuthConfig.IdentityID)
|
||||
if err != nil {
|
||||
return infisicalSdk.MachineIdentityCredential{}, fmt.Errorf("unable to get identity id: %v", err)
|
||||
}
|
||||
|
||||
serviceAccountTokenPath := os.Getenv(util.INFISICAL_KUBERNETES_SERVICE_ACCOUNT_TOKEN_NAME)
|
||||
if serviceAccountTokenPath == "" {
|
||||
serviceAccountTokenPath = kubernetesAuthConfig.ServiceAccountToken
|
||||
if serviceAccountTokenPath == "" {
|
||||
serviceAccountTokenPath = "/var/run/secrets/kubernetes.io/serviceaccount/token"
|
||||
}
|
||||
}
|
||||
|
||||
return tm.infisicalClient.Auth().KubernetesAuthLogin(identityId, serviceAccountTokenPath)
|
||||
|
||||
}
|
||||
|
||||
func (tm *AgentManager) FetchAzureAuthAccessToken() (credential infisicalSdk.MachineIdentityCredential, err error) {
|
||||
|
||||
var azureAuthConfig AzureAuth
|
||||
if err := ParseAuthConfig(tm.authConfigBytes, &azureAuthConfig); err != nil {
|
||||
return infisicalSdk.MachineIdentityCredential{}, fmt.Errorf("unable to parse auth config due to error: %v", err)
|
||||
}
|
||||
|
||||
identityId, err := util.GetEnvVarOrFileContent(util.INFISICAL_MACHINE_IDENTITY_ID_NAME, azureAuthConfig.IdentityID)
|
||||
if err != nil {
|
||||
return infisicalSdk.MachineIdentityCredential{}, fmt.Errorf("unable to get identity id: %v", err)
|
||||
}
|
||||
|
||||
return tm.infisicalClient.Auth().AzureAuthLogin(identityId)
|
||||
|
||||
}
|
||||
|
||||
func (tm *AgentManager) FetchGcpIdTokenAuthAccessToken() (credential infisicalSdk.MachineIdentityCredential, err error) {
|
||||
|
||||
var gcpIdTokenAuthConfig GcpIdTokenAuth
|
||||
if err := ParseAuthConfig(tm.authConfigBytes, &gcpIdTokenAuthConfig); err != nil {
|
||||
return infisicalSdk.MachineIdentityCredential{}, fmt.Errorf("unable to parse auth config due to error: %v", err)
|
||||
}
|
||||
|
||||
identityId, err := util.GetEnvVarOrFileContent(util.INFISICAL_MACHINE_IDENTITY_ID_NAME, gcpIdTokenAuthConfig.IdentityID)
|
||||
if err != nil {
|
||||
return infisicalSdk.MachineIdentityCredential{}, fmt.Errorf("unable to get identity id: %v", err)
|
||||
}
|
||||
|
||||
return tm.infisicalClient.Auth().GcpIdTokenAuthLogin(identityId)
|
||||
|
||||
}
|
||||
|
||||
func (tm *AgentManager) FetchGcpIamAuthAccessToken() (credential infisicalSdk.MachineIdentityCredential, err error) {
|
||||
|
||||
var gcpIamAuthConfig GcpIamAuth
|
||||
if err := ParseAuthConfig(tm.authConfigBytes, &gcpIamAuthConfig); err != nil {
|
||||
return infisicalSdk.MachineIdentityCredential{}, fmt.Errorf("unable to parse auth config due to error: %v", err)
|
||||
}
|
||||
|
||||
identityId, err := util.GetEnvVarOrFileContent(util.INFISICAL_MACHINE_IDENTITY_ID_NAME, gcpIamAuthConfig.IdentityID)
|
||||
if err != nil {
|
||||
return infisicalSdk.MachineIdentityCredential{}, fmt.Errorf("unable to get identity id: %v", err)
|
||||
}
|
||||
|
||||
serviceAccountKeyPath := os.Getenv(util.INFISICAL_GCP_IAM_SERVICE_ACCOUNT_KEY_FILE_PATH_NAME)
|
||||
if serviceAccountKeyPath == "" {
|
||||
// we don't need to read this file, because the service account key path is directly read inside the sdk
|
||||
serviceAccountKeyPath = gcpIamAuthConfig.ServiceAccountKey
|
||||
if serviceAccountKeyPath == "" {
|
||||
return infisicalSdk.MachineIdentityCredential{}, fmt.Errorf("gcp service account key path not found")
|
||||
}
|
||||
}
|
||||
|
||||
return tm.infisicalClient.Auth().GcpIamAuthLogin(identityId, serviceAccountKeyPath)
|
||||
|
||||
}
|
||||
|
||||
func (tm *AgentManager) FetchAwsIamAuthAccessToken() (credential infisicalSdk.MachineIdentityCredential, err error) {
|
||||
|
||||
var awsIamAuthConfig AwsIamAuth
|
||||
if err := ParseAuthConfig(tm.authConfigBytes, &awsIamAuthConfig); err != nil {
|
||||
return infisicalSdk.MachineIdentityCredential{}, fmt.Errorf("unable to parse auth config due to error: %v", err)
|
||||
}
|
||||
|
||||
identityId, err := util.GetEnvVarOrFileContent(util.INFISICAL_MACHINE_IDENTITY_ID_NAME, awsIamAuthConfig.IdentityID)
|
||||
|
||||
if err != nil {
|
||||
return infisicalSdk.MachineIdentityCredential{}, fmt.Errorf("unable to get identity id: %v", err)
|
||||
}
|
||||
|
||||
return tm.infisicalClient.Auth().AwsIamAuthLogin(identityId)
|
||||
|
||||
}
|
||||
|
||||
// Fetches a new access token using client credentials
|
||||
func (tm *AgentManager) FetchNewAccessToken() error {
|
||||
clientID := os.Getenv(util.INFISICAL_UNIVERSAL_AUTH_CLIENT_ID_NAME)
|
||||
if clientID == "" {
|
||||
clientIDAsByte, err := ReadFile(tm.clientIdPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to read client id from file path '%s' due to error: %v", tm.clientIdPath, err)
|
||||
}
|
||||
clientID = string(clientIDAsByte)
|
||||
|
||||
authStrategies := map[util.AuthStrategyType]func() (credential infisicalSdk.MachineIdentityCredential, e error){
|
||||
util.AuthStrategy.UNIVERSAL_AUTH: tm.FetchUniversalAuthAccessToken,
|
||||
util.AuthStrategy.KUBERNETES_AUTH: tm.FetchKubernetesAuthAccessToken,
|
||||
util.AuthStrategy.AZURE_AUTH: tm.FetchAzureAuthAccessToken,
|
||||
util.AuthStrategy.GCP_ID_TOKEN_AUTH: tm.FetchGcpIdTokenAuthAccessToken,
|
||||
util.AuthStrategy.GCP_IAM_AUTH: tm.FetchGcpIamAuthAccessToken,
|
||||
util.AuthStrategy.AWS_IAM_AUTH: tm.FetchAwsIamAuthAccessToken,
|
||||
}
|
||||
|
||||
clientSecret := os.Getenv("INFISICAL_UNIVERSAL_CLIENT_SECRET")
|
||||
if clientSecret == "" {
|
||||
clientSecretAsByte, err := ReadFile(tm.clientSecretPath)
|
||||
if err != nil {
|
||||
if len(tm.cachedClientSecret) == 0 {
|
||||
return fmt.Errorf("unable to read client secret from file and no cached client secret found: %v", err)
|
||||
} else {
|
||||
clientSecretAsByte = []byte(tm.cachedClientSecret)
|
||||
}
|
||||
}
|
||||
clientSecret = string(clientSecretAsByte)
|
||||
if _, ok := authStrategies[tm.authStrategy]; !ok {
|
||||
return fmt.Errorf("auth strategy %s not found", tm.authStrategy)
|
||||
}
|
||||
|
||||
// remove client secret after first read
|
||||
if tm.removeClientSecretOnRead {
|
||||
os.Remove(tm.clientSecretPath)
|
||||
}
|
||||
credential, err := authStrategies[tm.authStrategy]()
|
||||
|
||||
// save as cache in memory
|
||||
tm.cachedClientSecret = clientSecret
|
||||
|
||||
loginResponse, err := util.UniversalAuthLogin(clientID, clientSecret)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
accessTokenTTL := time.Duration(loginResponse.AccessTokenTTL * int(time.Second))
|
||||
accessTokenMaxTTL := time.Duration(loginResponse.AccessTokenMaxTTL * int(time.Second))
|
||||
accessTokenTTL := time.Duration(credential.ExpiresIn * int64(time.Second))
|
||||
accessTokenMaxTTL := time.Duration(credential.AccessTokenMaxTTL * int64(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.SetToken(loginResponse.AccessToken, accessTokenTTL, accessTokenMaxTTL)
|
||||
tm.SetToken(credential.AccessToken, accessTokenTTL, accessTokenMaxTTL)
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -541,7 +655,7 @@ func (tm *AgentManager) RefreshAccessToken() error {
|
||||
SetRetryWaitTime(5 * time.Second)
|
||||
|
||||
accessToken := tm.GetToken()
|
||||
response, err := api.CallUniversalAuthRefreshAccessToken(httpClient, api.UniversalAuthRefreshRequest{AccessToken: accessToken})
|
||||
response, err := api.CallMachineIdentityRefreshAccessToken(httpClient, api.UniversalAuthRefreshRequest{AccessToken: accessToken})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -578,6 +692,7 @@ func (tm *AgentManager) ManageTokenLifecycle() {
|
||||
continue
|
||||
}
|
||||
} 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...")
|
||||
err := tm.FetchNewAccessToken()
|
||||
if err != nil {
|
||||
@@ -588,6 +703,7 @@ func (tm *AgentManager) ManageTokenLifecycle() {
|
||||
continue
|
||||
}
|
||||
} 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...")
|
||||
err := tm.RefreshAccessToken()
|
||||
if err != nil {
|
||||
@@ -784,18 +900,33 @@ var agentCmd = &cobra.Command{
|
||||
return
|
||||
}
|
||||
|
||||
if agentConfig.Auth.Type != "universal-auth" {
|
||||
util.PrintErrorMessageAndExit("Only auth type of 'universal-auth' is supported at this time")
|
||||
}
|
||||
authMethodValid, authStrategy := util.IsAuthMethodValid(agentConfig.Auth.Type, false)
|
||||
|
||||
configUniversalAuthType := agentConfig.Auth.Config.(UniversalAuth)
|
||||
if !authMethodValid {
|
||||
util.PrintErrorMessageAndExit(fmt.Sprintf("The auth method '%s' is not supported.", agentConfig.Auth.Type))
|
||||
}
|
||||
|
||||
tokenRefreshNotifier := make(chan bool)
|
||||
sigChan := make(chan os.Signal, 1)
|
||||
signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
|
||||
|
||||
filePaths := agentConfig.Sinks
|
||||
tm := NewAgentManager(filePaths, agentConfig.Templates, configUniversalAuthType.ClientIDPath, configUniversalAuthType.ClientSecretPath, tokenRefreshNotifier, configUniversalAuthType.RemoveClientSecretOnRead, agentConfig.Infisical.ExitAfterAuth)
|
||||
|
||||
configBytes, err := yaml.Marshal(agentConfig.Auth.Config)
|
||||
if err != nil {
|
||||
log.Error().Msgf("unable to marshal auth config because %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
tm := NewAgentManager(NewAgentMangerOptions{
|
||||
FileDeposits: filePaths,
|
||||
Templates: agentConfig.Templates,
|
||||
AuthConfigBytes: configBytes,
|
||||
NewAccessTokenNotificationChan: tokenRefreshNotifier,
|
||||
ExitAfterAuth: agentConfig.Infisical.ExitAfterAuth,
|
||||
AuthStrategy: authStrategy,
|
||||
})
|
||||
|
||||
tm.dynamicSecretLeases = NewDynamicSecretLeaseManager(sigChan)
|
||||
|
||||
go tm.ManageTokenLifecycle()
|
||||
|
||||
@@ -55,6 +55,11 @@ var exportCmd = &cobra.Command{
|
||||
util.HandleError(err)
|
||||
}
|
||||
|
||||
token, err := util.GetInfisicalToken(cmd)
|
||||
if err != nil {
|
||||
util.HandleError(err, "Unable to parse flag")
|
||||
}
|
||||
|
||||
format, err := cmd.Flags().GetString("format")
|
||||
if err != nil {
|
||||
util.HandleError(err)
|
||||
@@ -70,11 +75,6 @@ var exportCmd = &cobra.Command{
|
||||
util.HandleError(err, "Unable to parse flag")
|
||||
}
|
||||
|
||||
token, err := util.GetInfisicalToken(cmd)
|
||||
if err != nil {
|
||||
util.HandleError(err, "Unable to parse flag")
|
||||
}
|
||||
|
||||
tagSlugs, err := cmd.Flags().GetString("tags")
|
||||
if err != nil {
|
||||
util.HandleError(err, "Unable to parse flag")
|
||||
@@ -169,9 +169,9 @@ func init() {
|
||||
exportCmd.Flags().StringP("format", "f", "dotenv", "Set the format of the output file (dotenv, json, csv)")
|
||||
exportCmd.Flags().Bool("secret-overriding", true, "Prioritizes personal secrets, if any, with the same name over shared secrets")
|
||||
exportCmd.Flags().Bool("include-imports", true, "Imported linked secrets")
|
||||
exportCmd.Flags().String("token", "", "Fetch secrets using the Infisical Token")
|
||||
exportCmd.Flags().String("token", "", "Fetch secrets using service token or machine identity access token")
|
||||
exportCmd.Flags().StringP("tags", "t", "", "filter secrets by tag slugs")
|
||||
exportCmd.Flags().String("projectId", "", "manually set the projectId to fetch secrets from")
|
||||
exportCmd.Flags().String("projectId", "", "manually set the projectId to export secrets from")
|
||||
exportCmd.Flags().String("path", "/", "get secrets within a folder path")
|
||||
exportCmd.Flags().String("template", "", "The path to the template file used to render secrets")
|
||||
}
|
||||
|
||||
+49
-16
@@ -1,6 +1,7 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/Infisical/infisical-merge/packages/models"
|
||||
@@ -71,10 +72,6 @@ var getCmd = &cobra.Command{
|
||||
var createCmd = &cobra.Command{
|
||||
Use: "create",
|
||||
Short: "Create a folder",
|
||||
PersistentPreRun: func(cmd *cobra.Command, args []string) {
|
||||
util.RequireLogin()
|
||||
util.RequireLocalWorkspaceFile()
|
||||
},
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
environmentName, _ := cmd.Flags().GetString("env")
|
||||
if !cmd.Flags().Changed("env") {
|
||||
@@ -84,6 +81,16 @@ var createCmd = &cobra.Command{
|
||||
}
|
||||
}
|
||||
|
||||
token, err := util.GetInfisicalToken(cmd)
|
||||
if err != nil {
|
||||
util.HandleError(err, "Unable to parse flag")
|
||||
}
|
||||
|
||||
projectId, err := cmd.Flags().GetString("projectId")
|
||||
if err != nil {
|
||||
util.HandleError(err, "Unable to parse flag")
|
||||
}
|
||||
|
||||
folderPath, err := cmd.Flags().GetString("path")
|
||||
if err != nil {
|
||||
util.HandleError(err, "Unable to parse flag")
|
||||
@@ -95,19 +102,31 @@ var createCmd = &cobra.Command{
|
||||
}
|
||||
|
||||
if folderName == "" {
|
||||
util.HandleError(fmt.Errorf("Invalid folder name"), "Folder name cannot be empty")
|
||||
util.HandleError(errors.New("invalid folder name, folder name cannot be empty"))
|
||||
}
|
||||
|
||||
workspaceFile, err := util.GetWorkSpaceFromFile()
|
||||
if err != nil {
|
||||
util.HandleError(err, "Unable to get workspace file")
|
||||
}
|
||||
|
||||
if projectId == "" {
|
||||
workspaceFile, err := util.GetWorkSpaceFromFile()
|
||||
if err != nil {
|
||||
util.HandleError(err, "Unable to get workspace file")
|
||||
}
|
||||
|
||||
projectId = workspaceFile.WorkspaceId
|
||||
}
|
||||
|
||||
params := models.CreateFolderParameters{
|
||||
FolderName: folderName,
|
||||
WorkspaceId: workspaceFile.WorkspaceId,
|
||||
Environment: environmentName,
|
||||
FolderPath: folderPath,
|
||||
WorkspaceId: projectId,
|
||||
}
|
||||
|
||||
if token != nil && (token.Type == util.SERVICE_TOKEN_IDENTIFIER || token.Type == util.UNIVERSAL_AUTH_TOKEN_IDENTIFIER) {
|
||||
params.InfisicalToken = token.Token
|
||||
}
|
||||
|
||||
_, err = util.CreateFolder(params)
|
||||
@@ -124,10 +143,6 @@ var createCmd = &cobra.Command{
|
||||
var deleteCmd = &cobra.Command{
|
||||
Use: "delete",
|
||||
Short: "Delete a folder",
|
||||
PersistentPreRun: func(cmd *cobra.Command, args []string) {
|
||||
util.RequireLogin()
|
||||
util.RequireLocalWorkspaceFile()
|
||||
},
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
|
||||
environmentName, _ := cmd.Flags().GetString("env")
|
||||
@@ -138,6 +153,16 @@ var deleteCmd = &cobra.Command{
|
||||
}
|
||||
}
|
||||
|
||||
token, err := util.GetInfisicalToken(cmd)
|
||||
if err != nil {
|
||||
util.HandleError(err, "Unable to parse flag")
|
||||
}
|
||||
|
||||
projectId, err := cmd.Flags().GetString("projectId")
|
||||
if err != nil {
|
||||
util.HandleError(err, "Unable to parse flag")
|
||||
}
|
||||
|
||||
folderPath, err := cmd.Flags().GetString("path")
|
||||
if err != nil {
|
||||
util.HandleError(err, "Unable to parse flag")
|
||||
@@ -149,21 +174,29 @@ var deleteCmd = &cobra.Command{
|
||||
}
|
||||
|
||||
if folderName == "" {
|
||||
util.HandleError(fmt.Errorf("Invalid folder name"), "Folder name cannot be empty")
|
||||
util.HandleError(errors.New("invalid folder name, folder name cannot be empty"))
|
||||
}
|
||||
|
||||
workspaceFile, err := util.GetWorkSpaceFromFile()
|
||||
if err != nil {
|
||||
util.HandleError(err, "Unable to get workspace file")
|
||||
if projectId == "" {
|
||||
workspaceFile, err := util.GetWorkSpaceFromFile()
|
||||
if err != nil {
|
||||
util.HandleError(err, "Unable to get workspace file")
|
||||
}
|
||||
|
||||
projectId = workspaceFile.WorkspaceId
|
||||
}
|
||||
|
||||
params := models.DeleteFolderParameters{
|
||||
FolderName: folderName,
|
||||
WorkspaceId: workspaceFile.WorkspaceId,
|
||||
WorkspaceId: projectId,
|
||||
Environment: environmentName,
|
||||
FolderPath: folderPath,
|
||||
}
|
||||
|
||||
if token != nil && (token.Type == util.SERVICE_TOKEN_IDENTIFIER || token.Type == util.UNIVERSAL_AUTH_TOKEN_IDENTIFIER) {
|
||||
params.InfisicalToken = token.Token
|
||||
}
|
||||
|
||||
_, err = util.DeleteFolder(params)
|
||||
if err != nil {
|
||||
util.HandleError(err, "Unable to delete folder")
|
||||
|
||||
+111
-33
@@ -34,6 +34,8 @@ import (
|
||||
"github.com/spf13/cobra"
|
||||
"golang.org/x/crypto/argon2"
|
||||
"golang.org/x/term"
|
||||
|
||||
infisicalSdk "github.com/infisical/go-sdk"
|
||||
)
|
||||
|
||||
type params struct {
|
||||
@@ -44,6 +46,86 @@ type params struct {
|
||||
keyLength uint32
|
||||
}
|
||||
|
||||
func handleUniversalAuthLogin(cmd *cobra.Command, infisicalClient infisicalSdk.InfisicalClientInterface) (credential infisicalSdk.MachineIdentityCredential, e error) {
|
||||
|
||||
clientId, err := util.GetCmdFlagOrEnv(cmd, "client-id", util.INFISICAL_UNIVERSAL_AUTH_CLIENT_ID_NAME)
|
||||
|
||||
if err != nil {
|
||||
return infisicalSdk.MachineIdentityCredential{}, err
|
||||
}
|
||||
|
||||
clientSecret, err := util.GetCmdFlagOrEnv(cmd, "client-secret", util.INFISICAL_UNIVERSAL_AUTH_CLIENT_SECRET_NAME)
|
||||
if err != nil {
|
||||
return infisicalSdk.MachineIdentityCredential{}, err
|
||||
}
|
||||
|
||||
return infisicalClient.Auth().UniversalAuthLogin(clientId, clientSecret)
|
||||
}
|
||||
|
||||
func handleKubernetesAuthLogin(cmd *cobra.Command, infisicalClient infisicalSdk.InfisicalClientInterface) (credential infisicalSdk.MachineIdentityCredential, e error) {
|
||||
|
||||
identityId, err := util.GetCmdFlagOrEnv(cmd, "machine-identity-id", util.INFISICAL_MACHINE_IDENTITY_ID_NAME)
|
||||
if err != nil {
|
||||
return infisicalSdk.MachineIdentityCredential{}, err
|
||||
}
|
||||
|
||||
serviceAccountTokenPath, err := util.GetCmdFlagOrEnv(cmd, "service-account-token-path", util.INFISICAL_KUBERNETES_SERVICE_ACCOUNT_TOKEN_NAME)
|
||||
if err != nil {
|
||||
return infisicalSdk.MachineIdentityCredential{}, err
|
||||
}
|
||||
|
||||
return infisicalClient.Auth().KubernetesAuthLogin(identityId, serviceAccountTokenPath)
|
||||
}
|
||||
|
||||
func handleAzureAuthLogin(cmd *cobra.Command, infisicalClient infisicalSdk.InfisicalClientInterface) (credential infisicalSdk.MachineIdentityCredential, e error) {
|
||||
|
||||
identityId, err := util.GetCmdFlagOrEnv(cmd, "machine-identity-id", util.INFISICAL_MACHINE_IDENTITY_ID_NAME)
|
||||
if err != nil {
|
||||
return infisicalSdk.MachineIdentityCredential{}, err
|
||||
}
|
||||
|
||||
return infisicalClient.Auth().AzureAuthLogin(identityId)
|
||||
}
|
||||
|
||||
func handleGcpIdTokenAuthLogin(cmd *cobra.Command, infisicalClient infisicalSdk.InfisicalClientInterface) (credential infisicalSdk.MachineIdentityCredential, e error) {
|
||||
|
||||
identityId, err := util.GetCmdFlagOrEnv(cmd, "machine-identity-id", util.INFISICAL_MACHINE_IDENTITY_ID_NAME)
|
||||
if err != nil {
|
||||
return infisicalSdk.MachineIdentityCredential{}, err
|
||||
}
|
||||
|
||||
return infisicalClient.Auth().GcpIdTokenAuthLogin(identityId)
|
||||
}
|
||||
|
||||
func handleGcpIamAuthLogin(cmd *cobra.Command, infisicalClient infisicalSdk.InfisicalClientInterface) (credential infisicalSdk.MachineIdentityCredential, e error) {
|
||||
|
||||
identityId, err := util.GetCmdFlagOrEnv(cmd, "machine-identity-id", util.INFISICAL_MACHINE_IDENTITY_ID_NAME)
|
||||
if err != nil {
|
||||
return infisicalSdk.MachineIdentityCredential{}, err
|
||||
}
|
||||
|
||||
serviceAccountKeyFilePath, err := util.GetCmdFlagOrEnv(cmd, "service-account-key-file-path", util.INFISICAL_GCP_IAM_SERVICE_ACCOUNT_KEY_FILE_PATH_NAME)
|
||||
if err != nil {
|
||||
return infisicalSdk.MachineIdentityCredential{}, err
|
||||
}
|
||||
|
||||
return infisicalClient.Auth().GcpIamAuthLogin(identityId, serviceAccountKeyFilePath)
|
||||
}
|
||||
|
||||
func handleAwsIamAuthLogin(cmd *cobra.Command, infisicalClient infisicalSdk.InfisicalClientInterface) (credential infisicalSdk.MachineIdentityCredential, e error) {
|
||||
|
||||
identityId, err := util.GetCmdFlagOrEnv(cmd, "machine-identity-id", util.INFISICAL_MACHINE_IDENTITY_ID_NAME)
|
||||
if err != nil {
|
||||
return infisicalSdk.MachineIdentityCredential{}, err
|
||||
}
|
||||
|
||||
return infisicalClient.Auth().AwsIamAuthLogin(identityId)
|
||||
}
|
||||
|
||||
func formatAuthMethod(authMethod string) string {
|
||||
return strings.ReplaceAll(authMethod, "-", " ")
|
||||
}
|
||||
|
||||
const ADD_USER = "Add a new account login"
|
||||
const REPLACE_USER = "Override current logged in user"
|
||||
const EXIT_USER_MENU = "Exit"
|
||||
@@ -56,6 +138,11 @@ var loginCmd = &cobra.Command{
|
||||
DisableFlagsInUseLine: true,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
|
||||
infisicalClient := infisicalSdk.NewInfisicalClient(infisicalSdk.Config{
|
||||
SiteUrl: config.INFISICAL_URL,
|
||||
UserAgent: api.USER_AGENT,
|
||||
})
|
||||
|
||||
loginMethod, err := cmd.Flags().GetString("method")
|
||||
if err != nil {
|
||||
util.HandleError(err)
|
||||
@@ -65,12 +152,13 @@ var loginCmd = &cobra.Command{
|
||||
util.HandleError(err)
|
||||
}
|
||||
|
||||
if loginMethod != "user" && loginMethod != "universal-auth" {
|
||||
util.PrintErrorMessageAndExit("Invalid login method. Please use either 'user' or 'universal-auth'")
|
||||
authMethodValid, strategy := util.IsAuthMethodValid(loginMethod, true)
|
||||
if !authMethodValid {
|
||||
util.PrintErrorMessageAndExit(fmt.Sprintf("Invalid login method: %s", loginMethod))
|
||||
}
|
||||
|
||||
// standalone user auth
|
||||
if loginMethod == "user" {
|
||||
|
||||
currentLoggedInUserDetails, err := util.GetCurrentLoggedInUserDetails()
|
||||
// if the key can't be found or there is an error getting current credentials from key ring, allow them to override
|
||||
if err != nil && (strings.Contains(err.Error(), "we couldn't find your logged in details")) {
|
||||
@@ -101,7 +189,7 @@ var loginCmd = &cobra.Command{
|
||||
//set domainQuery to false
|
||||
if !overrideDomain {
|
||||
domainQuery = false
|
||||
config.INFISICAL_URL = config.INFISICAL_URL_MANUAL_OVERRIDE
|
||||
config.INFISICAL_URL = util.AppendAPIEndpoint(config.INFISICAL_URL_MANUAL_OVERRIDE)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -133,7 +221,7 @@ var loginCmd = &cobra.Command{
|
||||
|
||||
err = util.StoreUserCredsInKeyRing(&userCredentialsToBeStored)
|
||||
if err != nil {
|
||||
log.Error().Msgf("Unable to store your credentials in system vault [%s]")
|
||||
log.Error().Msgf("Unable to store your credentials in system vault")
|
||||
log.Error().Msgf("\nTo trouble shoot further, read https://infisical.com/docs/cli/faq")
|
||||
log.Debug().Err(err)
|
||||
//return here
|
||||
@@ -160,47 +248,33 @@ var loginCmd = &cobra.Command{
|
||||
fmt.Println("- Learn to inject secrets into your application at https://infisical.com/docs/cli/usage")
|
||||
fmt.Println("- Stuck? Join our slack for quick support https://infisical.com/slack")
|
||||
Telemetry.CaptureEvent("cli-command:login", posthog.NewProperties().Set("infisical-backend", config.INFISICAL_URL).Set("version", util.CLI_VERSION))
|
||||
} else if loginMethod == "universal-auth" {
|
||||
} else {
|
||||
|
||||
clientId, err := cmd.Flags().GetString("client-id")
|
||||
if err != nil {
|
||||
util.HandleError(err)
|
||||
authStrategies := map[util.AuthStrategyType]func(cmd *cobra.Command, infisicalClient infisicalSdk.InfisicalClientInterface) (credential infisicalSdk.MachineIdentityCredential, e error){
|
||||
util.AuthStrategy.UNIVERSAL_AUTH: handleUniversalAuthLogin,
|
||||
util.AuthStrategy.KUBERNETES_AUTH: handleKubernetesAuthLogin,
|
||||
util.AuthStrategy.AZURE_AUTH: handleAzureAuthLogin,
|
||||
util.AuthStrategy.GCP_ID_TOKEN_AUTH: handleGcpIdTokenAuthLogin,
|
||||
util.AuthStrategy.GCP_IAM_AUTH: handleGcpIamAuthLogin,
|
||||
util.AuthStrategy.AWS_IAM_AUTH: handleAwsIamAuthLogin,
|
||||
}
|
||||
|
||||
clientSecret, err := cmd.Flags().GetString("client-secret")
|
||||
if err != nil {
|
||||
util.HandleError(err)
|
||||
}
|
||||
|
||||
if clientId == "" {
|
||||
clientId = os.Getenv(util.INFISICAL_UNIVERSAL_AUTH_CLIENT_ID_NAME)
|
||||
if clientId == "" {
|
||||
util.PrintErrorMessageAndExit("Please provide client-id")
|
||||
}
|
||||
}
|
||||
if clientSecret == "" {
|
||||
clientSecret = os.Getenv(util.INFISICAL_UNIVERSAL_AUTH_CLIENT_SECRET_NAME)
|
||||
if clientSecret == "" {
|
||||
util.PrintErrorMessageAndExit("Please provide client-secret")
|
||||
}
|
||||
}
|
||||
|
||||
res, err := util.UniversalAuthLogin(clientId, clientSecret)
|
||||
credential, err := authStrategies[strategy](cmd, infisicalClient)
|
||||
|
||||
if err != nil {
|
||||
util.HandleError(err)
|
||||
util.HandleError(fmt.Errorf("unable to authenticate with %s [err=%v]", formatAuthMethod(loginMethod), err))
|
||||
}
|
||||
|
||||
if plainOutput {
|
||||
fmt.Println(res.AccessToken)
|
||||
fmt.Println(credential.AccessToken)
|
||||
return
|
||||
}
|
||||
|
||||
boldGreen := color.New(color.FgGreen).Add(color.Bold)
|
||||
boldPlain := color.New(color.Bold)
|
||||
time.Sleep(time.Second * 1)
|
||||
boldGreen.Printf(">>>> Successfully authenticated with Universal Auth!\n\n")
|
||||
boldPlain.Printf("Universal Auth Access Token:\n%v", res.AccessToken)
|
||||
boldGreen.Printf(">>>> Successfully authenticated with %s!\n\n", formatAuthMethod(loginMethod))
|
||||
boldPlain.Printf("Access Token:\n%v", credential.AccessToken)
|
||||
|
||||
plainBold := color.New(color.Bold)
|
||||
plainBold.Println("\n\nYou can use this access token to authenticate through other commands in the CLI.")
|
||||
@@ -376,9 +450,12 @@ func init() {
|
||||
rootCmd.AddCommand(loginCmd)
|
||||
loginCmd.Flags().BoolP("interactive", "i", false, "login via the command line")
|
||||
loginCmd.Flags().String("method", "user", "login method [user, universal-auth]")
|
||||
loginCmd.Flags().String("client-id", "", "client id for universal auth")
|
||||
loginCmd.Flags().Bool("plain", false, "only output the token without any formatting")
|
||||
loginCmd.Flags().String("client-id", "", "client id for universal auth")
|
||||
loginCmd.Flags().String("client-secret", "", "client secret for universal auth")
|
||||
loginCmd.Flags().String("machine-identity-id", "", "machine identity id for kubernetes, azure, gcp-id-token, gcp-iam, and aws-iam auth methods")
|
||||
loginCmd.Flags().String("service-account-token-path", "", "service account token path for kubernetes auth")
|
||||
loginCmd.Flags().String("service-account-key-file-path", "", "service account key file path for GCP IAM auth")
|
||||
}
|
||||
|
||||
func DomainOverridePrompt() (bool, error) {
|
||||
@@ -539,6 +616,7 @@ func getFreshUserCredentials(email string, password string) (*api.GetLoginOneV2R
|
||||
loginTwoResponseResult, err := api.CallLogin2V2(httpClient, api.GetLoginTwoV2Request{
|
||||
Email: email,
|
||||
ClientProof: hex.EncodeToString(srpM1),
|
||||
Password: password,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
|
||||
@@ -43,6 +43,7 @@ func init() {
|
||||
rootCmd.PersistentFlags().Bool("silent", false, "Disable output of tip/info messages. Useful when running in scripts or CI/CD pipelines.")
|
||||
rootCmd.PersistentPreRun = func(cmd *cobra.Command, args []string) {
|
||||
silent, err := cmd.Flags().GetBool("silent")
|
||||
config.INFISICAL_URL = util.AppendAPIEndpoint(config.INFISICAL_URL)
|
||||
if err != nil {
|
||||
util.HandleError(err)
|
||||
}
|
||||
|
||||
@@ -237,8 +237,8 @@ func filterReservedEnvVars(env map[string]models.SingleEnvironmentVariable) {
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(runCmd)
|
||||
runCmd.Flags().String("token", "", "Fetch secrets using the Infisical Token")
|
||||
runCmd.Flags().String("projectId", "", "manually set the projectId to fetch folders from for machine identity")
|
||||
runCmd.Flags().String("token", "", "Fetch secrets using service token or machine identity access token")
|
||||
runCmd.Flags().String("projectId", "", "manually set the project ID to fetch secrets from when using machine identity based auth")
|
||||
runCmd.Flags().StringP("env", "e", "dev", "Set the environment (dev, prod, etc.) from which your secrets should be pulled from")
|
||||
runCmd.Flags().Bool("expand", true, "Parse shell parameter expansions in your secrets")
|
||||
runCmd.Flags().Bool("include-imports", true, "Import linked secrets ")
|
||||
|
||||
+78
-215
@@ -4,23 +4,17 @@ Copyright (c) 2023 Infisical Inc.
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"os"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strings"
|
||||
"unicode"
|
||||
|
||||
"github.com/Infisical/infisical-merge/packages/api"
|
||||
"github.com/Infisical/infisical-merge/packages/crypto"
|
||||
"github.com/Infisical/infisical-merge/packages/models"
|
||||
"github.com/Infisical/infisical-merge/packages/util"
|
||||
"github.com/Infisical/infisical-merge/packages/visualize"
|
||||
"github.com/go-resty/resty/v2"
|
||||
"github.com/posthog/posthog-go"
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
@@ -171,187 +165,38 @@ var secretsSetCmd = &cobra.Command{
|
||||
}
|
||||
}
|
||||
|
||||
token, err := util.GetInfisicalToken(cmd)
|
||||
if err != nil {
|
||||
util.HandleError(err, "Unable to parse flag")
|
||||
}
|
||||
|
||||
projectId, err := cmd.Flags().GetString("projectId")
|
||||
if err != nil {
|
||||
util.HandleError(err, "Unable to parse flag")
|
||||
}
|
||||
|
||||
secretsPath, err := cmd.Flags().GetString("path")
|
||||
if err != nil {
|
||||
util.HandleError(err, "Unable to parse flag")
|
||||
}
|
||||
|
||||
workspaceFile, err := util.GetWorkSpaceFromFile()
|
||||
secretType, err := cmd.Flags().GetString("type")
|
||||
if err != nil || (secretType != util.SECRET_TYPE_SHARED && secretType != util.SECRET_TYPE_PERSONAL) {
|
||||
util.HandleError(err, "Unable to parse secret type")
|
||||
}
|
||||
|
||||
var secretOperations []models.SecretSetOperation
|
||||
if token != nil && (token.Type == util.SERVICE_TOKEN_IDENTIFIER || token.Type == util.UNIVERSAL_AUTH_TOKEN_IDENTIFIER) {
|
||||
secretOperations, err = util.SetRawSecrets(args, secretType, environmentName, secretsPath, projectId, token)
|
||||
} else {
|
||||
util.RequireLogin()
|
||||
util.RequireLocalWorkspaceFile()
|
||||
|
||||
secretOperations, err = util.SetEncryptedSecrets(args, secretType, environmentName, secretsPath)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
util.HandleError(err, "Unable to get your local config details")
|
||||
}
|
||||
|
||||
loggedInUserDetails, err := util.GetCurrentLoggedInUserDetails()
|
||||
if err != nil {
|
||||
util.HandleError(err, "Unable to authenticate")
|
||||
}
|
||||
|
||||
if loggedInUserDetails.LoginExpired {
|
||||
util.PrintErrorMessageAndExit("Your login session has expired, please run [infisical login] and try again")
|
||||
}
|
||||
|
||||
httpClient := resty.New().
|
||||
SetAuthToken(loggedInUserDetails.UserCredentials.JTWToken).
|
||||
SetHeader("Accept", "application/json")
|
||||
|
||||
request := api.GetEncryptedWorkspaceKeyRequest{
|
||||
WorkspaceId: workspaceFile.WorkspaceId,
|
||||
}
|
||||
|
||||
workspaceKeyResponse, err := api.CallGetEncryptedWorkspaceKey(httpClient, request)
|
||||
if err != nil {
|
||||
util.HandleError(err, "unable to get your encrypted workspace key")
|
||||
}
|
||||
|
||||
encryptedWorkspaceKey, _ := base64.StdEncoding.DecodeString(workspaceKeyResponse.EncryptedKey)
|
||||
encryptedWorkspaceKeySenderPublicKey, _ := base64.StdEncoding.DecodeString(workspaceKeyResponse.Sender.PublicKey)
|
||||
encryptedWorkspaceKeyNonce, _ := base64.StdEncoding.DecodeString(workspaceKeyResponse.Nonce)
|
||||
currentUsersPrivateKey, _ := base64.StdEncoding.DecodeString(loggedInUserDetails.UserCredentials.PrivateKey)
|
||||
|
||||
if len(currentUsersPrivateKey) == 0 || len(encryptedWorkspaceKeySenderPublicKey) == 0 {
|
||||
log.Debug().Msgf("Missing credentials for generating plainTextEncryptionKey: [currentUsersPrivateKey=%s] [encryptedWorkspaceKeySenderPublicKey=%s]", currentUsersPrivateKey, encryptedWorkspaceKeySenderPublicKey)
|
||||
util.PrintErrorMessageAndExit("Some required user credentials are missing to generate your [plainTextEncryptionKey]. Please run [infisical login] then try again")
|
||||
}
|
||||
|
||||
// decrypt workspace key
|
||||
plainTextEncryptionKey := crypto.DecryptAsymmetric(encryptedWorkspaceKey, encryptedWorkspaceKeyNonce, encryptedWorkspaceKeySenderPublicKey, currentUsersPrivateKey)
|
||||
|
||||
infisicalTokenEnv := os.Getenv(util.INFISICAL_TOKEN_NAME)
|
||||
|
||||
// pull current secrets
|
||||
secrets, err := util.GetAllEnvironmentVariables(models.GetAllSecretsParameters{Environment: environmentName, SecretsPath: secretsPath, InfisicalToken: infisicalTokenEnv}, "")
|
||||
if err != nil {
|
||||
util.HandleError(err, "unable to retrieve secrets")
|
||||
}
|
||||
|
||||
type SecretSetOperation struct {
|
||||
SecretKey string
|
||||
SecretValue string
|
||||
SecretOperation string
|
||||
}
|
||||
|
||||
secretsToCreate := []api.Secret{}
|
||||
secretsToModify := []api.Secret{}
|
||||
secretOperations := []SecretSetOperation{}
|
||||
|
||||
secretByKey := getSecretsByKeys(secrets)
|
||||
|
||||
for _, arg := range args {
|
||||
splitKeyValueFromArg := strings.SplitN(arg, "=", 2)
|
||||
if splitKeyValueFromArg[0] == "" || splitKeyValueFromArg[1] == "" {
|
||||
util.PrintErrorMessageAndExit("ensure that each secret has a none empty key and value. Modify the input and try again")
|
||||
}
|
||||
|
||||
if unicode.IsNumber(rune(splitKeyValueFromArg[0][0])) {
|
||||
util.PrintErrorMessageAndExit("keys of secrets cannot start with a number. Modify the key name(s) and try again")
|
||||
}
|
||||
|
||||
// Key and value from argument
|
||||
key := splitKeyValueFromArg[0]
|
||||
value := splitKeyValueFromArg[1]
|
||||
|
||||
hashedKey := fmt.Sprintf("%x", sha256.Sum256([]byte(key)))
|
||||
encryptedKey, err := crypto.EncryptSymmetric([]byte(key), []byte(plainTextEncryptionKey))
|
||||
if err != nil {
|
||||
util.HandleError(err, "unable to encrypt your secrets")
|
||||
}
|
||||
|
||||
hashedValue := fmt.Sprintf("%x", sha256.Sum256([]byte(value)))
|
||||
encryptedValue, err := crypto.EncryptSymmetric([]byte(value), []byte(plainTextEncryptionKey))
|
||||
if err != nil {
|
||||
util.HandleError(err, "unable to encrypt your secrets")
|
||||
}
|
||||
|
||||
if existingSecret, ok := secretByKey[key]; ok {
|
||||
// case: secret exists in project so it needs to be modified
|
||||
encryptedSecretDetails := api.Secret{
|
||||
ID: existingSecret.ID,
|
||||
SecretValueCiphertext: base64.StdEncoding.EncodeToString(encryptedValue.CipherText),
|
||||
SecretValueIV: base64.StdEncoding.EncodeToString(encryptedValue.Nonce),
|
||||
SecretValueTag: base64.StdEncoding.EncodeToString(encryptedValue.AuthTag),
|
||||
SecretValueHash: hashedValue,
|
||||
PlainTextKey: key,
|
||||
Type: existingSecret.Type,
|
||||
}
|
||||
|
||||
// Only add to modifications if the value is different
|
||||
if existingSecret.Value != value {
|
||||
secretsToModify = append(secretsToModify, encryptedSecretDetails)
|
||||
secretOperations = append(secretOperations, SecretSetOperation{
|
||||
SecretKey: key,
|
||||
SecretValue: value,
|
||||
SecretOperation: "SECRET VALUE MODIFIED",
|
||||
})
|
||||
} else {
|
||||
// Current value is same as exisitng so no change
|
||||
secretOperations = append(secretOperations, SecretSetOperation{
|
||||
SecretKey: key,
|
||||
SecretValue: value,
|
||||
SecretOperation: "SECRET VALUE UNCHANGED",
|
||||
})
|
||||
}
|
||||
|
||||
} else {
|
||||
// case: secret doesn't exist in project so it needs to be created
|
||||
encryptedSecretDetails := api.Secret{
|
||||
SecretKeyCiphertext: base64.StdEncoding.EncodeToString(encryptedKey.CipherText),
|
||||
SecretKeyIV: base64.StdEncoding.EncodeToString(encryptedKey.Nonce),
|
||||
SecretKeyTag: base64.StdEncoding.EncodeToString(encryptedKey.AuthTag),
|
||||
SecretKeyHash: hashedKey,
|
||||
SecretValueCiphertext: base64.StdEncoding.EncodeToString(encryptedValue.CipherText),
|
||||
SecretValueIV: base64.StdEncoding.EncodeToString(encryptedValue.Nonce),
|
||||
SecretValueTag: base64.StdEncoding.EncodeToString(encryptedValue.AuthTag),
|
||||
SecretValueHash: hashedValue,
|
||||
Type: util.SECRET_TYPE_SHARED,
|
||||
PlainTextKey: key,
|
||||
}
|
||||
secretsToCreate = append(secretsToCreate, encryptedSecretDetails)
|
||||
secretOperations = append(secretOperations, SecretSetOperation{
|
||||
SecretKey: key,
|
||||
SecretValue: value,
|
||||
SecretOperation: "SECRET CREATED",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
for _, secret := range secretsToCreate {
|
||||
createSecretRequest := api.CreateSecretV3Request{
|
||||
WorkspaceID: workspaceFile.WorkspaceId,
|
||||
Environment: environmentName,
|
||||
SecretName: secret.PlainTextKey,
|
||||
SecretKeyCiphertext: secret.SecretKeyCiphertext,
|
||||
SecretKeyIV: secret.SecretKeyIV,
|
||||
SecretKeyTag: secret.SecretKeyTag,
|
||||
SecretValueCiphertext: secret.SecretValueCiphertext,
|
||||
SecretValueIV: secret.SecretValueIV,
|
||||
SecretValueTag: secret.SecretValueTag,
|
||||
Type: secret.Type,
|
||||
SecretPath: secretsPath,
|
||||
}
|
||||
|
||||
err = api.CallCreateSecretsV3(httpClient, createSecretRequest)
|
||||
if err != nil {
|
||||
util.HandleError(err, "Unable to process new secret creations")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
for _, secret := range secretsToModify {
|
||||
updateSecretRequest := api.UpdateSecretByNameV3Request{
|
||||
WorkspaceID: workspaceFile.WorkspaceId,
|
||||
Environment: environmentName,
|
||||
SecretValueCiphertext: secret.SecretValueCiphertext,
|
||||
SecretValueIV: secret.SecretValueIV,
|
||||
SecretValueTag: secret.SecretValueTag,
|
||||
Type: secret.Type,
|
||||
SecretPath: secretsPath,
|
||||
}
|
||||
|
||||
err = api.CallUpdateSecretsV3(httpClient, updateSecretRequest, secret.PlainTextKey)
|
||||
if err != nil {
|
||||
util.HandleError(err, "Unable to process secret update request")
|
||||
return
|
||||
}
|
||||
util.HandleError(err, "Unable to set secrets")
|
||||
}
|
||||
|
||||
// Print secret operations
|
||||
@@ -382,6 +227,16 @@ var secretsDeleteCmd = &cobra.Command{
|
||||
}
|
||||
}
|
||||
|
||||
token, err := util.GetInfisicalToken(cmd)
|
||||
if err != nil {
|
||||
util.HandleError(err, "Unable to parse flag")
|
||||
}
|
||||
|
||||
projectId, err := cmd.Flags().GetString("projectId")
|
||||
if err != nil {
|
||||
util.HandleError(err, "Unable to parse flag")
|
||||
}
|
||||
|
||||
secretsPath, err := cmd.Flags().GetString("path")
|
||||
if err != nil {
|
||||
util.HandleError(err, "Unable to parse flag")
|
||||
@@ -392,33 +247,44 @@ var secretsDeleteCmd = &cobra.Command{
|
||||
util.HandleError(err, "Unable to parse flag")
|
||||
}
|
||||
|
||||
loggedInUserDetails, err := util.GetCurrentLoggedInUserDetails()
|
||||
if err != nil {
|
||||
util.HandleError(err, "Unable to authenticate")
|
||||
httpClient := resty.New().
|
||||
SetHeader("Accept", "application/json")
|
||||
|
||||
if projectId == "" {
|
||||
workspaceFile, err := util.GetWorkSpaceFromFile()
|
||||
if err != nil {
|
||||
util.HandleError(err, "Unable to get local project details")
|
||||
}
|
||||
projectId = workspaceFile.WorkspaceId
|
||||
}
|
||||
|
||||
if loggedInUserDetails.LoginExpired {
|
||||
util.PrintErrorMessageAndExit("Your login session has expired, please run [infisical login] and try again")
|
||||
}
|
||||
if token != nil && (token.Type == util.SERVICE_TOKEN_IDENTIFIER || token.Type == util.UNIVERSAL_AUTH_TOKEN_IDENTIFIER) {
|
||||
httpClient.SetAuthToken(token.Token)
|
||||
} else {
|
||||
util.RequireLogin()
|
||||
util.RequireLocalWorkspaceFile()
|
||||
|
||||
workspaceFile, err := util.GetWorkSpaceFromFile()
|
||||
if err != nil {
|
||||
util.HandleError(err, "Unable to get local project details")
|
||||
loggedInUserDetails, err := util.GetCurrentLoggedInUserDetails()
|
||||
if err != nil {
|
||||
util.HandleError(err, "Unable to authenticate")
|
||||
}
|
||||
|
||||
if loggedInUserDetails.LoginExpired {
|
||||
util.PrintErrorMessageAndExit("Your login session has expired, please run [infisical login] and try again")
|
||||
}
|
||||
|
||||
httpClient.SetAuthToken(loggedInUserDetails.UserCredentials.JTWToken)
|
||||
}
|
||||
|
||||
for _, secretName := range args {
|
||||
request := api.DeleteSecretV3Request{
|
||||
WorkspaceId: workspaceFile.WorkspaceId,
|
||||
WorkspaceId: projectId,
|
||||
Environment: environmentName,
|
||||
SecretName: secretName,
|
||||
Type: secretType,
|
||||
SecretPath: secretsPath,
|
||||
}
|
||||
|
||||
httpClient := resty.New().
|
||||
SetAuthToken(loggedInUserDetails.UserCredentials.JTWToken).
|
||||
SetHeader("Accept", "application/json")
|
||||
|
||||
err = api.CallDeleteSecretsV3(httpClient, request)
|
||||
if err != nil {
|
||||
util.HandleError(err, "Unable to complete your delete request")
|
||||
@@ -787,13 +653,13 @@ func getSecretsByKeys(secrets []models.SingleEnvironmentVariable) map[string]mod
|
||||
}
|
||||
|
||||
func init() {
|
||||
secretsGenerateExampleEnvCmd.Flags().String("token", "", "Fetch secrets using the Infisical Token")
|
||||
secretsGenerateExampleEnvCmd.Flags().String("projectId", "", "manually set the projectId to fetch folders from for machine identity")
|
||||
secretsGenerateExampleEnvCmd.Flags().String("token", "", "Fetch secrets using service token or machine identity access token")
|
||||
secretsGenerateExampleEnvCmd.Flags().String("projectId", "", "manually set the projectId when using machine identity based auth")
|
||||
secretsGenerateExampleEnvCmd.Flags().String("path", "/", "Fetch secrets from within a folder path")
|
||||
secretsCmd.AddCommand(secretsGenerateExampleEnvCmd)
|
||||
|
||||
secretsGetCmd.Flags().String("token", "", "Fetch secrets using the Infisical Token")
|
||||
secretsGetCmd.Flags().String("projectId", "", "manually set the projectId to fetch folders from for machine identity")
|
||||
secretsGetCmd.Flags().String("token", "", "Fetch secrets using service token or machine identity access token")
|
||||
secretsGetCmd.Flags().String("projectId", "", "manually set the project ID to fetch secrets from when using machine identity based auth")
|
||||
secretsGetCmd.Flags().String("path", "/", "get secrets within a folder path")
|
||||
secretsGetCmd.Flags().Bool("plain", false, "print values without formatting, one per line")
|
||||
secretsGetCmd.Flags().Bool("raw-value", false, "deprecated. Returns only the value of secret, only works with one secret. Use --plain instead")
|
||||
@@ -803,40 +669,37 @@ func init() {
|
||||
secretsCmd.AddCommand(secretsGetCmd)
|
||||
secretsCmd.Flags().Bool("secret-overriding", true, "Prioritizes personal secrets, if any, with the same name over shared secrets")
|
||||
secretsCmd.AddCommand(secretsSetCmd)
|
||||
secretsSetCmd.Flags().String("token", "", "Fetch secrets using service token or machine identity access token")
|
||||
secretsSetCmd.Flags().String("projectId", "", "manually set the project ID to for setting secrets when using machine identity based auth")
|
||||
secretsSetCmd.Flags().String("path", "/", "set secrets within a folder path")
|
||||
|
||||
// Only supports logged in users (JWT auth)
|
||||
secretsSetCmd.PersistentPreRun = func(cmd *cobra.Command, args []string) {
|
||||
util.RequireLogin()
|
||||
util.RequireLocalWorkspaceFile()
|
||||
}
|
||||
secretsSetCmd.Flags().String("type", util.SECRET_TYPE_SHARED, "the type of secret to create: personal or shared")
|
||||
|
||||
secretsDeleteCmd.Flags().String("type", "personal", "the type of secret to delete: personal or shared (default: personal)")
|
||||
secretsDeleteCmd.Flags().String("token", "", "Fetch secrets using service token or machine identity access token")
|
||||
secretsDeleteCmd.Flags().String("projectId", "", "manually set the projectId to delete secrets from when using machine identity based auth")
|
||||
secretsDeleteCmd.Flags().String("path", "/", "get secrets within a folder path")
|
||||
secretsCmd.AddCommand(secretsDeleteCmd)
|
||||
|
||||
// Only supports logged in users (JWT auth)
|
||||
secretsDeleteCmd.PersistentPreRun = func(cmd *cobra.Command, args []string) {
|
||||
util.RequireLogin()
|
||||
util.RequireLocalWorkspaceFile()
|
||||
}
|
||||
|
||||
// *** Folders sub command ***
|
||||
folderCmd.PersistentFlags().String("env", "dev", "Used to select the environment name on which actions should be taken on")
|
||||
|
||||
// Add getCmd, createCmd and deleteCmd flags here
|
||||
getCmd.Flags().StringP("path", "p", "/", "The path from where folders should be fetched from")
|
||||
getCmd.Flags().String("token", "", "Fetch folders using the infisical token")
|
||||
getCmd.Flags().String("projectId", "", "manually set the projectId to fetch folders from for machine identity")
|
||||
getCmd.Flags().String("token", "", "Fetch secrets using service token or machine identity access token")
|
||||
getCmd.Flags().String("projectId", "", "manually set the projectId to fetch folders from when using machine identity based auth")
|
||||
folderCmd.AddCommand(getCmd)
|
||||
|
||||
// Add createCmd flags here
|
||||
createCmd.Flags().StringP("path", "p", "/", "Path to where the folder should be created")
|
||||
createCmd.Flags().StringP("name", "n", "", "Name of the folder to be created in selected `--path`")
|
||||
createCmd.Flags().String("token", "", "Fetch secrets using service token or machine identity access token")
|
||||
createCmd.Flags().String("projectId", "", "manually set the project ID for creating folders in when using machine identity based auth")
|
||||
folderCmd.AddCommand(createCmd)
|
||||
|
||||
// Add deleteCmd flags here
|
||||
deleteCmd.Flags().StringP("path", "p", "/", "Path to the folder to be deleted")
|
||||
deleteCmd.Flags().String("token", "", "Fetch secrets using service token or machine identity access token")
|
||||
deleteCmd.Flags().String("projectId", "", "manually set the projectId to delete folders when using machine identity based auth")
|
||||
deleteCmd.Flags().StringP("name", "n", "", "Name of the folder to be deleted within selected `--path`")
|
||||
folderCmd.AddCommand(deleteCmd)
|
||||
|
||||
@@ -844,8 +707,8 @@ func init() {
|
||||
|
||||
// ** End of folders sub command
|
||||
|
||||
secretsCmd.Flags().String("token", "", "Fetch secrets using the Infisical Token")
|
||||
secretsCmd.Flags().String("projectId", "", "manually set the projectId to fetch folders from for machine identity")
|
||||
secretsCmd.Flags().String("token", "", "Fetch secrets using service token or machine identity access token")
|
||||
secretsCmd.Flags().String("projectId", "", "manually set the projectId to fetch secrets when using machine identity based auth")
|
||||
secretsCmd.PersistentFlags().String("env", "dev", "Used to select the environment name on which actions should be taken on")
|
||||
secretsCmd.Flags().Bool("expand", true, "Parse shell parameter expansions in your secrets, and process your referenced secrets")
|
||||
secretsCmd.Flags().Bool("include-imports", true, "Imported linked secrets ")
|
||||
|
||||
@@ -39,7 +39,7 @@ var tokenRenewCmd = &cobra.Command{
|
||||
util.PrintErrorMessageAndExit("You are trying to renew a service token. You can only renew universal auth access tokens.")
|
||||
}
|
||||
|
||||
renewedAccessToken, err := util.RenewUniversalAuthAccessToken(token)
|
||||
renewedAccessToken, err := util.RenewMachineIdentityAccessToken(token)
|
||||
|
||||
if err != nil {
|
||||
util.HandleError(err, "Unable to renew token")
|
||||
|
||||
@@ -237,7 +237,7 @@ func NewDomainPrompt() (string, error) {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return domain, nil
|
||||
return util.AppendAPIEndpoint(domain), nil
|
||||
}
|
||||
|
||||
func LoggedInUsersPrompt(profiles []string) (string, error) {
|
||||
|
||||
Reference in New Issue
Block a user