blob: 5be7b11a62526b0658f50848cfbaf90bc9dd5fa3 [file] [log] [blame]
// Copyright Istio 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 chiron
import (
"bytes"
"context"
"reflect"
"testing"
"time"
)
import (
cert "k8s.io/api/certificates/v1"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes/fake"
)
import (
"github.com/apache/dubbo-go-pixiu/security/pkg/pki/ca"
"github.com/apache/dubbo-go-pixiu/security/pkg/pki/util"
)
const (
exampleExpiredCert = `-----BEGIN CERTIFICATE-----
MIIDXjCCAkagAwIBAgIQGbJDoVfdXBsPos+p8RGqZDANBgkqhkiG9w0BAQsFADBF
MQswCQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50
ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMB4XDTE5MDgxNjIxNDQzNVoXDTE5MDgxNjIx
NDQzNlowEzERMA8GA1UEChMISnVqdSBvcmcwggEiMA0GCSqGSIb3DQEBAQUAA4IB
DwAwggEKAoIBAQDYwh5Txh3pJVIjCt5JciYM4GMt3DsjF7E4JdVUUu682YpFevT8
rWJVaZjZajVQaT4IIYw+kxqf0hdVLJG11OHI3OeZ1IJe5yuV+STXks/+vEDMMLuD
vqWZl7oFIuXR6merPAPLAmxo0U5E9kp6ftfHJMK3uj1eNp/BZE/xH8QYe86kAckd
QYPsz0gW1YMdpxRG1OFmbih8CdbRUjCgHHPOxbAJOIDM4xtj8M1rFgVnyH+8NucW
DddKy63GASUphakC73hMnoEQksbVg6rdYlnYrdPcLgmcLeO+vdI5EjbXMaXy7GMk
JvTJI5KRAq+jPHOZmWHd2zAGUpLRPr8EFQp3AgMBAAGjfDB6MA4GA1UdDwEB/wQE
AwIFoDAMBgNVHRMBAf8EAjAAMB8GA1UdIwQYMBaAFKgUbNBCMgiPmanahMYt1KRO
dFX9MDkGA1UdEQEB/wQvMC2GK3NwaWZmZTovL2NsdXN0ZXIubG9jYWwvbnMvZGVm
YXVsdC9zYS9jbGllbnQwDQYJKoZIhvcNAQELBQADggEBAFEf0ZJnNz7h4MqGz720
0FShDX02WPmNSfl89gY/Q4/djTF5yfqgaDAZWf4PsDwpdMpT0eRrshpzvLrRjcd4
Ev6bXGncIwnNFtXQ4dseup+kmaYZF24zpRjyoH9owwu1T5Wb1cSrpFFby5xWuoTC
bsKlR5CZF+dHUwc1iMaj/4kuVjvt4imM0coeaOUzOcMCruO54IqsFcJg2YA80MI+
6UiM8hj8ERDls5iBNThWKKE0yva4HFh1gj5f427NP7CSikUFXs61gytlFHEjHyco
lK8KK65mLIDLshz2+6lPHFXv9tEpouEUws5lhR5O9Q+9LmfBLPE7rD2aUic8EApg
3TE=
-----END CERTIFICATE-----`
// The example certificate here can be generated through
// the following command:
// kubectl exec -it POD-NAME -n NAMESPACE -- cat /var/run/secrets/kubernetes.io/serviceaccount/ca.crt
// Its validity is 5 years.
exampleCACert1 = `-----BEGIN CERTIFICATE-----
MIIDCzCCAfOgAwIBAgIQbfOzhcKTldFipQ1X2WXpHDANBgkqhkiG9w0BAQsFADAv
MS0wKwYDVQQDEyRhNzU5YzcyZC1lNjcyLTQwMzYtYWMzYy1kYzAxMDBmMTVkNWUw
HhcNMTkwNTE2MjIxMTI2WhcNMjQwNTE0MjMxMTI2WjAvMS0wKwYDVQQDEyRhNzU5
YzcyZC1lNjcyLTQwMzYtYWMzYy1kYzAxMDBmMTVkNWUwggEiMA0GCSqGSIb3DQEB
AQUAA4IBDwAwggEKAoIBAQC6sSAN80Ci0DYFpNDumGYoejMQai42g6nSKYS+ekvs
E7uT+eepO74wj8o6nFMNDu58+XgIsvPbWnn+3WtUjJfyiQXxmmTg8om4uY1C7R1H
gMsrL26pUaXZ/lTE8ZV5CnQJ9XilagY4iZKeptuZkxrWgkFBD7tr652EA3hmj+3h
4sTCQ+pBJKG8BJZDNRrCoiABYBMcFLJsaKuGZkJ6KtxhQEO9QxJVaDoSvlCRGa8R
fcVyYQyXOZ+0VHZJQgaLtqGpiQmlFttpCwDiLfMkk3UAd79ovkhN1MCq+O5N7YVt
eVQWaTUqUV2tKUFvVq21Zdl4dRaq+CF5U8uOqLY/4Kg9AgMBAAGjIzAhMA4GA1Ud
DwEB/wQEAwICBDAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQCg
oF71Ey2b1QY22C6BXcANF1+wPzxJovFeKYAnUqwh3rF7pIYCS/adZXOKlgDBsbcS
MxAGnCRi1s+A7hMYj3sQAbBXttc31557lRoJrx58IeN5DyshT53t7q4VwCzuCXFT
3zRHVRHQnO6LHgZx1FuKfwtkhfSXDyYU2fQYw2Hcb9krYU/alViVZdE0rENXCClq
xO7AQk5MJcGg6cfE5wWAKU1ATjpK4CN+RTn8v8ODLoI2SW3pfsnXxm93O+pp9HN4
+O+1PQtNUWhCfh+g6BN2mYo2OEZ8qGSxDlMZej4YOdVkW8PHmFZTK0w9iJKqM5o1
V6g5gZlqSoRhICK09tpc
-----END CERTIFICATE-----`
exampleCACert2 = `-----BEGIN CERTIFICATE-----
MIIDDDCCAfSgAwIBAgIRALsN8ND73NbYSKTZZa4jf2EwDQYJKoZIhvcNAQELBQAw
LzEtMCsGA1UEAxMkZTNlM2RlZWQtYzIyNi00OWM2LThmOTktNDU3NmRmMzQ0YWQ1
MB4XDTE5MDYwMTE1NTU0M1oXDTI0MDUzMDE2NTU0M1owLzEtMCsGA1UEAxMkZTNl
M2RlZWQtYzIyNi00OWM2LThmOTktNDU3NmRmMzQ0YWQ1MIIBIjANBgkqhkiG9w0B
AQEFAAOCAQ8AMIIBCgKCAQEA43oeK/hS92ANjmg50LCl3tM7eYAlBB/XgCl+bfp3
KwEf+uW5yEvzSVHd2VPFI/kJJeLFrsyCRaU4FwxWcEr2Ld07DPL34oyZRRXQF0w6
4ZNSVmevBNdZLqHcoIUtR1iFJbkctE93HpGw5Kg1NXRLDu47wQtzcC3GDOEk1amu
mL916R2OcYEeOcyRDnlbLcsTYRvK5WBQsux4E0iu2Eo9GIajKmbxVLxA9fsmqG4i
/HoVkLmCg+ZRPR/66AFLPFV1J3RWp0K4HKGzBeCyd2RC+o0g8tJX3EVSuQpqzS8p
i2t71cYu/Sf5gt3wXsNHyzE6bF1o+acyzWvJlBym/HsbAQIDAQABoyMwITAOBgNV
HQ8BAf8EBAMCAgQwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEA
kJXGkFEybCp7RxUSILuIqMtKcYcQU9ulKmLSn51VrpcRHP4SH7UJ0aXMjAdRLsop
em7YgbvToGNingqcmSJlunR3jXDecSXJLUO1xcfw6N+B2BXRgUv8wV42btr2EV6q
4HKou+MnKdrQkMUx218AT8TNPBb/Yx01m8YUS7mGUTApAhBneGEcKJ8xOznIuR5v
CihWQA9AmUvfixpXNpJc4vqiYErwIXrYpuwc79SRtLuO70vV7FCctz+4JPpR7mp9
dHMZfGO1KXMbYT9P5bm+itlWSyrnn0qK/Cn5RHBoFyY91VcQJTgABS/z5O0pZ662
sNzF00Jhi0gU7th75QT3MA==
-----END CERTIFICATE-----`
)
func TestNewWebhookController(t *testing.T) {
testCases := map[string]struct {
gracePeriodRatio float32
minGracePeriod time.Duration
k8sCaCertFile string
dnsNames []string
secretNames []string
secretNamespace string
shouldFail bool
}{
"invalid grade period ratio": {
gracePeriodRatio: 1.5,
dnsNames: []string{"foo"},
secretNames: []string{"foo.secret"},
secretNamespace: "foo.ns",
k8sCaCertFile: "./test-data/example-invalid-ca-cert.pem",
shouldFail: true,
},
"invalid CA cert path": {
gracePeriodRatio: 0.6,
dnsNames: []string{"foo"},
secretNames: []string{"foo.secret"},
secretNamespace: "foo.ns",
k8sCaCertFile: "./invalid-path/invalid-file",
shouldFail: true,
},
"valid CA cert path": {
gracePeriodRatio: 0.6,
dnsNames: []string{"foo"},
secretNames: []string{"foo.secret"},
secretNamespace: "foo.ns",
k8sCaCertFile: "./test-data/example-ca-cert.pem",
shouldFail: false,
},
}
for _, tc := range testCases {
client := fake.NewSimpleClientset()
_, err := NewWebhookController(tc.gracePeriodRatio, tc.minGracePeriod,
client, tc.k8sCaCertFile, tc.secretNames, tc.dnsNames, tc.secretNamespace, "test-issuer")
if tc.shouldFail {
if err == nil {
t.Errorf("should have failed at NewWebhookController()")
} else {
// Should fail, skip the current case.
continue
}
} else if err != nil {
t.Errorf("should not fail at NewWebhookController(), err: %v", err)
}
}
}
func TestUpsertSecret(t *testing.T) {
dnsNames := []string{"foo"}
testCases := map[string]struct {
gracePeriodRatio float32
minGracePeriod time.Duration
k8sCaCertFile string
dnsNames []string
secretNames []string
secretNamespace string
scrtName string
expectFaill bool
}{
"upsert a valid secret name should succeed": {
gracePeriodRatio: 0.6,
k8sCaCertFile: "./test-data/example-ca-cert.pem",
dnsNames: dnsNames,
secretNames: []string{"istio.webhook.foo"},
secretNamespace: "foo.ns",
scrtName: "istio.webhook.foo",
expectFaill: false,
},
}
for _, tc := range testCases {
client := fake.NewSimpleClientset()
csr := &cert.CertificateSigningRequest{
ObjectMeta: metav1.ObjectMeta{
Name: "domain-cluster.local-ns--secret-mock-secret",
},
Status: cert.CertificateSigningRequestStatus{
Certificate: []byte(exampleIssuedCert),
},
}
client.PrependReactor("get", "certificatesigningrequests", defaultReactionFunc(csr))
certWatchTimeout = time.Millisecond
wc, err := NewWebhookController(tc.gracePeriodRatio, tc.minGracePeriod,
client, tc.k8sCaCertFile, tc.secretNames, tc.dnsNames, tc.secretNamespace, "test-issuer")
if err != nil {
t.Errorf("failed at creating webhook controller: %v", err)
continue
}
err = wc.upsertSecret(tc.scrtName, tc.dnsNames[0], tc.secretNamespace)
if tc.expectFaill {
if err == nil {
t.Errorf("should have failed at upsertSecret")
}
continue
} else if err != nil {
t.Errorf("should not failed at upsertSecret, err: %v", err)
}
}
}
func TestScrtDeleted(t *testing.T) {
dnsNames := []string{"foo"}
testCases := map[string]struct {
gracePeriodRatio float32
minGracePeriod time.Duration
k8sCaCertFile string
dnsNames []string
secretNames []string
secretNamespace string
}{
"recover a deleted secret should succeed": {
gracePeriodRatio: 0.6,
k8sCaCertFile: "./test-data/example-ca-cert.pem",
dnsNames: dnsNames,
secretNames: []string{"istio.webhook.foo"},
secretNamespace: "foo.ns",
},
}
csr := &cert.CertificateSigningRequest{
ObjectMeta: metav1.ObjectMeta{
Name: "domain-cluster.local-ns--secret-mock-secret",
},
Status: cert.CertificateSigningRequestStatus{
Certificate: []byte(exampleIssuedCert),
},
}
certWatchTimeout = time.Millisecond
for _, tc := range testCases {
client := fake.NewSimpleClientset()
client.PrependReactor("get", "certificatesigningrequests", defaultReactionFunc(csr))
wc, err := NewWebhookController(tc.gracePeriodRatio, tc.minGracePeriod,
client, tc.k8sCaCertFile, tc.secretNames, tc.dnsNames, tc.secretNamespace, "test-issuer")
if err != nil {
t.Errorf("failed at creating webhook controller: %v", err)
continue
}
_, err = client.CoreV1().Secrets(tc.secretNamespace).Create(context.TODO(), &v1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: tc.secretNames[0],
Labels: map[string]string{
"secret": "for-testing",
},
},
}, metav1.CreateOptions{})
if err != nil {
t.Fatalf("failed creating test secret (%v): %v", tc.secretNames[0], err)
}
scrt, err := client.CoreV1().Secrets(tc.secretNamespace).Get(context.TODO(), tc.secretNames[0], metav1.GetOptions{})
if err != nil || scrt == nil {
t.Fatalf("failed to get test secret (%v): err (%v), secret (%v)", tc.secretNames[0], err, scrt)
}
err = client.CoreV1().Secrets(tc.secretNamespace).Delete(context.TODO(), tc.secretNames[0], metav1.DeleteOptions{})
if err != nil {
t.Fatalf("failed deleting test secret (%v): %v", tc.secretNames[0], err)
}
_, err = client.CoreV1().Secrets(tc.secretNamespace).Get(context.TODO(), tc.secretNames[0], metav1.GetOptions{})
if err == nil {
t.Fatal("the deleted secret should not exist")
}
// The secret deleted should be recovered.
wc.scrtDeleted(scrt)
scrt, err = client.CoreV1().Secrets(tc.secretNamespace).Get(context.TODO(), tc.secretNames[0], metav1.GetOptions{})
if err != nil || scrt == nil {
t.Fatalf("after scrtDeleted(), failed to get test secret (%v): err (%v), secret (%v)",
tc.secretNames[0], err, scrt)
}
}
}
func TestScrtUpdated(t *testing.T) {
dnsNames := []string{"foo"}
certWatchTimeout = time.Millisecond
testCases := map[string]struct {
gracePeriodRatio float32
minGracePeriod time.Duration
k8sCaCertFile string
dnsNames []string
secretNames []string
secretNamespace string
changeCACert bool
invalidNewSecret bool
replaceWithExpiredCert bool
expectUpdate bool
newScrtName string
}{
"invalid new secret should not affect existing secret": {
gracePeriodRatio: 0.6,
k8sCaCertFile: "./test-data/example-ca-cert.pem",
dnsNames: dnsNames,
secretNamespace: "foo.ns",
secretNames: []string{"istio.webhook.foo"},
invalidNewSecret: true,
expectUpdate: false,
},
"non-webhook secret should not be updated": {
gracePeriodRatio: 0.6,
k8sCaCertFile: "./test-data/example-ca-cert.pem",
dnsNames: dnsNames,
secretNames: []string{"istio.webhook.foo"},
secretNamespace: "foo.ns",
newScrtName: "bar",
invalidNewSecret: false,
expectUpdate: false,
},
"expired certificate should be updated": {
gracePeriodRatio: 0.6,
k8sCaCertFile: "./test-data/example-ca-cert.pem",
dnsNames: dnsNames,
secretNames: []string{"istio.webhook.foo"},
secretNamespace: "foo.ns",
replaceWithExpiredCert: true,
expectUpdate: true,
},
"changing CA certificate should lead to updating secret": {
gracePeriodRatio: 0.6,
k8sCaCertFile: "./test-data/example-ca-cert.pem",
dnsNames: dnsNames,
secretNames: []string{"istio.webhook.foo"},
secretNamespace: "foo.ns",
changeCACert: true,
replaceWithExpiredCert: false,
expectUpdate: true,
},
}
csr := &cert.CertificateSigningRequest{
ObjectMeta: metav1.ObjectMeta{
Name: "domain-cluster.local-ns--secret-mock-secret",
},
Status: cert.CertificateSigningRequestStatus{
Certificate: []byte(exampleIssuedCert),
},
}
for _, tc := range testCases {
client := fake.NewSimpleClientset()
client.PrependReactor("get", "certificatesigningrequests", defaultReactionFunc(csr))
wc, err := NewWebhookController(tc.gracePeriodRatio, tc.minGracePeriod,
client, tc.k8sCaCertFile, tc.secretNames, tc.dnsNames, tc.secretNamespace, "test-issuer")
if err != nil {
t.Errorf("failed at creating webhook controller: %v", err)
continue
}
err = wc.upsertSecret(tc.secretNames[0], tc.dnsNames[0], tc.secretNamespace)
if err != nil {
t.Errorf("should not failed at upsertSecret, err: %v", err)
}
scrt, err := client.CoreV1().Secrets(tc.secretNamespace).Get(context.TODO(), tc.secretNames[0], metav1.GetOptions{})
if err != nil || scrt == nil {
t.Fatalf("failed to get test secret (%v): err (%v), secret (%v)", tc.secretNames[0], err, scrt)
}
if tc.newScrtName != "" {
scrt.Name = tc.newScrtName
}
if tc.replaceWithExpiredCert {
scrt.Data[ca.CertChainFile] = []byte(exampleExpiredCert)
}
if tc.changeCACert {
scrt.Data[ca.RootCertFile] = []byte(exampleCACert2)
}
var newScrt interface{}
if tc.invalidNewSecret {
// point to an invalid secret object
newScrt = &v1.ConfigMap{}
} else {
newScrt = &v1.Secret{}
scrt.DeepCopyInto(newScrt.(*v1.Secret))
}
wc.scrtUpdated(scrt, newScrt)
// scrt2 is the secret after updating, which will be compared against original scrt
scrt2, err := client.CoreV1().Secrets(tc.secretNamespace).Get(context.TODO(), tc.secretNames[0], metav1.GetOptions{})
if err != nil || scrt2 == nil {
t.Fatalf("failed to get test secret (%v): err (%v), secret (%v)", tc.secretNames[0], err, scrt2)
}
if tc.newScrtName != "" {
scrt2.Name = tc.newScrtName
}
if tc.expectUpdate {
if reflect.DeepEqual(scrt, scrt2) {
t.Errorf("change is expected while there is no change")
}
} else {
if !reflect.DeepEqual(scrt, scrt2) {
t.Errorf("change is not expected while there is change")
}
}
}
}
func TestRefreshSecret(t *testing.T) {
dnsNames := []string{"foo"}
testCases := map[string]struct {
gracePeriodRatio float32
minGracePeriod time.Duration
k8sCaCertFile string
dnsNames []string
secretNames []string
secretNamespace string
changeCACert bool
expectUpdate bool
}{
"refresh a secret with different CA cert should succeed": {
gracePeriodRatio: 0.6,
k8sCaCertFile: "./test-data/example-ca-cert.pem",
dnsNames: dnsNames,
secretNames: []string{"istio.webhook.foo"},
secretNamespace: "foo.ns",
changeCACert: true,
expectUpdate: true,
},
}
csr := &cert.CertificateSigningRequest{
ObjectMeta: metav1.ObjectMeta{
Name: "domain-cluster.local-ns--secret-mock-secret",
},
Status: cert.CertificateSigningRequestStatus{
Certificate: []byte(exampleIssuedCert),
},
}
for _, tc := range testCases {
client := fake.NewSimpleClientset()
client.PrependReactor("get", "certificatesigningrequests", defaultReactionFunc(csr))
wc, err := NewWebhookController(tc.gracePeriodRatio, tc.minGracePeriod,
client, tc.k8sCaCertFile, tc.secretNames, tc.dnsNames, tc.secretNamespace, "test-issuer")
if err != nil {
t.Errorf("failed at creating webhook controller: %v", err)
continue
}
err = wc.upsertSecret(tc.secretNames[0], tc.dnsNames[0], tc.secretNamespace)
if err != nil {
t.Errorf("should not failed at upsertSecret, err: %v", err)
}
scrt, err := client.CoreV1().Secrets(tc.secretNamespace).Get(context.TODO(), tc.secretNames[0], metav1.GetOptions{})
if err != nil || scrt == nil {
t.Fatalf("failed to get test secret (%v): err (%v), secret (%v)", tc.secretNames[0], err, scrt)
}
if tc.changeCACert {
scrt.Data[ca.RootCertFile] = []byte(exampleCACert2)
}
newScrt := &v1.Secret{}
scrt.DeepCopyInto(newScrt)
err = wc.refreshSecret(newScrt)
if err != nil {
t.Fatalf("failed to refresh secret (%v), err: %v", newScrt, err)
}
// scrt2 is the secret after refreshing, which will be compared against original scrt
scrt2, err := client.CoreV1().Secrets(tc.secretNamespace).Get(context.TODO(), tc.secretNames[0], metav1.GetOptions{})
if err != nil || scrt2 == nil {
t.Fatalf("failed to get test secret (%v): err (%v), secret (%v)", tc.secretNames[0], err, scrt2)
}
if tc.expectUpdate {
if reflect.DeepEqual(scrt, scrt2) {
t.Errorf("change is expected while there is no change")
}
} else {
if !reflect.DeepEqual(scrt, scrt2) {
t.Errorf("change is not expected while there is change")
}
}
}
}
func TestCleanUpCertGen(t *testing.T) {
dnsNames := []string{"foo"}
testCases := map[string]struct {
gracePeriodRatio float32
minGracePeriod time.Duration
k8sCaCertFile string
dnsNames []string
secretNames []string
secretNamespace string
}{
"clean up a CSR should succeed": {
gracePeriodRatio: 0.6,
k8sCaCertFile: "./test-data/example-ca-cert.pem",
dnsNames: dnsNames,
secretNames: []string{"istio.webhook.foo"},
secretNamespace: "foo.ns",
},
}
csrName := "test-csr"
for _, tc := range testCases {
client := fake.NewSimpleClientset()
wc, err := NewWebhookController(tc.gracePeriodRatio, tc.minGracePeriod,
client, tc.k8sCaCertFile, tc.secretNames, tc.dnsNames, tc.secretNamespace, "test-issuer")
if err != nil {
t.Fatalf("failed at creating webhook controller: %v", err)
}
options := util.CertOptions{
Host: "test-host",
RSAKeySize: keySize,
IsDualUse: false,
PKCS8Key: false,
}
csrPEM, _, err := util.GenCSR(options)
if err != nil {
t.Fatalf("CSR generation error (%v)", err)
}
k8sCSR := &cert.CertificateSigningRequest{
TypeMeta: metav1.TypeMeta{
Kind: "CertificateSigningRequest",
},
ObjectMeta: metav1.ObjectMeta{
Name: csrName,
},
Spec: cert.CertificateSigningRequestSpec{
Request: csrPEM,
SignerName: "test-signer",
Usages: []cert.KeyUsage{
cert.UsageDigitalSignature,
cert.UsageKeyEncipherment,
cert.UsageServerAuth,
cert.UsageClientAuth,
},
},
}
_, err = wc.clientset.CertificatesV1().CertificateSigningRequests().Create(context.TODO(), k8sCSR, metav1.CreateOptions{})
if err != nil {
t.Fatalf("error when creating CSR: %v", err)
}
csr, err := wc.clientset.CertificatesV1().CertificateSigningRequests().Get(context.TODO(), csrName, metav1.GetOptions{})
if err != nil || csr == nil {
t.Fatalf("failed to get CSR: name (%v), err (%v), CSR (%v)", csrName, err, csr)
}
// The CSR should be deleted.
err = cleanUpCertGen(wc.clientset, true, csrName)
if err != nil {
t.Errorf("cleanUpCertGen returns an error: %v", err)
}
_, err = wc.clientset.CertificatesV1().CertificateSigningRequests().Get(context.TODO(), csrName, metav1.GetOptions{})
if err == nil {
t.Fatalf("should failed at getting CSR: name (%v)", csrName)
}
}
}
func TestIsWebhookSecret(t *testing.T) {
dnsNames := []string{"foo", "bar"}
testCases := map[string]struct {
gracePeriodRatio float32
minGracePeriod time.Duration
k8sCaCertFile string
namespace string
dnsNames []string
secretNames []string
secretNamespace string
scrtName string
scrtNameSpace string
expectedRet bool
}{
"a valid webhook secret in valid namespace": {
gracePeriodRatio: 0.6,
k8sCaCertFile: "./test-data/example-ca-cert.pem",
dnsNames: dnsNames,
secretNames: []string{"istio.webhook.foo", "istio.webhook.bar"},
secretNamespace: "ns.foo",
namespace: "ns.foo",
scrtName: "istio.webhook.foo",
scrtNameSpace: "ns.foo",
expectedRet: true,
},
"an invalid webhook secret in valid namespace": {
gracePeriodRatio: 0.6,
k8sCaCertFile: "./test-data/example-ca-cert.pem",
namespace: "ns.foo",
dnsNames: dnsNames,
secretNames: []string{"istio.webhook.foo", "istio.webhook.bar"},
secretNamespace: "ns.foo",
scrtName: "istio.webhook.invalid",
scrtNameSpace: "ns.foo",
expectedRet: false,
},
"a valid webhook secret in invalid namespace": {
gracePeriodRatio: 0.6,
k8sCaCertFile: "./test-data/example-ca-cert.pem",
namespace: "ns.foo",
dnsNames: dnsNames,
secretNames: []string{"istio.webhook.foo", "istio.webhook.bar"},
secretNamespace: "ns.foo",
scrtName: "istio.webhook.foo",
scrtNameSpace: "ns.invalid",
expectedRet: false,
},
}
for _, tc := range testCases {
client := fake.NewSimpleClientset()
wc, err := NewWebhookController(tc.gracePeriodRatio, tc.minGracePeriod,
client,
tc.k8sCaCertFile, tc.secretNames, tc.dnsNames, tc.secretNamespace, "test-issuer")
if err != nil {
t.Fatalf("failed to create a webhook controller: %v", err)
}
ret := wc.isWebhookSecret(tc.scrtName, tc.scrtNameSpace)
if tc.expectedRet != ret {
t.Fatalf("expected result (%v) differs from the actual result (%v)", tc.expectedRet, ret)
}
// Sleep for watchers to release the file handles
time.Sleep(10 * time.Millisecond)
}
}
func TestGetCACert(t *testing.T) {
dnsNames := []string{"foo"}
testCases := map[string]struct {
gracePeriodRatio float32
minGracePeriod time.Duration
k8sCaCertFile string
namespace string
dnsNames []string
secretNames []string
secretNamespace string
expectFail bool
}{
"getCACert should succeed for a valid certificate": {
gracePeriodRatio: 0.6,
k8sCaCertFile: "./test-data/example-ca-cert.pem",
namespace: "foo.ns",
dnsNames: dnsNames,
secretNames: []string{"istio.webook.foo"},
secretNamespace: "foo.ns",
expectFail: false,
},
}
for _, tc := range testCases {
client := fake.NewSimpleClientset()
// If the CA cert. is invalid, NewWebhookController will fail.
wc, err := NewWebhookController(tc.gracePeriodRatio, tc.minGracePeriod,
client,
tc.k8sCaCertFile, tc.secretNames, tc.dnsNames, tc.secretNamespace, "test-issuer")
if err != nil {
t.Fatalf("failed at creating webhook controller: %v", err)
}
cert, err := wc.getCACert()
if !tc.expectFail {
if err != nil {
t.Errorf("failed to get CA cert: %v", err)
} else if !bytes.Equal(cert, []byte(exampleCACert1)) {
t.Errorf("the CA certificate read does not match the actual certificate")
}
} else if err == nil {
t.Error("expect failure on getting CA cert but succeeded")
}
}
}
func TestGetDNSName(t *testing.T) {
dnsNames := []string{"foo", "bar", "baz"}
testCases := map[string]struct {
gracePeriodRatio float32
minGracePeriod time.Duration
k8sCaCertFile string
dnsNames []string
secretNames []string
secretNamespace string
scrtName string
expectFound bool
expectedSvcName string
}{
"a service corresponding to a secret exists": {
gracePeriodRatio: 0.6,
k8sCaCertFile: "./test-data/example-ca-cert.pem",
dnsNames: dnsNames,
secretNames: []string{"istio.webhook.foo", "istio.webhook.bar", "istio.webhoo.baz"},
secretNamespace: "foo.ns",
scrtName: "istio.webhook.foo",
expectFound: true,
expectedSvcName: "foo",
},
"a service corresponding to a secret does not exists": {
gracePeriodRatio: 0.6,
k8sCaCertFile: "./test-data/example-ca-cert.pem",
dnsNames: dnsNames,
secretNames: []string{"istio.webhook.foo", "istio.webhook.bar", "istio.webhoo.baz"},
secretNamespace: "foo.ns",
scrtName: "istio.webhook.barr",
expectFound: false,
expectedSvcName: "bar",
},
}
for _, tc := range testCases {
client := fake.NewSimpleClientset()
wc, err := NewWebhookController(tc.gracePeriodRatio, tc.minGracePeriod,
client,
tc.k8sCaCertFile, tc.secretNames, tc.dnsNames, "foo.ns", "test-issuer")
if err != nil {
t.Errorf("failed to create a webhook controller: %v", err)
}
ret, found := wc.getDNSName(tc.scrtName)
if tc.expectFound != found {
t.Errorf("expected found (%v) differs from the actual found (%v)", tc.expectFound, found)
continue
}
if found && tc.expectedSvcName != ret {
t.Errorf("the service name (%v) returned is not as expcted (%v)", ret, tc.expectedSvcName)
}
}
}