/
registry.go
179 lines (154 loc) · 6.12 KB
/
registry.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
/*
Copyright 2020 The Jetstack cert-manager contributors.
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 accounts
import (
"crypto/rsa"
"errors"
"net/http"
"sync"
acmecl "github.com/jetstack/cert-manager/pkg/acme/client"
cmacme "github.com/jetstack/cert-manager/pkg/apis/acme/v1"
)
// ErrNotFound is returned by GetClient if there is no ACME client registered.
var ErrNotFound = errors.New("ACME client for issuer not initialised/available")
// A registry provides a means to store and access ACME clients using an issuer
// objects UID.
// This is used as a shared cache of ACME clients across various controllers.
type Registry interface {
// AddClient will ensure the registry has a stored ACME client for the Issuer
// object with the given UID, configuration and private key.
AddClient(client *http.Client, uid string, config cmacme.ACMEIssuer, privateKey *rsa.PrivateKey)
// RemoveClient will remove a registered client using the UID of the Issuer
// resource that constructed it.
RemoveClient(uid string)
Getter
}
// Getter is an interface that contains the read-only methods for a registry.
type Getter interface {
// GetClient will fetch a registered client using the UID of the Issuer
// resources that constructed it.
// If no client is found, ErrNotFound will be returned.
GetClient(uid string) (acmecl.Interface, error)
// ListClients will return a full list of all ACME clients by their UIDs.
// This can be used to enumerate all registered clients and call RemoveClient
// on any clients that should no longer be registered, e.g. because their
// corresponding Issuer resource has been deleted.
ListClients() map[string]acmecl.Interface
}
// NewDefaultRegistry returns a new default instantiation of a client registry.
func NewDefaultRegistry() Registry {
return ®istry{
clients: make(map[string]clientWithMeta),
}
}
// Implementation of the Registry interface
type registry struct {
lock sync.RWMutex
// a map of an issuer's 'uid' to an ACME client with metadata
clients map[string]clientWithMeta
}
// stableOptions contains data about an ACME client that can be used to compare
// for 'equality' between two clients. This is used to determine whether any
// options that should trigger a re-initialisation of a client have changed.
type stableOptions struct {
serverURL string
skipVerifyTLS bool
issuerUID string
publicKey string
exponent int
}
func (c stableOptions) equalTo(c2 stableOptions) bool {
return c == c2
}
func newStableOptions(uid string, config cmacme.ACMEIssuer, privateKey *rsa.PrivateKey) stableOptions {
// Encoding a big.Int cannot fail
publicNBytes, _ := privateKey.PublicKey.N.GobEncode()
return stableOptions{
serverURL: config.Server,
skipVerifyTLS: config.SkipTLSVerify,
issuerUID: uid,
publicKey: string(publicNBytes),
exponent: privateKey.PublicKey.E,
}
}
// clientWithMeta wraps an ACME client with additional metadata used to
// identify the options used to instantiate the client.
type clientWithMeta struct {
acmecl.Interface
stableOptions
}
// AddClient will ensure the registry has a stored ACME client for the Issuer
// object with the given UID, configuration and private key.
func (r *registry) AddClient(client *http.Client, uid string, config cmacme.ACMEIssuer, privateKey *rsa.PrivateKey) {
// ensure the client is up to date for the current configuration
r.ensureClient(client, uid, config, privateKey)
}
// ensureClient will ensure an ACME client with the given parameters is registered.
// If one is already registered and it was constructed using the same input options,
// the client will NOT be mutated or replaced, allowing this method to be called
// even if the client does not need replacing/updating without causing issues for
// consumers of the registry.
func (r *registry) ensureClient(client *http.Client, uid string, config cmacme.ACMEIssuer, privateKey *rsa.PrivateKey) {
// acquire a read-write lock even if we hit the fast-path where the client
// is already present to avoid having to RLock, RUnlock and Lock again,
// which could itself cause a race
r.lock.Lock()
defer r.lock.Unlock()
newOpts := newStableOptions(uid, config, privateKey)
// fast-path if there is nothing to do
if meta, ok := r.clients[uid]; ok && meta.equalTo(newOpts) {
return
}
// create a new client if one is not registered or if the
// 'metadata' does not match
r.clients[uid] = clientWithMeta{
Interface: NewClient(client, config, privateKey),
stableOptions: newOpts,
}
}
// GetClient will fetch a registered client using the UID of the Issuer
// resources that constructed it.
// If no client is found, ErrNotFound will be returned.
func (r *registry) GetClient(uid string) (acmecl.Interface, error) {
r.lock.RLock()
defer r.lock.RUnlock()
// fast-path if the client is already registered
if c, ok := r.clients[uid]; ok {
return c.Interface, nil
}
return nil, ErrNotFound
}
// RemoveClient will remove a registered client using the UID of the Issuer
// resource that constructed it.
func (r *registry) RemoveClient(uid string) {
r.lock.Lock()
defer r.lock.Unlock()
if _, ok := r.clients[uid]; !ok {
return
}
delete(r.clients, uid)
}
// ListClients will return a full list of all ACME clients by their UIDs.
// This can be used to enumerate all registered clients and call RemoveClient
// on any clients that should no longer be registered, e.g. because their
// corresponding Issuer resource has been deleted.
func (r *registry) ListClients() map[string]acmecl.Interface {
r.lock.RLock()
defer r.lock.RUnlock()
// strip the client metadata before returning
out := make(map[string]acmecl.Interface)
for k, v := range r.clients {
out[k] = v.Interface
}
return out
}