From 44dfe0ce2d38b872336969d5860dbf449ea5cc82 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20de=20la=20Pe=C3=B1a?= Date: Wed, 10 Apr 2024 13:36:54 +0200 Subject: [PATCH 01/21] fix: remove suspicious filepath.Join --- container.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/container.go b/container.go index 00da75783d..fad8e0a8b2 100644 --- a/container.go +++ b/container.go @@ -228,7 +228,7 @@ func (c *ContainerRequest) GetContext() (io.Reader, error) { if dockerIgnoreExists { // only add .dockerignore if it exists - includes = append(includes, filepath.Join(".dockerignore")) + includes = append(includes, ".dockerignore") } includes = append(includes, c.GetDockerfile()) From 7e06144cd83d31b74030dcb2d9b8191ce1149a59 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20de=20la=20Pe=C3=B1a?= Date: Wed, 10 Apr 2024 13:37:27 +0200 Subject: [PATCH 02/21] chore: fix lint --- docker.go | 1 - docker_exec_test.go | 1 + reaper.go | 1 - 3 files changed, 1 insertion(+), 2 deletions(-) diff --git a/docker.go b/docker.go index 6d10cf92a0..925269ac88 100644 --- a/docker.go +++ b/docker.go @@ -1506,7 +1506,6 @@ func (p *DockerProvider) getDefaultNetwork(ctx context.Context, cli client.APICl Attachable: true, Labels: core.DefaultLabels(core.SessionID()), }) - if err != nil { return "", err } diff --git a/docker_exec_test.go b/docker_exec_test.go index 4716c493ab..51f62fa819 100644 --- a/docker_exec_test.go +++ b/docker_exec_test.go @@ -8,6 +8,7 @@ import ( "github.com/docker/docker/pkg/stdcopy" "github.com/stretchr/testify/require" + tcexec "github.com/testcontainers/testcontainers-go/exec" ) diff --git a/reaper.go b/reaper.go index 3dff6a7c9d..859f8b76de 100644 --- a/reaper.go +++ b/reaper.go @@ -120,7 +120,6 @@ func lookUpReaperContainer(ctx context.Context, sessionID string) (*DockerContai return nil }, backoff.WithContext(exp, ctx)) - if err != nil { return nil, err } From df6afea0fb6b8f6a892b374d01b7a54b46d988ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20de=20la=20Pe=C3=B1a?= Date: Wed, 10 Apr 2024 13:38:04 +0200 Subject: [PATCH 03/21] fix: handle error --- docker.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docker.go b/docker.go index 925269ac88..79f85b8f00 100644 --- a/docker.go +++ b/docker.go @@ -637,6 +637,9 @@ func (c *DockerContainer) CopyToContainer(ctx context.Context, fileContent []byt func (c *DockerContainer) copyToContainer(ctx context.Context, fileContent func(tw io.Writer) error, fileContentSize int64, containerFilePath string, fileMode int64) error { buffer, err := tarFile(containerFilePath, fileContent, fileContentSize, fileMode) + if err != nil { + return err + } err = c.provider.client.CopyToContainer(ctx, c.ID, "/", buffer, types.CopyToContainerOptions{}) if err != nil { From e13191e160178f3ba2f01b02a4a82323534eb07f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20de=20la=20Pe=C3=B1a?= Date: Wed, 10 Apr 2024 13:38:50 +0200 Subject: [PATCH 04/21] chore: reverse assertion for lint --- docker_exec_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docker_exec_test.go b/docker_exec_test.go index 51f62fa819..11f187c226 100644 --- a/docker_exec_test.go +++ b/docker_exec_test.go @@ -134,6 +134,6 @@ func TestExecWithNonMultiplexedResponse(t *testing.T) { require.NotNil(t, stdout) require.NotNil(t, stderr) - require.Equal(t, stdout.String(), "stdout\n") - require.Equal(t, stderr.String(), "stderr\n") + require.Equal(t, "stdout\n", stdout.String()) + require.Equal(t, "stderr\n", stderr.String()) } From af10f22fbd5e36ebd50d22c2426dff97d1f78a64 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20de=20la=20Pe=C3=B1a?= Date: Wed, 10 Apr 2024 13:50:37 +0200 Subject: [PATCH 05/21] feat: support generating TLS certificates on the fly --- tls/generate.go | 210 +++++++++++++++++++++++++++++++++++++++++++ tls/generate_test.go | 206 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 416 insertions(+) create mode 100644 tls/generate.go create mode 100644 tls/generate_test.go diff --git a/tls/generate.go b/tls/generate.go new file mode 100644 index 0000000000..6fa0737fe9 --- /dev/null +++ b/tls/generate.go @@ -0,0 +1,210 @@ +package tls + +import ( + "crypto/rand" + "crypto/rsa" + "crypto/x509" + "crypto/x509/pkix" + "encoding/pem" + "fmt" + "math/big" + "net" + "strings" + "time" +) + +// Certificate represents a certificate and private key pair. It's a wrapper +// around the x509.Certificate and rsa.PrivateKey types, and includes the raw +// bytes of the certificate and private key. +type Certificate struct { + Cert *x509.Certificate + Bytes []byte + Key *rsa.PrivateKey + KeyBytes []byte +} + +type certRequest struct { + SubjectCommonName string // CommonName is the subject name of the certificate + Host string // Comma-separated hostnames and IPs to generate a certificate for + ValidFrom time.Time // Creation date formatted as Jan 1 15:04:05 2011 + ValidFor time.Duration // Duration that certificate is valid for + IsCA bool // whether this cert should be its own Certificate Authority + IPAddreses []net.IP // IP addresses to include in the Subject Alternative Name + Parent *x509.Certificate // Parent is the parent certificate, if any + Priv any // Priv is the private key of the parent certificate + Pem bool // Whether to return the certificate as PEM bytes +} + +type CertOpt func(*certRequest) + +func WithSubjectCommonName(commonName string) CertOpt { + return func(r *certRequest) { + r.SubjectCommonName = commonName + } +} + +// WithHost sets the hostnames and IPs to generate a certificate for. +// In the case the passed string contains comma-separated values, +// it will be split into multiple hostnames and IPs. Each hostname and IP +// will be trimmed of whitespace, and if the value is an IP, it will be +// added to the IPAddresses field of the certificate, after the ones +// passed with the WithIPAddresses option. Otherwise, it will be added +// to the DNSNames field. +func WithHost(host string) CertOpt { + return func(r *certRequest) { + r.Host = host + } +} + +func WithValidFrom(validFrom time.Time) CertOpt { + return func(r *certRequest) { + r.ValidFrom = validFrom + } +} + +func WithValidFor(validFor time.Duration) CertOpt { + return func(r *certRequest) { + r.ValidFor = validFor + } +} + +// AsCA sets the certificate as a Certificate Authority. +// When passed, the KeyUsage field of the certificate +// will append the x509.KeyUsageCertSign usage. +func AsCA() CertOpt { + return func(r *certRequest) { + r.IsCA = true + } +} + +// WithParent sets the parent certificate and private key of the certificate. +// It's used to sign the certificate with the parent certificate. +// At the moment the parent is set, the issuer of the certificate will be +// set to the common name of the parent certificate. +func WithParent(parent *x509.Certificate, priv any) CertOpt { + return func(r *certRequest) { + r.Parent = parent + r.Priv = priv + } +} + +// AsPem sets the certificate to be returned as PEM bytes. It will include +// the private key in the KeyBytes field of the Certificate struct. +func AsPem() CertOpt { + return func(r *certRequest) { + r.Pem = true + } +} + +// WithIPAddresses sets the IP addresses of the certificate. They will be +// added first to the IPAddresses field of the certificate: those coming +// from the WithHost option will be added after these. +func WithIPAddresses(ips ...net.IP) CertOpt { + return func(r *certRequest) { + r.IPAddreses = append(r.IPAddreses, ips...) + } +} + +// newCertRequest returns a new certRequest with default values +// to avoid nil pointers. +func newCertRequest() certRequest { + return certRequest{ + ValidFrom: time.Now().Add(-time.Hour), + ValidFor: 365 * 24 * time.Hour, + IPAddreses: make([]net.IP, 0), + } +} + +// GenerateCert Generate a self-signed X.509 certificate for a TLS server. Returns +// a struct containing the certificate and private key, as well as the raw bytes +// of the certificate. In the case the PEM option is set, the raw bytes will be +// PEM-encoded, including the bytes of the private key in the KeyBytes field. +func GenerateCert(opts ...CertOpt) (*Certificate, error) { + req := newCertRequest() + + for _, opt := range opts { + opt(&req) + } + + if len(req.Host) == 0 { + return nil, fmt.Errorf("missing required host") + } + + keyUsage := x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature + if req.IsCA { + keyUsage |= x509.KeyUsageCertSign + } + + template := x509.Certificate{ + SerialNumber: big.NewInt(2019), + Subject: pkix.Name{ + CommonName: req.SubjectCommonName, + }, + NotBefore: req.ValidFrom, + NotAfter: req.ValidFrom.Add(req.ValidFor), + KeyUsage: keyUsage, + BasicConstraintsValid: true, + IsCA: req.IsCA, + } + + if req.Parent == nil { + // if no parent is provided, use the generated certificate as the parent + req.Parent = &template + } else { + // if a parent is provided, use the parent's common name as the issuer + template.Issuer.CommonName = req.Parent.Subject.CommonName + } + + if len(req.IPAddreses) > 0 { + template.IPAddresses = req.IPAddreses + } + + hosts := strings.Split(req.Host, ",") + for _, h := range hosts { + h = strings.TrimSpace(h) + if ip := net.ParseIP(h); ip != nil { + template.IPAddresses = append(template.IPAddresses, ip) + } else { + template.DNSNames = append(template.DNSNames, h) + } + } + + pk, err := rsa.GenerateKey(rand.Reader, 2048) + if err != nil { + return nil, err + } + + if req.Priv == nil { + // if no parent private key is provided, use the generated private key + req.Priv = pk + } + + certBytes, err := x509.CreateCertificate(rand.Reader, &template, req.Parent, pk.Public(), req.Priv) + if err != nil { + return nil, err + } + + cert, err := x509.ParseCertificate(certBytes) + if err != nil { + return nil, err + } + + certificate := &Certificate{ + Cert: cert, + Key: pk, + Bytes: certBytes, + } + + if req.Pem { + certificate.Bytes = pem.EncodeToMemory(&pem.Block{ + Type: "CERTIFICATE", + Bytes: certBytes, + }) + certificate.KeyBytes = pem.EncodeToMemory(&pem.Block{ + Type: "RSA PRIVATE KEY", + Bytes: x509.MarshalPKCS1PrivateKey(pk), + }) + } + + return certificate, nil +} diff --git a/tls/generate_test.go b/tls/generate_test.go new file mode 100644 index 0000000000..2eeb702142 --- /dev/null +++ b/tls/generate_test.go @@ -0,0 +1,206 @@ +package tls_test + +import ( + "net" + "testing" + + "github.com/testcontainers/testcontainers-go/tls" +) + +func TestGenerate(t *testing.T) { + t.Run("No host returns error", func(t *testing.T) { + _, err := tls.GenerateCert() + if err == nil { + t.Fatal("expected error, got nil") + } + }) + + t.Run("With host", func(t *testing.T) { + cert, err := tls.GenerateCert(tls.WithHost("localhost")) + if err != nil { + t.Fatal(err) + } + + if cert == nil { + t.Fatal("expected cert, got nil") + } + + if cert.Key == nil { + t.Fatal("expected key, got nil") + } + }) + + t.Run("With multiple hosts", func(t *testing.T) { + ip := "1.2.3.4" + cert, err := tls.GenerateCert(tls.WithHost("localhost, " + ip)) + if err != nil { + t.Fatal(err) + } + + if cert == nil { + t.Fatal("expected cert, got nil") + } + + if cert.Key == nil { + t.Fatal("expected key, got nil") + } + + c := cert.Cert + if len(c.IPAddresses) != 1 { + t.Fatal("expected 1 IP address, got", len(c.IPAddresses)) + } + + if c.IPAddresses[0].String() != ip { + t.Fatalf("expected IP address to be %s, got %s\n", ip, c.IPAddresses[0].String()) + } + }) + + t.Run("With multiple hosts and IPs", func(t *testing.T) { + ip := "1.2.3.4" + ips := []net.IP{net.ParseIP("4.5.6.7"), net.ParseIP("8.9.10.11")} + cert, err := tls.GenerateCert(tls.WithHost("localhost, "+ip), tls.WithIPAddresses(ips...)) + if err != nil { + t.Fatal(err) + } + + if cert == nil { + t.Fatal("expected cert, got nil") + } + + if cert.Key == nil { + t.Fatal("expected key, got nil") + } + + c := cert.Cert + if len(c.IPAddresses) != 3 { + t.Fatal("expected 3 IP address, got", len(c.IPAddresses)) + } + + for i, ip := range ips { + if c.IPAddresses[i].String() != ip.String() { + t.Fatalf("expected IP address to be %s, got %s\n", ip.String(), c.IPAddresses[i].String()) + } + } + // the IP from the host comes after the IPs from the IPAddresses option + if c.IPAddresses[2].String() != ip { + t.Fatalf("expected IP address to be %s, got %s\n", ip, c.IPAddresses[0].String()) + } + }) + + t.Run("As CA", func(t *testing.T) { + cert, err := tls.GenerateCert(tls.WithHost("localhost"), tls.AsCA()) + if err != nil { + t.Fatal(err) + } + + if cert == nil { + t.Fatal("expected cert, got nil") + } + + if cert.Cert == nil { + t.Fatal("expected cert, got nil") + } + if cert.Key == nil { + t.Fatal("expected key, got nil") + } + if cert.Bytes == nil { + t.Fatal("expected bytes, got nil") + } + + if !cert.Cert.IsCA { + t.Fatal("expected cert to be CA, got false") + } + }) + + t.Run("As PEM", func(t *testing.T) { + cert, err := tls.GenerateCert(tls.WithHost("localhost"), tls.AsPem()) + if err != nil { + t.Fatal(err) + } + + if cert == nil { + t.Fatal("expected cert, got nil") + } + + // PEM format adds the key bytes to the cert struct + if cert.Bytes == nil { + t.Fatal("expected bytes, got nil") + } + if cert.KeyBytes == nil { + t.Fatal("expected key bytes, got nil") + } + }) + + t.Run("With Subject common name", func(t *testing.T) { + cert, err := tls.GenerateCert(tls.WithHost("localhost"), tls.WithSubjectCommonName("Test")) + if err != nil { + t.Fatal(err) + } + + if cert == nil { + t.Fatal("expected cert, got nil") + } + + if cert.Cert == nil { + t.Fatal("expected cert, got nil") + } + + c := cert.Cert + if c.Subject.CommonName != "Test" { + t.Fatal("expected common name to be Test, got", c.Subject.CommonName) + } + }) + + t.Run("With Parent cert", func(t *testing.T) { + parent, err := tls.GenerateCert(tls.WithHost("localhost"), tls.AsCA()) + if err != nil { + t.Fatal(err) + } + + cert, err := tls.GenerateCert(tls.WithHost("localhost"), tls.WithParent(parent.Cert, parent.Key)) + if err != nil { + t.Fatal(err) + } + + if cert == nil { + t.Fatal("expected cert, got nil") + } + + if cert.Cert == nil { + t.Fatal("expected cert, got nil") + } + if cert.Key == nil { + t.Fatal("expected key, got nil") + } + + c := cert.Cert + if c.Issuer.CommonName != parent.Cert.Subject.CommonName { + t.Fatal("expected issuer to be parent, got", c.Issuer.CommonName) + } + }) + + t.Run("With IP addresses", func(t *testing.T) { + ip := "1.2.3.4" + cert, err := tls.GenerateCert(tls.WithHost("localhost"), tls.WithIPAddresses(net.ParseIP(ip))) + if err != nil { + t.Fatal(err) + } + + if cert == nil { + t.Fatal("expected cert, got nil") + } + + if cert.Cert == nil { + t.Fatal("expected cert, got nil") + } + + c := cert.Cert + if len(c.IPAddresses) != 1 { + t.Fatal("expected 1 IP address, got", len(c.IPAddresses)) + } + + if c.IPAddresses[0].String() != ip { + t.Fatalf("expected IP address to be %s, got %s\n", ip, c.IPAddresses[0].String()) + } + }) +} From a1f846f36420dd60642995d37c8e5c3b08cbc2a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20de=20la=20Pe=C3=B1a?= Date: Wed, 10 Apr 2024 13:50:58 +0200 Subject: [PATCH 06/21] chore: apply to cockroachdb --- modules/cockroachdb/certs.go | 141 +++++++++-------------------------- 1 file changed, 34 insertions(+), 107 deletions(-) diff --git a/modules/cockroachdb/certs.go b/modules/cockroachdb/certs.go index fc1b399a42..696dc61583 100644 --- a/modules/cockroachdb/certs.go +++ b/modules/cockroachdb/certs.go @@ -1,14 +1,12 @@ package cockroachdb import ( - "crypto/rand" "crypto/rsa" "crypto/x509" - "crypto/x509/pkix" - "encoding/pem" - "math/big" "net" "time" + + tctls "github.com/testcontainers/testcontainers-go/tls" ) type TLSConfig struct { @@ -46,113 +44,42 @@ func NewTLSConfig() (*TLSConfig, error) { } func generateCA() (*x509.Certificate, *rsa.PrivateKey, error) { - template := x509.Certificate{ - SerialNumber: big.NewInt(2019), - Subject: pkix.Name{ - CommonName: "Cockroach Test CA", - }, - NotBefore: time.Now().Add(-time.Hour), - NotAfter: time.Now().Add(time.Hour), - KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign, - BasicConstraintsValid: true, - IsCA: true, - } - - caPrivKey, err := rsa.GenerateKey(rand.Reader, 2048) - if err != nil { - return nil, nil, err - } - - caBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, caPrivKey.Public(), caPrivKey) - if err != nil { - return nil, nil, err - } - - caCert, err := x509.ParseCertificate(caBytes) - if err != nil { - return nil, nil, err - } - - return caCert, caPrivKey, nil + caCert, err := tctls.GenerateCert( + tctls.WithHost("localhost"), + tctls.WithSubjectCommonName("Cockroach Test CA"), + tctls.AsCA(), + tctls.WithValidFrom(time.Now().Add(-time.Hour)), + tctls.WithValidFor(time.Hour), + ) + + return caCert.Cert, caCert.Key, err } func generateNode(caCert *x509.Certificate, caKey *rsa.PrivateKey) ([]byte, []byte, error) { - template := x509.Certificate{ - SerialNumber: big.NewInt(2019), - Subject: pkix.Name{ - CommonName: "node", - }, - DNSNames: []string{"localhost"}, - IPAddresses: []net.IP{ - net.IPv4(127, 0, 0, 1), - net.IPv6loopback, - }, - NotBefore: time.Now().Add(-time.Hour), - NotAfter: time.Now().Add(time.Hour), - KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, - ExtKeyUsage: []x509.ExtKeyUsage{ - x509.ExtKeyUsageServerAuth, - x509.ExtKeyUsageClientAuth, - }, - BasicConstraintsValid: true, - } - - certPrivKey, err := rsa.GenerateKey(rand.Reader, 2048) - if err != nil { - return nil, nil, err - } - - certBytes, err := x509.CreateCertificate(rand.Reader, &template, caCert, certPrivKey.Public(), caKey) - if err != nil { - return nil, nil, err - } - - cert := pem.EncodeToMemory(&pem.Block{ - Type: "CERTIFICATE", - Bytes: certBytes, - }) - certKey := pem.EncodeToMemory(&pem.Block{ - Type: "RSA PRIVATE KEY", - Bytes: x509.MarshalPKCS1PrivateKey(certPrivKey), - }) - - return cert, certKey, nil + cert, err := tctls.GenerateCert( + tctls.WithHost("localhost"), // the host will be passed as DNSNames + tctls.WithSubjectCommonName("node"), + tctls.AsCA(), + tctls.WithIPAddresses(net.IPv4(127, 0, 0, 1), net.IPv6loopback), + tctls.WithValidFrom(time.Now().Add(-time.Hour)), + tctls.WithValidFor(time.Hour), + tctls.AsPem(), + tctls.WithParent(caCert, caKey), + ) + + return cert.Bytes, cert.KeyBytes, err } func generateClient(caCert *x509.Certificate, caKey *rsa.PrivateKey) ([]byte, []byte, error) { - template := x509.Certificate{ - SerialNumber: big.NewInt(2019), - Subject: pkix.Name{ - CommonName: defaultUser, - }, - NotBefore: time.Now().Add(-time.Hour), - NotAfter: time.Now().Add(time.Hour), - KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, - ExtKeyUsage: []x509.ExtKeyUsage{ - x509.ExtKeyUsageServerAuth, - x509.ExtKeyUsageClientAuth, - }, - BasicConstraintsValid: true, - } - - certPrivKey, err := rsa.GenerateKey(rand.Reader, 2048) - if err != nil { - return nil, nil, err - } - - certBytes, err := x509.CreateCertificate(rand.Reader, &template, caCert, certPrivKey.Public(), caKey) - if err != nil { - return nil, nil, err - } - - cert := pem.EncodeToMemory(&pem.Block{ - Type: "CERTIFICATE", - Bytes: certBytes, - }) - certKey := pem.EncodeToMemory(&pem.Block{ - Type: "RSA PRIVATE KEY", - Bytes: x509.MarshalPKCS1PrivateKey(certPrivKey), - }) - - return cert, certKey, nil + cert, err := tctls.GenerateCert( + tctls.WithHost("localhost"), + tctls.WithSubjectCommonName(defaultUser), + tctls.AsCA(), + tctls.WithValidFrom(time.Now().Add(-time.Hour)), + tctls.WithValidFor(time.Hour), + tctls.AsPem(), + tctls.WithParent(caCert, caKey), + ) + + return cert.Bytes, cert.KeyBytes, err } From cb71d87f54fee78e43331656d7f2dd756e8de56f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20de=20la=20Pe=C3=B1a?= Date: Wed, 10 Apr 2024 14:49:43 +0200 Subject: [PATCH 07/21] chore: support saving the cert and priv key files to disk --- tls/generate.go | 38 +++++++++++++++++++++++++++++ tls/generate_test.go | 58 ++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 91 insertions(+), 5 deletions(-) diff --git a/tls/generate.go b/tls/generate.go index 6fa0737fe9..b70980bf80 100644 --- a/tls/generate.go +++ b/tls/generate.go @@ -9,8 +9,12 @@ import ( "fmt" "math/big" "net" + "os" + "path/filepath" "strings" "time" + + "github.com/google/uuid" ) // Certificate represents a certificate and private key pair. It's a wrapper @@ -21,6 +25,8 @@ type Certificate struct { Bytes []byte Key *rsa.PrivateKey KeyBytes []byte + CertPath string + KeyPath string } type certRequest struct { @@ -33,6 +39,7 @@ type certRequest struct { Parent *x509.Certificate // Parent is the parent certificate, if any Priv any // Priv is the private key of the parent certificate Pem bool // Whether to return the certificate as PEM bytes + SaveTo string // Parent directory to save the certificate and private key } type CertOpt func(*certRequest) @@ -105,6 +112,19 @@ func WithIPAddresses(ips ...net.IP) CertOpt { } } +// WithSaveToFile sets the directory to save the certificate and private key. +// For that reason, it will set the AsPem option, as the certificate +// will be saved as PEM bytes, including the private key. +func WithSaveToFile(dir string) CertOpt { + return func(r *certRequest) { + r.SaveTo = dir + + if !r.Pem { + AsPem()(r) + } + } +} + // newCertRequest returns a new certRequest with default values // to avoid nil pointers. func newCertRequest() certRequest { @@ -206,5 +226,23 @@ func GenerateCert(opts ...CertOpt) (*Certificate, error) { }) } + if req.SaveTo != "" { + id := uuid.NewString() + certPath := filepath.Join(req.SaveTo, "cert-"+id+".pem") + + if err := os.WriteFile(certPath, certificate.Bytes, 0644); err != nil { + return nil, err + } + certificate.CertPath = certPath + + if certificate.KeyBytes != nil { + keyPath := filepath.Join(req.SaveTo, "key-"+id+".pem") + if err := os.WriteFile(keyPath, certificate.KeyBytes, 0644); err != nil { + return nil, err + } + certificate.KeyPath = keyPath + } + } + return certificate, nil } diff --git a/tls/generate_test.go b/tls/generate_test.go index 2eeb702142..c31a5f0789 100644 --- a/tls/generate_test.go +++ b/tls/generate_test.go @@ -1,7 +1,9 @@ package tls_test import ( + stdlibtls "crypto/tls" "net" + "os" "testing" "github.com/testcontainers/testcontainers-go/tls" @@ -15,18 +17,23 @@ func TestGenerate(t *testing.T) { } }) - t.Run("With host", func(t *testing.T) { - cert, err := tls.GenerateCert(tls.WithHost("localhost")) + t.Run("With host", func(tt *testing.T) { + cert, err := tls.GenerateCert(tls.WithHost("localhost"), tls.AsPem()) if err != nil { - t.Fatal(err) + tt.Fatal(err) } if cert == nil { - t.Fatal("expected cert, got nil") + tt.Fatal("expected cert, got nil") } if cert.Key == nil { - t.Fatal("expected key, got nil") + tt.Fatal("expected key, got nil") + } + + _, err = stdlibtls.X509KeyPair(cert.Bytes, cert.KeyBytes) + if err != nil { + tt.Fatal(err) } }) @@ -203,4 +210,45 @@ func TestGenerate(t *testing.T) { t.Fatalf("expected IP address to be %s, got %s\n", ip, c.IPAddresses[0].String()) } }) + + t.Run("Save to file", func(tt *testing.T) { + tmp := tt.TempDir() + + // no need to pass the AsPem option, the SaveToFile option will do that + cert, err := tls.GenerateCert(tls.WithHost("localhost"), tls.WithSaveToFile(tmp)) + if err != nil { + tt.Fatal(err) + } + + if cert == nil { + tt.Fatal("expected cert, got nil") + } + + inMemoryCert, err := stdlibtls.X509KeyPair(cert.Bytes, cert.KeyBytes) + if err != nil { + tt.Fatal(err) + } + + // check if file existed + certBytes, err := os.ReadFile(cert.CertPath) + if err != nil { + tt.Fatal(err) + } + + certKeyBytes, err := os.ReadFile(cert.KeyPath) + if err != nil { + tt.Fatal(err) + } + + fileCert, err := stdlibtls.X509KeyPair(certBytes, certKeyBytes) + if err != nil { + tt.Fatal(err) + } + + for i, cert := range inMemoryCert.Certificate { + if string(cert) != string(fileCert.Certificate[i]) { + tt.Fatalf("expected certificate to be %s, got %s\n", string(cert), string(fileCert.Certificate[i])) + } + } + }) } From 8ea67370addc01895a05aedc9ab559c420a4db73 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20de=20la=20Pe=C3=B1a?= Date: Wed, 10 Apr 2024 15:03:19 +0200 Subject: [PATCH 08/21] chore: apply to rabbitmq --- modules/rabbitmq/examples_test.go | 28 ++++++++++++++++--- modules/rabbitmq/rabbitmq_test.go | 20 ++++++++++--- modules/rabbitmq/testdata/certs/server_ca.pem | 20 ------------- .../rabbitmq/testdata/certs/server_cert.pem | 21 -------------- .../rabbitmq/testdata/certs/server_key.pem | 27 ------------------ 5 files changed, 40 insertions(+), 76 deletions(-) delete mode 100644 modules/rabbitmq/testdata/certs/server_ca.pem delete mode 100644 modules/rabbitmq/testdata/certs/server_cert.pem delete mode 100644 modules/rabbitmq/testdata/certs/server_key.pem diff --git a/modules/rabbitmq/examples_test.go b/modules/rabbitmq/examples_test.go index a6fc829424..a589990425 100644 --- a/modules/rabbitmq/examples_test.go +++ b/modules/rabbitmq/examples_test.go @@ -5,13 +5,14 @@ import ( "fmt" "io" "log" - "path/filepath" + "os" "strings" amqp "github.com/rabbitmq/amqp091-go" "github.com/testcontainers/testcontainers-go" "github.com/testcontainers/testcontainers-go/modules/rabbitmq" + tctls "github.com/testcontainers/testcontainers-go/tls" ) func ExampleRunContainer() { @@ -89,10 +90,29 @@ func ExampleRunContainer_withSSL() { // enableSSL { ctx := context.Background() + tmpDir := os.TempDir() + certDirs := tmpDir + "/rabbitmq" + if err := os.MkdirAll(certDirs, 0755); err != nil { + log.Fatalf("failed to create temporary directory: %s", err) + } + defer os.RemoveAll(certDirs) + + // generates the CA certificate and the certificate + // using TestContainers TLS helper functions. + caCert, err := tctls.GenerateCert(tctls.WithHost("localhost"), tctls.AsCA(), tctls.WithSaveToFile(certDirs)) + if err != nil { + log.Fatalf("failed to generate CA certificate: %s", err) + } + + cert, err := tctls.GenerateCert(tctls.WithHost("localhost"), tctls.WithParent(caCert.Cert, caCert.Key), tctls.WithSaveToFile(certDirs)) + if err != nil { + log.Fatalf("failed to generate certificate: %s", err) + } + sslSettings := rabbitmq.SSLSettings{ - CACertFile: filepath.Join("testdata", "certs", "server_ca.pem"), - CertFile: filepath.Join("testdata", "certs", "server_cert.pem"), - KeyFile: filepath.Join("testdata", "certs", "server_key.pem"), + CACertFile: caCert.CertPath, + CertFile: cert.CertPath, + KeyFile: cert.KeyPath, VerificationMode: rabbitmq.SSLVerificationModePeer, FailIfNoCert: true, VerificationDepth: 1, diff --git a/modules/rabbitmq/rabbitmq_test.go b/modules/rabbitmq/rabbitmq_test.go index 7079379421..fcd4a96b82 100644 --- a/modules/rabbitmq/rabbitmq_test.go +++ b/modules/rabbitmq/rabbitmq_test.go @@ -7,7 +7,6 @@ import ( "fmt" "io" "io/ioutil" - "path/filepath" "strings" "testing" @@ -15,6 +14,7 @@ import ( "github.com/testcontainers/testcontainers-go" "github.com/testcontainers/testcontainers-go/modules/rabbitmq" + tctls "github.com/testcontainers/testcontainers-go/tls" ) func TestRunContainer_connectUsingAmqp(t *testing.T) { @@ -49,10 +49,22 @@ func TestRunContainer_connectUsingAmqp(t *testing.T) { func TestRunContainer_connectUsingAmqps(t *testing.T) { ctx := context.Background() + tmpDir := t.TempDir() + + caCert, err := tctls.GenerateCert(tctls.WithHost("localhost"), tctls.AsCA(), tctls.WithSaveToFile(tmpDir)) + if err != nil { + t.Fatalf("failed to generate CA certificate: %s", err) + } + + cert, err := tctls.GenerateCert(tctls.WithHost("localhost"), tctls.WithParent(caCert.Cert, caCert.Key), tctls.WithSaveToFile(tmpDir)) + if err != nil { + t.Fatalf("failed to generate certificate: %s", err) + } + sslSettings := rabbitmq.SSLSettings{ - CACertFile: filepath.Join("testdata", "certs", "server_ca.pem"), - CertFile: filepath.Join("testdata", "certs", "server_cert.pem"), - KeyFile: filepath.Join("testdata", "certs", "server_key.pem"), + CACertFile: caCert.CertPath, + CertFile: cert.CertPath, + KeyFile: cert.KeyPath, VerificationMode: rabbitmq.SSLVerificationModePeer, FailIfNoCert: false, VerificationDepth: 1, diff --git a/modules/rabbitmq/testdata/certs/server_ca.pem b/modules/rabbitmq/testdata/certs/server_ca.pem deleted file mode 100644 index f22df82909..0000000000 --- a/modules/rabbitmq/testdata/certs/server_ca.pem +++ /dev/null @@ -1,20 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIDRzCCAi+gAwIBAgIJAJJIMzvZuRzlMA0GCSqGSIb3DQEBCwUAMDExIDAeBgNV -BAMMF1RMU0dlblNlbGZTaWduZWR0Um9vdENBMQ0wCwYDVQQHDAQkJCQkMCAXDTE5 -MDUwMjA3MjI0OVoYDzIxMTkwNDA4MDcyMjQ5WjAxMSAwHgYDVQQDDBdUTFNHZW5T -ZWxmU2lnbmVkdFJvb3RDQTENMAsGA1UEBwwEJCQkJDCCASIwDQYJKoZIhvcNAQEB -BQADggEPADCCAQoCggEBAKko8FmfzrLHyZckvdR1oiSZf80m0t66TMqtLat1Oxjh -CjsxvswwJ/m2I5dM48hwZ+0b2ufkvaudLPq/8jDGyONVfjMGlbe1YlmQMDC7YWdI -XM1nCWAZIKaOHwIkfswuVBAdBVYV4Polu6wjVt5edEpl/IWEpPicXjLOY1Fw3q67 -5tP2Mmo6TJg5YqgB4fH4SmajtP3j+H4puQ8ZPIs26mInEgfCyrMWey/oQX8qqMph -pKMEJYE7DHawriFraOooJadJYojbY5H27nmJe8yXURb3wSQSaKnFZL25cmVm2kue -/lw+n+a2wLdHdU4cmghCURalhcXUNZe7UbdRZ9e9r2cCAwEAAaNgMF4wCwYDVR0P -BAQDAgEGMB0GA1UdDgQWBBSZiNur/XHsqSfdWnB1NPi/ql5+tzAfBgNVHSMEGDAW -gBSZiNur/XHsqSfdWnB1NPi/ql5+tzAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3 -DQEBCwUAA4IBAQAar/db/T7izD4pyh2titl7Dkgp2iTditfgqRlU0yVGiiB6rLmY -sYE2QAuFhgqyRLPcjVV8F39iRJHQ17SGT8e2iAaUTnbQj0AiskKjonF9+quKuVbr -TpYHk+guS0Jn2rU6HK8WQeYZOh3WdLTu4ArXkxywgwVssQQ9JmpTd9YEYePWfs7i -WZB6AQyL9CD3z1j4i1G4ft6pB1Ps5XjznqMZ2//7AUpoRTrettWqorPWwudQ9yna -B4S6KtvpnxUQSeHJW6Q4NvTrOsvHEOCa6OtwYbWmLf+qbpPb8oHt9UF3ze2PJopB -QzsQop1+gPudG0DX0SgyuQT+SsFjYlDazZdZ ------END CERTIFICATE----- diff --git a/modules/rabbitmq/testdata/certs/server_cert.pem b/modules/rabbitmq/testdata/certs/server_cert.pem deleted file mode 100644 index 8a78318338..0000000000 --- a/modules/rabbitmq/testdata/certs/server_cert.pem +++ /dev/null @@ -1,21 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIDajCCAlKgAwIBAgIBATANBgkqhkiG9w0BAQsFADAxMSAwHgYDVQQDDBdUTFNH -ZW5TZWxmU2lnbmVkdFJvb3RDQTENMAsGA1UEBwwEJCQkJDAgFw0xOTA1MDIwNzIy -NDlaGA8yMTE5MDQwODA3MjI0OVowIzEQMA4GA1UEAwwHQzY1U1RUMjEPMA0GA1UE -CgwGc2VydmVyMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqR0QXKtb -KVeEuCmZGcZAlAlTBC8E/G3UuX6qKwTR1xEOvUWeBH1n0WeXXGd/p/y6P4lRBeWN -BZ9KcvIlNDeDMy05NfxnO1vnJk9E8/0xwMiY1LJdMHzIzhmrrqXo0u3DT8MmoNR6 -7CTcnG21gi1GrjW8a747yFF0xfukEc6FkyVqLsjtCkHPwrc/sBHVS3aivNWGkJzA -eBXBdWJAg3ZC6T9U+Y8cndWQrpYMJvek1IewlyDSspHZDFmM1OwVwypnMt4fGgaX -5IlUMnNgKmisOSuI529rxLF+mvYIQLRl5bP+1/c9JD5MZ5krA3SrjdwRFS3sQXC3 -nuHqJofFXNkbXQIDAQABo4GYMIGVMAkGA1UdEwQCMAAwCwYDVR0PBAQDAgWgMBMG -A1UdJQQMMAoGCCsGAQUFBwMBMCYGA1UdEQQfMB2CB0M2NVNUVDKCB0M2NVNUVDKC -CWxvY2FsaG9zdDAdBgNVHQ4EFgQURq22sa46tA0SGHhEm9jxGP9aDrswHwYDVR0j -BBgwFoAUmYjbq/1x7Kkn3VpwdTT4v6pefrcwDQYJKoZIhvcNAQELBQADggEBAKUP -7RgmJyMVoHxg46F1fjWVhlF4BbQuEtB8mC+4G4e68lDU/TPAbmB3aj91oQDgBiTd -R2O7U6tyitxxrU2r7rFAHGhFHeyCQ3yZMwydO2V3Nm2Ywzdyk8er4yghjg9FS8tH -egDGDDod3l1yrAbHHuXmzDjnAFwHwRkm5cYUz00/IuZ3sQZ70XofL3KXNj1tAtfK -PSpdSAxSTO99ofjVKjlyywQSZKNbXfqD5DGz8e0rmqPfZ+3zi75E5nEuJ3UI2wXg -LuI4j6FIzNQyei/FdSynktcIm+hefQEyex4cho4C8RYB2S5S8RWrnP9jOzsaQFHn -bHXf7dKwRfA6/u8JmtQ= ------END CERTIFICATE----- diff --git a/modules/rabbitmq/testdata/certs/server_key.pem b/modules/rabbitmq/testdata/certs/server_key.pem deleted file mode 100644 index dfbfb6db7e..0000000000 --- a/modules/rabbitmq/testdata/certs/server_key.pem +++ /dev/null @@ -1,27 +0,0 @@ ------BEGIN RSA PRIVATE KEY----- -MIIEowIBAAKCAQEAqR0QXKtbKVeEuCmZGcZAlAlTBC8E/G3UuX6qKwTR1xEOvUWe -BH1n0WeXXGd/p/y6P4lRBeWNBZ9KcvIlNDeDMy05NfxnO1vnJk9E8/0xwMiY1LJd -MHzIzhmrrqXo0u3DT8MmoNR67CTcnG21gi1GrjW8a747yFF0xfukEc6FkyVqLsjt -CkHPwrc/sBHVS3aivNWGkJzAeBXBdWJAg3ZC6T9U+Y8cndWQrpYMJvek1IewlyDS -spHZDFmM1OwVwypnMt4fGgaX5IlUMnNgKmisOSuI529rxLF+mvYIQLRl5bP+1/c9 -JD5MZ5krA3SrjdwRFS3sQXC3nuHqJofFXNkbXQIDAQABAoIBAA0dxvYZCEIFmrKZ -71jzanDQ5FJvvyhA8H3OmC4r+oZ+uTDu5FmezF2OdkvhbyI9VMi2wsT9T9m+yAxw -QXhyUce3WzeXsv4Em8H55fQykBhOtqPQja/EDeMGVK2ACrXJYRufnDBfKoWEOmQb -kjddgZzjaBDHOWXJA5CTet8ysGOAJBTxyzU69k5Vj9B5abG9CofNzGOFF+Uleff5 -ip3sz7JpDXCex3oEs98veco6+8i/MZNo3BnwB5J+P+2MFFKONfPwuNyKAWBza2/X -66Lk3xXBjLJJ+Ww16jkqueTXEq6GCFXavNfdL9aonth5V5YYR/cj+2u2LM1oj9cJ -bp0xbvUCgYEA2Svq1DyR9cfTwrbc/0J2JfrjavClzDYU2oeO2fSU85WEEjJguaja -17Vdo/UsJtiUiSq4UhI1n0haaIpTBCeF2tHGXVEYZ7ZBi1zzdWbWlDxFmi+rcE57 -ytx5w+iLE366tQEMa/Jn3bly54pG5JZAr9TXkpg9sMbzWZri2ocyU/cCgYEAx1l/ -9X9C/OruDp/MhhmVwKfw/X2+RhZRuv0pPcpJu7/gIoLgaxNj41XSeLqLYMlisaRk -GFU17GFXtfRGE1a3z+jj8UPTP2sHk3w8m0yI+pgWgsvG0TJ0B+XsRfpVxFiIoaEs -3AsBaGR+hrRY1dpaJ9Cu3J9mEeToTpbCzPzVDksCgYEAzwSvWNvYY4u2UFHSvz2S -tMfBzCpUUiNno50/ToN5De4ENPhy/eh5nNDVz7qh+PHSPiNMC2gyV4E4NZlOY5Jt -Zdc8ma35brvtJTVZGxwKBsqhqsYwTeFy3kFnjZn6IX5X6r1yIuCzpEfowdEtnS+h -wDtLuAGKJR6x0UP1Zk0ka6cCgYBGE6I1rJzhx7wTi/0bjtbjuKWwlolSnfnxH5ll -zTyKMXMa7qLxQQm2Gq84HWtthJ2bEMzW+O1RwQ5SOiKAHdXT0mx+nXcfLgKlx+CO -PyNP5DLVm8iyNWgwdpTOLKgFs5GkL8JTP9Mo3VrVA4TO+EkFAgjWKXp6A9vd9IVa -Be7nbQKBgAVtFKuf9nbCMfN+W1gN0vlW2lwxCTa4w0KHgIlGIIvnYVuixSgu9fGt -uylQcQirEjqrdzdVF9L2BQ37ZcLaGh1LoCmx8XVCX/HhbwW2RP798P3Z1P7htm16 -ha5OfuPjHvoZklbYJo6EORJZQehS2VP63pjdnmUeMHPFzrPUevI5 ------END RSA PRIVATE KEY----- From 739d43a7f42891458d71ba0ed1b28d1a6eb8c09f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20de=20la=20Pe=C3=B1a?= Date: Wed, 10 Apr 2024 15:10:44 +0200 Subject: [PATCH 09/21] chore: simplify --- modules/cockroachdb/certs.go | 59 ++++++++++++------------------------ 1 file changed, 20 insertions(+), 39 deletions(-) diff --git a/modules/cockroachdb/certs.go b/modules/cockroachdb/certs.go index 696dc61583..f62486de52 100644 --- a/modules/cockroachdb/certs.go +++ b/modules/cockroachdb/certs.go @@ -1,7 +1,6 @@ package cockroachdb import ( - "crypto/rsa" "crypto/x509" "net" "time" @@ -19,31 +18,6 @@ type TLSConfig struct { // NewTLSConfig creates a new TLSConfig capable of running CockroachDB & connecting over TLS. func NewTLSConfig() (*TLSConfig, error) { - caCert, caKey, err := generateCA() - if err != nil { - return nil, err - } - - nodeCert, nodeKey, err := generateNode(caCert, caKey) - if err != nil { - return nil, err - } - - clientCert, clientKey, err := generateClient(caCert, caKey) - if err != nil { - return nil, err - } - - return &TLSConfig{ - CACert: caCert, - NodeCert: nodeCert, - NodeKey: nodeKey, - ClientCert: clientCert, - ClientKey: clientKey, - }, nil -} - -func generateCA() (*x509.Certificate, *rsa.PrivateKey, error) { caCert, err := tctls.GenerateCert( tctls.WithHost("localhost"), tctls.WithSubjectCommonName("Cockroach Test CA"), @@ -51,12 +25,11 @@ func generateCA() (*x509.Certificate, *rsa.PrivateKey, error) { tctls.WithValidFrom(time.Now().Add(-time.Hour)), tctls.WithValidFor(time.Hour), ) + if err != nil { + return nil, err + } - return caCert.Cert, caCert.Key, err -} - -func generateNode(caCert *x509.Certificate, caKey *rsa.PrivateKey) ([]byte, []byte, error) { - cert, err := tctls.GenerateCert( + nodeCert, err := tctls.GenerateCert( tctls.WithHost("localhost"), // the host will be passed as DNSNames tctls.WithSubjectCommonName("node"), tctls.AsCA(), @@ -64,22 +37,30 @@ func generateNode(caCert *x509.Certificate, caKey *rsa.PrivateKey) ([]byte, []by tctls.WithValidFrom(time.Now().Add(-time.Hour)), tctls.WithValidFor(time.Hour), tctls.AsPem(), - tctls.WithParent(caCert, caKey), + tctls.WithParent(caCert.Cert, caCert.Key), ) + if err != nil { + return nil, err + } - return cert.Bytes, cert.KeyBytes, err -} - -func generateClient(caCert *x509.Certificate, caKey *rsa.PrivateKey) ([]byte, []byte, error) { - cert, err := tctls.GenerateCert( + clientCert, err := tctls.GenerateCert( tctls.WithHost("localhost"), tctls.WithSubjectCommonName(defaultUser), tctls.AsCA(), tctls.WithValidFrom(time.Now().Add(-time.Hour)), tctls.WithValidFor(time.Hour), tctls.AsPem(), - tctls.WithParent(caCert, caKey), + tctls.WithParent(caCert.Cert, caCert.Key), ) + if err != nil { + return nil, err + } - return cert.Bytes, cert.KeyBytes, err + return &TLSConfig{ + CACert: caCert.Cert, + NodeCert: nodeCert.Bytes, + NodeKey: nodeCert.KeyBytes, + ClientCert: clientCert.Bytes, + ClientKey: clientCert.KeyBytes, + }, nil } From b3ab2e6e59e91d03f20f170b859777f0786da100 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20de=20la=20Pe=C3=B1a?= Date: Wed, 10 Apr 2024 16:28:57 +0200 Subject: [PATCH 10/21] chore: use in redpanda module --- modules/redpanda/redpanda_test.go | 84 +++++++------------------------ 1 file changed, 18 insertions(+), 66 deletions(-) diff --git a/modules/redpanda/redpanda_test.go b/modules/redpanda/redpanda_test.go index cdb264bd56..2cd229845e 100644 --- a/modules/redpanda/redpanda_test.go +++ b/modules/redpanda/redpanda_test.go @@ -22,6 +22,7 @@ import ( "github.com/testcontainers/testcontainers-go" "github.com/testcontainers/testcontainers-go/modules/redpanda" "github.com/testcontainers/testcontainers-go/network" + tctls "github.com/testcontainers/testcontainers-go/tls" ) func TestRedpanda(t *testing.T) { @@ -346,12 +347,13 @@ func TestRedpandaProduceWithAutoCreateTopics(t *testing.T) { } func TestRedpandaWithTLS(t *testing.T) { - cert, err := tls.X509KeyPair(localhostCert, localhostKey) - require.NoError(t, err, "failed to load key pair") + tmp := t.TempDir() + cert, err := tctls.GenerateCert(tctls.WithHost("localhost"), tctls.WithSaveToFile(tmp)) + require.NoError(t, err, "failed to generate cert") ctx := context.Background() - container, err := redpanda.RunContainer(ctx, redpanda.WithTLS(localhostCert, localhostKey)) + container, err := redpanda.RunContainer(ctx, redpanda.WithTLS(cert.Bytes, cert.KeyBytes)) require.NoError(t, err) t.Cleanup(func() { @@ -361,10 +363,13 @@ func TestRedpandaWithTLS(t *testing.T) { }) caCertPool := x509.NewCertPool() - caCertPool.AppendCertsFromPEM(localhostCert) + caCertPool.AddCert(cert.Cert) + + tlsCert, err := tls.X509KeyPair(cert.Bytes, cert.KeyBytes) + require.NoError(t, err) tlsConfig := &tls.Config{ - Certificates: []tls.Certificate{cert}, + Certificates: []tls.Certificate{tlsCert}, RootCAs: caCertPool, } @@ -415,13 +420,13 @@ func TestRedpandaWithTLS(t *testing.T) { } func TestRedpandaWithTLSAndSASL(t *testing.T) { - cert, err := tls.X509KeyPair(localhostCert, localhostKey) - require.NoError(t, err, "failed to load key pair") + cert, err := tctls.GenerateCert(tctls.WithHost("localhost")) + require.NoError(t, err, "failed to generate cert") ctx := context.Background() container, err := redpanda.RunContainer(ctx, - redpanda.WithTLS(localhostCert, localhostKey), + redpanda.WithTLS(cert.Bytes, cert.KeyBytes), redpanda.WithEnableSASL(), redpanda.WithEnableKafkaAuthorization(), redpanda.WithNewServiceAccount("superuser-1", "test"), @@ -436,10 +441,13 @@ func TestRedpandaWithTLSAndSASL(t *testing.T) { }) caCertPool := x509.NewCertPool() - caCertPool.AppendCertsFromPEM(localhostCert) + caCertPool.AddCert(cert.Cert) + + tlsCert, err := tls.X509KeyPair(cert.Bytes, cert.KeyBytes) + require.NoError(t, err) tlsConfig := &tls.Config{ - Certificates: []tls.Certificate{cert}, + Certificates: []tls.Certificate{tlsCert}, RootCAs: caCertPool, } @@ -573,59 +581,3 @@ func TestRedpandaListener_NoNetwork(t *testing.T) { require.Contains(t, err.Error(), "container must be attached to at least one network") } - -// localhostCert is a PEM-encoded TLS cert with SAN IPs -// generated from src/crypto/tls: -// go run generate_cert.go --rsa-bits 2048 --host 127.0.0.1,::1,localhost --ca --start-date "Jan 1 00:00:00 1970" --duration=1000000h -var localhostCert = []byte(`-----BEGIN CERTIFICATE----- -MIIDODCCAiCgAwIBAgIRAKMykg5qJSCb4L3WtcZznSQwDQYJKoZIhvcNAQELBQAw -EjEQMA4GA1UEChMHQWNtZSBDbzAgFw03MDAxMDEwMDAwMDBaGA8yMDg0MDEyOTE2 -MDAwMFowEjEQMA4GA1UEChMHQWNtZSBDbzCCASIwDQYJKoZIhvcNAQEBBQADggEP -ADCCAQoCggEBAPYcLIhqCsrmqvsY1gWqI1jx3Ytn5Qjfvlg3BPD/YeD4UVouBhgQ -NIIERFCmDUzu52pXYZeCouBIVDWqZKixQf3PyBzAqbFvX0pTsZrOnvjuoahzjEcl -x+CfkIp58mVaV/8v9TyBYCXNuHlI7Pndu/3U5d6npSg8+dTkwW3VZzZyHpsDW+a4 -ByW02NI58LoHzQPMRg9MFToL1qNQy4PFyADf2N/3/SYOkrbSrXA0jYqXE8yvQGYe -LWcoQ+4YkurSS1TgSNEKxrzGj8w4xRjEjRNsLVNWd8uxZkHwv6LXOn4s39ix3jN4 -7OJJHA8fJAWxAP4ThrpM1j5J+Rq1PD380u8CAwEAAaOBhjCBgzAOBgNVHQ8BAf8E -BAMCAqQwEwYDVR0lBAwwCgYIKwYBBQUHAwEwDwYDVR0TAQH/BAUwAwEB/zAdBgNV -HQ4EFgQU8gMBt2leRAnGgCQ6pgIYPHY35GAwLAYDVR0RBCUwI4IJbG9jYWxob3N0 -hwR/AAABhxAAAAAAAAAAAAAAAAAAAAABMA0GCSqGSIb3DQEBCwUAA4IBAQA5F6aw -6JJMsnCjxRGYXb252zqjxOxweawZ2je4UAGSsF27Phm1Bx6/2mzPpgIB0I7xNBFL -ljtqBG/FpH6qWpkkegljL8Z5soXiye/4r1G+V6hadm32/OLQCS//dyq7W1a2uVlS -KdFjoNqRW2PacVQLjnTbP2SJV5CnrJgCsSMXVoNnKdj5gr5ltNNAt9TAJ85iFa5d -rJla/XghtqEOzYtigKPF7EVqRRl4RmPu30hxwDZMT60ptFolfCEeXpDra5uonJMv -ElEbzK8ZzXmvWCj94RjPkGKZs8+SDM2qfKPk5ZW2xJxwqS3tkEkZlj1L+b7zYOlt -aJ65OWCXHLecrgdl ------END CERTIFICATE-----`) - -// localhostKey is the private key for localhostCert. -var localhostKey = []byte(testingKey(`-----BEGIN TESTING KEY----- -MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQD2HCyIagrK5qr7 -GNYFqiNY8d2LZ+UI375YNwTw/2Hg+FFaLgYYEDSCBERQpg1M7udqV2GXgqLgSFQ1 -qmSosUH9z8gcwKmxb19KU7Gazp747qGoc4xHJcfgn5CKefJlWlf/L/U8gWAlzbh5 -SOz53bv91OXep6UoPPnU5MFt1Wc2ch6bA1vmuAcltNjSOfC6B80DzEYPTBU6C9aj -UMuDxcgA39jf9/0mDpK20q1wNI2KlxPMr0BmHi1nKEPuGJLq0ktU4EjRCsa8xo/M -OMUYxI0TbC1TVnfLsWZB8L+i1zp+LN/Ysd4zeOziSRwPHyQFsQD+E4a6TNY+Sfka -tTw9/NLvAgMBAAECggEBALKxAiSJ2gw4Lyzhe4PhZIjQE+uEI+etjKbAS/YvdwHB -SlAP2pzeJ0G/l1p3NnEFhUDQ8SrwzxHJclsEvNE+4otGsiUuPgd2tdlhqzKbkxFr -MjT8sH14EQgm0uu4Xyb30ayXRZgI16abF7X4HRfOxxAl5EElt+TfYQYSkd8Nc0Mz -bD7g0riSdOKVhNIkUTT1U7x8ClIgff6vbWztOVP4hGezqEKpO/8+JBkg2GLeH3lC -PyuHEb33Foxg7SX35M1a89EKC2p4ER6/nfg6wGYyIsn42gBk1JgQdg23x7c/0WOu -vcw1unNP2kCbnsCeZ6KPRRGXEjbpTqOTzAUOekOeOgECgYEA9/jwK2wrX2J3kJN7 -v6kmxazigXHCa7XmFMgTfdqiQfXfjdi/4u+4KAX04jWra3ZH4KT98ztPOGjb6KhM -hfMldsxON8S9IQPcbDyj+5R77KU4BG/JQBEOX1uzS9KjMVG5e9ZUpG5UnSoSOgyM -oN3DZto7C5ULO2U2MT8JaoGb53cCgYEA/hPNMsCXFairxKy0BCsvJFan93+GIdwM -YoAGLc4Oj67ES8TYC4h9Im5i81JYOjpY4aZeKdj8S+ozmbqqa/iJiAfOr37xOMuX -AQA2T8uhPXXNXA5s6T3LaIXtzL0NmRRZCtuyEGdCidIXub7Bz8LrfsMc+s/jv57f -4IPmW12PPkkCgYBpEdDqBT5nfzh8SRGhR1IHZlbfVE12CDACVDh2FkK0QjNETjgY -N0zHoKZ/hxAoS4jvNdnoyxOpKj0r2sv54enY6X6nALTGnXUzY4p0GhlcTzFqJ9eV -TuTRIPDaytidGCzIvStGNP2jTmVEtXaM3wphtUxZfwCwXRVWToh12Y8uxwKBgA1a -FQp5vHbS6lPnj344lr2eIC2NcgsNeUkj2S9HCNTcJkylB4Vzor/QdTq8NQ66Sjlx -eLlSQc/retK1UIdkBDY10tK+JQcLC+Btlm0TEmIccrJHv8lyCeJwR1LfDHvi6dr8 -OJtMEd8UP1Lvh1fXsnBy6G71xc4oFzPBOrXKcOChAoGACOgyYe47ZizScsUGjCC7 -xARTEolZhtqHKVd5s9oi95P0r7A1gcNx/9YW0rCT2ZD8BD9H++HTE2L+mh3R9zDn -jwDeW7wVZec+oyGdc9L+B1xU25O+88zNLxlRAX8nXJbHdgL83UclmC51GbXejloP -D4ZNvyXf/6E27Ibu6v2p/vs= ------END TESTING KEY-----`)) - -func testingKey(s string) string { return strings.ReplaceAll(s, "TESTING KEY", "PRIVATE KEY") } From 5bbe9b69a1bfbb743253906dae5eccae4259f43f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20de=20la=20Pe=C3=B1a?= Date: Wed, 10 Apr 2024 16:31:06 +0200 Subject: [PATCH 11/21] chore: lint --- tls/generate.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tls/generate.go b/tls/generate.go index b70980bf80..c8052d3f93 100644 --- a/tls/generate.go +++ b/tls/generate.go @@ -230,14 +230,14 @@ func GenerateCert(opts ...CertOpt) (*Certificate, error) { id := uuid.NewString() certPath := filepath.Join(req.SaveTo, "cert-"+id+".pem") - if err := os.WriteFile(certPath, certificate.Bytes, 0644); err != nil { + if err := os.WriteFile(certPath, certificate.Bytes, 0o644); err != nil { return nil, err } certificate.CertPath = certPath if certificate.KeyBytes != nil { keyPath := filepath.Join(req.SaveTo, "key-"+id+".pem") - if err := os.WriteFile(keyPath, certificate.KeyBytes, 0644); err != nil { + if err := os.WriteFile(keyPath, certificate.KeyBytes, 0o644); err != nil { return nil, err } certificate.KeyPath = keyPath From 443528ccf75c595f3bab727a96a39ef310e53f5e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20de=20la=20Pe=C3=B1a?= Date: Wed, 10 Apr 2024 17:21:15 +0200 Subject: [PATCH 12/21] chore: set validFrom internally --- modules/cockroachdb/certs.go | 3 --- tls/generate.go | 20 ++++++++++---------- 2 files changed, 10 insertions(+), 13 deletions(-) diff --git a/modules/cockroachdb/certs.go b/modules/cockroachdb/certs.go index f62486de52..1515a3a150 100644 --- a/modules/cockroachdb/certs.go +++ b/modules/cockroachdb/certs.go @@ -22,7 +22,6 @@ func NewTLSConfig() (*TLSConfig, error) { tctls.WithHost("localhost"), tctls.WithSubjectCommonName("Cockroach Test CA"), tctls.AsCA(), - tctls.WithValidFrom(time.Now().Add(-time.Hour)), tctls.WithValidFor(time.Hour), ) if err != nil { @@ -34,7 +33,6 @@ func NewTLSConfig() (*TLSConfig, error) { tctls.WithSubjectCommonName("node"), tctls.AsCA(), tctls.WithIPAddresses(net.IPv4(127, 0, 0, 1), net.IPv6loopback), - tctls.WithValidFrom(time.Now().Add(-time.Hour)), tctls.WithValidFor(time.Hour), tctls.AsPem(), tctls.WithParent(caCert.Cert, caCert.Key), @@ -47,7 +45,6 @@ func NewTLSConfig() (*TLSConfig, error) { tctls.WithHost("localhost"), tctls.WithSubjectCommonName(defaultUser), tctls.AsCA(), - tctls.WithValidFrom(time.Now().Add(-time.Hour)), tctls.WithValidFor(time.Hour), tctls.AsPem(), tctls.WithParent(caCert.Cert, caCert.Key), diff --git a/tls/generate.go b/tls/generate.go index c8052d3f93..17e1b4d3ce 100644 --- a/tls/generate.go +++ b/tls/generate.go @@ -32,7 +32,6 @@ type Certificate struct { type certRequest struct { SubjectCommonName string // CommonName is the subject name of the certificate Host string // Comma-separated hostnames and IPs to generate a certificate for - ValidFrom time.Time // Creation date formatted as Jan 1 15:04:05 2011 ValidFor time.Duration // Duration that certificate is valid for IsCA bool // whether this cert should be its own Certificate Authority IPAddreses []net.IP // IP addresses to include in the Subject Alternative Name @@ -63,12 +62,6 @@ func WithHost(host string) CertOpt { } } -func WithValidFrom(validFrom time.Time) CertOpt { - return func(r *certRequest) { - r.ValidFrom = validFrom - } -} - func WithValidFor(validFor time.Duration) CertOpt { return func(r *certRequest) { r.ValidFor = validFor @@ -129,7 +122,6 @@ func WithSaveToFile(dir string) CertOpt { // to avoid nil pointers. func newCertRequest() certRequest { return certRequest{ - ValidFrom: time.Now().Add(-time.Hour), ValidFor: 365 * 24 * time.Hour, IPAddreses: make([]net.IP, 0), } @@ -139,6 +131,11 @@ func newCertRequest() certRequest { // a struct containing the certificate and private key, as well as the raw bytes // of the certificate. In the case the PEM option is set, the raw bytes will be // PEM-encoded, including the bytes of the private key in the KeyBytes field. +// Considerations for the generated certificate are as follows: +// - will be valid for the duration set in the ValidFor option, starting from 1 minute ago. Else, it will be valid for 1 year. +// - will be signed by the parent certificate if the WithParent option is set. Else, it will be self-signed. +// - will be saved to the directory set in the WithSaveToFile option. Else, it will not be saved to disk. +// - will be its own Certificate Authority if the AsCA option is set. Else, it will not be a CA. func GenerateCert(opts ...CertOpt) (*Certificate, error) { req := newCertRequest() @@ -155,13 +152,16 @@ func GenerateCert(opts ...CertOpt) (*Certificate, error) { keyUsage |= x509.KeyUsageCertSign } + // certificate is not valid before 1 minute ago + notBefore := time.Now().Add(-time.Minute) + template := x509.Certificate{ SerialNumber: big.NewInt(2019), Subject: pkix.Name{ CommonName: req.SubjectCommonName, }, - NotBefore: req.ValidFrom, - NotAfter: req.ValidFrom.Add(req.ValidFor), + NotBefore: notBefore, + NotAfter: notBefore.Add(req.ValidFor), KeyUsage: keyUsage, BasicConstraintsValid: true, IsCA: req.IsCA, From 670892bc26b4746e75430e9927aaa4341fdfb24f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20de=20la=20Pe=C3=B1a?= Date: Wed, 10 Apr 2024 17:51:29 +0200 Subject: [PATCH 13/21] fix: properly use the new API in redpanda --- modules/redpanda/redpanda_test.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/modules/redpanda/redpanda_test.go b/modules/redpanda/redpanda_test.go index 2cd229845e..95954de256 100644 --- a/modules/redpanda/redpanda_test.go +++ b/modules/redpanda/redpanda_test.go @@ -420,7 +420,9 @@ func TestRedpandaWithTLS(t *testing.T) { } func TestRedpandaWithTLSAndSASL(t *testing.T) { - cert, err := tctls.GenerateCert(tctls.WithHost("localhost")) + tmp := t.TempDir() + + cert, err := tctls.GenerateCert(tctls.WithHost("localhost"), tctls.WithSaveToFile(tmp)) require.NoError(t, err, "failed to generate cert") ctx := context.Background() From 25b7bd1c8a8a5927d858494063fe721207173cd0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20de=20la=20Pe=C3=B1a?= Date: Thu, 11 Apr 2024 00:17:08 +0200 Subject: [PATCH 14/21] docs: document the TLS helpers --- docs/features/tls.md | 44 +++++++++++++++++++++++ mkdocs.yml | 1 + tls/examples_test.go | 85 ++++++++++++++++++++++++++++++++++++++++++++ tls/generate.go | 3 ++ 4 files changed, 133 insertions(+) create mode 100644 docs/features/tls.md create mode 100644 tls/examples_test.go diff --git a/docs/features/tls.md b/docs/features/tls.md new file mode 100644 index 0000000000..84bb6e7974 --- /dev/null +++ b/docs/features/tls.md @@ -0,0 +1,44 @@ +# TLS certificates + +_Testcontainers for Go_ provides a way to interact with services that require TLS certificates. +You can create one or more on-the-fly certificates in order to communicate with your services. + +To create a certificate, you can use the `tctls.Certificate` struct, where the `tctls` namespace is imported from the `github.com/testcontainers/testcontainers-go/tls` package, to avoid conflicts with the `tls` package from the Go standard library. + +The `tctls.Certificate` struct has the following fields: + + +[Certificate struct](../../tls/generate.go) inside_block:testcontainersTLSCertificate + + +You can generate a certificate by calling the `tctls.GenerateCert` function. This function receives a variadic argument of functional options that allow you to customize the certificate: + +- `tctls.WithSubjectCommonName`: sets the subject's common name of the certificate. +- `tctls.WithHost`: sets the hostnames that the certificate will be valid for. In the case the passed string contains comma-separated values, +it will be split into multiple hostnames and IPs. Each hostname and IP will be trimmed of whitespace, and if the value is an IP, +it will be added to the IPAddresses field of the certificate, after the ones passed with the WithIPAddresses option. +Otherwise, it will be added to the DNSNames field. +- `tctls.WithValidFor`: sets the duration that the certificate will be valid for. By default, the certificate is valid for 365 days. +- `tctls.AsCA`: sets the certificate as a Certificate Authority (CA). This option is disabled by default. +When passed, the KeyUsage field of the certificate will append the x509.KeyUsageCertSign usage. +- `tctls.WithParent`: sets the parent certificate of the certificate. This option is disabled by default. +When passed, the parent certificate will be used to sign the generated certificate, +and the issuer of the certificate will be set to the common name of the parent certificate. +- `tctls.AsPem`: sets the certificate to be returned as PEM bytes. It will include the private key in the `KeyBytes` field of the Certificate struct. +- `tctls.WithIPAddresses`: sets the IP addresses that the certificate will be valid for. The IPs passed with this option will be added +first to the IPAddresses field of the certificate: those coming from the `WithHost` option will be added after them. +- `tctls.WithSaveToFile`: sets the parent directory where the certificate and its private key will be saved. Both the certificate and its private key will be saved in separate files, using an random UUID as part of the filename. E.g., `cert-.pem` and `key-.pem`. + +!!! note + If the `WithSaveToFile` option is passed, it will automatically set the `AsPem` option, as we need to the private key bytes too. + +### Examples + +In the following example we are going to start an HTTP server with a self-signed certificate. +It exposes one single handler that will return a simple message when accessed. +The example will also create a client that will connect to the server using the generated certificate, +demonstrating how to use the generated certificate to communicate with a service. + + +[Use a certificate](../../tls/examples_test.go) inside_block:ExampleGenerateCert + diff --git a/mkdocs.yml b/mkdocs.yml index 8239016533..dcfc17e496 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -45,6 +45,7 @@ nav: - features/files_and_mounts.md - features/creating_networks.md - features/networking.md + - features/tls.md - features/garbage_collector.md - features/build_from_dockerfile.md - features/docker_auth.md diff --git a/tls/examples_test.go b/tls/examples_test.go new file mode 100644 index 0000000000..1da5db09bf --- /dev/null +++ b/tls/examples_test.go @@ -0,0 +1,85 @@ +package tls_test + +import ( + "crypto/tls" + "crypto/x509" + "fmt" + "io" + "log" + "net/http" + "os" + + tctls "github.com/testcontainers/testcontainers-go/tls" +) + +func ExampleGenerateCert() { + tmp := os.TempDir() + certsDir := tmp + "/certs" + defer os.RemoveAll(certsDir) + + if err := os.MkdirAll(certsDir, 0755); err != nil { + log.Fatal(err) + } + + // Generate a certificate for localhost and save it to disk. + // There is no need to pass the AsPem option: the SaveToFile option will automatically save the certificate as PEM. + caCert, err := tctls.GenerateCert(tctls.WithHost("localhost"), tctls.WithSaveToFile(certsDir)) + if err != nil { + log.Fatal(err) + } + + cert, err := tctls.GenerateCert(tctls.WithHost("localhost"), tctls.WithSaveToFile(certsDir), tctls.WithParent(caCert.Cert, caCert.Key)) + if err != nil { + log.Fatal(err) + } + + // create an http server that uses the generated certificate + // and private key to serve requests over HTTPS + + server := &http.Server{ + Addr: ":8443", + } + + server.Handler = http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { + w.Header().Set("Content-Type", "text/plain") + w.Write([]byte("TLS works!\n")) + }) + + go func() { + _ = server.ListenAndServeTLS(cert.CertPath, cert.KeyPath) + }() + defer server.Close() + + // perform an HTTP request to the server, using the generated certificate + + caCertPool := x509.NewCertPool() + caCertPool.AddCert(cert.Cert) + + tlsConfig := &tls.Config{ + RootCAs: caCertPool, + } + + tr := &http.Transport{ + TLSClientConfig: tlsConfig, + } + + const url = "https://localhost:8443/hello" + + client := &http.Client{Transport: tr} + resp, err := client.Get(url) + if err != nil { + log.Fatalf("Failed to get response: %v", err) + } + defer resp.Body.Close() + + body, err := io.ReadAll(resp.Body) + if err != nil { + log.Fatalf("Failed to read response body: %v", err) + } + + fmt.Println(string(body)) + + // Output: + // TLS works! + +} diff --git a/tls/generate.go b/tls/generate.go index 17e1b4d3ce..146a3a0211 100644 --- a/tls/generate.go +++ b/tls/generate.go @@ -20,6 +20,7 @@ import ( // Certificate represents a certificate and private key pair. It's a wrapper // around the x509.Certificate and rsa.PrivateKey types, and includes the raw // bytes of the certificate and private key. +// testcontainersTLSCertificate { type Certificate struct { Cert *x509.Certificate Bytes []byte @@ -29,6 +30,8 @@ type Certificate struct { KeyPath string } +// } + type certRequest struct { SubjectCommonName string // CommonName is the subject name of the certificate Host string // Comma-separated hostnames and IPs to generate a certificate for From 4a2cdec9b8fb6838e27b23ada84d2b4c2584eb79 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20de=20la=20Pe=C3=B1a?= Date: Thu, 11 Apr 2024 10:11:32 +0200 Subject: [PATCH 15/21] chore: simplify WithParent to accept the struct directly --- modules/cockroachdb/certs.go | 4 +-- modules/rabbitmq/examples_test.go | 2 +- modules/rabbitmq/rabbitmq_test.go | 2 +- tls/examples_test.go | 2 +- tls/generate.go | 24 +++++++++-------- tls/generate_test.go | 45 +++++++------------------------ 6 files changed, 27 insertions(+), 52 deletions(-) diff --git a/modules/cockroachdb/certs.go b/modules/cockroachdb/certs.go index 1515a3a150..1fed523c08 100644 --- a/modules/cockroachdb/certs.go +++ b/modules/cockroachdb/certs.go @@ -35,7 +35,7 @@ func NewTLSConfig() (*TLSConfig, error) { tctls.WithIPAddresses(net.IPv4(127, 0, 0, 1), net.IPv6loopback), tctls.WithValidFor(time.Hour), tctls.AsPem(), - tctls.WithParent(caCert.Cert, caCert.Key), + tctls.WithParent(caCert), ) if err != nil { return nil, err @@ -47,7 +47,7 @@ func NewTLSConfig() (*TLSConfig, error) { tctls.AsCA(), tctls.WithValidFor(time.Hour), tctls.AsPem(), - tctls.WithParent(caCert.Cert, caCert.Key), + tctls.WithParent(caCert), ) if err != nil { return nil, err diff --git a/modules/rabbitmq/examples_test.go b/modules/rabbitmq/examples_test.go index a589990425..fb5b8d78ba 100644 --- a/modules/rabbitmq/examples_test.go +++ b/modules/rabbitmq/examples_test.go @@ -104,7 +104,7 @@ func ExampleRunContainer_withSSL() { log.Fatalf("failed to generate CA certificate: %s", err) } - cert, err := tctls.GenerateCert(tctls.WithHost("localhost"), tctls.WithParent(caCert.Cert, caCert.Key), tctls.WithSaveToFile(certDirs)) + cert, err := tctls.GenerateCert(tctls.WithHost("localhost"), tctls.WithParent(caCert), tctls.WithSaveToFile(certDirs)) if err != nil { log.Fatalf("failed to generate certificate: %s", err) } diff --git a/modules/rabbitmq/rabbitmq_test.go b/modules/rabbitmq/rabbitmq_test.go index fcd4a96b82..d7be42f25e 100644 --- a/modules/rabbitmq/rabbitmq_test.go +++ b/modules/rabbitmq/rabbitmq_test.go @@ -56,7 +56,7 @@ func TestRunContainer_connectUsingAmqps(t *testing.T) { t.Fatalf("failed to generate CA certificate: %s", err) } - cert, err := tctls.GenerateCert(tctls.WithHost("localhost"), tctls.WithParent(caCert.Cert, caCert.Key), tctls.WithSaveToFile(tmpDir)) + cert, err := tctls.GenerateCert(tctls.WithHost("localhost"), tctls.WithParent(caCert), tctls.WithSaveToFile(tmpDir)) if err != nil { t.Fatalf("failed to generate certificate: %s", err) } diff --git a/tls/examples_test.go b/tls/examples_test.go index 1da5db09bf..bed65e0c16 100644 --- a/tls/examples_test.go +++ b/tls/examples_test.go @@ -28,7 +28,7 @@ func ExampleGenerateCert() { log.Fatal(err) } - cert, err := tctls.GenerateCert(tctls.WithHost("localhost"), tctls.WithSaveToFile(certsDir), tctls.WithParent(caCert.Cert, caCert.Key)) + cert, err := tctls.GenerateCert(tctls.WithHost("localhost"), tctls.WithSaveToFile(certsDir), tctls.WithParent(caCert)) if err != nil { log.Fatal(err) } diff --git a/tls/generate.go b/tls/generate.go index 146a3a0211..a55009897a 100644 --- a/tls/generate.go +++ b/tls/generate.go @@ -84,10 +84,10 @@ func AsCA() CertOpt { // It's used to sign the certificate with the parent certificate. // At the moment the parent is set, the issuer of the certificate will be // set to the common name of the parent certificate. -func WithParent(parent *x509.Certificate, priv any) CertOpt { +func WithParent(parent Certificate) CertOpt { return func(r *certRequest) { - r.Parent = parent - r.Priv = priv + r.Parent = parent.Cert + r.Priv = parent.Key } } @@ -139,7 +139,9 @@ func newCertRequest() certRequest { // - will be signed by the parent certificate if the WithParent option is set. Else, it will be self-signed. // - will be saved to the directory set in the WithSaveToFile option. Else, it will not be saved to disk. // - will be its own Certificate Authority if the AsCA option is set. Else, it will not be a CA. -func GenerateCert(opts ...CertOpt) (*Certificate, error) { +func GenerateCert(opts ...CertOpt) (Certificate, error) { + var certificate Certificate + req := newCertRequest() for _, opt := range opts { @@ -147,7 +149,7 @@ func GenerateCert(opts ...CertOpt) (*Certificate, error) { } if len(req.Host) == 0 { - return nil, fmt.Errorf("missing required host") + return certificate, fmt.Errorf("missing required host") } keyUsage := x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature @@ -194,7 +196,7 @@ func GenerateCert(opts ...CertOpt) (*Certificate, error) { pk, err := rsa.GenerateKey(rand.Reader, 2048) if err != nil { - return nil, err + return certificate, err } if req.Priv == nil { @@ -204,15 +206,15 @@ func GenerateCert(opts ...CertOpt) (*Certificate, error) { certBytes, err := x509.CreateCertificate(rand.Reader, &template, req.Parent, pk.Public(), req.Priv) if err != nil { - return nil, err + return certificate, err } cert, err := x509.ParseCertificate(certBytes) if err != nil { - return nil, err + return certificate, err } - certificate := &Certificate{ + certificate = Certificate{ Cert: cert, Key: pk, Bytes: certBytes, @@ -234,14 +236,14 @@ func GenerateCert(opts ...CertOpt) (*Certificate, error) { certPath := filepath.Join(req.SaveTo, "cert-"+id+".pem") if err := os.WriteFile(certPath, certificate.Bytes, 0o644); err != nil { - return nil, err + return certificate, err } certificate.CertPath = certPath if certificate.KeyBytes != nil { keyPath := filepath.Join(req.SaveTo, "key-"+id+".pem") if err := os.WriteFile(keyPath, certificate.KeyBytes, 0o644); err != nil { - return nil, err + return certificate, err } certificate.KeyPath = keyPath } diff --git a/tls/generate_test.go b/tls/generate_test.go index c31a5f0789..fcdf90e9bc 100644 --- a/tls/generate_test.go +++ b/tls/generate_test.go @@ -4,6 +4,7 @@ import ( stdlibtls "crypto/tls" "net" "os" + "reflect" "testing" "github.com/testcontainers/testcontainers-go/tls" @@ -11,10 +12,14 @@ import ( func TestGenerate(t *testing.T) { t.Run("No host returns error", func(t *testing.T) { - _, err := tls.GenerateCert() + cert, err := tls.GenerateCert() if err == nil { t.Fatal("expected error, got nil") } + + if !reflect.ValueOf(cert).IsZero() { + t.Fatal("expected cert to be the zero value, got", cert) + } }) t.Run("With host", func(tt *testing.T) { @@ -23,8 +28,8 @@ func TestGenerate(t *testing.T) { tt.Fatal(err) } - if cert == nil { - tt.Fatal("expected cert, got nil") + if reflect.ValueOf(cert).IsZero() { + t.Fatal("expected cert not to be the zero value, got", cert) } if cert.Key == nil { @@ -44,10 +49,6 @@ func TestGenerate(t *testing.T) { t.Fatal(err) } - if cert == nil { - t.Fatal("expected cert, got nil") - } - if cert.Key == nil { t.Fatal("expected key, got nil") } @@ -70,10 +71,6 @@ func TestGenerate(t *testing.T) { t.Fatal(err) } - if cert == nil { - t.Fatal("expected cert, got nil") - } - if cert.Key == nil { t.Fatal("expected key, got nil") } @@ -100,10 +97,6 @@ func TestGenerate(t *testing.T) { t.Fatal(err) } - if cert == nil { - t.Fatal("expected cert, got nil") - } - if cert.Cert == nil { t.Fatal("expected cert, got nil") } @@ -125,10 +118,6 @@ func TestGenerate(t *testing.T) { t.Fatal(err) } - if cert == nil { - t.Fatal("expected cert, got nil") - } - // PEM format adds the key bytes to the cert struct if cert.Bytes == nil { t.Fatal("expected bytes, got nil") @@ -144,10 +133,6 @@ func TestGenerate(t *testing.T) { t.Fatal(err) } - if cert == nil { - t.Fatal("expected cert, got nil") - } - if cert.Cert == nil { t.Fatal("expected cert, got nil") } @@ -164,15 +149,11 @@ func TestGenerate(t *testing.T) { t.Fatal(err) } - cert, err := tls.GenerateCert(tls.WithHost("localhost"), tls.WithParent(parent.Cert, parent.Key)) + cert, err := tls.GenerateCert(tls.WithHost("localhost"), tls.WithParent(parent)) if err != nil { t.Fatal(err) } - if cert == nil { - t.Fatal("expected cert, got nil") - } - if cert.Cert == nil { t.Fatal("expected cert, got nil") } @@ -193,10 +174,6 @@ func TestGenerate(t *testing.T) { t.Fatal(err) } - if cert == nil { - t.Fatal("expected cert, got nil") - } - if cert.Cert == nil { t.Fatal("expected cert, got nil") } @@ -220,10 +197,6 @@ func TestGenerate(t *testing.T) { tt.Fatal(err) } - if cert == nil { - tt.Fatal("expected cert, got nil") - } - inMemoryCert, err := stdlibtls.X509KeyPair(cert.Bytes, cert.KeyBytes) if err != nil { tt.Fatal(err) From e095fd9fbbad163cad7563540e31b6cd645f5bd9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20de=20la=20Pe=C3=B1a?= Date: Thu, 11 Apr 2024 17:36:49 +0200 Subject: [PATCH 16/21] chore: use tlscert package instead --- modules/cockroachdb/certs.go | 59 ++++--- modules/cockroachdb/go.mod | 1 + modules/cockroachdb/go.sum | 2 + modules/rabbitmq/examples_test.go | 30 ++-- modules/rabbitmq/go.mod | 1 + modules/rabbitmq/go.sum | 2 + modules/rabbitmq/rabbitmq_test.go | 25 ++- modules/redpanda/go.mod | 1 + modules/redpanda/go.sum | 2 + modules/redpanda/redpanda_test.go | 42 ++--- tls/examples_test.go | 85 ---------- tls/generate.go | 253 ------------------------------ tls/generate_test.go | 227 --------------------------- 13 files changed, 91 insertions(+), 639 deletions(-) delete mode 100644 tls/examples_test.go delete mode 100644 tls/generate.go delete mode 100644 tls/generate_test.go diff --git a/modules/cockroachdb/certs.go b/modules/cockroachdb/certs.go index 1fed523c08..5a8f870a41 100644 --- a/modules/cockroachdb/certs.go +++ b/modules/cockroachdb/certs.go @@ -2,10 +2,11 @@ package cockroachdb import ( "crypto/x509" + "fmt" "net" "time" - tctls "github.com/testcontainers/testcontainers-go/tls" + "github.com/mdelapenya/tlscert" ) type TLSConfig struct { @@ -18,39 +19,37 @@ type TLSConfig struct { // NewTLSConfig creates a new TLSConfig capable of running CockroachDB & connecting over TLS. func NewTLSConfig() (*TLSConfig, error) { - caCert, err := tctls.GenerateCert( - tctls.WithHost("localhost"), - tctls.WithSubjectCommonName("Cockroach Test CA"), - tctls.AsCA(), - tctls.WithValidFor(time.Hour), - ) - if err != nil { - return nil, err + caCert := tlscert.SelfSignedFromRequest(tlscert.Request{ + Name: "ca", + SubjectCommonName: "Cockroach Test CA", + Host: "localhost,127.0.0.1", + IsCA: true, + ValidFor: time.Hour, + }) + if caCert == nil { + return nil, fmt.Errorf("failed to generate CA certificate") } - nodeCert, err := tctls.GenerateCert( - tctls.WithHost("localhost"), // the host will be passed as DNSNames - tctls.WithSubjectCommonName("node"), - tctls.AsCA(), - tctls.WithIPAddresses(net.IPv4(127, 0, 0, 1), net.IPv6loopback), - tctls.WithValidFor(time.Hour), - tctls.AsPem(), - tctls.WithParent(caCert), - ) - if err != nil { - return nil, err + nodeCert := tlscert.SelfSignedFromRequest(tlscert.Request{ + Name: "node", + Host: "localhost,127.0.0.1", + IPAddresses: []net.IP{net.IPv4(127, 0, 0, 1), net.IPv6loopback}, + ValidFor: time.Hour, + Parent: caCert, + }) + if nodeCert == nil { + return nil, fmt.Errorf("failed to generate node certificate") } - clientCert, err := tctls.GenerateCert( - tctls.WithHost("localhost"), - tctls.WithSubjectCommonName(defaultUser), - tctls.AsCA(), - tctls.WithValidFor(time.Hour), - tctls.AsPem(), - tctls.WithParent(caCert), - ) - if err != nil { - return nil, err + clientCert := tlscert.SelfSignedFromRequest(tlscert.Request{ + Name: "client", + SubjectCommonName: defaultUser, + Host: "localhost,127.0.0.1", + ValidFor: time.Hour, + Parent: caCert, + }) + if clientCert == nil { + return nil, fmt.Errorf("failed to generate client certificate") } return &TLSConfig{ diff --git a/modules/cockroachdb/go.mod b/modules/cockroachdb/go.mod index 094a61e8b5..6955bcdf48 100644 --- a/modules/cockroachdb/go.mod +++ b/modules/cockroachdb/go.mod @@ -36,6 +36,7 @@ require ( github.com/kr/text v0.2.0 // indirect github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect github.com/magiconair/properties v1.8.7 // indirect + github.com/mdelapenya/tlscert v0.0.0-20240411153446-76db85d39935 github.com/moby/patternmatcher v0.6.0 // indirect github.com/moby/sys/sequential v0.5.0 // indirect github.com/moby/sys/user v0.1.0 // indirect diff --git a/modules/cockroachdb/go.sum b/modules/cockroachdb/go.sum index 2365978d8b..fdb1187a4d 100644 --- a/modules/cockroachdb/go.sum +++ b/modules/cockroachdb/go.sum @@ -73,6 +73,8 @@ github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I= github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= +github.com/mdelapenya/tlscert v0.0.0-20240411153446-76db85d39935 h1:r+s8k5Jqis9pJqQudjCrsCKRFRMze3yD6j04vfKjY/s= +github.com/mdelapenya/tlscert v0.0.0-20240411153446-76db85d39935/go.mod h1:wrbyM/DwbFCeCeqdPX/8c6hNOqQgbf0rUDErE1uD+64= github.com/moby/patternmatcher v0.6.0 h1:GmP9lR19aU5GqSSFko+5pRqHi+Ohk1O69aFiKkVGiPk= github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc= github.com/moby/sys/sequential v0.5.0 h1:OPvI35Lzn9K04PBbCLW0g4LcFAJgHsvXsRyewg5lXtc= diff --git a/modules/rabbitmq/examples_test.go b/modules/rabbitmq/examples_test.go index fb5b8d78ba..55fcd143da 100644 --- a/modules/rabbitmq/examples_test.go +++ b/modules/rabbitmq/examples_test.go @@ -8,11 +8,11 @@ import ( "os" "strings" + "github.com/mdelapenya/tlscert" amqp "github.com/rabbitmq/amqp091-go" "github.com/testcontainers/testcontainers-go" "github.com/testcontainers/testcontainers-go/modules/rabbitmq" - tctls "github.com/testcontainers/testcontainers-go/tls" ) func ExampleRunContainer() { @@ -98,15 +98,25 @@ func ExampleRunContainer_withSSL() { defer os.RemoveAll(certDirs) // generates the CA certificate and the certificate - // using TestContainers TLS helper functions. - caCert, err := tctls.GenerateCert(tctls.WithHost("localhost"), tctls.AsCA(), tctls.WithSaveToFile(certDirs)) - if err != nil { - log.Fatalf("failed to generate CA certificate: %s", err) - } - - cert, err := tctls.GenerateCert(tctls.WithHost("localhost"), tctls.WithParent(caCert), tctls.WithSaveToFile(certDirs)) - if err != nil { - log.Fatalf("failed to generate certificate: %s", err) + caCert := tlscert.SelfSignedFromRequest(tlscert.Request{ + Name: "ca", + Host: "localhost,127.0.0.1", + IsCA: true, + ParentDir: certDirs, + }) + if caCert == nil { + log.Fatal("failed to generate CA certificate") + } + + cert := tlscert.SelfSignedFromRequest(tlscert.Request{ + Name: "client", + Host: "localhost,127.0.0.1", + IsCA: true, + Parent: caCert, + ParentDir: certDirs, + }) + if cert == nil { + log.Fatal("failed to generate certificate") } sslSettings := rabbitmq.SSLSettings{ diff --git a/modules/rabbitmq/go.mod b/modules/rabbitmq/go.mod index 4cc07b1c85..2de56db0c9 100644 --- a/modules/rabbitmq/go.mod +++ b/modules/rabbitmq/go.mod @@ -30,6 +30,7 @@ require ( github.com/klauspost/compress v1.16.0 // indirect github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect github.com/magiconair/properties v1.8.7 // indirect + github.com/mdelapenya/tlscert v0.0.0-20240411153446-76db85d39935 github.com/moby/patternmatcher v0.6.0 // indirect github.com/moby/sys/sequential v0.5.0 // indirect github.com/moby/sys/user v0.1.0 // indirect diff --git a/modules/rabbitmq/go.sum b/modules/rabbitmq/go.sum index 3199313edc..50cda64793 100644 --- a/modules/rabbitmq/go.sum +++ b/modules/rabbitmq/go.sum @@ -63,6 +63,8 @@ github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I= github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= +github.com/mdelapenya/tlscert v0.0.0-20240411153446-76db85d39935 h1:r+s8k5Jqis9pJqQudjCrsCKRFRMze3yD6j04vfKjY/s= +github.com/mdelapenya/tlscert v0.0.0-20240411153446-76db85d39935/go.mod h1:wrbyM/DwbFCeCeqdPX/8c6hNOqQgbf0rUDErE1uD+64= github.com/moby/patternmatcher v0.6.0 h1:GmP9lR19aU5GqSSFko+5pRqHi+Ohk1O69aFiKkVGiPk= github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc= github.com/moby/sys/sequential v0.5.0 h1:OPvI35Lzn9K04PBbCLW0g4LcFAJgHsvXsRyewg5lXtc= diff --git a/modules/rabbitmq/rabbitmq_test.go b/modules/rabbitmq/rabbitmq_test.go index d7be42f25e..ffb85d9a76 100644 --- a/modules/rabbitmq/rabbitmq_test.go +++ b/modules/rabbitmq/rabbitmq_test.go @@ -10,11 +10,11 @@ import ( "strings" "testing" + "github.com/mdelapenya/tlscert" amqp "github.com/rabbitmq/amqp091-go" "github.com/testcontainers/testcontainers-go" "github.com/testcontainers/testcontainers-go/modules/rabbitmq" - tctls "github.com/testcontainers/testcontainers-go/tls" ) func TestRunContainer_connectUsingAmqp(t *testing.T) { @@ -51,14 +51,25 @@ func TestRunContainer_connectUsingAmqps(t *testing.T) { tmpDir := t.TempDir() - caCert, err := tctls.GenerateCert(tctls.WithHost("localhost"), tctls.AsCA(), tctls.WithSaveToFile(tmpDir)) - if err != nil { - t.Fatalf("failed to generate CA certificate: %s", err) + caCert := tlscert.SelfSignedFromRequest(tlscert.Request{ + Name: "ca", + Host: "localhost,127.0.0.1", + IsCA: true, + ParentDir: tmpDir, + }) + if caCert == nil { + t.Fatal("failed to generate CA certificate") } - cert, err := tctls.GenerateCert(tctls.WithHost("localhost"), tctls.WithParent(caCert), tctls.WithSaveToFile(tmpDir)) - if err != nil { - t.Fatalf("failed to generate certificate: %s", err) + cert := tlscert.SelfSignedFromRequest(tlscert.Request{ + Name: "client", + Host: "localhost,127.0.0.1", + IsCA: true, + Parent: caCert, + ParentDir: tmpDir, + }) + if cert == nil { + t.Fatal("failed to generate certificate") } sslSettings := rabbitmq.SSLSettings{ diff --git a/modules/redpanda/go.mod b/modules/redpanda/go.mod index c78ba08e85..3257221cbb 100644 --- a/modules/redpanda/go.mod +++ b/modules/redpanda/go.mod @@ -35,6 +35,7 @@ require ( github.com/kr/pretty v0.3.1 // indirect github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect github.com/magiconair/properties v1.8.7 // indirect + github.com/mdelapenya/tlscert v0.0.0-20240411153446-76db85d39935 github.com/moby/patternmatcher v0.6.0 // indirect github.com/moby/sys/sequential v0.5.0 // indirect github.com/moby/sys/user v0.1.0 // indirect diff --git a/modules/redpanda/go.sum b/modules/redpanda/go.sum index 6394427cd9..ef0cb9c24c 100644 --- a/modules/redpanda/go.sum +++ b/modules/redpanda/go.sum @@ -65,6 +65,8 @@ github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I= github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= +github.com/mdelapenya/tlscert v0.0.0-20240411153446-76db85d39935 h1:r+s8k5Jqis9pJqQudjCrsCKRFRMze3yD6j04vfKjY/s= +github.com/mdelapenya/tlscert v0.0.0-20240411153446-76db85d39935/go.mod h1:wrbyM/DwbFCeCeqdPX/8c6hNOqQgbf0rUDErE1uD+64= github.com/moby/patternmatcher v0.6.0 h1:GmP9lR19aU5GqSSFko+5pRqHi+Ohk1O69aFiKkVGiPk= github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc= github.com/moby/sys/sequential v0.5.0 h1:OPvI35Lzn9K04PBbCLW0g4LcFAJgHsvXsRyewg5lXtc= diff --git a/modules/redpanda/redpanda_test.go b/modules/redpanda/redpanda_test.go index 95954de256..9d412be1c0 100644 --- a/modules/redpanda/redpanda_test.go +++ b/modules/redpanda/redpanda_test.go @@ -2,8 +2,6 @@ package redpanda_test import ( "context" - "crypto/tls" - "crypto/x509" "fmt" "io" "net/http" @@ -11,6 +9,7 @@ import ( "testing" "time" + "github.com/mdelapenya/tlscert" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/twmb/franz-go/pkg/kadm" @@ -22,7 +21,6 @@ import ( "github.com/testcontainers/testcontainers-go" "github.com/testcontainers/testcontainers-go/modules/redpanda" "github.com/testcontainers/testcontainers-go/network" - tctls "github.com/testcontainers/testcontainers-go/tls" ) func TestRedpanda(t *testing.T) { @@ -348,8 +346,12 @@ func TestRedpandaProduceWithAutoCreateTopics(t *testing.T) { func TestRedpandaWithTLS(t *testing.T) { tmp := t.TempDir() - cert, err := tctls.GenerateCert(tctls.WithHost("localhost"), tctls.WithSaveToFile(tmp)) - require.NoError(t, err, "failed to generate cert") + cert := tlscert.SelfSignedFromRequest(tlscert.Request{ + Name: "client", + Host: "localhost,127.0.0.1", + ParentDir: tmp, + }) + require.NotNil(t, cert, "failed to generate cert") ctx := context.Background() @@ -362,16 +364,7 @@ func TestRedpandaWithTLS(t *testing.T) { } }) - caCertPool := x509.NewCertPool() - caCertPool.AddCert(cert.Cert) - - tlsCert, err := tls.X509KeyPair(cert.Bytes, cert.KeyBytes) - require.NoError(t, err) - - tlsConfig := &tls.Config{ - Certificates: []tls.Certificate{tlsCert}, - RootCAs: caCertPool, - } + tlsConfig := cert.TLSConfig() httpCl := &http.Client{ Timeout: 5 * time.Second, @@ -422,8 +415,12 @@ func TestRedpandaWithTLS(t *testing.T) { func TestRedpandaWithTLSAndSASL(t *testing.T) { tmp := t.TempDir() - cert, err := tctls.GenerateCert(tctls.WithHost("localhost"), tctls.WithSaveToFile(tmp)) - require.NoError(t, err, "failed to generate cert") + cert := tlscert.SelfSignedFromRequest(tlscert.Request{ + Name: "client", + Host: "localhost,127.0.0.1", + ParentDir: tmp, + }) + require.NotNil(t, cert, "failed to generate cert") ctx := context.Background() @@ -442,16 +439,7 @@ func TestRedpandaWithTLSAndSASL(t *testing.T) { } }) - caCertPool := x509.NewCertPool() - caCertPool.AddCert(cert.Cert) - - tlsCert, err := tls.X509KeyPair(cert.Bytes, cert.KeyBytes) - require.NoError(t, err) - - tlsConfig := &tls.Config{ - Certificates: []tls.Certificate{tlsCert}, - RootCAs: caCertPool, - } + tlsConfig := cert.TLSConfig() broker, err := container.KafkaSeedBroker(ctx) require.NoError(t, err) diff --git a/tls/examples_test.go b/tls/examples_test.go deleted file mode 100644 index bed65e0c16..0000000000 --- a/tls/examples_test.go +++ /dev/null @@ -1,85 +0,0 @@ -package tls_test - -import ( - "crypto/tls" - "crypto/x509" - "fmt" - "io" - "log" - "net/http" - "os" - - tctls "github.com/testcontainers/testcontainers-go/tls" -) - -func ExampleGenerateCert() { - tmp := os.TempDir() - certsDir := tmp + "/certs" - defer os.RemoveAll(certsDir) - - if err := os.MkdirAll(certsDir, 0755); err != nil { - log.Fatal(err) - } - - // Generate a certificate for localhost and save it to disk. - // There is no need to pass the AsPem option: the SaveToFile option will automatically save the certificate as PEM. - caCert, err := tctls.GenerateCert(tctls.WithHost("localhost"), tctls.WithSaveToFile(certsDir)) - if err != nil { - log.Fatal(err) - } - - cert, err := tctls.GenerateCert(tctls.WithHost("localhost"), tctls.WithSaveToFile(certsDir), tctls.WithParent(caCert)) - if err != nil { - log.Fatal(err) - } - - // create an http server that uses the generated certificate - // and private key to serve requests over HTTPS - - server := &http.Server{ - Addr: ":8443", - } - - server.Handler = http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { - w.Header().Set("Content-Type", "text/plain") - w.Write([]byte("TLS works!\n")) - }) - - go func() { - _ = server.ListenAndServeTLS(cert.CertPath, cert.KeyPath) - }() - defer server.Close() - - // perform an HTTP request to the server, using the generated certificate - - caCertPool := x509.NewCertPool() - caCertPool.AddCert(cert.Cert) - - tlsConfig := &tls.Config{ - RootCAs: caCertPool, - } - - tr := &http.Transport{ - TLSClientConfig: tlsConfig, - } - - const url = "https://localhost:8443/hello" - - client := &http.Client{Transport: tr} - resp, err := client.Get(url) - if err != nil { - log.Fatalf("Failed to get response: %v", err) - } - defer resp.Body.Close() - - body, err := io.ReadAll(resp.Body) - if err != nil { - log.Fatalf("Failed to read response body: %v", err) - } - - fmt.Println(string(body)) - - // Output: - // TLS works! - -} diff --git a/tls/generate.go b/tls/generate.go deleted file mode 100644 index a55009897a..0000000000 --- a/tls/generate.go +++ /dev/null @@ -1,253 +0,0 @@ -package tls - -import ( - "crypto/rand" - "crypto/rsa" - "crypto/x509" - "crypto/x509/pkix" - "encoding/pem" - "fmt" - "math/big" - "net" - "os" - "path/filepath" - "strings" - "time" - - "github.com/google/uuid" -) - -// Certificate represents a certificate and private key pair. It's a wrapper -// around the x509.Certificate and rsa.PrivateKey types, and includes the raw -// bytes of the certificate and private key. -// testcontainersTLSCertificate { -type Certificate struct { - Cert *x509.Certificate - Bytes []byte - Key *rsa.PrivateKey - KeyBytes []byte - CertPath string - KeyPath string -} - -// } - -type certRequest struct { - SubjectCommonName string // CommonName is the subject name of the certificate - Host string // Comma-separated hostnames and IPs to generate a certificate for - ValidFor time.Duration // Duration that certificate is valid for - IsCA bool // whether this cert should be its own Certificate Authority - IPAddreses []net.IP // IP addresses to include in the Subject Alternative Name - Parent *x509.Certificate // Parent is the parent certificate, if any - Priv any // Priv is the private key of the parent certificate - Pem bool // Whether to return the certificate as PEM bytes - SaveTo string // Parent directory to save the certificate and private key -} - -type CertOpt func(*certRequest) - -func WithSubjectCommonName(commonName string) CertOpt { - return func(r *certRequest) { - r.SubjectCommonName = commonName - } -} - -// WithHost sets the hostnames and IPs to generate a certificate for. -// In the case the passed string contains comma-separated values, -// it will be split into multiple hostnames and IPs. Each hostname and IP -// will be trimmed of whitespace, and if the value is an IP, it will be -// added to the IPAddresses field of the certificate, after the ones -// passed with the WithIPAddresses option. Otherwise, it will be added -// to the DNSNames field. -func WithHost(host string) CertOpt { - return func(r *certRequest) { - r.Host = host - } -} - -func WithValidFor(validFor time.Duration) CertOpt { - return func(r *certRequest) { - r.ValidFor = validFor - } -} - -// AsCA sets the certificate as a Certificate Authority. -// When passed, the KeyUsage field of the certificate -// will append the x509.KeyUsageCertSign usage. -func AsCA() CertOpt { - return func(r *certRequest) { - r.IsCA = true - } -} - -// WithParent sets the parent certificate and private key of the certificate. -// It's used to sign the certificate with the parent certificate. -// At the moment the parent is set, the issuer of the certificate will be -// set to the common name of the parent certificate. -func WithParent(parent Certificate) CertOpt { - return func(r *certRequest) { - r.Parent = parent.Cert - r.Priv = parent.Key - } -} - -// AsPem sets the certificate to be returned as PEM bytes. It will include -// the private key in the KeyBytes field of the Certificate struct. -func AsPem() CertOpt { - return func(r *certRequest) { - r.Pem = true - } -} - -// WithIPAddresses sets the IP addresses of the certificate. They will be -// added first to the IPAddresses field of the certificate: those coming -// from the WithHost option will be added after these. -func WithIPAddresses(ips ...net.IP) CertOpt { - return func(r *certRequest) { - r.IPAddreses = append(r.IPAddreses, ips...) - } -} - -// WithSaveToFile sets the directory to save the certificate and private key. -// For that reason, it will set the AsPem option, as the certificate -// will be saved as PEM bytes, including the private key. -func WithSaveToFile(dir string) CertOpt { - return func(r *certRequest) { - r.SaveTo = dir - - if !r.Pem { - AsPem()(r) - } - } -} - -// newCertRequest returns a new certRequest with default values -// to avoid nil pointers. -func newCertRequest() certRequest { - return certRequest{ - ValidFor: 365 * 24 * time.Hour, - IPAddreses: make([]net.IP, 0), - } -} - -// GenerateCert Generate a self-signed X.509 certificate for a TLS server. Returns -// a struct containing the certificate and private key, as well as the raw bytes -// of the certificate. In the case the PEM option is set, the raw bytes will be -// PEM-encoded, including the bytes of the private key in the KeyBytes field. -// Considerations for the generated certificate are as follows: -// - will be valid for the duration set in the ValidFor option, starting from 1 minute ago. Else, it will be valid for 1 year. -// - will be signed by the parent certificate if the WithParent option is set. Else, it will be self-signed. -// - will be saved to the directory set in the WithSaveToFile option. Else, it will not be saved to disk. -// - will be its own Certificate Authority if the AsCA option is set. Else, it will not be a CA. -func GenerateCert(opts ...CertOpt) (Certificate, error) { - var certificate Certificate - - req := newCertRequest() - - for _, opt := range opts { - opt(&req) - } - - if len(req.Host) == 0 { - return certificate, fmt.Errorf("missing required host") - } - - keyUsage := x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature - if req.IsCA { - keyUsage |= x509.KeyUsageCertSign - } - - // certificate is not valid before 1 minute ago - notBefore := time.Now().Add(-time.Minute) - - template := x509.Certificate{ - SerialNumber: big.NewInt(2019), - Subject: pkix.Name{ - CommonName: req.SubjectCommonName, - }, - NotBefore: notBefore, - NotAfter: notBefore.Add(req.ValidFor), - KeyUsage: keyUsage, - BasicConstraintsValid: true, - IsCA: req.IsCA, - } - - if req.Parent == nil { - // if no parent is provided, use the generated certificate as the parent - req.Parent = &template - } else { - // if a parent is provided, use the parent's common name as the issuer - template.Issuer.CommonName = req.Parent.Subject.CommonName - } - - if len(req.IPAddreses) > 0 { - template.IPAddresses = req.IPAddreses - } - - hosts := strings.Split(req.Host, ",") - for _, h := range hosts { - h = strings.TrimSpace(h) - if ip := net.ParseIP(h); ip != nil { - template.IPAddresses = append(template.IPAddresses, ip) - } else { - template.DNSNames = append(template.DNSNames, h) - } - } - - pk, err := rsa.GenerateKey(rand.Reader, 2048) - if err != nil { - return certificate, err - } - - if req.Priv == nil { - // if no parent private key is provided, use the generated private key - req.Priv = pk - } - - certBytes, err := x509.CreateCertificate(rand.Reader, &template, req.Parent, pk.Public(), req.Priv) - if err != nil { - return certificate, err - } - - cert, err := x509.ParseCertificate(certBytes) - if err != nil { - return certificate, err - } - - certificate = Certificate{ - Cert: cert, - Key: pk, - Bytes: certBytes, - } - - if req.Pem { - certificate.Bytes = pem.EncodeToMemory(&pem.Block{ - Type: "CERTIFICATE", - Bytes: certBytes, - }) - certificate.KeyBytes = pem.EncodeToMemory(&pem.Block{ - Type: "RSA PRIVATE KEY", - Bytes: x509.MarshalPKCS1PrivateKey(pk), - }) - } - - if req.SaveTo != "" { - id := uuid.NewString() - certPath := filepath.Join(req.SaveTo, "cert-"+id+".pem") - - if err := os.WriteFile(certPath, certificate.Bytes, 0o644); err != nil { - return certificate, err - } - certificate.CertPath = certPath - - if certificate.KeyBytes != nil { - keyPath := filepath.Join(req.SaveTo, "key-"+id+".pem") - if err := os.WriteFile(keyPath, certificate.KeyBytes, 0o644); err != nil { - return certificate, err - } - certificate.KeyPath = keyPath - } - } - - return certificate, nil -} diff --git a/tls/generate_test.go b/tls/generate_test.go deleted file mode 100644 index fcdf90e9bc..0000000000 --- a/tls/generate_test.go +++ /dev/null @@ -1,227 +0,0 @@ -package tls_test - -import ( - stdlibtls "crypto/tls" - "net" - "os" - "reflect" - "testing" - - "github.com/testcontainers/testcontainers-go/tls" -) - -func TestGenerate(t *testing.T) { - t.Run("No host returns error", func(t *testing.T) { - cert, err := tls.GenerateCert() - if err == nil { - t.Fatal("expected error, got nil") - } - - if !reflect.ValueOf(cert).IsZero() { - t.Fatal("expected cert to be the zero value, got", cert) - } - }) - - t.Run("With host", func(tt *testing.T) { - cert, err := tls.GenerateCert(tls.WithHost("localhost"), tls.AsPem()) - if err != nil { - tt.Fatal(err) - } - - if reflect.ValueOf(cert).IsZero() { - t.Fatal("expected cert not to be the zero value, got", cert) - } - - if cert.Key == nil { - tt.Fatal("expected key, got nil") - } - - _, err = stdlibtls.X509KeyPair(cert.Bytes, cert.KeyBytes) - if err != nil { - tt.Fatal(err) - } - }) - - t.Run("With multiple hosts", func(t *testing.T) { - ip := "1.2.3.4" - cert, err := tls.GenerateCert(tls.WithHost("localhost, " + ip)) - if err != nil { - t.Fatal(err) - } - - if cert.Key == nil { - t.Fatal("expected key, got nil") - } - - c := cert.Cert - if len(c.IPAddresses) != 1 { - t.Fatal("expected 1 IP address, got", len(c.IPAddresses)) - } - - if c.IPAddresses[0].String() != ip { - t.Fatalf("expected IP address to be %s, got %s\n", ip, c.IPAddresses[0].String()) - } - }) - - t.Run("With multiple hosts and IPs", func(t *testing.T) { - ip := "1.2.3.4" - ips := []net.IP{net.ParseIP("4.5.6.7"), net.ParseIP("8.9.10.11")} - cert, err := tls.GenerateCert(tls.WithHost("localhost, "+ip), tls.WithIPAddresses(ips...)) - if err != nil { - t.Fatal(err) - } - - if cert.Key == nil { - t.Fatal("expected key, got nil") - } - - c := cert.Cert - if len(c.IPAddresses) != 3 { - t.Fatal("expected 3 IP address, got", len(c.IPAddresses)) - } - - for i, ip := range ips { - if c.IPAddresses[i].String() != ip.String() { - t.Fatalf("expected IP address to be %s, got %s\n", ip.String(), c.IPAddresses[i].String()) - } - } - // the IP from the host comes after the IPs from the IPAddresses option - if c.IPAddresses[2].String() != ip { - t.Fatalf("expected IP address to be %s, got %s\n", ip, c.IPAddresses[0].String()) - } - }) - - t.Run("As CA", func(t *testing.T) { - cert, err := tls.GenerateCert(tls.WithHost("localhost"), tls.AsCA()) - if err != nil { - t.Fatal(err) - } - - if cert.Cert == nil { - t.Fatal("expected cert, got nil") - } - if cert.Key == nil { - t.Fatal("expected key, got nil") - } - if cert.Bytes == nil { - t.Fatal("expected bytes, got nil") - } - - if !cert.Cert.IsCA { - t.Fatal("expected cert to be CA, got false") - } - }) - - t.Run("As PEM", func(t *testing.T) { - cert, err := tls.GenerateCert(tls.WithHost("localhost"), tls.AsPem()) - if err != nil { - t.Fatal(err) - } - - // PEM format adds the key bytes to the cert struct - if cert.Bytes == nil { - t.Fatal("expected bytes, got nil") - } - if cert.KeyBytes == nil { - t.Fatal("expected key bytes, got nil") - } - }) - - t.Run("With Subject common name", func(t *testing.T) { - cert, err := tls.GenerateCert(tls.WithHost("localhost"), tls.WithSubjectCommonName("Test")) - if err != nil { - t.Fatal(err) - } - - if cert.Cert == nil { - t.Fatal("expected cert, got nil") - } - - c := cert.Cert - if c.Subject.CommonName != "Test" { - t.Fatal("expected common name to be Test, got", c.Subject.CommonName) - } - }) - - t.Run("With Parent cert", func(t *testing.T) { - parent, err := tls.GenerateCert(tls.WithHost("localhost"), tls.AsCA()) - if err != nil { - t.Fatal(err) - } - - cert, err := tls.GenerateCert(tls.WithHost("localhost"), tls.WithParent(parent)) - if err != nil { - t.Fatal(err) - } - - if cert.Cert == nil { - t.Fatal("expected cert, got nil") - } - if cert.Key == nil { - t.Fatal("expected key, got nil") - } - - c := cert.Cert - if c.Issuer.CommonName != parent.Cert.Subject.CommonName { - t.Fatal("expected issuer to be parent, got", c.Issuer.CommonName) - } - }) - - t.Run("With IP addresses", func(t *testing.T) { - ip := "1.2.3.4" - cert, err := tls.GenerateCert(tls.WithHost("localhost"), tls.WithIPAddresses(net.ParseIP(ip))) - if err != nil { - t.Fatal(err) - } - - if cert.Cert == nil { - t.Fatal("expected cert, got nil") - } - - c := cert.Cert - if len(c.IPAddresses) != 1 { - t.Fatal("expected 1 IP address, got", len(c.IPAddresses)) - } - - if c.IPAddresses[0].String() != ip { - t.Fatalf("expected IP address to be %s, got %s\n", ip, c.IPAddresses[0].String()) - } - }) - - t.Run("Save to file", func(tt *testing.T) { - tmp := tt.TempDir() - - // no need to pass the AsPem option, the SaveToFile option will do that - cert, err := tls.GenerateCert(tls.WithHost("localhost"), tls.WithSaveToFile(tmp)) - if err != nil { - tt.Fatal(err) - } - - inMemoryCert, err := stdlibtls.X509KeyPair(cert.Bytes, cert.KeyBytes) - if err != nil { - tt.Fatal(err) - } - - // check if file existed - certBytes, err := os.ReadFile(cert.CertPath) - if err != nil { - tt.Fatal(err) - } - - certKeyBytes, err := os.ReadFile(cert.KeyPath) - if err != nil { - tt.Fatal(err) - } - - fileCert, err := stdlibtls.X509KeyPair(certBytes, certKeyBytes) - if err != nil { - tt.Fatal(err) - } - - for i, cert := range inMemoryCert.Certificate { - if string(cert) != string(fileCert.Certificate[i]) { - tt.Fatalf("expected certificate to be %s, got %s\n", string(cert), string(fileCert.Certificate[i])) - } - } - }) -} From ea50947fcd51efa6e12ccbbb3a71de3f9444b7d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20de=20la=20Pe=C3=B1a?= Date: Thu, 11 Apr 2024 17:37:58 +0200 Subject: [PATCH 17/21] fix: use non-deprecated API --- modules/rabbitmq/rabbitmq_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/rabbitmq/rabbitmq_test.go b/modules/rabbitmq/rabbitmq_test.go index ffb85d9a76..f1f03f8ba9 100644 --- a/modules/rabbitmq/rabbitmq_test.go +++ b/modules/rabbitmq/rabbitmq_test.go @@ -6,7 +6,7 @@ import ( "crypto/x509" "fmt" "io" - "io/ioutil" + "os" "strings" "testing" @@ -103,7 +103,7 @@ func TestRunContainer_connectUsingAmqps(t *testing.T) { certs := x509.NewCertPool() - pemData, err := ioutil.ReadFile(sslSettings.CACertFile) + pemData, err := os.ReadFile(sslSettings.CACertFile) if err != nil { t.Fatal(err) } From 208f42ec5538ae76bea3d4a0d672e1553bf3db87 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20de=20la=20Pe=C3=B1a?= Date: Thu, 11 Apr 2024 17:40:41 +0200 Subject: [PATCH 18/21] docs: update --- docs/features/tls.md | 32 ++------------------------------ 1 file changed, 2 insertions(+), 30 deletions(-) diff --git a/docs/features/tls.md b/docs/features/tls.md index 84bb6e7974..0c1346e102 100644 --- a/docs/features/tls.md +++ b/docs/features/tls.md @@ -1,36 +1,8 @@ # TLS certificates -_Testcontainers for Go_ provides a way to interact with services that require TLS certificates. -You can create one or more on-the-fly certificates in order to communicate with your services. +Interacting with services that require TLS certificates is a common issue when working with containers. You can create one or more on-the-fly certificates in order to communicate with your services. -To create a certificate, you can use the `tctls.Certificate` struct, where the `tctls` namespace is imported from the `github.com/testcontainers/testcontainers-go/tls` package, to avoid conflicts with the `tls` package from the Go standard library. - -The `tctls.Certificate` struct has the following fields: - - -[Certificate struct](../../tls/generate.go) inside_block:testcontainersTLSCertificate - - -You can generate a certificate by calling the `tctls.GenerateCert` function. This function receives a variadic argument of functional options that allow you to customize the certificate: - -- `tctls.WithSubjectCommonName`: sets the subject's common name of the certificate. -- `tctls.WithHost`: sets the hostnames that the certificate will be valid for. In the case the passed string contains comma-separated values, -it will be split into multiple hostnames and IPs. Each hostname and IP will be trimmed of whitespace, and if the value is an IP, -it will be added to the IPAddresses field of the certificate, after the ones passed with the WithIPAddresses option. -Otherwise, it will be added to the DNSNames field. -- `tctls.WithValidFor`: sets the duration that the certificate will be valid for. By default, the certificate is valid for 365 days. -- `tctls.AsCA`: sets the certificate as a Certificate Authority (CA). This option is disabled by default. -When passed, the KeyUsage field of the certificate will append the x509.KeyUsageCertSign usage. -- `tctls.WithParent`: sets the parent certificate of the certificate. This option is disabled by default. -When passed, the parent certificate will be used to sign the generated certificate, -and the issuer of the certificate will be set to the common name of the parent certificate. -- `tctls.AsPem`: sets the certificate to be returned as PEM bytes. It will include the private key in the `KeyBytes` field of the Certificate struct. -- `tctls.WithIPAddresses`: sets the IP addresses that the certificate will be valid for. The IPs passed with this option will be added -first to the IPAddresses field of the certificate: those coming from the `WithHost` option will be added after them. -- `tctls.WithSaveToFile`: sets the parent directory where the certificate and its private key will be saved. Both the certificate and its private key will be saved in separate files, using an random UUID as part of the filename. E.g., `cert-.pem` and `key-.pem`. - -!!! note - If the `WithSaveToFile` option is passed, it will automatically set the `AsPem` option, as we need to the private key bytes too. +_Testcontainers for Go_ uses a library to generate certificates on-the-fly. This library is called [tlscert](https://github.com/mdelapenya/tlscert). ### Examples From 00fff0ab08838c38deff7938f5c33ea632ede520 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20de=20la=20Pe=C3=B1a?= Date: Fri, 12 Apr 2024 10:45:47 +0200 Subject: [PATCH 19/21] docs: fix examples --- docs/features/tls.md | 3 ++- modules/cockroachdb/certs.go | 8 ++++++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/docs/features/tls.md b/docs/features/tls.md index 0c1346e102..fd8b95266d 100644 --- a/docs/features/tls.md +++ b/docs/features/tls.md @@ -12,5 +12,6 @@ The example will also create a client that will connect to the server using the demonstrating how to use the generated certificate to communicate with a service. -[Use a certificate](../../tls/examples_test.go) inside_block:ExampleGenerateCert +[Create a self-signed certificate](../../modules/cockroachdb/certs.go) inside_block:exampleSelfSignedCert +[Sign a self-signed certificate](../../modules/cockroachdb/certs.go) inside_block:exampleSignSelfSignedCert diff --git a/modules/cockroachdb/certs.go b/modules/cockroachdb/certs.go index 5a8f870a41..fa9dd5540c 100644 --- a/modules/cockroachdb/certs.go +++ b/modules/cockroachdb/certs.go @@ -19,6 +19,7 @@ type TLSConfig struct { // NewTLSConfig creates a new TLSConfig capable of running CockroachDB & connecting over TLS. func NewTLSConfig() (*TLSConfig, error) { + // exampleSelfSignedCert { caCert := tlscert.SelfSignedFromRequest(tlscert.Request{ Name: "ca", SubjectCommonName: "Cockroach Test CA", @@ -29,24 +30,27 @@ func NewTLSConfig() (*TLSConfig, error) { if caCert == nil { return nil, fmt.Errorf("failed to generate CA certificate") } + // } + // exampleSignSelfSignedCert { nodeCert := tlscert.SelfSignedFromRequest(tlscert.Request{ Name: "node", Host: "localhost,127.0.0.1", IPAddresses: []net.IP{net.IPv4(127, 0, 0, 1), net.IPv6loopback}, ValidFor: time.Hour, - Parent: caCert, + Parent: caCert, // using the CA certificate as parent }) if nodeCert == nil { return nil, fmt.Errorf("failed to generate node certificate") } + // } clientCert := tlscert.SelfSignedFromRequest(tlscert.Request{ Name: "client", SubjectCommonName: defaultUser, Host: "localhost,127.0.0.1", ValidFor: time.Hour, - Parent: caCert, + Parent: caCert, // using the CA certificate as parent }) if clientCert == nil { return nil, fmt.Errorf("failed to generate client certificate") From 9da68d3563faa6b6d8bc94d2b5ce9b91953c2ab7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20de=20la=20Pe=C3=B1a?= Date: Fri, 12 Apr 2024 12:13:24 +0200 Subject: [PATCH 20/21] chore: use released version of tlscert --- modules/cockroachdb/go.mod | 2 +- modules/cockroachdb/go.sum | 4 ++-- modules/rabbitmq/go.mod | 2 +- modules/rabbitmq/go.sum | 4 ++-- modules/redpanda/go.mod | 2 +- modules/redpanda/go.sum | 4 ++-- 6 files changed, 9 insertions(+), 9 deletions(-) diff --git a/modules/cockroachdb/go.mod b/modules/cockroachdb/go.mod index 6955bcdf48..8e78d6c8d5 100644 --- a/modules/cockroachdb/go.mod +++ b/modules/cockroachdb/go.mod @@ -36,7 +36,7 @@ require ( github.com/kr/text v0.2.0 // indirect github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect github.com/magiconair/properties v1.8.7 // indirect - github.com/mdelapenya/tlscert v0.0.0-20240411153446-76db85d39935 + github.com/mdelapenya/tlscert v0.1.0 github.com/moby/patternmatcher v0.6.0 // indirect github.com/moby/sys/sequential v0.5.0 // indirect github.com/moby/sys/user v0.1.0 // indirect diff --git a/modules/cockroachdb/go.sum b/modules/cockroachdb/go.sum index fdb1187a4d..1ec215bc59 100644 --- a/modules/cockroachdb/go.sum +++ b/modules/cockroachdb/go.sum @@ -73,8 +73,8 @@ github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I= github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= -github.com/mdelapenya/tlscert v0.0.0-20240411153446-76db85d39935 h1:r+s8k5Jqis9pJqQudjCrsCKRFRMze3yD6j04vfKjY/s= -github.com/mdelapenya/tlscert v0.0.0-20240411153446-76db85d39935/go.mod h1:wrbyM/DwbFCeCeqdPX/8c6hNOqQgbf0rUDErE1uD+64= +github.com/mdelapenya/tlscert v0.1.0 h1:YTpF579PYUX475eOL+6zyEO3ngLTOUWck78NBuJVXaM= +github.com/mdelapenya/tlscert v0.1.0/go.mod h1:wrbyM/DwbFCeCeqdPX/8c6hNOqQgbf0rUDErE1uD+64= github.com/moby/patternmatcher v0.6.0 h1:GmP9lR19aU5GqSSFko+5pRqHi+Ohk1O69aFiKkVGiPk= github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc= github.com/moby/sys/sequential v0.5.0 h1:OPvI35Lzn9K04PBbCLW0g4LcFAJgHsvXsRyewg5lXtc= diff --git a/modules/rabbitmq/go.mod b/modules/rabbitmq/go.mod index 2de56db0c9..2c8134c7dd 100644 --- a/modules/rabbitmq/go.mod +++ b/modules/rabbitmq/go.mod @@ -30,7 +30,7 @@ require ( github.com/klauspost/compress v1.16.0 // indirect github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect github.com/magiconair/properties v1.8.7 // indirect - github.com/mdelapenya/tlscert v0.0.0-20240411153446-76db85d39935 + github.com/mdelapenya/tlscert v0.1.0 github.com/moby/patternmatcher v0.6.0 // indirect github.com/moby/sys/sequential v0.5.0 // indirect github.com/moby/sys/user v0.1.0 // indirect diff --git a/modules/rabbitmq/go.sum b/modules/rabbitmq/go.sum index 50cda64793..79a23800a3 100644 --- a/modules/rabbitmq/go.sum +++ b/modules/rabbitmq/go.sum @@ -63,8 +63,8 @@ github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I= github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= -github.com/mdelapenya/tlscert v0.0.0-20240411153446-76db85d39935 h1:r+s8k5Jqis9pJqQudjCrsCKRFRMze3yD6j04vfKjY/s= -github.com/mdelapenya/tlscert v0.0.0-20240411153446-76db85d39935/go.mod h1:wrbyM/DwbFCeCeqdPX/8c6hNOqQgbf0rUDErE1uD+64= +github.com/mdelapenya/tlscert v0.1.0 h1:YTpF579PYUX475eOL+6zyEO3ngLTOUWck78NBuJVXaM= +github.com/mdelapenya/tlscert v0.1.0/go.mod h1:wrbyM/DwbFCeCeqdPX/8c6hNOqQgbf0rUDErE1uD+64= github.com/moby/patternmatcher v0.6.0 h1:GmP9lR19aU5GqSSFko+5pRqHi+Ohk1O69aFiKkVGiPk= github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc= github.com/moby/sys/sequential v0.5.0 h1:OPvI35Lzn9K04PBbCLW0g4LcFAJgHsvXsRyewg5lXtc= diff --git a/modules/redpanda/go.mod b/modules/redpanda/go.mod index 3257221cbb..a31e06220f 100644 --- a/modules/redpanda/go.mod +++ b/modules/redpanda/go.mod @@ -35,7 +35,7 @@ require ( github.com/kr/pretty v0.3.1 // indirect github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect github.com/magiconair/properties v1.8.7 // indirect - github.com/mdelapenya/tlscert v0.0.0-20240411153446-76db85d39935 + github.com/mdelapenya/tlscert v0.1.0 github.com/moby/patternmatcher v0.6.0 // indirect github.com/moby/sys/sequential v0.5.0 // indirect github.com/moby/sys/user v0.1.0 // indirect diff --git a/modules/redpanda/go.sum b/modules/redpanda/go.sum index ef0cb9c24c..e31f78abc6 100644 --- a/modules/redpanda/go.sum +++ b/modules/redpanda/go.sum @@ -65,8 +65,8 @@ github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I= github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= -github.com/mdelapenya/tlscert v0.0.0-20240411153446-76db85d39935 h1:r+s8k5Jqis9pJqQudjCrsCKRFRMze3yD6j04vfKjY/s= -github.com/mdelapenya/tlscert v0.0.0-20240411153446-76db85d39935/go.mod h1:wrbyM/DwbFCeCeqdPX/8c6hNOqQgbf0rUDErE1uD+64= +github.com/mdelapenya/tlscert v0.1.0 h1:YTpF579PYUX475eOL+6zyEO3ngLTOUWck78NBuJVXaM= +github.com/mdelapenya/tlscert v0.1.0/go.mod h1:wrbyM/DwbFCeCeqdPX/8c6hNOqQgbf0rUDErE1uD+64= github.com/moby/patternmatcher v0.6.0 h1:GmP9lR19aU5GqSSFko+5pRqHi+Ohk1O69aFiKkVGiPk= github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc= github.com/moby/sys/sequential v0.5.0 h1:OPvI35Lzn9K04PBbCLW0g4LcFAJgHsvXsRyewg5lXtc= From 60141163a1cf865c2446b2c2479c620357dc9813 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20de=20la=20Pe=C3=B1a?= Date: Fri, 12 Apr 2024 12:21:18 +0200 Subject: [PATCH 21/21] fix: add common name for the node cert --- modules/cockroachdb/certs.go | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/modules/cockroachdb/certs.go b/modules/cockroachdb/certs.go index fa9dd5540c..dab738192a 100644 --- a/modules/cockroachdb/certs.go +++ b/modules/cockroachdb/certs.go @@ -34,11 +34,12 @@ func NewTLSConfig() (*TLSConfig, error) { // exampleSignSelfSignedCert { nodeCert := tlscert.SelfSignedFromRequest(tlscert.Request{ - Name: "node", - Host: "localhost,127.0.0.1", - IPAddresses: []net.IP{net.IPv4(127, 0, 0, 1), net.IPv6loopback}, - ValidFor: time.Hour, - Parent: caCert, // using the CA certificate as parent + Name: "node", + SubjectCommonName: "node", + Host: "localhost,127.0.0.1", + IPAddresses: []net.IP{net.IPv4(127, 0, 0, 1), net.IPv6loopback}, + ValidFor: time.Hour, + Parent: caCert, // using the CA certificate as parent }) if nodeCert == nil { return nil, fmt.Errorf("failed to generate node certificate")