Skip to content

Commit 9d59c63

Browse files
committed
feat: add radsec-agent sidecar for HAProxy agent-check health probes
Replaces raw RadSec TCP health checks (which generate "unknown client" errors) with a proper HAProxy agent-check. The radsec-agent binary listens on TCP 2084, probes the FreeRADIUS status virtual server via Status-Server UDP, and responds up/down per the HAProxy agent-check protocol. HAProxy backend config: agent-check agent-port 2084
1 parent 016efdf commit 9d59c63

4 files changed

Lines changed: 87 additions & 0 deletions

File tree

Dockerfile

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,13 @@ RUN git config --system --add safe.directory '*'
1111
RUN CGO_ENABLED=0 GOOS=linux go build \
1212
-ldflags "-X github.com/ComputerScienceHouse/pint/internal/version.GitCommit=$(git rev-parse --short HEAD)" \
1313
-o pint ./cmd/pint/
14+
RUN CGO_ENABLED=0 GOOS=linux go build -o radsec-agent ./cmd/radsec-agent/
1415

1516
FROM alpine:3.19
1617
RUN apk add --no-cache ca-certificates
1718
WORKDIR /app
1819
COPY --from=builder /build/pint .
20+
COPY --from=builder /build/radsec-agent .
1921
COPY --from=builder /build/templates ./templates
2022

2123
EXPOSE 8080

chart/templates/freeradius-deployment.yaml

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,25 @@ spec:
4040
- name: pint-eap
4141
mountPath: /etc/pint/eap
4242
readOnly: true
43+
{{- if .Values.freeradius.agentCheck.enabled }}
44+
- name: radsec-agent
45+
image: {{ include "pint.image" . }}
46+
imagePullPolicy: {{ .Values.pint.image.pullPolicy }}
47+
command: ["./radsec-agent"]
48+
args:
49+
- -listen=:{{ .Values.freeradius.agentCheck.port }}
50+
- -status=127.0.0.1:18121
51+
- -secret-file=/etc/pint/config/status-secret
52+
ports:
53+
- containerPort: {{ .Values.freeradius.agentCheck.port }}
54+
name: agent-check
55+
securityContext:
56+
{{- toYaml .Values.freeradius.securityContext | nindent 12 }}
57+
volumeMounts:
58+
- name: pint-config
59+
mountPath: /etc/pint/config
60+
readOnly: true
61+
{{- end }}
4362
volumes:
4463
- name: pint-config
4564
secret:

chart/values.yaml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,12 @@ freeradius:
9191
enabled: true
9292
clientCIDR: "0.0.0.0/0" # CIDR allowed to query the status server; restrict to pod CIDR in production
9393
nodePort: null # set to expose status port via NodePort (dev only; PINT queries pods directly in prod)
94+
# HAProxy agent-check sidecar — pings the FreeRADIUS status server and
95+
# responds up/down on TCP so HAProxy can detect backend failures without
96+
# making raw RadSec connections that produce "unknown client" errors.
97+
agentCheck:
98+
enabled: true
99+
port: 2084 # must match agent-port in the HAProxy backend config
94100
securityContext:
95101
runAsNonRoot: true
96102
allowPrivilegeEscalation: false

cmd/radsec-agent/main.go

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
// cmd/radsec-agent/main.go
2+
// HAProxy agent-check sidecar for FreeRADIUS.
3+
// Listens on TCP and responds "up\n" or "down\n" based on a Status-Server
4+
// probe to the local FreeRADIUS status virtual server.
5+
package main
6+
7+
import (
8+
"context"
9+
"flag"
10+
"fmt"
11+
"log"
12+
"net"
13+
"os"
14+
"strings"
15+
"time"
16+
17+
"github.com/ComputerScienceHouse/pint/internal/radius"
18+
)
19+
20+
func main() {
21+
listenAddr := flag.String("listen", ":2084", "TCP address for HAProxy agent-check connections")
22+
statusAddr := flag.String("status", "127.0.0.1:18121", "FreeRADIUS status server UDP address")
23+
secretFile := flag.String("secret-file", "/etc/pint/config/status-secret", "File containing the RADIUS status server secret")
24+
flag.Parse()
25+
26+
secretBytes, err := os.ReadFile(*secretFile)
27+
if err != nil {
28+
log.Fatalf("read secret file: %v", err)
29+
}
30+
secret := strings.TrimSpace(string(secretBytes))
31+
32+
ln, err := net.Listen("tcp", *listenAddr)
33+
if err != nil {
34+
log.Fatalf("listen: %v", err)
35+
}
36+
log.Printf("radsec-agent listening on %s, checking %s", *listenAddr, *statusAddr)
37+
38+
for {
39+
conn, err := ln.Accept()
40+
if err != nil {
41+
log.Printf("accept: %v", err)
42+
continue
43+
}
44+
go handle(conn, *statusAddr, secret)
45+
}
46+
}
47+
48+
func handle(conn net.Conn, statusAddr, secret string) {
49+
defer conn.Close()
50+
conn.SetDeadline(time.Now().Add(5 * time.Second))
51+
52+
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
53+
defer cancel()
54+
55+
response := "down\n"
56+
if radius.QueryRADIUSStats(ctx, statusAddr, secret) != nil {
57+
response = "up\n"
58+
}
59+
fmt.Fprint(conn, response)
60+
}

0 commit comments

Comments
 (0)