Created
April 2, 2022 00:23
-
-
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/)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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