1 package entrypoint_test
2
3 import (
4 "context"
5 "encoding/json"
6 "reflect"
7 "sync"
8 "testing"
9 "time"
10
11 "github.com/datawire/ambassador/v2/cmd/entrypoint"
12 "github.com/datawire/ambassador/v2/pkg/kates"
13 "github.com/datawire/dlib/dlog"
14 "github.com/datawire/dlib/dtime"
15 )
16
17 const (
18 PUBLIC_KEY string = "fake-public-key"
19 PRIVATE_KEY string = "fake-private-key"
20 )
21
22 func fakeReadPEM(ctx context.Context, dir string, name string) ([]byte, bool) {
23 if name == "key.pem" {
24 return []byte(PRIVATE_KEY), true
25 } else if name == "cert-chain.pem" {
26 return []byte(PUBLIC_KEY), true
27 } else {
28 return nil, false
29 }
30 }
31
32 type icertMetadata struct {
33 t *testing.T
34 ft *dtime.FakeTime
35 icert *entrypoint.IstioCert
36 events []entrypoint.IstioCertUpdate
37 mutex sync.Mutex
38 }
39
40 func newICertMetadata(t *testing.T) (context.Context, *icertMetadata) {
41 ctx := dlog.NewTestContext(t, false)
42 ft := dtime.NewFakeTime()
43
44 updates := make(chan entrypoint.IstioCertUpdate)
45
46 icert := entrypoint.NewIstioCert("/tmp", "istio-test", "ambassador", updates)
47 icert.SetFetchTime(ft.Now)
48 icert.SetReadPEM(fakeReadPEM)
49
50 m := &icertMetadata{t: t, ft: ft, icert: icert}
51 m.events = make([]entrypoint.IstioCertUpdate, 0, 5)
52
53 go func() {
54 for {
55 evt := <-updates
56
57 m.mutex.Lock()
58 m.events = append(m.events, evt)
59 m.mutex.Unlock()
60
61 if evt.Op == "update" {
62 dlog.Infof(ctx, "Event handler: got update of %s", evt.Secret.ObjectMeta.Name)
63 } else {
64 dlog.Infof(ctx, "Event handler: got deletion")
65 }
66 }
67 }()
68
69 return ctx, m
70 }
71
72 func (m *icertMetadata) stepSec(sec int) {
73 m.ft.StepSec(sec)
74 }
75
76 func (m *icertMetadata) check(ctx context.Context, what string, name string, deleted bool, count int) {
77 m.icert.HandleEvent(ctx, name, deleted)
78 time.Sleep(250 * time.Millisecond)
79
80 m.mutex.Lock()
81 defer m.mutex.Unlock()
82 if len(m.events) != count {
83 m.t.Errorf("%s: wanted event count %d, got %d", what, count, len(m.events))
84 }
85 }
86
87 func (m *icertMetadata) checkNoSecret() {
88 m.mutex.Lock()
89 count := len(m.events)
90 m.mutex.Unlock()
91
92 if count > 0 {
93 m.mutex.Lock()
94 evt := m.events[count-1]
95 m.mutex.Unlock()
96
97 if evt.Op != "delete" {
98 m.t.Errorf("wanted no live secret, got %s op?", evt.Op)
99 }
100
101 if evt.Secret != nil {
102 m.t.Errorf("wanted no live secret, got %s", evt.Secret.ObjectMeta.Name)
103 }
104 }
105 }
106
107 func (m *icertMetadata) checkSecret(namespace string, publicPEM string, privatePEM string) {
108 wantedSecret := &kates.Secret{
109 TypeMeta: kates.TypeMeta{
110 APIVersion: "v1",
111 Kind: "Secret",
112 },
113 ObjectMeta: kates.ObjectMeta{
114 Name: "istio-test",
115 Namespace: namespace,
116 },
117 Type: kates.SecretTypeTLS,
118 Data: map[string][]byte{
119 "tls.key": []byte(privatePEM),
120 "tls.crt": []byte(publicPEM),
121 },
122 }
123
124 count := len(m.events)
125
126 if count == 0 {
127 m.t.Errorf("wanted live secret, have none")
128 return
129 }
130
131 m.mutex.Lock()
132 evt := m.events[count-1]
133 m.mutex.Unlock()
134
135 if evt.Op != "update" {
136 m.t.Errorf("wanted live secret, got %s op?", evt.Op)
137 }
138
139 if evt.Name != wantedSecret.ObjectMeta.Name {
140 m.t.Errorf("wanted name %s in update, got %s", wantedSecret.ObjectMeta.Name, evt.Name)
141 }
142
143 if evt.Namespace != wantedSecret.ObjectMeta.Namespace {
144 m.t.Errorf("wanted namespace %s in update, got %s", wantedSecret.ObjectMeta.Namespace, evt.Namespace)
145 }
146
147 if !reflect.DeepEqual(wantedSecret, evt.Secret) {
148
149 wantedSecretJSON, err1 := json.MarshalIndent(wantedSecret, "", " ")
150 secretJSON, err2 := json.MarshalIndent(evt.Secret, "", " ")
151
152 if (err1 != nil) || (err2 != nil) {
153
154 m.t.Errorf("secret mismatch AND impossible errors:\n-- wanted %#v\n--- (err %s)\n-- got %#v\n--- (err %s)",
155 wantedSecret, err1, evt.Secret, err2)
156 } else {
157
158 m.t.Errorf("secret mismatch:\n-- wanted %s\n-- got %s", wantedSecretJSON, secretJSON)
159 }
160 }
161 }
162
163 func TestIstioCertHappyBoot1(t *testing.T) {
164 ctx, m := newICertMetadata(t)
165
166 m.check(ctx, "boot foo", "/tmp/foo", false, 0)
167 m.check(ctx, "boot bar", "/tmp/bar", false, 0)
168 m.check(ctx, "boot root-cert.pem", "/tmp/root-cert.pem", false, 0)
169 m.check(ctx, "boot cert-chain.pem", "/tmp/cert-chain.pem", false, 0)
170 m.check(ctx, "boot key.pem", "/tmp/key.pem", false, 1)
171
172 m.checkSecret("ambassador", PUBLIC_KEY, PRIVATE_KEY)
173 }
174
175 func TestIstioCertHappyBoot2(t *testing.T) {
176 ctx, m := newICertMetadata(t)
177
178 m.check(ctx, "boot key.pem", "/tmp/key.pem", false, 0)
179 m.check(ctx, "boot foo", "/tmp/foo", false, 0)
180 m.check(ctx, "boot bar", "/tmp/bar", false, 0)
181 m.check(ctx, "boot root-cert.pem", "/tmp/root-cert.pem", false, 0)
182 m.check(ctx, "boot cert-chain.pem", "/tmp/cert-chain.pem", false, 1)
183
184 m.checkSecret("ambassador", PUBLIC_KEY, PRIVATE_KEY)
185 }
186
187 func TestIstioCertHappyNoBoot(t *testing.T) {
188 ctx, m := newICertMetadata(t)
189
190 m.stepSec(5)
191 m.check(ctx, "key.pem", "/tmp/key.pem", false, 0)
192 m.stepSec(1)
193 m.check(ctx, "root-cert.pem", "/tmp/root-cert.pem", false, 0)
194 m.stepSec(2)
195 m.check(ctx, "cert-chain.pem", "/tmp/cert-chain.pem", false, 1)
196
197 m.checkSecret("ambassador", PUBLIC_KEY, PRIVATE_KEY)
198 }
199
200 func TestIstioCertTooSlow1(t *testing.T) {
201 ctx, m := newICertMetadata(t)
202
203 m.stepSec(5)
204 m.check(ctx, "key.pem", "/tmp/key.pem", false, 0)
205 m.stepSec(5)
206 m.check(ctx, "root-cert.pem", "/tmp/root-cert.pem", false, 0)
207 m.stepSec(5)
208 m.check(ctx, "cert-chain.pem", "/tmp/cert-chain.pem", false, 0)
209 }
210
211 func TestIstioCertTooSlow2(t *testing.T) {
212 ctx, m := newICertMetadata(t)
213
214 m.stepSec(5)
215 m.check(ctx, "key.pem", "/tmp/key.pem", false, 0)
216 m.stepSec(1)
217 m.check(ctx, "root-cert.pem", "/tmp/root-cert.pem", false, 0)
218 m.stepSec(5)
219 m.check(ctx, "cert-chain.pem", "/tmp/cert-chain.pem", false, 0)
220 }
221
222 func TestIstioCertEventually(t *testing.T) {
223 ctx, m := newICertMetadata(t)
224
225 m.stepSec(5)
226 m.check(ctx, "key.pem", "/tmp/key.pem", false, 0)
227 m.stepSec(5)
228 m.check(ctx, "root-cert.pem", "/tmp/root-cert.pem", false, 0)
229 m.stepSec(1)
230 m.check(ctx, "cert-chain.pem", "/tmp/cert-chain.pem", false, 0)
231 m.stepSec(1)
232 m.check(ctx, "root-cert.pem", "/tmp/root-cert.pem", false, 0)
233 m.stepSec(2)
234 m.check(ctx, "key.pem", "/tmp/key.pem", false, 1)
235
236 m.checkSecret("ambassador", PUBLIC_KEY, PRIVATE_KEY)
237 }
238
239 func TestIstioCertDeletion(t *testing.T) {
240 ctx, m := newICertMetadata(t)
241
242 m.stepSec(5)
243 m.check(ctx, "key.pem", "/tmp/key.pem", false, 0)
244 m.checkNoSecret()
245
246 m.stepSec(1)
247 m.check(ctx, "root-cert.pem", "/tmp/root-cert.pem", false, 0)
248 m.stepSec(1)
249 m.check(ctx, "cert-chain.pem", "/tmp/cert-chain.pem", false, 1)
250 m.checkSecret("ambassador", PUBLIC_KEY, PRIVATE_KEY)
251
252 m.stepSec(1)
253 m.check(ctx, "root-cert.pem", "/tmp/root-cert.pem", true, 1)
254 m.checkSecret("ambassador", PUBLIC_KEY, PRIVATE_KEY)
255
256 m.stepSec(1)
257 m.check(ctx, "key.pem", "/tmp/key.pem", true, 2)
258 m.checkNoSecret()
259 }
260
View as plain text