From 60bf6f1cab868b9df3f930555f97b212bb17821e Mon Sep 17 00:00:00 2001 From: Janos <86970079+janosdebugs@users.noreply.github.com> Date: Mon, 12 Aug 2024 13:59:12 +0200 Subject: [PATCH 1/6] Certificate Authority Signed-off-by: Janos <86970079+janosdebugs@users.noreply.github.com> Co-authored-by: AbstractionFactory <179820029+abstractionfactory@users.noreply.github.com> --- README.md | 46 +++++++- ca.go | 14 +++ testca/README.md | 48 +++++++++ testca/ca.go | 262 ++++++++++++++++++++++++++++++++++++++++++++++ testca/ca_test.go | 139 ++++++++++++++++++++++++ 5 files changed, 508 insertions(+), 1 deletion(-) create mode 100644 ca.go create mode 100644 testca/README.md create mode 100644 testca/ca.go create mode 100644 testca/ca_test.go diff --git a/README.md b/README.md index 82bcff4..e7b3368 100644 --- a/README.md +++ b/README.md @@ -46,4 +46,48 @@ func TestMyApp(t *testing.T) { } ``` -For a full list of possible functions, please [check the Go docs](https://pkg.go.dev/github.com/opentofu/tofutestutils). \ No newline at end of file +For a full list of possible functions, please [check the Go docs](https://pkg.go.dev/github.com/opentofu/tofutestutils). + +## Certificate authority + +When you need an x509 certificate for a server or a client, you can use the `tofutestutils.CA` function to obtain a `testca.CertificateAuthority` implementation using a pseudo-random number generator. You can use this to create a certificate for a socket server: + +```go +package your_test + +import ( + "crypto/tls" + "io" + "net" + "strconv" + "testing" + + "github.com/opentofu/tofutestutils" +) + +func TestMySocket(t *testing.T) { + ca := tofutestutils.CA(t) + + // Server side: + tlsListener := tofutestutils.Must2(tls.Listen("tcp", "127.0.0.1:0", ca.CreateLocalhostServerCert().GetServerTLSConfig())) + go func() { + conn, serverErr := tlsListener.Accept() + if serverErr != nil { + return + } + defer func() { + _ = conn.Close() + }() + _, _ = conn.Write([]byte("Hello world!")) + }() + + // Client side: + port := tlsListener.Addr().(*net.TCPAddr).Port + client := tofutestutils.Must2(tls.Dial("tcp", net.JoinHostPort("127.0.0.1", strconv.Itoa(port)), ca.GetClientTLSConfig())) + defer func() { + _ = client.Close() + }() + + t.Logf("%s", tofutestutils.Must2(io.ReadAll(client))) +} +``` diff --git a/ca.go b/ca.go new file mode 100644 index 0000000..680f393 --- /dev/null +++ b/ca.go @@ -0,0 +1,14 @@ +package tofutestutils + +import ( + "testing" + "time" + + "github.com/opentofu/tofutestutils/testca" +) + +// CA returns a certificate authority configured for the provided test. This implementation will configure the CA to use +// a pseudorandom source. You can call testca.New() for more configuration options. +func CA(t *testing.T) testca.CertificateAuthority { + return testca.New(t, RandomSource(), time.Now) +} diff --git a/testca/README.md b/testca/README.md new file mode 100644 index 0000000..26ef0df --- /dev/null +++ b/testca/README.md @@ -0,0 +1,48 @@ +# Certificate authority + +This folder contains a basic x509 certificate authority implementation for testing purposes. You can use it whenever you need a certificate for servers or clients. + +```go +package your_test + +import ( + "crypto/tls" + "io" + "net" + "strconv" + "testing" + "time" + + "github.com/opentofu/tofutestutils" + "github.com/opentofu/tofutestutils/testca" + "github.com/opentofu/tofutestutils/testrandom" +) + +func TestMySocket(t *testing.T) { + // Configure a desired randomness and time source. You can use this to create deterministic behavior. + currentTimeSource := time.Now + ca := testca.New(t, testrandom.DeterministicSource(t), currentTimeSource) + + // Server side: + tlsListener := tofutestutils.Must2(tls.Listen("tcp", "127.0.0.1:0", ca.CreateLocalhostServerCert().GetServerTLSConfig())) + go func() { + conn, serverErr := tlsListener.Accept() + if serverErr != nil { + return + } + defer func() { + _ = conn.Close() + }() + _, _ = conn.Write([]byte("Hello world!")) + }() + + // Client side: + port := tlsListener.Addr().(*net.TCPAddr).Port + client := tofutestutils.Must2(tls.Dial("tcp", net.JoinHostPort("127.0.0.1", strconv.Itoa(port)), ca.GetClientTLSConfig())) + defer func() { + _ = client.Close() + }() + + t.Logf("%s", tofutestutils.Must2(io.ReadAll(client))) +} +``` \ No newline at end of file diff --git a/testca/ca.go b/testca/ca.go new file mode 100644 index 0000000..254ddf1 --- /dev/null +++ b/testca/ca.go @@ -0,0 +1,262 @@ +// Copyright (c) The OpenTofu Authors +// SPDX-License-Identifier: MPL-2.0 +// Copyright (c) 2023 HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package testca + +import ( + "bytes" + "crypto" + "crypto/rsa" + "crypto/tls" + "crypto/x509" + "crypto/x509/pkix" + "encoding/pem" + "io" + "math/big" + "net" + "sync" + "testing" + "time" +) + +const caKeySize = 2048 +const expirationYears = 10 + +// New creates an x509 CA certificate that can produce certificates for testing purposes. Pass a desired randomSource +// to create a deterministic source of certificates alongside a deterministic timeSource. +func New(t *testing.T, randomSource io.Reader, timeSource func() time.Time) CertificateAuthority { + // We use a non-deterministic cheap randomness source because the certificate won't be reproducible anyway due to + // the NotBefore / NotAfter being different every time. We don't use crypto/rand.Rand because it can get blocked + // if not enough entropy is available and it doesn't matter for the test use case. + + now := timeSource() + + caCert := &x509.Certificate{ + SerialNumber: big.NewInt(1), + Subject: pkix.Name{ + Organization: []string{"OpenTofu a Series of LF Projects, LLC"}, + Country: []string{"US"}, + }, + NotBefore: now, + NotAfter: now.AddDate(expirationYears, 0, 0), + IsCA: true, + ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth}, + KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign, + BasicConstraintsValid: true, + } + + caPrivateKey, err := rsa.GenerateKey(randomSource, caKeySize) + if err != nil { + t.Skipf("Failed to create private key: %v", err) + } + caCertData, err := x509.CreateCertificate(randomSource, caCert, caCert, &caPrivateKey.PublicKey, caPrivateKey) + if err != nil { + t.Skipf("Failed to create CA certificate: %v", err) + } + caPEM := new(bytes.Buffer) + if err := pem.Encode(caPEM, &pem.Block{ + Type: "CERTIFICATE", + Bytes: caCertData, + }); err != nil { + t.Skipf("Failed to encode CA cert: %v", err) + } + return &ca{ + t: t, + random: randomSource, + caCert: caCert, + caCertPEM: caPEM.Bytes(), + privateKey: caPrivateKey, + serial: big.NewInt(0), + lock: &sync.Mutex{}, + timeSource: timeSource, + } +} + +// CertConfig is the configuration structure for creating specialized certificates using +// CertificateAuthority.CreateConfiguredServerCert. +type CertConfig struct { + // IPAddresses contains a list of IP addresses that should be added to the SubjectAltName field of the certificate. + IPAddresses []string + // Hosts contains a list of host names that should be added to the SubjectAltName field of the certificate. + Hosts []string + // Subject is the subject (CN, etc) setting for the certificate. Most commonly, you will want the CN field to match + // one of hour host names. + Subject pkix.Name + // ExtKeyUsage describes the extended key usage. Typically, this should be: + // + // []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth} + ExtKeyUsage []x509.ExtKeyUsage +} + +// KeyPair contains a certificate and private key in PEM format. +type KeyPair struct { + // Certificate contains an x509 certificate in PEM format. + Certificate []byte + // PrivateKey contains an RSA or other private key in PEM format. + PrivateKey []byte +} + +// GetPrivateKey returns a crypto.Signer for the private key. +func (k KeyPair) GetPrivateKey() crypto.PrivateKey { + block, _ := pem.Decode(k.PrivateKey) + key, err := x509.ParsePKCS1PrivateKey(block.Bytes) + if err != nil { + panic(err) + } + return key +} + +// GetTLSCertificate returns the tls.Certificate based on this key pair. +func (k KeyPair) GetTLSCertificate() tls.Certificate { + cert, err := tls.X509KeyPair(k.Certificate, k.PrivateKey) + if err != nil { + panic(err) + } + return cert +} + +// GetServerTLSConfig returns a tls.Config suitable for a TLS server with this key pair. +func (k KeyPair) GetServerTLSConfig() *tls.Config { + return &tls.Config{ + Certificates: []tls.Certificate{ + k.GetTLSCertificate(), + }, + MinVersion: tls.VersionTLS12, + } +} + +// CertificateAuthority provides simple access to x509 CA functions for testing purposes only. +type CertificateAuthority interface { + // GetPEMCACert returns the CA certificate in PEM format. + GetPEMCACert() []byte + // GetCertPool returns an x509.CertPool configured for this CA. + GetCertPool() *x509.CertPool + // GetClientTLSConfig returns a *tls.Config with a valid cert pool configured for this CA. + GetClientTLSConfig() *tls.Config + // CreateLocalhostServerCert creates a server certificate pre-configured for "localhost", which is sufficient for + // most test cases. + CreateLocalhostServerCert() KeyPair + // CreateLocalhostClientCert creates a client certificate pre-configured for "localhost", which is sufficient for + // most test cases. + CreateLocalhostClientCert() KeyPair + // CreateConfiguredCert creates a certificate with a specialized configuration. + CreateConfiguredCert(config CertConfig) KeyPair +} + +type ca struct { + caCert *x509.Certificate + caCertPEM []byte + privateKey *rsa.PrivateKey + serial *big.Int + lock *sync.Mutex + t *testing.T + random io.Reader + timeSource func() time.Time +} + +func (c *ca) GetClientTLSConfig() *tls.Config { + certPool := c.GetCertPool() + + return &tls.Config{ + RootCAs: certPool, + MinVersion: tls.VersionTLS12, + } +} + +func (c *ca) GetCertPool() *x509.CertPool { + certPool := x509.NewCertPool() + certPool.AppendCertsFromPEM(c.caCertPEM) + return certPool +} + +func (c *ca) GetPEMCACert() []byte { + return c.caCertPEM +} + +func (c *ca) CreateConfiguredCert(config CertConfig) KeyPair { + c.lock.Lock() + defer c.lock.Unlock() + c.serial.Add(c.serial, big.NewInt(1)) + + ipAddresses := make([]net.IP, len(config.IPAddresses)) + for i, ip := range config.IPAddresses { + ipAddresses[i] = net.ParseIP(ip) + } + + now := c.timeSource() + + cert := &x509.Certificate{ + SerialNumber: c.serial, + Subject: config.Subject, + NotBefore: now, + NotAfter: now.AddDate(0, 0, 1), + SubjectKeyId: []byte{1}, + ExtKeyUsage: config.ExtKeyUsage, + KeyUsage: x509.KeyUsageDigitalSignature, + DNSNames: config.Hosts, + IPAddresses: ipAddresses, + } + certPrivKey, err := rsa.GenerateKey(c.random, caKeySize) + if err != nil { + c.t.Skipf("Failed to generate private key: %v", err) + } + certBytes, err := x509.CreateCertificate( + c.random, + cert, + c.caCert, + &certPrivKey.PublicKey, + c.privateKey, + ) + if err != nil { + c.t.Skipf("Failed to create certificate: %v", err) + } + certPrivKeyPEM := new(bytes.Buffer) + if err := pem.Encode(certPrivKeyPEM, &pem.Block{ + Type: "RSA PRIVATE KEY", + Bytes: x509.MarshalPKCS1PrivateKey(certPrivKey), + }); err != nil { + c.t.Skipf("Failed to encode private key: %v", err) + } + certPEM := new(bytes.Buffer) + if err := pem.Encode(certPEM, + &pem.Block{Type: "CERTIFICATE", Bytes: certBytes}, + ); err != nil { + c.t.Skipf("Failed to encode certificate: %v", err) + } + return KeyPair{ + Certificate: certPEM.Bytes(), + PrivateKey: certPrivKeyPEM.Bytes(), + } +} + +func (c *ca) CreateLocalhostServerCert() KeyPair { + return c.CreateConfiguredCert(CertConfig{ + IPAddresses: []string{"127.0.0.1", "::1"}, + Subject: pkix.Name{ + Country: []string{"US"}, + Organization: []string{"OpenTofu a Series of LF Projects, LLC"}, + CommonName: "localhost", + }, + ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, + Hosts: []string{ + "localhost", + }, + }) +} + +func (c *ca) CreateLocalhostClientCert() KeyPair { + return c.CreateConfiguredCert(CertConfig{ + IPAddresses: []string{"127.0.0.1", "::1"}, + Subject: pkix.Name{ + Country: []string{"US"}, + Organization: []string{"OpenTofu a Series of LF Projects, LLC"}, + CommonName: "localhost", + }, + ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth}, + Hosts: []string{ + "localhost", + }, + }) +} diff --git a/testca/ca_test.go b/testca/ca_test.go new file mode 100644 index 0000000..a0a4637 --- /dev/null +++ b/testca/ca_test.go @@ -0,0 +1,139 @@ +// Copyright (c) The OpenTofu Authors +// SPDX-License-Identifier: MPL-2.0 +// Copyright (c) 2023 HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package testca_test + +import ( + "bytes" + "context" + "crypto/tls" + "io" + "net" + "strconv" + "testing" + "time" + + "github.com/opentofu/tofutestutils" + "github.com/opentofu/tofutestutils/testca" + "github.com/opentofu/tofutestutils/testrandom" +) + +func TestCA(t *testing.T) { + t.Run("correct", testCACorrectCertificate) + t.Run("incorrect", testCAIncorrectCertificate) +} + +func testCAIncorrectCertificate(t *testing.T) { + ca1 := testca.New(t, testrandom.Source(), time.Now) + ca2 := testca.New(t, testrandom.Source(), time.Now) + + if bytes.Equal(ca1.GetPEMCACert(), ca2.GetPEMCACert()) { + t.Fatalf("The two CA's have the same CA PEM!") + } + + done := make(chan struct{}) + var serverErr error + t.Logf("🍦 Setting up TLS server...") + tlsListener := tofutestutils.Must2(tls.Listen( + "tcp", + "127.0.0.1:0", + ca1.CreateLocalhostServerCert().GetServerTLSConfig()), + ) + t.Cleanup(func() { + t.Logf("🍦 Server closing listener...") + _ = tlsListener.Close() + }) + port := tlsListener.Addr().(*net.TCPAddr).Port + go func() { + defer close(done) + t.Logf("🍦 Server accepting connection...") + var conn net.Conn + conn, serverErr = tlsListener.Accept() + if serverErr != nil { + t.Logf("🍦 Server correctly received an error: %v", serverErr) + return + } + // Force a handshake even without read/write. The client automatically performs + // the handshake, but the server listener doesn't before reading. + serverErr = conn.(*tls.Conn).HandshakeContext(context.Background()) + if serverErr == nil { + t.Logf("❌ Server unexpectedly did not receive an error.") + } else { + t.Logf("🍦 Server correctly received an error: %v", serverErr) + } + _ = conn.Close() + }() + t.Logf("🔌 Client connecting to server...") + conn, err := tls.Dial( + "tcp", + net.JoinHostPort("127.0.0.1", strconv.Itoa(port)), + ca2.GetClientTLSConfig(), + ) + if err == nil { + _ = conn.Close() + t.Fatalf("❌ The TLS connection succeeded despite the incorrect CA certificate.") + } + t.Logf("🔌 Client correctly received an error: %v", err) + <-done + if serverErr == nil { + t.Fatalf("❌ The TLS server didn't error despite the incorrect CA certificate.") + } +} + +func testCACorrectCertificate(t *testing.T) { + ca := testca.New(t, testrandom.Source(), time.Now) + const testGreeting = "Hello world!" + + var serverErr error + t.Cleanup(func() { + if serverErr != nil { + t.Fatalf("❌ TLS server failed: %v", serverErr) + } + }) + + done := make(chan struct{}) + + t.Logf("🍦 Setting up TLS server...") + tlsListener := tofutestutils.Must2(tls.Listen("tcp", "127.0.0.1:0", ca.CreateLocalhostServerCert().GetServerTLSConfig())) + t.Cleanup(func() { + t.Logf("🍦 Server closing listener...") + _ = tlsListener.Close() + }) + t.Logf("🍦 Starting TLS server...") + go func() { + defer close(done) + var conn net.Conn + t.Logf("🍦 Server accepting connection...") + conn, serverErr = tlsListener.Accept() + if serverErr != nil { + t.Errorf("❌ Server accept failed: %v", serverErr) + return + } + defer func() { + t.Logf("🍦 Server closing connection.") + _ = conn.Close() + }() + t.Logf("🍦 Server writing greeting...") + _, serverErr = conn.Write([]byte(testGreeting)) + if serverErr != nil { + t.Errorf("❌ Server write failed: %v", serverErr) + return + } + }() + t.Logf("🔌 Client connecting to server...") + port := tlsListener.Addr().(*net.TCPAddr).Port + client := tofutestutils.Must2(tls.Dial("tcp", net.JoinHostPort("127.0.0.1", strconv.Itoa(port)), ca.GetClientTLSConfig())) + defer func() { + t.Logf("🔌 Client closing connection...") + _ = client.Close() + }() + t.Logf("🔌 Client reading greeting...") + greeting := tofutestutils.Must2(io.ReadAll(client)) + if string(greeting) != testGreeting { + t.Fatalf("❌ Client received incorrect greeting: %s", greeting) + } + t.Logf("🔌 Waiting for server to finish...") + <-done +} From 990afd1daa1af329479c67310c0a6b01e4e1b3b2 Mon Sep 17 00:00:00 2001 From: Janos <86970079+janosdebugs@users.noreply.github.com> Date: Mon, 12 Aug 2024 15:20:44 +0200 Subject: [PATCH 2/6] License header Signed-off-by: Janos <86970079+janosdebugs@users.noreply.github.com> Co-authored-by: AbstractionFactory <179820029+abstractionfactory@users.noreply.github.com> --- ca.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/ca.go b/ca.go index 680f393..2a97300 100644 --- a/ca.go +++ b/ca.go @@ -1,3 +1,8 @@ +// Copyright (c) The OpenTofu Authors +// SPDX-License-Identifier: MPL-2.0 +// Copyright (c) 2023 HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package tofutestutils import ( From b1f0b51f5da56977c96bb3732bea7fbc06953df4 Mon Sep 17 00:00:00 2001 From: AbstractionFactory <179820029+abstractionfactory@users.noreply.github.com> Date: Wed, 22 Jan 2025 13:14:08 +0100 Subject: [PATCH 3/6] Reworked CA generation to be fully deterministic if desired and fixed error handling Signed-off-by: AbstractionFactory <179820029+abstractionfactory@users.noreply.github.com> --- ca.go | 6 ++---- testca/ca.go | 44 +++++++++++++++++++++++++++++--------------- testca/ca_test.go | 38 ++++++++++++++++++++++++-------------- 3 files changed, 55 insertions(+), 33 deletions(-) diff --git a/ca.go b/ca.go index 2a97300..7f7f8c8 100644 --- a/ca.go +++ b/ca.go @@ -6,14 +6,12 @@ package tofutestutils import ( - "testing" - "time" - "github.com/opentofu/tofutestutils/testca" + "testing" ) // CA returns a certificate authority configured for the provided test. This implementation will configure the CA to use // a pseudorandom source. You can call testca.New() for more configuration options. func CA(t *testing.T) testca.CertificateAuthority { - return testca.New(t, RandomSource(), time.Now) + return testca.New(t, RandomSource()) } diff --git a/testca/ca.go b/testca/ca.go index 254ddf1..4b27314 100644 --- a/testca/ca.go +++ b/testca/ca.go @@ -22,25 +22,25 @@ import ( ) const caKeySize = 2048 -const expirationYears = 10 -// New creates an x509 CA certificate that can produce certificates for testing purposes. Pass a desired randomSource -// to create a deterministic source of certificates alongside a deterministic timeSource. -func New(t *testing.T, randomSource io.Reader, timeSource func() time.Time) CertificateAuthority { - // We use a non-deterministic cheap randomness source because the certificate won't be reproducible anyway due to - // the NotBefore / NotAfter being different every time. We don't use crypto/rand.Rand because it can get blocked - // if not enough entropy is available and it doesn't matter for the test use case. +// startDate holds the date of the fork announcement. This is the starting date for the validity of the certificate by +// default. +var startDate = time.Date(2023, 9, 5, 0, 0, 0, 0, time.UTC) - now := timeSource() +// expirationYears is the number of years the certificate is valid by default. +const expirationYears = 30 +// New creates an x509 CA certificate that can produce certificates for testing purposes. Pass a desired deterministic +// randomSource to create a deterministic certificate. +func New(t *testing.T, randomSource io.Reader) CertificateAuthority { caCert := &x509.Certificate{ SerialNumber: big.NewInt(1), Subject: pkix.Name{ Organization: []string{"OpenTofu a Series of LF Projects, LLC"}, Country: []string{"US"}, }, - NotBefore: now, - NotAfter: now.AddDate(expirationYears, 0, 0), + NotBefore: startDate, + NotAfter: startDate.AddDate(expirationYears, 0, 0), IsCA: true, ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth}, KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign, @@ -70,7 +70,6 @@ func New(t *testing.T, randomSource io.Reader, timeSource func() time.Time) Cert privateKey: caPrivateKey, serial: big.NewInt(0), lock: &sync.Mutex{}, - timeSource: timeSource, } } @@ -88,6 +87,11 @@ type CertConfig struct { // // []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth} ExtKeyUsage []x509.ExtKeyUsage + // StartTime indicates when the certificate should start to be valid. This defaults to startDate. + NotBefore *time.Time + // NotAfter indicates a time at which the certificate should stop being valid. This defaults to expirationYears + // after startDate + NotAfter *time.Time } // KeyPair contains a certificate and private key in PEM format. @@ -153,7 +157,6 @@ type ca struct { lock *sync.Mutex t *testing.T random io.Reader - timeSource func() time.Time } func (c *ca) GetClientTLSConfig() *tls.Config { @@ -185,13 +188,24 @@ func (c *ca) CreateConfiguredCert(config CertConfig) KeyPair { ipAddresses[i] = net.ParseIP(ip) } - now := c.timeSource() + var notBefore time.Time + if config.NotBefore != nil { + notBefore = *config.NotBefore + } else { + notBefore = startDate + } + var notAfter time.Time + if config.NotAfter != nil { + notAfter = *config.NotAfter + } else { + notAfter = notBefore.Add(expirationYears * 365 * 24 * time.Hour) + } cert := &x509.Certificate{ SerialNumber: c.serial, Subject: config.Subject, - NotBefore: now, - NotAfter: now.AddDate(0, 0, 1), + NotBefore: notBefore, + NotAfter: notAfter, SubjectKeyId: []byte{1}, ExtKeyUsage: config.ExtKeyUsage, KeyUsage: x509.KeyUsageDigitalSignature, diff --git a/testca/ca_test.go b/testca/ca_test.go index a0a4637..81b7085 100644 --- a/testca/ca_test.go +++ b/testca/ca_test.go @@ -9,15 +9,13 @@ import ( "bytes" "context" "crypto/tls" + "github.com/opentofu/tofutestutils" + "github.com/opentofu/tofutestutils/testca" + "github.com/opentofu/tofutestutils/testrandom" "io" "net" "strconv" "testing" - "time" - - "github.com/opentofu/tofutestutils" - "github.com/opentofu/tofutestutils/testca" - "github.com/opentofu/tofutestutils/testrandom" ) func TestCA(t *testing.T) { @@ -26,8 +24,8 @@ func TestCA(t *testing.T) { } func testCAIncorrectCertificate(t *testing.T) { - ca1 := testca.New(t, testrandom.Source(), time.Now) - ca2 := testca.New(t, testrandom.Source(), time.Now) + ca1 := testca.New(t, testrandom.Source()) + ca2 := testca.New(t, testrandom.Source()) if bytes.Equal(ca1.GetPEMCACert(), ca2.GetPEMCACert()) { t.Fatalf("The two CA's have the same CA PEM!") @@ -43,7 +41,9 @@ func testCAIncorrectCertificate(t *testing.T) { ) t.Cleanup(func() { t.Logf("🍦 Server closing listener...") - _ = tlsListener.Close() + if err := tlsListener.Close(); err != nil { + t.Logf("❌ Failed to close server listener (%v)", err) + } }) port := tlsListener.Addr().(*net.TCPAddr).Port go func() { @@ -63,7 +63,9 @@ func testCAIncorrectCertificate(t *testing.T) { } else { t.Logf("🍦 Server correctly received an error: %v", serverErr) } - _ = conn.Close() + if err := conn.Close(); err != nil { + t.Logf("❌ Could not close the connection on the server side: %v", err) + } }() t.Logf("🔌 Client connecting to server...") conn, err := tls.Dial( @@ -72,7 +74,9 @@ func testCAIncorrectCertificate(t *testing.T) { ca2.GetClientTLSConfig(), ) if err == nil { - _ = conn.Close() + if err := conn.Close(); err != nil { + t.Logf("❌ Could not close the connection on the client side: %v", err) + } t.Fatalf("❌ The TLS connection succeeded despite the incorrect CA certificate.") } t.Logf("🔌 Client correctly received an error: %v", err) @@ -83,7 +87,7 @@ func testCAIncorrectCertificate(t *testing.T) { } func testCACorrectCertificate(t *testing.T) { - ca := testca.New(t, testrandom.Source(), time.Now) + ca := testca.New(t, testrandom.Source()) const testGreeting = "Hello world!" var serverErr error @@ -99,7 +103,9 @@ func testCACorrectCertificate(t *testing.T) { tlsListener := tofutestutils.Must2(tls.Listen("tcp", "127.0.0.1:0", ca.CreateLocalhostServerCert().GetServerTLSConfig())) t.Cleanup(func() { t.Logf("🍦 Server closing listener...") - _ = tlsListener.Close() + if err := tlsListener.Close(); err != nil { + t.Logf("❌ Could not close the server listener: %v", err) + } }) t.Logf("🍦 Starting TLS server...") go func() { @@ -113,7 +119,9 @@ func testCACorrectCertificate(t *testing.T) { } defer func() { t.Logf("🍦 Server closing connection.") - _ = conn.Close() + if err := conn.Close(); err != nil { + t.Logf("❌ Could not close the server connection: %v", err) + } }() t.Logf("🍦 Server writing greeting...") _, serverErr = conn.Write([]byte(testGreeting)) @@ -127,7 +135,9 @@ func testCACorrectCertificate(t *testing.T) { client := tofutestutils.Must2(tls.Dial("tcp", net.JoinHostPort("127.0.0.1", strconv.Itoa(port)), ca.GetClientTLSConfig())) defer func() { t.Logf("🔌 Client closing connection...") - _ = client.Close() + if err := client.Close(); err != nil { + t.Logf("❌ Could not close the client connection: %v", err) + } }() t.Logf("🔌 Client reading greeting...") greeting := tofutestutils.Must2(io.ReadAll(client)) From 2fe9c0c655aa5d5448db77ee78abb454c6276317 Mon Sep 17 00:00:00 2001 From: AbstractionFactory <179820029+abstractionfactory@users.noreply.github.com> Date: Wed, 22 Jan 2025 13:25:10 +0100 Subject: [PATCH 4/6] Readme update Signed-off-by: AbstractionFactory <179820029+abstractionfactory@users.noreply.github.com> --- README.md | 33 +++++++++++++++++++++++++++------ 1 file changed, 27 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index e7b3368..254d3c6 100644 --- a/README.md +++ b/README.md @@ -69,25 +69,46 @@ func TestMySocket(t *testing.T) { ca := tofutestutils.CA(t) // Server side: - tlsListener := tofutestutils.Must2(tls.Listen("tcp", "127.0.0.1:0", ca.CreateLocalhostServerCert().GetServerTLSConfig())) + tlsListener, err := tls.Listen("tcp", "127.0.0.1:0", ca.CreateLocalhostServerCert().GetServerTLSConfig()) + if err != nil { + t.Fatalf("Failed to open server: %v", err) + } + defer func() { + if err = tlsListener.Close(); err != nil { + t.Fatalf("Failed to close server listener: %v", err) + } + }() go func() { conn, serverErr := tlsListener.Accept() if serverErr != nil { return } defer func() { - _ = conn.Close() + if err := conn.Close(); err != nil { + t.Logf("Failed to close connection: %v", err) + } }() - _, _ = conn.Write([]byte("Hello world!")) + if _, err = conn.Write([]byte("Hello world!")); err != nil { + t.Logf("Failed to write to client: %v", err) + } }() // Client side: port := tlsListener.Addr().(*net.TCPAddr).Port - client := tofutestutils.Must2(tls.Dial("tcp", net.JoinHostPort("127.0.0.1", strconv.Itoa(port)), ca.GetClientTLSConfig())) + client, err := tls.Dial("tcp", net.JoinHostPort("127.0.0.1", strconv.Itoa(port)), ca.GetClientTLSConfig()) + if err != nil { + t.Fatalf("Failed to open connection to server: %v", err) + } defer func() { - _ = client.Close() + if err = client.Close(); err != nil { + t.Fatalf("Failed to close client: %v", err) + } }() - t.Logf("%s", tofutestutils.Must2(io.ReadAll(client))) + data, err := io.ReadAll(client) + if err != nil { + t.Fatal(err) + } + t.Logf("%s", data) } ``` From 89c003c23f02432943417e3cd9eef0c1277fef4a Mon Sep 17 00:00:00 2001 From: AbstractionFactory <179820029+abstractionfactory@users.noreply.github.com> Date: Wed, 22 Jan 2025 13:44:25 +0100 Subject: [PATCH 5/6] goimports Signed-off-by: AbstractionFactory <179820029+abstractionfactory@users.noreply.github.com> --- ca.go | 3 ++- testca/ca_test.go | 7 ++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/ca.go b/ca.go index 7f7f8c8..23716cf 100644 --- a/ca.go +++ b/ca.go @@ -6,8 +6,9 @@ package tofutestutils import ( - "github.com/opentofu/tofutestutils/testca" "testing" + + "github.com/opentofu/tofutestutils/testca" ) // CA returns a certificate authority configured for the provided test. This implementation will configure the CA to use diff --git a/testca/ca_test.go b/testca/ca_test.go index 81b7085..b92e5a0 100644 --- a/testca/ca_test.go +++ b/testca/ca_test.go @@ -9,13 +9,14 @@ import ( "bytes" "context" "crypto/tls" - "github.com/opentofu/tofutestutils" - "github.com/opentofu/tofutestutils/testca" - "github.com/opentofu/tofutestutils/testrandom" "io" "net" "strconv" "testing" + + "github.com/opentofu/tofutestutils" + "github.com/opentofu/tofutestutils/testca" + "github.com/opentofu/tofutestutils/testrandom" ) func TestCA(t *testing.T) { From 65b03e92e7e28373d360fe79ae94f237bdd94c25 Mon Sep 17 00:00:00 2001 From: AbstractionFactory <179820029+abstractionfactory@users.noreply.github.com> Date: Wed, 22 Jan 2025 13:52:27 +0100 Subject: [PATCH 6/6] Fixed broken tests after rebase Signed-off-by: AbstractionFactory <179820029+abstractionfactory@users.noreply.github.com> --- testca/ca_test.go | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/testca/ca_test.go b/testca/ca_test.go index b92e5a0..780ee2b 100644 --- a/testca/ca_test.go +++ b/testca/ca_test.go @@ -14,7 +14,6 @@ import ( "strconv" "testing" - "github.com/opentofu/tofutestutils" "github.com/opentofu/tofutestutils/testca" "github.com/opentofu/tofutestutils/testrandom" ) @@ -35,11 +34,14 @@ func testCAIncorrectCertificate(t *testing.T) { done := make(chan struct{}) var serverErr error t.Logf("🍦 Setting up TLS server...") - tlsListener := tofutestutils.Must2(tls.Listen( + tlsListener, err := tls.Listen( "tcp", "127.0.0.1:0", - ca1.CreateLocalhostServerCert().GetServerTLSConfig()), + ca1.CreateLocalhostServerCert().GetServerTLSConfig(), ) + if err != nil { + t.Fatalf("❌ Failed to set up listener: %v", err) + } t.Cleanup(func() { t.Logf("🍦 Server closing listener...") if err := tlsListener.Close(); err != nil { @@ -101,7 +103,10 @@ func testCACorrectCertificate(t *testing.T) { done := make(chan struct{}) t.Logf("🍦 Setting up TLS server...") - tlsListener := tofutestutils.Must2(tls.Listen("tcp", "127.0.0.1:0", ca.CreateLocalhostServerCert().GetServerTLSConfig())) + tlsListener, err := tls.Listen("tcp", "127.0.0.1:0", ca.CreateLocalhostServerCert().GetServerTLSConfig()) + if err != nil { + t.Fatalf("❌ Failed to set up listener: %v", err) + } t.Cleanup(func() { t.Logf("🍦 Server closing listener...") if err := tlsListener.Close(); err != nil { @@ -133,7 +138,10 @@ func testCACorrectCertificate(t *testing.T) { }() t.Logf("🔌 Client connecting to server...") port := tlsListener.Addr().(*net.TCPAddr).Port - client := tofutestutils.Must2(tls.Dial("tcp", net.JoinHostPort("127.0.0.1", strconv.Itoa(port)), ca.GetClientTLSConfig())) + client, err := tls.Dial("tcp", net.JoinHostPort("127.0.0.1", strconv.Itoa(port)), ca.GetClientTLSConfig()) + if err != nil { + t.Fatalf("❌ Failed to connect to server: %v", err) + } defer func() { t.Logf("🔌 Client closing connection...") if err := client.Close(); err != nil { @@ -141,7 +149,10 @@ func testCACorrectCertificate(t *testing.T) { } }() t.Logf("🔌 Client reading greeting...") - greeting := tofutestutils.Must2(io.ReadAll(client)) + greeting, err := io.ReadAll(client) + if err != nil { + t.Fatalf("❌ Failed to read greeting: %v", err) + } if string(greeting) != testGreeting { t.Fatalf("❌ Client received incorrect greeting: %s", greeting) }