Skip to content

feat: add tlsInsecureSkipVerify to ExternalModel spec#646

Open
nerdalert wants to merge 1 commit intoopendatahub-io:mainfrom
nerdalert:brent-insecure-tls-extmodel
Open

feat: add tlsInsecureSkipVerify to ExternalModel spec#646
nerdalert wants to merge 1 commit intoopendatahub-io:mainfrom
nerdalert:brent-insecure-tls-extmodel

Conversation

@nerdalert
Copy link
Copy Markdown
Member

@nerdalert nerdalert commented Mar 30, 2026

Adds an optional spec.tlsInsecureSkipVerify field to the ExternalModel CRD. When true, the reconciler generates the DestinationRule with insecureSkipVerify: true, allowing connections to endpoints with self-signed certificates without manual patching that gets overwritten on reconciliation. Default is false.

Closes #627

Leaving in draft until I've validated on a live cluster. cc/ @noyitz

ty!

Summary by CodeRabbit

  • New Features

    • Optional TLS verification toggle for external models to disable certificate checks.
    • Configurable access-check timeout (env/default) to bound model access validation.
  • Behavior Changes

    • Model listing responses add X-Access-Checked-At and set Cache-Control: no-store to prevent caching.
    • Configuration now validates a minimum access-check timeout value.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Mar 30, 2026

📝 Walkthrough

Walkthrough

Adds an optional boolean spec field tlsInsecureSkipVerify to the ExternalModel CRD and Go API, and passes it through the reconciler so the generated Istio DestinationRule can set trafficPolicy.tls.insecureSkipVerify from the CR. Separately, the models/manager API gained an accessCheckTimeout parameter (NewManager signature changed), FilterModelsByAccess now uses a context deadline instead of fixed per-client timeouts, handlers add Cache-Control: no-store and X-Access-Checked-At headers, config gained AccessCheckTimeoutSeconds, and related tests were updated.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Issues identified

  • Security: adding tlsInsecureSkipVerify permits disabling TLS certificate verification (CWE-295). Actionable items:

    1. Document explicit security warning that tlsInsecureSkipVerify: true is for dev/test only and should not be used in production.
    2. Consider admission controls or policy (e.g., OPA/Gatekeeper) to prevent enabling this field in sensitive namespaces/environments.
    3. Add audit logging on create/update when tlsInsecureSkipVerify: true to record operator intent and change events.
    4. Add unit/integration tests asserting DestinationRule contains insecureSkipVerify: true, and tests for absent/false cases.
  • API/compatibility: NewManager signature changed (adds access-check timeout). Actionable:

    1. Search for all call sites and update them; provide a compatibility shim or migration note if other packages depend on the old signature.
    2. Ensure the defaulting behavior (15s when <=0) is documented.
  • Timeout/availability semantics: switching to a context-based access-check deadline makes FilterModelsByAccess fail-closed on deadline expiry. Actionable:

    1. Confirm this fail-closed behavior is intended; if not, adjust to fail-open or add retry/backoff policy.
    2. Verify logging clearly distinguishes deadline-exceeded vs other errors for operational diagnostics.
  • Handler behavior / caching: handlers now set Cache-Control: no-store and X-Access-Checked-At. Actionable:

    1. Ensure these headers are acceptable for all clients and documented in API docs.
    2. Tests should cover header presence for both code paths (with and without maasModelRefLister).
  • Consistency / schema: verify exact JSON/YAML key casing tlsInsecureSkipVerify across CRD, Go struct tag, and examples; update CRD documentation and CR samples.

  • Tests: multiple tests were updated to pass the new manager parameter and to assert new headers. Actionable:

    1. Run full test suite to catch any remaining call-site misses.
    2. Add tests exercising timeout expiry behavior to avoid flakiness and to assert intended fail-closed semantics.
🚥 Pre-merge checks | ✅ 2 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Linked Issues check ⚠️ Warning Core objective from #627 partially met: tlsInsecureSkipVerify added to ExternalModel CRD/types and reconciler assignment implemented, but a critical accessCheckTimeout parameter addition is unrelated to and absent from #627 requirements. Reconcile the accessCheckTimeout changes in discovery.go/models_test.go/main.go/config.go—these appear orthogonal to #627. Either split into a separate PR or document their necessity.
Out of Scope Changes check ⚠️ Warning Out-of-scope changes detected: accessCheckTimeout feature (discovery.go, main.go, config.go, models_test.go, models.go) is unrelated to issue #627 tlsInsecureSkipVerify requirement. Separate the accessCheckTimeout implementation into its own PR. Only retain changes to externalmodel_types.go, reconciler.go, and CRD baseline for tlsInsecureSkipVerify.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed Title accurately summarizes the main change: adding tlsInsecureSkipVerify field to ExternalModel spec.

✏️ 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

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

@nerdalert nerdalert requested a review from noyitz March 30, 2026 18:41
Copy link
Copy Markdown
Contributor

@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.

🧹 Nitpick comments (3)
maas-controller/api/maas/v1alpha1/externalmodel_types.go (2)

69-74: Consider adding a printcolumn to surface insecure TLS status in kubectl get output.

Security-sensitive configurations benefit from visibility at the list level. This helps operators quickly identify resources with relaxed security settings during audits or troubleshooting.

🔧 Proposed printcolumn addition
 //+kubebuilder:printcolumn:name="Endpoint",type="string",JSONPath=".spec.endpoint"
+//+kubebuilder:printcolumn:name="InsecureTLS",type="boolean",JSONPath=".spec.tlsInsecureSkipVerify",priority=1
 //+kubebuilder:printcolumn:name="Phase",type="string",JSONPath=".status.phase"

Using priority=1 keeps it hidden from default output but visible with -o wide.

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

In `@maas-controller/api/maas/v1alpha1/externalmodel_types.go` around lines 69 -
74, Add a kubebuilder printcolumn to surface the insecure TLS flag (e.g.,
"Insecure TLS") in the CRD print columns next to the existing
Provider/Endpoint/Phase/Age tags; insert a line like a kubebuilder:printcolumn
with name="Insecure TLS", type="boolean" (or "string"), JSONPath pointing to
your spec field (for example ".spec.insecureSkipVerify" or
".spec.tls.insecureSkipVerify") and include priority=1 so it’s hidden by
default; place this tag alongside the other kubebuilder annotations above the
ExternalModel type definition in externalmodel_types.go.

44-56: Add explicit +kubebuilder:default=false marker for CRD schema clarity.

While Go's zero value handles the default correctly, adding the explicit marker improves the generated OpenAPI schema and kubectl explain output. This is especially important for security-sensitive fields where operators need unambiguous documentation.

🔧 Proposed marker addition
 	// WARNING: Do not enable in production. This is intended for testing only.
 	//
 	// Default: false (certificates are verified).
 	// +optional
+	// +kubebuilder:default=false
 	TLSInsecureSkipVerify bool `json:"tlsInsecureSkipVerify,omitempty"`

As per coding guidelines: "Kubebuilder validation markers on all user-facing fields."

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

In `@maas-controller/api/maas/v1alpha1/externalmodel_types.go` around lines 44 -
56, The TLSInsecureSkipVerify boolean field lacks an explicit kubebuilder
default marker; add the kubebuilder marker "// +kubebuilder:default=false"
immediately above the TLSInsecureSkipVerify field declaration in
externalmodel_types.go so the generated CRD/OpenAPI schema and kubectl explain
show the explicit default for the TLSInsecureSkipVerify field.
maas-controller/pkg/reconciler/externalmodel/reconciler.go (1)

279-286: Field propagation is correct; consider logging tlsInsecureSkipVerify for operational visibility.

The mapping from extModel.Spec.TLSInsecureSkipVerify is clean. However, the reconcile log at lines 113-117 doesn't capture whether insecure TLS is active. When operators troubleshoot connection issues or audit security posture, this visibility would help.

🔧 Proposed logging enhancement
 	log.Info("Reconciling ExternalModel",
 		"provider", spec.Provider,
 		"endpoint", spec.Endpoint,
 		"tls", spec.TLS,
+		"tlsInsecureSkipVerify", spec.TLSInsecureSkipVerify,
 	)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@maas-controller/pkg/reconciler/externalmodel/reconciler.go` around lines 279
- 286, The ExternalModelSpec is correctly setting TLSInsecureSkipVerify into
spec (TLSInsecureSkipVerify: extModel.Spec.TLSInsecureSkipVerify), but the
reconcile log that reports the external model connection info doesn't include
that field; update the reconcile logging call (the log/info call that currently
prints Provider, Endpoint, PathPrefix, Port) to also log TLSInsecureSkipVerify
by adding extModel.Spec.TLSInsecureSkipVerify or spec.TLSInsecureSkipVerify to
the structured log entry so operators can see whether insecure TLS is active.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@maas-controller/api/maas/v1alpha1/externalmodel_types.go`:
- Around line 69-74: Add a kubebuilder printcolumn to surface the insecure TLS
flag (e.g., "Insecure TLS") in the CRD print columns next to the existing
Provider/Endpoint/Phase/Age tags; insert a line like a kubebuilder:printcolumn
with name="Insecure TLS", type="boolean" (or "string"), JSONPath pointing to
your spec field (for example ".spec.insecureSkipVerify" or
".spec.tls.insecureSkipVerify") and include priority=1 so it’s hidden by
default; place this tag alongside the other kubebuilder annotations above the
ExternalModel type definition in externalmodel_types.go.
- Around line 44-56: The TLSInsecureSkipVerify boolean field lacks an explicit
kubebuilder default marker; add the kubebuilder marker "//
+kubebuilder:default=false" immediately above the TLSInsecureSkipVerify field
declaration in externalmodel_types.go so the generated CRD/OpenAPI schema and
kubectl explain show the explicit default for the TLSInsecureSkipVerify field.

In `@maas-controller/pkg/reconciler/externalmodel/reconciler.go`:
- Around line 279-286: The ExternalModelSpec is correctly setting
TLSInsecureSkipVerify into spec (TLSInsecureSkipVerify:
extModel.Spec.TLSInsecureSkipVerify), but the reconcile log that reports the
external model connection info doesn't include that field; update the reconcile
logging call (the log/info call that currently prints Provider, Endpoint,
PathPrefix, Port) to also log TLSInsecureSkipVerify by adding
extModel.Spec.TLSInsecureSkipVerify or spec.TLSInsecureSkipVerify to the
structured log entry so operators can see whether insecure TLS is active.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Central YAML (base), Organization UI (inherited)

Review profile: CHILL

Plan: Pro

Run ID: fbfd0938-a373-49ad-9a98-c04f82bedb7e

📥 Commits

Reviewing files that changed from the base of the PR and between 4f06bcf and 6cb4a67.

📒 Files selected for processing (3)
  • deployment/base/maas-controller/crd/bases/maas.opendatahub.io_externalmodels.yaml
  • maas-controller/api/maas/v1alpha1/externalmodel_types.go
  • maas-controller/pkg/reconciler/externalmodel/reconciler.go

@nerdalert
Copy link
Copy Markdown
Member Author

@nerdalert nerdalert marked this pull request as ready for review March 30, 2026 21:15
@openshift-ci openshift-ci Bot requested a review from usize March 30, 2026 21:15
jland-redhat
jland-redhat previously approved these changes Mar 31, 2026
Copy link
Copy Markdown
Contributor

@jland-redhat jland-redhat left a comment

Choose a reason for hiding this comment

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

lgtm

@jland-redhat
Copy link
Copy Markdown
Contributor

/retest

@openshift-ci
Copy link
Copy Markdown

openshift-ci Bot commented Mar 31, 2026

@nerdalert: The following test failed, say /retest to rerun all failed tests or /retest-required to rerun all mandatory failed tests:

Test name Commit Details Required Rerun command
ci/prow/e2e-test-maas 6cb4a67 link false /test e2e-test-maas

Full PR test history. Your PR dashboard.

Details

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository. I understand the commands that are listed here.

@jland-redhat
Copy link
Copy Markdown
Contributor

/retest

@openshift-ci
Copy link
Copy Markdown

openshift-ci Bot commented Apr 10, 2026

New changes are detected. LGTM label has been removed.

@openshift-ci
Copy link
Copy Markdown

openshift-ci Bot commented Apr 10, 2026

[APPROVALNOTIFIER] This PR is APPROVED

This pull-request has been approved by: nerdalert

The full list of commands accepted by this bot can be found here.

The pull request process is described here

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

1 similar comment
@openshift-ci
Copy link
Copy Markdown

openshift-ci Bot commented Apr 10, 2026

[APPROVALNOTIFIER] This PR is APPROVED

This pull-request has been approved by: nerdalert

The full list of commands accepted by this bot can be found here.

The pull request process is described here

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

  Adds an optional spec.tlsInsecureSkipVerify field to the
  ExternalModel CRD. When true, the reconciler generates the
  DestinationRule with insecureSkipVerify: true, allowing
  connections to endpoints with self-signed certificates
  without manual patching that gets overwritten on
  reconciliation. Default is false.

  Closes opendatahub-io#627

Signed-off-by: Brent Salisbury <bsalisbu@redhat.com>
@nerdalert nerdalert force-pushed the brent-insecure-tls-extmodel branch from c6e3181 to dcaf2ca Compare April 10, 2026 19:39
Copy link
Copy Markdown
Contributor

@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: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
maas-api/internal/models/discovery.go (1)

57-79: ⚠️ Potential issue | 🟠 Major

Remove InsecureSkipVerify from the probe client (high, CWE-295).

Any pod, proxy, or sidecar on the network path to these HTTPS probes can present an arbitrary certificate and spoof /v1/models responses. That lets an attacker make inaccessible backends look accessible during listing. Use the cluster trust bundle or an explicit CA pool instead of disabling verification.

Remediation sketch
 func NewManager(log *logger.Logger, accessCheckTimeoutSeconds int) (*Manager, error) {
@@
 		httpClient: &http.Client{
 			Transport: &http.Transport{
-				TLSClientConfig:     &tls.Config{InsecureSkipVerify: true}, //nolint:gosec // cluster-internal only
+				TLSClientConfig: &tls.Config{
+					MinVersion: tls.VersionTLS12,
+					// RootCAs: clusterCABundle,
+				},
 				MaxIdleConns:        httpMaxIdleConns,
 				MaxIdleConnsPerHost: maxDiscoveryConcurrency,
 				IdleConnTimeout:     httpIdleConnTimeout,
 			},
 		},

As per coding guidelines, **/*.go: No InsecureSkipVerify in TLS configs (enables MITM attacks).

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

In `@maas-api/internal/models/discovery.go` around lines 57 - 79, The TLS config
for the probe HTTP client in NewManager currently sets InsecureSkipVerify: true
which must be removed; update the TLSClientConfig used in the Transport so it
does not disable verification and instead sets RootCAs to a proper CA pool (e.g.
load x509.SystemCertPool() and augment it with the cluster trust bundle or an
explicit CA file) or otherwise use the cluster trust bundle provided by your
environment, ensuring the Manager.httpClient Transport uses tls.Config{RootCAs:
caPool} (and no InsecureSkipVerify) so probe HTTPS certificates are properly
validated.
🧹 Nitpick comments (2)
maas-api/internal/config/config.go (1)

49-53: Deduplicate the access-check default.

15 is now defined here and again in maas-api/internal/models/discovery.go as defaultAccessCheckTimeout. If one side changes and the other does not, operators will get a timeout different from the configured/documented default. Move this into one shared constant and have both Load() and NewManager() consume it.

Also applies to: 65-65

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

In `@maas-api/internal/config/config.go` around lines 49 - 53, The default
access-check timeout value is duplicated (literal 15) between
AccessCheckTimeoutSeconds in the config struct and defaultAccessCheckTimeout in
models/discovery.go; create a single shared constant (e.g.,
DefaultAccessCheckTimeout) in the config package and replace the hard-coded 15
in both Load() (config loading/defaulting) and NewManager() (models discovery)
to reference that constant so both consumers derive the same default value.
maas-api/internal/config/config_test.go (1)

123-170: Add explicit failing cases for AccessCheckTimeoutSeconds.

These updates only keep the happy-path table entries passing. The new ACCESS_CHECK_TIMEOUT_SECONDS must be at least 1 branch still has no direct test, so accepting 0 or -1 would regress silently.

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

In `@maas-api/internal/config/config_test.go` around lines 123 - 170, The test
table in config_test.go lacks negative cases for AccessCheckTimeoutSeconds, so
add explicit failing entries that set Config.AccessCheckTimeoutSeconds to 0 and
to -1 and assert validation fails (matching the "ACCESS_CHECK_TIMEOUT_SECONDS
must be at least 1" branch). Locate the table of test cases in the file (where
Config values like DBConnectionURL, APIKeyMaxExpirationDays, and
AccessCheckTimeoutSeconds are defined) and add two cases named e.g.
"AccessCheckTimeoutSeconds zero invalid" and "AccessCheckTimeoutSeconds negative
invalid" that expect an error from the same validation function used by other
cases.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@deployment/base/maas-controller/crd/bases/maas.opendatahub.io_externalmodels.yaml`:
- Around line 104-115: Remove the tenant-facing spec.tlsInsecureSkipVerify
boolean from the ExternalModel CRD schema and any code that reads it (look for
the spec.tlsInsecureSkipVerify field in maas.opendatahub.io_externalmodels.yaml
and related controller logic that builds Istio DestinationRules); instead
enforce certificate verification by default and implement any necessary cluster-
or operator-level configuration for testing-only insecure TLS (e.g.,
operator-only flag or admin-only ConfigMap) so application-level CRs cannot
disable TLS validation; update schema, controller code that sets
trafficPolicy.tls.insecureSkipVerify, and related docs to reflect removal.

In `@maas-api/internal/handlers/models.go`:
- Around line 264-265: The code currently sets accessCheckedAt :=
time.Now().UTC() right before sending the response which records completion time
rather than the start of the authorization checks; change this to capture the
start timestamp (e.g., accessCheckStart := time.Now().UTC()) at the beginning of
the probe/authorization batch and use that value when writing the
"X-Access-Checked-At" header (replace uses of accessCheckedAt), and make the
same change for the other occurrence around lines handling h.maasModelRefLister
so the header reflects the batch start time instead of completion.

In `@maas-controller/pkg/reconciler/externalmodel/reconciler.go`:
- Around line 287-293: The reconciler currently propagates the user-controlled
extModel.Spec.TLSInsecureSkipVerify into the constructed config's
TLSInsecureSkipVerify field, allowing TLS cert validation to be disabled; update
the code in reconciler.go so TLSInsecureSkipVerify is never set from untrusted
user input (extModel.Spec.TLSInsecureSkipVerify). Instead, always set
TLSInsecureSkipVerify = false in the created spec, or enforce a safe server-side
policy/feature-gate that requires explicit operator approval before allowing
true; reference the TLSInsecureSkipVerify field and
extModel.Spec.TLSInsecureSkipVerify when making the change.

---

Outside diff comments:
In `@maas-api/internal/models/discovery.go`:
- Around line 57-79: The TLS config for the probe HTTP client in NewManager
currently sets InsecureSkipVerify: true which must be removed; update the
TLSClientConfig used in the Transport so it does not disable verification and
instead sets RootCAs to a proper CA pool (e.g. load x509.SystemCertPool() and
augment it with the cluster trust bundle or an explicit CA file) or otherwise
use the cluster trust bundle provided by your environment, ensuring the
Manager.httpClient Transport uses tls.Config{RootCAs: caPool} (and no
InsecureSkipVerify) so probe HTTPS certificates are properly validated.

---

Nitpick comments:
In `@maas-api/internal/config/config_test.go`:
- Around line 123-170: The test table in config_test.go lacks negative cases for
AccessCheckTimeoutSeconds, so add explicit failing entries that set
Config.AccessCheckTimeoutSeconds to 0 and to -1 and assert validation fails
(matching the "ACCESS_CHECK_TIMEOUT_SECONDS must be at least 1" branch). Locate
the table of test cases in the file (where Config values like DBConnectionURL,
APIKeyMaxExpirationDays, and AccessCheckTimeoutSeconds are defined) and add two
cases named e.g. "AccessCheckTimeoutSeconds zero invalid" and
"AccessCheckTimeoutSeconds negative invalid" that expect an error from the same
validation function used by other cases.

In `@maas-api/internal/config/config.go`:
- Around line 49-53: The default access-check timeout value is duplicated
(literal 15) between AccessCheckTimeoutSeconds in the config struct and
defaultAccessCheckTimeout in models/discovery.go; create a single shared
constant (e.g., DefaultAccessCheckTimeout) in the config package and replace the
hard-coded 15 in both Load() (config loading/defaulting) and NewManager()
(models discovery) to reference that constant so both consumers derive the same
default value.
🪄 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: Central YAML (base), Organization UI (inherited)

Review profile: CHILL

Plan: Pro

Run ID: b6adf5b2-bcf8-49a5-9b5d-93ec96c23912

📥 Commits

Reviewing files that changed from the base of the PR and between 6cb4a67 and c6e3181.

📒 Files selected for processing (9)
  • deployment/base/maas-controller/crd/bases/maas.opendatahub.io_externalmodels.yaml
  • maas-api/cmd/main.go
  • maas-api/internal/config/config.go
  • maas-api/internal/config/config_test.go
  • maas-api/internal/handlers/models.go
  • maas-api/internal/handlers/models_test.go
  • maas-api/internal/models/discovery.go
  • maas-controller/api/maas/v1alpha1/externalmodel_types.go
  • maas-controller/pkg/reconciler/externalmodel/reconciler.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • maas-controller/api/maas/v1alpha1/externalmodel_types.go

Comment on lines +104 to +115
tlsInsecureSkipVerify:
description: |-
TLSInsecureSkipVerify disables TLS certificate verification on the
DestinationRule created for this external model. When true, the generated
Istio DestinationRule includes insecureSkipVerify: true under
trafficPolicy.tls, allowing connections to endpoints with self-signed or
untrusted certificates (e.g., simulators, dev environments).

WARNING: Do not enable in production. This is intended for testing only.

Default: false (certificates are verified).
type: boolean
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Remove spec.tlsInsecureSkipVerify from the CRD (Critical, CWE-295).

This exposes a tenant-facing switch for disabling TLS certificate validation; attackers with network position can MITM egress traffic when enabled.

Remediation patch
-              tlsInsecureSkipVerify:
-                description: |-
-                  TLSInsecureSkipVerify disables TLS certificate verification on the
-                  DestinationRule created for this external model. When true, the generated
-                  Istio DestinationRule includes insecureSkipVerify: true under
-                  trafficPolicy.tls, allowing connections to endpoints with self-signed or
-                  untrusted certificates (e.g., simulators, dev environments).
-
-                  WARNING: Do not enable in production. This is intended for testing only.
-
-                  Default: false (certificates are verified).
-                type: boolean

As per coding guidelines, REVIEW PRIORITIES: 1. Security vulnerabilities (provide severity, exploit scenario, and remediation code).

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
tlsInsecureSkipVerify:
description: |-
TLSInsecureSkipVerify disables TLS certificate verification on the
DestinationRule created for this external model. When true, the generated
Istio DestinationRule includes insecureSkipVerify: true under
trafficPolicy.tls, allowing connections to endpoints with self-signed or
untrusted certificates (e.g., simulators, dev environments).
WARNING: Do not enable in production. This is intended for testing only.
Default: false (certificates are verified).
type: boolean
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@deployment/base/maas-controller/crd/bases/maas.opendatahub.io_externalmodels.yaml`
around lines 104 - 115, Remove the tenant-facing spec.tlsInsecureSkipVerify
boolean from the ExternalModel CRD schema and any code that reads it (look for
the spec.tlsInsecureSkipVerify field in maas.opendatahub.io_externalmodels.yaml
and related controller logic that builds Istio DestinationRules); instead
enforce certificate verification by default and implement any necessary cluster-
or operator-level configuration for testing-only insecure TLS (e.g.,
operator-only flag or admin-only ConfigMap) so application-level CRs cannot
disable TLS validation; update schema, controller code that sets
trafficPolicy.tls.insecureSkipVerify, and related docs to reflect removal.

Comment on lines +264 to 265
accessCheckedAt := time.Now().UTC()
if h.maasModelRefLister != nil {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

X-Access-Checked-At currently reports completion time, not check time.

For a slow probe batch, the earliest authorization decision may already be several seconds old when this header is emitted. If clients use it to assess freshness, keep the initial timestamp and return the batch start time, or rename the header to reflect completion instead.

Minimal fix
 	modelList := []models.Model{}
 	accessCheckedAt := time.Now().UTC()
@@
-		accessCheckedAt = time.Now().UTC()
 		h.logger.Debug("Access validation complete", "listed", len(list), "accessible", len(modelList), "subscriptions", len(subscriptionsToUse))
 	} else {

Also applies to: 366-376

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

In `@maas-api/internal/handlers/models.go` around lines 264 - 265, The code
currently sets accessCheckedAt := time.Now().UTC() right before sending the
response which records completion time rather than the start of the
authorization checks; change this to capture the start timestamp (e.g.,
accessCheckStart := time.Now().UTC()) at the beginning of the
probe/authorization batch and use that value when writing the
"X-Access-Checked-At" header (replace uses of accessCheckedAt), and make the
same change for the other occurrence around lines handling h.maasModelRefLister
so the header reflects the batch start time instead of completion.

Comment on lines +287 to 293
Provider: extModel.Spec.Provider,
Endpoint: extModel.Spec.Endpoint,
PathPrefix: ann[AnnPathPrefix],
TLS: true,
Port: 443,
TLSInsecureSkipVerify: extModel.Spec.TLSInsecureSkipVerify,
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Block certificate-validation bypass in reconciled spec (Critical, CWE-295).

Line 292 propagates a user-controlled flag that disables upstream TLS cert verification; an on-path attacker can impersonate the external endpoint and intercept prompts/credentials.

Remediation patch
 spec := ExternalModelSpec{
 	Provider:              extModel.Spec.Provider,
 	Endpoint:              extModel.Spec.Endpoint,
 	PathPrefix:            ann[AnnPathPrefix],
 	TLS:                   true,
 	Port:                  443,
-	TLSInsecureSkipVerify: extModel.Spec.TLSInsecureSkipVerify,
+	TLSInsecureSkipVerify: false,
 }
+
+if extModel.Spec.TLSInsecureSkipVerify {
+	return spec, fmt.Errorf("spec.tlsInsecureSkipVerify is not allowed")
+}

As per coding guidelines, No InsecureSkipVerify in TLS configs (enables MITM attacks).

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
Provider: extModel.Spec.Provider,
Endpoint: extModel.Spec.Endpoint,
PathPrefix: ann[AnnPathPrefix],
TLS: true,
Port: 443,
TLSInsecureSkipVerify: extModel.Spec.TLSInsecureSkipVerify,
}
spec := ExternalModelSpec{
Provider: extModel.Spec.Provider,
Endpoint: extModel.Spec.Endpoint,
PathPrefix: ann[AnnPathPrefix],
TLS: true,
Port: 443,
TLSInsecureSkipVerify: false,
}
if extModel.Spec.TLSInsecureSkipVerify {
return spec, fmt.Errorf("spec.tlsInsecureSkipVerify is not allowed")
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@maas-controller/pkg/reconciler/externalmodel/reconciler.go` around lines 287
- 293, The reconciler currently propagates the user-controlled
extModel.Spec.TLSInsecureSkipVerify into the constructed config's
TLSInsecureSkipVerify field, allowing TLS cert validation to be disabled; update
the code in reconciler.go so TLSInsecureSkipVerify is never set from untrusted
user input (extModel.Spec.TLSInsecureSkipVerify). Instead, always set
TLSInsecureSkipVerify = false in the created spec, or enforce a safe server-side
policy/feature-gate that requires explicit operator approval before allowing
true; reference the TLSInsecureSkipVerify field and
extModel.Spec.TLSInsecureSkipVerify when making the change.

Copy link
Copy Markdown
Contributor

@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.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
maas-api/internal/models/discovery.go (1)

68-77: ⚠️ Potential issue | 🟠 Major

Remove InsecureSkipVerify from the probe client (CWE-295).

These probes forward the caller's Authorization header. Although filtering is restricted to cluster-internal models (ExternalModels are skipped), disabling certificate verification enables MITM attacks from compromised pods or network paths within the cluster to steal tokens or spoof access decisions. Use the cluster CA instead.

The suggested fix requires loading the cluster's root CA bundle, not the system CA pool alone. For in-cluster services, use x509.SystemCertPool() as a base, then append the cluster CA from the mounted service-account token or explicit bundle:

rootCAs, err := x509.SystemCertPool()
if err != nil {
	return nil, fmt.Errorf("load system cert pool: %w", err)
}
// Load cluster CA from /var/run/secrets/kubernetes.io/serviceaccount/ca.crt
clusterCA, err := os.ReadFile("/var/run/secrets/kubernetes.io/serviceaccount/ca.crt")
if err != nil {
	return nil, fmt.Errorf("load cluster CA: %w", err)
}
if !rootCAs.AppendCertsFromPEM(clusterCA) {
	return nil, errors.New("cluster CA certificate failed to parse")
}

Then set TLSClientConfig: &tls.Config{RootCAs: rootCAs, MinVersion: tls.VersionTLS12}.

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

In `@maas-api/internal/models/discovery.go` around lines 68 - 77, Remove the
insecure skip-verify flag on the probe HTTP client and instead build a proper
RootCAs pool: in the code that constructs httpClient (the http.Client with
Transport: &http.Transport{...} and TLSClientConfig: &tls.Config{...}), replace
InsecureSkipVerify: true with TLS config that sets RootCAs to
x509.SystemCertPool() with the cluster CA appended from
/var/run/secrets/kubernetes.io/serviceaccount/ca.crt, return/propagate errors if
loading the system pool or reading/parsing the cluster CA fails, and set
MinVersion to tls.VersionTLS12; keep the other transport fields (MaxIdleConns,
MaxIdleConnsPerHost, IdleConnTimeout) and ensure this change happens where the
probe client is initialized so callers (e.g., access check logic using
accessCheckTimeout and maxDiscoveryConcurrency) receive a properly verified
client.
♻️ Duplicate comments (2)
deployment/base/maas-controller/crd/bases/maas.opendatahub.io_externalmodels.yaml (1)

104-115: ⚠️ Potential issue | 🔴 Critical

Remove tenant-exposed spec.tlsInsecureSkipVerify from CRD (Critical, CWE-295).

This schema field enables certificate-validation bypass from a namespaced CR.
Exploit scenario: a compromised tenant workload or malicious user can configure traffic to skip TLS verification and transparently MITM external model connections.

Remediation patch
-              tlsInsecureSkipVerify:
-                description: |-
-                  TLSInsecureSkipVerify disables TLS certificate verification on the
-                  DestinationRule created for this external model. When true, the generated
-                  Istio DestinationRule includes insecureSkipVerify: true under
-                  trafficPolicy.tls, allowing connections to endpoints with self-signed or
-                  untrusted certificates (e.g., simulators, dev environments).
-
-                  WARNING: Do not enable in production. This is intended for testing only.
-
-                  Default: false (certificates are verified).
-                type: boolean

As per coding guidelines, REVIEW PRIORITIES: 1. Security vulnerabilities (provide severity, exploit scenario, and remediation code).

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

In
`@deployment/base/maas-controller/crd/bases/maas.opendatahub.io_externalmodels.yaml`
around lines 104 - 115, The CRD exposes a tenant-configurable boolean
tlsInsecureSkipVerify (spec.tlsInsecureSkipVerify) which allows disabling TLS
certificate verification in the generated DestinationRule
(trafficPolicy.tls.insecureSkipVerify) — remove this schema field so tenants
cannot opt out of cert validation; update the CRD by deleting the
tlsInsecureSkipVerify property and any references to it (and ensure code that
reads spec.tlsInsecureSkipVerify is hardened/removed from the controller that
creates DestinationRule objects so insecureSkipVerify is never set from CR
input).
maas-controller/pkg/reconciler/externalmodel/reconciler.go (1)

287-293: ⚠️ Potential issue | 🔴 Critical

Block tenant-controlled TLS verification bypass (Critical, CWE-295).

Line 292 propagates a user-controlled flag into transport policy, which allows DestinationRule.trafficPolicy.tls.insecureSkipVerify=true.
Exploit scenario: any on-path attacker (cluster network, egress gateway path, DNS poisoning + rogue cert) can impersonate the upstream endpoint and intercept/alter prompts, responses, or credentials.

Remediation patch
 spec := ExternalModelSpec{
 	Provider:              extModel.Spec.Provider,
 	Endpoint:              extModel.Spec.Endpoint,
 	PathPrefix:            ann[AnnPathPrefix],
 	TLS:                   true,
 	Port:                  443,
-	TLSInsecureSkipVerify: extModel.Spec.TLSInsecureSkipVerify,
+	TLSInsecureSkipVerify: false,
 }
+
+if extModel.Spec.TLSInsecureSkipVerify {
+	return spec, fmt.Errorf("spec.tlsInsecureSkipVerify is not allowed")
+}

As per coding guidelines, No InsecureSkipVerify in TLS configs (enables MITM attacks).

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

In `@maas-controller/pkg/reconciler/externalmodel/reconciler.go` around lines 287
- 293, The code currently propagates the tenant-controlled flag
extModel.Spec.TLSInsecureSkipVerify into the built TLS config (the
TLSInsecureSkipVerify field in the struct created in reconciler.go), enabling
insecureSkipVerify; change this so that TLSInsecureSkipVerify is never set from
tenant input (always set to false) and instead log or record any tenant attempts
to enable it (or reject the resource) to an audit/event; update the code that
builds the DestinationRule/transport policy in reconciler.go to ignore
extModel.Spec.TLSInsecureSkipVerify and ensure TLSInsecureSkipVerify = false and
add a validation/audit path to surface the denied override.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@maas-api/internal/models/discovery.go`:
- Around line 68-77: Remove the insecure skip-verify flag on the probe HTTP
client and instead build a proper RootCAs pool: in the code that constructs
httpClient (the http.Client with Transport: &http.Transport{...} and
TLSClientConfig: &tls.Config{...}), replace InsecureSkipVerify: true with TLS
config that sets RootCAs to x509.SystemCertPool() with the cluster CA appended
from /var/run/secrets/kubernetes.io/serviceaccount/ca.crt, return/propagate
errors if loading the system pool or reading/parsing the cluster CA fails, and
set MinVersion to tls.VersionTLS12; keep the other transport fields
(MaxIdleConns, MaxIdleConnsPerHost, IdleConnTimeout) and ensure this change
happens where the probe client is initialized so callers (e.g., access check
logic using accessCheckTimeout and maxDiscoveryConcurrency) receive a properly
verified client.

---

Duplicate comments:
In
`@deployment/base/maas-controller/crd/bases/maas.opendatahub.io_externalmodels.yaml`:
- Around line 104-115: The CRD exposes a tenant-configurable boolean
tlsInsecureSkipVerify (spec.tlsInsecureSkipVerify) which allows disabling TLS
certificate verification in the generated DestinationRule
(trafficPolicy.tls.insecureSkipVerify) — remove this schema field so tenants
cannot opt out of cert validation; update the CRD by deleting the
tlsInsecureSkipVerify property and any references to it (and ensure code that
reads spec.tlsInsecureSkipVerify is hardened/removed from the controller that
creates DestinationRule objects so insecureSkipVerify is never set from CR
input).

In `@maas-controller/pkg/reconciler/externalmodel/reconciler.go`:
- Around line 287-293: The code currently propagates the tenant-controlled flag
extModel.Spec.TLSInsecureSkipVerify into the built TLS config (the
TLSInsecureSkipVerify field in the struct created in reconciler.go), enabling
insecureSkipVerify; change this so that TLSInsecureSkipVerify is never set from
tenant input (always set to false) and instead log or record any tenant attempts
to enable it (or reject the resource) to an audit/event; update the code that
builds the DestinationRule/transport policy in reconciler.go to ignore
extModel.Spec.TLSInsecureSkipVerify and ensure TLSInsecureSkipVerify = false and
add a validation/audit path to surface the denied override.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Central YAML (base), Organization UI (inherited)

Review profile: CHILL

Plan: Pro

Run ID: 8f55beb7-f24a-4442-b94a-9b80175228ce

📥 Commits

Reviewing files that changed from the base of the PR and between c6e3181 and dcaf2ca.

📒 Files selected for processing (9)
  • deployment/base/maas-controller/crd/bases/maas.opendatahub.io_externalmodels.yaml
  • maas-api/cmd/main.go
  • maas-api/internal/config/config.go
  • maas-api/internal/config/config_test.go
  • maas-api/internal/handlers/models.go
  • maas-api/internal/handlers/models_test.go
  • maas-api/internal/models/discovery.go
  • maas-controller/api/maas/v1alpha1/externalmodel_types.go
  • maas-controller/pkg/reconciler/externalmodel/reconciler.go
🚧 Files skipped from review as they are similar to previous changes (6)
  • maas-api/cmd/main.go
  • maas-api/internal/config/config.go
  • maas-api/internal/config/config_test.go
  • maas-controller/api/maas/v1alpha1/externalmodel_types.go
  • maas-api/internal/handlers/models_test.go
  • maas-api/internal/handlers/models.go

@openshift-ci
Copy link
Copy Markdown

openshift-ci Bot commented Apr 14, 2026

PR needs rebase.

Details

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository.

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.

feat: support insecureSkipVerify in ExternalModel DestinationRule

3 participants