Skip to content

Instantly share code, notes, and snippets.

@pdecat
Created January 31, 2020 17:39
Show Gist options
  • Save pdecat/80f21e36583420abbfdeae0494a53501 to your computer and use it in GitHub Desktop.
Save pdecat/80f21e36583420abbfdeae0494a53501 to your computer and use it in GitHub Desktop.
Creating a GCS Signed URL without a Service Account Key from a GCE instance or a GKE Pod using Workload Identity
package main
import (
"context"
"flag"
"log"
"net/url"
"time"
"cloud.google.com/go/compute/metadata"
credentials "cloud.google.com/go/iam/credentials/apiv1"
"cloud.google.com/go/storage"
"golang.org/x/oauth2"
"golang.org/x/oauth2/google"
credentialspb "google.golang.org/genproto/googleapis/iam/credentials/v1"
)
func main() {
var bucket string
var object string
var method string
flag.StringVar(&bucket, "bucket", "", "The bucket")
flag.StringVar(&object, "object", "", "The object in the bucket")
flag.StringVar(&method, "method", "GET", "The HTTP method to use")
flag.Parse()
log.Printf("bucket: %v", bucket)
log.Printf("object: %v", object)
log.Printf("method: %v", method)
creds, err := google.FindDefaultCredentials(oauth2.NoContext)
if err != nil {
log.Printf("Error while getting default credentials: %v", err)
return
}
token, err := creds.TokenSource.Token()
if err != nil {
log.Printf("Error while getting token: %v", err)
return
}
accountID, ok := token.Extra("oauth2.google.serviceAccount").(string)
if !ok {
log.Printf("Error while getting account ID: %v", err)
return
}
log.Printf("accountID: %v", accountID)
client, err := google.DefaultClient(oauth2.NoContext)
if err != nil {
log.Printf("Error while getting default client: %v", err)
return
}
computeMetadataClient := metadata.NewClient(client)
email, err := computeMetadataClient.Email(accountID)
if err != nil {
log.Printf("Error while getting email: %v", err)
return
}
log.Printf("email: %v", email)
projectID, err := computeMetadataClient.ProjectID()
if err != nil {
log.Printf("Error while getting project ID: %v", err)
return
}
log.Printf("projectID: %v", projectID)
sc := storage.SignedURLOptions{
GoogleAccessID: email,
Method: method,
Expires: time.Now().Add(60 * time.Second),
ContentType: "",
}
sc.SignBytes = func(payload []byte) ([]byte, error) {
ctx := context.Background()
credsClient, err := credentials.NewIamCredentialsClient(ctx)
if err != nil {
return nil, err
}
req := &credentialspb.SignBlobRequest{
Name: email,
Payload: payload,
}
res, err := credsClient.SignBlob(ctx, req)
if err != nil {
return nil, err
}
return res.SignedBlob, err
}
rawURL, err := storage.SignedURL(bucket, object, &sc)
if err != nil {
log.Printf("Error while generating GCS pre-signed URL: %v", err)
return
}
URL, err := url.Parse(rawURL)
if err != nil {
log.Printf("Error while parsing generated URL: %v", err)
return
}
log.Printf("URL to use with %v method: %v", method, URL)
return
}
@challarao
Copy link

challarao commented Oct 7, 2020

I'm not getting the bindings similar to what you are getting. This is what I've tried:

  1. Create service account

image

  1. Add role

I added this only because I didn't see that option in step 3.

image

  1. Granted itself access to itself
    image

  2. Invoked the command you used:

gcloud iam service-accounts get-iam-policy  test13@myproject.iam.gserviceaccount.com                                                                                        
bindings:
- members:
  - serviceAccount:test13@myproject.iam.gserviceaccount.com
  role: roles/iam.serviceAccountUser
etag: BwWxEVwMnV4=
version: 1

In my output I don't see service account creator in the role. While you have it in your policy. What am I missing in the service account creation?

Thanks

@challarao
Copy link

I was able to create the binding using this command:

gcloud iam service-accounts add-iam-policy-binding test13@myproject.iam.gserviceaccount.com --member serviceAccount:test13@myproject.iam.gserviceaccount.com --
role roles/iam.serviceAccountTokenCreator

Now I'm getting the output similar to yours:

bindings:
- members:
  - serviceAccount:test13@myproject.iam.gserviceaccount.com
  role: roles/iam.serviceAccountTokenCreator
etag: BwWxEg_JjV0=
version: 1

Hoping this is the way to do it. Thanks.

@sarjumulmi
Copy link

@pdecat, is the email passed to SignBlobRequest.Name field in the format projects/-/serviceAccounts/{ACCOUNT_EMAIL_OR_UNIQUEID} as mentioned in the docs??

@pdecat
Copy link
Author

pdecat commented Apr 13, 2021

@pdecat, is the email passed to SignBlobRequest.Name field in the format projects/-/serviceAccounts/{ACCOUNT_EMAIL_OR_UNIQUEID} as mentioned in the docs??

It is the raw email address of the service account.

@sarjumulmi
Copy link

@pdecat, is the email passed to SignBlobRequest.Name field in the format projects/-/serviceAccounts/{ACCOUNT_EMAIL_OR_UNIQUEID} as mentioned in the docs??

It is the raw email address of the service account.

@pdecat, thanks for the reply. I was referring to the docs here which seems to suggest to use projects/-/serviceAccounts/{rawEmail} instead.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment