mirror of
https://github.com/juanfont/headscale.git
synced 2024-11-26 17:03:06 +00:00
Merge pull request #196 from kradalby/derp-improvements
Add ability to fetch DERP from url and file
This commit is contained in:
commit
5aaffaaecb
14 changed files with 349 additions and 227 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -16,6 +16,7 @@
|
||||||
|
|
||||||
/headscale
|
/headscale
|
||||||
config.json
|
config.json
|
||||||
|
config.yaml
|
||||||
*.key
|
*.key
|
||||||
/db.sqlite
|
/db.sqlite
|
||||||
*.sqlite3
|
*.sqlite3
|
||||||
|
|
|
@ -12,6 +12,11 @@ RUN test -e /go/bin/headscale
|
||||||
|
|
||||||
FROM ubuntu:20.04
|
FROM ubuntu:20.04
|
||||||
|
|
||||||
|
RUN apt-get update \
|
||||||
|
&& apt-get install -y ca-certificates \
|
||||||
|
&& update-ca-certificates \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
COPY --from=build /go/bin/headscale /usr/local/bin/headscale
|
COPY --from=build /go/bin/headscale /usr/local/bin/headscale
|
||||||
ENV TZ UTC
|
ENV TZ UTC
|
||||||
|
|
||||||
|
|
15
api.go
15
api.go
|
@ -82,7 +82,10 @@ func (h *Headscale) RegistrationHandler(c *gin.Context) {
|
||||||
|
|
||||||
now := time.Now().UTC()
|
now := time.Now().UTC()
|
||||||
var m Machine
|
var m Machine
|
||||||
if result := h.db.Preload("Namespace").First(&m, "machine_key = ?", mKey.HexString()); errors.Is(result.Error, gorm.ErrRecordNotFound) {
|
if result := h.db.Preload("Namespace").First(&m, "machine_key = ?", mKey.HexString()); errors.Is(
|
||||||
|
result.Error,
|
||||||
|
gorm.ErrRecordNotFound,
|
||||||
|
) {
|
||||||
log.Info().Str("machine", req.Hostinfo.Hostname).Msg("New machine")
|
log.Info().Str("machine", req.Hostinfo.Hostname).Msg("New machine")
|
||||||
m = Machine{
|
m = Machine{
|
||||||
Expiry: &req.Expiry,
|
Expiry: &req.Expiry,
|
||||||
|
@ -270,7 +273,7 @@ func (h *Headscale) getMapResponse(mKey wgkey.Key, req tailcfg.MapRequest, m *Ma
|
||||||
DNSConfig: dnsConfig,
|
DNSConfig: dnsConfig,
|
||||||
Domain: h.cfg.BaseDomain,
|
Domain: h.cfg.BaseDomain,
|
||||||
PacketFilter: *h.aclRules,
|
PacketFilter: *h.aclRules,
|
||||||
DERPMap: h.cfg.DerpMap,
|
DERPMap: h.DERPMap,
|
||||||
UserProfiles: profiles,
|
UserProfiles: profiles,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -329,7 +332,13 @@ func (h *Headscale) getMapKeepAliveResponse(mKey wgkey.Key, req tailcfg.MapReque
|
||||||
return data, nil
|
return data, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Headscale) handleAuthKey(c *gin.Context, db *gorm.DB, idKey wgkey.Key, req tailcfg.RegisterRequest, m Machine) {
|
func (h *Headscale) handleAuthKey(
|
||||||
|
c *gin.Context,
|
||||||
|
db *gorm.DB,
|
||||||
|
idKey wgkey.Key,
|
||||||
|
req tailcfg.RegisterRequest,
|
||||||
|
m Machine,
|
||||||
|
) {
|
||||||
log.Debug().
|
log.Debug().
|
||||||
Str("func", "handleAuthKey").
|
Str("func", "handleAuthKey").
|
||||||
Str("machine", req.Hostinfo.Hostname).
|
Str("machine", req.Hostinfo.Hostname).
|
||||||
|
|
32
app.go
32
app.go
|
@ -4,6 +4,7 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
|
@ -28,11 +29,12 @@ type Config struct {
|
||||||
ServerURL string
|
ServerURL string
|
||||||
Addr string
|
Addr string
|
||||||
PrivateKeyPath string
|
PrivateKeyPath string
|
||||||
DerpMap *tailcfg.DERPMap
|
|
||||||
EphemeralNodeInactivityTimeout time.Duration
|
EphemeralNodeInactivityTimeout time.Duration
|
||||||
IPPrefix netaddr.IPPrefix
|
IPPrefix netaddr.IPPrefix
|
||||||
BaseDomain string
|
BaseDomain string
|
||||||
|
|
||||||
|
DERP DERPConfig
|
||||||
|
|
||||||
DBtype string
|
DBtype string
|
||||||
DBpath string
|
DBpath string
|
||||||
DBhost string
|
DBhost string
|
||||||
|
@ -55,6 +57,13 @@ type Config struct {
|
||||||
DNSConfig *tailcfg.DNSConfig
|
DNSConfig *tailcfg.DNSConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type DERPConfig struct {
|
||||||
|
URLs []url.URL
|
||||||
|
Paths []string
|
||||||
|
AutoUpdate bool
|
||||||
|
UpdateFrequency time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
// Headscale represents the base app of the service
|
// Headscale represents the base app of the service
|
||||||
type Headscale struct {
|
type Headscale struct {
|
||||||
cfg Config
|
cfg Config
|
||||||
|
@ -65,6 +74,8 @@ type Headscale struct {
|
||||||
publicKey *wgkey.Key
|
publicKey *wgkey.Key
|
||||||
privateKey *wgkey.Private
|
privateKey *wgkey.Private
|
||||||
|
|
||||||
|
DERPMap *tailcfg.DERPMap
|
||||||
|
|
||||||
aclPolicy *ACLPolicy
|
aclPolicy *ACLPolicy
|
||||||
aclRules *[]tailcfg.FilterRule
|
aclRules *[]tailcfg.FilterRule
|
||||||
|
|
||||||
|
@ -153,11 +164,15 @@ func (h *Headscale) expireEphemeralNodesWorker() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
for _, m := range *machines {
|
for _, m := range *machines {
|
||||||
if m.AuthKey != nil && m.LastSeen != nil && m.AuthKey.Ephemeral && time.Now().After(m.LastSeen.Add(h.cfg.EphemeralNodeInactivityTimeout)) {
|
if m.AuthKey != nil && m.LastSeen != nil && m.AuthKey.Ephemeral &&
|
||||||
|
time.Now().After(m.LastSeen.Add(h.cfg.EphemeralNodeInactivityTimeout)) {
|
||||||
log.Info().Str("machine", m.Name).Msg("Ephemeral client removed from database")
|
log.Info().Str("machine", m.Name).Msg("Ephemeral client removed from database")
|
||||||
err = h.db.Unscoped().Delete(m).Error
|
err = h.db.Unscoped().Delete(m).Error
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error().Err(err).Str("machine", m.Name).Msg("🤮 Cannot delete ephemeral machine from the database")
|
log.Error().
|
||||||
|
Err(err).
|
||||||
|
Str("machine", m.Name).
|
||||||
|
Msg("🤮 Cannot delete ephemeral machine from the database")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -198,6 +213,15 @@ func (h *Headscale) Serve() error {
|
||||||
go h.watchForKVUpdates(5000)
|
go h.watchForKVUpdates(5000)
|
||||||
go h.expireEphemeralNodes(5000)
|
go h.expireEphemeralNodes(5000)
|
||||||
|
|
||||||
|
// Fetch an initial DERP Map before we start serving
|
||||||
|
h.DERPMap = GetDERPMap(h.cfg.DERP)
|
||||||
|
|
||||||
|
if h.cfg.DERP.AutoUpdate {
|
||||||
|
derpMapCancelChannel := make(chan struct{})
|
||||||
|
defer func() { derpMapCancelChannel <- struct{}{} }()
|
||||||
|
go h.scheduledDERPMapUpdateWorker(derpMapCancelChannel)
|
||||||
|
}
|
||||||
|
|
||||||
s := &http.Server{
|
s := &http.Server{
|
||||||
Addr: h.cfg.Addr,
|
Addr: h.cfg.Addr,
|
||||||
Handler: r,
|
Handler: r,
|
||||||
|
@ -273,7 +297,6 @@ func (h *Headscale) getLastStateChange(namespaces ...string) time.Time {
|
||||||
|
|
||||||
times = append(times, lastChange)
|
times = append(times, lastChange)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
sort.Slice(times, func(i, j int) bool {
|
sort.Slice(times, func(i, j int) bool {
|
||||||
|
@ -284,7 +307,6 @@ func (h *Headscale) getLastStateChange(namespaces ...string) time.Time {
|
||||||
|
|
||||||
if len(times) == 0 {
|
if len(times) == 0 {
|
||||||
return time.Now().UTC()
|
return time.Now().UTC()
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
return times[0]
|
return times[0]
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,7 @@ import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
@ -13,7 +13,6 @@ import (
|
||||||
"github.com/juanfont/headscale"
|
"github.com/juanfont/headscale"
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
"gopkg.in/yaml.v2"
|
|
||||||
"inet.af/netaddr"
|
"inet.af/netaddr"
|
||||||
"tailscale.com/tailcfg"
|
"tailscale.com/tailcfg"
|
||||||
"tailscale.com/types/dnstype"
|
"tailscale.com/types/dnstype"
|
||||||
|
@ -51,21 +50,26 @@ func LoadConfig(path string) error {
|
||||||
|
|
||||||
// Collect any validation errors and return them all at once
|
// Collect any validation errors and return them all at once
|
||||||
var errorText string
|
var errorText string
|
||||||
if (viper.GetString("tls_letsencrypt_hostname") != "") && ((viper.GetString("tls_cert_path") != "") || (viper.GetString("tls_key_path") != "")) {
|
if (viper.GetString("tls_letsencrypt_hostname") != "") &&
|
||||||
|
((viper.GetString("tls_cert_path") != "") || (viper.GetString("tls_key_path") != "")) {
|
||||||
errorText += "Fatal config error: set either tls_letsencrypt_hostname or tls_cert_path/tls_key_path, not both\n"
|
errorText += "Fatal config error: set either tls_letsencrypt_hostname or tls_cert_path/tls_key_path, not both\n"
|
||||||
}
|
}
|
||||||
|
|
||||||
if (viper.GetString("tls_letsencrypt_hostname") != "") && (viper.GetString("tls_letsencrypt_challenge_type") == "TLS-ALPN-01") && (!strings.HasSuffix(viper.GetString("listen_addr"), ":443")) {
|
if (viper.GetString("tls_letsencrypt_hostname") != "") &&
|
||||||
|
(viper.GetString("tls_letsencrypt_challenge_type") == "TLS-ALPN-01") &&
|
||||||
|
(!strings.HasSuffix(viper.GetString("listen_addr"), ":443")) {
|
||||||
// this is only a warning because there could be something sitting in front of headscale that redirects the traffic (e.g. an iptables rule)
|
// this is only a warning because there could be something sitting in front of headscale that redirects the traffic (e.g. an iptables rule)
|
||||||
log.Warn().
|
log.Warn().
|
||||||
Msg("Warning: when using tls_letsencrypt_hostname with TLS-ALPN-01 as challenge type, headscale must be reachable on port 443, i.e. listen_addr should probably end in :443")
|
Msg("Warning: when using tls_letsencrypt_hostname with TLS-ALPN-01 as challenge type, headscale must be reachable on port 443, i.e. listen_addr should probably end in :443")
|
||||||
}
|
}
|
||||||
|
|
||||||
if (viper.GetString("tls_letsencrypt_challenge_type") != "HTTP-01") && (viper.GetString("tls_letsencrypt_challenge_type") != "TLS-ALPN-01") {
|
if (viper.GetString("tls_letsencrypt_challenge_type") != "HTTP-01") &&
|
||||||
|
(viper.GetString("tls_letsencrypt_challenge_type") != "TLS-ALPN-01") {
|
||||||
errorText += "Fatal config error: the only supported values for tls_letsencrypt_challenge_type are HTTP-01 and TLS-ALPN-01\n"
|
errorText += "Fatal config error: the only supported values for tls_letsencrypt_challenge_type are HTTP-01 and TLS-ALPN-01\n"
|
||||||
}
|
}
|
||||||
|
|
||||||
if !strings.HasPrefix(viper.GetString("server_url"), "http://") && !strings.HasPrefix(viper.GetString("server_url"), "https://") {
|
if !strings.HasPrefix(viper.GetString("server_url"), "http://") &&
|
||||||
|
!strings.HasPrefix(viper.GetString("server_url"), "https://") {
|
||||||
errorText += "Fatal config error: server_url must start with https:// or http://\n"
|
errorText += "Fatal config error: server_url must start with https:// or http://\n"
|
||||||
}
|
}
|
||||||
if errorText != "" {
|
if errorText != "" {
|
||||||
|
@ -73,7 +77,35 @@ func LoadConfig(path string) error {
|
||||||
} else {
|
} else {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetDERPConfig() headscale.DERPConfig {
|
||||||
|
urlStrs := viper.GetStringSlice("derp.urls")
|
||||||
|
|
||||||
|
urls := make([]url.URL, len(urlStrs))
|
||||||
|
for index, urlStr := range urlStrs {
|
||||||
|
urlAddr, err := url.Parse(urlStr)
|
||||||
|
if err != nil {
|
||||||
|
log.Error().
|
||||||
|
Str("url", urlStr).
|
||||||
|
Err(err).
|
||||||
|
Msg("Failed to parse url, ignoring...")
|
||||||
|
}
|
||||||
|
|
||||||
|
urls[index] = *urlAddr
|
||||||
|
}
|
||||||
|
|
||||||
|
paths := viper.GetStringSlice("derp.paths")
|
||||||
|
|
||||||
|
autoUpdate := viper.GetBool("derp.auto_update_enabled")
|
||||||
|
updateFrequency := viper.GetDuration("derp.update_frequency")
|
||||||
|
|
||||||
|
return headscale.DERPConfig{
|
||||||
|
URLs: urls,
|
||||||
|
Paths: paths,
|
||||||
|
AutoUpdate: autoUpdate,
|
||||||
|
UpdateFrequency: updateFrequency,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetDNSConfig() (*tailcfg.DNSConfig, string) {
|
func GetDNSConfig() (*tailcfg.DNSConfig, string) {
|
||||||
|
@ -171,33 +203,30 @@ func absPath(path string) string {
|
||||||
}
|
}
|
||||||
|
|
||||||
func getHeadscaleApp() (*headscale.Headscale, error) {
|
func getHeadscaleApp() (*headscale.Headscale, error) {
|
||||||
derpPath := absPath(viper.GetString("derp_map_path"))
|
|
||||||
derpMap, err := loadDerpMap(derpPath)
|
|
||||||
if err != nil {
|
|
||||||
log.Error().
|
|
||||||
Str("path", derpPath).
|
|
||||||
Err(err).
|
|
||||||
Msg("Could not load DERP servers map file")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Minimum inactivity time out is keepalive timeout (60s) plus a few seconds
|
// Minimum inactivity time out is keepalive timeout (60s) plus a few seconds
|
||||||
// to avoid races
|
// to avoid races
|
||||||
minInactivityTimeout, _ := time.ParseDuration("65s")
|
minInactivityTimeout, _ := time.ParseDuration("65s")
|
||||||
if viper.GetDuration("ephemeral_node_inactivity_timeout") <= minInactivityTimeout {
|
if viper.GetDuration("ephemeral_node_inactivity_timeout") <= minInactivityTimeout {
|
||||||
err = fmt.Errorf("ephemeral_node_inactivity_timeout (%s) is set too low, must be more than %s\n", viper.GetString("ephemeral_node_inactivity_timeout"), minInactivityTimeout)
|
err := fmt.Errorf(
|
||||||
|
"ephemeral_node_inactivity_timeout (%s) is set too low, must be more than %s\n",
|
||||||
|
viper.GetString("ephemeral_node_inactivity_timeout"),
|
||||||
|
minInactivityTimeout,
|
||||||
|
)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
dnsConfig, baseDomain := GetDNSConfig()
|
dnsConfig, baseDomain := GetDNSConfig()
|
||||||
|
derpConfig := GetDERPConfig()
|
||||||
|
|
||||||
cfg := headscale.Config{
|
cfg := headscale.Config{
|
||||||
ServerURL: viper.GetString("server_url"),
|
ServerURL: viper.GetString("server_url"),
|
||||||
Addr: viper.GetString("listen_addr"),
|
Addr: viper.GetString("listen_addr"),
|
||||||
PrivateKeyPath: absPath(viper.GetString("private_key_path")),
|
PrivateKeyPath: absPath(viper.GetString("private_key_path")),
|
||||||
DerpMap: derpMap,
|
|
||||||
IPPrefix: netaddr.MustParseIPPrefix(viper.GetString("ip_prefix")),
|
IPPrefix: netaddr.MustParseIPPrefix(viper.GetString("ip_prefix")),
|
||||||
BaseDomain: baseDomain,
|
BaseDomain: baseDomain,
|
||||||
|
|
||||||
|
DERP: derpConfig,
|
||||||
|
|
||||||
EphemeralNodeInactivityTimeout: viper.GetDuration("ephemeral_node_inactivity_timeout"),
|
EphemeralNodeInactivityTimeout: viper.GetDuration("ephemeral_node_inactivity_timeout"),
|
||||||
|
|
||||||
DBtype: viper.GetString("db_type"),
|
DBtype: viper.GetString("db_type"),
|
||||||
|
@ -243,21 +272,6 @@ func getHeadscaleApp() (*headscale.Headscale, error) {
|
||||||
return h, nil
|
return h, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func loadDerpMap(path string) (*tailcfg.DERPMap, error) {
|
|
||||||
derpFile, err := os.Open(path)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer derpFile.Close()
|
|
||||||
var derpMap tailcfg.DERPMap
|
|
||||||
b, err := io.ReadAll(derpFile)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
err = yaml.Unmarshal(b, &derpMap)
|
|
||||||
return &derpMap, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func JsonOutput(result interface{}, errResult error, outputFormat string) {
|
func JsonOutput(result interface{}, errResult error, outputFormat string) {
|
||||||
var j []byte
|
var j []byte
|
||||||
var err error
|
var err error
|
||||||
|
|
|
@ -25,7 +25,6 @@ func (s *Suite) SetUpSuite(c *check.C) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Suite) TearDownSuite(c *check.C) {
|
func (s *Suite) TearDownSuite(c *check.C) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (*Suite) TestPostgresConfigLoading(c *check.C) {
|
func (*Suite) TestPostgresConfigLoading(c *check.C) {
|
||||||
|
@ -53,7 +52,6 @@ func (*Suite) TestPostgresConfigLoading(c *check.C) {
|
||||||
// Test that config file was interpreted correctly
|
// Test that config file was interpreted correctly
|
||||||
c.Assert(viper.GetString("server_url"), check.Equals, "http://127.0.0.1:8080")
|
c.Assert(viper.GetString("server_url"), check.Equals, "http://127.0.0.1:8080")
|
||||||
c.Assert(viper.GetString("listen_addr"), check.Equals, "0.0.0.0:8080")
|
c.Assert(viper.GetString("listen_addr"), check.Equals, "0.0.0.0:8080")
|
||||||
c.Assert(viper.GetString("derp_map_path"), check.Equals, "derp.yaml")
|
|
||||||
c.Assert(viper.GetString("db_type"), check.Equals, "postgres")
|
c.Assert(viper.GetString("db_type"), check.Equals, "postgres")
|
||||||
c.Assert(viper.GetString("db_port"), check.Equals, "5432")
|
c.Assert(viper.GetString("db_port"), check.Equals, "5432")
|
||||||
c.Assert(viper.GetString("tls_letsencrypt_hostname"), check.Equals, "")
|
c.Assert(viper.GetString("tls_letsencrypt_hostname"), check.Equals, "")
|
||||||
|
@ -86,7 +84,7 @@ func (*Suite) TestSqliteConfigLoading(c *check.C) {
|
||||||
// Test that config file was interpreted correctly
|
// Test that config file was interpreted correctly
|
||||||
c.Assert(viper.GetString("server_url"), check.Equals, "http://127.0.0.1:8080")
|
c.Assert(viper.GetString("server_url"), check.Equals, "http://127.0.0.1:8080")
|
||||||
c.Assert(viper.GetString("listen_addr"), check.Equals, "0.0.0.0:8080")
|
c.Assert(viper.GetString("listen_addr"), check.Equals, "0.0.0.0:8080")
|
||||||
c.Assert(viper.GetString("derp_map_path"), check.Equals, "derp.yaml")
|
c.Assert(viper.GetStringSlice("derp.paths")[0], check.Equals, "derp-example.yaml")
|
||||||
c.Assert(viper.GetString("db_type"), check.Equals, "sqlite3")
|
c.Assert(viper.GetString("db_type"), check.Equals, "sqlite3")
|
||||||
c.Assert(viper.GetString("db_path"), check.Equals, "db.sqlite")
|
c.Assert(viper.GetString("db_path"), check.Equals, "db.sqlite")
|
||||||
c.Assert(viper.GetString("tls_letsencrypt_hostname"), check.Equals, "")
|
c.Assert(viper.GetString("tls_letsencrypt_hostname"), check.Equals, "")
|
||||||
|
@ -128,7 +126,7 @@ func (*Suite) TestDNSConfigLoading(c *check.C) {
|
||||||
func writeConfig(c *check.C, tmpDir string, configYaml []byte) {
|
func writeConfig(c *check.C, tmpDir string, configYaml []byte) {
|
||||||
// Populate a custom config file
|
// Populate a custom config file
|
||||||
configFile := filepath.Join(tmpDir, "config.yaml")
|
configFile := filepath.Join(tmpDir, "config.yaml")
|
||||||
err := ioutil.WriteFile(configFile, configYaml, 0644)
|
err := ioutil.WriteFile(configFile, configYaml, 0o644)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.Fatalf("Couldn't write file %s", configFile)
|
c.Fatalf("Couldn't write file %s", configFile)
|
||||||
}
|
}
|
||||||
|
@ -142,7 +140,9 @@ func (*Suite) TestTLSConfigValidation(c *check.C) {
|
||||||
// defer os.RemoveAll(tmpDir)
|
// defer os.RemoveAll(tmpDir)
|
||||||
fmt.Println(tmpDir)
|
fmt.Println(tmpDir)
|
||||||
|
|
||||||
configYaml := []byte("---\ntls_letsencrypt_hostname: \"example.com\"\ntls_letsencrypt_challenge_type: \"\"\ntls_cert_path: \"abc.pem\"")
|
configYaml := []byte(
|
||||||
|
"---\ntls_letsencrypt_hostname: \"example.com\"\ntls_letsencrypt_challenge_type: \"\"\ntls_cert_path: \"abc.pem\"",
|
||||||
|
)
|
||||||
writeConfig(c, tmpDir, configYaml)
|
writeConfig(c, tmpDir, configYaml)
|
||||||
|
|
||||||
// Check configuration validation errors (1)
|
// Check configuration validation errors (1)
|
||||||
|
@ -150,13 +150,23 @@ func (*Suite) TestTLSConfigValidation(c *check.C) {
|
||||||
c.Assert(err, check.NotNil)
|
c.Assert(err, check.NotNil)
|
||||||
// check.Matches can not handle multiline strings
|
// check.Matches can not handle multiline strings
|
||||||
tmp := strings.ReplaceAll(err.Error(), "\n", "***")
|
tmp := strings.ReplaceAll(err.Error(), "\n", "***")
|
||||||
c.Assert(tmp, check.Matches, ".*Fatal config error: set either tls_letsencrypt_hostname or tls_cert_path/tls_key_path, not both.*")
|
c.Assert(
|
||||||
c.Assert(tmp, check.Matches, ".*Fatal config error: the only supported values for tls_letsencrypt_challenge_type are.*")
|
tmp,
|
||||||
|
check.Matches,
|
||||||
|
".*Fatal config error: set either tls_letsencrypt_hostname or tls_cert_path/tls_key_path, not both.*",
|
||||||
|
)
|
||||||
|
c.Assert(
|
||||||
|
tmp,
|
||||||
|
check.Matches,
|
||||||
|
".*Fatal config error: the only supported values for tls_letsencrypt_challenge_type are.*",
|
||||||
|
)
|
||||||
c.Assert(tmp, check.Matches, ".*Fatal config error: server_url must start with https:// or http://.*")
|
c.Assert(tmp, check.Matches, ".*Fatal config error: server_url must start with https:// or http://.*")
|
||||||
fmt.Println(tmp)
|
fmt.Println(tmp)
|
||||||
|
|
||||||
// Check configuration validation errors (2)
|
// Check configuration validation errors (2)
|
||||||
configYaml = []byte("---\nserver_url: \"http://127.0.0.1:8080\"\ntls_letsencrypt_hostname: \"example.com\"\ntls_letsencrypt_challenge_type: \"TLS-ALPN-01\"")
|
configYaml = []byte(
|
||||||
|
"---\nserver_url: \"http://127.0.0.1:8080\"\ntls_letsencrypt_hostname: \"example.com\"\ntls_letsencrypt_challenge_type: \"TLS-ALPN-01\"",
|
||||||
|
)
|
||||||
writeConfig(c, tmpDir, configYaml)
|
writeConfig(c, tmpDir, configYaml)
|
||||||
err = cli.LoadConfig(tmpDir)
|
err = cli.LoadConfig(tmpDir)
|
||||||
c.Assert(err, check.IsNil)
|
c.Assert(err, check.IsNil)
|
||||||
|
|
|
@ -2,7 +2,6 @@
|
||||||
server_url: http://127.0.0.1:8080
|
server_url: http://127.0.0.1:8080
|
||||||
listen_addr: 0.0.0.0:8080
|
listen_addr: 0.0.0.0:8080
|
||||||
private_key_path: private.key
|
private_key_path: private.key
|
||||||
derp_map_path: derp.yaml
|
|
||||||
ephemeral_node_inactivity_timeout: 30m
|
ephemeral_node_inactivity_timeout: 30m
|
||||||
|
|
||||||
# Postgres config
|
# Postgres config
|
||||||
|
|
|
@ -1,23 +1,40 @@
|
||||||
---
|
---
|
||||||
|
log_level: info
|
||||||
server_url: http://127.0.0.1:8080
|
server_url: http://127.0.0.1:8080
|
||||||
listen_addr: 0.0.0.0:8080
|
listen_addr: 0.0.0.0:8080
|
||||||
private_key_path: private.key
|
private_key_path: private.key
|
||||||
derp_map_path: derp.yaml
|
|
||||||
ephemeral_node_inactivity_timeout: 30m
|
ephemeral_node_inactivity_timeout: 30m
|
||||||
|
|
||||||
# SQLite config (uncomment it if you want to use SQLite)
|
# SQLite config (uncomment it if you want to use SQLite)
|
||||||
db_type: sqlite3
|
db_type: sqlite3
|
||||||
db_path: db.sqlite
|
db_path: db.sqlite
|
||||||
|
|
||||||
|
derp:
|
||||||
|
# List of externally available DERP maps encoded in JSON
|
||||||
|
urls:
|
||||||
|
- https://controlplane.tailscale.com/derpmap/default
|
||||||
|
|
||||||
|
# Locally available DERP map files encoded in YAML
|
||||||
|
paths:
|
||||||
|
- derp-example.yaml
|
||||||
|
|
||||||
|
# If enabled, a worker will be set up to periodically
|
||||||
|
# refresh the given sources and update the derpmap
|
||||||
|
# will be set up.
|
||||||
|
auto_update_enabled: true
|
||||||
|
|
||||||
|
# How often should we check for updates?
|
||||||
|
update_frequency: 24h
|
||||||
|
|
||||||
acme_url: https://acme-v02.api.letsencrypt.org/directory
|
acme_url: https://acme-v02.api.letsencrypt.org/directory
|
||||||
acme_email: ''
|
acme_email: ""
|
||||||
tls_letsencrypt_hostname: ''
|
tls_letsencrypt_hostname: ""
|
||||||
tls_letsencrypt_listen: ":http"
|
tls_letsencrypt_listen: ":http"
|
||||||
tls_letsencrypt_cache_dir: ".cache"
|
tls_letsencrypt_cache_dir: ".cache"
|
||||||
tls_letsencrypt_challenge_type: HTTP-01
|
tls_letsencrypt_challenge_type: HTTP-01
|
||||||
tls_cert_path: ''
|
tls_cert_path: ""
|
||||||
tls_key_path: ''
|
tls_key_path: ""
|
||||||
acl_policy_path: ''
|
acl_policy_path: ""
|
||||||
dns_config:
|
dns_config:
|
||||||
nameservers:
|
nameservers:
|
||||||
- 1.1.1.1
|
- 1.1.1.1
|
||||||
|
|
15
derp-example.yaml
Normal file
15
derp-example.yaml
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
# If you plan to somehow use headscale, please deploy your own DERP infra: https://tailscale.com/kb/1118/custom-derp-servers/
|
||||||
|
regions:
|
||||||
|
900:
|
||||||
|
regionid: 900
|
||||||
|
regioncode: custom
|
||||||
|
regionname: My Region
|
||||||
|
nodes:
|
||||||
|
- name: 1a
|
||||||
|
regionid: 1
|
||||||
|
hostname: myderp.mydomain.no
|
||||||
|
ipv4: 123.123.123.123
|
||||||
|
ipv6: "2604:a880:400:d1::828:b001"
|
||||||
|
stunport: 0
|
||||||
|
stunonly: false
|
||||||
|
derptestport: 0
|
152
derp.go
Normal file
152
derp.go
Normal file
|
@ -0,0 +1,152 @@
|
||||||
|
package headscale
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
|
|
||||||
|
"gopkg.in/yaml.v2"
|
||||||
|
|
||||||
|
"tailscale.com/tailcfg"
|
||||||
|
)
|
||||||
|
|
||||||
|
func loadDERPMapFromPath(path string) (*tailcfg.DERPMap, error) {
|
||||||
|
derpFile, err := os.Open(path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer derpFile.Close()
|
||||||
|
var derpMap tailcfg.DERPMap
|
||||||
|
b, err := io.ReadAll(derpFile)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
err = yaml.Unmarshal(b, &derpMap)
|
||||||
|
return &derpMap, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadDERPMapFromURL(addr url.URL) (*tailcfg.DERPMap, error) {
|
||||||
|
client := http.Client{
|
||||||
|
Timeout: 10 * time.Second,
|
||||||
|
}
|
||||||
|
resp, err := client.Get(addr.String())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
defer resp.Body.Close()
|
||||||
|
body, err := ioutil.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var derpMap tailcfg.DERPMap
|
||||||
|
err = json.Unmarshal(body, &derpMap)
|
||||||
|
return &derpMap, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// mergeDERPMaps naively merges a list of DERPMaps into a single
|
||||||
|
// DERPMap, it will _only_ look at the Regions, an integer.
|
||||||
|
// If a region exists in two of the given DERPMaps, the region
|
||||||
|
// form the _last_ DERPMap will be preserved.
|
||||||
|
// An empty DERPMap list will result in a DERPMap with no regions
|
||||||
|
func mergeDERPMaps(derpMaps []*tailcfg.DERPMap) *tailcfg.DERPMap {
|
||||||
|
result := tailcfg.DERPMap{
|
||||||
|
OmitDefaultRegions: false,
|
||||||
|
Regions: map[int]*tailcfg.DERPRegion{},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, derpMap := range derpMaps {
|
||||||
|
for id, region := range derpMap.Regions {
|
||||||
|
result.Regions[id] = region
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &result
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetDERPMap(cfg DERPConfig) *tailcfg.DERPMap {
|
||||||
|
derpMaps := make([]*tailcfg.DERPMap, 0)
|
||||||
|
|
||||||
|
for _, path := range cfg.Paths {
|
||||||
|
log.Debug().
|
||||||
|
Str("func", "GetDERPMap").
|
||||||
|
Str("path", path).
|
||||||
|
Msg("Loading DERPMap from path")
|
||||||
|
derpMap, err := loadDERPMapFromPath(path)
|
||||||
|
if err != nil {
|
||||||
|
log.Error().
|
||||||
|
Str("func", "GetDERPMap").
|
||||||
|
Str("path", path).
|
||||||
|
Err(err).
|
||||||
|
Msg("Could not load DERP map from path")
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
derpMaps = append(derpMaps, derpMap)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, addr := range cfg.URLs {
|
||||||
|
derpMap, err := loadDERPMapFromURL(addr)
|
||||||
|
log.Debug().
|
||||||
|
Str("func", "GetDERPMap").
|
||||||
|
Str("url", addr.String()).
|
||||||
|
Msg("Loading DERPMap from path")
|
||||||
|
if err != nil {
|
||||||
|
log.Error().
|
||||||
|
Str("func", "GetDERPMap").
|
||||||
|
Str("url", addr.String()).
|
||||||
|
Err(err).
|
||||||
|
Msg("Could not load DERP map from path")
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
derpMaps = append(derpMaps, derpMap)
|
||||||
|
}
|
||||||
|
|
||||||
|
derpMap := mergeDERPMaps(derpMaps)
|
||||||
|
|
||||||
|
log.Trace().Interface("derpMap", derpMap).Msg("DERPMap loaded")
|
||||||
|
|
||||||
|
if len(derpMap.Regions) == 0 {
|
||||||
|
log.Warn().
|
||||||
|
Msg("DERP map is empty, not a single DERP map datasource was loaded correctly or contained a region")
|
||||||
|
}
|
||||||
|
|
||||||
|
return derpMap
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Headscale) scheduledDERPMapUpdateWorker(cancelChan <-chan struct{}) {
|
||||||
|
log.Info().
|
||||||
|
Dur("frequency", h.cfg.DERP.UpdateFrequency).
|
||||||
|
Msg("Setting up a DERPMap update worker")
|
||||||
|
ticker := time.NewTicker(h.cfg.DERP.UpdateFrequency)
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-cancelChan:
|
||||||
|
return
|
||||||
|
|
||||||
|
case <-ticker.C:
|
||||||
|
log.Info().Msg("Fetching DERPMap updates")
|
||||||
|
h.DERPMap = GetDERPMap(h.cfg.DERP)
|
||||||
|
|
||||||
|
namespaces, err := h.ListNamespaces()
|
||||||
|
if err != nil {
|
||||||
|
log.Error().
|
||||||
|
Err(err).
|
||||||
|
Msg("Failed to fetch namespaces")
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, namespace := range *namespaces {
|
||||||
|
h.setLastStateChangeToNow(namespace.Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
146
derp.yaml
146
derp.yaml
|
@ -1,146 +0,0 @@
|
||||||
# This file contains some of the official Tailscale DERP servers,
|
|
||||||
# shamelessly taken from https://github.com/tailscale/tailscale/blob/main/net/dnsfallback/dns-fallback-servers.json
|
|
||||||
#
|
|
||||||
# If you plan to somehow use headscale, please deploy your own DERP infra: https://tailscale.com/kb/1118/custom-derp-servers/
|
|
||||||
regions:
|
|
||||||
1:
|
|
||||||
regionid: 1
|
|
||||||
regioncode: nyc
|
|
||||||
regionname: New York City
|
|
||||||
nodes:
|
|
||||||
- name: 1a
|
|
||||||
regionid: 1
|
|
||||||
hostname: derp1.tailscale.com
|
|
||||||
ipv4: 159.89.225.99
|
|
||||||
ipv6: "2604:a880:400:d1::828:b001"
|
|
||||||
stunport: 0
|
|
||||||
stunonly: false
|
|
||||||
derptestport: 0
|
|
||||||
- name: 1b
|
|
||||||
regionid: 1
|
|
||||||
hostname: derp1b.tailscale.com
|
|
||||||
ipv4: 45.55.35.93
|
|
||||||
ipv6: "2604:a880:800:a1::f:2001"
|
|
||||||
stunport: 0
|
|
||||||
stunonly: false
|
|
||||||
derptestport: 0
|
|
||||||
2:
|
|
||||||
regionid: 2
|
|
||||||
regioncode: sfo
|
|
||||||
regionname: San Francisco
|
|
||||||
nodes:
|
|
||||||
- name: 2a
|
|
||||||
regionid: 2
|
|
||||||
hostname: derp2.tailscale.com
|
|
||||||
ipv4: 167.172.206.31
|
|
||||||
ipv6: "2604:a880:2:d1::c5:7001"
|
|
||||||
stunport: 0
|
|
||||||
stunonly: false
|
|
||||||
derptestport: 0
|
|
||||||
- name: 2b
|
|
||||||
regionid: 2
|
|
||||||
hostname: derp2b.tailscale.com
|
|
||||||
ipv4: 64.227.106.23
|
|
||||||
ipv6: "2604:a880:4:1d0::29:9000"
|
|
||||||
stunport: 0
|
|
||||||
stunonly: false
|
|
||||||
derptestport: 0
|
|
||||||
3:
|
|
||||||
regionid: 3
|
|
||||||
regioncode: sin
|
|
||||||
regionname: Singapore
|
|
||||||
nodes:
|
|
||||||
- name: 3a
|
|
||||||
regionid: 3
|
|
||||||
hostname: derp3.tailscale.com
|
|
||||||
ipv4: 68.183.179.66
|
|
||||||
ipv6: "2400:6180:0:d1::67d:8001"
|
|
||||||
stunport: 0
|
|
||||||
stunonly: false
|
|
||||||
derptestport: 0
|
|
||||||
4:
|
|
||||||
regionid: 4
|
|
||||||
regioncode: fra
|
|
||||||
regionname: Frankfurt
|
|
||||||
nodes:
|
|
||||||
- name: 4a
|
|
||||||
regionid: 4
|
|
||||||
hostname: derp4.tailscale.com
|
|
||||||
ipv4: 167.172.182.26
|
|
||||||
ipv6: "2a03:b0c0:3:e0::36e:900"
|
|
||||||
stunport: 0
|
|
||||||
stunonly: false
|
|
||||||
derptestport: 0
|
|
||||||
- name: 4b
|
|
||||||
regionid: 4
|
|
||||||
hostname: derp4b.tailscale.com
|
|
||||||
ipv4: 157.230.25.0
|
|
||||||
ipv6: "2a03:b0c0:3:e0::58f:3001"
|
|
||||||
stunport: 0
|
|
||||||
stunonly: false
|
|
||||||
derptestport: 0
|
|
||||||
5:
|
|
||||||
regionid: 5
|
|
||||||
regioncode: syd
|
|
||||||
regionname: Sydney
|
|
||||||
nodes:
|
|
||||||
- name: 5a
|
|
||||||
regionid: 5
|
|
||||||
hostname: derp5.tailscale.com
|
|
||||||
ipv4: 103.43.75.49
|
|
||||||
ipv6: "2001:19f0:5801:10b7:5400:2ff:feaa:284c"
|
|
||||||
stunport: 0
|
|
||||||
stunonly: false
|
|
||||||
derptestport: 0
|
|
||||||
6:
|
|
||||||
regionid: 6
|
|
||||||
regioncode: blr
|
|
||||||
regionname: Bangalore
|
|
||||||
nodes:
|
|
||||||
- name: 6a
|
|
||||||
regionid: 6
|
|
||||||
hostname: derp6.tailscale.com
|
|
||||||
ipv4: 68.183.90.120
|
|
||||||
ipv6: "2400:6180:100:d0::982:d001"
|
|
||||||
stunport: 0
|
|
||||||
stunonly: false
|
|
||||||
derptestport: 0
|
|
||||||
7:
|
|
||||||
regionid: 7
|
|
||||||
regioncode: tok
|
|
||||||
regionname: Tokyo
|
|
||||||
nodes:
|
|
||||||
- name: 7a
|
|
||||||
regionid: 7
|
|
||||||
hostname: derp7.tailscale.com
|
|
||||||
ipv4: 167.179.89.145
|
|
||||||
ipv6: "2401:c080:1000:467f:5400:2ff:feee:22aa"
|
|
||||||
stunport: 0
|
|
||||||
stunonly: false
|
|
||||||
derptestport: 0
|
|
||||||
8:
|
|
||||||
regionid: 8
|
|
||||||
regioncode: lhr
|
|
||||||
regionname: London
|
|
||||||
nodes:
|
|
||||||
- name: 8a
|
|
||||||
regionid: 8
|
|
||||||
hostname: derp8.tailscale.com
|
|
||||||
ipv4: 167.71.139.179
|
|
||||||
ipv6: "2a03:b0c0:1:e0::3cc:e001"
|
|
||||||
stunport: 0
|
|
||||||
stunonly: false
|
|
||||||
derptestport: 0
|
|
||||||
9:
|
|
||||||
regionid: 9
|
|
||||||
regioncode: sao
|
|
||||||
regionname: São Paulo
|
|
||||||
nodes:
|
|
||||||
- name: 9a
|
|
||||||
regionid: 9
|
|
||||||
hostname: derp9.tailscale.com
|
|
||||||
ipv4: 207.148.3.137
|
|
||||||
ipv6: "2001:19f0:6401:1d9c:5400:2ff:feef:bb82"
|
|
||||||
stunport: 0
|
|
||||||
stunonly: false
|
|
||||||
derptestport: 0
|
|
|
@ -230,7 +230,6 @@ func (s *IntegrationTestSuite) SetupSuite() {
|
||||||
Name: "headscale",
|
Name: "headscale",
|
||||||
Mounts: []string{
|
Mounts: []string{
|
||||||
fmt.Sprintf("%s/integration_test/etc:/etc/headscale", currentPath),
|
fmt.Sprintf("%s/integration_test/etc:/etc/headscale", currentPath),
|
||||||
fmt.Sprintf("%s/derp.yaml:/etc/headscale/derp.yaml", currentPath),
|
|
||||||
},
|
},
|
||||||
Networks: []*dockertest.Network{&network},
|
Networks: []*dockertest.Network{&network},
|
||||||
Cmd: []string{"headscale", "serve"},
|
Cmd: []string{"headscale", "serve"},
|
||||||
|
@ -289,7 +288,16 @@ func (s *IntegrationTestSuite) SetupSuite() {
|
||||||
fmt.Printf("Creating pre auth key for %s\n", namespace)
|
fmt.Printf("Creating pre auth key for %s\n", namespace)
|
||||||
authKey, err := executeCommand(
|
authKey, err := executeCommand(
|
||||||
&headscale,
|
&headscale,
|
||||||
[]string{"headscale", "--namespace", namespace, "preauthkeys", "create", "--reusable", "--expiration", "24h"},
|
[]string{
|
||||||
|
"headscale",
|
||||||
|
"--namespace",
|
||||||
|
namespace,
|
||||||
|
"preauthkeys",
|
||||||
|
"create",
|
||||||
|
"--reusable",
|
||||||
|
"--expiration",
|
||||||
|
"24h",
|
||||||
|
},
|
||||||
[]string{},
|
[]string{},
|
||||||
)
|
)
|
||||||
assert.Nil(s.T(), err)
|
assert.Nil(s.T(), err)
|
||||||
|
@ -298,7 +306,16 @@ func (s *IntegrationTestSuite) SetupSuite() {
|
||||||
|
|
||||||
fmt.Printf("Joining tailscale containers to headscale at %s\n", headscaleEndpoint)
|
fmt.Printf("Joining tailscale containers to headscale at %s\n", headscaleEndpoint)
|
||||||
for hostname, tailscale := range scales.tailscales {
|
for hostname, tailscale := range scales.tailscales {
|
||||||
command := []string{"tailscale", "up", "-login-server", headscaleEndpoint, "--authkey", strings.TrimSuffix(authKey, "\n"), "--hostname", hostname}
|
command := []string{
|
||||||
|
"tailscale",
|
||||||
|
"up",
|
||||||
|
"-login-server",
|
||||||
|
headscaleEndpoint,
|
||||||
|
"--authkey",
|
||||||
|
strings.TrimSuffix(authKey, "\n"),
|
||||||
|
"--hostname",
|
||||||
|
hostname,
|
||||||
|
}
|
||||||
|
|
||||||
fmt.Println("Join command:", command)
|
fmt.Println("Join command:", command)
|
||||||
fmt.Printf("Running join command for %s\n", hostname)
|
fmt.Printf("Running join command for %s\n", hostname)
|
||||||
|
@ -661,7 +678,13 @@ func (s *IntegrationTestSuite) TestMagicDNS() {
|
||||||
fmt.Sprintf("%s.%s.headscale.net", peername, namespace),
|
fmt.Sprintf("%s.%s.headscale.net", peername, namespace),
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("Pinging using Hostname (magicdns) from %s (%s) to %s (%s)\n", hostname, ips[hostname], peername, ip)
|
fmt.Printf(
|
||||||
|
"Pinging using Hostname (magicdns) from %s (%s) to %s (%s)\n",
|
||||||
|
hostname,
|
||||||
|
ips[hostname],
|
||||||
|
peername,
|
||||||
|
ip,
|
||||||
|
)
|
||||||
result, err := executeCommand(
|
result, err := executeCommand(
|
||||||
&tailscale,
|
&tailscale,
|
||||||
command,
|
command,
|
||||||
|
|
|
@ -1,19 +0,0 @@
|
||||||
{
|
|
||||||
"server_url": "http://headscale:8080",
|
|
||||||
"listen_addr": "0.0.0.0:8080",
|
|
||||||
"private_key_path": "private.key",
|
|
||||||
"derp_map_path": "derp.yaml",
|
|
||||||
"ephemeral_node_inactivity_timeout": "30m",
|
|
||||||
"db_type": "sqlite3",
|
|
||||||
"db_path": "/tmp/integration_test_db.sqlite3",
|
|
||||||
"acl_policy_path": "",
|
|
||||||
"log_level": "trace",
|
|
||||||
"dns_config": {
|
|
||||||
"nameservers": [
|
|
||||||
"1.1.1.1"
|
|
||||||
],
|
|
||||||
"domains": [],
|
|
||||||
"magic_dns": true,
|
|
||||||
"base_domain": "headscale.net"
|
|
||||||
}
|
|
||||||
}
|
|
20
integration_test/etc/config.yaml
Normal file
20
integration_test/etc/config.yaml
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
log_level: trace
|
||||||
|
acl_policy_path: ""
|
||||||
|
db_type: sqlite3
|
||||||
|
ephemeral_node_inactivity_timeout: 30m
|
||||||
|
dns_config:
|
||||||
|
base_domain: headscale.net
|
||||||
|
magic_dns: true
|
||||||
|
domains: []
|
||||||
|
nameservers:
|
||||||
|
- 1.1.1.1
|
||||||
|
db_path: /tmp/integration_test_db.sqlite3
|
||||||
|
private_key_path: private.key
|
||||||
|
listen_addr: 0.0.0.0:8080
|
||||||
|
server_url: http://headscale:8080
|
||||||
|
|
||||||
|
derp:
|
||||||
|
urls:
|
||||||
|
- https://controlplane.tailscale.com/derpmap/default
|
||||||
|
auto_update_enabled: false
|
||||||
|
update_frequency: 1m
|
Loading…
Reference in a new issue