Handle browser gateway push pam auth

Former-commit-id: 1ba9a419cb
This commit is contained in:
Owen
2026-06-04 11:28:20 -07:00
parent e5bf31d118
commit 8eddb908e0
7 changed files with 91 additions and 14 deletions
+7
View File
@@ -7,6 +7,8 @@ import (
"net/http"
"strings"
"sync"
"github.com/fosrl/newt/nativessh"
)
// Forwarding buffer size. RDP graphics traffic is bursty and TLS records cap
@@ -36,6 +38,9 @@ type Config struct {
// to match against). For all proxy targets (RDP/SSH/VNC), auth tokens are
// stored per-Target and validated by isAllowed.
AuthToken string
// SSHCredentials are used by native SSH browser sessions for certificate
// validation against the in-memory CA/principal store.
SSHCredentials *nativessh.CredentialStore
}
// Gateway is a browser-based RDP/SSH/VNC WebSocket proxy.
@@ -43,6 +48,7 @@ type Config struct {
// HandleRDP / HandleSSH / HandleVNC http.HandlerFunc methods.
type Gateway struct {
authToken string
sshCreds *nativessh.CredentialStore
mu sync.RWMutex
targets map[int]Target // keyed by Target.ID
@@ -54,6 +60,7 @@ type Gateway struct {
func New(cfg Config) *Gateway {
return &Gateway{
authToken: cfg.AuthToken,
sshCreds: cfg.SSHCredentials,
targets: make(map[int]Target),
}
}
+2 -1
View File
@@ -21,6 +21,7 @@ type sshClientMsg struct {
Type string `json:"type"`
Password string `json:"password,omitempty"` // used when type="auth"
PrivateKey string `json:"privateKey,omitempty"` // used when type="auth"
Certificate string `json:"certificate,omitempty"` // used when type="auth"
Data string `json:"data,omitempty"` // used when type="data"
Cols uint32 `json:"cols,omitempty"` // used when type="resize"
Rows uint32 `json:"rows,omitempty"` // used when type="resize"
@@ -89,7 +90,7 @@ func (g *Gateway) HandleSSH(w http.ResponseWriter, r *http.Request) {
defer ws.CloseNow() //nolint:errcheck
if nativeSSH {
if err := serveNativeSSHSession(ctx, ws, username); err != nil {
if err := serveNativeSSHSession(ctx, ws, username, g.sshCreds); err != nil {
log.Printf("SSH native session error: %v", err)
}
} else {
+2 -2
View File
@@ -17,7 +17,7 @@ import (
// The auth frame from the browser must be a JSON sshClientMsg with type="auth"
// carrying the same password/privateKey fields used by the proxy SSH path.
// The target username is passed in from the HTTP layer (query param).
func serveNativeSSHSession(ctx context.Context, ws *websocket.Conn, username string) error {
func serveNativeSSHSession(ctx context.Context, ws *websocket.Conn, username string, creds *nativessh.CredentialStore) error {
// Read the auth frame.
_, authBytes, err := ws.Read(ctx)
if err != nil {
@@ -29,7 +29,7 @@ func serveNativeSSHSession(ctx context.Context, ws *websocket.Conn, username str
}
// Authenticate using host authorized_keys or PAM password.
if err := nativessh.Authenticate(username, authMsg.Password, authMsg.PrivateKey); err != nil {
if err := nativessh.AuthenticateWithCertificate(creds, username, authMsg.Password, authMsg.PrivateKey, authMsg.Certificate); err != nil {
sendSSHError(ctx, ws, "Authentication failed")
return fmt.Errorf("auth for user %q: %w", username, err)
}