blob: 99b2d6b5c7143d12812aa9e35cf7cb9e0569d0cb [file] [log] [blame]
khenaidood948f772021-08-11 17:49:24 -04001// 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
15package auth
16
17import (
18 "context"
19 "crypto/rsa"
20 "io/ioutil"
21
22 jwt "github.com/dgrijalva/jwt-go"
23)
24
25type tokenJWT struct {
26 signMethod string
27 signKey *rsa.PrivateKey
28 verifyKey *rsa.PublicKey
29}
30
31func (t *tokenJWT) enable() {}
32func (t *tokenJWT) disable() {}
33func (t *tokenJWT) invalidateUser(string) {}
34func (t *tokenJWT) genTokenPrefix() (string, error) { return "", nil }
35
36func (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
66func (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
86func 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
106func 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}