Merge branch 'main' into feature-random-suffix-on-collision

This commit is contained in:
Kristoffer Dalby 2022-10-11 08:24:21 +02:00 committed by GitHub
commit 03194e2d66
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 243 additions and 62 deletions

View file

@ -15,6 +15,7 @@
- Give a warning when running Headscale with reverse proxy improperly configured for WebSockets [#788](https://github.com/juanfont/headscale/pull/788) - Give a warning when running Headscale with reverse proxy improperly configured for WebSockets [#788](https://github.com/juanfont/headscale/pull/788)
- Fix subnet routers with Primary Routes [#811](https://github.com/juanfont/headscale/pull/811) - Fix subnet routers with Primary Routes [#811](https://github.com/juanfont/headscale/pull/811)
- Added support for JSON logs [#653](https://github.com/juanfont/headscale/issues/653) - Added support for JSON logs [#653](https://github.com/juanfont/headscale/issues/653)
- Sanitise the node key passed to registration url [#823](https://github.com/juanfont/headscale/pull/823)
- Add support for generating pre-auth keys with tags [#767](https://github.com/juanfont/headscale/pull/767) - Add support for generating pre-auth keys with tags [#767](https://github.com/juanfont/headscale/pull/767)
- Add support for evaluating `autoApprovers` ACL entries when a machine is registered [#763](https://github.com/juanfont/headscale/pull/763) - Add support for evaluating `autoApprovers` ACL entries when a machine is registered [#763](https://github.com/juanfont/headscale/pull/763)
- Add config flag to allow Headscale to start if OIDC provider is down [#829](https://github.com/juanfont/headscale/pull/829) - Add config flag to allow Headscale to start if OIDC provider is down [#829](https://github.com/juanfont/headscale/pull/829)

View file

@ -27,16 +27,40 @@ test:
test_integration: test_integration_cli test_integration_derp test_integration_oidc test_integration_general test_integration: test_integration_cli test_integration_derp test_integration_oidc test_integration_general
test_integration_cli: test_integration_cli:
go test -failfast -tags integration_cli,integration -timeout 30m -count=1 ./... docker network rm $$(docker network ls --filter name=headscale --quiet) || true
docker network create headscale-test || true
docker run -t --rm \
--network headscale-test \
-v $$PWD:$$PWD -w $$PWD \
-v /var/run/docker.sock:/var/run/docker.sock golang:1 \
go test -failfast -tags integration_cli,integration -timeout 30m -count=1 ./...
test_integration_derp: test_integration_derp:
go test -failfast -tags integration_derp,integration -timeout 30m -count=1 ./... docker network rm $$(docker network ls --filter name=headscale --quiet) || true
docker network create headscale-test || true
docker run -t --rm \
--network headscale-test \
-v $$PWD:$$PWD -w $$PWD \
-v /var/run/docker.sock:/var/run/docker.sock golang:1 \
go test -failfast -tags integration_derp,integration -timeout 30m -count=1 ./...
test_integration_general: test_integration_general:
go test -failfast -tags integration_general,integration -timeout 30m -count=1 ./... docker network rm $$(docker network ls --filter name=headscale --quiet) || true
docker network create headscale-test || true
docker run -t --rm \
--network headscale-test \
-v $$PWD:$$PWD -w $$PWD \
-v /var/run/docker.sock:/var/run/docker.sock golang:1 \
go test -failfast -tags integration_general,integration -timeout 30m -count=1 ./...
test_integration_oidc: test_integration_oidc:
go test -failfast -tags integration_oidc,integration -timeout 30m -count=1 ./... docker network rm $$(docker network ls --filter name=headscale --quiet) || true
docker network create headscale-test || true
docker run -t --rm \
--network headscale-test \
-v $$PWD:$$PWD -w $$PWD \
-v /var/run/docker.sock:/var/run/docker.sock golang:1 \
go test -failfast -tags integration_oidc,integration -timeout 30m -count=1 ./...
coverprofile_func: coverprofile_func:
go tool cover -func=coverage.out go tool cover -func=coverage.out

32
api.go
View file

@ -9,6 +9,7 @@ import (
"github.com/gorilla/mux" "github.com/gorilla/mux"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
"tailscale.com/types/key"
) )
const ( const (
@ -93,7 +94,34 @@ func (h *Headscale) RegisterWebAPI(
) { ) {
vars := mux.Vars(req) vars := mux.Vars(req)
nodeKeyStr, ok := vars["nkey"] nodeKeyStr, ok := vars["nkey"]
if !ok || nodeKeyStr == "" {
if !NodePublicKeyRegex.Match([]byte(nodeKeyStr)) {
log.Warn().Str("node_key", nodeKeyStr).Msg("Invalid node key passed to registration url")
writer.Header().Set("Content-Type", "text/plain; charset=utf-8")
writer.WriteHeader(http.StatusUnauthorized)
_, err := writer.Write([]byte("Unauthorized"))
if err != nil {
log.Error().
Caller().
Err(err).
Msg("Failed to write response")
}
return
}
// We need to make sure we dont open for XSS style injections, if the parameter that
// is passed as a key is not parsable/validated as a NodePublic key, then fail to render
// the template and log an error.
var nodeKey key.NodePublic
err := nodeKey.UnmarshalText(
[]byte(NodePublicKeyEnsurePrefix(nodeKeyStr)),
)
if !ok || nodeKeyStr == "" || err != nil {
log.Warn().Err(err).Msg("Failed to parse incoming nodekey")
writer.Header().Set("Content-Type", "text/plain; charset=utf-8") writer.Header().Set("Content-Type", "text/plain; charset=utf-8")
writer.WriteHeader(http.StatusBadRequest) writer.WriteHeader(http.StatusBadRequest)
_, err := writer.Write([]byte("Wrong params")) _, err := writer.Write([]byte("Wrong params"))
@ -130,7 +158,7 @@ func (h *Headscale) RegisterWebAPI(
writer.Header().Set("Content-Type", "text/html; charset=utf-8") writer.Header().Set("Content-Type", "text/html; charset=utf-8")
writer.WriteHeader(http.StatusOK) writer.WriteHeader(http.StatusOK)
_, err := writer.Write(content.Bytes()) _, err = writer.Write(content.Bytes())
if err != nil { if err != nil {
log.Error(). log.Error().
Caller(). Caller().

View file

@ -46,6 +46,10 @@ func mockOIDC() error {
if clientSecret == "" { if clientSecret == "" {
return errMockOidcClientSecretNotDefined return errMockOidcClientSecretNotDefined
} }
addrStr := os.Getenv("MOCKOIDC_ADDR")
if addrStr == "" {
return errMockOidcPortNotDefined
}
portStr := os.Getenv("MOCKOIDC_PORT") portStr := os.Getenv("MOCKOIDC_PORT")
if portStr == "" { if portStr == "" {
return errMockOidcPortNotDefined return errMockOidcPortNotDefined
@ -61,7 +65,7 @@ func mockOIDC() error {
return err return err
} }
listener, err := net.Listen("tcp", fmt.Sprintf("mockoidc:%d", port)) listener, err := net.Listen("tcp", fmt.Sprintf("%s:%d", addrStr, port))
if err != nil { if err != nil {
return err return err
} }

View file

@ -128,7 +128,13 @@
}; };
in rec { in rec {
# `nix develop` # `nix develop`
devShell = pkgs.mkShell {buildInputs = devDeps;}; devShell = pkgs.mkShell {
buildInputs = devDeps;
shellHook = ''
export GOFLAGS=-tags="integration,integration_general,integration_oidc,integration_cli,integration_derp"
'';
};
# `nix build` # `nix build`
packages = with pkgs; { packages = with pkgs; {

View file

@ -13,6 +13,7 @@ import (
v1 "github.com/juanfont/headscale/gen/go/headscale/v1" v1 "github.com/juanfont/headscale/gen/go/headscale/v1"
"github.com/ory/dockertest/v3" "github.com/ory/dockertest/v3"
"github.com/ory/dockertest/v3/docker"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/suite" "github.com/stretchr/testify/suite"
) )
@ -42,11 +43,11 @@ func (s *IntegrationCLITestSuite) SetupTest() {
s.FailNow(fmt.Sprintf("Could not connect to docker: %s", err), "") s.FailNow(fmt.Sprintf("Could not connect to docker: %s", err), "")
} }
if pnetwork, err := s.pool.CreateNetwork("headscale-test"); err == nil { network, err := GetFirstOrCreateNetwork(&s.pool, headscaleNetwork)
s.network = *pnetwork if err != nil {
} else { s.FailNow(fmt.Sprintf("Failed to create or get network: %s", err), "")
s.FailNow(fmt.Sprintf("Could not create network: %s", err), "")
} }
s.network = network
headscaleBuildOptions := &dockertest.BuildOptions{ headscaleBuildOptions := &dockertest.BuildOptions{
Dockerfile: "Dockerfile", Dockerfile: "Dockerfile",
@ -63,8 +64,12 @@ func (s *IntegrationCLITestSuite) SetupTest() {
Mounts: []string{ Mounts: []string{
fmt.Sprintf("%s/integration_test/etc:/etc/headscale", currentPath), fmt.Sprintf("%s/integration_test/etc:/etc/headscale", currentPath),
}, },
Networks: []*dockertest.Network{&s.network}, Cmd: []string{"headscale", "serve"},
Cmd: []string{"headscale", "serve"}, Networks: []*dockertest.Network{&s.network},
ExposedPorts: []string{"8080/tcp"},
PortBindings: map[docker.Port][]docker.PortBinding{
"8080/tcp": {{HostPort: "8080"}},
},
} }
err = s.pool.RemoveContainerByName(headscaleHostname) err = s.pool.RemoveContainerByName(headscaleHostname)
@ -87,7 +92,9 @@ func (s *IntegrationCLITestSuite) SetupTest() {
fmt.Println("Created headscale container for CLI tests") fmt.Println("Created headscale container for CLI tests")
fmt.Println("Waiting for headscale to be ready for CLI tests") fmt.Println("Waiting for headscale to be ready for CLI tests")
hostEndpoint := fmt.Sprintf("localhost:%s", s.headscale.GetPort("8080/tcp")) hostEndpoint := fmt.Sprintf("%s:%s",
s.headscale.GetIPInNetwork(&s.network),
s.headscale.GetPort("8080/tcp"))
if err := s.pool.Retry(func() error { if err := s.pool.Retry(func() error {
url := fmt.Sprintf("http://%s/health", hostEndpoint) url := fmt.Sprintf("http://%s/health", hostEndpoint)

View file

@ -19,7 +19,8 @@ import (
) )
const ( const (
headscaleHostname = "headscale-derp" headscaleNetwork = "headscale-test"
headscaleHostname = "headscale"
DOCKER_EXECUTE_TIMEOUT = 10 * time.Second DOCKER_EXECUTE_TIMEOUT = 10 * time.Second
) )
@ -32,7 +33,7 @@ var (
tailscaleVersions = []string{ tailscaleVersions = []string{
// "head", // "head",
// "unstable", // "unstable",
"1.30.0", "1.30.2",
"1.28.0", "1.28.0",
"1.26.2", "1.26.2",
"1.24.2", "1.24.2",
@ -115,13 +116,19 @@ func ExecuteCommand(
fmt.Println("stdout: ", stdout.String()) fmt.Println("stdout: ", stdout.String())
fmt.Println("stderr: ", stderr.String()) fmt.Println("stderr: ", stderr.String())
return stdout.String(), stderr.String(), fmt.Errorf("command failed with: %s", stderr.String()) return stdout.String(), stderr.String(), fmt.Errorf(
"command failed with: %s",
stderr.String(),
)
} }
return stdout.String(), stderr.String(), nil return stdout.String(), stderr.String(), nil
case <-time.After(execConfig.timeout): case <-time.After(execConfig.timeout):
return stdout.String(), stderr.String(), fmt.Errorf("command timed out after %s", execConfig.timeout) return stdout.String(), stderr.String(), fmt.Errorf(
"command timed out after %s",
execConfig.timeout,
)
} }
} }
@ -316,3 +323,22 @@ func GetEnvBool(key string) (bool, error) {
return v, nil return v, nil
} }
func GetFirstOrCreateNetwork(pool *dockertest.Pool, name string) (dockertest.Network, error) {
networks, err := pool.NetworksByName(name)
if err != nil || len(networks) == 0 {
if _, err := pool.CreateNetwork(name); err == nil {
// Create does not give us an updated version of the resource, so we need to
// get it again.
networks, err := pool.NetworksByName(name)
if err != nil {
return dockertest.Network{}, err
}
return networks[0], nil
}
}
return networks[0], nil
}

View file

@ -27,18 +27,20 @@ import (
) )
const ( const (
namespaceName = "derpnamespace" headscaleDerpHostname = "headscale-derp"
totalContainers = 3 namespaceName = "derpnamespace"
totalContainers = 3
) )
type IntegrationDERPTestSuite struct { type IntegrationDERPTestSuite struct {
suite.Suite suite.Suite
stats *suite.SuiteInformation stats *suite.SuiteInformation
pool dockertest.Pool pool dockertest.Pool
networks map[int]dockertest.Network // so we keep the containers isolated network dockertest.Network
headscale dockertest.Resource containerNetworks map[int]dockertest.Network // so we keep the containers isolated
saveLogs bool headscale dockertest.Resource
saveLogs bool
tailscales map[string]dockertest.Resource tailscales map[string]dockertest.Resource
joinWaitGroup sync.WaitGroup joinWaitGroup sync.WaitGroup
@ -53,7 +55,7 @@ func TestDERPIntegrationTestSuite(t *testing.T) {
s := new(IntegrationDERPTestSuite) s := new(IntegrationDERPTestSuite)
s.tailscales = make(map[string]dockertest.Resource) s.tailscales = make(map[string]dockertest.Resource)
s.networks = make(map[int]dockertest.Network) s.containerNetworks = make(map[int]dockertest.Network)
s.saveLogs = saveLogs s.saveLogs = saveLogs
suite.Run(t, s) suite.Run(t, s)
@ -78,7 +80,7 @@ func TestDERPIntegrationTestSuite(t *testing.T) {
log.Printf("Could not purge resource: %s\n", err) log.Printf("Could not purge resource: %s\n", err)
} }
for _, network := range s.networks { for _, network := range s.containerNetworks {
if err := network.Close(); err != nil { if err := network.Close(); err != nil {
log.Printf("Could not close network: %s\n", err) log.Printf("Could not close network: %s\n", err)
} }
@ -93,9 +95,15 @@ func (s *IntegrationDERPTestSuite) SetupSuite() {
s.FailNow(fmt.Sprintf("Could not connect to docker: %s", err), "") s.FailNow(fmt.Sprintf("Could not connect to docker: %s", err), "")
} }
network, err := GetFirstOrCreateNetwork(&s.pool, headscaleNetwork)
if err != nil {
s.FailNow(fmt.Sprintf("Failed to create or get network: %s", err), "")
}
s.network = network
for i := 0; i < totalContainers; i++ { for i := 0; i < totalContainers; i++ {
if pnetwork, err := s.pool.CreateNetwork(fmt.Sprintf("headscale-derp-%d", i)); err == nil { if pnetwork, err := s.pool.CreateNetwork(fmt.Sprintf("headscale-derp-%d", i)); err == nil {
s.networks[i] = *pnetwork s.containerNetworks[i] = *pnetwork
} else { } else {
s.FailNow(fmt.Sprintf("Could not create network: %s", err), "") s.FailNow(fmt.Sprintf("Could not create network: %s", err), "")
} }
@ -112,7 +120,8 @@ func (s *IntegrationDERPTestSuite) SetupSuite() {
} }
headscaleOptions := &dockertest.RunOptions{ headscaleOptions := &dockertest.RunOptions{
Name: headscaleHostname,
Name: headscaleDerpHostname,
Mounts: []string{ Mounts: []string{
fmt.Sprintf( fmt.Sprintf(
"%s/integration_test/etc_embedded_derp:/etc/headscale", "%s/integration_test/etc_embedded_derp:/etc/headscale",
@ -120,6 +129,7 @@ func (s *IntegrationDERPTestSuite) SetupSuite() {
), ),
}, },
Cmd: []string{"headscale", "serve"}, Cmd: []string{"headscale", "serve"},
Networks: []*dockertest.Network{&s.network},
ExposedPorts: []string{"8443/tcp", "3478/udp"}, ExposedPorts: []string{"8443/tcp", "3478/udp"},
PortBindings: map[docker.Port][]docker.PortBinding{ PortBindings: map[docker.Port][]docker.PortBinding{
"8443/tcp": {{HostPort: "8443"}}, "8443/tcp": {{HostPort: "8443"}},
@ -127,7 +137,7 @@ func (s *IntegrationDERPTestSuite) SetupSuite() {
}, },
} }
err = s.pool.RemoveContainerByName(headscaleHostname) err = s.pool.RemoveContainerByName(headscaleDerpHostname)
if err != nil { if err != nil {
s.FailNow( s.FailNow(
fmt.Sprintf( fmt.Sprintf(
@ -153,13 +163,15 @@ func (s *IntegrationDERPTestSuite) SetupSuite() {
hostname, container := s.tailscaleContainer( hostname, container := s.tailscaleContainer(
fmt.Sprint(i), fmt.Sprint(i),
version, version,
s.networks[i], s.containerNetworks[i],
) )
s.tailscales[hostname] = *container s.tailscales[hostname] = *container
} }
log.Println("Waiting for headscale to be ready for embedded DERP tests") log.Println("Waiting for headscale to be ready for embedded DERP tests")
hostEndpoint := fmt.Sprintf("localhost:%s", s.headscale.GetPort("8443/tcp")) hostEndpoint := fmt.Sprintf("%s:%s",
s.headscale.GetIPInNetwork(&s.network),
s.headscale.GetPort("8443/tcp"))
if err := s.pool.Retry(func() error { if err := s.pool.Retry(func() error {
url := fmt.Sprintf("https://%s/health", hostEndpoint) url := fmt.Sprintf("https://%s/health", hostEndpoint)
@ -320,7 +332,7 @@ func (s *IntegrationDERPTestSuite) TearDownSuite() {
log.Printf("Could not purge resource: %s\n", err) log.Printf("Could not purge resource: %s\n", err)
} }
for _, network := range s.networks { for _, network := range s.containerNetworks {
if err := network.Close(); err != nil { if err := network.Close(); err != nil {
log.Printf("Could not close network: %s\n", err) log.Printf("Could not close network: %s\n", err)
} }
@ -428,7 +440,9 @@ func (s *IntegrationDERPTestSuite) TestPingAllPeersByHostname() {
} }
func (s *IntegrationDERPTestSuite) TestDERPSTUN() { func (s *IntegrationDERPTestSuite) TestDERPSTUN() {
headscaleSTUNAddr := fmt.Sprintf("localhost:%s", s.headscale.GetPort("3478/udp")) headscaleSTUNAddr := fmt.Sprintf("%s:%s",
s.headscale.GetIPInNetwork(&s.network),
s.headscale.GetPort("3478/udp"))
client := stun.NewClient() client := stun.NewClient()
client.SetVerbose(true) client.SetVerbose(true)
client.SetVVerbose(true) client.SetVVerbose(true)

View file

@ -191,6 +191,17 @@ func (s *IntegrationTestSuite) tailscaleContainer(
}, },
} }
err := s.pool.RemoveContainerByName(hostname)
if err != nil {
s.FailNow(
fmt.Sprintf(
"Could not remove existing container before building test: %s",
err,
),
"",
)
}
pts, err := s.pool.BuildAndRunWithBuildOptions( pts, err := s.pool.BuildAndRunWithBuildOptions(
tailscaleBuildOptions, tailscaleBuildOptions,
tailscaleOptions, tailscaleOptions,
@ -219,11 +230,11 @@ func (s *IntegrationTestSuite) SetupSuite() {
s.FailNow(fmt.Sprintf("Could not connect to docker: %s", err), "") s.FailNow(fmt.Sprintf("Could not connect to docker: %s", err), "")
} }
if pnetwork, err := s.pool.CreateNetwork("headscale-test"); err == nil { network, err := GetFirstOrCreateNetwork(&s.pool, headscaleNetwork)
s.network = *pnetwork if err != nil {
} else { s.FailNow(fmt.Sprintf("Failed to create or get network: %s", err), "")
s.FailNow(fmt.Sprintf("Could not create network: %s", err), "")
} }
s.network = network
headscaleBuildOptions := &dockertest.BuildOptions{ headscaleBuildOptions := &dockertest.BuildOptions{
Dockerfile: "Dockerfile", Dockerfile: "Dockerfile",
@ -236,10 +247,14 @@ func (s *IntegrationTestSuite) SetupSuite() {
} }
headscaleOptions := &dockertest.RunOptions{ headscaleOptions := &dockertest.RunOptions{
Name: "headscale", Name: headscaleHostname,
Mounts: []string{ Mounts: []string{
fmt.Sprintf("%s/integration_test/etc:/etc/headscale", currentPath), fmt.Sprintf("%s/integration_test/etc:/etc/headscale", currentPath),
}, },
ExposedPorts: []string{"8080/tcp"},
PortBindings: map[docker.Port][]docker.PortBinding{
"8080/tcp": {{HostPort: "8080"}},
},
Networks: []*dockertest.Network{&s.network}, Networks: []*dockertest.Network{&s.network},
Cmd: []string{"headscale", "serve"}, Cmd: []string{"headscale", "serve"},
} }
@ -278,7 +293,9 @@ func (s *IntegrationTestSuite) SetupSuite() {
} }
log.Println("Waiting for headscale to be ready for core integration tests") log.Println("Waiting for headscale to be ready for core integration tests")
hostEndpoint := fmt.Sprintf("localhost:%s", s.headscale.GetPort("8080/tcp")) hostEndpoint := fmt.Sprintf("%s:%s",
s.headscale.GetIPInNetwork(&s.network),
s.headscale.GetPort("8080/tcp"))
if err := s.pool.Retry(func() error { if err := s.pool.Retry(func() error {
url := fmt.Sprintf("http://%s/health", hostEndpoint) url := fmt.Sprintf("http://%s/health", hostEndpoint)

View file

@ -25,7 +25,8 @@ import (
) )
const ( const (
oidcHeadscaleHostname = "headscale" oidcHeadscaleHostname = "headscale-oidc"
oidcMockHostname = "headscale-mock-oidc"
oidcNamespaceName = "oidcnamespace" oidcNamespaceName = "oidcnamespace"
totalOidcContainers = 3 totalOidcContainers = 3
) )
@ -95,33 +96,25 @@ func (s *IntegrationOIDCTestSuite) SetupSuite() {
s.FailNow(fmt.Sprintf("Could not connect to docker: %s", err), "") s.FailNow(fmt.Sprintf("Could not connect to docker: %s", err), "")
} }
if pnetwork, err := s.pool.CreateNetwork("headscale-test"); err == nil { network, err := GetFirstOrCreateNetwork(&s.pool, headscaleNetwork)
s.network = *pnetwork
} else {
s.FailNow(fmt.Sprintf("Could not create network: %s", err), "")
}
// Create does not give us an updated version of the resource, so we need to
// get it again.
networks, err := s.pool.NetworksByName("headscale-test")
if err != nil { if err != nil {
s.FailNow(fmt.Sprintf("Could not get network: %s", err), "") s.FailNow(fmt.Sprintf("Failed to create or get network: %s", err), "")
} }
s.network = networks[0] s.network = network
log.Printf("Network config: %v", s.network.Network.IPAM.Config[0]) log.Printf("Network config: %v", s.network.Network.IPAM.Config[0])
s.Suite.T().Log("Setting up mock OIDC") s.Suite.T().Log("Setting up mock OIDC")
mockOidcOptions := &dockertest.RunOptions{ mockOidcOptions := &dockertest.RunOptions{
Name: "mockoidc", Name: oidcMockHostname,
Hostname: "mockoidc",
Cmd: []string{"headscale", "mockoidc"}, Cmd: []string{"headscale", "mockoidc"},
ExposedPorts: []string{"10000/tcp"}, ExposedPorts: []string{"10000/tcp"},
Networks: []*dockertest.Network{&s.network},
PortBindings: map[docker.Port][]docker.PortBinding{ PortBindings: map[docker.Port][]docker.PortBinding{
"10000/tcp": {{HostPort: "10000"}}, "10000/tcp": {{HostPort: "10000"}},
}, },
Networks: []*dockertest.Network{&s.network},
Env: []string{ Env: []string{
fmt.Sprintf("MOCKOIDC_ADDR=%s", oidcMockHostname),
"MOCKOIDC_PORT=10000", "MOCKOIDC_PORT=10000",
"MOCKOIDC_CLIENT_ID=superclient", "MOCKOIDC_CLIENT_ID=superclient",
"MOCKOIDC_CLIENT_SECRET=supersecret", "MOCKOIDC_CLIENT_SECRET=supersecret",
@ -133,6 +126,17 @@ func (s *IntegrationOIDCTestSuite) SetupSuite() {
ContextDir: ".", ContextDir: ".",
} }
err = s.pool.RemoveContainerByName(oidcMockHostname)
if err != nil {
s.FailNow(
fmt.Sprintf(
"Could not remove existing container before building test: %s",
err,
),
"",
)
}
if pmockoidc, err := s.pool.BuildAndRunWithBuildOptions( if pmockoidc, err := s.pool.BuildAndRunWithBuildOptions(
headscaleBuildOptions, headscaleBuildOptions,
mockOidcOptions, mockOidcOptions,
@ -142,6 +146,35 @@ func (s *IntegrationOIDCTestSuite) SetupSuite() {
s.FailNow(fmt.Sprintf("Could not start mockOIDC container: %s", err), "") s.FailNow(fmt.Sprintf("Could not start mockOIDC container: %s", err), "")
} }
s.Suite.T().Logf("Waiting for headscale mock oidc to be ready for tests")
hostEndpoint := fmt.Sprintf(
"%s:%s",
s.mockOidc.GetIPInNetwork(&s.network),
s.mockOidc.GetPort("10000/tcp"),
)
if err := s.pool.Retry(func() error {
url := fmt.Sprintf("http://%s/oidc/.well-known/openid-configuration", hostEndpoint)
resp, err := http.Get(url)
if err != nil {
log.Printf("headscale mock OIDC tests is not ready: %s\n", err)
return err
}
if resp.StatusCode != http.StatusOK {
return fmt.Errorf("status code not OK")
}
return nil
}); err != nil {
// TODO(kradalby): If we cannot access headscale, or any other fatal error during
// test setup, we need to abort and tear down. However, testify does not seem to
// support that at the moment:
// https://github.com/stretchr/testify/issues/849
return // fmt.Errorf("Could not connect to headscale: %s", err)
}
s.Suite.T().Log("headscale-mock-oidc container is ready for embedded OIDC tests")
oidcCfg := fmt.Sprintf(` oidcCfg := fmt.Sprintf(`
oidc: oidc:
issuer: http://%s:10000/oidc issuer: http://%s:10000/oidc
@ -216,10 +249,14 @@ oidc:
} }
s.Suite.T().Logf("Waiting for headscale to be ready for embedded OIDC tests") s.Suite.T().Logf("Waiting for headscale to be ready for embedded OIDC tests")
hostEndpoint := fmt.Sprintf("localhost:%s", s.headscale.GetPort("8443/tcp")) hostMockEndpoint := fmt.Sprintf(
"%s:%s",
s.headscale.GetIPInNetwork(&s.network),
s.headscale.GetPort("8443/tcp"),
)
if err := s.pool.Retry(func() error { if err := s.pool.Retry(func() error {
url := fmt.Sprintf("https://%s/health", hostEndpoint) url := fmt.Sprintf("https://%s/health", hostMockEndpoint)
insecureTransport := http.DefaultTransport.(*http.Transport).Clone() insecureTransport := http.DefaultTransport.(*http.Transport).Clone()
insecureTransport.TLSClientConfig = &tls.Config{InsecureSkipVerify: true} insecureTransport.TLSClientConfig = &tls.Config{InsecureSkipVerify: true}
client := &http.Client{Transport: insecureTransport} client := &http.Client{Transport: insecureTransport}
@ -294,6 +331,8 @@ func (s *IntegrationOIDCTestSuite) AuthenticateOIDC(
resp, err := client.Get(loginURL.String()) resp, err := client.Get(loginURL.String())
assert.Nil(s.T(), err) assert.Nil(s.T(), err)
log.Printf("auth body, err: %#v, %s", resp, err)
body, err := io.ReadAll(resp.Body) body, err := io.ReadAll(resp.Body)
assert.Nil(s.T(), err) assert.Nil(s.T(), err)
@ -308,7 +347,6 @@ func (s *IntegrationOIDCTestSuite) joinOIDC(
endpoint, hostname string, endpoint, hostname string,
tailscale dockertest.Resource, tailscale dockertest.Resource,
) (*url.URL, error) { ) (*url.URL, error) {
command := []string{ command := []string{
"tailscale", "tailscale",
"up", "up",
@ -374,7 +412,13 @@ func (s *IntegrationOIDCTestSuite) tailscaleContainer(
DockerAllowNetworkAdministration, DockerAllowNetworkAdministration,
) )
if err != nil { if err != nil {
log.Fatalf("Could not start tailscale container version %s: %s", version, err) s.FailNow(
fmt.Sprintf(
"Could not start tailscale container version %s: %s",
version,
err,
),
)
} }
log.Printf("Created %s container\n", hostname) log.Printf("Created %s container\n", hostname)
@ -497,7 +541,12 @@ func (s *IntegrationOIDCTestSuite) TestPingAllPeersByAddress() {
[]string{}, []string{},
) )
assert.Nil(t, err) assert.Nil(t, err)
log.Printf("result for %s: stdout: %s, stderr: %s\n", hostname, stdout, stderr) log.Printf(
"result for %s: stdout: %s, stderr: %s\n",
hostname,
stdout,
stderr,
)
assert.Contains(t, stdout, "pong") assert.Contains(t, stdout, "pong")
}) })
} }

View file

@ -11,7 +11,7 @@ private_key_path: private.key
noise: noise:
private_key_path: noise_private.key private_key_path: noise_private.key
listen_addr: 0.0.0.0:8443 listen_addr: 0.0.0.0:8443
server_url: https://localhost:8443 server_url: https://headscale-oidc:8443
tls_cert_path: "/etc/headscale/tls/server.crt" tls_cert_path: "/etc/headscale/tls/server.crt"
tls_key_path: "/etc/headscale/tls/server.key" tls_key_path: "/etc/headscale/tls/server.key"
tls_client_auth_mode: disabled tls_client_auth_mode: disabled

View file

@ -17,6 +17,7 @@ import (
"os" "os"
"path/filepath" "path/filepath"
"reflect" "reflect"
"regexp"
"strconv" "strconv"
"strings" "strings"
@ -64,6 +65,8 @@ const (
ZstdCompression = "zstd" ZstdCompression = "zstd"
) )
var NodePublicKeyRegex = regexp.MustCompile("nodekey:[a-fA-F0-9]+")
func MachinePublicKeyStripPrefix(machineKey key.MachinePublic) string { func MachinePublicKeyStripPrefix(machineKey key.MachinePublic) string {
return strings.TrimPrefix(machineKey.String(), machinePublicHexPrefix) return strings.TrimPrefix(machineKey.String(), machinePublicHexPrefix)
} }
@ -325,7 +328,9 @@ func GenerateRandomStringDNSSafe(size int) (string, error) {
if err != nil { if err != nil {
return "", err return "", err
} }
str = strings.ToLower(strings.ReplaceAll(strings.ReplaceAll(str, "_", ""), "-", "")) str = strings.ToLower(
strings.ReplaceAll(strings.ReplaceAll(str, "_", ""), "-", ""),
)
} }
return str[:size], nil return str[:size], nil