internal/google/adc.go (101 lines of code) (raw):
/*
Copyright © 2024 Google LLC
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package google
import (
"context"
"errors"
"fmt"
"os"
"os/exec"
"strings"
"github.com/GoogleCloudPlatform/pastures-poc-toolkit/internal/utils"
"github.com/golang-jwt/jwt/v4"
"github.com/lestrrat-go/jwx/jwk"
"golang.org/x/oauth2/google"
)
type UserClaim struct {
jwt.RegisteredClaims
ID int
Email string
Name string
}
const (
googleKeys string = "https://www.googleapis.com/oauth2/v3/certs"
adcJsonName string = "application_default_credentials.json"
)
var (
userClaim UserClaim
)
func findToken(jsonPath string) (string, error) {
ctx := context.Background()
// locate application default credentials
// read in JSON from path
jsonData, _ := utils.ReadFile(jsonPath)
adc, err := google.CredentialsFromJSON(ctx, jsonData)
if err != nil {
return "", err // can't find adc
}
rawToken, err := adc.TokenSource.Token()
if err != nil {
return "", err // found adc, but token is likely expired
}
// get google signing keys
fetchedKeys, err := jwk.Fetch(ctx, googleKeys)
if err != nil {
return "", errors.New("unable to fetch signing keys from google")
}
// Assuming rawToken is of a type that has an Extra method
// returning an interface{}
idTokenInterface := rawToken.Extra("id_token")
if idTokenInterface == nil {
// Handle the error: id_token is missing or nil
return "", errors.New("id_token is missing or nil")
}
// Safely assert idTokenInterface to a string
idToken, ok := idTokenInterface.(string)
if !ok {
// Handle the error: idTokenInterface is not a string
return "", errors.New("idTokenInterface is not a string")
}
// parse the token
jwt.ParseWithClaims(
idToken,
&userClaim,
func(token *jwt.Token) (interface{}, error) {
if _, ok := token.Method.(*jwt.SigningMethodRSA); !ok {
return "", errors.New("unexpected token signature method")
}
kid, ok := token.Header["kid"].(string)
if !ok {
return "", errors.New("could not find key id in token header")
}
keys, ok := fetchedKeys.LookupKeyID(kid)
if !ok {
return "", errors.New(
"no keys found matching key id in token header",
)
}
var empty interface{}
return empty, keys.Raw(&empty)
},
)
return userClaim.Email, nil
}
func AppDefaultCredentials() (string, error) {
adcPath, err := exec.Command(
"gcloud", "info", "--format", "value(config.paths.global_config_dir)",
).Output()
filePath := strings.Trim(string(adcPath), "\n")
filePath += "/" + adcJsonName
if err != nil {
return "", err
}
email, err := findToken(filePath)
if err != nil {
fmt.Println("\nNo default credentials found - authorizing with Google")
authCmd := exec.Command(
"gcloud", "auth", "application-default", "login",
"--no-launch-browser",
)
authCmd.Stdout = os.Stdout
authCmd.Stderr = os.Stderr
authCmd.Stdin = os.Stdin
if err := authCmd.Run(); err != nil {
fmt.Printf("Error starting gcloud command: %v\n", err)
return "", err
}
// need to refresh adc context to get email now that we're auth'd
email, _ = findToken(filePath)
} else {
fmt.Println("\nFound default Google credentials - skipping")
}
return email, nil
}