Skip to content

Commit 379dda0

Browse files
authored
Merge pull request #14 from bubustack/feat/hardening-reconcile-webhooks-runs
harden reconcile context, validation, and storage guards
2 parents f28db35 + 2173928 commit 379dda0

20 files changed

+1266
-982
lines changed

.github/release-please-config.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
".": {
55
"release-type": "go",
66
"package-name": "bobrapet",
7+
"include-component-in-tag": false,
78
"changelog-sections": [
89
{ "type": "feat", "section": "Features", "hidden": false },
910
{ "type": "fix", "section": "Bug Fixes", "hidden": false },

.github/workflows/release-please.yml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ jobs:
5858
type=semver,pattern={{version}},value=${{ steps.release.outputs.tag_name }}
5959
type=semver,pattern={{major}}.{{minor}},value=${{ steps.release.outputs.tag_name }}
6060
type=semver,pattern={{major}},value=${{ steps.release.outputs.tag_name }}
61+
type=raw,value=${{ steps.release.outputs.tag_name }}
6162
type=raw,value=latest
6263
6364
- name: Build and push Docker image
@@ -95,6 +96,21 @@ jobs:
9596
run: |
9697
gh release upload ${{ steps.release.outputs.tag_name }} dist/install.yaml
9798
99+
- name: Warm Go module caches (proxy/sum/pkg.go.dev)
100+
if: ${{ steps.release.outputs.release_created }}
101+
run: |
102+
set -euo pipefail
103+
MOD=github.com/${{ github.repository }}
104+
VER=${{ steps.release.outputs.tag_name }}
105+
echo "Warming proxy.golang.org for $MOD@$VER"
106+
curl -sSfL "https://proxy.golang.org/${MOD}/@v/${VER}.info" || true
107+
echo "Warming sum.golang.org for $MOD@$VER"
108+
curl -sSfL "https://sum.golang.org/lookup/${MOD}@${VER}" || true
109+
echo "Triggering pkg.go.dev indexing for $MOD@$VER"
110+
curl -sSfL "https://pkg.go.dev/${MOD}@${VER}" > /dev/null || true
111+
echo "Triggering Go Report Card for $MOD"
112+
curl -X POST -F "repo=${MOD}" https://goreportcard.com/checks
113+
98114
- name: Create release summary
99115
if: ${{ steps.release.outputs.release_created }}
100116
run: |

.golangci.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ linters:
2222
- unparam
2323
- unused
2424
settings:
25+
gocyclo:
26+
min-complexity: 15
2527
revive:
2628
rules:
2729
- name: comment-spacings

cmd/main.go

Lines changed: 137 additions & 119 deletions
Original file line numberDiff line numberDiff line change
@@ -100,71 +100,27 @@ func main() {
100100

101101
ctrl.SetLogger(zap.New(zap.UseFlagOptions(&opts)))
102102

103-
// if the enable-http2 flag is false (the default), http/2 should be disabled
104-
// due to its vulnerabilities. More specifically, disabling http/2 will
105-
// prevent from being vulnerable to the HTTP/2 Stream Cancellation and
106-
// Rapid Reset CVEs. For more information see:
107-
// - https://github.com/advisories/GHSA-qppj-fm5r-hxr3
108-
// - https://github.com/advisories/GHSA-4374-p667-p6c8
109-
disableHTTP2 := func(c *tls.Config) {
110-
setupLog.Info("disabling http/2")
111-
c.NextProtos = []string{"http/1.1"}
112-
}
113-
114-
if !enableHTTP2 {
115-
tlsOpts = append(tlsOpts, disableHTTP2)
116-
}
117-
118-
// Initial webhook TLS options
119-
webhookTLSOpts := tlsOpts
120-
webhookServerOptions := webhook.Options{
121-
TLSOpts: webhookTLSOpts,
122-
}
123-
124-
if len(webhookCertPath) > 0 {
125-
setupLog.Info("Initializing webhook certificate watcher using provided certificates",
126-
"webhook-cert-path", webhookCertPath, "webhook-cert-name", webhookCertName, "webhook-cert-key", webhookCertKey)
127-
128-
webhookServerOptions.CertDir = webhookCertPath
129-
webhookServerOptions.CertName = webhookCertName
130-
webhookServerOptions.KeyName = webhookCertKey
131-
}
132-
133-
webhookServer := webhook.NewServer(webhookServerOptions)
134-
135-
// Metrics endpoint is enabled in 'config/default/kustomization.yaml'. The Metrics options configure the server.
136-
// More info:
137-
// - https://pkg.go.dev/sigs.k8s.io/[email protected]/pkg/metrics/server
138-
// - https://book.kubebuilder.io/reference/metrics.html
139-
metricsServerOptions := metricsserver.Options{
140-
BindAddress: metricsAddr,
141-
SecureServing: secureMetrics,
142-
TLSOpts: tlsOpts,
143-
}
144-
145-
if secureMetrics {
146-
// FilterProvider is used to protect the metrics endpoint with authn/authz.
147-
// These configurations ensure that only authorized users and service accounts
148-
// can access the metrics endpoint. The RBAC are configured in 'config/rbac/kustomization.yaml'. More info:
149-
// https://pkg.go.dev/sigs.k8s.io/[email protected]/pkg/metrics/filters#WithAuthenticationAndAuthorization
150-
metricsServerOptions.FilterProvider = filters.WithAuthenticationAndAuthorization
151-
}
152-
153-
// If the certificate is not specified, controller-runtime will automatically
154-
// generate self-signed certificates for the metrics server. While convenient for development and testing,
155-
// this setup is not recommended for production.
156-
//
157-
// - [METRICS-WITH-CERTS] at config/default/kustomization.yaml to generate and use certificates
158-
// managed by cert-manager for the metrics server.
159-
// - [PROMETHEUS-WITH-CERTS] at config/prometheus/kustomization.yaml for TLS certification.
160-
if len(metricsCertPath) > 0 {
161-
setupLog.Info("Initializing metrics certificate watcher using provided certificates",
162-
"metrics-cert-path", metricsCertPath, "metrics-cert-name", metricsCertName, "metrics-cert-key", metricsCertKey)
103+
// Configure HTTP/2 and TLS options
104+
configureHTTP2(enableHTTP2, &tlsOpts)
105+
106+
// Build servers' options
107+
webhookServer := webhook.NewServer(
108+
buildWebhookServerOptions(
109+
webhookCertPath,
110+
webhookCertName,
111+
webhookCertKey,
112+
tlsOpts,
113+
),
114+
)
163115

164-
metricsServerOptions.CertDir = metricsCertPath
165-
metricsServerOptions.CertName = metricsCertName
166-
metricsServerOptions.KeyName = metricsCertKey
167-
}
116+
metricsServerOptions := buildMetricsServerOptions(
117+
metricsAddr,
118+
secureMetrics,
119+
metricsCertPath,
120+
metricsCertName,
121+
metricsCertKey,
122+
tlsOpts,
123+
)
168124

169125
mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{
170126
Scheme: scheme,
@@ -173,16 +129,6 @@ func main() {
173129
HealthProbeBindAddress: probeAddr,
174130
LeaderElection: enableLeaderElection,
175131
LeaderElectionID: "d3a8b358.bubustack.io",
176-
// LeaderElectionReleaseOnCancel defines if the leader should step down voluntarily
177-
// when the Manager ends. This requires the binary to immediately end when the
178-
// Manager is stopped, otherwise, this setting is unsafe. Setting this significantly
179-
// speeds up voluntary leader transitions as the new leader don't have to wait
180-
// LeaseDuration time first.
181-
//
182-
// In the default scaffold provided, the program ends immediately after
183-
// the manager stops, so would be fine to enable this option. However,
184-
// if you are doing or is intended to do any operation such as perform cleanups
185-
// after the manager stops then its usage might be unsafe.
186132
// LeaderElectionReleaseOnCancel: true,
187133
})
188134
if err != nil {
@@ -194,6 +140,81 @@ func main() {
194140
managerCtx := ctrl.SetupSignalHandler()
195141
setup.SetupIndexers(managerCtx, mgr)
196142

143+
operatorConfigManager, controllerConfig, configResolver, celEvaluator := mustInitOperatorServices(mgr)
144+
145+
deps := config.ControllerDependencies{
146+
Client: mgr.GetClient(),
147+
Scheme: mgr.GetScheme(),
148+
ConfigResolver: configResolver,
149+
CELEvaluator: *celEvaluator,
150+
}
151+
152+
mustSetupControllers(mgr, deps, controllerConfig)
153+
154+
setupWebhooksIfEnabled(mgr, operatorConfigManager)
155+
// +kubebuilder:scaffold:builder
156+
157+
mustAddHealthChecks(mgr)
158+
159+
setupLog.Info("starting manager")
160+
defer celEvaluator.Close()
161+
if err := mgr.Start(managerCtx); err != nil {
162+
setupLog.Error(err, "problem running manager")
163+
os.Exit(1)
164+
}
165+
}
166+
167+
func configureHTTP2(enableHTTP2 bool, tlsOpts *[]func(*tls.Config)) {
168+
// if the enable-http2 flag is false (the default), http/2 should be disabled
169+
// due to its vulnerabilities.
170+
disableHTTP2 := func(c *tls.Config) {
171+
setupLog.Info("disabling http/2")
172+
c.NextProtos = []string{"http/1.1"}
173+
}
174+
if !enableHTTP2 {
175+
*tlsOpts = append(*tlsOpts, disableHTTP2)
176+
}
177+
}
178+
179+
func buildWebhookServerOptions(certPath, certName, certKey string, tlsOpts []func(*tls.Config)) webhook.Options {
180+
opts := webhook.Options{TLSOpts: tlsOpts}
181+
if len(certPath) > 0 {
182+
setupLog.Info("Initializing webhook certificate watcher using provided certificates",
183+
"webhook-cert-path", certPath, "webhook-cert-name", certName, "webhook-cert-key", certKey)
184+
opts.CertDir = certPath
185+
opts.CertName = certName
186+
opts.KeyName = certKey
187+
}
188+
return opts
189+
}
190+
191+
func buildMetricsServerOptions(
192+
metricsAddr string,
193+
secure bool,
194+
certPath, certName, certKey string,
195+
tlsOpts []func(*tls.Config),
196+
) metricsserver.Options {
197+
opts := metricsserver.Options{
198+
BindAddress: metricsAddr,
199+
SecureServing: secure,
200+
TLSOpts: tlsOpts,
201+
}
202+
if secure {
203+
opts.FilterProvider = filters.WithAuthenticationAndAuthorization
204+
}
205+
if len(certPath) > 0 {
206+
setupLog.Info("Initializing metrics certificate watcher using provided certificates",
207+
"metrics-cert-path", certPath, "metrics-cert-name", certName, "metrics-cert-key", certKey)
208+
opts.CertDir = certPath
209+
opts.CertName = certName
210+
opts.KeyName = certKey
211+
}
212+
return opts
213+
}
214+
215+
func mustInitOperatorServices(
216+
mgr ctrl.Manager,
217+
) (*config.OperatorConfigManager, *config.ControllerConfig, *config.Resolver, *cel.Evaluator) {
197218
operatorConfigManager := config.NewOperatorConfigManager(
198219
mgr.GetClient(),
199220
"bobrapet-system",
@@ -204,7 +225,6 @@ func main() {
204225
setupLog.Error(err, "unable to add operator config manager to manager")
205226
os.Exit(1)
206227
}
207-
208228
controllerConfig := operatorConfigManager.GetControllerConfig()
209229
setupLog.Info("Controller configuration loaded")
210230
configResolver := config.NewResolver(mgr.GetClient(), operatorConfigManager)
@@ -215,14 +235,14 @@ func main() {
215235
setupLog.Error(err, "unable to create CEL evaluator")
216236
os.Exit(1)
217237
}
238+
return operatorConfigManager, controllerConfig, configResolver, celEvaluator
239+
}
218240

219-
deps := config.ControllerDependencies{
220-
Client: mgr.GetClient(),
221-
Scheme: mgr.GetScheme(),
222-
ConfigResolver: configResolver,
223-
CELEvaluator: *celEvaluator,
224-
}
225-
241+
func mustSetupControllers(
242+
mgr ctrl.Manager,
243+
deps config.ControllerDependencies,
244+
controllerConfig *config.ControllerConfig,
245+
) {
226246
if err := (&controller.StoryReconciler{
227247
ControllerDependencies: deps,
228248
}).SetupWithManager(mgr, controllerConfig.BuildStoryControllerOptions()); err != nil {
@@ -265,38 +285,44 @@ func main() {
265285
setupLog.Error(err, "unable to create controller", "controller", "ImpulseTemplate")
266286
os.Exit(1)
267287
}
288+
}
268289

269-
if os.Getenv("ENABLE_WEBHOOKS") != "false" {
270-
setupLog.Info("setting up webhooks")
271-
if err := (&webhookv1alpha1.StoryWebhook{}).SetupWebhookWithManager(mgr); err != nil {
272-
setupLog.Error(err, "unable to create webhook", "webhook", "Story")
273-
os.Exit(1)
274-
}
275-
if err := (&webhookv1alpha1.EngramWebhook{
276-
Config: operatorConfigManager.GetControllerConfig(),
277-
}).SetupWebhookWithManager(mgr); err != nil {
278-
setupLog.Error(err, "unable to create webhook", "webhook", "Engram")
279-
os.Exit(1)
280-
}
281-
if err := (&webhookv1alpha1.ImpulseWebhook{
282-
Config: operatorConfigManager.GetControllerConfig(),
283-
}).SetupWebhookWithManager(mgr); err != nil {
284-
setupLog.Error(err, "unable to create webhook", "webhook", "Impulse")
285-
os.Exit(1)
286-
}
287-
if err := (&webhookrunsv1alpha1.StoryRunWebhook{
288-
Config: operatorConfigManager.GetControllerConfig(),
289-
}).SetupWebhookWithManager(mgr); err != nil {
290-
setupLog.Error(err, "unable to create webhook", "webhook", "StoryRun")
291-
os.Exit(1)
292-
}
293-
if err := (&webhookrunsv1alpha1.StepRunWebhook{}).SetupWebhookWithManager(mgr); err != nil {
294-
setupLog.Error(err, "unable to create webhook", "webhook", "StepRun")
295-
os.Exit(1)
296-
}
290+
func setupWebhooksIfEnabled(mgr ctrl.Manager, operatorConfigManager *config.OperatorConfigManager) {
291+
if os.Getenv("ENABLE_WEBHOOKS") == "false" {
292+
return
297293
}
298-
// +kubebuilder:scaffold:builder
294+
setupLog.Info("setting up webhooks")
295+
if err := (&webhookv1alpha1.StoryWebhook{
296+
Config: operatorConfigManager.GetControllerConfig(),
297+
}).SetupWebhookWithManager(mgr); err != nil {
298+
setupLog.Error(err, "unable to create webhook", "webhook", "Story")
299+
os.Exit(1)
300+
}
301+
if err := (&webhookv1alpha1.EngramWebhook{
302+
Config: operatorConfigManager.GetControllerConfig(),
303+
}).SetupWebhookWithManager(mgr); err != nil {
304+
setupLog.Error(err, "unable to create webhook", "webhook", "Engram")
305+
os.Exit(1)
306+
}
307+
if err := (&webhookv1alpha1.ImpulseWebhook{
308+
Config: operatorConfigManager.GetControllerConfig(),
309+
}).SetupWebhookWithManager(mgr); err != nil {
310+
setupLog.Error(err, "unable to create webhook", "webhook", "Impulse")
311+
os.Exit(1)
312+
}
313+
if err := (&webhookrunsv1alpha1.StoryRunWebhook{
314+
Config: operatorConfigManager.GetControllerConfig(),
315+
}).SetupWebhookWithManager(mgr); err != nil {
316+
setupLog.Error(err, "unable to create webhook", "webhook", "StoryRun")
317+
os.Exit(1)
318+
}
319+
if err := (&webhookrunsv1alpha1.StepRunWebhook{}).SetupWebhookWithManager(mgr); err != nil {
320+
setupLog.Error(err, "unable to create webhook", "webhook", "StepRun")
321+
os.Exit(1)
322+
}
323+
}
299324

325+
func mustAddHealthChecks(mgr ctrl.Manager) {
300326
if err := mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil {
301327
setupLog.Error(err, "unable to set up health check")
302328
os.Exit(1)
@@ -305,12 +331,4 @@ func main() {
305331
setupLog.Error(err, "unable to set up ready check")
306332
os.Exit(1)
307333
}
308-
309-
setupLog.Info("starting manager")
310-
// Ensure CEL evaluator background routines stop on manager shutdown
311-
defer celEvaluator.Close()
312-
if err := mgr.Start(managerCtx); err != nil {
313-
setupLog.Error(err, "problem running manager")
314-
os.Exit(1)
315-
}
316334
}

0 commit comments

Comments
 (0)