Skip to content

feat: add kubernetes connection proxy for http check#2839

Open
yashmehrotra wants to merge 3 commits intomasterfrom
http-k8s-proxy
Open

feat: add kubernetes connection proxy for http check#2839
yashmehrotra wants to merge 3 commits intomasterfrom
http-k8s-proxy

Conversation

@yashmehrotra
Copy link
Copy Markdown
Member

@yashmehrotra yashmehrotra commented Jan 17, 2026

Fixes: flanksource/duty#1654

Summary by CodeRabbit

  • New Features
    • HTTP checks now support Kubernetes connections for proxied requests through service endpoints
    • Added kubernetes field with support for multiple credential providers: cnrm, eks, gke, kubeconfig, and direct connections
    • Kubernetes-backed checks support TLS verification configuration and project/zone settings

✏️ Tip: You can customize this high-level summary in your review settings.

@yashmehrotra yashmehrotra requested a review from moshloop January 17, 2026 02:30
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Jan 17, 2026

Walkthrough

Introduces Kubernetes-aware HTTP checks enabling HTTP requests to route through Kubernetes API proxies, allowing internal cluster service access without external exposure. Changes include new Kubernetes connection field on HTTPCheck struct, HTTP checker implementation for Kubernetes transport injection and URL rewriting, schema definitions for multi-provider Kubernetes connections (EKS, GKE, CNRM, kubeconfig), and test fixtures.

Changes

Cohort / File(s) Summary
Core API Structs
api/v1/checks.go, api/v1/zz_generated.deepcopy.go
Added optional Kubernetes *connection.KubernetesConnection field to HTTPCheck with YAML/JSON tags. Generated deepcopy support for nested Kubernetes field.
HTTP Checker Implementation
checks/http.go
Expanded request generation to instantiate Kubernetes client from context, extract REST transport, and inject into HTTP client when check.Kubernetes is set. Rewrites URL to target Kubernetes service proxy path. Updated generateHTTPRequest method signature to accept *v1.HTTPCheck pointer. Enhanced hydration flow to populate Kubernetes config in context. Added URL logging.
Schema Definitions
config/schemas/health_http.schema.json
Added four new public schema types: KubernetesConnection, GKEConnection, EKSConnection, CNRMConnection with provider-specific fields (cluster, projectID, zone, accessKey, assumeRole, region, etc.) and credential source definitions.
Schema References
config/schemas/canary.schema.json, config/schemas/component.schema.json, config/schemas/topology.schema.json
Added kubernetes property to HTTPCheck and AlertManagerCheck definitions, referencing KubernetesConnection schema.
Manifest Definitions
config/deploy/Canary.yml, config/deploy/manifests.yaml
Extended HTTP check configuration with kubernetes field containing full provider-specific schemas (cnrm, connection, eks, gke, kubeconfig) with nested credential references (configMapKeyRef, helmRef, secretKeyRef, serviceAccount).
Test Fixture
fixtures/k8s/http-kubernetes-proxy.yaml
New Canary manifest fixture demonstrating Kubernetes-backed HTTP check with empty kubernetes config and httpbin endpoint.

Suggested reviewers

  • moshloop
🚥 Pre-merge checks | ✅ 4 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% 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 'feat: add kubernetes connection proxy for http check' directly and specifically describes the main change in the PR: adding Kubernetes connection proxy support for HTTP checks.
Linked Issues check ✅ Passed The PR successfully implements the objective from issue #1654 to enable HTTP checks to connect through Kubernetes API proxy to services inside clusters.
Out of Scope Changes check ✅ Passed All changes are directly related to implementing Kubernetes connection proxy for HTTP checks; no unrelated or out-of-scope modifications detected.

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

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch http-k8s-proxy

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.

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: 6

Caution

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

⚠️ Outside diff range comments (1)
checks/http.go (1)

79-104: Fix fmt.Errorf formatting at line 98 to unblock vet/Go test.

Line 98 uses %s with *connection.KubernetesConnection (a pointer type), which causes SA5009. Use %v instead to properly format the pointer.

🐛 Proposed fix
-if err != nil { -return nil, fmt.Errorf("failed to instantiate k8s client (%s): %w", check.Kubernetes, err) -} +if err != nil { +return nil, fmt.Errorf("failed to instantiate k8s client (%v): %w", check.Kubernetes, err) +}
🤖 Fix all issues with AI agents
In `@checks/http.go`: - Around line 199-205: Remove the trailing whitespace flagged by the linter in the Kubernetes hydration block: edit the block that checks check.Kubernetes (the if check.Kubernetes != nil { ... } section that calls ctx.WithKubernetesConnection and check.Kubernetes.Populate) and trim any extra spaces at the end of the closing brace/line so there is no trailing whitespace left. - Around line 110-121: The rewritten K8s proxy URL drops query parameters; update the logic that sets check.URL (where parsedURL, port, svc, ns are used) to preserve parsedURL.RawQuery (and optionally Fragment) when rebuilding the URL: after constructing the path portion (using strings.TrimPrefix(parsedURL.Path, "/")), append the original RawQuery prefixed with "?" if non-empty so query parameters are forwarded to the proxied endpoint; ensure the resulting check.URL still uses k8s.Config.Host and the same namespace/service/port composition. - Around line 413-417: The code currently logs the full URL via logger.Infof("Check url is %s", check.URL) which may expose sensitive query params; update the logging to use debug level and redact query parameters before logging: in the flow after c.generateHTTPRequest(...) (referencing generateHTTPRequest and check.URL) parse the URL, strip or replace RawQuery with a placeholder (e.g., "<redacted>") or remove it entirely, then call logger.Debugf with the sanitized URL instead of logger.Infof; ensure no full query string is emitted anywhere in the info-level logs. In `@config/deploy/manifests.yaml`: - Around line 4488-4816: The http.kubernetes schema is missing the serviceAccount field used for proxy auth; update the kubernetes block in this diff to add a serviceAccount property (name: serviceAccount, description: "ServiceAccount specifies the service account whose token should be fetched", type: string) in the same places other Kubernetes connection schemas include it (e.g., alongside other valueFrom/serviceAccount usages and under kubernetes properties), ensuring the symbol "kubernetes" in the HTTP connection schema now contains "serviceAccount" so CRD validation accepts manifests that supply that token. In `@config/schemas/health_http.schema.json`: - Around line 180-214: Add descriptive documentation entries to the GKEConnection schema to clarify the difference between "project" and "projectID": update the "properties" for GKEConnection to include a "description" for "projectID" stating it is the required unique GCP project identifier used for API operations, and a "description" for "project" stating it is an optional human-readable/display name for the project; keep "projectID" in the "required" array and leave "project" optional. In `@fixtures/k8s/http-kubernetes-proxy.yaml`: - Around line 1-10: The fixture file named by metadata.name "http-check" is not following the test naming convention so the suite won't run it; rename the file to end with _pass.yaml (e.g., http-kubernetes-proxy_pass.yaml) so it is executed, and ensure the kubernetes: {} entry (the empty KubernetesConnection) either relies on in-cluster/default credentials or is populated explicitly: update the fixture or test documentation to state that the hydrate path calls Populate(ctx.Context, true) on the KubernetesConnection and that an empty config must resolve to in-cluster/default auth (or provide the required fields in the fixture) so runtime Populate(...) does not fail. 
🧹 Nitpick comments (1)
config/schemas/health_http.schema.json (1)

372-392: Consider adding JSON schema validation to ensure at least one Kubernetes connection method is specified.

The KubernetesConnection definition has no required fields, allowing an empty object {} to pass schema validation. While the Populate() method in the duty package validates the configuration at runtime (see checks/http.go line 201), adding a schema-level constraint such as oneOf across the connection methods or minProperties: 1 would provide earlier validation feedback to users.

If this flexibility is intentional to support in-cluster defaults or other connection modes, no changes are required.

Comment on lines +110 to +121
parsedURL, err := url.Parse(check.URL)
if err != nil {
return nil, fmt.Errorf("error parsing check url[%s]: %w", check.URL, err)
}

port := lo.CoalesceOrEmpty(parsedURL.Port(), lo.Ternary(parsedURL.Scheme == "https", "443", "80"))
parts := strings.Split(parsedURL.Hostname(), ".")
if len(parts) < 2 {
return nil, fmt.Errorf("check host[%s] is invalid. Use `service.namespace` format", parsedURL.Hostname())
}
svc, ns := parts[0], parts[1]
check.URL = fmt.Sprintf("%s/api/v1/namespaces/%s/services/%s:%s/proxy/%s", k8s.Config.Host, ns, svc, port, strings.TrimPrefix(parsedURL.Path, "/"))
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

Preserve query parameters when rewriting the K8s proxy URL.

Line 121 rebuilds the URL but drops RawQuery, so checks with query params will hit the wrong endpoint.

🔧 Proposed fix
-check.URL = fmt.Sprintf("%s/api/v1/namespaces/%s/services/%s:%s/proxy/%s", k8s.Config.Host, ns, svc, port, strings.TrimPrefix(parsedURL.Path, "/")) +path := strings.TrimPrefix(parsedURL.EscapedPath(), "/") +proxyURL := fmt.Sprintf("%s/api/v1/namespaces/%s/services/%s:%s/proxy/%s", k8s.Config.Host, ns, svc, port, path) +if parsedURL.RawQuery != "" { +proxyURL += "?" + parsedURL.RawQuery +} +check.URL = proxyURL
📝 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
parsedURL, err := url.Parse(check.URL)
if err != nil {
return nil, fmt.Errorf("error parsing check url[%s]: %w", check.URL, err)
}
port := lo.CoalesceOrEmpty(parsedURL.Port(), lo.Ternary(parsedURL.Scheme == "https", "443", "80"))
parts := strings.Split(parsedURL.Hostname(), ".")
if len(parts) < 2 {
return nil, fmt.Errorf("check host[%s] is invalid. Use `service.namespace` format", parsedURL.Hostname())
}
svc, ns := parts[0], parts[1]
check.URL = fmt.Sprintf("%s/api/v1/namespaces/%s/services/%s:%s/proxy/%s", k8s.Config.Host, ns, svc, port, strings.TrimPrefix(parsedURL.Path, "/"))
parsedURL, err := url.Parse(check.URL)
if err != nil {
return nil, fmt.Errorf("error parsing check url[%s]: %w", check.URL, err)
}
port := lo.CoalesceOrEmpty(parsedURL.Port(), lo.Ternary(parsedURL.Scheme == "https", "443", "80"))
parts := strings.Split(parsedURL.Hostname(), ".")
if len(parts) < 2 {
return nil, fmt.Errorf("check host[%s] is invalid. Use `service.namespace` format", parsedURL.Hostname())
}
svc, ns := parts[0], parts[1]
path := strings.TrimPrefix(parsedURL.EscapedPath(), "/")
proxyURL := fmt.Sprintf("%s/api/v1/namespaces/%s/services/%s:%s/proxy/%s", k8s.Config.Host, ns, svc, port, path)
if parsedURL.RawQuery != "" {
proxyURL += "?" + parsedURL.RawQuery
}
check.URL = proxyURL
🤖 Prompt for AI Agents
In `@checks/http.go` around lines 110 - 121, The rewritten K8s proxy URL drops query parameters; update the logic that sets check.URL (where parsedURL, port, svc, ns are used) to preserve parsedURL.RawQuery (and optionally Fragment) when rebuilding the URL: after constructing the path portion (using strings.TrimPrefix(parsedURL.Path, "/")), append the original RawQuery prefixed with "?" if non-empty so query parameters are forwarded to the proxied endpoint; ensure the resulting check.URL still uses k8s.Config.Host and the same namespace/service/port composition. 
Comment on lines +199 to +205
if check.Kubernetes != nil {
*ctx = ctx.WithKubernetesConnection(*check.Kubernetes)
if _, _, err := check.Kubernetes.Populate(ctx.Context, true); err != nil {
return nil, nil, oops, results.Invalidf("failed to hydrate kubernetes connection: %v", err)
}

}
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 | 🟡 Minor

Remove trailing whitespace to satisfy lint.

Lint flags unnecessary trailing whitespace at Line 205; please trim it to keep CI green.

🧰 Tools
🪛 GitHub Check: lint

[failure] 205-205:
unnecessary trailing newline (whitespace)

🤖 Prompt for AI Agents
In `@checks/http.go` around lines 199 - 205, Remove the trailing whitespace flagged by the linter in the Kubernetes hydration block: edit the block that checks check.Kubernetes (the if check.Kubernetes != nil { ... } section that calls ctx.WithKubernetesConnection and check.Kubernetes.Populate) and trim any extra spaces at the end of the closing brace/line so there is no trailing whitespace left. 
Comment on lines +413 to +417
request, err := c.generateHTTPRequest(ctx, check, connection)
if err != nil {
return results.ErrorMessage(oops.Wrap(err))
}
logger.Infof("Check url is %s", check.URL)
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

Avoid logging full URLs at info level.

Line 417 logs the complete URL; query params often carry tokens. Prefer debug-level logging with query redaction.

🔐 Suggested change
-logger.Infof("Check url is %s", check.URL) +if ctx.IsDebug() { +if u, err := url.Parse(check.URL); err == nil { +u.RawQuery = "" +ctx.Debugf("Check url is %s", u.String()) +} else { +ctx.Debugf("Check url is %s", check.URL) +} +}
🤖 Prompt for AI Agents
In `@checks/http.go` around lines 413 - 417, The code currently logs the full URL via logger.Infof("Check url is %s", check.URL) which may expose sensitive query params; update the logging to use debug level and redact query parameters before logging: in the flow after c.generateHTTPRequest(...) (referencing generateHTTPRequest and check.URL) parse the URL, strip or replace RawQuery with a placeholder (e.g., "<redacted>") or remove it entirely, then call logger.Debugf with the sanitized URL instead of logger.Infof; ensure no full query string is emitted anywhere in the info-level logs. 
Comment on lines +4488 to +4816
kubernetes:
description: Kubernetes connection to use for the HTTP request
properties:
cnrm:
properties:
clusterResource:
type: string
clusterResourceNamespace:
type: string
gke:
properties:
cluster:
type: string
connection:
description: ConnectionName of the connection. It'll be used to populate the endpoint and credentials.
type: string
credentials:
properties:
name:
type: string
value:
type: string
valueFrom:
properties:
configMapKeyRef:
properties:
key:
type: string
name:
type: string
required:
- key
type: object
helmRef:
properties:
key:
description: Key is a JSONPath expression used to fetch the key from the merged JSON.
type: string
name:
type: string
required:
- key
type: object
secretKeyRef:
properties:
key:
type: string
name:
type: string
required:
- key
type: object
serviceAccount:
description: ServiceAccount specifies the service account whose token should be fetched
type: string
type: object
type: object
endpoint:
type: string
project:
type: string
projectID:
type: string
skipTLSVerify:
description: Skip TLS verify
type: boolean
zone:
type: string
required:
- cluster
- projectID
- zone
type: object
required:
- clusterResource
- clusterResourceNamespace
- gke
type: object
connection:
description: Connection name to populate kubeconfig
type: string
eks:
properties:
accessKey:
properties:
name:
type: string
value:
type: string
valueFrom:
properties:
configMapKeyRef:
properties:
key:
type: string
name:
type: string
required:
- key
type: object
helmRef:
properties:
key:
description: Key is a JSONPath expression used to fetch the key from the merged JSON.
type: string
name:
type: string
required:
- key
type: object
secretKeyRef:
properties:
key:
type: string
name:
type: string
required:
- key
type: object
serviceAccount:
description: ServiceAccount specifies the service account whose token should be fetched
type: string
type: object
type: object
assumeRole:
type: string
cluster:
type: string
connection:
description: ConnectionName of the connection. It'll be used to populate the endpoint, accessKey and secretKey.
type: string
endpoint:
type: string
region:
type: string
secretKey:
properties:
name:
type: string
value:
type: string
valueFrom:
properties:
configMapKeyRef:
properties:
key:
type: string
name:
type: string
required:
- key
type: object
helmRef:
properties:
key:
description: Key is a JSONPath expression used to fetch the key from the merged JSON.
type: string
name:
type: string
required:
- key
type: object
secretKeyRef:
properties:
key:
type: string
name:
type: string
required:
- key
type: object
serviceAccount:
description: ServiceAccount specifies the service account whose token should be fetched
type: string
type: object
type: object
sessionToken:
properties:
name:
type: string
value:
type: string
valueFrom:
properties:
configMapKeyRef:
properties:
key:
type: string
name:
type: string
required:
- key
type: object
helmRef:
properties:
key:
description: Key is a JSONPath expression used to fetch the key from the merged JSON.
type: string
name:
type: string
required:
- key
type: object
secretKeyRef:
properties:
key:
type: string
name:
type: string
required:
- key
type: object
serviceAccount:
description: ServiceAccount specifies the service account whose token should be fetched
type: string
type: object
type: object
skipTLSVerify:
description: Skip TLS verify when connecting to aws
type: boolean
required:
- cluster
type: object
gke:
properties:
cluster:
type: string
connection:
description: ConnectionName of the connection. It'll be used to populate the endpoint and credentials.
type: string
credentials:
properties:
name:
type: string
value:
type: string
valueFrom:
properties:
configMapKeyRef:
properties:
key:
type: string
name:
type: string
required:
- key
type: object
helmRef:
properties:
key:
description: Key is a JSONPath expression used to fetch the key from the merged JSON.
type: string
name:
type: string
required:
- key
type: object
secretKeyRef:
properties:
key:
type: string
name:
type: string
required:
- key
type: object
serviceAccount:
description: ServiceAccount specifies the service account whose token should be fetched
type: string
type: object
type: object
endpoint:
type: string
project:
type: string
projectID:
type: string
skipTLSVerify:
description: Skip TLS verify
type: boolean
zone:
type: string
required:
- cluster
- projectID
- zone
type: object
kubeconfig:
properties:
name:
type: string
value:
type: string
valueFrom:
properties:
configMapKeyRef:
properties:
key:
type: string
name:
type: string
required:
- key
type: object
helmRef:
properties:
key:
description: Key is a JSONPath expression used to fetch the key from the merged JSON.
type: string
name:
type: string
required:
- key
type: object
secretKeyRef:
properties:
key:
type: string
name:
type: string
required:
- key
type: object
serviceAccount:
description: ServiceAccount specifies the service account whose token should be fetched
type: string
type: object
type: object
type: object
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

Add serviceAccount to the HTTP Kubernetes connection schema.

The new http.kubernetes block omits serviceAccount, which is present in the Kubernetes connection schema elsewhere in this CRD. If users configure serviceAccount for proxy auth, CRD validation will reject otherwise valid manifests.

🔧 Proposed fix
 kubernetes: description: Kubernetes connection to use for the HTTP request properties: cnrm: properties: clusterResource: type: string @@ kubeconfig: properties: name: type: string value: type: string valueFrom: properties: configMapKeyRef: properties: key: type: string name: type: string required: - key type: object helmRef: properties: key: description: Key is a JSONPath expression used to fetch the key from the merged JSON. name: type: string required: - key type: object secretKeyRef: properties: key: type: string name: type: string required: - key type: object serviceAccount: description: ServiceAccount specifies the service account whose token should be fetched type: string type: object type: object + serviceAccount: + description: ServiceAccount when enabled will allow access to KUBERNETES env vars + type: boolean type: object
🤖 Prompt for AI Agents
In `@config/deploy/manifests.yaml` around lines 4488 - 4816, The http.kubernetes schema is missing the serviceAccount field used for proxy auth; update the kubernetes block in this diff to add a serviceAccount property (name: serviceAccount, description: "ServiceAccount specifies the service account whose token should be fetched", type: string) in the same places other Kubernetes connection schemas include it (e.g., alongside other valueFrom/serviceAccount usages and under kubernetes properties), ensuring the symbol "kubernetes" in the HTTP connection schema now contains "serviceAccount" so CRD validation accepts manifests that supply that token. 
Comment on lines +180 to +214
"GKEConnection": {
"properties": {
"connection": {
"type": "string"
},
"endpoint": {
"type": "string"
},
"credentials": {
"$ref": "#/$defs/EnvVar"
},
"skipTLSVerify": {
"type": "boolean"
},
"project": {
"type": "string"
},
"projectID": {
"type": "string"
},
"zone": {
"type": "string"
},
"cluster": {
"type": "string"
}
},
"additionalProperties": false,
"type": "object",
"required": [
"projectID",
"zone",
"cluster"
]
},
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 | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

rg -n 'project[ID]*' --type=go config/ | grep -i gke | head -100

Repository: flanksource/canary-checker

Length of output: 52


🏁 Script executed:

rg -n 'GKEConnection' --type=go -A 10 | head -150

Repository: flanksource/canary-checker

Length of output: 52


🏁 Script executed:

fd . -name "*.go" | xargs grep -l "GKEConnection\|projectID.*project[^ID]" | head -10

Repository: flanksource/canary-checker

Length of output: 300


🏁 Script executed:

rg 'GKE' --type=go -B2 -A10 | head -150

Repository: flanksource/canary-checker

Length of output: 52


🏁 Script executed:

find . -name "*.go" -type f | xargs grep -l "GKE" | head -20

Repository: flanksource/canary-checker

Length of output: 52


🏁 Script executed:

rg '"project"' config/schemas/ -A2 -B2

Repository: flanksource/canary-checker

Length of output: 12227


🏁 Script executed:

cat config/schemas/health_http.schema.json | grep -n "GKE" -A 35 -B 2

Repository: flanksource/canary-checker

Length of output: 2853


🏁 Script executed:

cat config/schemas/health_http.schema.json | grep -n "GCP\|project" | head -50

Repository: flanksource/canary-checker

Length of output: 146


🏁 Script executed:

find . -name "*.go" -type f -exec grep -l "projectID\|gke\|GKE" {} \; 2>/dev/null | head -20

Repository: flanksource/canary-checker

Length of output: 121


🏁 Script executed:

rg 'type.*GKE' -A 20 --type=go

Repository: flanksource/canary-checker

Length of output: 52


🏁 Script executed:

find . -path "*/pkg/*" -o -path "*/internal/*" | grep -E "\.go$|gke" | head -20

Repository: flanksource/canary-checker

Length of output: 543


🏁 Script executed:

rg 'projectID' --type=go -B3 -A3 | head -100

Repository: flanksource/canary-checker

Length of output: 2136


🏁 Script executed:

rg '"projectID"' . -A2 -B2 | head -100

Repository: flanksource/canary-checker

Length of output: 3710


🏁 Script executed:

cat ./api/context/functions.go | grep -i "gke\|project" -B2 -A2 | head -50

Repository: flanksource/canary-checker

Length of output: 2298


🏁 Script executed:

find . -name "*.go" -type f -exec grep -l "GKE\|gke\|kubernetes.*connection" {} \; | head -10

Repository: flanksource/canary-checker

Length of output: 131


🏁 Script executed:

rg 'zone.*cluster' --type=go -B5 -A5 | head -100

Repository: flanksource/canary-checker

Length of output: 52


🏁 Script executed:

grep -n "GKE\|project\|zone\|cluster" ./checks/http.go | head -100

Repository: flanksource/canary-checker

Length of output: 52


🏁 Script executed:

grep -n "type.*HTTP\|GKE" ./checks/http.go -A 30 | head -200

Repository: flanksource/canary-checker

Length of output: 1109


🏁 Script executed:

rg 'struct|GKE' ./checks/http.go -A 20 | head -150

Repository: flanksource/canary-checker

Length of output: 1488


🏁 Script executed:

find . -name "*.go" -type f | xargs grep -l "type.*GKEConnection" 2>/dev/null

Repository: flanksource/canary-checker

Length of output: 54


🏁 Script executed:

find . -path "*/v1/*" -name "*.go" | xargs grep -l "GKE\|gke" 2>/dev/null | head -10

Repository: flanksource/canary-checker

Length of output: 52


🏁 Script executed:

rg 'type GKEConnection|GKEConnection struct' --type=go -A 20

Repository: flanksource/canary-checker

Length of output: 52


🏁 Script executed:

find . -name "*generate*" -o -name "*gen*" | grep -E "\.(sh|go|yaml|json)$" | head -20

Repository: flanksource/canary-checker

Length of output: 143


🏁 Script executed:

rg 'project.*projectID|projectID.*project' config/schemas/ -B2 -A2 | head -80

Repository: flanksource/canary-checker

Length of output: 52


🏁 Script executed:

cat ./config/schemas/health_http.schema.json | jq '.["$defs"]["GKEConnection"]' 2>/dev/null

Repository: flanksource/canary-checker

Length of output: 609


Add descriptions to clarify the distinction between project and projectID.

Both properties serve intentional, different purposes: projectID is the required unique GCP project identifier needed for API operations, while project is an optional field for the project display name. Add field descriptions to the schema to document this distinction for users.

🤖 Prompt for AI Agents
In `@config/schemas/health_http.schema.json` around lines 180 - 214, Add descriptive documentation entries to the GKEConnection schema to clarify the difference between "project" and "projectID": update the "properties" for GKEConnection to include a "description" for "projectID" stating it is the required unique GCP project identifier used for API operations, and a "description" for "project" stating it is an optional human-readable/display name for the project; keep "projectID" in the "required" array and leave "project" optional. 
Comment on lines +1 to +10
apiVersion: canaries.flanksource.com/v1
kind: Canary
metadata:
name: http-check
spec:
schedule: "@every 30s"
http:
- name: k8s-check
kubernetes: {}
url: http://httpbin.default/status/404
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 | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash # First, find where KubernetesConnection is defined rg -n "type KubernetesConnection" -A 10 -B 2 -g '*.go'

Repository: flanksource/canary-checker

Length of output: 52


🏁 Script executed:

#!/bin/bash # Search for HTTP check handler that processes kubernetes config rg -n "func.*HTTP|type.*HTTP" -g '*.go' | head -30 rg -n "kubernetes\s*\{" -A 5 -B 2 -g '*.go' | head -50

Repository: flanksource/canary-checker

Length of output: 2168


🏁 Script executed:

#!/bin/bash # Look for InClusterConfig or default config handling rg -n "InClusterConfig|BuildConfigFromFlags" -A 5 -B 2 -g '*.go'

Repository: flanksource/canary-checker

Length of output: 52


🏁 Script executed:

#!/bin/bash # Read the HTTPCheck struct definition sed -n '117,200p' api/v1/checks.go

Repository: flanksource/canary-checker

Length of output: 3747


🏁 Script executed:

#!/bin/bash # Read the hydrate function sed -n '190,250p' checks/http.go

Repository: flanksource/canary-checker

Length of output: 1886


🏁 Script executed:

#!/bin/bash # Search for kubernetes field in HTTPCheck or HTTP type rg -n "kubernetes\s*\*|Kubernetes\s*\*" -B 2 -A 2 -g '*.go' api/v1/checks.go

Repository: flanksource/canary-checker

Length of output: 400


🏁 Script executed:

#!/bin/bash # Find KubernetesConnection definition rg -n "type KubernetesConnection" -A 20 -g '*.go'

Repository: flanksource/canary-checker

Length of output: 52


🏁 Script executed:

#!/bin/bash # Find Populate method for KubernetesConnection rg -n "func.*KubernetesConnection.*Populate|func \(.*\*KubernetesConnection.*Populate" -A 15 -g '*.go'

Repository: flanksource/canary-checker

Length of output: 52


🏁 Script executed:

#!/bin/bash # Search for KubernetesConnection in connection package fd -type f -name '*.go' -path '*/connection/*' | head -20

Repository: flanksource/canary-checker

Length of output: 240


🏁 Script executed:

#!/bin/bash # Search for imports of KubernetesConnection rg -n "import|KubernetesConnection" checks/http.go | head -20

Repository: flanksource/canary-checker

Length of output: 141


🏁 Script executed:

#!/bin/bash # Search for connection package imports rg -n "\".*connection\"" -g '*.go' | grep -i import | head -10

Repository: flanksource/canary-checker

Length of output: 52


🏁 Script executed:

#!/bin/bash # Look for Populate method across the codebase rg -n "\.Populate\(" checks/http.go -B 3 -A 3

Repository: flanksource/canary-checker

Length of output: 364


🏁 Script executed:

#!/bin/bash # Read the import section of checks/http.go head -30 checks/http.go

Repository: flanksource/canary-checker

Length of output: 748


🏁 Script executed:

#!/bin/bash # Search for Populate method definitions in the codebase rg -n "func.*Populate.*\{" -A 10 -g '*.go' | head -50

Repository: flanksource/canary-checker

Length of output: 52


🏁 Script executed:

#!/bin/bash # Search for github imports that might contain KubernetesConnection rg -n "github.com.*connection|\".*duty" -g '*.go' | head -20

Repository: flanksource/canary-checker

Length of output: 1533


🏁 Script executed:

#!/bin/bash # Check if KubernetesConnection is defined in the canary-checker codebase rg -n "type KubernetesConnection|KubernetesConnection" api/v1/ -A 5 | head -40

Repository: flanksource/canary-checker

Length of output: 2700


🏁 Script executed:

#!/bin/bash # Look at api/v1/checks.go imports to see where connection comes from head -50 api/v1/checks.go | grep -A 30 "^import"

Repository: flanksource/canary-checker

Length of output: 932


🏁 Script executed:

#!/bin/bash # Search for where KubernetesConnection is used in context rg -n "WithKubernetesConnection" -B 2 -A 5 -g '*.go'

Repository: flanksource/canary-checker

Length of output: 1850


🏁 Script executed:

#!/bin/bash # Search for duty package to understand if it's a dependency grep -n "duty" go.mod | head -20

Repository: flanksource/canary-checker

Length of output: 165


🏁 Script executed:

#!/bin/bash # Look for other usages of KubernetesConnection with empty/minimal configs in fixtures find . -name "*.yaml" -o -name "*.yml" | xargs grep -l "kubernetes:" | head -10

Repository: flanksource/canary-checker

Length of output: 488


🏁 Script executed:

#!/bin/bash # Search for tests that use kubernetes: {} config rg -n "kubernetes\s*:\s*\{\}" -B 3 -A 3 -g '*.yaml' -g '*.yml'

Repository: flanksource/canary-checker

Length of output: 401


🏁 Script executed:

#!/bin/bash # Look at other kubernetes check fixtures to see how they handle kubernetes config cat fixtures/k8s/kubernetes-check-inline-kubeconfig.yaml

Repository: flanksource/canary-checker

Length of output: 1277


🏁 Script executed:

#!/bin/bash # Search for tests that use the http-kubernetes-proxy fixture rg -n "http-kubernetes-proxy" -B 3 -A 3 -g '*.go'

Repository: flanksource/canary-checker

Length of output: 52


🏁 Script executed:

#!/bin/bash # Search for any test or documentation about Populate behavior with empty config rg -n "Populate.*true|kubernetes.*Populate" -B 5 -A 5 -g '*.go' checks/

Repository: flanksource/canary-checker

Length of output: 589


🏁 Script executed:

#!/bin/bash # Search for tests that reference this fixture file rg -n "http-kubernetes-proxy|fixtures/k8s" -g '*_test.go' -l

Repository: flanksource/canary-checker

Length of output: 52


🏁 Script executed:

#!/bin/bash # Look for any test files in the fixtures directory ls -la fixtures/k8s/ | grep -i test

Repository: flanksource/canary-checker

Length of output: 52


🏁 Script executed:

#!/bin/bash # Search for integration or fixture tests find . -name "*test*.go" -o -name "*_test.go" | xargs grep -l "fixtures/k8s" | head -5

Repository: flanksource/canary-checker

Length of output: 52


🏁 Script executed:

#!/bin/bash # Search for how Populate is used in other checks with KubernetesConnection rg -n "Populate\(" checks/ -B 3 -A 3

Repository: flanksource/canary-checker

Length of output: 2678


🏁 Script executed:

#!/bin/bash # Look at the kubernetes resource check to see how it handles KubernetesConnection sed -n '70,100p' checks/kubernetes_resource.go

Repository: flanksource/canary-checker

Length of output: 1011


🏁 Script executed:

#!/bin/bash # Look for documentation or comments about empty kubernetes config rg -n "in-cluster|InClusterConfig|default.*kubernetes" -i -g '*.go' checks/ api/ | head -20

Repository: flanksource/canary-checker

Length of output: 52


🏁 Script executed:

#!/bin/bash # Search for CI/test configurations that might run fixtures find . -name "*.yml" -o -name "*.yaml" | xargs grep -l "http-kubernetes-proxy" 2>/dev/null

Repository: flanksource/canary-checker

Length of output: 54


🏁 Script executed:

#!/bin/bash # Look for test runner configuration ls -la | grep -E "Makefile|\.github|\.gitlab|\.circleci|\.travis"

Repository: flanksource/canary-checker

Length of output: 190


🏁 Script executed:

#!/bin/bash # Check if there's a test or run script that uses fixtures find . -name "run_test.go" -o -name "*_test.go" | xargs grep -l "fixtures" 2>/dev/null | head -3

Repository: flanksource/canary-checker

Length of output: 115


🏁 Script executed:

#!/bin/bash # Look at test/run_test.go to see how fixtures are used head -100 test/run_test.go

Repository: flanksource/canary-checker

Length of output: 2571


🏁 Script executed:

#!/bin/bash # Check the exact filename of the fixture ls -la fixtures/k8s/http-kubernetes-proxy*

Repository: flanksource/canary-checker

Length of output: 157


🏁 Script executed:

#!/bin/bash # Look for any references to http-kubernetes-proxy in test or CI configuration rg -r "http-kubernetes-proxy" .github Makefile 2>/dev/null

Repository: flanksource/canary-checker

Length of output: 866


🏁 Script executed:

#!/bin/bash # Check the Makefile for test targets grep -n "test\|fixtures" Makefile | head -20

Repository: flanksource/canary-checker

Length of output: 637


🏁 Script executed:

#!/bin/bash # Look at the fixture directory to see naming patterns ls -la fixtures/k8s/ | grep -E "pass|fail"

Repository: flanksource/canary-checker

Length of output: 1153


🏁 Script executed:

#!/bin/bash # Look for any documentation or comments about Populate in local codebase rg -n "Populate.*empty|empty.*kubernetes|in.?cluster" -i -g '*.go' -g '*.md'

Repository: flanksource/canary-checker

Length of output: 615


🏁 Script executed:

#!/bin/bash # Check git history or comments about why this fixture doesn't follow naming convention git log --oneline -20 -- fixtures/k8s/http-kubernetes-proxy.yaml 2>/dev/null || echo "No git history available"

Repository: flanksource/canary-checker

Length of output: 93


🏁 Script executed:

#!/bin/bash # Verify the fixture content one more time and check if similar fixtures exist cat fixtures/k8s/http-kubernetes-proxy.yaml ls -la fixtures/k8s/ | grep http

Repository: flanksource/canary-checker

Length of output: 659


🏁 Script executed:

#!/bin/bash # Check if there's a specific reason this fixture isn't named with _pass/_fail git show cf9d3b9:fixtures/k8s/http-kubernetes-proxy.yaml 2>/dev/null || echo "Cannot show commit"

Repository: flanksource/canary-checker

Length of output: 278


🏁 Script executed:

#!/bin/bash # Search for any documentation about kubernetes empty config or in-cluster defaults rg -n "kubernetes.*\{\}|empty.*config|in.?cluster" -i docs/ README* 2>/dev/null | head -20

Repository: flanksource/canary-checker

Length of output: 52


🏁 Script executed:

#!/bin/bash # Look for how other external checks handle empty configs (e.g., AWS, S3) rg -n "AWSConnection\(\)|S3Connection\(\)" -B 2 -A 2 -g '*.go'

Repository: flanksource/canary-checker

Length of output: 52


🏁 Script executed:

#!/bin/bash # Final check: confirm what happens if Populate fails - look at error handling grep -n "Invalidf\|failed to hydrate" checks/http.go

Repository: flanksource/canary-checker

Length of output: 1397


🏁 Script executed:

#!/bin/bash # Check if any HTTP checks actually use kubernetes config in passing tests rg -n "kubernetes:" fixtures/k8s/ -g '*.yaml' -B 2 -A 2

Repository: flanksource/canary-checker

Length of output: 4047


Fixture must follow test naming convention to be executed.

This fixture doesn't end with _pass.yaml, _pass.yml, _fail.yaml, or _fail.yml, so it won't be run by the test suite. Rename it accordingly (e.g., http-kubernetes-proxy_pass.yaml) to ensure the kubernetes: {} configuration is tested.

Additionally, the hydrate function will call Populate(ctx.Context, true) on the non-nil empty KubernetesConnection, which may fail at runtime depending on the duty package's behavior. Confirm that empty config uses in-cluster/default credentials or explicitly document the expected behavior.

🤖 Prompt for AI Agents
In `@fixtures/k8s/http-kubernetes-proxy.yaml` around lines 1 - 10, The fixture file named by metadata.name "http-check" is not following the test naming convention so the suite won't run it; rename the file to end with _pass.yaml (e.g., http-kubernetes-proxy_pass.yaml) so it is executed, and ensure the kubernetes: {} entry (the empty KubernetesConnection) either relies on in-cluster/default credentials or is populated explicitly: update the fixture or test documentation to state that the hydrate path calls Populate(ctx.Context, true) on the KubernetesConnection and that an empty config must resolve to in-cluster/default auth (or provide the required fields in the fixture) so runtime Populate(...) does not fail. 
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: 2

🤖 Fix all issues with AI agents
In `@checks/http.go`: - Around line 95-122: The Kubernetes proxy rewrite in the if check.Kubernetes block currently rebuilds check.URL with fmt.Sprintf and drops parsedURL.RawQuery and proper path escaping; update the logic that computes check.URL (after obtaining parsedURL and port/svc/ns) to construct a url.URL (or otherwise append the original parsedURL.RawQuery and use url.PathEscape/ResolveReference to preserve path escaping) so the final check.URL includes the original parsedURL.RawQuery and an appropriately escaped path; ensure this change is applied where check.URL is set (reference parsedURL, k8s.Config.Host, svc, ns, port) and that the rest.TransportFor/k8s client usage remains unchanged. - Around line 412-416: The current logger.Infof call prints the full check.URL which may include sensitive query/userinfo; instead create a redacted URL string (e.g., parse check.URL with url.Parse, clear u.User and u.RawQuery and/or mask u.Path as needed, then use u.String()) and pass that redacted value to logger.Infof; update the logging at the site where generateHTTPRequest is called (the logger.Infof("Check url is %s", check.URL) line) to log the sanitized URL variable rather than check.URL. 
🧹 Nitpick comments (2)
fixtures/k8s/http-kubernetes-proxy.yaml (1)

6-10: Clarify expected HTTP status in the fixture.
If this fixture is meant to represent a healthy check, the 404 endpoint will fail by default unless responseCodes includes 404. Consider switching to a 200 endpoint or adding responseCodes: [404] if failure is intentional.

config/deploy/manifests.yaml (1)

4487-4816: Consider enforcing or documenting a single Kubernetes provider choice.
The schema allows cnrm, eks, gke, kubeconfig, and connection to be set together, which can lead to ambiguous resolution. Consider documenting precedence or adding a schema constraint (e.g., one-of/validation) to ensure only one provider is selected.

Comment on lines +95 to +122
if check.Kubernetes != nil {
k8s, err := ctx.Kubernetes()
if err != nil {
return nil, fmt.Errorf("failed to instantiate k8s client (%s): %w", check.Kubernetes, err)
}

k8srt, err := rest.TransportFor(k8s.Config)
if err != nil {
return nil, fmt.Errorf("failed to get transport config for k8s: %w", err)
}

client.Use(func(rt nethttp.RoundTripper) nethttp.RoundTripper {
return k8srt
})

parsedURL, err := url.Parse(check.URL)
if err != nil {
return nil, fmt.Errorf("error parsing check url[%s]: %w", check.URL, err)
}

port := lo.CoalesceOrEmpty(parsedURL.Port(), lo.Ternary(parsedURL.Scheme == "https", "443", "80"))
parts := strings.Split(parsedURL.Hostname(), ".")
if len(parts) < 2 {
return nil, fmt.Errorf("check host[%s] is invalid. Use `service.namespace` format", parsedURL.Hostname())
}
svc, ns := parts[0], parts[1]
check.URL = fmt.Sprintf("%s/api/v1/namespaces/%s/services/%s:%s/proxy/%s", k8s.Config.Host, ns, svc, port, strings.TrimPrefix(parsedURL.Path, "/"))
}
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

Preserve query parameters when proxying via the Kubernetes API.

The rewrite drops RawQuery (and path escaping), so any checks that rely on query strings will be sent without them.

🛠️ Proposed fix
-check.URL = fmt.Sprintf("%s/api/v1/namespaces/%s/services/%s:%s/proxy/%s", k8s.Config.Host, ns, svc, port, strings.TrimPrefix(parsedURL.Path, "/")) +proxyPath := strings.TrimPrefix(parsedURL.EscapedPath(), "/") +proxyURL := fmt.Sprintf("%s/api/v1/namespaces/%s/services/%s:%s/proxy/%s", k8s.Config.Host, ns, svc, port, proxyPath) +if parsedURL.RawQuery != "" { +proxyURL = proxyURL + "?" + parsedURL.RawQuery +} +check.URL = proxyURL
📝 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
if check.Kubernetes != nil {
k8s, err := ctx.Kubernetes()
if err != nil {
return nil, fmt.Errorf("failed to instantiate k8s client (%s): %w", check.Kubernetes, err)
}
k8srt, err := rest.TransportFor(k8s.Config)
if err != nil {
return nil, fmt.Errorf("failed to get transport config for k8s: %w", err)
}
client.Use(func(rt nethttp.RoundTripper) nethttp.RoundTripper {
return k8srt
})
parsedURL, err := url.Parse(check.URL)
if err != nil {
return nil, fmt.Errorf("error parsing check url[%s]: %w", check.URL, err)
}
port := lo.CoalesceOrEmpty(parsedURL.Port(), lo.Ternary(parsedURL.Scheme == "https", "443", "80"))
parts := strings.Split(parsedURL.Hostname(), ".")
if len(parts) < 2 {
return nil, fmt.Errorf("check host[%s] is invalid. Use `service.namespace` format", parsedURL.Hostname())
}
svc, ns := parts[0], parts[1]
check.URL = fmt.Sprintf("%s/api/v1/namespaces/%s/services/%s:%s/proxy/%s", k8s.Config.Host, ns, svc, port, strings.TrimPrefix(parsedURL.Path, "/"))
}
if check.Kubernetes != nil {
k8s, err := ctx.Kubernetes()
if err != nil {
return nil, fmt.Errorf("failed to instantiate k8s client (%s): %w", check.Kubernetes, err)
}
k8srt, err := rest.TransportFor(k8s.Config)
if err != nil {
return nil, fmt.Errorf("failed to get transport config for k8s: %w", err)
}
client.Use(func(rt nethttp.RoundTripper) nethttp.RoundTripper {
return k8srt
})
parsedURL, err := url.Parse(check.URL)
if err != nil {
return nil, fmt.Errorf("error parsing check url[%s]: %w", check.URL, err)
}
port := lo.CoalesceOrEmpty(parsedURL.Port(), lo.Ternary(parsedURL.Scheme == "https", "443", "80"))
parts := strings.Split(parsedURL.Hostname(), ".")
if len(parts) < 2 {
return nil, fmt.Errorf("check host[%s] is invalid. Use `service.namespace` format", parsedURL.Hostname())
}
svc, ns := parts[0], parts[1]
proxyPath := strings.TrimPrefix(parsedURL.EscapedPath(), "/")
proxyURL := fmt.Sprintf("%s/api/v1/namespaces/%s/services/%s:%s/proxy/%s", k8s.Config.Host, ns, svc, port, proxyPath)
if parsedURL.RawQuery != "" {
proxyURL = proxyURL + "?" + parsedURL.RawQuery
}
check.URL = proxyURL
}
🤖 Prompt for AI Agents
In `@checks/http.go` around lines 95 - 122, The Kubernetes proxy rewrite in the if check.Kubernetes block currently rebuilds check.URL with fmt.Sprintf and drops parsedURL.RawQuery and proper path escaping; update the logic that computes check.URL (after obtaining parsedURL and port/svc/ns) to construct a url.URL (or otherwise append the original parsedURL.RawQuery and use url.PathEscape/ResolveReference to preserve path escaping) so the final check.URL includes the original parsedURL.RawQuery and an appropriately escaped path; ensure this change is applied where check.URL is set (reference parsedURL, k8s.Config.Host, svc, ns, port) and that the rest.TransportFor/k8s client usage remains unchanged. 
Comment on lines +412 to +416
request, err := c.generateHTTPRequest(ctx, check, connection)
if err != nil {
return results.ErrorMessage(oops.Wrap(err))
}
logger.Infof("Check url is %s", check.URL)
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

Avoid logging full URLs with query parameters.

URLs can contain secrets (query tokens, paths). Logging them at info level risks leakage.

🛡️ Proposed fix (redact userinfo/query before logging)
-logger.Infof("Check url is %s", check.URL) +if parsed, err := url.Parse(check.URL); err == nil { +parsed.User = nil +parsed.RawQuery = "" +logger.Infof("Check url is %s", parsed.String()) +} else { +logger.Infof("Check url is %s", check.URL) +}
🤖 Prompt for AI Agents
In `@checks/http.go` around lines 412 - 416, The current logger.Infof call prints the full check.URL which may include sensitive query/userinfo; instead create a redacted URL string (e.g., parse check.URL with url.Parse, clear u.User and u.RawQuery and/or mask u.Path as needed, then use u.String()) and pass that redacted value to logger.Infof; update the logging at the site where generateHTTPRequest is called (the logger.Infof("Check url is %s", check.URL) line) to log the sanitized URL variable rather than check.URL. 
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

1 participant