Expand surface of hsic for better TLS support

This commit is contained in:
Juan Font 2023-04-13 21:10:08 +00:00
parent 17b597ed77
commit b8bf0a3b9f

View file

@ -15,6 +15,7 @@ import (
"math/big" "math/big"
"net" "net"
"net/http" "net/http"
"net/url"
"time" "time"
"github.com/davecgh/go-spew/spew" "github.com/davecgh/go-spew/spew"
@ -23,6 +24,7 @@ import (
"github.com/juanfont/headscale/integration/dockertestutil" "github.com/juanfont/headscale/integration/dockertestutil"
"github.com/juanfont/headscale/integration/integrationutil" "github.com/juanfont/headscale/integration/integrationutil"
"github.com/ory/dockertest/v3" "github.com/ory/dockertest/v3"
"github.com/ory/dockertest/v3/docker"
) )
const ( const (
@ -52,6 +54,8 @@ type HeadscaleInContainer struct {
// optional config // optional config
port int port int
extraPorts []string
hostPortBindings map[string][]string
aclPolicy *headscale.ACLPolicy aclPolicy *headscale.ACLPolicy
env map[string]string env map[string]string
tlsCert []byte tlsCert []byte
@ -77,7 +81,7 @@ func WithACLPolicy(acl *headscale.ACLPolicy) Option {
// WithTLS creates certificates and enables HTTPS. // WithTLS creates certificates and enables HTTPS.
func WithTLS() Option { func WithTLS() Option {
return func(hsic *HeadscaleInContainer) { return func(hsic *HeadscaleInContainer) {
cert, key, err := createCertificate() cert, key, err := createCertificate(hsic.hostname)
if err != nil { if err != nil {
log.Fatalf("failed to create certificates for headscale test: %s", err) log.Fatalf("failed to create certificates for headscale test: %s", err)
} }
@ -108,6 +112,19 @@ func WithPort(port int) Option {
} }
} }
// WithExtraPorts exposes additional ports on the container (e.g. 3478/udp for STUN)
func WithExtraPorts(ports []string) Option {
return func(hsic *HeadscaleInContainer) {
hsic.extraPorts = ports
}
}
func WithHostPortBindings(bindings map[string][]string) Option {
return func(hsic *HeadscaleInContainer) {
hsic.hostPortBindings = bindings
}
}
// WithTestName sets a name for the test, this will be reflected // WithTestName sets a name for the test, this will be reflected
// in the Docker container name. // in the Docker container name.
func WithTestName(testName string) Option { func WithTestName(testName string) Option {
@ -173,6 +190,16 @@ func New(
portProto := fmt.Sprintf("%d/tcp", hsic.port) portProto := fmt.Sprintf("%d/tcp", hsic.port)
serverUrl, err := url.Parse(hsic.env["HEADSCALE_SERVER_URL"])
if err != nil {
return nil, err
}
if len(hsic.tlsCert) != 0 && len(hsic.tlsKey) != 0 {
serverUrl.Scheme = "https"
hsic.env["HEADSCALE_SERVER_URL"] = serverUrl.String()
}
headscaleBuildOptions := &dockertest.BuildOptions{ headscaleBuildOptions := &dockertest.BuildOptions{
Dockerfile: "Dockerfile.debug", Dockerfile: "Dockerfile.debug",
ContextDir: dockerContextPath, ContextDir: dockerContextPath,
@ -187,7 +214,7 @@ func New(
runOptions := &dockertest.RunOptions{ runOptions := &dockertest.RunOptions{
Name: hsic.hostname, Name: hsic.hostname,
ExposedPorts: []string{portProto}, ExposedPorts: append([]string{portProto}, hsic.extraPorts...),
Networks: []*dockertest.Network{network}, Networks: []*dockertest.Network{network},
// Cmd: []string{"headscale", "serve"}, // Cmd: []string{"headscale", "serve"},
// TODO(kradalby): Get rid of this hack, we currently need to give us some // TODO(kradalby): Get rid of this hack, we currently need to give us some
@ -196,6 +223,18 @@ func New(
Env: env, Env: env,
} }
if len(hsic.hostPortBindings) > 0 {
runOptions.PortBindings = map[docker.Port][]docker.PortBinding{}
for port, hostPorts := range hsic.hostPortBindings {
runOptions.PortBindings[docker.Port(port)] = []docker.PortBinding{}
for _, hostPort := range hostPorts {
runOptions.PortBindings[docker.Port(port)] = append(
runOptions.PortBindings[docker.Port(port)],
docker.PortBinding{HostPort: hostPort})
}
}
}
// dockertest isnt very good at handling containers that has already // dockertest isnt very good at handling containers that has already
// been created, this is an attempt to make sure this container isnt // been created, this is an attempt to make sure this container isnt
// present. // present.
@ -456,7 +495,7 @@ func (t *HeadscaleInContainer) WriteFile(path string, data []byte) error {
} }
// nolint // nolint
func createCertificate() ([]byte, []byte, error) { func createCertificate(hostname string) ([]byte, []byte, error) {
// From: // From:
// https://shaneutt.com/blog/golang-ca-and-signed-cert-go/ // https://shaneutt.com/blog/golang-ca-and-signed-cert-go/
@ -468,7 +507,7 @@ func createCertificate() ([]byte, []byte, error) {
Locality: []string{"Leiden"}, Locality: []string{"Leiden"},
}, },
NotBefore: time.Now(), NotBefore: time.Now(),
NotAfter: time.Now().Add(30 * time.Minute), NotAfter: time.Now().Add(60 * time.Minute),
IsCA: true, IsCA: true,
ExtKeyUsage: []x509.ExtKeyUsage{ ExtKeyUsage: []x509.ExtKeyUsage{
x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageClientAuth,
@ -486,16 +525,17 @@ func createCertificate() ([]byte, []byte, error) {
cert := &x509.Certificate{ cert := &x509.Certificate{
SerialNumber: big.NewInt(1658), SerialNumber: big.NewInt(1658),
Subject: pkix.Name{ Subject: pkix.Name{
CommonName: hostname,
Organization: []string{"Headscale testing INC"}, Organization: []string{"Headscale testing INC"},
Country: []string{"NL"}, Country: []string{"NL"},
Locality: []string{"Leiden"}, Locality: []string{"Leiden"},
}, },
IPAddresses: []net.IP{net.IPv4(127, 0, 0, 1), net.IPv6loopback},
NotBefore: time.Now(), NotBefore: time.Now(),
NotAfter: time.Now().Add(30 * time.Minute), NotAfter: time.Now().Add(60 * time.Minute),
SubjectKeyId: []byte{1, 2, 3, 4, 6}, SubjectKeyId: []byte{1, 2, 3, 4, 6},
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth}, ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth},
KeyUsage: x509.KeyUsageDigitalSignature, KeyUsage: x509.KeyUsageDigitalSignature,
DNSNames: []string{hostname},
} }
certPrivKey, err := rsa.GenerateKey(rand.Reader, 4096) certPrivKey, err := rsa.GenerateKey(rand.Reader, 4096)