-
Notifications
You must be signed in to change notification settings - Fork 32
/
keymanager.go
183 lines (168 loc) · 4.74 KB
/
keymanager.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
package auth
import (
"bytes"
"crypto/rsa"
"crypto/tls"
"encoding/json"
"errors"
"io"
"net/http"
"github.com/codeready-toolchain/registration-service/pkg/configuration"
"github.com/codeready-toolchain/registration-service/pkg/log"
authsupport "github.com/codeready-toolchain/toolchain-common/pkg/test/auth"
"gopkg.in/square/go-jose.v2"
)
// KeyManagerConfiguration represents a partition of the configuration
// that is used for configuring the KeyManager.
type KeyManagerConfiguration interface {
GetAuthClientPublicKeysURL() string
GetEnvironment() string
}
// PublicKey represents an RSA public key with a Key ID
type PublicKey struct {
KeyID string
Key *rsa.PublicKey
}
// JSONKeys the remote keys encoded in a json document
type JSONKeys struct {
Keys []interface{} `json:"keys"`
}
// KeyManager manages the public keys for token validation.
type KeyManager struct {
keyMap map[string]*rsa.PublicKey
}
// NewKeyManager creates a new KeyManager and retrieves the public keys from the given URL.
func NewKeyManager() (*KeyManager, error) {
cfg := configuration.GetRegistrationServiceConfig()
keysEndpointURL := cfg.Auth().AuthClientPublicKeysURL()
km := &KeyManager{
keyMap: make(map[string]*rsa.PublicKey),
}
// fetch raw keys
if keysEndpointURL != "" {
if cfg.Environment() == "e2e-tests" {
log.Infof(nil, "fetching e2e public keys")
keys := authsupport.GetE2ETestPublicKey()
// add them to the kid map
for _, key := range keys {
km.keyMap[key.KeyID] = key.Key
}
} else {
log.Infof(nil, "fetching public keys from url: %s", keysEndpointURL)
keys, err := km.fetchKeys(keysEndpointURL)
if err != nil {
return nil, err
}
// add them to the kid map
for _, key := range keys {
km.keyMap[key.KeyID] = key.Key
}
}
} else {
log.Info(nil, "no public key url given, not fetching keys")
}
return km, nil
}
// Key retrieves the public key for a given kid.
func (km *KeyManager) Key(kid string) (*rsa.PublicKey, error) {
key, ok := km.keyMap[kid]
if !ok {
return nil, errors.New("unknown kid")
}
return key, nil
}
// unmarshalKeys unmarshals keys from given JSON.
func (km *KeyManager) unmarshalKeys(jsonData []byte) ([]*PublicKey, error) {
var keys []*PublicKey
var raw JSONKeys
err := json.Unmarshal(jsonData, &raw)
if err != nil {
return nil, err
}
for _, key := range raw.Keys {
jsonKeyData, err := json.Marshal(key)
if err != nil {
return nil, err
}
publicKey, err := km.unmarshalKey(jsonKeyData)
if err != nil {
return nil, err
}
keys = append(keys, publicKey)
}
return keys, nil
}
// unmarshalKey unmarshals a single key from a given JSON.
func (km *KeyManager) unmarshalKey(jsonData []byte) (*PublicKey, error) {
key := &jose.JSONWebKey{}
err := key.UnmarshalJSON(jsonData)
if err != nil {
return nil, err
}
rsaKey, ok := key.Key.(*rsa.PublicKey)
if !ok {
return nil, errors.New("Key is not an *rsa.PublicKey")
}
return &PublicKey{key.KeyID, rsaKey}, nil
}
// unmarshalls the keys from a byte array.
func (km *KeyManager) fetchKeysFromBytes(keysBytes []byte) ([]*PublicKey, error) {
keys, err := km.unmarshalKeys(keysBytes)
if err != nil {
return nil, err
}
log.Infof(nil, "%v public keys loaded", string(keysBytes))
// return the retrieved keys
return keys, nil
}
// fetchKeys fetches the keys from the given URL, unmarshalling them.
func (km *KeyManager) fetchKeys(keysEndpointURL string) ([]*PublicKey, error) {
// use httpClient to perform request
transport := http.DefaultTransport
if !configuration.GetRegistrationServiceConfig().IsProdEnvironment() {
transport = &http.Transport{
TLSClientConfig: &tls.Config{
InsecureSkipVerify: true, // nolint:gosec
},
}
}
httpClient := &http.Client{Transport: transport}
req, err := http.NewRequest("GET", keysEndpointURL, nil)
if err != nil {
return nil, err
}
res, err := httpClient.Do(req)
if err != nil {
return nil, err
}
// cleanup and close after being done
defer func() {
_, err := io.ReadAll(res.Body)
if errors.Is(err, io.EOF) {
log.Error(nil, err, "failed read remaining data before closing response")
}
err = res.Body.Close()
if err != nil {
log.Error(nil, err, "failed to close response after reading")
}
}()
// read and parse response body
buf := new(bytes.Buffer)
_, err = buf.ReadFrom(res.Body)
if err != nil {
return nil, err
}
bodyString := buf.String()
// if status code was not OK, bail out
if res.StatusCode != http.StatusOK {
err := errors.New("unable to obtain public keys from remote service")
log.WithValues(map[string]interface{}{
"response_status": res.Status,
"response_body": bodyString,
"keys_url": keysEndpointURL,
}).Error(nil, err, "")
return nil, err
}
// unmarshal the keys
return km.fetchKeysFromBytes([]byte(bodyString))
}