blob: e6aad1857d05607843d82b12fa04c9823aa5678f [file] [log] [blame]
Abhay Kumar40252eb2025-10-13 13:25:53 +00001// 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/ecdsa"
20 "crypto/ed25519"
21 "crypto/rsa"
22 "errors"
23 "time"
24
25 "github.com/golang-jwt/jwt/v5"
26 "go.uber.org/zap"
27)
28
29type tokenJWT struct {
30 lg *zap.Logger
31 signMethod jwt.SigningMethod
32 key any
33 ttl time.Duration
34 verifyOnly bool
35}
36
37func (t *tokenJWT) enable() {}
38func (t *tokenJWT) disable() {}
39func (t *tokenJWT) invalidateUser(string) {}
40func (t *tokenJWT) genTokenPrefix() (string, error) { return "", nil }
41
42func (t *tokenJWT) info(ctx context.Context, token string, rev uint64) (*AuthInfo, bool) {
43 // rev isn't used in JWT, it is only used in simple token
44 var (
45 username string
46 revision float64
47 )
48
49 parsed, err := jwt.Parse(token, func(token *jwt.Token) (any, error) {
50 if token.Method.Alg() != t.signMethod.Alg() {
51 return nil, errors.New("invalid signing method")
52 }
53 switch k := t.key.(type) {
54 case *rsa.PrivateKey:
55 return &k.PublicKey, nil
56 case *ecdsa.PrivateKey:
57 return &k.PublicKey, nil
58 case ed25519.PrivateKey:
59 return k.Public(), nil
60 default:
61 return t.key, nil
62 }
63 })
64 if err != nil {
65 t.lg.Warn(
66 "failed to parse a JWT token",
67 zap.Error(err),
68 )
69 return nil, false
70 }
71
72 claims, ok := parsed.Claims.(jwt.MapClaims)
73 if !parsed.Valid || !ok {
74 t.lg.Warn("failed to obtain claims from a JWT token")
75 return nil, false
76 }
77
78 username, ok = claims["username"].(string)
79 if !ok {
80 t.lg.Warn("failed to obtain user claims from jwt token")
81 return nil, false
82 }
83
84 revision, ok = claims["revision"].(float64)
85 if !ok {
86 t.lg.Warn("failed to obtain revision claims from jwt token")
87 return nil, false
88 }
89
90 return &AuthInfo{Username: username, Revision: uint64(revision)}, true
91}
92
93func (t *tokenJWT) assign(ctx context.Context, username string, revision uint64) (string, error) {
94 if t.verifyOnly {
95 return "", ErrVerifyOnly
96 }
97
98 // Future work: let a jwt token include permission information would be useful for
99 // permission checking in proxy side.
100 tk := jwt.NewWithClaims(t.signMethod,
101 jwt.MapClaims{
102 "username": username,
103 "revision": revision,
104 "exp": time.Now().Add(t.ttl).Unix(),
105 })
106
107 token, err := tk.SignedString(t.key)
108 if err != nil {
109 t.lg.Debug(
110 "failed to sign a JWT token",
111 zap.String("user-name", username),
112 zap.Uint64("revision", revision),
113 zap.Error(err),
114 )
115 return "", err
116 }
117
118 t.lg.Debug(
119 "created/assigned a new JWT token",
120 zap.String("user-name", username),
121 zap.Uint64("revision", revision),
122 zap.String("token", token),
123 )
124 return token, err
125}
126
127func newTokenProviderJWT(lg *zap.Logger, optMap map[string]string) (*tokenJWT, error) {
128 if lg == nil {
129 lg = zap.NewNop()
130 }
131 var err error
132 var opts jwtOptions
133 err = opts.ParseWithDefaults(optMap)
134 if err != nil {
135 lg.Error("problem loading JWT options", zap.Error(err))
136 return nil, ErrInvalidAuthOpts
137 }
138
139 keys := make([]string, 0, len(optMap))
140 for k := range optMap {
141 if !knownOptions[k] {
142 keys = append(keys, k)
143 }
144 }
145 if len(keys) > 0 {
146 lg.Warn("unknown JWT options", zap.Strings("keys", keys))
147 }
148
149 key, err := opts.Key()
150 if err != nil {
151 return nil, err
152 }
153
154 t := &tokenJWT{
155 lg: lg,
156 ttl: opts.TTL,
157 signMethod: opts.SignMethod,
158 key: key,
159 }
160
161 switch t.signMethod.(type) {
162 case *jwt.SigningMethodECDSA:
163 if _, ok := t.key.(*ecdsa.PublicKey); ok {
164 t.verifyOnly = true
165 }
166 case *jwt.SigningMethodEd25519:
167 if _, ok := t.key.(ed25519.PublicKey); ok {
168 t.verifyOnly = true
169 }
170 case *jwt.SigningMethodRSA, *jwt.SigningMethodRSAPSS:
171 if _, ok := t.key.(*rsa.PublicKey); ok {
172 t.verifyOnly = true
173 }
174 }
175
176 return t, nil
177}