Skip to content

Fixes - Config DS K8s token refresh and envoy ext_proc to fail closed#3320

Merged
thivindu merged 2 commits into
wso2:1.xfrom
thivindu:1.x
May 5, 2026
Merged

Fixes - Config DS K8s token refresh and envoy ext_proc to fail closed#3320
thivindu merged 2 commits into
wso2:1.xfrom
thivindu:1.x

Conversation

@thivindu
Copy link
Copy Markdown
Contributor

Purpose

  1. This PR fixes the issue of the config deployer service not picking up the updated service account token when it is rotated by Kubernetes so the config deployer continues using the expired token. Therefore, all subsequent Kubernetes API calls are failing.
  2. This PR fixes the issue where the Envoy failing open when ext proc fails.

Related to -
#3318

Goals

  1. Read the SA token from disk on every call, ensuring that token rotation is handled seamlessly.
  2. Ensure that successful processing by the ext_proc service is a strict prerequisite for the request to continue or else block the request.

Approach

Dynamically read the Kubernetes Service Account (SA) token for every API request, ensuring compatibility with token rotation, and consistently attaching the correct authentication header to all requests.

Kubernetes API Authentication Handling:

  • Introduced the getK8sAuthHeader() function in K8sClient.bal to read the SA token from disk on every call, ensuring that token rotation is handled seamlessly. All Kubernetes API requests now include the up-to-date Authorization header.
  • Removed the static reading and storage of the SA token at module initialization, eliminating issues caused by token rotation.
  • Updated all HTTP client calls in K8sClient.bal to use the dynamic authentication header by passing headers = getK8sAuthHeader() for every request (GET, POST, PUT, DELETE).
  • Removed the hardcoded auth block from the HTTP client initialization, since the header is now dynamically attached per request.

Set the failure_mode_allow: false in the ext proc configuration to ensure Envoy to fail close.

Ext Proc failure Handling:

  • Changed FailureModeAllow from true to false in the Envoy external processing filter configuration.

@thivindu thivindu requested a review from Krishanx92 as a code owner April 24, 2026 06:33
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 24, 2026

📝 Walkthrough

Summary

This pull request addresses two issues: ensuring the config deployer service uses the latest Kubernetes service account tokens, and making the Envoy external processing filter fail closed on errors.

Changes

Kubernetes Token Refresh

  • Added getK8sAuthHeader() function in K8sClient.bal to dynamically read the service account token from disk on each Kubernetes API call, enabling support for token rotation
  • Removed static token initialization and the hardcoded auth configuration from the HTTP client
  • Updated all Kubernetes API requests (GET, POST, PUT, DELETE) to include the fresh authorization header with each call

Envoy External Processing Behavior

  • Modified the Envoy ext_proc HTTP filter configuration to set FailureModeAllow to false, ensuring external processing failures block the request instead of allowing it to proceed

Impact

These changes ensure the config deployer maintains valid Kubernetes authentication when tokens are rotated, and strengthen the security posture by requiring successful external processing for all requests to proceed.

Walkthrough

The pull request contains two modifications to authentication and failure handling. The first change updates the Envoy ext_proc filter configuration to set FailureModeAllow to false, meaning requests will no longer proceed if the external processor fails. The second change refactors Kubernetes API client authentication by replacing static token reading at module initialization with on-demand token retrieval. A new getK8sAuthHeader() function reads the token from disk per request and constructs the Authorization header, and all Kubernetes API calls (GET, POST, PUT, DELETE operations across configmaps, routes, policies, and other resources) have been updated to include this header.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title directly summarizes both main changes: K8s token refresh handling and Envoy ext_proc failure behavior correction.
Description check ✅ Passed The description covers Purpose, Goals, and Approach sections with clear explanations. However, several template sections are missing (User stories, Release note, Documentation, Training, Certification, Marketing, Automation tests, Security checks, Samples, Related PRs, Migrations, Test environment, Learning).
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 golangci-lint (2.11.4)

level=error msg="[linters_context] typechecking error: pattern ./...: directory prefix . does not contain main module or its selected dependencies"


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.

Comment on lines 248 to +249
},
FailureModeAllow: true,
FailureModeAllow: false,
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Log Improvement Suggestion No: 1

Suggested change
},
FailureModeAllow: true,
FailureModeAllow: false,
},
FailureModeAllow: false,
log.Info("External process HTTP filter configured with FailureModeAllow set to false")

Comment on lines +47 to +52
isolated function getK8sAuthHeader() returns map<string|string[]> {
string|error token = io:fileReadString(k8sConfiguration.serviceAccountPath + "/token");
if token is string {
return {"Authorization": "Bearer " + token};
}
return {};
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Log Improvement Suggestion No: 2

Suggested change
isolated function getK8sAuthHeader() returns map<string|string[]> {
string|error token = io:fileReadString(k8sConfiguration.serviceAccountPath + "/token");
if token is string {
return {"Authorization": "Bearer " + token};
}
return {};
isolated function getK8sAuthHeader() returns map<string|string[]> {
string|error token = io:fileReadString(k8sConfiguration.serviceAccountPath + "/token");
if token is string {
log:printDebug("Successfully read SA token from disk");
return {"Authorization": "Bearer " + token};
}
log:printError("Failed to read SA token", 'error = token);
return {};
}

Copy link
Copy Markdown

@wso2-engineering wso2-engineering Bot left a comment

Choose a reason for hiding this comment

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

AI Agent Log Improvement Checklist

⚠️ Warning: AI-Generated Review Comments

  • The log-related comments and suggestions in this review were generated by an AI tool to assist with identifying potential improvements. Purpose of reviewing the code for log improvements is to improve the troubleshooting capabilities of our products.
  • Please make sure to manually review and validate all suggestions before applying any changes. Not every code suggestion would make sense or add value to our purpose. Therefore, you have the freedom to decide which of the suggestions are helpful.

✅ Before merging this pull request:

  • Review all AI-generated comments for accuracy and relevance.
  • Complete and verify the table below. We need your feedback to measure the accuracy of these suggestions and the value they add. If you are rejecting a certain code suggestion, please mention the reason briefly in the suggestion for us to capture it.
Comment Accepted (Y/N) Reason
#### Log Improvement Suggestion No: 1
#### Log Improvement Suggestion No: 2

@thivindu
Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 24, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

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.

♻️ Duplicate comments (1)
runtime/config-deployer-service/ballerina/K8sClient.bal (1)

47-53: ⚠️ Potential issue | 🟡 Minor

Log token read failures for observability.

When io:fileReadString fails, the function silently returns an empty header map. Downstream calls will then receive 401 responses with no indication of the root cause. Adding a log statement on the error path will make token-rotation or mount issues diagnosable in production.

🪵 Proposed fix to add error logging
 isolated function getK8sAuthHeader() returns map<string|string[]> {
     string|error token = io:fileReadString(k8sConfiguration.serviceAccountPath + "/token");
     if token is string {
         return {"Authorization": "Bearer " + token};
     }
+    log:printError("Failed to read Kubernetes service account token", 'error = token);
     return {};
 }

Note: this will require adding import ballerina/log; if not already imported in this module.

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

In `@runtime/config-deployer-service/ballerina/K8sClient.bal` around lines 47 -
53, The getK8sAuthHeader function currently swallows errors from
io:fileReadString and returns an empty header; update getK8sAuthHeader to log
the error when token is not a string (i.e., when io:fileReadString returns an
error) using ballerina/log so failures reading the service account token are
recorded, then continue to return the empty map; add the required import
statement (import ballerina/log;) if it is not already present.
🧹 Nitpick comments (3)
adapter/internal/oasparser/envoyconf/http_filters.go (1)

249-249: Fail-closed behavior correctly configured; verify operational readiness.

Setting FailureModeAllow: false aligns with the PR objective and is consistent with the ext_authz filter configuration in adapter/internal/operator/gateway-api/translator/extauth.go (lines 110-135). This is a meaningful behavioral change: any ext_proc unavailability, timeout, or connection failure will now result in request denials rather than bypassing the processor. Please ensure the enforcer's availability, response timeout (conf.Envoy.EnforcerResponseTimeoutInSeconds), and rollout/health-check posture are sized accordingly, and that monitoring/alerting on the ext_proc cluster is in place before release.

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

In `@adapter/internal/oasparser/envoyconf/http_filters.go` at line 249, You set
FailureModeAllow: false (fail-closed) for ext_proc; ensure operational readiness
by verifying the enforcer/ext_proc cluster is highly available, tuned for the
configured conf.Envoy.EnforcerResponseTimeoutInSeconds, and has proper health
checks and readiness probes before rollout; add/confirm monitoring and alerting
for ext_proc latency/errors, update deployment/rollout docs to reflect the
change, and cross-check ext_authz behavior in the related translator (ext_authz)
to ensure consistent timeout and retry settings.
runtime/config-deployer-service/ballerina/K8sClient.bal (2)

45-53: Consider caching the token to avoid per-request disk I/O.

Kubernetes projected service account tokens rotate on the order of minutes to hours, so reading the token from disk on every Kubernetes API call is more frequent than required and adds file I/O to the hot path. A small in-memory cache invalidated by a short TTL (or by the file's modification time) would preserve the token-rotation fix while reducing I/O. Deferrable, but worth considering if the deployer handles a high request volume.

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

In `@runtime/config-deployer-service/ballerina/K8sClient.bal` around lines 45 -
53, The getK8sAuthHeader function currently reads the service account token from
disk on every call; change it to cache the token in-memory with a short TTL (or
validate against the token file's modification time) so repeated calls return
the cached token until invalidated, then re-read and update the cache; update
references to k8sConfiguration.serviceAccountPath and ensure the cache is safe
for concurrent isolated function usage and returns the same map<string|string[]>
shape as before when valid.

163-163: Use named argument for consistency with other call sites.

Every other updated call site passes the header map via the named argument headers = getK8sAuthHeader(), but this line uses a positional argument. Using named arguments improves readability and makes the code intent explicit.

♻️ Proposed fix
-        http:Response response = check k8sApiServerEp->get(endpoint, getK8sAuthHeader());
+        http:Response response = check k8sApiServerEp->get(endpoint, headers = getK8sAuthHeader());
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@runtime/config-deployer-service/ballerina/K8sClient.bal` at line 163, The
call to k8sApiServerEp->get is using a positional argument for headers
(k8sApiServerEp->get(endpoint, getK8sAuthHeader())) inconsistent with other
sites; change it to pass the header map as a named argument (headers =
getK8sAuthHeader()) so the http:Response response = check
k8sApiServerEp->get(...) call uses headers = getK8sAuthHeader(), keeping the
same endpoint and response handling.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@runtime/config-deployer-service/ballerina/K8sClient.bal`:
- Around line 47-53: The getK8sAuthHeader function currently swallows errors
from io:fileReadString and returns an empty header; update getK8sAuthHeader to
log the error when token is not a string (i.e., when io:fileReadString returns
an error) using ballerina/log so failures reading the service account token are
recorded, then continue to return the empty map; add the required import
statement (import ballerina/log;) if it is not already present.

---

Nitpick comments:
In `@adapter/internal/oasparser/envoyconf/http_filters.go`:
- Line 249: You set FailureModeAllow: false (fail-closed) for ext_proc; ensure
operational readiness by verifying the enforcer/ext_proc cluster is highly
available, tuned for the configured conf.Envoy.EnforcerResponseTimeoutInSeconds,
and has proper health checks and readiness probes before rollout; add/confirm
monitoring and alerting for ext_proc latency/errors, update deployment/rollout
docs to reflect the change, and cross-check ext_authz behavior in the related
translator (ext_authz) to ensure consistent timeout and retry settings.

In `@runtime/config-deployer-service/ballerina/K8sClient.bal`:
- Around line 45-53: The getK8sAuthHeader function currently reads the service
account token from disk on every call; change it to cache the token in-memory
with a short TTL (or validate against the token file's modification time) so
repeated calls return the cached token until invalidated, then re-read and
update the cache; update references to k8sConfiguration.serviceAccountPath and
ensure the cache is safe for concurrent isolated function usage and returns the
same map<string|string[]> shape as before when valid.
- Line 163: The call to k8sApiServerEp->get is using a positional argument for
headers (k8sApiServerEp->get(endpoint, getK8sAuthHeader())) inconsistent with
other sites; change it to pass the header map as a named argument (headers =
getK8sAuthHeader()) so the http:Response response = check
k8sApiServerEp->get(...) call uses headers = getK8sAuthHeader(), keeping the
same endpoint and response handling.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: ccd2eb16-ef70-491e-9906-c2a704299a1e

📥 Commits

Reviewing files that changed from the base of the PR and between 04059fc and a3b40a2.

📒 Files selected for processing (2)
  • adapter/internal/oasparser/envoyconf/http_filters.go
  • runtime/config-deployer-service/ballerina/K8sClient.bal

@thivindu thivindu merged commit 5770425 into wso2:1.x May 5, 2026
2 checks passed
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.

2 participants