@@ -108,66 +108,113 @@ var pamSshCmd = &cobra.Command{
108108
109109var pamSshAccessCmd = &cobra.Command{
110110 Use: "access",
111- Short: "Start SSH session to PAM account",
112- Long: "Start an SSH session to a PAM-managed SSH account. This command automatically launches an SSH client connected through the Infisical Gateway.",
113- Example: "infisical pam ssh access --resource prod-servers --account root --project-id b38bef10-2685-43c4-9a2c-635206d60bec -- duration 1h",
111+ Short: "Start interactive SSH session to PAM account",
112+ Long: "Start an interactive SSH session to a PAM-managed SSH account. This command automatically launches an SSH client connected through the Infisical Gateway.",
113+ Example: "infisical pam ssh access --resource prod-servers --account root --duration 1h",
114114 DisableFlagsInUseLine: true,
115115 Args: cobra.NoArgs,
116116 Run: func(cmd *cobra.Command, args []string) {
117- util.RequireLogin()
117+ runSSHCommand(cmd, args, pam.SSHAccessOptions{})
118+ },
119+ }
118120
119- resourceName, _ := cmd.Flags().GetString("resource")
120- accountName, _ := cmd.Flags().GetString("account")
121+ var pamSshExecCmd = &cobra.Command{
122+ Use: "exec [command]",
123+ Short: "Execute a command on a PAM SSH account",
124+ Long: `Execute a single command on a PAM-managed SSH account and return the output.
125+ This is useful for CI/CD pipelines and scripting where interactive sessions are not needed.`,
126+ Example: ` # Run a command and get output
127+ infisical pam ssh exec "ls -la /var/log" --resource prod-servers --account root
121128
122- if resourceName == "" || accountName == "" {
123- util.PrintErrorMessageAndExit("Both --resource and --account flags are required")
124- }
129+ # Use in a script
130+ OUTPUT=$(infisical pam ssh exec "cat /etc/hostname" --resource prod-servers --account root)`,
131+ DisableFlagsInUseLine: true,
132+ Args: cobra.ExactArgs(1),
133+ Run: func(cmd *cobra.Command, args []string) {
134+ runSSHCommand(cmd, args, pam.SSHAccessOptions{
135+ ExecCommand: args[0],
136+ })
137+ },
138+ }
125139
126- durationStr, err := cmd.Flags().GetString("duration")
127- if err != nil {
128- util.HandleError(err, "Unable to parse duration flag")
129- }
140+ var pamSshProxyCmd = &cobra.Command{
141+ Use: "proxy",
142+ Short: "Start SSH proxy for SCP, SFTP, or rsync",
143+ Long: `Start an SSH proxy without launching an interactive session.
144+ This is useful for file transfers using SCP, SFTP, rsync, or other SSH-based tools.
145+ The proxy prints connection details and waits until terminated with Ctrl+C.`,
146+ Example: ` # Start the proxy
147+ infisical pam ssh proxy --resource prod-servers --account root
130148
131- _, err = time.ParseDuration(durationStr)
132- if err != nil {
133- util.HandleError(err, "Invalid duration format. Use formats like '1h', '30m', '2h30m'")
134- }
149+ # Then in another terminal, use SCP:
150+ scp -P <port> -o StrictHostKeyChecking=no local-file.txt root@127.0.0.1:/remote/path/
135151
136- projectID, err := cmd.Flags().GetString("project-id")
137- if err != nil {
138- util.HandleError(err, "Unable to parse project-id flag")
139- }
152+ # Or use rsync:
153+ rsync -e "ssh -p <port> -o StrictHostKeyChecking=no" local-dir/ root@127.0.0.1:/remote/path/`,
154+ DisableFlagsInUseLine: true,
155+ Args: cobra.NoArgs,
156+ Run: func(cmd *cobra.Command, args []string) {
157+ runSSHCommand(cmd, args, pam.SSHAccessOptions{
158+ ProxyOnly: true,
159+ })
160+ },
161+ }
140162
141- if projectID == "" {
142- workspaceFile, err := util.GetWorkSpaceFromFile()
143- if err != nil {
144- util.PrintErrorMessageAndExit("Please either run infisical init to connect to a project or pass in project id with --project-id flag")
145- }
146- projectID = workspaceFile.WorkspaceId
147- }
163+ // runSSHCommand is the shared implementation for all SSH subcommands
164+ func runSSHCommand(cmd *cobra.Command, args []string, options pam.SSHAccessOptions) {
165+ util.RequireLogin()
148166
149- log.Debug().Msg("PAM SSH Access: Trying to fetch credentials using logged in details")
167+ resourceName, _ := cmd.Flags().GetString("resource")
168+ accountName, _ := cmd.Flags().GetString("account")
150169
151- loggedInUserDetails, err := util.GetCurrentLoggedInUserDetails(true)
152- isConnected := util.ValidateInfisicalAPIConnection()
170+ if resourceName == "" || accountName == "" {
171+ util.PrintErrorMessageAndExit("Both --resource and --account flags are required")
172+ }
153173
154- if isConnected {
155- log.Debug().Msg("PAM SSH Access: Connected to Infisical instance, checking logged in creds")
156- }
174+ durationStr, err := cmd.Flags().GetString("duration")
175+ if err != nil {
176+ util.HandleError(err, "Unable to parse duration flag")
177+ }
178+
179+ _, err = time.ParseDuration(durationStr)
180+ if err != nil {
181+ util.HandleError(err, "Invalid duration format. Use formats like '1h', '30m', '2h30m'")
182+ }
183+
184+ projectID, err := cmd.Flags().GetString("project-id")
185+ if err != nil {
186+ util.HandleError(err, "Unable to parse project-id flag")
187+ }
157188
189+ if projectID == "" {
190+ workspaceFile, err := util.GetWorkSpaceFromFile()
158191 if err != nil {
159- util.HandleError(err, "Unable to get logged in user details ")
192+ util.PrintErrorMessageAndExit("Please either run infisical init to connect to a project or pass in project id with --project-id flag ")
160193 }
194+ projectID = workspaceFile.WorkspaceId
195+ }
161196
162- if isConnected && loggedInUserDetails.LoginExpired {
163- loggedInUserDetails = util.EstablishUserLoginSession()
164- }
197+ log.Debug().Msg("PAM SSH: Trying to fetch credentials using logged in details")
165198
166- pam.StartSSHLocalProxy(loggedInUserDetails.UserCredentials.JTWToken, pam.PAMAccessParams{
167- ResourceName: resourceName,
168- AccountName: accountName,
169- }, projectID, durationStr)
170- },
199+ loggedInUserDetails, err := util.GetCurrentLoggedInUserDetails(true)
200+ isConnected := util.ValidateInfisicalAPIConnection()
201+
202+ if isConnected {
203+ log.Debug().Msg("PAM SSH: Connected to Infisical instance, checking logged in creds")
204+ }
205+
206+ if err != nil {
207+ util.HandleError(err, "Unable to get logged in user details")
208+ }
209+
210+ if isConnected && loggedInUserDetails.LoginExpired {
211+ loggedInUserDetails = util.EstablishUserLoginSession()
212+ }
213+
214+ pam.StartSSHLocalProxy(loggedInUserDetails.UserCredentials.JTWToken, pam.PAMAccessParams{
215+ ResourceName: resourceName,
216+ AccountName: accountName,
217+ }, projectID, durationStr, options)
171218}
172219
173220// ==================== Kubernetes Commands ====================
@@ -340,14 +387,24 @@ func init() {
340387 pamDbAccessCmd.MarkFlagRequired("resource")
341388 pamDbAccessCmd.MarkFlagRequired("account")
342389
343- // SSH commands
390+ // SSH commands - shared flags helper
391+ addSSHFlags := func(cmd *cobra.Command) {
392+ cmd.Flags().String("resource", "", "Name of the PAM resource to access")
393+ cmd.Flags().String("account", "", "Name of the account within the resource")
394+ cmd.Flags().String("duration", "1h", "Duration for SSH access session (e.g., '1h', '30m', '2h30m')")
395+ cmd.Flags().String("project-id", "", "Project ID of the account to access")
396+ cmd.MarkFlagRequired("resource")
397+ cmd.MarkFlagRequired("account")
398+ }
399+
344400 pamSshCmd.AddCommand(pamSshAccessCmd)
345- pamSshAccessCmd.Flags().String("resource", "", "Name of the PAM resource to access")
346- pamSshAccessCmd.Flags().String("account", "", "Name of the account within the resource")
347- pamSshAccessCmd.Flags().String("duration", "1h", "Duration for SSH access session (e.g., '1h', '30m', '2h30m')")
348- pamSshAccessCmd.Flags().String("project-id", "", "Project ID of the account to access")
349- pamSshAccessCmd.MarkFlagRequired("resource")
350- pamSshAccessCmd.MarkFlagRequired("account")
401+ addSSHFlags(pamSshAccessCmd)
402+
403+ pamSshCmd.AddCommand(pamSshExecCmd)
404+ addSSHFlags(pamSshExecCmd)
405+
406+ pamSshCmd.AddCommand(pamSshProxyCmd)
407+ addSSHFlags(pamSshProxyCmd)
351408
352409 // Kubernetes commands
353410 pamKubernetesCmd.AddCommand(pamKubernetesAccessCmd)
0 commit comments