hashicorp / terraform-provider-googleworkspace

Terraform Provider for Google Workspace
https://registry.terraform.io/providers/hashicorp/googleworkspace
Mozilla Public License 2.0
130 stars 58 forks source link

Support for Service Account Access Tokens #127

Closed brondum closed 3 years ago

brondum commented 3 years ago

Feature request:

The possibility to use Access tokens instead of service account keys to authenticate with Google Workspace.

Why:

  1. Can generate infinite number of tokens per roleset, service account keys have a maximum of 10 keys per account.
  2. Service account keys have infinite lifetime in GCP (i.e. if they are not managed properly, leaked keys can live forever)

References

The old terraform-provider-gsuite provider had the same unsolved issue: https://github.com/DeviaVir/terraform-provider-gsuite/issues/70

halradaideh commented 3 years ago

This is really needed, so we can do SA impersonation

halradaideh commented 3 years ago

@megan07 is it possible?

megan07 commented 3 years ago

Hi @brondum and @halradaideh! Sorry for the delay on responding to this. I know we have this option implemented in the Google provider, and I had heard in the past some issues surrounding that, so I wanted to get the background before I responded here.

It sounds like the main problem with the access_token approach is that they expire after an hour and don’t auto-refresh, thus, if an apply takes longer than an hour, it can cause some cryptic issues. That being said, most of our current resources in the Google Workspace provider don’t take that long to apply, and thus we likely run into this issue. I still need to take the time to see if/how this can be implemented for Google Workspace, so there is more research on my end that I need to do here.

However, I ran into these issues as well when I was creating a CI for the acceptance tests and ended up using Vault for that. Depending on your situation, it may not solve the fact that you can only have 10 keys per account, but it will rotate those keys every , which would solve the infinite lifetime issue. You can also have multiple backends, so if 10 keys aren’t enough at any given time, depending on your situation, maybe you would split it among different backends. Does that make sense? Just wanted to share that idea if it would help you at all! Let me know if you have further questions on it. Here is how I set it up with Terraform for the CI on this project.

halradaideh commented 3 years ago

the need for the access_token is to use SA impersonation, as I have a lot of projects, having multiple keys is a security risk so we use impersonation

and impersonation only provide access_token for auth, that's why I need it

brondum commented 3 years ago

Hi @brondum and @halradaideh! Sorry for the delay on responding to this. I know we have this option implemented in the Google provider, and I had heard in the past some issues surrounding that, so I wanted to get the background before I responded here.

It sounds like the main problem with the access_token approach is that they expire after an hour and don’t auto-refresh, thus, if an apply takes longer than an hour, it can cause some cryptic issues. That being said, most of our current resources in the Google Workspace provider don’t take that long to apply, and thus we likely run into this issue. I still need to take the time to see if/how this can be implemented for Google Workspace, so there is more research on my end that I need to do here.

However, I ran into these issues as well when I was creating a CI for the acceptance tests and ended up using Vault for that. Depending on your situation, it may not solve the fact that you can only have 10 keys per account, but it will rotate those keys every , which would solve the infinite lifetime issue. You can also have multiple backends, so if 10 keys aren’t enough at any given time, depending on your situation, maybe you would split it among different backends. Does that make sense? Just wanted to share that idea if it would help you at all! Let me know if you have further questions on it. Here is how I set it up with Terraform for the CI on this project.

@megan07 Thank you for looking into this. I can definitely see the problem with tokens not refreshing, that could be an equal pain as you would end up running jobs multiple times. We actually use Vault to rotate the keys, but the problem is even if you have expiration set to an hour you are still only able to do 10 runs. In some cases we have automated stuff that runs in peak more than 10/hour. It is definitely a workaround to use Vault and multiple backends, i guess if, like in our case, it is fully automated then you have to write some logic checking if a backend has used all its keys and change to the next.

megan07 commented 3 years ago

Hi all! Just want to update here. I have code written and am running into the same problem that was hit in the original gsuite provider. I have a question out to the Google Workspace team at Google to see if this is possible and/or what I might be running into. I haven't heard back yet, but will continue working on it once I do. Thanks for your patience on this!

gabor-farkas commented 3 years ago

Hi @megan07 thanks for the update! May I ask what's the exact problem? I don't seem to have access to the PR linked in the original issue you linked. I managed to call the directory api using a service account access token (with curl)

megan07 commented 3 years ago

Hi @gabor-farkas ! Sure! So I have a service account which I can generate an access token for, and that's fine, but when I go to generate an access token for the impersonated user, I get a 404: "Requested entity was not found" error.

Here's my branch. It fails in the test here. Although, I imagine it has something to do with how I'm generating the credentials. But I'm not entirely sure.

I don't think it has anything to do with my iam roles, as I created a key for my service account and tried to make it work that way, and it was able to impersonate my user.

If you have an idea of what may be wrong, I'd love to hear your feedback!

gabor-farkas commented 3 years ago

Hi @megan07 thanks for the details, I'll take a look into the details tomorrow! With curl I used the service account access token directly, without impersonating a workspace user - workspace now supports adding service accounts directly as group or user admins. I had to provide the X-Goog-User-Project header though, and after just a quick look I'm not sure how the googleoauth library supports providing that. Nevertheless I'll try to see why user impersonation would not work here.

gabor-farkas commented 3 years ago

Hi @megan07 I checked the branch you shared now. I'm still checking why user impersonation for DwD doesn't work, I get the same error. The access token usage, however, does work without DwD, with the following setup:

provider "google" {
  alias = "impersonate"

  scopes = [
    "https://www.googleapis.com/auth/cloud-platform",
    "https://www.googleapis.com/auth/userinfo.email",
  ]
}

data "google_service_account_access_token" "default" {
  provider               = google.impersonate
  target_service_account = "<<SA>>@<<PRJ>>.iam.gserviceaccount.com"
  scopes                 = ["userinfo-email", "cloud-platform", "https://www.googleapis.com/auth/admin.directory.group"]
  lifetime               = "600s"
}

provider "googleworkspace" {
  customer_id             = "<<ID>>"
  access_token            = data.google_service_account_access_token.default.access_token
}

I can manage groups with this setup, adding the service account as a Group Admin.

I'll try to see what goes wrong with DwD, but I think supporting an access_token without impersonated_user_email makes sense as a feature on its own.

gabor-farkas commented 3 years ago

I went after the 404 error too. So it basically comes because the impersonateCredentials call will try to use the generateAccessToken method, but that only supports service accounts, and the impersonated_user_email will be treated as a service account, and won't be found, thus the 404.

The kind of impersonation we need now can be supported using the singJwt method, which is implemented in the google.golang.org/api/impersonate package. I could get this working in the provider as follows:

if c.ImpersonatedUserEmail != "" {
    tokenSource, err := impersonate.CredentialsTokenSource(context.TODO(), impersonate.CredentialsConfig{
        TargetPrincipal: MY_SERVICE_ACCOUNT,
        Scopes: []string{"https://www.googleapis.com/auth/cloud-platform",
        "https://www.googleapis.com/auth/userinfo.email", "https://www.googleapis.com/auth/admin.directory.group"},
        Subject: c.ImpersonatedUserEmail,
    }, option.WithTokenSource(oauth2.StaticTokenSource(token)))
    if err != nil {
        return diag.FromErr(err)
    }

    creds := googleoauth.Credentials{
        TokenSource: tokenSource,
    }
    diags = c.SetupClient(ctx, &creds)
    return diags
}

A caveat here is that we have to repeat the service account that we already have the access_token token for, so that calls for a further attribute on the provider. Also, the service account has to be granted the 'Service Account Token Creator' role, so that it can sign jwt tokens for itself.

(In the example code I used a smaller set of auth scopes, but that shouldn't matter)

So in the end this setup is a bit awkward: we use google_service_account_access_token to generate an access token for the target service account, then use that service account to sign a jwt token for itself that uses the admin user as sub. A simpler auth flow would be to directly sign a JWT token with the ADC identity, but there's no such data type now in the google provider.

This method can still work though but it's probably worth a few paragraphs in the provider description, also mentioning that for group and user management, the google_service_account_access_token for the service account can just work on its own, without impersonating further with ImpersonatedUserEmail.

megan07 commented 3 years ago

Ahhh thank you for all of this @gabor-farkas! If you have it working and want to submit a PR, I'd be more than happy to review it! If you can, please add a test and documentation as well. If you don't have time, I'd be happy to pick it up as well, just let me know. Thanks again!

gabor-farkas commented 3 years ago

Hi @megan07! No probs, I'm happy could help! I would rather call my local copy a playground (all the changes I have are the lines quoted above), so feel free to pick it up!