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