mirror of
https://github.com/juanfont/headscale.git
synced 2024-11-26 08:53:05 +00:00
Go format with shorter lines
This commit is contained in:
parent
edfcdc466c
commit
03b7ec62ca
35 changed files with 794 additions and 192 deletions
4
acls.go
4
acls.go
|
@ -108,7 +108,9 @@ func (h *Headscale) generateACLPolicySrcIP(u string) ([]string, error) {
|
||||||
return h.expandAlias(u)
|
return h.expandAlias(u)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Headscale) generateACLPolicyDestPorts(d string) ([]tailcfg.NetPortRange, error) {
|
func (h *Headscale) generateACLPolicyDestPorts(
|
||||||
|
d string,
|
||||||
|
) ([]tailcfg.NetPortRange, error) {
|
||||||
tokens := strings.Split(d, ":")
|
tokens := strings.Split(d, ":")
|
||||||
if len(tokens) < 2 || len(tokens) > 3 {
|
if len(tokens) < 2 || len(tokens) > 3 {
|
||||||
return nil, errorInvalidPortFormat
|
return nil, errorInvalidPortFormat
|
||||||
|
|
|
@ -22,7 +22,11 @@ func (s *Suite) TestInvalidPolicyHuson(c *check.C) {
|
||||||
|
|
||||||
func (s *Suite) TestParseHosts(c *check.C) {
|
func (s *Suite) TestParseHosts(c *check.C) {
|
||||||
var hs Hosts
|
var hs Hosts
|
||||||
err := hs.UnmarshalJSON([]byte(`{"example-host-1": "100.100.100.100","example-host-2": "100.100.101.100/24"}`))
|
err := hs.UnmarshalJSON(
|
||||||
|
[]byte(
|
||||||
|
`{"example-host-1": "100.100.100.100","example-host-2": "100.100.101.100/24"}`,
|
||||||
|
),
|
||||||
|
)
|
||||||
c.Assert(hs, check.NotNil)
|
c.Assert(hs, check.NotNil)
|
||||||
c.Assert(err, check.IsNil)
|
c.Assert(err, check.IsNil)
|
||||||
}
|
}
|
||||||
|
|
55
api.go
55
api.go
|
@ -95,7 +95,8 @@ func (h *Headscale) RegistrationHandler(c *gin.Context) {
|
||||||
Str("handler", "Registration").
|
Str("handler", "Registration").
|
||||||
Err(err).
|
Err(err).
|
||||||
Msg("Could not create row")
|
Msg("Could not create row")
|
||||||
machineRegistrations.WithLabelValues("unknown", "web", "error", m.Namespace.Name).Inc()
|
machineRegistrations.WithLabelValues("unknown", "web", "error", m.Namespace.Name).
|
||||||
|
Inc()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
m = &newMachine
|
m = &newMachine
|
||||||
|
@ -156,11 +157,13 @@ func (h *Headscale) RegistrationHandler(c *gin.Context) {
|
||||||
Str("handler", "Registration").
|
Str("handler", "Registration").
|
||||||
Err(err).
|
Err(err).
|
||||||
Msg("Cannot encode message")
|
Msg("Cannot encode message")
|
||||||
machineRegistrations.WithLabelValues("update", "web", "error", m.Namespace.Name).Inc()
|
machineRegistrations.WithLabelValues("update", "web", "error", m.Namespace.Name).
|
||||||
|
Inc()
|
||||||
c.String(http.StatusInternalServerError, "")
|
c.String(http.StatusInternalServerError, "")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
machineRegistrations.WithLabelValues("update", "web", "success", m.Namespace.Name).Inc()
|
machineRegistrations.WithLabelValues("update", "web", "success", m.Namespace.Name).
|
||||||
|
Inc()
|
||||||
c.Data(200, "application/json; charset=utf-8", respBody)
|
c.Data(200, "application/json; charset=utf-8", respBody)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -195,11 +198,13 @@ func (h *Headscale) RegistrationHandler(c *gin.Context) {
|
||||||
Str("handler", "Registration").
|
Str("handler", "Registration").
|
||||||
Err(err).
|
Err(err).
|
||||||
Msg("Cannot encode message")
|
Msg("Cannot encode message")
|
||||||
machineRegistrations.WithLabelValues("new", "web", "error", m.Namespace.Name).Inc()
|
machineRegistrations.WithLabelValues("new", "web", "error", m.Namespace.Name).
|
||||||
|
Inc()
|
||||||
c.String(http.StatusInternalServerError, "")
|
c.String(http.StatusInternalServerError, "")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
machineRegistrations.WithLabelValues("new", "web", "success", m.Namespace.Name).Inc()
|
machineRegistrations.WithLabelValues("new", "web", "success", m.Namespace.Name).
|
||||||
|
Inc()
|
||||||
c.Data(200, "application/json; charset=utf-8", respBody)
|
c.Data(200, "application/json; charset=utf-8", respBody)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -234,7 +239,11 @@ func (h *Headscale) RegistrationHandler(c *gin.Context) {
|
||||||
Str("machine", m.Name).
|
Str("machine", m.Name).
|
||||||
Msg("The node is sending us a new NodeKey, sending auth url")
|
Msg("The node is sending us a new NodeKey, sending auth url")
|
||||||
if h.cfg.OIDC.Issuer != "" {
|
if h.cfg.OIDC.Issuer != "" {
|
||||||
resp.AuthURL = fmt.Sprintf("%s/oidc/register/%s", strings.TrimSuffix(h.cfg.ServerURL, "/"), mKey.HexString())
|
resp.AuthURL = fmt.Sprintf(
|
||||||
|
"%s/oidc/register/%s",
|
||||||
|
strings.TrimSuffix(h.cfg.ServerURL, "/"),
|
||||||
|
mKey.HexString(),
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
resp.AuthURL = fmt.Sprintf("%s/register?key=%s",
|
resp.AuthURL = fmt.Sprintf("%s/register?key=%s",
|
||||||
strings.TrimSuffix(h.cfg.ServerURL, "/"), mKey.HexString())
|
strings.TrimSuffix(h.cfg.ServerURL, "/"), mKey.HexString())
|
||||||
|
@ -257,7 +266,11 @@ func (h *Headscale) RegistrationHandler(c *gin.Context) {
|
||||||
c.Data(200, "application/json; charset=utf-8", respBody)
|
c.Data(200, "application/json; charset=utf-8", respBody)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Headscale) getMapResponse(mKey wgkey.Key, req tailcfg.MapRequest, m *Machine) ([]byte, error) {
|
func (h *Headscale) getMapResponse(
|
||||||
|
mKey wgkey.Key,
|
||||||
|
req tailcfg.MapRequest,
|
||||||
|
m *Machine,
|
||||||
|
) ([]byte, error) {
|
||||||
log.Trace().
|
log.Trace().
|
||||||
Str("func", "getMapResponse").
|
Str("func", "getMapResponse").
|
||||||
Str("machine", req.Hostinfo.Hostname).
|
Str("machine", req.Hostinfo.Hostname).
|
||||||
|
@ -291,7 +304,12 @@ func (h *Headscale) getMapResponse(mKey wgkey.Key, req tailcfg.MapRequest, m *Ma
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
dnsConfig, err := getMapResponseDNSConfig(h.cfg.DNSConfig, h.cfg.BaseDomain, *m, peers)
|
dnsConfig, err := getMapResponseDNSConfig(
|
||||||
|
h.cfg.DNSConfig,
|
||||||
|
h.cfg.BaseDomain,
|
||||||
|
*m,
|
||||||
|
peers,
|
||||||
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error().
|
log.Error().
|
||||||
Str("func", "getMapResponse").
|
Str("func", "getMapResponse").
|
||||||
|
@ -340,7 +358,11 @@ func (h *Headscale) getMapResponse(mKey wgkey.Key, req tailcfg.MapRequest, m *Ma
|
||||||
return data, nil
|
return data, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Headscale) getMapKeepAliveResponse(mKey wgkey.Key, req tailcfg.MapRequest, m *Machine) ([]byte, error) {
|
func (h *Headscale) getMapKeepAliveResponse(
|
||||||
|
mKey wgkey.Key,
|
||||||
|
req tailcfg.MapRequest,
|
||||||
|
m *Machine,
|
||||||
|
) ([]byte, error) {
|
||||||
resp := tailcfg.MapResponse{
|
resp := tailcfg.MapResponse{
|
||||||
KeepAlive: true,
|
KeepAlive: true,
|
||||||
}
|
}
|
||||||
|
@ -394,7 +416,8 @@ func (h *Headscale) handleAuthKey(
|
||||||
Err(err).
|
Err(err).
|
||||||
Msg("Cannot encode message")
|
Msg("Cannot encode message")
|
||||||
c.String(http.StatusInternalServerError, "")
|
c.String(http.StatusInternalServerError, "")
|
||||||
machineRegistrations.WithLabelValues("new", "authkey", "error", m.Namespace.Name).Inc()
|
machineRegistrations.WithLabelValues("new", "authkey", "error", m.Namespace.Name).
|
||||||
|
Inc()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
c.Data(401, "application/json; charset=utf-8", respBody)
|
c.Data(401, "application/json; charset=utf-8", respBody)
|
||||||
|
@ -402,7 +425,8 @@ func (h *Headscale) handleAuthKey(
|
||||||
Str("func", "handleAuthKey").
|
Str("func", "handleAuthKey").
|
||||||
Str("machine", m.Name).
|
Str("machine", m.Name).
|
||||||
Msg("Failed authentication via AuthKey")
|
Msg("Failed authentication via AuthKey")
|
||||||
machineRegistrations.WithLabelValues("new", "authkey", "error", m.Namespace.Name).Inc()
|
machineRegistrations.WithLabelValues("new", "authkey", "error", m.Namespace.Name).
|
||||||
|
Inc()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -416,7 +440,8 @@ func (h *Headscale) handleAuthKey(
|
||||||
Str("func", "handleAuthKey").
|
Str("func", "handleAuthKey").
|
||||||
Str("machine", m.Name).
|
Str("machine", m.Name).
|
||||||
Msg("Failed to find an available IP")
|
Msg("Failed to find an available IP")
|
||||||
machineRegistrations.WithLabelValues("new", "authkey", "error", m.Namespace.Name).Inc()
|
machineRegistrations.WithLabelValues("new", "authkey", "error", m.Namespace.Name).
|
||||||
|
Inc()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
log.Info().
|
log.Info().
|
||||||
|
@ -445,11 +470,13 @@ func (h *Headscale) handleAuthKey(
|
||||||
Str("machine", m.Name).
|
Str("machine", m.Name).
|
||||||
Err(err).
|
Err(err).
|
||||||
Msg("Cannot encode message")
|
Msg("Cannot encode message")
|
||||||
machineRegistrations.WithLabelValues("new", "authkey", "error", m.Namespace.Name).Inc()
|
machineRegistrations.WithLabelValues("new", "authkey", "error", m.Namespace.Name).
|
||||||
|
Inc()
|
||||||
c.String(http.StatusInternalServerError, "Extremely sad!")
|
c.String(http.StatusInternalServerError, "Extremely sad!")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
machineRegistrations.WithLabelValues("new", "authkey", "success", m.Namespace.Name).Inc()
|
machineRegistrations.WithLabelValues("new", "authkey", "success", m.Namespace.Name).
|
||||||
|
Inc()
|
||||||
c.Data(200, "application/json; charset=utf-8", respBody)
|
c.Data(200, "application/json; charset=utf-8", respBody)
|
||||||
log.Info().
|
log.Info().
|
||||||
Str("func", "handleAuthKey").
|
Str("func", "handleAuthKey").
|
||||||
|
|
75
app.go
75
app.go
|
@ -152,8 +152,14 @@ func NewHeadscale(cfg Config) (*Headscale, error) {
|
||||||
var dbString string
|
var dbString string
|
||||||
switch cfg.DBtype {
|
switch cfg.DBtype {
|
||||||
case "postgres":
|
case "postgres":
|
||||||
dbString = fmt.Sprintf("host=%s port=%d dbname=%s user=%s password=%s sslmode=disable", cfg.DBhost,
|
dbString = fmt.Sprintf(
|
||||||
cfg.DBport, cfg.DBname, cfg.DBuser, cfg.DBpass)
|
"host=%s port=%d dbname=%s user=%s password=%s sslmode=disable",
|
||||||
|
cfg.DBhost,
|
||||||
|
cfg.DBport,
|
||||||
|
cfg.DBname,
|
||||||
|
cfg.DBuser,
|
||||||
|
cfg.DBpass,
|
||||||
|
)
|
||||||
case "sqlite3":
|
case "sqlite3":
|
||||||
dbString = cfg.DBpath
|
dbString = cfg.DBpath
|
||||||
default:
|
default:
|
||||||
|
@ -182,7 +188,10 @@ func NewHeadscale(cfg Config) (*Headscale, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if h.cfg.DNSConfig != nil && h.cfg.DNSConfig.Proxied { // if MagicDNS
|
if h.cfg.DNSConfig != nil && h.cfg.DNSConfig.Proxied { // if MagicDNS
|
||||||
magicDNSDomains, err := generateMagicDNSRootDomains(h.cfg.IPPrefix, h.cfg.BaseDomain)
|
magicDNSDomains, err := generateMagicDNSRootDomains(
|
||||||
|
h.cfg.IPPrefix,
|
||||||
|
h.cfg.BaseDomain,
|
||||||
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -224,7 +233,10 @@ func (h *Headscale) expireEphemeralNodesWorker() {
|
||||||
for _, ns := range namespaces {
|
for _, ns := range namespaces {
|
||||||
machines, err := h.ListMachinesInNamespace(ns.Name)
|
machines, err := h.ListMachinesInNamespace(ns.Name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error().Err(err).Str("namespace", ns.Name).Msg("Error listing machines in namespace")
|
log.Error().
|
||||||
|
Err(err).
|
||||||
|
Str("namespace", ns.Name).
|
||||||
|
Msg("Error listing machines in namespace")
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -232,7 +244,9 @@ func (h *Headscale) expireEphemeralNodesWorker() {
|
||||||
for _, m := range machines {
|
for _, m := range machines {
|
||||||
if m.AuthKey != nil && m.LastSeen != nil && m.AuthKey.Ephemeral &&
|
if m.AuthKey != nil && m.LastSeen != nil && m.AuthKey.Ephemeral &&
|
||||||
time.Now().After(m.LastSeen.Add(h.cfg.EphemeralNodeInactivityTimeout)) {
|
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 {
|
||||||
|
@ -274,18 +288,33 @@ func (h *Headscale) grpcAuthenticationInterceptor(ctx context.Context,
|
||||||
// the server
|
// the server
|
||||||
p, _ := peer.FromContext(ctx)
|
p, _ := peer.FromContext(ctx)
|
||||||
|
|
||||||
log.Trace().Caller().Str("client_address", p.Addr.String()).Msg("Client is trying to authenticate")
|
log.Trace().
|
||||||
|
Caller().
|
||||||
|
Str("client_address", p.Addr.String()).
|
||||||
|
Msg("Client is trying to authenticate")
|
||||||
|
|
||||||
md, ok := metadata.FromIncomingContext(ctx)
|
md, ok := metadata.FromIncomingContext(ctx)
|
||||||
if !ok {
|
if !ok {
|
||||||
log.Error().Caller().Str("client_address", p.Addr.String()).Msg("Retrieving metadata is failed")
|
log.Error().
|
||||||
return ctx, status.Errorf(codes.InvalidArgument, "Retrieving metadata is failed")
|
Caller().
|
||||||
|
Str("client_address", p.Addr.String()).
|
||||||
|
Msg("Retrieving metadata is failed")
|
||||||
|
return ctx, status.Errorf(
|
||||||
|
codes.InvalidArgument,
|
||||||
|
"Retrieving metadata is failed",
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
authHeader, ok := md["authorization"]
|
authHeader, ok := md["authorization"]
|
||||||
if !ok {
|
if !ok {
|
||||||
log.Error().Caller().Str("client_address", p.Addr.String()).Msg("Authorization token is not supplied")
|
log.Error().
|
||||||
return ctx, status.Errorf(codes.Unauthenticated, "Authorization token is not supplied")
|
Caller().
|
||||||
|
Str("client_address", p.Addr.String()).
|
||||||
|
Msg("Authorization token is not supplied")
|
||||||
|
return ctx, status.Errorf(
|
||||||
|
codes.Unauthenticated,
|
||||||
|
"Authorization token is not supplied",
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
token := authHeader[0]
|
token := authHeader[0]
|
||||||
|
@ -295,7 +324,10 @@ func (h *Headscale) grpcAuthenticationInterceptor(ctx context.Context,
|
||||||
Caller().
|
Caller().
|
||||||
Str("client_address", p.Addr.String()).
|
Str("client_address", p.Addr.String()).
|
||||||
Msg(`missing "Bearer " prefix in "Authorization" header`)
|
Msg(`missing "Bearer " prefix in "Authorization" header`)
|
||||||
return ctx, status.Error(codes.Unauthenticated, `missing "Bearer " prefix in "Authorization" header`)
|
return ctx, status.Error(
|
||||||
|
codes.Unauthenticated,
|
||||||
|
`missing "Bearer " prefix in "Authorization" header`,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO(kradalby): Implement API key backend:
|
// TODO(kradalby): Implement API key backend:
|
||||||
|
@ -307,7 +339,10 @@ func (h *Headscale) grpcAuthenticationInterceptor(ctx context.Context,
|
||||||
// Currently all other than localhost traffic is unauthorized, this is intentional to allow
|
// Currently all other than localhost traffic is unauthorized, this is intentional to allow
|
||||||
// us to make use of gRPC for our CLI, but not having to implement any of the remote capabilities
|
// us to make use of gRPC for our CLI, but not having to implement any of the remote capabilities
|
||||||
// and API key auth
|
// and API key auth
|
||||||
return ctx, status.Error(codes.Unauthenticated, "Authentication is not implemented yet")
|
return ctx, status.Error(
|
||||||
|
codes.Unauthenticated,
|
||||||
|
"Authentication is not implemented yet",
|
||||||
|
)
|
||||||
|
|
||||||
//if strings.TrimPrefix(token, AUTH_PREFIX) != a.Token {
|
//if strings.TrimPrefix(token, AUTH_PREFIX) != a.Token {
|
||||||
// log.Error().Caller().Str("client_address", p.Addr.String()).Msg("invalid token")
|
// log.Error().Caller().Str("client_address", p.Addr.String()).Msg("invalid token")
|
||||||
|
@ -405,7 +440,10 @@ func (h *Headscale) Serve() error {
|
||||||
// Match gRPC requests here
|
// Match gRPC requests here
|
||||||
grpcListener := m.MatchWithWriters(
|
grpcListener := m.MatchWithWriters(
|
||||||
cmux.HTTP2MatchHeaderFieldSendSettings("content-type", "application/grpc"),
|
cmux.HTTP2MatchHeaderFieldSendSettings("content-type", "application/grpc"),
|
||||||
cmux.HTTP2MatchHeaderFieldSendSettings("content-type", "application/grpc+proto"),
|
cmux.HTTP2MatchHeaderFieldSendSettings(
|
||||||
|
"content-type",
|
||||||
|
"application/grpc+proto",
|
||||||
|
),
|
||||||
)
|
)
|
||||||
// Otherwise match regular http requests.
|
// Otherwise match regular http requests.
|
||||||
httpListener := m.Match(cmux.Any())
|
httpListener := m.Match(cmux.Any())
|
||||||
|
@ -436,7 +474,10 @@ func (h *Headscale) Serve() error {
|
||||||
p := ginprometheus.NewPrometheus("gin")
|
p := ginprometheus.NewPrometheus("gin")
|
||||||
p.Use(r)
|
p.Use(r)
|
||||||
|
|
||||||
r.GET("/health", func(c *gin.Context) { c.JSON(http.StatusOK, gin.H{"healthy": "ok"}) })
|
r.GET(
|
||||||
|
"/health",
|
||||||
|
func(c *gin.Context) { c.JSON(http.StatusOK, gin.H{"healthy": "ok"}) },
|
||||||
|
)
|
||||||
r.GET("/key", h.KeyHandler)
|
r.GET("/key", h.KeyHandler)
|
||||||
r.GET("/register", h.RegisterWebAPI)
|
r.GET("/register", h.RegisterWebAPI)
|
||||||
r.POST("/machine/:id/map", h.PollNetMapHandler)
|
r.POST("/machine/:id/map", h.PollNetMapHandler)
|
||||||
|
@ -537,7 +578,8 @@ func (h *Headscale) Serve() error {
|
||||||
|
|
||||||
g.Go(func() error { return m.Serve() })
|
g.Go(func() error { return m.Serve() })
|
||||||
|
|
||||||
log.Info().Msgf("listening and serving (multiplexed HTTP and gRPC) on: %s", h.cfg.Addr)
|
log.Info().
|
||||||
|
Msgf("listening and serving (multiplexed HTTP and gRPC) on: %s", h.cfg.Addr)
|
||||||
|
|
||||||
return g.Wait()
|
return g.Wait()
|
||||||
}
|
}
|
||||||
|
@ -545,7 +587,8 @@ func (h *Headscale) Serve() error {
|
||||||
func (h *Headscale) getTLSSettings() (*tls.Config, error) {
|
func (h *Headscale) getTLSSettings() (*tls.Config, error) {
|
||||||
if h.cfg.TLSLetsEncryptHostname != "" {
|
if h.cfg.TLSLetsEncryptHostname != "" {
|
||||||
if !strings.HasPrefix(h.cfg.ServerURL, "https://") {
|
if !strings.HasPrefix(h.cfg.ServerURL, "https://") {
|
||||||
log.Warn().Msg("Listening with TLS but ServerURL does not start with https://")
|
log.Warn().
|
||||||
|
Msg("Listening with TLS but ServerURL does not start with https://")
|
||||||
}
|
}
|
||||||
|
|
||||||
m := autocert.Manager{
|
m := autocert.Manager{
|
||||||
|
|
|
@ -17,8 +17,10 @@ var _ = check.Suite(&Suite{})
|
||||||
|
|
||||||
type Suite struct{}
|
type Suite struct{}
|
||||||
|
|
||||||
var tmpDir string
|
var (
|
||||||
var h Headscale
|
tmpDir string
|
||||||
|
h Headscale
|
||||||
|
)
|
||||||
|
|
||||||
func (s *Suite) SetUpTest(c *check.C) {
|
func (s *Suite) SetUpTest(c *check.C) {
|
||||||
s.ResetDB(c)
|
s.ResetDB(c)
|
||||||
|
|
|
@ -73,7 +73,11 @@ func (h *Headscale) AppleMobileConfig(c *gin.Context) {
|
||||||
Str("handler", "AppleMobileConfig").
|
Str("handler", "AppleMobileConfig").
|
||||||
Err(err).
|
Err(err).
|
||||||
Msg("Could not render Apple index template")
|
Msg("Could not render Apple index template")
|
||||||
c.Data(http.StatusInternalServerError, "text/html; charset=utf-8", []byte("Could not render Apple index template"))
|
c.Data(
|
||||||
|
http.StatusInternalServerError,
|
||||||
|
"text/html; charset=utf-8",
|
||||||
|
[]byte("Could not render Apple index template"),
|
||||||
|
)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -89,7 +93,11 @@ func (h *Headscale) ApplePlatformConfig(c *gin.Context) {
|
||||||
Str("handler", "ApplePlatformConfig").
|
Str("handler", "ApplePlatformConfig").
|
||||||
Err(err).
|
Err(err).
|
||||||
Msg("Failed not create UUID")
|
Msg("Failed not create UUID")
|
||||||
c.Data(http.StatusInternalServerError, "text/html; charset=utf-8", []byte("Failed to create UUID"))
|
c.Data(
|
||||||
|
http.StatusInternalServerError,
|
||||||
|
"text/html; charset=utf-8",
|
||||||
|
[]byte("Failed to create UUID"),
|
||||||
|
)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -99,7 +107,11 @@ func (h *Headscale) ApplePlatformConfig(c *gin.Context) {
|
||||||
Str("handler", "ApplePlatformConfig").
|
Str("handler", "ApplePlatformConfig").
|
||||||
Err(err).
|
Err(err).
|
||||||
Msg("Failed not create UUID")
|
Msg("Failed not create UUID")
|
||||||
c.Data(http.StatusInternalServerError, "text/html; charset=utf-8", []byte("Failed to create UUID"))
|
c.Data(
|
||||||
|
http.StatusInternalServerError,
|
||||||
|
"text/html; charset=utf-8",
|
||||||
|
[]byte("Failed to create UUID"),
|
||||||
|
)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -117,7 +129,11 @@ func (h *Headscale) ApplePlatformConfig(c *gin.Context) {
|
||||||
Str("handler", "ApplePlatformConfig").
|
Str("handler", "ApplePlatformConfig").
|
||||||
Err(err).
|
Err(err).
|
||||||
Msg("Could not render Apple macOS template")
|
Msg("Could not render Apple macOS template")
|
||||||
c.Data(http.StatusInternalServerError, "text/html; charset=utf-8", []byte("Could not render Apple macOS template"))
|
c.Data(
|
||||||
|
http.StatusInternalServerError,
|
||||||
|
"text/html; charset=utf-8",
|
||||||
|
[]byte("Could not render Apple macOS template"),
|
||||||
|
)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
case "ios":
|
case "ios":
|
||||||
|
@ -126,11 +142,19 @@ func (h *Headscale) ApplePlatformConfig(c *gin.Context) {
|
||||||
Str("handler", "ApplePlatformConfig").
|
Str("handler", "ApplePlatformConfig").
|
||||||
Err(err).
|
Err(err).
|
||||||
Msg("Could not render Apple iOS template")
|
Msg("Could not render Apple iOS template")
|
||||||
c.Data(http.StatusInternalServerError, "text/html; charset=utf-8", []byte("Could not render Apple iOS template"))
|
c.Data(
|
||||||
|
http.StatusInternalServerError,
|
||||||
|
"text/html; charset=utf-8",
|
||||||
|
[]byte("Could not render Apple iOS template"),
|
||||||
|
)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
c.Data(http.StatusOK, "text/html; charset=utf-8", []byte("Invalid platform, only ios and macos is supported"))
|
c.Data(
|
||||||
|
http.StatusOK,
|
||||||
|
"text/html; charset=utf-8",
|
||||||
|
[]byte("Invalid platform, only ios and macos is supported"),
|
||||||
|
)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -146,11 +170,19 @@ func (h *Headscale) ApplePlatformConfig(c *gin.Context) {
|
||||||
Str("handler", "ApplePlatformConfig").
|
Str("handler", "ApplePlatformConfig").
|
||||||
Err(err).
|
Err(err).
|
||||||
Msg("Could not render Apple platform template")
|
Msg("Could not render Apple platform template")
|
||||||
c.Data(http.StatusInternalServerError, "text/html; charset=utf-8", []byte("Could not render Apple platform template"))
|
c.Data(
|
||||||
|
http.StatusInternalServerError,
|
||||||
|
"text/html; charset=utf-8",
|
||||||
|
[]byte("Could not render Apple platform template"),
|
||||||
|
)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
c.Data(http.StatusOK, "application/x-apple-aspen-config; charset=utf-8", content.Bytes())
|
c.Data(
|
||||||
|
http.StatusOK,
|
||||||
|
"application/x-apple-aspen-config; charset=utf-8",
|
||||||
|
content.Bytes(),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
type AppleMobileConfig struct {
|
type AppleMobileConfig struct {
|
||||||
|
@ -164,7 +196,8 @@ type AppleMobilePlatformConfig struct {
|
||||||
Url string
|
Url string
|
||||||
}
|
}
|
||||||
|
|
||||||
var commonTemplate = template.Must(template.New("mobileconfig").Parse(`<?xml version="1.0" encoding="UTF-8"?>
|
var commonTemplate = template.Must(
|
||||||
|
template.New("mobileconfig").Parse(`<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
<plist version="1.0">
|
<plist version="1.0">
|
||||||
<dict>
|
<dict>
|
||||||
|
@ -187,7 +220,8 @@ var commonTemplate = template.Must(template.New("mobileconfig").Parse(`<?xml ver
|
||||||
{{.Payload}}
|
{{.Payload}}
|
||||||
</array>
|
</array>
|
||||||
</dict>
|
</dict>
|
||||||
</plist>`))
|
</plist>`),
|
||||||
|
)
|
||||||
|
|
||||||
var iosTemplate = template.Must(template.New("iosTemplate").Parse(`
|
var iosTemplate = template.Must(template.New("iosTemplate").Parse(`
|
||||||
<dict>
|
<dict>
|
||||||
|
|
|
@ -28,7 +28,10 @@ func (s *Suite) TestRegisterMachine(c *check.C) {
|
||||||
_, err = h.GetMachine("test", "testmachine")
|
_, err = h.GetMachine("test", "testmachine")
|
||||||
c.Assert(err, check.IsNil)
|
c.Assert(err, check.IsNil)
|
||||||
|
|
||||||
m2, err := h.RegisterMachine("8ce002a935f8c394e55e78fbbb410576575ff8ec5cfa2e627e4b807f1be15b0e", n.Name)
|
m2, err := h.RegisterMachine(
|
||||||
|
"8ce002a935f8c394e55e78fbbb410576575ff8ec5cfa2e627e4b807f1be15b0e",
|
||||||
|
n.Name,
|
||||||
|
)
|
||||||
c.Assert(err, check.IsNil)
|
c.Assert(err, check.IsNil)
|
||||||
c.Assert(m2.Registered, check.Equals, true)
|
c.Assert(m2.Registered, check.Equals, true)
|
||||||
|
|
||||||
|
|
|
@ -27,7 +27,8 @@ func init() {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal().Err(err).Msg("")
|
log.Fatal().Err(err).Msg("")
|
||||||
}
|
}
|
||||||
createNodeCmd.Flags().StringSliceP("route", "r", []string{}, "List (or repeated flags) of routes to advertise")
|
createNodeCmd.Flags().
|
||||||
|
StringSliceP("route", "r", []string{}, "List (or repeated flags) of routes to advertise")
|
||||||
|
|
||||||
debugCmd.AddCommand(createNodeCmd)
|
debugCmd.AddCommand(createNodeCmd)
|
||||||
}
|
}
|
||||||
|
@ -56,19 +57,31 @@ var createNodeCmd = &cobra.Command{
|
||||||
|
|
||||||
name, err := cmd.Flags().GetString("name")
|
name, err := cmd.Flags().GetString("name")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ErrorOutput(err, fmt.Sprintf("Error getting node from flag: %s", err), output)
|
ErrorOutput(
|
||||||
|
err,
|
||||||
|
fmt.Sprintf("Error getting node from flag: %s", err),
|
||||||
|
output,
|
||||||
|
)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
machineKey, err := cmd.Flags().GetString("key")
|
machineKey, err := cmd.Flags().GetString("key")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ErrorOutput(err, fmt.Sprintf("Error getting key from flag: %s", err), output)
|
ErrorOutput(
|
||||||
|
err,
|
||||||
|
fmt.Sprintf("Error getting key from flag: %s", err),
|
||||||
|
output,
|
||||||
|
)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
routes, err := cmd.Flags().GetStringSlice("route")
|
routes, err := cmd.Flags().GetStringSlice("route")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ErrorOutput(err, fmt.Sprintf("Error getting routes from flag: %s", err), output)
|
ErrorOutput(
|
||||||
|
err,
|
||||||
|
fmt.Sprintf("Error getting routes from flag: %s", err),
|
||||||
|
output,
|
||||||
|
)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -81,7 +94,11 @@ var createNodeCmd = &cobra.Command{
|
||||||
|
|
||||||
response, err := client.DebugCreateMachine(ctx, request)
|
response, err := client.DebugCreateMachine(ctx, request)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ErrorOutput(err, fmt.Sprintf("Cannot create machine: %s", status.Convert(err).Message()), output)
|
ErrorOutput(
|
||||||
|
err,
|
||||||
|
fmt.Sprintf("Cannot create machine: %s", status.Convert(err).Message()),
|
||||||
|
output,
|
||||||
|
)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -48,7 +48,14 @@ var createNamespaceCmd = &cobra.Command{
|
||||||
log.Trace().Interface("request", request).Msg("Sending CreateNamespace request")
|
log.Trace().Interface("request", request).Msg("Sending CreateNamespace request")
|
||||||
response, err := client.CreateNamespace(ctx, request)
|
response, err := client.CreateNamespace(ctx, request)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ErrorOutput(err, fmt.Sprintf("Cannot create namespace: %s", status.Convert(err).Message()), output)
|
ErrorOutput(
|
||||||
|
err,
|
||||||
|
fmt.Sprintf(
|
||||||
|
"Cannot create namespace: %s",
|
||||||
|
status.Convert(err).Message(),
|
||||||
|
),
|
||||||
|
output,
|
||||||
|
)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -78,7 +85,14 @@ var destroyNamespaceCmd = &cobra.Command{
|
||||||
|
|
||||||
response, err := client.DeleteNamespace(ctx, request)
|
response, err := client.DeleteNamespace(ctx, request)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ErrorOutput(err, fmt.Sprintf("Cannot destroy namespace: %s", status.Convert(err).Message()), output)
|
ErrorOutput(
|
||||||
|
err,
|
||||||
|
fmt.Sprintf(
|
||||||
|
"Cannot destroy namespace: %s",
|
||||||
|
status.Convert(err).Message(),
|
||||||
|
),
|
||||||
|
output,
|
||||||
|
)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -100,7 +114,11 @@ var listNamespacesCmd = &cobra.Command{
|
||||||
|
|
||||||
response, err := client.ListNamespaces(ctx, request)
|
response, err := client.ListNamespaces(ctx, request)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ErrorOutput(err, fmt.Sprintf("Cannot get namespaces: %s", status.Convert(err).Message()), output)
|
ErrorOutput(
|
||||||
|
err,
|
||||||
|
fmt.Sprintf("Cannot get namespaces: %s", status.Convert(err).Message()),
|
||||||
|
output,
|
||||||
|
)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -122,7 +140,11 @@ var listNamespacesCmd = &cobra.Command{
|
||||||
}
|
}
|
||||||
err = pterm.DefaultTable.WithHasHeader().WithData(d).Render()
|
err = pterm.DefaultTable.WithHasHeader().WithData(d).Render()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ErrorOutput(err, fmt.Sprintf("Failed to render pterm table: %s", err), output)
|
ErrorOutput(
|
||||||
|
err,
|
||||||
|
fmt.Sprintf("Failed to render pterm table: %s", err),
|
||||||
|
output,
|
||||||
|
)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -151,7 +173,14 @@ var renameNamespaceCmd = &cobra.Command{
|
||||||
|
|
||||||
response, err := client.RenameNamespace(ctx, request)
|
response, err := client.RenameNamespace(ctx, request)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ErrorOutput(err, fmt.Sprintf("Cannot rename namespace: %s", status.Convert(err).Message()), output)
|
ErrorOutput(
|
||||||
|
err,
|
||||||
|
fmt.Sprintf(
|
||||||
|
"Cannot rename namespace: %s",
|
||||||
|
status.Convert(err).Message(),
|
||||||
|
),
|
||||||
|
output,
|
||||||
|
)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -86,7 +86,11 @@ var registerNodeCmd = &cobra.Command{
|
||||||
|
|
||||||
machineKey, err := cmd.Flags().GetString("key")
|
machineKey, err := cmd.Flags().GetString("key")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ErrorOutput(err, fmt.Sprintf("Error getting machine key from flag: %s", err), output)
|
ErrorOutput(
|
||||||
|
err,
|
||||||
|
fmt.Sprintf("Error getting machine key from flag: %s", err),
|
||||||
|
output,
|
||||||
|
)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -97,7 +101,14 @@ var registerNodeCmd = &cobra.Command{
|
||||||
|
|
||||||
response, err := client.RegisterMachine(ctx, request)
|
response, err := client.RegisterMachine(ctx, request)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ErrorOutput(err, fmt.Sprintf("Cannot register machine: %s\n", status.Convert(err).Message()), output)
|
ErrorOutput(
|
||||||
|
err,
|
||||||
|
fmt.Sprintf(
|
||||||
|
"Cannot register machine: %s\n",
|
||||||
|
status.Convert(err).Message(),
|
||||||
|
),
|
||||||
|
output,
|
||||||
|
)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -126,7 +137,11 @@ var listNodesCmd = &cobra.Command{
|
||||||
|
|
||||||
response, err := client.ListMachines(ctx, request)
|
response, err := client.ListMachines(ctx, request)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ErrorOutput(err, fmt.Sprintf("Cannot get nodes: %s", status.Convert(err).Message()), output)
|
ErrorOutput(
|
||||||
|
err,
|
||||||
|
fmt.Sprintf("Cannot get nodes: %s", status.Convert(err).Message()),
|
||||||
|
output,
|
||||||
|
)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -143,7 +158,11 @@ var listNodesCmd = &cobra.Command{
|
||||||
|
|
||||||
err = pterm.DefaultTable.WithHasHeader().WithData(d).Render()
|
err = pterm.DefaultTable.WithHasHeader().WithData(d).Render()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ErrorOutput(err, fmt.Sprintf("Failed to render pterm table: %s", err), output)
|
ErrorOutput(
|
||||||
|
err,
|
||||||
|
fmt.Sprintf("Failed to render pterm table: %s", err),
|
||||||
|
output,
|
||||||
|
)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -157,7 +176,11 @@ var deleteNodeCmd = &cobra.Command{
|
||||||
|
|
||||||
id, err := cmd.Flags().GetInt("identifier")
|
id, err := cmd.Flags().GetInt("identifier")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ErrorOutput(err, fmt.Sprintf("Error converting ID to integer: %s", err), output)
|
ErrorOutput(
|
||||||
|
err,
|
||||||
|
fmt.Sprintf("Error converting ID to integer: %s", err),
|
||||||
|
output,
|
||||||
|
)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -171,7 +194,14 @@ var deleteNodeCmd = &cobra.Command{
|
||||||
|
|
||||||
getResponse, err := client.GetMachine(ctx, getRequest)
|
getResponse, err := client.GetMachine(ctx, getRequest)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ErrorOutput(err, fmt.Sprintf("Error getting node node: %s", status.Convert(err).Message()), output)
|
ErrorOutput(
|
||||||
|
err,
|
||||||
|
fmt.Sprintf(
|
||||||
|
"Error getting node node: %s",
|
||||||
|
status.Convert(err).Message(),
|
||||||
|
),
|
||||||
|
output,
|
||||||
|
)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -183,7 +213,10 @@ var deleteNodeCmd = &cobra.Command{
|
||||||
force, _ := cmd.Flags().GetBool("force")
|
force, _ := cmd.Flags().GetBool("force")
|
||||||
if !force {
|
if !force {
|
||||||
prompt := &survey.Confirm{
|
prompt := &survey.Confirm{
|
||||||
Message: fmt.Sprintf("Do you want to remove the node %s?", getResponse.GetMachine().Name),
|
Message: fmt.Sprintf(
|
||||||
|
"Do you want to remove the node %s?",
|
||||||
|
getResponse.GetMachine().Name,
|
||||||
|
),
|
||||||
}
|
}
|
||||||
err = survey.AskOne(prompt, &confirm)
|
err = survey.AskOne(prompt, &confirm)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -198,10 +231,21 @@ var deleteNodeCmd = &cobra.Command{
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ErrorOutput(err, fmt.Sprintf("Error deleting node: %s", status.Convert(err).Message()), output)
|
ErrorOutput(
|
||||||
|
err,
|
||||||
|
fmt.Sprintf(
|
||||||
|
"Error deleting node: %s",
|
||||||
|
status.Convert(err).Message(),
|
||||||
|
),
|
||||||
|
output,
|
||||||
|
)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
SuccessOutput(map[string]string{"Result": "Node deleted"}, "Node deleted", output)
|
SuccessOutput(
|
||||||
|
map[string]string{"Result": "Node deleted"},
|
||||||
|
"Node deleted",
|
||||||
|
output,
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
SuccessOutput(map[string]string{"Result": "Node not deleted"}, "Node not deleted", output)
|
SuccessOutput(map[string]string{"Result": "Node not deleted"}, "Node not deleted", output)
|
||||||
}
|
}
|
||||||
|
@ -235,7 +279,11 @@ func sharingWorker(
|
||||||
|
|
||||||
machineResponse, err := client.GetMachine(ctx, machineRequest)
|
machineResponse, err := client.GetMachine(ctx, machineRequest)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ErrorOutput(err, fmt.Sprintf("Error getting node node: %s", status.Convert(err).Message()), output)
|
ErrorOutput(
|
||||||
|
err,
|
||||||
|
fmt.Sprintf("Error getting node node: %s", status.Convert(err).Message()),
|
||||||
|
output,
|
||||||
|
)
|
||||||
return "", nil, nil, err
|
return "", nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -245,7 +293,11 @@ func sharingWorker(
|
||||||
|
|
||||||
namespaceResponse, err := client.GetNamespace(ctx, namespaceRequest)
|
namespaceResponse, err := client.GetNamespace(ctx, namespaceRequest)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ErrorOutput(err, fmt.Sprintf("Error getting node node: %s", status.Convert(err).Message()), output)
|
ErrorOutput(
|
||||||
|
err,
|
||||||
|
fmt.Sprintf("Error getting node node: %s", status.Convert(err).Message()),
|
||||||
|
output,
|
||||||
|
)
|
||||||
return "", nil, nil, err
|
return "", nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -258,7 +310,11 @@ var shareMachineCmd = &cobra.Command{
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
output, machine, namespace, err := sharingWorker(cmd, args)
|
output, machine, namespace, err := sharingWorker(cmd, args)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ErrorOutput(err, fmt.Sprintf("Failed to fetch namespace or machine: %s", err), output)
|
ErrorOutput(
|
||||||
|
err,
|
||||||
|
fmt.Sprintf("Failed to fetch namespace or machine: %s", err),
|
||||||
|
output,
|
||||||
|
)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -273,7 +329,11 @@ var shareMachineCmd = &cobra.Command{
|
||||||
|
|
||||||
response, err := client.ShareMachine(ctx, request)
|
response, err := client.ShareMachine(ctx, request)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ErrorOutput(err, fmt.Sprintf("Error sharing node: %s", status.Convert(err).Message()), output)
|
ErrorOutput(
|
||||||
|
err,
|
||||||
|
fmt.Sprintf("Error sharing node: %s", status.Convert(err).Message()),
|
||||||
|
output,
|
||||||
|
)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -287,7 +347,11 @@ var unshareMachineCmd = &cobra.Command{
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
output, machine, namespace, err := sharingWorker(cmd, args)
|
output, machine, namespace, err := sharingWorker(cmd, args)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ErrorOutput(err, fmt.Sprintf("Failed to fetch namespace or machine: %s", err), output)
|
ErrorOutput(
|
||||||
|
err,
|
||||||
|
fmt.Sprintf("Failed to fetch namespace or machine: %s", err),
|
||||||
|
output,
|
||||||
|
)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -302,7 +366,11 @@ var unshareMachineCmd = &cobra.Command{
|
||||||
|
|
||||||
response, err := client.UnshareMachine(ctx, request)
|
response, err := client.UnshareMachine(ctx, request)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ErrorOutput(err, fmt.Sprintf("Error unsharing node: %s", status.Convert(err).Message()), output)
|
ErrorOutput(
|
||||||
|
err,
|
||||||
|
fmt.Sprintf("Error unsharing node: %s", status.Convert(err).Message()),
|
||||||
|
output,
|
||||||
|
)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -310,8 +378,22 @@ var unshareMachineCmd = &cobra.Command{
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
func nodesToPtables(currentNamespace string, machines []*v1.Machine) (pterm.TableData, error) {
|
func nodesToPtables(
|
||||||
d := pterm.TableData{{"ID", "Name", "NodeKey", "Namespace", "IP address", "Ephemeral", "Last seen", "Online"}}
|
currentNamespace string,
|
||||||
|
machines []*v1.Machine,
|
||||||
|
) (pterm.TableData, error) {
|
||||||
|
d := pterm.TableData{
|
||||||
|
{
|
||||||
|
"ID",
|
||||||
|
"Name",
|
||||||
|
"NodeKey",
|
||||||
|
"Namespace",
|
||||||
|
"IP address",
|
||||||
|
"Ephemeral",
|
||||||
|
"Last seen",
|
||||||
|
"Online",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
for _, machine := range machines {
|
for _, machine := range machines {
|
||||||
var ephemeral bool
|
var ephemeral bool
|
||||||
|
@ -331,7 +413,9 @@ func nodesToPtables(currentNamespace string, machines []*v1.Machine) (pterm.Tabl
|
||||||
nodeKey := tailcfg.NodeKey(nKey)
|
nodeKey := tailcfg.NodeKey(nKey)
|
||||||
|
|
||||||
var online string
|
var online string
|
||||||
if lastSeen.After(time.Now().Add(-5 * time.Minute)) { // TODO: Find a better way to reliably show if online
|
if lastSeen.After(
|
||||||
|
time.Now().Add(-5 * time.Minute),
|
||||||
|
) { // TODO: Find a better way to reliably show if online
|
||||||
online = pterm.LightGreen("true")
|
online = pterm.LightGreen("true")
|
||||||
} else {
|
} else {
|
||||||
online = pterm.LightRed("false")
|
online = pterm.LightRed("false")
|
||||||
|
|
|
@ -22,8 +22,10 @@ func init() {
|
||||||
preauthkeysCmd.AddCommand(listPreAuthKeys)
|
preauthkeysCmd.AddCommand(listPreAuthKeys)
|
||||||
preauthkeysCmd.AddCommand(createPreAuthKeyCmd)
|
preauthkeysCmd.AddCommand(createPreAuthKeyCmd)
|
||||||
preauthkeysCmd.AddCommand(expirePreAuthKeyCmd)
|
preauthkeysCmd.AddCommand(expirePreAuthKeyCmd)
|
||||||
createPreAuthKeyCmd.PersistentFlags().Bool("reusable", false, "Make the preauthkey reusable")
|
createPreAuthKeyCmd.PersistentFlags().
|
||||||
createPreAuthKeyCmd.PersistentFlags().Bool("ephemeral", false, "Preauthkey for ephemeral nodes")
|
Bool("reusable", false, "Make the preauthkey reusable")
|
||||||
|
createPreAuthKeyCmd.PersistentFlags().
|
||||||
|
Bool("ephemeral", false, "Preauthkey for ephemeral nodes")
|
||||||
createPreAuthKeyCmd.Flags().
|
createPreAuthKeyCmd.Flags().
|
||||||
DurationP("expiration", "e", 24*time.Hour, "Human-readable expiration of the key (30m, 24h, 365d...)")
|
DurationP("expiration", "e", 24*time.Hour, "Human-readable expiration of the key (30m, 24h, 365d...)")
|
||||||
}
|
}
|
||||||
|
@ -55,7 +57,11 @@ var listPreAuthKeys = &cobra.Command{
|
||||||
|
|
||||||
response, err := client.ListPreAuthKeys(ctx, request)
|
response, err := client.ListPreAuthKeys(ctx, request)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ErrorOutput(err, fmt.Sprintf("Error getting the list of keys: %s", err), output)
|
ErrorOutput(
|
||||||
|
err,
|
||||||
|
fmt.Sprintf("Error getting the list of keys: %s", err),
|
||||||
|
output,
|
||||||
|
)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -64,7 +70,9 @@ var listPreAuthKeys = &cobra.Command{
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
d := pterm.TableData{{"ID", "Key", "Reusable", "Ephemeral", "Used", "Expiration", "Created"}}
|
d := pterm.TableData{
|
||||||
|
{"ID", "Key", "Reusable", "Ephemeral", "Used", "Expiration", "Created"},
|
||||||
|
}
|
||||||
for _, k := range response.PreAuthKeys {
|
for _, k := range response.PreAuthKeys {
|
||||||
expiration := "-"
|
expiration := "-"
|
||||||
if k.GetExpiration() != nil {
|
if k.GetExpiration() != nil {
|
||||||
|
@ -91,7 +99,11 @@ var listPreAuthKeys = &cobra.Command{
|
||||||
}
|
}
|
||||||
err = pterm.DefaultTable.WithHasHeader().WithData(d).Render()
|
err = pterm.DefaultTable.WithHasHeader().WithData(d).Render()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ErrorOutput(err, fmt.Sprintf("Failed to render pterm table: %s", err), output)
|
ErrorOutput(
|
||||||
|
err,
|
||||||
|
fmt.Sprintf("Failed to render pterm table: %s", err),
|
||||||
|
output,
|
||||||
|
)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -139,7 +151,11 @@ var createPreAuthKeyCmd = &cobra.Command{
|
||||||
|
|
||||||
response, err := client.CreatePreAuthKey(ctx, request)
|
response, err := client.CreatePreAuthKey(ctx, request)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ErrorOutput(err, fmt.Sprintf("Cannot create Pre Auth Key: %s\n", err), output)
|
ErrorOutput(
|
||||||
|
err,
|
||||||
|
fmt.Sprintf("Cannot create Pre Auth Key: %s\n", err),
|
||||||
|
output,
|
||||||
|
)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -175,7 +191,11 @@ var expirePreAuthKeyCmd = &cobra.Command{
|
||||||
|
|
||||||
response, err := client.ExpirePreAuthKey(ctx, request)
|
response, err := client.ExpirePreAuthKey(ctx, request)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ErrorOutput(err, fmt.Sprintf("Cannot expire Pre Auth Key: %s\n", err), output)
|
ErrorOutput(
|
||||||
|
err,
|
||||||
|
fmt.Sprintf("Cannot expire Pre Auth Key: %s\n", err),
|
||||||
|
output,
|
||||||
|
)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -10,7 +10,8 @@ import (
|
||||||
func init() {
|
func init() {
|
||||||
rootCmd.PersistentFlags().
|
rootCmd.PersistentFlags().
|
||||||
StringP("output", "o", "", "Output format. Empty for human-readable, 'json', 'json-line' or 'yaml'")
|
StringP("output", "o", "", "Output format. Empty for human-readable, 'json', 'json-line' or 'yaml'")
|
||||||
rootCmd.PersistentFlags().Bool("force", false, "Disable prompts and forces the execution")
|
rootCmd.PersistentFlags().
|
||||||
|
Bool("force", false, "Disable prompts and forces the execution")
|
||||||
}
|
}
|
||||||
|
|
||||||
var rootCmd = &cobra.Command{
|
var rootCmd = &cobra.Command{
|
||||||
|
|
|
@ -21,7 +21,8 @@ func init() {
|
||||||
}
|
}
|
||||||
routesCmd.AddCommand(listRoutesCmd)
|
routesCmd.AddCommand(listRoutesCmd)
|
||||||
|
|
||||||
enableRouteCmd.Flags().StringSliceP("route", "r", []string{}, "List (or repeated flags) of routes to enable")
|
enableRouteCmd.Flags().
|
||||||
|
StringSliceP("route", "r", []string{}, "List (or repeated flags) of routes to enable")
|
||||||
enableRouteCmd.Flags().Uint64P("identifier", "i", 0, "Node identifier (ID)")
|
enableRouteCmd.Flags().Uint64P("identifier", "i", 0, "Node identifier (ID)")
|
||||||
err = enableRouteCmd.MarkFlagRequired("identifier")
|
err = enableRouteCmd.MarkFlagRequired("identifier")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -46,7 +47,11 @@ var listRoutesCmd = &cobra.Command{
|
||||||
|
|
||||||
machineId, err := cmd.Flags().GetUint64("identifier")
|
machineId, err := cmd.Flags().GetUint64("identifier")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ErrorOutput(err, fmt.Sprintf("Error getting machine id from flag: %s", err), output)
|
ErrorOutput(
|
||||||
|
err,
|
||||||
|
fmt.Sprintf("Error getting machine id from flag: %s", err),
|
||||||
|
output,
|
||||||
|
)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -60,7 +65,11 @@ var listRoutesCmd = &cobra.Command{
|
||||||
|
|
||||||
response, err := client.GetMachineRoute(ctx, request)
|
response, err := client.GetMachineRoute(ctx, request)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ErrorOutput(err, fmt.Sprintf("Cannot get nodes: %s", status.Convert(err).Message()), output)
|
ErrorOutput(
|
||||||
|
err,
|
||||||
|
fmt.Sprintf("Cannot get nodes: %s", status.Convert(err).Message()),
|
||||||
|
output,
|
||||||
|
)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -77,7 +86,11 @@ var listRoutesCmd = &cobra.Command{
|
||||||
|
|
||||||
err = pterm.DefaultTable.WithHasHeader().WithData(d).Render()
|
err = pterm.DefaultTable.WithHasHeader().WithData(d).Render()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ErrorOutput(err, fmt.Sprintf("Failed to render pterm table: %s", err), output)
|
ErrorOutput(
|
||||||
|
err,
|
||||||
|
fmt.Sprintf("Failed to render pterm table: %s", err),
|
||||||
|
output,
|
||||||
|
)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -95,13 +108,21 @@ omit the route you do not want to enable.
|
||||||
output, _ := cmd.Flags().GetString("output")
|
output, _ := cmd.Flags().GetString("output")
|
||||||
machineId, err := cmd.Flags().GetUint64("identifier")
|
machineId, err := cmd.Flags().GetUint64("identifier")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ErrorOutput(err, fmt.Sprintf("Error getting machine id from flag: %s", err), output)
|
ErrorOutput(
|
||||||
|
err,
|
||||||
|
fmt.Sprintf("Error getting machine id from flag: %s", err),
|
||||||
|
output,
|
||||||
|
)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
routes, err := cmd.Flags().GetStringSlice("route")
|
routes, err := cmd.Flags().GetStringSlice("route")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ErrorOutput(err, fmt.Sprintf("Error getting routes from flag: %s", err), output)
|
ErrorOutput(
|
||||||
|
err,
|
||||||
|
fmt.Sprintf("Error getting routes from flag: %s", err),
|
||||||
|
output,
|
||||||
|
)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -116,7 +137,14 @@ omit the route you do not want to enable.
|
||||||
|
|
||||||
response, err := client.EnableMachineRoutes(ctx, request)
|
response, err := client.EnableMachineRoutes(ctx, request)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ErrorOutput(err, fmt.Sprintf("Cannot register machine: %s\n", status.Convert(err).Message()), output)
|
ErrorOutput(
|
||||||
|
err,
|
||||||
|
fmt.Sprintf(
|
||||||
|
"Cannot register machine: %s\n",
|
||||||
|
status.Convert(err).Message(),
|
||||||
|
),
|
||||||
|
output,
|
||||||
|
)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -133,7 +161,11 @@ omit the route you do not want to enable.
|
||||||
|
|
||||||
err = pterm.DefaultTable.WithHasHeader().WithData(d).Render()
|
err = pterm.DefaultTable.WithHasHeader().WithData(d).Render()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ErrorOutput(err, fmt.Sprintf("Failed to render pterm table: %s", err), output)
|
ErrorOutput(
|
||||||
|
err,
|
||||||
|
fmt.Sprintf("Failed to render pterm table: %s", err),
|
||||||
|
output,
|
||||||
|
)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -149,9 +149,14 @@ func GetDNSConfig() (*tailcfg.DNSConfig, string) {
|
||||||
if viper.IsSet("dns_config.restricted_nameservers") {
|
if viper.IsSet("dns_config.restricted_nameservers") {
|
||||||
if len(dnsConfig.Nameservers) > 0 {
|
if len(dnsConfig.Nameservers) > 0 {
|
||||||
dnsConfig.Routes = make(map[string][]dnstype.Resolver)
|
dnsConfig.Routes = make(map[string][]dnstype.Resolver)
|
||||||
restrictedDNS := viper.GetStringMapStringSlice("dns_config.restricted_nameservers")
|
restrictedDNS := viper.GetStringMapStringSlice(
|
||||||
|
"dns_config.restricted_nameservers",
|
||||||
|
)
|
||||||
for domain, restrictedNameservers := range restrictedDNS {
|
for domain, restrictedNameservers := range restrictedDNS {
|
||||||
restrictedResolvers := make([]dnstype.Resolver, len(restrictedNameservers))
|
restrictedResolvers := make(
|
||||||
|
[]dnstype.Resolver,
|
||||||
|
len(restrictedNameservers),
|
||||||
|
)
|
||||||
for index, nameserverStr := range restrictedNameservers {
|
for index, nameserverStr := range restrictedNameservers {
|
||||||
nameserver, err := netaddr.ParseIP(nameserverStr)
|
nameserver, err := netaddr.ParseIP(nameserverStr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -219,7 +224,9 @@ func getHeadscaleConfig() headscale.Config {
|
||||||
"10h",
|
"10h",
|
||||||
) // use 10h here because it is the length of a standard business day plus a small amount of leeway
|
) // use 10h here because it is the length of a standard business day plus a small amount of leeway
|
||||||
if viper.GetDuration("max_machine_registration_duration") >= time.Second {
|
if viper.GetDuration("max_machine_registration_duration") >= time.Second {
|
||||||
maxMachineRegistrationDuration = viper.GetDuration("max_machine_registration_duration")
|
maxMachineRegistrationDuration = viper.GetDuration(
|
||||||
|
"max_machine_registration_duration",
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// defaultMachineRegistrationDuration is the default time assigned to a machine registration if one is not
|
// defaultMachineRegistrationDuration is the default time assigned to a machine registration if one is not
|
||||||
|
@ -229,7 +236,9 @@ func getHeadscaleConfig() headscale.Config {
|
||||||
"8h",
|
"8h",
|
||||||
) // use 8h here because it's the length of a standard business day
|
) // use 8h here because it's the length of a standard business day
|
||||||
if viper.GetDuration("default_machine_registration_duration") >= time.Second {
|
if viper.GetDuration("default_machine_registration_duration") >= time.Second {
|
||||||
defaultMachineRegistrationDuration = viper.GetDuration("default_machine_registration_duration")
|
defaultMachineRegistrationDuration = viper.GetDuration(
|
||||||
|
"default_machine_registration_duration",
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
dnsConfig, baseDomain := GetDNSConfig()
|
dnsConfig, baseDomain := GetDNSConfig()
|
||||||
|
@ -244,7 +253,9 @@ func getHeadscaleConfig() headscale.Config {
|
||||||
|
|
||||||
DERP: derpConfig,
|
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"),
|
||||||
DBpath: absPath(viper.GetString("db_path")),
|
DBpath: absPath(viper.GetString("db_path")),
|
||||||
|
@ -254,9 +265,11 @@ func getHeadscaleConfig() headscale.Config {
|
||||||
DBuser: viper.GetString("db_user"),
|
DBuser: viper.GetString("db_user"),
|
||||||
DBpass: viper.GetString("db_pass"),
|
DBpass: viper.GetString("db_pass"),
|
||||||
|
|
||||||
TLSLetsEncryptHostname: viper.GetString("tls_letsencrypt_hostname"),
|
TLSLetsEncryptHostname: viper.GetString("tls_letsencrypt_hostname"),
|
||||||
TLSLetsEncryptListen: viper.GetString("tls_letsencrypt_listen"),
|
TLSLetsEncryptListen: viper.GetString("tls_letsencrypt_listen"),
|
||||||
TLSLetsEncryptCacheDir: absPath(viper.GetString("tls_letsencrypt_cache_dir")),
|
TLSLetsEncryptCacheDir: absPath(
|
||||||
|
viper.GetString("tls_letsencrypt_cache_dir"),
|
||||||
|
),
|
||||||
TLSLetsEncryptChallengeType: viper.GetString("tls_letsencrypt_challenge_type"),
|
TLSLetsEncryptChallengeType: viper.GetString("tls_letsencrypt_challenge_type"),
|
||||||
|
|
||||||
TLSCertPath: absPath(viper.GetString("tls_cert_path")),
|
TLSCertPath: absPath(viper.GetString("tls_cert_path")),
|
||||||
|
@ -431,7 +444,10 @@ type tokenAuth struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return value is mapped to request headers.
|
// Return value is mapped to request headers.
|
||||||
func (t tokenAuth) GetRequestMetadata(ctx context.Context, in ...string) (map[string]string, error) {
|
func (t tokenAuth) GetRequestMetadata(
|
||||||
|
ctx context.Context,
|
||||||
|
in ...string,
|
||||||
|
) (map[string]string, error) {
|
||||||
return map[string]string{
|
return map[string]string{
|
||||||
"authorization": "Bearer " + t.token,
|
"authorization": "Bearer " + t.token,
|
||||||
}, nil
|
}, nil
|
||||||
|
|
|
@ -63,7 +63,8 @@ func main() {
|
||||||
}
|
}
|
||||||
|
|
||||||
if !viper.GetBool("disable_check_updates") && !machineOutput {
|
if !viper.GetBool("disable_check_updates") && !machineOutput {
|
||||||
if (runtime.GOOS == "linux" || runtime.GOOS == "darwin") && cli.Version != "dev" {
|
if (runtime.GOOS == "linux" || runtime.GOOS == "darwin") &&
|
||||||
|
cli.Version != "dev" {
|
||||||
githubTag := &latest.GithubTag{
|
githubTag := &latest.GithubTag{
|
||||||
Owner: "juanfont",
|
Owner: "juanfont",
|
||||||
Repository: "headscale",
|
Repository: "headscale",
|
||||||
|
|
|
@ -40,7 +40,10 @@ func (*Suite) TestConfigLoading(c *check.C) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Symlink the example config file
|
// Symlink the example config file
|
||||||
err = os.Symlink(filepath.Clean(path+"/../../config-example.yaml"), filepath.Join(tmpDir, "config.yaml"))
|
err = os.Symlink(
|
||||||
|
filepath.Clean(path+"/../../config-example.yaml"),
|
||||||
|
filepath.Join(tmpDir, "config.yaml"),
|
||||||
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.Fatal(err)
|
c.Fatal(err)
|
||||||
}
|
}
|
||||||
|
@ -74,7 +77,10 @@ func (*Suite) TestDNSConfigLoading(c *check.C) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Symlink the example config file
|
// Symlink the example config file
|
||||||
err = os.Symlink(filepath.Clean(path+"/../../config-example.yaml"), filepath.Join(tmpDir, "config.yaml"))
|
err = os.Symlink(
|
||||||
|
filepath.Clean(path+"/../../config-example.yaml"),
|
||||||
|
filepath.Join(tmpDir, "config.yaml"),
|
||||||
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.Fatal(err)
|
c.Fatal(err)
|
||||||
}
|
}
|
||||||
|
@ -128,7 +134,11 @@ func (*Suite) TestTLSConfigValidation(c *check.C) {
|
||||||
check.Matches,
|
check.Matches,
|
||||||
".*Fatal config error: the only supported values for tls_letsencrypt_challenge_type are.*",
|
".*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)
|
||||||
|
|
5
db.go
5
db.go
|
@ -87,7 +87,10 @@ func (h *Headscale) openDB() (*gorm.DB, error) {
|
||||||
// getValue returns the value for the given key in KV
|
// getValue returns the value for the given key in KV
|
||||||
func (h *Headscale) getValue(key string) (string, error) {
|
func (h *Headscale) getValue(key string) (string, error) {
|
||||||
var row KV
|
var row KV
|
||||||
if result := h.db.First(&row, "key = ?", key); errors.Is(result.Error, gorm.ErrRecordNotFound) {
|
if result := h.db.First(&row, "key = ?", key); errors.Is(
|
||||||
|
result.Error,
|
||||||
|
gorm.ErrRecordNotFound,
|
||||||
|
) {
|
||||||
return "", errors.New("not found")
|
return "", errors.New("not found")
|
||||||
}
|
}
|
||||||
return row.Value, nil
|
return row.Value, nil
|
||||||
|
|
17
dns.go
17
dns.go
|
@ -30,7 +30,10 @@ import (
|
||||||
|
|
||||||
// From the netmask we can find out the wildcard bits (the bits that are not set in the netmask).
|
// From the netmask we can find out the wildcard bits (the bits that are not set in the netmask).
|
||||||
// This allows us to then calculate the subnets included in the subsequent class block and generate the entries.
|
// This allows us to then calculate the subnets included in the subsequent class block and generate the entries.
|
||||||
func generateMagicDNSRootDomains(ipPrefix netaddr.IPPrefix, baseDomain string) ([]dnsname.FQDN, error) {
|
func generateMagicDNSRootDomains(
|
||||||
|
ipPrefix netaddr.IPPrefix,
|
||||||
|
baseDomain string,
|
||||||
|
) ([]dnsname.FQDN, error) {
|
||||||
// TODO(juanfont): we are not handing out IPv6 addresses yet
|
// TODO(juanfont): we are not handing out IPv6 addresses yet
|
||||||
// and in fact this is Tailscale.com's range (note the fd7a:115c:a1e0: range in the fc00::/7 network)
|
// and in fact this is Tailscale.com's range (note the fd7a:115c:a1e0: range in the fc00::/7 network)
|
||||||
ipv6base := dnsname.FQDN("0.e.1.a.c.5.1.1.a.7.d.f.ip6.arpa.")
|
ipv6base := dnsname.FQDN("0.e.1.a.c.5.1.1.a.7.d.f.ip6.arpa.")
|
||||||
|
@ -69,12 +72,20 @@ func generateMagicDNSRootDomains(ipPrefix netaddr.IPPrefix, baseDomain string) (
|
||||||
return fqdns, nil
|
return fqdns, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func getMapResponseDNSConfig(dnsConfigOrig *tailcfg.DNSConfig, baseDomain string, m Machine, peers Machines) (*tailcfg.DNSConfig, error) {
|
func getMapResponseDNSConfig(
|
||||||
|
dnsConfigOrig *tailcfg.DNSConfig,
|
||||||
|
baseDomain string,
|
||||||
|
m Machine,
|
||||||
|
peers Machines,
|
||||||
|
) (*tailcfg.DNSConfig, error) {
|
||||||
var dnsConfig *tailcfg.DNSConfig
|
var dnsConfig *tailcfg.DNSConfig
|
||||||
if dnsConfigOrig != nil && dnsConfigOrig.Proxied { // if MagicDNS is enabled
|
if dnsConfigOrig != nil && dnsConfigOrig.Proxied { // if MagicDNS is enabled
|
||||||
// Only inject the Search Domain of the current namespace - shared nodes should use their full FQDN
|
// Only inject the Search Domain of the current namespace - shared nodes should use their full FQDN
|
||||||
dnsConfig = dnsConfigOrig.Clone()
|
dnsConfig = dnsConfigOrig.Clone()
|
||||||
dnsConfig.Domains = append(dnsConfig.Domains, fmt.Sprintf("%s.%s", m.Namespace.Name, baseDomain))
|
dnsConfig.Domains = append(
|
||||||
|
dnsConfig.Domains,
|
||||||
|
fmt.Sprintf("%s.%s", m.Namespace.Name, baseDomain),
|
||||||
|
)
|
||||||
|
|
||||||
namespaceSet := set.New(set.ThreadSafe)
|
namespaceSet := set.New(set.ThreadSafe)
|
||||||
namespaceSet.Add(m.Namespace)
|
namespaceSet.Add(m.Namespace)
|
||||||
|
|
15
grpcv1.go
15
grpcv1.go
|
@ -155,7 +155,10 @@ func (api headscaleV1APIServer) RegisterMachine(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
request *v1.RegisterMachineRequest,
|
request *v1.RegisterMachineRequest,
|
||||||
) (*v1.RegisterMachineResponse, error) {
|
) (*v1.RegisterMachineResponse, error) {
|
||||||
log.Trace().Str("namespace", request.GetNamespace()).Str("machine_key", request.GetKey()).Msg("Registering machine")
|
log.Trace().
|
||||||
|
Str("namespace", request.GetNamespace()).
|
||||||
|
Str("machine_key", request.GetKey()).
|
||||||
|
Msg("Registering machine")
|
||||||
machine, err := api.h.RegisterMachine(
|
machine, err := api.h.RegisterMachine(
|
||||||
request.GetKey(),
|
request.GetKey(),
|
||||||
request.GetNamespace(),
|
request.GetNamespace(),
|
||||||
|
@ -208,7 +211,9 @@ func (api headscaleV1APIServer) ListMachines(
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
sharedMachines, err := api.h.ListSharedMachinesInNamespace(request.GetNamespace())
|
sharedMachines, err := api.h.ListSharedMachinesInNamespace(
|
||||||
|
request.GetNamespace(),
|
||||||
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -338,7 +343,11 @@ func (api headscaleV1APIServer) DebugCreateMachine(
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Trace().Caller().Interface("route-prefix", routes).Interface("route-str", request.GetRoutes()).Msg("")
|
log.Trace().
|
||||||
|
Caller().
|
||||||
|
Interface("route-prefix", routes).
|
||||||
|
Interface("route-str", request.GetRoutes()).
|
||||||
|
Msg("")
|
||||||
|
|
||||||
hostinfo := tailcfg.Hostinfo{
|
hostinfo := tailcfg.Hostinfo{
|
||||||
RoutableIPs: routes,
|
RoutableIPs: routes,
|
||||||
|
|
|
@ -109,7 +109,10 @@ func (s *IntegrationCLITestSuite) TearDownTest() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *IntegrationCLITestSuite) HandleStats(suiteName string, stats *suite.SuiteInformation) {
|
func (s *IntegrationCLITestSuite) HandleStats(
|
||||||
|
suiteName string,
|
||||||
|
stats *suite.SuiteInformation,
|
||||||
|
) {
|
||||||
s.stats = stats
|
s.stats = stats
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -298,11 +301,26 @@ func (s *IntegrationCLITestSuite) TestPreAuthKeyCommand() {
|
||||||
assert.True(s.T(), listedPreAuthKeys[3].Expiration.AsTime().After(time.Now()))
|
assert.True(s.T(), listedPreAuthKeys[3].Expiration.AsTime().After(time.Now()))
|
||||||
assert.True(s.T(), listedPreAuthKeys[4].Expiration.AsTime().After(time.Now()))
|
assert.True(s.T(), listedPreAuthKeys[4].Expiration.AsTime().After(time.Now()))
|
||||||
|
|
||||||
assert.True(s.T(), listedPreAuthKeys[0].Expiration.AsTime().Before(time.Now().Add(time.Hour*26)))
|
assert.True(
|
||||||
assert.True(s.T(), listedPreAuthKeys[1].Expiration.AsTime().Before(time.Now().Add(time.Hour*26)))
|
s.T(),
|
||||||
assert.True(s.T(), listedPreAuthKeys[2].Expiration.AsTime().Before(time.Now().Add(time.Hour*26)))
|
listedPreAuthKeys[0].Expiration.AsTime().Before(time.Now().Add(time.Hour*26)),
|
||||||
assert.True(s.T(), listedPreAuthKeys[3].Expiration.AsTime().Before(time.Now().Add(time.Hour*26)))
|
)
|
||||||
assert.True(s.T(), listedPreAuthKeys[4].Expiration.AsTime().Before(time.Now().Add(time.Hour*26)))
|
assert.True(
|
||||||
|
s.T(),
|
||||||
|
listedPreAuthKeys[1].Expiration.AsTime().Before(time.Now().Add(time.Hour*26)),
|
||||||
|
)
|
||||||
|
assert.True(
|
||||||
|
s.T(),
|
||||||
|
listedPreAuthKeys[2].Expiration.AsTime().Before(time.Now().Add(time.Hour*26)),
|
||||||
|
)
|
||||||
|
assert.True(
|
||||||
|
s.T(),
|
||||||
|
listedPreAuthKeys[3].Expiration.AsTime().Before(time.Now().Add(time.Hour*26)),
|
||||||
|
)
|
||||||
|
assert.True(
|
||||||
|
s.T(),
|
||||||
|
listedPreAuthKeys[4].Expiration.AsTime().Before(time.Now().Add(time.Hour*26)),
|
||||||
|
)
|
||||||
|
|
||||||
// Expire three keys
|
// Expire three keys
|
||||||
for i := 0; i < 3; i++ {
|
for i := 0; i < 3; i++ {
|
||||||
|
@ -341,11 +359,26 @@ func (s *IntegrationCLITestSuite) TestPreAuthKeyCommand() {
|
||||||
err = json.Unmarshal([]byte(listAfterExpireResult), &listedAfterExpirePreAuthKeys)
|
err = json.Unmarshal([]byte(listAfterExpireResult), &listedAfterExpirePreAuthKeys)
|
||||||
assert.Nil(s.T(), err)
|
assert.Nil(s.T(), err)
|
||||||
|
|
||||||
assert.True(s.T(), listedAfterExpirePreAuthKeys[0].Expiration.AsTime().Before(time.Now()))
|
assert.True(
|
||||||
assert.True(s.T(), listedAfterExpirePreAuthKeys[1].Expiration.AsTime().Before(time.Now()))
|
s.T(),
|
||||||
assert.True(s.T(), listedAfterExpirePreAuthKeys[2].Expiration.AsTime().Before(time.Now()))
|
listedAfterExpirePreAuthKeys[0].Expiration.AsTime().Before(time.Now()),
|
||||||
assert.True(s.T(), listedAfterExpirePreAuthKeys[3].Expiration.AsTime().After(time.Now()))
|
)
|
||||||
assert.True(s.T(), listedAfterExpirePreAuthKeys[4].Expiration.AsTime().After(time.Now()))
|
assert.True(
|
||||||
|
s.T(),
|
||||||
|
listedAfterExpirePreAuthKeys[1].Expiration.AsTime().Before(time.Now()),
|
||||||
|
)
|
||||||
|
assert.True(
|
||||||
|
s.T(),
|
||||||
|
listedAfterExpirePreAuthKeys[2].Expiration.AsTime().Before(time.Now()),
|
||||||
|
)
|
||||||
|
assert.True(
|
||||||
|
s.T(),
|
||||||
|
listedAfterExpirePreAuthKeys[3].Expiration.AsTime().After(time.Now()),
|
||||||
|
)
|
||||||
|
assert.True(
|
||||||
|
s.T(),
|
||||||
|
listedAfterExpirePreAuthKeys[4].Expiration.AsTime().After(time.Now()),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *IntegrationCLITestSuite) TestPreAuthKeyCommandWithoutExpiry() {
|
func (s *IntegrationCLITestSuite) TestPreAuthKeyCommandWithoutExpiry() {
|
||||||
|
@ -689,7 +722,10 @@ func (s *IntegrationCLITestSuite) TestNodeCommand() {
|
||||||
assert.Nil(s.T(), err)
|
assert.Nil(s.T(), err)
|
||||||
|
|
||||||
var listOnlySharedMachineNamespace []v1.Machine
|
var listOnlySharedMachineNamespace []v1.Machine
|
||||||
err = json.Unmarshal([]byte(listOnlySharedMachineNamespaceResult), &listOnlySharedMachineNamespace)
|
err = json.Unmarshal(
|
||||||
|
[]byte(listOnlySharedMachineNamespaceResult),
|
||||||
|
&listOnlySharedMachineNamespace,
|
||||||
|
)
|
||||||
assert.Nil(s.T(), err)
|
assert.Nil(s.T(), err)
|
||||||
|
|
||||||
assert.Len(s.T(), listOnlySharedMachineNamespace, 2)
|
assert.Len(s.T(), listOnlySharedMachineNamespace, 2)
|
||||||
|
@ -738,7 +774,10 @@ func (s *IntegrationCLITestSuite) TestNodeCommand() {
|
||||||
assert.Nil(s.T(), err)
|
assert.Nil(s.T(), err)
|
||||||
|
|
||||||
var listOnlyMachineNamespaceAfterDelete []v1.Machine
|
var listOnlyMachineNamespaceAfterDelete []v1.Machine
|
||||||
err = json.Unmarshal([]byte(listOnlyMachineNamespaceAfterDeleteResult), &listOnlyMachineNamespaceAfterDelete)
|
err = json.Unmarshal(
|
||||||
|
[]byte(listOnlyMachineNamespaceAfterDeleteResult),
|
||||||
|
&listOnlyMachineNamespaceAfterDelete,
|
||||||
|
)
|
||||||
assert.Nil(s.T(), err)
|
assert.Nil(s.T(), err)
|
||||||
|
|
||||||
assert.Len(s.T(), listOnlyMachineNamespaceAfterDelete, 4)
|
assert.Len(s.T(), listOnlyMachineNamespaceAfterDelete, 4)
|
||||||
|
@ -789,7 +828,10 @@ func (s *IntegrationCLITestSuite) TestNodeCommand() {
|
||||||
assert.Nil(s.T(), err)
|
assert.Nil(s.T(), err)
|
||||||
|
|
||||||
var listOnlyMachineNamespaceAfterShare []v1.Machine
|
var listOnlyMachineNamespaceAfterShare []v1.Machine
|
||||||
err = json.Unmarshal([]byte(listOnlyMachineNamespaceAfterShareResult), &listOnlyMachineNamespaceAfterShare)
|
err = json.Unmarshal(
|
||||||
|
[]byte(listOnlyMachineNamespaceAfterShareResult),
|
||||||
|
&listOnlyMachineNamespaceAfterShare,
|
||||||
|
)
|
||||||
assert.Nil(s.T(), err)
|
assert.Nil(s.T(), err)
|
||||||
|
|
||||||
assert.Len(s.T(), listOnlyMachineNamespaceAfterShare, 5)
|
assert.Len(s.T(), listOnlyMachineNamespaceAfterShare, 5)
|
||||||
|
@ -846,7 +888,10 @@ func (s *IntegrationCLITestSuite) TestNodeCommand() {
|
||||||
assert.Nil(s.T(), err)
|
assert.Nil(s.T(), err)
|
||||||
|
|
||||||
var listOnlyMachineNamespaceAfterUnshare []v1.Machine
|
var listOnlyMachineNamespaceAfterUnshare []v1.Machine
|
||||||
err = json.Unmarshal([]byte(listOnlyMachineNamespaceAfterUnshareResult), &listOnlyMachineNamespaceAfterUnshare)
|
err = json.Unmarshal(
|
||||||
|
[]byte(listOnlyMachineNamespaceAfterUnshareResult),
|
||||||
|
&listOnlyMachineNamespaceAfterUnshare,
|
||||||
|
)
|
||||||
assert.Nil(s.T(), err)
|
assert.Nil(s.T(), err)
|
||||||
|
|
||||||
assert.Len(s.T(), listOnlyMachineNamespaceAfterUnshare, 4)
|
assert.Len(s.T(), listOnlyMachineNamespaceAfterUnshare, 4)
|
||||||
|
@ -1010,5 +1055,9 @@ func (s *IntegrationCLITestSuite) TestRouteCommand() {
|
||||||
)
|
)
|
||||||
assert.Nil(s.T(), err)
|
assert.Nil(s.T(), err)
|
||||||
|
|
||||||
assert.Contains(s.T(), string(failEnableNonAdvertisedRoute), "route (route-machine) is not available on node")
|
assert.Contains(
|
||||||
|
s.T(),
|
||||||
|
string(failEnableNonAdvertisedRoute),
|
||||||
|
"route (route-machine) is not available on node",
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,7 +12,11 @@ import (
|
||||||
"github.com/ory/dockertest/v3/docker"
|
"github.com/ory/dockertest/v3/docker"
|
||||||
)
|
)
|
||||||
|
|
||||||
func ExecuteCommand(resource *dockertest.Resource, cmd []string, env []string) (string, error) {
|
func ExecuteCommand(
|
||||||
|
resource *dockertest.Resource,
|
||||||
|
cmd []string,
|
||||||
|
env []string,
|
||||||
|
) (string, error) {
|
||||||
var stdout bytes.Buffer
|
var stdout bytes.Buffer
|
||||||
var stderr bytes.Buffer
|
var stderr bytes.Buffer
|
||||||
|
|
||||||
|
|
|
@ -89,7 +89,10 @@ func TestIntegrationTestSuite(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *IntegrationTestSuite) saveLog(resource *dockertest.Resource, basePath string) error {
|
func (s *IntegrationTestSuite) saveLog(
|
||||||
|
resource *dockertest.Resource,
|
||||||
|
basePath string,
|
||||||
|
) error {
|
||||||
err := os.MkdirAll(basePath, os.ModePerm)
|
err := os.MkdirAll(basePath, os.ModePerm)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -118,12 +121,20 @@ func (s *IntegrationTestSuite) saveLog(resource *dockertest.Resource, basePath s
|
||||||
|
|
||||||
fmt.Printf("Saving logs for %s to %s\n", resource.Container.Name, basePath)
|
fmt.Printf("Saving logs for %s to %s\n", resource.Container.Name, basePath)
|
||||||
|
|
||||||
err = ioutil.WriteFile(path.Join(basePath, resource.Container.Name+".stdout.log"), []byte(stdout.String()), 0o644)
|
err = ioutil.WriteFile(
|
||||||
|
path.Join(basePath, resource.Container.Name+".stdout.log"),
|
||||||
|
[]byte(stdout.String()),
|
||||||
|
0o644,
|
||||||
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = ioutil.WriteFile(path.Join(basePath, resource.Container.Name+".stderr.log"), []byte(stdout.String()), 0o644)
|
err = ioutil.WriteFile(
|
||||||
|
path.Join(basePath, resource.Container.Name+".stderr.log"),
|
||||||
|
[]byte(stdout.String()),
|
||||||
|
0o644,
|
||||||
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -144,14 +155,27 @@ func (s *IntegrationTestSuite) tailscaleContainer(
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
hostname := fmt.Sprintf("%s-tailscale-%s-%s", namespace, strings.Replace(version, ".", "-", -1), identifier)
|
hostname := fmt.Sprintf(
|
||||||
|
"%s-tailscale-%s-%s",
|
||||||
|
namespace,
|
||||||
|
strings.Replace(version, ".", "-", -1),
|
||||||
|
identifier,
|
||||||
|
)
|
||||||
tailscaleOptions := &dockertest.RunOptions{
|
tailscaleOptions := &dockertest.RunOptions{
|
||||||
Name: hostname,
|
Name: hostname,
|
||||||
Networks: []*dockertest.Network{&s.network},
|
Networks: []*dockertest.Network{&s.network},
|
||||||
Cmd: []string{"tailscaled", "--tun=userspace-networking", "--socks5-server=localhost:1055"},
|
Cmd: []string{
|
||||||
|
"tailscaled",
|
||||||
|
"--tun=userspace-networking",
|
||||||
|
"--socks5-server=localhost:1055",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
pts, err := s.pool.BuildAndRunWithBuildOptions(tailscaleBuildOptions, tailscaleOptions, DockerRestartPolicy)
|
pts, err := s.pool.BuildAndRunWithBuildOptions(
|
||||||
|
tailscaleBuildOptions,
|
||||||
|
tailscaleOptions,
|
||||||
|
DockerRestartPolicy,
|
||||||
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Could not start resource: %s", err)
|
log.Fatalf("Could not start resource: %s", err)
|
||||||
}
|
}
|
||||||
|
@ -210,7 +234,11 @@ func (s *IntegrationTestSuite) SetupSuite() {
|
||||||
for i := 0; i < scales.count; i++ {
|
for i := 0; i < scales.count; i++ {
|
||||||
version := tailscaleVersions[i%len(tailscaleVersions)]
|
version := tailscaleVersions[i%len(tailscaleVersions)]
|
||||||
|
|
||||||
hostname, container := s.tailscaleContainer(namespace, fmt.Sprint(i), version)
|
hostname, container := s.tailscaleContainer(
|
||||||
|
namespace,
|
||||||
|
fmt.Sprint(i),
|
||||||
|
version,
|
||||||
|
)
|
||||||
scales.tailscales[hostname] = *container
|
scales.tailscales[hostname] = *container
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -273,7 +301,10 @@ func (s *IntegrationTestSuite) SetupSuite() {
|
||||||
|
|
||||||
headscaleEndpoint := "http://headscale:8080"
|
headscaleEndpoint := "http://headscale:8080"
|
||||||
|
|
||||||
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{
|
command := []string{
|
||||||
"tailscale",
|
"tailscale",
|
||||||
|
@ -307,7 +338,10 @@ func (s *IntegrationTestSuite) SetupSuite() {
|
||||||
func (s *IntegrationTestSuite) TearDownSuite() {
|
func (s *IntegrationTestSuite) TearDownSuite() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *IntegrationTestSuite) HandleStats(suiteName string, stats *suite.SuiteInformation) {
|
func (s *IntegrationTestSuite) HandleStats(
|
||||||
|
suiteName string,
|
||||||
|
stats *suite.SuiteInformation,
|
||||||
|
) {
|
||||||
s.stats = stats
|
s.stats = stats
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -427,7 +461,13 @@ func (s *IntegrationTestSuite) TestPingAllPeers() {
|
||||||
ip.String(),
|
ip.String(),
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("Pinging from %s (%s) to %s (%s)\n", hostname, ips[hostname], peername, ip)
|
fmt.Printf(
|
||||||
|
"Pinging from %s (%s) to %s (%s)\n",
|
||||||
|
hostname,
|
||||||
|
ips[hostname],
|
||||||
|
peername,
|
||||||
|
ip,
|
||||||
|
)
|
||||||
result, err := ExecuteCommand(
|
result, err := ExecuteCommand(
|
||||||
&tailscale,
|
&tailscale,
|
||||||
command,
|
command,
|
||||||
|
@ -449,7 +489,15 @@ func (s *IntegrationTestSuite) TestSharedNodes() {
|
||||||
|
|
||||||
result, err := ExecuteCommand(
|
result, err := ExecuteCommand(
|
||||||
&s.headscale,
|
&s.headscale,
|
||||||
[]string{"headscale", "nodes", "list", "--output", "json", "--namespace", "shared"},
|
[]string{
|
||||||
|
"headscale",
|
||||||
|
"nodes",
|
||||||
|
"list",
|
||||||
|
"--output",
|
||||||
|
"json",
|
||||||
|
"--namespace",
|
||||||
|
"shared",
|
||||||
|
},
|
||||||
[]string{},
|
[]string{},
|
||||||
)
|
)
|
||||||
assert.Nil(s.T(), err)
|
assert.Nil(s.T(), err)
|
||||||
|
@ -520,7 +568,13 @@ func (s *IntegrationTestSuite) TestSharedNodes() {
|
||||||
ip.String(),
|
ip.String(),
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("Pinging from %s (%s) to %s (%s)\n", hostname, mainIps[hostname], peername, ip)
|
fmt.Printf(
|
||||||
|
"Pinging from %s (%s) to %s (%s)\n",
|
||||||
|
hostname,
|
||||||
|
mainIps[hostname],
|
||||||
|
peername,
|
||||||
|
ip,
|
||||||
|
)
|
||||||
result, err := ExecuteCommand(
|
result, err := ExecuteCommand(
|
||||||
&tailscale,
|
&tailscale,
|
||||||
command,
|
command,
|
||||||
|
@ -578,9 +632,19 @@ func (s *IntegrationTestSuite) TestTailDrop() {
|
||||||
"PUT",
|
"PUT",
|
||||||
"--upload-file",
|
"--upload-file",
|
||||||
fmt.Sprintf("/tmp/file_from_%s", hostname),
|
fmt.Sprintf("/tmp/file_from_%s", hostname),
|
||||||
fmt.Sprintf("%s/v0/put/file_from_%s", peerAPI, hostname),
|
fmt.Sprintf(
|
||||||
|
"%s/v0/put/file_from_%s",
|
||||||
|
peerAPI,
|
||||||
|
hostname,
|
||||||
|
),
|
||||||
}
|
}
|
||||||
fmt.Printf("Sending file from %s (%s) to %s (%s)\n", hostname, ips[hostname], peername, ip)
|
fmt.Printf(
|
||||||
|
"Sending file from %s (%s) to %s (%s)\n",
|
||||||
|
hostname,
|
||||||
|
ips[hostname],
|
||||||
|
peername,
|
||||||
|
ip,
|
||||||
|
)
|
||||||
_, err = ExecuteCommand(
|
_, err = ExecuteCommand(
|
||||||
&tailscale,
|
&tailscale,
|
||||||
command,
|
command,
|
||||||
|
@ -621,7 +685,13 @@ func (s *IntegrationTestSuite) TestTailDrop() {
|
||||||
"ls",
|
"ls",
|
||||||
fmt.Sprintf("/tmp/file_from_%s", peername),
|
fmt.Sprintf("/tmp/file_from_%s", peername),
|
||||||
}
|
}
|
||||||
fmt.Printf("Checking file in %s (%s) from %s (%s)\n", hostname, ips[hostname], peername, ip)
|
fmt.Printf(
|
||||||
|
"Checking file in %s (%s) from %s (%s)\n",
|
||||||
|
hostname,
|
||||||
|
ips[hostname],
|
||||||
|
peername,
|
||||||
|
ip,
|
||||||
|
)
|
||||||
result, err := ExecuteCommand(
|
result, err := ExecuteCommand(
|
||||||
&tailscale,
|
&tailscale,
|
||||||
command,
|
command,
|
||||||
|
@ -629,7 +699,11 @@ func (s *IntegrationTestSuite) TestTailDrop() {
|
||||||
)
|
)
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
fmt.Printf("Result for %s: %s\n", peername, result)
|
fmt.Printf("Result for %s: %s\n", peername, result)
|
||||||
assert.Equal(t, result, fmt.Sprintf("/tmp/file_from_%s\n", peername))
|
assert.Equal(
|
||||||
|
t,
|
||||||
|
result,
|
||||||
|
fmt.Sprintf("/tmp/file_from_%s\n", peername),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -699,7 +773,9 @@ func getIPs(tailscales map[string]dockertest.Resource) (map[string]netaddr.IP, e
|
||||||
return ips, nil
|
return ips, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func getAPIURLs(tailscales map[string]dockertest.Resource) (map[netaddr.IP]string, error) {
|
func getAPIURLs(
|
||||||
|
tailscales map[string]dockertest.Resource,
|
||||||
|
) (map[netaddr.IP]string, error) {
|
||||||
fts := make(map[netaddr.IP]string)
|
fts := make(map[netaddr.IP]string)
|
||||||
for _, tailscale := range tailscales {
|
for _, tailscale := range tailscales {
|
||||||
command := []string{
|
command := []string{
|
||||||
|
|
34
machine.go
34
machine.go
|
@ -73,8 +73,12 @@ func (m Machine) isExpired() bool {
|
||||||
func (h *Headscale) updateMachineExpiry(m *Machine) {
|
func (h *Headscale) updateMachineExpiry(m *Machine) {
|
||||||
if m.isExpired() {
|
if m.isExpired() {
|
||||||
now := time.Now().UTC()
|
now := time.Now().UTC()
|
||||||
maxExpiry := now.Add(h.cfg.MaxMachineRegistrationDuration) // calculate the maximum expiry
|
maxExpiry := now.Add(
|
||||||
defaultExpiry := now.Add(h.cfg.DefaultMachineRegistrationDuration) // calculate the default expiry
|
h.cfg.MaxMachineRegistrationDuration,
|
||||||
|
) // calculate the maximum expiry
|
||||||
|
defaultExpiry := now.Add(
|
||||||
|
h.cfg.DefaultMachineRegistrationDuration,
|
||||||
|
) // calculate the default expiry
|
||||||
|
|
||||||
// clamp the expiry time of the machine registration to the maximum allowed, or use the default if none supplied
|
// clamp the expiry time of the machine registration to the maximum allowed, or use the default if none supplied
|
||||||
if maxExpiry.Before(*m.RequestedExpiry) {
|
if maxExpiry.Before(*m.RequestedExpiry) {
|
||||||
|
@ -157,7 +161,9 @@ func (h *Headscale) getSharedTo(m *Machine) (Machines, error) {
|
||||||
|
|
||||||
peers := make(Machines, 0)
|
peers := make(Machines, 0)
|
||||||
for _, sharedMachine := range sharedMachines {
|
for _, sharedMachine := range sharedMachines {
|
||||||
namespaceMachines, err := h.ListMachinesInNamespace(sharedMachine.Namespace.Name)
|
namespaceMachines, err := h.ListMachinesInNamespace(
|
||||||
|
sharedMachine.Namespace.Name,
|
||||||
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return Machines{}, err
|
return Machines{}, err
|
||||||
}
|
}
|
||||||
|
@ -392,7 +398,11 @@ func (ms Machines) toNodes(
|
||||||
|
|
||||||
// toNode converts a Machine into a Tailscale Node. includeRoutes is false for shared nodes
|
// toNode converts a Machine into a Tailscale Node. includeRoutes is false for shared nodes
|
||||||
// as per the expected behaviour in the official SaaS
|
// as per the expected behaviour in the official SaaS
|
||||||
func (m Machine) toNode(baseDomain string, dnsConfig *tailcfg.DNSConfig, includeRoutes bool) (*tailcfg.Node, error) {
|
func (m Machine) toNode(
|
||||||
|
baseDomain string,
|
||||||
|
dnsConfig *tailcfg.DNSConfig,
|
||||||
|
includeRoutes bool,
|
||||||
|
) (*tailcfg.Node, error) {
|
||||||
nKey, err := wgkey.ParseHex(m.NodeKey)
|
nKey, err := wgkey.ParseHex(m.NodeKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -425,7 +435,10 @@ func (m Machine) toNode(baseDomain string, dnsConfig *tailcfg.DNSConfig, include
|
||||||
addrs = append(addrs, ip) // missing the ipv6 ?
|
addrs = append(addrs, ip) // missing the ipv6 ?
|
||||||
|
|
||||||
allowedIPs := []netaddr.IPPrefix{}
|
allowedIPs := []netaddr.IPPrefix{}
|
||||||
allowedIPs = append(allowedIPs, ip) // we append the node own IP, as it is required by the clients
|
allowedIPs = append(
|
||||||
|
allowedIPs,
|
||||||
|
ip,
|
||||||
|
) // we append the node own IP, as it is required by the clients
|
||||||
|
|
||||||
if includeRoutes {
|
if includeRoutes {
|
||||||
routesStr := []string{}
|
routesStr := []string{}
|
||||||
|
@ -571,7 +584,10 @@ func (h *Headscale) RegisterMachine(key string, namespace string) (*Machine, err
|
||||||
}
|
}
|
||||||
|
|
||||||
m := Machine{}
|
m := Machine{}
|
||||||
if result := h.db.First(&m, "machine_key = ?", mKey.HexString()); errors.Is(result.Error, gorm.ErrRecordNotFound) {
|
if result := h.db.First(&m, "machine_key = ?", mKey.HexString()); errors.Is(
|
||||||
|
result.Error,
|
||||||
|
gorm.ErrRecordNotFound,
|
||||||
|
) {
|
||||||
return nil, errors.New("Machine not found")
|
return nil, errors.New("Machine not found")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -693,7 +709,11 @@ func (h *Headscale) EnableRoutes(m *Machine, routeStrs ...string) error {
|
||||||
|
|
||||||
for _, newRoute := range newRoutes {
|
for _, newRoute := range newRoutes {
|
||||||
if !containsIpPrefix(availableRoutes, newRoute) {
|
if !containsIpPrefix(availableRoutes, newRoute) {
|
||||||
return fmt.Errorf("route (%s) is not available on node %s", m.Name, newRoute)
|
return fmt.Errorf(
|
||||||
|
"route (%s) is not available on node %s",
|
||||||
|
m.Name,
|
||||||
|
newRoute,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -32,7 +32,7 @@ var (
|
||||||
Name: "update_request_sent_to_node_total",
|
Name: "update_request_sent_to_node_total",
|
||||||
Help: "The number of calls/messages issued on a specific nodes update channel",
|
Help: "The number of calls/messages issued on a specific nodes update channel",
|
||||||
}, []string{"namespace", "machine", "status"})
|
}, []string{"namespace", "machine", "status"})
|
||||||
//TODO(kradalby): This is very debugging, we might want to remove it.
|
// TODO(kradalby): This is very debugging, we might want to remove it.
|
||||||
updateRequestsReceivedOnChannel = promauto.NewCounterVec(prometheus.CounterOpts{
|
updateRequestsReceivedOnChannel = promauto.NewCounterVec(prometheus.CounterOpts{
|
||||||
Namespace: prometheusNamespace,
|
Namespace: prometheusNamespace,
|
||||||
Name: "update_request_received_on_channel_total",
|
Name: "update_request_received_on_channel_total",
|
||||||
|
|
|
@ -102,7 +102,10 @@ func (h *Headscale) RenameNamespace(oldName, newName string) error {
|
||||||
// GetNamespace fetches a namespace by name
|
// GetNamespace fetches a namespace by name
|
||||||
func (h *Headscale) GetNamespace(name string) (*Namespace, error) {
|
func (h *Headscale) GetNamespace(name string) (*Namespace, error) {
|
||||||
n := Namespace{}
|
n := Namespace{}
|
||||||
if result := h.db.First(&n, "name = ?", name); errors.Is(result.Error, gorm.ErrRecordNotFound) {
|
if result := h.db.First(&n, "name = ?", name); errors.Is(
|
||||||
|
result.Error,
|
||||||
|
gorm.ErrRecordNotFound,
|
||||||
|
) {
|
||||||
return nil, errorNamespaceNotFound
|
return nil, errorNamespaceNotFound
|
||||||
}
|
}
|
||||||
return &n, nil
|
return &n, nil
|
||||||
|
@ -144,7 +147,9 @@ func (h *Headscale) ListSharedMachinesInNamespace(name string) ([]Machine, error
|
||||||
|
|
||||||
machines := []Machine{}
|
machines := []Machine{}
|
||||||
for _, sharedMachine := range sharedMachines {
|
for _, sharedMachine := range sharedMachines {
|
||||||
machine, err := h.GetMachineByID(sharedMachine.MachineID) // otherwise not everything comes filled
|
machine, err := h.GetMachineByID(
|
||||||
|
sharedMachine.MachineID,
|
||||||
|
) // otherwise not everything comes filled
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -173,7 +178,10 @@ func (h *Headscale) RequestMapUpdates(namespaceID uint) error {
|
||||||
|
|
||||||
v, err := h.getValue("namespaces_pending_updates")
|
v, err := h.getValue("namespaces_pending_updates")
|
||||||
if err != nil || v == "" {
|
if err != nil || v == "" {
|
||||||
err = h.setValue("namespaces_pending_updates", fmt.Sprintf(`["%s"]`, namespace.Name))
|
err = h.setValue(
|
||||||
|
"namespaces_pending_updates",
|
||||||
|
fmt.Sprintf(`["%s"]`, namespace.Name),
|
||||||
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -182,7 +190,10 @@ func (h *Headscale) RequestMapUpdates(namespaceID uint) error {
|
||||||
names := []string{}
|
names := []string{}
|
||||||
err = json.Unmarshal([]byte(v), &names)
|
err = json.Unmarshal([]byte(v), &names)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = h.setValue("namespaces_pending_updates", fmt.Sprintf(`["%s"]`, namespace.Name))
|
err = h.setValue(
|
||||||
|
"namespaces_pending_updates",
|
||||||
|
fmt.Sprintf(`["%s"]`, namespace.Name),
|
||||||
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
38
oidc.go
38
oidc.go
|
@ -39,8 +39,11 @@ func (h *Headscale) initOIDC() error {
|
||||||
ClientID: h.cfg.OIDC.ClientID,
|
ClientID: h.cfg.OIDC.ClientID,
|
||||||
ClientSecret: h.cfg.OIDC.ClientSecret,
|
ClientSecret: h.cfg.OIDC.ClientSecret,
|
||||||
Endpoint: h.oidcProvider.Endpoint(),
|
Endpoint: h.oidcProvider.Endpoint(),
|
||||||
RedirectURL: fmt.Sprintf("%s/oidc/callback", strings.TrimSuffix(h.cfg.ServerURL, "/")),
|
RedirectURL: fmt.Sprintf(
|
||||||
Scopes: []string{oidc.ScopeOpenID, "profile", "email"},
|
"%s/oidc/callback",
|
||||||
|
strings.TrimSuffix(h.cfg.ServerURL, "/"),
|
||||||
|
),
|
||||||
|
Scopes: []string{oidc.ScopeOpenID, "profile", "email"},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -127,7 +130,10 @@ func (h *Headscale) OIDCCallback(c *gin.Context) {
|
||||||
// Extract custom claims
|
// Extract custom claims
|
||||||
var claims IDTokenClaims
|
var claims IDTokenClaims
|
||||||
if err = idToken.Claims(&claims); err != nil {
|
if err = idToken.Claims(&claims); err != nil {
|
||||||
c.String(http.StatusBadRequest, fmt.Sprintf("Failed to decode id token claims: %s", err))
|
c.String(
|
||||||
|
http.StatusBadRequest,
|
||||||
|
fmt.Sprintf("Failed to decode id token claims: %s", err),
|
||||||
|
)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -135,7 +141,8 @@ func (h *Headscale) OIDCCallback(c *gin.Context) {
|
||||||
mKeyIf, mKeyFound := h.oidcStateCache.Get(state)
|
mKeyIf, mKeyFound := h.oidcStateCache.Get(state)
|
||||||
|
|
||||||
if !mKeyFound {
|
if !mKeyFound {
|
||||||
log.Error().Msg("requested machine state key expired before authorisation completed")
|
log.Error().
|
||||||
|
Msg("requested machine state key expired before authorisation completed")
|
||||||
c.String(http.StatusBadRequest, "state has expired")
|
c.String(http.StatusBadRequest, "state has expired")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -151,7 +158,10 @@ func (h *Headscale) OIDCCallback(c *gin.Context) {
|
||||||
m, err := h.GetMachineByMachineKey(mKeyStr)
|
m, err := h.GetMachineByMachineKey(mKeyStr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error().Msg("machine key not found in database")
|
log.Error().Msg("machine key not found in database")
|
||||||
c.String(http.StatusInternalServerError, "could not get machine info from database")
|
c.String(
|
||||||
|
http.StatusInternalServerError,
|
||||||
|
"could not get machine info from database",
|
||||||
|
)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -168,15 +178,22 @@ func (h *Headscale) OIDCCallback(c *gin.Context) {
|
||||||
ns, err = h.CreateNamespace(nsName)
|
ns, err = h.CreateNamespace(nsName)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error().Msgf("could not create new namespace '%s'", claims.Email)
|
log.Error().
|
||||||
c.String(http.StatusInternalServerError, "could not create new namespace")
|
Msgf("could not create new namespace '%s'", claims.Email)
|
||||||
|
c.String(
|
||||||
|
http.StatusInternalServerError,
|
||||||
|
"could not create new namespace",
|
||||||
|
)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ip, err := h.getAvailableIP()
|
ip, err := h.getAvailableIP()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.String(http.StatusInternalServerError, "could not get an IP from the pool")
|
c.String(
|
||||||
|
http.StatusInternalServerError,
|
||||||
|
"could not get an IP from the pool",
|
||||||
|
)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -209,7 +226,10 @@ func (h *Headscale) OIDCCallback(c *gin.Context) {
|
||||||
Str("username", claims.Username).
|
Str("username", claims.Username).
|
||||||
Str("machine", m.Name).
|
Str("machine", m.Name).
|
||||||
Msg("Email could not be mapped to a namespace")
|
Msg("Email could not be mapped to a namespace")
|
||||||
c.String(http.StatusBadRequest, "email from claim could not be mapped to a namespace")
|
c.String(
|
||||||
|
http.StatusBadRequest,
|
||||||
|
"email from claim could not be mapped to a namespace",
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// getNamespaceFromEmail passes the users email through a list of "matchers"
|
// getNamespaceFromEmail passes the users email through a list of "matchers"
|
||||||
|
|
12
oidc_test.go
12
oidc_test.go
|
@ -164,10 +164,18 @@ func TestHeadscale_getNamespaceFromEmail(t *testing.T) {
|
||||||
}
|
}
|
||||||
got, got1 := h.getNamespaceFromEmail(tt.args.email)
|
got, got1 := h.getNamespaceFromEmail(tt.args.email)
|
||||||
if got != tt.want {
|
if got != tt.want {
|
||||||
t.Errorf("Headscale.getNamespaceFromEmail() got = %v, want %v", got, tt.want)
|
t.Errorf(
|
||||||
|
"Headscale.getNamespaceFromEmail() got = %v, want %v",
|
||||||
|
got,
|
||||||
|
tt.want,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
if got1 != tt.want1 {
|
if got1 != tt.want1 {
|
||||||
t.Errorf("Headscale.getNamespaceFromEmail() got1 = %v, want %v", got1, tt.want1)
|
t.Errorf(
|
||||||
|
"Headscale.getNamespaceFromEmail() got1 = %v, want %v",
|
||||||
|
got1,
|
||||||
|
tt.want1,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
37
poll.go
37
poll.go
|
@ -158,7 +158,8 @@ func (h *Headscale) PollNetMapHandler(c *gin.Context) {
|
||||||
|
|
||||||
// It sounds like we should update the nodes when we have received a endpoint update
|
// It sounds like we should update the nodes when we have received a endpoint update
|
||||||
// even tho the comments in the tailscale code dont explicitly say so.
|
// even tho the comments in the tailscale code dont explicitly say so.
|
||||||
updateRequestsFromNode.WithLabelValues(m.Name, m.Namespace.Name, "endpoint-update").Inc()
|
updateRequestsFromNode.WithLabelValues(m.Name, m.Namespace.Name, "endpoint-update").
|
||||||
|
Inc()
|
||||||
go func() { updateChan <- struct{}{} }()
|
go func() { updateChan <- struct{}{} }()
|
||||||
return
|
return
|
||||||
} else if req.OmitPeers && req.Stream {
|
} else if req.OmitPeers && req.Stream {
|
||||||
|
@ -184,10 +185,20 @@ func (h *Headscale) PollNetMapHandler(c *gin.Context) {
|
||||||
Str("handler", "PollNetMap").
|
Str("handler", "PollNetMap").
|
||||||
Str("machine", m.Name).
|
Str("machine", m.Name).
|
||||||
Msg("Notifying peers")
|
Msg("Notifying peers")
|
||||||
updateRequestsFromNode.WithLabelValues(m.Name, m.Namespace.Name, "full-update").Inc()
|
updateRequestsFromNode.WithLabelValues(m.Name, m.Namespace.Name, "full-update").
|
||||||
|
Inc()
|
||||||
go func() { updateChan <- struct{}{} }()
|
go func() { updateChan <- struct{}{} }()
|
||||||
|
|
||||||
h.PollNetMapStream(c, m, req, mKey, pollDataChan, keepAliveChan, updateChan, cancelKeepAlive)
|
h.PollNetMapStream(
|
||||||
|
c,
|
||||||
|
m,
|
||||||
|
req,
|
||||||
|
mKey,
|
||||||
|
pollDataChan,
|
||||||
|
keepAliveChan,
|
||||||
|
updateChan,
|
||||||
|
cancelKeepAlive,
|
||||||
|
)
|
||||||
log.Trace().
|
log.Trace().
|
||||||
Str("handler", "PollNetMap").
|
Str("handler", "PollNetMap").
|
||||||
Str("id", c.Param("id")).
|
Str("id", c.Param("id")).
|
||||||
|
@ -260,7 +271,8 @@ func (h *Headscale) PollNetMapStream(
|
||||||
now := time.Now().UTC()
|
now := time.Now().UTC()
|
||||||
m.LastSeen = &now
|
m.LastSeen = &now
|
||||||
|
|
||||||
lastStateUpdate.WithLabelValues(m.Namespace.Name, m.Name).Set(float64(now.Unix()))
|
lastStateUpdate.WithLabelValues(m.Namespace.Name, m.Name).
|
||||||
|
Set(float64(now.Unix()))
|
||||||
m.LastSuccessfulUpdate = &now
|
m.LastSuccessfulUpdate = &now
|
||||||
|
|
||||||
h.db.Save(&m)
|
h.db.Save(&m)
|
||||||
|
@ -324,7 +336,8 @@ func (h *Headscale) PollNetMapStream(
|
||||||
Str("machine", m.Name).
|
Str("machine", m.Name).
|
||||||
Str("channel", "update").
|
Str("channel", "update").
|
||||||
Msg("Received a request for update")
|
Msg("Received a request for update")
|
||||||
updateRequestsReceivedOnChannel.WithLabelValues(m.Name, m.Namespace.Name).Inc()
|
updateRequestsReceivedOnChannel.WithLabelValues(m.Name, m.Namespace.Name).
|
||||||
|
Inc()
|
||||||
if h.isOutdated(m) {
|
if h.isOutdated(m) {
|
||||||
log.Debug().
|
log.Debug().
|
||||||
Str("handler", "PollNetMapStream").
|
Str("handler", "PollNetMapStream").
|
||||||
|
@ -349,7 +362,8 @@ func (h *Headscale) PollNetMapStream(
|
||||||
Str("channel", "update").
|
Str("channel", "update").
|
||||||
Err(err).
|
Err(err).
|
||||||
Msg("Could not write the map response")
|
Msg("Could not write the map response")
|
||||||
updateRequestsSentToNode.WithLabelValues(m.Name, m.Namespace.Name, "failed").Inc()
|
updateRequestsSentToNode.WithLabelValues(m.Name, m.Namespace.Name, "failed").
|
||||||
|
Inc()
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
log.Trace().
|
log.Trace().
|
||||||
|
@ -357,7 +371,8 @@ func (h *Headscale) PollNetMapStream(
|
||||||
Str("machine", m.Name).
|
Str("machine", m.Name).
|
||||||
Str("channel", "update").
|
Str("channel", "update").
|
||||||
Msg("Updated Map has been sent")
|
Msg("Updated Map has been sent")
|
||||||
updateRequestsSentToNode.WithLabelValues(m.Name, m.Namespace.Name, "success").Inc()
|
updateRequestsSentToNode.WithLabelValues(m.Name, m.Namespace.Name, "success").
|
||||||
|
Inc()
|
||||||
|
|
||||||
// Keep track of the last successful update,
|
// Keep track of the last successful update,
|
||||||
// we sometimes end in a state were the update
|
// we sometimes end in a state were the update
|
||||||
|
@ -377,7 +392,8 @@ func (h *Headscale) PollNetMapStream(
|
||||||
}
|
}
|
||||||
now := time.Now().UTC()
|
now := time.Now().UTC()
|
||||||
|
|
||||||
lastStateUpdate.WithLabelValues(m.Namespace.Name, m.Name).Set(float64(now.Unix()))
|
lastStateUpdate.WithLabelValues(m.Namespace.Name, m.Name).
|
||||||
|
Set(float64(now.Unix()))
|
||||||
m.LastSuccessfulUpdate = &now
|
m.LastSuccessfulUpdate = &now
|
||||||
|
|
||||||
h.db.Save(&m)
|
h.db.Save(&m)
|
||||||
|
@ -424,7 +440,7 @@ func (h *Headscale) PollNetMapStream(
|
||||||
Str("machine", m.Name).
|
Str("machine", m.Name).
|
||||||
Str("channel", "Done").
|
Str("channel", "Done").
|
||||||
Msg("Closing update channel")
|
Msg("Closing update channel")
|
||||||
//h.closeUpdateChannel(m)
|
// h.closeUpdateChannel(m)
|
||||||
close(updateChan)
|
close(updateChan)
|
||||||
|
|
||||||
log.Trace().
|
log.Trace().
|
||||||
|
@ -483,7 +499,8 @@ func (h *Headscale) scheduledPollWorker(
|
||||||
Str("func", "scheduledPollWorker").
|
Str("func", "scheduledPollWorker").
|
||||||
Str("machine", m.Name).
|
Str("machine", m.Name).
|
||||||
Msg("Sending update request")
|
Msg("Sending update request")
|
||||||
updateRequestsFromNode.WithLabelValues(m.Name, m.Namespace.Name, "scheduled-update").Inc()
|
updateRequestsFromNode.WithLabelValues(m.Name, m.Namespace.Name, "scheduled-update").
|
||||||
|
Inc()
|
||||||
updateChan <- struct{}{}
|
updateChan <- struct{}{}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -105,7 +105,10 @@ func (h *Headscale) ExpirePreAuthKey(k *PreAuthKey) error {
|
||||||
// If returns no error and a PreAuthKey, it can be used
|
// If returns no error and a PreAuthKey, it can be used
|
||||||
func (h *Headscale) checkKeyValidity(k string) (*PreAuthKey, error) {
|
func (h *Headscale) checkKeyValidity(k string) (*PreAuthKey, error) {
|
||||||
pak := PreAuthKey{}
|
pak := PreAuthKey{}
|
||||||
if result := h.db.Preload("Namespace").First(&pak, "key = ?", k); errors.Is(result.Error, gorm.ErrRecordNotFound) {
|
if result := h.db.Preload("Namespace").First(&pak, "key = ?", k); errors.Is(
|
||||||
|
result.Error,
|
||||||
|
gorm.ErrRecordNotFound,
|
||||||
|
) {
|
||||||
return nil, errorAuthKeyNotFound
|
return nil, errorAuthKeyNotFound
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
22
routes.go
22
routes.go
|
@ -11,7 +11,10 @@ import (
|
||||||
// Deprecated: use machine function instead
|
// Deprecated: use machine function instead
|
||||||
// GetAdvertisedNodeRoutes returns the subnet routes advertised by a node (identified by
|
// GetAdvertisedNodeRoutes returns the subnet routes advertised by a node (identified by
|
||||||
// namespace and node name)
|
// namespace and node name)
|
||||||
func (h *Headscale) GetAdvertisedNodeRoutes(namespace string, nodeName string) (*[]netaddr.IPPrefix, error) {
|
func (h *Headscale) GetAdvertisedNodeRoutes(
|
||||||
|
namespace string,
|
||||||
|
nodeName string,
|
||||||
|
) (*[]netaddr.IPPrefix, error) {
|
||||||
m, err := h.GetMachine(namespace, nodeName)
|
m, err := h.GetMachine(namespace, nodeName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -27,7 +30,10 @@ func (h *Headscale) GetAdvertisedNodeRoutes(namespace string, nodeName string) (
|
||||||
// Deprecated: use machine function instead
|
// Deprecated: use machine function instead
|
||||||
// GetEnabledNodeRoutes returns the subnet routes enabled by a node (identified by
|
// GetEnabledNodeRoutes returns the subnet routes enabled by a node (identified by
|
||||||
// namespace and node name)
|
// namespace and node name)
|
||||||
func (h *Headscale) GetEnabledNodeRoutes(namespace string, nodeName string) ([]netaddr.IPPrefix, error) {
|
func (h *Headscale) GetEnabledNodeRoutes(
|
||||||
|
namespace string,
|
||||||
|
nodeName string,
|
||||||
|
) ([]netaddr.IPPrefix, error) {
|
||||||
m, err := h.GetMachine(namespace, nodeName)
|
m, err := h.GetMachine(namespace, nodeName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -58,7 +64,11 @@ func (h *Headscale) GetEnabledNodeRoutes(namespace string, nodeName string) ([]n
|
||||||
|
|
||||||
// Deprecated: use machine function instead
|
// Deprecated: use machine function instead
|
||||||
// IsNodeRouteEnabled checks if a certain route has been enabled
|
// IsNodeRouteEnabled checks if a certain route has been enabled
|
||||||
func (h *Headscale) IsNodeRouteEnabled(namespace string, nodeName string, routeStr string) bool {
|
func (h *Headscale) IsNodeRouteEnabled(
|
||||||
|
namespace string,
|
||||||
|
nodeName string,
|
||||||
|
routeStr string,
|
||||||
|
) bool {
|
||||||
route, err := netaddr.ParseIPPrefix(routeStr)
|
route, err := netaddr.ParseIPPrefix(routeStr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false
|
return false
|
||||||
|
@ -80,7 +90,11 @@ func (h *Headscale) IsNodeRouteEnabled(namespace string, nodeName string, routeS
|
||||||
// Deprecated: use EnableRoute in machine.go
|
// Deprecated: use EnableRoute in machine.go
|
||||||
// EnableNodeRoute enables a subnet route advertised by a node (identified by
|
// EnableNodeRoute enables a subnet route advertised by a node (identified by
|
||||||
// namespace and node name)
|
// namespace and node name)
|
||||||
func (h *Headscale) EnableNodeRoute(namespace string, nodeName string, routeStr string) error {
|
func (h *Headscale) EnableNodeRoute(
|
||||||
|
namespace string,
|
||||||
|
nodeName string,
|
||||||
|
routeStr string,
|
||||||
|
) error {
|
||||||
m, err := h.GetMachine(namespace, nodeName)
|
m, err := h.GetMachine(namespace, nodeName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
|
@ -93,7 +93,10 @@ func (s *Suite) TestGetEnableRoutes(c *check.C) {
|
||||||
}
|
}
|
||||||
h.db.Save(&m)
|
h.db.Save(&m)
|
||||||
|
|
||||||
availableRoutes, err := h.GetAdvertisedNodeRoutes("test", "test_enable_route_machine")
|
availableRoutes, err := h.GetAdvertisedNodeRoutes(
|
||||||
|
"test",
|
||||||
|
"test_enable_route_machine",
|
||||||
|
)
|
||||||
c.Assert(err, check.IsNil)
|
c.Assert(err, check.IsNil)
|
||||||
c.Assert(len(*availableRoutes), check.Equals, 2)
|
c.Assert(len(*availableRoutes), check.Equals, 2)
|
||||||
|
|
||||||
|
|
12
sharing.go
12
sharing.go
|
@ -2,9 +2,11 @@ package headscale
|
||||||
|
|
||||||
import "gorm.io/gorm"
|
import "gorm.io/gorm"
|
||||||
|
|
||||||
const errorSameNamespace = Error("Destination namespace same as origin")
|
const (
|
||||||
const errorMachineAlreadyShared = Error("Node already shared to this namespace")
|
errorSameNamespace = Error("Destination namespace same as origin")
|
||||||
const errorMachineNotShared = Error("Machine not shared to this namespace")
|
errorMachineAlreadyShared = Error("Node already shared to this namespace")
|
||||||
|
errorMachineNotShared = Error("Machine not shared to this namespace")
|
||||||
|
)
|
||||||
|
|
||||||
// SharedMachine is a join table to support sharing nodes between namespaces
|
// SharedMachine is a join table to support sharing nodes between namespaces
|
||||||
type SharedMachine struct {
|
type SharedMachine struct {
|
||||||
|
@ -48,7 +50,9 @@ func (h *Headscale) RemoveSharedMachineFromNamespace(m *Machine, ns *Namespace)
|
||||||
}
|
}
|
||||||
|
|
||||||
sharedMachine := SharedMachine{}
|
sharedMachine := SharedMachine{}
|
||||||
result := h.db.Where("machine_id = ? AND namespace_id = ?", m.ID, ns.ID).Unscoped().Delete(&sharedMachine)
|
result := h.db.Where("machine_id = ? AND namespace_id = ?", m.ID, ns.ID).
|
||||||
|
Unscoped().
|
||||||
|
Delete(&sharedMachine)
|
||||||
if result.Error != nil {
|
if result.Error != nil {
|
||||||
return result.Error
|
return result.Error
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,10 @@ import (
|
||||||
"gopkg.in/check.v1"
|
"gopkg.in/check.v1"
|
||||||
)
|
)
|
||||||
|
|
||||||
func CreateNodeNamespace(c *check.C, namespace, node, key, IP string) (*Namespace, *Machine) {
|
func CreateNodeNamespace(
|
||||||
|
c *check.C,
|
||||||
|
namespace, node, key, IP string,
|
||||||
|
) (*Namespace, *Machine) {
|
||||||
n1, err := h.CreateNamespace(namespace)
|
n1, err := h.CreateNamespace(namespace)
|
||||||
c.Assert(err, check.IsNil)
|
c.Assert(err, check.IsNil)
|
||||||
|
|
||||||
|
@ -229,7 +232,11 @@ func (s *Suite) TestComplexSharingAcrossNamespaces(c *check.C) {
|
||||||
|
|
||||||
p1sAfter, err := h.getPeers(m1)
|
p1sAfter, err := h.getPeers(m1)
|
||||||
c.Assert(err, check.IsNil)
|
c.Assert(err, check.IsNil)
|
||||||
c.Assert(len(p1sAfter), check.Equals, 2) // node1 can see node2 (shared) and node4 (same namespace)
|
c.Assert(
|
||||||
|
len(p1sAfter),
|
||||||
|
check.Equals,
|
||||||
|
2,
|
||||||
|
) // node1 can see node2 (shared) and node4 (same namespace)
|
||||||
c.Assert(p1sAfter[0].Name, check.Equals, m2.Name)
|
c.Assert(p1sAfter[0].Name, check.Equals, m2.Name)
|
||||||
c.Assert(p1sAfter[1].Name, check.Equals, m4.Name)
|
c.Assert(p1sAfter[1].Name, check.Equals, m4.Name)
|
||||||
|
|
||||||
|
|
|
@ -53,7 +53,11 @@ func SwaggerUI(c *gin.Context) {
|
||||||
Caller().
|
Caller().
|
||||||
Err(err).
|
Err(err).
|
||||||
Msg("Could not render Swagger")
|
Msg("Could not render Swagger")
|
||||||
c.Data(http.StatusInternalServerError, "text/html; charset=utf-8", []byte("Could not render Swagger"))
|
c.Data(
|
||||||
|
http.StatusInternalServerError,
|
||||||
|
"text/html; charset=utf-8",
|
||||||
|
[]byte("Could not render Swagger"),
|
||||||
|
)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
20
utils.go
20
utils.go
|
@ -25,11 +25,21 @@ type Error string
|
||||||
|
|
||||||
func (e Error) Error() string { return string(e) }
|
func (e Error) Error() string { return string(e) }
|
||||||
|
|
||||||
func decode(msg []byte, v interface{}, pubKey *wgkey.Key, privKey *wgkey.Private) error {
|
func decode(
|
||||||
|
msg []byte,
|
||||||
|
v interface{},
|
||||||
|
pubKey *wgkey.Key,
|
||||||
|
privKey *wgkey.Private,
|
||||||
|
) error {
|
||||||
return decodeMsg(msg, v, pubKey, privKey)
|
return decodeMsg(msg, v, pubKey, privKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
func decodeMsg(msg []byte, v interface{}, pubKey *wgkey.Key, privKey *wgkey.Private) error {
|
func decodeMsg(
|
||||||
|
msg []byte,
|
||||||
|
v interface{},
|
||||||
|
pubKey *wgkey.Key,
|
||||||
|
privKey *wgkey.Private,
|
||||||
|
) error {
|
||||||
decrypted, err := decryptMsg(msg, pubKey, privKey)
|
decrypted, err := decryptMsg(msg, pubKey, privKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -156,7 +166,11 @@ func tailNodesToString(nodes []*tailcfg.Node) string {
|
||||||
}
|
}
|
||||||
|
|
||||||
func tailMapResponseToString(resp tailcfg.MapResponse) string {
|
func tailMapResponseToString(resp tailcfg.MapResponse) string {
|
||||||
return fmt.Sprintf("{ Node: %s, Peers: %s }", resp.Node.Name, tailNodesToString(resp.Peers))
|
return fmt.Sprintf(
|
||||||
|
"{ Node: %s, Peers: %s }",
|
||||||
|
resp.Node.Name,
|
||||||
|
tailNodesToString(resp.Peers),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
func GrpcSocketDialer(ctx context.Context, addr string) (net.Conn, error) {
|
func GrpcSocketDialer(ctx context.Context, addr string) (net.Conn, error) {
|
||||||
|
|
Loading…
Reference in a new issue