diff --git a/packages/cmd/export.go b/packages/cmd/export.go index a23dd430..982e8aae 100644 --- a/packages/cmd/export.go +++ b/packages/cmd/export.go @@ -57,6 +57,12 @@ var exportCmd = &cobra.Command{ util.HandleError(err) } + if resolved, err := util.ResolveProjectSlug(cmd); err != nil { + util.HandleError(err) + } else if resolved != "" { + projectId = resolved + } + token, err := util.GetInfisicalToken(cmd) if err != nil { util.HandleError(err, "Unable to parse flag") @@ -272,6 +278,7 @@ func init() { 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 export secrets from") + exportCmd.Flags().String("project-slug", "", "use project slug instead of project ID") exportCmd.Flags().String("path", "/", "get secrets within a folder path") exportCmd.Flags().String("template", "", "The path to the template file used to render secrets") exportCmd.Flags().StringP("output-file", "o", "", "The path to write the output file to. Can be a full file path, directory, or filename. If not specified, output will be printed to stdout") diff --git a/packages/cmd/folder.go b/packages/cmd/folder.go index bd114759..7a59e67f 100644 --- a/packages/cmd/folder.go +++ b/packages/cmd/folder.go @@ -38,6 +38,12 @@ var getCmd = &cobra.Command{ util.HandleError(err, "Unable to parse flag") } + if resolved, err := util.ResolveProjectSlug(cmd); err != nil { + util.HandleError(err) + } else if resolved != "" { + projectId = resolved + } + token, err := util.GetInfisicalToken(cmd) if err != nil { util.HandleError(err, "Unable to parse flag") @@ -115,6 +121,12 @@ var createCmd = &cobra.Command{ util.HandleError(err, "Unable to parse flag") } + if resolved, err := util.ResolveProjectSlug(cmd); err != nil { + util.HandleError(err) + } else if resolved != "" { + projectId = resolved + } + folderPath, err := cmd.Flags().GetString("path") if err != nil { util.HandleError(err, "Unable to parse flag") @@ -207,6 +219,12 @@ var deleteCmd = &cobra.Command{ util.HandleError(err, "Unable to parse flag") } + if resolved, err := util.ResolveProjectSlug(cmd); err != nil { + util.HandleError(err) + } else if resolved != "" { + projectId = resolved + } + folderPath, err := cmd.Flags().GetString("path") if err != nil { util.HandleError(err, "Unable to parse flag") diff --git a/packages/cmd/run.go b/packages/cmd/run.go index fecbccb1..4e3d7351 100644 --- a/packages/cmd/run.go +++ b/packages/cmd/run.go @@ -82,6 +82,12 @@ var runCmd = &cobra.Command{ util.HandleError(err, "Unable to parse flag") } + if resolved, err := util.ResolveProjectSlug(cmd); err != nil { + util.HandleError(err) + } else if resolved != "" { + projectId = resolved + } + command, err := cmd.Flags().GetString("command") if err != nil { util.HandleError(err, "Unable to parse flag") @@ -211,6 +217,7 @@ func init() { RootCmd.AddCommand(runCmd) 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().String("project-slug", "", "use project slug instead of project ID") 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 ") diff --git a/packages/cmd/secrets.go b/packages/cmd/secrets.go index 904db0d8..f11ee6e8 100644 --- a/packages/cmd/secrets.go +++ b/packages/cmd/secrets.go @@ -43,6 +43,12 @@ var secretsCmd = &cobra.Command{ util.HandleError(err, "Unable to parse flag") } + if resolved, err := util.ResolveProjectSlug(cmd); err != nil { + util.HandleError(err) + } else if resolved != "" { + projectId = resolved + } + secretsPath, err := cmd.Flags().GetString("path") if err != nil { util.HandleError(err, "Unable to parse flag") @@ -196,6 +202,12 @@ var secretsSetCmd = &cobra.Command{ util.HandleError(err, "Unable to parse flag") } + if resolved, err := util.ResolveProjectSlug(cmd); err != nil { + util.HandleError(err) + } else if resolved != "" { + projectId = resolved + } + if token == nil && projectId == "" { _, err := util.GetWorkSpaceFromFile() if err != nil { @@ -346,6 +358,12 @@ var secretsDeleteCmd = &cobra.Command{ util.HandleError(err, "Unable to parse flag") } + if resolved, err := util.ResolveProjectSlug(cmd); err != nil { + util.HandleError(err) + } else if resolved != "" { + projectId = resolved + } + secretsPath, err := cmd.Flags().GetString("path") if err != nil { util.HandleError(err, "Unable to parse flag") @@ -459,6 +477,12 @@ func getSecretsByNames(cmd *cobra.Command, args []string) { util.HandleError(err, "Unable to parse flag") } + if resolved, err := util.ResolveProjectSlug(cmd); err != nil { + util.HandleError(err) + } else if resolved != "" { + projectId = resolved + } + secretsPath, err := cmd.Flags().GetString("path") if err != nil { util.HandleError(err, "Unable to parse path flag") @@ -602,6 +626,12 @@ func generateExampleEnv(cmd *cobra.Command, args []string) { util.HandleError(err, "Unable to parse flag") } + if resolved, err := util.ResolveProjectSlug(cmd); err != nil { + util.HandleError(err) + } else if resolved != "" { + projectId = resolved + } + tagSlugs, err := cmd.Flags().GetString("tags") if err != nil { util.HandleError(err, "Unable to parse flag") @@ -813,11 +843,13 @@ func init() { // not doing this one 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("project-slug", "", "use project slug instead of project ID") secretsGenerateExampleEnvCmd.Flags().String("path", "/", "Fetch secrets from within a folder path") secretsCmd.AddCommand(secretsGenerateExampleEnvCmd) 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("project-slug", "", "use project slug instead of project ID") 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") @@ -832,6 +864,7 @@ func init() { 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("project-slug", "", "use project slug instead of project ID") secretsSetCmd.Flags().String("path", "/", "set secrets within a folder path") secretsSetCmd.Flags().String("type", util.SECRET_TYPE_SHARED, "the type of secret to create: personal or shared") secretsSetCmd.Flags().String("file", "", "Load secrets from the specified file. File format: .env or YAML (comments: # or //). This option is mutually exclusive with command-line secrets arguments.") @@ -840,6 +873,7 @@ func init() { 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("project-slug", "", "use project slug instead of project ID") secretsDeleteCmd.Flags().String("path", "/", "get secrets within a folder path") util.AddOutputFlagsToCmd(secretsDeleteCmd, "The output to format the secrets in.") secretsCmd.AddCommand(secretsDeleteCmd) @@ -851,6 +885,7 @@ func init() { getCmd.Flags().StringP("path", "p", "/", "The path from where folders should be fetched from") 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") + getCmd.Flags().String("project-slug", "", "use project slug instead of project ID") util.AddOutputFlagsToCmd(getCmd, "The output to format the folders in.") folderCmd.AddCommand(getCmd) @@ -859,6 +894,7 @@ func init() { 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") + createCmd.Flags().String("project-slug", "", "use project slug instead of project ID") util.AddOutputFlagsToCmd(createCmd, "The output to format the folders in.") folderCmd.AddCommand(createCmd) @@ -866,6 +902,7 @@ func init() { 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().String("project-slug", "", "use project slug instead of project ID") deleteCmd.Flags().StringP("name", "n", "", "Name of the folder to be deleted within selected `--path`") util.AddOutputFlagsToCmd(deleteCmd, "The output to format the folders in.") folderCmd.AddCommand(deleteCmd) @@ -876,6 +913,7 @@ func init() { 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.Flags().String("project-slug", "", "use project slug instead of project ID") 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 ") diff --git a/packages/cmd/ssh.go b/packages/cmd/ssh.go index 8a7c40c6..0255af58 100644 --- a/packages/cmd/ssh.go +++ b/packages/cmd/ssh.go @@ -888,8 +888,15 @@ func sshAddHost(cmd *cobra.Command, args []string) { if err != nil { util.HandleError(err, "Unable to parse --projectId flag") } + + if resolved, err := util.ResolveProjectSlug(cmd); err != nil { + util.HandleError(err) + } else if resolved != "" { + projectId = resolved + } + if projectId == "" { - util.PrintErrorMessageAndExit("You must provide --projectId") + util.PrintErrorMessageAndExit("You must provide --projectId or --project-slug") } hostname, err := cmd.Flags().GetString("hostname") @@ -1128,6 +1135,7 @@ func init() { sshAddHostCmd.Flags().String("token", "", "Use a machine identity access token") sshAddHostCmd.Flags().String("projectId", "", "Project ID the host belongs to (required)") + sshAddHostCmd.Flags().String("project-slug", "", "use project slug instead of project ID") sshAddHostCmd.Flags().String("hostname", "", "Hostname of the SSH host (required)") sshAddHostCmd.Flags().String("alias", "", "Alias for the SSH host") sshAddHostCmd.Flags().Bool("write-user-ca-to-file", false, "Write User CA public key to /etc/ssh/infisical_user_ca.pub") diff --git a/packages/cmd/tokens.go b/packages/cmd/tokens.go index 28b54ba0..2375dc78 100644 --- a/packages/cmd/tokens.go +++ b/packages/cmd/tokens.go @@ -58,6 +58,12 @@ var tokensCreateCmd = &cobra.Command{ util.HandleError(err, "Unable to parse flag") } + if resolved, err := util.ResolveProjectSlug(cmd); err != nil { + util.HandleError(err) + } else if resolved != "" { + workspaceId = resolved + } + if workspaceId == "" { configFile, err := util.GetWorkSpaceFromFile() if err != nil { @@ -168,6 +174,7 @@ var tokensCreateCmd = &cobra.Command{ func init() { tokensCreateCmd.Flags().String("projectId", "", "The project ID you'd like to create the service token for. Default: will use linked Infisical project in .infisical.json") + tokensCreateCmd.Flags().String("project-slug", "", "use project slug instead of project ID") tokensCreateCmd.Flags().StringSliceP("scope", "s", []string{}, "Environment and secret path. Example format: :") tokensCreateCmd.Flags().StringP("name", "n", "Service token generated via CLI", "Service token name") tokensCreateCmd.Flags().StringSliceP("access-level", "a", []string{}, "The type of access the service token should have. Can be 'read' and or 'write'") diff --git a/packages/util/helper.go b/packages/util/helper.go index cdaef77c..3b4c0014 100644 --- a/packages/util/helper.go +++ b/packages/util/helper.go @@ -323,6 +323,61 @@ func GetInfisicalToken(cmd *cobra.Command) (token *models.TokenDetails, err erro } +// ResolveProjectSlug reads the --project-slug flag. If set, it resolves the +// slug to a project ID via the API using whatever auth is available (machine +// token or user session). Returns ("", nil) when --project-slug is absent. +func ResolveProjectSlug(cmd *cobra.Command) (string, error) { + projectSlug, err := cmd.Flags().GetString("project-slug") + if err != nil { + return "", fmt.Errorf("unable to read --project-slug flag: %w", err) + } + if projectSlug == "" { + return "", nil + } + + projectId, _ := cmd.Flags().GetString("projectId") + if projectId != "" { + return "", fmt.Errorf("cannot specify both --projectId and --project-slug; use one or the other") + } + + var authToken string + token, tokenErr := GetInfisicalToken(cmd) + if tokenErr != nil { + // If the error is because the command doesn't define --token flag, + // fall through to user auth instead of failing + if cmd.Flags().Lookup("token") == nil { + token = nil + } else { + return "", fmt.Errorf("unable to retrieve auth token for slug resolution: %w", tokenErr) + } + } + if token != nil { + authToken = token.Token + } else { + RequireLogin() + loggedInDetails, err := GetCurrentLoggedInUserDetails(false) + if err != nil { + return "", fmt.Errorf("unable to get login details for slug resolution: %w", err) + } + if loggedInDetails.LoginExpired { + loggedInDetails = EstablishUserLoginSession() + } + authToken = loggedInDetails.UserCredentials.JTWToken + } + + httpClient, err := GetRestyClientWithCustomHeaders() + if err != nil { + return "", fmt.Errorf("unable to create HTTP client: %w", err) + } + httpClient.SetAuthToken(authToken) + + project, err := api.CallGetProjectBySlug(httpClient, projectSlug) + if err != nil { + return "", fmt.Errorf("unable to resolve project slug %q: %w", projectSlug, err) + } + return project.ID, nil +} + func UniversalAuthLogin(clientId string, clientSecret string) (api.UniversalAuthLoginResponse, error) { httpClient, err := GetRestyClientWithCustomHeaders() if err != nil {