-
Notifications
You must be signed in to change notification settings - Fork 16
OAuth passthrough support #145
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
OAuth passthrough support #145
Conversation
|
Thanks for your pull request! It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA). View this failed invocation of the CLA check for more information. For the most up to date status, view the checks section at the bottom of the pull request. |
|
The AI I am using provided the following review. When you get a chance, can you please take a look and let me know your thoughts? Can we address at least the critical issues? Thanks! PR #145 Code Review: OAuth Passthrough AuthenticationSummaryThis PR adds OAuth Passthrough authentication support, allowing users to authenticate to Google Cloud Logging using their browser OAuth token from Grafana's Google authentication. 🔴 Critical Issues (Must Fix)1. Race Condition + CRITICAL SECURITY VULNERABILITY (Confused Deputy / Data Leakage) (pkg/plugin/plugin.go)func (d *CloudLoggingDatasource) SetOauthClient(ctx context.Context, headers map[string]string) error {
client, err := cloudlogging.NewClientWithPassThrough(ctx, headers)
if err != nil {
return err
}
d.client = client // ⚠️ RACE CONDITION + SECURITY VULNERABILITY
return nil
}Problem: The
This is a Confused Deputy vulnerability that can lead to unauthorized data access between users. Recommendation:
func (d *CloudLoggingDatasource) QueryData(ctx context.Context, req *backend.QueryDataRequest) (*backend.QueryDataResponse, error) {
var client cloudlogging.API
if d.oauthPassThrough {
var err error
client, err = cloudlogging.NewClientWithPassThrough(ctx, req.Headers)
if err != nil {
return nil, err
}
defer client.Close() // Ensure cleanup
} else {
client = d.client
}
// Use 'client' for all operations in this request
}2. Resource Leak - Clients Not Closed (pkg/plugin/plugin.go)func (d *CloudLoggingDatasource) SetOauthClient(ctx context.Context, headers map[string]string) error {
client, err := cloudlogging.NewClientWithPassThrough(ctx, headers)
// Previous d.client is never closed!
d.client = client
return nil
}Problem: Each request creates a new client but the previous client is never closed, causing gRPC connection leaks that will eventually exhaust system resources. Recommendation: When fixing Issue #1 with per-request clients, ensure 3. Nil Pointer Dereference in Dispose() (pkg/plugin/plugin.go)func (d *CloudLoggingDatasource) Dispose() {
if err := d.client.Close(); err != nil { // ⚠️ d.client is nil for oauthPassthrough
log.DefaultLogger.Error("failed closing client", "error", err)
}
}Problem: When Recommendation: Add nil check: func (d *CloudLoggingDatasource) Dispose() {
if d.client != nil {
if err := d.client.Close(); err != nil {
log.DefaultLogger.Error("failed closing client", "error", err)
}
}
}4. Missing Token Validation (pkg/plugin/cloudlogging/client.go)func NewClientWithPassThrough(ctx context.Context, headers map[string]string) (*Client, error) {
token, _ := strings.CutPrefix(headers["Authorization"], "Bearer ")
// No validation if token is empty!
oauthOpt := option.WithTokenSource(...)Problem: If the Recommendation: Validate the token exists: token, found := strings.CutPrefix(headers["Authorization"], "Bearer ")
if !found || token == "" {
return nil, errors.New("missing or invalid Authorization header")
}🟡 Medium Issues5. Inconsistent Header Key Casing (pkg/plugin/plugin.go & client.go)// In plugin.go - CallResource uses map[string]string with original casing
headers[k] = strings.Join(v, ",")
// In client.go - expects "Authorization" with exact casing
token, _ := strings.CutPrefix(headers["Authorization"], "Bearer ")Problem: HTTP headers are case-insensitive, but Go maps are case-sensitive. The header might come as Recommendation: Use case-insensitive header lookup: func getHeader(headers map[string]string, key string) string {
for k, v := range headers {
if strings.EqualFold(k, key) {
return v
}
}
return ""
}6. Multiple Authorization Headers Edge Case (pkg/plugin/plugin.go)// In CallResource
headers[k] = strings.Join(v, ",")Problem: If multiple Recommendation: For Authorization headers, strictly select the first value: for k, v := range req.Headers {
if strings.EqualFold(k, "Authorization") && len(v) > 0 {
headers[k] = v[0] // Take only the first Authorization header
} else {
headers[k] = strings.Join(v, ",")
}
}7. Missing User-Agent (pkg/plugin/cloudlogging/client.go)func NewClientWithPassThrough(ctx context.Context, headers map[string]string) (*Client, error) {
// Missing: option.WithUserAgent("googlecloud-logging-datasource")
client, err := logging.NewClient(ctx, oauthOpt)Problem: Other client constructors include Recommendation: Add 8. State Synchronization Issue in ConfigEditor (src/ConfigEditor.tsx)state = {
isChecked: this.props.options.jsonData.usingImpersonation || false,
sa: this.props.options.jsonData.serviceAccountToImpersonate || '',
authenticationMethod: this.props.options.jsonData.authenticationType || GoogleAuthType.JWT,
};Problem: Recommendation: Either remove unused state or update it in 🟢 Minor Issues / Suggestions9. Unnecessary
|
|
Hi @xiangshen-dk, I've updated based on the review, PTAL |
Adding OAuth passthrough support to the GCL data source.
This functionality will allow users that are logged into Grafana using Google OAuth to also authenticate the data source with their OAuth token rather than static credentials. This allows for improved security and more granular data access.
I've also refactored the config editor a little to make it clearer what authentication type is being used. This can be a little confusing for the authentication methods that come from the SDK itself and I'm happy to take feedback here.
I've updated the docs accordingly and have tested that this works as expected.