From 097512c6914b4ece4732b150a61873f976bc0c6e Mon Sep 17 00:00:00 2001 From: Tuan Dang Date: Sun, 15 Dec 2024 17:30:57 -0800 Subject: [PATCH 1/7] Begin adding ssh commands to cli --- cli/go.mod | 2 +- cli/go.sum | 2 + cli/packages/cmd/ssh.go | 387 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 390 insertions(+), 1 deletion(-) create mode 100644 cli/packages/cmd/ssh.go diff --git a/cli/go.mod b/cli/go.mod index b55a49c63..d9adb8cc8 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.4.3 + github.com/infisical/go-sdk v0.4.5 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 537a146cb..5db12269c 100644 --- a/cli/go.sum +++ b/cli/go.sum @@ -267,6 +267,8 @@ github.com/inconshreveable/mousetrap v1.0.1 h1:U3uMjPSQEBMNp1lFxmllqCPM6P5u/Xq7P github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= 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/infisical/go-sdk v0.4.5 h1:rQSgWW+thZQ2WfIbOD5SBflqx3BUUSNlvuDZdexXMFk= +github.com/infisical/go-sdk v0.4.5/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/cmd/ssh.go b/cli/packages/cmd/ssh.go new file mode 100644 index 000000000..0bf50f11d --- /dev/null +++ b/cli/packages/cmd/ssh.go @@ -0,0 +1,387 @@ +/* +Copyright (c) 2023 Infisical Inc. +*/ +package cmd + +import ( + "context" + "fmt" + "os" + "path/filepath" + "strings" + + "github.com/Infisical/infisical-merge/packages/api" + "github.com/Infisical/infisical-merge/packages/config" + "github.com/Infisical/infisical-merge/packages/util" + infisicalSdk "github.com/infisical/go-sdk" + infisicalSdkUtil "github.com/infisical/go-sdk/packages/util" + "github.com/spf13/cobra" +) + +var sshCmd = &cobra.Command{ + Example: `infisical ssh`, + Short: "Used to issue SSH credentials", + Use: "ssh", + DisableFlagsInUseLine: true, + Args: cobra.NoArgs, +} + +var sshIssueCredentialsCmd = &cobra.Command{ + Example: `ssh issue-credentials`, + Short: "Used to issue SSH credentials against a certificate template", + Use: "issue-credentials", + DisableFlagsInUseLine: true, + Args: cobra.NoArgs, + Run: issueCredentials, +} + +var sshSignKeyCmd = &cobra.Command{ + Example: `ssh sign-key`, + Short: "Used to sign a SSH public key against a certificate template", + Use: "sign-key", + DisableFlagsInUseLine: true, + Args: cobra.NoArgs, + Run: signKey, +} + +func isValidKeyAlgorithm(algo infisicalSdkUtil.CertKeyAlgorithm) bool { + switch algo { + case infisicalSdkUtil.RSA2048, infisicalSdkUtil.RSA4096, infisicalSdkUtil.ECDSAP256, infisicalSdkUtil.ECDSAP384: + return true + default: + return false + } +} + +func isValidCertType(certType infisicalSdkUtil.SshCertType) bool { + switch certType { + case infisicalSdkUtil.UserCert, infisicalSdkUtil.HostCert: + return true + default: + return false + } +} + +// writeToFile writes content to the specified file with given permissions +func writeToFile(filePath string, content string, perm os.FileMode) error { + // Ensure the directory exists + dir := filepath.Dir(filePath) + if err := os.MkdirAll(dir, 0755); err != nil { + return fmt.Errorf("failed to create directory %s: %w", dir, err) + } + + // Write the content to the file + err := os.WriteFile(filePath, []byte(content), perm) + if err != nil { + return fmt.Errorf("failed to write to file %s: %w", filePath, err) + } + + return nil +} + +func issueCredentials(cmd *cobra.Command, args []string) { + var infisicalToken string + util.RequireLogin() + loggedInUserDetails, err := util.GetCurrentLoggedInUserDetails(true) + 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 + + projectId, err := cmd.Flags().GetString("projectId") + if err != nil { + util.HandleError(err, "Unable to parse flag") + } + if projectId == "" { + util.PrintErrorMessageAndExit("You must set the --projectId flag") + } + + templateName, err := cmd.Flags().GetString("templateName") + if err != nil { + util.HandleError(err, "Unable to parse flag") + } + if templateName == "" { + util.PrintErrorMessageAndExit("You must set the --templateName flag") + } + + principalsStr, err := cmd.Flags().GetString("principals") + if err != nil { + util.HandleError(err, "Unable to parse flag") + } + + // Convert the comma-delimited string into a slice of strings + principals := strings.Split(principalsStr, ",") + + // Trim whitespace around each principal + for i, principal := range principals { + principals[i] = strings.TrimSpace(principal) + } + + keyAlgorithm, err := cmd.Flags().GetString("keyAlgorithm") + if err != nil { + util.HandleError(err, "Unable to parse keyAlgorithm flag") + } + + if !isValidKeyAlgorithm(infisicalSdkUtil.CertKeyAlgorithm(keyAlgorithm)) { + util.HandleError(fmt.Errorf("invalid keyAlgorithm: %s", keyAlgorithm), + "Valid values: RSA_2048, RSA_4096, EC_prime256v1, EC_secp384r1") + } + + certType, err := cmd.Flags().GetString("certType") + if err != nil { + util.HandleError(err, "Unable to parse flag") + } + + if !isValidCertType(infisicalSdkUtil.SshCertType(certType)) { + util.HandleError(fmt.Errorf("invalid certType: %s", certType), + "Valid values: user, host") + } + + ttl, err := cmd.Flags().GetString("ttl") + if err != nil { + util.HandleError(err, "Unable to parse flag") + } + + keyId, err := cmd.Flags().GetString("keyId") + if err != nil { + util.HandleError(err, "Unable to parse flag") + } + + outFilePath, err := cmd.Flags().GetString("outFilePath") + if err != nil { + util.HandleError(err, "Unable to parse flag") + } + + fmt.Println("outFilePath a", outFilePath) + + // Determine the output directory + var outputDir string + if outFilePath == "" { + // Use current working directory + cwd, err := os.Getwd() + if err != nil { + util.HandleError(err, "Failed to get current working directory") + } + outputDir = cwd + fmt.Println("outFilePath b", outputDir) + } else { + // Expand ~ to home directory if present + if strings.HasPrefix(outFilePath, "~") { + homeDir, err := os.UserHomeDir() + if err != nil { + util.HandleError(err, "Failed to resolve home directory") + } + outFilePath = strings.Replace(outFilePath, "~", homeDir, 1) + } + outputDir = outFilePath + + // Check if the directory exists; if not, create it + info, err := os.Stat(outputDir) + if os.IsNotExist(err) { + err = os.MkdirAll(outputDir, 0755) + if err != nil { + util.HandleError(err, "Failed to create output directory") + } + } else if err != nil { + util.HandleError(err, "Failed to access output directory") + } else if !info.IsDir() { + util.PrintErrorMessageAndExit("The provided --outFilePath is not a directory") + } + } + + infisicalClient := infisicalSdk.NewInfisicalClient(context.Background(), infisicalSdk.Config{ + SiteUrl: config.INFISICAL_URL, + UserAgent: api.USER_AGENT, + AutoTokenRefresh: false, + }) + infisicalClient.Auth().SetAccessToken(infisicalToken) + + creds, err := infisicalClient.Ssh().IssueCredentials(infisicalSdk.IssueSshCredsOptions{ + ProjectID: projectId, + TemplateName: templateName, + Principals: principals, + KeyAlgorithm: infisicalSdkUtil.CertKeyAlgorithm(keyAlgorithm), + CertType: infisicalSdkUtil.SshCertType(certType), + TTL: ttl, + KeyID: keyId, + }) + + if err != nil { + util.HandleError(err, "To issue SSH credentials") + } + + fmt.Println(creds) + + // // Define file paths + // privateKeyPath := filepath.Join(outputDir, "id_key") + // publicKeyPath := filepath.Join(outputDir, "id_key.pub") + // signedKeyPath := filepath.Join(outputDir, "id_key-cert.pub") + + // // Write Private Key + // err = writeToFile(privateKeyPath, creds.PrivateKey, 0600) + // if err != nil { + // util.HandleError(err, "Failed to write Private Key to file") + // } + + // // Write Public Key + // err = writeToFile(publicKeyPath, creds.PublicKey, 0644) + // if err != nil { + // util.HandleError(err, "Failed to write Public Key to file") + // } + + // // Write Signed Key (Certificate) + // err = writeToFile(signedKeyPath, creds.SignedKey, 0644) + // if err != nil { + // util.HandleError(err, "Failed to write Signed Key to file") + // } + + // TODO: write creds.PrivateKey to outFilePath under id_key, creds.PublicKey to outFilePath under id_key.pub and creds.SignedKey to outFilePath under id_key-cert.pub +} + +func signKey(cmd *cobra.Command, args []string) { + var infisicalToken string + util.RequireLogin() + loggedInUserDetails, err := util.GetCurrentLoggedInUserDetails(true) + 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 + + projectId, err := cmd.Flags().GetString("projectId") + if err != nil { + util.HandleError(err, "Unable to parse flag") + } + if projectId == "" { + util.PrintErrorMessageAndExit("You must set the --projectId flag") + } + + templateName, err := cmd.Flags().GetString("templateName") + if err != nil { + util.HandleError(err, "Unable to parse flag") + } + if templateName == "" { + util.PrintErrorMessageAndExit("You must set the --templateName flag") + } + + publicKey, err := cmd.Flags().GetString("publicKey") + if err != nil { + util.HandleError(err, "Unable to parse flag") + } + + publicKeyFilePath, err := cmd.Flags().GetString("publicKeyFilePath") + if err != nil { + util.HandleError(err, "Unable to parse flag") + } + + if publicKey == "" && publicKeyFilePath == "" { + util.HandleError(fmt.Errorf("either --public-key or --public-key-file must be provided"), "Invalid input") + } + + if publicKey != "" && publicKeyFilePath != "" { + util.HandleError(fmt.Errorf("only one of --public-key or --public-key-file can be provided"), "Invalid input") + } + + if publicKeyFilePath != "" { + if strings.HasPrefix(publicKeyFilePath, "~") { + // Expand the tilde (~) to the user's home directory + homeDir, err := os.UserHomeDir() + if err != nil { + util.HandleError(err, "Failed to resolve home directory") + } + publicKeyFilePath = strings.Replace(publicKeyFilePath, "~", homeDir, 1) + } + + content, err := os.ReadFile(publicKeyFilePath) + if err != nil { + util.HandleError(err, "Failed to read public key file") + } + publicKey = string(content) + } + + principalsStr, err := cmd.Flags().GetString("principals") + if err != nil { + util.HandleError(err, "Unable to parse flag") + } + + // Convert the comma-delimited string into a slice of strings + principals := strings.Split(principalsStr, ",") + + // Trim whitespace around each principal + for i, principal := range principals { + principals[i] = strings.TrimSpace(principal) + } + + certType, err := cmd.Flags().GetString("certType") + if err != nil { + util.HandleError(err, "Unable to parse flag") + } + + if !isValidCertType(infisicalSdkUtil.SshCertType(certType)) { + util.HandleError(fmt.Errorf("invalid certType: %s", certType), + "Valid values: user, host") + } + + ttl, err := cmd.Flags().GetString("ttl") + if err != nil { + util.HandleError(err, "Unable to parse flag") + } + + keyId, err := cmd.Flags().GetString("keyId") + if err != nil { + util.HandleError(err, "Unable to parse flag") + } + + infisicalClient := infisicalSdk.NewInfisicalClient(context.Background(), infisicalSdk.Config{ + SiteUrl: config.INFISICAL_URL, + UserAgent: api.USER_AGENT, + AutoTokenRefresh: false, + }) + infisicalClient.Auth().SetAccessToken(infisicalToken) + + creds, err := infisicalClient.Ssh().SignKey(infisicalSdk.SignSshPublicKeyOptions{ + ProjectID: projectId, + TemplateName: templateName, + PublicKey: publicKey, + Principals: principals, + CertType: infisicalSdkUtil.SshCertType(certType), + TTL: ttl, + KeyID: keyId, + }) + + if err != nil { + util.HandleError(err, "To sign a SSH public key") + } + + fmt.Println(creds) +} + +func init() { + sshSignKeyCmd.Flags().String("projectId", "", "The projectId to issue credentials for") + sshSignKeyCmd.Flags().String("templateName", "", "The template name to issue credentials for") + sshSignKeyCmd.Flags().String("publicKey", "", "The public key to sign") + sshSignKeyCmd.Flags().String("publicKeyFilePath", "", "The path to the public key file to sign") + sshSignKeyCmd.Flags().String("principals", "", "The principals that the certificate should be signed for") + sshSignKeyCmd.Flags().String("certType", string(infisicalSdkUtil.UserCert), "The cert type for the created certificate") + sshSignKeyCmd.Flags().String("ttl", "", "The ttl for the created certificate") + sshSignKeyCmd.Flags().String("keyId", "", "The keyId that the created certificate should have") + sshCmd.AddCommand(sshSignKeyCmd) + + sshIssueCredentialsCmd.Flags().String("projectId", "", "The projectId to issue SSH credentials for") + sshIssueCredentialsCmd.Flags().String("templateName", "", "The template name to issue SSH credentials for") + sshIssueCredentialsCmd.Flags().String("principals", "", "The principals to issue SSH credentials for") + sshIssueCredentialsCmd.Flags().String("keyAlgorithm", string(infisicalSdkUtil.RSA2048), "The key algorithm to issue SSH credentials for") + sshIssueCredentialsCmd.Flags().String("certType", string(infisicalSdkUtil.UserCert), "The cert type to issue SSH credentials for") + sshIssueCredentialsCmd.Flags().String("ttl", "", "The ttl to issue SSH credentials for") + sshIssueCredentialsCmd.Flags().String("keyId", "", "The keyId to issue SSH credentials for") + sshIssueCredentialsCmd.Flags().String("outFilePath", "", "The path to the file to write the SSH credentials to. If not provided, the credentials will be saved to the current working directory") + sshCmd.AddCommand(sshIssueCredentialsCmd) + rootCmd.AddCommand(sshCmd) +} \ No newline at end of file From be368273926d42b21f52b29133886fe5a01eafa3 Mon Sep 17 00:00:00 2001 From: Tuan Dang Date: Mon, 16 Dec 2024 17:42:11 -0800 Subject: [PATCH 2/7] Finish ssh cli sign/issue commands --- cli/packages/cmd/ssh.go | 225 +++++++++++++++++++++++++++++++--------- 1 file changed, 178 insertions(+), 47 deletions(-) diff --git a/cli/packages/cmd/ssh.go b/cli/packages/cmd/ssh.go index 0bf50f11d..090fcd48d 100644 --- a/cli/packages/cmd/ssh.go +++ b/cli/packages/cmd/ssh.go @@ -53,6 +53,19 @@ func isValidKeyAlgorithm(algo infisicalSdkUtil.CertKeyAlgorithm) bool { } } +func getFileName(algo infisicalSdkUtil.CertKeyAlgorithm) string { + switch algo { + case infisicalSdkUtil.RSA2048: + return "id_rsa_2048" + case infisicalSdkUtil.RSA4096: + return "id_rsa_4096" + case infisicalSdkUtil.ECDSAP256: + return "id_ecdsa_p256" + default: + return "id_ecdsa_p384" + } +} + func isValidCertType(certType infisicalSdkUtil.SshCertType) bool { switch certType { case infisicalSdkUtil.UserCert, infisicalSdkUtil.HostCert: @@ -62,7 +75,6 @@ func isValidCertType(certType infisicalSdkUtil.SshCertType) bool { } } -// writeToFile writes content to the specified file with given permissions func writeToFile(filePath string, content string, perm os.FileMode) error { // Ensure the directory exists dir := filepath.Dir(filePath) @@ -115,8 +127,6 @@ func issueCredentials(cmd *cobra.Command, args []string) { // Convert the comma-delimited string into a slice of strings principals := strings.Split(principalsStr, ",") - - // Trim whitespace around each principal for i, principal := range principals { principals[i] = strings.TrimSpace(principal) } @@ -156,10 +166,13 @@ func issueCredentials(cmd *cobra.Command, args []string) { util.HandleError(err, "Unable to parse flag") } - fmt.Println("outFilePath a", outFilePath) - - // Determine the output directory - var outputDir string + var ( + outputDir string + privateKeyPath string + publicKeyPath string + signedKeyPath string + ) + if outFilePath == "" { // Use current working directory cwd, err := os.Getwd() @@ -167,7 +180,6 @@ func issueCredentials(cmd *cobra.Command, args []string) { util.HandleError(err, "Failed to get current working directory") } outputDir = cwd - fmt.Println("outFilePath b", outputDir) } else { // Expand ~ to home directory if present if strings.HasPrefix(outFilePath, "~") { @@ -177,19 +189,58 @@ func issueCredentials(cmd *cobra.Command, args []string) { } outFilePath = strings.Replace(outFilePath, "~", homeDir, 1) } - outputDir = outFilePath - // Check if the directory exists; if not, create it - info, err := os.Stat(outputDir) - if os.IsNotExist(err) { - err = os.MkdirAll(outputDir, 0755) - if err != nil { - util.HandleError(err, "Failed to create output directory") + // Check if outFilePath ends with "-cert.pub" + if strings.HasSuffix(outFilePath, "-cert.pub") { + // Treat outFilePath as the signed key path + signedKeyPath = outFilePath + + // Derive the base name by removing "-cert.pub" + baseName := strings.TrimSuffix(filepath.Base(outFilePath), "-cert.pub") + + // Set the output directory + outputDir = filepath.Dir(outFilePath) + + // Define private and public key paths + privateKeyPath = filepath.Join(outputDir, baseName) + publicKeyPath = filepath.Join(outputDir, baseName+".pub") + } else { + // Treat outFilePath as a directory + outputDir = outFilePath + + // Check if the directory exists; if not, create it + info, err := os.Stat(outputDir) + if os.IsNotExist(err) { + err = os.MkdirAll(outputDir, 0755) + if err != nil { + util.HandleError(err, "Failed to create output directory") + } + } else if err != nil { + util.HandleError(err, "Failed to access output directory") + } else if !info.IsDir() { + util.PrintErrorMessageAndExit("The provided --outFilePath is not a directory") } - } else if err != nil { - util.HandleError(err, "Failed to access output directory") - } else if !info.IsDir() { - util.PrintErrorMessageAndExit("The provided --outFilePath is not a directory") + + // Define file names based on key algorithm + fileName := getFileName(infisicalSdkUtil.CertKeyAlgorithm(keyAlgorithm)) + + // Define file paths + privateKeyPath = filepath.Join(outputDir, fileName) + publicKeyPath = filepath.Join(outputDir, fileName+".pub") + signedKeyPath = filepath.Join(outputDir, fileName+"-cert.pub") + } + } + + // If outFilePath ends with "-cert.pub", ensure the signedKeyPath is set + if strings.HasSuffix(outFilePath, "-cert.pub") { + // Ensure the signedKeyPath was set + if signedKeyPath == "" { + util.HandleError(fmt.Errorf("signedKeyPath is not set correctly"), "Internal error") + } + } else { + // Ensure all paths are set + if privateKeyPath == "" || publicKeyPath == "" || signedKeyPath == "" { + util.HandleError(fmt.Errorf("file paths are not set correctly"), "Internal error") } } @@ -214,32 +265,34 @@ func issueCredentials(cmd *cobra.Command, args []string) { util.HandleError(err, "To issue SSH credentials") } - fmt.Println(creds) + // If signedKeyPath wasn't set in the directory scenario, set it now + if signedKeyPath == "" { + fileName := getFileName(infisicalSdkUtil.CertKeyAlgorithm(keyAlgorithm)) + signedKeyPath = filepath.Join(outputDir, fileName + "-cert.pub") + } - // // Define file paths - // privateKeyPath := filepath.Join(outputDir, "id_key") - // publicKeyPath := filepath.Join(outputDir, "id_key.pub") - // signedKeyPath := filepath.Join(outputDir, "id_key-cert.pub") + if privateKeyPath == "" { + privateKeyPath = filepath.Join(outputDir, getFileName(infisicalSdkUtil.CertKeyAlgorithm(keyAlgorithm))) + } + err = writeToFile(privateKeyPath, creds.PrivateKey, 0600) + if err != nil { + util.HandleError(err, "Failed to write Private Key to file") + } - // // Write Private Key - // err = writeToFile(privateKeyPath, creds.PrivateKey, 0600) - // if err != nil { - // util.HandleError(err, "Failed to write Private Key to file") - // } + if publicKeyPath == "" { + publicKeyPath = privateKeyPath + ".pub" + } + err = writeToFile(publicKeyPath, creds.PublicKey, 0644) + if err != nil { + util.HandleError(err, "Failed to write Public Key to file") + } - // // Write Public Key - // err = writeToFile(publicKeyPath, creds.PublicKey, 0644) - // if err != nil { - // util.HandleError(err, "Failed to write Public Key to file") - // } + err = writeToFile(signedKeyPath, creds.SignedKey, 0644) + if err != nil { + util.HandleError(err, "Failed to write Signed Key to file") + } - // // Write Signed Key (Certificate) - // err = writeToFile(signedKeyPath, creds.SignedKey, 0644) - // if err != nil { - // util.HandleError(err, "Failed to write Signed Key to file") - // } - - // TODO: write creds.PrivateKey to outFilePath under id_key, creds.PublicKey to outFilePath under id_key.pub and creds.SignedKey to outFilePath under id_key-cert.pub + fmt.Println("Successfully wrote SSH certificate tox:", signedKeyPath) } func signKey(cmd *cobra.Command, args []string) { @@ -282,11 +335,11 @@ func signKey(cmd *cobra.Command, args []string) { } if publicKey == "" && publicKeyFilePath == "" { - util.HandleError(fmt.Errorf("either --public-key or --public-key-file must be provided"), "Invalid input") + util.HandleError(fmt.Errorf("either --publicKey or --publicKeyFilePath must be provided"), "Invalid input") } if publicKey != "" && publicKeyFilePath != "" { - util.HandleError(fmt.Errorf("only one of --public-key or --public-key-file can be provided"), "Invalid input") + util.HandleError(fmt.Errorf("only one of --publicKey or --publicKeyFile can be provided"), "Invalid input") } if publicKeyFilePath != "" { @@ -298,12 +351,22 @@ func signKey(cmd *cobra.Command, args []string) { } publicKeyFilePath = strings.Replace(publicKeyFilePath, "~", homeDir, 1) } + + // Ensure the file has a .pub extension + if !strings.HasSuffix(publicKeyFilePath, ".pub") { + util.HandleError(fmt.Errorf("public key file must have a .pub extension"), "Invalid input") + } content, err := os.ReadFile(publicKeyFilePath) if err != nil { util.HandleError(err, "Failed to read public key file") } - publicKey = string(content) + + publicKey = strings.TrimSpace(string(content)) + } + + if (strings.TrimSpace(publicKey) == "") { + util.HandleError(fmt.Errorf("Public key is empty"), "Invalid input") } principalsStr, err := cmd.Flags().GetString("principals") @@ -339,6 +402,68 @@ func signKey(cmd *cobra.Command, args []string) { util.HandleError(err, "Unable to parse flag") } + outFilePath, err := cmd.Flags().GetString("outFilePath") + if err != nil { + util.HandleError(err, "Unable to parse flag") + } + + var ( + outputDir string + signedKeyPath string + ) + + if outFilePath == "" { + // Use current working directory + if err != nil { + util.HandleError(err, "Failed to get current working directory") + } + + // check if public key path exists + if (publicKeyFilePath == "") { + util.PrintErrorMessageAndExit("--outFilePath must be specified when --publicKeyFilePath is not provided") + } + + outputDir = filepath.Dir(publicKeyFilePath) + // Derive the base name by removing "-cert.pub" + baseName := strings.TrimSuffix(filepath.Base(publicKeyFilePath), ".pub") + signedKeyPath = filepath.Join(outputDir, baseName + "-cert.pub") + } else { + // Expand ~ to home directory if present + if strings.HasPrefix(outFilePath, "~") { + homeDir, err := os.UserHomeDir() + if err != nil { + util.HandleError(err, "Failed to resolve home directory") + } + outFilePath = strings.Replace(outFilePath, "~", homeDir, 1) + } + + // Check if outFilePath ends with "-cert.pub" + if (!strings.HasSuffix(outFilePath, "-cert.pub")) { + util.PrintErrorMessageAndExit("--outFilePath must end with -cert.pub") + } + + // Extract the directory from outFilePath + outputDir = filepath.Dir(outFilePath) + + // Validate the output directory + info, err := os.Stat(outputDir) + if os.IsNotExist(err) { + // Directory does not exist; attempt to create it + err = os.MkdirAll(outputDir, 0755) + if err != nil { + util.HandleError(err, "Failed to create output directory") + } + } else if err != nil { + // Other errors accessing the directory + util.HandleError(err, "Failed to access output directory") + } else if !info.IsDir() { + // Path exists but is not a directory + util.PrintErrorMessageAndExit("The provided --outFilePath's directory is not valid") + } + + signedKeyPath = outFilePath + } + infisicalClient := infisicalSdk.NewInfisicalClient(context.Background(), infisicalSdk.Config{ SiteUrl: config.INFISICAL_URL, UserAgent: api.USER_AGENT, @@ -360,14 +485,20 @@ func signKey(cmd *cobra.Command, args []string) { util.HandleError(err, "To sign a SSH public key") } - fmt.Println(creds) + err = writeToFile(signedKeyPath, creds.SignedKey, 0644) + if err != nil { + util.HandleError(err, "Failed to write Signed Key to file") + } + + fmt.Println("Successfully wrote SSH certificate to:", signedKeyPath) } func init() { sshSignKeyCmd.Flags().String("projectId", "", "The projectId to issue credentials for") sshSignKeyCmd.Flags().String("templateName", "", "The template name to issue credentials for") sshSignKeyCmd.Flags().String("publicKey", "", "The public key to sign") - sshSignKeyCmd.Flags().String("publicKeyFilePath", "", "The path to the public key file to sign") + sshSignKeyCmd.Flags().String("publicKeyFilePath", "", "The file path to the public key file to sign") + sshSignKeyCmd.Flags().String("outFilePath", "", "The path to write the SSH certificate to such as ~/.ssh/id_rsa-cert.pub. If not provided, the credentials will be saved to the directory of the specified public key file path or the current working directory") sshSignKeyCmd.Flags().String("principals", "", "The principals that the certificate should be signed for") sshSignKeyCmd.Flags().String("certType", string(infisicalSdkUtil.UserCert), "The cert type for the created certificate") sshSignKeyCmd.Flags().String("ttl", "", "The ttl for the created certificate") @@ -381,7 +512,7 @@ func init() { sshIssueCredentialsCmd.Flags().String("certType", string(infisicalSdkUtil.UserCert), "The cert type to issue SSH credentials for") sshIssueCredentialsCmd.Flags().String("ttl", "", "The ttl to issue SSH credentials for") sshIssueCredentialsCmd.Flags().String("keyId", "", "The keyId to issue SSH credentials for") - sshIssueCredentialsCmd.Flags().String("outFilePath", "", "The path to the file to write the SSH credentials to. If not provided, the credentials will be saved to the current working directory") + sshIssueCredentialsCmd.Flags().String("outFilePath", "", "The path to write the SSH credentials to such as ~/.ssh, ./some_folder, ./some_folder/id_rsa-cert.pub. If not provided, the credentials will be saved to the current working directory") sshCmd.AddCommand(sshIssueCredentialsCmd) rootCmd.AddCommand(sshCmd) } \ No newline at end of file From 765280eef6982fa426af1a1b56efd4727d85f5b3 Mon Sep 17 00:00:00 2001 From: Tuan Dang Date: Tue, 17 Dec 2024 13:12:47 -0800 Subject: [PATCH 3/7] Update ssh cli issue/sign according to review --- cli/go.mod | 2 +- cli/go.sum | 2 + cli/packages/cmd/ssh.go | 438 ++++++++++++++++++++-------------------- 3 files changed, 227 insertions(+), 215 deletions(-) diff --git a/cli/go.mod b/cli/go.mod index d9adb8cc8..f279616bf 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.4.5 + github.com/infisical/go-sdk v0.4.6 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 5db12269c..4622661bd 100644 --- a/cli/go.sum +++ b/cli/go.sum @@ -269,6 +269,8 @@ github.com/infisical/go-sdk v0.4.3 h1:O5ZJ2eCBAZDE9PIAfBPq9Utb2CgQKrhmj9R0oFTRu4 github.com/infisical/go-sdk v0.4.3/go.mod h1:6fWzAwTPIoKU49mQ2Oxu+aFnJu9n7k2JcNrZjzhHM2M= github.com/infisical/go-sdk v0.4.5 h1:rQSgWW+thZQ2WfIbOD5SBflqx3BUUSNlvuDZdexXMFk= github.com/infisical/go-sdk v0.4.5/go.mod h1:6fWzAwTPIoKU49mQ2Oxu+aFnJu9n7k2JcNrZjzhHM2M= +github.com/infisical/go-sdk v0.4.6 h1:RXXcmucfO2T5dSzXao9h9pZVn+m2usuflVO45Lpoz/o= +github.com/infisical/go-sdk v0.4.6/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/cmd/ssh.go b/cli/packages/cmd/ssh.go index 090fcd48d..63160b8bb 100644 --- a/cli/packages/cmd/ssh.go +++ b/cli/packages/cmd/ssh.go @@ -44,6 +44,13 @@ var sshSignKeyCmd = &cobra.Command{ Run: signKey, } +var algoToFileName = map[infisicalSdkUtil.CertKeyAlgorithm]string{ + infisicalSdkUtil.RSA2048: "id_rsa_2048", + infisicalSdkUtil.RSA4096: "id_rsa_4096", + infisicalSdkUtil.ECDSAP256: "id_ecdsa_p256", + infisicalSdkUtil.ECDSAP384: "id_ecdsa_p384", +} + func isValidKeyAlgorithm(algo infisicalSdkUtil.CertKeyAlgorithm) bool { switch algo { case infisicalSdkUtil.RSA2048, infisicalSdkUtil.RSA4096, infisicalSdkUtil.ECDSAP256, infisicalSdkUtil.ECDSAP384: @@ -53,19 +60,6 @@ func isValidKeyAlgorithm(algo infisicalSdkUtil.CertKeyAlgorithm) bool { } } -func getFileName(algo infisicalSdkUtil.CertKeyAlgorithm) string { - switch algo { - case infisicalSdkUtil.RSA2048: - return "id_rsa_2048" - case infisicalSdkUtil.RSA4096: - return "id_rsa_4096" - case infisicalSdkUtil.ECDSAP256: - return "id_ecdsa_p256" - default: - return "id_ecdsa_p384" - } -} - func isValidCertType(certType infisicalSdkUtil.SshCertType) bool { switch certType { case infisicalSdkUtil.UserCert, infisicalSdkUtil.HostCert: @@ -76,48 +70,53 @@ func isValidCertType(certType infisicalSdkUtil.SshCertType) bool { } func writeToFile(filePath string, content string, perm os.FileMode) error { - // Ensure the directory exists - dir := filepath.Dir(filePath) - if err := os.MkdirAll(dir, 0755); err != nil { - return fmt.Errorf("failed to create directory %s: %w", dir, err) - } + // Ensure the directory exists + dir := filepath.Dir(filePath) + if err := os.MkdirAll(dir, 0755); err != nil { + return fmt.Errorf("failed to create directory %s: %w", dir, err) + } - // Write the content to the file - err := os.WriteFile(filePath, []byte(content), perm) - if err != nil { - return fmt.Errorf("failed to write to file %s: %w", filePath, err) - } + // Write the content to the file + err := os.WriteFile(filePath, []byte(content), perm) + if err != nil { + return fmt.Errorf("failed to write to file %s: %w", filePath, err) + } - return nil + return nil } func issueCredentials(cmd *cobra.Command, args []string) { + + token, err := util.GetInfisicalToken(cmd) + if err != nil { + util.HandleError(err, "Unable to parse flag") + } + var infisicalToken string - util.RequireLogin() - loggedInUserDetails, err := util.GetCurrentLoggedInUserDetails(true) - if err != nil { - util.HandleError(err, "Unable to authenticate") + + 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(true) + 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 } - if loggedInUserDetails.LoginExpired { - util.PrintErrorMessageAndExit("Your login session has expired, please run [infisical login] and try again") - } - infisicalToken = loggedInUserDetails.UserCredentials.JTWToken - - projectId, err := cmd.Flags().GetString("projectId") + certificateTemplateId, err := cmd.Flags().GetString("certificateTemplateId") if err != nil { util.HandleError(err, "Unable to parse flag") } - if projectId == "" { - util.PrintErrorMessageAndExit("You must set the --projectId flag") - } - - templateName, err := cmd.Flags().GetString("templateName") - if err != nil { - util.HandleError(err, "Unable to parse flag") - } - if templateName == "" { - util.PrintErrorMessageAndExit("You must set the --templateName flag") + if certificateTemplateId == "" { + util.PrintErrorMessageAndExit("You must set the --certificateTemplateId flag") } principalsStr, err := cmd.Flags().GetString("principals") @@ -125,6 +124,11 @@ func issueCredentials(cmd *cobra.Command, args []string) { util.HandleError(err, "Unable to parse flag") } + // Check if the input string is empty before splitting + if principalsStr == "" { + util.HandleError(fmt.Errorf("no principals provided"), "The 'principals' flag cannot be empty") + } + // Convert the comma-delimited string into a slice of strings principals := strings.Split(principalsStr, ",") for i, principal := range principals { @@ -137,112 +141,112 @@ func issueCredentials(cmd *cobra.Command, args []string) { } if !isValidKeyAlgorithm(infisicalSdkUtil.CertKeyAlgorithm(keyAlgorithm)) { - util.HandleError(fmt.Errorf("invalid keyAlgorithm: %s", keyAlgorithm), + util.HandleError(fmt.Errorf("invalid keyAlgorithm: %s", keyAlgorithm), "Valid values: RSA_2048, RSA_4096, EC_prime256v1, EC_secp384r1") } - + certType, err := cmd.Flags().GetString("certType") if err != nil { util.HandleError(err, "Unable to parse flag") } if !isValidCertType(infisicalSdkUtil.SshCertType(certType)) { - util.HandleError(fmt.Errorf("invalid certType: %s", certType), + util.HandleError(fmt.Errorf("invalid certType: %s", certType), "Valid values: user, host") } - + ttl, err := cmd.Flags().GetString("ttl") if err != nil { util.HandleError(err, "Unable to parse flag") } - + keyId, err := cmd.Flags().GetString("keyId") if err != nil { util.HandleError(err, "Unable to parse flag") } - + outFilePath, err := cmd.Flags().GetString("outFilePath") if err != nil { util.HandleError(err, "Unable to parse flag") } - + var ( - outputDir string + outputDir string privateKeyPath string - publicKeyPath string - signedKeyPath string + publicKeyPath string + signedKeyPath string ) - - if outFilePath == "" { - // Use current working directory - cwd, err := os.Getwd() - if err != nil { - util.HandleError(err, "Failed to get current working directory") - } - outputDir = cwd - } else { - // Expand ~ to home directory if present - if strings.HasPrefix(outFilePath, "~") { - homeDir, err := os.UserHomeDir() - if err != nil { - util.HandleError(err, "Failed to resolve home directory") - } - outFilePath = strings.Replace(outFilePath, "~", homeDir, 1) - } + + if outFilePath == "" { + // Use current working directory + cwd, err := os.Getwd() + if err != nil { + util.HandleError(err, "Failed to get current working directory") + } + outputDir = cwd + } else { + // Expand ~ to home directory if present + if strings.HasPrefix(outFilePath, "~") { + homeDir, err := os.UserHomeDir() + if err != nil { + util.HandleError(err, "Failed to resolve home directory") + } + outFilePath = strings.Replace(outFilePath, "~", homeDir, 1) + } // Check if outFilePath ends with "-cert.pub" - if strings.HasSuffix(outFilePath, "-cert.pub") { - // Treat outFilePath as the signed key path - signedKeyPath = outFilePath + if strings.HasSuffix(outFilePath, "-cert.pub") { + // Treat outFilePath as the signed key path + signedKeyPath = outFilePath - // Derive the base name by removing "-cert.pub" - baseName := strings.TrimSuffix(filepath.Base(outFilePath), "-cert.pub") + // Derive the base name by removing "-cert.pub" + baseName := strings.TrimSuffix(filepath.Base(outFilePath), "-cert.pub") - // Set the output directory - outputDir = filepath.Dir(outFilePath) + // Set the output directory + outputDir = filepath.Dir(outFilePath) - // Define private and public key paths - privateKeyPath = filepath.Join(outputDir, baseName) - publicKeyPath = filepath.Join(outputDir, baseName+".pub") - } else { - // Treat outFilePath as a directory - outputDir = outFilePath + // Define private and public key paths + privateKeyPath = filepath.Join(outputDir, baseName) + publicKeyPath = filepath.Join(outputDir, baseName+".pub") + } else { + // Treat outFilePath as a directory + outputDir = outFilePath - // Check if the directory exists; if not, create it - info, err := os.Stat(outputDir) - if os.IsNotExist(err) { - err = os.MkdirAll(outputDir, 0755) - if err != nil { - util.HandleError(err, "Failed to create output directory") - } - } else if err != nil { - util.HandleError(err, "Failed to access output directory") - } else if !info.IsDir() { - util.PrintErrorMessageAndExit("The provided --outFilePath is not a directory") - } + // Check if the directory exists; if not, create it + info, err := os.Stat(outputDir) + if os.IsNotExist(err) { + err = os.MkdirAll(outputDir, 0755) + if err != nil { + util.HandleError(err, "Failed to create output directory") + } + } else if err != nil { + util.HandleError(err, "Failed to access output directory") + } else if !info.IsDir() { + util.PrintErrorMessageAndExit("The provided --outFilePath is not a directory") + } + } + } - // Define file names based on key algorithm - fileName := getFileName(infisicalSdkUtil.CertKeyAlgorithm(keyAlgorithm)) + // Define file names based on key algorithm + fileName := algoToFileName[infisicalSdkUtil.CertKeyAlgorithm(keyAlgorithm)] - // Define file paths - privateKeyPath = filepath.Join(outputDir, fileName) - publicKeyPath = filepath.Join(outputDir, fileName+".pub") - signedKeyPath = filepath.Join(outputDir, fileName+"-cert.pub") - } - } + // Define file paths + privateKeyPath = filepath.Join(outputDir, fileName) + publicKeyPath = filepath.Join(outputDir, fileName+".pub") + signedKeyPath = filepath.Join(outputDir, fileName+"-cert.pub") // If outFilePath ends with "-cert.pub", ensure the signedKeyPath is set - if strings.HasSuffix(outFilePath, "-cert.pub") { - // Ensure the signedKeyPath was set - if signedKeyPath == "" { - util.HandleError(fmt.Errorf("signedKeyPath is not set correctly"), "Internal error") - } - } else { - // Ensure all paths are set - if privateKeyPath == "" || publicKeyPath == "" || signedKeyPath == "" { - util.HandleError(fmt.Errorf("file paths are not set correctly"), "Internal error") - } - } + if strings.HasSuffix(outFilePath, "-cert.pub") { + // Ensure the signedKeyPath was set + if signedKeyPath == "" { + util.HandleError(fmt.Errorf("signedKeyPath is not set correctly"), "Internal error") + } + } else { + // Ensure all paths are set + if privateKeyPath == "" || publicKeyPath == "" || signedKeyPath == "" { + util.HandleError(fmt.Errorf("file paths are not set correctly"), "Internal error") + } + } infisicalClient := infisicalSdk.NewInfisicalClient(context.Background(), infisicalSdk.Config{ SiteUrl: config.INFISICAL_URL, @@ -252,83 +256,87 @@ func issueCredentials(cmd *cobra.Command, args []string) { infisicalClient.Auth().SetAccessToken(infisicalToken) creds, err := infisicalClient.Ssh().IssueCredentials(infisicalSdk.IssueSshCredsOptions{ - ProjectID: projectId, - TemplateName: templateName, - Principals: principals, - KeyAlgorithm: infisicalSdkUtil.CertKeyAlgorithm(keyAlgorithm), - CertType: infisicalSdkUtil.SshCertType(certType), - TTL: ttl, - KeyID: keyId, + CertificateTemplateID: certificateTemplateId, + Principals: principals, + KeyAlgorithm: infisicalSdkUtil.CertKeyAlgorithm(keyAlgorithm), + CertType: infisicalSdkUtil.SshCertType(certType), + TTL: ttl, + KeyID: keyId, }) if err != nil { util.HandleError(err, "To issue SSH credentials") } - // If signedKeyPath wasn't set in the directory scenario, set it now - if signedKeyPath == "" { - fileName := getFileName(infisicalSdkUtil.CertKeyAlgorithm(keyAlgorithm)) - signedKeyPath = filepath.Join(outputDir, fileName + "-cert.pub") - } + // If signedKeyPath wasn't set in the directory scenario, set it now + if signedKeyPath == "" { + fileName := algoToFileName[infisicalSdkUtil.CertKeyAlgorithm(keyAlgorithm)] + signedKeyPath = filepath.Join(outputDir, fileName+"-cert.pub") + } - if privateKeyPath == "" { - privateKeyPath = filepath.Join(outputDir, getFileName(infisicalSdkUtil.CertKeyAlgorithm(keyAlgorithm))) - } - err = writeToFile(privateKeyPath, creds.PrivateKey, 0600) - if err != nil { - util.HandleError(err, "Failed to write Private Key to file") - } + if privateKeyPath == "" { + privateKeyPath = filepath.Join(outputDir, algoToFileName[infisicalSdkUtil.CertKeyAlgorithm(keyAlgorithm)]) + } + err = writeToFile(privateKeyPath, creds.PrivateKey, 0600) + if err != nil { + util.HandleError(err, "Failed to write Private Key to file") + } - if publicKeyPath == "" { - publicKeyPath = privateKeyPath + ".pub" - } - err = writeToFile(publicKeyPath, creds.PublicKey, 0644) - if err != nil { - util.HandleError(err, "Failed to write Public Key to file") - } + if publicKeyPath == "" { + publicKeyPath = privateKeyPath + ".pub" + } + err = writeToFile(publicKeyPath, creds.PublicKey, 0644) + if err != nil { + util.HandleError(err, "Failed to write Public Key to file") + } - err = writeToFile(signedKeyPath, creds.SignedKey, 0644) - if err != nil { - util.HandleError(err, "Failed to write Signed Key to file") - } + err = writeToFile(signedKeyPath, creds.SignedKey, 0644) + if err != nil { + util.HandleError(err, "Failed to write Signed Key to file") + } - fmt.Println("Successfully wrote SSH certificate tox:", signedKeyPath) + fmt.Println("Successfully wrote SSH certificate to:", signedKeyPath) } func signKey(cmd *cobra.Command, args []string) { + + token, err := util.GetInfisicalToken(cmd) + if err != nil { + util.HandleError(err, "Unable to parse flag") + } + var infisicalToken string - util.RequireLogin() - loggedInUserDetails, err := util.GetCurrentLoggedInUserDetails(true) - if err != nil { - util.HandleError(err, "Unable to authenticate") + + 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(true) + 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 } - if loggedInUserDetails.LoginExpired { - util.PrintErrorMessageAndExit("Your login session has expired, please run [infisical login] and try again") - } - infisicalToken = loggedInUserDetails.UserCredentials.JTWToken - - projectId, err := cmd.Flags().GetString("projectId") + certificateTemplateId, err := cmd.Flags().GetString("certificateTemplateId") if err != nil { util.HandleError(err, "Unable to parse flag") } - if projectId == "" { - util.PrintErrorMessageAndExit("You must set the --projectId flag") + if certificateTemplateId == "" { + util.PrintErrorMessageAndExit("You must set the --certificateTemplateId flag") } - templateName, err := cmd.Flags().GetString("templateName") - if err != nil { - util.HandleError(err, "Unable to parse flag") - } - if templateName == "" { - util.PrintErrorMessageAndExit("You must set the --templateName flag") - } - publicKey, err := cmd.Flags().GetString("publicKey") if err != nil { util.HandleError(err, "Unable to parse flag") } - + publicKeyFilePath, err := cmd.Flags().GetString("publicKeyFilePath") if err != nil { util.HandleError(err, "Unable to parse flag") @@ -356,7 +364,7 @@ func signKey(cmd *cobra.Command, args []string) { if !strings.HasSuffix(publicKeyFilePath, ".pub") { util.HandleError(fmt.Errorf("public key file must have a .pub extension"), "Invalid input") } - + content, err := os.ReadFile(publicKeyFilePath) if err != nil { util.HandleError(err, "Failed to read public key file") @@ -365,7 +373,7 @@ func signKey(cmd *cobra.Command, args []string) { publicKey = strings.TrimSpace(string(content)) } - if (strings.TrimSpace(publicKey) == "") { + if strings.TrimSpace(publicKey) == "" { util.HandleError(fmt.Errorf("Public key is empty"), "Invalid input") } @@ -374,29 +382,32 @@ func signKey(cmd *cobra.Command, args []string) { util.HandleError(err, "Unable to parse flag") } + // Check if the input string is empty before splitting + if principalsStr == "" { + util.HandleError(fmt.Errorf("no principals provided"), "The 'principals' flag cannot be empty") + } + // Convert the comma-delimited string into a slice of strings principals := strings.Split(principalsStr, ",") - - // Trim whitespace around each principal for i, principal := range principals { principals[i] = strings.TrimSpace(principal) } - + certType, err := cmd.Flags().GetString("certType") if err != nil { util.HandleError(err, "Unable to parse flag") } if !isValidCertType(infisicalSdkUtil.SshCertType(certType)) { - util.HandleError(fmt.Errorf("invalid certType: %s", certType), + util.HandleError(fmt.Errorf("invalid certType: %s", certType), "Valid values: user, host") } - + ttl, err := cmd.Flags().GetString("ttl") if err != nil { util.HandleError(err, "Unable to parse flag") } - + keyId, err := cmd.Flags().GetString("keyId") if err != nil { util.HandleError(err, "Unable to parse flag") @@ -406,39 +417,39 @@ func signKey(cmd *cobra.Command, args []string) { if err != nil { util.HandleError(err, "Unable to parse flag") } - + var ( - outputDir string + outputDir string signedKeyPath string ) if outFilePath == "" { - // Use current working directory - if err != nil { - util.HandleError(err, "Failed to get current working directory") - } - - // check if public key path exists - if (publicKeyFilePath == "") { - util.PrintErrorMessageAndExit("--outFilePath must be specified when --publicKeyFilePath is not provided") + // Use current working directory + if err != nil { + util.HandleError(err, "Failed to get current working directory") } - + + // check if public key path exists + if publicKeyFilePath == "" { + util.PrintErrorMessageAndExit("--outFilePath must be specified when --publicKeyFilePath is not provided") + } + outputDir = filepath.Dir(publicKeyFilePath) // Derive the base name by removing "-cert.pub" baseName := strings.TrimSuffix(filepath.Base(publicKeyFilePath), ".pub") - signedKeyPath = filepath.Join(outputDir, baseName + "-cert.pub") - } else { + signedKeyPath = filepath.Join(outputDir, baseName+"-cert.pub") + } else { // Expand ~ to home directory if present - if strings.HasPrefix(outFilePath, "~") { - homeDir, err := os.UserHomeDir() - if err != nil { - util.HandleError(err, "Failed to resolve home directory") - } - outFilePath = strings.Replace(outFilePath, "~", homeDir, 1) - } - + if strings.HasPrefix(outFilePath, "~") { + homeDir, err := os.UserHomeDir() + if err != nil { + util.HandleError(err, "Failed to resolve home directory") + } + outFilePath = strings.Replace(outFilePath, "~", homeDir, 1) + } + // Check if outFilePath ends with "-cert.pub" - if (!strings.HasSuffix(outFilePath, "-cert.pub")) { + if !strings.HasSuffix(outFilePath, "-cert.pub") { util.PrintErrorMessageAndExit("--outFilePath must end with -cert.pub") } @@ -472,13 +483,12 @@ func signKey(cmd *cobra.Command, args []string) { infisicalClient.Auth().SetAccessToken(infisicalToken) creds, err := infisicalClient.Ssh().SignKey(infisicalSdk.SignSshPublicKeyOptions{ - ProjectID: projectId, - TemplateName: templateName, - PublicKey: publicKey, - Principals: principals, - CertType: infisicalSdkUtil.SshCertType(certType), - TTL: ttl, - KeyID: keyId, + CertificateTemplateID: certificateTemplateId, + PublicKey: publicKey, + Principals: principals, + CertType: infisicalSdkUtil.SshCertType(certType), + TTL: ttl, + KeyID: keyId, }) if err != nil { @@ -486,16 +496,16 @@ func signKey(cmd *cobra.Command, args []string) { } err = writeToFile(signedKeyPath, creds.SignedKey, 0644) - if err != nil { - util.HandleError(err, "Failed to write Signed Key to file") - } - - fmt.Println("Successfully wrote SSH certificate to:", signedKeyPath) + if err != nil { + util.HandleError(err, "Failed to write Signed Key to file") + } + + fmt.Println("Successfully wrote SSH certificate to:", signedKeyPath) } func init() { - sshSignKeyCmd.Flags().String("projectId", "", "The projectId to issue credentials for") - sshSignKeyCmd.Flags().String("templateName", "", "The template name to issue credentials for") + sshSignKeyCmd.Flags().String("token", "", "Issue SSH certificate using machine identity access token") + sshSignKeyCmd.Flags().String("certificateTemplateId", "", "The ID of the SSH certificate template to issue the SSH certificate for") sshSignKeyCmd.Flags().String("publicKey", "", "The public key to sign") sshSignKeyCmd.Flags().String("publicKeyFilePath", "", "The file path to the public key file to sign") sshSignKeyCmd.Flags().String("outFilePath", "", "The path to write the SSH certificate to such as ~/.ssh/id_rsa-cert.pub. If not provided, the credentials will be saved to the directory of the specified public key file path or the current working directory") @@ -505,8 +515,8 @@ func init() { sshSignKeyCmd.Flags().String("keyId", "", "The keyId that the created certificate should have") sshCmd.AddCommand(sshSignKeyCmd) - sshIssueCredentialsCmd.Flags().String("projectId", "", "The projectId to issue SSH credentials for") - sshIssueCredentialsCmd.Flags().String("templateName", "", "The template name to issue SSH credentials for") + sshIssueCredentialsCmd.Flags().String("token", "", "Issue SSH credentials using machine identity access token") + sshIssueCredentialsCmd.Flags().String("certificateTemplateId", "", "The ID of the SSH certificate template to issue SSH credentials for") sshIssueCredentialsCmd.Flags().String("principals", "", "The principals to issue SSH credentials for") sshIssueCredentialsCmd.Flags().String("keyAlgorithm", string(infisicalSdkUtil.RSA2048), "The key algorithm to issue SSH credentials for") sshIssueCredentialsCmd.Flags().String("certType", string(infisicalSdkUtil.UserCert), "The cert type to issue SSH credentials for") @@ -515,4 +525,4 @@ func init() { sshIssueCredentialsCmd.Flags().String("outFilePath", "", "The path to write the SSH credentials to such as ~/.ssh, ./some_folder, ./some_folder/id_rsa-cert.pub. If not provided, the credentials will be saved to the current working directory") sshCmd.AddCommand(sshIssueCredentialsCmd) rootCmd.AddCommand(sshCmd) -} \ No newline at end of file +} From 31a21a432d0fad9a5e6dd61b2c265f2beddb6adf Mon Sep 17 00:00:00 2001 From: Tuan Dang Date: Tue, 17 Dec 2024 20:40:03 -0800 Subject: [PATCH 4/7] Update isValidKeyAlgorithm impl --- cli/packages/cmd/ssh.go | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/cli/packages/cmd/ssh.go b/cli/packages/cmd/ssh.go index 63160b8bb..d611e3b8d 100644 --- a/cli/packages/cmd/ssh.go +++ b/cli/packages/cmd/ssh.go @@ -52,12 +52,8 @@ var algoToFileName = map[infisicalSdkUtil.CertKeyAlgorithm]string{ } func isValidKeyAlgorithm(algo infisicalSdkUtil.CertKeyAlgorithm) bool { - switch algo { - case infisicalSdkUtil.RSA2048, infisicalSdkUtil.RSA4096, infisicalSdkUtil.ECDSAP256, infisicalSdkUtil.ECDSAP384: - return true - default: - return false - } + _, exists := algoToFileName[algo] + return exists } func isValidCertType(certType infisicalSdkUtil.SshCertType) bool { From b4b417658fb1cd70962dcee5a0a27d0e26f31ebb Mon Sep 17 00:00:00 2001 From: Tuan Dang Date: Wed, 18 Dec 2024 09:22:06 -0800 Subject: [PATCH 5/7] Update ssh cli api error messages --- cli/packages/cmd/ssh.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cli/packages/cmd/ssh.go b/cli/packages/cmd/ssh.go index d611e3b8d..b97d6572f 100644 --- a/cli/packages/cmd/ssh.go +++ b/cli/packages/cmd/ssh.go @@ -261,7 +261,7 @@ func issueCredentials(cmd *cobra.Command, args []string) { }) if err != nil { - util.HandleError(err, "To issue SSH credentials") + util.HandleError(err, "Failed to issue SSH credentials") } // If signedKeyPath wasn't set in the directory scenario, set it now @@ -488,7 +488,7 @@ func signKey(cmd *cobra.Command, args []string) { }) if err != nil { - util.HandleError(err, "To sign a SSH public key") + util.HandleError(err, "Failed to sign SSH public key") } err = writeToFile(signedKeyPath, creds.SignedKey, 0644) From ee4eb7f84b5cb9dfd7889deb6ed8f89057c338e9 Mon Sep 17 00:00:00 2001 From: Tuan Dang Date: Wed, 18 Dec 2024 11:32:09 -0800 Subject: [PATCH 6/7] Update Go SDK version dependency --- cli/go.mod | 2 +- cli/go.sum | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/cli/go.mod b/cli/go.mod index f279616bf..82d529da4 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.4.6 + github.com/infisical/go-sdk v0.4.7 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 4622661bd..add85df20 100644 --- a/cli/go.sum +++ b/cli/go.sum @@ -271,6 +271,8 @@ github.com/infisical/go-sdk v0.4.5 h1:rQSgWW+thZQ2WfIbOD5SBflqx3BUUSNlvuDZdexXMF github.com/infisical/go-sdk v0.4.5/go.mod h1:6fWzAwTPIoKU49mQ2Oxu+aFnJu9n7k2JcNrZjzhHM2M= github.com/infisical/go-sdk v0.4.6 h1:RXXcmucfO2T5dSzXao9h9pZVn+m2usuflVO45Lpoz/o= github.com/infisical/go-sdk v0.4.6/go.mod h1:6fWzAwTPIoKU49mQ2Oxu+aFnJu9n7k2JcNrZjzhHM2M= +github.com/infisical/go-sdk v0.4.7 h1:+cxIdDfciMh0Syxbxbqjhvz9/ShnN1equ2zqlVQYGtw= +github.com/infisical/go-sdk v0.4.7/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= From 058475fc3f872dee2deae81b0f2fa8d740bf1b40 Mon Sep 17 00:00:00 2001 From: Tuan Dang Date: Wed, 18 Dec 2024 11:45:09 -0800 Subject: [PATCH 7/7] Ran go mod tidy --- cli/go.sum | 6 ------ 1 file changed, 6 deletions(-) diff --git a/cli/go.sum b/cli/go.sum index add85df20..be90a0f2f 100644 --- a/cli/go.sum +++ b/cli/go.sum @@ -265,12 +265,6 @@ 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.4.3 h1:O5ZJ2eCBAZDE9PIAfBPq9Utb2CgQKrhmj9R0oFTRu4U= -github.com/infisical/go-sdk v0.4.3/go.mod h1:6fWzAwTPIoKU49mQ2Oxu+aFnJu9n7k2JcNrZjzhHM2M= -github.com/infisical/go-sdk v0.4.5 h1:rQSgWW+thZQ2WfIbOD5SBflqx3BUUSNlvuDZdexXMFk= -github.com/infisical/go-sdk v0.4.5/go.mod h1:6fWzAwTPIoKU49mQ2Oxu+aFnJu9n7k2JcNrZjzhHM2M= -github.com/infisical/go-sdk v0.4.6 h1:RXXcmucfO2T5dSzXao9h9pZVn+m2usuflVO45Lpoz/o= -github.com/infisical/go-sdk v0.4.6/go.mod h1:6fWzAwTPIoKU49mQ2Oxu+aFnJu9n7k2JcNrZjzhHM2M= github.com/infisical/go-sdk v0.4.7 h1:+cxIdDfciMh0Syxbxbqjhvz9/ShnN1equ2zqlVQYGtw= github.com/infisical/go-sdk v0.4.7/go.mod h1:6fWzAwTPIoKU49mQ2Oxu+aFnJu9n7k2JcNrZjzhHM2M= github.com/jedib0t/go-pretty v4.3.0+incompatible h1:CGs8AVhEKg/n9YbUenWmNStRW2PHJzaeDodcfvRAbIo=