treewide: introduce osbase package and move things around
All except localregistry moved from metropolis/pkg to osbase,
localregistry moved to metropolis/test as its only used there anyway.
Change-Id: If1a4bf377364bef0ac23169e1b90379c71b06d72
Reviewed-on: https://review.monogon.dev/c/monogon/+/3079
Tested-by: Jenkins CI
Reviewed-by: Serge Bazanski <serge@monogon.tech>
diff --git a/osbase/pki/certificate_test.go b/osbase/pki/certificate_test.go
new file mode 100644
index 0000000..e751c54
--- /dev/null
+++ b/osbase/pki/certificate_test.go
@@ -0,0 +1,192 @@
+package pki
+
+import (
+ "bytes"
+ "context"
+ "crypto/ed25519"
+ "crypto/rand"
+ "crypto/x509"
+ "testing"
+
+ "go.etcd.io/etcd/client/pkg/v3/testutil"
+ "go.etcd.io/etcd/tests/v3/integration"
+ "go.uber.org/zap"
+
+ "source.monogon.dev/osbase/logtree"
+)
+
+// TestManaged ensures Managed Certificates work, including re-ensuring
+// certificates with the same data and issuing subordinate certificates.
+func TestManaged(t *testing.T) {
+ lt := logtree.New()
+ logtree.PipeAllToTest(t, lt)
+ tb, cancel := testutil.NewTestingTBProthesis("pki-managed")
+ defer cancel()
+ cluster := integration.NewClusterV3(tb, &integration.ClusterConfig{
+ Size: 1,
+ LoggerBuilder: func(memberName string) *zap.Logger {
+ dn := logtree.DN("etcd." + memberName)
+ return logtree.Zapify(lt.MustLeveledFor(dn), zap.WarnLevel)
+ },
+ })
+ cl := cluster.Client(0)
+ defer cluster.Terminate(tb)
+ ctx, ctxC := context.WithCancel(context.Background())
+ defer ctxC()
+ ns := Namespaced("/test-managed/")
+
+ // Test CA certificate issuance.
+ ca := &Certificate{
+ Namespace: &ns,
+ Issuer: SelfSigned,
+ Name: "ca",
+ Template: CA("Test CA"),
+ }
+ caBytes, err := ca.Ensure(ctx, cl)
+ if err != nil {
+ t.Fatalf("Failed to Ensure CA: %v", err)
+ }
+ caCert, err := x509.ParseCertificate(caBytes)
+ if err != nil {
+ t.Fatalf("Failed to parse newly emited CA cert: %v", err)
+ }
+ if !caCert.IsCA {
+ t.Errorf("Newly emitted CA cert is not CA")
+ }
+ if ca.PublicKey == nil {
+ t.Errorf("Newly emitted CA cert has no public key")
+ }
+ if ca.PrivateKey == nil {
+ t.Errorf("Newly emitted CA cert has no public key")
+ }
+
+ // Re-emitting CA certificate with same parameters should return exact same
+ // data.
+ ca2 := &Certificate{
+ Namespace: &ns,
+ Issuer: SelfSigned,
+ Name: "ca",
+ Template: CA("Test CA"),
+ }
+ caBytes2, err := ca2.Ensure(ctx, cl)
+ if err != nil {
+ t.Fatalf("Failed to re-Ensure CA: %v", err)
+ }
+ if !bytes.Equal(caBytes, caBytes2) {
+ t.Errorf("New CA has different x509 certificate")
+ }
+ if !bytes.Equal(ca.PublicKey, ca2.PublicKey) {
+ t.Errorf("New CA has different public key")
+ }
+ if !bytes.Equal(ca.PrivateKey, ca2.PrivateKey) {
+ t.Errorf("New CA has different private key")
+ }
+
+ // Emitting a subordinate certificate should work.
+ client := &Certificate{
+ Namespace: &ns,
+ Issuer: ca2,
+ Name: "client",
+ Template: Client("foo", nil),
+ }
+ clientBytes, err := client.Ensure(ctx, cl)
+ if err != nil {
+ t.Fatalf("Failed to ensure client certificate: %v", err)
+ }
+ clientCert, err := x509.ParseCertificate(clientBytes)
+ if err != nil {
+ t.Fatalf("Failed to parse newly emitted client certificate: %v", err)
+ }
+ if clientCert.IsCA {
+ t.Errorf("New client cert is CA")
+ }
+ if want, got := "foo", clientCert.Subject.CommonName; want != got {
+ t.Errorf("New client CN should be %q, got %q", want, got)
+ }
+ if want, got := caCert.Subject.String(), clientCert.Issuer.String(); want != got {
+ t.Errorf("New client issuer should be %q, got %q", want, got)
+ }
+}
+
+// TestExternal ensures External certificates work correctly, including
+// re-Ensuring certificates with the same public key, and attempting to re-issue
+// the same certificate with a different public key (which should fail).
+func TestExternal(t *testing.T) {
+ lt := logtree.New()
+ logtree.PipeAllToTest(t, lt)
+ tb, cancel := testutil.NewTestingTBProthesis("pki-managed")
+ defer cancel()
+ cluster := integration.NewClusterV3(tb, &integration.ClusterConfig{
+ Size: 1,
+ LoggerBuilder: func(memberName string) *zap.Logger {
+ dn := logtree.DN("etcd." + memberName)
+ return logtree.Zapify(lt.MustLeveledFor(dn), zap.WarnLevel)
+ },
+ })
+ cl := cluster.Client(0)
+ defer cluster.Terminate(tb)
+ ctx, ctxC := context.WithCancel(context.Background())
+ defer ctxC()
+ ns := Namespaced("/test-external/")
+
+ ca := &Certificate{
+ Namespace: &ns,
+ Issuer: SelfSigned,
+ Name: "ca",
+ Template: CA("Test CA"),
+ }
+
+ // Issuing an external certificate should work.
+ pk, _, err := ed25519.GenerateKey(rand.Reader)
+ if err != nil {
+ t.Fatalf("GenerateKey: %v", err)
+ }
+ server := &Certificate{
+ Namespace: &ns,
+ Issuer: ca,
+ Name: "server",
+ Template: Server([]string{"server"}, nil),
+ Mode: CertificateExternal,
+ PublicKey: pk,
+ }
+ serverBytes, err := server.Ensure(ctx, cl)
+ if err != nil {
+ t.Fatalf("Failed to Ensure server certificate: %v", err)
+ }
+
+ // Issuing an external certificate with the same name but different public key
+ // should fail.
+ pk2, _, err := ed25519.GenerateKey(rand.Reader)
+ if err != nil {
+ t.Fatalf("GenerateKey: %v", err)
+ }
+ server2 := &Certificate{
+ Namespace: &ns,
+ Issuer: ca,
+ Name: "server",
+ Template: Server([]string{"server"}, nil),
+ Mode: CertificateExternal,
+ PublicKey: pk2,
+ }
+ if _, err := server2.Ensure(ctx, cl); err == nil {
+ t.Fatalf("Issuing server certificate with different public key should have failed")
+ }
+
+ // Issuing the external certificate with the same name and same public key
+ // should work and yield the same x509 bytes.
+ server3 := &Certificate{
+ Namespace: &ns,
+ Issuer: ca,
+ Name: "server",
+ Template: Server([]string{"server"}, nil),
+ Mode: CertificateExternal,
+ PublicKey: pk,
+ }
+ serverBytes3, err := server3.Ensure(ctx, cl)
+ if err != nil {
+ t.Fatalf("Failed to re-Ensure server certificate: %v", err)
+ }
+ if !bytes.Equal(serverBytes, serverBytes3) {
+ t.Errorf("New server certificate has different x509 certificate")
+ }
+}