Skip to content

Commit dd5bc3d

Browse files
authored
Merge pull request #2 from ComputerScienceHouse/dev
Handle Proxy Client Registration
2 parents c0ab6e2 + 88837e7 commit dd5bc3d

11 files changed

Lines changed: 80 additions & 16 deletions

File tree

chart/Chart.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,5 @@ apiVersion: v2
22
name: pint
33
description: Pouring IPA for Network Trust - CSH WiFi EAP-TLS enrollment and home RadSec management
44
type: application
5-
version: 0.3.3
5+
version: 0.4.0
66
appVersion: "0.1.0"

chart/templates/deployment.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,10 @@ spec:
7878
- name: PINT_RADIUS_RADSEC_PROXY_PROTOCOL
7979
value: "true"
8080
{{- end }}
81+
{{- if .Values.config.radSecProxyHosts }}
82+
- name: PINT_RADIUS_RADSEC_PROXY_HOSTS
83+
value: {{ .Values.config.radSecProxyHosts | join "," | quote }}
84+
{{- end }}
8185
{{- if .Values.config.radSecStatusPort }}
8286
- name: PINT_RADIUS_STATUS_PORT
8387
value: {{ .Values.config.radSecStatusPort | quote }}

chart/values.schema.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,14 @@
9696
"description": "Enable PROXY protocol on the RadSec listener. Required when HAProxy fronts FreeRADIUS.",
9797
"default": false
9898
},
99+
"radSecProxyHosts": {
100+
"type": "array",
101+
"description": "IPs/CIDRs of trusted proxy hosts (e.g. HAProxy). Required alongside radSecProxyProtocol so FreeRADIUS accepts their TCP connections before reading the PROXY header.",
102+
"items": {
103+
"type": "string"
104+
},
105+
"default": []
106+
},
99107
"radSecStatusPort": {
100108
"type": "string",
101109
"description": "Override the FreeRADIUS status server port (default: 18121)."

chart/values.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ config:
3636
# RADIUS / RadSec
3737
radSecCheckCRL: true # set false to disable CRL checking in the RadSec TLS listener
3838
radSecProxyProtocol: true # set false when HAProxy is not fronting FreeRADIUS
39+
radSecProxyHosts: [] # IPs/CIDRs of trusted proxy hosts (e.g. HAProxy); required alongside radSecProxyProtocol so FreeRADIUS accepts connections before reading the PROXY header
3940
radSecStatusPort: "" # overrides the FreeRADIUS status server port (default: 18121)
4041
radSecStatusAddr: "" # overrides the status server address (host:port); useful in dev when pod IPs are unreachable
4142

cmd/pint/main.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,13 @@ func main() {
9696
if err := radius.WriteRadSecTLS(ctx, k8sClient, cfg.Namespace, cfg.ConfigSecret, cfg.FreeRADIUSDeployment, cfg.RadSecCheckCRL, cfg.RadSecProxyProtocol); err != nil {
9797
log.Fatal("write radsec-tls.conf failed", zap.Error(err))
9898
}
99+
clientStore := radius.NewClientStore(k8sClient, cfg.Namespace, cfg.ConfigSecret)
100+
if err := clientStore.Load(ctx); err != nil {
101+
log.Fatal("load radius client store failed", zap.Error(err))
102+
}
103+
if err := radius.WriteRadiusConfig(ctx, k8sClient, cfg.Namespace, cfg.ConfigSecret, cfg.FreeRADIUSDeployment, clientStore.All(), cfg.RadSecProxyHosts); err != nil {
104+
log.Fatal("write clients.conf failed", zap.Error(err))
105+
}
99106

100107
// Fetch CA certs in parallel.
101108
var (

internal/config/config.go

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,9 @@ type Config struct {
4242
// FreeRADIUS status virtual server
4343
RADIUSStatusPort string // PINT_RADIUS_STATUS_PORT: port for the FreeRADIUS status virtual server
4444
RADIUSStatusAddr string // PINT_RADIUS_STATUS_ADDR: override address (host:port) for status queries; replaces per-pod IP (useful when pod IPs are unreachable, e.g. local dev against kind)
45-
RadSecCheckCRL bool // PINT_RADIUS_RADSEC_CHECK_CRL: enable CRL checking in the RadSec TLS listener (default true; set false for local dev)
46-
RadSecProxyProtocol bool // PINT_RADIUS_RADSEC_PROXY_PROTOCOL: expect HAProxy PROXY protocol header on RadSec connections (default false)
45+
RadSecCheckCRL bool // PINT_RADIUS_RADSEC_CHECK_CRL: enable CRL checking in the RadSec TLS listener (default true; set false for local dev)
46+
RadSecProxyProtocol bool // PINT_RADIUS_RADSEC_PROXY_PROTOCOL: expect HAProxy PROXY protocol header on RadSec connections (default false)
47+
RadSecProxyHosts []string // PINT_RADIUS_RADSEC_PROXY_HOSTS: comma-separated IPs/CIDRs of trusted proxy hosts (e.g. HAProxy); added as clients so FreeRADIUS accepts their connections before reading the PROXY header
4748

4849

4950
// Apple profile signing
@@ -127,6 +128,13 @@ func Load() (*Config, error) {
127128
cfg.IPASkipTLSVerify = os.Getenv("PINT_IPA_SKIP_TLS_VERIFY") == "true"
128129
cfg.RadSecCheckCRL = os.Getenv("PINT_RADIUS_RADSEC_CHECK_CRL") != "false"
129130
cfg.RadSecProxyProtocol = os.Getenv("PINT_RADIUS_RADSEC_PROXY_PROTOCOL") == "true"
131+
if v := os.Getenv("PINT_RADIUS_RADSEC_PROXY_HOSTS"); v != "" {
132+
for _, h := range strings.Split(v, ",") {
133+
if h = strings.TrimSpace(h); h != "" {
134+
cfg.RadSecProxyHosts = append(cfg.RadSecProxyHosts, h)
135+
}
136+
}
137+
}
130138
cfg.RootCAName = os.Getenv("PINT_IPA_ROOT_CA_NAME")
131139
if cfg.RootCAName == "" {
132140
cfg.RootCAName = "ipa"

internal/handlers/radius.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -195,7 +195,7 @@ func (s *Server) commitStore(c *gin.Context, store *radius.ClientStore) error {
195195
s.fail(c, http.StatusInternalServerError, "radius store save failed", err)
196196
return err
197197
}
198-
if err := radius.WriteRadiusConfig(ctx, s.K8s, s.Cfg.Namespace, s.Cfg.ConfigSecret, s.Cfg.FreeRADIUSDeployment, store.All()); err != nil {
198+
if err := radius.WriteRadiusConfig(ctx, s.K8s, s.Cfg.Namespace, s.Cfg.ConfigSecret, s.Cfg.FreeRADIUSDeployment, store.All(), s.Cfg.RadSecProxyHosts); err != nil {
199199
s.fail(c, http.StatusInternalServerError, "radius config write failed", err)
200200
return err
201201
}

internal/radius/config.go

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,9 +39,11 @@ func RenderRadSecTLS(checkCRL, proxyProtocol bool) string {
3939
`, proxy, crl)
4040
}
4141

42-
// RenderClientsConf generates the FreeRADIUS clients.conf content from the given client list.
43-
// Each client block includes proto = tls for RadSec (RFC 6614) support.
44-
func RenderClientsConf(clients []RadiusClient) string {
42+
// RenderClientsConf generates the FreeRADIUS clients.conf content from the given client list
43+
// and optional proxy host IPs/CIDRs. Each client block includes proto = tls for RadSec
44+
// (RFC 6614) support. Proxy hosts are added as bare TLS clients so FreeRADIUS accepts their
45+
// TCP connections before reading the HAProxy PROXY protocol header.
46+
func RenderClientsConf(clients []RadiusClient, proxyHosts []string) string {
4547
var b strings.Builder
4648
b.WriteString("# Auto-generated by PINT - do not edit manually\n\n")
4749

@@ -61,5 +63,16 @@ func RenderClientsConf(clients []RadiusClient) string {
6163
b.WriteString(" virtual_server = radsec\n")
6264
b.WriteString("}\n\n")
6365
}
66+
67+
for i, host := range proxyHosts {
68+
fmt.Fprintf(&b, "client pint_proxy_%d {\n", i)
69+
fmt.Fprintf(&b, " ipaddr = %s\n", host)
70+
b.WriteString(" secret = radsec\n")
71+
b.WriteString(" proto = tls\n")
72+
fmt.Fprintf(&b, " shortname = pint-proxy-%d\n", i)
73+
b.WriteString(" virtual_server = radsec\n")
74+
b.WriteString("}\n\n")
75+
}
76+
6477
return b.String()
6578
}

internal/radius/config_test.go

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ func TestRenderClientsConf_WithIP(t *testing.T) {
5050
clients := []radius.RadiusClient{
5151
{Username: "mbillow", IPCIDR: &ip},
5252
}
53-
out := radius.RenderClientsConf(clients)
53+
out := radius.RenderClientsConf(clients, nil)
5454

5555
if !strings.Contains(out, "client mbillow_home") {
5656
t.Error("missing client block header")
@@ -76,7 +76,7 @@ func TestRenderClientsConf_NoIP(t *testing.T) {
7676
clients := []radius.RadiusClient{
7777
{Username: "jsmith", IPCIDR: nil},
7878
}
79-
out := radius.RenderClientsConf(clients)
79+
out := radius.RenderClientsConf(clients, nil)
8080

8181
if !strings.Contains(out, "ipaddr = 0.0.0.0/0") {
8282
t.Error("missing wildcard ipaddr for nil IPCIDR")
@@ -89,7 +89,7 @@ func TestRenderClientsConf_MultipleClients(t *testing.T) {
8989
{Username: "alice", IPCIDR: &ip},
9090
{Username: "bob", IPCIDR: nil},
9191
}
92-
out := radius.RenderClientsConf(clients)
92+
out := radius.RenderClientsConf(clients, nil)
9393

9494
if !strings.Contains(out, "client alice_home") {
9595
t.Error("missing alice block")
@@ -100,8 +100,31 @@ func TestRenderClientsConf_MultipleClients(t *testing.T) {
100100
}
101101

102102
func TestRenderClientsConf_Empty(t *testing.T) {
103-
out := radius.RenderClientsConf(nil)
103+
out := radius.RenderClientsConf(nil, nil)
104104
if !strings.Contains(out, "Auto-generated by PINT") {
105105
t.Error("missing header comment")
106106
}
107107
}
108+
109+
func TestRenderClientsConf_ProxyHosts(t *testing.T) {
110+
out := radius.RenderClientsConf(nil, []string{"10.0.0.1", "10.0.0.2/32"})
111+
112+
if !strings.Contains(out, "client pint_proxy_0") {
113+
t.Error("missing pint_proxy_0 block")
114+
}
115+
if !strings.Contains(out, "ipaddr = 10.0.0.1") {
116+
t.Error("missing first proxy host IP")
117+
}
118+
if !strings.Contains(out, "client pint_proxy_1") {
119+
t.Error("missing pint_proxy_1 block")
120+
}
121+
if !strings.Contains(out, "ipaddr = 10.0.0.2/32") {
122+
t.Error("missing second proxy host IP")
123+
}
124+
if !strings.Contains(out, "proto = tls") {
125+
t.Error("missing proto = tls on proxy client")
126+
}
127+
if !strings.Contains(out, "virtual_server = radsec") {
128+
t.Error("missing virtual_server = radsec on proxy client")
129+
}
130+
}

internal/radius/reload.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,10 @@ const (
2222
KeyRadSecTLS = "radsec-tls.conf"
2323
)
2424

25-
// WriteRadiusConfig renders clients.conf from the given client list, patches the
25+
// WriteRadiusConfig renders clients.conf from the given client list and proxy hosts, patches the
2626
// key in the named Kubernetes Secret, and triggers a FreeRADIUS rollout restart.
27-
func WriteRadiusConfig(ctx context.Context, k8s kubernetes.Interface, namespace, secretName, deployment string, clients []RadiusClient) error {
28-
if err := patchSecretKey(ctx, k8s, namespace, secretName, KeyClientsConf, []byte(RenderClientsConf(clients))); err != nil {
27+
func WriteRadiusConfig(ctx context.Context, k8s kubernetes.Interface, namespace, secretName, deployment string, clients []RadiusClient, proxyHosts []string) error {
28+
if err := patchSecretKey(ctx, k8s, namespace, secretName, KeyClientsConf, []byte(RenderClientsConf(clients, proxyHosts))); err != nil {
2929
return err
3030
}
3131
return Reload(ctx, k8s, namespace, deployment)
@@ -89,7 +89,7 @@ func EnsureConfigSecret(ctx context.Context, k8s kubernetes.Interface, namespace
8989
ObjectMeta: metav1.ObjectMeta{Name: secretName, Namespace: namespace},
9090
Data: map[string][]byte{
9191
KeyClientsJSON: []byte("[]"),
92-
KeyClientsConf: []byte(RenderClientsConf(nil)),
92+
KeyClientsConf: []byte(RenderClientsConf(nil, nil)),
9393
KeyStatusSecret: []byte(""),
9494
KeyStatus: []byte(""),
9595
KeyRadSecTLS: []byte(RenderRadSecTLS(true, false)),

0 commit comments

Comments
 (0)