Skip to content

feat(security): add require_tls config to enforce HTTPS connections#954

Merged
manusa merged 5 commits into
containers:mainfrom
nader-ziada:require-tls
Mar 30, 2026
Merged

feat(security): add require_tls config to enforce HTTPS connections#954
manusa merged 5 commits into
containers:mainfrom
nader-ziada:require-tls

Conversation

@nader-ziada

Copy link
Copy Markdown
Collaborator

Add require_tls configuration option that enforces TLS for all server and client connections when enabled

Changes:

  • All TLS configs now enforce MinVersion TLS 1.2
  • if require_tls is true
    • Server refuses to start without TLS certificates configured if require_tls is true
    • Outbound URLs (Kiali, authorization_url) must use HTTPS

Add require_tls configuration option that enforces TLS for all
server and client connections when enabled

Signed-off-by: Nader Ziada <nziada@redhat.com>

@Cali0707 Cali0707 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Overall this looks solid! A few things I would want to think about (not necessarily blocking)

  1. What happens on SIGHUP - I don't think we would revalidate the tls bits with the enforce tls flag, but am not sure
  2. Is it worth adding defence in depth somehow by adding a roundtripper that double checks the url the connection is going to is https? Or perhaps having a constructor for creating new http clients that adds a roundtripper to do such?

Comment thread pkg/kiali/config.go Outdated
Comment on lines +64 to +69
// Validate URL scheme when require_tls is enabled
if config.RequireTLSFromContext(ctx) {
if err := config.ValidateURLRequiresTLS(cfg.Url, "Kiali URL"); err != nil {
return nil, err
}
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

will remembering to check any outgoing url on contributed toolsets be a requirement going forwards now?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

No, toolset contributors don't need to manually validate URLs. The TLSEnforcingTransport handles this automatically at runtime, but toolset contributors need to use the IsRequireTLS function from the config provider to wrap their HTTP client when making HTTP requests

Kiali is currently the only toolset that makes direct outbound HTTP requests to an external service

Comment thread pkg/kubernetes-mcp-server/cmd/root.go Outdated
}
// Validate outbound URLs when require_tls is enabled
if m.StaticConfig.RequireTLS {
if err := config.ValidateURLRequiresTLS(m.StaticConfig.AuthorizationURL, "authorization_url"); err != nil {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

maybe check m.StaticConfig.ServerURL and m.StaticConfig.SSEBaseURL as well? do we also need to validate the urls set within the kubeconfig file?

Also this would be more of a stretch but should we be validating the issuer urls in the token exchange config that a toolset can optionally return?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

added validation for ServerURL and SSEBaseURL

token exchange issuer urls from toolsets: handled at runtime, TargetTokenExchangeConfig has a RequireTLS func() bool field, and HTTPClient() wraps the transport with TLSEnforcingTransport, so any non-https request just gets blocked at runtime. no explicit url validation needed

Comment thread pkg/config/config.go

func (c *StaticConfig) IsRequireTLS() bool {
return c.RequireTLS
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

maybe I missed this but I didn't see this consumed anywhere - it seems like we are only use the context accessor for this?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Context accessor is needed during TOML parsing (config object doesn't exist yet), func is used after config is built for runtime HTTP client wrapping.

@manusa manusa left a comment

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.

Thanks for looking into this.
I think Calum made a lot of good points.

For me the most important ones are:

  • currently only validating certain outbound urls: kiali is covered now, but maybe others aren't. We should look into solutions for this (providing an HTTP client with enforced HTTPS consumption for toolset implementers, or providing a e NewRequireTLSClient() helper to enforce HTTPS in HTTP clients created by implementers, or provided HTTP enforced roundtripper, or anything along those lines.
  • Validation after SIGHUP reload: we definitely need to move the validation to a different stage or at least revalidate on config changes

Comment thread pkg/config/tls.go Outdated
if err != nil {
return fmt.Errorf("invalid %s: %w", fieldName, err)
}
if u.Scheme != "https" {

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.

I'm not sure if some urls might attempt to connect to a non-http protocol such as a web socket (wss://

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

added wss since the only two protocols currently used in the codebase are http and ws so didn't want any other ones

@nader-ziada

Copy link
Copy Markdown
Collaborator Author

@Cali0707 @manusa

comments addressed by adding two-layer tls enforcement:

  • layer 1 (config-time): validates known config urls at startup/reload for fail-fast feedback
  • layer 2 (runtime): TLSEnforcingTransport wraps http clients and rejects non-https at the transport level, catches all outbound calls regardless of where the url came from (dynamic, toolset-specific, whatever we might miss in layer 1)
    sighup: transport uses func() bool reading from Configuration.IsRequireTLS() per-request, so reloads take effect immediately, no re-validation needed, non-https just gets blocked if require_tls is true

Layer 1: Config-time URL validation for fail-fast feedback
Layer 2: TLSEnforcingTransport wrapper that blocks non-HTTPS requests at runtime

Signed-off-by: Nader Ziada <nziada@redhat.com>
Comment thread pkg/config/tls.go Outdated
Comment thread pkg/config/tls.go Outdated
Comment thread pkg/tokenexchange/config.go Outdated

// RequireTLS is a function that returns whether TLS is required for outbound connections.
// When set and returns true, HTTP requests to non-HTTPS endpoints will be rejected.
RequireTLS func() bool `toml:"-"`

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

are we setting this anywhere where we are creating these configs?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

this is a cli flag and a config to be added in the toml, not sure what you mean? I didn't set it be default

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.

I believe that the problem is that this field IS NOT explicitly populated in the codebase, since this is a function and non-serializable field, it's effectively useless.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

yeah removed it

and improve error message

Signed-off-by: Nader Ziada <nziada@redhat.com>
@manusa
manusa self-requested a review March 25, 2026 10:50
Comment thread pkg/mcp/mcp.go Outdated
Comment on lines +48 to +54
// IsRequireTLS returns whether TLS is required for outbound connections.
// This method exists to support dynamic config reload via SIGHUP - it reads
// from the current StaticConfig rather than being a promoted method bound
// to a potentially stale config reference.
func (c *Configuration) IsRequireTLS() bool {
return c.RequireTLS
}

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.

I think this is not needed since StaticConfig.IsRequireTLS should already be promoted.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

removed it

Comment thread pkg/config/tls.go Outdated

func (t *TLSEnforcingTransport) RoundTrip(req *http.Request) (*http.Response, error) {
if t.RequireTLS != nil && t.RequireTLS() && !isSecureHTTPScheme(req.URL.Scheme) {
klog.V(0).Infof("require_tls: blocked request to %s", req.URL.Host)

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.

I'm not sure, but maybe the log level (0) is too low and might clutter the server logs. Are we sure 0 is the right choice here?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

change to v(1) to be similar to authorization.go

Signed-off-by: Nader Ziada <nziada@redhat.com>
@manusa
manusa requested a review from Cali0707 March 26, 2026 02:57
Comment thread pkg/kiali/config.go
}

// Validate URL scheme when require_tls is enabled
if config.RequireTLSFromContext(ctx) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

shuld htis check on cfg.Insecure ?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

i haven't been assuming that require_tls also means should reject Insecure=true because the admin explicitly sets Insecure=true knowing what it means

also let me know if you think this is not a good assumption

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Agree that admins should know, but the value of require_tls is catching mis-configurations. Like someone copies a dev config with insecure = true to production, adds require_tls = true, and assumes they're covered. Perhaps some startup error/validation saves those from a false sense of security?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

added error if insecure = true


transport.TLSClientConfig = tlsConfig

c.client = &http.Client{

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

the other outbound clients (well-known proxy, OIDC, Kiali) are wrapped with TLSEnforcingTransport. Should this one be wrapped too for consistency?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

I think the reason was that the TokenURL is typed directly into a config file by an admin. If they write http://internal-keycloak:8080/, that's an explicit choice, not an accident. But i can add it if you don't think that's a good reason

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

For consistency I'd wrap this one too, better safe than sorry on these layered security thing and catching those for te admins/users?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

looked into this more and the HTTPClient() code path is actually not used today since no provider implements TokenExchangeProvider, so the type assertion in ExchangeTokenInContext always fails and we never reach it

Comment thread pkg/config/tls.go

func (t *TLSEnforcingTransport) RoundTrip(req *http.Request) (*http.Response, error) {
if t.RequireTLS != nil && t.RequireTLS() && !isSecureHTTPScheme(req.URL.Scheme) {
klog.V(1).Infof("require_tls: blocked request to %s", req.URL.Host)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

like here, I guess there are more areas where we could do the same thing: when backend TLS handshakes fails (e.g. expired certs, protocol mismatches etc), they show up as generic HTTP errors in the logs.

Might be worth a follow-up to surface those more clearly for debugging.

Signed-off-by: Nader Ziada <nziada@redhat.com>
manusa
manusa previously requested changes Mar 30, 2026

@manusa manusa left a comment

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.

(deleted)

@manusa
manusa self-requested a review March 30, 2026 14:12
@manusa
manusa dismissed their stale review March 30, 2026 15:00

Dismissing stale review — re-reviewing with full context of existing discussions.

@manusa manusa added this to the 0.1.0 milestone Mar 30, 2026

@manusa manusa left a comment

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.

LGTM, thx!

@manusa
manusa merged commit beed6f9 into containers:main Mar 30, 2026
9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants