Skip to content

Instantly share code, notes, and snippets.

@salrashid123
Created April 2, 2022 00:23
Show Gist options
  • Save salrashid123/8e8682d29215fa585606c976904ee4f3 to your computer and use it in GitHub Desktop.
Save salrashid123/8e8682d29215fa585606c976904ee4f3 to your computer and use it in GitHub Desktop.
Using signBlob to get an access and id_token (https://blog.salrashid.dev/articles/2022/concentric_iam/)
package main
import (
"context"
"encoding/base64"
"encoding/json"
"flag"
"fmt"
"io/ioutil"
"net/http"
"net/url"
"time"
credentials "cloud.google.com/go/iam/credentials/apiv1"
"golang.org/x/oauth2"
credentialspb "google.golang.org/genproto/googleapis/iam/credentials/v1"
)
const (
tokenEndpoint = "https://oauth2.googleapis.com/token"
audience = "https://oauth2.googleapis.com/token"
)
var (
client *credentials.IamCredentialsClient
svcAccountEmail = flag.String("svcAccountEmail", "svcaccount1@$PROJECT_ID.iam.gserviceaccount.com", "Service Account Email")
)
type header struct {
Alg string `json:"alg"`
Typ string `json:"typ"`
Kid string `json:"kid,omitempty"`
}
type claimSet struct {
Aud string `json:"aud"`
Exp int64 `json:"exp"`
Iss string `json:"iss"`
Iat int64 `json:"iat"`
Sub string `json:"sub,omitempty"`
Email string `json:"email"`
Scope string `json:"scope,omitempty"`
TargetAudience string `json:"target_audience,omitempty"`
}
type unsignedJWT struct {
header
claimSet
}
type idTokenResponse struct {
IdToken string `json:"id_token"`
}
func sign(ctx context.Context, bytesToSign []byte) ([]byte, error) {
sreq := &credentialspb.SignBlobRequest{
Name: fmt.Sprintf("projects/-/serviceAccounts/%s", *svcAccountEmail),
Delegates: []string{},
Payload: bytesToSign,
}
resp, err := client.SignBlob(ctx, sreq)
if err != nil {
fmt.Printf("%v", err)
return nil, err
}
return resp.SignedBlob, nil
}
func main() {
flag.Parse()
ctx := context.Background()
var err error
client, err = credentials.NewIamCredentialsClient(ctx)
if err != nil {
fmt.Printf("%v", err)
return
}
defer client.Close()
fmt.Println("-------------- signBlob --> signedJWT --> access_token")
fmt.Println("------------------------------------------------------")
header := &header{
Alg: "RS256",
Typ: "JWT",
}
scope := "https://www.googleapis.com/auth/cloud-platform"
payload := &claimSet{
Exp: time.Now().Add(time.Second * 30).Unix(),
Aud: audience,
Iss: *svcAccountEmail,
Iat: time.Now().Unix(),
Scope: scope,
}
hstr, err := json.Marshal(*header)
if err != nil {
fmt.Printf("%v", err)
return
}
pstr, err := json.Marshal(*payload)
if err != nil {
fmt.Printf("%v", err)
return
}
unsignedJWT := fmt.Sprintf("%s.%s", base64.StdEncoding.EncodeToString(hstr), base64.StdEncoding.EncodeToString(pstr))
j, err := sign(ctx, []byte(unsignedJWT))
if err != nil {
fmt.Printf("%v", err)
return
}
fmt.Printf("SignBlob: %s\n", base64.StdEncoding.EncodeToString(j))
fmt.Println()
signedJWT := unsignedJWT + "." + base64.StdEncoding.EncodeToString(j)
fmt.Printf("SignJWT: %s\n", signedJWT)
fmt.Println()
form := url.Values{}
form.Add("grant_type", "urn:ietf:params:oauth:grant-type:jwt-bearer")
form.Add("assertion", signedJWT)
client := &http.Client{}
accessTokenResponse, err := client.PostForm(tokenEndpoint, form)
defer accessTokenResponse.Body.Close()
if err != nil {
fmt.Printf("%v", err)
return
}
if accessTokenResponse.StatusCode != http.StatusOK {
bodyBytes, err := ioutil.ReadAll(accessTokenResponse.Body)
fmt.Printf("Unable to exchange token %s, %v", string(bodyBytes), err)
return
}
tresp := &oauth2.Token{}
err = json.NewDecoder(accessTokenResponse.Body).Decode(tresp)
if err != nil {
fmt.Printf("Error Decoding GCP STS TokenResponse %v", err)
return
}
fmt.Printf("access_token: %s\n", tresp.AccessToken)
fmt.Println()
fmt.Println("------------------ signBlob --> signedJWT --> id_token")
fmt.Println("------------------------------------------------------")
target_audience := "https://foo.bar"
payload = &claimSet{
Exp: time.Now().Add(time.Second * 30).Unix(),
Aud: audience,
Iss: *svcAccountEmail,
Iat: time.Now().Unix(),
TargetAudience: target_audience,
}
hstr, err = json.Marshal(*header)
if err != nil {
fmt.Printf("%v", err)
return
}
pstr, err = json.Marshal(*payload)
if err != nil {
fmt.Printf("%v", err)
return
}
unsignedJWT = fmt.Sprintf("%s.%s", base64.StdEncoding.EncodeToString(hstr), base64.StdEncoding.EncodeToString(pstr))
j, err = sign(ctx, []byte(unsignedJWT))
if err != nil {
fmt.Printf("%v", err)
return
}
fmt.Printf("SignedBlob: %s\n", base64.StdEncoding.EncodeToString(j))
fmt.Println()
signedJWT = unsignedJWT + "." + base64.StdEncoding.EncodeToString(j)
fmt.Printf("SignedJWT: %s\n", signedJWT)
fmt.Println()
form = url.Values{}
form.Add("grant_type", "urn:ietf:params:oauth:grant-type:jwt-bearer")
form.Add("assertion", signedJWT)
client = &http.Client{}
ir, err := client.PostForm(tokenEndpoint, form)
defer ir.Body.Close()
if err != nil {
fmt.Printf("%v", err)
return
}
if ir.StatusCode != http.StatusOK {
bodyBytes, err := ioutil.ReadAll(ir.Body)
fmt.Printf("Unable to exchange token %s, %v", string(bodyBytes), err)
return
}
idresp := &idTokenResponse{}
err = json.NewDecoder(ir.Body).Decode(idresp)
if err != nil {
fmt.Printf("Error Decoding GCP STS TokenResponse %v", err)
return
}
fmt.Printf("id_token: %v\n", idresp.IdToken)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment