Skip to content

Commit 91e24b4

Browse files
Merge pull request #226 from PillaiManish/master
CM-423: Adds e2e test for istio-csr controller with grpc CreateCertificate call
2 parents 59984c6 + efa7e23 commit 91e24b4

File tree

7 files changed

+532
-7
lines changed

7 files changed

+532
-7
lines changed

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: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ var _ = Describe("ACME Certificate", Ordered, func() {
7474
Expect(err).NotTo(HaveOccurred(), "Operator is expected to be available")
7575

7676
By("creating a test namespace")
77-
namespace, err := loader.CreateTestingNS("e2e-acme-certs")
77+
namespace, err := loader.CreateTestingNS("e2e-acme-certs", false)
7878
Expect(err).NotTo(HaveOccurred())
7979
ns = namespace
8080

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

685685
By("creating a test namespace")
686-
namespace, err := loader.CreateTestingNS("e2e-self-signed-certs")
686+
namespace, err := loader.CreateTestingNS("e2e-self-signed-certs", false)
687687
Expect(err).NotTo(HaveOccurred())
688688
ns = namespace
689689

test/e2e/istio_csr_test.go

Lines changed: 287 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,287 @@
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+
// backOffLimit is the max retries for the Job
30+
const backOffLimit int32 = 10
31+
32+
// istioCSRProtoURL links to proto for istio-csr API spec
33+
const istioCSRProtoURL = "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.TODO()
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(istioCSRProtoURL)
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(backOffLimit),
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+
Name: "GOPATH",
178+
Value: "/tmp/go",
179+
},
180+
},
181+
Args: []string{
182+
"go install github.com/fullstorydev/grpcurl/cmd/grpcurl@v1.9.2 >/dev/null 2>&1 && " +
183+
"TOKEN=$(cat /var/run/secrets/istio-ca/token) && " +
184+
"/tmp/go/bin/grpcurl " +
185+
"-import-path /proto " +
186+
"-proto /proto/ca.proto " +
187+
"-H \"Authorization: Bearer $TOKEN\" " +
188+
fmt.Sprintf("-d '{\"csr\": \"%s\", \"validity_duration\": 3600}' ", csr) +
189+
"-cacert /etc/root-secret/ca.crt " +
190+
"-key /etc/root-secret/tls.key " +
191+
"-cert /etc/root-secret/tls.crt " +
192+
fmt.Sprintf("%s istio.v1.auth.IstioCertificateService/CreateCertificate", istioCSRGRPCEndpoint),
193+
},
194+
VolumeMounts: []corev1.VolumeMount{
195+
{Name: "root-secret", MountPath: "/etc/root-secret"},
196+
{Name: "proto", MountPath: "/proto"},
197+
{Name: "sa-token", MountPath: "/var/run/secrets/istio-ca"},
198+
},
199+
},
200+
},
201+
Volumes: []corev1.Volume{
202+
{
203+
Name: "sa-token",
204+
VolumeSource: corev1.VolumeSource{
205+
Projected: &corev1.ProjectedVolumeSource{
206+
DefaultMode: ptr.To(int32(420)),
207+
Sources: []corev1.VolumeProjection{
208+
{
209+
ServiceAccountToken: &corev1.ServiceAccountTokenProjection{
210+
Audience: "istio-ca",
211+
ExpirationSeconds: ptr.To(int64(3600)),
212+
Path: "token",
213+
},
214+
},
215+
},
216+
},
217+
},
218+
},
219+
{
220+
Name: "root-secret",
221+
VolumeSource: corev1.VolumeSource{
222+
Secret: &corev1.SecretVolumeSource{
223+
SecretName: "istiod-tls",
224+
},
225+
},
226+
},
227+
{
228+
Name: "proto",
229+
VolumeSource: corev1.VolumeSource{
230+
ConfigMap: &corev1.ConfigMapVolumeSource{
231+
LocalObjectReference: corev1.LocalObjectReference{
232+
Name: "proto-cm",
233+
},
234+
},
235+
},
236+
},
237+
},
238+
},
239+
},
240+
},
241+
}
242+
_, err = clientset.BatchV1().Jobs(ns.Name).Create(context.TODO(), job, metav1.CreateOptions{})
243+
Expect(err).Should(BeNil())
244+
defer clientset.BatchV1().Jobs(ns.Name).Delete(ctx, job.Name, metav1.DeleteOptions{})
245+
246+
By("waiting for the job to be completed")
247+
err = pollTillJobCompleted(ctx, clientset, ns.Name, "grpcurl-job")
248+
Expect(err).Should(BeNil())
249+
250+
By("fetching logs of the grpcurl job")
251+
pods, err := clientset.CoreV1().Pods(ns.Name).List(context.TODO(), metav1.ListOptions{
252+
LabelSelector: fmt.Sprintf("app=%s", grpcAppName),
253+
})
254+
Expect(err).Should(BeNil())
255+
256+
By("fetching succeeded pod name")
257+
var succeededPodName string
258+
for _, pod := range pods.Items {
259+
if pod.Status.Phase == corev1.PodSucceeded {
260+
succeededPodName = pod.Name
261+
}
262+
}
263+
Expect(succeededPodName).ShouldNot(BeEmpty())
264+
265+
req := clientset.CoreV1().Pods(ns.Name).GetLogs(succeededPodName, &corev1.PodLogOptions{})
266+
logs, err := req.Stream(context.TODO())
267+
Expect(err).Should(BeNil())
268+
269+
defer logs.Close()
270+
271+
logData, err := io.ReadAll(logs)
272+
Expect(err).Should(BeNil())
273+
274+
var entry LogEntry
275+
err = json.Unmarshal(logData, &entry)
276+
Expect(err).Should(BeNil())
277+
Expect(entry.CertChain).ShouldNot(BeEmpty())
278+
279+
By("validating each certificate")
280+
for _, certPEM := range entry.CertChain {
281+
err = library.ValidateCertificate(certPEM, "my-selfsigned-ca")
282+
Expect(err).Should(BeNil())
283+
}
284+
285+
})
286+
})
287+
})
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)