Merge pull request #1983 from Infisical/daniel/login-all-auth-methods

feat(cli): Support for all authentication methods in `infisical login` command
This commit is contained in:
Daniel Hougaard
2024-06-19 19:44:42 +02:00
committed by GitHub
5 changed files with 379 additions and 50 deletions

View File

@@ -900,7 +900,7 @@ var agentCmd = &cobra.Command{
return
}
authMethodValid, authStrategy := util.IsAuthMethodValid(agentConfig.Auth.Type)
authMethodValid, authStrategy := util.IsAuthMethodValid(agentConfig.Auth.Type, false)
if !authMethodValid {
util.PrintErrorMessageAndExit(fmt.Sprintf("The auth method '%s' is not supported.", agentConfig.Auth.Type))

View File

@@ -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")) {
@@ -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) {

View File

@@ -27,9 +27,9 @@ var AVAILABLE_AUTH_STRATEGIES = []AuthStrategyType{
AuthStrategy.AWS_IAM_AUTH,
}
func IsAuthMethodValid(authMethod string) (isValid bool, strategy AuthStrategyType) {
func IsAuthMethodValid(authMethod string, allowUserAuth bool) (isValid bool, strategy AuthStrategyType) {
if authMethod == "user" {
if authMethod == "user" && allowUserAuth {
return true, ""
}

View File

@@ -273,3 +273,17 @@ func GetEnvVarOrFileContent(envName string, filePath string) (string, error) {
return fileContent, nil
}
func GetCmdFlagOrEnv(cmd *cobra.Command, flag, envName string) (string, error) {
value, flagsErr := cmd.Flags().GetString(flag)
if flagsErr != nil {
return "", flagsErr
}
if value == "" {
value = os.Getenv(envName)
}
if value == "" {
return "", fmt.Errorf("please provide %s flag", flag)
}
return value, nil
}

View File

@@ -7,32 +7,38 @@ description: "Login into Infisical from the CLI"
infisical login
```
## Description
### Description
The CLI uses authentication to verify your identity. When you enter the correct email and password for your account, a token is generated and saved in your system Keyring to allow you to make future interactions with the CLI.
To change where the login credentials are stored, visit the [vaults command](./vault).
If you have added multiple users, you can switch between the users by using the [user command](./user).
<Info>
When you authenticate with **any other method than `user`**, an access token will be printed to the console upon successful login. This token can be used to authenticate with the Infisical API and the CLI by passing it in the `--token` flag when applicable.
Use flag `--plain` along with `--silent` to print only the token in plain text when using a machine identity auth method.
</Info>
### Flags
The login command supports a number of flags that you can use for different authentication methods. Below is a list of all the flags that can be used with the login command.
<AccordionGroup>
<Accordion title="--method">
```bash
infisical login --method=<auth-method> # Optional, will default to 'user'.
```
#### Valid values for the `method` flag are:
- `user`: Login using email and password.
- `user`: Login using email and password. (default)
- `universal-auth`: Login using a universal auth client ID and client secret.
<Info>
When `method` is set to `universal-auth`, the `client-id` and `client-secret` flags are required. Optionally you can set the `INFISICAL_UNIVERSAL_AUTH_CLIENT_ID` and `INFISICAL_UNIVERSAL_AUTH_CLIENT_SECRET` environment variables instead of using the flags.
When you authenticate with universal auth, an access token will be printed to the console upon successful login. This token can be used to authenticate with the Infisical API and the CLI by passing it in the `--token` flag when applicable.
Use flag `--plain` along with `--silent` to print only the token in plain text when using the `universal-auth` method.
</Info>
- `kubernetes`: Login using a Kubernetes native auth.
- `azure`: Login using an Azure native auth.
- `gcp-id-token`: Login using a GCP ID token native auth.
- `gcp-iam`: Login using a GCP IAM.
- `aws-iam`: Login using an AWS IAM native auth.
</Accordion>
<Accordion title="--client-id">
@@ -41,7 +47,7 @@ If you have added multiple users, you can switch between the users by using the
```
#### Description
The client ID of the universal auth client. This is required if the `--method` flag is set to `universal-auth`.
The client ID of the universal auth machine identity. This is required if the `--method` flag is set to `universal-auth`.
<Tip>
The `client-id` flag can be substituted with the `INFISICAL_UNIVERSAL_AUTH_CLIENT_ID` environment variable.
@@ -52,13 +58,245 @@ If you have added multiple users, you can switch between the users by using the
infisical login --client-secret=<client-secret> # Optional, required if --method=universal-auth.
```
#### Description
The client secret of the universal auth client. This is required if the `--method` flag is set to `universal-auth`.
The client secret of the universal auth machine identity. This is required if the `--method` flag is set to `universal-auth`.
<Tip>
The `client-secret` flag can be substituted with the `INFISICAL_UNIVERSAL_AUTH_CLIENT_SECRET` environment variable.
</Tip>
</Accordion>
<Accordion title="--machine-identity-id">
```bash
infisical login --machine-identity-id=<your-machine-identity-id> # Optional, required if --method=kubernetes, azure, gcp-id-token, gcp-iam, or aws-iam.
```
#### Description
The ID of the machine identity. This is required if the `--method` flag is set to `kubernetes`, `azure`, `gcp-id-token`, `gcp-iam`, or `aws-iam`.
<Tip>
The `machine-identity-id` flag can be substituted with the `INFISICAL_MACHINE_IDENTITY_ID` environment variable.
</Tip>
</Accordion>
<Accordion title="--service-account-token-path">
```bash
infisical login --service-account-token-path=<service-account-token-path> # Optional Will default to '/var/run/secrets/kubernetes.io/serviceaccount/token'.
```
#### Description
The path to the Kubernetes service account token to use for authentication.
This is optional and will default to `/var/run/secrets/kubernetes.io/serviceaccount/token`.
<Tip>
The `service-account-token-path` flag can be substituted with the `INFISICAL_KUBERNETES_SERVICE_ACCOUNT_TOKEN_PATH` environment variable.
</Tip>
</Accordion>
<Accordion title="--service-account-key-file-path">
```bash
infisical login --service-account-key-file-path=<gcp-service-account-key-file-path> # Optional, but required if --method=gcp-iam.
```
#### Description
The path to your GCP service account key file. This is required if the `--method` flag is set to `gcp-iam`.
<Tip>
The `service-account-key-path` flag can be substituted with the `INFISICAL_GCP_IAM_SERVICE_ACCOUNT_KEY_FILE_PATH` environment variable.
</Tip>
</Accordion>
</AccordionGroup>
### Authentication Methods
The Infisical CLI supports multiple authentication methods. Below are the available authentication methods, with their respective flags.
<AccordionGroup>
<Accordion title="Universal Auth">
The Universal Auth method is a simple and secure way to authenticate with Infisical. It requires a client ID and a client secret to authenticate with Infisical.
<ParamField query="Flags">
<Expandable title="properties">
<ParamField query="client-id" type="string" required>
Your machine identity client ID.
</ParamField>
<ParamField query="client-secret" type="string" required>
Your machine identity client secret.
</ParamField>
</Expandable>
</ParamField>
<Steps>
<Step title="Create a universal auth machine identity">
To create a universal auth machine identity, follow the step by step guide outlined [here](/documentation/platform/identities/universal-auth).
</Step>
<Step title="Obtain an access token">
Run the `login` command with the following flags to obtain an access token:
```bash
infisical login --method=universal-auth --client-id=<client-id> --client-secret=<client-secret>
```
</Step>
</Steps>
</Accordion>
<Accordion title="Native Kubernetes">
The Native Kubernetes method is used to authenticate with Infisical when running in a Kubernetes environment. It requires a service account token to authenticate with Infisical.
<ParamField query="Flags">
<Expandable title="properties">
<ParamField query="machine-identity-id" type="string" required>
Your machine identity ID.
</ParamField>
<ParamField query="service-account-token-path" type="string" optional>
Path to the Kubernetes service account token to use. Default: `/var/run/secrets/kubernetes.io/serviceaccount/token`.
</ParamField>
</Expandable>
</ParamField>
<Steps>
<Step title="Create a Kubernetes machine identity">
To create a Kubernetes machine identity, follow the step by step guide outlined [here](/documentation/platform/identities/kubernetes-auth).
</Step>
<Step title="Obtain access an token">
Run the `login` command with the following flags to obtain an access token:
```bash
# --service-account-token-path is optional, and will default to '/var/run/secrets/kubernetes.io/serviceaccount/token' if not provided.
infisical login --method=kubernetes --machine-identity-id=<machine-identity-id> --service-account-token-path=<service-account-token-path>
```
</Step>
</Steps>
</Accordion>
<Accordion title="Native Azure">
The Native Azure method is used to authenticate with Infisical when running in an Azure environment.
<ParamField query="Flags">
<Expandable title="properties">
<ParamField query="machine-identity-id" type="string" required>
Your machine identity ID.
</ParamField>
</Expandable>
</ParamField>
<Steps>
<Step title="Create an Azure machine identity">
To create an Azure machine identity, follow the step by step guide outlined [here](/documentation/platform/identities/azure-auth).
</Step>
<Step title="Obtain an access token">
Run the `login` command with the following flags to obtain an access token:
```bash
infisical login --method=azure --machine-identity-id=<machine-identity-id>
```
</Step>
</Steps>
</Accordion>
<Accordion title="Native GCP ID Token">
The Native GCP ID Token method is used to authenticate with Infisical when running in a GCP environment.
<ParamField query="Flags">
<Expandable title="properties">
<ParamField query="machine-identity-id" type="string" required>
Your machine identity ID.
</ParamField>
</Expandable>
</ParamField>
<Steps>
<Step title="Create a GCP machine identity">
To create a GCP machine identity, follow the step by step guide outlined [here](/documentation/platform/identities/gcp-auth).
</Step>
<Step title="Obtain an access token">
Run the `login` command with the following flags to obtain an access token:
```bash
infisical login --method=gcp-id-token --machine-identity-id=<machine-identity-id>
```
</Step>
</Steps>
</Accordion>
<Accordion title="GCP IAM">
The GCP IAM method is used to authenticate with Infisical with a GCP service account key.
<ParamField query="Flags">
<Expandable title="properties">
<ParamField query="machine-identity-id" type="string" required>
Your machine identity ID.
</ParamField>
<ParamField query="service-account-key-file-path" type="string" required>
Path to your GCP service account key file _(Must be in JSON format!)_
</ParamField>
</Expandable>
</ParamField>
<Steps>
<Step title="Create a GCP machine identity">
To create a GCP machine identity, follow the step by step guide outlined [here](/documentation/platform/identities/gcp-auth).
</Step>
<Step title="Obtain an access token">
Run the `login` command with the following flags to obtain an access token:
```bash
infisical login --method=gcp-iam --machine-identity-id=<machine-identity-id> --service-account-key-file-path=<service-account-key-file-path>
```
</Step>
</Steps>
</Accordion>
<Accordion title="Native AWS IAM">
The AWS IAM method is used to authenticate with Infisical with an AWS IAM role while running in an AWS environment like EC2, Lambda, etc.
<ParamField query="Flags">
<Expandable title="properties">
<ParamField query="machine-identity-id" type="string" required>
Your machine identity ID.
</ParamField>
</Expandable>
</ParamField>
<Steps>
<Step title="Create an AWS machine identity">
To create an AWS machine identity, follow the step by step guide outlined [here](/documentation/platform/identities/aws-auth).
</Step>
<Step title="Obtain an access token">
Run the `login` command with the following flags to obtain an access token:
```bash
infisical login --method=aws-iam --machine-identity-id=<machine-identity-id>
```
</Step>
</Steps>
</Accordion>
</AccordionGroup>
### Machine Identity Authentication Quick Start
In this example we'll be using the `universal-auth` method to login to obtain an Infisical access token, which we will then use to fetch secrets with.
<Steps>
<Step title="Obtain an access token">
```bash
export INFISICAL_TOKEN=$(infisical login --method=universal-auth --client-id=<client-id> --client-secret=<client-secret> --silent --plain) # silent and plain is important to ensure only the token itself is printed, so we can easily set it as an environment variable.
```
Now that we've set the `INFISICAL_TOKEN` environment variable, we can use the CLI to interact with Infisical. The CLI will automatically check for the presence of the `INFISICAL_TOKEN` environment variable and use it for authentication.
Alternatively, if you would rather use the `--token` flag to pass the token directly, you can do so by running the following command:
```bash
infisical [command] --token=<your-access-token> # The token output from the login command.
```
</Step>
<Step title="Fetch all secrets from an evironment">
```bash
infisical secrets --projectId=<your-project-id --env=dev --recursive
```
This command will fetch all secrets from the `dev` environment in your project, including all secrets in subfolders.
<Info>
The `--recursive`, and `--env` flag is optional and will fetch all secrets in subfolders. The default environment is `dev` if no `--env` flag is provided.
</Info>
</Step>
</Steps>
And that's it! Now you're ready to start using the Infisical CLI to interact with your secrets, with the use of Machine Identities.