Skip to content

feat: Add validation for provided URL when running caib login#269

Merged
bennyz merged 1 commit into
centos-automotive-suite:mainfrom
bkhizgiy:validation
May 5, 2026
Merged

feat: Add validation for provided URL when running caib login#269
bennyz merged 1 commit into
centos-automotive-suite:mainfrom
bkhizgiy:validation

Conversation

@bkhizgiy

@bkhizgiy bkhizgiy commented Apr 30, 2026

Copy link
Copy Markdown
Contributor

Summary

Related Issues

Type of Change

  • Bug fix
  • New feature
  • Breaking change
  • Documentation update
  • CI/CD improvement
  • Refactoring

Testing

  • Unit tests pass (make test)
  • Linter passes (make lint)
  • Manifests are up to date (make manifests generate)
  • Tested on OpenShift cluster (if applicable) + locally with caib

releted to: #138

Summary by CodeRabbit

  • Bug Fixes

    • Login now validates and normalizes server URLs (trims input, auto-adds https when needed, rejects invalid formats) and verifies server connectivity before saving configuration.
  • Tests

    • Added tests covering URL normalization, server reachability behavior, and config persistence to ensure correct save/no-save behavior based on connectivity.

@coderabbitai

coderabbitai Bot commented Apr 30, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

The login command centralizes server URL handling by adding normalizeServerURL for strict normalization/validation and checkServerReachable to perform a timeout-bounded GET to <server>/v1/healthz. runLogin now uses these helpers and verifies reachability before persisting the server URL. Tests for normalization, reachability, and config persistence were added.

Changes

Login Server URL Validation and Reachability

Layer / File(s) Summary
Data Shape / Input Normalization
cmd/caib/login.go
Adds normalizeServerURL that trims input, auto-prefixes https:// when no scheme present, parses the URL, and rejects missing/invalid scheme or host, credentials, query params, fragments, non-root paths, and unsupported schemes.
Core Behavior
cmd/caib/login.go
Adds checkServerReachable creating an HTTP client with a 10s timeout (optional TLS skip) and GETs <server>/v1/healthz; treats any network/connection failure as error but ignores HTTP status codes.
Wiring / Command Flow
cmd/caib/login.go
runLogin now calls normalizeServerURL(args[0]), runs checkServerReachable before saving, and wraps connection failures with cannot connect to server <server>: <err>.
Tests
cmd/caib/login_test.go
Adds TestNormalizeServerURL (validation cases), TestCheckServerReachable_* (unreachable, reachable, non-OK status), and TestLogin* (ensures config not saved when unreachable; saved when reachable via httptest).
sequenceDiagram
    participant CLI as rgba(46,125,50,0.5) CLI (runLogin)
    participant Normalizer as rgba(30,136,229,0.5) normalizeServerURL
    participant HTTP as rgba(255,138,101,0.5) checkServerReachable (HTTP)
    participant Server as rgba(123,31,162,0.5) Server /v1/healthz
    participant FS as rgba(3,169,244,0.5) Config File

    CLI->>Normalizer: input server arg
    Normalizer-->>CLI: normalized canonical URL or error
    CLI->>HTTP: GET <server>/v1/healthz (10s timeout)
    HTTP->>Server: request
    Server-->>HTTP: response (any status)
    HTTP-->>CLI: success or network error
    alt reachable
      CLI->>FS: persist server URL
      FS-->>CLI: write success
    else unreachable
      CLI-->>CLI: abort, return error
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Suggested reviewers

  • bennyz

Poem

🐇 I sniff the bytes and scrub the space,
I add https when schemes misplaced.
I tap /v1/healthz with careful grace,
Then tuck the URL in its place. ✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the primary change: adding validation for the provided URL in the caib login command, which is the main focus of the changeset.
Docstring Coverage ✅ Passed Docstring coverage is 88.89% 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

Tip

💬 Introducing Slack Agent: The best way for teams to turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


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
Review rate limit: 0/1 reviews remaining, refill in 60 minutes.

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

@bkhizgiy

bkhizgiy commented Apr 30, 2026

Copy link
Copy Markdown
Contributor Author

@ambient-code please review

@ambient-code ambient-code Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Good refactoring — extracting normalizeServerURL into a standalone function makes the validation logic testable and the tests are well-structured. Two things worth discussing before merging:

Behavioral change beyond validation: This PR also reorders the login flow so that auth.GetTokenWithReauth runs before config.SaveServerURL, and changes auth failure from a warning (fmt.Printf) to a fatal error (handleErroros.Exit(1)). That means users who could previously save a URL despite auth issues (e.g., kubeconfig/token users, users not yet on VPN, expired OIDC) can no longer do so. The issue (#138) asks for syntactic validation before saving — the reachability gate is a separate concern and might warrant discussion. A lighter option would be a simple health check against /v1/healthz (like DeriveServerFromJumpstarter already does) instead of tying reachability to the full auth flow.

simulateUnreachableAuth test coupling: The helper knows to hit /v1/auth/config, which is an internal detail of auth.GetTokenWithReauth. If that endpoint changes, this test will silently stop testing the right thing. Consider noting the coupling in a comment, or testing via runLogin directly (though that requires handling the os.Exit).

Comment thread cmd/caib/login.go
Comment thread cmd/caib/login_test.go Outdated
@bkhizgiy

bkhizgiy commented May 3, 2026

Copy link
Copy Markdown
Contributor Author

@ambient-code please review

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (2)
cmd/caib/login.go (2)

56-58: 💤 Low value

Missing MinVersion in tls.Config flagged by static analysis.

Without an explicit MinVersion, Go's TLS client defaults to TLS 1.2, so TLS 1.0/1.1 connections are theoretically possible against legacy servers. Since this block only executes when the user has already opted into --insecure-skip-tls-verify, the practical impact is minimal, but setting an explicit floor keeps the security posture consistent.

🔒 Proposed fix
-		transport.TLSClientConfig = &tls.Config{InsecureSkipVerify: true} //nolint:gosec
+		transport.TLSClientConfig = &tls.Config{InsecureSkipVerify: true, MinVersion: tls.VersionTLS12} //nolint:gosec
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cmd/caib/login.go` around lines 56 - 58, When building the TLS config in the
insecure-skip branch, explicitly set a minimum TLS version to avoid allowing
legacy TLS 1.0/1.1; update the transport.TLSClientConfig assignment (the
tls.Config created when insecureSkipTLS is true) to include MinVersion:
tls.VersionTLS12 so the config becomes &tls.Config{InsecureSkipVerify: true,
MinVersion: tls.VersionTLS12}, keeping the existing nolint if needed.

65-65: 💤 Low value

Redundant error context when wrapping at the call site creates a confusing double message.

checkServerReachable returns "server is not reachable: <cause>" (line 65), which is then wrapped at line 89 as "cannot connect to server %q: server is not reachable: <cause>". Both phrases say the same thing.

Either drop the prefix in checkServerReachable and let runLogin own the full message, or keep the inner prefix and don't double-wrap at line 89.

✂️ Option A – remove the prefix inside `checkServerReachable` (line 65)
-	return fmt.Errorf("server is not reachable: %w", err)
+	return err
✂️ Option B – don't wrap at `runLogin` (line 89)
-		handleError(fmt.Errorf("cannot connect to server %q: %w", server, err))
+		handleError(err)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cmd/caib/login.go` at line 65, The inner function checkServerReachable
currently returns an error prefixed with "server is not reachable: …", which
gets double-wrapped by runLogin; change checkServerReachable to return the
underlying error (remove the "server is not reachable: " prefix and just return
err or wrap only the cause) so that runLogin's fmt.Errorf("cannot connect to
server %q: %w", host, err) is the single place providing the human-facing
context; update checkServerReachable's return at the spot that currently does
return fmt.Errorf("server is not reachable: %w", err) to instead return the raw
err (or fmt.Errorf("%w", err)) and leave runLogin's wrapping as-is.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@cmd/caib/login_test.go`:
- Around line 109-126: The tests TestLoginDoesNotSaveConfigWhenServerUnreachable
and TestLoginSavesConfigWhenServerReachable are only calling
checkServerReachable and/or config.SaveServerURL directly so they don't exercise
runLogin's ordering/guard; either update the tests to invoke runLogin (so the
guard that calls checkServerReachable before config.SaveServerURL is validated)
by making handleError injectable or catching os.Exit via recover/test helper, or
instead rename the tests to reflect they only validate
checkServerReachable/config.SaveServerURL in isolation (e.g.,
TestCheckServerReachable_DoesNotWriteConfig and TestSaveServerURL_PersistsURL)
and add a new integration-style test that calls runLogin to assert the
conditional behavior. Ensure references to runLogin, checkServerReachable,
config.SaveServerURL, and handleError are used to locate the code to change.

In `@cmd/caib/login.go`:
- Around line 25-34: The input handling currently unconditionally prepends
"https://" to server, which makes explicit-scheme inputs like "ftp://..." be
mangled and makes the later parsedURL.Scheme check unreachable; change the logic
in the login URL parsing so you first detect whether the original raw input
includes an explicit scheme (e.g., contains "://", or use url.Parse on raw and
check parsed.Scheme), and only if there is no explicit scheme treat it as a bare
hostname and prepend "https://"; then call url.Parse on the normalized server,
validate parsedURL.Scheme against allowed schemes ("http"/"https") and produce
an accurate error for unsupported explicit schemes, using the same variables
(raw, server, parsedURL, parsedURL.Scheme) and url.Parse to locate and update
the code.

---

Nitpick comments:
In `@cmd/caib/login.go`:
- Around line 56-58: When building the TLS config in the insecure-skip branch,
explicitly set a minimum TLS version to avoid allowing legacy TLS 1.0/1.1;
update the transport.TLSClientConfig assignment (the tls.Config created when
insecureSkipTLS is true) to include MinVersion: tls.VersionTLS12 so the config
becomes &tls.Config{InsecureSkipVerify: true, MinVersion: tls.VersionTLS12},
keeping the existing nolint if needed.
- Line 65: The inner function checkServerReachable currently returns an error
prefixed with "server is not reachable: …", which gets double-wrapped by
runLogin; change checkServerReachable to return the underlying error (remove the
"server is not reachable: " prefix and just return err or wrap only the cause)
so that runLogin's fmt.Errorf("cannot connect to server %q: %w", host, err) is
the single place providing the human-facing context; update
checkServerReachable's return at the spot that currently does return
fmt.Errorf("server is not reachable: %w", err) to instead return the raw err (or
fmt.Errorf("%w", err)) and leave runLogin's wrapping as-is.
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: a0c5e4b1-4bed-4e2c-9384-a2fe21011ea5

📥 Commits

Reviewing files that changed from the base of the PR and between 25ea0ab and 64d5714.

📒 Files selected for processing (2)
  • cmd/caib/login.go
  • cmd/caib/login_test.go

Comment thread cmd/caib/login_test.go
Comment on lines +109 to +126
func TestLoginDoesNotSaveConfigWhenServerUnreachable(t *testing.T) {
tmpDir := t.TempDir()
t.Setenv("XDG_CONFIG_HOME", tmpDir)

cfgPath := filepath.Join(tmpDir, "caib", "cli.json")
if _, err := os.Stat(cfgPath); !os.IsNotExist(err) {
t.Fatal("config file should not exist before test")
}

// checkServerReachable fails → SaveServerURL must not be called.
if err := checkServerReachable("http://127.0.0.1:1", false); err == nil {
t.Fatal("expected reachability error, got nil")
}

if _, err := os.Stat(cfgPath); !os.IsNotExist(err) {
t.Error("config file must not be written when the server is unreachable")
}
}

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 | ⚡ Quick win

TestLoginDoesNotSaveConfigWhenServerUnreachable is trivially true and doesn't test the runLogin guard.

The test calls checkServerReachable (which errors), then asserts the config file doesn't exist — but config.SaveServerURL is never called in the test body, so the second assertion is vacuously true regardless of any implementation change. The critical integration behaviour described in the PR ("runLogin guards SaveServerURL behind checkServerReachable") is not exercised at all. If the order in runLogin were reversed (save first, then check reachability), this test would still pass.

The same applies to TestLoginSavesConfigWhenServerReachable (lines 130–160): it calls checkServerReachable and config.SaveServerURL in sequence but never invokes runLogin, so it can't catch ordering or conditional bugs in the actual command flow.

Consider either:

  • Refactoring both tests to call runLogin directly (possibly extracting handleError as an injectable hook, or using recover to catch the os.Exit), or
  • Renaming them to accurately reflect what is being tested (e.g. TestCheckServerReachable_DoesNotWriteConfig, TestSaveServerURL_CorrectlyPersistsURL) to avoid misleading future contributors.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cmd/caib/login_test.go` around lines 109 - 126, The tests
TestLoginDoesNotSaveConfigWhenServerUnreachable and
TestLoginSavesConfigWhenServerReachable are only calling checkServerReachable
and/or config.SaveServerURL directly so they don't exercise runLogin's
ordering/guard; either update the tests to invoke runLogin (so the guard that
calls checkServerReachable before config.SaveServerURL is validated) by making
handleError injectable or catching os.Exit via recover/test helper, or instead
rename the tests to reflect they only validate
checkServerReachable/config.SaveServerURL in isolation (e.g.,
TestCheckServerReachable_DoesNotWriteConfig and TestSaveServerURL_PersistsURL)
and add a new integration-style test that calls runLogin to assert the
conditional behavior. Ensure references to runLogin, checkServerReachable,
config.SaveServerURL, and handleError are used to locate the code to change.

Comment thread cmd/caib/login.go Outdated

@ambient-code ambient-code Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Re-review — Thanks for updating the PR. The main concern from the first review (coupling reachability to the full auth flow) has been addressed well — checkServerReachable hitting /v1/healthz is much cleaner.

Two remaining issues, one correctness and one cosmetic:

Comment thread cmd/caib/login.go Outdated
Comment thread cmd/caib/login.go Outdated
Assisted-by: claude-4.6-sonnet
Signed-off-by: Bella Khizgiyaev <bkhizgiy@redhat.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
cmd/caib/login.go (1)

64-68: 💤 Low value

Consider using defer for response body cleanup.

While the current approach works, using defer is more idiomatic and ensures cleanup even if the function is later modified to add additional logic paths.

♻️ Proposed refactor
 	resp, err := client.Get(strings.TrimSuffix(serverURL, "/") + "/v1/healthz")
 	if err != nil {
 		return err
 	}
-	_ = resp.Body.Close()
+	defer resp.Body.Close()
 	return nil
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cmd/caib/login.go` around lines 64 - 68, Replace the immediate
resp.Body.Close() call with a deferred close to ensure the response body is
always cleaned up even if future logic returns early: after the client.Get(...)
call and its error check (the block using client.Get and if err != nil { return
err }), add defer resp.Body.Close() instead of the current "_ =
resp.Body.Close()"; this uses resp from the Get call and guarantees cleanup of
resp.Body.Close.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@cmd/caib/login.go`:
- Around line 56-59: The TLS config created in the login flow (the transport
variable and tls.Config used when insecureSkipTLS is true) must also set a
minimum TLS version; update the tls.Config initialization in the block that sets
InsecureSkipVerify to include MinVersion: tls.VersionTLS12 (or higher) so the
config enforces a minimum protocol even when InsecureSkipVerify is enabled;
modify the tls.Config in the transport assignment where
tls.Config{InsecureSkipVerify: true} is created to add MinVersion:
tls.VersionTLS12.

---

Nitpick comments:
In `@cmd/caib/login.go`:
- Around line 64-68: Replace the immediate resp.Body.Close() call with a
deferred close to ensure the response body is always cleaned up even if future
logic returns early: after the client.Get(...) call and its error check (the
block using client.Get and if err != nil { return err }), add defer
resp.Body.Close() instead of the current "_ = resp.Body.Close()"; this uses resp
from the Get call and guarantees cleanup of resp.Body.Close.
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 0d374862-6bca-42b0-9330-00e26e009dad

📥 Commits

Reviewing files that changed from the base of the PR and between 64d5714 and af363f1.

📒 Files selected for processing (2)
  • cmd/caib/login.go
  • cmd/caib/login_test.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • cmd/caib/login_test.go

Comment thread cmd/caib/login.go
@bkhizgiy bkhizgiy requested a review from bennyz May 4, 2026 06:02
@bennyz bennyz merged commit d399cb2 into centos-automotive-suite:main May 5, 2026
4 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