Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: create TLS certs in a consistent manner #2478

Merged
merged 21 commits into from
Apr 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion container.go
Original file line number Diff line number Diff line change
Expand Up @@ -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())
Expand Down
4 changes: 3 additions & 1 deletion docker.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
mdelapenya marked this conversation as resolved.
Show resolved Hide resolved
return err
}

err = c.provider.client.CopyToContainer(ctx, c.ID, "/", buffer, types.CopyToContainerOptions{})
if err != nil {
Expand Down Expand Up @@ -1506,7 +1509,6 @@ func (p *DockerProvider) getDefaultNetwork(ctx context.Context, cli client.APICl
Attachable: true,
Labels: core.DefaultLabels(core.SessionID()),
})

if err != nil {
return "", err
}
Expand Down
5 changes: 3 additions & 2 deletions docker_exec_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (

"github.com/docker/docker/pkg/stdcopy"
"github.com/stretchr/testify/require"

tcexec "github.com/testcontainers/testcontainers-go/exec"
)

Expand Down Expand Up @@ -133,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())
}
17 changes: 17 additions & 0 deletions docs/features/tls.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# TLS certificates

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.

_Testcontainers for Go_ uses a library to generate certificates on-the-fly. This library is called [tlscert](https://github.com/mdelapenya/tlscert).

### 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.

<!--codeinclude-->
[Create a self-signed certificate](../../modules/cockroachdb/certs.go) inside_block:exampleSelfSignedCert
[Sign a self-signed certificate](../../modules/cockroachdb/certs.go) inside_block:exampleSignSelfSignedCert
<!--/codeinclude-->
1 change: 1 addition & 0 deletions mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
175 changes: 42 additions & 133 deletions modules/cockroachdb/certs.go
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
package cockroachdb

import (
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"crypto/x509/pkix"
"encoding/pem"
"math/big"
"fmt"
"net"
"time"

"github.com/mdelapenya/tlscert"
)

type TLSConfig struct {
Expand All @@ -21,138 +19,49 @@ 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
// exampleSelfSignedCert {
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, nodeKey, err := generateNode(caCert, caKey)
if err != nil {
return nil, err
// }

// exampleSignSelfSignedCert {
nodeCert := tlscert.SelfSignedFromRequest(tlscert.Request{
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")
}

clientCert, clientKey, err := generateClient(caCert, caKey)
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, // using the CA certificate as parent
})
if clientCert == nil {
return nil, fmt.Errorf("failed to generate client certificate")
}

return &TLSConfig{
CACert: caCert,
NodeCert: nodeCert,
NodeKey: nodeKey,
ClientCert: clientCert,
ClientKey: clientKey,
CACert: caCert.Cert,
NodeCert: nodeCert.Bytes,
NodeKey: nodeCert.KeyBytes,
ClientCert: clientCert.Bytes,
ClientKey: clientCert.KeyBytes,
}, nil
}

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
}

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
}

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
}
1 change: 1 addition & 0 deletions modules/cockroachdb/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -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.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
Expand Down
2 changes: 2 additions & 0 deletions modules/cockroachdb/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -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.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=
Expand Down
38 changes: 34 additions & 4 deletions modules/rabbitmq/examples_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,10 @@ import (
"fmt"
"io"
"log"
"path/filepath"
"os"
"strings"

"github.com/mdelapenya/tlscert"
amqp "github.com/rabbitmq/amqp091-go"

"github.com/testcontainers/testcontainers-go"
Expand Down Expand Up @@ -89,10 +90,39 @@ 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
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{
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,
Expand Down
1 change: 1 addition & 0 deletions modules/rabbitmq/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -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.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
Expand Down
2 changes: 2 additions & 0 deletions modules/rabbitmq/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -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.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=
Expand Down
Loading
Loading