OLD | NEW |
(Empty) | |
| 1 // Copyright 2016 The LUCI Authors. All rights reserved. |
| 2 // Use of this source code is governed under the Apache License, Version 2.0 |
| 3 // that can be found in the LICENSE file. |
| 4 |
| 5 package delegation |
| 6 |
| 7 import ( |
| 8 "encoding/gob" |
| 9 "fmt" |
| 10 "time" |
| 11 |
| 12 "golang.org/x/net/context" |
| 13 |
| 14 "github.com/luci/luci-go/common/clock" |
| 15 "github.com/luci/luci-go/server/auth/identity" |
| 16 "github.com/luci/luci-go/server/auth/internal" |
| 17 ) |
| 18 |
| 19 // TokenRequest describes parameters of a new delegation token. |
| 20 type TokenRequest struct { |
| 21 // AuthServiceURL is root URL (e.g. https://<host>) of the service to us
e for |
| 22 // minting the delegation token. |
| 23 // |
| 24 // The token will be signed by the service's private key. Only services
that |
| 25 // trust this auth service would be able to accept the new token. |
| 26 // |
| 27 // Required. |
| 28 AuthServiceURL string |
| 29 |
| 30 // Audience is to whom caller's identity is delegated. |
| 31 // |
| 32 // Only clients that can prove they are intended audience (e.g. by prese
nting |
| 33 // valid access token) would be able to use the delegation token. |
| 34 // |
| 35 // Must be empty if UnlimitedAudience is true. |
| 36 Audience []identity.Identity |
| 37 |
| 38 // AudienceGroups can be used to specify a group (or a bunch of groups)
as |
| 39 // a target audience of the token. |
| 40 // |
| 41 // It works in addition to Audience. |
| 42 // |
| 43 // Must be empty if UnlimitedAudience is true. |
| 44 AudienceGroups []string |
| 45 |
| 46 // UnlimitedAudience, if true, indicates that the delegation token can b
e |
| 47 // used by any bearer. |
| 48 // |
| 49 // Tokens with unlimited audience are roughly as powerful as OAuth acces
s |
| 50 // tokens and should be used only if absolutely necessary. |
| 51 // |
| 52 // Prefer to limit token's audience as much as possible. See Audience an
d |
| 53 // AudienceGroups fields above. |
| 54 // |
| 55 // If UnlimitedAudience is true, Audience and AudienceGroups must be emp
ty. |
| 56 UnlimitedAudience bool |
| 57 |
| 58 // TargetServices is a list of 'service:...' identities with services th
at |
| 59 // accept the token. |
| 60 // |
| 61 // This can be used to limit the scope of the token. |
| 62 // |
| 63 // Must be empty if Untargeted is true. |
| 64 TargetServices []identity.Identity |
| 65 |
| 66 // Untargeted, if true, indicates that the delegation token should be ac
cepted |
| 67 // by any LUCI service. |
| 68 // |
| 69 // Use this if you are preparing a token in advance, not yet knowing whe
re it |
| 70 // is going to be used. |
| 71 // |
| 72 // If Untargeted is true, TargetServices must be empty. |
| 73 Untargeted bool |
| 74 |
| 75 // Impersonate defines on whose behalf the token is being created. |
| 76 // |
| 77 // By default the token delegates an identity of whoever requested it. T
his |
| 78 // identity is extracted from credentials that accompany token minting r
equest |
| 79 // (e.g. OAuth access token). |
| 80 // |
| 81 // By using 'Impersonate' a caller can make a token that delegates someo
ne |
| 82 // else's identity, effectively producing an impersonation token. |
| 83 // |
| 84 // Only limited set of callers can do this. The set of who can be impers
onated |
| 85 // is also limited. The rules are enforced by the auth service. |
| 86 Impersonate identity.Identity |
| 87 |
| 88 // ValidityDuration defines for how long the token would be valid. |
| 89 // |
| 90 // Maximum theoretical TTL is limited by the lifetime of the signing key
of |
| 91 // the auth service (~= 24 hours). Minimum acceptable value is 30 sec. |
| 92 // |
| 93 // Required. |
| 94 ValidityDuration time.Duration |
| 95 |
| 96 // Intent is a reason why the token is created. |
| 97 // |
| 98 // Used only for logging purposes on the auth service, will be indexed.
Should |
| 99 // be a short identifier-like string. |
| 100 // |
| 101 // Optional. |
| 102 Intent string |
| 103 } |
| 104 |
| 105 // Token is actual delegation token with its expiration time and ID. |
| 106 type Token struct { |
| 107 Token string // base64-encoded URL-safe blob with the token |
| 108 Expiry time.Time // UTC time when it expires (also encoded in Token) |
| 109 SubtokenID string // identifier of the token (also encoded in Token) |
| 110 } |
| 111 |
| 112 // CreateToken makes a request to the auth service to generate the token. |
| 113 // |
| 114 // If uses current service's credentials to authenticate the request. |
| 115 // |
| 116 // If req.Impersonate is not used, the identity encoded in the authentication |
| 117 // credentials will be delegated by the token, otherwise the service will check |
| 118 // that caller is allowed to do the impersonation and will return a token that |
| 119 // delegates the identity specified by req.Impersonate. |
| 120 func CreateToken(c context.Context, req TokenRequest) (*Token, error) { |
| 121 // See https://github.com/luci/luci-py/blob/master/appengine/auth_servic
e/delegation.py. |
| 122 var params struct { |
| 123 Audience []string `json:"audience,omitempty"` |
| 124 Services []string `json:"services,omitempty"` |
| 125 ValidityDuration int `json:"validity_duration"` |
| 126 Impersonate string `json:"impersonate,omitempty"` |
| 127 Intent string `json:"intent,omitempty"` |
| 128 } |
| 129 |
| 130 // Audience. |
| 131 params.Audience = make([]string, 0, len(req.Audience)+len(req.AudienceGr
oups)) |
| 132 for _, aud := range req.Audience { |
| 133 if err := aud.Validate(); err != nil { |
| 134 return nil, err |
| 135 } |
| 136 params.Audience = append(params.Audience, string(aud)) |
| 137 } |
| 138 for _, group := range req.AudienceGroups { |
| 139 params.Audience = append(params.Audience, "group:"+group) |
| 140 } |
| 141 switch { |
| 142 case req.UnlimitedAudience && len(params.Audience) != 0: |
| 143 return nil, fmt.Errorf("delegation: can't specify audience for U
nlimitedAudience=true token") |
| 144 case !req.UnlimitedAudience && len(params.Audience) == 0: |
| 145 return nil, fmt.Errorf("delegation: either Audience/AudienceGrou
ps or UnlimitedAudience=true are required") |
| 146 } |
| 147 if req.UnlimitedAudience { |
| 148 params.Audience = []string{"*"} |
| 149 } |
| 150 |
| 151 // Services. |
| 152 params.Services = make([]string, 0, len(req.TargetServices)) |
| 153 for _, srv := range req.TargetServices { |
| 154 if err := srv.Validate(); err != nil { |
| 155 return nil, err |
| 156 } |
| 157 params.Services = append(params.Services, string(srv)) |
| 158 } |
| 159 switch { |
| 160 case req.Untargeted && len(params.Services) != 0: |
| 161 return nil, fmt.Errorf("delegation: can't specify TargetServices
for Untargeted=true token") |
| 162 case !req.Untargeted && len(params.Services) == 0: |
| 163 return nil, fmt.Errorf("delegation: either TargetServices or Unt
argeted=true are required") |
| 164 } |
| 165 if req.Untargeted { |
| 166 params.Services = []string{"*"} |
| 167 } |
| 168 |
| 169 // Validity duration. |
| 170 params.ValidityDuration = int(req.ValidityDuration / time.Second) |
| 171 if params.ValidityDuration < 30 { |
| 172 return nil, fmt.Errorf("ValidityDuration must be >= 30 sec, got
%s", req.ValidityDuration) |
| 173 } |
| 174 if params.ValidityDuration > 24*3600 { |
| 175 return nil, fmt.Errorf("ValidityDuration must be <= 24h, got %s"
, req.ValidityDuration) |
| 176 } |
| 177 |
| 178 // The rest of the fields. |
| 179 if req.Impersonate != "" { |
| 180 if err := req.Impersonate.Validate(); err != nil { |
| 181 return nil, err |
| 182 } |
| 183 params.Impersonate = string(req.Impersonate) |
| 184 } |
| 185 params.Intent = req.Intent |
| 186 |
| 187 var response struct { |
| 188 DelegationToken string `json:"delegation_token,omitempty"` |
| 189 ValidityDuration int `json:"validity_duration,omitempty"` |
| 190 SubtokenID string `json:"subtoken_id,omitempty"` |
| 191 Text string `json:"text,omitempty"` // for error res
ponses |
| 192 } |
| 193 |
| 194 httpReq := internal.Request{ |
| 195 Method: "POST", |
| 196 URL: req.AuthServiceURL + "/auth_service/api/v1/delegation/to
ken/create", |
| 197 Scopes: []string{"https://www.googleapis.com/auth/userinfo.email
"}, |
| 198 Body: ¶ms, |
| 199 Out: &response, |
| 200 } |
| 201 if err := httpReq.Do(c); err != nil { |
| 202 if response.Text != "" { |
| 203 err = fmt.Errorf("%s - %s", err, response.Text) |
| 204 } |
| 205 return nil, err |
| 206 } |
| 207 |
| 208 return &Token{ |
| 209 Token: response.DelegationToken, |
| 210 Expiry: clock.Now(c).Add(time.Duration(response.ValidityDura
tion) * time.Second).UTC(), |
| 211 SubtokenID: response.SubtokenID, |
| 212 }, nil |
| 213 } |
| 214 |
| 215 func init() { |
| 216 // For token cache. |
| 217 gob.Register(Token{}) |
| 218 } |
OLD | NEW |