Skip to content

Commit d0ab879

Browse files
committed
Add SSH certificate expiry date to metrics
1 parent dcfa228 commit d0ab879

File tree

5 files changed

+68
-37
lines changed

5 files changed

+68
-37
lines changed

client.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import (
1414

1515
type client struct {
1616
key clientKey
17+
sshCert *ssh.Certificate
1718
sshConfig ssh.ClientConfig
1819
sshClient *ssh.Client
1920
httpClient *http.Client
@@ -42,17 +43,16 @@ func (key *clientKey) String() string {
4243

4344
// establishes the SSH connection and sets up the HTTP client
4445
func (client *client) connect() error {
45-
4646
sshClient, err := ssh.Dial("tcp", client.key.hostPort(), &client.sshConfig)
4747
if err != nil {
4848
metrics.connections.failed++
49-
log.Printf("SSH connection to %v failed: %v", client.key.String(), err)
49+
log.Printf("SSH connection to %s failed: %v", client.key.String(), err)
5050
return err
5151
}
5252

5353
client.sshClient = sshClient
5454
metrics.connections.established++
55-
log.Printf("SSH connection to %v established", client.key.String())
55+
log.Printf("SSH connection to %s established", client.key.String())
5656

5757
return nil
5858
}
@@ -87,10 +87,10 @@ retry:
8787

8888
if err == nil {
8989
metrics.forwardings.established++
90-
log.Printf("TCP forwarding via %v to %s established", client.key.String(), address)
90+
log.Printf("TCP forwarding via %s to %s established", client.key.String(), address)
9191
} else {
9292
metrics.forwardings.failed++
93-
log.Printf("TCP forwarding via %v to %s failed: %s", client.key.String(), address, err)
93+
log.Printf("TCP forwarding via %s to %s failed: %s", client.key.String(), address, err)
9494
}
9595

9696
return conn, err

client_test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,7 @@ func TestClientDialHTTPS(t *testing.T) {
139139
response.Body.Close()
140140
}
141141
}
142+
142143
func TestInvalidRequestURI(t *testing.T) {
143144
assert := assert.New(t)
144145

main.go

Lines changed: 16 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,9 @@ var home = func() string {
2424
return os.Getenv("HOME")
2525
}()
2626

27-
var sshKeyDir = or(os.Getenv("HOS_KEY_DIR"), filepath.Join(home, ".ssh"))
28-
2927
var (
30-
sshKeys = []string{
28+
sshKeyDir = envStr("HOS_KEY_DIR", filepath.Join(home, ".ssh"))
29+
sshKeys = []string{
3130
filepath.Join(sshKeyDir, "id_rsa"),
3231
filepath.Join(sshKeyDir, "id_ed25519"),
3332
}
@@ -37,18 +36,10 @@ var (
3736

3837
// command line flags
3938
var (
40-
listen = or(os.Getenv("HOS_LISTEN"), "[::1]:8080")
41-
enableMetrics = os.Getenv("HOS_METRICS") != "0"
42-
sshUser = or(os.Getenv("HOS_USER"), "root")
43-
sshTimeout = func() time.Duration {
44-
dur := os.Getenv("HOS_TIMEOUT")
45-
if dur != "" {
46-
if d, err := time.ParseDuration(dur); err != nil {
47-
return d
48-
}
49-
}
50-
return 10 * time.Second
51-
}()
39+
listen = envStr("HOS_LISTEN", "[::1]:8080")
40+
enableMetrics = envStr("HOS_METRICS", "1") != "0"
41+
sshUser = envStr("HOS_USER", "root")
42+
sshTimeout = envDur("HOS_TIMEOUT", 10*time.Second)
5243
)
5344

5445
// build flags
@@ -97,9 +88,16 @@ func main() {
9788
log.Fatal(http.ListenAndServe(listen, nil))
9889
}
9990

100-
func or(s, alt string) string {
101-
if s != "" {
91+
func envStr(name, fallback string) string {
92+
if s := os.Getenv(name); s != "" {
10293
return s
10394
}
104-
return alt
95+
return fallback
96+
}
97+
98+
func envDur(name string, fallback time.Duration) time.Duration {
99+
if dur, err := time.ParseDuration(os.Getenv(name)); err == nil {
100+
return dur
101+
}
102+
return fallback
105103
}

metrics.go

Lines changed: 36 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,47 +1,69 @@
11
package main
22

3-
import "github.com/prometheus/client_golang/prometheus"
3+
import (
4+
"github.com/prometheus/client_golang/prometheus"
5+
)
46

57
type connectionStats struct {
68
established uint
79
failed uint
810
}
911

1012
type prometheusExporter struct {
13+
certTTL *prometheus.Desc
14+
connUp *prometheus.Desc
15+
conns *prometheus.Desc
16+
fwds *prometheus.Desc
17+
1118
connections connectionStats
1219
forwardings connectionStats
1320
}
1421

1522
var (
16-
metrics = prometheusExporter{}
17-
18-
variableLabels = []string{"state"}
19-
sshConnectionUpDesc = prometheus.NewDesc("sshproxy_connection_up", "SSH connection up", []string{"host"}, nil)
20-
sshConnectionsDesc = prometheus.NewDesc("sshproxy_connections_total", "SSH connections", variableLabels, nil)
21-
sshForwardingsDesc = prometheus.NewDesc("sshproxy_forwardings_total", "TCP forwardings", variableLabels, nil)
23+
connLabels = []string{"state"}
24+
hostLabel = []string{"host"}
2225
)
2326

27+
var metrics = prometheusExporter{
28+
certTTL: prometheus.NewDesc("sshproxy_certificate_ttl", "TTL until SSH certificate expires", hostLabel, nil),
29+
connUp: prometheus.NewDesc("sshproxy_connection_up", "SSH connection up", hostLabel, nil),
30+
conns: prometheus.NewDesc("sshproxy_connections_total", "SSH connections", connLabels, nil),
31+
fwds: prometheus.NewDesc("sshproxy_forwardings_total", "TCP forwardings", connLabels, nil),
32+
}
33+
2434
// Describe implements (part of the) prometheus.Collector interface.
2535
func (e *prometheusExporter) Describe(c chan<- *prometheus.Desc) {
26-
c <- sshConnectionsDesc
27-
c <- sshForwardingsDesc
36+
c <- metrics.certTTL
37+
c <- metrics.connUp
38+
c <- metrics.conns
39+
c <- metrics.fwds
2840
}
2941

3042
// Collect implements (part of the) prometheus.Collector interface.
3143
func (e prometheusExporter) Collect(c chan<- prometheus.Metric) {
3244
const C = prometheus.CounterValue
33-
c <- prometheus.MustNewConstMetric(sshConnectionsDesc, C, float64(e.connections.established), "established")
34-
c <- prometheus.MustNewConstMetric(sshConnectionsDesc, C, float64(e.connections.failed), "failed")
35-
c <- prometheus.MustNewConstMetric(sshForwardingsDesc, C, float64(e.forwardings.established), "established")
36-
c <- prometheus.MustNewConstMetric(sshForwardingsDesc, C, float64(e.forwardings.failed), "failed")
45+
const G = prometheus.GaugeValue
46+
met := prometheus.MustNewConstMetric
47+
48+
c <- met(metrics.conns, C, float64(e.connections.established), "established")
49+
c <- met(metrics.conns, C, float64(e.connections.failed), "failed")
50+
c <- met(metrics.fwds, C, float64(e.forwardings.established), "established")
51+
c <- met(metrics.fwds, C, float64(e.forwardings.failed), "failed")
3752

3853
proxy.mtx.Lock()
3954
for key, client := range proxy.clients {
55+
host := key.String()
56+
4057
var up float64
4158
if client.sshClient != nil {
4259
up = 1
4360
}
44-
c <- prometheus.MustNewConstMetric(sshConnectionUpDesc, prometheus.GaugeValue, up, key.String())
61+
c <- met(metrics.connUp, G, up, host)
62+
63+
if cert := client.sshCert; cert != nil {
64+
ttl := float64(cert.ValidBefore)
65+
c <- met(metrics.certTTL, G, ttl, host)
66+
}
4567
}
4668
proxy.mtx.Unlock()
4769
}

proxy.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,16 @@ func (proxy *Proxy) getClient(key clientKey) *client {
3838
key: key,
3939
sshConfig: proxy.sshConfig, // make copy
4040
}
41+
pClient.sshConfig.HostKeyCallback = func(hostname string, remote net.Addr, key ssh.PublicKey) error {
42+
if err := proxy.sshConfig.HostKeyCallback(hostname, remote, key); err != nil {
43+
return err
44+
}
45+
if cert, ok := key.(*ssh.Certificate); ok && cert != nil {
46+
pClient.sshCert = cert
47+
}
48+
return nil
49+
}
50+
4151
if key.username != "" {
4252
pClient.sshConfig.User = key.username
4353
}

0 commit comments

Comments
 (0)