Skip to content
This repository was archived by the owner on Jul 11, 2023. It is now read-only.

Commit 8fbb83d

Browse files
committed
feat(cert): cert rotation state management
Enable the certificate handler to correctly issue certificates from the correct issuer based on the set state. We don't actually populate any additional states yet. Signed-off-by: Sean Teeling <[email protected]>
1 parent 00bc363 commit 8fbb83d

File tree

8 files changed

+267
-71
lines changed

8 files changed

+267
-71
lines changed

pkg/certificate/certificate.go

+13
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,19 @@ const (
2020
noiseSeconds = 5
2121
)
2222

23+
// mergeRoot will merge in the provided root CA for future calls to GetIssuingCA. It guarantees to not mutate
24+
// the underlying IssuingCA field. By doing so, we ensure that we don't need locks.
25+
// NOTE: this does not return a full copy, mutations to the other byte slices could cause data races.
26+
func (c *Certificate) newMergedWithRoot(root pem.RootCertificate) *Certificate {
27+
cert := *c
28+
29+
buf := make([]byte, 0, len(root)+len(c.IssuingCA))
30+
buf = append(buf, c.IssuingCA...)
31+
buf = append(buf, root...)
32+
cert.IssuingCA = buf
33+
return &cert
34+
}
35+
2336
// GetCommonName returns the Common Name of the certificate
2437
func (c *Certificate) GetCommonName() CommonName {
2538
return c.CommonName

pkg/certificate/fake_manager.go

+13-3
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"k8s.io/client-go/tools/cache"
88

99
"github.com/openservicemesh/osm/pkg/apis/config/v1alpha2"
10+
"github.com/openservicemesh/osm/pkg/certificate/pem"
1011
)
1112

1213
var (
@@ -19,8 +20,8 @@ var (
1920

2021
type fakeMRCClient struct{}
2122

22-
func (c *fakeMRCClient) GetCertIssuerForMRC(mrc *v1alpha2.MeshRootCertificate) (Issuer, error) {
23-
return &fakeIssuer{}, nil
23+
func (c *fakeMRCClient) GetCertIssuerForMRC(mrc *v1alpha2.MeshRootCertificate) (Issuer, string, error) {
24+
return &fakeIssuer{}, "fake-issuer-1", nil
2425
}
2526

2627
// List returns the single, pre-generated MRC. It is intended to implement the certificate.MRCClient interface.
@@ -33,13 +34,22 @@ func (c *fakeMRCClient) List() ([]*v1alpha2.MeshRootCertificate, error) {
3334
// method to implement the certificate.MRCClient interface.
3435
func (c *fakeMRCClient) AddEventHandler(cache.ResourceEventHandler) {}
3536

36-
type fakeIssuer struct{}
37+
type fakeIssuer struct {
38+
err bool
39+
id string
40+
}
3741

3842
// IssueCertificate is a testing helper to satisfy the certificate client interface
3943
func (i *fakeIssuer) IssueCertificate(cn CommonName, validityPeriod time.Duration) (*Certificate, error) {
44+
if i.err {
45+
return nil, fmt.Errorf("%s failed", i.id)
46+
}
4047
return &Certificate{
4148
CommonName: cn,
4249
Expiration: time.Now().Add(validityPeriod),
50+
// simply used to distinguish the private/public key from other issuers
51+
IssuingCA: pem.RootCertificate(i.id),
52+
PrivateKey: pem.PrivateKey(i.id),
4353
}, nil
4454
}
4555

pkg/certificate/manager.go

+52-35
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package certificate
22

33
import (
4+
"fmt"
45
"time"
56

67
"github.com/pkg/errors"
@@ -22,14 +23,17 @@ func NewManager(mrcClient MRCClient, serviceCertValidityDuration time.Duration,
2223
return nil, err
2324
}
2425

25-
client, err := mrcClient.GetCertIssuerForMRC(mrcs[0])
26+
client, clientID, err := mrcClient.GetCertIssuerForMRC(mrcs[0])
2627
if err != nil {
2728
return nil, err
2829
}
2930

31+
c := &issuer{Issuer: client, ID: clientID}
32+
3033
m := &Manager{
3134
// The root certificate signing all newly issued certificates
32-
clients: []Issuer{client},
35+
keyIssuer: c,
36+
pubIssuer: c,
3337
serviceCertValidityDuration: serviceCertValidityDuration,
3438
msgBroker: msgBroker,
3539
}
@@ -61,12 +65,7 @@ func (m *Manager) Start(checkInterval time.Duration, certRotation <-chan struct{
6165
}
6266

6367
func (m *Manager) checkAndRotate() {
64-
certs, err := m.ListCertificates()
65-
if err != nil {
66-
log.Error().Err(err).Msgf("Error listing all certificates")
67-
}
68-
69-
for _, cert := range certs {
68+
for _, cert := range m.ListIssuedCertificates() {
7069
shouldRotate := cert.ShouldRotate()
7170

7271
word := map[bool]string{true: "will", false: "will not"}[shouldRotate]
@@ -106,16 +105,34 @@ func (m *Manager) getFromCache(cn CommonName) *Certificate {
106105

107106
// IssueCertificate implements Manager and returns a newly issued certificate from the given client.
108107
func (m *Manager) IssueCertificate(cn CommonName, validityPeriod time.Duration) (*Certificate, error) {
108+
// var additionalRoot pem.RootCertificate
109+
var err error
110+
cert := m.getFromCache(cn) // Don't call this while holding the lock
111+
112+
m.mu.RLock()
113+
pubIssuer := m.pubIssuer
114+
keyIssuer := m.keyIssuer
115+
m.mu.RUnlock()
116+
109117
start := time.Now()
118+
if cert == nil || cert.keyIssuer != keyIssuer.ID || cert.pubIssuer != pubIssuer.ID {
119+
fmt.Println("issuing!")
120+
cert, err = keyIssuer.IssueCertificate(cn, validityPeriod)
121+
if err != nil {
122+
return nil, err
123+
}
124+
if pubIssuer.ID != keyIssuer.ID {
125+
pubCert, err := pubIssuer.IssueCertificate(cn, validityPeriod)
126+
if err != nil {
127+
return nil, err
128+
}
110129

111-
if cert := m.getFromCache(cn); cert != nil {
112-
return cert, nil
113-
}
130+
cert = cert.newMergedWithRoot(pubCert.GetIssuingCA())
131+
}
114132

115-
// TODO(#4502): determine client(s) to use based on the rotation stage(s) of the client(s)
116-
cert, err := m.clients[0].IssueCertificate(cn, validityPeriod)
117-
if err != nil {
118-
return cert, err
133+
cert.keyIssuer = keyIssuer.ID
134+
cert.pubIssuer = m.pubIssuer.ID
135+
fmt.Println("issued!", cert.keyIssuer, keyIssuer.ID)
119136
}
120137

121138
m.cache.Store(cn, cert)
@@ -131,14 +148,6 @@ func (m *Manager) ReleaseCertificate(cn CommonName) {
131148
m.cache.Delete(cn)
132149
}
133150

134-
// GetCertificate returns a certificate given its Common Name (CN)
135-
func (m *Manager) GetCertificate(cn CommonName) (*Certificate, error) {
136-
if cert := m.getFromCache(cn); cert != nil {
137-
return cert, nil
138-
}
139-
return nil, errCertNotFound
140-
}
141-
142151
// RotateCertificate implements Manager and rotates an existing
143152
func (m *Manager) RotateCertificate(cn CommonName) (*Certificate, error) {
144153
start := time.Now()
@@ -171,28 +180,36 @@ func (m *Manager) RotateCertificate(cn CommonName) (*Certificate, error) {
171180
return newCert, nil
172181
}
173182

174-
// ListCertificates lists all certificates issued
175-
func (m *Manager) ListCertificates() ([]*Certificate, error) {
183+
// ListIssuedCertificates implements CertificateDebugger interface and returns the list of issued certificates.
184+
func (m *Manager) ListIssuedCertificates() []*Certificate {
176185
var certs []*Certificate
177-
m.cache.Range(func(_ interface{}, certInterface interface{}) bool {
186+
m.cache.Range(func(cnInterface interface{}, certInterface interface{}) bool {
178187
certs = append(certs, certInterface.(*Certificate))
179188
return true // continue the iteration
180189
})
181-
return certs, nil
182-
}
183-
184-
// GetRootCertificate returns the root
185-
func (m *Manager) GetRootCertificate() *Certificate {
186-
// TODO(#4502): determine client(s) to use based on the rotation stage(s) of the client(s)
187-
return m.clients[0].GetRootCertificate()
190+
return certs
188191
}
189192

190193
// ListIssuedCertificates implements CertificateDebugger interface and returns the list of issued certificates.
191-
func (m *Manager) ListIssuedCertificates() []*Certificate {
194+
func (m *Manager) ListCertificates() ([]*Certificate, error) {
192195
var certs []*Certificate
193196
m.cache.Range(func(cnInterface interface{}, certInterface interface{}) bool {
194197
certs = append(certs, certInterface.(*Certificate))
195198
return true // continue the iteration
196199
})
197-
return certs
200+
return certs, nil
201+
}
202+
203+
// GetCertificate returns a certificate given its Common Name (CN)
204+
func (m *Manager) GetCertificate(cn CommonName) (*Certificate, error) {
205+
if cert := m.getFromCache(cn); cert != nil {
206+
return cert, nil
207+
}
208+
return nil, errCertNotFound
209+
}
210+
211+
// GetRootCertificate returns the root
212+
func (m *Manager) GetRootCertificate() *Certificate {
213+
// TODO(#4502): determine client(s) to use based on the rotation stage(s) of the client(s)
214+
return m.keyIssuer.GetRootCertificate()
198215
}

pkg/certificate/manager_test.go

+121-6
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package certificate
22

33
import (
4-
"fmt"
54
"testing"
65
time "time"
76

@@ -10,6 +9,7 @@ import (
109
tassert "github.com/stretchr/testify/assert"
1110

1211
"github.com/openservicemesh/osm/pkg/announcements"
12+
"github.com/openservicemesh/osm/pkg/certificate/pem"
1313
"github.com/openservicemesh/osm/pkg/messaging"
1414
)
1515

@@ -70,11 +70,8 @@ func TestRotor(t *testing.T) {
7070
assert.NoError(err)
7171
certRotateChan := msgBroker.GetCertPubSub().Sub(announcements.CertificateRotated.String())
7272

73-
start := time.Now()
74-
// Wait for one certificate rotation to be announced and terminate
73+
// Wait for two certificate rotations to be announced and terminate
7574
<-certRotateChan
76-
77-
fmt.Printf("It took %+v to rotate certificate %s\n", time.Since(start), cn)
7875
newCert, err := certManager.IssueCertificate(cn, validityPeriod)
7976
assert.NoError(err)
8077
assert.NotEqual(certA.GetExpiration(), newCert.GetExpiration())
@@ -216,9 +213,127 @@ func TestListCertificate(t *testing.T) {
216213
func TestGetRootCertificate(t *testing.T) {
217214
assert := tassert.New(t)
218215

219-
manager := &Manager{clients: []Issuer{&fakeIssuer{}}}
216+
manager := &Manager{
217+
keyIssuer: &issuer{ID: "fake-1", Issuer: &fakeIssuer{}},
218+
pubIssuer: &issuer{ID: "fake-1", Issuer: &fakeIssuer{}},
219+
}
220220

221221
got := manager.GetRootCertificate()
222222

223223
assert.Equal(caCert, got)
224224
}
225+
226+
func TestIssueCertificate(t *testing.T) {
227+
cn := CommonName("fake-cert-cn")
228+
assert := tassert.New(t)
229+
230+
t.Run("single key issuer", func(t *testing.T) {
231+
cm := &Manager{
232+
// The root certificate signing all newly issued certificates
233+
keyIssuer: &issuer{ID: "id1", Issuer: &fakeIssuer{id: "id1"}},
234+
pubIssuer: &issuer{ID: "id1", Issuer: &fakeIssuer{id: "id1"}},
235+
}
236+
// single keyIssuer, not cached
237+
cert1, err := cm.IssueCertificate(cn, time.Minute)
238+
assert.NoError(err)
239+
assert.NotNil(cert1)
240+
assert.Equal(cert1.keyIssuer, "id1")
241+
assert.Equal(cert1.pubIssuer, "id1")
242+
assert.Equal(cert1.GetIssuingCA(), pem.RootCertificate("id1"))
243+
244+
// single keyIssuer cached
245+
cert2, err := cm.IssueCertificate(cn, time.Minute)
246+
assert.NoError(err)
247+
assert.Equal(cert1, cert2)
248+
249+
// single key issuer, old version cached
250+
// TODO: could use informer logic to test mrc updates instead of just manually making changes.
251+
cm.keyIssuer = &issuer{ID: "id2", Issuer: &fakeIssuer{id: "id2"}}
252+
cm.pubIssuer = &issuer{ID: "id2", Issuer: &fakeIssuer{id: "id2"}}
253+
254+
cert3, err := cm.IssueCertificate(cn, time.Minute)
255+
assert.NoError(err)
256+
assert.NotNil(cert3)
257+
assert.Equal(cert3.keyIssuer, "id2")
258+
assert.Equal(cert3.pubIssuer, "id2")
259+
assert.NotEqual(cert2, cert3)
260+
assert.Equal(cert3.GetIssuingCA(), pem.RootCertificate("id2"))
261+
})
262+
263+
t.Run("2 issuers", func(t *testing.T) {
264+
cm := &Manager{
265+
// The root certificate signing all newly issued certificates
266+
keyIssuer: &issuer{ID: "id1", Issuer: &fakeIssuer{id: "id1"}},
267+
pubIssuer: &issuer{ID: "id2", Issuer: &fakeIssuer{id: "id2"}},
268+
}
269+
270+
// Not cached
271+
cert1, err := cm.IssueCertificate(cn, time.Minute)
272+
assert.NoError(err)
273+
assert.NotNil(cert1)
274+
assert.Equal(cert1.keyIssuer, "id1")
275+
assert.Equal(cert1.pubIssuer, "id2")
276+
assert.Equal(cert1.GetIssuingCA(), pem.RootCertificate("idid2"))
277+
278+
// cached
279+
cert2, err := cm.IssueCertificate(cn, time.Minute)
280+
assert.NoError(err)
281+
assert.Equal(cert1, cert2)
282+
283+
// cached, but pubIssuer is removed
284+
cm.pubIssuer = cm.keyIssuer
285+
cert3, err := cm.IssueCertificate(cn, time.Minute)
286+
assert.NoError(err)
287+
assert.NotEqual(cert1, cert3)
288+
assert.Equal(cert3.keyIssuer, "id1")
289+
assert.Equal(cert3.pubIssuer, "id1")
290+
assert.Equal(cert3.GetIssuingCA(), pem.RootCertificate("id1"))
291+
292+
// cached, but keyIssuer is old
293+
cm.keyIssuer = &issuer{ID: "id2", Issuer: &fakeIssuer{id: "id2"}}
294+
cert4, err := cm.IssueCertificate(cn, time.Minute)
295+
assert.NoError(err)
296+
assert.NotEqual(cert3, cert4)
297+
assert.Equal(cert4.keyIssuer, "id2")
298+
assert.Equal(cert4.pubIssuer, "id1")
299+
assert.Equal(cert4.GetIssuingCA(), pem.RootCertificate("idid1"))
300+
301+
// cached, but pubIssuer is old
302+
cm.pubIssuer = &issuer{ID: "id3", Issuer: &fakeIssuer{id: "id3"}}
303+
cert5, err := cm.IssueCertificate(cn, time.Minute)
304+
assert.NoError(err)
305+
assert.NotEqual(cert4, cert5)
306+
assert.Equal(cert5.keyIssuer, "id2")
307+
assert.Equal(cert5.pubIssuer, "id3")
308+
assert.Equal(cert5.GetIssuingCA(), pem.RootCertificate("idid3"))
309+
})
310+
311+
t.Run("bad issuers", func(t *testing.T) {
312+
cm := &Manager{
313+
// The root certificate signing all newly issued certificates
314+
keyIssuer: &issuer{ID: "id1", Issuer: &fakeIssuer{id: "id1", err: true}},
315+
pubIssuer: &issuer{ID: "id2", Issuer: &fakeIssuer{id: "id2", err: true}},
316+
}
317+
318+
// bad private key
319+
cert, err := cm.IssueCertificate(cn, time.Minute)
320+
assert.Nil(cert)
321+
assert.EqualError(err, "id1 failed")
322+
323+
// bad public key
324+
cm.keyIssuer = &issuer{ID: "id3", Issuer: &fakeIssuer{id: "id3"}}
325+
cert, err = cm.IssueCertificate(cn, time.Minute)
326+
assert.Nil(cert)
327+
assert.EqualError(err, "id2 failed")
328+
329+
// insert a cached cert
330+
cm.pubIssuer = cm.keyIssuer
331+
cert, err = cm.IssueCertificate(cn, time.Minute)
332+
assert.NoError(err)
333+
334+
// bad public key on an existing cached cert, because the pubIssuer is new
335+
cm.pubIssuer = &issuer{ID: "id1", Issuer: &fakeIssuer{id: "id1", err: true}}
336+
cert, err = cm.IssueCertificate(cn, time.Minute)
337+
assert.EqualError(err, "id1 failed")
338+
})
339+
}

0 commit comments

Comments
 (0)