зеркало из https://github.com/Azure/ARO-RP.git
297 строки
12 KiB
Go
297 строки
12 KiB
Go
package env
|
|
|
|
// Copyright (c) Microsoft Corporation.
|
|
// Licensed under the Apache License 2.0.
|
|
|
|
import (
|
|
"context"
|
|
"crypto/rsa"
|
|
"crypto/x509"
|
|
"errors"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/sirupsen/logrus"
|
|
"go.uber.org/mock/gomock"
|
|
|
|
"github.com/Azure/ARO-RP/pkg/util/keyvault"
|
|
mock_keyvault "github.com/Azure/ARO-RP/pkg/util/mocks/keyvault"
|
|
utilpem "github.com/Azure/ARO-RP/pkg/util/pem"
|
|
)
|
|
|
|
const testCertBundle1 = `-----BEGIN PRIVATE KEY-----
|
|
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDwFPCd+9yh5Qyx
|
|
ctlSvtG6D/5Y1ClB33NSaJ2x7ZwW+xJH2NkWaR4eAP0vzrlV9zdfc5PxF0+skOEd
|
|
DuyhFQwgzGXWfoIFwaxtkvibWo8+qz1PDzxdWnlK2Qk9BbF44I3J0cCYE0+6NXLR
|
|
/8gmUVzt2Rk1xFez8dVvFEEJDK6OiVEeduMAeTfjmUcHBUqsHzPtDbxMsG4fhhuE
|
|
yWH58f9QKIk9Q9SPZ07jAnSKcVuWY+0Tox4797hPNTrn2/7c6BqsitOijUD/55RB
|
|
Jo3wpiLcyyIWxLgkMgIlg01nDAY0qhOjFPoPlUCKXdRr7BhazanGCArK0z2FzIWA
|
|
5S4CEDflAgMBAAECggEABX+lRykGk5qoYMQNoCyIpydInwY077JLdN66heG4Snpz
|
|
n7uitTWxH+TL57VnX0WrOf9uqv3qsDwdO8okt0fBIFsuFeyN083swhG0qfI4B6pq
|
|
XA4wRr8UuhcgdApWVztlY/Lu40zF7bDdsVuXXPFOHJB1WFrn21I1njariqaEtPT6
|
|
z5bWELFP5Syq9WfXY5ug4MhNXLMuoMQTLXtspQ0M0gCldJEb7dQzczgiODI/q+JU
|
|
uMROZ5xssAr8C880fapvaxo3dcBqt4W22ya7zhahduLgfG4FsMUjYOBvWhe+gBA0
|
|
YPGz9Qaej/pLjYQgiKUOSkR0d8tkT50AfZXD1d4kHQKBgQDx3rarPnAk+1B2HruI
|
|
Duxy8DG7hGCzd5PqlaSpQpDKqJJY6DaoZ+tuV0CkqqODNe+L+i69wk1JwFR8HOyr
|
|
ReILU0AvN1DxexRvIc4KTCRjC4xOTbrMOOtSMRchVIaGOnCN5ElfJxKEuoBYCLPi
|
|
Xw7QVkrI7lV1Jhc8QW6X+7EW2wKBgQD+G3u25ngsk+9scGxq34pVz3YohVyine8h
|
|
fUgsyCVdERRR5RKLrKng/GSdgTzl0+smwO7N+/9Q+QSL1SANr2zjmHVa6n02Hehm
|
|
S+4YvHsA4WPGFrlmaVgO80KwVNmm5Xey6p0PnNt9zKYi4SRrEHPRMd8+EQuqL2Nn
|
|
KkVlI4pIPwKBgQC06Kti3HnO/3bIUuZbtyXeNpBMPJCDy+4EKVeXDmX0Xy/Pdijj
|
|
v47V4kdEoylYS/BXl5J8dqeOgV/v0UaoOMYBSIyahFpztGatVPCivR7+QjX4n6UX
|
|
eX9x46v0Tx+rqGxlhRnoJPZx9nlm32OE7yrKY7DeJ34d+JaqiBprbWOgvwKBgHEV
|
|
7hLRsn20QIMz7SwK29eggmc6IqXEP53Z0XsMf4RRi4d+uKgsaVXVPTnTQDTQAQC4
|
|
MA6/rTpt+BX6/U7Z2U3YlbGmVZ715G1SMV4U03Dq3apUhqILE8NjgzRSLqLV0FVx
|
|
kABYwF3V68HuDHURV1msJjvK/jP47vYEm+mMzYelAoGAK2zS87DVUMbwiQOHveGQ
|
|
lhvC6HZsja/CEk2C3RTo+0IsEzc/O8/qyDZd6+rqCmJ9ViKHccvtYwRTzRrrDtP1
|
|
XQ9FR4VO2eH2YLAREzCgfykEzwBPFinM1kBhtpbZID4n8YizSODJTI9pasC1z/zL
|
|
x5MTZmC2cAtxmyx3pY/KFa8=
|
|
-----END PRIVATE KEY-----
|
|
-----BEGIN CERTIFICATE-----
|
|
MIIDLjCCAhagAwIBAgIQHkfoyE/KTKuEhOTumdk4+zANBgkqhkiG9w0BAQsFADAU
|
|
MRIwEAYDVQQDEwlwZXRyLnRlc3QwHhcNMjEwNTI2MDkzNjU5WhcNMjEwNjI2MDk0
|
|
NjU5WjAUMRIwEAYDVQQDEwlwZXRyLnRlc3QwggEiMA0GCSqGSIb3DQEBAQUAA4IB
|
|
DwAwggEKAoIBAQDwFPCd+9yh5QyxctlSvtG6D/5Y1ClB33NSaJ2x7ZwW+xJH2NkW
|
|
aR4eAP0vzrlV9zdfc5PxF0+skOEdDuyhFQwgzGXWfoIFwaxtkvibWo8+qz1PDzxd
|
|
WnlK2Qk9BbF44I3J0cCYE0+6NXLR/8gmUVzt2Rk1xFez8dVvFEEJDK6OiVEeduMA
|
|
eTfjmUcHBUqsHzPtDbxMsG4fhhuEyWH58f9QKIk9Q9SPZ07jAnSKcVuWY+0Tox47
|
|
97hPNTrn2/7c6BqsitOijUD/55RBJo3wpiLcyyIWxLgkMgIlg01nDAY0qhOjFPoP
|
|
lUCKXdRr7BhazanGCArK0z2FzIWA5S4CEDflAgMBAAGjfDB6MA4GA1UdDwEB/wQE
|
|
AwIFoDAJBgNVHRMEAjAAMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjAf
|
|
BgNVHSMEGDAWgBSiCXS05A51/N/eKXEBLiNi/W5ZwDAdBgNVHQ4EFgQUogl0tOQO
|
|
dfzf3ilxAS4jYv1uWcAwDQYJKoZIhvcNAQELBQADggEBADPwcFnUq8NYMlyZriF1
|
|
Yk3tTiLwlMrwHQViz143t/C+lMlQfVME515xxn1SUdEG0JAseCOGiIsqLVpwc042
|
|
cFkBgbCAkIMg3BIJoKMIFMEXdlcbQ9TlGY0QQPxvfT+L1giGaK6mcmBuIBi9iM9k
|
|
8ClJkAbkMgX3A68eWbI8PEaV/KyyHD/zHX/UmnyquXYxUZ9Cdazt3rG7vmt+NTw1
|
|
LlsPZY/5jnNhkjNt+qgruByc5/XNcVJE4VZcEUiDaAwhi5XDigIPx7eM9tF+yuIf
|
|
C+cV51VbGUPQBvJTr0LjGaUDbryrVOAwma5qNi4ekdcOvSrBBs4hflrn8up+HUoX
|
|
zB0=
|
|
-----END CERTIFICATE-----`
|
|
|
|
const testCertBundle2 = `-----BEGIN PRIVATE KEY-----
|
|
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDGwJS2bDR2ugfs
|
|
ODr1/VkhX9A2b6/w66r4habosbIeX9meMsKZbtsMOrWH2uten9H7L0o2rYbU5c3L
|
|
AtEaeEplGG4AamtF4s35axIF8/jgMvkswHjodb2f79iImOx96tE/Odhu3T/uoR1V
|
|
543Q1f80/HZzdpqnIpNZd/NrDMv2Hjm1LcLFMpcZFBpdF4CmAm6J59LjfZWm5qq5
|
|
aJf3MFEdxsX0VwEURmP0lrsvZTJhsu+httv+soFareiOk2W5nU0UhFSpKfOrFfld
|
|
cHxNPX6T2wr7JTYLDfgeRG6VoboPwcdVCCllrKWUlP9zdq+A6zbByHqC+QG6IP2c
|
|
zzXUAMR9AgMBAAECggEAOHAjSpH7a+NzsH5fL88bakC87VhVy8IAMMYzGUysWbe6
|
|
IhQj8lHqXdSmC8f8okgb5ooNNu2bpgUDpSxNmIikB4UiZ3fQsm2vM65V1d7rwy52
|
|
V2DodIpKqIoBIPjb3v25IY0ZipHFP8v8epJgUPcTm0Y9lJgPXnqRAQzw0Gs319FI
|
|
wxVy/RkwJ/p6BYufJs/T7HjCjCSsBA0JPx46qiCGA90u4gmYOwQFmZoEjlcMzPaL
|
|
Oinbfs2WN5mSazev/4zY/bEnFuO1vbpvx1qAEZpoDvN2CeYjAHvhmMe5zQwEFgsY
|
|
Bjhfd8pHuujbkIvHNo8mcRY8D7tFggw0QC8YZBl9+QKBgQDNcJQPQPaLUy8ZUxfL
|
|
8HMfpnVPAt9g0mctMo52CiF95WqMjXhJ5tbn7J2R6JFawCoIunNAWDwCkOg9dzgE
|
|
SwkGAQjSvHNlLOAIx1ej+vjeLvjyCgXqiv66bG16JK4ZA3K/Dewe/o27Tbpny0/7
|
|
AkfjrknhyRjK4pnnZ/kWj6wJtwKBgQD3qqrXHBJD78lo0HnZmNLeI8noM5ruJD23
|
|
9dfTEkr99/cpe6mdD2OJKNsBfUB4y/Ces7EHPa6UEPLl36vIHhg+5chrqrNbRfIv
|
|
LeCeRPkjVTxEuPCtUx7Bt1LqlM1tPfOp67V6160aroLnpIdUsWSOQFuAvGqHKLhG
|
|
Nau31SDzawKBgQC9PIYlxuFTVTx9R10ULljdPqewMCUzOpxvtbIkaRCQt1J+RZIY
|
|
ANrUp9A9Js09muUdRSIEk0Iz2ucSN08SJUwai7lk5NIm0D9N1tGT6wpzHzGRQkpQ
|
|
0dfyQQ5XBJKZ1+NKubhWlIRZlC+wjEcQH/m4cEL+CA8eU70Qu2VmstD14QKBgEmo
|
|
PWT6WUhRMUJ19jdL5zLfy/W+G07GAoEKoaSJpToBHEX/HEO0xvKM7w1zVdBXPvnE
|
|
EVtI8fnhTIwnSGyc3rMeHcw/mVYE6HE1oL8RXlMuz1zU7+dseBI+1m8j0DC0Ixqf
|
|
GnstV7M+wXnpCcKbe39/DnesEbae2qcu4SIsRb9/AoGBAJw6iTZm2kjk/AK3i/ZN
|
|
RpuP6/8jbHZNTvLz8Aw9J4wxoM8wp3kpJe0kMOgzWZPiya4QX7kCma+uoibiR3v/
|
|
ohPVpYqZtLFdfEd9pDXMZCg/q1sOcL94gNiv9w0QCbuvBhAno7OHk9JZMIr3pJ0h
|
|
tT1hverLghBfQ8KhO9Gk+FSM
|
|
-----END PRIVATE KEY-----
|
|
-----BEGIN CERTIFICATE-----
|
|
MIIDLjCCAhagAwIBAgIQD/cl3WNvT3ynxkUONemaPjANBgkqhkiG9w0BAQsFADAU
|
|
MRIwEAYDVQQDEwlwZXRyLnRlc3QwHhcNMjEwNTI2MDkzNDQzWhcNMjEwNjI2MDk0
|
|
NDQzWjAUMRIwEAYDVQQDEwlwZXRyLnRlc3QwggEiMA0GCSqGSIb3DQEBAQUAA4IB
|
|
DwAwggEKAoIBAQDGwJS2bDR2ugfsODr1/VkhX9A2b6/w66r4habosbIeX9meMsKZ
|
|
btsMOrWH2uten9H7L0o2rYbU5c3LAtEaeEplGG4AamtF4s35axIF8/jgMvkswHjo
|
|
db2f79iImOx96tE/Odhu3T/uoR1V543Q1f80/HZzdpqnIpNZd/NrDMv2Hjm1LcLF
|
|
MpcZFBpdF4CmAm6J59LjfZWm5qq5aJf3MFEdxsX0VwEURmP0lrsvZTJhsu+httv+
|
|
soFareiOk2W5nU0UhFSpKfOrFfldcHxNPX6T2wr7JTYLDfgeRG6VoboPwcdVCCll
|
|
rKWUlP9zdq+A6zbByHqC+QG6IP2czzXUAMR9AgMBAAGjfDB6MA4GA1UdDwEB/wQE
|
|
AwIFoDAJBgNVHRMEAjAAMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjAf
|
|
BgNVHSMEGDAWgBTVoGOzKcn+Y+vypnh2e5BzIcQ6NzAdBgNVHQ4EFgQU1aBjsynJ
|
|
/mPr8qZ4dnuQcyHEOjcwDQYJKoZIhvcNAQELBQADggEBAGREAWRpsAUlhLtnqgyh
|
|
qhRDd0xuzlBte5uMWYJxojwK7jmJlb4YbIgm6OfSMW3cd1xN497egji5AiiW37wN
|
|
UDONqYUL4qF3nnPJl9yjvptJCdpZQC1Ma5b2BLF8X8kurh8rYtyx2NNncj6/p/IA
|
|
D/R4qcuA0UaXLdARsppjLTCzk6T/Z8v4qaaXQJBfdHLY2fZo4vSI1yLyTcGia703
|
|
Zn30eMz0PLFCkMEYhZADUia0peReH08WO7RTtE5Nf8r5ZiFSfuJJlBmURMU3MAxJ
|
|
PeihXgcD511I3eMq7A8r7+xlNFEFYaleYwmoYSOt0cBGtoZttXDYacR/xvEkVaxO
|
|
1LQ=
|
|
-----END CERTIFICATE-----`
|
|
|
|
func TestRefreshingCertificate(t *testing.T) {
|
|
mockController := gomock.NewController(t)
|
|
|
|
key1, certs1, err := utilpem.Parse([]byte(testCertBundle1))
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
key2, certs2, err := utilpem.Parse([]byte(testCertBundle2))
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
testCertName := "rotation-test-0"
|
|
|
|
cannotPull := errors.New("Cannot pull certificates from keyvault")
|
|
|
|
// newMockTicker inline function is used to mock time.Ticker to pull the certificate
|
|
// return two functions:
|
|
//
|
|
// mockTicker := func() (tick <-chan time.Time, stop func()) returns chanel to pass ticks and stop signal
|
|
// which is roughly equivalent to:
|
|
// ticker := time.NewTicker(time.Minute)
|
|
// ticker.Stop() // <- this
|
|
// when stop() is called, done is passed to signal end of processing
|
|
//
|
|
// func(context.CancelFunc) is used to generate ticks, passed func() is used as cancel signal
|
|
// for context.WithCancel to signal all ticks are passed
|
|
//
|
|
// Call order is depicted in example for two ticks bellow
|
|
//
|
|
// context mockSource mockTicker fetchCertificate
|
|
// --------------------------------------------------------------------------
|
|
// withCancel
|
|
// 1 -> tick -> fetchCertificateonce
|
|
// 2 -> tick -> fetchCertificateonce
|
|
// <- cancel
|
|
// Done -> stop
|
|
// done <- stop <-
|
|
newMockTicker := func(n int) (func() (<-chan time.Time, func()), func(context.CancelFunc)) {
|
|
s := make(chan time.Time)
|
|
done := make(chan struct{})
|
|
|
|
mockTicker := func() (tick <-chan time.Time, stop func()) {
|
|
return s, func() {
|
|
done <- struct{}{}
|
|
}
|
|
}
|
|
|
|
mockSource := func(cancel context.CancelFunc) {
|
|
for i := 0; i < n; i++ {
|
|
s <- time.Time{}
|
|
}
|
|
cancel()
|
|
<-done
|
|
}
|
|
|
|
return mockTicker, mockSource
|
|
}
|
|
|
|
tt := []struct {
|
|
name string
|
|
tickCount int
|
|
managerFactory func(*gomock.Controller) keyvault.Manager
|
|
wantKey *rsa.PrivateKey
|
|
wantCert *x509.Certificate
|
|
wantErr error
|
|
}{
|
|
{
|
|
name: "test initial certificate, pull exactly once, ticks one time",
|
|
managerFactory: func(controller *gomock.Controller) keyvault.Manager {
|
|
manager := mock_keyvault.NewMockManager(controller)
|
|
manager.EXPECT().GetCertificateSecret(gomock.Any(), testCertName).Return(key1, certs1, nil)
|
|
return manager
|
|
},
|
|
tickCount: 0,
|
|
wantKey: key1,
|
|
wantCert: certs1[0],
|
|
wantErr: nil,
|
|
},
|
|
{
|
|
name: "test refresh certificate, pull exactly twice, first on start, second on refresh, one tick",
|
|
managerFactory: func(controller *gomock.Controller) keyvault.Manager {
|
|
manager := mock_keyvault.NewMockManager(mockController)
|
|
gomock.InOrder(
|
|
manager.EXPECT().GetCertificateSecret(gomock.Any(), testCertName).Return(key1, certs1, nil),
|
|
manager.EXPECT().GetCertificateSecret(gomock.Any(), testCertName).Return(key2, certs2, nil),
|
|
)
|
|
return manager
|
|
},
|
|
tickCount: 1,
|
|
wantKey: key2,
|
|
wantCert: certs2[0],
|
|
wantErr: nil,
|
|
},
|
|
{
|
|
name: "test initial error, pull exactly once with an error, no tick",
|
|
managerFactory: func(controller *gomock.Controller) keyvault.Manager {
|
|
manager := mock_keyvault.NewMockManager(mockController)
|
|
manager.EXPECT().GetCertificateSecret(gomock.Any(), testCertName).Return(nil, nil, cannotPull)
|
|
return manager
|
|
},
|
|
tickCount: 0,
|
|
wantKey: nil,
|
|
wantCert: nil,
|
|
wantErr: cannotPull,
|
|
},
|
|
{
|
|
name: "test refresh error, pull exactly twice, first on start, second time with an error, one tick",
|
|
managerFactory: func(controller *gomock.Controller) keyvault.Manager {
|
|
manager := mock_keyvault.NewMockManager(controller)
|
|
gomock.InOrder(
|
|
manager.EXPECT().GetCertificateSecret(gomock.Any(), testCertName).Return(key1, certs1, nil),
|
|
manager.EXPECT().GetCertificateSecret(gomock.Any(), testCertName).Return(nil, nil, cannotPull),
|
|
)
|
|
return manager
|
|
},
|
|
tickCount: 1,
|
|
wantKey: key1,
|
|
wantCert: certs1[0],
|
|
wantErr: nil,
|
|
},
|
|
{
|
|
name: "test refresh, pull exactly 5 times, 4 ticks",
|
|
managerFactory: func(controller *gomock.Controller) keyvault.Manager {
|
|
manager := mock_keyvault.NewMockManager(controller)
|
|
manager.EXPECT().GetCertificateSecret(gomock.Any(), testCertName).Return(key1, certs1, nil).Times(5)
|
|
return manager
|
|
},
|
|
tickCount: 4,
|
|
wantKey: key1,
|
|
wantCert: certs1[0],
|
|
wantErr: nil,
|
|
},
|
|
}
|
|
|
|
for _, test := range tt {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
defer cancel()
|
|
|
|
mock, tick := newMockTicker(test.tickCount)
|
|
|
|
refreshing := newCertificateRefresher(
|
|
logrus.NewEntry(logrus.StandardLogger()),
|
|
// interval is not used in tests, it is mocked
|
|
0,
|
|
test.managerFactory(mockController),
|
|
testCertName,
|
|
)
|
|
refreshing.(*refreshingCertificate).newTicker = mock
|
|
|
|
err := refreshing.Start(ctx)
|
|
if err != test.wantErr {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
// call tick to do all registered ticks, once finished, cancel context and wait for done channel
|
|
// canceled context finishes the fetchCertificate goroutine, this triggers registered stop()
|
|
// which sends done, which finally tells tick to end and allow code to continue
|
|
tick(cancel)
|
|
|
|
testkey, testCerts := refreshing.GetCertificates()
|
|
if !testkey.Equal(test.wantKey) {
|
|
t.Error("returned private key does not match")
|
|
}
|
|
|
|
if !testCerts[0].Equal(test.wantCert) {
|
|
t.Error("returned certificate does not match")
|
|
}
|
|
})
|
|
}
|
|
}
|