Skip to content

Commit

Permalink
Merge pull request #11 from boinkor-net/prometheus
Browse files Browse the repository at this point in the history
Serve prometheus metrics
  • Loading branch information
antifuchs authored Mar 20, 2024
2 parents 4591b3b + 9a4c26a commit d3ffbb4
Show file tree
Hide file tree
Showing 6 changed files with 109 additions and 14 deletions.
4 changes: 4 additions & 0 deletions cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (

"github.com/gliderlabs/ssh"
"github.com/peterbourgon/ff/v3/ffcli"
gossh "golang.org/x/crypto/ssh"
)

// TailnetSSH defines an SSH service that listens on a tailnet and runs a given shell program.
Expand All @@ -25,8 +26,10 @@ type TailnetSSH struct {
tsnetVerbose bool
deleteExisting bool
maxNodeAge time.Duration
prometheusAddr string
tags []string
command []string
authorizedPubKeys []gossh.PublicKey
}

var ErrMissingServiceName = fmt.Errorf("service name must be set via -name")
Expand All @@ -44,6 +47,7 @@ func TailnetSSHFromArgs(args []string) (*TailnetSSH, error) {
fs.BoolVar(&s.tsnetVerbose, "tsnetVerbose", false, "Log tsnet messages verbosely")
fs.BoolVar(&s.deleteExisting, "deleteExisting", false, "Delete any down node with a conflicting name, if one exists")
fs.DurationVar(&s.maxNodeAge, "maxNodeAge", 30*time.Second, "Matching node must be offline at least this long if -deleteExisting is set")
fs.StringVar(&s.prometheusAddr, "prometheusAddr", ":9021", "Address on the tailnet node where prometheus requests get answered")

var tags string
fs.StringVar(&tags, "tags", "", "Tailnet ACL tags assigned to the node, comma-separated")
Expand Down
2 changes: 1 addition & 1 deletion default.sri
Original file line number Diff line number Diff line change
@@ -1 +1 @@
sha256-s/BwC3UXRO9jffeM91Xm8HojVCpuLkwFuwsumkCt2SQ=
sha256-Q3uOv8iG6qGlSlM27wmeXxtD20KnWROqe4tTFS0c9cI=
6 changes: 6 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ require (
github.com/creack/pty v1.1.21
github.com/gliderlabs/ssh v0.3.6
github.com/peterbourgon/ff/v3 v3.4.0
github.com/prometheus/client_golang v1.18.0
golang.org/x/crypto v0.21.0
golang.org/x/oauth2 v0.18.0
tailscale.com v1.62.0
Expand All @@ -32,6 +33,8 @@ require (
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.21.7 // indirect
github.com/aws/aws-sdk-go-v2/service/sts v1.26.7 // indirect
github.com/aws/smithy-go v1.19.0 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/coreos/go-iptables v0.7.0 // indirect
github.com/coreos/go-systemd/v22 v22.5.0 // indirect
github.com/dblohm7/wingoes v0.0.0-20240119213807-a09d6be7affa // indirect
Expand Down Expand Up @@ -62,6 +65,9 @@ require (
github.com/miekg/dns v1.1.58 // indirect
github.com/mitchellh/go-ps v1.0.0 // indirect
github.com/pierrec/lz4/v4 v4.1.21 // indirect
github.com/prometheus/client_model v0.5.0 // indirect
github.com/prometheus/common v0.46.0 // indirect
github.com/prometheus/procfs v0.12.0 // indirect
github.com/safchain/ethtool v0.3.0 // indirect
github.com/tailscale/certstore v0.1.1-0.20231202035212-d3fa0460f47e // indirect
github.com/tailscale/go-winio v0.0.0-20231025203758-c4f33415bf55 // indirect
Expand Down
12 changes: 12 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,10 @@ github.com/aws/aws-sdk-go-v2/service/sts v1.26.7 h1:NzO4Vrau795RkUdSHKEwiR01FaGz
github.com/aws/aws-sdk-go-v2/service/sts v1.26.7/go.mod h1:6h2YuIoxaMSCFf5fi1EgZAwdfkGMgDY+DVfa61uLe4U=
github.com/aws/smithy-go v1.19.0 h1:KWFKQV80DpP3vJrrA9sVAHQ5gc2z8i4EzrLhLlWXcBM=
github.com/aws/smithy-go v1.19.0/go.mod h1:NukqUGpCZIILqqiV0NIjeFh24kd/FAa4beRb6nbIUPE=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cilium/ebpf v0.12.3 h1:8ht6F9MquybnY97at+VDZb3eQQr8ev79RueWeVaEcG4=
github.com/cilium/ebpf v0.12.3/go.mod h1:TctK1ivibvI3znr66ljgi4hqOT8EYQjz1KWBfb1UVgM=
github.com/coreos/go-iptables v0.7.0 h1:XWM3V+MPRr5/q51NuWSgU0fqMad64Zyxs8ZUoMsamr8=
Expand Down Expand Up @@ -141,6 +145,14 @@ github.com/pkg/sftp v1.13.6 h1:JFZT4XbOU7l77xGSpOdW+pwIMqP044IyjXX6FGyEKFo=
github.com/pkg/sftp v1.13.6/go.mod h1:tz1ryNURKu77RL+GuCzmoJYxQczL3wLNNpPWagdg4Qk=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_golang v1.18.0 h1:HzFfmkOzH5Q8L8G+kSJKUx5dtG87sewO+FoDDqP5Tbk=
github.com/prometheus/client_golang v1.18.0/go.mod h1:T+GXkCk5wSJyOqMIzVgvvjFDlkOQntgjkJWKrN5txjA=
github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw=
github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI=
github.com/prometheus/common v0.46.0 h1:doXzt5ybi1HBKpsZOL0sSkaNHJJqkyfEWZGGqqScV0Y=
github.com/prometheus/common v0.46.0/go.mod h1:Tp0qkxpb9Jsg54QMe+EAmqXkSV7Evdy1BTn+g2pa/hQ=
github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo=
github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo=
github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
github.com/safchain/ethtool v0.3.0 h1:gimQJpsI6sc1yIqP/y8GYgiXn/NjgvpM0RNoWLVVmP0=
Expand Down
52 changes: 52 additions & 0 deletions prometheus.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package hoopsnake

import (
"fmt"
"log"
"net/http"
"os"
"time"

"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promauto"
"github.com/prometheus/client_golang/prometheus/promhttp"
"tailscale.com/tsnet"
)

func (s *TailnetSSH) setupPrometheus(srv *tsnet.Server) error {
if s.prometheusAddr == "" {
return nil
}
mux := http.NewServeMux()
mux.Handle("/metrics", promhttp.Handler())
listener, err := srv.Listen("tcp", s.prometheusAddr)
if err != nil {
return fmt.Errorf("could not listen on prometheus address %v: %w", s.prometheusAddr, err)
}
go func() {
server := http.Server{
Handler: mux,
ReadHeaderTimeout: 1 * time.Second,
}
log.Printf("Failed to listen on prometheus address: %v", server.Serve(listener))
os.Exit(20)
}()

v4, v6 := srv.TailscaleIPs()
promauto.NewGaugeFunc(prometheus.GaugeOpts{
Name: "hoopsnake_running",
Help: "A counter set to 1.0 if hoopsnake is running.",
ConstLabels: prometheus.Labels{
"ipv4": v4.String(),
"ipv6": v6.String(),
"hostname": srv.Hostname,
},
}, func() float64 { return 1.0 })
listenAddr := s.prometheusAddr
if listenAddr[0] == ':' {
listenAddr = v4.String() + listenAddr
}

log.Printf("Serving prometheus metrics at http://%s/metrics", listenAddr)
return nil
}
47 changes: 34 additions & 13 deletions ssh.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,47 +7,64 @@ import (
"log"
"os"
"os/exec"
"strconv"
"syscall"
"unsafe"

"github.com/creack/pty"
"github.com/gliderlabs/ssh"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promauto"
gossh "golang.org/x/crypto/ssh"
"tailscale.com/client/tailscale"
"tailscale.com/tsnet"
"tailscale.com/types/logger"
)

var (
authentications = promauto.NewCounterVec(prometheus.CounterOpts{
Name: "hoopsnake_authentications",
Help: "Number of authentications attempted",
}, []string{"user", "pubkey_fpr", "pubkey", "success"})
)

func (s *TailnetSSH) setupAuthorizedKeys() error {
authorizedKeysBytes, err := os.ReadFile(s.authorizedKeyFile)
if err != nil {
log.Fatalf("Could not read authorized keys file %q: %v", s.authorizedKeyFile, err)
}
var authorizedPubKeys []gossh.PublicKey
for len(authorizedKeysBytes) > 0 {
pubKey, _, _, rest, err := ssh.ParseAuthorizedKey(authorizedKeysBytes)
if err != nil {
return fmt.Errorf("Could not parse authorized key: %w", err)
}

authorizedPubKeys = append(authorizedPubKeys, pubKey)
s.authorizedPubKeys = append(s.authorizedPubKeys, pubKey)
authorizedKeysBytes = rest
}
if len(authorizedPubKeys) > 0 {
s.Server.PublicKeyHandler = func(ctx ssh.Context, key ssh.PublicKey) bool {
log.Printf("Attempting auth for user %q with public key %q", ctx.User(), gossh.MarshalAuthorizedKey(key))
matched := false
for _, authorized := range authorizedPubKeys {
if ssh.KeysEqual(key, authorized) {
matched = true
}
}
return matched
}
if len(s.authorizedPubKeys) > 0 {
s.Server.PublicKeyHandler = s.validatePubkey
}
return nil
}

func (s *TailnetSSH) validatePubkey(ctx ssh.Context, key ssh.PublicKey) bool {
log.Printf("Attempting auth for user %q with public key %q", ctx.User(), gossh.MarshalAuthorizedKey(key))
matched := false
for _, authorized := range s.authorizedPubKeys {
if ssh.KeysEqual(key, authorized) {
matched = true
}
}
authentications.With(prometheus.Labels{
"user": ctx.User(),
"pubkey": string(gossh.MarshalAuthorizedKey(key)),
"pubkey_fpr": gossh.FingerprintSHA256(key),
"success": strconv.FormatBool(matched),
}).Inc()
return matched
}

func (s *TailnetSSH) setupHostKey() error {
if s.hostKeyFile != "" {
return ssh.HostKeyFile(s.hostKeyFile)(&s.Server)
Expand Down Expand Up @@ -108,6 +125,10 @@ func (s *TailnetSSH) Run(ctx context.Context, quit <-chan os.Signal) error {
srv.Close()
}()

err = s.setupPrometheus(srv)
if err != nil {
log.Printf("Setting up prometheus failed, but continuing anyway: %v", err)
}
log.Printf("starting ssh server on port :22...")
err = s.Server.Serve(listener)
if err != nil && !terminated {
Expand Down

0 comments on commit d3ffbb4

Please sign in to comment.