diff --git a/packages/api/model.go b/packages/api/model.go index b96ba3fd..61d39dce 100644 --- a/packages/api/model.go +++ b/packages/api/model.go @@ -862,8 +862,9 @@ type PAMAccessRequest struct { MfaSessionId string `json:"mfaSessionId,omitempty"` // Common fields - Duration string `json:"duration,omitempty"` - Reason string `json:"reason,omitempty"` + Duration string `json:"duration,omitempty"` + Reason string `json:"reason,omitempty"` + TargetHost string `json:"targetHost,omitempty"` } type PAMAccessResponse struct { diff --git a/packages/cmd/pam.go b/packages/cmd/pam.go index d40d4b6b..3f3f0bf9 100644 --- a/packages/cmd/pam.go +++ b/packages/cmd/pam.go @@ -49,6 +49,11 @@ The path format is: /folder/account-name (leading slash optional)`, util.HandleError(err, "Unable to parse port flag") } + targetHost, err := cmd.Flags().GetString("target") + if err != nil { + util.HandleError(err, "Unable to parse target flag") + } + loggedInUserDetails, err := util.GetCurrentLoggedInUserDetails(true) if err != nil { util.HandleError(err, "Unable to get logged in user details") @@ -59,7 +64,7 @@ The path format is: /folder/account-name (leading slash optional)`, loggedInUserDetails = util.EstablishUserLoginSession() } - pam.StartPAMAccess(loggedInUserDetails.UserCredentials.JTWToken, path, reason, durationStr, port) + pam.StartPAMAccess(loggedInUserDetails.UserCredentials.JTWToken, path, reason, durationStr, targetHost, port) }, } @@ -67,6 +72,7 @@ func init() { pamAccessCmd.Flags().String("reason", "", "Reason for accessing the account (stored for audit purposes)") pamAccessCmd.Flags().String("duration", "1h", "Duration for access session (e.g., '1h', '30m', '2h30m')") pamAccessCmd.Flags().Int("port", 0, "Port for the local proxy server (0 for auto-assign)") + pamAccessCmd.Flags().String("target", "", "Target host to connect to (for accounts that allow multiple hosts, e.g. Windows AD)") pamCmd.AddCommand(pamAccessCmd) RootCmd.AddCommand(pamCmd) diff --git a/packages/pam/local/access.go b/packages/pam/local/access.go index 41284164..b37972d3 100644 --- a/packages/pam/local/access.go +++ b/packages/pam/local/access.go @@ -17,17 +17,17 @@ import ( // Account type constants (match API enum) const ( - AccountTypePostgres = "postgres" - AccountTypeSSH = "ssh" - AccountTypeMySQL = "mysql" - AccountTypeMsSQL = "mssql" - AccountTypeMongoDB = "mongodb" - AccountTypeOracleDB = "oracledb" - AccountTypeRedis = "redis" - AccountTypeKubernetes = "kubernetes" - AccountTypeAwsIam = "aws-iam" - AccountTypeWindows = "windows" - AccountTypeActiveDirectory = "active-directory" + AccountTypePostgres = "postgres" + AccountTypeSSH = "ssh" + AccountTypeMySQL = "mysql" + AccountTypeMsSQL = "mssql" + AccountTypeMongoDB = "mongodb" + AccountTypeOracleDB = "oracledb" + AccountTypeRedis = "redis" + AccountTypeKubernetes = "kubernetes" + AccountTypeAwsIam = "aws-iam" + AccountTypeWindows = "windows" + AccountTypeWindowsAd = "windows-ad" ) // normalizePath ensures the path has a leading slash for display purposes. @@ -53,7 +53,7 @@ func parsePath(path string) (folder, account string) { // StartPAMAccess initiates a PAM session for the account at the given path. // The account type is determined from the API response and routed to the appropriate handler. -func StartPAMAccess(accessToken, path, reason, durationStr string, port int) { +func StartPAMAccess(accessToken, path, reason, durationStr, targetHost string, port int) { // Normalize path for display (ensure leading slash) displayPath := normalizePath(path) @@ -65,9 +65,10 @@ func StartPAMAccess(accessToken, path, reason, durationStr string, port int) { httpClient.SetHeader("User-Agent", api.USER_AGENT) pamResponse, err := CallPAMAccessWithMFA(httpClient, api.PAMAccessRequest{ - Path: path, - Duration: durationStr, - Reason: reason, + Path: path, + Duration: durationStr, + Reason: reason, + TargetHost: targetHost, }, true) if err != nil { util.HandleError(err, "Failed to create PAM session") @@ -91,10 +92,8 @@ func StartPAMAccess(accessToken, path, reason, durationStr string, port int) { startKubernetesProxy(httpClient, &pamResponse, displayPath, durationStr, port) case AccountTypeAwsIam: util.PrintErrorMessageAndExit("AWS IAM access not yet supported in the new PAM model") - case AccountTypeWindows: + case AccountTypeWindows, AccountTypeWindowsAd: startRDPProxy(httpClient, &pamResponse, displayPath, durationStr, port) - case AccountTypeActiveDirectory: - util.PrintErrorMessageAndExit("Active Directory access not yet supported in the new PAM model") default: util.PrintErrorMessageAndExit(fmt.Sprintf("Unsupported account type: %s", pamResponse.AccountType)) } @@ -276,10 +275,11 @@ func startRDPProxy(httpClient *resty.Client, response *api.PAMAccessResponse, pa gatewayServerCertChain: response.GatewayServerCertificateChain, sessionExpiry: time.Now().Add(duration), sessionId: response.SessionId, - resourceType: response.AccountType, - ctx: ctx, - cancel: cancel, - shutdownCh: make(chan struct{}), + // Windows AD is brokered through the Windows RDP gateway protocol + resourceType: AccountTypeWindows, + ctx: ctx, + cancel: cancel, + shutdownCh: make(chan struct{}), }, }