blob: 6feba89a8e70ec7cd686b0b3061f7b54780835de [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 ra
import (
"context"
"os"
"path"
"testing"
"time"
)
import (
meshconfig "istio.io/api/mesh/v1alpha1"
cert "k8s.io/api/certificates/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
import (
"github.com/apache/dubbo-go-pixiu/pkg/kube"
"github.com/apache/dubbo-go-pixiu/pkg/spiffe"
"github.com/apache/dubbo-go-pixiu/pkg/test"
"github.com/apache/dubbo-go-pixiu/pkg/test/env"
"github.com/apache/dubbo-go-pixiu/security/pkg/pki/ca"
pkiutil "github.com/apache/dubbo-go-pixiu/security/pkg/pki/util"
)
const (
TestCertificatePEM = `-----BEGIN CERTIFICATE-----
MIIDGDCCAgCgAwIBAgIRAKvYcPLFqnJcwtshCGfNzTswDQYJKoZIhvcNAQELBQAw
LzEtMCsGA1UEAxMkYTc1OWM3MmQtZTY3Mi00MDM2LWFjM2MtZGMwMTAwZjE1ZDVl
MB4XDTE5MDgwNjE5NTU0NVoXDTI0MDgwNDE5NTU0NVowCzEJMAcGA1UEChMAMIIB
IjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyLIFJU5yJ5VXhbmizir+7Glm
1tVEYXKGiqYbMRbfsFm7V6Z4l00D9/eHvfTXaFpqhv6HBm31MArjYB3OaaV6krvT
whBUEPSkGBFe/eMPSFWBW27a0nw0cK2s/5yuFhTRtcUrZ9+ojJg4IS3oSm2UZ6UJ
DuNI3qwB6OlPQOcWX8uEp4eAaolD1lIbLRQYvxYrBqnyCZBLE+MJgA1/VB3dAECB
TxPtAqcwLFcvsM5ABys8yK8FrqRn5Bx54NiztgG+yU30W33xjdqzmEmuIIk4JjPU
ZQRsug7XClDvQKM6lbYcYS1td2zT08hdgURFXJ9VR64ALFp00/bvglpryu8FmQID
AQABo1MwUTAMBgNVHRMBAf8EAjAAMEEGA1UdEQQ6MDiCHHByb3RvbXV0YXRlLmlz
dGlvLXN5c3RlbS5zdmOCGHByb3RvbXV0YXRlLmlzdGlvLXN5c3RlbTANBgkqhkiG
9w0BAQsFAAOCAQEAhcVEZSuNMqMUJrWVb3b+6pmw9o1f7j6a51KWxOiIl6YuTYFS
WaR0lHSW8wLesjsjm1awWO/F3QRuYWbalANy7434GMAGF53u/uc+Z8aE3EItER9o
SpAJos6OfJqyok7JXDdOYRDD5/hBerj68R9llWzNJd27/1jZ0NF2sIE1W4QFddy/
+8YA4+IqwkWB5/LbeRznl3EjFZDpCEJk0gg5XwAR5eIEy4QU8GueTwrDkssFdBGq
0naco7/Es7CWQscYdKHAgYgk0UAyu8sGV235Uw3hlOrbZ/kqvyUmsSujgT8irmDV
e+5z6MTAO6ktvHdQlSuH6ARn47bJrZOlkttAhg==
-----END CERTIFICATE-----
`
)
var (
testCsrHostName = spiffe.Identity{TrustDomain: "cluster.local", Namespace: "default", ServiceAccount: "bookinfo-productpage"}.String()
TestCACertFile = "../testdata/example-ca-cert.pem"
mismatchCertChainFile = "../testdata/cert-chain.pem"
)
func TestK8sSignWithMeshConfig(t *testing.T) {
cases := []struct {
name string
rootCertForMeshConfig string
certChain string
updatedRootCertForMeshConfig string
expectedFail bool
}{
{
name: "Root cert from mesh config and cert chain does not match",
rootCertForMeshConfig: path.Join(env.IstioSrc, "samples/certs", "root-cert.pem"),
certChain: mismatchCertChainFile,
expectedFail: true,
},
{
name: "Root cert is specified in mesh config and Root cert from cert chain is empty(only one leaf cert)",
rootCertForMeshConfig: path.Join(env.IstioSrc, "samples/certs", "root-cert.pem"),
certChain: path.Join(env.IstioSrc, "samples/certs", "cert-chain.pem"),
},
{
name: "Root cert is specified in mesh config and cert chain contains only intermediate CA(only leaf cert + intermediate CA) ",
rootCertForMeshConfig: path.Join(env.IstioSrc, "samples/certs", "root-cert.pem"),
certChain: path.Join(env.IstioSrc, "samples/certs", "workload-foo-cert.pem"),
},
{
name: "Root cert is specified in mesh config and be updated to an invalid value",
rootCertForMeshConfig: path.Join(env.IstioSrc, "samples/certs", "root-cert.pem"),
certChain: path.Join(env.IstioSrc, "samples/certs", "cert-chain.pem"),
updatedRootCertForMeshConfig: TestCACertFile,
expectedFail: true,
},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
csrPEM := createFakeCsr(t)
rootCertPem, err := os.ReadFile(tc.rootCertForMeshConfig)
if err != nil {
t.Errorf("Failed to read sample root-cert.pem")
}
certChainPem, err := os.ReadFile(tc.certChain)
if err != nil {
t.Errorf("Failed to read sample cert-chain.pem")
}
client := initFakeKubeClient(t, certChainPem)
ra, err := createFakeK8sRA(client, "")
if err != nil {
t.Errorf("Failed to create Fake K8s RA")
}
signer := "kubernates.io/kube-apiserver-client"
ra.certSignerDomain = "kubernates.io"
caCertificates := []*meshconfig.MeshConfig_CertificateData{
{CertificateData: &meshconfig.MeshConfig_CertificateData_Pem{Pem: string(rootCertPem)}, CertSigners: []string{signer}},
}
ra.SetCACertificatesFromMeshConfig(caCertificates)
subjectID := spiffe.Identity{TrustDomain: "cluster.local", Namespace: "default", ServiceAccount: "bookinfo-productpage"}.String()
certOptions := ca.CertOpts{
SubjectIDs: []string{subjectID},
TTL: 60 * time.Second, ForCA: false,
CertSigner: "kube-apiserver-client",
}
// expect to sign back successfully
_, err = ra.SignWithCertChain(csrPEM, certOptions)
if err != nil && !tc.expectedFail {
t.Fatal(err)
}
if tc.updatedRootCertForMeshConfig != "" {
testCACert, err := os.ReadFile(tc.updatedRootCertForMeshConfig)
if err != nil && !tc.expectedFail {
t.Errorf("Failed to read test CA Cert file")
}
updatedCACertificates := []*meshconfig.MeshConfig_CertificateData{
{CertificateData: &meshconfig.MeshConfig_CertificateData_Pem{Pem: string(testCACert)}, CertSigners: []string{signer}},
}
ra.SetCACertificatesFromMeshConfig(updatedCACertificates)
// expect failure in sign since root cert in mesh config does not match
_, err = ra.SignWithCertChain(csrPEM, certOptions)
if err == nil && !tc.expectedFail {
t.Fatalf("expected failed, got none")
}
}
})
}
}
func createFakeCsr(t *testing.T) []byte {
options := pkiutil.CertOptions{
Host: testCsrHostName,
RSAKeySize: 2048,
PKCS8Key: false,
ECSigAlg: pkiutil.SupportedECSignatureAlgorithms("ECDSA"),
}
csrPEM, _, err := pkiutil.GenCSR(options)
if err != nil {
t.Fatalf("Error creating Mock CA client: %v", err)
return nil
}
return csrPEM
}
func initFakeKubeClient(t test.Failer, certificate []byte) kube.ExtendedClient {
client := kube.NewFakeClient()
ctx, cancel := context.WithCancel(context.Background())
t.Cleanup(cancel)
w, _ := client.CertificatesV1().CertificateSigningRequests().Watch(ctx, metav1.ListOptions{})
go func() {
for {
select {
case <-ctx.Done():
return
case r := <-w.ResultChan():
csr := r.Object.(*cert.CertificateSigningRequest).DeepCopy()
if csr.Status.Certificate != nil {
continue
}
csr.Status.Certificate = certificate
client.CertificatesV1().CertificateSigningRequests().UpdateStatus(ctx, csr, metav1.UpdateOptions{})
}
}
}()
return client
}
func createFakeK8sRA(client kube.Client, caCertFile string) (*KubernetesRA, error) {
defaultCertTTL := 30 * time.Minute
maxCertTTL := time.Hour
caSigner := "kubernates.io/kube-apiserver-client"
raOpts := &IstioRAOptions{
ExternalCAType: ExtCAK8s,
DefaultCertTTL: defaultCertTTL,
MaxCertTTL: maxCertTTL,
CaSigner: caSigner,
CaCertFile: caCertFile,
VerifyAppendCA: true,
K8sClient: client,
}
return NewKubernetesRA(raOpts)
}
// TestK8sSign : Verify that ra.k8sSign returns a valid certPEM while using k8s Fake Client to create a CSR
func TestK8sSign(t *testing.T) {
csrPEM := createFakeCsr(t)
client := initFakeKubeClient(t, []byte(TestCertificatePEM))
r, err := createFakeK8sRA(client, TestCACertFile)
if err != nil {
t.Errorf("Validation CSR failed")
}
subjectID := spiffe.Identity{TrustDomain: "cluster.local", Namespace: "default", ServiceAccount: "bookinfo-productpage"}.String()
_, err = r.Sign(csrPEM, ca.CertOpts{
SubjectIDs: []string{subjectID},
TTL: 60 * time.Second, ForCA: false,
})
if err != nil {
t.Errorf("K8s CA Signing CSR failed")
}
}
func TestValidateCSR(t *testing.T) {
csrPEM := createFakeCsr(t)
client := initFakeKubeClient(t, []byte(TestCertificatePEM))
_, err := createFakeK8sRA(client, TestCACertFile)
if err != nil {
t.Errorf("Validation CSR failed")
}
var testSubjectIDs []string
// Test Case 1
testSubjectIDs = []string{testCsrHostName, "Random-Host-Name"}
if !ValidateCSR(csrPEM, testSubjectIDs) {
t.Errorf("Test 1: CSR Validation failed")
}
// Test Case 2
testSubjectIDs = []string{"Random-Host-Name"}
if ValidateCSR(csrPEM, testSubjectIDs) {
t.Errorf("Test 2: CSR Validation failed")
}
}