2021-10-14 12:27:03 +01:00
|
|
|
// Copyright (C) 2021 Storj Labs, Inc.
|
|
|
|
// See LICENSE for copying information.
|
|
|
|
|
|
|
|
package uitest
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
2021-08-23 12:43:22 +01:00
|
|
|
"crypto/ecdsa"
|
|
|
|
"crypto/elliptic"
|
2021-10-14 12:27:03 +01:00
|
|
|
"crypto/rand"
|
2021-08-23 12:43:22 +01:00
|
|
|
"crypto/x509"
|
|
|
|
"crypto/x509/pkix"
|
2021-10-14 12:27:03 +01:00
|
|
|
"encoding/binary"
|
2021-08-23 12:43:22 +01:00
|
|
|
"encoding/pem"
|
2021-10-14 12:27:03 +01:00
|
|
|
"fmt"
|
2021-08-23 12:43:22 +01:00
|
|
|
"math/big"
|
2021-10-14 12:27:03 +01:00
|
|
|
"net"
|
|
|
|
"os"
|
|
|
|
"strconv"
|
|
|
|
"testing"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/go-rod/rod"
|
|
|
|
"github.com/spf13/pflag"
|
|
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"go.uber.org/zap"
|
|
|
|
|
|
|
|
"storj.io/common/sync2"
|
|
|
|
"storj.io/common/testcontext"
|
2021-08-23 12:43:22 +01:00
|
|
|
"storj.io/gateway-mt/pkg/auth"
|
2021-10-14 12:27:03 +01:00
|
|
|
"storj.io/gateway-mt/pkg/authclient"
|
|
|
|
"storj.io/gateway-mt/pkg/server"
|
|
|
|
"storj.io/gateway-mt/pkg/trustedip"
|
|
|
|
"storj.io/private/cfgstruct"
|
|
|
|
"storj.io/storj/cmd/uplink/cmd"
|
|
|
|
"storj.io/storj/private/testplanet"
|
|
|
|
"storj.io/storj/satellite"
|
|
|
|
)
|
|
|
|
|
|
|
|
// EdgePlanet contains defaults for testplanet with Edge.
|
|
|
|
type EdgePlanet struct {
|
|
|
|
*testplanet.Planet
|
|
|
|
|
|
|
|
Gateway struct {
|
|
|
|
AccessKey string
|
|
|
|
SecretKey string
|
|
|
|
|
|
|
|
Addr string
|
|
|
|
}
|
|
|
|
|
|
|
|
Auth struct {
|
|
|
|
Addr string
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// EdgeTest defines common args for edge testing.
|
|
|
|
type EdgeTest func(t *testing.T, ctx *testcontext.Context, planet *EdgePlanet, browser *rod.Browser)
|
|
|
|
|
|
|
|
// Edge starts a testplanet together with auth service and gateway.
|
|
|
|
func Edge(t *testing.T, test EdgeTest) {
|
|
|
|
edgehost := os.Getenv("STORJ_TEST_EDGE_HOST")
|
|
|
|
if edgehost == "" {
|
2022-05-03 14:17:24 +01:00
|
|
|
edgehost = "127.0.0.1"
|
2021-10-14 12:27:03 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// TODO: make address not hardcoded the address selection here may
|
|
|
|
// conflict with some automatically bound address.
|
2021-08-23 12:43:22 +01:00
|
|
|
startPort := randomRange(20000, 40000)
|
|
|
|
authSvcAddr := net.JoinHostPort(edgehost, strconv.Itoa(startPort))
|
|
|
|
authSvcAddrTLS := net.JoinHostPort(edgehost, strconv.Itoa(startPort+1))
|
|
|
|
authSvcDrpcAddrTLS := net.JoinHostPort(edgehost, strconv.Itoa(startPort+2))
|
2021-10-14 12:27:03 +01:00
|
|
|
|
|
|
|
testplanet.Run(t, testplanet.Config{
|
|
|
|
SatelliteCount: 1, StorageNodeCount: 4, UplinkCount: 1,
|
|
|
|
Reconfigure: testplanet.Reconfigure{
|
|
|
|
Satellite: func(log *zap.Logger, index int, config *satellite.Config) {
|
2021-11-12 08:46:09 +00:00
|
|
|
configureSatellite(log, index, config)
|
2021-10-14 12:27:03 +01:00
|
|
|
// TODO: this should be dynamically set from the auth service
|
|
|
|
config.Console.GatewayCredentialsRequestURL = "http://" + authSvcAddr
|
|
|
|
},
|
|
|
|
},
|
|
|
|
NonParallel: true, // Note, do not remove this, because the code above uses same auth service addr.
|
|
|
|
}, func(t *testing.T, ctx *testcontext.Context, planet *testplanet.Planet) {
|
|
|
|
access := planet.Uplinks[0].Access[planet.Satellites[0].ID()]
|
|
|
|
|
|
|
|
gwConfig := server.Config{}
|
|
|
|
cfgstruct.Bind(&pflag.FlagSet{}, &gwConfig, cfgstruct.UseTestDefaults())
|
|
|
|
gwConfig.Server.Address = "127.0.0.1:0"
|
2021-08-23 12:43:22 +01:00
|
|
|
gwConfig.Auth.BaseURL = "http://" + authSvcAddr
|
|
|
|
gwConfig.Auth.Token = "super-secret"
|
|
|
|
gwConfig.Auth.Timeout = 5 * time.Minute
|
2021-10-14 12:27:03 +01:00
|
|
|
gwConfig.InsecureLogAll = true
|
|
|
|
|
2021-08-23 12:43:22 +01:00
|
|
|
authClient := authclient.New(gwConfig.Auth)
|
2021-10-14 12:27:03 +01:00
|
|
|
|
2021-08-23 12:43:22 +01:00
|
|
|
gateway, err := server.New(gwConfig, planet.Log().Named("gateway"), nil, trustedip.NewListTrustAll(), []string{}, authClient, []string{})
|
2021-10-14 12:27:03 +01:00
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
defer ctx.Check(gateway.Close)
|
|
|
|
|
2021-08-23 12:43:22 +01:00
|
|
|
certFile, keyFile, _, _ := createSelfSignedCertificateFile(t, edgehost)
|
|
|
|
|
2021-10-14 12:27:03 +01:00
|
|
|
authConfig := auth.Config{
|
2021-08-23 12:43:22 +01:00
|
|
|
Endpoint: "http://" + gateway.Address(),
|
|
|
|
AuthToken: "super-secret",
|
|
|
|
KVBackend: "memory://",
|
|
|
|
ListenAddr: authSvcAddr,
|
|
|
|
ListenAddrTLS: authSvcAddrTLS,
|
2022-05-03 14:17:24 +01:00
|
|
|
DRPCListenAddr: net.JoinHostPort(edgehost, "0"),
|
2021-08-23 12:43:22 +01:00
|
|
|
DRPCListenAddrTLS: authSvcDrpcAddrTLS,
|
|
|
|
CertFile: certFile.Name(),
|
|
|
|
KeyFile: keyFile.Name(),
|
2021-10-14 12:27:03 +01:00
|
|
|
}
|
|
|
|
for _, sat := range planet.Satellites {
|
|
|
|
authConfig.AllowedSatellites = append(authConfig.AllowedSatellites, sat.NodeURL().String())
|
|
|
|
}
|
|
|
|
|
2021-08-23 12:43:22 +01:00
|
|
|
authPeer, err := auth.New(ctx, planet.Log().Named("auth"), authConfig, ctx.Dir("authservice"))
|
2021-10-14 12:27:03 +01:00
|
|
|
require.NoError(t, err)
|
|
|
|
|
2021-08-23 12:43:22 +01:00
|
|
|
defer ctx.Check(authPeer.Close)
|
|
|
|
ctx.Go(func() error { return authPeer.Run(ctx) })
|
|
|
|
require.NoError(t, waitForAddress(ctx, authSvcAddrTLS, 3*time.Second))
|
|
|
|
require.NoError(t, waitForAddress(ctx, authSvcDrpcAddrTLS, 3*time.Second))
|
2021-10-14 12:27:03 +01:00
|
|
|
|
|
|
|
ctx.Go(gateway.Run)
|
|
|
|
require.NoError(t, waitForAddress(ctx, gateway.Address(), 3*time.Second))
|
|
|
|
|
2021-08-23 12:43:22 +01:00
|
|
|
edgeCredentials, err := cmd.RegisterAccess(ctx, access, authSvcDrpcAddrTLS, false, certFile.Name())
|
2021-10-14 12:27:03 +01:00
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
edge := &EdgePlanet{}
|
|
|
|
edge.Planet = planet
|
2021-08-23 12:43:22 +01:00
|
|
|
edge.Gateway.AccessKey = edgeCredentials.AccessKeyID
|
|
|
|
edge.Gateway.SecretKey = edgeCredentials.SecretKey
|
|
|
|
edge.Gateway.Addr = edgeCredentials.Endpoint
|
2021-10-14 12:27:03 +01:00
|
|
|
edge.Auth.Addr = authSvcAddr
|
|
|
|
|
2021-11-12 11:02:55 +00:00
|
|
|
Browser(t, ctx, planet, func(browser *rod.Browser) {
|
2021-10-14 12:27:03 +01:00
|
|
|
test(t, ctx, edge, browser)
|
|
|
|
})
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2021-08-23 12:43:22 +01:00
|
|
|
func createSelfSignedCertificateFile(t *testing.T, hostname string) (certFile *os.File, keyFile *os.File, certificatePEM []byte, privateKeyPEM []byte) {
|
|
|
|
certificatePEM, privateKeyPEM = createSelfSignedCertificate(t, hostname)
|
|
|
|
|
2022-10-31 15:12:17 +00:00
|
|
|
certFile, err := os.CreateTemp(os.TempDir(), "*-cert.pem")
|
2021-08-23 12:43:22 +01:00
|
|
|
require.NoError(t, err)
|
|
|
|
_, err = certFile.Write(certificatePEM)
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
2022-10-31 15:12:17 +00:00
|
|
|
keyFile, err = os.CreateTemp(os.TempDir(), "*-key.pem")
|
2021-08-23 12:43:22 +01:00
|
|
|
require.NoError(t, err)
|
|
|
|
_, err = keyFile.Write(privateKeyPEM)
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
return certFile, keyFile, certificatePEM, privateKeyPEM
|
|
|
|
}
|
|
|
|
|
|
|
|
func createSelfSignedCertificate(t *testing.T, hostname string) (certificatePEM []byte, privateKeyPEM []byte) {
|
|
|
|
notAfter := time.Now().Add(1 * time.Minute)
|
|
|
|
|
|
|
|
var ips []net.IP
|
|
|
|
ip := net.ParseIP(hostname)
|
|
|
|
if ip != nil {
|
|
|
|
ips = []net.IP{ip}
|
|
|
|
}
|
|
|
|
|
|
|
|
// first create a server certificate
|
|
|
|
template := x509.Certificate{
|
|
|
|
Subject: pkix.Name{
|
|
|
|
CommonName: hostname,
|
|
|
|
},
|
|
|
|
DNSNames: []string{hostname},
|
|
|
|
IPAddresses: ips,
|
|
|
|
SerialNumber: big.NewInt(1337),
|
|
|
|
BasicConstraintsValid: false,
|
|
|
|
IsCA: true,
|
|
|
|
NotAfter: notAfter,
|
|
|
|
}
|
|
|
|
|
|
|
|
privateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
certificateDERBytes, err := x509.CreateCertificate(
|
|
|
|
rand.Reader,
|
|
|
|
&template,
|
|
|
|
&template,
|
|
|
|
&privateKey.PublicKey,
|
|
|
|
privateKey,
|
|
|
|
)
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
certificatePEM = pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: certificateDERBytes})
|
|
|
|
|
|
|
|
privateKeyBytes, err := x509.MarshalPKCS8PrivateKey(privateKey)
|
|
|
|
require.NoError(t, err)
|
|
|
|
privateKeyPEM = pem.EncodeToMemory(&pem.Block{Type: "PRIVATE KEY", Bytes: privateKeyBytes})
|
|
|
|
|
|
|
|
return certificatePEM, privateKeyPEM
|
|
|
|
}
|
|
|
|
|
2021-10-14 12:27:03 +01:00
|
|
|
func randomRange(low, high int) int {
|
|
|
|
// this generates biased crypt random numbers
|
|
|
|
// but it uses crypt/rand to avoid potentially
|
|
|
|
// someone seeding math/rand.
|
|
|
|
span := high - low
|
|
|
|
return low + int(randomUint64()&0x7FFF_FFFF)%span
|
|
|
|
}
|
|
|
|
|
|
|
|
func randomUint64() uint64 {
|
|
|
|
var value [8]byte
|
|
|
|
if _, err := rand.Read(value[:]); err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return binary.LittleEndian.Uint64(value[:])
|
|
|
|
}
|
|
|
|
|
|
|
|
// waitForAddress will monitor starting when we are able to start the process.
|
|
|
|
func waitForAddress(ctx context.Context, address string, maxStartupWait time.Duration) error {
|
|
|
|
defer mon.Task()(&ctx)(nil)
|
|
|
|
|
|
|
|
start := time.Now()
|
|
|
|
for time.Since(start) < maxStartupWait {
|
|
|
|
if tryConnect(ctx, address) {
|
|
|
|
return ctx.Err()
|
|
|
|
}
|
|
|
|
|
|
|
|
// wait a bit before retrying to reduce load
|
|
|
|
if !sync2.Sleep(ctx, 50*time.Millisecond) {
|
|
|
|
return ctx.Err()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return fmt.Errorf("did not start in required time %v", maxStartupWait)
|
|
|
|
}
|
|
|
|
|
|
|
|
// tryConnect will try to connect to the process public address.
|
|
|
|
func tryConnect(ctx context.Context, address string) bool {
|
|
|
|
defer mon.Task()(&ctx)(nil)
|
|
|
|
|
|
|
|
dialer := net.Dialer{}
|
|
|
|
|
|
|
|
conn, err := dialer.DialContext(ctx, "tcp", address)
|
|
|
|
if err != nil {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
// write empty byte slice to trigger refresh on connection
|
|
|
|
_, _ = conn.Write([]byte{})
|
|
|
|
// ignoring errors, because we only care about being able to connect
|
|
|
|
_ = conn.Close()
|
|
|
|
return true
|
|
|
|
}
|