Skip to content

Commit e19e35f

Browse files
committed
Add DelegateTokenSource
1 parent 9dcd33a commit e19e35f

File tree

1 file changed

+117
-0
lines changed

1 file changed

+117
-0
lines changed

google/delegate.go

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
// Copyright 2018 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 google
6+
7+
import (
8+
"context"
9+
"fmt"
10+
"strconv"
11+
"sync"
12+
"time"
13+
14+
"golang.org/x/oauth2"
15+
"google.golang.org/api/iamcredentials/v1"
16+
)
17+
18+
// DelegateTokenSource allows a TokenSource issued to a user or
19+
// service account to impersonate another. The target service account
20+
// must grant the orginating credential principal the
21+
// "Service Account Token Creator" IAM role:
22+
// https://cloud.google.com/iam/docs/service-accounts#the_service_account_token_creator_role
23+
//
24+
// rootSource (TokenSource): The root TokenSource
25+
// used as to acquire the delegated identity TokenSource.
26+
// rootSource *must* include scopes that includes
27+
// "https://www.googleapis.com/auth/iam"
28+
// principal (string): The service account to impersonate.
29+
// new_scopes ([]string): Scopes to request during the
30+
// authorization grant.
31+
// delegates ([]string): The chained list of delegates required
32+
// to grant the final access_token.
33+
// lifetime (int): Number of seconds the delegated credential should
34+
// be valid for (upto 3600).
35+
//
36+
// Usage:
37+
// principal := "[email protected]"
38+
// lifetime := 30
39+
// delegates := []string{}
40+
// newScopes := []string{storage.ScopeReadOnly}
41+
// rootTokenSource, err := google.DefaultTokenSource(ctx,
42+
// "https://www.googleapis.com/auth/iam")
43+
// delegatetokenSource, err := google.DelegateTokenSource(ctx,
44+
// rootTokenSource,
45+
// principal, lifetime, delegates, newScopes)
46+
// storeageClient, _ = storage.NewClient(ctx,
47+
// option.WithTokenSource(delegatetokenSource))
48+
49+
// Note that this is not a standard OAuth flow, but rather uses Google Cloud
50+
// IAMCredentials API to exchange one oauth token for an impersonated account
51+
// see: https://cloud.google.com/iam/credentials/reference/rest/v1/projects.serviceAccounts/generateAccessToken
52+
func DelegateTokenSource(ctx context.Context, rootSource oauth2.TokenSource,
53+
principal string, lifetime int, delegates []string,
54+
newScopes []string) (oauth2.TokenSource, error) {
55+
56+
return &delegateTokenSource{
57+
ctx: ctx,
58+
rootSource: rootSource,
59+
principal: principal,
60+
lifetime: strconv.Itoa(lifetime) + "s",
61+
delegates: delegates,
62+
newScopes: newScopes,
63+
}, nil
64+
}
65+
66+
type delegateTokenSource struct {
67+
ctx context.Context
68+
rootSource oauth2.TokenSource
69+
principal string
70+
lifetime string
71+
delegates []string
72+
newScopes []string
73+
}
74+
75+
var (
76+
mu sync.Mutex
77+
tok *oauth2.Token
78+
)
79+
80+
func (ts *delegateTokenSource) Token() (*oauth2.Token, error) {
81+
82+
mu.Lock()
83+
defer mu.Unlock()
84+
85+
if tok.Valid() {
86+
return tok, nil
87+
}
88+
89+
client := oauth2.NewClient(context.Background(), ts.rootSource)
90+
91+
service, err := iamcredentials.New(client)
92+
if err != nil {
93+
return nil, fmt.Errorf("Error creating IAMCredentials: %v", err)
94+
}
95+
name := "projects/-/serviceAccounts/" + ts.principal
96+
tokenRequest := &iamcredentials.GenerateAccessTokenRequest{
97+
Lifetime: ts.lifetime,
98+
Delegates: ts.delegates,
99+
Scope: ts.newScopes,
100+
}
101+
at, err := service.Projects.ServiceAccounts.GenerateAccessToken(name, tokenRequest).Do()
102+
if err != nil {
103+
return nil, fmt.Errorf("Error calling GenerateAccessToken: %v", err)
104+
}
105+
106+
expireAt, err := time.Parse(time.RFC3339, at.ExpireTime)
107+
if err != nil {
108+
return nil, fmt.Errorf("Error parsing ExpireTime: %v", err)
109+
}
110+
111+
tok = &oauth2.Token{
112+
AccessToken: at.AccessToken,
113+
Expiry: expireAt,
114+
}
115+
116+
return tok, nil
117+
}

0 commit comments

Comments
 (0)