Skip to content

Instantly share code, notes, and snippets.

@salrashid123
Created August 4, 2020 23:05
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save salrashid123/575d761b1b4a28751173e49a95d773a3 to your computer and use it in GitHub Desktop.
Save salrashid123/575d761b1b4a28751173e49a95d773a3 to your computer and use it in GitHub Desktop.
basic GCP IAMCredentials `crypto.Signer()`
package kms
import (
"crypto"
"encoding/base64"
"io"
"sync"
"context"
"fmt"
"golang.org/x/oauth2"
"google.golang.org/api/iam/v1"
)
const ()
var (
refreshMutex = &sync.Mutex{}
rootTokenSource oauth2.TokenSource
serviceAccountName string
service *iam.Service
)
type IAM struct {
crypto.Signer
crypto.Decrypter
RootTokenSource oauth2.TokenSource
ServiceAccountName string
}
func NewIAMCrypto(conf *IAM) (IAM, error) {
if conf.RootTokenSource == nil || conf.ServiceAccountName == "" {
return IAM{}, fmt.Errorf("RootTokenSource cannot be null")
}
var err error
ctx := context.Background()
client := oauth2.NewClient(ctx, conf.RootTokenSource)
service, err = iam.New(client)
if err != nil {
return IAM{}, err
}
return *conf, nil
}
func (t IAM) Sign(_ io.Reader, digest []byte, opts crypto.SignerOpts) ([]byte, error) {
refreshMutex.Lock()
defer refreshMutex.Unlock()
name := fmt.Sprintf("projects/-/serviceAccounts/%s", t.ServiceAccountName)
signRequest := &iam.SignBlobRequest{
BytesToSign: base64.StdEncoding.EncodeToString(digest),
}
at, err := service.Projects.ServiceAccounts.SignBlob(name, signRequest).Do()
if err != nil {
return nil, fmt.Errorf("oauth2/google: Error calling iamcredentials.SignBlob: %v", err)
}
sDec, err := base64.StdEncoding.DecodeString(at.Signature)
if err != nil {
return nil, fmt.Errorf("oauth2/google: Error calling iamcredentials.SignBlob: %v", err)
}
return sDec, err
}
@salrashid123
Copy link
Author

package main

import (
	"context"
	"crypto"
	"crypto/rand"
	"crypto/sha256"
	"io/ioutil"
	"log"
	"net/http"
	"time"

	"golang.org/x/oauth2/google"

	"cloud.google.com/go/storage"
	sal "github.com/salrashid123/signer/iam"
)

var (
	projectId  = "mineral-minutia-820"
	bucketName = "srashid-1"
)

func main() {

	ctx := context.Background()

	rootTokenSource, err := google.DefaultTokenSource(ctx,
		"https://www.googleapis.com/auth/iam")
	if err != nil {
		log.Fatal(err)
	}

	r, err := sal.NewIAMCrypto(&sal.IAM{
		RootTokenSource:    rootTokenSource,
		ServiceAccountName: "impersonated-account@project.iam.gserviceaccount.com",
	})
	if err != nil {
		log.Println(err)
		return
	}

	object := "foo.txt"
	expires := time.Now().Add(time.Minute * 10)
	key := "impersonated-account@project.iam.gserviceaccount.com"

	s, err := storage.SignedURL(bucketName, object, &storage.SignedURLOptions{
		Scheme:         storage.SigningSchemeV4,
		GoogleAccessID: key,
		SignBytes: func(b []byte) ([]byte, error) {
			sum := sha256.Sum256(b)
			return r.Sign(rand.Reader, sum[:], crypto.SHA256)
		},
		Method:  "GET",
		Expires: expires,
	})
	if err != nil {
		log.Fatal(err)
	}
	log.Println(s)

	resp, err := http.Get(s)
	if err != nil {
		panic(err)
	}
	defer resp.Body.Close()
	body, err := ioutil.ReadAll(resp.Body)
	log.Println("SignedURL Response :\n", string(body))
	if err != nil {
		log.Fatal(err)
	}

}

@dictav
Copy link

dictav commented Dec 20, 2020

Thank you for your sample!
I reached here from issue comment

However, this sample code does not work for me, so I made a new sample.
I hope this helps someone.

package main

import (
	"context"
	"io/ioutil"
	"log"
	"net/http"
	"time"

	"cloud.google.com/go/iam/credentials/apiv1"
	"cloud.google.com/go/storage"
	pb "google.golang.org/genproto/googleapis/iam/credentials/v1"
)

const (
	project        = "<PROJECT>"
	bucket         = "<BUCKET>"
	object         = "foo.txt"
	serviceAccount = "<SERVICE_ACCOUNT>@<PROJECT>.iam.gserviceaccount.com"

	scopeIAM = "https://www.googleapis.com/auth/iam"
)

func main() {
	ctx := context.Background()

	// prepare download file
	gcs, err := storage.NewClient(ctx)
	if err != nil {
		log.Fatal(err)
	}

	obj := gcs.Bucket(bucket).Object(object)
	w := obj.NewWriter(ctx)

	if _, err := w.Write([]byte("hello, foo")); err != nil {
		log.Fatal(err)
	}

	if err := w.Close(); err != nil {
		log.Fatal(err)
	}

	// create signed url
	s, err := storage.SignedURL(bucket, object, &storage.SignedURLOptions{
		Scheme:         storage.SigningSchemeV4,
		GoogleAccessID: serviceAccount,
		Method:         "GET",
		Expires:        time.Now().Add(time.Minute * 1),
		SignBytes: func(b []byte) ([]byte, error) {
			c, err := credentials.NewIamCredentialsClient(context.Background())
			if err != nil {
				return nil, err
			}

			signRequest := &pb.SignBlobRequest{
				Name:    "projects/-/serviceAccounts/" + serviceAccount,
				Payload: b,
			}

			res, err := c.SignBlob(ctx, signRequest)
			if err != nil {
				return nil, err
			}

			return res.GetSignedBlob(), nil
		},
	})
	if err != nil {
		log.Fatal(err)
	}

	log.Println("signed url:", s)

	resp, err := http.Get(s)
	if err != nil {
		log.Fatal(err)
	}

	defer resp.Body.Close()

	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		log.Fatal(err)
	}

	log.Println("SignedURL Response :\n", string(body))
}

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