Chromium Code Reviews| Index: server/auth/delegation/minter.go |
| diff --git a/server/auth/delegation/minter.go b/server/auth/delegation/minter.go |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..d0468f5f76c6fe78fed24b632aadc55f58220064 |
| --- /dev/null |
| +++ b/server/auth/delegation/minter.go |
| @@ -0,0 +1,187 @@ |
| +// Copyright 2016 The LUCI Authors. All rights reserved. |
| +// Use of this source code is governed under the Apache License, Version 2.0 |
| +// that can be found in the LICENSE file. |
| + |
| +package delegation |
| + |
| +import ( |
| + "encoding/gob" |
| + "fmt" |
| + "time" |
| + |
| + "golang.org/x/net/context" |
| + |
| + "github.com/luci/luci-go/common/clock" |
| + "github.com/luci/luci-go/server/auth/identity" |
| + "github.com/luci/luci-go/server/auth/internal" |
| +) |
| + |
| +// TokenRequest describes parameters of a new delegation token. |
| +type TokenRequest struct { |
| + // AuthServiceURL is root URL (e.g. https://<host>) of the service to use 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
|
| + // minting the delegation token. |
| + // |
| + // The token will be signed by the service's private key. Only services that |
| + // trust this auth service would be able to accept the new token. |
| + // |
| + // Required. |
| + AuthServiceURL string |
| + |
| + // 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.
|
| + // |
| + // Only clients that can prove they are intended audience (e.g. by presenting |
| + // valid access token) would be able to use the delegation token. |
| + // |
| + // Optional. If both Audience and AudienceGroups are empty, the token is |
| + // 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.
|
| + Audience []identity.Identity |
| + |
| + // AudienceGroups can be used to specify a group (or a bunch of groups) as |
| + // a target audience of the token. |
| + // |
| + // It works in addition to Audience. |
| + // |
| + // Optional. If both Audience and AudienceGroups are empty, the token is |
| + // usable by any bearer. |
| + AudienceGroups []string |
| + |
| + // Services is a list of 'service:...' identities with services that accept |
| + // the token. |
| + // |
| + // This can be used to limit the scope of the token. |
| + // |
| + // Optional. If empty, the token is accepted by any LUCI service. |
| + 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.
|
| + |
| + // Impersonate defines on whose behalf the token is being created. |
| + // |
| + // By default the token delegates an identity of whoever requested it. This |
| + // identity is extracted from credentials that accompany token minting request |
| + // (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
|
| + // |
| + // By using 'Impersonate' a caller can make a token that delegates someone |
| + // else's identity, effectively producing an impersonation token. |
| + // |
| + // Only limited set of callers can do this. The set of who can be impersonated |
| + // is also limited. The rules are enforced by the auth service. |
| + Impersonate identity.Identity |
| + |
| + // ValidityDuration defines for how long the token would be valid. |
| + // |
| + // Maximum theoretical TTL is limited by the lifetime of the signing key of |
| + // the auth service (~= 24 hours). Minimum acceptable value is 30 sec. |
| + // |
| + // Required. |
| + ValidityDuration time.Duration |
| + |
| + // Intent is a reason why the token is created. |
| + // |
| + // Used only for logging purposes on the auth service, will be indexed. Should |
| + // 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
|
| + // |
| + // Optional. |
| + Intent string |
| +} |
| + |
| +// Token is actual delegation token with its expiration time and ID. |
| +type Token struct { |
| + Token string // base64-encoded URL-safe blob with the token |
| + Expiry time.Time // UTC time when it expires (also encoded in Token) |
| + SubtokenID string // identifier of the token (also encoded in Token) |
| +} |
| + |
| +// CreateToken makes a request to the auth service to generate the token. |
| +// |
| +// If uses current service's credentials to authenticate the request. |
| +// |
| +// If req.Impersonate is not used, the identity encoded in the authentication |
| +// credentials will be delegated by the token, otherwise the service will check |
| +// that caller is allowed to do the impersonation and will return a token that |
| +// delegates the identity specified by req.Impersonate. |
| +func CreateToken(c context.Context, req TokenRequest) (*Token, error) { |
| + // See https://github.com/luci/luci-py/blob/master/appengine/auth_service/delegation.py. |
| + var params struct { |
| + Audience []string `json:"audience,omitempty"` |
| + Services []string `json:"services,omitempty"` |
| + ValidityDuration int `json:"validity_duration"` |
| + Impersonate string `json:"impersonate,omitempty"` |
| + Intent string `json:"intent,omitempty"` |
| + } |
| + |
| + // Audience. |
| + params.Audience = make([]string, 0, len(req.Audience)+len(req.AudienceGroups)) |
| + for _, aud := range req.Audience { |
| + if err := aud.Validate(); err != nil { |
| + return nil, err |
| + } |
| + params.Audience = append(params.Audience, string(aud)) |
| + } |
| + for _, group := range req.AudienceGroups { |
| + params.Audience = append(params.Audience, "group:"+group) |
| + } |
| + if len(params.Audience) == 0 { |
| + params.Audience = []string{"*"} // means "Any bearer" |
| + } |
| + |
| + // Services. |
| + params.Services = make([]string, 0, len(req.Services)) |
| + for _, srv := range req.Services { |
| + if err := srv.Validate(); err != nil { |
| + return nil, err |
| + } |
| + params.Services = append(params.Services, string(srv)) |
| + } |
| + if len(params.Services) == 0 { |
| + params.Services = []string{"*"} // means "Any service" |
| + } |
| + |
| + // Validity duration. |
| + params.ValidityDuration = int(req.ValidityDuration / time.Second) |
| + if params.ValidityDuration < 30 { |
| + return nil, fmt.Errorf("ValidityDuration must be >= 30 sec, got %s", req.ValidityDuration) |
| + } |
| + if params.ValidityDuration > 24*3600 { |
| + return nil, fmt.Errorf("ValidityDuration must be <= 24h, got %s", req.ValidityDuration) |
| + } |
| + |
| + // The rest of the fields. |
| + if req.Impersonate != "" { |
| + if err := req.Impersonate.Validate(); err != nil { |
| + return nil, err |
| + } |
| + params.Impersonate = string(req.Impersonate) |
| + } |
| + params.Intent = req.Intent |
| + |
| + var response struct { |
| + DelegationToken string `json:"delegation_token,omitempty"` |
| + ValidityDuration int `json:"validity_duration,omitempty"` |
| + SubtokenID string `json:"subtoken_id,omitempty"` |
| + Text string `json:"text,omitempty"` // for error responses |
| + } |
| + |
| + httpReq := internal.Request{ |
| + Method: "POST", |
| + URL: req.AuthServiceURL + "/auth_service/api/v1/delegation/token/create", |
| + Scopes: []string{"https://www.googleapis.com/auth/userinfo.email"}, |
| + Body: ¶ms, |
| + Out: &response, |
| + } |
| + if err := httpReq.Do(c); err != nil { |
| + if response.Text != "" { |
| + err = fmt.Errorf("%s - %s", err, response.Text) |
| + } |
| + return nil, err |
| + } |
| + |
| + return &Token{ |
| + Token: response.DelegationToken, |
| + Expiry: clock.Now(c).Add(time.Duration(response.ValidityDuration) * time.Second).UTC(), |
| + SubtokenID: response.SubtokenID, |
| + }, nil |
| +} |
| + |
| +func init() { |
| + // For token cache. |
| + gob.Register(Token{}) |
| +} |