Initial commit
This commit is contained in:
219
vendor/github.com/docker/distribution/contrib/token-server/token.go
generated
vendored
Normal file
219
vendor/github.com/docker/distribution/contrib/token-server/token.go
generated
vendored
Normal file
@@ -0,0 +1,219 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/rand"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/docker/distribution/context"
|
||||
"github.com/docker/distribution/registry/auth"
|
||||
"github.com/docker/distribution/registry/auth/token"
|
||||
"github.com/docker/libtrust"
|
||||
)
|
||||
|
||||
// ResolveScopeSpecifiers converts a list of scope specifiers from a token
|
||||
// request's `scope` query parameters into a list of standard access objects.
|
||||
func ResolveScopeSpecifiers(ctx context.Context, scopeSpecs []string) []auth.Access {
|
||||
requestedAccessSet := make(map[auth.Access]struct{}, 2*len(scopeSpecs))
|
||||
|
||||
for _, scopeSpecifier := range scopeSpecs {
|
||||
// There should be 3 parts, separated by a `:` character.
|
||||
parts := strings.SplitN(scopeSpecifier, ":", 3)
|
||||
|
||||
if len(parts) != 3 {
|
||||
context.GetLogger(ctx).Infof("ignoring unsupported scope format %s", scopeSpecifier)
|
||||
continue
|
||||
}
|
||||
|
||||
resourceType, resourceName, actions := parts[0], parts[1], parts[2]
|
||||
|
||||
resourceType, resourceClass := splitResourceClass(resourceType)
|
||||
if resourceType == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
// Actions should be a comma-separated list of actions.
|
||||
for _, action := range strings.Split(actions, ",") {
|
||||
requestedAccess := auth.Access{
|
||||
Resource: auth.Resource{
|
||||
Type: resourceType,
|
||||
Class: resourceClass,
|
||||
Name: resourceName,
|
||||
},
|
||||
Action: action,
|
||||
}
|
||||
|
||||
// Add this access to the requested access set.
|
||||
requestedAccessSet[requestedAccess] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
requestedAccessList := make([]auth.Access, 0, len(requestedAccessSet))
|
||||
for requestedAccess := range requestedAccessSet {
|
||||
requestedAccessList = append(requestedAccessList, requestedAccess)
|
||||
}
|
||||
|
||||
return requestedAccessList
|
||||
}
|
||||
|
||||
var typeRegexp = regexp.MustCompile(`^([a-z0-9]+)(\([a-z0-9]+\))?$`)
|
||||
|
||||
func splitResourceClass(t string) (string, string) {
|
||||
matches := typeRegexp.FindStringSubmatch(t)
|
||||
if len(matches) < 2 {
|
||||
return "", ""
|
||||
}
|
||||
if len(matches) == 2 || len(matches[2]) < 2 {
|
||||
return matches[1], ""
|
||||
}
|
||||
return matches[1], matches[2][1 : len(matches[2])-1]
|
||||
}
|
||||
|
||||
// ResolveScopeList converts a scope list from a token request's
|
||||
// `scope` parameter into a list of standard access objects.
|
||||
func ResolveScopeList(ctx context.Context, scopeList string) []auth.Access {
|
||||
scopes := strings.Split(scopeList, " ")
|
||||
return ResolveScopeSpecifiers(ctx, scopes)
|
||||
}
|
||||
|
||||
func scopeString(a auth.Access) string {
|
||||
if a.Class != "" {
|
||||
return fmt.Sprintf("%s(%s):%s:%s", a.Type, a.Class, a.Name, a.Action)
|
||||
}
|
||||
return fmt.Sprintf("%s:%s:%s", a.Type, a.Name, a.Action)
|
||||
}
|
||||
|
||||
// ToScopeList converts a list of access to a
|
||||
// scope list string
|
||||
func ToScopeList(access []auth.Access) string {
|
||||
var s []string
|
||||
for _, a := range access {
|
||||
s = append(s, scopeString(a))
|
||||
}
|
||||
return strings.Join(s, ",")
|
||||
}
|
||||
|
||||
// TokenIssuer represents an issuer capable of generating JWT tokens
|
||||
type TokenIssuer struct {
|
||||
Issuer string
|
||||
SigningKey libtrust.PrivateKey
|
||||
Expiration time.Duration
|
||||
}
|
||||
|
||||
// CreateJWT creates and signs a JSON Web Token for the given subject and
|
||||
// audience with the granted access.
|
||||
func (issuer *TokenIssuer) CreateJWT(subject string, audience string, grantedAccessList []auth.Access) (string, error) {
|
||||
// Make a set of access entries to put in the token's claimset.
|
||||
resourceActionSets := make(map[auth.Resource]map[string]struct{}, len(grantedAccessList))
|
||||
for _, access := range grantedAccessList {
|
||||
actionSet, exists := resourceActionSets[access.Resource]
|
||||
if !exists {
|
||||
actionSet = map[string]struct{}{}
|
||||
resourceActionSets[access.Resource] = actionSet
|
||||
}
|
||||
actionSet[access.Action] = struct{}{}
|
||||
}
|
||||
|
||||
accessEntries := make([]*token.ResourceActions, 0, len(resourceActionSets))
|
||||
for resource, actionSet := range resourceActionSets {
|
||||
actions := make([]string, 0, len(actionSet))
|
||||
for action := range actionSet {
|
||||
actions = append(actions, action)
|
||||
}
|
||||
|
||||
accessEntries = append(accessEntries, &token.ResourceActions{
|
||||
Type: resource.Type,
|
||||
Class: resource.Class,
|
||||
Name: resource.Name,
|
||||
Actions: actions,
|
||||
})
|
||||
}
|
||||
|
||||
randomBytes := make([]byte, 15)
|
||||
_, err := io.ReadFull(rand.Reader, randomBytes)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
randomID := base64.URLEncoding.EncodeToString(randomBytes)
|
||||
|
||||
now := time.Now()
|
||||
|
||||
signingHash := crypto.SHA256
|
||||
var alg string
|
||||
switch issuer.SigningKey.KeyType() {
|
||||
case "RSA":
|
||||
alg = "RS256"
|
||||
case "EC":
|
||||
alg = "ES256"
|
||||
default:
|
||||
panic(fmt.Errorf("unsupported signing key type %q", issuer.SigningKey.KeyType()))
|
||||
}
|
||||
|
||||
joseHeader := token.Header{
|
||||
Type: "JWT",
|
||||
SigningAlg: alg,
|
||||
}
|
||||
|
||||
if x5c := issuer.SigningKey.GetExtendedField("x5c"); x5c != nil {
|
||||
joseHeader.X5c = x5c.([]string)
|
||||
} else {
|
||||
var jwkMessage json.RawMessage
|
||||
jwkMessage, err = issuer.SigningKey.PublicKey().MarshalJSON()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
joseHeader.RawJWK = &jwkMessage
|
||||
}
|
||||
|
||||
exp := issuer.Expiration
|
||||
if exp == 0 {
|
||||
exp = 5 * time.Minute
|
||||
}
|
||||
|
||||
claimSet := token.ClaimSet{
|
||||
Issuer: issuer.Issuer,
|
||||
Subject: subject,
|
||||
Audience: audience,
|
||||
Expiration: now.Add(exp).Unix(),
|
||||
NotBefore: now.Unix(),
|
||||
IssuedAt: now.Unix(),
|
||||
JWTID: randomID,
|
||||
|
||||
Access: accessEntries,
|
||||
}
|
||||
|
||||
var (
|
||||
joseHeaderBytes []byte
|
||||
claimSetBytes []byte
|
||||
)
|
||||
|
||||
if joseHeaderBytes, err = json.Marshal(joseHeader); err != nil {
|
||||
return "", fmt.Errorf("unable to encode jose header: %s", err)
|
||||
}
|
||||
if claimSetBytes, err = json.Marshal(claimSet); err != nil {
|
||||
return "", fmt.Errorf("unable to encode claim set: %s", err)
|
||||
}
|
||||
|
||||
encodedJoseHeader := joseBase64Encode(joseHeaderBytes)
|
||||
encodedClaimSet := joseBase64Encode(claimSetBytes)
|
||||
encodingToSign := fmt.Sprintf("%s.%s", encodedJoseHeader, encodedClaimSet)
|
||||
|
||||
var signatureBytes []byte
|
||||
if signatureBytes, _, err = issuer.SigningKey.Sign(strings.NewReader(encodingToSign), signingHash); err != nil {
|
||||
return "", fmt.Errorf("unable to sign jwt payload: %s", err)
|
||||
}
|
||||
|
||||
signature := joseBase64Encode(signatureBytes)
|
||||
|
||||
return fmt.Sprintf("%s.%s", encodingToSign, signature), nil
|
||||
}
|
||||
|
||||
func joseBase64Encode(data []byte) string {
|
||||
return strings.TrimRight(base64.URLEncoding.EncodeToString(data), "=")
|
||||
}
|
||||
Reference in New Issue
Block a user