// Copyright 2017 The etcd Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package auth import ( "context" "crypto/rsa" "io/ioutil" jwt "github.com/dgrijalva/jwt-go" ) type tokenJWT struct { signMethod string signKey *rsa.PrivateKey verifyKey *rsa.PublicKey } func (t *tokenJWT) enable() {} func (t *tokenJWT) disable() {} func (t *tokenJWT) invalidateUser(string) {} func (t *tokenJWT) genTokenPrefix() (string, error) { return "", nil } func (t *tokenJWT) info(ctx context.Context, token string, rev uint64) (*AuthInfo, bool) { // rev isn't used in JWT, it is only used in simple token var ( username string revision uint64 ) parsed, err := jwt.Parse(token, func(token *jwt.Token) (interface{}, error) { return t.verifyKey, nil }) switch err.(type) { case nil: if !parsed.Valid { plog.Warningf("invalid jwt token: %s", token) return nil, false } claims := parsed.Claims.(jwt.MapClaims) username = claims["username"].(string) revision = uint64(claims["revision"].(float64)) default: plog.Warningf("failed to parse jwt token: %s", err) return nil, false } return &AuthInfo{Username: username, Revision: revision}, true } func (t *tokenJWT) assign(ctx context.Context, username string, revision uint64) (string, error) { // Future work: let a jwt token include permission information would be useful for // permission checking in proxy side. tk := jwt.NewWithClaims(jwt.GetSigningMethod(t.signMethod), jwt.MapClaims{ "username": username, "revision": revision, }) token, err := tk.SignedString(t.signKey) if err != nil { plog.Debugf("failed to sign jwt token: %s", err) return "", err } plog.Debugf("jwt token: %s", token) return token, err } func prepareOpts(opts map[string]string) (jwtSignMethod, jwtPubKeyPath, jwtPrivKeyPath string, err error) { for k, v := range opts { switch k { case "sign-method": jwtSignMethod = v case "pub-key": jwtPubKeyPath = v case "priv-key": jwtPrivKeyPath = v default: plog.Errorf("unknown token specific option: %s", k) return "", "", "", ErrInvalidAuthOpts } } if len(jwtSignMethod) == 0 { return "", "", "", ErrInvalidAuthOpts } return jwtSignMethod, jwtPubKeyPath, jwtPrivKeyPath, nil } func newTokenProviderJWT(opts map[string]string) (*tokenJWT, error) { jwtSignMethod, jwtPubKeyPath, jwtPrivKeyPath, err := prepareOpts(opts) if err != nil { return nil, ErrInvalidAuthOpts } t := &tokenJWT{} t.signMethod = jwtSignMethod verifyBytes, err := ioutil.ReadFile(jwtPubKeyPath) if err != nil { plog.Errorf("failed to read public key (%s) for jwt: %s", jwtPubKeyPath, err) return nil, err } t.verifyKey, err = jwt.ParseRSAPublicKeyFromPEM(verifyBytes) if err != nil { plog.Errorf("failed to parse public key (%s): %s", jwtPubKeyPath, err) return nil, err } signBytes, err := ioutil.ReadFile(jwtPrivKeyPath) if err != nil { plog.Errorf("failed to read private key (%s) for jwt: %s", jwtPrivKeyPath, err) return nil, err } t.signKey, err = jwt.ParseRSAPrivateKeyFromPEM(signBytes) if err != nil { plog.Errorf("failed to parse private key (%s): %s", jwtPrivKeyPath, err) return nil, err } return t, nil }