From a34c804cd586b11632aa806c08e0d93a33fb8039 Mon Sep 17 00:00:00 2001 From: Ryoga Saito Date: Tue, 30 Dec 2025 01:50:11 +0900 Subject: [PATCH 1/2] Add logging for SSH helper --- controllers/nclet/ssh_server.go | 60 ++++++++++++++++++++++----------- 1 file changed, 40 insertions(+), 20 deletions(-) diff --git a/controllers/nclet/ssh_server.go b/controllers/nclet/ssh_server.go index 79842ce..7fce8e5 100644 --- a/controllers/nclet/ssh_server.go +++ b/controllers/nclet/ssh_server.go @@ -12,6 +12,7 @@ import ( "os/exec" "path" "strings" + "time" "github.com/creack/pty" "github.com/gliderlabs/ssh" @@ -150,35 +151,42 @@ func (r *SSHServer) injectHostKeys(server *ssh.Server) error { return nil } -func (r *SSHServer) handlePasswordAuthentication(ctx context.Context, sCtx ssh.Context, password string) bool { +func (r *SSHServer) handlePasswordAuthentication(ctx context.Context, sCtx ssh.Context, password string) error { user, err := parseUser(sCtx.User()) if err != nil { - return false + return errors.New("invalid user format") } if user.Admin { - return password == r.adminPassword - } else { - problemEnvironment := netconv1alpha1.ProblemEnvironment{} - if err := r.Get(ctx, types.NamespacedName{ - Namespace: "netcon", - Name: user.ProblemEnvironmentName, - }, &problemEnvironment); err != nil { - return false + if password != r.adminPassword { + return errors.New("invalid password") } + return nil + } - if util.GetProblemEnvironmentCondition( - &problemEnvironment, - netconv1alpha1.ProblemEnvironmentConditionAssigned, - ) != metav1.ConditionTrue { - return false - } + problemEnvironment := netconv1alpha1.ProblemEnvironment{} + if err := r.Get(ctx, types.NamespacedName{ + Namespace: "netcon", + Name: user.ProblemEnvironmentName, + }, &problemEnvironment); err != nil { + return errors.New("problem environment not found") + } - return password == problemEnvironment.Status.Password + if util.GetProblemEnvironmentCondition( + &problemEnvironment, + netconv1alpha1.ProblemEnvironmentConditionAssigned, + ) != metav1.ConditionTrue { + return errors.New("problem environment not assigned") } + + if password != problemEnvironment.Status.Password { + return errors.New("invalid password") + } + + return nil } -func (r *SSHServer) handle(ctx context.Context, s ssh.Session) { +func (r *SSHServer) handle(_ context.Context, s ssh.Session) { user, err := parseUser(s.User()) if err != nil { return @@ -217,10 +225,22 @@ func (r *SSHServer) Start(ctx context.Context) error { server := &ssh.Server{ Addr: r.sshAddr, PasswordHandler: func(sctx ssh.Context, password string) bool { - // TODO: Add logging - return r.handlePasswordAuthentication(ctx, sctx, password) + log := log.FromContext(ctx) + if err := r.handlePasswordAuthentication(ctx, sctx, password); err != nil { + log.Info("ssh authentication failed", "remoteAddr", sctx.RemoteAddr().String(), "user", sctx.User(), "reason", err) + return false + } + log.Info("ssh authentication successful", "remoteAddr", sctx.RemoteAddr().String(), "user", sctx.User()) + return true }, Handler: func(s ssh.Session) { + log := log.FromContext(ctx) + start := time.Now() + defer func() { + duration := time.Since(start).Seconds() + log.Info("ssh session finished", "remoteAddr", s.RemoteAddr().String(), "user", s.User(), "durationSecond", duration) + }() + r.handle(ctx, s) }, } From 1bc9f362f03ae8888df6964dfd7225ab054a637d Mon Sep 17 00:00:00 2001 From: Ryoga Saito Date: Tue, 30 Dec 2025 02:11:35 +0900 Subject: [PATCH 2/2] Add metrics (SSH related) --- cmd/nclet/main.go | 3 +++ controllers/nclet/metrics.go | 35 +++++++++++++++++++++++++++++++++ controllers/nclet/ssh_server.go | 3 +++ 3 files changed, 41 insertions(+) create mode 100644 controllers/nclet/metrics.go diff --git a/cmd/nclet/main.go b/cmd/nclet/main.go index 998da6f..0ab9a66 100644 --- a/cmd/nclet/main.go +++ b/cmd/nclet/main.go @@ -36,6 +36,7 @@ import ( ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/healthz" "sigs.k8s.io/controller-runtime/pkg/log/zap" + "sigs.k8s.io/controller-runtime/pkg/metrics" "sigs.k8s.io/controller-runtime/pkg/metrics/server" netconv1alpha1 "github.com/janog-netcon/netcon-problem-management-subsystem/api/v1alpha1" @@ -71,6 +72,8 @@ func init() { } func main() { + controllers.RegisterMetrics(metrics.Registry) + flag.StringVar(&metricsAddr, "metrics-bind-address", ":8080", "The address the metric endpoint binds to.") flag.StringVar(&probeAddr, "health-probe-bind-address", ":8081", "The address the probe endpoint binds to.") flag.StringVar(&sshAddr, "ssh-bind-address", ":2222", "The address SSH server binds to.") diff --git a/controllers/nclet/metrics.go b/controllers/nclet/metrics.go new file mode 100644 index 0000000..58c2bdf --- /dev/null +++ b/controllers/nclet/metrics.go @@ -0,0 +1,35 @@ +package controllers + +import "github.com/prometheus/client_golang/prometheus" + +func RegisterMetrics(reg prometheus.Registerer) { + reg.MustRegister(sshAuthTotal) + reg.MustRegister(sshSessionDuration) +} + +var ( + sshAuthTotal = prometheus.NewCounterVec( + prometheus.CounterOpts{ + Namespace: "netcon", + Subsystem: "nclet", + Name: "ssh_auth_total", + Help: "Total number of SSH authentication attempts", + }, + []string{"result"}, + ) + + sshAuthTotalSucceeded = sshAuthTotal.WithLabelValues("succeeded") + sshAuthTotalFailed = sshAuthTotal.WithLabelValues("failed") + + sshSessionDuration = prometheus.NewHistogram( + prometheus.HistogramOpts{ + Namespace: "netcon", + Subsystem: "nclet", + Name: "ssh_session_duration_seconds", + Help: "Duration of SSH sessions in seconds", + Buckets: []float64{ + 10, 30, 60, 180, 300, 600, 900, 1200, 1800, 2700, 3600, 7200, 10800, 14400, 21600, 28800, 36000, 43200, + }, + }, + ) +) diff --git a/controllers/nclet/ssh_server.go b/controllers/nclet/ssh_server.go index 7fce8e5..32c0c9b 100644 --- a/controllers/nclet/ssh_server.go +++ b/controllers/nclet/ssh_server.go @@ -228,9 +228,11 @@ func (r *SSHServer) Start(ctx context.Context) error { log := log.FromContext(ctx) if err := r.handlePasswordAuthentication(ctx, sctx, password); err != nil { log.Info("ssh authentication failed", "remoteAddr", sctx.RemoteAddr().String(), "user", sctx.User(), "reason", err) + sshAuthTotalFailed.Inc() return false } log.Info("ssh authentication successful", "remoteAddr", sctx.RemoteAddr().String(), "user", sctx.User()) + sshAuthTotalSucceeded.Inc() return true }, Handler: func(s ssh.Session) { @@ -239,6 +241,7 @@ func (r *SSHServer) Start(ctx context.Context) error { defer func() { duration := time.Since(start).Seconds() log.Info("ssh session finished", "remoteAddr", s.RemoteAddr().String(), "user", s.User(), "durationSecond", duration) + sshSessionDuration.Observe(duration) }() r.handle(ctx, s)