diff --git a/internal/k8s/exec.go b/internal/k8s/exec.go index 56041975..b53c66c7 100644 --- a/internal/k8s/exec.go +++ b/internal/k8s/exec.go @@ -198,6 +198,10 @@ func (c *Client) Exec(ctx context.Context, namespace, deployment, if err != nil { return fmt.Errorf("couldn't get executor: %v", err) } + // Ensure the TerminalSizeQueue goroutine is cancelled immediately after + // command exection completes by deferring its cancellation here. + ctx, cancel := context.WithCancel(ctx) + defer cancel() // execute the command return exec.StreamWithContext(ctx, remotecommand.StreamOptions{ Stdin: stdio, diff --git a/internal/sshportalapi/sshportal.go b/internal/sshportalapi/sshportal.go index 61418640..ed353869 100644 --- a/internal/sshportalapi/sshportal.go +++ b/internal/sshportalapi/sshportal.go @@ -111,21 +111,32 @@ func sshportal(ctx context.Context, log *zap.Logger, c *nats.EncodedConn, zap.Error(err)) return } + log.Debug("keycloak user attributes", + zap.Strings("realmRoles", realmRoles), + zap.Strings("userGroups", userGroups), + zap.Any("groupProjectIDs", groupProjectIDs), + zap.String("userUUID", user.UUID.String()), + zap.String("sessionID", query.SessionID), + ) // check permission ok := p.UserCanSSHToEnvironment(ctx, env, realmRoles, userGroups, groupProjectIDs) + var logMsg string if ok { - log.Info("validated SSH access", - zap.Int("environmentID", env.ID), - zap.Int("projectID", env.ProjectID), - zap.String("SSHFingerprint", query.SSHFingerprint), - zap.String("environmentName", env.Name), - zap.String("namespace", query.NamespaceName), - zap.String("projectName", env.ProjectName), - zap.String("sessionID", query.SessionID), - zap.String("userUUID", user.UUID.String()), - ) + logMsg = "SSH access authorized" + } else { + logMsg = "SSH access not authorized" } + log.Info(logMsg, + zap.Int("environmentID", env.ID), + zap.Int("projectID", env.ProjectID), + zap.String("SSHFingerprint", query.SSHFingerprint), + zap.String("environmentName", env.Name), + zap.String("namespace", query.NamespaceName), + zap.String("projectName", env.ProjectName), + zap.String("sessionID", query.SessionID), + zap.String("userUUID", user.UUID.String()), + ) if err = c.Publish(replySubject, ok); err != nil { log.Error("couldn't publish reply", zap.Any("query", query), diff --git a/internal/sshserver/authhandler.go b/internal/sshserver/authhandler.go index 47cd07ef..f3577cf3 100644 --- a/internal/sshserver/authhandler.go +++ b/internal/sshserver/authhandler.go @@ -70,9 +70,8 @@ func pubKeyAuth(log *zap.Logger, nc *nats.EncodedConn, SessionID: ctx.SessionID(), } // send query - var response bool - err = nc.Request(sshportalapi.SubjectSSHAccessQuery, q, &response, - natsTimeout) + var ok bool + err = nc.Request(sshportalapi.SubjectSSHAccessQuery, q, &ok, natsTimeout) if err != nil { log.Warn("couldn't make NATS request", zap.String("sessionID", ctx.SessionID()), @@ -80,19 +79,23 @@ func pubKeyAuth(log *zap.Logger, nc *nats.EncodedConn, return false } // handle response - if response { - authSuccessTotal.Inc() - ctx.SetValue(environmentIDKey, eid) - ctx.SetValue(environmentNameKey, ename) - ctx.SetValue(projectIDKey, pid) - ctx.SetValue(projectNameKey, pname) - ctx.SetValue(sshFingerprint, fingerprint) - log.Debug("Lagoon authorization granted", + if !ok { + log.Debug("SSH access not authorized", zap.String("sessionID", ctx.SessionID()), zap.String("fingerprint", fingerprint), zap.String("namespace", ctx.User())) - return true + return false } - return false + authSuccessTotal.Inc() + ctx.SetValue(environmentIDKey, eid) + ctx.SetValue(environmentNameKey, ename) + ctx.SetValue(projectIDKey, pid) + ctx.SetValue(projectNameKey, pname) + ctx.SetValue(sshFingerprint, fingerprint) + log.Debug("SSH access authorized", + zap.String("sessionID", ctx.SessionID()), + zap.String("fingerprint", fingerprint), + zap.String("namespace", ctx.User())) + return true } } diff --git a/internal/sshserver/sessionhandler.go b/internal/sshserver/sessionhandler.go index 5c5690ef..33741bb6 100644 --- a/internal/sshserver/sessionhandler.go +++ b/internal/sshserver/sessionhandler.go @@ -19,7 +19,9 @@ var ( }) ) -func sshifyCommand(sftp bool, cmd []string) []string { +// getSSHIntent analyses the SFTP flag and the raw command strings to determine +// if the command should be wrapped. +func getSSHIntent(sftp bool, cmd []string) []string { // if this is an sftp session we ignore any commands if sftp { return []string{"sftp-server", "-u", "0002"} @@ -45,7 +47,8 @@ func sshifyCommand(sftp bool, cmd []string) []string { func sessionHandler(log *zap.Logger, c *k8s.Client, sftp bool) ssh.Handler { return func(s ssh.Session) { sessionTotal.Inc() - sid := s.Context().SessionID() + ctx := s.Context() + sid := ctx.SessionID() // start the command log.Debug("starting command exec", zap.String("sessionID", sid), @@ -53,8 +56,8 @@ func sessionHandler(log *zap.Logger, c *k8s.Client, sftp bool) ssh.Handler { zap.String("subsystem", s.Subsystem()), ) // parse the command line arguments to extract any service or container args - service, container, cmd := parseConnectionParams(s.Command()) - cmd = sshifyCommand(sftp, cmd) + service, container, rawCmd := parseConnectionParams(s.Command()) + cmd := getSSHIntent(sftp, rawCmd) // validate the service and container if err := k8s.ValidateLabelValue(service); err != nil { log.Debug("invalid service name", @@ -85,7 +88,7 @@ func sessionHandler(log *zap.Logger, c *k8s.Client, sftp bool) ssh.Handler { return } // find the deployment name based on the given service name - deployment, err := c.FindDeployment(s.Context(), s.User(), service) + deployment, err := c.FindDeployment(ctx, s.User(), service) if err != nil { log.Debug("couldn't find deployment for service", zap.String("service", service), @@ -103,7 +106,6 @@ func sessionHandler(log *zap.Logger, c *k8s.Client, sftp bool) ssh.Handler { // check if a pty was requested, and get the window size channel _, winch, pty := s.Pty() // extract info passed through the context by the authhandler - ctx := s.Context() eid, ok := ctx.Value(environmentIDKey).(int) if !ok { log.Warn("couldn't extract environment ID from session context") @@ -137,7 +139,7 @@ func sessionHandler(log *zap.Logger, c *k8s.Client, sftp bool) ssh.Handler { zap.String("sessionID", sid), zap.Strings("command", cmd), ) - err = c.Exec(s.Context(), s.User(), deployment, container, cmd, s, + err = c.Exec(ctx, s.User(), deployment, container, cmd, s, s.Stderr(), pty, winch) if err != nil { if exitErr, ok := err.(exec.ExitError); ok {