...

Source file src/google.golang.org/api/internal/impersonate/impersonate.go

Documentation: google.golang.org/api/internal/impersonate

     1  // Copyright 2020 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 is used to impersonate Google Credentials.
     6  package impersonate
     7  
     8  import (
     9  	"bytes"
    10  	"context"
    11  	"encoding/json"
    12  	"fmt"
    13  	"io"
    14  	"net/http"
    15  	"time"
    16  
    17  	"golang.org/x/oauth2"
    18  )
    19  
    20  // Config for generating impersonated credentials.
    21  type Config struct {
    22  	// Target is the service account to impersonate. Required.
    23  	Target string
    24  	// Scopes the impersonated credential should have. Required.
    25  	Scopes []string
    26  	// Delegates are the service accounts in a delegation chain. Each service
    27  	// account must be granted roles/iam.serviceAccountTokenCreator on the next
    28  	// service account in the chain. Optional.
    29  	Delegates []string
    30  }
    31  
    32  // TokenSource returns an impersonated TokenSource configured with the provided
    33  // config using ts as the base credential provider for making requests.
    34  func TokenSource(ctx context.Context, ts oauth2.TokenSource, config *Config) (oauth2.TokenSource, error) {
    35  	if len(config.Scopes) == 0 {
    36  		return nil, fmt.Errorf("impersonate: scopes must be provided")
    37  	}
    38  	its := impersonatedTokenSource{
    39  		ctx:  ctx,
    40  		ts:   ts,
    41  		name: formatIAMServiceAccountName(config.Target),
    42  		// Default to the longest acceptable value of one hour as the token will
    43  		// be refreshed automatically.
    44  		lifetime: "3600s",
    45  	}
    46  
    47  	its.delegates = make([]string, len(config.Delegates))
    48  	for i, v := range config.Delegates {
    49  		its.delegates[i] = formatIAMServiceAccountName(v)
    50  	}
    51  	its.scopes = make([]string, len(config.Scopes))
    52  	copy(its.scopes, config.Scopes)
    53  
    54  	return oauth2.ReuseTokenSource(nil, its), nil
    55  }
    56  
    57  func formatIAMServiceAccountName(name string) string {
    58  	return fmt.Sprintf("projects/-/serviceAccounts/%s", name)
    59  }
    60  
    61  type generateAccessTokenReq struct {
    62  	Delegates []string `json:"delegates,omitempty"`
    63  	Lifetime  string   `json:"lifetime,omitempty"`
    64  	Scope     []string `json:"scope,omitempty"`
    65  }
    66  
    67  type generateAccessTokenResp struct {
    68  	AccessToken string `json:"accessToken"`
    69  	ExpireTime  string `json:"expireTime"`
    70  }
    71  
    72  type impersonatedTokenSource struct {
    73  	ctx context.Context
    74  	ts  oauth2.TokenSource
    75  
    76  	name      string
    77  	lifetime  string
    78  	scopes    []string
    79  	delegates []string
    80  }
    81  
    82  // Token returns an impersonated Token.
    83  func (i impersonatedTokenSource) Token() (*oauth2.Token, error) {
    84  	hc := oauth2.NewClient(i.ctx, i.ts)
    85  	reqBody := generateAccessTokenReq{
    86  		Delegates: i.delegates,
    87  		Lifetime:  i.lifetime,
    88  		Scope:     i.scopes,
    89  	}
    90  	b, err := json.Marshal(reqBody)
    91  	if err != nil {
    92  		return nil, fmt.Errorf("impersonate: unable to marshal request: %v", err)
    93  	}
    94  	url := fmt.Sprintf("https://iamcredentials.googleapis.com/v1/%s:generateAccessToken", i.name)
    95  	req, err := http.NewRequest("POST", url, bytes.NewReader(b))
    96  	if err != nil {
    97  		return nil, fmt.Errorf("impersonate: unable to create request: %v", err)
    98  	}
    99  	req = req.WithContext(i.ctx)
   100  	req.Header.Set("Content-Type", "application/json")
   101  
   102  	resp, err := hc.Do(req)
   103  	if err != nil {
   104  		return nil, fmt.Errorf("impersonate: unable to generate access token: %v", err)
   105  	}
   106  	defer resp.Body.Close()
   107  	body, err := io.ReadAll(io.LimitReader(resp.Body, 1<<20))
   108  	if err != nil {
   109  		return nil, fmt.Errorf("impersonate: unable to read body: %v", err)
   110  	}
   111  	if c := resp.StatusCode; c < 200 || c > 299 {
   112  		return nil, fmt.Errorf("impersonate: status code %d: %s", c, body)
   113  	}
   114  
   115  	var accessTokenResp generateAccessTokenResp
   116  	if err := json.Unmarshal(body, &accessTokenResp); err != nil {
   117  		return nil, fmt.Errorf("impersonate: unable to parse response: %v", err)
   118  	}
   119  	expiry, err := time.Parse(time.RFC3339, accessTokenResp.ExpireTime)
   120  	if err != nil {
   121  		return nil, fmt.Errorf("impersonate: unable to parse expiry: %v", err)
   122  	}
   123  	return &oauth2.Token{
   124  		AccessToken: accessTokenResp.AccessToken,
   125  		Expiry:      expiry,
   126  	}, nil
   127  }
   128  

View as plain text