go.etcd.io/etcd@v3.3.27+incompatible/auth/jwt.go (about)

     1  // Copyright 2017 The etcd Authors
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package auth
    16  
    17  import (
    18  	"context"
    19  	"crypto/rsa"
    20  	"io/ioutil"
    21  
    22  	jwt "github.com/dgrijalva/jwt-go"
    23  )
    24  
    25  type tokenJWT struct {
    26  	signMethod string
    27  	signKey    *rsa.PrivateKey
    28  	verifyKey  *rsa.PublicKey
    29  }
    30  
    31  func (t *tokenJWT) enable()                         {}
    32  func (t *tokenJWT) disable()                        {}
    33  func (t *tokenJWT) invalidateUser(string)           {}
    34  func (t *tokenJWT) genTokenPrefix() (string, error) { return "", nil }
    35  
    36  func (t *tokenJWT) info(ctx context.Context, token string, rev uint64) (*AuthInfo, bool) {
    37  	// rev isn't used in JWT, it is only used in simple token
    38  	var (
    39  		username string
    40  		revision uint64
    41  	)
    42  
    43  	parsed, err := jwt.Parse(token, func(token *jwt.Token) (interface{}, error) {
    44  		return t.verifyKey, nil
    45  	})
    46  
    47  	switch err.(type) {
    48  	case nil:
    49  		if !parsed.Valid {
    50  			plog.Warningf("invalid jwt token: %s", token)
    51  			return nil, false
    52  		}
    53  
    54  		claims := parsed.Claims.(jwt.MapClaims)
    55  
    56  		username = claims["username"].(string)
    57  		revision = uint64(claims["revision"].(float64))
    58  	default:
    59  		plog.Warningf("failed to parse jwt token: %s", err)
    60  		return nil, false
    61  	}
    62  
    63  	return &AuthInfo{Username: username, Revision: revision}, true
    64  }
    65  
    66  func (t *tokenJWT) assign(ctx context.Context, username string, revision uint64) (string, error) {
    67  	// Future work: let a jwt token include permission information would be useful for
    68  	// permission checking in proxy side.
    69  	tk := jwt.NewWithClaims(jwt.GetSigningMethod(t.signMethod),
    70  		jwt.MapClaims{
    71  			"username": username,
    72  			"revision": revision,
    73  		})
    74  
    75  	token, err := tk.SignedString(t.signKey)
    76  	if err != nil {
    77  		plog.Debugf("failed to sign jwt token: %s", err)
    78  		return "", err
    79  	}
    80  
    81  	plog.Debugf("jwt token: %s", token)
    82  
    83  	return token, err
    84  }
    85  
    86  func prepareOpts(opts map[string]string) (jwtSignMethod, jwtPubKeyPath, jwtPrivKeyPath string, err error) {
    87  	for k, v := range opts {
    88  		switch k {
    89  		case "sign-method":
    90  			jwtSignMethod = v
    91  		case "pub-key":
    92  			jwtPubKeyPath = v
    93  		case "priv-key":
    94  			jwtPrivKeyPath = v
    95  		default:
    96  			plog.Errorf("unknown token specific option: %s", k)
    97  			return "", "", "", ErrInvalidAuthOpts
    98  		}
    99  	}
   100  	if len(jwtSignMethod) == 0 {
   101  		return "", "", "", ErrInvalidAuthOpts
   102  	}
   103  	return jwtSignMethod, jwtPubKeyPath, jwtPrivKeyPath, nil
   104  }
   105  
   106  func newTokenProviderJWT(opts map[string]string) (*tokenJWT, error) {
   107  	jwtSignMethod, jwtPubKeyPath, jwtPrivKeyPath, err := prepareOpts(opts)
   108  	if err != nil {
   109  		return nil, ErrInvalidAuthOpts
   110  	}
   111  
   112  	t := &tokenJWT{}
   113  
   114  	t.signMethod = jwtSignMethod
   115  
   116  	verifyBytes, err := ioutil.ReadFile(jwtPubKeyPath)
   117  	if err != nil {
   118  		plog.Errorf("failed to read public key (%s) for jwt: %s", jwtPubKeyPath, err)
   119  		return nil, err
   120  	}
   121  	t.verifyKey, err = jwt.ParseRSAPublicKeyFromPEM(verifyBytes)
   122  	if err != nil {
   123  		plog.Errorf("failed to parse public key (%s): %s", jwtPubKeyPath, err)
   124  		return nil, err
   125  	}
   126  
   127  	signBytes, err := ioutil.ReadFile(jwtPrivKeyPath)
   128  	if err != nil {
   129  		plog.Errorf("failed to read private key (%s) for jwt: %s", jwtPrivKeyPath, err)
   130  		return nil, err
   131  	}
   132  	t.signKey, err = jwt.ParseRSAPrivateKeyFromPEM(signBytes)
   133  	if err != nil {
   134  		plog.Errorf("failed to parse private key (%s): %s", jwtPrivKeyPath, err)
   135  		return nil, err
   136  	}
   137  
   138  	return t, nil
   139  }