-
Notifications
You must be signed in to change notification settings - Fork 28
feat: Dex integration testing #1004
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
Signed-off-by: Tapas Sharma <tapas@platform9.com>
| if (isAuthenticated) { | ||
| // User is already authenticated, redirect to dashboard | ||
| const returnUrl = new URLSearchParams(window.location.search).get('rd') || '/dashboard/migrations'; | ||
| window.location.href = returnUrl; |
Check warning
Code scanning / CodeQL
Client-side URL redirect Medium
user-provided value
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 2 months ago
The best way to fix this vulnerability is to whitelist return URLs so that only expected, safe paths on your own site can be used as redirect destinations. Specifically, we should check that returnUrl is a pathname within the application's domain (e.g., /dashboard/migrations or any other internal route) and never allow an absolute URL (such as https://evil.com). To do this, we can check if the value of returnUrl starts with a / and does not contain suspicious patterns (e.g., double slashes, schema). If the validation fails, fallback to a safe default route.
You need to adjust both:
- The redirect in
window.location.href = returnUrl;insidecheckAuthStatus - The use of
authService.initiateLogin(returnUrl);inhandleLogin(if it also relies on a redirect target: ideally, sanitize the param in both places)
Add a utility function such as getSafeRedirectUrl, and update both usages to use this.
All changes are within ui/src/pages/auth/LoginPage.tsx.
-
Copy modified lines R31-R48 -
Copy modified lines R62-R63 -
Copy modified lines R76-R77
| @@ -28,6 +28,24 @@ | ||
| boxShadow: '0 8px 32px rgba(0, 0, 0, 0.1)', | ||
| })); | ||
|
|
||
| // Whitelist internal routes for safe redirection | ||
| function getSafeRedirectUrl(redirectUrl: string | null): string { | ||
| // Only allow relative paths starting with / | ||
| if ( | ||
| redirectUrl && | ||
| redirectUrl.startsWith('/') && | ||
| // Prevent // (protocol-like) and suspicious substrings | ||
| !redirectUrl.startsWith('//') && | ||
| !redirectUrl.includes('://') && | ||
| !redirectUrl.includes('\r') && | ||
| !redirectUrl.includes('\n') | ||
| ) { | ||
| return redirectUrl; | ||
| } | ||
| // Default safe route | ||
| return '/dashboard/migrations'; | ||
| } | ||
|
|
||
| const LoginPage = () => { | ||
| const [isChecking, setIsChecking] = useState(true); | ||
| const [error, setError] = useState<string | null>(null); | ||
| @@ -41,7 +59,8 @@ | ||
| const isAuthenticated = await authService.checkAuth(); | ||
| if (isAuthenticated) { | ||
| // User is already authenticated, redirect to dashboard | ||
| const returnUrl = new URLSearchParams(window.location.search).get('rd') || '/dashboard/migrations'; | ||
| const unsafeReturnUrl = new URLSearchParams(window.location.search).get('rd'); | ||
| const returnUrl = getSafeRedirectUrl(unsafeReturnUrl); | ||
| window.location.href = returnUrl; | ||
| } else { | ||
| setIsChecking(false); | ||
| @@ -54,7 +73,8 @@ | ||
|
|
||
| const handleLogin = () => { | ||
| try { | ||
| const returnUrl = new URLSearchParams(window.location.search).get('rd') || '/dashboard/migrations'; | ||
| const unsafeReturnUrl = new URLSearchParams(window.location.search).get('rd'); | ||
| const returnUrl = getSafeRedirectUrl(unsafeReturnUrl); | ||
| authService.initiateLogin(returnUrl); | ||
| } catch (err: any) { | ||
| setError(err.message || 'Failed to initiate login'); |
| if (isAuthenticated) { | ||
| // User is already authenticated, redirect to dashboard | ||
| const returnUrl = new URLSearchParams(window.location.search).get('rd') || '/dashboard/migrations'; | ||
| window.location.href = returnUrl; |
Check failure
Code scanning / CodeQL
Client-side cross-site scripting High
user-provided value
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 2 months ago
The best approach is to only allow the rd ("return destination") parameter to navigate to URLs within the application's allowed paths. This avoids both open redirects and XSS vectors. Specifically:
- Only accept
rdvalues that (a) are relative paths beginning with/, and (b) do not contain protocol specifiers (:), as those could be abused. - If
rdis not present, or does not match allowed patterns, default to the safe in-app path (/dashboard/migrations).
You'll need to change both usages of returnUrl (in checkAuthStatus and handleLogin). Any line like window.location.href = returnUrl should only occur with a validated, safe path.
Implementation:
- You can introduce a small utility function, e.g.,
getSafeReturnUrl, within this file, before the component, to encapsulate the logic for extracting and validating the return URL. - That function will parse the query param and only return the path if it is application-local and safe.
- No new dependencies are needed, as the required operations are simple.
-
Copy modified line R43 -
Copy modified line R56
| @@ -12,7 +12,6 @@ | ||
| import { styled } from '@mui/material/styles'; | ||
| import { authService } from '../../api/auth/authService'; | ||
|
|
||
| const LoginContainer = styled(Container)(({ theme }) => ({ | ||
| minHeight: '100vh', | ||
| display: 'flex', | ||
| alignItems: 'center', | ||
| @@ -41,7 +40,7 @@ | ||
| const isAuthenticated = await authService.checkAuth(); | ||
| if (isAuthenticated) { | ||
| // User is already authenticated, redirect to dashboard | ||
| const returnUrl = new URLSearchParams(window.location.search).get('rd') || '/dashboard/migrations'; | ||
| const returnUrl = getSafeReturnUrl(); | ||
| window.location.href = returnUrl; | ||
| } else { | ||
| setIsChecking(false); | ||
| @@ -54,7 +53,7 @@ | ||
|
|
||
| const handleLogin = () => { | ||
| try { | ||
| const returnUrl = new URLSearchParams(window.location.search).get('rd') || '/dashboard/migrations'; | ||
| const returnUrl = getSafeReturnUrl(); | ||
| authService.initiateLogin(returnUrl); | ||
| } catch (err: any) { | ||
| setError(err.message || 'Failed to initiate login'); |
- Removed unused styled Logo component causing TypeScript build error - Redirect already correctly goes to /dashboard/migrations (not /onboarding) Signed-off-by: Tapas Sharma <tapas@platform9.com>
| password := os.Args[1] | ||
|
|
||
| // Generate bcrypt hash using Go's bcrypt library (same as Dex) | ||
| fmt.Printf("Generating bcrypt hash for password: %s\n", password) |
Check failure
Code scanning / CodeQL
Clear-text logging of sensitive information High
Sensitive data returned by an access to password
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 2 months ago
The fix is to ensure that sensitive information (the plaintext password) is not logged. The statement at line 26, fmt.Printf("Generating bcrypt hash for password: %s\n", password), should be changed to avoid printing the password. We can simply change it to print a generic message like "Generating bcrypt hash for password...", without including the actual password value. No further changes are necessary, as the rest of the script does not log the password itself. This fix should be made in the file scripts/update-dex-password.go, replacing just line 26. No new imports or methods are required.
-
Copy modified line R26
| @@ -23,7 +23,7 @@ | ||
| password := os.Args[1] | ||
|
|
||
| // Generate bcrypt hash using Go's bcrypt library (same as Dex) | ||
| fmt.Printf("Generating bcrypt hash for password: %s\n", password) | ||
| fmt.Println("Generating bcrypt hash for password...") | ||
| hash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost) | ||
| if err != nil { | ||
| log.Fatalf("Failed to generate bcrypt hash: %v", err) |
| fmt.Printf(" kubectl delete pod -n %s -l app=dex\n", namespace) | ||
| fmt.Printf("\nNew credentials:\n") | ||
| fmt.Printf(" Email: admin@vjailbreak.local\n") | ||
| fmt.Printf(" Password: %s\n", password) |
Check failure
Code scanning / CodeQL
Clear-text logging of sensitive information High
Sensitive data returned by an access to password
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 2 months ago
To fix the clear-text logging of the sensitive password, we should remove or obfuscate the logging statement that prints the plaintext password to the console on line 112. The best practice is to omit printing any sensitive information, or at minimum, to mask (obfuscate) it—e.g., "********"—so the value cannot be retrieved from the logs.
- Edit scripts/update-dex-password.go: Remove or redact the line that prints the password (
fmt.Printf(" Password: %s\n", password)). - Optionally, change the message to indicate that the password has been set, but do not display its value.
- No new methods or imports are needed.
-
Copy modified line R112
| @@ -109,7 +109,7 @@ | ||
| fmt.Printf(" kubectl delete pod -n %s -l app=dex\n", namespace) | ||
| fmt.Printf("\nNew credentials:\n") | ||
| fmt.Printf(" Email: admin@vjailbreak.local\n") | ||
| fmt.Printf(" Password: %s\n", password) | ||
| fmt.Printf(" Password: [redacted, see input]\n") | ||
| } | ||
|
|
||
| func splitLines(s string) []string { |
Signed-off-by: Tapas Sharma <tapas@platform9.com>
Signed-off-by: Tapas Sharma <tapas@platform9.com>
Signed-off-by: Tapas Sharma <tapas@platform9.com>
Signed-off-by: Tapas Sharma <tapas@platform9.com>
Signed-off-by: Tapas Sharma <tapas@platform9.com>
Signed-off-by: Tapas Sharma <tapas@platform9.com>
Signed-off-by: Tapas Sharma <tapas@platform9.com>
Signed-off-by: Tapas Sharma <tapas@platform9.com>
The UI was sending 'Authorization: Bearer null' because it tried to read tokens from localStorage which was never populated. OAuth2 proxy handles authentication via cookies (_oauth2_proxy), so we don't need to manually manage Bearer tokens. Changes: - Simplified createAuthenticatedClient() to only set withCredentials - Removed manual Authorization header that was always null - Backend nginx/oauth2-proxy forwards auth headers automatically Fixes: 502 Bad Gateway errors when creating users from UI Signed-off-by: Tapas Sharma <tapas@platform9.com>
- Added Create, Update, Delete tests for local users - Tests use healthcheck@vjailbreak.local test user - Added Body field to APIEndpoint struct for POST/PUT payloads - Added Skip field to allow disabling individual tests - Updated documentation with CRUD test details Total tests increased from 15 to 18 endpoints Signed-off-by: Tapas Sharma <tapas@platform9.com>
Signed-off-by: Tapas Sharma <tapas@platform9.com>
- Update Dex issuer to https://HOST_IP/dex - Update OAuth2 Proxy to use HTTPS URLs for external endpoints - Enable secure cookies (cookie_secure = true) - Add SSL insecure skip verify for self-signed certs - Configure ingress for API endpoints (routing to K8s API) - Update UI ingress paths to avoid catching /api and /apis - Add priority annotations to ingresses Working: - Dex authentication with HTTPS - OAuth2 Proxy with secure cookies - UI login redirects to /dashboard - SSL termination at ingress Known Issue: - API endpoints (/api, /apis) return 401 through ingress - Documented in KNOWN_ISSUES.md for investigation - Likely requires UI backend to proxy API requests Signed-off-by: Tapas Sharma <tapas@platform9.com>
What this PR does / why we need it
Which issue(s) this PR fixes
(optional, in
fixes #<issue number>(, fixes #<issue_number>, ...)format, will close the issue(s) when PR gets merged)fixes #
Special notes for your reviewer
Testing done
please add testing details (logs, screenshots, etc.)