diff --git a/config/config.yaml b/config/config.yaml index d47f456..96e5ee4 100644 --- a/config/config.yaml +++ b/config/config.yaml @@ -42,6 +42,7 @@ server: recording: recordShell: true recordExec: false + recordSFTP: false recordDirectTCPIP: true nats: @@ -56,11 +57,19 @@ identity: # caCertPath: /opt/shared/auth/ca.crt # serverName: identity.k8shell-test.svc +# authz service - leave address empty to disable authorization checks +authz: + # address: authz.k8shell-staging:9040 + # tokenFilePath: /opt/shared/auth/api-server/authz-token + # caCertPath: /opt/shared/auth/ca.crt + # serverName: authz.k8shell-staging.svc + session: address: session.k8shell-staging:9010 tokenFilePath: /opt/shared/auth/api-server/session-token # caCertPath: /opt/shared/auth/ca.crt # serverName: session.k8shell-test.svc + provisioner: address: 10.42.1.229:9030 #provisioner.k8shell-test:9030 # 10.42.2.194:9030 tokenFilePath: /opt/shared/auth/api-server/provisioner-token diff --git a/go.mod b/go.mod index a05ef4c..e405632 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/k8shell-io/ssh-proxy go 1.24.5 require ( - github.com/k8shell-io/common v0.21.0 + github.com/k8shell-io/common v0.29.4 github.com/nats-io/nats.go v1.47.0 github.com/rs/zerolog v1.34.0 golang.org/x/crypto v0.43.0 diff --git a/go.sum b/go.sum index e5a1868..d77edb3 100644 --- a/go.sum +++ b/go.sum @@ -35,6 +35,44 @@ github.com/k8shell-io/common v0.20.20 h1:4Tze71ObbtFT6l45wwhDGw8u35w0gTRzuAvH55b github.com/k8shell-io/common v0.20.20/go.mod h1:E8dsb9ta4v3ne61AJgtRyTTbTkMMmKeCMAcXD+/9+cY= github.com/k8shell-io/common v0.21.0 h1:EOwaQOFnHQJsHcDLDVEAwNnJJe7uvQCqcOiI6BTu3GE= github.com/k8shell-io/common v0.21.0/go.mod h1:E8dsb9ta4v3ne61AJgtRyTTbTkMMmKeCMAcXD+/9+cY= +github.com/k8shell-io/common v0.22.4 h1:zii3NIHOldIrwIldtuwWFFqjEV6JYhbtT50na5pDxLk= +github.com/k8shell-io/common v0.22.4/go.mod h1:E8dsb9ta4v3ne61AJgtRyTTbTkMMmKeCMAcXD+/9+cY= +github.com/k8shell-io/common v0.23.0 h1:3v5KjcITfMpdKGVHinHASvV5Y3zAZiBA8y6Br+gusx8= +github.com/k8shell-io/common v0.23.0/go.mod h1:E8dsb9ta4v3ne61AJgtRyTTbTkMMmKeCMAcXD+/9+cY= +github.com/k8shell-io/common v0.23.1 h1:Mk0vlt8/J7w/KhYWNS5/lKhk7Dr14ER5UOkYdu4xsP4= +github.com/k8shell-io/common v0.23.1/go.mod h1:E8dsb9ta4v3ne61AJgtRyTTbTkMMmKeCMAcXD+/9+cY= +github.com/k8shell-io/common v0.23.2 h1:Gvnt6XzHq9tiAeuhCw64uw5RIUmVQZY57U2X55BLgg8= +github.com/k8shell-io/common v0.23.2/go.mod h1:E8dsb9ta4v3ne61AJgtRyTTbTkMMmKeCMAcXD+/9+cY= +github.com/k8shell-io/common v0.23.3 h1:m/1f8x2TfhIqY6hNFBkOthpieCsYtEwac8+QaYarX2c= +github.com/k8shell-io/common v0.23.3/go.mod h1:E8dsb9ta4v3ne61AJgtRyTTbTkMMmKeCMAcXD+/9+cY= +github.com/k8shell-io/common v0.23.4 h1:T4VEI0I8efV/Dz2dXmQMUdJ3wOWPy1R/+m/50IEC68A= +github.com/k8shell-io/common v0.23.4/go.mod h1:E8dsb9ta4v3ne61AJgtRyTTbTkMMmKeCMAcXD+/9+cY= +github.com/k8shell-io/common v0.23.6 h1:t9ltW+n6pMpxp1q51+7OSoRrM9qMpM108aO6WtVeGTk= +github.com/k8shell-io/common v0.23.6/go.mod h1:E8dsb9ta4v3ne61AJgtRyTTbTkMMmKeCMAcXD+/9+cY= +github.com/k8shell-io/common v0.24.0 h1:jnuojNP9uLF2EpxavZPsR7coCkRsczIJUOtxh2uBlNQ= +github.com/k8shell-io/common v0.24.0/go.mod h1:E8dsb9ta4v3ne61AJgtRyTTbTkMMmKeCMAcXD+/9+cY= +github.com/k8shell-io/common v0.24.1 h1:NRXjzT9JlHkfsBU4g0tGv24xJbl4Y3NdswFu2FmT8Q8= +github.com/k8shell-io/common v0.24.1/go.mod h1:E8dsb9ta4v3ne61AJgtRyTTbTkMMmKeCMAcXD+/9+cY= +github.com/k8shell-io/common v0.24.3 h1:/ni3OBj9WXpe4sPLauIrRzJud5UEAwZOUJxfQDD4yu4= +github.com/k8shell-io/common v0.24.3/go.mod h1:E8dsb9ta4v3ne61AJgtRyTTbTkMMmKeCMAcXD+/9+cY= +github.com/k8shell-io/common v0.24.4 h1:clB+fM0K6yZHnSPyGL9kAimPL9g8Blxr0aVwkaEMasg= +github.com/k8shell-io/common v0.24.4/go.mod h1:E8dsb9ta4v3ne61AJgtRyTTbTkMMmKeCMAcXD+/9+cY= +github.com/k8shell-io/common v0.24.5 h1:NwgsWDr4S8gLzXVVgVOHOk44m0eO3uCypmjeph+wBJg= +github.com/k8shell-io/common v0.24.5/go.mod h1:E8dsb9ta4v3ne61AJgtRyTTbTkMMmKeCMAcXD+/9+cY= +github.com/k8shell-io/common v0.24.6 h1:JbdxoOgYbx8D2zRNO5p/EyRZCrNbRtQj6RGetibW9lk= +github.com/k8shell-io/common v0.24.6/go.mod h1:E8dsb9ta4v3ne61AJgtRyTTbTkMMmKeCMAcXD+/9+cY= +github.com/k8shell-io/common v0.25.0 h1:wPVpNT5bjq2teOwWd6J6jIMLP4c5JrDeNCNjXXdIkbg= +github.com/k8shell-io/common v0.25.0/go.mod h1:E8dsb9ta4v3ne61AJgtRyTTbTkMMmKeCMAcXD+/9+cY= +github.com/k8shell-io/common v0.25.2 h1:RuxkyxlDhAXmTdOaI2KupZnS/ckQVAXLSat1L9ZmFmw= +github.com/k8shell-io/common v0.25.2/go.mod h1:E8dsb9ta4v3ne61AJgtRyTTbTkMMmKeCMAcXD+/9+cY= +github.com/k8shell-io/common v0.26.0 h1:IdqXufdCJOPw+HygsmNYSQlDJiMEJIPeGVAus5BIJw4= +github.com/k8shell-io/common v0.26.0/go.mod h1:E8dsb9ta4v3ne61AJgtRyTTbTkMMmKeCMAcXD+/9+cY= +github.com/k8shell-io/common v0.26.1 h1:hJQoq5pHd20pFjHWThnD+VIgPlrv9qXE37P7IH68/m4= +github.com/k8shell-io/common v0.26.1/go.mod h1:E8dsb9ta4v3ne61AJgtRyTTbTkMMmKeCMAcXD+/9+cY= +github.com/k8shell-io/common v0.26.2 h1:pGuamWyw+UoZI8Z5gEltwttfgHJiCHmy+2QSOgPWeL0= +github.com/k8shell-io/common v0.26.2/go.mod h1:E8dsb9ta4v3ne61AJgtRyTTbTkMMmKeCMAcXD+/9+cY= +github.com/k8shell-io/common v0.29.4 h1:patjuhCWs3g/JVNmx7SVa+wgvu46Z5RYAkD7YY6A4tY= +github.com/k8shell-io/common v0.29.4/go.mod h1:E8dsb9ta4v3ne61AJgtRyTTbTkMMmKeCMAcXD+/9+cY= github.com/k8shell-io/crypto v0.41.1-ssh-proxy h1:8+q6Ofc2ky23Oc9iNyiq8aeiQBIP+y3+O6zzHqe1f48= github.com/k8shell-io/crypto v0.41.1-ssh-proxy/go.mod h1:RVZeOJCpqtogniULztSXQESKJCfcI8WCxsS0FagMA8U= github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= diff --git a/internal/server/authzcheck.go b/internal/server/authzcheck.go new file mode 100644 index 0000000..873a0d4 --- /dev/null +++ b/internal/server/authzcheck.go @@ -0,0 +1,56 @@ +// Copyright 2026 The K8shell Authors. All rights reserved. +// Use of this source code is governed by a AGPLv3 +// license that can be found in the LICENSE file. + +package server + +import ( + "context" + "fmt" + + "github.com/k8shell-io/common/pkg/authz" +) + +// checkSSHAuthz evaluates an SSH authz request against the authz service. +// Returns nil when authz is not configured (opt-in) or when the action is allowed. +func (s *Server) checkSSHAuthz(ctx context.Context, token string, req *authz.SSHEvalRequest) error { + if s.authzClient == nil { + return nil + } + if err := req.Validate(); err != nil { + return fmt.Errorf("authz: invalid request: %w", err) + } + protoReq := req.ToProto(token) + protoReq.Package = "ssh" + resp, err := s.authzClient.Evaluate(ctx, protoReq) + if err != nil { + return fmt.Errorf("authz: evaluate %s: %w", req.Action, err) + } + if !resp.GetAllowed() { + return fmt.Errorf("action %q denied: %s", req.Action, resp.GetReason()) + } + return nil +} + +// checkSessionAuthz evaluates a session:start request and returns the recording +// obligation from the policy engine. When authz is not configured, returns +// (zero, false, nil) so callers fall back to their configured defaults. +func (s *Server) checkSessionAuthz(ctx context.Context, token string, req *authz.SessionStartEvalRequest) (authz.RecordObligation, bool, error) { + if s.authzClient == nil { + return authz.RecordObligation{}, false, nil + } + if err := req.Validate(); err != nil { + return authz.RecordObligation{}, false, fmt.Errorf("authz: invalid request: %w", err) + } + protoReq := req.ToProto(token) + protoReq.Package = "session" + resp, err := s.authzClient.Evaluate(ctx, protoReq) + if err != nil { + return authz.RecordObligation{}, false, fmt.Errorf("authz: evaluate %s: %w", req.Action, err) + } + if !resp.GetAllowed() { + return authz.RecordObligation{}, false, fmt.Errorf("action %q denied: %s", req.Action, resp.GetReason()) + } + ob, found := authz.ParseRecordObligation(resp.GetObligations()) + return ob, found, nil +} diff --git a/internal/server/config.go b/internal/server/config.go index c1bb077..6e7e6d3 100644 --- a/internal/server/config.go +++ b/internal/server/config.go @@ -23,6 +23,7 @@ type Config struct { Session gapi.ClientConfig `yaml:"session"` Provisioner gapi.ClientConfig `yaml:"provisioner"` K8shelld gapi.ClientConfig `yaml:"k8shelld"` + Authz gapi.ClientConfig `yaml:"authz"` } // ServerConfig represents the SSH server configuration. @@ -53,6 +54,7 @@ type PublishSshFailuresConfig struct { type RecordingConfig struct { RecordShell bool `yaml:"recordShell"` RecordExec bool `yaml:"recordExec"` + RecordSFTP bool `yaml:"recordSFTP"` RecordDirectTCPIP bool `yaml:"recordDirectTCPIP"` } diff --git a/internal/server/directcpip.go b/internal/server/directcpip.go index 44ae6c2..ff90efc 100644 --- a/internal/server/directcpip.go +++ b/internal/server/directcpip.go @@ -8,6 +8,7 @@ import ( "encoding/binary" "fmt" + "github.com/k8shell-io/common/pkg/authz" "github.com/k8shell-io/ssh-proxy/internal/workspace" "golang.org/x/crypto/ssh" ) @@ -92,10 +93,33 @@ func (s *Server) handleDirectTCPIPChannel(_ *ssh.ServerConn, connInfo *Connectio return } + if authzErr := s.checkSSHAuthz(connInfo.ctx, userToken, + authz.NewSSHEvalRequest(authz.SSHActionDirectTCPIP, connInfo.workspaceName). + WithOwner(connInfo.user.Username). + WithHost(tcpipInfo.destHost). + WithPort(fmt.Sprintf("%d", tcpipInfo.destPort)), + ); authzErr != nil { + s.log.Warn().Msgf("SSH direct-tcpip denied for user %s: %v", connInfo.user.Username, authzErr) + return + } + + recordTCPIP := s.Config.Server.Recording.RecordDirectTCPIP + if ob, found, authzErr := s.checkSessionAuthz(connInfo.ctx, userToken, + authz.NewSessionStartEvalRequest(authz.SessionActionStart, connInfo.workspaceName, authz.SessionTypeTCPIP). + WithSource(authz.SessionSourceSSHProxy). + WithOwner(connInfo.user.Username). + WithBlueprint(connInfo.userStr.Blueprint()), + ); authzErr != nil { + s.log.Warn().Msgf("Session start denied for user %s: %v", connInfo.user.Username, authzErr) + return + } else if found { + recordTCPIP = ob.DirectTCPIP + } + rw := &workspace.ChannelAdapter{Channel: channel} if err := k8shelld.RunPortForward(connInfo.ctx, userToken, rw, tcpipInfo.directTCPIPId, tcpipInfo.originHost, tcpipInfo.originPort, tcpipInfo.destHost, - tcpipInfo.destPort, s.Config.Server.Recording.RecordDirectTCPIP); err != nil { + tcpipInfo.destPort, recordTCPIP); err != nil { s.log.Error().Msgf("Port forward failed for user %s: %v", connInfo.user.Username, err) } else { s.log.Debug().Msgf("Port forward %s completed for user %s", tcpipInfo.directTCPIPId, connInfo.user.Username) diff --git a/internal/server/directstream.go b/internal/server/directstream.go index 5fde803..fa31895 100644 --- a/internal/server/directstream.go +++ b/internal/server/directstream.go @@ -1,9 +1,14 @@ +// Copyright 2025 The K8shell Authors. All rights reserved. +// Use of this source code is governed by a AGPLv3 +// license that can be found in the LICENSE file. + package server import ( "encoding/binary" "fmt" + "github.com/k8shell-io/common/pkg/authz" "github.com/k8shell-io/ssh-proxy/internal/workspace" "golang.org/x/crypto/ssh" ) @@ -49,6 +54,15 @@ func (s *Server) handleDirectStreamLocal(_ *ssh.ServerConn, connInfo *Connection return } + if authzErr := s.checkSSHAuthz(connInfo.ctx, userToken, + authz.NewSSHEvalRequest(authz.SSHActionDirectStreamlocal, connInfo.workspaceName). + WithOwner(connInfo.user.Username). + WithSocketPath(streamLocal.destPath), + ); authzErr != nil { + s.log.Warn().Msgf("SSH direct-streamlocal denied for user %s: %v", connInfo.user.Username, authzErr) + return + } + if err := k8shelld.RunUnixSocket(connInfo.ctx, userToken, &workspace.ChannelAdapter{Channel: ch}, streamLocal.streamLocalId, streamLocal.destPath, "UNIX_SOCKET_MODE_DIAL"); err != nil { s.log.Error().Err(err).Msg("unix socket connect failed") diff --git a/internal/server/nats.go b/internal/server/nats.go index 6b7d7bd..9e33a4b 100644 --- a/internal/server/nats.go +++ b/internal/server/nats.go @@ -1,3 +1,7 @@ +// Copyright 2025 The K8shell Authors. All rights reserved. +// Use of this source code is governed by a AGPLv3 +// license that can be found in the LICENSE file. + package server import ( diff --git a/internal/server/session.go b/internal/server/session.go index 3859f6b..582ca48 100644 --- a/internal/server/session.go +++ b/internal/server/session.go @@ -9,6 +9,7 @@ import ( "fmt" "time" + "github.com/k8shell-io/common/pkg/authz" "github.com/k8shell-io/common/pkg/models" "github.com/k8shell-io/ssh-proxy/internal/workspace" "golang.org/x/crypto/ssh" @@ -282,6 +283,46 @@ func (s *Server) handleShellRequest(sshConn *ssh.ServerConn, connInfo *Connectio return } + userToken, err := connInfo.GetUserToken() + if err != nil { + s.log.Error().Msgf("Failed to get user token for user %s: %v", connInfo.user.Username, err) + return + } + + if authzErr := s.checkSSHAuthz(connInfo.ctx, userToken, + authz.NewSSHEvalRequest(authz.SSHActionShell, connInfo.workspaceName). + WithOwner(connInfo.user.Username). + WithAsUser(connInfo.userStr.User()). + WithBlueprint(connInfo.userStr.Blueprint()). + WithPTY(session.hasPTY), + ); authzErr != nil { + s.log.Warn().Msgf("SSH shell denied for user %s: %v", connInfo.user.Username, authzErr) + return + } + + recordShell := s.Config.Server.Recording.RecordShell + if ob, found, authzErr := s.checkSessionAuthz(connInfo.ctx, userToken, + authz.NewSessionStartEvalRequest(authz.SessionActionStart, connInfo.workspaceName, authz.SessionTypeShell). + WithSource(authz.SessionSourceSSHProxy). + WithOwner(connInfo.user.Username). + WithBlueprint(connInfo.userStr.Blueprint()), + ); authzErr != nil { + s.log.Warn().Msgf("Session start denied for user %s: %v", connInfo.user.Username, authzErr) + return + } else if found { + recordShell = ob.Shell + } + + if session.hasAgent { + if authzErr := s.checkSSHAuthz(connInfo.ctx, userToken, + authz.NewSSHEvalRequest(authz.SSHActionAgentForward, connInfo.workspaceName). + WithOwner(connInfo.user.Username), + ); authzErr != nil { + s.log.Warn().Msgf("Agent forwarding denied for user %s: %v", connInfo.user.Username, authzErr) + session.hasAgent = false + } + } + if session.hasAgent { agentChannel, err := s.handleAgent(sshConn, connInfo) if err != nil { @@ -291,9 +332,9 @@ func (s *Server) handleShellRequest(sshConn *ssh.ServerConn, connInfo *Connectio } } - userToken, err := connInfo.GetUserToken() - if err != nil { - s.log.Error().Msgf("Failed to get user token for user %s: %v", connInfo.user.Username, err) + if s.authzClient == nil && connInfo.userStr.User() == "root" && !connInfo.user.Sudo { + s.log.Warn().Msgf("User %s requested root shell without sudo permissions, denying shell access", + connInfo.user.Username) return } @@ -303,7 +344,7 @@ func (s *Server) handleShellRequest(sshConn *ssh.ServerConn, connInfo *Connectio rw := &workspace.ChannelAdapter{Channel: channel} if err := k8shelld.RunShell(connInfo.ctx, userToken, connInfo.userStr.User(), rw, session.sessionId, session.env, session.termWidth, session.termHeight, - session.hasPTY, "", false, true, s.Config.Server.Recording.RecordShell, connInfo.SetPtyName); err != nil { + session.hasPTY, "", false, true, recordShell, connInfo.SetPtyName); err != nil { s.log.Error().Msgf("Shell session error: %v", err) } else { s.log.Debug().Msgf("Shell session %s completed for user %s", session.sessionId, session.username) @@ -349,29 +390,39 @@ func (s *Server) cancelOnCtrlC(channel ssh.Channel, connInfo *Connection, stopCh func (s *Server) handleSFTPSubsystem(_ *ssh.ServerConn, connInfo *Connection, channel ssh.Channel) { session := connInfo.session - s.log.Info().Msgf("Handling sftp subsystem in channel for user %s, command: %s", session.username, session.command) + s.log.Info().Msgf("Handling sftp subsystem in channel for user %s", session.username) k8shelld, err := connInfo.Handshake(nil, nil, s) if err != nil { - s.log.Error().Msgf("Failed to get k8shelld client for sftp exec: %v", err) + s.log.Error().Msgf("Failed to get k8shelld client for sftp: %v", err) return } - if session.signalChan != nil { - s.log.Error().Msgf("Signal channel already exists for user %s, cannot start exec", session.username) + userToken, err := connInfo.GetUserToken() + if err != nil { + s.log.Error().Msgf("Failed to get user token for user %s: %v", connInfo.user.Username, err) return } - session.signalChan = make(chan string, 10) - defer func() { - close(session.signalChan) - session.signalChan = nil - }() + if authzErr := s.checkSSHAuthz(connInfo.ctx, userToken, + authz.NewSSHEvalRequest(authz.SSHActionSFTP, connInfo.workspaceName). + WithOwner(connInfo.user.Username), + ); authzErr != nil { + s.log.Warn().Msgf("SSH sftp denied for user %s: %v", connInfo.user.Username, authzErr) + return + } - userToken, err := connInfo.GetUserToken() - if err != nil { - s.log.Error().Msgf("Failed to get user token for user %s: %v", connInfo.user.Username, err) + recordSFTP := s.Config.Server.Recording.RecordSFTP + if ob, found, authzErr := s.checkSessionAuthz(connInfo.ctx, userToken, + authz.NewSessionStartEvalRequest(authz.SessionActionStart, connInfo.workspaceName, authz.SessionTypeSFTP). + WithSource(authz.SessionSourceSSHProxy). + WithOwner(connInfo.user.Username). + WithBlueprint(connInfo.userStr.Blueprint()), + ); authzErr != nil { + s.log.Warn().Msgf("Session start denied for user %s: %v", connInfo.user.Username, authzErr) return + } else if found { + recordSFTP = ob.SFTP } execID := fmt.Sprintf("sf-%s%d", connInfo.connId, connInfo.SeqNumber()) @@ -379,10 +430,10 @@ func (s *Server) handleSFTPSubsystem(_ *ssh.ServerConn, connInfo *Connection, ch session.username, execID, s.Config.Server.SftpBinary) rw := &workspace.ChannelAdapter{Channel: channel} - exitcode, err := k8shelld.RunExec(connInfo.ctx, userToken, connInfo.userStr.User(), rw, execID, - s.Config.Server.SftpBinary, "", []string{}, session.signalChan, s.Config.Server.Recording.RecordExec) + exitcode, err := k8shelld.RunSFTP(connInfo.ctx, userToken, connInfo.userStr.User(), rw, execID, + s.Config.Server.SftpBinary, []string{}, recordSFTP) if err != nil { - s.log.Error().Msgf("sftp exec failed for command '%s': %v", s.Config.Server.SftpBinary, err) + s.log.Error().Msgf("sftp failed for command '%s': %v", s.Config.Server.SftpBinary, err) } s.log.Debug().Msgf("sftp '%s' executed in the workspace, exit-code=%d", s.Config.Server.SftpBinary, exitcode) @@ -420,13 +471,37 @@ func (s *Server) handleExecRequest(connInfo *Connection, channel ssh.Channel) { return } + if authzErr := s.checkSSHAuthz(connInfo.ctx, userToken, + authz.NewSSHEvalRequest(authz.SSHActionExec, connInfo.workspaceName). + WithOwner(connInfo.user.Username). + WithCommand(session.command), + ); authzErr != nil { + s.log.Warn().Msgf("SSH exec denied for user %s: %v", connInfo.user.Username, authzErr) + s.sendExitStatus(channel, 1) + return + } + + recordExec := s.Config.Server.Recording.RecordExec + if ob, found, authzErr := s.checkSessionAuthz(connInfo.ctx, userToken, + authz.NewSessionStartEvalRequest(authz.SessionActionStart, connInfo.workspaceName, authz.SessionTypeExec). + WithSource(authz.SessionSourceSSHProxy). + WithOwner(connInfo.user.Username). + WithBlueprint(connInfo.userStr.Blueprint()), + ); authzErr != nil { + s.log.Warn().Msgf("Session start denied for user %s: %v", connInfo.user.Username, authzErr) + s.sendExitStatus(channel, 1) + return + } else if found { + recordExec = ob.Exec + } + execID := fmt.Sprintf("ex-%s%d", connInfo.connId, connInfo.SeqNumber()) s.log.Debug().Msgf("Starting exec for user %s, exec ID: %s, command: %s", session.username, execID, session.command) rw := &workspace.ChannelAdapter{Channel: channel} exitcode, err := k8shelld.RunExec(connInfo.ctx, userToken, connInfo.userStr.User(), rw, execID, session.command, - "/bin/sh", session.env, session.signalChan, s.Config.Server.Recording.RecordExec) + "/bin/sh", session.env, session.signalChan, recordExec) if err != nil { s.log.Error().Msgf("Exec failed for command '%s': %v", session.command, err) } diff --git a/internal/server/ssh.go b/internal/server/ssh.go index 51b486d..455ca49 100644 --- a/internal/server/ssh.go +++ b/internal/server/ssh.go @@ -24,6 +24,7 @@ import ( "github.com/k8shell-io/common/pkg/api/client/identity" "github.com/k8shell-io/common/pkg/api/client/provisioner" sessionc "github.com/k8shell-io/common/pkg/api/client/session" + authzv1 "github.com/k8shell-io/common/pkg/api/gen/go/authz/v1" "github.com/k8shell-io/common/pkg/gapi" log "github.com/k8shell-io/common/pkg/logger" "github.com/k8shell-io/common/pkg/models" @@ -51,6 +52,7 @@ type Server struct { wg sync.WaitGroup identity *identity.IdentityClient provisioner *provisioner.Client + authzClient authzv1.AuthzServiceClient fpub *NatsFailuresPublisher configPath string } @@ -104,6 +106,14 @@ func NewServer(configPath string) (*Server, error) { return nil, fmt.Errorf("failed to create identity client: %w", err) } + if config.Authz.IsEnabled() { + authzConn, err := gapi.NewClient(config.Authz) + if err != nil { + return nil, fmt.Errorf("failed to create authz client: %w", err) + } + server.authzClient = authzv1.NewAuthzServiceClient(authzConn.Conn) + } + server.nats, err = natsc.NewNATSClient(config.Nats) if err != nil { return nil, fmt.Errorf("failed to create NATS client: %w", err) @@ -220,6 +230,14 @@ func HandleConnectionChildProcess(configPath string) error { return fmt.Errorf("failed to create identity client: %w", err) } + if config.Authz.IsEnabled() { + authzConn, err := gapi.NewClient(config.Authz) + if err != nil { + return fmt.Errorf("failed to create authz client: %w", err) + } + server.authzClient = authzv1.NewAuthzServiceClient(authzConn.Conn) + } + server.nats, err = natsc.NewNATSClient(config.Nats) if err != nil { return fmt.Errorf("failed to create NATS client: %w", err) diff --git a/internal/workspace/client.go b/internal/workspace/client.go index f3c01d2..c84f9d1 100644 --- a/internal/workspace/client.go +++ b/internal/workspace/client.go @@ -27,8 +27,11 @@ type K8shelldClient interface { RunUnixSocket(ctx context.Context, userToken string, upstream k8shelldClient.BufferedReadWriter, unixSocketId, socketPath, mode string) error RunPortForward(ctx context.Context, userToken string, upstream k8shelldClient.BufferedReadWriter, portForwardID, sourceIP string, sourcePort uint32, destinationIP string, destinationPort uint32, enableRecording bool) error - RunExec(ctx context.Context, userToken string, asUser string, upstream k8shelldClient.BufferedReadWriter, execID string, command string, - shellBinary string, envVars []string, signalChan <-chan string, enableRecording bool) (int32, error) + RunExec(ctx context.Context, userToken string, asUser string, upstream k8shelldClient.BufferedReadWriter, + execID string, command string, shellBinary string, envVars []string, signalChan <-chan string, + enableRecording bool) (int32, error) + RunSFTP(ctx context.Context, userToken string, asUser string, upstream k8shelldClient.BufferedReadWriter, + sessionID string, command string, envVars []string, enableRecording bool) (int32, error) RunCommandProcessor(ctx context.Context, handler k8shelldClient.CommandHandler) error Close() error }