Skip to content

Commit

Permalink
logging and session start
Browse files Browse the repository at this point in the history
Signed-off-by: Fabrice Aneche <[email protected]>
  • Loading branch information
akhenakh committed Sep 18, 2024
1 parent 3d8405a commit d19e07a
Show file tree
Hide file tree
Showing 5 changed files with 59 additions and 34 deletions.
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -172,4 +172,5 @@ There is a `Dockerfile` to be used with Docker & Podman too.
- [X] kubernetes example
- [ ] helm example
- [X] tailscale
- [ ] network policies
- [ ] network policies
- [ ] add a sshsession id for tracking in logs
54 changes: 44 additions & 10 deletions cmd/sshjump/bubbletea.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,13 @@ package main

import (
"fmt"
"log/slog"
"time"

tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss"
"github.com/charmbracelet/ssh"
"github.com/charmbracelet/wish"
"github.com/charmbracelet/wish/bubbletea"
)

Expand All @@ -14,6 +17,8 @@ import (
// pass it to the new model. You can also return tea.ProgramOptions (such as
// tea.WithAltScreen) on a session by session basis.
func (srv *Server) teaHandler(s ssh.Session) (tea.Model, []tea.ProgramOption) {
userConnections.WithLabelValues(s.User()).Inc()

// This should never fail, as we are using the activeterm middleware.
pty, _, _ := s.Pty()

Expand All @@ -35,6 +40,9 @@ func (srv *Server) teaHandler(s ssh.Session) (tea.Model, []tea.ProgramOption) {
bg = "dark"
}

// get the current targeted port
// currentPort := s.Context().Value(portContextKey).(Port) //nolint:forcetypeassert

m := model{
term: pty.Term,
profile: renderer.ColorProfile().Name(),
Expand All @@ -44,21 +52,23 @@ func (srv *Server) teaHandler(s ssh.Session) (tea.Model, []tea.ProgramOption) {
txtStyle: txtStyle,
quitStyle: quitStyle,
user: s.User(),
// currentPort: currentPort,
}

return m, []tea.ProgramOption{tea.WithAltScreen()}
}

// Just a generic tea.Model to demo terminal information of ssh.
type model struct {
term string
profile string
width int
height int
bg string
user string
txtStyle lipgloss.Style
quitStyle lipgloss.Style
term string
profile string
width int
height int
bg string
user string
currentPort Port
txtStyle lipgloss.Style
quitStyle lipgloss.Style
}

func (m model) Init() tea.Cmd {
Expand All @@ -81,8 +91,32 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
}

func (m model) View() string {
s := fmt.Sprintf("Your term is %s\nYour window size is %dx%d\nBackground: %s\nColor Profile: %s",
m.term, m.width, m.height, m.bg, m.profile)
s := fmt.Sprintf("User %s", m.user)

return m.txtStyle.Render(s) + "\n\n" + m.quitStyle.Render("Press 'q' to quit\n")
}

// StructuredMiddlewareWithLogger provides basic connection logging in a structured form.
// Connects are logged with the remote address, invoked command, TERM setting,
// window dimensions, client version, and if the auth was public key based.
// Disconnect will log the remote address and connection duration.
func StructuredMiddlewareWithLogger(logger *slog.Logger) wish.Middleware {
return func(next ssh.Handler) ssh.Handler {
return func(sess ssh.Session) {
ct := time.Now()
logger.Info(
"connect",
"user", sess.User(),
"remote-addr", sess.RemoteAddr().String(),
"client-version", sess.Context().ClientVersion(),
)
next(sess)
logger.Info(
"disconnect",
"user", sess.User(),
"remote-addr", sess.RemoteAddr().String(),
"duration", time.Since(ct),
)
}
}
}
2 changes: 0 additions & 2 deletions cmd/sshjump/port_match.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@ func Allowed(ports Ports, userPerms Permission) Ports {
fullAccess := userPerms.AllowAll
if fullAccess {
for _, p := range ports {
p.addr = fmt.Sprintf("%s:%d", p.addr, p.port)
allowed = append(allowed, p)
}

Expand All @@ -52,7 +51,6 @@ func Allowed(ports Ports, userPerms Permission) Ports {
if userNs.Namespace == port.namespace {
// check if user got the full access to the namespace no restriction
if len(userNs.Pods) == 0 {
port.addr = fmt.Sprintf("%s:%d", port.addr, port.port)
allowed = append(allowed, port)

continue
Expand Down
4 changes: 2 additions & 2 deletions cmd/sshjump/port_match_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ func TestAllowed(t *testing.T) {
namespace: "default",
pod: "nginx",
port: 8080,
addr: "10.16.0.10",
addr: "10.16.0.10:8080",
}},
allowAllPerm,
[]Port{{
Expand All @@ -111,7 +111,7 @@ func TestAllowed(t *testing.T) {
namespace: "default",
pod: "nginx",
port: 8080,
addr: "10.16.0.10",
addr: "10.16.0.10:8080",
}},
namespacePerm,
[]Port{{
Expand Down
30 changes: 11 additions & 19 deletions cmd/sshjump/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,19 +16,12 @@ import (
"github.com/charmbracelet/wish"
"github.com/charmbracelet/wish/activeterm"
"github.com/charmbracelet/wish/bubbletea"
"github.com/charmbracelet/wish/logging"
"github.com/fsnotify/fsnotify"
gossh "golang.org/x/crypto/ssh"
"google.golang.org/grpc/health"
"k8s.io/client-go/kubernetes"
)

var (
// TODO: parametrize.
DeadlineTimeout = 30 * time.Second
IdleTimeout = 10 * time.Second
)

type Server struct {
logger *slog.Logger
healthServer *health.Server
Expand All @@ -50,6 +43,14 @@ type localForwardChannelData struct {
OriginPort uint32
}

// TODO: parametrize.
var (
IdleTimeout = 2 * time.Minute
portContextKey contextKey
)

type contextKey int

func NewServer(
logger *slog.Logger,
healthServer *health.Server,
Expand All @@ -68,10 +69,12 @@ func NewServer(
wish.WithMiddleware(
bubbletea.Middleware(jumps.teaHandler),
activeterm.Middleware(), // Bubble Tea apps usually require a PTY.
logging.Middleware(),
StructuredMiddlewareWithLogger(logger),
),
func(s *ssh.Server) error {
s.LocalPortForwardingCallback = func(ctx ssh.Context, bindHost string, bindPort uint32) bool {
// TODO: prevalidation ?

slog.Debug("Accepted forward", "host", bindHost, "port", bindPort, "user", ctx.User())

return true
Expand All @@ -81,7 +84,6 @@ func NewServer(
"session": ssh.DefaultSessionHandler,
}

s.MaxTimeout = DeadlineTimeout
s.IdleTimeout = IdleTimeout
s.AddHostKey(privateKey)

Expand All @@ -98,16 +100,6 @@ func NewServer(
return jumps
}

func (srv *Server) Handler(s ssh.Session) {
srv.logger.Info("login",
"username", s.User(),
"ip", s.RemoteAddr().String(),
)
userConnections.WithLabelValues(s.User()).Inc()
io.WriteString(s, fmt.Sprintf("user %s\n", s.User()))
select {}
}

// PermsForUser returns the permissions for a given user.
// It locks the server mutex to ensure safe access to the permissions map.
func (srv *Server) PermsForUser(user string) *Permission {
Expand Down

0 comments on commit d19e07a

Please sign in to comment.