Chromium Code Reviews| 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 | |
|
iannucci
2016/08/11 22:52:35
I think I almost asked this elsewhere, but deleted
Vadim Sh.
2016/08/11 23:25:37
Usually using https://<host> is less strings manip
| |
| 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. | |
|
iannucci
2016/08/11 22:52:35
IIUC, the Audience set (all identities in this sli
Vadim Sh.
2016/08/11 23:25:37
Correct.
| |
| 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 // Optional. If both Audience and AudienceGroups are empty, the token is | |
| 36 // usable by any bearer. | |
|
iannucci
2016/08/11 22:52:35
that's a bit awkward; if you somehow strip both of
Vadim Sh.
2016/08/11 23:25:37
Done.
| |
| 37 Audience []identity.Identity | |
| 38 | |
| 39 // AudienceGroups can be used to specify a group (or a bunch of groups) as | |
| 40 // a target audience of the token. | |
| 41 // | |
| 42 // It works in addition to Audience. | |
| 43 // | |
| 44 // Optional. If both Audience and AudienceGroups are empty, the token is | |
| 45 // usable by any bearer. | |
| 46 AudienceGroups []string | |
| 47 | |
| 48 // Services is a list of 'service:...' identities with services that acc ept | |
| 49 // the token. | |
| 50 // | |
| 51 // This can be used to limit the scope of the token. | |
| 52 // | |
| 53 // Optional. If empty, the token is accepted by any LUCI service. | |
| 54 Services []identity.Identity | |
|
iannucci
2016/08/11 22:52:35
Yeah I'm not super happy with go's zero-value for
Vadim Sh.
2016/08/11 23:25:37
Done.
| |
| 55 | |
| 56 // Impersonate defines on whose behalf the token is being created. | |
| 57 // | |
| 58 // By default the token delegates an identity of whoever requested it. T his | |
| 59 // identity is extracted from credentials that accompany token minting r equest | |
| 60 // (e.g. OAuth access token). | |
|
iannucci
2016/08/11 22:52:35
could we ever run into a situation where this extr
Vadim Sh.
2016/08/11 23:25:37
No, auth_service enforces that user is not using d
| |
| 61 // | |
| 62 // By using 'Impersonate' a caller can make a token that delegates someo ne | |
| 63 // else's identity, effectively producing an impersonation token. | |
| 64 // | |
| 65 // Only limited set of callers can do this. The set of who can be impers onated | |
| 66 // is also limited. The rules are enforced by the auth service. | |
| 67 Impersonate identity.Identity | |
| 68 | |
| 69 // ValidityDuration defines for how long the token would be valid. | |
| 70 // | |
| 71 // Maximum theoretical TTL is limited by the lifetime of the signing key of | |
| 72 // the auth service (~= 24 hours). Minimum acceptable value is 30 sec. | |
| 73 // | |
| 74 // Required. | |
| 75 ValidityDuration time.Duration | |
| 76 | |
| 77 // Intent is a reason why the token is created. | |
| 78 // | |
| 79 // Used only for logging purposes on the auth service, will be indexed. Should | |
| 80 // be a short identifier-like string. | |
|
iannucci
2016/08/11 22:52:35
Will it be publicly visible, or could you put priv
Vadim Sh.
2016/08/11 23:25:37
It is not encoded in the token body. It ends up in
| |
| 81 // | |
| 82 // Optional. | |
| 83 Intent string | |
| 84 } | |
| 85 | |
| 86 // Token is actual delegation token with its expiration time and ID. | |
| 87 type Token struct { | |
| 88 Token string // base64-encoded URL-safe blob with the token | |
| 89 Expiry time.Time // UTC time when it expires (also encoded in Token) | |
| 90 SubtokenID string // identifier of the token (also encoded in Token) | |
| 91 } | |
| 92 | |
| 93 // CreateToken makes a request to the auth service to generate the token. | |
| 94 // | |
| 95 // If uses current service's credentials to authenticate the request. | |
| 96 // | |
| 97 // If req.Impersonate is not used, the identity encoded in the authentication | |
| 98 // credentials will be delegated by the token, otherwise the service will check | |
| 99 // that caller is allowed to do the impersonation and will return a token that | |
| 100 // delegates the identity specified by req.Impersonate. | |
| 101 func CreateToken(c context.Context, req TokenRequest) (*Token, error) { | |
| 102 // See https://github.com/luci/luci-py/blob/master/appengine/auth_servic e/delegation.py. | |
| 103 var params struct { | |
| 104 Audience []string `json:"audience,omitempty"` | |
| 105 Services []string `json:"services,omitempty"` | |
| 106 ValidityDuration int `json:"validity_duration"` | |
| 107 Impersonate string `json:"impersonate,omitempty"` | |
| 108 Intent string `json:"intent,omitempty"` | |
| 109 } | |
| 110 | |
| 111 // Audience. | |
| 112 params.Audience = make([]string, 0, len(req.Audience)+len(req.AudienceGr oups)) | |
| 113 for _, aud := range req.Audience { | |
| 114 if err := aud.Validate(); err != nil { | |
| 115 return nil, err | |
| 116 } | |
| 117 params.Audience = append(params.Audience, string(aud)) | |
| 118 } | |
| 119 for _, group := range req.AudienceGroups { | |
| 120 params.Audience = append(params.Audience, "group:"+group) | |
| 121 } | |
| 122 if len(params.Audience) == 0 { | |
| 123 params.Audience = []string{"*"} // means "Any bearer" | |
| 124 } | |
| 125 | |
| 126 // Services. | |
| 127 params.Services = make([]string, 0, len(req.Services)) | |
| 128 for _, srv := range req.Services { | |
| 129 if err := srv.Validate(); err != nil { | |
| 130 return nil, err | |
| 131 } | |
| 132 params.Services = append(params.Services, string(srv)) | |
| 133 } | |
| 134 if len(params.Services) == 0 { | |
| 135 params.Services = []string{"*"} // means "Any service" | |
| 136 } | |
| 137 | |
| 138 // Validity duration. | |
| 139 params.ValidityDuration = int(req.ValidityDuration / time.Second) | |
| 140 if params.ValidityDuration < 30 { | |
| 141 return nil, fmt.Errorf("ValidityDuration must be >= 30 sec, got %s", req.ValidityDuration) | |
| 142 } | |
| 143 if params.ValidityDuration > 24*3600 { | |
| 144 return nil, fmt.Errorf("ValidityDuration must be <= 24h, got %s" , req.ValidityDuration) | |
| 145 } | |
| 146 | |
| 147 // The rest of the fields. | |
| 148 if req.Impersonate != "" { | |
| 149 if err := req.Impersonate.Validate(); err != nil { | |
| 150 return nil, err | |
| 151 } | |
| 152 params.Impersonate = string(req.Impersonate) | |
| 153 } | |
| 154 params.Intent = req.Intent | |
| 155 | |
| 156 var response struct { | |
| 157 DelegationToken string `json:"delegation_token,omitempty"` | |
| 158 ValidityDuration int `json:"validity_duration,omitempty"` | |
| 159 SubtokenID string `json:"subtoken_id,omitempty"` | |
| 160 Text string `json:"text,omitempty"` // for error res ponses | |
| 161 } | |
| 162 | |
| 163 httpReq := internal.Request{ | |
| 164 Method: "POST", | |
| 165 URL: req.AuthServiceURL + "/auth_service/api/v1/delegation/to ken/create", | |
| 166 Scopes: []string{"https://www.googleapis.com/auth/userinfo.email "}, | |
| 167 Body: ¶ms, | |
| 168 Out: &response, | |
| 169 } | |
| 170 if err := httpReq.Do(c); err != nil { | |
| 171 if response.Text != "" { | |
| 172 err = fmt.Errorf("%s - %s", err, response.Text) | |
| 173 } | |
| 174 return nil, err | |
| 175 } | |
| 176 | |
| 177 return &Token{ | |
| 178 Token: response.DelegationToken, | |
| 179 Expiry: clock.Now(c).Add(time.Duration(response.ValidityDura tion) * time.Second).UTC(), | |
| 180 SubtokenID: response.SubtokenID, | |
| 181 }, nil | |
| 182 } | |
| 183 | |
| 184 func init() { | |
| 185 // For token cache. | |
| 186 gob.Register(Token{}) | |
| 187 } | |
| OLD | NEW |