Skip to content

Commit a712e39

Browse files
committed
added e2e test case for istio-csr:#423
- applys IstioCSR resource - deploys grpcurl job - which calls the grpc endpoint of istio-csr - checks the response and validates the certificate
1 parent a4590f3 commit a712e39

7 files changed

Lines changed: 533 additions & 14 deletions

File tree

test/e2e/cert_manager_deployment_test.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ func TestSelfSignedCerts(t *testing.T) {
4242
ctx := context.Background()
4343
loader := library.NewDynamicResourceLoader(ctx, t)
4444

45-
ns, err := loader.CreateTestingNS("e2e-self-signed-cert")
45+
ns, err := loader.CreateTestingNS("e2e-self-signed-cert", false)
4646
require.NoError(t, err)
4747
defer loader.DeleteTestingNS(ns.Name, t.Failed)
4848
loader.CreateFromFile(testassets.ReadFile, filepath.Join("testdata", "self_signed", "cluster_issuer.yaml"), ns.Name)
@@ -73,7 +73,7 @@ func TestACMECertsIngress(t *testing.T) {
7373
config, err := library.GetConfigForTest(t)
7474
require.NoError(t, err)
7575

76-
ns, err := loader.CreateTestingNS("e2e-acme-ingress-cert")
76+
ns, err := loader.CreateTestingNS("e2e-acme-ingress-cert", false)
7777
require.NoError(t, err)
7878
defer loader.DeleteTestingNS(ns.Name, t.Failed)
7979
loader.CreateFromFile(testassets.ReadFile, filepath.Join("testdata", "acme", "clusterissuer.yaml"), ns.Name)
@@ -159,7 +159,7 @@ func TestCertRenew(t *testing.T) {
159159
config, err := library.GetConfigForTest(t)
160160
require.NoErrorf(t, err, "failed to fetch host configuration: %v", err)
161161

162-
ns, err := loader.CreateTestingNS("e2e-cert-renew")
162+
ns, err := loader.CreateTestingNS("e2e-cert-renew", false)
163163
require.NoErrorf(t, err, "failed to create namespace: %v", err)
164164
defer loader.DeleteTestingNS(ns.Name, t.Failed)
165165
loader.CreateFromFile(testassets.ReadFile, filepath.Join("testdata", "self_signed", "cluster_issuer.yaml"), ns.Name)

test/e2e/certificates_test.go

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,10 @@ import (
1414
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
1515
"k8s.io/apimachinery/pkg/util/wait"
1616

17-
"github.com/openshift/cert-manager-operator/test/library"
18-
1917
v1 "github.com/cert-manager/cert-manager/pkg/apis/acme/v1"
2018
certmanagerv1 "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1"
2119
certmanagermetav1 "github.com/cert-manager/cert-manager/pkg/apis/meta/v1"
20+
"github.com/openshift/cert-manager-operator/test/library"
2221

2322
. "github.com/onsi/ginkgo/v2"
2423
. "github.com/onsi/gomega"
@@ -74,7 +73,7 @@ var _ = Describe("ACME Certificate", Ordered, func() {
7473
Expect(err).NotTo(HaveOccurred(), "Operator is expected to be available")
7574

7675
By("creating a test namespace")
77-
namespace, err := loader.CreateTestingNS("e2e-acme-certs")
76+
namespace, err := loader.CreateTestingNS("e2e-acme-certs", false)
7877
Expect(err).NotTo(HaveOccurred())
7978
ns = namespace
8079

@@ -683,7 +682,7 @@ var _ = Describe("Self-signed Certificate", Ordered, func() {
683682
ctx = context.Background()
684683

685684
By("creating a test namespace")
686-
namespace, err := loader.CreateTestingNS("e2e-self-signed-certs")
685+
namespace, err := loader.CreateTestingNS("e2e-self-signed-certs", false)
687686
Expect(err).NotTo(HaveOccurred())
688687
ns = namespace
689688

test/e2e/istio_csr_test.go

Lines changed: 284 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,284 @@
1+
//go:build e2e
2+
// +build e2e
3+
4+
package e2e
5+
6+
import (
7+
"context"
8+
"crypto/x509"
9+
"crypto/x509/pkix"
10+
"encoding/json"
11+
"fmt"
12+
"io"
13+
"net/url"
14+
"path/filepath"
15+
16+
batchv1 "k8s.io/api/batch/v1"
17+
corev1 "k8s.io/api/core/v1"
18+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
19+
"k8s.io/client-go/dynamic"
20+
"k8s.io/client-go/kubernetes"
21+
"k8s.io/utils/ptr"
22+
23+
"github.com/openshift/cert-manager-operator/test/library"
24+
25+
. "github.com/onsi/ginkgo/v2"
26+
. "github.com/onsi/gomega"
27+
)
28+
29+
// back_off_limits is for setting max retries in the job spec
30+
const back_off_limits int32 = 10
31+
32+
// istio_csr_url is for fetching proto content
33+
const istio_csr_url = "https://raw.githubusercontent.com/istio/api/v1.24.1/security/v1alpha1/ca.proto"
34+
35+
type LogEntry struct {
36+
CertChain []string `json:"certChain"`
37+
}
38+
39+
var _ = Describe("Istio-CSR", Ordered, Label("TechPreview", "Feature:IstioCSR"), func() {
40+
ctx := context.Background()
41+
var clientset *kubernetes.Clientset
42+
var dynamicClient *dynamic.DynamicClient
43+
44+
BeforeAll(func() {
45+
var err error
46+
clientset, err = kubernetes.NewForConfig(cfg)
47+
Expect(err).Should(BeNil())
48+
49+
dynamicClient, err = dynamic.NewForConfig(cfg)
50+
Expect(err).Should(BeNil())
51+
})
52+
53+
var ns *corev1.Namespace
54+
55+
BeforeEach(func() {
56+
By("waiting for operator status to become available")
57+
err := verifyOperatorStatusCondition(certmanageroperatorclient, []string{
58+
certManagerControllerDeploymentControllerName,
59+
certManagerWebhookDeploymentControllerName,
60+
certManagerCAInjectorDeploymentControllerName,
61+
}, validOperatorStatusConditions)
62+
Expect(err).NotTo(HaveOccurred(), "Operator is expected to be available")
63+
64+
By("creating a test namespace")
65+
namespace, err := loader.CreateTestingNS("istio-system", true)
66+
Expect(err).NotTo(HaveOccurred())
67+
ns = namespace
68+
69+
DeferCleanup(func() {
70+
loader.DeleteTestingNS(ns.Name, func() bool { return CurrentSpecReport().Failed() })
71+
})
72+
})
73+
74+
Context("grpc call istio.v1.auth.IstioCertificateService/CreateCertificate to istio-csr agent", func() {
75+
It("should return cert-chain as response", func() {
76+
serviceAccountName := "cert-manager-istio-csr"
77+
grpcAppName := "grpcurl"
78+
79+
By("creating cluster issuer")
80+
loader.CreateFromFile(testassets.ReadFile, filepath.Join("testdata", "self_signed", "cluster_issuer.yaml"), ns.Name)
81+
defer loader.DeleteFromFile(testassets.ReadFile, filepath.Join("testdata", "self_signed", "cluster_issuer.yaml"), ns.Name)
82+
83+
By("issuing TLS certificate")
84+
loader.CreateFromFile(testassets.ReadFile, filepath.Join("testdata", "self_signed", "certificate.yaml"), ns.Name)
85+
defer loader.DeleteFromFile(testassets.ReadFile, filepath.Join("testdata", "self_signed", "certificate.yaml"), ns.Name)
86+
87+
By("fetching proto file from api")
88+
protoContent, err := library.FetchFileFromURL(istio_csr_url)
89+
Expect(err).Should(BeNil())
90+
Expect(protoContent).NotTo(BeEmpty())
91+
92+
By("creating proto config map")
93+
configMap := &corev1.ConfigMap{
94+
ObjectMeta: metav1.ObjectMeta{
95+
Name: "proto-cm",
96+
Namespace: ns.Name,
97+
},
98+
Data: map[string]string{
99+
"ca.proto": protoContent,
100+
},
101+
}
102+
_, err = clientset.CoreV1().ConfigMaps(ns.Name).Create(ctx, configMap, metav1.CreateOptions{})
103+
Expect(err).Should(BeNil())
104+
defer clientset.CoreV1().ConfigMaps(ns.Name).Delete(ctx, configMap.Name, metav1.DeleteOptions{})
105+
106+
By("creating istio-ca issuer")
107+
loader.CreateFromFile(testassets.ReadFile, filepath.Join("testdata", "istio", "istio-ca-issuer.yaml"), ns.Name)
108+
defer loader.DeleteFromFile(testassets.ReadFile, filepath.Join("testdata", "istio", "istio-ca-issuer.yaml"), ns.Name)
109+
110+
By("creating istiocsr.operator.openshift.io resource")
111+
loader.CreateFromFile(testassets.ReadFile, filepath.Join("testdata", "istio", "istio-csr.yaml"), ns.Name)
112+
defer loader.DeleteFromFile(testassets.ReadFile, filepath.Join("testdata", "istio", "istio-csr.yaml"), ns.Name)
113+
114+
By("poll till cert-manager-istio-csr is available")
115+
err = pollTillDeploymentAvailable(ctx, clientset, ns.Name, "cert-manager-istio-csr")
116+
Expect(err).Should(BeNil())
117+
118+
istioCSRGRPCEndpoint, err := pollTillIstioCSRAvailable(ctx, dynamicClient, ns.Name, "default")
119+
Expect(err).Should(BeNil())
120+
121+
By("poll till the service account is available")
122+
err = pollTillServiceAccountAvailable(ctx, clientset, ns.Name, serviceAccountName)
123+
Expect(err).Should(BeNil())
124+
125+
By("generate csr request")
126+
127+
csrTemplate := &x509.CertificateRequest{
128+
Subject: pkix.Name{
129+
Organization: []string{"My Organization"},
130+
OrganizationalUnit: []string{"IT Department"},
131+
Country: []string{"US"},
132+
Locality: []string{"Los Angeles"},
133+
Province: []string{"California"},
134+
},
135+
URIs: []*url.URL{
136+
{Scheme: "spiffe", Host: "cluster.local", Path: "/ns/istio-system/sa/cert-manager-istio-csr"},
137+
},
138+
SignatureAlgorithm: x509.SHA256WithRSA,
139+
}
140+
141+
csr, err := library.GenerateCSR(csrTemplate)
142+
Expect(err).Should(BeNil())
143+
144+
By("creating an grpcurl job")
145+
job := &batchv1.Job{
146+
ObjectMeta: metav1.ObjectMeta{
147+
Name: "grpcurl-job",
148+
},
149+
Spec: batchv1.JobSpec{
150+
Completions: ptr.To(int32(1)),
151+
BackoffLimit: ptr.To(back_off_limits),
152+
Template: corev1.PodTemplateSpec{
153+
ObjectMeta: metav1.ObjectMeta{
154+
Name: grpcAppName,
155+
Labels: map[string]string{
156+
"app": grpcAppName,
157+
},
158+
},
159+
Spec: corev1.PodSpec{
160+
ServiceAccountName: serviceAccountName,
161+
AutomountServiceAccountToken: ptr.To(false),
162+
RestartPolicy: corev1.RestartPolicyOnFailure,
163+
Containers: []corev1.Container{
164+
{
165+
Name: grpcAppName,
166+
Image: "registry.redhat.io/rhel9/go-toolset",
167+
Command: []string{
168+
"/bin/sh",
169+
"-c",
170+
},
171+
Env: []corev1.EnvVar{
172+
{
173+
Name: "GOCACHE",
174+
Value: "/tmp/go-cache",
175+
},
176+
},
177+
Args: []string{
178+
"GOCACHE=/tmp/go-cache && " +
179+
"export GOPATH=/tmp/go && " +
180+
"go install github.com/fullstorydev/grpcurl/cmd/grpcurl@v1.9.2 >/dev/null 2>&1 && " +
181+
"TOKEN=$(cat /var/run/secrets/istio-ca/token) && " +
182+
"/tmp/go/bin/grpcurl " +
183+
"-import-path /proto " +
184+
"-proto /proto/ca.proto " +
185+
"-H \"Authorization: Bearer $TOKEN\" " +
186+
fmt.Sprintf("-d '{\"csr\": \"%s\", \"validity_duration\": 3600}' ", csr) +
187+
"-cacert /etc/root-secret/ca.crt " +
188+
"-key /etc/root-secret/tls.key " +
189+
"-cert /etc/root-secret/tls.crt " +
190+
fmt.Sprintf("%s istio.v1.auth.IstioCertificateService/CreateCertificate", istioCSRGRPCEndpoint),
191+
},
192+
VolumeMounts: []corev1.VolumeMount{
193+
{Name: "root-secret", MountPath: "/etc/root-secret"},
194+
{Name: "proto", MountPath: "/proto"},
195+
{Name: "service-token", MountPath: "/var/run/secrets/istio-ca"},
196+
},
197+
},
198+
},
199+
Volumes: []corev1.Volume{
200+
{
201+
Name: "service-token",
202+
VolumeSource: corev1.VolumeSource{
203+
Projected: &corev1.ProjectedVolumeSource{
204+
DefaultMode: ptr.To(int32(420)),
205+
Sources: []corev1.VolumeProjection{
206+
{
207+
ServiceAccountToken: &corev1.ServiceAccountTokenProjection{
208+
Audience: "istio-ca",
209+
ExpirationSeconds: ptr.To(int64(3600)),
210+
Path: "token",
211+
},
212+
},
213+
},
214+
},
215+
},
216+
},
217+
{
218+
Name: "root-secret",
219+
VolumeSource: corev1.VolumeSource{
220+
Secret: &corev1.SecretVolumeSource{
221+
SecretName: "istiod-tls",
222+
},
223+
},
224+
},
225+
{
226+
Name: "proto",
227+
VolumeSource: corev1.VolumeSource{
228+
ConfigMap: &corev1.ConfigMapVolumeSource{
229+
LocalObjectReference: corev1.LocalObjectReference{
230+
Name: "proto-cm",
231+
},
232+
},
233+
},
234+
},
235+
},
236+
},
237+
},
238+
},
239+
}
240+
_, err = clientset.BatchV1().Jobs(ns.Name).Create(context.TODO(), job, metav1.CreateOptions{})
241+
Expect(err).Should(BeNil())
242+
defer clientset.BatchV1().Jobs(ns.Name).Delete(ctx, job.Name, metav1.DeleteOptions{})
243+
244+
By("waiting for the job to be completed")
245+
err = pollTillJobCompleted(ctx, clientset, ns.Name, "grpcurl-job")
246+
Expect(err).Should(BeNil())
247+
248+
By("fetching logs of the grpcurl job")
249+
pods, err := clientset.CoreV1().Pods(ns.Name).List(context.TODO(), metav1.ListOptions{
250+
LabelSelector: fmt.Sprintf("app=%s", grpcAppName),
251+
})
252+
Expect(err).Should(BeNil())
253+
254+
By("fetching succeeded pod name")
255+
var succeededPodName string
256+
for _, pod := range pods.Items {
257+
if pod.Status.Phase == "Succeeded" {
258+
succeededPodName = pod.Name
259+
}
260+
}
261+
Expect(succeededPodName).ShouldNot(BeEmpty())
262+
263+
req := clientset.CoreV1().Pods(ns.Name).GetLogs(succeededPodName, &corev1.PodLogOptions{})
264+
logs, err := req.Stream(context.TODO())
265+
Expect(err).Should(BeNil())
266+
267+
defer logs.Close()
268+
269+
logData, err := io.ReadAll(logs)
270+
Expect(err).Should(BeNil())
271+
272+
var entry LogEntry
273+
err = json.Unmarshal(logData, &entry)
274+
Expect(err).Should(BeNil())
275+
276+
By("validating each certificate")
277+
for _, certPEM := range entry.CertChain {
278+
err = library.ValidateCertificate(certPEM, "my-selfsigned-ca")
279+
Expect(err).Should(BeNil())
280+
}
281+
282+
})
283+
})
284+
})
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
apiVersion: cert-manager.io/v1
2+
kind: Issuer
3+
metadata:
4+
name: istio-ca
5+
namespace: istio-system
6+
spec:
7+
ca:
8+
secretName: root-secret
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
apiVersion: operator.openshift.io/v1alpha1
2+
kind: IstioCSR
3+
metadata:
4+
name: default
5+
namespace: istio-system
6+
spec:
7+
istioCSRConfig:
8+
certManager:
9+
issuerRef:
10+
name: istio-ca
11+
kind: Issuer
12+
group: cert-manager.io
13+
istiodTLSConfig:
14+
trustDomain: cluster.local
15+
istio:
16+
namespace: istio-system

0 commit comments

Comments
 (0)