From 1ae62133871e3b7864928da478f4289d0f93a5bd Mon Sep 17 00:00:00 2001 From: = Date: Fri, 15 Nov 2024 20:32:37 +0530 Subject: [PATCH 1/4] feat: completed dynamic secret support in cli --- .../dynamic-secret-lease-service.ts | 4 +- cli/go.mod | 2 +- cli/go.sum | 4 +- cli/packages/api/api.go | 19 + cli/packages/api/model.go | 10 + cli/packages/cmd/dynamic_secret.go | 570 ++++++++++++++++++ .../visualize/dynamic_secret_leases.go | 38 ++ cli/packages/visualize/visualize.go | 46 ++ 8 files changed, 688 insertions(+), 5 deletions(-) create mode 100644 cli/packages/cmd/dynamic_secret.go create mode 100644 cli/packages/visualize/dynamic_secret_leases.go diff --git a/backend/src/ee/services/dynamic-secret-lease/dynamic-secret-lease-service.ts b/backend/src/ee/services/dynamic-secret-lease/dynamic-secret-lease-service.ts index 38d7d1abdb..82d1604eb6 100644 --- a/backend/src/ee/services/dynamic-secret-lease/dynamic-secret-lease-service.ts +++ b/backend/src/ee/services/dynamic-secret-lease/dynamic-secret-lease-service.ts @@ -112,7 +112,7 @@ export const dynamicSecretLeaseServiceFactory = ({ }) ) as object; - const selectedTTL = ttl ?? dynamicSecretCfg.defaultTTL; + const selectedTTL = ttl || dynamicSecretCfg.defaultTTL; const { maxTTL } = dynamicSecretCfg; const expireAt = new Date(new Date().getTime() + ms(selectedTTL)); if (maxTTL) { @@ -187,7 +187,7 @@ export const dynamicSecretLeaseServiceFactory = ({ }) ) as object; - const selectedTTL = ttl ?? dynamicSecretCfg.defaultTTL; + const selectedTTL = ttl || dynamicSecretCfg.defaultTTL; const { maxTTL } = dynamicSecretCfg; const expireAt = new Date(dynamicSecretLease.expireAt.getTime() + ms(selectedTTL)); if (maxTTL) { diff --git a/cli/go.mod b/cli/go.mod index 36277ac374..b55a49c63b 100644 --- a/cli/go.mod +++ b/cli/go.mod @@ -10,7 +10,7 @@ require ( github.com/fatih/semgroup v1.2.0 github.com/gitleaks/go-gitdiff v0.8.0 github.com/h2non/filetype v1.1.3 - github.com/infisical/go-sdk v0.3.8 + github.com/infisical/go-sdk v0.4.3 github.com/mattn/go-isatty v0.0.20 github.com/muesli/ansi v0.0.0-20221106050444-61f0cd9a192a github.com/muesli/mango-cobra v1.2.0 diff --git a/cli/go.sum b/cli/go.sum index 733a7b93de..537a146cb8 100644 --- a/cli/go.sum +++ b/cli/go.sum @@ -265,8 +265,8 @@ github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1: github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/inconshreveable/mousetrap v1.0.1 h1:U3uMjPSQEBMNp1lFxmllqCPM6P5u/Xq7Pgzkat/bFNc= github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= -github.com/infisical/go-sdk v0.3.8 h1:0dGOhF3cwt0q5QzpnUs4lxwBiEza+DQYOyvEn7AfrM0= -github.com/infisical/go-sdk v0.3.8/go.mod h1:HHW7DgUqoolyQIUw/9HdpkZ3bDLwWyZ0HEtYiVaDKQw= +github.com/infisical/go-sdk v0.4.3 h1:O5ZJ2eCBAZDE9PIAfBPq9Utb2CgQKrhmj9R0oFTRu4U= +github.com/infisical/go-sdk v0.4.3/go.mod h1:6fWzAwTPIoKU49mQ2Oxu+aFnJu9n7k2JcNrZjzhHM2M= github.com/jedib0t/go-pretty v4.3.0+incompatible h1:CGs8AVhEKg/n9YbUenWmNStRW2PHJzaeDodcfvRAbIo= github.com/jedib0t/go-pretty v4.3.0+incompatible/go.mod h1:XemHduiw8R651AF9Pt4FwCTKeG3oo7hrHJAoznj9nag= github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= diff --git a/cli/packages/api/api.go b/cli/packages/api/api.go index 35767cd3f1..3d050cd973 100644 --- a/cli/packages/api/api.go +++ b/cli/packages/api/api.go @@ -205,6 +205,25 @@ func CallGetAllWorkSpacesUserBelongsTo(httpClient *resty.Client) (GetWorkSpacesR return workSpacesResponse, nil } +func CallGetProjectById(httpClient *resty.Client, id string) (Project, error) { + var projectResponse GetProjectByIdResponse + response, err := httpClient. + R(). + SetResult(&projectResponse). + SetHeader("User-Agent", USER_AGENT). + Get(fmt.Sprintf("%v/v1/workspace/%s", config.INFISICAL_URL, id)) + + if err != nil { + return Project{}, err + } + + if response.IsError() { + return Project{}, fmt.Errorf("CallGetProjectById: Unsuccessful response: [response=%v]", response) + } + + return projectResponse.Project, nil +} + func CallIsAuthenticated(httpClient *resty.Client) bool { var workSpacesResponse GetWorkSpacesResponse response, err := httpClient. diff --git a/cli/packages/api/model.go b/cli/packages/api/model.go index adf7a814d3..0ccd7af3a3 100644 --- a/cli/packages/api/model.go +++ b/cli/packages/api/model.go @@ -128,6 +128,10 @@ type GetWorkSpacesResponse struct { } `json:"workspaces"` } +type GetProjectByIdResponse struct { + Project Project `json:"workspace"` +} + type GetOrganizationsResponse struct { Organizations []struct { ID string `json:"id"` @@ -162,6 +166,12 @@ type Secret struct { PlainTextKey string `json:"plainTextKey"` } +type Project struct { + ID string `json:"id"` + Name string `json:"name"` + Slug string `json:"slug"` +} + type RawSecret struct { SecretKey string `json:"secretKey,omitempty"` SecretValue string `json:"secretValue,omitempty"` diff --git a/cli/packages/cmd/dynamic_secret.go b/cli/packages/cmd/dynamic_secret.go new file mode 100644 index 0000000000..befc356328 --- /dev/null +++ b/cli/packages/cmd/dynamic_secret.go @@ -0,0 +1,570 @@ +/* +Copyright (c) 2023 Infisical Inc. +*/ +package cmd + +import ( + "context" + "fmt" + + "github.com/Infisical/infisical-merge/packages/api" + "github.com/Infisical/infisical-merge/packages/config" + "github.com/Infisical/infisical-merge/packages/visualize" + + // "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/spf13/cobra" + + infisicalSdk "github.com/infisical/go-sdk" + infisicalSdkModels "github.com/infisical/go-sdk/packages/models" +) + +var dynamicSecretCmd = &cobra.Command{ + Example: `infisical dynamic-secrets`, + Short: "Used to list dynamic secrets", + Use: "dynamic-secrets", + DisableFlagsInUseLine: true, + Args: cobra.NoArgs, + Run: getDynamicSecretList, +} + +func getDynamicSecretList(cmd *cobra.Command, args []string) { + environmentName, _ := cmd.Flags().GetString("env") + if !cmd.Flags().Changed("env") { + environmentFromWorkspace := util.GetEnvFromWorkspaceFile() + if environmentFromWorkspace != "" { + environmentName = environmentFromWorkspace + } + } + + 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 path flag") + } + + var infisicalToken string + httpClient := resty.New() + + if projectId == "" { + workspaceFile, err := util.GetWorkSpaceFromFile() + if err != nil { + util.HandleError(err, "Unable to get local project details") + } + projectId = workspaceFile.WorkspaceId + } + + if token != nil && (token.Type == util.SERVICE_TOKEN_IDENTIFIER || token.Type == util.UNIVERSAL_AUTH_TOKEN_IDENTIFIER) { + infisicalToken = token.Token + } else { + util.RequireLogin() + util.RequireLocalWorkspaceFile() + + 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") + } + infisicalToken = loggedInUserDetails.UserCredentials.JTWToken + } + + httpClient.SetAuthToken(infisicalToken) + + infisicalClient := infisicalSdk.NewInfisicalClient(context.Background(), infisicalSdk.Config{ + SiteUrl: config.INFISICAL_URL, + UserAgent: api.USER_AGENT, + AutoTokenRefresh: false, + }) + infisicalClient.Auth().SetAccessToken(infisicalToken) + + projectDetails, err := api.CallGetProjectById(httpClient, projectId) + if err != nil { + util.HandleError(err, "To fetch project details") + } + + dynamicSecretRootCredentials, err := infisicalClient.DynamicSecrets().List(infisicalSdk.ListDynamicSecretsRootCredentialsOptions{ + ProjectSlug: projectDetails.Slug, + SecretPath: secretsPath, + EnvironmentSlug: environmentName, + }) + + if err != nil { + util.HandleError(err, "To fetch dynamic secret root credentials details") + } + + visualize.PrintAllDynamicRootCredentials(dynamicSecretRootCredentials) + Telemetry.CaptureEvent("cli-command:dynamic-secrets", posthog.NewProperties().Set("count", len(dynamicSecretRootCredentials)).Set("version", util.CLI_VERSION)) +} + +var dynamicSecretLeaseCmd = &cobra.Command{ + Example: `lease`, + Short: "Manage leases for dynamic secrets", + Use: "lease", + DisableFlagsInUseLine: true, +} + +var dynamicSecretLeaseCreateCmd = &cobra.Command{ + Example: `lease create "`, + Short: "Used to lease dynamic secret by name", + Use: "create [dynamic-secret]", + DisableFlagsInUseLine: true, + Args: cobra.ExactArgs(1), + Run: createDynamicSecretLeaseByName, +} + +func createDynamicSecretLeaseByName(cmd *cobra.Command, args []string) { + dynamicSecretRootCredentialName := args[0] + + environmentName, _ := cmd.Flags().GetString("env") + if !cmd.Flags().Changed("env") { + environmentFromWorkspace := util.GetEnvFromWorkspaceFile() + if environmentFromWorkspace != "" { + environmentName = environmentFromWorkspace + } + } + + 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") + } + + ttl, err := cmd.Flags().GetString("ttl") + 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 path flag") + } + + plainOutput, err := cmd.Flags().GetBool("plain") + if err != nil { + util.HandleError(err, "Unable to parse flag") + } + + var infisicalToken string + httpClient := resty.New() + + if projectId == "" { + workspaceFile, err := util.GetWorkSpaceFromFile() + if err != nil { + util.HandleError(err, "Unable to get local project details") + } + projectId = workspaceFile.WorkspaceId + } + + if token != nil && (token.Type == util.SERVICE_TOKEN_IDENTIFIER || token.Type == util.UNIVERSAL_AUTH_TOKEN_IDENTIFIER) { + infisicalToken = token.Token + } else { + util.RequireLogin() + util.RequireLocalWorkspaceFile() + + 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") + } + infisicalToken = loggedInUserDetails.UserCredentials.JTWToken + } + + httpClient.SetAuthToken(infisicalToken) + + infisicalClient := infisicalSdk.NewInfisicalClient(context.Background(), infisicalSdk.Config{ + SiteUrl: config.INFISICAL_URL, + UserAgent: api.USER_AGENT, + AutoTokenRefresh: false, + }) + infisicalClient.Auth().SetAccessToken(infisicalToken) + + projectDetails, err := api.CallGetProjectById(httpClient, projectId) + if err != nil { + util.HandleError(err, "To fetch project details") + } + + dynamicSecretRootCredential, err := infisicalClient.DynamicSecrets().GetByName(infisicalSdk.GetDynamicSecretRootCredentialByNameOptions{ + DynamicSecretName: dynamicSecretRootCredentialName, + ProjectSlug: projectDetails.Slug, + SecretPath: secretsPath, + EnvironmentSlug: environmentName, + }) + + if err != nil { + util.HandleError(err, "To fetch dynamic secret root credentials details") + } + + leaseCredentials, _, leaseDetails, err := infisicalClient.DynamicSecrets().Leases().Create(infisicalSdk.CreateDynamicSecretLeaseOptions{ + DynamicSecretName: dynamicSecretRootCredential.Name, + ProjectSlug: projectDetails.Slug, + TTL: ttl, + SecretPath: secretsPath, + EnvironmentSlug: environmentName, + }) + if err != nil { + util.HandleError(err, "To lease dynamic secret") + } + + if plainOutput { + for key, value := range leaseCredentials { + if cred, ok := value.(string); ok { + fmt.Printf("%s=%s\n", key, cred) + } + } + } else { + fmt.Println("Dynamic Secret Leasing") + fmt.Printf("Name: %s\n", dynamicSecretRootCredential.Name) + fmt.Printf("Provider: %s\n", dynamicSecretRootCredential.Type) + fmt.Printf("Expire At: %s\n", leaseDetails.ExpireAt.Local().Format("02-Jan-2006 03:04:05 PM")) + visualize.PrintAllDyamicSecretLeaseCredentials(leaseCredentials) + } + + Telemetry.CaptureEvent("cli-command:dynamic-secrets lease", posthog.NewProperties().Set("type", dynamicSecretRootCredential.Type).Set("version", util.CLI_VERSION)) +} + +var dynamicSecretLeaseRenewCmd = &cobra.Command{ + Example: `lease create "`, + Short: "Used to renew dynamic secret lease by name", + Use: "renew [lease-id]", + DisableFlagsInUseLine: true, + Args: cobra.ExactArgs(1), + Run: renewDynamicSecretLeaseByName, +} + +func renewDynamicSecretLeaseByName(cmd *cobra.Command, args []string) { + dynamicSecretLeaseId := args[0] + + environmentName, _ := cmd.Flags().GetString("env") + if !cmd.Flags().Changed("env") { + environmentFromWorkspace := util.GetEnvFromWorkspaceFile() + if environmentFromWorkspace != "" { + environmentName = environmentFromWorkspace + } + } + + 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") + } + + ttl, err := cmd.Flags().GetString("ttl") + 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 path flag") + } + + var infisicalToken string + httpClient := resty.New() + + if projectId == "" { + workspaceFile, err := util.GetWorkSpaceFromFile() + if err != nil { + util.HandleError(err, "Unable to get local project details") + } + projectId = workspaceFile.WorkspaceId + } + + if token != nil && (token.Type == util.SERVICE_TOKEN_IDENTIFIER || token.Type == util.UNIVERSAL_AUTH_TOKEN_IDENTIFIER) { + infisicalToken = token.Token + } else { + util.RequireLogin() + util.RequireLocalWorkspaceFile() + + 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") + } + infisicalToken = loggedInUserDetails.UserCredentials.JTWToken + } + + httpClient.SetAuthToken(infisicalToken) + + infisicalClient := infisicalSdk.NewInfisicalClient(context.Background(), infisicalSdk.Config{ + SiteUrl: config.INFISICAL_URL, + UserAgent: api.USER_AGENT, + AutoTokenRefresh: false, + }) + infisicalClient.Auth().SetAccessToken(infisicalToken) + + projectDetails, err := api.CallGetProjectById(httpClient, projectId) + if err != nil { + util.HandleError(err, "To fetch project details") + } + + if err != nil { + util.HandleError(err, "To fetch dynamic secret root credentials details") + } + + leaseDetails, err := infisicalClient.DynamicSecrets().Leases().RenewById(infisicalSdk.RenewDynamicSecretLeaseOptions{ + ProjectSlug: projectDetails.Slug, + TTL: ttl, + SecretPath: secretsPath, + EnvironmentSlug: environmentName, + LeaseId: dynamicSecretLeaseId, + }) + if err != nil { + util.HandleError(err, "To renew dynamic secret lease") + } + + fmt.Println("Successfully renewed dynamic secret lease") + visualize.PrintAllDynamicSecretLeases([]infisicalSdkModels.DynamicSecretLease{leaseDetails}) + + Telemetry.CaptureEvent("cli-command:dynamic-secrets lease renew", posthog.NewProperties().Set("version", util.CLI_VERSION)) +} + +var dynamicSecretLeaseRevokeCmd = &cobra.Command{ + Example: `lease delete "`, + Short: "Used to delete dynamic secret lease by name", + Use: "delete [lease-id]", + DisableFlagsInUseLine: true, + Args: cobra.ExactArgs(1), + Run: revokeDynamicSecretLeaseByName, +} + +func revokeDynamicSecretLeaseByName(cmd *cobra.Command, args []string) { + dynamicSecretLeaseId := args[0] + + environmentName, _ := cmd.Flags().GetString("env") + if !cmd.Flags().Changed("env") { + environmentFromWorkspace := util.GetEnvFromWorkspaceFile() + if environmentFromWorkspace != "" { + environmentName = environmentFromWorkspace + } + } + + 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 path flag") + } + + var infisicalToken string + httpClient := resty.New() + + if projectId == "" { + workspaceFile, err := util.GetWorkSpaceFromFile() + if err != nil { + util.HandleError(err, "Unable to get local project details") + } + projectId = workspaceFile.WorkspaceId + } + + if token != nil && (token.Type == util.SERVICE_TOKEN_IDENTIFIER || token.Type == util.UNIVERSAL_AUTH_TOKEN_IDENTIFIER) { + infisicalToken = token.Token + } else { + util.RequireLogin() + util.RequireLocalWorkspaceFile() + + 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") + } + infisicalToken = loggedInUserDetails.UserCredentials.JTWToken + } + + httpClient.SetAuthToken(infisicalToken) + + infisicalClient := infisicalSdk.NewInfisicalClient(context.Background(), infisicalSdk.Config{ + SiteUrl: config.INFISICAL_URL, + UserAgent: api.USER_AGENT, + AutoTokenRefresh: false, + }) + infisicalClient.Auth().SetAccessToken(infisicalToken) + + projectDetails, err := api.CallGetProjectById(httpClient, projectId) + if err != nil { + util.HandleError(err, "To fetch project details") + } + + if err != nil { + util.HandleError(err, "To fetch dynamic secret root credentials details") + } + + leaseDetails, err := infisicalClient.DynamicSecrets().Leases().DeleteById(infisicalSdk.DeleteDynamicSecretLeaseOptions{ + ProjectSlug: projectDetails.Slug, + SecretPath: secretsPath, + EnvironmentSlug: environmentName, + LeaseId: dynamicSecretLeaseId, + }) + if err != nil { + util.HandleError(err, "To revoke dynamic secret lease") + } + + fmt.Println("Successfully revoked dynamic secret lease") + visualize.PrintAllDynamicSecretLeases([]infisicalSdkModels.DynamicSecretLease{leaseDetails}) + + Telemetry.CaptureEvent("cli-command:dynamic-secrets lease revoke", posthog.NewProperties().Set("version", util.CLI_VERSION)) +} + +var dynamicSecretLeaseListCmd = &cobra.Command{ + Example: `lease list "`, + Short: "Used to list leases of a dynamic secret by name", + Use: "list [dynamic-secret]", + DisableFlagsInUseLine: true, + Args: cobra.ExactArgs(1), + Run: listDynamicSecretLeaseByName, +} + +func listDynamicSecretLeaseByName(cmd *cobra.Command, args []string) { + dynamicSecretRootCredentialName := args[0] + + environmentName, _ := cmd.Flags().GetString("env") + if !cmd.Flags().Changed("env") { + environmentFromWorkspace := util.GetEnvFromWorkspaceFile() + if environmentFromWorkspace != "" { + environmentName = environmentFromWorkspace + } + } + + 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 path flag") + } + + var infisicalToken string + httpClient := resty.New() + + if projectId == "" { + workspaceFile, err := util.GetWorkSpaceFromFile() + if err != nil { + util.HandleError(err, "Unable to get local project details") + } + projectId = workspaceFile.WorkspaceId + } + + if token != nil && (token.Type == util.SERVICE_TOKEN_IDENTIFIER || token.Type == util.UNIVERSAL_AUTH_TOKEN_IDENTIFIER) { + infisicalToken = token.Token + } else { + util.RequireLogin() + util.RequireLocalWorkspaceFile() + + 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") + } + infisicalToken = loggedInUserDetails.UserCredentials.JTWToken + } + + httpClient.SetAuthToken(infisicalToken) + + infisicalClient := infisicalSdk.NewInfisicalClient(context.Background(), infisicalSdk.Config{ + SiteUrl: config.INFISICAL_URL, + UserAgent: api.USER_AGENT, + AutoTokenRefresh: false, + }) + infisicalClient.Auth().SetAccessToken(infisicalToken) + + projectDetails, err := api.CallGetProjectById(httpClient, projectId) + if err != nil { + util.HandleError(err, "To fetch project details") + } + + dynamicSecretLeases, err := infisicalClient.DynamicSecrets().Leases().List(infisicalSdk.ListDynamicSecretLeasesOptions{ + DynamicSecretName: dynamicSecretRootCredentialName, + ProjectSlug: projectDetails.Slug, + SecretPath: secretsPath, + EnvironmentSlug: environmentName, + }) + + if err != nil { + util.HandleError(err, "To fetch dynamic secret leases list") + } + + visualize.PrintAllDynamicSecretLeases(dynamicSecretLeases) + Telemetry.CaptureEvent("cli-command:dynamic-secrets lease list", posthog.NewProperties().Set("lease-count", len(dynamicSecretLeases)).Set("version", util.CLI_VERSION)) +} + +func init() { + dynamicSecretLeaseCreateCmd.Flags().StringP("path", "p", "/", "The path from where dynamic secret should be leased from") + dynamicSecretLeaseCreateCmd.Flags().String("token", "", "Create dynamic secret leases using machine identity access token") + dynamicSecretLeaseCreateCmd.Flags().String("projectId", "", "Manually set the projectId to fetch leased from when using machine identity based auth") + dynamicSecretLeaseCreateCmd.Flags().String("ttl", "", "The lease lifetime TTL. If not provided the default TTL of dynamic secret will be used.") + dynamicSecretLeaseCreateCmd.Flags().Bool("plain", false, "Print leased credentials without formatting, one per line") + dynamicSecretLeaseCmd.AddCommand(dynamicSecretLeaseCreateCmd) + + dynamicSecretLeaseListCmd.Flags().StringP("path", "p", "/", "The path from where dynamic secret should be leased from") + dynamicSecretLeaseListCmd.Flags().String("token", "", "Fetch dynamic secret leases machine identity access token") + dynamicSecretLeaseListCmd.Flags().String("projectId", "", "Manually set the projectId to fetch leased from when using machine identity based auth") + dynamicSecretLeaseCmd.AddCommand(dynamicSecretLeaseListCmd) + + dynamicSecretLeaseRenewCmd.Flags().StringP("path", "p", "/", "The path from where dynamic secret should be leased from") + dynamicSecretLeaseRenewCmd.Flags().String("token", "", "Renew dynamic secrets machine identity access token") + dynamicSecretLeaseRenewCmd.Flags().String("projectId", "", "Manually set the projectId to fetch leased from when using machine identity based auth") + dynamicSecretLeaseRenewCmd.Flags().String("ttl", "", "The lease lifetime TTL. If not provided the default TTL of dynamic secret will be used.") + dynamicSecretLeaseCmd.AddCommand(dynamicSecretLeaseRenewCmd) + + dynamicSecretLeaseRevokeCmd.Flags().StringP("path", "p", "/", "The path from where dynamic secret should be leased from") + dynamicSecretLeaseRevokeCmd.Flags().String("token", "", "Delete dynamic secrets using machine identity access token") + dynamicSecretLeaseRevokeCmd.Flags().String("projectId", "", "Manually set the projectId to fetch leased from when using machine identity based auth") + dynamicSecretLeaseCmd.AddCommand(dynamicSecretLeaseRevokeCmd) + + dynamicSecretCmd.AddCommand(dynamicSecretLeaseCmd) + + dynamicSecretCmd.Flags().String("token", "", "Fetch secrets using service token or machine identity access token") + dynamicSecretCmd.Flags().String("projectId", "", "Manually set the projectId to fetch dynamic-secret when using machine identity based auth") + dynamicSecretCmd.PersistentFlags().String("env", "dev", "Used to select the environment name on which actions should be taken on") + dynamicSecretCmd.Flags().String("path", "/", "get dynamic secret within a folder path") + rootCmd.AddCommand(dynamicSecretCmd) +} diff --git a/cli/packages/visualize/dynamic_secret_leases.go b/cli/packages/visualize/dynamic_secret_leases.go new file mode 100644 index 0000000000..157e4e9816 --- /dev/null +++ b/cli/packages/visualize/dynamic_secret_leases.go @@ -0,0 +1,38 @@ +package visualize + +import infisicalModels "github.com/infisical/go-sdk/packages/models" + +func PrintAllDyamicSecretLeaseCredentials(leaseCredentials map[string]any) { + rows := [][]string{} + for key, value := range leaseCredentials { + if cred, ok := value.(string); ok { + rows = append(rows, []string{key, cred}) + } + } + + headers := []string{"Key", "Value"} + + GenericTable(headers, rows) +} + +func PrintAllDynamicRootCredentials(dynamicRootCredentials []infisicalModels.DynamicSecret) { + rows := [][]string{} + for _, el := range dynamicRootCredentials { + rows = append(rows, []string{el.Name, el.Type, el.DefaultTTL, el.MaxTTL}) + } + + headers := []string{"Name", "Provider", "Default TTL", "Max TTL"} + + GenericTable(headers, rows) +} + +func PrintAllDynamicSecretLeases(dynamicSecretLeases []infisicalModels.DynamicSecretLease) { + rows := [][]string{} + for _, el := range dynamicSecretLeases { + rows = append(rows, []string{el.Id, el.ExpireAt.Local().Format("02-Jan-2006 03:04:05 PM"), el.CreatedAt.Local().Format("02-Jan-2006 03:04:05 PM")}) + } + + headers := []string{"ID", "Expire At", "Created At"} + + GenericTable(headers, rows) +} diff --git a/cli/packages/visualize/visualize.go b/cli/packages/visualize/visualize.go index 365cbeac41..57a3064e74 100644 --- a/cli/packages/visualize/visualize.go +++ b/cli/packages/visualize/visualize.go @@ -94,6 +94,52 @@ func getLongestValues(rows [][3]string) (longestSecretName, longestSecretType in return } +func GenericTable(headers []string, rows [][]string) { + // if we're not in a terminal or cygwin terminal, don't truncate the secret value + shouldTruncate := isatty.IsTerminal(os.Stdout.Fd()) + + // This will return an error if we're not in a terminal or + // if the terminal is a cygwin terminal like Git Bash. + width, _, err := term.GetSize(int(os.Stdout.Fd())) + if err != nil { + if shouldTruncate { + log.Error().Msgf("error getting terminal size: %s", err) + } else { + log.Debug().Err(err) + } + } + + availableWidth := width - borderWidths + if availableWidth < 0 { + availableWidth = 0 + } + + t := table.NewWriter() + t.SetOutputMirror(os.Stdout) + t.SetStyle(table.StyleLight) + + // t.SetTitle(tableOptions.Title) + t.Style().Options.DrawBorder = true + t.Style().Options.SeparateHeader = true + t.Style().Options.SeparateColumns = true + + tableHeaders := table.Row{} + for _, header := range headers { + tableHeaders = append(tableHeaders, header) + } + + t.AppendHeader(tableHeaders) + for _, row := range rows { + tableRow := table.Row{} + for _, val := range row { + tableRow = append(tableRow, val) + } + t.AppendRow(tableRow) + } + + t.Render() +} + // stringWidth returns the width of a string. // ANSI escape sequences are ignored and double-width characters are handled correctly. func stringWidth(str string) (width int) { From ed7fc0e5cdbf4eff3062df6e24c9facd9f6e6e09 Mon Sep 17 00:00:00 2001 From: = Date: Fri, 15 Nov 2024 20:33:06 +0530 Subject: [PATCH 2/4] docs: updated dynamic secret command cli docs --- docs/cli/commands/dynamic-secrets.mdx | 293 ++++++++++++++++++++++++++ docs/mint.json | 1 + 2 files changed, 294 insertions(+) create mode 100644 docs/cli/commands/dynamic-secrets.mdx diff --git a/docs/cli/commands/dynamic-secrets.mdx b/docs/cli/commands/dynamic-secrets.mdx new file mode 100644 index 0000000000..22dbecb8bb --- /dev/null +++ b/docs/cli/commands/dynamic-secrets.mdx @@ -0,0 +1,293 @@ +--- +title: "infisical dynamic-secrets" +description: "Perform various operations with Infisical dynamic secrets" +--- + +``` +infisical dynamic-secrets +``` + +## Description + +This command enables you to perform list, lease, renew lease, and revoke lease operations on dynamic secrets within your Infisical project. + +### Sub-commands + + + Use this command to print out all of the dynamic secrets in your project. + +```bash +$ infisical dynamic-secrets +``` + +### Environment variables + + + Used to fetch dynamic secrets via a [machine identity](/documentation/platform/identities/machine-identities) instead of logged-in credentials. Simply, export this variable in the terminal before running this command. + +```bash +# Example +export INFISICAL_TOKEN=$(infisical login --method=universal-auth --client-id= --client-secret= --silent --plain) # --plain flag will output only the token, so it can be fed to an environment variable. --silent will disable any update messages. +``` + + + + + Used to disable the check for new CLI versions. This can improve the time it takes to run this command. Recommended for production environments. + +To use, simply export this variable in the terminal before running this command. + +```bash +# Example +export INFISICAL_DISABLE_UPDATE_CHECK=true +``` + + + +### Flags + + + The project ID to fetch dynamic secrets from. This is required when using a machine identity to authenticate. + +```bash +# Example +infisical dynamic-secrets --projectId= +``` + + + + + The authenticated token to fetch dynamic secrets from. This is required when using a machine identity to authenticate. + +```bash +# Example +infisical dynamic-secrets --token= +``` + + + + + Used to select the environment name on which actions should be taken. Default + value: `dev` + + + + Use to select the project folder on which dynamic secrets will be accessed. + +```bash +# Example +infisical dynamic-secrets --path="/" --env=dev +``` + + + + + This command is used to create a new lease for a dynamic secret. + +```bash +$ infisical dynamic-secrets lease create +``` + +### Flags + + + Used to select the environment name on which actions should be taken. Default + value: `dev` + + + + The `--plain` flag will output dynamic secret lease credentials values without formatting, one per line. + Default value: `false` + +```bash +# Example +infisical dynamic-secrets lease create dynamic-secret-postgres --plain +``` + + + + + The `--path` flag indicates which project folder dynamic secrets will be injected from. + +```bash +# Example +infisical dynamic-secrets lease create --path="/" --env=dev +``` + + + + + The project ID of the dynamic secrets to lease from. This is required when using a machine identity to authenticate. + +```bash +# Example +infisical dynamic-secrets lease create --projectId= +``` + + + + + The authenticated token to create dynamic secret leases. This is required when using a machine identity to authenticate. + +```bash +# Example +infisical dynamic-secrets lease create --token= +``` + + + + + The lease lifetime. If not provided, the default TTL of the dynamic secret root credential will be used. + +```bash +# Example +infisical dynamic-secrets lease create --ttl= +``` + + + + + + This command is used to list leases for a dynamic secret. + +```bash +$ infisical dynamic-secrets lease list +``` + +### Flags + + + Used to select the environment name on which actions should be taken. Default + value: `dev` + + + + The `--path` flag indicates which project folder dynamic secrets will be injected from. + +```bash +# Example +infisical dynamic-secrets lease list --path="/" --env=dev +``` + + + + + The project ID of the dynamic secrets to list leases from. This is required when using a machine identity to authenticate. + +```bash +# Example +infisical dynamic-secrets lease list --projectId= +``` + + + + + The authenticated token to list dynamic secret leases. This is required when using a machine identity to authenticate. + +```bash +# Example +infisical dynamic-secrets lease list --token= +``` + + + + + + This command is used to renew a lease before it expires. + +```bash +$ infisical dynamic-secrets lease renew +``` + +### Flags + + + Used to select the environment name on which actions should be taken. Default + value: `dev` + + + + The `--path` flag indicates which project folder dynamic secrets will be renewed from. + +```bash +# Example +infisical dynamic-secrets lease renew --path="/" --env=dev +``` + + + + + The project ID of the dynamic secret's lease from. This is required when using a machine identity to authenticate. + +```bash +# Example +infisical dynamic-secrets lease renew --projectId= +``` + + + + + The authenticated token to create dynamic secret leases. This is required when using a machine identity to authenticate. + +```bash +# Example +infisical dynamic-secrets lease renew --token= +``` + + + + + The lease lifetime. If not provided, the default TTL of the dynamic secret root credential will be used. + +```bash +# Example +infisical dynamic-secrets lease renew --ttl= +``` + + + + + + This command is used to delete a lease. + +```bash +$ infisical dynamic-secrets lease delete +``` + +### Flags + + + Used to select the environment name on which actions should be taken. Default + value: `dev` + + + + The `--path` flag indicates which project folder dynamic secrets will be deleted from. + +```bash +# Example +infisical dynamic-secrets lease delete --path="/" --env=dev +``` + + + + + The project ID of the dynamic secret's lease from. This is required when using a machine identity to authenticate. + +```bash +# Example +infisical dynamic-secrets lease delete --projectId= +``` + + + + + The authenticated token to delete dynamic secret leases. This is required when using a machine identity to authenticate. + +```bash +# Example +infisical dynamic-secrets lease delete --token= +``` + + + diff --git a/docs/mint.json b/docs/mint.json index f070ae88a2..ad41b22db6 100644 --- a/docs/mint.json +++ b/docs/mint.json @@ -314,6 +314,7 @@ "cli/commands/init", "cli/commands/run", "cli/commands/secrets", + "cli/commands/dynamic-secrets", "cli/commands/export", "cli/commands/token", "cli/commands/service-token", From be39e63832ec4a220f2a9adc34e84d29f6ff4998 Mon Sep 17 00:00:00 2001 From: = Date: Mon, 25 Nov 2024 20:28:43 +0530 Subject: [PATCH 3/4] feat: updated pr based on review --- cli/packages/cmd/dynamic_secret.go | 3 ++- .../visualize/dynamic_secret_leases.go | 3 ++- cli/packages/visualize/visualize.go | 19 ------------------- docs/cli/commands/dynamic-secrets.mdx | 4 +++- 4 files changed, 7 insertions(+), 22 deletions(-) diff --git a/cli/packages/cmd/dynamic_secret.go b/cli/packages/cmd/dynamic_secret.go index befc356328..8c699386d3 100644 --- a/cli/packages/cmd/dynamic_secret.go +++ b/cli/packages/cmd/dynamic_secret.go @@ -237,6 +237,7 @@ func createDynamicSecretLeaseByName(cmd *cobra.Command, args []string) { fmt.Println("Dynamic Secret Leasing") fmt.Printf("Name: %s\n", dynamicSecretRootCredential.Name) fmt.Printf("Provider: %s\n", dynamicSecretRootCredential.Type) + fmt.Printf("Lease ID: %s\n", leaseDetails.Id) fmt.Printf("Expire At: %s\n", leaseDetails.ExpireAt.Local().Format("02-Jan-2006 03:04:05 PM")) visualize.PrintAllDyamicSecretLeaseCredentials(leaseCredentials) } @@ -245,7 +246,7 @@ func createDynamicSecretLeaseByName(cmd *cobra.Command, args []string) { } var dynamicSecretLeaseRenewCmd = &cobra.Command{ - Example: `lease create "`, + Example: `lease renew "`, Short: "Used to renew dynamic secret lease by name", Use: "renew [lease-id]", DisableFlagsInUseLine: true, diff --git a/cli/packages/visualize/dynamic_secret_leases.go b/cli/packages/visualize/dynamic_secret_leases.go index 157e4e9816..dbb5886246 100644 --- a/cli/packages/visualize/dynamic_secret_leases.go +++ b/cli/packages/visualize/dynamic_secret_leases.go @@ -28,8 +28,9 @@ func PrintAllDynamicRootCredentials(dynamicRootCredentials []infisicalModels.Dyn func PrintAllDynamicSecretLeases(dynamicSecretLeases []infisicalModels.DynamicSecretLease) { rows := [][]string{} + const timeformat = "02-Jan-2006 03:04:05 PM" for _, el := range dynamicSecretLeases { - rows = append(rows, []string{el.Id, el.ExpireAt.Local().Format("02-Jan-2006 03:04:05 PM"), el.CreatedAt.Local().Format("02-Jan-2006 03:04:05 PM")}) + rows = append(rows, []string{el.Id, el.ExpireAt.Local().Format(timeformat), el.CreatedAt.Local().Format(timeformat)}) } headers := []string{"ID", "Expire At", "Created At"} diff --git a/cli/packages/visualize/visualize.go b/cli/packages/visualize/visualize.go index 57a3064e74..7fbd24fb8b 100644 --- a/cli/packages/visualize/visualize.go +++ b/cli/packages/visualize/visualize.go @@ -95,25 +95,6 @@ func getLongestValues(rows [][3]string) (longestSecretName, longestSecretType in } func GenericTable(headers []string, rows [][]string) { - // if we're not in a terminal or cygwin terminal, don't truncate the secret value - shouldTruncate := isatty.IsTerminal(os.Stdout.Fd()) - - // This will return an error if we're not in a terminal or - // if the terminal is a cygwin terminal like Git Bash. - width, _, err := term.GetSize(int(os.Stdout.Fd())) - if err != nil { - if shouldTruncate { - log.Error().Msgf("error getting terminal size: %s", err) - } else { - log.Debug().Err(err) - } - } - - availableWidth := width - borderWidths - if availableWidth < 0 { - availableWidth = 0 - } - t := table.NewWriter() t.SetOutputMirror(os.Stdout) t.SetStyle(table.StyleLight) diff --git a/docs/cli/commands/dynamic-secrets.mdx b/docs/cli/commands/dynamic-secrets.mdx index 22dbecb8bb..c345c3e2d2 100644 --- a/docs/cli/commands/dynamic-secrets.mdx +++ b/docs/cli/commands/dynamic-secrets.mdx @@ -1,6 +1,6 @@ --- title: "infisical dynamic-secrets" -description: "Perform various operations with Infisical dynamic secrets" +description: "Perform dynamic secret operations directly with the CLI" --- ``` @@ -9,6 +9,8 @@ infisical dynamic-secrets ## Description +Dynamic secrets are unique secrets generated on demand based on the provided configuration settings. For more details, refer to [dynamics secrets section](/documentation/platform/dynamic-secrets/overview). + This command enables you to perform list, lease, renew lease, and revoke lease operations on dynamic secrets within your Infisical project. ### Sub-commands From 3d6ea3251e2026ec6684ece99121fc2524aa0532 Mon Sep 17 00:00:00 2001 From: = Date: Mon, 25 Nov 2024 23:36:32 +0530 Subject: [PATCH 4/4] feat: renamed dynamic_secrets to match with the command --- cli/packages/cmd/{dynamic_secret.go => dynamic_secrets.go} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename cli/packages/cmd/{dynamic_secret.go => dynamic_secrets.go} (100%) diff --git a/cli/packages/cmd/dynamic_secret.go b/cli/packages/cmd/dynamic_secrets.go similarity index 100% rename from cli/packages/cmd/dynamic_secret.go rename to cli/packages/cmd/dynamic_secrets.go