From 62a7290e3d44684fa2b3637859585f63cf8ac78e Mon Sep 17 00:00:00 2001 From: Johan Siebens Date: Wed, 28 Feb 2024 13:14:33 +0100 Subject: [PATCH] chore: run tests with tls using self-signed certificate --- .dockerignore | 3 +- tests/certs/main.go | 140 ++++++++++++++++++++++++++++++ tests/config/ca.pem | 26 ++++++ tests/config/config.yaml | 8 +- tests/config/ionscale.key | 28 ++++++ tests/config/ionscale.pem | 23 +++++ tests/docker/tailscale/Dockerfile | 2 + tests/sc/scenario.go | 20 +++-- 8 files changed, 238 insertions(+), 12 deletions(-) create mode 100644 tests/certs/main.go create mode 100644 tests/config/ca.pem create mode 100644 tests/config/ionscale.key create mode 100644 tests/config/ionscale.pem diff --git a/.dockerignore b/.dockerignore index 9ab441a..fe50e4c 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,3 +1,4 @@ .git .idea -tests \ No newline at end of file +tests +!tests/config/ca.pem \ No newline at end of file diff --git a/tests/certs/main.go b/tests/certs/main.go new file mode 100644 index 0000000..08cf265 --- /dev/null +++ b/tests/certs/main.go @@ -0,0 +1,140 @@ +// partially taken from https://github.com/FiloSottile/mkcert +package main + +import ( + "crypto" + "crypto/rand" + "crypto/rsa" + "crypto/sha1" + "crypto/x509" + "crypto/x509/pkix" + "encoding/asn1" + "encoding/pem" + "log" + "math/big" + "net" + "net/mail" + "net/url" + "os" + "path/filepath" + "time" +) + +func main() { + path := "./tests/config" + caCert, caKey := newCA(path) + makeCert(path, caCert, caKey, []string{"ionscale"}) +} + +func generateKey(rootCA bool) (crypto.PrivateKey, error) { + if rootCA { + return rsa.GenerateKey(rand.Reader, 3072) + } + return rsa.GenerateKey(rand.Reader, 2048) +} + +func makeCert(path string, caCert *x509.Certificate, caKey any, hosts []string) { + priv, err := generateKey(false) + fatalIfErr(err) + pub := priv.(crypto.Signer).Public() + + expiration := time.Now().AddDate(2, 3, 0) + + tpl := &x509.Certificate{ + SerialNumber: randomSerialNumber(), + Subject: pkix.Name{ + Organization: []string{"ionscale tests cert"}, + OrganizationalUnit: []string{"ionscale"}, + }, + + NotBefore: time.Now(), NotAfter: expiration, + + KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, + } + + for _, h := range hosts { + if ip := net.ParseIP(h); ip != nil { + tpl.IPAddresses = append(tpl.IPAddresses, ip) + } else if email, err := mail.ParseAddress(h); err == nil && email.Address == h { + tpl.EmailAddresses = append(tpl.EmailAddresses, h) + } else if uriName, err := url.Parse(h); err == nil && uriName.Scheme != "" && uriName.Host != "" { + tpl.URIs = append(tpl.URIs, uriName) + } else { + tpl.DNSNames = append(tpl.DNSNames, h) + } + } + + if len(tpl.IPAddresses) > 0 || len(tpl.DNSNames) > 0 || len(tpl.URIs) > 0 { + tpl.ExtKeyUsage = append(tpl.ExtKeyUsage, x509.ExtKeyUsageServerAuth) + } + if len(tpl.EmailAddresses) > 0 { + tpl.ExtKeyUsage = append(tpl.ExtKeyUsage, x509.ExtKeyUsageEmailProtection) + } + + cert, err := x509.CreateCertificate(rand.Reader, tpl, caCert, pub, caKey) + fatalIfErr(err) + + certPEM := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: cert}) + privDER, err := x509.MarshalPKCS8PrivateKey(priv) + fatalIfErr(err) + privPEM := pem.EncodeToMemory(&pem.Block{Type: "PRIVATE KEY", Bytes: privDER}) + + fatalIfErr(os.WriteFile(filepath.Join(path, "ionscale.pem"), certPEM, 0644)) + fatalIfErr(os.WriteFile(filepath.Join(path, "ionscale.key"), privPEM, 0600)) +} + +func newCA(path string) (*x509.Certificate, any) { + priv, err := generateKey(true) + fatalIfErr(err) + pub := priv.(crypto.Signer).Public() + + spkiASN1, err := x509.MarshalPKIXPublicKey(pub) + fatalIfErr(err) + + var spki struct { + Algorithm pkix.AlgorithmIdentifier + SubjectPublicKey asn1.BitString + } + _, err = asn1.Unmarshal(spkiASN1, &spki) + fatalIfErr(err) + + skid := sha1.Sum(spki.SubjectPublicKey.Bytes) + + tpl := &x509.Certificate{ + SerialNumber: randomSerialNumber(), + Subject: pkix.Name{ + Organization: []string{"ionscale tests CA"}, + OrganizationalUnit: []string{"ionscale"}, + CommonName: "ionscale", + }, + SubjectKeyId: skid[:], + + NotAfter: time.Now().AddDate(10, 0, 0), + NotBefore: time.Now(), + + KeyUsage: x509.KeyUsageCertSign, + + BasicConstraintsValid: true, + IsCA: true, + MaxPathLenZero: true, + } + + cert, err := x509.CreateCertificate(rand.Reader, tpl, tpl, pub, priv) + fatalIfErr(err) + fatalIfErr(os.WriteFile(filepath.Join(path, "ca.pem"), pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: cert}), 0644)) + + return tpl, priv +} + +func randomSerialNumber() *big.Int { + serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128) + serialNumber, err := rand.Int(rand.Reader, serialNumberLimit) + fatalIfErr(err) + return serialNumber +} + +func fatalIfErr(err error) { + if err != nil { + log.Fatal(err) + } +} diff --git a/tests/config/ca.pem b/tests/config/ca.pem new file mode 100644 index 0000000..24552d3 --- /dev/null +++ b/tests/config/ca.pem @@ -0,0 +1,26 @@ +-----BEGIN CERTIFICATE----- +MIIEVDCCArygAwIBAgIRAMIAYisvcyOuv3qmGjMmJqAwDQYJKoZIhvcNAQELBQAw +QjEaMBgGA1UEChMRaW9uc2NhbGUgdGVzdHMgQ0ExETAPBgNVBAsTCGlvbnNjYWxl +MREwDwYDVQQDEwhpb25zY2FsZTAeFw0yNDAyMjgxMDU1MjJaFw0zNDAyMjgxMDU1 +MjJaMEIxGjAYBgNVBAoTEWlvbnNjYWxlIHRlc3RzIENBMREwDwYDVQQLEwhpb25z +Y2FsZTERMA8GA1UEAxMIaW9uc2NhbGUwggGiMA0GCSqGSIb3DQEBAQUAA4IBjwAw +ggGKAoIBgQDE2OSk75B3+2t/A/YxnCg0Ppz4Gzbw2oEZ2nw6bjDuIzjCOg+/CwoM +prFQcgEBmACKAnQ6gBvXhxVFQ4ROhqWlmGtQ7LzNG/ByKGI7h1hHz4jMJTDmCWH1 +rfvIRvApUer+Pmp4+39/++WPhkdy4UWu1puVOWdaOIbPigzd5hpqihyWy5FdEKyA +d0V5ElnTNjUHOjJ2MM9f33VxGe3U21u785w4jXndLgG5IxMYfpeMZ0HtOu5JTrsB +ec8taLqM3SxClpOFOJWDgieb3eacpl5GARh+a5/lVEJtM1CJE4CEPwghjGtjow4E +7nze5pckPEetHtWEax9hz/dC7J/mcMa1MFgpKcxlaHotT0I9goCEdvEuHI0r0Nt5 +TRfGjRTV2LGbHBnOY4Pn6N6OlfmesG4oTHErCkS9X1CZrB+Nxxywtvy1M2hEtbLY +bZMnWRhEoH8E9plWVS/HmyMza1rq4MEBv1BQhywoBChy+W+hqY1CQcdSgPeRj20F +JR9LvmD3W98CAwEAAaNFMEMwDgYDVR0PAQH/BAQDAgIEMBIGA1UdEwEB/wQIMAYB +Af8CAQAwHQYDVR0OBBYEFLNqb6/tfxMEdf8F13/mOausIT9hMA0GCSqGSIb3DQEB +CwUAA4IBgQCH0fc9cCYV5G7Oy0QVY5L0iYYz4aWXy1dsHxPtSvzbai0ZvNGupesp +fpsAb426mGp9vEJ+xQYnnTBhmvPJRZAbKbD2PV2vh/SpbD86gJS2azYneLSM0QXe +jgKb0aK/OSQYA5wzp/An1Yggg8SvT7SxS+fD4D7uD1UXPSqPjSO4IHT+8GPnm+rg +P3uierAyO5bAdE831yswTj33E4gm9HVKLqDW8YdIw9xI48T0ylj5F2DEQkSZWhFA +BqLN58e3zfo+3uh8KeVikd/k1KTQgrJeknDy12NMY1SzblBCEO8jF8LdraHn0RMy +QlJcRG315JNM0OIEbc22LeTUmeyJT7qrkERmoK1A7iRTQVpjJe4ZdocUtjKc03Pa +sAPs2xRXr58g9fVsN1ZevKSfvAvZWbXECdKRnw6z/LeqnjEpskbtcAoDoPV5cQ3L +1r9WpZYPq3D0Y9weX/XI0cGUnjIXqhY/mcBy/KwlVOfeen6eV0N6KYwDdd19zn8D +PRxxinJCKkg= +-----END CERTIFICATE----- diff --git a/tests/config/config.yaml b/tests/config/config.yaml index c7f5d70..0002ac4 100644 --- a/tests/config/config.yaml +++ b/tests/config/config.yaml @@ -1,9 +1,11 @@ -web_listen_addr: ":80" -web_public_addr: "http://ionscale:80" +web_listen_addr: ":443" +web_public_addr: "ionscale:443" tls: - disable: true + disable: false force_https: false + key_file: /etc/ionscale/ionscale.key + cert_file: /etc/ionscale/ionscale.pem keys: system_admin_key: "804ecd57365342254ce6647da5c249e85c10a0e51e74856bfdf292a2136b4249" diff --git a/tests/config/ionscale.key b/tests/config/ionscale.key new file mode 100644 index 0000000..328e603 --- /dev/null +++ b/tests/config/ionscale.key @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQDOcaJGhEiAO/Tu +usxtQWbuxIqotV7bDdyQRBlLhPb2UORo6U+KNNP26xPWv8eSg4P1hYroyRGPGRtQ +e6if49cYTRxh26lzO/umLfjFbiY4nyVpXVtOEwpz9A6okRgno7k/TVw4cCriXnIg +bqqwhK+FBGWVT/yTxVYE8OXJ5oIzD8XJ9ZVekSCNr/Pn5HmklViyMuGdH49yEIRt +LOPLW3dU5Rt8vPyWec1w/KJQyScBUMS/yk1r1Tc/FT90RpHqiJRcVIZQG1RZEOg0 ++rgoaEW8XsoLIPUHqBqkAuM2pPvAtTzPwKjA6ktel+FdRRwNCe5NEy95BBBBDyF0 +QsqhGRV5AgMBAAECggEAPjXGBeP1CReIPqxxz/amcwstEZveIrqxnZO336cI2L8V +pXuxKV+0BDNgvhT6qe8Rw9njOzTkIrAZKA6riMsa1UrfY6nTDVOlCLXPwobn46Yp +mu+0BPn962U/SZK3qbJPr4C7apJ6ic6WK6nNq/XAAqCJkA4TeeA8hK6nQElDMOlk +JrSbpRpqUs9H+LYGUPku+Ti33UMcz/DWtHM2XpaXM7MWkkNQfu2Z89lQbeGoLLVP +FA90ufwh9o6z2kTkscDuCT+iYQvf5W4Yu3MYugdHt9am7JbDWx1FGJf0vosg7Ckb +A5ccEK5791bjn+41oTUKcjJnRfDenFUyxs2JMaV/RQKBgQDbRk2shTk00DRAeQhl +sEzv7kyjKVEdo52Xd4Z0MKwcTwBGuEF91fvC1saSxFii5MVu8uFnCzZby63yxbqp +PKyncGSI8uHSDNN2nKlcrWa8ecFPWloU2qNFka7Pj8tcAt7fXN/x0BOeyPl2ZgKI +JjGJ5zEY1ZRjUVajaFPCLb9wPwKBgQDxBTHx0qq4j4J7tOJWrFVfY1GFoXYXBzDs +mxgLEXm7Tln7/O+1bOUOsViaSR+JlwkMzYMYAX0+5Dwq2X7D5UeICzHboF44VCxb +WA0RUcI3fXX/fXYvB4kgcDq697MyUd7uXMz9aJtL8y9x3UWkg7YBtcLa8Nj22NS/ +5aNOGMIMRwKBgF4SJzigXw59VC1kv2mA3UEB5vcIvrgMfYuBx7kJOI1chy3P1qj5 +qGzKX56Phcc8hkc3A+SFNuji1NmWYqJAWYHsAfWO3bqTrhTw2OfdrHmncntss8DG +m8041tpNQl1TDfKdkaXYMtKoPf3BfcyTNiWHfdS9rE9/kb/A/k4L+llZAoGAUDV8 +Y4/KRNVthSGzWUjMuIvi62fKVuuLupH6tGRCcLUoeRW56FSu/e7DH8VJ+44J8vrJ +r8l/Ftj5tQDgkOzSm1Gua/q4oGJSyKtK3gKpcbIM/NOR6yDE38i5otMgDZT1bbnw +djzDwcjSd0A+FvvGLFeC2z3f1nZehuYzLKBMo60CgYAPjANh0rVnCbbnsQYwLJa7 +83vatZWtb4Qy1VJcsGT6TFqf3gUTKel8KttM2RN0rs9nZ/Slso9q778Fv16aTnlI +48P8x8Qw+Zen7042NvrsjnXcOi4sj0b2SLWvOBDBO7eY7GXSqa7O0PpPUSD7vTLu +EDO0H/OF6MRC8YSaz8WrkQ== +-----END PRIVATE KEY----- diff --git a/tests/config/ionscale.pem b/tests/config/ionscale.pem new file mode 100644 index 0000000..1022ba5 --- /dev/null +++ b/tests/config/ionscale.pem @@ -0,0 +1,23 @@ +-----BEGIN CERTIFICATE----- +MIID2jCCAkKgAwIBAgIQSH/qIg/vOs/ZPoTlMlg2GDANBgkqhkiG9w0BAQsFADBC +MRowGAYDVQQKExFpb25zY2FsZSB0ZXN0cyBDQTERMA8GA1UECxMIaW9uc2NhbGUx +ETAPBgNVBAMTCGlvbnNjYWxlMB4XDTI0MDIyODEwNTUyMloXDTI2MDUyODA5NTUy +MlowMTEcMBoGA1UEChMTaW9uc2NhbGUgdGVzdHMgY2VydDERMA8GA1UECxMIaW9u +c2NhbGUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDOcaJGhEiAO/Tu +usxtQWbuxIqotV7bDdyQRBlLhPb2UORo6U+KNNP26xPWv8eSg4P1hYroyRGPGRtQ +e6if49cYTRxh26lzO/umLfjFbiY4nyVpXVtOEwpz9A6okRgno7k/TVw4cCriXnIg +bqqwhK+FBGWVT/yTxVYE8OXJ5oIzD8XJ9ZVekSCNr/Pn5HmklViyMuGdH49yEIRt +LOPLW3dU5Rt8vPyWec1w/KJQyScBUMS/yk1r1Tc/FT90RpHqiJRcVIZQG1RZEOg0 ++rgoaEW8XsoLIPUHqBqkAuM2pPvAtTzPwKjA6ktel+FdRRwNCe5NEy95BBBBDyF0 +QsqhGRV5AgMBAAGjXTBbMA4GA1UdDwEB/wQEAwIFoDATBgNVHSUEDDAKBggrBgEF +BQcDATAfBgNVHSMEGDAWgBSzam+v7X8TBHX/Bdd/5jmrrCE/YTATBgNVHREEDDAK +gghpb25zY2FsZTANBgkqhkiG9w0BAQsFAAOCAYEALUO2i7zsClFSTdBzMEsPupAd +TYMxFG0ms8jBma9VixQycudiLS5ICgoGErXtYqsfHOOi3OxwZ8SUVF3JnqaWStFD +Hi3ckdjBprBJEVXaZUGSeaKJHYIuBrFZBf5uhtd7o9qC3uPYLp8yzdDM/JsyRCl9 +V7NUq69fKkYi1vokHwL0xTU+hbTygVAsok1j4E2dBmtLpRsfokjAExdx5QykIlh2 +mAf2owD1e2JY9bW1zHZpgkfapV6bv95jrKSerG0PM/fUpZ3/QxjB0OenQXHz2dcK +hIS+lWj+Sns6m5iyTsLVQyw4giLTGLDPAztWotA+zaHUcS6iR9nrChZ5J4tklRD4 ++OAmL+iiR4z0aIED/CLRiw27W/m8+K5XfisTfNk6IhgsE/cILAhxnHJy6asa/0pT +KanbA1ilcr3nAYuETW39f2NIrjvm8ELvrOrzC5LB75FxoJyp799QO939XqSKwoxL +T4JDjOOprtH1cPwag5MJF6NZj0fb2ej77RR3R5cI +-----END CERTIFICATE----- diff --git a/tests/docker/tailscale/Dockerfile b/tests/docker/tailscale/Dockerfile index 830c047..710f614 100644 --- a/tests/docker/tailscale/Dockerfile +++ b/tests/docker/tailscale/Dockerfile @@ -4,6 +4,8 @@ FROM tailscale/tailscale:${TAILSCALE_VERSION} as src FROM alpine:latest RUN apk update && apk add ca-certificates openssh curl && rm -rf /var/cache/apk/* +COPY ./tests/config/ca.pem /usr/local/share/ca-certificates/ +RUN update-ca-certificates WORKDIR /app COPY --from=src /usr/local/bin/tailscale . diff --git a/tests/sc/scenario.go b/tests/sc/scenario.go index 9296e8d..810e7e5 100644 --- a/tests/sc/scenario.go +++ b/tests/sc/scenario.go @@ -169,7 +169,7 @@ func (s *Scenario) NewTailscaleNode(opts ...TailscaleNodeOpt) *tsn.TailscaleNode s.resources = append(s.resources, resource) - return tsn.New(s.t, config.Hostname, "http://ionscale", resource, s.pool.Retry) + return tsn.New(s.t, config.Hostname, "https://ionscale", resource, s.pool.Retry) } func Run(t *testing.T, f func(s *Scenario)) { @@ -247,25 +247,28 @@ func Run(t *testing.T, f func(s *Scenario)) { fmt.Sprintf("%s/config:/etc/ionscale", currentPath), }, Networks: []*dockertest.Network{s.network}, - ExposedPorts: []string{"80"}, + ExposedPorts: []string{"443"}, Cmd: []string{"server", "--config", "/etc/ionscale/config.yaml"}, } s.ionscale, err = pool.RunWithOptions(ionscale, restartPolicy) require.NoError(s.t, err) - port := s.ionscale.GetPort("80/tcp") + port := s.ionscale.GetPort("443/tcp") - err = pool.Retry(httpCheck(port, "/key")) - require.NoError(s.t, err) - - addr := fmt.Sprintf("http://localhost:%s", port) + addr := fmt.Sprintf("https://localhost:%s", port) auth, err := ionscaleclt.LoadClientAuth(addr, "804ecd57365342254ce6647da5c249e85c10a0e51e74856bfdf292a2136b4249") require.NoError(s.t, err) s.ionscaleClient, err = ionscaleclt.NewClient(auth, addr, true) require.NoError(s.t, err) + err = pool.Retry(func() error { + _, err := s.ionscaleClient.GetVersion(context.Background(), connect.NewRequest(&api.GetVersionRequest{})) + return err + }) + require.NoError(s.t, err) + f(s) } @@ -313,7 +316,8 @@ func prepareDockerPoolAndImages() { pool, _ = dockertest.NewPool("") buildOpts := &dockertest.BuildOptions{ - ContextDir: "./docker/tailscale", + ContextDir: "../", + Dockerfile: "tests/docker/tailscale/Dockerfile", BuildArgs: []docker.BuildArg{ { Name: "TAILSCALE_VERSION",