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 }