-
Notifications
You must be signed in to change notification settings - Fork 0
/
manager.go
185 lines (152 loc) · 5.34 KB
/
manager.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
// Copyright 2017 The Kubernetes 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 jwe
import (
"errors"
"time"
authApi "github.com/kubernetes/dashboard/src/app/backend/auth/api"
kdErrors "github.com/kubernetes/dashboard/src/app/backend/errors"
jose "gopkg.in/square/go-jose.v2"
"k8s.io/apimachinery/pkg/util/json"
"k8s.io/client-go/tools/clientcmd/api"
)
// Implements TokenManager interface
type jweTokenManager struct {
keyHolder KeyHolder
tokenTTL time.Duration
}
// AdditionalAuthData contains information required to validate token. It is integrity protected.
// For more information check: https://tools.ietf.org/html/rfc7516 (Chapter 2: Terminology)
type AdditionalAuthData map[Claim]string
// Claim represent token claims used in AAD header. For more information check:
// https://self-issued.info/docs/draft-ietf-oauth-json-web-token.html#rfc.section.4
type Claim string
const (
// Time format used when generating AAD header for token. Required to set token creation/expiration time.
timeFormat = time.RFC3339
// IAT claim is part of token AAD header. It represents token "issued at" time.
IAT Claim = "iat"
// EXP claim is part of token AAD header. It represents token expiration time.
EXP Claim = "exp"
)
// Generate and encrypt JWE token based on provided AuthInfo structure. AuthInfo will be embedded in a token payload and
// encrypted with autogenerated signing key.
func (self *jweTokenManager) Generate(authInfo api.AuthInfo) (string, error) {
marshalledAuthInfo, err := json.Marshal(authInfo)
if err != nil {
return "", err
}
jweObject, err := self.getEncrypter().EncryptWithAuthData(marshalledAuthInfo, self.generateAAD())
if err != nil {
return "", err
}
return jweObject.FullSerialize(), nil
}
// Decrypt provides token and returns AuthInfo structure saved in a token payload.
func (self *jweTokenManager) Decrypt(jweToken string) (*api.AuthInfo, error) {
jweTokenObject, err := self.validate(jweToken)
if err != nil {
return nil, err
}
decrypted, err := jweTokenObject.Decrypt(self.keyHolder.Key())
if err == jose.ErrCryptoFailure {
// Force key refresh and try to decrypt again
self.keyHolder.Refresh()
decrypted, err = jweTokenObject.Decrypt(self.keyHolder.Key())
}
if err != nil {
return nil, err
}
authInfo := new(api.AuthInfo)
err = json.Unmarshal(decrypted, authInfo)
return authInfo, err
}
// Refresh implements token manager interface. See TokenManager for more information.
func (self *jweTokenManager) Refresh(jweToken string) (string, error) {
if len(jweToken) == 0 {
return "", errors.New("Can not refresh token. No token provided.")
}
jweTokenObject, err := self.validate(jweToken)
if err != nil {
return "", err
}
decrypted, err := jweTokenObject.Decrypt(self.keyHolder.Key())
if err != nil {
return "", err
}
authInfo := new(api.AuthInfo)
err = json.Unmarshal(decrypted, authInfo)
if err != nil {
return "", errors.New("Token refresh error. Could not unmarshal token payload.")
}
return self.Generate(*authInfo)
}
// SetTokenTTL implements token manager interface. See TokenManager for more information.
func (self *jweTokenManager) SetTokenTTL(ttl time.Duration) {
if ttl < 0 {
ttl = 0
}
self.tokenTTL = ttl * time.Second
}
func (self *jweTokenManager) getEncrypter() jose.Encrypter {
return self.keyHolder.Encrypter()
}
// Parses and validates provided token to check if it hasn't been manipulated with.
func (self *jweTokenManager) validate(jweToken string) (*jose.JSONWebEncryption, error) {
jwe, err := jose.ParseEncrypted(jweToken)
if err != nil {
return nil, err
}
if self.tokenTTL > 0 {
aad := AdditionalAuthData{}
err = json.Unmarshal(jwe.GetAuthData(), &aad)
if err != nil {
return nil, errors.New("Token validation error. Could not unmarshal AAD.")
}
if self.isExpired(aad[IAT], aad[EXP]) {
return nil, errors.New(kdErrors.MSG_TOKEN_EXPIRED_ERROR)
}
}
return jwe, nil
}
// Returns true if token has expired. In case time could not be parsed it might mean that token was tampered with and
// token will be marked as expired. This will force user to log in again.
func (self *jweTokenManager) isExpired(iatStr, expStr string) bool {
iat, err := time.Parse(timeFormat, iatStr)
if err != nil {
return true
}
exp, err := time.Parse(timeFormat, expStr)
if err != nil {
return true
}
age := time.Now().Sub(iat.Local())
return iat.Add(age).After(exp)
}
func (self *jweTokenManager) generateAAD() []byte {
now := time.Now()
aad := AdditionalAuthData{
IAT: now.Format(timeFormat),
}
if self.tokenTTL > 0 {
aad[EXP] = now.Add(self.tokenTTL).Format(timeFormat)
}
rawAAD, _ := json.Marshal(aad)
return rawAAD
}
// Creates and returns default JWE token manager instance.
func NewJWETokenManager(holder KeyHolder) authApi.TokenManager {
manager := &jweTokenManager{keyHolder: holder, tokenTTL: authApi.DefaultTokenTTL * time.Second}
return manager
}