Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion chart/Chart.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@ apiVersion: v2
name: pint
description: Pouring IPA for Network Trust - CSH WiFi EAP-TLS enrollment and home RadSec management
type: application
version: 0.3.3
version: 0.4.0
appVersion: "0.1.0"
4 changes: 4 additions & 0 deletions chart/templates/deployment.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,10 @@ spec:
- name: PINT_RADIUS_RADSEC_PROXY_PROTOCOL
value: "true"
{{- end }}
{{- if .Values.config.radSecProxyHosts }}
- name: PINT_RADIUS_RADSEC_PROXY_HOSTS
value: {{ .Values.config.radSecProxyHosts | join "," | quote }}
{{- end }}
{{- if .Values.config.radSecStatusPort }}
- name: PINT_RADIUS_STATUS_PORT
value: {{ .Values.config.radSecStatusPort | quote }}
Expand Down
8 changes: 8 additions & 0 deletions chart/values.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,14 @@
"description": "Enable PROXY protocol on the RadSec listener. Required when HAProxy fronts FreeRADIUS.",
"default": false
},
"radSecProxyHosts": {
"type": "array",
"description": "IPs/CIDRs of trusted proxy hosts (e.g. HAProxy). Required alongside radSecProxyProtocol so FreeRADIUS accepts their TCP connections before reading the PROXY header.",
"items": {
"type": "string"
},
"default": []
},
"radSecStatusPort": {
"type": "string",
"description": "Override the FreeRADIUS status server port (default: 18121)."
Expand Down
1 change: 1 addition & 0 deletions chart/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ config:
# RADIUS / RadSec
radSecCheckCRL: true # set false to disable CRL checking in the RadSec TLS listener
radSecProxyProtocol: true # set false when HAProxy is not fronting FreeRADIUS
radSecProxyHosts: [] # IPs/CIDRs of trusted proxy hosts (e.g. HAProxy); required alongside radSecProxyProtocol so FreeRADIUS accepts connections before reading the PROXY header
radSecStatusPort: "" # overrides the FreeRADIUS status server port (default: 18121)
radSecStatusAddr: "" # overrides the status server address (host:port); useful in dev when pod IPs are unreachable

Expand Down
7 changes: 7 additions & 0 deletions cmd/pint/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,13 @@ func main() {
if err := radius.WriteRadSecTLS(ctx, k8sClient, cfg.Namespace, cfg.ConfigSecret, cfg.FreeRADIUSDeployment, cfg.RadSecCheckCRL, cfg.RadSecProxyProtocol); err != nil {
log.Fatal("write radsec-tls.conf failed", zap.Error(err))
}
clientStore := radius.NewClientStore(k8sClient, cfg.Namespace, cfg.ConfigSecret)
if err := clientStore.Load(ctx); err != nil {
log.Fatal("load radius client store failed", zap.Error(err))
}
if err := radius.WriteRadiusConfig(ctx, k8sClient, cfg.Namespace, cfg.ConfigSecret, cfg.FreeRADIUSDeployment, clientStore.All(), cfg.RadSecProxyHosts); err != nil {
log.Fatal("write clients.conf failed", zap.Error(err))
}

// Fetch CA certs in parallel.
var (
Expand Down
12 changes: 10 additions & 2 deletions internal/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,9 @@ type Config struct {
// FreeRADIUS status virtual server
RADIUSStatusPort string // PINT_RADIUS_STATUS_PORT: port for the FreeRADIUS status virtual server
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)
RadSecCheckCRL bool // PINT_RADIUS_RADSEC_CHECK_CRL: enable CRL checking in the RadSec TLS listener (default true; set false for local dev)
RadSecProxyProtocol bool // PINT_RADIUS_RADSEC_PROXY_PROTOCOL: expect HAProxy PROXY protocol header on RadSec connections (default false)
RadSecCheckCRL bool // PINT_RADIUS_RADSEC_CHECK_CRL: enable CRL checking in the RadSec TLS listener (default true; set false for local dev)
RadSecProxyProtocol bool // PINT_RADIUS_RADSEC_PROXY_PROTOCOL: expect HAProxy PROXY protocol header on RadSec connections (default false)
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


// Apple profile signing
Expand Down Expand Up @@ -127,6 +128,13 @@ func Load() (*Config, error) {
cfg.IPASkipTLSVerify = os.Getenv("PINT_IPA_SKIP_TLS_VERIFY") == "true"
cfg.RadSecCheckCRL = os.Getenv("PINT_RADIUS_RADSEC_CHECK_CRL") != "false"
cfg.RadSecProxyProtocol = os.Getenv("PINT_RADIUS_RADSEC_PROXY_PROTOCOL") == "true"
if v := os.Getenv("PINT_RADIUS_RADSEC_PROXY_HOSTS"); v != "" {
for _, h := range strings.Split(v, ",") {
if h = strings.TrimSpace(h); h != "" {
cfg.RadSecProxyHosts = append(cfg.RadSecProxyHosts, h)
}
}
}
cfg.RootCAName = os.Getenv("PINT_IPA_ROOT_CA_NAME")
if cfg.RootCAName == "" {
cfg.RootCAName = "ipa"
Expand Down
2 changes: 1 addition & 1 deletion internal/handlers/radius.go
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,7 @@ func (s *Server) commitStore(c *gin.Context, store *radius.ClientStore) error {
s.fail(c, http.StatusInternalServerError, "radius store save failed", err)
return err
}
if err := radius.WriteRadiusConfig(ctx, s.K8s, s.Cfg.Namespace, s.Cfg.ConfigSecret, s.Cfg.FreeRADIUSDeployment, store.All()); err != nil {
if err := radius.WriteRadiusConfig(ctx, s.K8s, s.Cfg.Namespace, s.Cfg.ConfigSecret, s.Cfg.FreeRADIUSDeployment, store.All(), s.Cfg.RadSecProxyHosts); err != nil {
s.fail(c, http.StatusInternalServerError, "radius config write failed", err)
return err
}
Expand Down
19 changes: 16 additions & 3 deletions internal/radius/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,11 @@ func RenderRadSecTLS(checkCRL, proxyProtocol bool) string {
`, proxy, crl)
}

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

Expand All @@ -61,5 +63,16 @@ func RenderClientsConf(clients []RadiusClient) string {
b.WriteString(" virtual_server = radsec\n")
b.WriteString("}\n\n")
}

for i, host := range proxyHosts {
fmt.Fprintf(&b, "client pint_proxy_%d {\n", i)
fmt.Fprintf(&b, " ipaddr = %s\n", host)
b.WriteString(" secret = radsec\n")
b.WriteString(" proto = tls\n")
fmt.Fprintf(&b, " shortname = pint-proxy-%d\n", i)
b.WriteString(" virtual_server = radsec\n")
b.WriteString("}\n\n")
}

return b.String()
}
31 changes: 27 additions & 4 deletions internal/radius/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ func TestRenderClientsConf_WithIP(t *testing.T) {
clients := []radius.RadiusClient{
{Username: "mbillow", IPCIDR: &ip},
}
out := radius.RenderClientsConf(clients)
out := radius.RenderClientsConf(clients, nil)

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

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

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

func TestRenderClientsConf_Empty(t *testing.T) {
out := radius.RenderClientsConf(nil)
out := radius.RenderClientsConf(nil, nil)
if !strings.Contains(out, "Auto-generated by PINT") {
t.Error("missing header comment")
}
}

func TestRenderClientsConf_ProxyHosts(t *testing.T) {
out := radius.RenderClientsConf(nil, []string{"10.0.0.1", "10.0.0.2/32"})

if !strings.Contains(out, "client pint_proxy_0") {
t.Error("missing pint_proxy_0 block")
}
if !strings.Contains(out, "ipaddr = 10.0.0.1") {
t.Error("missing first proxy host IP")
}
if !strings.Contains(out, "client pint_proxy_1") {
t.Error("missing pint_proxy_1 block")
}
if !strings.Contains(out, "ipaddr = 10.0.0.2/32") {
t.Error("missing second proxy host IP")
}
if !strings.Contains(out, "proto = tls") {
t.Error("missing proto = tls on proxy client")
}
if !strings.Contains(out, "virtual_server = radsec") {
t.Error("missing virtual_server = radsec on proxy client")
}
}
8 changes: 4 additions & 4 deletions internal/radius/reload.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,10 @@ const (
KeyRadSecTLS = "radsec-tls.conf"
)

// WriteRadiusConfig renders clients.conf from the given client list, patches the
// WriteRadiusConfig renders clients.conf from the given client list and proxy hosts, patches the
// key in the named Kubernetes Secret, and triggers a FreeRADIUS rollout restart.
func WriteRadiusConfig(ctx context.Context, k8s kubernetes.Interface, namespace, secretName, deployment string, clients []RadiusClient) error {
if err := patchSecretKey(ctx, k8s, namespace, secretName, KeyClientsConf, []byte(RenderClientsConf(clients))); err != nil {
func WriteRadiusConfig(ctx context.Context, k8s kubernetes.Interface, namespace, secretName, deployment string, clients []RadiusClient, proxyHosts []string) error {
if err := patchSecretKey(ctx, k8s, namespace, secretName, KeyClientsConf, []byte(RenderClientsConf(clients, proxyHosts))); err != nil {
return err
}
return Reload(ctx, k8s, namespace, deployment)
Expand Down Expand Up @@ -89,7 +89,7 @@ func EnsureConfigSecret(ctx context.Context, k8s kubernetes.Interface, namespace
ObjectMeta: metav1.ObjectMeta{Name: secretName, Namespace: namespace},
Data: map[string][]byte{
KeyClientsJSON: []byte("[]"),
KeyClientsConf: []byte(RenderClientsConf(nil)),
KeyClientsConf: []byte(RenderClientsConf(nil, nil)),
KeyStatusSecret: []byte(""),
KeyStatus: []byte(""),
KeyRadSecTLS: []byte(RenderRadSecTLS(true, false)),
Expand Down
2 changes: 1 addition & 1 deletion internal/radius/reload_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ func TestWriteRadiusConfig(t *testing.T) {
{Username: "mbillow", IPCIDR: nil},
}

if err := radius.WriteRadiusConfig(ctx, k8s, "default", "pint-config", "", clients); err != nil {
if err := radius.WriteRadiusConfig(ctx, k8s, "default", "pint-config", "", clients, nil); err != nil {
t.Fatalf("WriteRadiusConfig() error: %v", err)
}

Expand Down