Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(agent,ssh,pkg): add support for agent forwarding #4273

Merged
merged 1 commit into from
Nov 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docker-compose.agent.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ services:
- /etc/group:/etc/group
- /var/run/docker.sock:/var/run/docker.sock
- ./.golangci.yaml:/.golangci.yaml
- /tmp:/tmp
depends_on:
- api
- ssh
Expand Down
1 change: 1 addition & 0 deletions install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ docker_install() {
-v /etc/resolv.conf:/etc/resolv.conf \
-v /var/run:/var/run \
-v /var/log:/var/log \
-v /tmp:/tmp \
-e SHELLHUB_SERVER_ADDRESS=$SERVER_ADDRESS \
-e SHELLHUB_PRIVATE_KEY=/host/etc/shellhub.key \
-e SHELLHUB_TENANT_ID=$TENANT_ID \
Expand Down
4 changes: 2 additions & 2 deletions pkg/agent/server/modes/host/sessioner.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ func NewSessioner(deviceName *string, cmds map[string]*exec.Cmd) *Sessioner {
func (s *Sessioner) Shell(session gliderssh.Session) error {
sspty, winCh, isPty := session.Pty()

scmd := newShellCmd(*s.deviceName, session.User(), sspty.Term, session.Environ())
scmd := generateShellCmd(*s.deviceName, session, sspty.Term)

pts, err := startPty(scmd, session, winCh)
if err != nil {
Expand Down Expand Up @@ -110,7 +110,7 @@ func (s *Sessioner) Shell(session gliderssh.Session) error {
func (s *Sessioner) Heredoc(session gliderssh.Session) error {
_, _, isPty := session.Pty()

cmd := newShellCmd(*s.deviceName, session.User(), "", session.Environ())
cmd := generateShellCmd(*s.deviceName, session, "")

stdout, _ := cmd.StdoutPipe()
stdin, _ := cmd.StdinPipe()
Expand Down
12 changes: 11 additions & 1 deletion pkg/agent/server/modes/host/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,19 @@
package host

import (
"fmt"
"os"
"os/exec"

gliderssh "github.com/gliderlabs/ssh"
"github.com/shellhub-io/shellhub/pkg/agent/pkg/osauth"
"github.com/shellhub-io/shellhub/pkg/agent/server/modes/host/command"
)

func newShellCmd(deviceName string, username string, term string, envs []string) *exec.Cmd {
func generateShellCmd(deviceName string, session gliderssh.Session, term string) *exec.Cmd {
username := session.User()
envs := session.Environ()

shell := os.Getenv("SHELL")

user, err := osauth.LookupUser(username)
Expand All @@ -26,6 +31,11 @@ func newShellCmd(deviceName string, username string, term string, envs []string)
term = "xterm"
}

authSock := session.Context().Value("SSH_AUTH_SOCK")
if authSock != nil {
envs = append(envs, fmt.Sprintf("%s=%s", "SSH_AUTH_SOCK", authSock.(string)))
}

cmd := command.NewCmd(user, shell, term, deviceName, envs, shell, "--login")

return cmd
Expand Down
56 changes: 55 additions & 1 deletion pkg/agent/server/session.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@ package server

import (
"fmt"
"os"
"os/user"
"path"
"strconv"

gliderssh "github.com/gliderlabs/ssh"
log "github.com/sirupsen/logrus"
Expand Down Expand Up @@ -51,7 +55,57 @@ func GetSessionType(session gliderssh.Session) (Type, error) {
func (s *Server) sessionHandler(session gliderssh.Session) {
log.Info("New session request")

go s.startKeepAliveLoop(session)
if gliderssh.AgentRequested(session) {
user, err := user.Lookup(session.User())
if err != nil {
log.WithError(err).Error("failed to get the user")

return
}

id, err := strconv.Atoi(user.Uid)
if err != nil {
log.WithError(err).Error("failed to get the user ID")

return
}

gid, err := strconv.Atoi(user.Gid)
if err != nil {
log.WithError(err).Error("failed to get the group IP")

return
}

l, err := gliderssh.NewAgentListener()
if err != nil {
log.WithError(err).Error("failed to create agent listener")

return
}

defer l.Close()

authSock := l.Addr().String()

// NOTE: When the agent is started by the root user, we need to change the ownership of the Unix socket created
// to allow access for the logged-in user.
if err := os.Chown(path.Dir(authSock), id, gid); err != nil {
log.WithError(err).Error("failed to change the permission of directory where unix socket was created")

return
}

if err := os.Chown(authSock, id, gid); err != nil {
log.WithError(err).Error("failed to change the permission of unix socket")

return
}

session.Context().SetValue("SSH_AUTH_SOCK", authSock)

go gliderssh.ForwardAgentConnections(l, session)
}

sessionType, err := GetSessionType(session)
if err != nil {
Expand Down
87 changes: 87 additions & 0 deletions ssh/server/channels/session.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package channels

import (
"io"
"strings"
"sync"

Expand Down Expand Up @@ -59,6 +60,20 @@ const (
ExitStatusRequest = "exit-status"
)

// A client may request agent forwarding for a previously-opened session using the following channel request. This
// request is sent after the channel has been opened, but before a [ShellRequestType], command or
// [SubsystemRequestType] has been executed.
//
// https://www.ietf.org/archive/id/draft-miller-ssh-agent-11.html#section-4.1
const AuthRequestOpenSSHRequest = "auth-agent-req@openssh.com"

// After a client has requested that a session have agent forwarding enabled, the server later may request a connection
// to the forwarded agent. The server does this by requesting a dedicated channel to communicate with the client's
// agent.
//
// https://www.ietf.org/archive/id/draft-miller-ssh-agent-11.html#section-4.2
const AuthRequestOpenSSHChannel = "auth-agent@openssh.com"

type DefaultSessionHandlerOptions struct {
RecordURL string
}
Expand Down Expand Up @@ -261,6 +276,78 @@ func DefaultSessionHandler(opts DefaultSessionHandlerOptions) gliderssh.ChannelH
return
}
}
case AuthRequestOpenSSHRequest:
_, err := agent.SendRequest(AuthRequestOpenSSHRequest, req.WantReply, req.Payload)
if err != nil {
reject(nil, "failed to the auth request to agent")

return
}

req.Reply(true, nil) //nolint:errcheck

gliderssh.SetAgentRequested(ctx)

go func() {
clientConn := ctx.Value(gliderssh.ContextKeyConn).(gossh.Conn)
agentChannels := sess.AgentClient.HandleChannelOpen(AuthRequestOpenSSHChannel)

for {
newAgentChannel, ok := <-agentChannels
if !ok {
reject(nil, "channel for agent forwarding done")

return
}

agentChannel, reqs, err := newAgentChannel.Accept()
if err != nil {
reject(nil, "failed to accept the chanel request from agent on auth request")

return
}

defer agentChannel.Close()
go gossh.DiscardRequests(reqs)

go func() {
clientChannel, reqs, err := clientConn.OpenChannel(AuthRequestOpenSSHChannel, nil)
if err != nil {
reject(nil, "failed to open the auth request channel from agent to client")

return
}

defer clientChannel.Close()
go gossh.DiscardRequests(reqs)

var wg sync.WaitGroup

wg.Add(1)
go func() {
defer agentChannel.CloseWrite() //nolint:errcheck
defer wg.Done()

if _, err := io.Copy(agentChannel, clientChannel); err != nil && err != io.EOF {
logger.WithError(err).Trace("auth agent forwarding coping from client to agent")
}
}()

wg.Add(1)
go func() {
defer clientChannel.CloseWrite() //nolint:errcheck
defer wg.Done()

if _, err := io.Copy(clientChannel, agentChannel); err != nil && err != io.EOF {
logger.WithError(err).Trace("auth agent forwarding coping from agent to client")
}
}()

wg.Wait()
}()
logger.WithError(err).Trace("auth request channel piping done")
}
}()
default:
if req.WantReply {
if err := req.Reply(ok, nil); err != nil {
Expand Down
Loading