golang.org/x/oauth2@v0.18.0/google/internal/impersonate/impersonate.go (about) 1 // Copyright 2021 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package impersonate 6 7 import ( 8 "bytes" 9 "context" 10 "encoding/json" 11 "fmt" 12 "io" 13 "io/ioutil" 14 "net/http" 15 "time" 16 17 "golang.org/x/oauth2" 18 ) 19 20 // generateAccesstokenReq is used for service account impersonation 21 type generateAccessTokenReq struct { 22 Delegates []string `json:"delegates,omitempty"` 23 Lifetime string `json:"lifetime,omitempty"` 24 Scope []string `json:"scope,omitempty"` 25 } 26 27 type impersonateTokenResponse struct { 28 AccessToken string `json:"accessToken"` 29 ExpireTime string `json:"expireTime"` 30 } 31 32 // ImpersonateTokenSource uses a source credential, stored in Ts, to request an access token to the provided URL. 33 // Scopes can be defined when the access token is requested. 34 type ImpersonateTokenSource struct { 35 // Ctx is the execution context of the impersonation process 36 // used to perform http call to the URL. Required 37 Ctx context.Context 38 // Ts is the source credential used to generate a token on the 39 // impersonated service account. Required. 40 Ts oauth2.TokenSource 41 42 // URL is the endpoint to call to generate a token 43 // on behalf the service account. Required. 44 URL string 45 // Scopes that the impersonated credential should have. Required. 46 Scopes []string 47 // Delegates are the service account email addresses in a delegation chain. 48 // Each service account must be granted roles/iam.serviceAccountTokenCreator 49 // on the next service account in the chain. Optional. 50 Delegates []string 51 // TokenLifetimeSeconds is the number of seconds the impersonation token will 52 // be valid for. 53 TokenLifetimeSeconds int 54 } 55 56 // Token performs the exchange to get a temporary service account token to allow access to GCP. 57 func (its ImpersonateTokenSource) Token() (*oauth2.Token, error) { 58 lifetimeString := "3600s" 59 if its.TokenLifetimeSeconds != 0 { 60 lifetimeString = fmt.Sprintf("%ds", its.TokenLifetimeSeconds) 61 } 62 reqBody := generateAccessTokenReq{ 63 Lifetime: lifetimeString, 64 Scope: its.Scopes, 65 Delegates: its.Delegates, 66 } 67 b, err := json.Marshal(reqBody) 68 if err != nil { 69 return nil, fmt.Errorf("oauth2/google: unable to marshal request: %v", err) 70 } 71 client := oauth2.NewClient(its.Ctx, its.Ts) 72 req, err := http.NewRequest("POST", its.URL, bytes.NewReader(b)) 73 if err != nil { 74 return nil, fmt.Errorf("oauth2/google: unable to create impersonation request: %v", err) 75 } 76 req = req.WithContext(its.Ctx) 77 req.Header.Set("Content-Type", "application/json") 78 79 resp, err := client.Do(req) 80 if err != nil { 81 return nil, fmt.Errorf("oauth2/google: unable to generate access token: %v", err) 82 } 83 defer resp.Body.Close() 84 body, err := ioutil.ReadAll(io.LimitReader(resp.Body, 1<<20)) 85 if err != nil { 86 return nil, fmt.Errorf("oauth2/google: unable to read body: %v", err) 87 } 88 if c := resp.StatusCode; c < 200 || c > 299 { 89 return nil, fmt.Errorf("oauth2/google: status code %d: %s", c, body) 90 } 91 92 var accessTokenResp impersonateTokenResponse 93 if err := json.Unmarshal(body, &accessTokenResp); err != nil { 94 return nil, fmt.Errorf("oauth2/google: unable to parse response: %v", err) 95 } 96 expiry, err := time.Parse(time.RFC3339, accessTokenResp.ExpireTime) 97 if err != nil { 98 return nil, fmt.Errorf("oauth2/google: unable to parse expiry: %v", err) 99 } 100 return &oauth2.Token{ 101 AccessToken: accessTokenResp.AccessToken, 102 Expiry: expiry, 103 TokenType: "Bearer", 104 }, nil 105 }