blob: 10dce40c9dc8d8a2b0efd6e57e81810a8a09caf3 [file] [log] [blame]
Tim Windelschmidt6d33a432025-02-04 14:34:25 +01001// Copyright The Monogon Project Authors.
2// SPDX-License-Identifier: Apache-2.0
3
Serge Bazanski52538842021-08-11 16:22:41 +02004package pki
5
6import (
7 "bytes"
8 "context"
9 "crypto/ed25519"
10 "crypto/rand"
11 "crypto/x509"
12 "testing"
13
Lorenz Brund13c1c62022-03-30 19:58:58 +020014 "go.etcd.io/etcd/client/pkg/v3/testutil"
15 "go.etcd.io/etcd/tests/v3/integration"
Serge Bazanski98e05e12023-04-05 12:44:14 +020016 "go.uber.org/zap"
17
Tim Windelschmidt9f21f532024-05-07 15:14:20 +020018 "source.monogon.dev/osbase/logtree"
Serge Bazanski52538842021-08-11 16:22:41 +020019)
20
21// TestManaged ensures Managed Certificates work, including re-ensuring
22// certificates with the same data and issuing subordinate certificates.
23func TestManaged(t *testing.T) {
Serge Bazanski98e05e12023-04-05 12:44:14 +020024 lt := logtree.New()
25 logtree.PipeAllToTest(t, lt)
Lorenz Brund13c1c62022-03-30 19:58:58 +020026 tb, cancel := testutil.NewTestingTBProthesis("pki-managed")
27 defer cancel()
28 cluster := integration.NewClusterV3(tb, &integration.ClusterConfig{
Serge Bazanski52538842021-08-11 16:22:41 +020029 Size: 1,
Serge Bazanski98e05e12023-04-05 12:44:14 +020030 LoggerBuilder: func(memberName string) *zap.Logger {
31 dn := logtree.DN("etcd." + memberName)
32 return logtree.Zapify(lt.MustLeveledFor(dn), zap.WarnLevel)
33 },
Serge Bazanski52538842021-08-11 16:22:41 +020034 })
35 cl := cluster.Client(0)
Lorenz Brund13c1c62022-03-30 19:58:58 +020036 defer cluster.Terminate(tb)
Serge Bazanski52538842021-08-11 16:22:41 +020037 ctx, ctxC := context.WithCancel(context.Background())
38 defer ctxC()
39 ns := Namespaced("/test-managed/")
40
41 // Test CA certificate issuance.
42 ca := &Certificate{
43 Namespace: &ns,
44 Issuer: SelfSigned,
45 Name: "ca",
46 Template: CA("Test CA"),
47 }
48 caBytes, err := ca.Ensure(ctx, cl)
49 if err != nil {
50 t.Fatalf("Failed to Ensure CA: %v", err)
51 }
52 caCert, err := x509.ParseCertificate(caBytes)
53 if err != nil {
54 t.Fatalf("Failed to parse newly emited CA cert: %v", err)
55 }
56 if !caCert.IsCA {
57 t.Errorf("Newly emitted CA cert is not CA")
58 }
59 if ca.PublicKey == nil {
60 t.Errorf("Newly emitted CA cert has no public key")
61 }
62 if ca.PrivateKey == nil {
63 t.Errorf("Newly emitted CA cert has no public key")
64 }
65
66 // Re-emitting CA certificate with same parameters should return exact same
67 // data.
68 ca2 := &Certificate{
69 Namespace: &ns,
70 Issuer: SelfSigned,
71 Name: "ca",
72 Template: CA("Test CA"),
73 }
74 caBytes2, err := ca2.Ensure(ctx, cl)
75 if err != nil {
76 t.Fatalf("Failed to re-Ensure CA: %v", err)
77 }
78 if !bytes.Equal(caBytes, caBytes2) {
79 t.Errorf("New CA has different x509 certificate")
80 }
81 if !bytes.Equal(ca.PublicKey, ca2.PublicKey) {
82 t.Errorf("New CA has different public key")
83 }
84 if !bytes.Equal(ca.PrivateKey, ca2.PrivateKey) {
85 t.Errorf("New CA has different private key")
86 }
87
88 // Emitting a subordinate certificate should work.
89 client := &Certificate{
90 Namespace: &ns,
91 Issuer: ca2,
92 Name: "client",
93 Template: Client("foo", nil),
94 }
95 clientBytes, err := client.Ensure(ctx, cl)
96 if err != nil {
97 t.Fatalf("Failed to ensure client certificate: %v", err)
98 }
99 clientCert, err := x509.ParseCertificate(clientBytes)
100 if err != nil {
101 t.Fatalf("Failed to parse newly emitted client certificate: %v", err)
102 }
103 if clientCert.IsCA {
104 t.Errorf("New client cert is CA")
105 }
106 if want, got := "foo", clientCert.Subject.CommonName; want != got {
107 t.Errorf("New client CN should be %q, got %q", want, got)
108 }
109 if want, got := caCert.Subject.String(), clientCert.Issuer.String(); want != got {
110 t.Errorf("New client issuer should be %q, got %q", want, got)
111 }
112}
113
114// TestExternal ensures External certificates work correctly, including
115// re-Ensuring certificates with the same public key, and attempting to re-issue
116// the same certificate with a different public key (which should fail).
117func TestExternal(t *testing.T) {
Serge Bazanski98e05e12023-04-05 12:44:14 +0200118 lt := logtree.New()
119 logtree.PipeAllToTest(t, lt)
Lorenz Brund13c1c62022-03-30 19:58:58 +0200120 tb, cancel := testutil.NewTestingTBProthesis("pki-managed")
121 defer cancel()
122 cluster := integration.NewClusterV3(tb, &integration.ClusterConfig{
Serge Bazanski52538842021-08-11 16:22:41 +0200123 Size: 1,
Serge Bazanski98e05e12023-04-05 12:44:14 +0200124 LoggerBuilder: func(memberName string) *zap.Logger {
125 dn := logtree.DN("etcd." + memberName)
126 return logtree.Zapify(lt.MustLeveledFor(dn), zap.WarnLevel)
127 },
Serge Bazanski52538842021-08-11 16:22:41 +0200128 })
129 cl := cluster.Client(0)
Lorenz Brund13c1c62022-03-30 19:58:58 +0200130 defer cluster.Terminate(tb)
Serge Bazanski52538842021-08-11 16:22:41 +0200131 ctx, ctxC := context.WithCancel(context.Background())
132 defer ctxC()
133 ns := Namespaced("/test-external/")
134
135 ca := &Certificate{
136 Namespace: &ns,
137 Issuer: SelfSigned,
138 Name: "ca",
139 Template: CA("Test CA"),
140 }
141
142 // Issuing an external certificate should work.
143 pk, _, err := ed25519.GenerateKey(rand.Reader)
144 if err != nil {
145 t.Fatalf("GenerateKey: %v", err)
146 }
147 server := &Certificate{
148 Namespace: &ns,
149 Issuer: ca,
150 Name: "server",
151 Template: Server([]string{"server"}, nil),
152 Mode: CertificateExternal,
153 PublicKey: pk,
154 }
155 serverBytes, err := server.Ensure(ctx, cl)
156 if err != nil {
157 t.Fatalf("Failed to Ensure server certificate: %v", err)
158 }
159
160 // Issuing an external certificate with the same name but different public key
161 // should fail.
162 pk2, _, err := ed25519.GenerateKey(rand.Reader)
163 if err != nil {
164 t.Fatalf("GenerateKey: %v", err)
165 }
166 server2 := &Certificate{
167 Namespace: &ns,
168 Issuer: ca,
169 Name: "server",
170 Template: Server([]string{"server"}, nil),
171 Mode: CertificateExternal,
172 PublicKey: pk2,
173 }
174 if _, err := server2.Ensure(ctx, cl); err == nil {
175 t.Fatalf("Issuing server certificate with different public key should have failed")
176 }
177
178 // Issuing the external certificate with the same name and same public key
179 // should work and yield the same x509 bytes.
180 server3 := &Certificate{
181 Namespace: &ns,
182 Issuer: ca,
183 Name: "server",
184 Template: Server([]string{"server"}, nil),
185 Mode: CertificateExternal,
186 PublicKey: pk,
187 }
188 serverBytes3, err := server3.Ensure(ctx, cl)
189 if err != nil {
190 t.Fatalf("Failed to re-Ensure server certificate: %v", err)
191 }
192 if !bytes.Equal(serverBytes, serverBytes3) {
193 t.Errorf("New server certificate has different x509 certificate")
194 }
195}