@@ -17,11 +17,19 @@ limitations under the License.
17
17
package certificate
18
18
19
19
import (
20
+ "bytes"
21
+ "context"
22
+ "fmt"
20
23
"net"
24
+ "os"
25
+ "path/filepath"
21
26
"reflect"
22
27
"testing"
28
+ "time"
23
29
24
30
v1 "k8s.io/api/core/v1"
31
+ "k8s.io/apimachinery/pkg/util/wait"
32
+ "k8s.io/client-go/util/cert"
25
33
netutils "k8s.io/utils/net"
26
34
)
27
35
@@ -100,3 +108,156 @@ func TestAddressesToHostnamesAndIPs(t *testing.T) {
100
108
})
101
109
}
102
110
}
111
+
112
+ func removeThenCreate (name string , data []byte , perm os.FileMode ) error {
113
+ if err := os .Remove (name ); err != nil {
114
+ if ! os .IsNotExist (err ) {
115
+ return err
116
+ }
117
+ }
118
+ return os .WriteFile (name , data , perm )
119
+ }
120
+
121
+ func createCertAndKeyFiles (certDir string ) (string , string , error ) {
122
+ cert , key , err := cert .GenerateSelfSignedCertKey ("k8s.io" , nil , nil )
123
+ if err != nil {
124
+ return "" , "" , nil
125
+ }
126
+
127
+ certPath := filepath .Join (certDir , "kubelet.cert" )
128
+ keyPath := filepath .Join (certDir , "kubelet.key" )
129
+ if err := removeThenCreate (certPath , cert , os .FileMode (0644 )); err != nil {
130
+ return "" , "" , err
131
+ }
132
+
133
+ if err := removeThenCreate (keyPath , key , os .FileMode (0600 )); err != nil {
134
+ return "" , "" , err
135
+ }
136
+
137
+ return certPath , keyPath , nil
138
+ }
139
+
140
+ // createCertAndKeyFilesUsingRename creates cert and key files under a parent dir `identity` as
141
+ // <certDir>/identity/kubelet.cert, <certDir>/identity/kubelet.key
142
+ func createCertAndKeyFilesUsingRename (certDir string ) (string , string , error ) {
143
+ cert , key , err := cert .GenerateSelfSignedCertKey ("k8s.io" , nil , nil )
144
+ if err != nil {
145
+ return "" , "" , nil
146
+ }
147
+
148
+ var certKeyPathFn = func (dataDir string ) (string , string , string ) {
149
+ outputDir := filepath .Join (certDir , dataDir )
150
+ return outputDir , filepath .Join (outputDir , "kubelet.cert" ), filepath .Join (outputDir , "kubelet.key" )
151
+ }
152
+
153
+ writeDir , writeCertPath , writeKeyPath := certKeyPathFn ("identity.tmp" )
154
+ if err := os .Mkdir (writeDir , 0777 ); err != nil {
155
+ return "" , "" , err
156
+ }
157
+
158
+ if err := removeThenCreate (writeCertPath , cert , os .FileMode (0644 )); err != nil {
159
+ return "" , "" , err
160
+ }
161
+
162
+ if err := removeThenCreate (writeKeyPath , key , os .FileMode (0600 )); err != nil {
163
+ return "" , "" , err
164
+ }
165
+
166
+ targetDir , certPath , keyPath := certKeyPathFn ("identity" )
167
+ if err := os .RemoveAll (targetDir ); err != nil {
168
+ if ! os .IsNotExist (err ) {
169
+ return "" , "" , err
170
+ }
171
+ }
172
+ if err := os .Rename (writeDir , targetDir ); err != nil {
173
+ return "" , "" , err
174
+ }
175
+
176
+ return certPath , keyPath , nil
177
+ }
178
+
179
+ func TestKubeletServerCertificateFromFiles (t * testing.T ) {
180
+ // test two common ways of certificate file updates:
181
+ // 1. delete and write the cert and key files directly
182
+ // 2. create the cert and key files under a child dir and perform dir rename during update
183
+ tests := []struct {
184
+ name string
185
+ useRename bool
186
+ }{
187
+ {
188
+ name : "remove and create" ,
189
+ useRename : false ,
190
+ },
191
+ {
192
+ name : "rename cert dir" ,
193
+ useRename : true ,
194
+ },
195
+ }
196
+
197
+ for _ , tt := range tests {
198
+ t .Run (tt .name , func (t * testing.T ) {
199
+ createFn := createCertAndKeyFiles
200
+ if tt .useRename {
201
+ createFn = createCertAndKeyFilesUsingRename
202
+ }
203
+
204
+ certDir := t .TempDir ()
205
+ certPath , keyPath , err := createFn (certDir )
206
+ if err != nil {
207
+ t .Fatalf ("Unable to setup cert files: %v" , err )
208
+ }
209
+
210
+ m , err := NewKubeletServerCertificateDynamicFileManager (certPath , keyPath )
211
+ if err != nil {
212
+ t .Fatalf ("Unable to create certificte provider: %v" , err )
213
+ }
214
+
215
+ m .Start ()
216
+ defer m .Stop ()
217
+
218
+ c := m .Current ()
219
+ if c == nil {
220
+ t .Fatal ("failed to provide valid certificate" )
221
+ }
222
+ time .Sleep (100 * time .Millisecond )
223
+ c2 := m .Current ()
224
+ if c2 == nil {
225
+ t .Fatal ("failed to provide valid certificate" )
226
+ }
227
+ if c2 != c {
228
+ t .Errorf ("expected the same loaded certificate object when there is no cert file change, got different" )
229
+ }
230
+
231
+ // simulate certificate files updated in the background
232
+ if _ , _ , err := createFn (certDir ); err != nil {
233
+ t .Fatalf ("got errors when rotating certificate files in the test: %v" , err )
234
+ }
235
+
236
+ err = wait .PollUntilContextTimeout (context .Background (),
237
+ 100 * time .Millisecond , 10 * time .Second , true ,
238
+ func (_ context.Context ) (bool , error ) {
239
+ c3 := m .Current ()
240
+ if c3 == nil {
241
+ return false , fmt .Errorf ("expected valid certificate regardless of file changes, but got nil" )
242
+ }
243
+ if bytes .Equal (c .Certificate [0 ], c3 .Certificate [0 ]) {
244
+ t .Logf ("loaded certificate is not updated" )
245
+ return false , nil
246
+ }
247
+ return true , nil
248
+ })
249
+ if err != nil {
250
+ t .Errorf ("failed to provide the updated certificate after file changes: %v" , err )
251
+ }
252
+
253
+ if err = os .Remove (certPath ); err != nil {
254
+ t .Errorf ("could not delete file in order to perform test" )
255
+ }
256
+
257
+ time .Sleep (1 * time .Second )
258
+ if m .Current () == nil {
259
+ t .Errorf ("expected the manager still provides cached content when certificate file was not available" )
260
+ }
261
+ })
262
+ }
263
+ }
0 commit comments