4
votes

I want to query GSuite Admin SDK Directory API to return all users in a group, in Go, and authenticated as a GCP service account (the script will be executed in a Google Compute Engine VM or as a Google Cloud Function).

The service account I use (let's call it [email protected]) have been granted necessary scopes in GSuite :

At my disposal, I also have a GSuite Admin account (let's call it [email protected]). This account will be used for delegation (see the docs on delegation).

I am able to return all users in a group with the following code (based on the code in the link provided above, and I have removed most of error handling to make the code shorter) :

package main

import (
    "io/ioutil"
    "log"
    "os"

    "golang.org/x/net/context"
    "golang.org/x/oauth2/google"
    admin "google.golang.org/api/admin/directory/v1"
)

func main() {
    srv := createAdminDirectoryService(
        os.Getenv("SERVICE_ACCOUNT_FILE_PATH"),
        os.Getenv("GSUITE_ADMIN_USER_EMAIL"),
    )
    members := listUsersInGroup(srv, os.Args[1])
    log.Println(members)
}

func createAdminDirectoryService(serviceAccountFilePath,
                                 gsuiteAdminUserEmail string) *admin.Service {
    jsonCredentials, _ := ioutil.ReadFile(serviceAccountFilePath)

    config, _ := google.JWTConfigFromJSON(
        jsonCredentials,
        admin.AdminDirectoryGroupMemberReadonlyScope,
    )
    config.Subject = gsuiteAdminUserEmail

    ctx := context.Background()
    client := config.Client(ctx)

    srv, _ := admin.New(client)
    return srv
}

func listUsersInGroup(srv *admin.Service, groupEmail string) []string {
    members, err := srv.Members.List(groupEmail).Do()
    if err != nil {
        log.Fatal(err)
    }

    membersEmails := make([]string, len(members.Members))
    for i, member := range members.Members {
        membersEmails[i] = member.Email
    }

    return membersEmails
}

As you can see, that code requires to have a JSON key file of [email protected]. This is the only way I have found to create a jwt.Config object.

Moreover, note that delegation is done with the line config.Subject = gsuiteAdminUserEmail; without that, I got the error :

googleapi: Error 403: Insufficient Permission: Request had insufficient authentication scopes., insufficientPermissions

Anyway, running :

SERVICE_ACCOUNT_FILE_PATH=/path/to/json/key/of/my/service/account \
[email protected] \
go run main.go [email protected]

prints with success all users in [email protected].

However, since this code will be run from a Google Compute Engine VM (or a Google Cloud Function) with [email protected] used as service account, it seems ridiculous to need a JSON key of that service account to authenticate (since I am already authenticated as [email protected], on the VM or inside the GCF).

I have tried to replace the content of createAdminDirectoryService() with the following code to authenticate as the user who launched the script (with default credentials) :

func createAdminDirectoryService() *admin.Service {
    ctx := context.Background()

    client, _ := google.DefaultClient(ctx, scopes...)

    srv, _ := admin.New(client)
    return srv
}

But listing users returns an error :

googleapi: Error 403: Insufficient Permission: Request had insufficient authentication scopes., insufficientPermissions

As this is the same error I have got when I removed the delegation, I think this is related. Indeed, I did not provide my GSuite Admin account anywhere during the admin.Service creation.

Can anyone help about one of these points :

  • How can I authenticate directly with the user running the go script on a Google Compute Engine VM, or a Google Cloud Function?
  • Do I really need a JSON key file to generate a jwt.Config object?
  • I have looked into the source code of golang.org/x/oauth2/google, I could get a oauth2.Config instead of jwt.Config, but it does not seem possible to "delegate" with a oauth2 token. How else can I perform the delegation?
2
Did you ever manage to solve this? - jdsalaro
@anantary Unfortunately no, I've just embedded the content of the service account key (JSON string) as an environment variable of my Cloud Function. I've used Google KMS to encrypt the JSON string though to avoid viewers seeing the unencrypted JSON string in the environment variables section. - norbjd

2 Answers

0
votes

This post is somewhat old, but hope this can help:

I had the same problem and made it work by changing the OAuth scopes in the Compute Instance (by default, the scopes only include https://www.googleapis.com/auth/cloud-platform). In my case, I had to add the https://www.googleapis.com/auth/admin.directory.group.readonly scope. It can be done using the gcloud compute instances set-service-account command. That way the service account bearer token received from the metadata server has the correct scopes to access the Admin Directory API.

Unfortunately, it cannot be done for Cloud Functions as the Cloud Functions OAuth scopes do not seem to be customizable.

See this post for more details.

Btw it is also possible now to give a service account access to the Admin Directory API without domain-wide delegation.

-1
votes
    r, err := srv.Users.List().
        ViewType("domain_public").
        Customer("my_customer").
        OrderBy("email").
        Do()

Try using this.