Compare commits

...

34 Commits

Author SHA1 Message Date
Juan Font
c3aa9a5d4c Merge pull request #69 from juanfont/change-default-port
Use 8080 as default port in the example config
2021-07-31 11:47:15 +02:00
Juan Font Alonso
4fb55e1684 Use 8080 as default port, like in the Kubernetes yamls 2021-07-30 17:07:19 +02:00
Juan Font Alonso
91bfb481c1 Fix identation 2021-07-30 16:42:26 +02:00
Juan Font
201ba109c3 Merge pull request #62 from ohdearaugustin/topic/refactor-config
Topic/refactor config
2021-07-30 16:40:38 +02:00
Juan Font
d3f965d493 Merge pull request #66 from juanfont/remove-old-docker
Remove old docker code
2021-07-28 13:43:58 +02:00
Juan Font
f832d7325b Merge pull request #67 from kradalby/patch-1
Fix typo in example
2021-07-27 19:58:15 +02:00
Kristoffer Dalby
b1d1bd32c3 Fix typo in example
The example command is missing the `s` in `preauthkeys`
2021-07-27 18:37:43 +01:00
Juan Font Alonso
df6d4de6fd Remove old docker code 2021-07-27 17:05:22 +02:00
ohdearaugustin
ea3043cdcb cmd: Add error check for Persistent Flags 2021-07-25 16:26:15 +02:00
ohdearaugustin
04dffcc4ae Refactor cli commands 2021-07-25 15:14:09 +02:00
ohdearaugustin
3a07360b6e Add root cmd 2021-07-25 15:10:34 +02:00
ohdearaugustin
b97d6f71b1 Refactor version cmd 2021-07-25 15:09:53 +02:00
ohdearaugustin
4915902e04 Refactor server cmd 2021-07-25 15:09:33 +02:00
ohdearaugustin
d87a4c87cc Refactor routes cmd 2021-07-25 15:08:40 +02:00
ohdearaugustin
e56755fd67 Refactor preauthkeys cmd 2021-07-25 15:07:27 +02:00
ohdearaugustin
2862c2034b Refactor nodes cmd 2021-07-25 15:04:06 +02:00
ohdearaugustin
53185eaa9e Refactor namespaces cmd 2021-07-25 15:03:45 +02:00
Juan Font
b83ecc3e6e Merge pull request #61 from ohdearaugustin/topic/refactor-cli-versionCmd
Refactor cmdVersion to cli package
2021-07-25 12:00:06 +02:00
Juan Font
04fdd94201 Merge pull request #60 from cure/tls-more-readme-changes
Add some more detail to the README about the different Let's Encrypt
2021-07-25 11:38:31 +02:00
ohdearaugustin
48ec51d166 Refactor cmdVersion to cli package 2021-07-25 02:02:05 +02:00
Ward Vandewege
3260362436 Add some more detail to the README about the different Let's Encrypt
validation methods.
2021-07-24 09:20:38 -04:00
Ward Vandewege
5f60671d12 Merge pull request #59 from qbit/tls_letsencrypt_listen
Add a 'tls_letsencrypt_listen' config option
2021-07-24 09:03:04 -04:00
Aaron Bieber
69d77f6e9d Add a 'tls_letsencrypt_listen' config option
Currently the default (and non-configurable) Let's Encrypt listener will
bind to all IPs. This isn't ideal if we want to run headscale on a specific
IP only.

This also allows for one to set the listener to something other than
port 80. This is useful for OSs like OpenBSD which only allow root to
bind the lower port ranges (and don't have `setcap`) as we can now run
`headscale` as a non-privileged user while still using the baked in ACME
magic. Obviously this configuration would also require a reverse proxy
or firewall rule to redirect traffic. I attempted to outline that in the
README change.
2021-07-23 16:12:01 -06:00
Juan Font
1af9c11bdd Merge pull request #54 from juanfont/delete-nodes
Implement node deletion
2021-07-19 16:18:09 +02:00
Juan Font Alonso
57c115e60a Fix linting error: 2021-07-17 11:17:42 +02:00
Juan Font Alonso
96b4d2f391 Mark the machine as unregistered before soft delete 2021-07-17 11:12:24 +02:00
Juan Font Alonso
0f649aae8b Ask for confirmation before deleting 2021-07-17 11:09:42 +02:00
Juan Font
f491db232b Merge pull request #55 from cure/letsencrypt-more-flexible-config
Turn the combination of TLS-ALPN-01 and listen_addr on a port other than
2021-07-17 11:01:08 +02:00
Ward Vandewege
9a24340bd4 Turn the combination of TLS-ALPN-01 and listen_addr on a port other than
443 into a warning, not an error, refs #53.
2021-07-16 22:02:05 -04:00
Juan Font Alonso
39b756cf55 Fixed linting 2021-07-17 00:29:14 +02:00
Juan Font Alonso
9ca2ae7fc5 Implemented delete nodes (#52) 2021-07-17 00:23:12 +02:00
Juan Font Alonso
f3139d26c8 Added methods to delete nodes 2021-07-17 00:14:22 +02:00
Juan Font
6f20a1fc68 Merge pull request #51 from tianon/typo
Fix minor typo
2021-07-16 18:04:46 +02:00
Tianon Gravi
243b961cbe Fix minor typo
> Error: unknown command "namespace" for "headscale"
2021-07-16 15:07:13 +00:00
23 changed files with 318 additions and 279 deletions

View File

@@ -62,7 +62,7 @@ Suggestions/PRs welcomed!
4. Create a namespace (a namespace is a 'tailnet', a group of Tailscale nodes that can talk to each other)
```shell
headscale namespace create myfirstnamespace
headscale namespaces create myfirstnamespace
```
5. Run the server
@@ -93,7 +93,7 @@ Alternatively, you can use Auth Keys to register your machines:
1. Create an authkey
```shell
headscale -n myfirstnamespace preauthkey create --reusable --expiration 24h
headscale -n myfirstnamespace preauthkeys create --reusable --expiration 24h
```
2. Use the authkey from your machine to register it
@@ -111,8 +111,8 @@ Please bear in mind that all the commands from headscale support adding `-o json
Headscale's configuration file is named `config.json` or `config.yaml`. Headscale will look for it in `/etc/headscale`, `~/.headscale` and finally the directory from where the Headscale binary is executed.
```
"server_url": "http://192.168.1.12:8000",
"listen_addr": "0.0.0.0:8000",
"server_url": "http://192.168.1.12:8080",
"listen_addr": "0.0.0.0:8080",
```
`server_url` is the external URL via which Headscale is reachable. `listen_addr` is the IP address and port the Headscale program should listen on.
@@ -157,12 +157,22 @@ Headscale can be configured to expose its web service via TLS. To configure the
```
"tls_letsencrypt_hostname": "",
"tls_letsencrypt_listen": ":http",
"tls_letsencrypt_cache_dir": ".cache",
"tls_letsencrypt_challenge_type": "HTTP-01",
```
To get a certificate automatically via [Let's Encrypt](https://letsencrypt.org/), set `tls_letsencrypt_hostname` to the desired certificate hostname. This name must resolve to the IP address(es) Headscale is reachable on (i.e., it must correspond to the `server_url` configuration parameter). The certificate and Let's Encrypt account credentials will be stored in the directory configured in `tls_letsencrypt_cache_dir`. If the path is relative, it will be interpreted as relative to the directory the configuration file was read from. The certificate will automatically be renewed as needed. The default challenge type HTTP-01 requires that Headscale listens on port 80 for the Let's Encrypt automated validation, in addition to whatever port is configured in `listen_addr`. Alternatively, `tls_letsencrypt_challenge_type` can be set to `TLS-ALPN-01`. In this configuration, Headscale must be reachable via port 443, but port 80 is not required.
To get a certificate automatically via [Let's Encrypt](https://letsencrypt.org/), set `tls_letsencrypt_hostname` to the desired certificate hostname. This name must resolve to the IP address(es) Headscale is reachable on (i.e., it must correspond to the `server_url` configuration parameter). The certificate and Let's Encrypt account credentials will be stored in the directory configured in `tls_letsencrypt_cache_dir`. If the path is relative, it will be interpreted as relative to the directory the configuration file was read from. The certificate will automatically be renewed as needed.
#### Challenge type HTTP-01
The default challenge type `HTTP-01` requires that Headscale is reachable on port 80 for the Let's Encrypt automated validation, in addition to whatever port is configured in `listen_addr`. By default, Headscale listens on port 80 on all local IPs for Let's Encrypt automated validation.
If you need to change the ip and/or port used by Headscale for the Let's Encrypt validation process, set `tls_letsencrypt_listen` to the appropriate value. This can be handy if you are running Headscale as a non-root user (or can't run `setcap`). Keep in mind, however, that Let's Encrypt will _only_ connect to port 80 for the validation callback, so if you change `tls_letsencrypt_listen` you will also need to configure something else (e.g. a firewall rule) to forward the traffic from port 80 to the ip:port combination specified in `tls_letsencrypt_listen`.
#### Challenge type TLS-ALPN-01
Alternatively, `tls_letsencrypt_challenge_type` can be set to `TLS-ALPN-01`. In this configuration, Headscale listens on the ip:port combination defined in `listen_addr`. Let's Encrypt will _only_ connect to port 443 for the validation callback, so if `listen_addr` is not set to port 443, something else (e.g. a firewall rule) will be required to forward the traffic from port 443 to the ip:port combination specified in `listen_addr`.
### Policy ACLs

5
app.go
View File

@@ -33,6 +33,7 @@ type Config struct {
DBuser string
DBpass string
TLSLetsEncryptListen string
TLSLetsEncryptHostname string
TLSLetsEncryptCacheDir string
TLSLetsEncryptChallengeType string
@@ -164,14 +165,14 @@ func (h *Headscale) Serve() error {
if h.cfg.TLSLetsEncryptChallengeType == "TLS-ALPN-01" {
// Configuration via autocert with TLS-ALPN-01 (https://tools.ietf.org/html/rfc8737)
// The RFC requires that the validation is done on port 443; in other words, headscale
// must be configured to run on port 443.
// must be reachable on port 443.
err = s.ListenAndServeTLS("", "")
} else if h.cfg.TLSLetsEncryptChallengeType == "HTTP-01" {
// Configuration via autocert with HTTP-01. This requires listening on
// port 80 for the certificate validation in addition to the headscale
// service, which can be configured to run on any other port.
go func() {
log.Fatal(http.ListenAndServe(":http", m.HTTPHandler(http.HandlerFunc(h.redirect))))
log.Fatal(http.ListenAndServe(h.cfg.TLSLetsEncryptListen, m.HTTPHandler(http.HandlerFunc(h.redirect))))
}()
err = s.ListenAndServeTLS("", "")
} else {

View File

@@ -8,12 +8,19 @@ import (
"github.com/spf13/cobra"
)
var NamespaceCmd = &cobra.Command{
func init() {
rootCmd.AddCommand(namespaceCmd)
namespaceCmd.AddCommand(createNamespaceCmd)
namespaceCmd.AddCommand(listNamespacesCmd)
namespaceCmd.AddCommand(destroyNamespaceCmd)
}
var namespaceCmd = &cobra.Command{
Use: "namespaces",
Short: "Manage the namespaces of Headscale",
}
var CreateNamespaceCmd = &cobra.Command{
var createNamespaceCmd = &cobra.Command{
Use: "create NAME",
Short: "Creates a new namespace",
Args: func(cmd *cobra.Command, args []string) error {
@@ -41,7 +48,7 @@ var CreateNamespaceCmd = &cobra.Command{
},
}
var DestroyNamespaceCmd = &cobra.Command{
var destroyNamespaceCmd = &cobra.Command{
Use: "destroy NAME",
Short: "Destroys a namespace",
Args: func(cmd *cobra.Command, args []string) error {
@@ -69,7 +76,7 @@ var DestroyNamespaceCmd = &cobra.Command{
},
}
var ListNamespacesCmd = &cobra.Command{
var listNamespacesCmd = &cobra.Command{
Use: "list",
Short: "List all the namespaces",
Run: func(cmd *cobra.Command, args []string) {

View File

@@ -3,18 +3,31 @@ package cli
import (
"fmt"
"log"
"strconv"
"strings"
"time"
survey "github.com/AlecAivazis/survey/v2"
"github.com/spf13/cobra"
)
var NodeCmd = &cobra.Command{
func init () {
nodeCmd.PersistentFlags().StringP("namespace", "n", "", "Namespace")
err := nodeCmd.MarkPersistentFlagRequired("namespace")
if err != nil {
log.Fatalf(err.Error())
}
nodeCmd.AddCommand(listNodesCmd)
nodeCmd.AddCommand(registerNodeCmd)
nodeCmd.AddCommand(deleteNodeCmd)
}
var nodeCmd = &cobra.Command{
Use: "nodes",
Short: "Manage the nodes of Headscale",
}
var RegisterCmd = &cobra.Command{
var registerNodeCmd = &cobra.Command{
Use: "register machineID",
Short: "Registers a machine to your network",
Args: func(cmd *cobra.Command, args []string) error {
@@ -47,7 +60,7 @@ var RegisterCmd = &cobra.Command{
},
}
var ListNodesCmd = &cobra.Command{
var listNodesCmd = &cobra.Command{
Use: "list",
Short: "List the nodes in a given namespace",
Run: func(cmd *cobra.Command, args []string) {
@@ -71,7 +84,7 @@ var ListNodesCmd = &cobra.Command{
log.Fatalf("Error getting nodes: %s", err)
}
fmt.Printf("name\t\tlast seen\t\tephemeral\n")
fmt.Printf("ID\tname\t\tlast seen\t\tephemeral\n")
for _, m := range *machines {
var ephemeral bool
if m.AuthKey != nil && m.AuthKey.Ephemeral {
@@ -81,8 +94,52 @@ var ListNodesCmd = &cobra.Command{
if m.LastSeen != nil {
lastSeen = *m.LastSeen
}
fmt.Printf("%s\t%s\t%t\n", m.Name, lastSeen.Format("2006-01-02 15:04:05"), ephemeral)
fmt.Printf("%d\t%s\t%s\t%t\n", m.ID, m.Name, lastSeen.Format("2006-01-02 15:04:05"), ephemeral)
}
},
}
var deleteNodeCmd = &cobra.Command{
Use: "delete ID",
Short: "Delete a node",
Args: func(cmd *cobra.Command, args []string) error {
if len(args) < 1 {
return fmt.Errorf("Missing parameters")
}
return nil
},
Run: func(cmd *cobra.Command, args []string) {
h, err := getHeadscaleApp()
if err != nil {
log.Fatalf("Error initializing: %s", err)
}
id, err := strconv.Atoi(args[0])
if err != nil {
log.Fatalf("Error converting ID to integer: %s", err)
}
m, err := h.GetMachineByID(uint64(id))
if err != nil {
log.Fatalf("Error getting node: %s", err)
}
confirm := false
prompt := &survey.Confirm{
Message: fmt.Sprintf("Do you want to remove the node %s?", m.Name),
}
err = survey.AskOne(prompt, &confirm)
if err != nil {
return
}
if confirm {
err = h.DeleteMachine(m)
if err != nil {
log.Fatalf("Error deleting node: %s", err)
}
fmt.Printf("Node deleted\n")
} else {
fmt.Printf("Node not deleted\n")
}
},
}

View File

@@ -10,12 +10,26 @@ import (
"github.com/spf13/cobra"
)
var PreauthkeysCmd = &cobra.Command{
func init() {
rootCmd.AddCommand(preauthkeysCmd)
preauthkeysCmd.PersistentFlags().StringP("namespace", "n", "", "Namespace")
err := preauthkeysCmd.MarkPersistentFlagRequired("namespace")
if err != nil {
log.Fatalf(err.Error())
}
preauthkeysCmd.AddCommand(listPreAuthKeys)
preauthkeysCmd.AddCommand(createPreAuthKeyCmd)
createPreAuthKeyCmd.PersistentFlags().Bool("reusable", false, "Make the preauthkey reusable")
createPreAuthKeyCmd.PersistentFlags().Bool("ephemeral", false, "Preauthkey for ephemeral nodes")
createPreAuthKeyCmd.Flags().StringP("expiration", "e", "", "Human-readable expiration of the key (30m, 24h, 365d...)")
}
var preauthkeysCmd = &cobra.Command{
Use: "preauthkeys",
Short: "Handle the preauthkeys in Headscale",
}
var ListPreAuthKeys = &cobra.Command{
var listPreAuthKeys = &cobra.Command{
Use: "list",
Short: "List the preauthkeys for this namespace",
Run: func(cmd *cobra.Command, args []string) {
@@ -65,7 +79,7 @@ var ListPreAuthKeys = &cobra.Command{
},
}
var CreatePreAuthKeyCmd = &cobra.Command{
var createPreAuthKeyCmd = &cobra.Command{
Use: "create",
Short: "Creates a new preauthkey in the specified namespace",
Run: func(cmd *cobra.Command, args []string) {

28
cmd/headscale/cli/root.go Normal file
View File

@@ -0,0 +1,28 @@
package cli
import (
"fmt"
"github.com/spf13/cobra"
"os"
)
func init() {
rootCmd.PersistentFlags().StringP("output", "o", "", "Output format. Empty for human-readable, 'json' or 'json-line'")
}
var rootCmd = &cobra.Command{
Use: "headscale",
Short: "headscale - a Tailscale control server",
Long: `
headscale is an open source implementation of the Tailscale control server
Juan Font Alonso <juanfontalonso@gmail.com> - 2021
https://gitlab.com/juanfont/headscale`,
}
func Execute() {
if err := rootCmd.Execute(); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}

View File

@@ -8,12 +8,23 @@ import (
"github.com/spf13/cobra"
)
var RoutesCmd = &cobra.Command{
func init() {
rootCmd.AddCommand(routesCmd)
routesCmd.PersistentFlags().StringP("namespace", "n", "", "Namespace")
err := routesCmd.MarkPersistentFlagRequired("namespace")
if err != nil {
log.Fatalf(err.Error())
}
routesCmd.AddCommand(listRoutesCmd)
routesCmd.AddCommand(enableRouteCmd)
}
var routesCmd = &cobra.Command{
Use: "routes",
Short: "Manage the routes of Headscale",
}
var ListRoutesCmd = &cobra.Command{
var listRoutesCmd = &cobra.Command{
Use: "list NODE",
Short: "List the routes exposed by this node",
Args: func(cmd *cobra.Command, args []string) error {
@@ -49,7 +60,7 @@ var ListRoutesCmd = &cobra.Command{
},
}
var EnableRouteCmd = &cobra.Command{
var enableRouteCmd = &cobra.Command{
Use: "enable node-name route",
Short: "Allows exposing a route declared by this node to the rest of the nodes",
Args: func(cmd *cobra.Command, args []string) error {

View File

@@ -6,7 +6,11 @@ import (
"github.com/spf13/cobra"
)
var ServeCmd = &cobra.Command{
func init() {
rootCmd.AddCommand(serveCmd)
}
var serveCmd = &cobra.Command{
Use: "serve",
Short: "Launches the headscale server",
Args: func(cmd *cobra.Command, args []string) error {

View File

@@ -48,7 +48,8 @@ func LoadConfig(path string) error {
}
if (viper.GetString("tls_letsencrypt_hostname") != "") && (viper.GetString("tls_letsencrypt_challenge_type") == "TLS-ALPN-01") && (!strings.HasSuffix(viper.GetString("listen_addr"), ":443")) {
errorText += "Fatal config error: when using tls_letsencrypt_hostname with TLS-ALPN-01 as challenge type, listen_addr must end in :443\n"
// this is only a warning because there could be something sitting in front of headscale that redirects the traffic (e.g. an iptables rule)
log.Println("Warning: when using tls_letsencrypt_hostname with TLS-ALPN-01 as challenge type, headscale must be reachable on port 443, i.e. listen_addr should probably end in :443")
}
if (viper.GetString("tls_letsencrypt_challenge_type") != "HTTP-01") && (viper.GetString("tls_letsencrypt_challenge_type") != "TLS-ALPN-01") {
@@ -108,6 +109,7 @@ func getHeadscaleApp() (*headscale.Headscale, error) {
DBpass: viper.GetString("db_pass"),
TLSLetsEncryptHostname: viper.GetString("tls_letsencrypt_hostname"),
TLSLetsEncryptListen: viper.GetString("tls_letsencrypt_listen"),
TLSLetsEncryptCacheDir: absPath(viper.GetString("tls_letsencrypt_cache_dir")),
TLSLetsEncryptChallengeType: viper.GetString("tls_letsencrypt_challenge_type"),

View File

@@ -0,0 +1,27 @@
package cli
import (
"fmt"
"github.com/spf13/cobra"
"strings"
)
var version = "dev"
func init() {
rootCmd.AddCommand(versionCmd)
}
var versionCmd = &cobra.Command{
Use: "version",
Short: "Print the version.",
Long: "The version of headscale.",
Run: func(cmd *cobra.Command, args []string) {
o, _ := cmd.Flags().GetString("output")
if strings.HasPrefix(o, "json") {
JsonOutput(map[string]string{"version": version}, nil, o)
return
}
fmt.Println(version)
},
}

View File

@@ -1,93 +1,16 @@
package main
import (
"fmt"
"log"
"os"
"strings"
"github.com/juanfont/headscale/cmd/headscale/cli"
"github.com/spf13/cobra"
)
var version = "dev"
var versionCmd = &cobra.Command{
Use: "version",
Short: "Print the version.",
Long: "The version of headscale.",
Run: func(cmd *cobra.Command, args []string) {
o, _ := cmd.Flags().GetString("output")
if strings.HasPrefix(o, "json") {
cli.JsonOutput(map[string]string{"version": version}, nil, o)
return
}
fmt.Println(version)
},
}
var headscaleCmd = &cobra.Command{
Use: "headscale",
Short: "headscale - a Tailscale control server",
Long: `
headscale is an open source implementation of the Tailscale control server
Juan Font Alonso <juanfontalonso@gmail.com> - 2021
https://gitlab.com/juanfont/headscale`,
}
func main() {
err := cli.LoadConfig("")
if err != nil {
log.Fatalf(err.Error())
}
headscaleCmd.AddCommand(cli.NamespaceCmd)
headscaleCmd.AddCommand(cli.NodeCmd)
headscaleCmd.AddCommand(cli.PreauthkeysCmd)
headscaleCmd.AddCommand(cli.RoutesCmd)
headscaleCmd.AddCommand(cli.ServeCmd)
headscaleCmd.AddCommand(versionCmd)
cli.NodeCmd.PersistentFlags().StringP("namespace", "n", "", "Namespace")
err = cli.NodeCmd.MarkPersistentFlagRequired("namespace")
if err != nil {
log.Fatalf(err.Error())
}
cli.PreauthkeysCmd.PersistentFlags().StringP("namespace", "n", "", "Namespace")
err = cli.PreauthkeysCmd.MarkPersistentFlagRequired("namespace")
if err != nil {
log.Fatalf(err.Error())
}
cli.RoutesCmd.PersistentFlags().StringP("namespace", "n", "", "Namespace")
err = cli.RoutesCmd.MarkPersistentFlagRequired("namespace")
if err != nil {
log.Fatalf(err.Error())
}
cli.NamespaceCmd.AddCommand(cli.CreateNamespaceCmd)
cli.NamespaceCmd.AddCommand(cli.ListNamespacesCmd)
cli.NamespaceCmd.AddCommand(cli.DestroyNamespaceCmd)
cli.NodeCmd.AddCommand(cli.ListNodesCmd)
cli.NodeCmd.AddCommand(cli.RegisterCmd)
cli.RoutesCmd.AddCommand(cli.ListRoutesCmd)
cli.RoutesCmd.AddCommand(cli.EnableRouteCmd)
cli.PreauthkeysCmd.AddCommand(cli.ListPreAuthKeys)
cli.PreauthkeysCmd.AddCommand(cli.CreatePreAuthKeyCmd)
cli.CreatePreAuthKeyCmd.PersistentFlags().Bool("reusable", false, "Make the preauthkey reusable")
cli.CreatePreAuthKeyCmd.PersistentFlags().Bool("ephemeral", false, "Preauthkey for ephemeral nodes")
cli.CreatePreAuthKeyCmd.Flags().StringP("expiration", "e", "", "Human-readable expiration of the key (30m, 24h, 365d...)")
headscaleCmd.PersistentFlags().StringP("output", "o", "", "Output format. Empty for human-readable, 'json' or 'json-line'")
if err := headscaleCmd.Execute(); err != nil {
fmt.Println(err)
os.Exit(-1)
}
cli.Execute()
}

View File

@@ -51,12 +51,13 @@ func (*Suite) TestPostgresConfigLoading(c *check.C) {
c.Assert(err, check.IsNil)
// Test that config file was interpreted correctly
c.Assert(viper.GetString("server_url"), check.Equals, "http://127.0.0.1:8000")
c.Assert(viper.GetString("listen_addr"), check.Equals, "0.0.0.0:8000")
c.Assert(viper.GetString("server_url"), check.Equals, "http://127.0.0.1:8080")
c.Assert(viper.GetString("listen_addr"), check.Equals, "0.0.0.0:8080")
c.Assert(viper.GetString("derp_map_path"), check.Equals, "derp.yaml")
c.Assert(viper.GetString("db_type"), check.Equals, "postgres")
c.Assert(viper.GetString("db_port"), check.Equals, "5432")
c.Assert(viper.GetString("tls_letsencrypt_hostname"), check.Equals, "")
c.Assert(viper.GetString("tls_letsencrypt_listen"), check.Equals, ":http")
c.Assert(viper.GetString("tls_letsencrypt_challenge_type"), check.Equals, "HTTP-01")
}
@@ -83,12 +84,13 @@ func (*Suite) TestSqliteConfigLoading(c *check.C) {
c.Assert(err, check.IsNil)
// Test that config file was interpreted correctly
c.Assert(viper.GetString("server_url"), check.Equals, "http://127.0.0.1:8000")
c.Assert(viper.GetString("listen_addr"), check.Equals, "0.0.0.0:8000")
c.Assert(viper.GetString("server_url"), check.Equals, "http://127.0.0.1:8080")
c.Assert(viper.GetString("listen_addr"), check.Equals, "0.0.0.0:8080")
c.Assert(viper.GetString("derp_map_path"), check.Equals, "derp.yaml")
c.Assert(viper.GetString("db_type"), check.Equals, "sqlite3")
c.Assert(viper.GetString("db_path"), check.Equals, "db.sqlite")
c.Assert(viper.GetString("tls_letsencrypt_hostname"), check.Equals, "")
c.Assert(viper.GetString("tls_letsencrypt_listen"), check.Equals, ":http")
c.Assert(viper.GetString("tls_letsencrypt_challenge_type"), check.Equals, "HTTP-01")
}
@@ -123,9 +125,8 @@ func (*Suite) TestTLSConfigValidation(c *check.C) {
fmt.Println(tmp)
// Check configuration validation errors (2)
configYaml = []byte("---\nserver_url: \"http://127.0.0.1:8000\"\ntls_letsencrypt_hostname: \"example.com\"\ntls_letsencrypt_challenge_type: \"TLS-ALPN-01\"")
configYaml = []byte("---\nserver_url: \"http://127.0.0.1:8080\"\ntls_letsencrypt_hostname: \"example.com\"\ntls_letsencrypt_challenge_type: \"TLS-ALPN-01\"")
writeConfig(c, tmpDir, configYaml)
err = cli.LoadConfig(tmpDir)
c.Assert(err, check.NotNil)
c.Assert(err, check.ErrorMatches, "Fatal config error: when using tls_letsencrypt_hostname with TLS-ALPN-01 as challenge type, listen_addr must end in :443.*")
c.Assert(err, check.IsNil)
}

View File

@@ -1,6 +1,6 @@
{
"server_url": "http://127.0.0.1:8000",
"listen_addr": "0.0.0.0:8000",
"server_url": "http://127.0.0.1:8080",
"listen_addr": "0.0.0.0:8080",
"private_key_path": "private.key",
"derp_map_path": "derp.yaml",
"ephemeral_node_inactivity_timeout": "30m",
@@ -11,6 +11,7 @@
"db_user": "foo",
"db_pass": "bar",
"tls_letsencrypt_hostname": "",
"tls_letsencrypt_listen": ":http",
"tls_letsencrypt_cache_dir": ".cache",
"tls_letsencrypt_challenge_type": "HTTP-01",
"tls_cert_path": "",

View File

@@ -1,12 +1,13 @@
{
"server_url": "http://127.0.0.1:8000",
"listen_addr": "0.0.0.0:8000",
"server_url": "http://127.0.0.1:8080",
"listen_addr": "0.0.0.0:8080",
"private_key_path": "private.key",
"derp_map_path": "derp.yaml",
"ephemeral_node_inactivity_timeout": "30m",
"db_type": "sqlite3",
"db_path": "db.sqlite",
"tls_letsencrypt_hostname": "",
"tls_letsencrypt_listen": ":http",
"tls_letsencrypt_cache_dir": ".cache",
"tls_letsencrypt_challenge_type": "HTTP-01",
"tls_cert_path": "",

View File

@@ -1,62 +0,0 @@
FROM golang:alpine
# Set necessary environmet variables needed for our image
ENV GO111MODULE=on \
CGO_ENABLED=0 \
GOOS=linux \
GOARCH=amd64
ENV PATH /usr/lib/postgresql/$PG_MAJOR/bin:$PATH
ENV PGDATA /var/lib/postgresql/data
ENV POSTGRES_DB headscale
ENV POSTGRES_USER admin
ENV LANG en_US.utf8
RUN apk update && \
apk add git su-exec tzdata libpq postgresql-client postgresql postgresql-contrib gnupg supervisor inotify-tools wireguard-tools openssh && \
mkdir /docker-entrypoint-initdb.d && \
rm -rf /var/cache/apk/*
RUN gpg --keyserver ipv4.pool.sks-keyservers.net --recv-keys B42F6819007F00F88E364FD4036A9C25BF357DD4
RUN gpg --list-keys --fingerprint --with-colons | sed -E -n -e 's/^fpr:::::::::([0-9A-F]+):$/\1:6:/p' | gpg --import-ownertrust
RUN wget -O /usr/local/bin/gosu "https://github.com/tianon/gosu/releases/download/1.7/gosu-amd64" && \
wget -O /usr/local/bin/gosu.asc "https://github.com/tianon/gosu/releases/download/1.7/gosu-amd64.asc" && \
gpg --verify /usr/local/bin/gosu.asc && \
rm /usr/local/bin/gosu.asc && \
chmod +x /usr/local/bin/gosu
RUN apk --purge del gnupg ca-certificates
VOLUME /var/lib/postgresql/data
RUN rm -rf /etc/ssh/ssh_host_rsa_key /etc/ssh/ssh_host_dsa_key
WORKDIR /build
RUN git clone https://github.com/juanfont/headscale.git
WORKDIR /build/headscale
RUN go build cmd/headscale/headscale.go
COPY headscale.sh /headscale.sh
COPY postgres.sh /postgres.sh
COPY supervisord.conf /etc/supervisord.conf
WORKDIR /
RUN mkdir -p /run/postgresql
RUN chown postgres:postgres /run/postgresql
RUN adduser -S headscale
#ENV GIN_MODE release
EXPOSE 8000
CMD ["supervisord","--nodaemon", "--configuration", "/etc/supervisord.conf"]

View File

@@ -1,28 +0,0 @@
#!/bin/bash
cd /build/headscale
echo 'Writing config...'
echo '''
{
"server_url": "$SERVER_URL",
"listen_addr": "0.0.0.0:8000",
"private_key_path": "private.key",
"public_key_path": "public.key",
"db_host": "localhost",
"db_port": 5432,
"db_name": "headscale",
"db_user": "admin",
"db_pass": "$POSTGRES_PASSWORD"
}
''' > config.json
# Wait until PostgreSQL started and listens on port 5432.
while [ -z "`netstat -tln | grep 5432`" ]; do
echo 'Waiting for PostgreSQL to start ...'
sleep 1
done
echo 'PostgreSQL started.'
# Start server.
echo 'Starting server...'
./headscale

View File

@@ -1,58 +0,0 @@
#!/bin/sh
chown -R postgres "$PGDATA"
if [ -z "$(ls -A "$PGDATA")" ]; then
gosu postgres initdb
sed -ri "s/^#(listen_addresses\s*=\s*)\S+/\1'*'/" "$PGDATA"/postgresql.conf
: ${POSTGRES_USER:="postgres"}
: ${POSTGRES_DB:=$POSTGRES_USER}
if [ "$POSTGRES_PASSWORD" ]; then
pass="PASSWORD '$POSTGRES_PASSWORD'"
authMethod=md5
else
echo "==============================="
echo "!!! NO PASSWORD SET !!! (Use \$POSTGRES_PASSWORD env var)"
echo "==============================="
pass=
authMethod=trust
fi
echo
if [ "$POSTGRES_DB" != 'postgres' ]; then
createSql="CREATE DATABASE $POSTGRES_DB;"
echo $createSql | gosu postgres postgres --single -jE
echo
fi
if [ "$POSTGRES_USER" != 'postgres' ]; then
op=CREATE
else
op=ALTER
fi
userSql="$op USER $POSTGRES_USER WITH SUPERUSER $pass;"
echo $userSql | gosu postgres postgres --single -jE
echo
gosu postgres pg_ctl -D "$PGDATA" \
-o "-c listen_addresses=''" \
-w start
echo
for f in /docker-entrypoint-initdb.d/*; do
case "$f" in
*.sh) echo "$0: running $f"; . "$f" ;;
*.sql) echo "$0: running $f"; psql --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" < "$f" && echo ;;
*) echo "$0: ignoring $f" ;;
esac
echo
done
gosu postgres pg_ctl -D "$PGDATA" -m fast -w stop
{ echo; echo "host all all 0.0.0.0/0 $authMethod"; } >> "$PGDATA"/pg_hba.conf
fi
exec gosu postgres postgres

View File

@@ -1,4 +0,0 @@
# Example of how to user the docker image
POSTGRES_PASSWORD=
docker build . -t headscale-docker
docker run -p 8000:8000 -v $(pwd)/pgdata:/var/lib/postgresql/data -v "$(pwd)/private.key:/build/headscale/private.key" -v "$(pwd)/public.key:/build/headscale/public.key" -e SERVER_URL=127.0.0.1:8000 -e POSTGRES_PASSWORD=$POSTGRES_PASSWORD -ti headscale-docker

View File

@@ -1,13 +0,0 @@
[supervisord]
nodaemon=true
user = root
[program:headscale]
command=/bin/bash -c "/headscale.sh"
stdout_logfile=/dev/stdout
stdout_logfile_maxbytes=0
[program:postgres]
command=/bin/bash -c "/postgres.sh"
stdout_logfile=/dev/stdout
stdout_logfile_maxbytes=0

1
go.mod
View File

@@ -3,6 +3,7 @@ module github.com/juanfont/headscale
go 1.16
require (
github.com/AlecAivazis/survey/v2 v2.0.5
github.com/gin-gonic/gin v1.7.2
github.com/hako/durafmt v0.0.0-20210608085754-5c1018a4e16b
github.com/klauspost/compress v1.13.1

18
go.sum
View File

@@ -38,6 +38,8 @@ cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohl
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
github.com/AlecAivazis/survey/v2 v2.0.5 h1:xpZp+Q55wi5C7Iaze+40onHnEkex1jSc34CltJjOoPM=
github.com/AlecAivazis/survey/v2 v2.0.5/go.mod h1:WYBhg6f0y/fNYUuesWQc0PKbJcEliGcYHB9sNT3Bg74=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/Djarvur/go-err113 v0.0.0-20200511133814-5174e21577d5/go.mod h1:4UJr5HIiMZrwgkSPdsjy2uOQExX/WEILpIrO9UPGuXs=
@@ -53,6 +55,8 @@ github.com/Masterminds/sprig v2.22.0+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuN
github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA=
github.com/Microsoft/go-winio v0.4.15/go.mod h1:tTuCMEN+UleMWgg9dVx4Hu52b1bJo+59jBh3ajtinzw=
github.com/Microsoft/go-winio v0.4.16/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0=
github.com/Netflix/go-expect v0.0.0-20180615182759-c93bf25de8e8 h1:xzYJEypr/85nBpB11F9br+3HUrpgb+fcm5iADzXXYEw=
github.com/Netflix/go-expect v0.0.0-20180615182759-c93bf25de8e8/go.mod h1:oX5x61PbNXchhh0oikYAH+4Pcfw5LKv21+Jnpr6r6Pc=
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
github.com/OpenPeeDeeP/depguard v1.0.1/go.mod h1:xsIw86fROiiwelg+jB2uM9PiKihMMmUx/1V+TNhjQvM=
github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo=
@@ -124,6 +128,7 @@ github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfc
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
github.com/creack/pty v1.1.9 h1:uDmaGzcdjhF4i/plgjmEsriH11Y0o7RKapEf/LDaM3w=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/daixiang0/gci v0.2.4/go.mod h1:+AV8KmHTGxxwp/pY84TLQfFKp2vuKXXJVzF3kD/hfR4=
github.com/daixiang0/gci v0.2.7/go.mod h1:+4dZ7TISfSmqfAGv59ePaHfNzgGtIkHAhhdKggP1JAc=
@@ -360,6 +365,8 @@ github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO
github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=
github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
github.com/hinshun/vt10x v0.0.0-20180616224451-1954e6464174 h1:WlZsjVhE8Af9IcZDGgJGQpNflI3+MJSBhsgT5PCtzBQ=
github.com/hinshun/vt10x v0.0.0-20180616224451-1954e6464174/go.mod h1:DqJ97dSdRW1W22yXSB90986pcOyQ7r45iio1KN2ez1A=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/huandu/xstrings v1.3.2/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg=
@@ -462,6 +469,7 @@ github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/X
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs=
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
github.com/kevinburke/ssh_config v0.0.0-20190725054713-01f96b0aa0cd/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
@@ -482,6 +490,8 @@ github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORN
github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/pty v1.1.4/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/pty v1.1.8 h1:AkaSdXYQOWeaO3neb8EM634ahkXXe3jYbVh/F9lq+GI=
github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
@@ -514,6 +524,7 @@ github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcncea
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8=
github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
@@ -546,6 +557,8 @@ github.com/mdlayher/netlink v1.4.0/go.mod h1:dRJi5IABcZpBD2A3D0Mv/AiX8I9uDEu5oGk
github.com/mdlayher/netlink v1.4.1/go.mod h1:e4/KuJ+s8UhfUpO9z00/fDZZmhSrs+oxyqAS9cNgn6Q=
github.com/mdlayher/sdnotify v0.0.0-20210228150836-ea3ec207d697/go.mod h1:HtjVsQfsrBm1GDcDTUFn4ZXhftxTwO/hxrvEiRc61U4=
github.com/mdlayher/socket v0.0.0-20210307095302-262dc9984e00/go.mod h1:GAFlyu4/XV68LkQKYzKhIo/WW7j3Zi0YRAz/BOoanUc=
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b h1:j7+1HpAFS1zy5+Q4qx1fWh90gTKwiN4QCGoY9TWyyO4=
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
github.com/miekg/dns v1.1.42/go.mod h1:+evo5L0630/F6ca/Z9+GAqzhjGyn8/c+TBaOyfEl0V4=
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
@@ -735,6 +748,7 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
github.com/stretchr/objx v0.3.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
github.com/stretchr/testify v1.2.1/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
@@ -829,6 +843,7 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk
golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190530122614-20be4c3c3ed5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
@@ -973,6 +988,7 @@ golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190530182044-ad28b68e88f1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -1343,3 +1359,5 @@ sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=
sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU=
tailscale.com v1.10.0 h1:3EWYxpXkCmXsMh1WgqoEjQ/xalxzxU+YD5ZmtaHS5cY=
tailscale.com v1.10.0/go.mod h1:kgFF5AZPTltwdXjX2/ci4ghlcO3qKNWVIjD9s39pr8c=
tailscale.com v1.10.2 h1:0EbwydLGDxw7//yB5/1GTKz3hDJvGTUCajPZZPMDDGQ=
tailscale.com v1.10.2/go.mod h1:kgFF5AZPTltwdXjX2/ci4ghlcO3qKNWVIjD9s39pr8c=

View File

@@ -188,6 +188,33 @@ func (h *Headscale) GetMachine(namespace string, name string) (*Machine, error)
return nil, fmt.Errorf("not found")
}
// GetMachineByID finds a Machine by ID and returns the Machine struct
func (h *Headscale) GetMachineByID(id uint64) (*Machine, error) {
m := Machine{}
if result := h.db.Find(&Machine{ID: id}).First(&m); result.Error != nil {
return nil, result.Error
}
return &m, nil
}
// DeleteMachine softs deletes a Machine from the database
func (h *Headscale) DeleteMachine(m *Machine) error {
m.Registered = false
h.db.Save(&m) // we mark it as unregistered, just in case
if err := h.db.Delete(&m).Error; err != nil {
return err
}
return nil
}
// HardDeleteMachine hard deletes a Machine from the database
func (h *Headscale) HardDeleteMachine(m *Machine) error {
if err := h.db.Unscoped().Delete(&m).Error; err != nil {
return err
}
return nil
}
// GetHostInfo returns a Hostinfo struct for the machine
func (m *Machine) GetHostInfo() (*tailcfg.Hostinfo, error) {
hostinfo := tailcfg.Hostinfo{}

View File

@@ -32,5 +32,76 @@ func (s *Suite) TestGetMachine(c *check.C) {
_, err = m1.GetHostInfo()
c.Assert(err, check.IsNil)
}
func (s *Suite) TestGetMachineByID(c *check.C) {
n, err := h.CreateNamespace("test")
c.Assert(err, check.IsNil)
pak, err := h.CreatePreAuthKey(n.Name, false, false, nil)
c.Assert(err, check.IsNil)
_, err = h.GetMachineByID(0)
c.Assert(err, check.NotNil)
m := Machine{
ID: 0,
MachineKey: "foo",
NodeKey: "bar",
DiscoKey: "faa",
Name: "testmachine",
NamespaceID: n.ID,
Registered: true,
RegisterMethod: "authKey",
AuthKeyID: uint(pak.ID),
}
h.db.Save(&m)
m1, err := h.GetMachineByID(0)
c.Assert(err, check.IsNil)
_, err = m1.GetHostInfo()
c.Assert(err, check.IsNil)
}
func (s *Suite) TestDeleteMachine(c *check.C) {
n, err := h.CreateNamespace("test")
c.Assert(err, check.IsNil)
m := Machine{
ID: 0,
MachineKey: "foo",
NodeKey: "bar",
DiscoKey: "faa",
Name: "testmachine",
NamespaceID: n.ID,
Registered: true,
RegisterMethod: "authKey",
AuthKeyID: uint(1),
}
h.db.Save(&m)
err = h.DeleteMachine(&m)
c.Assert(err, check.IsNil)
_, err = h.GetMachine(n.Name, "testmachine")
c.Assert(err, check.NotNil)
}
func (s *Suite) TestHardDeleteMachine(c *check.C) {
n, err := h.CreateNamespace("test")
c.Assert(err, check.IsNil)
m := Machine{
ID: 0,
MachineKey: "foo",
NodeKey: "bar",
DiscoKey: "faa",
Name: "testmachine3",
NamespaceID: n.ID,
Registered: true,
RegisterMethod: "authKey",
AuthKeyID: uint(1),
}
h.db.Save(&m)
err = h.HardDeleteMachine(&m)
c.Assert(err, check.IsNil)
_, err = h.GetMachine(n.Name, "testmachine3")
c.Assert(err, check.NotNil)
}