mirror of
				https://github.com/juanfont/headscale.git
				synced 2025-11-04 01:51:04 +01:00 
			
		
		
		
	Merge branch 'main' into remove-sponsorship
This commit is contained in:
		
						commit
						6fe86dff00
					
				
							
								
								
									
										9
									
								
								.github/workflows/test-integration.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										9
									
								
								.github/workflows/test-integration.yml
									
									
									
									
										vendored
									
									
								
							@ -48,6 +48,15 @@ jobs:
 | 
				
			|||||||
          retry_on: error
 | 
					          retry_on: error
 | 
				
			||||||
          command: nix develop --command -- make test_integration_derp
 | 
					          command: nix develop --command -- make test_integration_derp
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      - name: Run OIDC integration tests
 | 
				
			||||||
 | 
					        if: steps.changed-files.outputs.any_changed == 'true'
 | 
				
			||||||
 | 
					        uses: nick-fields/retry@v2
 | 
				
			||||||
 | 
					        with:
 | 
				
			||||||
 | 
					          timeout_minutes: 240
 | 
				
			||||||
 | 
					          max_attempts: 5
 | 
				
			||||||
 | 
					          retry_on: error
 | 
				
			||||||
 | 
					          command: nix develop --command -- make test_integration_oidc
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      - name: Run general integration tests
 | 
					      - name: Run general integration tests
 | 
				
			||||||
        if: steps.changed-files.outputs.any_changed == 'true'
 | 
					        if: steps.changed-files.outputs.any_changed == 'true'
 | 
				
			||||||
        uses: nick-fields/retry@v2
 | 
					        uses: nick-fields/retry@v2
 | 
				
			||||||
 | 
				
			|||||||
@ -2,12 +2,19 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
## 0.17.0 (2022-XX-XX)
 | 
					## 0.17.0 (2022-XX-XX)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### BREAKING
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Log level option `log_level` was moved to a distinct `log` config section and renamed to `level` [#768](https://github.com/juanfont/headscale/pull/768)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Changes
 | 
				
			||||||
 | 
					
 | 
				
			||||||
- Added support for Tailscale TS2021 protocol [#738](https://github.com/juanfont/headscale/pull/738)
 | 
					- Added support for Tailscale TS2021 protocol [#738](https://github.com/juanfont/headscale/pull/738)
 | 
				
			||||||
- Add ability to specify config location via env var `HEADSCALE_CONFIG` [#674](https://github.com/juanfont/headscale/issues/674)
 | 
					- Add ability to specify config location via env var `HEADSCALE_CONFIG` [#674](https://github.com/juanfont/headscale/issues/674)
 | 
				
			||||||
- Target Go 1.19 for Headscale [#778](https://github.com/juanfont/headscale/pull/778)
 | 
					- Target Go 1.19 for Headscale [#778](https://github.com/juanfont/headscale/pull/778)
 | 
				
			||||||
- Target Tailscale v1.30.0 to build Headscale [#780](https://github.com/juanfont/headscale/pull/780)
 | 
					- Target Tailscale v1.30.0 to build Headscale [#780](https://github.com/juanfont/headscale/pull/780)
 | 
				
			||||||
- Give a warning when running Headscale with reverse proxy improperly configured for WebSockets [#788](https://github.com/juanfont/headscale/pull/788)
 | 
					- Give a warning when running Headscale with reverse proxy improperly configured for WebSockets [#788](https://github.com/juanfont/headscale/pull/788)
 | 
				
			||||||
- Fix subnet routers with Primary Routes [#811](https://github.com/juanfont/headscale/pull/811)
 | 
					- Fix subnet routers with Primary Routes [#811](https://github.com/juanfont/headscale/pull/811)
 | 
				
			||||||
 | 
					- Added support for JSON logs [#653](https://github.com/juanfont/headscale/issues/653)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## 0.16.4 (2022-08-21)
 | 
					## 0.16.4 (2022-08-21)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										5
									
								
								Makefile
									
									
									
									
									
								
							
							
						
						
									
										5
									
								
								Makefile
									
									
									
									
									
								
							@ -24,7 +24,7 @@ dev: lint test build
 | 
				
			|||||||
test:
 | 
					test:
 | 
				
			||||||
	@go test -coverprofile=coverage.out ./...
 | 
						@go test -coverprofile=coverage.out ./...
 | 
				
			||||||
 | 
					
 | 
				
			||||||
test_integration: test_integration_cli test_integration_derp test_integration_general
 | 
					test_integration: test_integration_cli test_integration_derp test_integration_oidc test_integration_general
 | 
				
			||||||
 | 
					
 | 
				
			||||||
test_integration_cli:
 | 
					test_integration_cli:
 | 
				
			||||||
	go test -failfast -tags integration_cli,integration -timeout 30m -count=1 ./...
 | 
						go test -failfast -tags integration_cli,integration -timeout 30m -count=1 ./...
 | 
				
			||||||
@ -35,6 +35,9 @@ test_integration_derp:
 | 
				
			|||||||
test_integration_general:
 | 
					test_integration_general:
 | 
				
			||||||
	go test -failfast -tags integration_general,integration -timeout 30m -count=1 ./...
 | 
						go test -failfast -tags integration_general,integration -timeout 30m -count=1 ./...
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					test_integration_oidc:
 | 
				
			||||||
 | 
						go test -failfast -tags integration_oidc,integration -timeout 30m -count=1 ./...
 | 
				
			||||||
 | 
					
 | 
				
			||||||
coverprofile_func:
 | 
					coverprofile_func:
 | 
				
			||||||
	go tool cover -func=coverage.out
 | 
						go tool cover -func=coverage.out
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -13,7 +13,7 @@ func (h *Headscale) generateMapResponse(
 | 
				
			|||||||
		Str("func", "generateMapResponse").
 | 
							Str("func", "generateMapResponse").
 | 
				
			||||||
		Str("machine", mapRequest.Hostinfo.Hostname).
 | 
							Str("machine", mapRequest.Hostinfo.Hostname).
 | 
				
			||||||
		Msg("Creating Map response")
 | 
							Msg("Creating Map response")
 | 
				
			||||||
	node, err := machine.toNode(h.cfg.BaseDomain, h.cfg.DNSConfig, true)
 | 
						node, err := machine.toNode(h.cfg.BaseDomain, h.cfg.DNSConfig)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		log.Error().
 | 
							log.Error().
 | 
				
			||||||
			Caller().
 | 
								Caller().
 | 
				
			||||||
@ -37,7 +37,7 @@ func (h *Headscale) generateMapResponse(
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	profiles := getMapResponseUserProfiles(*machine, peers)
 | 
						profiles := getMapResponseUserProfiles(*machine, peers)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	nodePeers, err := peers.toNodes(h.cfg.BaseDomain, h.cfg.DNSConfig, true)
 | 
						nodePeers, err := peers.toNodes(h.cfg.BaseDomain, h.cfg.DNSConfig)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		log.Error().
 | 
							log.Error().
 | 
				
			||||||
			Caller().
 | 
								Caller().
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										100
									
								
								cmd/headscale/cli/mockoidc.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										100
									
								
								cmd/headscale/cli/mockoidc.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,100 @@
 | 
				
			|||||||
 | 
					package cli
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
						"net"
 | 
				
			||||||
 | 
						"os"
 | 
				
			||||||
 | 
						"strconv"
 | 
				
			||||||
 | 
						"time"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"github.com/oauth2-proxy/mockoidc"
 | 
				
			||||||
 | 
						"github.com/rs/zerolog/log"
 | 
				
			||||||
 | 
						"github.com/spf13/cobra"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const (
 | 
				
			||||||
 | 
						errMockOidcClientIDNotDefined     = Error("MOCKOIDC_CLIENT_ID not defined")
 | 
				
			||||||
 | 
						errMockOidcClientSecretNotDefined = Error("MOCKOIDC_CLIENT_SECRET not defined")
 | 
				
			||||||
 | 
						errMockOidcPortNotDefined         = Error("MOCKOIDC_PORT not defined")
 | 
				
			||||||
 | 
						accessTTL                         = 10 * time.Minute
 | 
				
			||||||
 | 
						refreshTTL                        = 60 * time.Minute
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func init() {
 | 
				
			||||||
 | 
						rootCmd.AddCommand(mockOidcCmd)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					var mockOidcCmd = &cobra.Command{
 | 
				
			||||||
 | 
						Use:   "mockoidc",
 | 
				
			||||||
 | 
						Short: "Runs a mock OIDC server for testing",
 | 
				
			||||||
 | 
						Long:  "This internal command runs a OpenID Connect for testing purposes",
 | 
				
			||||||
 | 
						Run: func(cmd *cobra.Command, args []string) {
 | 
				
			||||||
 | 
							err := mockOIDC()
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								log.Error().Err(err).Msgf("Error running mock OIDC server")
 | 
				
			||||||
 | 
								os.Exit(1)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func mockOIDC() error {
 | 
				
			||||||
 | 
						clientID := os.Getenv("MOCKOIDC_CLIENT_ID")
 | 
				
			||||||
 | 
						if clientID == "" {
 | 
				
			||||||
 | 
							return errMockOidcClientIDNotDefined
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						clientSecret := os.Getenv("MOCKOIDC_CLIENT_SECRET")
 | 
				
			||||||
 | 
						if clientSecret == "" {
 | 
				
			||||||
 | 
							return errMockOidcClientSecretNotDefined
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						portStr := os.Getenv("MOCKOIDC_PORT")
 | 
				
			||||||
 | 
						if portStr == "" {
 | 
				
			||||||
 | 
							return errMockOidcPortNotDefined
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						port, err := strconv.Atoi(portStr)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						mock, err := getMockOIDC(clientID, clientSecret)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						listener, err := net.Listen("tcp", fmt.Sprintf("mockoidc:%d", port))
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						err = mock.Start(listener, nil)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						log.Info().Msgf("Mock OIDC server listening on %s", listener.Addr().String())
 | 
				
			||||||
 | 
						log.Info().Msgf("Issuer: %s", mock.Issuer())
 | 
				
			||||||
 | 
						c := make(chan struct{})
 | 
				
			||||||
 | 
						<-c
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func getMockOIDC(clientID string, clientSecret string) (*mockoidc.MockOIDC, error) {
 | 
				
			||||||
 | 
						keypair, err := mockoidc.NewKeypair(nil)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						mock := mockoidc.MockOIDC{
 | 
				
			||||||
 | 
							ClientID:                      clientID,
 | 
				
			||||||
 | 
							ClientSecret:                  clientSecret,
 | 
				
			||||||
 | 
							AccessTTL:                     accessTTL,
 | 
				
			||||||
 | 
							RefreshTTL:                    refreshTTL,
 | 
				
			||||||
 | 
							CodeChallengeMethodsSupported: []string{"plain", "S256"},
 | 
				
			||||||
 | 
							Keypair:                       keypair,
 | 
				
			||||||
 | 
							SessionStore:                  mockoidc.NewSessionStore(),
 | 
				
			||||||
 | 
							UserQueue:                     &mockoidc.UserQueue{},
 | 
				
			||||||
 | 
							ErrorQueue:                    &mockoidc.ErrorQueue{},
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return &mock, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -15,6 +15,10 @@ import (
 | 
				
			|||||||
var cfgFile string = ""
 | 
					var cfgFile string = ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func init() {
 | 
					func init() {
 | 
				
			||||||
 | 
						if len(os.Args) > 1 && os.Args[1] == "version" || os.Args[1] == "mockoidc" {
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	cobra.OnInitialize(initConfig)
 | 
						cobra.OnInitialize(initConfig)
 | 
				
			||||||
	rootCmd.PersistentFlags().
 | 
						rootCmd.PersistentFlags().
 | 
				
			||||||
		StringVarP(&cfgFile, "config", "c", "", "config file (default is /etc/headscale/config.yaml)")
 | 
							StringVarP(&cfgFile, "config", "c", "", "config file (default is /etc/headscale/config.yaml)")
 | 
				
			||||||
@ -47,7 +51,7 @@ func initConfig() {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	machineOutput := HasMachineOutputFlag()
 | 
						machineOutput := HasMachineOutputFlag()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	zerolog.SetGlobalLevel(cfg.LogLevel)
 | 
						zerolog.SetGlobalLevel(cfg.Log.Level)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// If the user has requested a "machine" readable format,
 | 
						// If the user has requested a "machine" readable format,
 | 
				
			||||||
	// then disable login so the output remains valid.
 | 
						// then disable login so the output remains valid.
 | 
				
			||||||
@ -55,6 +59,10 @@ func initConfig() {
 | 
				
			|||||||
		zerolog.SetGlobalLevel(zerolog.Disabled)
 | 
							zerolog.SetGlobalLevel(zerolog.Disabled)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if cfg.Log.Format == headscale.JSONLogFormat {
 | 
				
			||||||
 | 
							log.Logger = log.Output(os.Stdout)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if !cfg.DisableUpdateCheck && !machineOutput {
 | 
						if !cfg.DisableUpdateCheck && !machineOutput {
 | 
				
			||||||
		if (runtime.GOOS == "linux" || runtime.GOOS == "darwin") &&
 | 
							if (runtime.GOOS == "linux" || runtime.GOOS == "darwin") &&
 | 
				
			||||||
			Version != "dev" {
 | 
								Version != "dev" {
 | 
				
			||||||
 | 
				
			|||||||
@ -172,7 +172,10 @@ tls_letsencrypt_listen: ":http"
 | 
				
			|||||||
tls_cert_path: ""
 | 
					tls_cert_path: ""
 | 
				
			||||||
tls_key_path: ""
 | 
					tls_key_path: ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
log_level: info
 | 
					log:
 | 
				
			||||||
 | 
					  # Output formatting for logs: text or json
 | 
				
			||||||
 | 
					  format: text
 | 
				
			||||||
 | 
					  level: info
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Path to a file containg ACL policies.
 | 
					# Path to a file containg ACL policies.
 | 
				
			||||||
# ACLs can be defined as YAML or HUJSON.
 | 
					# ACLs can be defined as YAML or HUJSON.
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										50
									
								
								config.go
									
									
									
									
									
								
							
							
						
						
									
										50
									
								
								config.go
									
									
									
									
									
								
							@ -22,6 +22,9 @@ import (
 | 
				
			|||||||
const (
 | 
					const (
 | 
				
			||||||
	tlsALPN01ChallengeType = "TLS-ALPN-01"
 | 
						tlsALPN01ChallengeType = "TLS-ALPN-01"
 | 
				
			||||||
	http01ChallengeType    = "HTTP-01"
 | 
						http01ChallengeType    = "HTTP-01"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						JSONLogFormat = "json"
 | 
				
			||||||
 | 
						TextLogFormat = "text"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Config contains the initial Headscale configuration.
 | 
					// Config contains the initial Headscale configuration.
 | 
				
			||||||
@ -37,7 +40,7 @@ type Config struct {
 | 
				
			|||||||
	PrivateKeyPath                 string
 | 
						PrivateKeyPath                 string
 | 
				
			||||||
	NoisePrivateKeyPath            string
 | 
						NoisePrivateKeyPath            string
 | 
				
			||||||
	BaseDomain                     string
 | 
						BaseDomain                     string
 | 
				
			||||||
	LogLevel                       zerolog.Level
 | 
						Log                            LogConfig
 | 
				
			||||||
	DisableUpdateCheck             bool
 | 
						DisableUpdateCheck             bool
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	DERP DERPConfig
 | 
						DERP DERPConfig
 | 
				
			||||||
@ -124,6 +127,11 @@ type ACLConfig struct {
 | 
				
			|||||||
	PolicyPath string
 | 
						PolicyPath string
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type LogConfig struct {
 | 
				
			||||||
 | 
						Format string
 | 
				
			||||||
 | 
						Level  zerolog.Level
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func LoadConfig(path string, isFile bool) error {
 | 
					func LoadConfig(path string, isFile bool) error {
 | 
				
			||||||
	if isFile {
 | 
						if isFile {
 | 
				
			||||||
		viper.SetConfigFile(path)
 | 
							viper.SetConfigFile(path)
 | 
				
			||||||
@ -147,7 +155,8 @@ func LoadConfig(path string, isFile bool) error {
 | 
				
			|||||||
	viper.SetDefault("tls_letsencrypt_challenge_type", http01ChallengeType)
 | 
						viper.SetDefault("tls_letsencrypt_challenge_type", http01ChallengeType)
 | 
				
			||||||
	viper.SetDefault("tls_client_auth_mode", "relaxed")
 | 
						viper.SetDefault("tls_client_auth_mode", "relaxed")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	viper.SetDefault("log_level", "info")
 | 
						viper.SetDefault("log.level", "info")
 | 
				
			||||||
 | 
						viper.SetDefault("log.format", TextLogFormat)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	viper.SetDefault("dns_config", nil)
 | 
						viper.SetDefault("dns_config", nil)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -334,6 +343,34 @@ func GetACLConfig() ACLConfig {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func GetLogConfig() LogConfig {
 | 
				
			||||||
 | 
						logLevelStr := viper.GetString("log.level")
 | 
				
			||||||
 | 
						logLevel, err := zerolog.ParseLevel(logLevelStr)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							logLevel = zerolog.DebugLevel
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						logFormatOpt := viper.GetString("log.format")
 | 
				
			||||||
 | 
						var logFormat string
 | 
				
			||||||
 | 
						switch logFormatOpt {
 | 
				
			||||||
 | 
						case "json":
 | 
				
			||||||
 | 
							logFormat = JSONLogFormat
 | 
				
			||||||
 | 
						case "text":
 | 
				
			||||||
 | 
							logFormat = TextLogFormat
 | 
				
			||||||
 | 
						case "":
 | 
				
			||||||
 | 
							logFormat = TextLogFormat
 | 
				
			||||||
 | 
						default:
 | 
				
			||||||
 | 
							log.Error().
 | 
				
			||||||
 | 
								Str("func", "GetLogConfig").
 | 
				
			||||||
 | 
								Msgf("Could not parse log format: %s. Valid choices are 'json' or 'text'", logFormatOpt)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return LogConfig{
 | 
				
			||||||
 | 
							Format: logFormat,
 | 
				
			||||||
 | 
							Level:  logLevel,
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func GetDNSConfig() (*tailcfg.DNSConfig, string) {
 | 
					func GetDNSConfig() (*tailcfg.DNSConfig, string) {
 | 
				
			||||||
	if viper.IsSet("dns_config") {
 | 
						if viper.IsSet("dns_config") {
 | 
				
			||||||
		dnsConfig := &tailcfg.DNSConfig{}
 | 
							dnsConfig := &tailcfg.DNSConfig{}
 | 
				
			||||||
@ -430,12 +467,6 @@ func GetHeadscaleConfig() (*Config, error) {
 | 
				
			|||||||
	configuredPrefixes := viper.GetStringSlice("ip_prefixes")
 | 
						configuredPrefixes := viper.GetStringSlice("ip_prefixes")
 | 
				
			||||||
	parsedPrefixes := make([]netip.Prefix, 0, len(configuredPrefixes)+1)
 | 
						parsedPrefixes := make([]netip.Prefix, 0, len(configuredPrefixes)+1)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	logLevelStr := viper.GetString("log_level")
 | 
					 | 
				
			||||||
	logLevel, err := zerolog.ParseLevel(logLevelStr)
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		logLevel = zerolog.DebugLevel
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	legacyPrefixField := viper.GetString("ip_prefix")
 | 
						legacyPrefixField := viper.GetString("ip_prefix")
 | 
				
			||||||
	if len(legacyPrefixField) > 0 {
 | 
						if len(legacyPrefixField) > 0 {
 | 
				
			||||||
		log.
 | 
							log.
 | 
				
			||||||
@ -488,7 +519,6 @@ func GetHeadscaleConfig() (*Config, error) {
 | 
				
			|||||||
		GRPCAddr:           viper.GetString("grpc_listen_addr"),
 | 
							GRPCAddr:           viper.GetString("grpc_listen_addr"),
 | 
				
			||||||
		GRPCAllowInsecure:  viper.GetBool("grpc_allow_insecure"),
 | 
							GRPCAllowInsecure:  viper.GetBool("grpc_allow_insecure"),
 | 
				
			||||||
		DisableUpdateCheck: viper.GetBool("disable_check_updates"),
 | 
							DisableUpdateCheck: viper.GetBool("disable_check_updates"),
 | 
				
			||||||
		LogLevel:           logLevel,
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
		IPPrefixes: prefixes,
 | 
							IPPrefixes: prefixes,
 | 
				
			||||||
		PrivateKeyPath: AbsolutePathFromConfigPath(
 | 
							PrivateKeyPath: AbsolutePathFromConfigPath(
 | 
				
			||||||
@ -550,5 +580,7 @@ func GetHeadscaleConfig() (*Config, error) {
 | 
				
			|||||||
		},
 | 
							},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		ACL: GetACLConfig(),
 | 
							ACL: GetACLConfig(),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							Log: GetLogConfig(),
 | 
				
			||||||
	}, nil
 | 
						}, nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -24,7 +24,7 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
              # When updating go.mod or go.sum, a new sha will need to be calculated,
 | 
					              # When updating go.mod or go.sum, a new sha will need to be calculated,
 | 
				
			||||||
              # update this if you have a mismatch after doing a change to thos files.
 | 
					              # update this if you have a mismatch after doing a change to thos files.
 | 
				
			||||||
              vendorSha256 = "sha256-kc8EU+TkwRlsKM2+ljm/88aWe5h2QMgd/ZGPSgdd9QQ=";
 | 
					              vendorSha256 = "sha256-DosFCSiQ5FURbIrt4NcPGkExc84t2MGMqe9XLxNHdIM=";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
              ldflags = [ "-s" "-w" "-X github.com/juanfont/headscale/cmd/headscale/cli.Version=v${version}" ];
 | 
					              ldflags = [ "-s" "-w" "-X github.com/juanfont/headscale/cmd/headscale/cli.Version=v${version}" ];
 | 
				
			||||||
            };
 | 
					            };
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										2
									
								
								go.sum
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								go.sum
									
									
									
									
									
								
							@ -273,8 +273,6 @@ github.com/fzipp/gocyclo v0.3.1/go.mod h1:DJHO6AUmbdqj2ET4Z9iArSuwWgYDRryYt2wASx
 | 
				
			|||||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
 | 
					github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
 | 
				
			||||||
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
 | 
					github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
 | 
				
			||||||
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
 | 
					github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
 | 
				
			||||||
github.com/gin-gonic/gin v1.6.3 h1:ahKqKTFpO5KTPHxWZjEdPScmYaGtLo8Y4DMHoEsnp14=
 | 
					 | 
				
			||||||
github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M=
 | 
					 | 
				
			||||||
github.com/glebarez/go-sqlite v1.17.3 h1:Rji9ROVSTTfjuWD6j5B+8DtkNvPILoUC3xRhkQzGxvk=
 | 
					github.com/glebarez/go-sqlite v1.17.3 h1:Rji9ROVSTTfjuWD6j5B+8DtkNvPILoUC3xRhkQzGxvk=
 | 
				
			||||||
github.com/glebarez/go-sqlite v1.17.3/go.mod h1:Hg+PQuhUy98XCxWEJEaWob8x7lhJzhNYF1nZbUiRGIY=
 | 
					github.com/glebarez/go-sqlite v1.17.3/go.mod h1:Hg+PQuhUy98XCxWEJEaWob8x7lhJzhNYF1nZbUiRGIY=
 | 
				
			||||||
github.com/glebarez/go-sqlite v1.18.1 h1:w0xtxKWktqYsUsXg//SQK+l1IcpKb3rGOQHmMptvL2U=
 | 
					github.com/glebarez/go-sqlite v1.18.1 h1:w0xtxKWktqYsUsXg//SQK+l1IcpKb3rGOQHmMptvL2U=
 | 
				
			||||||
 | 
				
			|||||||
@ -129,7 +129,7 @@ func (s *IntegrationCLITestSuite) HandleStats(
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (s *IntegrationCLITestSuite) createNamespace(name string) (*v1.Namespace, error) {
 | 
					func (s *IntegrationCLITestSuite) createNamespace(name string) (*v1.Namespace, error) {
 | 
				
			||||||
	result, err := ExecuteCommand(
 | 
						result, _, err := ExecuteCommand(
 | 
				
			||||||
		&s.headscale,
 | 
							&s.headscale,
 | 
				
			||||||
		[]string{
 | 
							[]string{
 | 
				
			||||||
			"headscale",
 | 
								"headscale",
 | 
				
			||||||
@ -172,7 +172,7 @@ func (s *IntegrationCLITestSuite) TestNamespaceCommand() {
 | 
				
			|||||||
	assert.Equal(s.T(), names[2], namespaces[2].Name)
 | 
						assert.Equal(s.T(), names[2], namespaces[2].Name)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Test list namespaces
 | 
						// Test list namespaces
 | 
				
			||||||
	listResult, err := ExecuteCommand(
 | 
						listResult, _, err := ExecuteCommand(
 | 
				
			||||||
		&s.headscale,
 | 
							&s.headscale,
 | 
				
			||||||
		[]string{
 | 
							[]string{
 | 
				
			||||||
			"headscale",
 | 
								"headscale",
 | 
				
			||||||
@ -194,7 +194,7 @@ func (s *IntegrationCLITestSuite) TestNamespaceCommand() {
 | 
				
			|||||||
	assert.Equal(s.T(), names[2], listedNamespaces[2].Name)
 | 
						assert.Equal(s.T(), names[2], listedNamespaces[2].Name)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Test rename namespace
 | 
						// Test rename namespace
 | 
				
			||||||
	renameResult, err := ExecuteCommand(
 | 
						renameResult, _, err := ExecuteCommand(
 | 
				
			||||||
		&s.headscale,
 | 
							&s.headscale,
 | 
				
			||||||
		[]string{
 | 
							[]string{
 | 
				
			||||||
			"headscale",
 | 
								"headscale",
 | 
				
			||||||
@ -216,7 +216,7 @@ func (s *IntegrationCLITestSuite) TestNamespaceCommand() {
 | 
				
			|||||||
	assert.Equal(s.T(), renamedNamespace.Name, "newname")
 | 
						assert.Equal(s.T(), renamedNamespace.Name, "newname")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Test list after rename namespaces
 | 
						// Test list after rename namespaces
 | 
				
			||||||
	listAfterRenameResult, err := ExecuteCommand(
 | 
						listAfterRenameResult, _, err := ExecuteCommand(
 | 
				
			||||||
		&s.headscale,
 | 
							&s.headscale,
 | 
				
			||||||
		[]string{
 | 
							[]string{
 | 
				
			||||||
			"headscale",
 | 
								"headscale",
 | 
				
			||||||
@ -247,7 +247,7 @@ func (s *IntegrationCLITestSuite) TestPreAuthKeyCommand() {
 | 
				
			|||||||
	assert.Nil(s.T(), err)
 | 
						assert.Nil(s.T(), err)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	for i := 0; i < count; i++ {
 | 
						for i := 0; i < count; i++ {
 | 
				
			||||||
		preAuthResult, err := ExecuteCommand(
 | 
							preAuthResult, _, err := ExecuteCommand(
 | 
				
			||||||
			&s.headscale,
 | 
								&s.headscale,
 | 
				
			||||||
			[]string{
 | 
								[]string{
 | 
				
			||||||
				"headscale",
 | 
									"headscale",
 | 
				
			||||||
@ -275,7 +275,7 @@ func (s *IntegrationCLITestSuite) TestPreAuthKeyCommand() {
 | 
				
			|||||||
	assert.Len(s.T(), keys, 5)
 | 
						assert.Len(s.T(), keys, 5)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Test list of keys
 | 
						// Test list of keys
 | 
				
			||||||
	listResult, err := ExecuteCommand(
 | 
						listResult, _, err := ExecuteCommand(
 | 
				
			||||||
		&s.headscale,
 | 
							&s.headscale,
 | 
				
			||||||
		[]string{
 | 
							[]string{
 | 
				
			||||||
			"headscale",
 | 
								"headscale",
 | 
				
			||||||
@ -335,7 +335,7 @@ func (s *IntegrationCLITestSuite) TestPreAuthKeyCommand() {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	// Expire three keys
 | 
						// Expire three keys
 | 
				
			||||||
	for i := 0; i < 3; i++ {
 | 
						for i := 0; i < 3; i++ {
 | 
				
			||||||
		_, err := ExecuteCommand(
 | 
							_, _, err := ExecuteCommand(
 | 
				
			||||||
			&s.headscale,
 | 
								&s.headscale,
 | 
				
			||||||
			[]string{
 | 
								[]string{
 | 
				
			||||||
				"headscale",
 | 
									"headscale",
 | 
				
			||||||
@ -351,7 +351,7 @@ func (s *IntegrationCLITestSuite) TestPreAuthKeyCommand() {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Test list pre auth keys after expire
 | 
						// Test list pre auth keys after expire
 | 
				
			||||||
	listAfterExpireResult, err := ExecuteCommand(
 | 
						listAfterExpireResult, _, err := ExecuteCommand(
 | 
				
			||||||
		&s.headscale,
 | 
							&s.headscale,
 | 
				
			||||||
		[]string{
 | 
							[]string{
 | 
				
			||||||
			"headscale",
 | 
								"headscale",
 | 
				
			||||||
@ -396,7 +396,7 @@ func (s *IntegrationCLITestSuite) TestPreAuthKeyCommandWithoutExpiry() {
 | 
				
			|||||||
	namespace, err := s.createNamespace("pre-auth-key-without-exp-namespace")
 | 
						namespace, err := s.createNamespace("pre-auth-key-without-exp-namespace")
 | 
				
			||||||
	assert.Nil(s.T(), err)
 | 
						assert.Nil(s.T(), err)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	preAuthResult, err := ExecuteCommand(
 | 
						preAuthResult, _, err := ExecuteCommand(
 | 
				
			||||||
		&s.headscale,
 | 
							&s.headscale,
 | 
				
			||||||
		[]string{
 | 
							[]string{
 | 
				
			||||||
			"headscale",
 | 
								"headscale",
 | 
				
			||||||
@ -417,7 +417,7 @@ func (s *IntegrationCLITestSuite) TestPreAuthKeyCommandWithoutExpiry() {
 | 
				
			|||||||
	assert.Nil(s.T(), err)
 | 
						assert.Nil(s.T(), err)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Test list of keys
 | 
						// Test list of keys
 | 
				
			||||||
	listResult, err := ExecuteCommand(
 | 
						listResult, _, err := ExecuteCommand(
 | 
				
			||||||
		&s.headscale,
 | 
							&s.headscale,
 | 
				
			||||||
		[]string{
 | 
							[]string{
 | 
				
			||||||
			"headscale",
 | 
								"headscale",
 | 
				
			||||||
@ -449,7 +449,7 @@ func (s *IntegrationCLITestSuite) TestPreAuthKeyCommandReusableEphemeral() {
 | 
				
			|||||||
	namespace, err := s.createNamespace("pre-auth-key-reus-ephm-namespace")
 | 
						namespace, err := s.createNamespace("pre-auth-key-reus-ephm-namespace")
 | 
				
			||||||
	assert.Nil(s.T(), err)
 | 
						assert.Nil(s.T(), err)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	preAuthReusableResult, err := ExecuteCommand(
 | 
						preAuthReusableResult, _, err := ExecuteCommand(
 | 
				
			||||||
		&s.headscale,
 | 
							&s.headscale,
 | 
				
			||||||
		[]string{
 | 
							[]string{
 | 
				
			||||||
			"headscale",
 | 
								"headscale",
 | 
				
			||||||
@ -472,7 +472,7 @@ func (s *IntegrationCLITestSuite) TestPreAuthKeyCommandReusableEphemeral() {
 | 
				
			|||||||
	assert.True(s.T(), preAuthReusableKey.GetReusable())
 | 
						assert.True(s.T(), preAuthReusableKey.GetReusable())
 | 
				
			||||||
	assert.False(s.T(), preAuthReusableKey.GetEphemeral())
 | 
						assert.False(s.T(), preAuthReusableKey.GetEphemeral())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	preAuthEphemeralResult, err := ExecuteCommand(
 | 
						preAuthEphemeralResult, _, err := ExecuteCommand(
 | 
				
			||||||
		&s.headscale,
 | 
							&s.headscale,
 | 
				
			||||||
		[]string{
 | 
							[]string{
 | 
				
			||||||
			"headscale",
 | 
								"headscale",
 | 
				
			||||||
@ -514,7 +514,7 @@ func (s *IntegrationCLITestSuite) TestPreAuthKeyCommandReusableEphemeral() {
 | 
				
			|||||||
	// assert.NotNil(s.T(), err)
 | 
						// assert.NotNil(s.T(), err)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Test list of keys
 | 
						// Test list of keys
 | 
				
			||||||
	listResult, err := ExecuteCommand(
 | 
						listResult, _, err := ExecuteCommand(
 | 
				
			||||||
		&s.headscale,
 | 
							&s.headscale,
 | 
				
			||||||
		[]string{
 | 
							[]string{
 | 
				
			||||||
			"headscale",
 | 
								"headscale",
 | 
				
			||||||
@ -548,7 +548,7 @@ func (s *IntegrationCLITestSuite) TestNodeTagCommand() {
 | 
				
			|||||||
	assert.Nil(s.T(), err)
 | 
						assert.Nil(s.T(), err)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	for index, machineKey := range machineKeys {
 | 
						for index, machineKey := range machineKeys {
 | 
				
			||||||
		_, err := ExecuteCommand(
 | 
							_, _, err := ExecuteCommand(
 | 
				
			||||||
			&s.headscale,
 | 
								&s.headscale,
 | 
				
			||||||
			[]string{
 | 
								[]string{
 | 
				
			||||||
				"headscale",
 | 
									"headscale",
 | 
				
			||||||
@ -567,7 +567,7 @@ func (s *IntegrationCLITestSuite) TestNodeTagCommand() {
 | 
				
			|||||||
		)
 | 
							)
 | 
				
			||||||
		assert.Nil(s.T(), err)
 | 
							assert.Nil(s.T(), err)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		machineResult, err := ExecuteCommand(
 | 
							machineResult, _, err := ExecuteCommand(
 | 
				
			||||||
			&s.headscale,
 | 
								&s.headscale,
 | 
				
			||||||
			[]string{
 | 
								[]string{
 | 
				
			||||||
				"headscale",
 | 
									"headscale",
 | 
				
			||||||
@ -592,7 +592,7 @@ func (s *IntegrationCLITestSuite) TestNodeTagCommand() {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
	assert.Len(s.T(), machines, len(machineKeys))
 | 
						assert.Len(s.T(), machines, len(machineKeys))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	addTagResult, err := ExecuteCommand(
 | 
						addTagResult, _, err := ExecuteCommand(
 | 
				
			||||||
		&s.headscale,
 | 
							&s.headscale,
 | 
				
			||||||
		[]string{
 | 
							[]string{
 | 
				
			||||||
			"headscale",
 | 
								"headscale",
 | 
				
			||||||
@ -612,7 +612,7 @@ func (s *IntegrationCLITestSuite) TestNodeTagCommand() {
 | 
				
			|||||||
	assert.Equal(s.T(), []string{"tag:test"}, machine.ForcedTags)
 | 
						assert.Equal(s.T(), []string{"tag:test"}, machine.ForcedTags)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// try to set a wrong tag and retrieve the error
 | 
						// try to set a wrong tag and retrieve the error
 | 
				
			||||||
	wrongTagResult, err := ExecuteCommand(
 | 
						wrongTagResult, _, err := ExecuteCommand(
 | 
				
			||||||
		&s.headscale,
 | 
							&s.headscale,
 | 
				
			||||||
		[]string{
 | 
							[]string{
 | 
				
			||||||
			"headscale",
 | 
								"headscale",
 | 
				
			||||||
@ -634,7 +634,7 @@ func (s *IntegrationCLITestSuite) TestNodeTagCommand() {
 | 
				
			|||||||
	assert.Contains(s.T(), errorOutput.Error, "tag must start with the string 'tag:'")
 | 
						assert.Contains(s.T(), errorOutput.Error, "tag must start with the string 'tag:'")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Test list all nodes after added seconds
 | 
						// Test list all nodes after added seconds
 | 
				
			||||||
	listAllResult, err := ExecuteCommand(
 | 
						listAllResult, _, err := ExecuteCommand(
 | 
				
			||||||
		&s.headscale,
 | 
							&s.headscale,
 | 
				
			||||||
		[]string{
 | 
							[]string{
 | 
				
			||||||
			"headscale",
 | 
								"headscale",
 | 
				
			||||||
@ -684,7 +684,7 @@ func (s *IntegrationCLITestSuite) TestNodeCommand() {
 | 
				
			|||||||
	assert.Nil(s.T(), err)
 | 
						assert.Nil(s.T(), err)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	for index, machineKey := range machineKeys {
 | 
						for index, machineKey := range machineKeys {
 | 
				
			||||||
		_, err := ExecuteCommand(
 | 
							_, _, err := ExecuteCommand(
 | 
				
			||||||
			&s.headscale,
 | 
								&s.headscale,
 | 
				
			||||||
			[]string{
 | 
								[]string{
 | 
				
			||||||
				"headscale",
 | 
									"headscale",
 | 
				
			||||||
@ -703,7 +703,7 @@ func (s *IntegrationCLITestSuite) TestNodeCommand() {
 | 
				
			|||||||
		)
 | 
							)
 | 
				
			||||||
		assert.Nil(s.T(), err)
 | 
							assert.Nil(s.T(), err)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		machineResult, err := ExecuteCommand(
 | 
							machineResult, _, err := ExecuteCommand(
 | 
				
			||||||
			&s.headscale,
 | 
								&s.headscale,
 | 
				
			||||||
			[]string{
 | 
								[]string{
 | 
				
			||||||
				"headscale",
 | 
									"headscale",
 | 
				
			||||||
@ -730,7 +730,7 @@ func (s *IntegrationCLITestSuite) TestNodeCommand() {
 | 
				
			|||||||
	assert.Len(s.T(), machines, len(machineKeys))
 | 
						assert.Len(s.T(), machines, len(machineKeys))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Test list all nodes after added seconds
 | 
						// Test list all nodes after added seconds
 | 
				
			||||||
	listAllResult, err := ExecuteCommand(
 | 
						listAllResult, _, err := ExecuteCommand(
 | 
				
			||||||
		&s.headscale,
 | 
							&s.headscale,
 | 
				
			||||||
		[]string{
 | 
							[]string{
 | 
				
			||||||
			"headscale",
 | 
								"headscale",
 | 
				
			||||||
@ -769,7 +769,7 @@ func (s *IntegrationCLITestSuite) TestNodeCommand() {
 | 
				
			|||||||
	assert.Nil(s.T(), err)
 | 
						assert.Nil(s.T(), err)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	for index, machineKey := range otherNamespaceMachineKeys {
 | 
						for index, machineKey := range otherNamespaceMachineKeys {
 | 
				
			||||||
		_, err := ExecuteCommand(
 | 
							_, _, err := ExecuteCommand(
 | 
				
			||||||
			&s.headscale,
 | 
								&s.headscale,
 | 
				
			||||||
			[]string{
 | 
								[]string{
 | 
				
			||||||
				"headscale",
 | 
									"headscale",
 | 
				
			||||||
@ -788,7 +788,7 @@ func (s *IntegrationCLITestSuite) TestNodeCommand() {
 | 
				
			|||||||
		)
 | 
							)
 | 
				
			||||||
		assert.Nil(s.T(), err)
 | 
							assert.Nil(s.T(), err)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		machineResult, err := ExecuteCommand(
 | 
							machineResult, _, err := ExecuteCommand(
 | 
				
			||||||
			&s.headscale,
 | 
								&s.headscale,
 | 
				
			||||||
			[]string{
 | 
								[]string{
 | 
				
			||||||
				"headscale",
 | 
									"headscale",
 | 
				
			||||||
@ -815,7 +815,7 @@ func (s *IntegrationCLITestSuite) TestNodeCommand() {
 | 
				
			|||||||
	assert.Len(s.T(), otherNamespaceMachines, len(otherNamespaceMachineKeys))
 | 
						assert.Len(s.T(), otherNamespaceMachines, len(otherNamespaceMachineKeys))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Test list all nodes after added otherNamespace
 | 
						// Test list all nodes after added otherNamespace
 | 
				
			||||||
	listAllWithotherNamespaceResult, err := ExecuteCommand(
 | 
						listAllWithotherNamespaceResult, _, err := ExecuteCommand(
 | 
				
			||||||
		&s.headscale,
 | 
							&s.headscale,
 | 
				
			||||||
		[]string{
 | 
							[]string{
 | 
				
			||||||
			"headscale",
 | 
								"headscale",
 | 
				
			||||||
@ -845,7 +845,7 @@ func (s *IntegrationCLITestSuite) TestNodeCommand() {
 | 
				
			|||||||
	assert.Equal(s.T(), "otherNamespace-machine-2", listAllWithotherNamespace[6].Name)
 | 
						assert.Equal(s.T(), "otherNamespace-machine-2", listAllWithotherNamespace[6].Name)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Test list all nodes after added otherNamespace
 | 
						// Test list all nodes after added otherNamespace
 | 
				
			||||||
	listOnlyotherNamespaceMachineNamespaceResult, err := ExecuteCommand(
 | 
						listOnlyotherNamespaceMachineNamespaceResult, _, err := ExecuteCommand(
 | 
				
			||||||
		&s.headscale,
 | 
							&s.headscale,
 | 
				
			||||||
		[]string{
 | 
							[]string{
 | 
				
			||||||
			"headscale",
 | 
								"headscale",
 | 
				
			||||||
@ -884,7 +884,7 @@ func (s *IntegrationCLITestSuite) TestNodeCommand() {
 | 
				
			|||||||
	)
 | 
						)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Delete a machines
 | 
						// Delete a machines
 | 
				
			||||||
	_, err = ExecuteCommand(
 | 
						_, _, err = ExecuteCommand(
 | 
				
			||||||
		&s.headscale,
 | 
							&s.headscale,
 | 
				
			||||||
		[]string{
 | 
							[]string{
 | 
				
			||||||
			"headscale",
 | 
								"headscale",
 | 
				
			||||||
@ -902,7 +902,7 @@ func (s *IntegrationCLITestSuite) TestNodeCommand() {
 | 
				
			|||||||
	assert.Nil(s.T(), err)
 | 
						assert.Nil(s.T(), err)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Test: list main namespace after machine is deleted
 | 
						// Test: list main namespace after machine is deleted
 | 
				
			||||||
	listOnlyMachineNamespaceAfterDeleteResult, err := ExecuteCommand(
 | 
						listOnlyMachineNamespaceAfterDeleteResult, _, err := ExecuteCommand(
 | 
				
			||||||
		&s.headscale,
 | 
							&s.headscale,
 | 
				
			||||||
		[]string{
 | 
							[]string{
 | 
				
			||||||
			"headscale",
 | 
								"headscale",
 | 
				
			||||||
@ -943,7 +943,7 @@ func (s *IntegrationCLITestSuite) TestNodeExpireCommand() {
 | 
				
			|||||||
	assert.Nil(s.T(), err)
 | 
						assert.Nil(s.T(), err)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	for index, machineKey := range machineKeys {
 | 
						for index, machineKey := range machineKeys {
 | 
				
			||||||
		_, err := ExecuteCommand(
 | 
							_, _, err := ExecuteCommand(
 | 
				
			||||||
			&s.headscale,
 | 
								&s.headscale,
 | 
				
			||||||
			[]string{
 | 
								[]string{
 | 
				
			||||||
				"headscale",
 | 
									"headscale",
 | 
				
			||||||
@ -962,7 +962,7 @@ func (s *IntegrationCLITestSuite) TestNodeExpireCommand() {
 | 
				
			|||||||
		)
 | 
							)
 | 
				
			||||||
		assert.Nil(s.T(), err)
 | 
							assert.Nil(s.T(), err)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		machineResult, err := ExecuteCommand(
 | 
							machineResult, _, err := ExecuteCommand(
 | 
				
			||||||
			&s.headscale,
 | 
								&s.headscale,
 | 
				
			||||||
			[]string{
 | 
								[]string{
 | 
				
			||||||
				"headscale",
 | 
									"headscale",
 | 
				
			||||||
@ -988,7 +988,7 @@ func (s *IntegrationCLITestSuite) TestNodeExpireCommand() {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	assert.Len(s.T(), machines, len(machineKeys))
 | 
						assert.Len(s.T(), machines, len(machineKeys))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	listAllResult, err := ExecuteCommand(
 | 
						listAllResult, _, err := ExecuteCommand(
 | 
				
			||||||
		&s.headscale,
 | 
							&s.headscale,
 | 
				
			||||||
		[]string{
 | 
							[]string{
 | 
				
			||||||
			"headscale",
 | 
								"headscale",
 | 
				
			||||||
@ -1014,7 +1014,7 @@ func (s *IntegrationCLITestSuite) TestNodeExpireCommand() {
 | 
				
			|||||||
	assert.True(s.T(), listAll[4].Expiry.AsTime().IsZero())
 | 
						assert.True(s.T(), listAll[4].Expiry.AsTime().IsZero())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	for i := 0; i < 3; i++ {
 | 
						for i := 0; i < 3; i++ {
 | 
				
			||||||
		_, err := ExecuteCommand(
 | 
							_, _, err := ExecuteCommand(
 | 
				
			||||||
			&s.headscale,
 | 
								&s.headscale,
 | 
				
			||||||
			[]string{
 | 
								[]string{
 | 
				
			||||||
				"headscale",
 | 
									"headscale",
 | 
				
			||||||
@ -1028,7 +1028,7 @@ func (s *IntegrationCLITestSuite) TestNodeExpireCommand() {
 | 
				
			|||||||
		assert.Nil(s.T(), err)
 | 
							assert.Nil(s.T(), err)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	listAllAfterExpiryResult, err := ExecuteCommand(
 | 
						listAllAfterExpiryResult, _, err := ExecuteCommand(
 | 
				
			||||||
		&s.headscale,
 | 
							&s.headscale,
 | 
				
			||||||
		[]string{
 | 
							[]string{
 | 
				
			||||||
			"headscale",
 | 
								"headscale",
 | 
				
			||||||
@ -1070,7 +1070,7 @@ func (s *IntegrationCLITestSuite) TestNodeRenameCommand() {
 | 
				
			|||||||
	assert.Nil(s.T(), err)
 | 
						assert.Nil(s.T(), err)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	for index, machineKey := range machineKeys {
 | 
						for index, machineKey := range machineKeys {
 | 
				
			||||||
		_, err := ExecuteCommand(
 | 
							_, _, err := ExecuteCommand(
 | 
				
			||||||
			&s.headscale,
 | 
								&s.headscale,
 | 
				
			||||||
			[]string{
 | 
								[]string{
 | 
				
			||||||
				"headscale",
 | 
									"headscale",
 | 
				
			||||||
@ -1089,7 +1089,7 @@ func (s *IntegrationCLITestSuite) TestNodeRenameCommand() {
 | 
				
			|||||||
		)
 | 
							)
 | 
				
			||||||
		assert.Nil(s.T(), err)
 | 
							assert.Nil(s.T(), err)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		machineResult, err := ExecuteCommand(
 | 
							machineResult, _, err := ExecuteCommand(
 | 
				
			||||||
			&s.headscale,
 | 
								&s.headscale,
 | 
				
			||||||
			[]string{
 | 
								[]string{
 | 
				
			||||||
				"headscale",
 | 
									"headscale",
 | 
				
			||||||
@ -1115,7 +1115,7 @@ func (s *IntegrationCLITestSuite) TestNodeRenameCommand() {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	assert.Len(s.T(), machines, len(machineKeys))
 | 
						assert.Len(s.T(), machines, len(machineKeys))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	listAllResult, err := ExecuteCommand(
 | 
						listAllResult, _, err := ExecuteCommand(
 | 
				
			||||||
		&s.headscale,
 | 
							&s.headscale,
 | 
				
			||||||
		[]string{
 | 
							[]string{
 | 
				
			||||||
			"headscale",
 | 
								"headscale",
 | 
				
			||||||
@ -1141,7 +1141,7 @@ func (s *IntegrationCLITestSuite) TestNodeRenameCommand() {
 | 
				
			|||||||
	assert.Contains(s.T(), listAll[4].GetGivenName(), "machine-5")
 | 
						assert.Contains(s.T(), listAll[4].GetGivenName(), "machine-5")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	for i := 0; i < 3; i++ {
 | 
						for i := 0; i < 3; i++ {
 | 
				
			||||||
		_, err := ExecuteCommand(
 | 
							_, _, err := ExecuteCommand(
 | 
				
			||||||
			&s.headscale,
 | 
								&s.headscale,
 | 
				
			||||||
			[]string{
 | 
								[]string{
 | 
				
			||||||
				"headscale",
 | 
									"headscale",
 | 
				
			||||||
@ -1156,7 +1156,7 @@ func (s *IntegrationCLITestSuite) TestNodeRenameCommand() {
 | 
				
			|||||||
		assert.Nil(s.T(), err)
 | 
							assert.Nil(s.T(), err)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	listAllAfterRenameResult, err := ExecuteCommand(
 | 
						listAllAfterRenameResult, _, err := ExecuteCommand(
 | 
				
			||||||
		&s.headscale,
 | 
							&s.headscale,
 | 
				
			||||||
		[]string{
 | 
							[]string{
 | 
				
			||||||
			"headscale",
 | 
								"headscale",
 | 
				
			||||||
@ -1182,7 +1182,7 @@ func (s *IntegrationCLITestSuite) TestNodeRenameCommand() {
 | 
				
			|||||||
	assert.Contains(s.T(), listAllAfterRename[4].GetGivenName(), "machine-5")
 | 
						assert.Contains(s.T(), listAllAfterRename[4].GetGivenName(), "machine-5")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Test failure for too long names
 | 
						// Test failure for too long names
 | 
				
			||||||
	result, err := ExecuteCommand(
 | 
						result, _, err := ExecuteCommand(
 | 
				
			||||||
		&s.headscale,
 | 
							&s.headscale,
 | 
				
			||||||
		[]string{
 | 
							[]string{
 | 
				
			||||||
			"headscale",
 | 
								"headscale",
 | 
				
			||||||
@ -1197,7 +1197,7 @@ func (s *IntegrationCLITestSuite) TestNodeRenameCommand() {
 | 
				
			|||||||
	assert.Nil(s.T(), err)
 | 
						assert.Nil(s.T(), err)
 | 
				
			||||||
	assert.Contains(s.T(), result, "not be over 63 chars")
 | 
						assert.Contains(s.T(), result, "not be over 63 chars")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	listAllAfterRenameAttemptResult, err := ExecuteCommand(
 | 
						listAllAfterRenameAttemptResult, _, err := ExecuteCommand(
 | 
				
			||||||
		&s.headscale,
 | 
							&s.headscale,
 | 
				
			||||||
		[]string{
 | 
							[]string{
 | 
				
			||||||
			"headscale",
 | 
								"headscale",
 | 
				
			||||||
@ -1233,7 +1233,7 @@ func (s *IntegrationCLITestSuite) TestRouteCommand() {
 | 
				
			|||||||
	// Randomly generated machine keys
 | 
						// Randomly generated machine keys
 | 
				
			||||||
	machineKey := "9b2ffa7e08cc421a3d2cca9012280f6a236fd0de0b4ce005b30a98ad930306fe"
 | 
						machineKey := "9b2ffa7e08cc421a3d2cca9012280f6a236fd0de0b4ce005b30a98ad930306fe"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	_, err = ExecuteCommand(
 | 
						_, _, err = ExecuteCommand(
 | 
				
			||||||
		&s.headscale,
 | 
							&s.headscale,
 | 
				
			||||||
		[]string{
 | 
							[]string{
 | 
				
			||||||
			"headscale",
 | 
								"headscale",
 | 
				
			||||||
@ -1256,7 +1256,7 @@ func (s *IntegrationCLITestSuite) TestRouteCommand() {
 | 
				
			|||||||
	)
 | 
						)
 | 
				
			||||||
	assert.Nil(s.T(), err)
 | 
						assert.Nil(s.T(), err)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	machineResult, err := ExecuteCommand(
 | 
						machineResult, _, err := ExecuteCommand(
 | 
				
			||||||
		&s.headscale,
 | 
							&s.headscale,
 | 
				
			||||||
		[]string{
 | 
							[]string{
 | 
				
			||||||
			"headscale",
 | 
								"headscale",
 | 
				
			||||||
@ -1280,7 +1280,7 @@ func (s *IntegrationCLITestSuite) TestRouteCommand() {
 | 
				
			|||||||
	assert.Equal(s.T(), uint64(1), machine.Id)
 | 
						assert.Equal(s.T(), uint64(1), machine.Id)
 | 
				
			||||||
	assert.Equal(s.T(), "route-machine", machine.Name)
 | 
						assert.Equal(s.T(), "route-machine", machine.Name)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	listAllResult, err := ExecuteCommand(
 | 
						listAllResult, _, err := ExecuteCommand(
 | 
				
			||||||
		&s.headscale,
 | 
							&s.headscale,
 | 
				
			||||||
		[]string{
 | 
							[]string{
 | 
				
			||||||
			"headscale",
 | 
								"headscale",
 | 
				
			||||||
@ -1305,7 +1305,7 @@ func (s *IntegrationCLITestSuite) TestRouteCommand() {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	assert.Empty(s.T(), listAll.EnabledRoutes)
 | 
						assert.Empty(s.T(), listAll.EnabledRoutes)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	enableTwoRoutesResult, err := ExecuteCommand(
 | 
						enableTwoRoutesResult, _, err := ExecuteCommand(
 | 
				
			||||||
		&s.headscale,
 | 
							&s.headscale,
 | 
				
			||||||
		[]string{
 | 
							[]string{
 | 
				
			||||||
			"headscale",
 | 
								"headscale",
 | 
				
			||||||
@ -1337,7 +1337,7 @@ func (s *IntegrationCLITestSuite) TestRouteCommand() {
 | 
				
			|||||||
	assert.Contains(s.T(), enableTwoRoutes.EnabledRoutes, "192.168.1.0/24")
 | 
						assert.Contains(s.T(), enableTwoRoutes.EnabledRoutes, "192.168.1.0/24")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Enable only one route, effectively disabling one of the routes
 | 
						// Enable only one route, effectively disabling one of the routes
 | 
				
			||||||
	enableOneRouteResult, err := ExecuteCommand(
 | 
						enableOneRouteResult, _, err := ExecuteCommand(
 | 
				
			||||||
		&s.headscale,
 | 
							&s.headscale,
 | 
				
			||||||
		[]string{
 | 
							[]string{
 | 
				
			||||||
			"headscale",
 | 
								"headscale",
 | 
				
			||||||
@ -1366,7 +1366,7 @@ func (s *IntegrationCLITestSuite) TestRouteCommand() {
 | 
				
			|||||||
	assert.Contains(s.T(), enableOneRoute.EnabledRoutes, "10.0.0.0/8")
 | 
						assert.Contains(s.T(), enableOneRoute.EnabledRoutes, "10.0.0.0/8")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Enable only one route, effectively disabling one of the routes
 | 
						// Enable only one route, effectively disabling one of the routes
 | 
				
			||||||
	failEnableNonAdvertisedRoute, err := ExecuteCommand(
 | 
						failEnableNonAdvertisedRoute, _, err := ExecuteCommand(
 | 
				
			||||||
		&s.headscale,
 | 
							&s.headscale,
 | 
				
			||||||
		[]string{
 | 
							[]string{
 | 
				
			||||||
			"headscale",
 | 
								"headscale",
 | 
				
			||||||
@ -1390,7 +1390,7 @@ func (s *IntegrationCLITestSuite) TestRouteCommand() {
 | 
				
			|||||||
	)
 | 
						)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Enable all routes on host
 | 
						// Enable all routes on host
 | 
				
			||||||
	enableAllRouteResult, err := ExecuteCommand(
 | 
						enableAllRouteResult, _, err := ExecuteCommand(
 | 
				
			||||||
		&s.headscale,
 | 
							&s.headscale,
 | 
				
			||||||
		[]string{
 | 
							[]string{
 | 
				
			||||||
			"headscale",
 | 
								"headscale",
 | 
				
			||||||
@ -1425,7 +1425,7 @@ func (s *IntegrationCLITestSuite) TestApiKeyCommand() {
 | 
				
			|||||||
	keys := make([]string, count)
 | 
						keys := make([]string, count)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	for i := 0; i < count; i++ {
 | 
						for i := 0; i < count; i++ {
 | 
				
			||||||
		apiResult, err := ExecuteCommand(
 | 
							apiResult, _, err := ExecuteCommand(
 | 
				
			||||||
			&s.headscale,
 | 
								&s.headscale,
 | 
				
			||||||
			[]string{
 | 
								[]string{
 | 
				
			||||||
				"headscale",
 | 
									"headscale",
 | 
				
			||||||
@ -1451,7 +1451,7 @@ func (s *IntegrationCLITestSuite) TestApiKeyCommand() {
 | 
				
			|||||||
	assert.Len(s.T(), keys, 5)
 | 
						assert.Len(s.T(), keys, 5)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Test list of keys
 | 
						// Test list of keys
 | 
				
			||||||
	listResult, err := ExecuteCommand(
 | 
						listResult, _, err := ExecuteCommand(
 | 
				
			||||||
		&s.headscale,
 | 
							&s.headscale,
 | 
				
			||||||
		[]string{
 | 
							[]string{
 | 
				
			||||||
			"headscale",
 | 
								"headscale",
 | 
				
			||||||
@ -1513,7 +1513,7 @@ func (s *IntegrationCLITestSuite) TestApiKeyCommand() {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	// Expire three keys
 | 
						// Expire three keys
 | 
				
			||||||
	for i := 0; i < 3; i++ {
 | 
						for i := 0; i < 3; i++ {
 | 
				
			||||||
		_, err := ExecuteCommand(
 | 
							_, _, err := ExecuteCommand(
 | 
				
			||||||
			&s.headscale,
 | 
								&s.headscale,
 | 
				
			||||||
			[]string{
 | 
								[]string{
 | 
				
			||||||
				"headscale",
 | 
									"headscale",
 | 
				
			||||||
@ -1530,7 +1530,7 @@ func (s *IntegrationCLITestSuite) TestApiKeyCommand() {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Test list pre auth keys after expire
 | 
						// Test list pre auth keys after expire
 | 
				
			||||||
	listAfterExpireResult, err := ExecuteCommand(
 | 
						listAfterExpireResult, _, err := ExecuteCommand(
 | 
				
			||||||
		&s.headscale,
 | 
							&s.headscale,
 | 
				
			||||||
		[]string{
 | 
							[]string{
 | 
				
			||||||
			"headscale",
 | 
								"headscale",
 | 
				
			||||||
@ -1573,7 +1573,7 @@ func (s *IntegrationCLITestSuite) TestNodeMoveCommand() {
 | 
				
			|||||||
	// Randomly generated machine key
 | 
						// Randomly generated machine key
 | 
				
			||||||
	machineKey := "688411b767663479632d44140f08a9fde87383adc7cdeb518f62ce28a17ef0aa"
 | 
						machineKey := "688411b767663479632d44140f08a9fde87383adc7cdeb518f62ce28a17ef0aa"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	_, err = ExecuteCommand(
 | 
						_, _, err = ExecuteCommand(
 | 
				
			||||||
		&s.headscale,
 | 
							&s.headscale,
 | 
				
			||||||
		[]string{
 | 
							[]string{
 | 
				
			||||||
			"headscale",
 | 
								"headscale",
 | 
				
			||||||
@ -1592,7 +1592,7 @@ func (s *IntegrationCLITestSuite) TestNodeMoveCommand() {
 | 
				
			|||||||
	)
 | 
						)
 | 
				
			||||||
	assert.Nil(s.T(), err)
 | 
						assert.Nil(s.T(), err)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	machineResult, err := ExecuteCommand(
 | 
						machineResult, _, err := ExecuteCommand(
 | 
				
			||||||
		&s.headscale,
 | 
							&s.headscale,
 | 
				
			||||||
		[]string{
 | 
							[]string{
 | 
				
			||||||
			"headscale",
 | 
								"headscale",
 | 
				
			||||||
@ -1619,7 +1619,7 @@ func (s *IntegrationCLITestSuite) TestNodeMoveCommand() {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	machineId := fmt.Sprintf("%d", machine.Id)
 | 
						machineId := fmt.Sprintf("%d", machine.Id)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	moveToNewNSResult, err := ExecuteCommand(
 | 
						moveToNewNSResult, _, err := ExecuteCommand(
 | 
				
			||||||
		&s.headscale,
 | 
							&s.headscale,
 | 
				
			||||||
		[]string{
 | 
							[]string{
 | 
				
			||||||
			"headscale",
 | 
								"headscale",
 | 
				
			||||||
@ -1641,7 +1641,7 @@ func (s *IntegrationCLITestSuite) TestNodeMoveCommand() {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	assert.Equal(s.T(), machine.Namespace, newNamespace)
 | 
						assert.Equal(s.T(), machine.Namespace, newNamespace)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	listAllNodesResult, err := ExecuteCommand(
 | 
						listAllNodesResult, _, err := ExecuteCommand(
 | 
				
			||||||
		&s.headscale,
 | 
							&s.headscale,
 | 
				
			||||||
		[]string{
 | 
							[]string{
 | 
				
			||||||
			"headscale",
 | 
								"headscale",
 | 
				
			||||||
@ -1664,7 +1664,7 @@ func (s *IntegrationCLITestSuite) TestNodeMoveCommand() {
 | 
				
			|||||||
	assert.Equal(s.T(), allNodes[0].Namespace, machine.Namespace)
 | 
						assert.Equal(s.T(), allNodes[0].Namespace, machine.Namespace)
 | 
				
			||||||
	assert.Equal(s.T(), allNodes[0].Namespace, newNamespace)
 | 
						assert.Equal(s.T(), allNodes[0].Namespace, newNamespace)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	moveToNonExistingNSResult, err := ExecuteCommand(
 | 
						moveToNonExistingNSResult, _, err := ExecuteCommand(
 | 
				
			||||||
		&s.headscale,
 | 
							&s.headscale,
 | 
				
			||||||
		[]string{
 | 
							[]string{
 | 
				
			||||||
			"headscale",
 | 
								"headscale",
 | 
				
			||||||
@ -1688,7 +1688,7 @@ func (s *IntegrationCLITestSuite) TestNodeMoveCommand() {
 | 
				
			|||||||
	)
 | 
						)
 | 
				
			||||||
	assert.Equal(s.T(), machine.Namespace, newNamespace)
 | 
						assert.Equal(s.T(), machine.Namespace, newNamespace)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	moveToOldNSResult, err := ExecuteCommand(
 | 
						moveToOldNSResult, _, err := ExecuteCommand(
 | 
				
			||||||
		&s.headscale,
 | 
							&s.headscale,
 | 
				
			||||||
		[]string{
 | 
							[]string{
 | 
				
			||||||
			"headscale",
 | 
								"headscale",
 | 
				
			||||||
@ -1710,7 +1710,7 @@ func (s *IntegrationCLITestSuite) TestNodeMoveCommand() {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	assert.Equal(s.T(), machine.Namespace, oldNamespace)
 | 
						assert.Equal(s.T(), machine.Namespace, oldNamespace)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	moveToSameNSResult, err := ExecuteCommand(
 | 
						moveToSameNSResult, _, err := ExecuteCommand(
 | 
				
			||||||
		&s.headscale,
 | 
							&s.headscale,
 | 
				
			||||||
		[]string{
 | 
							[]string{
 | 
				
			||||||
			"headscale",
 | 
								"headscale",
 | 
				
			||||||
@ -1742,7 +1742,7 @@ func (s *IntegrationCLITestSuite) TestLoadConfigFromCommand() {
 | 
				
			|||||||
	altEnvConfig, err := os.ReadFile("integration_test/etc/alt-env-config.dump.gold.yaml")
 | 
						altEnvConfig, err := os.ReadFile("integration_test/etc/alt-env-config.dump.gold.yaml")
 | 
				
			||||||
	assert.Nil(s.T(), err)
 | 
						assert.Nil(s.T(), err)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	_, err = ExecuteCommand(
 | 
						_, _, err = ExecuteCommand(
 | 
				
			||||||
		&s.headscale,
 | 
							&s.headscale,
 | 
				
			||||||
		[]string{
 | 
							[]string{
 | 
				
			||||||
			"headscale",
 | 
								"headscale",
 | 
				
			||||||
@ -1757,7 +1757,7 @@ func (s *IntegrationCLITestSuite) TestLoadConfigFromCommand() {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	assert.YAMLEq(s.T(), string(defaultConfig), string(defaultDumpConfig))
 | 
						assert.YAMLEq(s.T(), string(defaultConfig), string(defaultDumpConfig))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	_, err = ExecuteCommand(
 | 
						_, _, err = ExecuteCommand(
 | 
				
			||||||
		&s.headscale,
 | 
							&s.headscale,
 | 
				
			||||||
		[]string{
 | 
							[]string{
 | 
				
			||||||
			"headscale",
 | 
								"headscale",
 | 
				
			||||||
@ -1774,7 +1774,7 @@ func (s *IntegrationCLITestSuite) TestLoadConfigFromCommand() {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	assert.YAMLEq(s.T(), string(altConfig), string(altDumpConfig))
 | 
						assert.YAMLEq(s.T(), string(altConfig), string(altDumpConfig))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	_, err = ExecuteCommand(
 | 
						_, _, err = ExecuteCommand(
 | 
				
			||||||
		&s.headscale,
 | 
							&s.headscale,
 | 
				
			||||||
		[]string{
 | 
							[]string{
 | 
				
			||||||
			"headscale",
 | 
								"headscale",
 | 
				
			||||||
@ -1791,7 +1791,7 @@ func (s *IntegrationCLITestSuite) TestLoadConfigFromCommand() {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	assert.YAMLEq(s.T(), string(altEnvConfig), string(altEnvDumpConfig))
 | 
						assert.YAMLEq(s.T(), string(altEnvConfig), string(altEnvDumpConfig))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	_, err = ExecuteCommand(
 | 
						_, _, err = ExecuteCommand(
 | 
				
			||||||
		&s.headscale,
 | 
							&s.headscale,
 | 
				
			||||||
		[]string{
 | 
							[]string{
 | 
				
			||||||
			"headscale",
 | 
								"headscale",
 | 
				
			||||||
 | 
				
			|||||||
@ -68,7 +68,7 @@ func ExecuteCommand(
 | 
				
			|||||||
	cmd []string,
 | 
						cmd []string,
 | 
				
			||||||
	env []string,
 | 
						env []string,
 | 
				
			||||||
	options ...ExecuteCommandOption,
 | 
						options ...ExecuteCommandOption,
 | 
				
			||||||
) (string, error) {
 | 
					) (string, string, error) {
 | 
				
			||||||
	var stdout bytes.Buffer
 | 
						var stdout bytes.Buffer
 | 
				
			||||||
	var stderr bytes.Buffer
 | 
						var stderr bytes.Buffer
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -78,7 +78,7 @@ func ExecuteCommand(
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	for _, opt := range options {
 | 
						for _, opt := range options {
 | 
				
			||||||
		if err := opt(&execConfig); err != nil {
 | 
							if err := opt(&execConfig); err != nil {
 | 
				
			||||||
			return "", fmt.Errorf("execute-command/options: %w", err)
 | 
								return "", "", fmt.Errorf("execute-command/options: %w", err)
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -107,7 +107,7 @@ func ExecuteCommand(
 | 
				
			|||||||
	select {
 | 
						select {
 | 
				
			||||||
	case res := <-resultChan:
 | 
						case res := <-resultChan:
 | 
				
			||||||
		if res.err != nil {
 | 
							if res.err != nil {
 | 
				
			||||||
			return "", res.err
 | 
								return stdout.String(), stderr.String(), res.err
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if res.exitCode != 0 {
 | 
							if res.exitCode != 0 {
 | 
				
			||||||
@ -115,13 +115,13 @@ func ExecuteCommand(
 | 
				
			|||||||
			fmt.Println("stdout: ", stdout.String())
 | 
								fmt.Println("stdout: ", stdout.String())
 | 
				
			||||||
			fmt.Println("stderr: ", stderr.String())
 | 
								fmt.Println("stderr: ", stderr.String())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			return "", fmt.Errorf("command failed with: %s", stderr.String())
 | 
								return stdout.String(), stderr.String(), fmt.Errorf("command failed with: %s", stderr.String())
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		return stdout.String(), nil
 | 
							return stdout.String(), stderr.String(), nil
 | 
				
			||||||
	case <-time.After(execConfig.timeout):
 | 
						case <-time.After(execConfig.timeout):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		return "", fmt.Errorf("command timed out after %s", execConfig.timeout)
 | 
							return stdout.String(), stderr.String(), fmt.Errorf("command timed out after %s", execConfig.timeout)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -200,7 +200,7 @@ func getIPs(
 | 
				
			|||||||
	for hostname, tailscale := range tailscales {
 | 
						for hostname, tailscale := range tailscales {
 | 
				
			||||||
		command := []string{"tailscale", "ip"}
 | 
							command := []string{"tailscale", "ip"}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		result, err := ExecuteCommand(
 | 
							result, _, err := ExecuteCommand(
 | 
				
			||||||
			&tailscale,
 | 
								&tailscale,
 | 
				
			||||||
			command,
 | 
								command,
 | 
				
			||||||
			[]string{},
 | 
								[]string{},
 | 
				
			||||||
@ -228,7 +228,7 @@ func getIPs(
 | 
				
			|||||||
func getDNSNames(
 | 
					func getDNSNames(
 | 
				
			||||||
	headscale *dockertest.Resource,
 | 
						headscale *dockertest.Resource,
 | 
				
			||||||
) ([]string, error) {
 | 
					) ([]string, error) {
 | 
				
			||||||
	listAllResult, err := ExecuteCommand(
 | 
						listAllResult, _, err := ExecuteCommand(
 | 
				
			||||||
		headscale,
 | 
							headscale,
 | 
				
			||||||
		[]string{
 | 
							[]string{
 | 
				
			||||||
			"headscale",
 | 
								"headscale",
 | 
				
			||||||
@ -261,7 +261,7 @@ func getDNSNames(
 | 
				
			|||||||
func getMagicFQDN(
 | 
					func getMagicFQDN(
 | 
				
			||||||
	headscale *dockertest.Resource,
 | 
						headscale *dockertest.Resource,
 | 
				
			||||||
) ([]string, error) {
 | 
					) ([]string, error) {
 | 
				
			||||||
	listAllResult, err := ExecuteCommand(
 | 
						listAllResult, _, err := ExecuteCommand(
 | 
				
			||||||
		headscale,
 | 
							headscale,
 | 
				
			||||||
		[]string{
 | 
							[]string{
 | 
				
			||||||
			"headscale",
 | 
								"headscale",
 | 
				
			||||||
 | 
				
			|||||||
@ -187,7 +187,7 @@ func (s *IntegrationDERPTestSuite) SetupSuite() {
 | 
				
			|||||||
	log.Println("headscale container is ready for embedded DERP tests")
 | 
						log.Println("headscale container is ready for embedded DERP tests")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	log.Printf("Creating headscale namespace: %s\n", namespaceName)
 | 
						log.Printf("Creating headscale namespace: %s\n", namespaceName)
 | 
				
			||||||
	result, err := ExecuteCommand(
 | 
						result, _, err := ExecuteCommand(
 | 
				
			||||||
		&s.headscale,
 | 
							&s.headscale,
 | 
				
			||||||
		[]string{"headscale", "namespaces", "create", namespaceName},
 | 
							[]string{"headscale", "namespaces", "create", namespaceName},
 | 
				
			||||||
		[]string{},
 | 
							[]string{},
 | 
				
			||||||
@ -196,7 +196,7 @@ func (s *IntegrationDERPTestSuite) SetupSuite() {
 | 
				
			|||||||
	assert.Nil(s.T(), err)
 | 
						assert.Nil(s.T(), err)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	log.Printf("Creating pre auth key for %s\n", namespaceName)
 | 
						log.Printf("Creating pre auth key for %s\n", namespaceName)
 | 
				
			||||||
	preAuthResult, err := ExecuteCommand(
 | 
						preAuthResult, _, err := ExecuteCommand(
 | 
				
			||||||
		&s.headscale,
 | 
							&s.headscale,
 | 
				
			||||||
		[]string{
 | 
							[]string{
 | 
				
			||||||
			"headscale",
 | 
								"headscale",
 | 
				
			||||||
@ -259,7 +259,7 @@ func (s *IntegrationDERPTestSuite) Join(
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	log.Println("Join command:", command)
 | 
						log.Println("Join command:", command)
 | 
				
			||||||
	log.Printf("Running join command for %s\n", hostname)
 | 
						log.Printf("Running join command for %s\n", hostname)
 | 
				
			||||||
	_, err := ExecuteCommand(
 | 
						_, _, err := ExecuteCommand(
 | 
				
			||||||
		&tailscale,
 | 
							&tailscale,
 | 
				
			||||||
		command,
 | 
							command,
 | 
				
			||||||
		[]string{},
 | 
							[]string{},
 | 
				
			||||||
@ -414,7 +414,7 @@ func (s *IntegrationDERPTestSuite) TestPingAllPeersByHostname() {
 | 
				
			|||||||
					peername,
 | 
										peername,
 | 
				
			||||||
				)
 | 
									)
 | 
				
			||||||
				log.Println(command)
 | 
									log.Println(command)
 | 
				
			||||||
				result, err := ExecuteCommand(
 | 
									result, _, err := ExecuteCommand(
 | 
				
			||||||
					&tailscale,
 | 
										&tailscale,
 | 
				
			||||||
					command,
 | 
										command,
 | 
				
			||||||
					[]string{},
 | 
										[]string{},
 | 
				
			||||||
 | 
				
			|||||||
@ -163,7 +163,7 @@ func (s *IntegrationTestSuite) Join(
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	log.Println("Join command:", command)
 | 
						log.Println("Join command:", command)
 | 
				
			||||||
	log.Printf("Running join command for %s\n", hostname)
 | 
						log.Printf("Running join command for %s\n", hostname)
 | 
				
			||||||
	_, err := ExecuteCommand(
 | 
						_, _, err := ExecuteCommand(
 | 
				
			||||||
		&tailscale,
 | 
							&tailscale,
 | 
				
			||||||
		command,
 | 
							command,
 | 
				
			||||||
		[]string{},
 | 
							[]string{},
 | 
				
			||||||
@ -305,7 +305,7 @@ func (s *IntegrationTestSuite) SetupSuite() {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	for namespace, scales := range s.namespaces {
 | 
						for namespace, scales := range s.namespaces {
 | 
				
			||||||
		log.Printf("Creating headscale namespace: %s\n", namespace)
 | 
							log.Printf("Creating headscale namespace: %s\n", namespace)
 | 
				
			||||||
		result, err := ExecuteCommand(
 | 
							result, _, err := ExecuteCommand(
 | 
				
			||||||
			&s.headscale,
 | 
								&s.headscale,
 | 
				
			||||||
			[]string{"headscale", "namespaces", "create", namespace},
 | 
								[]string{"headscale", "namespaces", "create", namespace},
 | 
				
			||||||
			[]string{},
 | 
								[]string{},
 | 
				
			||||||
@ -314,7 +314,7 @@ func (s *IntegrationTestSuite) SetupSuite() {
 | 
				
			|||||||
		assert.Nil(s.T(), err)
 | 
							assert.Nil(s.T(), err)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		log.Printf("Creating pre auth key for %s\n", namespace)
 | 
							log.Printf("Creating pre auth key for %s\n", namespace)
 | 
				
			||||||
		preAuthResult, err := ExecuteCommand(
 | 
							preAuthResult, _, err := ExecuteCommand(
 | 
				
			||||||
			&s.headscale,
 | 
								&s.headscale,
 | 
				
			||||||
			[]string{
 | 
								[]string{
 | 
				
			||||||
				"headscale",
 | 
									"headscale",
 | 
				
			||||||
@ -386,7 +386,7 @@ func (s *IntegrationTestSuite) HandleStats(
 | 
				
			|||||||
func (s *IntegrationTestSuite) TestListNodes() {
 | 
					func (s *IntegrationTestSuite) TestListNodes() {
 | 
				
			||||||
	for namespace, scales := range s.namespaces {
 | 
						for namespace, scales := range s.namespaces {
 | 
				
			||||||
		log.Println("Listing nodes")
 | 
							log.Println("Listing nodes")
 | 
				
			||||||
		result, err := ExecuteCommand(
 | 
							result, _, err := ExecuteCommand(
 | 
				
			||||||
			&s.headscale,
 | 
								&s.headscale,
 | 
				
			||||||
			[]string{"headscale", "--namespace", namespace, "nodes", "list"},
 | 
								[]string{"headscale", "--namespace", namespace, "nodes", "list"},
 | 
				
			||||||
			[]string{},
 | 
								[]string{},
 | 
				
			||||||
@ -518,7 +518,7 @@ func (s *IntegrationTestSuite) TestPingAllPeersByAddress() {
 | 
				
			|||||||
								peername,
 | 
													peername,
 | 
				
			||||||
								ip,
 | 
													ip,
 | 
				
			||||||
							)
 | 
												)
 | 
				
			||||||
							result, err := ExecuteCommand(
 | 
												result, _, err := ExecuteCommand(
 | 
				
			||||||
								&tailscale,
 | 
													&tailscale,
 | 
				
			||||||
								command,
 | 
													command,
 | 
				
			||||||
								[]string{},
 | 
													[]string{},
 | 
				
			||||||
@ -552,7 +552,7 @@ func (s *IntegrationTestSuite) TestTailDrop() {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
		for hostname, tailscale := range scales.tailscales {
 | 
							for hostname, tailscale := range scales.tailscales {
 | 
				
			||||||
			command := []string{"touch", fmt.Sprintf("/tmp/file_from_%s", hostname)}
 | 
								command := []string{"touch", fmt.Sprintf("/tmp/file_from_%s", hostname)}
 | 
				
			||||||
			_, err := ExecuteCommand(
 | 
								_, _, err := ExecuteCommand(
 | 
				
			||||||
				&tailscale,
 | 
									&tailscale,
 | 
				
			||||||
				command,
 | 
									command,
 | 
				
			||||||
				[]string{},
 | 
									[]string{},
 | 
				
			||||||
@ -586,7 +586,7 @@ func (s *IntegrationTestSuite) TestTailDrop() {
 | 
				
			|||||||
							hostname,
 | 
												hostname,
 | 
				
			||||||
							peername,
 | 
												peername,
 | 
				
			||||||
						)
 | 
											)
 | 
				
			||||||
						_, err := ExecuteCommand(
 | 
											_, _, err := ExecuteCommand(
 | 
				
			||||||
							&tailscale,
 | 
												&tailscale,
 | 
				
			||||||
							command,
 | 
												command,
 | 
				
			||||||
							[]string{},
 | 
												[]string{},
 | 
				
			||||||
@ -606,7 +606,7 @@ func (s *IntegrationTestSuite) TestTailDrop() {
 | 
				
			|||||||
				"get",
 | 
									"get",
 | 
				
			||||||
				"/tmp/",
 | 
									"/tmp/",
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
			_, err := ExecuteCommand(
 | 
								_, _, err := ExecuteCommand(
 | 
				
			||||||
				&tailscale,
 | 
									&tailscale,
 | 
				
			||||||
				command,
 | 
									command,
 | 
				
			||||||
				[]string{},
 | 
									[]string{},
 | 
				
			||||||
@ -628,7 +628,7 @@ func (s *IntegrationTestSuite) TestTailDrop() {
 | 
				
			|||||||
						peername,
 | 
											peername,
 | 
				
			||||||
						ip,
 | 
											ip,
 | 
				
			||||||
					)
 | 
										)
 | 
				
			||||||
					result, err := ExecuteCommand(
 | 
										result, _, err := ExecuteCommand(
 | 
				
			||||||
						&tailscale,
 | 
											&tailscale,
 | 
				
			||||||
						command,
 | 
											command,
 | 
				
			||||||
						[]string{},
 | 
											[]string{},
 | 
				
			||||||
@ -672,7 +672,7 @@ func (s *IntegrationTestSuite) TestPingAllPeersByHostname() {
 | 
				
			|||||||
						hostname,
 | 
											hostname,
 | 
				
			||||||
						peername,
 | 
											peername,
 | 
				
			||||||
					)
 | 
										)
 | 
				
			||||||
					result, err := ExecuteCommand(
 | 
										result, _, err := ExecuteCommand(
 | 
				
			||||||
						&tailscale,
 | 
											&tailscale,
 | 
				
			||||||
						command,
 | 
											command,
 | 
				
			||||||
						[]string{},
 | 
											[]string{},
 | 
				
			||||||
@ -724,7 +724,7 @@ func (s *IntegrationTestSuite) TestMagicDNS() {
 | 
				
			|||||||
							peername,
 | 
												peername,
 | 
				
			||||||
							hostname,
 | 
												hostname,
 | 
				
			||||||
						)
 | 
											)
 | 
				
			||||||
						result, err := ExecuteCommand(
 | 
											result, _, err := ExecuteCommand(
 | 
				
			||||||
							&tailscale,
 | 
												&tailscale,
 | 
				
			||||||
							command,
 | 
												command,
 | 
				
			||||||
							[]string{},
 | 
												[]string{},
 | 
				
			||||||
@ -757,7 +757,7 @@ func getAPIURLs(
 | 
				
			|||||||
			"/run/tailscale/tailscaled.sock",
 | 
								"/run/tailscale/tailscaled.sock",
 | 
				
			||||||
			"http://localhost/localapi/v0/file-targets",
 | 
								"http://localhost/localapi/v0/file-targets",
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		result, err := ExecuteCommand(
 | 
							result, _, err := ExecuteCommand(
 | 
				
			||||||
			&tailscale,
 | 
								&tailscale,
 | 
				
			||||||
			command,
 | 
								command,
 | 
				
			||||||
			[]string{},
 | 
								[]string{},
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										506
									
								
								integration_oidc_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										506
									
								
								integration_oidc_test.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,506 @@
 | 
				
			|||||||
 | 
					//go:build integration_oidc
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					package headscale
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"bytes"
 | 
				
			||||||
 | 
						"context"
 | 
				
			||||||
 | 
						"crypto/tls"
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
						"io"
 | 
				
			||||||
 | 
						"log"
 | 
				
			||||||
 | 
						"net/http"
 | 
				
			||||||
 | 
						"net/url"
 | 
				
			||||||
 | 
						"os"
 | 
				
			||||||
 | 
						"path"
 | 
				
			||||||
 | 
						"strings"
 | 
				
			||||||
 | 
						"sync"
 | 
				
			||||||
 | 
						"testing"
 | 
				
			||||||
 | 
						"time"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"github.com/ory/dockertest/v3"
 | 
				
			||||||
 | 
						"github.com/ory/dockertest/v3/docker"
 | 
				
			||||||
 | 
						"github.com/stretchr/testify/assert"
 | 
				
			||||||
 | 
						"github.com/stretchr/testify/suite"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const (
 | 
				
			||||||
 | 
						oidcHeadscaleHostname = "headscale"
 | 
				
			||||||
 | 
						oidcNamespaceName     = "oidcnamespace"
 | 
				
			||||||
 | 
						totalOidcContainers   = 3
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type IntegrationOIDCTestSuite struct {
 | 
				
			||||||
 | 
						suite.Suite
 | 
				
			||||||
 | 
						stats *suite.SuiteInformation
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						pool      dockertest.Pool
 | 
				
			||||||
 | 
						network   dockertest.Network
 | 
				
			||||||
 | 
						headscale dockertest.Resource
 | 
				
			||||||
 | 
						mockOidc  dockertest.Resource
 | 
				
			||||||
 | 
						saveLogs  bool
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						tailscales    map[string]dockertest.Resource
 | 
				
			||||||
 | 
						joinWaitGroup sync.WaitGroup
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func TestOIDCIntegrationTestSuite(t *testing.T) {
 | 
				
			||||||
 | 
						saveLogs, err := GetEnvBool("HEADSCALE_INTEGRATION_SAVE_LOG")
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							saveLogs = false
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						s := new(IntegrationOIDCTestSuite)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						s.tailscales = make(map[string]dockertest.Resource)
 | 
				
			||||||
 | 
						s.saveLogs = saveLogs
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						suite.Run(t, s)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// HandleStats, which allows us to check if we passed and save logs
 | 
				
			||||||
 | 
						// is called after TearDown, so we cannot tear down containers before
 | 
				
			||||||
 | 
						// we have potentially saved the logs.
 | 
				
			||||||
 | 
						if s.saveLogs {
 | 
				
			||||||
 | 
							for _, tailscale := range s.tailscales {
 | 
				
			||||||
 | 
								if err := s.pool.Purge(&tailscale); err != nil {
 | 
				
			||||||
 | 
									log.Printf("Could not purge resource: %s\n", err)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if !s.stats.Passed() {
 | 
				
			||||||
 | 
								err := s.saveLog(&s.headscale, "test_output")
 | 
				
			||||||
 | 
								if err != nil {
 | 
				
			||||||
 | 
									log.Printf("Could not save log: %s\n", err)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if err := s.pool.Purge(&s.mockOidc); err != nil {
 | 
				
			||||||
 | 
								log.Printf("Could not purge resource: %s\n", err)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if err := s.pool.Purge(&s.headscale); err != nil {
 | 
				
			||||||
 | 
								t.Logf("Could not purge resource: %s\n", err)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if err := s.network.Close(); err != nil {
 | 
				
			||||||
 | 
								log.Printf("Could not close network: %s\n", err)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (s *IntegrationOIDCTestSuite) SetupSuite() {
 | 
				
			||||||
 | 
						if ppool, err := dockertest.NewPool(""); err == nil {
 | 
				
			||||||
 | 
							s.pool = *ppool
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							s.FailNow(fmt.Sprintf("Could not connect to docker: %s", err), "")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if pnetwork, err := s.pool.CreateNetwork("headscale-test"); err == nil {
 | 
				
			||||||
 | 
							s.network = *pnetwork
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							s.FailNow(fmt.Sprintf("Could not create network: %s", err), "")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Create does not give us an updated version of the resource, so we need to
 | 
				
			||||||
 | 
						// get it again.
 | 
				
			||||||
 | 
						networks, err := s.pool.NetworksByName("headscale-test")
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							s.FailNow(fmt.Sprintf("Could not get network: %s", err), "")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						s.network = networks[0]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						log.Printf("Network config: %v", s.network.Network.IPAM.Config[0])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						s.Suite.T().Log("Setting up mock OIDC")
 | 
				
			||||||
 | 
						mockOidcOptions := &dockertest.RunOptions{
 | 
				
			||||||
 | 
							Name:         "mockoidc",
 | 
				
			||||||
 | 
							Hostname:     "mockoidc",
 | 
				
			||||||
 | 
							Cmd:          []string{"headscale", "mockoidc"},
 | 
				
			||||||
 | 
							ExposedPorts: []string{"10000/tcp"},
 | 
				
			||||||
 | 
							Networks:     []*dockertest.Network{&s.network},
 | 
				
			||||||
 | 
							PortBindings: map[docker.Port][]docker.PortBinding{
 | 
				
			||||||
 | 
								"10000/tcp": {{HostPort: "10000"}},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							Env: []string{
 | 
				
			||||||
 | 
								"MOCKOIDC_PORT=10000",
 | 
				
			||||||
 | 
								"MOCKOIDC_CLIENT_ID=superclient",
 | 
				
			||||||
 | 
								"MOCKOIDC_CLIENT_SECRET=supersecret",
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						headscaleBuildOptions := &dockertest.BuildOptions{
 | 
				
			||||||
 | 
							Dockerfile: "Dockerfile.debug",
 | 
				
			||||||
 | 
							ContextDir: ".",
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if pmockoidc, err := s.pool.BuildAndRunWithBuildOptions(
 | 
				
			||||||
 | 
							headscaleBuildOptions,
 | 
				
			||||||
 | 
							mockOidcOptions,
 | 
				
			||||||
 | 
							DockerRestartPolicy); err == nil {
 | 
				
			||||||
 | 
							s.mockOidc = *pmockoidc
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							s.FailNow(fmt.Sprintf("Could not start mockOIDC container: %s", err), "")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						oidcCfg := fmt.Sprintf(`
 | 
				
			||||||
 | 
					oidc:
 | 
				
			||||||
 | 
					  issuer: http://%s:10000/oidc
 | 
				
			||||||
 | 
					  client_id: superclient
 | 
				
			||||||
 | 
					  client_secret: supersecret
 | 
				
			||||||
 | 
					  strip_email_domain: true`, s.mockOidc.GetIPInNetwork(&s.network))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						currentPath, err := os.Getwd()
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							s.FailNow(fmt.Sprintf("Could not determine current path: %s", err), "")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						baseConfig, err := os.ReadFile(
 | 
				
			||||||
 | 
							path.Join(currentPath, "integration_test/etc_oidc/base_config.yaml"))
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							s.FailNow(fmt.Sprintf("Could not read base config: %s", err), "")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						config := string(baseConfig) + oidcCfg
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						log.Println(config)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						configPath := path.Join(currentPath, "integration_test/etc_oidc/config.yaml")
 | 
				
			||||||
 | 
						err = os.WriteFile(configPath, []byte(config), 0644)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							s.FailNow(fmt.Sprintf("Could not write config: %s", err), "")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						headscaleOptions := &dockertest.RunOptions{
 | 
				
			||||||
 | 
							Name:     oidcHeadscaleHostname,
 | 
				
			||||||
 | 
							Networks: []*dockertest.Network{&s.network},
 | 
				
			||||||
 | 
							Mounts: []string{
 | 
				
			||||||
 | 
								path.Join(currentPath,
 | 
				
			||||||
 | 
									"integration_test/etc_oidc:/etc/headscale",
 | 
				
			||||||
 | 
								),
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							Cmd:          []string{"headscale", "serve"},
 | 
				
			||||||
 | 
							ExposedPorts: []string{"8443/tcp", "3478/udp"},
 | 
				
			||||||
 | 
							PortBindings: map[docker.Port][]docker.PortBinding{
 | 
				
			||||||
 | 
								"8443/tcp": {{HostPort: "8443"}},
 | 
				
			||||||
 | 
								"3478/udp": {{HostPort: "3478"}},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						err = s.pool.RemoveContainerByName(oidcHeadscaleHostname)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							s.FailNow(
 | 
				
			||||||
 | 
								fmt.Sprintf(
 | 
				
			||||||
 | 
									"Could not remove existing container before building test: %s",
 | 
				
			||||||
 | 
									err,
 | 
				
			||||||
 | 
								),
 | 
				
			||||||
 | 
								"",
 | 
				
			||||||
 | 
							)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						s.Suite.T().Logf("Creating headscale container for OIDC integration tests")
 | 
				
			||||||
 | 
						if pheadscale, err := s.pool.BuildAndRunWithBuildOptions(headscaleBuildOptions, headscaleOptions, DockerRestartPolicy); err == nil {
 | 
				
			||||||
 | 
							s.headscale = *pheadscale
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							s.FailNow(fmt.Sprintf("Could not start headscale container: %s", err), "")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						s.Suite.T().Logf("Created headscale container for embedded OIDC tests")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						s.Suite.T().Logf("Creating tailscale containers for embedded OIDC tests")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for i := 0; i < totalOidcContainers; i++ {
 | 
				
			||||||
 | 
							version := tailscaleVersions[i%len(tailscaleVersions)]
 | 
				
			||||||
 | 
							hostname, container := s.tailscaleContainer(
 | 
				
			||||||
 | 
								fmt.Sprint(i),
 | 
				
			||||||
 | 
								version,
 | 
				
			||||||
 | 
							)
 | 
				
			||||||
 | 
							s.tailscales[hostname] = *container
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						s.Suite.T().Logf("Waiting for headscale to be ready for embedded OIDC tests")
 | 
				
			||||||
 | 
						hostEndpoint := fmt.Sprintf("localhost:%s", s.headscale.GetPort("8443/tcp"))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if err := s.pool.Retry(func() error {
 | 
				
			||||||
 | 
							url := fmt.Sprintf("https://%s/health", hostEndpoint)
 | 
				
			||||||
 | 
							insecureTransport := http.DefaultTransport.(*http.Transport).Clone()
 | 
				
			||||||
 | 
							insecureTransport.TLSClientConfig = &tls.Config{InsecureSkipVerify: true}
 | 
				
			||||||
 | 
							client := &http.Client{Transport: insecureTransport}
 | 
				
			||||||
 | 
							resp, err := client.Get(url)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								log.Printf("headscale for embedded OIDC tests is not ready: %s\n", err)
 | 
				
			||||||
 | 
								return err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if resp.StatusCode != http.StatusOK {
 | 
				
			||||||
 | 
								return fmt.Errorf("status code not OK")
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							return nil
 | 
				
			||||||
 | 
						}); err != nil {
 | 
				
			||||||
 | 
							// TODO(kradalby): If we cannot access headscale, or any other fatal error during
 | 
				
			||||||
 | 
							// test setup, we need to abort and tear down. However, testify does not seem to
 | 
				
			||||||
 | 
							// support that at the moment:
 | 
				
			||||||
 | 
							// https://github.com/stretchr/testify/issues/849
 | 
				
			||||||
 | 
							return // fmt.Errorf("Could not connect to headscale: %s", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						s.Suite.T().Log("headscale container is ready for embedded OIDC tests")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						s.Suite.T().Logf("Creating headscale namespace: %s\n", oidcNamespaceName)
 | 
				
			||||||
 | 
						result, _, err := ExecuteCommand(
 | 
				
			||||||
 | 
							&s.headscale,
 | 
				
			||||||
 | 
							[]string{"headscale", "namespaces", "create", oidcNamespaceName},
 | 
				
			||||||
 | 
							[]string{},
 | 
				
			||||||
 | 
						)
 | 
				
			||||||
 | 
						log.Println("headscale create namespace result: ", result)
 | 
				
			||||||
 | 
						assert.Nil(s.T(), err)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						headscaleEndpoint := fmt.Sprintf(
 | 
				
			||||||
 | 
							"https://headscale:%s",
 | 
				
			||||||
 | 
							s.headscale.GetPort("8443/tcp"),
 | 
				
			||||||
 | 
						)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						log.Printf(
 | 
				
			||||||
 | 
							"Joining tailscale containers to headscale at %s\n",
 | 
				
			||||||
 | 
							headscaleEndpoint,
 | 
				
			||||||
 | 
						)
 | 
				
			||||||
 | 
						for hostname, tailscale := range s.tailscales {
 | 
				
			||||||
 | 
							s.joinWaitGroup.Add(1)
 | 
				
			||||||
 | 
							go s.AuthenticateOIDC(headscaleEndpoint, hostname, tailscale)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// TODO(juan): Workaround for https://github.com/juanfont/headscale/issues/814
 | 
				
			||||||
 | 
							time.Sleep(1 * time.Second)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						s.joinWaitGroup.Wait()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// The nodes need a bit of time to get their updated maps from headscale
 | 
				
			||||||
 | 
						// TODO: See if we can have a more deterministic wait here.
 | 
				
			||||||
 | 
						time.Sleep(60 * time.Second)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (s *IntegrationOIDCTestSuite) AuthenticateOIDC(
 | 
				
			||||||
 | 
						endpoint, hostname string,
 | 
				
			||||||
 | 
						tailscale dockertest.Resource,
 | 
				
			||||||
 | 
					) {
 | 
				
			||||||
 | 
						defer s.joinWaitGroup.Done()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						loginURL, err := s.joinOIDC(endpoint, hostname, tailscale)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							s.FailNow(fmt.Sprintf("Could not join OIDC node: %s", err), "")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						insecureTransport := &http.Transport{
 | 
				
			||||||
 | 
							TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						client := &http.Client{Transport: insecureTransport}
 | 
				
			||||||
 | 
						resp, err := client.Get(loginURL.String())
 | 
				
			||||||
 | 
						assert.Nil(s.T(), err)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						body, err := io.ReadAll(resp.Body)
 | 
				
			||||||
 | 
						assert.Nil(s.T(), err)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							s.FailNow(fmt.Sprintf("Could not read login page: %s", err), "")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						log.Printf("Login page for %s: %s", hostname, string(body))
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (s *IntegrationOIDCTestSuite) joinOIDC(
 | 
				
			||||||
 | 
						endpoint, hostname string,
 | 
				
			||||||
 | 
						tailscale dockertest.Resource,
 | 
				
			||||||
 | 
					) (*url.URL, error) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						command := []string{
 | 
				
			||||||
 | 
							"tailscale",
 | 
				
			||||||
 | 
							"up",
 | 
				
			||||||
 | 
							"-login-server",
 | 
				
			||||||
 | 
							endpoint,
 | 
				
			||||||
 | 
							"--hostname",
 | 
				
			||||||
 | 
							hostname,
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						log.Println("Join command:", command)
 | 
				
			||||||
 | 
						log.Printf("Running join command for %s\n", hostname)
 | 
				
			||||||
 | 
						_, stderr, _ := ExecuteCommand(
 | 
				
			||||||
 | 
							&tailscale,
 | 
				
			||||||
 | 
							command,
 | 
				
			||||||
 | 
							[]string{},
 | 
				
			||||||
 | 
						)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// This piece of code just gets the login URL out of the stderr of the tailscale client.
 | 
				
			||||||
 | 
						// See https://github.com/tailscale/tailscale/blob/main/cmd/tailscale/cli/up.go#L584.
 | 
				
			||||||
 | 
						urlStr := strings.ReplaceAll(stderr, "\nTo authenticate, visit:\n\n\t", "")
 | 
				
			||||||
 | 
						urlStr = strings.TrimSpace(urlStr)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// parse URL
 | 
				
			||||||
 | 
						loginUrl, err := url.Parse(urlStr)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							log.Printf("Could not parse login URL: %s", err)
 | 
				
			||||||
 | 
							log.Printf("Original join command result: %s", stderr)
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return loginUrl, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (s *IntegrationOIDCTestSuite) tailscaleContainer(
 | 
				
			||||||
 | 
						identifier, version string,
 | 
				
			||||||
 | 
					) (string, *dockertest.Resource) {
 | 
				
			||||||
 | 
						tailscaleBuildOptions := getDockerBuildOptions(version)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						hostname := fmt.Sprintf(
 | 
				
			||||||
 | 
							"tailscale-%s-%s",
 | 
				
			||||||
 | 
							strings.Replace(version, ".", "-", -1),
 | 
				
			||||||
 | 
							identifier,
 | 
				
			||||||
 | 
						)
 | 
				
			||||||
 | 
						tailscaleOptions := &dockertest.RunOptions{
 | 
				
			||||||
 | 
							Name:     hostname,
 | 
				
			||||||
 | 
							Networks: []*dockertest.Network{&s.network},
 | 
				
			||||||
 | 
							Cmd: []string{
 | 
				
			||||||
 | 
								"tailscaled", "--tun=tsdev",
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// expose the host IP address, so we can access it from inside the container
 | 
				
			||||||
 | 
							ExtraHosts: []string{
 | 
				
			||||||
 | 
								"host.docker.internal:host-gateway",
 | 
				
			||||||
 | 
								"headscale:host-gateway",
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						pts, err := s.pool.BuildAndRunWithBuildOptions(
 | 
				
			||||||
 | 
							tailscaleBuildOptions,
 | 
				
			||||||
 | 
							tailscaleOptions,
 | 
				
			||||||
 | 
							DockerRestartPolicy,
 | 
				
			||||||
 | 
							DockerAllowLocalIPv6,
 | 
				
			||||||
 | 
							DockerAllowNetworkAdministration,
 | 
				
			||||||
 | 
						)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							log.Fatalf("Could not start tailscale container version %s: %s", version, err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						log.Printf("Created %s container\n", hostname)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return hostname, pts
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (s *IntegrationOIDCTestSuite) TearDownSuite() {
 | 
				
			||||||
 | 
						if !s.saveLogs {
 | 
				
			||||||
 | 
							for _, tailscale := range s.tailscales {
 | 
				
			||||||
 | 
								if err := s.pool.Purge(&tailscale); err != nil {
 | 
				
			||||||
 | 
									log.Printf("Could not purge resource: %s\n", err)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if err := s.pool.Purge(&s.headscale); err != nil {
 | 
				
			||||||
 | 
								log.Printf("Could not purge resource: %s\n", err)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if err := s.pool.Purge(&s.mockOidc); err != nil {
 | 
				
			||||||
 | 
								log.Printf("Could not purge resource: %s\n", err)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if err := s.network.Close(); err != nil {
 | 
				
			||||||
 | 
								log.Printf("Could not close network: %s\n", err)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (s *IntegrationOIDCTestSuite) HandleStats(
 | 
				
			||||||
 | 
						suiteName string,
 | 
				
			||||||
 | 
						stats *suite.SuiteInformation,
 | 
				
			||||||
 | 
					) {
 | 
				
			||||||
 | 
						s.stats = stats
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (s *IntegrationOIDCTestSuite) saveLog(
 | 
				
			||||||
 | 
						resource *dockertest.Resource,
 | 
				
			||||||
 | 
						basePath string,
 | 
				
			||||||
 | 
					) error {
 | 
				
			||||||
 | 
						err := os.MkdirAll(basePath, os.ModePerm)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						var stdout bytes.Buffer
 | 
				
			||||||
 | 
						var stderr bytes.Buffer
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						err = s.pool.Client.Logs(
 | 
				
			||||||
 | 
							docker.LogsOptions{
 | 
				
			||||||
 | 
								Context:      context.TODO(),
 | 
				
			||||||
 | 
								Container:    resource.Container.ID,
 | 
				
			||||||
 | 
								OutputStream: &stdout,
 | 
				
			||||||
 | 
								ErrorStream:  &stderr,
 | 
				
			||||||
 | 
								Tail:         "all",
 | 
				
			||||||
 | 
								RawTerminal:  false,
 | 
				
			||||||
 | 
								Stdout:       true,
 | 
				
			||||||
 | 
								Stderr:       true,
 | 
				
			||||||
 | 
								Follow:       false,
 | 
				
			||||||
 | 
								Timestamps:   false,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						log.Printf("Saving logs for %s to %s\n", resource.Container.Name, basePath)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						err = os.WriteFile(
 | 
				
			||||||
 | 
							path.Join(basePath, resource.Container.Name+".stdout.log"),
 | 
				
			||||||
 | 
							[]byte(stdout.String()),
 | 
				
			||||||
 | 
							0o644,
 | 
				
			||||||
 | 
						)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						err = os.WriteFile(
 | 
				
			||||||
 | 
							path.Join(basePath, resource.Container.Name+".stderr.log"),
 | 
				
			||||||
 | 
							[]byte(stdout.String()),
 | 
				
			||||||
 | 
							0o644,
 | 
				
			||||||
 | 
						)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (s *IntegrationOIDCTestSuite) TestPingAllPeersByAddress() {
 | 
				
			||||||
 | 
						for hostname, tailscale := range s.tailscales {
 | 
				
			||||||
 | 
							ips, err := getIPs(s.tailscales)
 | 
				
			||||||
 | 
							assert.Nil(s.T(), err)
 | 
				
			||||||
 | 
							for peername, peerIPs := range ips {
 | 
				
			||||||
 | 
								for i, ip := range peerIPs {
 | 
				
			||||||
 | 
									// We currently cant ping ourselves, so skip that.
 | 
				
			||||||
 | 
									if peername == hostname {
 | 
				
			||||||
 | 
										continue
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									s.T().
 | 
				
			||||||
 | 
										Run(fmt.Sprintf("%s-%s-%d", hostname, peername, i), func(t *testing.T) {
 | 
				
			||||||
 | 
											// We are only interested in "direct ping" which means what we
 | 
				
			||||||
 | 
											// might need a couple of more attempts before reaching the node.
 | 
				
			||||||
 | 
											command := []string{
 | 
				
			||||||
 | 
												"tailscale", "ping",
 | 
				
			||||||
 | 
												"--timeout=1s",
 | 
				
			||||||
 | 
												"--c=10",
 | 
				
			||||||
 | 
												"--until-direct=true",
 | 
				
			||||||
 | 
												ip.String(),
 | 
				
			||||||
 | 
											}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
											log.Printf(
 | 
				
			||||||
 | 
												"Pinging from %s to %s (%s)\n",
 | 
				
			||||||
 | 
												hostname,
 | 
				
			||||||
 | 
												peername,
 | 
				
			||||||
 | 
												ip,
 | 
				
			||||||
 | 
											)
 | 
				
			||||||
 | 
											stdout, stderr, err := ExecuteCommand(
 | 
				
			||||||
 | 
												&tailscale,
 | 
				
			||||||
 | 
												command,
 | 
				
			||||||
 | 
												[]string{},
 | 
				
			||||||
 | 
											)
 | 
				
			||||||
 | 
											assert.Nil(t, err)
 | 
				
			||||||
 | 
											log.Printf("result for %s: stdout: %s, stderr: %s\n", hostname, stdout, stderr)
 | 
				
			||||||
 | 
											assert.Contains(t, stdout, "pong")
 | 
				
			||||||
 | 
										})
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -28,7 +28,9 @@ ip_prefixes:
 | 
				
			|||||||
  - fd7a:115c:a1e0::/48
 | 
					  - fd7a:115c:a1e0::/48
 | 
				
			||||||
  - 100.64.0.0/10
 | 
					  - 100.64.0.0/10
 | 
				
			||||||
listen_addr: 0.0.0.0:18080
 | 
					listen_addr: 0.0.0.0:18080
 | 
				
			||||||
log_level: disabled
 | 
					log:
 | 
				
			||||||
 | 
					  level: disabled
 | 
				
			||||||
 | 
					  format: text
 | 
				
			||||||
logtail:
 | 
					logtail:
 | 
				
			||||||
  enabled: false
 | 
					  enabled: false
 | 
				
			||||||
metrics_listen_addr: 127.0.0.1:19090
 | 
					metrics_listen_addr: 127.0.0.1:19090
 | 
				
			||||||
 | 
				
			|||||||
@ -1,4 +1,5 @@
 | 
				
			|||||||
log_level: trace
 | 
					log:
 | 
				
			||||||
 | 
					  level: trace
 | 
				
			||||||
acl_policy_path: ""
 | 
					acl_policy_path: ""
 | 
				
			||||||
db_type: sqlite3
 | 
					db_type: sqlite3
 | 
				
			||||||
ephemeral_node_inactivity_timeout: 30m
 | 
					ephemeral_node_inactivity_timeout: 30m
 | 
				
			||||||
 | 
				
			|||||||
@ -27,7 +27,9 @@ ip_prefixes:
 | 
				
			|||||||
  - fd7a:115c:a1e0::/48
 | 
					  - fd7a:115c:a1e0::/48
 | 
				
			||||||
  - 100.64.0.0/10
 | 
					  - 100.64.0.0/10
 | 
				
			||||||
listen_addr: 0.0.0.0:18080
 | 
					listen_addr: 0.0.0.0:18080
 | 
				
			||||||
log_level: disabled
 | 
					log:
 | 
				
			||||||
 | 
					  level: disabled
 | 
				
			||||||
 | 
					  format: text
 | 
				
			||||||
logtail:
 | 
					logtail:
 | 
				
			||||||
  enabled: false
 | 
					  enabled: false
 | 
				
			||||||
metrics_listen_addr: 127.0.0.1:19090
 | 
					metrics_listen_addr: 127.0.0.1:19090
 | 
				
			||||||
 | 
				
			|||||||
@ -1,4 +1,5 @@
 | 
				
			|||||||
log_level: trace
 | 
					log:
 | 
				
			||||||
 | 
					  level: trace
 | 
				
			||||||
acl_policy_path: ""
 | 
					acl_policy_path: ""
 | 
				
			||||||
db_type: sqlite3
 | 
					db_type: sqlite3
 | 
				
			||||||
ephemeral_node_inactivity_timeout: 30m
 | 
					ephemeral_node_inactivity_timeout: 30m
 | 
				
			||||||
 | 
				
			|||||||
@ -28,7 +28,9 @@ ip_prefixes:
 | 
				
			|||||||
  - fd7a:115c:a1e0::/48
 | 
					  - fd7a:115c:a1e0::/48
 | 
				
			||||||
  - 100.64.0.0/10
 | 
					  - 100.64.0.0/10
 | 
				
			||||||
listen_addr: 0.0.0.0:8080
 | 
					listen_addr: 0.0.0.0:8080
 | 
				
			||||||
log_level: disabled
 | 
					log:
 | 
				
			||||||
 | 
					  format: text
 | 
				
			||||||
 | 
					  level: disabled
 | 
				
			||||||
logtail:
 | 
					logtail:
 | 
				
			||||||
  enabled: false
 | 
					  enabled: false
 | 
				
			||||||
metrics_listen_addr: 127.0.0.1:9090
 | 
					metrics_listen_addr: 127.0.0.1:9090
 | 
				
			||||||
 | 
				
			|||||||
@ -1,4 +1,5 @@
 | 
				
			|||||||
log_level: trace
 | 
					log:
 | 
				
			||||||
 | 
					  level: trace
 | 
				
			||||||
acl_policy_path: ""
 | 
					acl_policy_path: ""
 | 
				
			||||||
db_type: sqlite3
 | 
					db_type: sqlite3
 | 
				
			||||||
ephemeral_node_inactivity_timeout: 30m
 | 
					ephemeral_node_inactivity_timeout: 30m
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										22
									
								
								integration_test/etc_oidc/base_config.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								integration_test/etc_oidc/base_config.yaml
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,22 @@
 | 
				
			|||||||
 | 
					log_level: trace
 | 
				
			||||||
 | 
					acl_policy_path: ""
 | 
				
			||||||
 | 
					db_type: sqlite3
 | 
				
			||||||
 | 
					ephemeral_node_inactivity_timeout: 30m
 | 
				
			||||||
 | 
					node_update_check_interval: 10s
 | 
				
			||||||
 | 
					ip_prefixes:
 | 
				
			||||||
 | 
					  - fd7a:115c:a1e0::/48
 | 
				
			||||||
 | 
					  - 100.64.0.0/10
 | 
				
			||||||
 | 
					db_path: /tmp/integration_test_db.sqlite3
 | 
				
			||||||
 | 
					private_key_path: private.key
 | 
				
			||||||
 | 
					noise:
 | 
				
			||||||
 | 
					  private_key_path: noise_private.key
 | 
				
			||||||
 | 
					listen_addr: 0.0.0.0:8443
 | 
				
			||||||
 | 
					server_url: https://localhost:8443
 | 
				
			||||||
 | 
					tls_cert_path: "/etc/headscale/tls/server.crt"
 | 
				
			||||||
 | 
					tls_key_path: "/etc/headscale/tls/server.key"
 | 
				
			||||||
 | 
					tls_client_auth_mode: disabled
 | 
				
			||||||
 | 
					derp:
 | 
				
			||||||
 | 
					  urls:
 | 
				
			||||||
 | 
					    - https://controlplane.tailscale.com/derpmap/default
 | 
				
			||||||
 | 
					  auto_update_enabled: true
 | 
				
			||||||
 | 
					  update_frequency: 1m
 | 
				
			||||||
							
								
								
									
										22
									
								
								integration_test/etc_oidc/tls/server.crt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								integration_test/etc_oidc/tls/server.crt
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,22 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					-----BEGIN CERTIFICATE-----
 | 
				
			||||||
 | 
					MIIC8jCCAdqgAwIBAgIULbu+UbSTMG/LtxooLLh7BgSEyqEwDQYJKoZIhvcNAQEL
 | 
				
			||||||
 | 
					BQAwFDESMBAGA1UEAwwJaGVhZHNjYWxlMCAXDTIyMDMwNTE2NDgwM1oYDzI1MjEx
 | 
				
			||||||
 | 
					MTA0MTY0ODAzWjAUMRIwEAYDVQQDDAloZWFkc2NhbGUwggEiMA0GCSqGSIb3DQEB
 | 
				
			||||||
 | 
					AQUAA4IBDwAwggEKAoIBAQDqcfpToLZUF0rlNwXkkt3lbyw4Cl4TJdx36o2PKaOK
 | 
				
			||||||
 | 
					U+tze/IjRsCWeMwrcR1o9TNZcxsD+c2J48D1WATuQJlMeg+2UJXGaTGRKkkbPMy3
 | 
				
			||||||
 | 
					5m7AFf/Q16UEOgm2NYjZaQ8faRGIMYURG/6sXmNeETJvBixpBev9yKJuVXgqHNS4
 | 
				
			||||||
 | 
					NpEkNwdOCuAZXrmw0HCbiusawJOay4tFvhH14rav8Uimonl8UTNVXufMzyUOuoaQ
 | 
				
			||||||
 | 
					TGflmzYX3hIoswRnTPlIWFoqObvx2Q8H+of3uQJXy0m8I6OrIoXLNxnqYMfFls79
 | 
				
			||||||
 | 
					9SYgVc2jPsCbh5fwyRbx2Hof7sIZ1K/mNgxJRG1E3ZiLAgMBAAGjOjA4MBQGA1Ud
 | 
				
			||||||
 | 
					EQQNMAuCCWhlYWRzY2FsZTALBgNVHQ8EBAMCB4AwEwYDVR0lBAwwCgYIKwYBBQUH
 | 
				
			||||||
 | 
					AwEwDQYJKoZIhvcNAQELBQADggEBANGlVN7NCsJaKz0k0nhlRGK+tcxn2p1PXN/i
 | 
				
			||||||
 | 
					Iy+JX8ahixPC4ocRwOhrXgb390ZXLLwq08HrWYRB/Wi1VUzCp5d8dVxvrR43dJ+v
 | 
				
			||||||
 | 
					L2EOBiIKgcu2C3pWW1qRR46/EoXUU9kSH2VNBvIhNufi32kEOidoDzxtQf6qVCoF
 | 
				
			||||||
 | 
					guUt1JkAqrynv1UvR/2ZRM/WzM/oJ8qfECwrwDxyYhkqU5Z5jCWg0C6kPIBvNdzt
 | 
				
			||||||
 | 
					B0eheWS+ZxVwkePTR4e17kIafwknth3lo+orxVrq/xC+OVM1bGrt2ZyD64ZvEqQl
 | 
				
			||||||
 | 
					w6kgbzBdLScAQptWOFThwhnJsg0UbYKimZsnYmjVEuN59TJv92M=
 | 
				
			||||||
 | 
					-----END CERTIFICATE-----
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					(Expires on Nov  4 16:48:03 2521 GMT)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
							
								
								
									
										28
									
								
								integration_test/etc_oidc/tls/server.key
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								integration_test/etc_oidc/tls/server.key
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,28 @@
 | 
				
			|||||||
 | 
					-----BEGIN PRIVATE KEY-----
 | 
				
			||||||
 | 
					MIIEwAIBADANBgkqhkiG9w0BAQEFAASCBKowggSmAgEAAoIBAQDqcfpToLZUF0rl
 | 
				
			||||||
 | 
					NwXkkt3lbyw4Cl4TJdx36o2PKaOKU+tze/IjRsCWeMwrcR1o9TNZcxsD+c2J48D1
 | 
				
			||||||
 | 
					WATuQJlMeg+2UJXGaTGRKkkbPMy35m7AFf/Q16UEOgm2NYjZaQ8faRGIMYURG/6s
 | 
				
			||||||
 | 
					XmNeETJvBixpBev9yKJuVXgqHNS4NpEkNwdOCuAZXrmw0HCbiusawJOay4tFvhH1
 | 
				
			||||||
 | 
					4rav8Uimonl8UTNVXufMzyUOuoaQTGflmzYX3hIoswRnTPlIWFoqObvx2Q8H+of3
 | 
				
			||||||
 | 
					uQJXy0m8I6OrIoXLNxnqYMfFls799SYgVc2jPsCbh5fwyRbx2Hof7sIZ1K/mNgxJ
 | 
				
			||||||
 | 
					RG1E3ZiLAgMBAAECggEBALu1Ni/u5Qy++YA8ZcN0s6UXNdhItLmv/q0kZuLQ+9et
 | 
				
			||||||
 | 
					CT8VZfFInLndTdsaXenDKLHdryunviFA8SV+q7P2lMbek+Xs735EiyMnMBFWxLIZ
 | 
				
			||||||
 | 
					FWNGOeQERGL19QCmLEOmEi2b+iWJQHlKaMWpbPXL3w11a+lKjIBNO4ALfoJ5QveZ
 | 
				
			||||||
 | 
					cGMKsJdm/mpqBvLeNeh2eAFk3Gp6sT1g80Ge8NkgyzFBNIqnut0eerM15kPTc6Qz
 | 
				
			||||||
 | 
					12JLaOXUuV3PrcB4PN4nOwrTDg88GDNOQtc1Pc9r4nOHyLfr8X7QEtj1wXSwmOuK
 | 
				
			||||||
 | 
					d6ynMnAmoxVA9wEnupLbil1bzohRzpsTpkmDruYaBEECgYEA/Z09I8D6mt2NVqIE
 | 
				
			||||||
 | 
					KyvLjBK39ijSV9r3/lvB2Ple2OOL5YQEd+yTrIFy+3zdUnDgD1zmNnXjmjvHZ9Lc
 | 
				
			||||||
 | 
					IFf2o06AF84QLNB5gLPdDQkGNFdDqUxljBrfAfE3oANmPS/B0SijMGOOOiDO2FtO
 | 
				
			||||||
 | 
					xl1nfRr78mswuRs9awoUWCdNRKUCgYEA7KaTYKIQW/FEjw9lshp74q5vbn6zoXF5
 | 
				
			||||||
 | 
					7N8VkwI+bBVNvRbM9XZ8qhfgRdu9eXs5oL/N4mSYY54I8fA//pJ0Z2vpmureMm1V
 | 
				
			||||||
 | 
					mL5WBUmSD9DIbAchoK+sRiQhVmNMBQC6cHMABA7RfXvBeGvWrm9pKCS6ZLgLjkjp
 | 
				
			||||||
 | 
					PsmAcaXQcW8CgYEA2inAxljjOwUK6FNGsrxhxIT1qtNC3kCGxE+6WSNq67gSR8Vg
 | 
				
			||||||
 | 
					8qiX//T7LEslOB3RIGYRwxd2St7RkgZZRZllmOWWWuPwFhzf6E7RAL2akLvggGov
 | 
				
			||||||
 | 
					kG4tGEagSw2hjVDfsUT73ExHtMk0Jfmlsg33UC8+PDLpHtLH6qQpDAwC8+ECgYEA
 | 
				
			||||||
 | 
					o+AqOIWhvHmT11l7O915Ip1WzvZwYADbxLsrDnVEUsZh4epTHjvh0kvcY6PqTqCV
 | 
				
			||||||
 | 
					ZIrOANNWb811Nkz/k8NJVoD08PFp0xPBbZeIq/qpachTsfMyRzq/mobUiyUR9Hjv
 | 
				
			||||||
 | 
					ooUQYr78NOApNsG+lWbTNBhS9wI4BlzZIECbcJe5g4MCgYEAndRoy8S+S0Hx/S8a
 | 
				
			||||||
 | 
					O3hzXeDmivmgWqn8NVD4AKOovpkz4PaIVVQbAQkiNfAx8/DavPvjEKAbDezJ4ECV
 | 
				
			||||||
 | 
					j7IsOWtDVI7pd6eF9fTcECwisrda8aUoiOap8AQb48153Vx+g2N4Vy3uH0xJs4cz
 | 
				
			||||||
 | 
					TDALZPOBg8VlV+HEFDP43sp9Bf0=
 | 
				
			||||||
 | 
					-----END PRIVATE KEY-----
 | 
				
			||||||
@ -573,12 +573,11 @@ func (machines MachinesP) String() string {
 | 
				
			|||||||
func (machines Machines) toNodes(
 | 
					func (machines Machines) toNodes(
 | 
				
			||||||
	baseDomain string,
 | 
						baseDomain string,
 | 
				
			||||||
	dnsConfig *tailcfg.DNSConfig,
 | 
						dnsConfig *tailcfg.DNSConfig,
 | 
				
			||||||
	includeRoutes bool,
 | 
					 | 
				
			||||||
) ([]*tailcfg.Node, error) {
 | 
					) ([]*tailcfg.Node, error) {
 | 
				
			||||||
	nodes := make([]*tailcfg.Node, len(machines))
 | 
						nodes := make([]*tailcfg.Node, len(machines))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	for index, machine := range machines {
 | 
						for index, machine := range machines {
 | 
				
			||||||
		node, err := machine.toNode(baseDomain, dnsConfig, includeRoutes)
 | 
							node, err := machine.toNode(baseDomain, dnsConfig)
 | 
				
			||||||
		if err != nil {
 | 
							if err != nil {
 | 
				
			||||||
			return nil, err
 | 
								return nil, err
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
@ -594,7 +593,6 @@ func (machines Machines) toNodes(
 | 
				
			|||||||
func (machine Machine) toNode(
 | 
					func (machine Machine) toNode(
 | 
				
			||||||
	baseDomain string,
 | 
						baseDomain string,
 | 
				
			||||||
	dnsConfig *tailcfg.DNSConfig,
 | 
						dnsConfig *tailcfg.DNSConfig,
 | 
				
			||||||
	includeRoutes bool,
 | 
					 | 
				
			||||||
) (*tailcfg.Node, error) {
 | 
					) (*tailcfg.Node, error) {
 | 
				
			||||||
	var nodeKey key.NodePublic
 | 
						var nodeKey key.NodePublic
 | 
				
			||||||
	err := nodeKey.UnmarshalText([]byte(NodePublicKeyEnsurePrefix(machine.NodeKey)))
 | 
						err := nodeKey.UnmarshalText([]byte(NodePublicKeyEnsurePrefix(machine.NodeKey)))
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user