Skip to content

Commit 34c1fe2

Browse files
committed
refactor: bake FreeRADIUS reload into all radius write functions
WriteRadiusConfig, WriteRadSecTLS, WriteRadSecServerCert, and WriteStatusConfig now accept a deployment name and call Reload internally whenever they change content. Callers no longer need a separate Reload call, and the cert-watcher's redundant Reload after WriteRadSecServerCert is removed.
1 parent 4c2ebae commit 34c1fe2

5 files changed

Lines changed: 48 additions & 71 deletions

File tree

cmd/pint/main.go

Lines changed: 6 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -87,27 +87,13 @@ func main() {
8787
log.Fatal("ensure status secret failed", zap.Error(err))
8888
}
8989
statusConf := radius.RenderStatusConfig(statusSecret, "0.0.0.0/0")
90-
updated, err := radius.WriteStatusConfig(context.Background(), k8sClient, cfg.Namespace, cfg.ConfigSecret, statusConf)
91-
if err != nil {
90+
if err := radius.WriteStatusConfig(context.Background(), k8sClient, cfg.Namespace, cfg.ConfigSecret, cfg.FreeRADIUSDeployment, statusConf); err != nil {
9291
log.Fatal("write status config failed", zap.Error(err))
9392
}
94-
if updated {
95-
log.Info("updated FreeRADIUS status config, triggering rollout restart")
96-
if err := radius.Reload(context.Background(), k8sClient, cfg.Namespace, cfg.FreeRADIUSDeployment); err != nil {
97-
log.Warn("status config reload failed", zap.Error(err))
98-
}
99-
}
10093

101-
tlsUpdated, err := radius.WriteRadSecTLS(context.Background(), k8sClient, cfg.Namespace, cfg.ConfigSecret, cfg.RadSecCheckCRL, cfg.RadSecProxyProtocol)
102-
if err != nil {
94+
if err := radius.WriteRadSecTLS(context.Background(), k8sClient, cfg.Namespace, cfg.ConfigSecret, cfg.FreeRADIUSDeployment, cfg.RadSecCheckCRL, cfg.RadSecProxyProtocol); err != nil {
10395
log.Fatal("write radsec-tls.conf failed", zap.Error(err))
10496
}
105-
if tlsUpdated {
106-
log.Info("updated radsec-tls.conf, triggering rollout restart", zap.Bool("check_crl", cfg.RadSecCheckCRL))
107-
if err := radius.Reload(context.Background(), k8sClient, cfg.Namespace, cfg.FreeRADIUSDeployment); err != nil {
108-
log.Warn("radsec tls config reload failed", zap.Error(err))
109-
}
110-
}
11197

11298
// Fetch CA certs in parallel. Code-signing CA is fetched only when configured.
11399
var (
@@ -328,13 +314,10 @@ func loadOrRenewRadSecServerCert(ctx context.Context, log *zap.Logger, k8sClient
328314
zap.Int("stored_bytes", len(stored)),
329315
zap.Int("expected_bytes", len(wifiCAPEM)),
330316
)
331-
if patchErr := radius.PatchSecretKey(ctx, k8sClient, cfg.Namespace, cfg.RadSecCertSecret, "wifi-ca.pem", wifiCAPEM); patchErr != nil {
332-
log.Error("failed to update wifi-ca.pem in secret", zap.Error(patchErr))
317+
if writeErr := radius.WriteRadSecServerCert(ctx, k8sClient, cfg.Namespace, cfg.RadSecCertSecret, cfg.FreeRADIUSDeployment, existing, key, caPEM, wifiCAPEM); writeErr != nil {
318+
log.Error("failed to update wifi-ca.pem in secret", zap.Error(writeErr))
333319
} else {
334320
log.Info("updated wifi-ca.pem in secret, triggering FreeRADIUS rollout restart")
335-
if reloadErr := radius.Reload(ctx, k8sClient, cfg.Namespace, cfg.FreeRADIUSDeployment); reloadErr != nil {
336-
log.Warn("FreeRADIUS rollout restart failed after wifi-ca.pem update", zap.Error(reloadErr))
337-
}
338321
}
339322
}
340323

@@ -362,7 +345,7 @@ func loadOrRenewRadSecServerCert(ctx context.Context, log *zap.Logger, k8sClient
362345
}
363346
newKeyPEM := pem.EncodeToMemory(&pem.Block{Type: "EC PRIVATE KEY", Bytes: ecKeyBytes})
364347

365-
if writeErr := radius.WriteRadSecServerCert(ctx, k8sClient, cfg.Namespace, cfg.RadSecCertSecret, newCertPEM, newKeyPEM, caPEM, wifiCAPEM); writeErr != nil {
348+
if writeErr := radius.WriteRadSecServerCert(ctx, k8sClient, cfg.Namespace, cfg.RadSecCertSecret, cfg.FreeRADIUSDeployment, newCertPEM, newKeyPEM, caPEM, wifiCAPEM); writeErr != nil {
366349
return nil, nil, false, fmt.Errorf("write radsec cert: %w", writeErr)
367350
}
368351
log.Info("issued and stored new RadSec server cert")
@@ -383,11 +366,7 @@ func watchRadSecServerCert(log *zap.Logger, k8sClient kubernetes.Interface, ipaC
383366
continue
384367
}
385368
if renewed {
386-
if err := radius.Reload(ctx, k8sClient, cfg.Namespace, cfg.FreeRADIUSDeployment); err != nil {
387-
log.Error("radsec cert watcher: freeradius reload failed", zap.Error(err))
388-
} else {
389-
log.Info("radsec cert watcher: renewed cert and reloaded freeradius")
390-
}
369+
log.Info("radsec cert watcher: renewed cert and reloaded freeradius")
391370
}
392371
}
393372
}

internal/handlers/radius.go

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -206,15 +206,11 @@ func commitStore(c *gin.Context, log *zap.Logger, store *radius.ClientStore, k8s
206206
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
207207
return "", err
208208
}
209-
if err := radius.WriteRadiusConfig(ctx, k8s, cfg.Namespace, cfg.ConfigSecret, store.All()); err != nil {
210-
log.Error("radius config write failed", zap.Error(err))
209+
if err := radius.WriteRadiusConfig(ctx, k8s, cfg.Namespace, cfg.ConfigSecret, cfg.FreeRADIUSDeployment, store.All()); err != nil {
210+
log.Error("radius config write/reload failed", zap.Error(err))
211211
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
212212
return "", err
213213
}
214-
if err := radius.Reload(ctx, k8s, cfg.Namespace, cfg.FreeRADIUSDeployment); err != nil {
215-
log.Warn("freeradius reload failed", zap.Error(err))
216-
return err.Error(), nil
217-
}
218214
log.Debug("freeradius reloaded")
219215
return "", nil
220216
}

internal/radius/reload.go

Lines changed: 27 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -23,37 +23,47 @@ const (
2323
KeyRadSecTLS = "radsec-tls.conf"
2424
)
2525

26-
// WriteRadiusConfig renders clients.conf from the given client list and patches
27-
// the clients.conf key in the named Kubernetes Secret.
28-
func WriteRadiusConfig(ctx context.Context, k8s kubernetes.Interface, namespace, secretName string, clients []RadiusClient) error {
29-
return patchSecretKey(ctx, k8s, namespace, secretName, KeyClientsConf, []byte(RenderClientsConf(clients)))
26+
// WriteRadiusConfig renders clients.conf from the given client list, patches the
27+
// key in the named Kubernetes Secret, and triggers a FreeRADIUS rollout restart.
28+
func WriteRadiusConfig(ctx context.Context, k8s kubernetes.Interface, namespace, secretName, deployment string, clients []RadiusClient) error {
29+
if err := patchSecretKey(ctx, k8s, namespace, secretName, KeyClientsConf, []byte(RenderClientsConf(clients))); err != nil {
30+
return err
31+
}
32+
return Reload(ctx, k8s, namespace, deployment)
3033
}
3134

3235
// WriteRadSecTLS renders and patches the radsec-tls.conf key in the named K8s Secret.
33-
// Returns (didUpdate, err). didUpdate is false when the existing value is identical.
34-
func WriteRadSecTLS(ctx context.Context, k8s kubernetes.Interface, namespace, secretName string, checkCRL, proxyProtocol bool) (bool, error) {
36+
// If the content changed, FreeRADIUS is reloaded. No-op (no reload) when unchanged.
37+
func WriteRadSecTLS(ctx context.Context, k8s kubernetes.Interface, namespace, secretName, deployment string, checkCRL, proxyProtocol bool) error {
3538
rendered := RenderRadSecTLS(checkCRL, proxyProtocol)
3639
existing, err := k8s.CoreV1().Secrets(namespace).Get(ctx, secretName, metav1.GetOptions{})
3740
if err == nil && string(existing.Data[KeyRadSecTLS]) == rendered {
38-
return false, nil
41+
return nil
3942
}
40-
return true, patchSecretKey(ctx, k8s, namespace, secretName, KeyRadSecTLS, []byte(rendered))
43+
if err := patchSecretKey(ctx, k8s, namespace, secretName, KeyRadSecTLS, []byte(rendered)); err != nil {
44+
return err
45+
}
46+
return Reload(ctx, k8s, namespace, deployment)
4147
}
4248

43-
// WriteRadSecServerCert writes all FreeRADIUS TLS material to the named K8s Secret:
49+
// WriteRadSecServerCert writes all FreeRADIUS TLS material to the named K8s Secret
50+
// and triggers a FreeRADIUS rollout restart:
4451
// - tls.crt / tls.key: server cert presented to RadSec clients and EAP supplicants
4552
// - ca.pem: RadSec CA chain; verifies connecting router client certificates
46-
// - wifi-ca.pem: WiFi CA cert; verifies EAP-TLS user certificates
47-
func WriteRadSecServerCert(ctx context.Context, k8s kubernetes.Interface, namespace, secretName string, certPEM, keyPEM, caPEM, wifiCAPEM []byte) error {
48-
return UpsertSecret(ctx, k8s, &corev1.Secret{
53+
// - wifi-ca.pem: WiFi CA chain; verifies EAP-TLS user client certificates
54+
func WriteRadSecServerCert(ctx context.Context, k8s kubernetes.Interface, namespace, secretName, deployment string, certPEM, keyPEM, caPEM, wifiCAPEM []byte) error {
55+
if err := UpsertSecret(ctx, k8s, &corev1.Secret{
4956
ObjectMeta: metav1.ObjectMeta{Name: secretName, Namespace: namespace},
5057
Data: map[string][]byte{
5158
"tls.crt": certPEM,
5259
"tls.key": keyPEM,
5360
"ca.pem": caPEM,
5461
"wifi-ca.pem": wifiCAPEM,
5562
},
56-
})
63+
}); err != nil {
64+
return err
65+
}
66+
return Reload(ctx, k8s, namespace, deployment)
5767
}
5868

5969
// EnsureConfigSecret creates the combined PINT config secret with all keys
@@ -72,13 +82,6 @@ func EnsureConfigSecret(ctx context.Context, k8s kubernetes.Interface, namespace
7282
})
7383
}
7484

75-
// patchSecretKey updates a single key without touching the rest of the secret,
76-
// avoiding races with other components that may patch different keys concurrently.
77-
// PatchSecretKey updates a single key in an existing K8s Secret via a merge patch.
78-
func PatchSecretKey(ctx context.Context, k8s kubernetes.Interface, namespace, secretName, key string, value []byte) error {
79-
return patchSecretKey(ctx, k8s, namespace, secretName, key, value)
80-
}
81-
8285
func patchSecretKey(ctx context.Context, k8s kubernetes.Interface, namespace, secretName, key string, value []byte) error {
8386
p, err := json.Marshal(map[string]interface{}{
8487
"data": map[string][]byte{key: value},
@@ -110,7 +113,11 @@ func createIfAbsent(ctx context.Context, k8s kubernetes.Interface, secret *corev
110113

111114
// Reload triggers a rollout restart of the FreeRADIUS deployment by patching
112115
// the pod template annotation, equivalent to kubectl rollout restart.
116+
// A no-op when deployment is empty (e.g. FreeRADIUS is disabled).
113117
func Reload(ctx context.Context, k8s kubernetes.Interface, namespace, deployment string) error {
118+
if deployment == "" {
119+
return nil
120+
}
114121
patch := fmt.Sprintf(
115122
`{"spec":{"template":{"metadata":{"annotations":{"kubectl.kubernetes.io/restartedAt":%q}}}}}`,
116123
time.Now().Format(time.RFC3339),

internal/radius/reload_test.go

Lines changed: 6 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ func TestWriteRadiusConfig(t *testing.T) {
3434
{Username: "mbillow", IPCIDR: nil},
3535
}
3636

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

@@ -60,7 +60,7 @@ func TestWriteRadSecServerCert(t *testing.T) {
6060
caPEM := []byte("-----BEGIN CERTIFICATE-----\nfake-ca\n-----END CERTIFICATE-----\n")
6161
wifiCAPEM := []byte("-----BEGIN CERTIFICATE-----\nfake-wifi-ca\n-----END CERTIFICATE-----\n")
6262

63-
if err := radius.WriteRadSecServerCert(ctx, k8s, "default", "pint-radsec-server", certPEM, keyPEM, caPEM, wifiCAPEM); err != nil {
63+
if err := radius.WriteRadSecServerCert(ctx, k8s, "default", "pint-radsec-server", "", certPEM, keyPEM, caPEM, wifiCAPEM); err != nil {
6464
t.Fatalf("WriteRadSecServerCert() error: %v", err)
6565
}
6666

@@ -82,7 +82,7 @@ func TestWriteRadSecServerCert(t *testing.T) {
8282
}
8383

8484
// Call again to exercise the update path
85-
if err := radius.WriteRadSecServerCert(ctx, k8s, "default", "pint-radsec-server", certPEM, keyPEM, caPEM, wifiCAPEM); err != nil {
85+
if err := radius.WriteRadSecServerCert(ctx, k8s, "default", "pint-radsec-server", "", certPEM, keyPEM, caPEM, wifiCAPEM); err != nil {
8686
t.Fatalf("WriteRadSecServerCert() update error: %v", err)
8787
}
8888
}
@@ -91,13 +91,9 @@ func TestWriteRadSecTLS_WritesAndDetectsChanges(t *testing.T) {
9191
ctx := context.Background()
9292
k8s := fake.NewSimpleClientset(newConfigSecret("default", "pint-config"))
9393

94-
updated, err := radius.WriteRadSecTLS(ctx, k8s, "default", "pint-config", false, false)
95-
if err != nil {
94+
if err := radius.WriteRadSecTLS(ctx, k8s, "default", "pint-config", "", false, false); err != nil {
9695
t.Fatalf("WriteRadSecTLS() error: %v", err)
9796
}
98-
if !updated {
99-
t.Error("expected updated=true on first write")
100-
}
10197

10298
secret, err := k8s.CoreV1().Secrets("default").Get(ctx, "pint-config", metav1.GetOptions{})
10399
if err != nil {
@@ -107,14 +103,10 @@ func TestWriteRadSecTLS_WritesAndDetectsChanges(t *testing.T) {
107103
t.Error("expected check_crl = no in radsec-tls.conf")
108104
}
109105

110-
// Second write with same value should not report updated.
111-
updated, err = radius.WriteRadSecTLS(ctx, k8s, "default", "pint-config", false, false)
112-
if err != nil {
106+
// Second write with same value should be a no-op (no error).
107+
if err := radius.WriteRadSecTLS(ctx, k8s, "default", "pint-config", "", false, false); err != nil {
113108
t.Fatalf("WriteRadSecTLS() second call error: %v", err)
114109
}
115-
if updated {
116-
t.Error("expected updated=false when config unchanged")
117-
}
118110
}
119111

120112
func TestReload_DeploymentNotFound(t *testing.T) {

internal/radius/statusconfig.go

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -43,11 +43,14 @@ func RenderStatusConfig(secret, cidr string) string {
4343
}
4444

4545
// WriteStatusConfig patches the status key in the named Secret with the rendered config.
46-
// Returns (didUpdate, err). didUpdate is false when the existing value is identical.
47-
func WriteStatusConfig(ctx context.Context, k8s kubernetes.Interface, namespace, secretName, config string) (bool, error) {
46+
// If the content changed, FreeRADIUS is reloaded. No-op (no reload) when unchanged.
47+
func WriteStatusConfig(ctx context.Context, k8s kubernetes.Interface, namespace, secretName, deployment, config string) error {
4848
existing, err := k8s.CoreV1().Secrets(namespace).Get(ctx, secretName, metav1.GetOptions{})
4949
if err == nil && string(existing.Data[KeyStatus]) == config {
50-
return false, nil
50+
return nil
5151
}
52-
return true, patchSecretKey(ctx, k8s, namespace, secretName, KeyStatus, []byte(config))
52+
if err := patchSecretKey(ctx, k8s, namespace, secretName, KeyStatus, []byte(config)); err != nil {
53+
return err
54+
}
55+
return Reload(ctx, k8s, namespace, deployment)
5356
}

0 commit comments

Comments
 (0)