Open6

Google Admin API の Go クライントの利用は SA の JSON Key 必須であるかの調査

ttyfkyttyfky

Google Admin API は Subject を上書きして impersonate する必要があり、しばらく前は以下の形で google.config を生成後に Subject を上書きするため GOOGLE_APPLICATION_CREDENTIALS で使われる Service Account の JSON Key を利用(GAE や Run の場合は Secret Manager から JSON を取得)する必要があった。
SA Key への依存をなくせるかの再調査を行う。

import (
	"context"
	"fmt"
	"golang.org/x/oauth2/google"
	admin "google.golang.org/api/admin/directory/v1"
	"google.golang.org/api/googleapi"
	"google.golang.org/api/option"
)
...
        jsonCredentials, _ := ioutil.ReadFile(os.Getenv("GOOGLE_APPLICATION_CREDENTIALS"))
	config, _ := google.JWTConfigFromJSON(jsonCredentials, scopes...)
	config.Subject = userEmail // impersonating
	ts := config.TokenSource(ctx)
	srv, _ := admin.NewService(ctx, option.WithTokenSource(ts))
        srv.Groups.List()
ttyfkyttyfky

以下の形で途中で config を挟まなくても Subject の上書きが可能になっている。

// 	"golang.org/x/oauth2/google"
credentials, err := google.FindDefaultCredentialsWithParams(ctx, google.CredentialsParams{
		Scopes:  scopes,
		Subject: subject,
	})
srv, err := admin.NewService(ctx, option.WithCredentials(credentials))
ttyfkyttyfky

ローカルの場合は CredentialsParams が仕事をするが、Cloud Run や GCE 環境の場合は params の Subject は利用されていないため FindDefaultCredentialsWithParams を用いる形では通用しない、、、

https://github.com/golang/oauth2/blob/master/google/default.go#L111

func FindDefaultCredentialsWithParams(ctx context.Context, params CredentialsParams) (*Credentials, error) {

	const envVar = "GOOGLE_APPLICATION_CREDENTIALS"
	if filename := os.Getenv(envVar); filename != "" {// ローカル
		creds, err := readCredentialsFile(ctx, filename, params)
		if err != nil {
			return nil, fmt.Errorf("google: error getting credentials using %v environment variable: %v", envVar, err)
		}
		return creds, nil
	}

......

	// Fourth, if we're on Google Compute Engine, an App Engine standard second generation runtime,
	// or App Engine flexible, use the metadata server.
	if metadata.OnGCE() { // Cloud Run の場合
		id, _ := metadata.ProjectID()
		return &DefaultCredentials{
			ProjectID:   id,
			TokenSource: ComputeTokenSource("", params.Scopes...),
		}, nil
	}

ttyfkyttyfky

jwt.Config は基本的に File からしか作れない。直接作るにしても SecretKey などはファイルにある

func (f *credentialsFile) jwtConfig(scopes []string, subject string) *jwt.Config {
ttyfkyttyfky

CredentialsConfig の Subject を与えたものを試したが権限で落ちた。SA に Service Account Token Creator を与えるだけで良いと思ったがなにか足りない?

https://github.com/googleapis/google-api-go-client/blob/main/impersonate/impersonate.go#L29

Failed to list group: Get "https://admin.googleapis.com/admin/directory/v1/groups?alt=json&domain=***&prettyPrint=false": impersonate: unable to sign JWT: Post "https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/SA:signJwt": oauth2: cannot fetch token: 401 Unauthorized
Response: {
  "error": "unauthorized_client",
  "error_description": "Client is unauthorized to retrieve access tokens using this method, or client not authorized for any of the scopes requested."
}