Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
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 CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ This changelog keeps track of work items that have been completed and are ready

### Improvements

- **General**: TODO ([#TODO](https://github.com/kedacore/http-add-on/issues/TODO))
- **Interceptor**: Document TLS SNI certificate selection and fallback behavior; add tests covering SNI-specific certificate preference over the default certificate ([#1600](https://github.com/kedacore/http-add-on/issues/1600))

### Fixes

Expand Down
10 changes: 10 additions & 0 deletions docs/developing.md
Original file line number Diff line number Diff line change
Expand Up @@ -122,3 +122,13 @@ make e2e-test E2E_ARGS="--dry-run"
The `PROFILE` variable selects a test profile directory under `test/e2e/` (e.g. `PROFILE=tls` runs `./test/e2e/tls/...`). Each subdirectory in `test/e2e/` is a profile.
The `RUN` variable filters tests by name using Go's `-run` flag (supports regex, e.g. `RUN=TestColdStart` or `RUN="TestHost|TestPath"`).
The `E2E_ARGS` variable passes flags to the [e2e-framework](https://github.com/kubernetes-sigs/e2e-framework) via `-args` (e.g. `--labels`, `--feature`, `--skip-labels`, `--dry-run`).

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

We moved the docs a few hours before you opened the PR here, could you move it there? https://keda.sh/http-add-on/0.14/operations/configure-tls/ (See the Suggest changes button)

### TLS SNI behavior

The interceptor can serve more than one certificate from the TLS listener by setting `KEDA_HTTP_PROXY_TLS_CERT_STORE_PATHS` to one or more directories that contain certificate/key pairs. During the TLS handshake it:
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Done — the docs now call out that KEDA_HTTP_PROXY_TLS_CERT_STORE_PATHS expects a comma-separated list when multiple directories are configured.


1. Looks for an exact match between the client SNI value and a certificate SAN loaded from the configured certificate-store directories.
2. Falls back to the certificate from `KEDA_HTTP_PROXY_TLS_CERT_PATH` and `KEDA_HTTP_PROXY_TLS_KEY_PATH` when no SNI-specific certificate matches.
3. Fails the handshake when there is no matching SNI certificate and no default certificate is configured.

The existing `test/e2e/tls` profile covers successful TLS termination. The interceptor unit tests in `interceptor/tls_config_test.go` additionally cover the no-match fallback and no-default error paths.
5 changes: 4 additions & 1 deletion interceptor/config/serving.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,10 @@ type Serving struct {
TLSCertPath string `env:"KEDA_HTTP_PROXY_TLS_CERT_PATH" envDefault:"/certs/tls.crt"`
// TLSKeyPath is the path to read the private key file from for the TLS server
TLSKeyPath string `env:"KEDA_HTTP_PROXY_TLS_KEY_PATH" envDefault:"/certs/tls.key"`
// TLSCertStorePaths is a comma separated list of paths to read the certificate/key pairs for the TLS server
// TLSCertStorePaths is a comma separated list of directories containing additional
// certificate/key pairs for the TLS server. During the TLS handshake, the proxy
// first looks for an exact SNI/SAN match in these directories and falls back to
// TLSCertPath/TLSKeyPath when no matching certificate is found.
TLSCertStorePaths string `env:"KEDA_HTTP_PROXY_TLS_CERT_STORE_PATHS" envDefault:""`
Comment thread
linkvt marked this conversation as resolved.
Outdated
// TLSSkipVerify is a boolean flag to specify whether the interceptor should skip TLS verification for upstreams
TLSSkipVerify bool `env:"KEDA_HTTP_PROXY_TLS_SKIP_VERIFY" envDefault:"false"`
Expand Down
28 changes: 28 additions & 0 deletions interceptor/tls_config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,34 @@ func TestBuildTLSConfig_FallbackToDefault(t *testing.T) {
requireCertForHost(t, tlsCfg, "unknown.example.com")
}

func TestBuildTLSConfig_PrefersSNIMatchOverDefault(t *testing.T) {
dir := t.TempDir()
writeCert(t, dir, "default", "default.example.com")
writeCert(t, dir, "app", "app.example.com")

opts := TLSOptions{
CertificatePath: filepath.Join(dir, "default.crt"),
KeyPath: filepath.Join(dir, "default.key"),
CertStorePaths: dir,
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Done — I moved the additional certificate into a separate temp store directory so the test exercises SNI preference without re-loading the default cert via the store path.

}

tlsCfg, err := BuildTLSConfig(opts, logr.Discard())
if err != nil {
t.Fatalf("failed to build TLS config: %v", err)
}

cert, err := tlsCfg.GetCertificate(&tls.ClientHelloInfo{ServerName: "app.example.com"})
if err != nil {
t.Fatalf("expected SNI-matched certificate, got error: %v", err)
}
if cert == nil || cert.Leaf == nil {
t.Fatal("expected certificate leaf to be populated")
}
if got := cert.Leaf.DNSNames; len(got) != 1 || got[0] != "app.example.com" {
t.Fatalf("expected app.example.com certificate, got %v", got)
}
}

func TestBuildTLSConfig_NoDefaultCert(t *testing.T) {
opts := TLSOptions{}

Expand Down
Loading