mirror of https://github.com/k3s-io/k3s
260 lines
8.1 KiB
Go
260 lines
8.1 KiB
Go
![]() |
/*
|
||
|
Copyright 2017 The Kubernetes Authors.
|
||
|
|
||
|
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 certificate
|
||
|
|
||
|
import (
|
||
|
"crypto/tls"
|
||
|
"crypto/x509"
|
||
|
"fmt"
|
||
|
"testing"
|
||
|
"time"
|
||
|
|
||
|
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||
|
watch "k8s.io/apimachinery/pkg/watch"
|
||
|
certificates "k8s.io/kubernetes/pkg/apis/certificates/v1beta1"
|
||
|
certificatesclient "k8s.io/kubernetes/pkg/client/clientset_generated/clientset/typed/certificates/v1beta1"
|
||
|
)
|
||
|
|
||
|
const (
|
||
|
privateKeyData = `-----BEGIN RSA PRIVATE KEY-----
|
||
|
MIIEowIBAAKCAQEA03ppJ1S3xK2UaXIatBPMbstHm8U9fwIFAj3a2WDV6FHo6zi2
|
||
|
YHVwCwSVnHL6D+Q5mmlbhnUpSD8SGTLk4EESAe2h203iBOBPBhymhTWA/gAEFk23
|
||
|
aP1/KlubjYN1+eyksA0lOVcO3sCuRZ64yjYJ369IfV1w8APZ4BXoFtU3uuYpjxyF
|
||
|
XlydkbLqQZLrBa1B5E8hEkDn4ywNDptGjRN3gT2GMQwnaCkWiLjGK6AxTCleXnjG
|
||
|
/JyEwbczv0zAE43utcYPW7qk1m5QsKMUAu4/K8y8oGBFy2ygpY1qckcgr5haehOS
|
||
|
IbFEvVd2oqW8NBicKNmSlh0OcAvQQZtaXhLg/QIDAQABAoIBAFkBmUZLerjVkbQ7
|
||
|
qQ+HkbBD8FSYVESjVfZWkEiTYBRSfSSbDu9UHh8VA97/6U1M8g2SMEpL/17/5J8k
|
||
|
c34LBQg4urmxcuI4gioBXviLx0mgOhglB3+xyZbLTZHm9X2F4t6R+cvDX2fTUsXM
|
||
|
gtvgmJFDlc/lxwXNqSKONct+W+FV/9D2H1Vzf8fQHfa+lltAy8e8MrbmGQTgev+5
|
||
|
vz/UR/bZz/CHRxXVA6txgvf4AL8BYibxgx6ihW9zKHy6GykqtQ2p0T5XCkObt41S
|
||
|
6KwUmIHP8CHY23MJ9BPIxYH2+lOXFLizB1VFuxRE1W+je7wVWxzQgFS4IMOLVYDD
|
||
|
LtprVQUCgYEA4g9ODbyW5vvyp8mmAWAvgeunOR1aP79IIyHiwefEIup4FNo+K2wZ
|
||
|
QhRPf0LsVvnthJXFWeW9arAWZRWKCFWwISq/cIIB6KXCIIsjiTUe8SYE/8bxAkvL
|
||
|
0lJhWugTpOnFd8oVuRivrsIWL+SXTNiO5JOP3/qfo+HFk3dqjDhXg4MCgYEA73y1
|
||
|
Cy+8vHweHKr8HTkPF13GAB1I43SvzTnGT2BT9q6Ia+zQDF1dHjnMrswD1v0+6Xmq
|
||
|
lKc5M69WBVuLIAfWfMQy0WANpsEMm5MYHShJ3YEYAqBiSTUWi23nLH/Poos4IUDV
|
||
|
nTAgFuoKFaG/9cLKA736zqJaiJCE/IR2/gqcYX8CgYA5PCjF/5axWt8ALmTyejjt
|
||
|
Cw4mvtDHzRVll8HC2HxnXrgSh4MwGUl32o6aKQaPqu3BIO57qVhA995jr4VoQNG8
|
||
|
RAd+Y9w53CX/eVsA9UslQTwIyoTg0PIFCUiO7K10lp+hia/gUmjAtXFKpPTNxxK+
|
||
|
usG1ss3Sf2o3wQdgAy/dIwKBgQCcHa1fZ3UfYcG3ancDDckasFR8ipqTO+PGYt01
|
||
|
rVPOwSPJRwywosQrCf62C+SM53V1eYyLbx9I5AmtYGmnLbTSjIucFYOQqtPvLspP
|
||
|
Z44PSTI/tBGeK29Q4QoL5h2SljK26q7V0yN4DIUaaODb8mkCW3v967QcxikK+8ce
|
||
|
AAjFPQKBgHnfVRX+00xSeNE0zya1FtQH3db9+fm3IYGK10NI/jTNF6RhUwHJ6X3+
|
||
|
TR6OhnTQ2j8eAo+6IlLqlDeC1X7GDvaxqstPvGi0lZjoQQGnQqw2m58AMJu3s9fW
|
||
|
2iddptVycNU0+187DIO39cM3o5s0822VUWDbmymD9cW4i8G6Yto9
|
||
|
-----END RSA PRIVATE KEY-----`
|
||
|
certificateData = `-----BEGIN CERTIFICATE-----
|
||
|
MIIDEzCCAfugAwIBAgIBATANBgkqhkiG9w0BAQsFADAjMSEwHwYDVQQDDBhrLWEt
|
||
|
bm9kZS12YzFzQDE0ODYzMzM1NDgwHhcNMTcwMjA1MjIyNTQ4WhcNMTgwMjA1MjIy
|
||
|
NTQ4WjAjMSEwHwYDVQQDDBhrLWEtbm9kZS12YzFzQDE0ODYzMzM1NDgwggEiMA0G
|
||
|
CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDTemknVLfErZRpchq0E8xuy0ebxT1/
|
||
|
AgUCPdrZYNXoUejrOLZgdXALBJWccvoP5DmaaVuGdSlIPxIZMuTgQRIB7aHbTeIE
|
||
|
4E8GHKaFNYD+AAQWTbdo/X8qW5uNg3X57KSwDSU5Vw7ewK5FnrjKNgnfr0h9XXDw
|
||
|
A9ngFegW1Te65imPHIVeXJ2RsupBkusFrUHkTyESQOfjLA0Om0aNE3eBPYYxDCdo
|
||
|
KRaIuMYroDFMKV5eeMb8nITBtzO/TMATje61xg9buqTWblCwoxQC7j8rzLygYEXL
|
||
|
bKCljWpyRyCvmFp6E5IhsUS9V3aipbw0GJwo2ZKWHQ5wC9BBm1peEuD9AgMBAAGj
|
||
|
UjBQMA4GA1UdDwEB/wQEAwICpDATBgNVHSUEDDAKBggrBgEFBQcDATAPBgNVHRMB
|
||
|
Af8EBTADAQH/MBgGA1UdEQQRMA+CDWstYS1ub2RlLXZjMXMwDQYJKoZIhvcNAQEL
|
||
|
BQADggEBAAHap+dwrAuejnIK8X/CA2kp2CNZgK8cQbTz6gHcAF7FESv5fL7BiYbJ
|
||
|
eljhZauh1MSU7hCeXNOK92I1ba7fa8gSdQoSblf9MOmeuNJ4tTwT0y5Cv0dE7anr
|
||
|
EEPWhp5BeHM10lvw/S2uPiN5CNo9pSniMamDcSC4JPXqfRbpqNQkeFOjByb/Y+ez
|
||
|
t+4mGQIouLdHDbx53xc0mmDXEfxwfE5K0gcF8T9EOE/azKlVA8Fk84vjMpVR2gka
|
||
|
O1eRCsCGPAnUCviFgNeH15ug+6N54DTTR6ZV/TTV64FDOcsox9nrhYcmH9sYuITi
|
||
|
0WC0XoXDL9tMOyzRR1ax/a26ks3Q3IY=
|
||
|
-----END CERTIFICATE-----`
|
||
|
)
|
||
|
|
||
|
func TestNewManagerNoRotation(t *testing.T) {
|
||
|
cert, err := tls.X509KeyPair([]byte(certificateData), []byte(privateKeyData))
|
||
|
if err != nil {
|
||
|
t.Fatalf("Unable to initialize a certificate: %v", err)
|
||
|
}
|
||
|
|
||
|
store := &fakeStore{cert: &cert}
|
||
|
if _, err := NewManager(nil, &x509.CertificateRequest{}, []certificates.KeyUsage{}, store, 0); err != nil {
|
||
|
t.Fatalf("Failed to initialize the certificate manager: %v", err)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func TestShouldRotate(t *testing.T) {
|
||
|
now := time.Now()
|
||
|
tests := []struct {
|
||
|
name string
|
||
|
notBefore time.Time
|
||
|
notAfter time.Time
|
||
|
shouldRotate bool
|
||
|
}{
|
||
|
{"half way", now.Add(-24 * time.Hour), now.Add(24 * time.Hour), false},
|
||
|
{"nearly there", now.Add(-100 * time.Hour), now.Add(1 * time.Hour), true},
|
||
|
{"just started", now.Add(-1 * time.Hour), now.Add(100 * time.Hour), false},
|
||
|
}
|
||
|
|
||
|
for _, test := range tests {
|
||
|
m := manager{
|
||
|
cert: &tls.Certificate{
|
||
|
Leaf: &x509.Certificate{
|
||
|
NotAfter: test.notAfter,
|
||
|
NotBefore: test.notBefore,
|
||
|
},
|
||
|
},
|
||
|
template: &x509.CertificateRequest{},
|
||
|
usages: []certificates.KeyUsage{},
|
||
|
shouldRotatePercent: 10,
|
||
|
}
|
||
|
|
||
|
if m.shouldRotate() != test.shouldRotate {
|
||
|
t.Errorf("For test case %s, time %v, a certificate issued for (%v, %v) should rotate should be %t.",
|
||
|
test.name,
|
||
|
now,
|
||
|
m.cert.Leaf.NotBefore,
|
||
|
m.cert.Leaf.NotAfter,
|
||
|
test.shouldRotate)
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func TestRotateCertCreateCSRError(t *testing.T) {
|
||
|
now := time.Now()
|
||
|
m := manager{
|
||
|
cert: &tls.Certificate{
|
||
|
Leaf: &x509.Certificate{
|
||
|
NotAfter: now.Add(-1 * time.Hour),
|
||
|
NotBefore: now.Add(-2 * time.Hour),
|
||
|
},
|
||
|
},
|
||
|
template: &x509.CertificateRequest{},
|
||
|
usages: []certificates.KeyUsage{},
|
||
|
certSigningRequestClient: fakeClient{
|
||
|
failureType: createError,
|
||
|
},
|
||
|
}
|
||
|
|
||
|
if err := m.rotateCerts(); err == nil {
|
||
|
t.Errorf("Expected an error from 'rotateCerts'.")
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func TestRotateCertWaitingForResultError(t *testing.T) {
|
||
|
now := time.Now()
|
||
|
m := manager{
|
||
|
cert: &tls.Certificate{
|
||
|
Leaf: &x509.Certificate{
|
||
|
NotAfter: now.Add(-1 * time.Hour),
|
||
|
NotBefore: now.Add(-2 * time.Hour),
|
||
|
},
|
||
|
},
|
||
|
template: &x509.CertificateRequest{},
|
||
|
usages: []certificates.KeyUsage{},
|
||
|
certSigningRequestClient: fakeClient{
|
||
|
failureType: watchError,
|
||
|
},
|
||
|
}
|
||
|
|
||
|
if err := m.rotateCerts(); err == nil {
|
||
|
t.Errorf("Expected an error receiving results from the CSR request but nothing was received.")
|
||
|
}
|
||
|
}
|
||
|
|
||
|
type fakeClientFailureType int
|
||
|
|
||
|
const (
|
||
|
none fakeClientFailureType = iota
|
||
|
createError
|
||
|
watchError
|
||
|
certificateSigningRequestDenied
|
||
|
)
|
||
|
|
||
|
type fakeClient struct {
|
||
|
certificatesclient.CertificateSigningRequestInterface
|
||
|
failureType fakeClientFailureType
|
||
|
}
|
||
|
|
||
|
func (c fakeClient) Create(*certificates.CertificateSigningRequest) (*certificates.CertificateSigningRequest, error) {
|
||
|
if c.failureType == createError {
|
||
|
return nil, fmt.Errorf("Create error")
|
||
|
}
|
||
|
csr := certificates.CertificateSigningRequest{}
|
||
|
csr.UID = "fake-uid"
|
||
|
return &csr, nil
|
||
|
}
|
||
|
|
||
|
func (c fakeClient) Watch(opts v1.ListOptions) (watch.Interface, error) {
|
||
|
if c.failureType == watchError {
|
||
|
return nil, fmt.Errorf("Watch error")
|
||
|
}
|
||
|
return &fakeWatch{
|
||
|
failureType: c.failureType,
|
||
|
}, nil
|
||
|
}
|
||
|
|
||
|
type fakeWatch struct {
|
||
|
failureType fakeClientFailureType
|
||
|
}
|
||
|
|
||
|
func (w *fakeWatch) Stop() {
|
||
|
}
|
||
|
|
||
|
func (w *fakeWatch) ResultChan() <-chan watch.Event {
|
||
|
var condition certificates.CertificateSigningRequestCondition
|
||
|
if w.failureType == certificateSigningRequestDenied {
|
||
|
condition = certificates.CertificateSigningRequestCondition{
|
||
|
Type: certificates.CertificateDenied,
|
||
|
}
|
||
|
} else {
|
||
|
condition = certificates.CertificateSigningRequestCondition{
|
||
|
Type: certificates.CertificateApproved,
|
||
|
}
|
||
|
}
|
||
|
|
||
|
csr := certificates.CertificateSigningRequest{
|
||
|
Status: certificates.CertificateSigningRequestStatus{
|
||
|
Conditions: []certificates.CertificateSigningRequestCondition{
|
||
|
condition,
|
||
|
},
|
||
|
Certificate: []byte(certificateData),
|
||
|
},
|
||
|
}
|
||
|
csr.UID = "fake-uid"
|
||
|
|
||
|
c := make(chan watch.Event, 1)
|
||
|
c <- watch.Event{
|
||
|
Type: watch.Added,
|
||
|
Object: &csr,
|
||
|
}
|
||
|
return c
|
||
|
}
|
||
|
|
||
|
type fakeStore struct {
|
||
|
cert *tls.Certificate
|
||
|
}
|
||
|
|
||
|
func (s *fakeStore) Current() (*tls.Certificate, error) {
|
||
|
return s.cert, nil
|
||
|
}
|
||
|
|
||
|
// Accepts the PEM data for the cert/key pair and makes the new cert/key
|
||
|
// pair the 'current' pair, that will be returned by future calls to
|
||
|
// Current().
|
||
|
func (s *fakeStore) Update(certPEM, keyPEM []byte) (*tls.Certificate, error) {
|
||
|
cert, err := tls.X509KeyPair(certPEM, keyPEM)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
s.cert = &cert
|
||
|
return s.cert, nil
|
||
|
}
|