Merge branch 'configurable-mtls' of github.com:arch4ngel/headscale into configurable-mtls

This commit is contained in:
Justin Angel 2022-01-31 07:19:14 -05:00
commit 52db80ab0d
12 changed files with 268 additions and 424 deletions

View File

@ -2,6 +2,13 @@
**TBD (TBD):** **TBD (TBD):**
**Changes**:
- Make gRPC Unix Socket permissions configurable [#292](https://github.com/juanfont/headscale/pull/292)
- Trim whitespace before reading Private Key from file [#289](https://github.com/juanfont/headscale/pull/289)
- Add new command to generate a private key for `headscale` [#290](https://github.com/juanfont/headscale/pull/290)
- Fixed issue where hosts deleted from control server may be written back to the database, as long as they are connected to the control server [#278](https://github.com/juanfont/headscale/pull/278)
**0.12.3 (2022-01-13):** **0.12.3 (2022-01-13):**
**Changes**: **Changes**:

View File

@ -25,8 +25,11 @@ const (
) )
const ( const (
Base8 = 8
Base10 = 10 Base10 = 10
BitSize16 = 16 BitSize16 = 16
BitSize32 = 32
BitSize64 = 64
portRangeBegin = 0 portRangeBegin = 0
portRangeEnd = 65535 portRangeEnd = 65535
expectedTokenItems = 2 expectedTokenItems = 2

9
app.go
View File

@ -6,6 +6,7 @@ import (
"errors" "errors"
"fmt" "fmt"
"io" "io"
"io/fs"
"net" "net"
"net/http" "net/http"
"net/url" "net/url"
@ -100,7 +101,8 @@ type Config struct {
DNSConfig *tailcfg.DNSConfig DNSConfig *tailcfg.DNSConfig
UnixSocket string UnixSocket string
UnixSocketPermission fs.FileMode
OIDC OIDCConfig OIDC OIDCConfig
@ -431,6 +433,11 @@ func (h *Headscale) Serve() error {
return fmt.Errorf("failed to set up gRPC socket: %w", err) return fmt.Errorf("failed to set up gRPC socket: %w", err)
} }
// Change socket permissions
if err := os.Chmod(h.cfg.UnixSocket, h.cfg.UnixSocketPermission); err != nil {
return fmt.Errorf("failed change permission of gRPC socket: %w", err)
}
// Handle common process-killing signals so we can gracefully shut down: // Handle common process-killing signals so we can gracefully shut down:
sigc := make(chan os.Signal, 1) sigc := make(chan os.Signal, 1)
signal.Notify(sigc, os.Interrupt, syscall.SIGTERM) signal.Notify(sigc, os.Interrupt, syscall.SIGTERM)

View File

@ -0,0 +1,41 @@
package cli
import (
"fmt"
"github.com/spf13/cobra"
"tailscale.com/types/key"
)
func init() {
rootCmd.AddCommand(generateCmd)
generateCmd.AddCommand(generatePrivateKeyCmd)
}
var generateCmd = &cobra.Command{
Use: "generate",
Short: "Generate commands",
}
var generatePrivateKeyCmd = &cobra.Command{
Use: "private-key",
Short: "Generate a private key for the headscale server",
Run: func(cmd *cobra.Command, args []string) {
output, _ := cmd.Flags().GetString("output")
machineKey := key.NewMachine()
machineKeyStr, err := machineKey.MarshalText()
if err != nil {
ErrorOutput(
err,
fmt.Sprintf("Error getting machine key from flag: %s", err),
output,
)
}
SuccessOutput(map[string]string{
"private_key": string(machineKeyStr),
},
string(machineKeyStr), output)
},
}

View File

@ -5,10 +5,12 @@ import (
"encoding/json" "encoding/json"
"errors" "errors"
"fmt" "fmt"
"io/fs"
"net/url" "net/url"
"os" "os"
"path/filepath" "path/filepath"
"regexp" "regexp"
"strconv"
"strings" "strings"
"time" "time"
@ -23,6 +25,10 @@ import (
"tailscale.com/types/dnstype" "tailscale.com/types/dnstype"
) )
const (
PermissionFallback = 0o700
)
func LoadConfig(path string) error { func LoadConfig(path string) error {
viper.SetConfigName("config") viper.SetConfigName("config")
if path == "" { if path == "" {
@ -49,6 +55,7 @@ func LoadConfig(path string) error {
viper.SetDefault("dns_config", nil) viper.SetDefault("dns_config", nil)
viper.SetDefault("unix_socket", "/var/run/headscale.sock") viper.SetDefault("unix_socket", "/var/run/headscale.sock")
viper.SetDefault("unix_socket_permission", "0o770")
viper.SetDefault("cli.insecure", false) viper.SetDefault("cli.insecure", false)
viper.SetDefault("cli.timeout", "5s") viper.SetDefault("cli.timeout", "5s")
@ -265,7 +272,8 @@ func getHeadscaleConfig() headscale.Config {
ACMEEmail: viper.GetString("acme_email"), ACMEEmail: viper.GetString("acme_email"),
ACMEURL: viper.GetString("acme_url"), ACMEURL: viper.GetString("acme_url"),
UnixSocket: viper.GetString("unix_socket"), UnixSocket: viper.GetString("unix_socket"),
UnixSocketPermission: GetFileMode("unix_socket_permission"),
OIDC: headscale.OIDCConfig{ OIDC: headscale.OIDCConfig{
Issuer: viper.GetString("oidc.issuer"), Issuer: viper.GetString("oidc.issuer"),
@ -456,3 +464,14 @@ func loadOIDCMatchMap() map[string]string {
return strMap return strMap
} }
func GetFileMode(key string) fs.FileMode {
modeStr := viper.GetString(key)
mode, err := strconv.ParseUint(modeStr, headscale.Base8, headscale.BitSize64)
if err != nil {
return PermissionFallback
}
return fs.FileMode(mode)
}

View File

@ -1,6 +1,7 @@
package main package main
import ( import (
"io/fs"
"io/ioutil" "io/ioutil"
"os" "os"
"path/filepath" "path/filepath"
@ -60,6 +61,7 @@ func (*Suite) TestConfigLoading(c *check.C) {
c.Assert(viper.GetString("tls_letsencrypt_listen"), check.Equals, ":http") c.Assert(viper.GetString("tls_letsencrypt_listen"), check.Equals, ":http")
c.Assert(viper.GetString("tls_letsencrypt_challenge_type"), check.Equals, "HTTP-01") c.Assert(viper.GetString("tls_letsencrypt_challenge_type"), check.Equals, "HTTP-01")
c.Assert(viper.GetStringSlice("dns_config.nameservers")[0], check.Equals, "1.1.1.1") c.Assert(viper.GetStringSlice("dns_config.nameservers")[0], check.Equals, "1.1.1.1")
c.Assert(cli.GetFileMode("unix_socket_permission"), check.Equals, fs.FileMode(0o770))
} }
func (*Suite) TestDNSConfigLoading(c *check.C) { func (*Suite) TestDNSConfigLoading(c *check.C) {

View File

@ -156,6 +156,7 @@ dns_config:
# Note: for local development, you probably want to change this to: # Note: for local development, you probably want to change this to:
# unix_socket: ./headscale.sock # unix_socket: ./headscale.sock
unix_socket: /var/run/headscale.sock unix_socket: /var/run/headscale.sock
unix_socket_permission: "0770"
# #
# headscale supports experimental OpenID connect support, # headscale supports experimental OpenID connect support,
# it is still being tested and might have some bugs, please # it is still being tested and might have some bugs, please

30
go.mod
View File

@ -21,12 +21,12 @@ require (
github.com/rs/zerolog v1.26.0 github.com/rs/zerolog v1.26.0
github.com/soheilhy/cmux v0.1.5 github.com/soheilhy/cmux v0.1.5
github.com/spf13/cobra v1.2.1 github.com/spf13/cobra v1.2.1
github.com/spf13/viper v1.8.1 github.com/spf13/viper v1.9.0
github.com/stretchr/testify v1.7.0 github.com/stretchr/testify v1.7.0
github.com/tailscale/hujson v0.0.0-20210923003652-c3758b31534b github.com/tailscale/hujson v0.0.0-20211105212140-3a0adc019d83
github.com/tcnksm/go-latest v0.0.0-20170313132115-e3007ae9052e github.com/tcnksm/go-latest v0.0.0-20170313132115-e3007ae9052e
github.com/zsais/go-gin-prometheus v0.1.0 github.com/zsais/go-gin-prometheus v0.1.0
golang.org/x/crypto v0.0.0-20211117183948-ae814b36b871 golang.org/x/crypto v0.0.0-20211202192323-5770296d904e
golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
google.golang.org/genproto v0.0.0-20211104193956-4c6863e31247 google.golang.org/genproto v0.0.0-20211104193956-4c6863e31247
@ -40,12 +40,12 @@ require (
gorm.io/driver/sqlite v1.1.5 gorm.io/driver/sqlite v1.1.5
gorm.io/gorm v1.21.15 gorm.io/gorm v1.21.15
inet.af/netaddr v0.0.0-20211027220019-c74959edd3b6 inet.af/netaddr v0.0.0-20211027220019-c74959edd3b6
tailscale.com v1.18.1 tailscale.com v1.20.3
) )
require ( require (
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect
github.com/Microsoft/go-winio v0.5.0 // indirect github.com/Microsoft/go-winio v0.5.1 // indirect
github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 // indirect github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 // indirect
github.com/atomicgo/cursor v0.0.1 // indirect github.com/atomicgo/cursor v0.0.1 // indirect
github.com/beorn7/perks v1.0.1 // indirect github.com/beorn7/perks v1.0.1 // indirect
@ -57,7 +57,7 @@ require (
github.com/docker/docker v20.10.8+incompatible // indirect github.com/docker/docker v20.10.8+incompatible // indirect
github.com/docker/go-connections v0.4.0 // indirect github.com/docker/go-connections v0.4.0 // indirect
github.com/docker/go-units v0.4.0 // indirect github.com/docker/go-units v0.4.0 // indirect
github.com/fsnotify/fsnotify v1.4.9 // indirect github.com/fsnotify/fsnotify v1.5.1 // indirect
github.com/ghodss/yaml v1.0.0 // indirect github.com/ghodss/yaml v1.0.0 // indirect
github.com/gin-contrib/sse v0.1.0 // indirect github.com/gin-contrib/sse v0.1.0 // indirect
github.com/go-playground/locales v0.14.0 // indirect github.com/go-playground/locales v0.14.0 // indirect
@ -92,30 +92,30 @@ require (
github.com/leodido/go-urn v1.2.1 // indirect github.com/leodido/go-urn v1.2.1 // indirect
github.com/lib/pq v1.10.3 // indirect github.com/lib/pq v1.10.3 // indirect
github.com/magiconair/properties v1.8.5 // indirect github.com/magiconair/properties v1.8.5 // indirect
github.com/mattn/go-colorable v0.1.8 // indirect github.com/mattn/go-colorable v0.1.12 // indirect
github.com/mattn/go-isatty v0.0.14 // indirect github.com/mattn/go-isatty v0.0.14 // indirect
github.com/mattn/go-runewidth v0.0.13 // indirect github.com/mattn/go-runewidth v0.0.13 // indirect
github.com/mattn/go-sqlite3 v1.14.8 // indirect github.com/mattn/go-sqlite3 v1.14.8 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b // indirect github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b // indirect
github.com/mitchellh/mapstructure v1.4.1 // indirect github.com/mitchellh/mapstructure v1.4.3 // indirect
github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6 // indirect github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/opencontainers/image-spec v1.0.2 // indirect github.com/opencontainers/image-spec v1.0.2 // indirect
github.com/opencontainers/runc v1.0.3 // indirect github.com/opencontainers/runc v1.0.3 // indirect
github.com/pelletier/go-toml v1.9.3 // indirect github.com/pelletier/go-toml v1.9.4 // indirect
github.com/pkg/errors v0.9.1 // indirect github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/prometheus/client_model v0.2.0 // indirect github.com/prometheus/client_model v0.2.0 // indirect
github.com/prometheus/common v0.32.1 // indirect github.com/prometheus/common v0.32.1 // indirect
github.com/prometheus/procfs v0.7.3 // indirect github.com/prometheus/procfs v0.7.3 // indirect
github.com/rivo/uniseg v0.2.0 // indirect github.com/rivo/uniseg v0.2.0 // indirect
github.com/rogpeppe/go-internal v1.8.0 // indirect github.com/rogpeppe/go-internal v1.8.1-0.20211023094830-115ce09fd6b4 // indirect
github.com/sirupsen/logrus v1.8.1 // indirect github.com/sirupsen/logrus v1.8.1 // indirect
github.com/spf13/afero v1.6.0 // indirect github.com/spf13/afero v1.6.0 // indirect
github.com/spf13/cast v1.3.1 // indirect github.com/spf13/cast v1.4.1 // indirect
github.com/spf13/jwalterweatherman v1.1.0 // indirect github.com/spf13/jwalterweatherman v1.1.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect github.com/spf13/pflag v1.0.5 // indirect
github.com/subosito/gotenv v1.2.0 // indirect github.com/subosito/gotenv v1.2.0 // indirect
@ -127,12 +127,12 @@ require (
go4.org/intern v0.0.0-20211027215823-ae77deb06f29 // indirect go4.org/intern v0.0.0-20211027215823-ae77deb06f29 // indirect
go4.org/mem v0.0.0-20210711025021-927187094b94 // indirect go4.org/mem v0.0.0-20210711025021-927187094b94 // indirect
go4.org/unsafe/assume-no-moving-gc v0.0.0-20211027215541-db492cf91b37 // indirect go4.org/unsafe/assume-no-moving-gc v0.0.0-20211027215541-db492cf91b37 // indirect
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 // indirect golang.org/x/net v0.0.0-20211205041911-012df41ee64c // indirect
golang.org/x/sys v0.0.0-20211124211545-fe61309f8881 // indirect golang.org/x/sys v0.0.0-20211205182925-97ca703d548d // indirect
golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b // indirect golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect
golang.org/x/text v0.3.7 // indirect golang.org/x/text v0.3.7 // indirect
google.golang.org/appengine v1.6.7 // indirect google.golang.org/appengine v1.6.7 // indirect
gopkg.in/ini.v1 v1.62.0 // indirect gopkg.in/ini.v1 v1.66.2 // indirect
gopkg.in/square/go-jose.v2 v2.6.0 // indirect gopkg.in/square/go-jose.v2 v2.6.0 // indirect
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
) )

484
go.sum

File diff suppressed because it is too large Load Diff

View File

@ -28,7 +28,7 @@ import (
"tailscale.com/ipn/ipnstate" "tailscale.com/ipn/ipnstate"
) )
var tailscaleVersions = []string{"1.20.0", "1.18.2", "1.16.2", "1.14.3", "1.12.3"} var tailscaleVersions = []string{"1.20.2", "1.18.2", "1.16.2", "1.14.3", "1.12.3"}
type TestNamespace struct { type TestNamespace struct {
count int count int

View File

@ -319,6 +319,14 @@ func (h *Headscale) DeleteMachine(machine *Machine) error {
return h.RequestMapUpdates(namespaceID) return h.RequestMapUpdates(namespaceID)
} }
func (h *Headscale) TouchMachine(machine *Machine) error {
return h.db.Updates(Machine{
ID: machine.ID,
LastSeen: machine.LastSeen,
LastSuccessfulUpdate: machine.LastSuccessfulUpdate,
}).Error
}
// HardDeleteMachine hard deletes a Machine from the database. // HardDeleteMachine hard deletes a Machine from the database.
func (h *Headscale) HardDeleteMachine(machine *Machine) error { func (h *Headscale) HardDeleteMachine(machine *Machine) error {
err := h.RemoveSharedMachineFromAllNamespaces(machine) err := h.RemoveSharedMachineFromAllNamespaces(machine)

84
poll.go
View File

@ -102,7 +102,7 @@ func (h *Headscale) PollNetMapHandler(ctx *gin.Context) {
machine.Endpoints = datatypes.JSON(endpoints) machine.Endpoints = datatypes.JSON(endpoints)
machine.LastSeen = &now machine.LastSeen = &now
} }
h.db.Save(&machine) h.db.Updates(machine)
data, err := h.getMapResponse(machineKey, req, machine) data, err := h.getMapResponse(machineKey, req, machine)
if err != nil { if err != nil {
@ -291,6 +291,10 @@ func (h *Headscale) PollNetMapStream(
Str("channel", "pollData"). Str("channel", "pollData").
Err(err). Err(err).
Msg("Cannot update machine from database") Msg("Cannot update machine from database")
// client has been removed from database
// since the stream opened, terminate connection.
return false
} }
now := time.Now().UTC() now := time.Now().UTC()
machine.LastSeen = &now machine.LastSeen = &now
@ -299,13 +303,22 @@ func (h *Headscale) PollNetMapStream(
Set(float64(now.Unix())) Set(float64(now.Unix()))
machine.LastSuccessfulUpdate = &now machine.LastSuccessfulUpdate = &now
h.db.Save(&machine) err = h.TouchMachine(machine)
log.Trace(). if err != nil {
Str("handler", "PollNetMapStream"). log.Error().
Str("machine", machine.Name). Str("handler", "PollNetMapStream").
Str("channel", "pollData"). Str("machine", machine.Name).
Int("bytes", len(data)). Str("channel", "pollData").
Msg("Machine entry in database updated successfully after sending pollData") Err(err).
Msg("Cannot update machine LastSuccessfulUpdate")
} else {
log.Trace().
Str("handler", "PollNetMapStream").
Str("machine", machine.Name).
Str("channel", "pollData").
Int("bytes", len(data)).
Msg("Machine entry in database updated successfully after sending pollData")
}
return true return true
@ -344,16 +357,29 @@ func (h *Headscale) PollNetMapStream(
Str("channel", "keepAlive"). Str("channel", "keepAlive").
Err(err). Err(err).
Msg("Cannot update machine from database") Msg("Cannot update machine from database")
// client has been removed from database
// since the stream opened, terminate connection.
return false
} }
now := time.Now().UTC() now := time.Now().UTC()
machine.LastSeen = &now machine.LastSeen = &now
h.db.Save(&machine) err = h.TouchMachine(machine)
log.Trace(). if err != nil {
Str("handler", "PollNetMapStream"). log.Error().
Str("machine", machine.Name). Str("handler", "PollNetMapStream").
Str("channel", "keepAlive"). Str("machine", machine.Name).
Int("bytes", len(data)). Str("channel", "keepAlive").
Msg("Machine updated successfully after sending keep alive") Err(err).
Msg("Cannot update machine LastSeen")
} else {
log.Trace().
Str("handler", "PollNetMapStream").
Str("machine", machine.Name).
Str("channel", "keepAlive").
Int("bytes", len(data)).
Msg("Machine updated successfully after sending keep alive")
}
return true return true
@ -417,6 +443,10 @@ func (h *Headscale) PollNetMapStream(
Str("channel", "update"). Str("channel", "update").
Err(err). Err(err).
Msg("Cannot update machine from database") Msg("Cannot update machine from database")
// client has been removed from database
// since the stream opened, terminate connection.
return false
} }
now := time.Now().UTC() now := time.Now().UTC()
@ -424,7 +454,15 @@ func (h *Headscale) PollNetMapStream(
Set(float64(now.Unix())) Set(float64(now.Unix()))
machine.LastSuccessfulUpdate = &now machine.LastSuccessfulUpdate = &now
h.db.Save(&machine) err = h.TouchMachine(machine)
if err != nil {
log.Error().
Str("handler", "PollNetMapStream").
Str("machine", machine.Name).
Str("channel", "update").
Err(err).
Msg("Cannot update machine LastSuccessfulUpdate")
}
} else { } else {
log.Trace(). log.Trace().
Str("handler", "PollNetMapStream"). Str("handler", "PollNetMapStream").
@ -452,10 +490,22 @@ func (h *Headscale) PollNetMapStream(
Str("channel", "Done"). Str("channel", "Done").
Err(err). Err(err).
Msg("Cannot update machine from database") Msg("Cannot update machine from database")
// client has been removed from database
// since the stream opened, terminate connection.
return false
} }
now := time.Now().UTC() now := time.Now().UTC()
machine.LastSeen = &now machine.LastSeen = &now
h.db.Save(&machine) err = h.TouchMachine(machine)
if err != nil {
log.Error().
Str("handler", "PollNetMapStream").
Str("machine", machine.Name).
Str("channel", "Done").
Err(err).
Msg("Cannot update machine LastSeen")
}
log.Trace(). log.Trace().
Str("handler", "PollNetMapStream"). Str("handler", "PollNetMapStream").