Skip to content

Conversation

@drduker
Copy link

@drduker drduker commented Nov 28, 2025

This implementation adds Google Cloud Platform OAuth 2.0 authentication to Headlamp, providing a replacement for the deprecated Identity Service for GKE. Users can now authenticate with their Google Cloud accounts when accessing Headlamp deployed on GKE clusters.

Backend Changes

  • Add GCP OAuth configuration system (backend/pkg/config/config.go)
  • Implement GCP authenticator with PKCE support (backend/pkg/gcp/auth.go)
  • Add OAuth HTTP handlers for login, callback, and feature detection (backend/pkg/auth/gcp.go)
  • Register OAuth routes: /gcp-auth/login, /gcp-auth/callback, /gcp-auth/enabled
  • Support for token refresh and caching
  • Environment variable configuration for OAuth credentials

Frontend Changes

  • Add GCPLoginButton component for Google sign-in (frontend/src/components/cluster/GCPLoginButton.tsx)
  • Implement GKE cluster detection (frontend/src/lib/k8s/gke.ts)
  • Integrate OAuth flow into AuthChooser component
  • Add backend feature detection to conditionally show GCP login option
  • Disable auto-redirect to token page to allow users to see authentication options

Key Features

  • RFC 7636 compliant PKCE (Proof Key for Code Exchange) implementation
  • Base64url encoding without padding for code challenge
  • OAuth state parameter for CSRF protection
  • Automatic token refresh handling
  • GKE cluster detection based on server URL patterns
  • Comprehensive error handling and logging

Documentation

  • Comprehensive setup guide (docs/GCP_OAUTH_SETUP.md)
  • Implementation status and troubleshooting (docs/GCP_OAUTH_IMPLEMENTATION_STATUS.md)
  • Deployment instructions for GKE
  • RBAC configuration examples

Testing

  • Unit tests for GCP authenticator functions
  • Unit tests for GKE cluster detection
  • Component tests for GCPLoginButton

This implementation has been tested and verified to work with GKE clusters, including successful OAuth flow initiation and PKCE code challenge generation.

Summary

This PR adds/fixes [feature/bug] by [brief description of what the change does].

Related Issue

Fixes #ISSUE_NUMBER

Changes

  • Added/Updated [component/file/logic]
  • Fixed [bug/issue/typo]
  • Refactored [code/module] for clarity/performance

Steps to Test

  1. [Step 1: e.g., Navigate to ...]
  2. [Step 2: Click on ...]
  3. [Step 3: Observe behavior or check logs/output]

Screenshots (if applicable)

Notes for the Reviewer

  • [e.g., This touches the i18n layer, so please check language consistency.]

@linux-foundation-easycla
Copy link

linux-foundation-easycla bot commented Nov 28, 2025

CLA Not Signed

@k8s-ci-robot
Copy link
Contributor

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by: drduker
Once this PR has been reviewed and has the lgtm label, please assign sniok for approval. For more information see the Code Review Process.

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

Needs approval from an approver in each of these files:

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

@k8s-ci-robot
Copy link
Contributor

Welcome @drduker!

It looks like this is your first PR to kubernetes-sigs/headlamp 🎉. Please refer to our pull request process documentation to help your PR have a smooth ride to approval.

You will be prompted by a bot to use commands during the review process. Do not be afraid to follow the prompts! It is okay to experiment. Here is the bot commands documentation.

You can also check if kubernetes-sigs/headlamp has its own contribution guidelines.

You may want to refer to our testing guide if you run into trouble with your tests not passing.

If you are having difficulty getting your pull request seen, please follow the recommended escalation practices. Also, for tips and tricks in the contribution process you may want to read the Kubernetes contributor cheat sheet. We want to make sure your contribution gets all the attention it needs!

Thank you, and welcome to Kubernetes. 😃

@k8s-ci-robot k8s-ci-robot added cncf-cla: no Indicates the PR's author has not signed the CNCF CLA. size/XXL Denotes a PR that changes 1000+ lines, ignoring generated files. labels Nov 28, 2025
This implementation adds GCP OAuth 2.0 authentication to Headlamp for
Google Kubernetes Engine (GKE) deployments, replacing the deprecated
Identity Service for GKE.

Features:
- OAuth 2.0 authentication flow with Google
- PKCE (Proof Key for Code Exchange) support for enhanced security
- Automatic token refresh mechanism
- GKE cluster detection and automatic OAuth enablement
- "Sign in with Google" button in authentication chooser
- Comprehensive GKE deployment documentation with RBAC examples

Backend Changes:
- New GCP authenticator package (backend/pkg/gcp/auth.go)
- OAuth route handlers (/gcp-auth/login, /gcp-auth/callback, /gcp-auth/refresh)
- Configuration support via environment variables
- Token caching and refresh logic

Frontend Changes:
- GCPLoginButton component for Google sign-in
- Modified auth chooser to show OAuth option
- GKE cluster detection utilities

Documentation:
- Complete GKE setup guide with step-by-step instructions
- Architecture overview and authentication flow documentation
- RBAC configuration examples
- Troubleshooting guide
@drduker drduker force-pushed the feature/gcp-oauth-authentication branch from 32982e2 to fc40cb0 Compare November 28, 2025 06:13
@illume illume requested a review from Copilot November 28, 2025 14:18
Copilot finished reviewing on behalf of illume November 28, 2025 14:22
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR adds Google Cloud Platform OAuth 2.0 authentication support to Headlamp, providing a modern replacement for the deprecated GKE Identity Service. The implementation enables users to authenticate with their Google Cloud accounts when accessing Headlamp deployed on GKE clusters.

Key Changes:

  • Implements RFC 7636-compliant PKCE OAuth 2.0 flow with Google as the identity provider
  • Adds automatic GKE cluster detection based on server URL patterns
  • Integrates OAuth flow into the authentication chooser UI with conditional rendering

Reviewed changes

Copilot reviewed 14 out of 15 changed files in this pull request and generated 9 comments.

Show a summary per file
File Description
frontend/src/lib/k8s/gke.ts Implements GKE cluster detection and OAuth initiation utilities
frontend/src/lib/k8s/gke.test.ts Comprehensive unit tests for GKE utilities
frontend/src/lib/k8s/cluster.ts Adds optional server property to Cluster interface
frontend/src/components/cluster/GCPLoginButton.tsx React component for Google sign-in button with conditional rendering
frontend/src/components/cluster/GCPLoginButton.test.tsx Component tests for GCPLoginButton
frontend/src/components/authchooser/index.tsx Integrates GCP OAuth option and disables auto-redirect to token page
backend/pkg/gcp/auth.go Core OAuth 2.0 authenticator with PKCE, token refresh, and caching
backend/pkg/gcp/auth_test.go Unit tests for GCP authenticator functions
backend/pkg/auth/gcp.go HTTP handlers for OAuth login, callback, and token refresh flows
backend/pkg/config/config.go Adds GCP OAuth configuration fields with validation
backend/cmd/server.go Populates GCP OAuth configuration from config
backend/cmd/headlamp.go Registers OAuth routes and clears in-cluster auth when GCP OAuth enabled
backend/go.mod Adds cloud.google.com/go/compute/metadata dependency
backend/go.sum Updates dependency checksums including golang.org/x/sys version bump
docs/GCP_OAUTH_GKE_SETUP.md Comprehensive deployment and configuration guide

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +610 to +611
- [GCP OAuth Setup Guide](./GCP_OAUTH_SETUP.md) - General OAuth setup
- [Implementation Status](./GCP_OAUTH_IMPLEMENTATION_STATUS.md) - Technical details
Copy link

Copilot AI Nov 28, 2025

Choose a reason for hiding this comment

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

The documentation references two files that are not included in this PR:

  • ./GCP_OAUTH_SETUP.md
  • ./GCP_OAUTH_IMPLEMENTATION_STATUS.md

Either add these referenced documents to the PR, or remove the references to avoid broken links.

Suggested change
- [GCP OAuth Setup Guide](./GCP_OAUTH_SETUP.md) - General OAuth setup
- [Implementation Status](./GCP_OAUTH_IMPLEMENTATION_STATUS.md) - Technical details

Copilot uses AI. Check for mistakes.
setOAuthCookie(w, gcpOAuthStateCookie, state, secure)
setOAuthCookie(w, gcpOAuthClusterCookie, cluster, secure)
setOAuthCookie(w, gcpOAuthVerifierCookie, codeVerifier, secure)
setOAuthCookie(w, gcpOAuthChallengeCookie, codeChallenge, secure)
Copy link

Copilot AI Nov 28, 2025

Choose a reason for hiding this comment

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

The gcpOAuthChallengeCookie is stored in the login handler and cleared in the callback, but it's never actually read or validated. Since PKCE uses the code challenge at the authorization endpoint and the code verifier at the token exchange, and only the verifier needs to be stored (which is in gcpOAuthVerifierCookie), the challenge cookie appears to be unnecessary. Consider removing it to reduce the attack surface and simplify the implementation.

Copilot uses AI. Check for mistakes.
}, nil, "GCP OAuth flow completed successfully")

// Redirect to cluster view
redirectURL := fmt.Sprintf("/#/c/%s", cluster)
Copy link

Copilot AI Nov 28, 2025

Choose a reason for hiding this comment

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

The cluster value from the cookie is directly interpolated into the redirect URL without validation or sanitization. While cookies have CSRF protection via the state parameter, an attacker who can control cookies could potentially craft a malicious cluster name to create an open redirect or inject special characters that affect URL parsing.

Consider validating the cluster name against a whitelist of known clusters or sanitizing it to ensure it only contains safe characters (e.g., alphanumeric, hyphens, underscores):

// Validate cluster name format
if !isValidClusterName(cluster) {
    logger.Log(logger.LevelError, map[string]string{"cluster": cluster}, nil, "invalid cluster name format")
    http.Error(w, "invalid cluster name", http.StatusBadRequest)
    return
}

Copilot uses AI. Check for mistakes.
Comment on lines +58 to +60
isGCPOAuthEnabled().then(enabled => {
setGcpOAuthEnabled(enabled);
});
Copy link

Copilot AI Nov 28, 2025

Choose a reason for hiding this comment

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

The isGCPOAuthEnabled() promise is not handling potential errors. If the fetch fails, the promise will be rejected but the error is silently ignored. Consider adding error handling to prevent unhandled promise rejections:

React.useEffect(() => {
  isGCPOAuthEnabled()
    .then(enabled => {
      setGcpOAuthEnabled(enabled);
    })
    .catch(error => {
      console.warn('Failed to check GCP OAuth status:', error);
      setGcpOAuthEnabled(false);
    });
}, []);
Suggested change
isGCPOAuthEnabled().then(enabled => {
setGcpOAuthEnabled(enabled);
});
isGCPOAuthEnabled()
.then(enabled => {
setGcpOAuthEnabled(enabled);
})
.catch(error => {
console.warn('Failed to check GCP OAuth status:', error);
setGcpOAuthEnabled(false);
});

Copilot uses AI. Check for mistakes.
Comment on lines +178 to +184
// else if (cluster.useToken) {
// history.replace({
// pathname: generatePath(getClusterPrefixedPath('token'), {
// cluster: clusterName as string,
// }),
// });
// }
Copy link

Copilot AI Nov 28, 2025

Choose a reason for hiding this comment

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

Large blocks of commented-out code should be removed instead of left in the codebase. The version control system preserves the history if this code needs to be recovered. Consider removing the commented code blocks and relying on git history for recovery if needed.

Suggested change
// else if (cluster.useToken) {
// history.replace({
// pathname: generatePath(getClusterPrefixedPath('token'), {
// cluster: clusterName as string,
// }),
// });
// }

Copilot uses AI. Check for mistakes.
Comment on lines +39 to +43
beforeEach(() => {
// Create spies for GKE functions
isGKEClusterSpy = vi.spyOn(gke, 'isGKECluster');
initiateGCPLoginSpy = vi.spyOn(gke, 'initiateGCPLogin').mockImplementation(() => {});
});
Copy link

Copilot AI Nov 28, 2025

Choose a reason for hiding this comment

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

The tests don't mock isGCPOAuthEnabled(), which is called in the component's useEffect hook. This could cause the tests to make actual network requests or fail unexpectedly. Add a mock for this function:

beforeEach(() => {
  // Create spies for GKE functions
  isGKEClusterSpy = vi.spyOn(gke, 'isGKECluster');
  initiateGCPLoginSpy = vi.spyOn(gke, 'initiateGCPLogin').mockImplementation(() => {});
  vi.spyOn(gke, 'isGCPOAuthEnabled').mockResolvedValue(false); // Add this
});

Copilot uses AI. Check for mistakes.
Comment on lines +31 to +35
2. **Route Handlers** (`backend/pkg/gcp/handlers.go`)
- `/gcp-auth/login`: Initiates OAuth flow
- `/gcp-auth/callback`: Handles OAuth callback
- `/gcp-auth/refresh`: Refreshes expired tokens
- `/gcp-auth/enabled`: Check if GCP OAuth is enabled
Copy link

Copilot AI Nov 28, 2025

Choose a reason for hiding this comment

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

The documentation references backend/pkg/gcp/handlers.go but the actual implementation is in backend/pkg/auth/gcp.go. Update the documentation to reflect the correct file location.

Copilot uses AI. Check for mistakes.
Comment on lines +66 to +78
export async function isGCPOAuthEnabled(): Promise<boolean> {
try {
const response = await fetch('/gcp-auth/enabled');
if (!response.ok) {
return false;
}
const data = await response.json();
return data.enabled === true;
} catch (error) {
console.warn('Failed to check GCP OAuth status:', error);
return false;
}
}
Copy link

Copilot AI Nov 28, 2025

Choose a reason for hiding this comment

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

The isGCPOAuthEnabled function is exported and used in production code but lacks test coverage. Consider adding tests for this function to verify:

  1. Successful fetch with enabled: true
  2. Successful fetch with enabled: false
  3. Network failure handling
  4. Invalid JSON response handling
  5. HTTP error response handling

Copilot uses AI. Check for mistakes.
Comment on lines +918 to +928
// Endpoint to check if GCP OAuth is enabled (frontend needs this)
r.HandleFunc("/gcp-auth/enabled", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
if config.gcpOAuthEnabled {
w.WriteHeader(http.StatusOK)
w.Write([]byte(`{"enabled": true}`))
} else {
w.WriteHeader(http.StatusOK)
w.Write([]byte(`{"enabled": false}`))
}
}).Methods("GET")
Copy link

Copilot AI Nov 28, 2025

Choose a reason for hiding this comment

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

The /gcp-auth/enabled endpoint is registered outside the if config.gcpOAuthEnabled block, which means it's always available regardless of the configuration. While this may be intentional to allow the frontend to detect the feature, it creates an inconsistency where the endpoint is accessible but GCP OAuth routes are not. Consider either:

  1. Moving this endpoint inside the conditional block if it should only be available when GCP OAuth is enabled
  2. Adding a comment explaining why it's intentionally outside the block

The current implementation returns the correct value, but the inconsistent registration pattern could be confusing for maintainability.

Copilot uses AI. Check for mistakes.
Copy link
Contributor

@skoeva skoeva left a comment

Choose a reason for hiding this comment

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

Hi, thanks for looking into this.

I see the PR was generated with Claude, please make sure to thoroughly go through the Copilot comments, test your changes, and ensure tests pass before marking this PR ready for review.

@skoeva skoeva marked this pull request as draft December 1, 2025 16:04
@k8s-ci-robot k8s-ci-robot added the do-not-merge/work-in-progress Indicates that a PR should not merge because it is a work in progress. label Dec 1, 2025
@drduker
Copy link
Author

drduker commented Dec 1, 2025

Hi, thanks for looking into this.

I see the PR was generated with Claude, please make sure to thoroughly go through the Copilot comments, test your changes, and ensure tests pass before marking this PR ready for review.

Yes, was just trying to get it working, which it is. Not sure how much more time want to put into this yet.

@skoeva
Copy link
Contributor

skoeva commented Dec 1, 2025

No worries! That's good to know. Feel free to ping if you would like someone else to take this over

@k8s-ci-robot k8s-ci-robot added the needs-rebase Indicates a PR cannot be merged because it has merge conflicts with HEAD. label Dec 2, 2025
@k8s-ci-robot
Copy link
Contributor

PR needs rebase.

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

cncf-cla: no Indicates the PR's author has not signed the CNCF CLA. do-not-merge/work-in-progress Indicates that a PR should not merge because it is a work in progress. needs-rebase Indicates a PR cannot be merged because it has merge conflicts with HEAD. size/XXL Denotes a PR that changes 1000+ lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants