Skip to content

fix(webhook): align standalone webhook HTTP/2 hardening#1588

Open
doup2001 wants to merge 2 commits intoargoproj-labs:masterfrom
doup2001:fix-1559-webhook-enable-http2
Open

fix(webhook): align standalone webhook HTTP/2 hardening#1588
doup2001 wants to merge 2 commits intoargoproj-labs:masterfrom
doup2001:fix-1559-webhook-enable-http2

Conversation

@doup2001
Copy link
Copy Markdown

@doup2001 doup2001 commented Apr 15, 2026

Summary

This change threads the enable-http2 setting through the webhook configuration so the standalone webhook server follows the same HTTP/2 hardening policy as the controller-managed path.

When HTTP/2 is not explicitly enabled, the webhook TLS server now restricts ALPN negotiation to http/1.1. A standalone webhook command flag was also added so users can opt in to HTTP/2 intentionally.

Closes #1559

What Changed

  • added EnableHTTP2 to WebhookConfig
  • added --enable-http2 to the standalone webhook command
  • passed the controller's enableHTTP2 setting into webhook server setup
  • updated webhook TLS config building to disable HTTP/2 by default via NextProtos = []string{"http/1.1"}
  • added regression coverage for flag defaults, config threading, and TLS config behavior

User Impact

  • standalone webhook mode now matches the project's existing HTTP/2 hardening behavior
  • HTTP/2 remains disabled by default
  • users can explicitly opt in with --enable-http2

Summary by CodeRabbit

  • New Features

    • Added a new --enable-http2 flag (disabled by default) to let the webhook server explicitly enable HTTP/2; when disabled the server negotiates HTTP/1.1 only.
  • Bug Fixes

    • Fixed propagation so the server’s TLS/HTTP behavior reliably follows the CLI flag.
  • Documentation

    • Documented the new --enable-http2 flag and its default.
  • Tests

    • Added/updated tests to validate HTTP/2 enablement, disablement, and TLS negotiation.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 15, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 8f5633b7-89d0-450b-8cb1-6e69f8c5d39a

📥 Commits

Reviewing files that changed from the base of the PR and between 4f2962a and 0bdb345.

📒 Files selected for processing (8)
  • cmd/common.go
  • cmd/common_test.go
  • cmd/run.go
  • cmd/webhook.go
  • cmd/webhook_test.go
  • docs/install/cmd/webhook.md
  • pkg/webhook/server.go
  • pkg/webhook/server_test.go
✅ Files skipped from review due to trivial changes (1)
  • docs/install/cmd/webhook.md
🚧 Files skipped from review as they are similar to previous changes (3)
  • cmd/webhook_test.go
  • cmd/run.go
  • pkg/webhook/server_test.go

Walkthrough

Threads a new EnableHTTP2 boolean from a CLI flag into WebhookConfig, through SetupWebhookServer, and into the webhook TLSConfig. When EnableHTTP2 is false, the TLS config forces NextProtos = ["http/1.1"] to disable HTTP/2 negotiation.

Changes

Cohort / File(s) Summary
CLI & config
cmd/common.go, cmd/webhook.go, cmd/run.go
Added EnableHTTP2 bool to WebhookConfig; added --enable-http2 flag (default false) and synchronize flag into webhookCfg.EnableHTTP2.
Webhook server setup & tests
cmd/common.go, cmd/common_test.go
Propagated webhookCfg.EnableHTTP2 into SetupWebhookServer so constructed server.TLS.EnableHTTP2 reflects the config; tests assert server.TLS non-nil and verify propagation for true/false cases.
TLS implementation & tests
pkg/webhook/server.go, pkg/webhook/server_test.go
Added exported EnableHTTP2 to TLSConfig; buildTLSConfig() sets tls.Config.NextProtos = []string{"http/1.1"} when EnableHTTP2 is false and leaves it unset when true; tests validate NextProtos behavior.
Docs & command tests
docs/install/cmd/webhook.md, cmd/webhook_test.go
Documented --enable-http2 flag (default false); command tests extended to assert the flag default is "false"; minor whitespace cleanup in docs.

Sequence Diagram(s)

sequenceDiagram
    participant CLI as CLI (--enable-http2)
    participant Cmd as WebhookCmd
    participant Setup as SetupWebhookServer
    participant TLSConf as TLSConfig
    participant Server as HTTPServer

    CLI->>Cmd: parse flags (enable-http2)
    Cmd->>Setup: call SetupWebhookServer(webhookCfg{EnableHTTP2})
    Setup->>TLSConf: construct TLSConfig (EnableHTTP2 set)
    TLSConf->>TLSConf: buildTLSConfig() — if EnableHTTP2==false set NextProtos=["http/1.1"]
    Setup->>Server: start HTTP server with tls.Config (NextProtos applied)
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 57.14% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'fix(webhook): align standalone webhook HTTP/2 hardening' directly summarizes the main change: ensuring webhook HTTP/2 hardening consistency.
Linked Issues check ✅ Passed All objectives from issue #1559 are met: EnableHTTP2 field added to WebhookConfig, --enable-http2 flag added to webhook command, enableHTTP2 threaded through SetupWebhookServer, NextProtos set to http/1.1 when disabled, and tests added.
Out of Scope Changes check ✅ Passed All changes are directly scoped to implementing HTTP/2 hardening for the webhook server: config struct extension, CLI flag addition, TLS config logic, and test coverage.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
Review rate limit: 7/8 reviews remaining, refill in 7 minutes and 30 seconds.

Comment @coderabbitai help to get the list of available commands and usage tips.

@codecov-commenter
Copy link
Copy Markdown

codecov-commenter commented Apr 15, 2026

Codecov Report

❌ Patch coverage is 90.00000% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 73.55%. Comparing base (c1674be) to head (0bdb345).
⚠️ Report is 87 commits behind head on master.

Files with missing lines Patch % Lines
cmd/run.go 0.00% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master    #1588      +/-   ##
==========================================
+ Coverage   71.48%   73.55%   +2.07%     
==========================================
  Files          50       63      +13     
  Lines        4667     6591    +1924     
==========================================
+ Hits         3336     4848    +1512     
- Misses       1133     1425     +292     
- Partials      198      318     +120     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@doup2001 doup2001 force-pushed the fix-1559-webhook-enable-http2 branch from 82c8577 to 5b1a51b Compare April 15, 2026 04:26
@doup2001 doup2001 force-pushed the fix-1559-webhook-enable-http2 branch 2 times, most recently from 2295e38 to 4f2962a Compare April 18, 2026 03:26
@doup2001
Copy link
Copy Markdown
Author

This is a rebase, so please ignore the force-push.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@pkg/webhook/server.go`:
- Around line 217-219: The server currently only sets tlsCfg.NextProtos when
t.EnableHTTP2 is false, which doesn't stop Go's net/http from enabling HTTP/2;
update the server setup to set the http.Server's TLSNextProto to an empty map
when t.EnableHTTP2 is false (e.g., server.TLSNextProto =
make(map[string]func(*http.Server, *tls.Conn, http.Handler))) so HTTP/2
negotiation is fully disabled, and add an end-to-end regression test that starts
the TLS server with EnableHTTP2: false, attempts ALPN HTTP/2 negotiation, and
asserts the negotiated protocol is http/1.1 (or that HTTP/2 is rejected).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 7f4d9ec7-a4d7-4c6c-81ba-776d90101c2e

📥 Commits

Reviewing files that changed from the base of the PR and between 2295e38 and 4f2962a.

📒 Files selected for processing (8)
  • cmd/common.go
  • cmd/common_test.go
  • cmd/run.go
  • cmd/webhook.go
  • cmd/webhook_test.go
  • docs/install/cmd/webhook.md
  • pkg/webhook/server.go
  • pkg/webhook/server_test.go
✅ Files skipped from review due to trivial changes (3)
  • cmd/webhook_test.go
  • docs/install/cmd/webhook.md
  • cmd/common_test.go
🚧 Files skipped from review as they are similar to previous changes (4)
  • cmd/webhook.go
  • cmd/run.go
  • pkg/webhook/server_test.go
  • cmd/common.go

Comment thread pkg/webhook/server.go
Comment on lines +217 to +219
if !t.EnableHTTP2 {
tlsCfg.NextProtos = []string{"http/1.1"}
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify the webhook TLS path disables net/http automatic HTTP/2, not just tls.Config ALPN.
# Expected after the fix: webhook server setup contains both the http/1.1 NextProtos override
# and a non-nil empty Server.TLSNextProto assignment when EnableHTTP2 is false.
rg -n -C3 '\b(EnableHTTP2|NextProtos|TLSNextProto)\b' --type go

Repository: argoproj-labs/argocd-image-updater

Length of output: 5794


🏁 Script executed:

# Find where Server.ListenAndServeTLS is called and Server is initialized
rg -n -B5 -A5 'ListenAndServeTLS|\.Server\s*=' --type go pkg/webhook/

Repository: argoproj-labs/argocd-image-updater

Length of output: 2106


🏁 Script executed:

# Check the complete start() method implementation
sed -n '1,350p' pkg/webhook/server.go | tail -100

Repository: argoproj-labs/argocd-image-updater

Length of output: 3488


🏁 Script executed:

# Search for any direct Server.TLSNextProto assignments in the entire repo
rg -n 'TLSNextProto' --type go

Repository: argoproj-labs/argocd-image-updater

Length of output: 60


🌐 Web query:

Go net/http ListenAndServeTLS TLSNextProto automatic HTTP/2 registration behavior

💡 Result:

In Go's net/http package, http.ListenAndServeTLS(addr, certFile, keyFile, handler) automatically enables HTTP/2 support over TLS ("h2" via ALPN) by default, starting from Go 1.6. This is transparent and requires no explicit configuration. The mechanism works as follows: - ListenAndServeTLS internally creates an *http.Server with the provided handler (or DefaultServeMux if nil). - When the server starts serving TLS connections (via ServeTLS or similar), it checks if HTTP/2 should be configured using shouldConfigureHTTP2ForServe, which returns true if TLSConfig is nil or includes "h2" in NextProtos. - If TLSNextProto is nil (the default), the server calls an internal function (onceSetNextProtoDefaults) which invokes http2ConfigureServer (from the bundled h2_bundle.go, embedding golang.org/x/net/http2). - http2ConfigureServer sets srv.TLSNextProto["h2"] to a handler function that takes over "h2" connections, delegating to the http2.Server implementation for multiplexing, etc. - During TLS handshake, if the client negotiates "h2" via ALPN, the TLSNextProto["h2"] handler is invoked with the *tls.Conn and http.Handler, enabling HTTP/2 automatically. To disable automatic HTTP/2: - Set Server.TLSNextProto = make(map[string]func(*http.Server, *tls.Conn, http.Handler)) (non-nil empty map) before serving. - Or use GODEBUG=http2server=0. If TLSNextProto is set by the user (non-nil), automatic registration does not occur to avoid overriding user handlers, but if "h2" is in TLSConfig.NextProtos, clients may still attempt HTTP/2 (falling back to HTTP/1.1 if no handler). This behavior is consistent across recent Go versions (1.21+ to tip as of 2026), with h2_bundle.go bundling the http2 implementation directly in std. Recent changes (e.g., issue #69649) introduce linkname-based registration for future extensions like h2c, but do not alter the core TLS automatic behavior. Example enabling HTTP/2 (default): log.Fatal(http.ListenAndServeTLS(":443", "cert.pem", "key.pem", nil)) Example disabling: srv := &http.Server{Addr: ":443", TLSNextProto: make(map[string]func(*http.Server, *tls.Conn, http.Handler))} log.Fatal(srv.ListenAndServeTLS("cert.pem", "key.pem", nil))

Citations:


🌐 Web query:

Go http.Server TLSNextProto field purpose disable automatic HTTP/2

💡 Result:

The TLSNextProto field in Go's http.Server is a map from ALPN protocol names (e.g., "h2" for HTTP/2) to handler functions that process connections using those protocols over TLS. By default, Go's net/http package registers a handler for "h2" to enable automatic HTTP/2 support on HTTPS servers (since Go 1.6). To disable automatic HTTP/2, set Server.TLSNextProto to a non-nil, empty map. This prevents registration of the HTTP/2 handler, forcing fallback to HTTP/1.1. Example: srv := &http.Server{ Addr: ":443", Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "Hello!") }), TLSNextProto: make(map[string]func(*http.Server, *tls.Conn, http.Handler)), // Disables HTTP/2 } log.Fatal(srv.ListenAndServeTLS("cert.pem", "key.pem")) In Go 1.24+, Server.Protocols offers finer control (e.g., Protocols{}.SetHTTP2(false)), but TLSNextProto remains the standard way to disable HTTP/2 as per official docs. Alternatives: GODEBUG=http2server=0 (env var, not guaranteed stable) or //go:debug http2server=0 (Go 1.21+ source comment).

Citations:


🏁 Script executed:

# Check existing tests for HTTP/2 behavior or protocol negotiation
rg -n 'http2|HTTP/2|protocol.*negotiat|ALPN' --type go pkg/webhook/server_test.go

Repository: argoproj-labs/argocd-image-updater

Length of output: 144


🏁 Script executed:

# Verify the complete buildTLSConfig function to ensure nothing else affects HTTP/2
sed -n '190,225p' pkg/webhook/server.go

Repository: argoproj-labs/argocd-image-updater

Length of output: 1145


Disable HTTP/2 on the http.Server when EnableHTTP2 is false.

Go's net/http automatically registers an HTTP/2 handler unless Server.TLSNextProto is explicitly set to a non-nil value. Setting only tls.Config.NextProtos prevents ALPN advertisement but does not prevent the server from accepting HTTP/2 connections. Set Server.TLSNextProto to an empty map to fully disable HTTP/2 negotiation.

Proposed fix
 	if !t.EnableHTTP2 {
 		tlsCfg.NextProtos = []string{"http/1.1"}
 	}
 
 	return tlsCfg, nil
 }
 
 // Start starts the webhook server
 func (s *WebhookServer) Start(ctx context.Context) error {
 	log := log.LoggerFromContext(ctx)
 	// Create server and register routes
 	mux := http.NewServeMux()
 	mux.HandleFunc("/webhook", s.handleWebhook)
 	mux.HandleFunc("/healthz", s.handleHealth)
 
 	addr := fmt.Sprintf(":%d", s.Port)
 	s.Server = &http.Server{
 		Addr:    addr,
 		Handler: mux,
 	}
 
 	// errCh captures startup errors from the server goroutine so we can
 	// fail fast instead of blocking on ctx.Done() with a dead listener.
 	errCh := make(chan error, 1)
 
 	if s.DisableTLS {
 		log.Warnf("Starting webhook server in insecure mode (plain HTTP) on port %d", s.Port)
 
 		go func() {
 			if err := s.Server.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
 				errCh <- err
 			}
 		}()
 	} else {
 		// Default TLS settings if not explicitly configured
 		if s.TLS == nil {
 			s.TLS = &TLSConfig{
 				CertFile:   DefaultTLSCertPath,
 				KeyFile:    DefaultTLSKeyPath,
 				MinVersion: DefaultTLSMinVersion,
 				MaxVersion: DefaultTLSMaxVersion,
 			}
 		}
 		// Build TLS config from settings
 		tlsCfg, err := s.TLS.buildTLSConfig()
 		if err != nil {
 			return fmt.Errorf("failed to configure TLS: %w", err)
 		}
+		if !s.TLS.EnableHTTP2 {
+			// Prevent net/http from automatically enabling HTTP/2 for TLS servers.
+			s.Server.TLSNextProto = map[string]func(*http.Server, *tls.Conn, http.Handler){}
+		}
 
 		// Determine whether to load certs from files or generate self-signed

Also add an end-to-end regression test that starts the TLS server with EnableHTTP2: false, attempts to negotiate HTTP/2 via ALPN, and verifies the negotiated protocol is HTTP/1.1 (or that HTTP/2 is rejected).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pkg/webhook/server.go` around lines 217 - 219, The server currently only sets
tlsCfg.NextProtos when t.EnableHTTP2 is false, which doesn't stop Go's net/http
from enabling HTTP/2; update the server setup to set the http.Server's
TLSNextProto to an empty map when t.EnableHTTP2 is false (e.g.,
server.TLSNextProto = make(map[string]func(*http.Server, *tls.Conn,
http.Handler))) so HTTP/2 negotiation is fully disabled, and add an end-to-end
regression test that starts the TLS server with EnableHTTP2: false, attempts
ALPN HTTP/2 negotiation, and asserts the negotiated protocol is http/1.1 (or
that HTTP/2 is rejected).

Signed-off-by: eedo_y <doup2001@naver.com>
Signed-off-by: eedo_y <doup2001@naver.com>
@doup2001 doup2001 force-pushed the fix-1559-webhook-enable-http2 branch from 4f2962a to 0bdb345 Compare April 29, 2026 03:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(webhook): thread enable-http2 setting through WebhookConfig for TLS server hardening

2 participants