mirror of
				https://github.com/juanfont/headscale.git
				synced 2025-10-28 10:51:44 +01:00 
			
		
		
		
	Merge branch 'main' into update-contributors
This commit is contained in:
		
						commit
						d803fe6123
					
				@ -14,4 +14,3 @@ docker-compose*
 | 
				
			|||||||
README.md
 | 
					README.md
 | 
				
			||||||
LICENSE
 | 
					LICENSE
 | 
				
			||||||
.vscode
 | 
					.vscode
 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										19
									
								
								.github/workflows/lint.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										19
									
								
								.github/workflows/lint.yml
									
									
									
									
										vendored
									
									
								
							@ -18,22 +18,3 @@ jobs:
 | 
				
			|||||||
      # below, but it's still much faster in the end than installing
 | 
					      # below, but it's still much faster in the end than installing
 | 
				
			||||||
      # golangci-lint manually in the `Run lint` step.
 | 
					      # golangci-lint manually in the `Run lint` step.
 | 
				
			||||||
      - uses: golangci/golangci-lint-action@v2
 | 
					      - uses: golangci/golangci-lint-action@v2
 | 
				
			||||||
        with:
 | 
					 | 
				
			||||||
          args: --timeout 5m
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      # Setup Go
 | 
					 | 
				
			||||||
      - name: Setup Go
 | 
					 | 
				
			||||||
        uses: actions/setup-go@v2
 | 
					 | 
				
			||||||
        with:
 | 
					 | 
				
			||||||
          go-version: "1.16.3" # The Go version to download (if necessary) and use.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      # Install all the dependencies
 | 
					 | 
				
			||||||
      - name: Install dependencies
 | 
					 | 
				
			||||||
        run: |
 | 
					 | 
				
			||||||
          go version
 | 
					 | 
				
			||||||
          go install golang.org/x/lint/golint@latest
 | 
					 | 
				
			||||||
          sudo apt update
 | 
					 | 
				
			||||||
          sudo apt install -y make
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      - name: Run lint
 | 
					 | 
				
			||||||
        run: make lint
 | 
					 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							@ -16,6 +16,7 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
/headscale
 | 
					/headscale
 | 
				
			||||||
config.json
 | 
					config.json
 | 
				
			||||||
 | 
					config.yaml
 | 
				
			||||||
*.key
 | 
					*.key
 | 
				
			||||||
/db.sqlite
 | 
					/db.sqlite
 | 
				
			||||||
*.sqlite3
 | 
					*.sqlite3
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										7
									
								
								.golangci.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								.golangci.yaml
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,7 @@
 | 
				
			|||||||
 | 
					---
 | 
				
			||||||
 | 
					run:
 | 
				
			||||||
 | 
					  timeout: 5m
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					issues:
 | 
				
			||||||
 | 
					  skip-dirs:
 | 
				
			||||||
 | 
					    - gen
 | 
				
			||||||
							
								
								
									
										11
									
								
								Dockerfile
									
									
									
									
									
								
							
							
						
						
									
										11
									
								
								Dockerfile
									
									
									
									
									
								
							@ -1,17 +1,24 @@
 | 
				
			|||||||
 | 
					FROM bufbuild/buf:1.0.0-rc6 as buf
 | 
				
			||||||
 | 
					
 | 
				
			||||||
FROM golang:1.17.1-bullseye AS build
 | 
					FROM golang:1.17.1-bullseye AS build
 | 
				
			||||||
ENV GOPATH /go
 | 
					ENV GOPATH /go
 | 
				
			||||||
 | 
					WORKDIR /go/src/headscale
 | 
				
			||||||
 | 
					
 | 
				
			||||||
COPY go.mod go.sum /go/src/headscale/
 | 
					COPY go.mod go.sum /go/src/headscale/
 | 
				
			||||||
WORKDIR /go/src/headscale
 | 
					 | 
				
			||||||
RUN go mod download
 | 
					RUN go mod download
 | 
				
			||||||
 | 
					
 | 
				
			||||||
COPY . /go/src/headscale
 | 
					COPY . .
 | 
				
			||||||
 | 
					
 | 
				
			||||||
RUN go install -a -ldflags="-extldflags=-static" -tags netgo,sqlite_omit_load_extension ./cmd/headscale
 | 
					RUN go install -a -ldflags="-extldflags=-static" -tags netgo,sqlite_omit_load_extension ./cmd/headscale
 | 
				
			||||||
RUN test -e /go/bin/headscale
 | 
					RUN test -e /go/bin/headscale
 | 
				
			||||||
 | 
					
 | 
				
			||||||
FROM ubuntu:20.04
 | 
					FROM ubuntu:20.04
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					RUN apt-get update \
 | 
				
			||||||
 | 
					    && apt-get install -y ca-certificates \
 | 
				
			||||||
 | 
					    && update-ca-certificates \
 | 
				
			||||||
 | 
					    && rm -rf /var/lib/apt/lists/*
 | 
				
			||||||
 | 
					
 | 
				
			||||||
COPY --from=build /go/bin/headscale /usr/local/bin/headscale
 | 
					COPY --from=build /go/bin/headscale /usr/local/bin/headscale
 | 
				
			||||||
ENV TZ UTC
 | 
					ENV TZ UTC
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										13
									
								
								Makefile
									
									
									
									
									
								
							
							
						
						
									
										13
									
								
								Makefile
									
									
									
									
									
								
							@ -19,9 +19,18 @@ coverprofile_html:
 | 
				
			|||||||
	go tool cover -html=coverage.out
 | 
						go tool cover -html=coverage.out
 | 
				
			||||||
 | 
					
 | 
				
			||||||
lint:
 | 
					lint:
 | 
				
			||||||
	golint
 | 
						golangci-lint run --fix
 | 
				
			||||||
	golangci-lint run --timeout 5m
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
compress: build
 | 
					compress: build
 | 
				
			||||||
	upx --brute headscale
 | 
						upx --brute headscale
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					generate:
 | 
				
			||||||
 | 
						rm -rf gen
 | 
				
			||||||
 | 
						buf generate proto
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					install-protobuf-plugins:
 | 
				
			||||||
 | 
						go install \
 | 
				
			||||||
 | 
							github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-grpc-gateway \
 | 
				
			||||||
 | 
							github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-openapiv2 \
 | 
				
			||||||
 | 
							google.golang.org/protobuf/cmd/protoc-gen-go \
 | 
				
			||||||
 | 
							google.golang.org/grpc/cmd/protoc-gen-go-grpc
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										38
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										38
									
								
								README.md
									
									
									
									
									
								
							@ -6,6 +6,8 @@ An open source, self-hosted implementation of the Tailscale coordination server.
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
Join our [Discord](https://discord.gg/XcQxk2VHjx) server for a chat.
 | 
					Join our [Discord](https://discord.gg/XcQxk2VHjx) server for a chat.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**Note:** Always select the same GitHub tag as the released version you use to ensure you have the correct example configuration and documentation. The `main` branch might contain unreleased changes.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## Overview
 | 
					## Overview
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Tailscale is [a modern VPN](https://tailscale.com/) built on top of [Wireguard](https://www.wireguard.com/). It [works like an overlay network](https://tailscale.com/blog/how-tailscale-works/) between the computers of your networks - using all kinds of [NAT traversal sorcery](https://tailscale.com/blog/how-nat-traversal-works/).
 | 
					Tailscale is [a modern VPN](https://tailscale.com/) built on top of [Wireguard](https://www.wireguard.com/). It [works like an overlay network](https://tailscale.com/blog/how-tailscale-works/) between the computers of your networks - using all kinds of [NAT traversal sorcery](https://tailscale.com/blog/how-nat-traversal-works/).
 | 
				
			||||||
@ -29,6 +31,7 @@ headscale implements this coordination server.
 | 
				
			|||||||
- [x] Taildrop (File Sharing)
 | 
					- [x] Taildrop (File Sharing)
 | 
				
			||||||
- [x] Support for alternative IP ranges in the tailnets (default Tailscale's 100.64.0.0/10)
 | 
					- [x] Support for alternative IP ranges in the tailnets (default Tailscale's 100.64.0.0/10)
 | 
				
			||||||
- [x] DNS (passing DNS servers to nodes)
 | 
					- [x] DNS (passing DNS servers to nodes)
 | 
				
			||||||
 | 
					- [x] Single-Sign-On (via Open ID Connect)
 | 
				
			||||||
- [x] Share nodes between namespaces
 | 
					- [x] Share nodes between namespaces
 | 
				
			||||||
- [x] MagicDNS (see `docs/`)
 | 
					- [x] MagicDNS (see `docs/`)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -47,7 +50,6 @@ headscale implements this coordination server.
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
Suggestions/PRs welcomed!
 | 
					Suggestions/PRs welcomed!
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 | 
				
			||||||
## Running headscale
 | 
					## Running headscale
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Please have a look at the documentation under [`docs/`](docs/).
 | 
					Please have a look at the documentation under [`docs/`](docs/).
 | 
				
			||||||
@ -58,6 +60,40 @@ Please have a look at the documentation under [`docs/`](docs/).
 | 
				
			|||||||
1. We have nothing to do with Tailscale, or Tailscale Inc.
 | 
					1. We have nothing to do with Tailscale, or Tailscale Inc.
 | 
				
			||||||
2. The purpose of writing this was to learn how Tailscale works.
 | 
					2. The purpose of writing this was to learn how Tailscale works.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Contributing
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					To contribute to Headscale you would need the lastest version of [Go](golang.org) and [Buf](https://buf.build)(Protobuf generator).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Install development tools
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Go
 | 
				
			||||||
 | 
					- Buf
 | 
				
			||||||
 | 
					- Protobuf tools:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```shell 
 | 
				
			||||||
 | 
					make install-protobuf-plugins
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Testing and building
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Some parts of the project requires the generation of Go code from Protobuf (if changes is made in `proto/`) and it must be (re-)generated with:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```shell
 | 
				
			||||||
 | 
					make generate
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					**Note**: Please check in changes from `gen/` in a separate commit to make it easier to review.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					To run the tests:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```shell
 | 
				
			||||||
 | 
					make test
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					To build the program:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```shell
 | 
				
			||||||
 | 
					make build
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## Contributors
 | 
					## Contributors
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										137
									
								
								api.go
									
									
									
									
									
								
							
							
						
						
									
										137
									
								
								api.go
									
									
									
									
									
								
							@ -7,6 +7,7 @@ import (
 | 
				
			|||||||
	"fmt"
 | 
						"fmt"
 | 
				
			||||||
	"io"
 | 
						"io"
 | 
				
			||||||
	"net/http"
 | 
						"net/http"
 | 
				
			||||||
 | 
						"strings"
 | 
				
			||||||
	"time"
 | 
						"time"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"github.com/rs/zerolog/log"
 | 
						"github.com/rs/zerolog/log"
 | 
				
			||||||
@ -43,7 +44,7 @@ func (h *Headscale) RegisterWebAPI(c *gin.Context) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	<p>
 | 
						<p>
 | 
				
			||||||
		<code>
 | 
							<code>
 | 
				
			||||||
			<b>headscale -n NAMESPACE nodes register %s</b>
 | 
								<b>headscale -n NAMESPACE nodes register -k %s</b>
 | 
				
			||||||
		</code>
 | 
							</code>
 | 
				
			||||||
	</p>
 | 
						</p>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -64,7 +65,7 @@ func (h *Headscale) RegistrationHandler(c *gin.Context) {
 | 
				
			|||||||
			Str("handler", "Registration").
 | 
								Str("handler", "Registration").
 | 
				
			||||||
			Err(err).
 | 
								Err(err).
 | 
				
			||||||
			Msg("Cannot parse machine key")
 | 
								Msg("Cannot parse machine key")
 | 
				
			||||||
		machineRegistrations.WithLabelValues("unkown", "web", "error", "unknown").Inc()
 | 
							machineRegistrations.WithLabelValues("unknown", "web", "error", "unknown").Inc()
 | 
				
			||||||
		c.String(http.StatusInternalServerError, "Sad!")
 | 
							c.String(http.StatusInternalServerError, "Sad!")
 | 
				
			||||||
		return
 | 
							return
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@ -75,34 +76,33 @@ func (h *Headscale) RegistrationHandler(c *gin.Context) {
 | 
				
			|||||||
			Str("handler", "Registration").
 | 
								Str("handler", "Registration").
 | 
				
			||||||
			Err(err).
 | 
								Err(err).
 | 
				
			||||||
			Msg("Cannot decode message")
 | 
								Msg("Cannot decode message")
 | 
				
			||||||
		machineRegistrations.WithLabelValues("unkown", "web", "error", "unknown").Inc()
 | 
							machineRegistrations.WithLabelValues("unknown", "web", "error", "unknown").Inc()
 | 
				
			||||||
		c.String(http.StatusInternalServerError, "Very sad!")
 | 
							c.String(http.StatusInternalServerError, "Very sad!")
 | 
				
			||||||
		return
 | 
							return
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	now := time.Now().UTC()
 | 
						now := time.Now().UTC()
 | 
				
			||||||
	var m Machine
 | 
						m, err := h.GetMachineByMachineKey(mKey.HexString())
 | 
				
			||||||
	if result := h.db.Preload("Namespace").First(&m, "machine_key = ?", mKey.HexString()); errors.Is(result.Error, gorm.ErrRecordNotFound) {
 | 
						if errors.Is(err, gorm.ErrRecordNotFound) {
 | 
				
			||||||
		log.Info().Str("machine", req.Hostinfo.Hostname).Msg("New machine")
 | 
							log.Info().Str("machine", req.Hostinfo.Hostname).Msg("New machine")
 | 
				
			||||||
		m = Machine{
 | 
							newMachine := Machine{
 | 
				
			||||||
			Expiry:               &req.Expiry,
 | 
								Expiry:     &time.Time{},
 | 
				
			||||||
			MachineKey:           mKey.HexString(),
 | 
								MachineKey: mKey.HexString(),
 | 
				
			||||||
			Name:                 req.Hostinfo.Hostname,
 | 
								Name:       req.Hostinfo.Hostname,
 | 
				
			||||||
			NodeKey:              wgkey.Key(req.NodeKey).HexString(),
 | 
					 | 
				
			||||||
			LastSuccessfulUpdate: &now,
 | 
					 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		if err := h.db.Create(&m).Error; err != nil {
 | 
							if err := h.db.Create(&newMachine).Error; err != nil {
 | 
				
			||||||
			log.Error().
 | 
								log.Error().
 | 
				
			||||||
				Str("handler", "Registration").
 | 
									Str("handler", "Registration").
 | 
				
			||||||
				Err(err).
 | 
									Err(err).
 | 
				
			||||||
				Msg("Could not create row")
 | 
									Msg("Could not create row")
 | 
				
			||||||
			machineRegistrations.WithLabelValues("unkown", "web", "error", m.Namespace.Name).Inc()
 | 
								machineRegistrations.WithLabelValues("unknown", "web", "error", m.Namespace.Name).Inc()
 | 
				
			||||||
			return
 | 
								return
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
							m = &newMachine
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if !m.Registered && req.Auth.AuthKey != "" {
 | 
						if !m.Registered && req.Auth.AuthKey != "" {
 | 
				
			||||||
		h.handleAuthKey(c, h.db, mKey, req, m)
 | 
							h.handleAuthKey(c, h.db, mKey, req, *m)
 | 
				
			||||||
		return
 | 
							return
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -110,7 +110,36 @@ func (h *Headscale) RegistrationHandler(c *gin.Context) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	// We have the updated key!
 | 
						// We have the updated key!
 | 
				
			||||||
	if m.NodeKey == wgkey.Key(req.NodeKey).HexString() {
 | 
						if m.NodeKey == wgkey.Key(req.NodeKey).HexString() {
 | 
				
			||||||
		if m.Registered {
 | 
					
 | 
				
			||||||
 | 
							// The client sends an Expiry in the past if the client is requesting to expire the key (aka logout)
 | 
				
			||||||
 | 
							//   https://github.com/tailscale/tailscale/blob/main/tailcfg/tailcfg.go#L648
 | 
				
			||||||
 | 
							if !req.Expiry.IsZero() && req.Expiry.UTC().Before(now) {
 | 
				
			||||||
 | 
								log.Info().
 | 
				
			||||||
 | 
									Str("handler", "Registration").
 | 
				
			||||||
 | 
									Str("machine", m.Name).
 | 
				
			||||||
 | 
									Msg("Client requested logout")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								m.Expiry = &req.Expiry // save the expiry so that the machine is marked as expired
 | 
				
			||||||
 | 
								h.db.Save(&m)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								resp.AuthURL = ""
 | 
				
			||||||
 | 
								resp.MachineAuthorized = false
 | 
				
			||||||
 | 
								resp.User = *m.Namespace.toUser()
 | 
				
			||||||
 | 
								respBody, err := encode(resp, &mKey, h.privateKey)
 | 
				
			||||||
 | 
								if err != nil {
 | 
				
			||||||
 | 
									log.Error().
 | 
				
			||||||
 | 
										Str("handler", "Registration").
 | 
				
			||||||
 | 
										Err(err).
 | 
				
			||||||
 | 
										Msg("Cannot encode message")
 | 
				
			||||||
 | 
									c.String(http.StatusInternalServerError, "")
 | 
				
			||||||
 | 
									return
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								c.Data(200, "application/json; charset=utf-8", respBody)
 | 
				
			||||||
 | 
								return
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if m.Registered && m.Expiry.UTC().After(now) {
 | 
				
			||||||
 | 
								// The machine registration is valid, respond with redirect to /map
 | 
				
			||||||
			log.Debug().
 | 
								log.Debug().
 | 
				
			||||||
				Str("handler", "Registration").
 | 
									Str("handler", "Registration").
 | 
				
			||||||
				Str("machine", m.Name).
 | 
									Str("machine", m.Name).
 | 
				
			||||||
@ -119,6 +148,8 @@ func (h *Headscale) RegistrationHandler(c *gin.Context) {
 | 
				
			|||||||
			resp.AuthURL = ""
 | 
								resp.AuthURL = ""
 | 
				
			||||||
			resp.MachineAuthorized = true
 | 
								resp.MachineAuthorized = true
 | 
				
			||||||
			resp.User = *m.Namespace.toUser()
 | 
								resp.User = *m.Namespace.toUser()
 | 
				
			||||||
 | 
								resp.Login = *m.Namespace.toLogin()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			respBody, err := encode(resp, &mKey, h.privateKey)
 | 
								respBody, err := encode(resp, &mKey, h.privateKey)
 | 
				
			||||||
			if err != nil {
 | 
								if err != nil {
 | 
				
			||||||
				log.Error().
 | 
									log.Error().
 | 
				
			||||||
@ -134,12 +165,30 @@ func (h *Headscale) RegistrationHandler(c *gin.Context) {
 | 
				
			|||||||
			return
 | 
								return
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// The client has registered before, but has expired
 | 
				
			||||||
		log.Debug().
 | 
							log.Debug().
 | 
				
			||||||
			Str("handler", "Registration").
 | 
								Str("handler", "Registration").
 | 
				
			||||||
			Str("machine", m.Name).
 | 
								Str("machine", m.Name).
 | 
				
			||||||
			Msg("Not registered and not NodeKey rotation. Sending a authurl to register")
 | 
								Msg("Machine registration has expired. Sending a authurl to register")
 | 
				
			||||||
		resp.AuthURL = fmt.Sprintf("%s/register?key=%s",
 | 
					
 | 
				
			||||||
			h.cfg.ServerURL, mKey.HexString())
 | 
							if h.cfg.OIDC.Issuer != "" {
 | 
				
			||||||
 | 
								resp.AuthURL = fmt.Sprintf("%s/oidc/register/%s",
 | 
				
			||||||
 | 
									strings.TrimSuffix(h.cfg.ServerURL, "/"), mKey.HexString())
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								resp.AuthURL = fmt.Sprintf("%s/register?key=%s",
 | 
				
			||||||
 | 
									strings.TrimSuffix(h.cfg.ServerURL, "/"), mKey.HexString())
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// When a client connects, it may request a specific expiry time in its
 | 
				
			||||||
 | 
							// RegisterRequest (https://github.com/tailscale/tailscale/blob/main/tailcfg/tailcfg.go#L634)
 | 
				
			||||||
 | 
							// RequestedExpiry is used to store the clients requested expiry time since the authentication flow is broken
 | 
				
			||||||
 | 
							// into two steps (which cant pass arbitrary data between them easily) and needs to be
 | 
				
			||||||
 | 
							// retrieved again after the user has authenticated. After the authentication flow
 | 
				
			||||||
 | 
							// completes, RequestedExpiry is copied into Expiry.
 | 
				
			||||||
 | 
							m.RequestedExpiry = &req.Expiry
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							h.db.Save(&m)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		respBody, err := encode(resp, &mKey, h.privateKey)
 | 
							respBody, err := encode(resp, &mKey, h.privateKey)
 | 
				
			||||||
		if err != nil {
 | 
							if err != nil {
 | 
				
			||||||
			log.Error().
 | 
								log.Error().
 | 
				
			||||||
@ -155,8 +204,8 @@ func (h *Headscale) RegistrationHandler(c *gin.Context) {
 | 
				
			|||||||
		return
 | 
							return
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// The NodeKey we have matches OldNodeKey, which means this is a refresh after an key expiration
 | 
						// The NodeKey we have matches OldNodeKey, which means this is a refresh after a key expiration
 | 
				
			||||||
	if m.NodeKey == wgkey.Key(req.OldNodeKey).HexString() {
 | 
						if m.NodeKey == wgkey.Key(req.OldNodeKey).HexString() && m.Expiry.UTC().After(now) {
 | 
				
			||||||
		log.Debug().
 | 
							log.Debug().
 | 
				
			||||||
			Str("handler", "Registration").
 | 
								Str("handler", "Registration").
 | 
				
			||||||
			Str("machine", m.Name).
 | 
								Str("machine", m.Name).
 | 
				
			||||||
@ -179,35 +228,23 @@ func (h *Headscale) RegistrationHandler(c *gin.Context) {
 | 
				
			|||||||
		return
 | 
							return
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// We arrive here after a client is restarted without finalizing the authentication flow or
 | 
						// The machine registration is new, redirect the client to the registration URL
 | 
				
			||||||
	// when headscale is stopped in the middle of the auth process.
 | 
					 | 
				
			||||||
	if m.Registered {
 | 
					 | 
				
			||||||
		log.Debug().
 | 
					 | 
				
			||||||
			Str("handler", "Registration").
 | 
					 | 
				
			||||||
			Str("machine", m.Name).
 | 
					 | 
				
			||||||
			Msg("The node is sending us a new NodeKey, but machine is registered. All clear for /map")
 | 
					 | 
				
			||||||
		resp.AuthURL = ""
 | 
					 | 
				
			||||||
		resp.MachineAuthorized = true
 | 
					 | 
				
			||||||
		resp.User = *m.Namespace.toUser()
 | 
					 | 
				
			||||||
		respBody, err := encode(resp, &mKey, h.privateKey)
 | 
					 | 
				
			||||||
		if err != nil {
 | 
					 | 
				
			||||||
			log.Error().
 | 
					 | 
				
			||||||
				Str("handler", "Registration").
 | 
					 | 
				
			||||||
				Err(err).
 | 
					 | 
				
			||||||
				Msg("Cannot encode message")
 | 
					 | 
				
			||||||
			c.String(http.StatusInternalServerError, "")
 | 
					 | 
				
			||||||
			return
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		c.Data(200, "application/json; charset=utf-8", respBody)
 | 
					 | 
				
			||||||
		return
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	log.Debug().
 | 
						log.Debug().
 | 
				
			||||||
		Str("handler", "Registration").
 | 
							Str("handler", "Registration").
 | 
				
			||||||
		Str("machine", m.Name).
 | 
							Str("machine", m.Name).
 | 
				
			||||||
		Msg("The node is sending us a new NodeKey, sending auth url")
 | 
							Msg("The node is sending us a new NodeKey, sending auth url")
 | 
				
			||||||
	resp.AuthURL = fmt.Sprintf("%s/register?key=%s",
 | 
						if h.cfg.OIDC.Issuer != "" {
 | 
				
			||||||
		h.cfg.ServerURL, mKey.HexString())
 | 
							resp.AuthURL = fmt.Sprintf("%s/oidc/register/%s", strings.TrimSuffix(h.cfg.ServerURL, "/"), mKey.HexString())
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							resp.AuthURL = fmt.Sprintf("%s/register?key=%s",
 | 
				
			||||||
 | 
								strings.TrimSuffix(h.cfg.ServerURL, "/"), mKey.HexString())
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// save the requested expiry time for retrieval later in the authentication flow
 | 
				
			||||||
 | 
						m.RequestedExpiry = &req.Expiry
 | 
				
			||||||
 | 
						m.NodeKey = wgkey.Key(req.NodeKey).HexString() // save the NodeKey
 | 
				
			||||||
 | 
						h.db.Save(&m)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	respBody, err := encode(resp, &mKey, h.privateKey)
 | 
						respBody, err := encode(resp, &mKey, h.privateKey)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		log.Error().
 | 
							log.Error().
 | 
				
			||||||
@ -270,7 +307,7 @@ func (h *Headscale) getMapResponse(mKey wgkey.Key, req tailcfg.MapRequest, m *Ma
 | 
				
			|||||||
		DNSConfig:    dnsConfig,
 | 
							DNSConfig:    dnsConfig,
 | 
				
			||||||
		Domain:       h.cfg.BaseDomain,
 | 
							Domain:       h.cfg.BaseDomain,
 | 
				
			||||||
		PacketFilter: *h.aclRules,
 | 
							PacketFilter: *h.aclRules,
 | 
				
			||||||
		DERPMap:      h.cfg.DerpMap,
 | 
							DERPMap:      h.DERPMap,
 | 
				
			||||||
		UserProfiles: profiles,
 | 
							UserProfiles: profiles,
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -329,7 +366,13 @@ func (h *Headscale) getMapKeepAliveResponse(mKey wgkey.Key, req tailcfg.MapReque
 | 
				
			|||||||
	return data, nil
 | 
						return data, nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (h *Headscale) handleAuthKey(c *gin.Context, db *gorm.DB, idKey wgkey.Key, req tailcfg.RegisterRequest, m Machine) {
 | 
					func (h *Headscale) handleAuthKey(
 | 
				
			||||||
 | 
						c *gin.Context,
 | 
				
			||||||
 | 
						db *gorm.DB,
 | 
				
			||||||
 | 
						idKey wgkey.Key,
 | 
				
			||||||
 | 
						req tailcfg.RegisterRequest,
 | 
				
			||||||
 | 
						m Machine,
 | 
				
			||||||
 | 
					) {
 | 
				
			||||||
	log.Debug().
 | 
						log.Debug().
 | 
				
			||||||
		Str("func", "handleAuthKey").
 | 
							Str("func", "handleAuthKey").
 | 
				
			||||||
		Str("machine", req.Hostinfo.Hostname).
 | 
							Str("machine", req.Hostinfo.Hostname).
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										186
									
								
								app.go
									
									
									
									
									
								
							
							
						
						
									
										186
									
								
								app.go
									
									
									
									
									
								
							@ -1,21 +1,33 @@
 | 
				
			|||||||
package headscale
 | 
					package headscale
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
 | 
						"context"
 | 
				
			||||||
 | 
						"crypto/tls"
 | 
				
			||||||
	"errors"
 | 
						"errors"
 | 
				
			||||||
	"fmt"
 | 
						"fmt"
 | 
				
			||||||
 | 
						"net"
 | 
				
			||||||
	"net/http"
 | 
						"net/http"
 | 
				
			||||||
 | 
						"net/url"
 | 
				
			||||||
	"os"
 | 
						"os"
 | 
				
			||||||
	"sort"
 | 
						"sort"
 | 
				
			||||||
	"strings"
 | 
						"strings"
 | 
				
			||||||
	"sync"
 | 
						"sync"
 | 
				
			||||||
	"time"
 | 
						"time"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"github.com/rs/zerolog/log"
 | 
						"github.com/coreos/go-oidc/v3/oidc"
 | 
				
			||||||
 | 
						"github.com/patrickmn/go-cache"
 | 
				
			||||||
 | 
						"golang.org/x/oauth2"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"github.com/gin-gonic/gin"
 | 
						"github.com/gin-gonic/gin"
 | 
				
			||||||
 | 
						"github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
 | 
				
			||||||
 | 
						apiV1 "github.com/juanfont/headscale/gen/go/v1"
 | 
				
			||||||
 | 
						"github.com/rs/zerolog/log"
 | 
				
			||||||
 | 
						"github.com/soheilhy/cmux"
 | 
				
			||||||
	ginprometheus "github.com/zsais/go-gin-prometheus"
 | 
						ginprometheus "github.com/zsais/go-gin-prometheus"
 | 
				
			||||||
	"golang.org/x/crypto/acme"
 | 
						"golang.org/x/crypto/acme"
 | 
				
			||||||
	"golang.org/x/crypto/acme/autocert"
 | 
						"golang.org/x/crypto/acme/autocert"
 | 
				
			||||||
 | 
						"golang.org/x/sync/errgroup"
 | 
				
			||||||
 | 
						"google.golang.org/grpc"
 | 
				
			||||||
	"gorm.io/gorm"
 | 
						"gorm.io/gorm"
 | 
				
			||||||
	"inet.af/netaddr"
 | 
						"inet.af/netaddr"
 | 
				
			||||||
	"tailscale.com/tailcfg"
 | 
						"tailscale.com/tailcfg"
 | 
				
			||||||
@ -23,16 +35,17 @@ import (
 | 
				
			|||||||
	"tailscale.com/types/wgkey"
 | 
						"tailscale.com/types/wgkey"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Config contains the initial Headscale configuration
 | 
					// Config contains the initial Headscale configuration.
 | 
				
			||||||
type Config struct {
 | 
					type Config struct {
 | 
				
			||||||
	ServerURL                      string
 | 
						ServerURL                      string
 | 
				
			||||||
	Addr                           string
 | 
						Addr                           string
 | 
				
			||||||
	PrivateKeyPath                 string
 | 
						PrivateKeyPath                 string
 | 
				
			||||||
	DerpMap                        *tailcfg.DERPMap
 | 
					 | 
				
			||||||
	EphemeralNodeInactivityTimeout time.Duration
 | 
						EphemeralNodeInactivityTimeout time.Duration
 | 
				
			||||||
	IPPrefix                       netaddr.IPPrefix
 | 
						IPPrefix                       netaddr.IPPrefix
 | 
				
			||||||
	BaseDomain                     string
 | 
						BaseDomain                     string
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						DERP DERPConfig
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	DBtype string
 | 
						DBtype string
 | 
				
			||||||
	DBpath string
 | 
						DBpath string
 | 
				
			||||||
	DBhost string
 | 
						DBhost string
 | 
				
			||||||
@ -53,9 +66,28 @@ type Config struct {
 | 
				
			|||||||
	ACMEEmail string
 | 
						ACMEEmail string
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	DNSConfig *tailcfg.DNSConfig
 | 
						DNSConfig *tailcfg.DNSConfig
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						OIDC OIDCConfig
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						MaxMachineRegistrationDuration     time.Duration
 | 
				
			||||||
 | 
						DefaultMachineRegistrationDuration time.Duration
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Headscale represents the base app of the service
 | 
					type OIDCConfig struct {
 | 
				
			||||||
 | 
						Issuer       string
 | 
				
			||||||
 | 
						ClientID     string
 | 
				
			||||||
 | 
						ClientSecret string
 | 
				
			||||||
 | 
						MatchMap     map[string]string
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type DERPConfig struct {
 | 
				
			||||||
 | 
						URLs            []url.URL
 | 
				
			||||||
 | 
						Paths           []string
 | 
				
			||||||
 | 
						AutoUpdate      bool
 | 
				
			||||||
 | 
						UpdateFrequency time.Duration
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Headscale represents the base app of the service.
 | 
				
			||||||
type Headscale struct {
 | 
					type Headscale struct {
 | 
				
			||||||
	cfg        Config
 | 
						cfg        Config
 | 
				
			||||||
	db         *gorm.DB
 | 
						db         *gorm.DB
 | 
				
			||||||
@ -65,18 +97,25 @@ type Headscale struct {
 | 
				
			|||||||
	publicKey  *wgkey.Key
 | 
						publicKey  *wgkey.Key
 | 
				
			||||||
	privateKey *wgkey.Private
 | 
						privateKey *wgkey.Private
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						DERPMap *tailcfg.DERPMap
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	aclPolicy *ACLPolicy
 | 
						aclPolicy *ACLPolicy
 | 
				
			||||||
	aclRules  *[]tailcfg.FilterRule
 | 
						aclRules  *[]tailcfg.FilterRule
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	lastStateChange sync.Map
 | 
						lastStateChange sync.Map
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						oidcProvider   *oidc.Provider
 | 
				
			||||||
 | 
						oauth2Config   *oauth2.Config
 | 
				
			||||||
 | 
						oidcStateCache *cache.Cache
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// NewHeadscale returns the Headscale app
 | 
					// NewHeadscale returns the Headscale app.
 | 
				
			||||||
func NewHeadscale(cfg Config) (*Headscale, error) {
 | 
					func NewHeadscale(cfg Config) (*Headscale, error) {
 | 
				
			||||||
	content, err := os.ReadFile(cfg.PrivateKeyPath)
 | 
						content, err := os.ReadFile(cfg.PrivateKeyPath)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return nil, err
 | 
							return nil, err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	privKey, err := wgkey.ParsePrivate(string(content))
 | 
						privKey, err := wgkey.ParsePrivate(string(content))
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return nil, err
 | 
							return nil, err
 | 
				
			||||||
@ -108,6 +147,13 @@ func NewHeadscale(cfg Config) (*Headscale, error) {
 | 
				
			|||||||
		return nil, err
 | 
							return nil, err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if cfg.OIDC.Issuer != "" {
 | 
				
			||||||
 | 
							err = h.initOIDC()
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return nil, err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if h.cfg.DNSConfig != nil && h.cfg.DNSConfig.Proxied { // if MagicDNS
 | 
						if h.cfg.DNSConfig != nil && h.cfg.DNSConfig.Proxied { // if MagicDNS
 | 
				
			||||||
		magicDNSDomains, err := generateMagicDNSRootDomains(h.cfg.IPPrefix, h.cfg.BaseDomain)
 | 
							magicDNSDomains, err := generateMagicDNSRootDomains(h.cfg.IPPrefix, h.cfg.BaseDomain)
 | 
				
			||||||
		if err != nil {
 | 
							if err != nil {
 | 
				
			||||||
@ -125,14 +171,14 @@ func NewHeadscale(cfg Config) (*Headscale, error) {
 | 
				
			|||||||
	return &h, nil
 | 
						return &h, nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Redirect to our TLS url
 | 
					// Redirect to our TLS url.
 | 
				
			||||||
func (h *Headscale) redirect(w http.ResponseWriter, req *http.Request) {
 | 
					func (h *Headscale) redirect(w http.ResponseWriter, req *http.Request) {
 | 
				
			||||||
	target := h.cfg.ServerURL + req.URL.RequestURI()
 | 
						target := h.cfg.ServerURL + req.URL.RequestURI()
 | 
				
			||||||
	http.Redirect(w, req, target, http.StatusFound)
 | 
						http.Redirect(w, req, target, http.StatusFound)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// expireEphemeralNodes deletes ephemeral machine records that have not been
 | 
					// expireEphemeralNodes deletes ephemeral machine records that have not been
 | 
				
			||||||
// seen for longer than h.cfg.EphemeralNodeInactivityTimeout
 | 
					// seen for longer than h.cfg.EphemeralNodeInactivityTimeout.
 | 
				
			||||||
func (h *Headscale) expireEphemeralNodes(milliSeconds int64) {
 | 
					func (h *Headscale) expireEphemeralNodes(milliSeconds int64) {
 | 
				
			||||||
	ticker := time.NewTicker(time.Duration(milliSeconds) * time.Millisecond)
 | 
						ticker := time.NewTicker(time.Duration(milliSeconds) * time.Millisecond)
 | 
				
			||||||
	for range ticker.C {
 | 
						for range ticker.C {
 | 
				
			||||||
@ -144,29 +190,39 @@ func (h *Headscale) expireEphemeralNodesWorker() {
 | 
				
			|||||||
	namespaces, err := h.ListNamespaces()
 | 
						namespaces, err := h.ListNamespaces()
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		log.Error().Err(err).Msg("Error listing namespaces")
 | 
							log.Error().Err(err).Msg("Error listing namespaces")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		return
 | 
							return
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	for _, ns := range *namespaces {
 | 
						for _, ns := range *namespaces {
 | 
				
			||||||
		machines, err := h.ListMachinesInNamespace(ns.Name)
 | 
							machines, err := h.ListMachinesInNamespace(ns.Name)
 | 
				
			||||||
		if err != nil {
 | 
							if err != nil {
 | 
				
			||||||
			log.Error().Err(err).Str("namespace", ns.Name).Msg("Error listing machines in namespace")
 | 
								log.Error().Err(err).Str("namespace", ns.Name).Msg("Error listing machines in namespace")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			return
 | 
								return
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		for _, m := range *machines {
 | 
							for _, m := range *machines {
 | 
				
			||||||
			if m.AuthKey != nil && m.LastSeen != nil && m.AuthKey.Ephemeral && time.Now().After(m.LastSeen.Add(h.cfg.EphemeralNodeInactivityTimeout)) {
 | 
								if m.AuthKey != nil && m.LastSeen != nil && m.AuthKey.Ephemeral &&
 | 
				
			||||||
 | 
									time.Now().After(m.LastSeen.Add(h.cfg.EphemeralNodeInactivityTimeout)) {
 | 
				
			||||||
				log.Info().Str("machine", m.Name).Msg("Ephemeral client removed from database")
 | 
									log.Info().Str("machine", m.Name).Msg("Ephemeral client removed from database")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
				err = h.db.Unscoped().Delete(m).Error
 | 
									err = h.db.Unscoped().Delete(m).Error
 | 
				
			||||||
				if err != nil {
 | 
									if err != nil {
 | 
				
			||||||
					log.Error().Err(err).Str("machine", m.Name).Msg("🤮 Cannot delete ephemeral machine from the database")
 | 
										log.Error().
 | 
				
			||||||
 | 
											Err(err).
 | 
				
			||||||
 | 
											Str("machine", m.Name).
 | 
				
			||||||
 | 
											Msg("🤮 Cannot delete ephemeral machine from the database")
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		h.setLastStateChangeToNow(ns.Name)
 | 
							h.setLastStateChangeToNow(ns.Name)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// WatchForKVUpdates checks the KV DB table for requests to perform tailnet upgrades
 | 
					// WatchForKVUpdates checks the KV DB table for requests to perform tailnet upgrades
 | 
				
			||||||
// This is a way to communitate the CLI with the headscale server
 | 
					// This is a way to communitate the CLI with the headscale server.
 | 
				
			||||||
func (h *Headscale) watchForKVUpdates(milliSeconds int64) {
 | 
					func (h *Headscale) watchForKVUpdates(milliSeconds int64) {
 | 
				
			||||||
	ticker := time.NewTicker(time.Duration(milliSeconds) * time.Millisecond)
 | 
						ticker := time.NewTicker(time.Duration(milliSeconds) * time.Millisecond)
 | 
				
			||||||
	for range ticker.C {
 | 
						for range ticker.C {
 | 
				
			||||||
@ -179,26 +235,77 @@ func (h *Headscale) watchForKVUpdatesWorker() {
 | 
				
			|||||||
	// more functions will come here in the future
 | 
						// more functions will come here in the future
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Serve launches a GIN server with the Headscale API
 | 
					// Serve launches a GIN server with the Headscale API.
 | 
				
			||||||
func (h *Headscale) Serve() error {
 | 
					func (h *Headscale) Serve() error {
 | 
				
			||||||
 | 
						var err error
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						ctx := context.Background()
 | 
				
			||||||
 | 
						ctx, cancel := context.WithCancel(ctx)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						defer cancel()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						l, err := net.Listen("tcp", h.cfg.Addr)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							panic(err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Create the cmux object that will multiplex 2 protocols on the same port.
 | 
				
			||||||
 | 
						// The two following listeners will be served on the same port below gracefully.
 | 
				
			||||||
 | 
						m := cmux.New(l)
 | 
				
			||||||
 | 
						// Match gRPC requests here
 | 
				
			||||||
 | 
						grpcListener := m.MatchWithWriters(cmux.HTTP2MatchHeaderFieldSendSettings("content-type", "application/grpc"))
 | 
				
			||||||
 | 
						// Otherwise match regular http requests.
 | 
				
			||||||
 | 
						httpListener := m.Match(cmux.Any())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Now create the grpc server with those options.
 | 
				
			||||||
 | 
						grpcServer := grpc.NewServer()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// TODO(kradalby): register the new server when we have authentication ready
 | 
				
			||||||
 | 
						// apiV1.RegisterHeadscaleServiceServer(grpcServer, newHeadscaleV1APIServer(h))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						grpcGatewayMux := runtime.NewServeMux()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						opts := []grpc.DialOption{grpc.WithInsecure()}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						err = apiV1.RegisterHeadscaleServiceHandlerFromEndpoint(ctx, grpcGatewayMux, h.cfg.Addr, opts)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	r := gin.Default()
 | 
						r := gin.Default()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	p := ginprometheus.NewPrometheus("gin")
 | 
						p := ginprometheus.NewPrometheus("gin")
 | 
				
			||||||
	p.Use(r)
 | 
						p.Use(r)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	r.GET("/health", func(c *gin.Context) { c.JSON(200, gin.H{"healthy": "ok"}) })
 | 
						r.GET("/health", func(c *gin.Context) { c.JSON(http.StatusOK, gin.H{"healthy": "ok"}) })
 | 
				
			||||||
	r.GET("/key", h.KeyHandler)
 | 
						r.GET("/key", h.KeyHandler)
 | 
				
			||||||
	r.GET("/register", h.RegisterWebAPI)
 | 
						r.GET("/register", h.RegisterWebAPI)
 | 
				
			||||||
	r.POST("/machine/:id/map", h.PollNetMapHandler)
 | 
						r.POST("/machine/:id/map", h.PollNetMapHandler)
 | 
				
			||||||
	r.POST("/machine/:id", h.RegistrationHandler)
 | 
						r.POST("/machine/:id", h.RegistrationHandler)
 | 
				
			||||||
 | 
						r.GET("/oidc/register/:mkey", h.RegisterOIDC)
 | 
				
			||||||
 | 
						r.GET("/oidc/callback", h.OIDCCallback)
 | 
				
			||||||
	r.GET("/apple", h.AppleMobileConfig)
 | 
						r.GET("/apple", h.AppleMobileConfig)
 | 
				
			||||||
	r.GET("/apple/:platform", h.ApplePlatformConfig)
 | 
						r.GET("/apple/:platform", h.ApplePlatformConfig)
 | 
				
			||||||
	var err error
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	go h.watchForKVUpdates(5000)
 | 
						r.Any("/api/v1/*any", gin.WrapF(grpcGatewayMux.ServeHTTP))
 | 
				
			||||||
	go h.expireEphemeralNodes(5000)
 | 
						r.StaticFile("/swagger/swagger.json", "gen/openapiv2/v1/headscale.swagger.json")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	s := &http.Server{
 | 
						updateMillisecondsWait := int64(5000)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Fetch an initial DERP Map before we start serving
 | 
				
			||||||
 | 
						h.DERPMap = GetDERPMap(h.cfg.DERP)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if h.cfg.DERP.AutoUpdate {
 | 
				
			||||||
 | 
							derpMapCancelChannel := make(chan struct{})
 | 
				
			||||||
 | 
							defer func() { derpMapCancelChannel <- struct{}{} }()
 | 
				
			||||||
 | 
							go h.scheduledDERPMapUpdateWorker(derpMapCancelChannel)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// I HATE THIS
 | 
				
			||||||
 | 
						go h.watchForKVUpdates(updateMillisecondsWait)
 | 
				
			||||||
 | 
						go h.expireEphemeralNodes(updateMillisecondsWait)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						httpServer := &http.Server{
 | 
				
			||||||
		Addr:        h.cfg.Addr,
 | 
							Addr:        h.cfg.Addr,
 | 
				
			||||||
		Handler:     r,
 | 
							Handler:     r,
 | 
				
			||||||
		ReadTimeout: 30 * time.Second,
 | 
							ReadTimeout: 30 * time.Second,
 | 
				
			||||||
@ -209,6 +316,29 @@ func (h *Headscale) Serve() error {
 | 
				
			|||||||
		WriteTimeout: 0,
 | 
							WriteTimeout: 0,
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						tlsConfig, err := h.getTLSSettings()
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							log.Error().Err(err).Msg("Failed to set up TLS configuration")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if tlsConfig != nil {
 | 
				
			||||||
 | 
							httpServer.TLSConfig = tlsConfig
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						g := new(errgroup.Group)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						g.Go(func() error { return grpcServer.Serve(grpcListener) })
 | 
				
			||||||
 | 
						g.Go(func() error { return httpServer.Serve(httpListener) })
 | 
				
			||||||
 | 
						g.Go(func() error { return m.Serve() })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						log.Info().Msgf("listening and serving (multiplexed HTTP and gRPC) on: %s", h.cfg.Addr)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return g.Wait()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (h *Headscale) getTLSSettings() (*tls.Config, error) {
 | 
				
			||||||
	if h.cfg.TLSLetsEncryptHostname != "" {
 | 
						if h.cfg.TLSLetsEncryptHostname != "" {
 | 
				
			||||||
		if !strings.HasPrefix(h.cfg.ServerURL, "https://") {
 | 
							if !strings.HasPrefix(h.cfg.ServerURL, "https://") {
 | 
				
			||||||
			log.Warn().Msg("Listening with TLS but ServerURL does not start with https://")
 | 
								log.Warn().Msg("Listening with TLS but ServerURL does not start with https://")
 | 
				
			||||||
@ -224,13 +354,11 @@ func (h *Headscale) Serve() error {
 | 
				
			|||||||
			Email: h.cfg.ACMEEmail,
 | 
								Email: h.cfg.ACMEEmail,
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		s.TLSConfig = m.TLSConfig()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		if h.cfg.TLSLetsEncryptChallengeType == "TLS-ALPN-01" {
 | 
							if h.cfg.TLSLetsEncryptChallengeType == "TLS-ALPN-01" {
 | 
				
			||||||
			// Configuration via autocert with TLS-ALPN-01 (https://tools.ietf.org/html/rfc8737)
 | 
								// Configuration via autocert with TLS-ALPN-01 (https://tools.ietf.org/html/rfc8737)
 | 
				
			||||||
			// The RFC requires that the validation is done on port 443; in other words, headscale
 | 
								// The RFC requires that the validation is done on port 443; in other words, headscale
 | 
				
			||||||
			// must be reachable on port 443.
 | 
								// must be reachable on port 443.
 | 
				
			||||||
			err = s.ListenAndServeTLS("", "")
 | 
								return m.TLSConfig(), nil
 | 
				
			||||||
		} else if h.cfg.TLSLetsEncryptChallengeType == "HTTP-01" {
 | 
							} else if h.cfg.TLSLetsEncryptChallengeType == "HTTP-01" {
 | 
				
			||||||
			// Configuration via autocert with HTTP-01. This requires listening on
 | 
								// Configuration via autocert with HTTP-01. This requires listening on
 | 
				
			||||||
			// port 80 for the certificate validation in addition to the headscale
 | 
								// port 80 for the certificate validation in addition to the headscale
 | 
				
			||||||
@ -240,22 +368,30 @@ func (h *Headscale) Serve() error {
 | 
				
			|||||||
					Err(http.ListenAndServe(h.cfg.TLSLetsEncryptListen, m.HTTPHandler(http.HandlerFunc(h.redirect)))).
 | 
										Err(http.ListenAndServe(h.cfg.TLSLetsEncryptListen, m.HTTPHandler(http.HandlerFunc(h.redirect)))).
 | 
				
			||||||
					Msg("failed to set up a HTTP server")
 | 
										Msg("failed to set up a HTTP server")
 | 
				
			||||||
			}()
 | 
								}()
 | 
				
			||||||
			err = s.ListenAndServeTLS("", "")
 | 
					
 | 
				
			||||||
 | 
								return m.TLSConfig(), nil
 | 
				
			||||||
		} else {
 | 
							} else {
 | 
				
			||||||
			return errors.New("unknown value for TLSLetsEncryptChallengeType")
 | 
								return nil, errors.New("unknown value for TLSLetsEncryptChallengeType")
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	} else if h.cfg.TLSCertPath == "" {
 | 
						} else if h.cfg.TLSCertPath == "" {
 | 
				
			||||||
		if !strings.HasPrefix(h.cfg.ServerURL, "http://") {
 | 
							if !strings.HasPrefix(h.cfg.ServerURL, "http://") {
 | 
				
			||||||
			log.Warn().Msg("Listening without TLS but ServerURL does not start with http://")
 | 
								log.Warn().Msg("Listening without TLS but ServerURL does not start with http://")
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		err = s.ListenAndServe()
 | 
					
 | 
				
			||||||
 | 
							return nil, nil
 | 
				
			||||||
	} else {
 | 
						} else {
 | 
				
			||||||
		if !strings.HasPrefix(h.cfg.ServerURL, "https://") {
 | 
							if !strings.HasPrefix(h.cfg.ServerURL, "https://") {
 | 
				
			||||||
			log.Warn().Msg("Listening with TLS but ServerURL does not start with https://")
 | 
								log.Warn().Msg("Listening with TLS but ServerURL does not start with https://")
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		err = s.ListenAndServeTLS(h.cfg.TLSCertPath, h.cfg.TLSKeyPath)
 | 
							var err error
 | 
				
			||||||
 | 
							tlsConfig := &tls.Config{}
 | 
				
			||||||
 | 
							tlsConfig.ClientAuth = tls.RequireAnyClientCert
 | 
				
			||||||
 | 
							tlsConfig.NextProtos = []string{"http/1.1"}
 | 
				
			||||||
 | 
							tlsConfig.Certificates = make([]tls.Certificate, 1)
 | 
				
			||||||
 | 
							tlsConfig.Certificates[0], err = tls.LoadX509KeyPair(h.cfg.TLSCertPath, h.cfg.TLSKeyPath)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							return tlsConfig, err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	return err
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (h *Headscale) setLastStateChangeToNow(namespace string) {
 | 
					func (h *Headscale) setLastStateChangeToNow(namespace string) {
 | 
				
			||||||
@ -273,7 +409,6 @@ func (h *Headscale) getLastStateChange(namespaces ...string) time.Time {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
			times = append(times, lastChange)
 | 
								times = append(times, lastChange)
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	sort.Slice(times, func(i, j int) bool {
 | 
						sort.Slice(times, func(i, j int) bool {
 | 
				
			||||||
@ -284,7 +419,6 @@ func (h *Headscale) getLastStateChange(namespaces ...string) time.Time {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	if len(times) == 0 {
 | 
						if len(times) == 0 {
 | 
				
			||||||
		return time.Now().UTC()
 | 
							return time.Now().UTC()
 | 
				
			||||||
 | 
					 | 
				
			||||||
	} else {
 | 
						} else {
 | 
				
			||||||
		return times[0]
 | 
							return times[0]
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										21
									
								
								buf.gen.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								buf.gen.yaml
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,21 @@
 | 
				
			|||||||
 | 
					version: v1
 | 
				
			||||||
 | 
					plugins:
 | 
				
			||||||
 | 
					  - name: go
 | 
				
			||||||
 | 
					    out: gen/go
 | 
				
			||||||
 | 
					    opt:
 | 
				
			||||||
 | 
					      - paths=source_relative
 | 
				
			||||||
 | 
					  - name: go-grpc
 | 
				
			||||||
 | 
					    out: gen/go
 | 
				
			||||||
 | 
					    opt:
 | 
				
			||||||
 | 
					      - paths=source_relative
 | 
				
			||||||
 | 
					  - name: grpc-gateway
 | 
				
			||||||
 | 
					    out: gen/go
 | 
				
			||||||
 | 
					    opt:
 | 
				
			||||||
 | 
					      - paths=source_relative
 | 
				
			||||||
 | 
					      - generate_unbound_methods=true
 | 
				
			||||||
 | 
					  # - name: gorm
 | 
				
			||||||
 | 
					  #   out: gen/go
 | 
				
			||||||
 | 
					  #   opt:
 | 
				
			||||||
 | 
					  #     - paths=source_relative,enums=string,gateway=true
 | 
				
			||||||
 | 
					  - name: openapiv2
 | 
				
			||||||
 | 
					    out: gen/openapiv2
 | 
				
			||||||
							
								
								
									
										3
									
								
								cli.go
									
									
									
									
									
								
							
							
						
						
									
										3
									
								
								cli.go
									
									
									
									
									
								
							@ -23,6 +23,8 @@ func (h *Headscale) RegisterMachine(key string, namespace string) (*Machine, err
 | 
				
			|||||||
		return nil, errors.New("Machine not found")
 | 
							return nil, errors.New("Machine not found")
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						h.updateMachineExpiry(&m) // update the machine's expiry before bailing if its already registered
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if m.isAlreadyRegistered() {
 | 
						if m.isAlreadyRegistered() {
 | 
				
			||||||
		return nil, errors.New("Machine already registered")
 | 
							return nil, errors.New("Machine already registered")
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@ -36,5 +38,6 @@ func (h *Headscale) RegisterMachine(key string, namespace string) (*Machine, err
 | 
				
			|||||||
	m.Registered = true
 | 
						m.Registered = true
 | 
				
			||||||
	m.RegisterMethod = "cli"
 | 
						m.RegisterMethod = "cli"
 | 
				
			||||||
	h.db.Save(&m)
 | 
						h.db.Save(&m)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return &m, nil
 | 
						return &m, nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										20
									
								
								cli_test.go
									
									
									
									
									
								
							
							
						
						
									
										20
									
								
								cli_test.go
									
									
									
									
									
								
							@ -1,6 +1,8 @@
 | 
				
			|||||||
package headscale
 | 
					package headscale
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
 | 
						"time"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"gopkg.in/check.v1"
 | 
						"gopkg.in/check.v1"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -8,14 +10,18 @@ func (s *Suite) TestRegisterMachine(c *check.C) {
 | 
				
			|||||||
	n, err := h.CreateNamespace("test")
 | 
						n, err := h.CreateNamespace("test")
 | 
				
			||||||
	c.Assert(err, check.IsNil)
 | 
						c.Assert(err, check.IsNil)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						now := time.Now().UTC()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	m := Machine{
 | 
						m := Machine{
 | 
				
			||||||
		ID:          0,
 | 
							ID:              0,
 | 
				
			||||||
		MachineKey:  "8ce002a935f8c394e55e78fbbb410576575ff8ec5cfa2e627e4b807f1be15b0e",
 | 
							MachineKey:      "8ce002a935f8c394e55e78fbbb410576575ff8ec5cfa2e627e4b807f1be15b0e",
 | 
				
			||||||
		NodeKey:     "bar",
 | 
							NodeKey:         "bar",
 | 
				
			||||||
		DiscoKey:    "faa",
 | 
							DiscoKey:        "faa",
 | 
				
			||||||
		Name:        "testmachine",
 | 
							Name:            "testmachine",
 | 
				
			||||||
		NamespaceID: n.ID,
 | 
							NamespaceID:     n.ID,
 | 
				
			||||||
		IPAddress:   "10.0.0.1",
 | 
							IPAddress:       "10.0.0.1",
 | 
				
			||||||
 | 
							Expiry:          &now,
 | 
				
			||||||
 | 
							RequestedExpiry: &now,
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	h.db.Save(&m)
 | 
						h.db.Save(&m)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -17,15 +17,50 @@ import (
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
func init() {
 | 
					func init() {
 | 
				
			||||||
	rootCmd.AddCommand(nodeCmd)
 | 
						rootCmd.AddCommand(nodeCmd)
 | 
				
			||||||
	nodeCmd.PersistentFlags().StringP("namespace", "n", "", "Namespace")
 | 
						listNodesCmd.Flags().StringP("namespace", "n", "", "Filter by namespace")
 | 
				
			||||||
	err := nodeCmd.MarkPersistentFlagRequired("namespace")
 | 
						nodeCmd.AddCommand(listNodesCmd)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						registerNodeCmd.Flags().StringP("namespace", "n", "", "Namespace")
 | 
				
			||||||
 | 
						err := registerNodeCmd.MarkFlagRequired("namespace")
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							log.Fatalf(err.Error())
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						registerNodeCmd.Flags().StringP("key", "k", "", "Key")
 | 
				
			||||||
 | 
						err = registerNodeCmd.MarkFlagRequired("key")
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		log.Fatalf(err.Error())
 | 
							log.Fatalf(err.Error())
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	nodeCmd.AddCommand(listNodesCmd)
 | 
					 | 
				
			||||||
	nodeCmd.AddCommand(registerNodeCmd)
 | 
						nodeCmd.AddCommand(registerNodeCmd)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						deleteNodeCmd.Flags().IntP("identifier", "i", 0, "Node identifier (ID)")
 | 
				
			||||||
 | 
						err = deleteNodeCmd.MarkFlagRequired("identifier")
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							log.Fatalf(err.Error())
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
	nodeCmd.AddCommand(deleteNodeCmd)
 | 
						nodeCmd.AddCommand(deleteNodeCmd)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						shareMachineCmd.Flags().StringP("namespace", "n", "", "Namespace")
 | 
				
			||||||
 | 
						err = shareMachineCmd.MarkFlagRequired("namespace")
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							log.Fatalf(err.Error())
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						shareMachineCmd.Flags().IntP("identifier", "i", 0, "Node identifier (ID)")
 | 
				
			||||||
 | 
						err = shareMachineCmd.MarkFlagRequired("identifier")
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							log.Fatalf(err.Error())
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
	nodeCmd.AddCommand(shareMachineCmd)
 | 
						nodeCmd.AddCommand(shareMachineCmd)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						unshareMachineCmd.Flags().StringP("namespace", "n", "", "Namespace")
 | 
				
			||||||
 | 
						err = unshareMachineCmd.MarkFlagRequired("namespace")
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							log.Fatalf(err.Error())
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						unshareMachineCmd.Flags().IntP("identifier", "i", 0, "Node identifier (ID)")
 | 
				
			||||||
 | 
						err = unshareMachineCmd.MarkFlagRequired("identifier")
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							log.Fatalf(err.Error())
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
	nodeCmd.AddCommand(unshareMachineCmd)
 | 
						nodeCmd.AddCommand(unshareMachineCmd)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -35,14 +70,8 @@ var nodeCmd = &cobra.Command{
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
var registerNodeCmd = &cobra.Command{
 | 
					var registerNodeCmd = &cobra.Command{
 | 
				
			||||||
	Use:   "register machineID",
 | 
						Use:   "register",
 | 
				
			||||||
	Short: "Registers a machine to your network",
 | 
						Short: "Registers a machine to your network",
 | 
				
			||||||
	Args: func(cmd *cobra.Command, args []string) error {
 | 
					 | 
				
			||||||
		if len(args) < 1 {
 | 
					 | 
				
			||||||
			return fmt.Errorf("missing parameters")
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		return nil
 | 
					 | 
				
			||||||
	},
 | 
					 | 
				
			||||||
	Run: func(cmd *cobra.Command, args []string) {
 | 
						Run: func(cmd *cobra.Command, args []string) {
 | 
				
			||||||
		n, err := cmd.Flags().GetString("namespace")
 | 
							n, err := cmd.Flags().GetString("namespace")
 | 
				
			||||||
		if err != nil {
 | 
							if err != nil {
 | 
				
			||||||
@ -54,7 +83,11 @@ var registerNodeCmd = &cobra.Command{
 | 
				
			|||||||
		if err != nil {
 | 
							if err != nil {
 | 
				
			||||||
			log.Fatalf("Error initializing: %s", err)
 | 
								log.Fatalf("Error initializing: %s", err)
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		m, err := h.RegisterMachine(args[0], n)
 | 
							machineIDStr, err := cmd.Flags().GetString("key")
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								log.Fatalf("Error getting machine ID: %s", err)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							m, err := h.RegisterMachine(machineIDStr, n)
 | 
				
			||||||
		if strings.HasPrefix(o, "json") {
 | 
							if strings.HasPrefix(o, "json") {
 | 
				
			||||||
			JsonOutput(m, err, o)
 | 
								JsonOutput(m, err, o)
 | 
				
			||||||
			return
 | 
								return
 | 
				
			||||||
@ -69,7 +102,7 @@ var registerNodeCmd = &cobra.Command{
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
var listNodesCmd = &cobra.Command{
 | 
					var listNodesCmd = &cobra.Command{
 | 
				
			||||||
	Use:   "list",
 | 
						Use:   "list",
 | 
				
			||||||
	Short: "List the nodes in a given namespace",
 | 
						Short: "List nodes",
 | 
				
			||||||
	Run: func(cmd *cobra.Command, args []string) {
 | 
						Run: func(cmd *cobra.Command, args []string) {
 | 
				
			||||||
		n, err := cmd.Flags().GetString("namespace")
 | 
							n, err := cmd.Flags().GetString("namespace")
 | 
				
			||||||
		if err != nil {
 | 
							if err != nil {
 | 
				
			||||||
@ -82,23 +115,44 @@ var listNodesCmd = &cobra.Command{
 | 
				
			|||||||
			log.Fatalf("Error initializing: %s", err)
 | 
								log.Fatalf("Error initializing: %s", err)
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		namespace, err := h.GetNamespace(n)
 | 
							var namespaces []headscale.Namespace
 | 
				
			||||||
		if err != nil {
 | 
							var namespace *headscale.Namespace
 | 
				
			||||||
			log.Fatalf("Error fetching namespace: %s", err)
 | 
							var sharedMachines *[]headscale.Machine
 | 
				
			||||||
 | 
							if len(n) == 0 {
 | 
				
			||||||
 | 
								// no namespace provided, list all
 | 
				
			||||||
 | 
								tmp, err := h.ListNamespaces()
 | 
				
			||||||
 | 
								if err != nil {
 | 
				
			||||||
 | 
									log.Fatalf("Error fetching namespace: %s", err)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								namespaces = *tmp
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								namespace, err = h.GetNamespace(n)
 | 
				
			||||||
 | 
								if err != nil {
 | 
				
			||||||
 | 
									log.Fatalf("Error fetching namespace: %s", err)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								namespaces = append(namespaces, *namespace)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								sharedMachines, err = h.ListSharedMachinesInNamespace(n)
 | 
				
			||||||
 | 
								if err != nil {
 | 
				
			||||||
 | 
									log.Fatalf("Error fetching shared machines: %s", err)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		machines, err := h.ListMachinesInNamespace(n)
 | 
							var allMachines []headscale.Machine
 | 
				
			||||||
		if err != nil {
 | 
							for _, namespace := range namespaces {
 | 
				
			||||||
			log.Fatalf("Error fetching machines: %s", err)
 | 
								machines, err := h.ListMachinesInNamespace(namespace.Name)
 | 
				
			||||||
 | 
								if err != nil {
 | 
				
			||||||
 | 
									log.Fatalf("Error fetching machines: %s", err)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								allMachines = append(allMachines, *machines...)
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		sharedMachines, err := h.ListSharedMachinesInNamespace(n)
 | 
							// listing sharedMachines is only relevant when a particular namespace is
 | 
				
			||||||
		if err != nil {
 | 
							// requested
 | 
				
			||||||
			log.Fatalf("Error fetching shared machines: %s", err)
 | 
							if sharedMachines != nil {
 | 
				
			||||||
 | 
								allMachines = append(allMachines, *sharedMachines...)
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		allMachines := append(*machines, *sharedMachines...)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		if strings.HasPrefix(o, "json") {
 | 
							if strings.HasPrefix(o, "json") {
 | 
				
			||||||
			JsonOutput(allMachines, err, o)
 | 
								JsonOutput(allMachines, err, o)
 | 
				
			||||||
			return
 | 
								return
 | 
				
			||||||
@ -108,7 +162,7 @@ var listNodesCmd = &cobra.Command{
 | 
				
			|||||||
			log.Fatalf("Error getting nodes: %s", err)
 | 
								log.Fatalf("Error getting nodes: %s", err)
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		d, err := nodesToPtables(*namespace, allMachines)
 | 
							d, err := nodesToPtables(namespace, allMachines)
 | 
				
			||||||
		if err != nil {
 | 
							if err != nil {
 | 
				
			||||||
			log.Fatalf("Error converting to table: %s", err)
 | 
								log.Fatalf("Error converting to table: %s", err)
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
@ -121,21 +175,15 @@ var listNodesCmd = &cobra.Command{
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
var deleteNodeCmd = &cobra.Command{
 | 
					var deleteNodeCmd = &cobra.Command{
 | 
				
			||||||
	Use:   "delete ID",
 | 
						Use:   "delete",
 | 
				
			||||||
	Short: "Delete a node",
 | 
						Short: "Delete a node",
 | 
				
			||||||
	Args: func(cmd *cobra.Command, args []string) error {
 | 
					 | 
				
			||||||
		if len(args) < 1 {
 | 
					 | 
				
			||||||
			return fmt.Errorf("missing parameters")
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		return nil
 | 
					 | 
				
			||||||
	},
 | 
					 | 
				
			||||||
	Run: func(cmd *cobra.Command, args []string) {
 | 
						Run: func(cmd *cobra.Command, args []string) {
 | 
				
			||||||
		output, _ := cmd.Flags().GetString("output")
 | 
							output, _ := cmd.Flags().GetString("output")
 | 
				
			||||||
		h, err := getHeadscaleApp()
 | 
							h, err := getHeadscaleApp()
 | 
				
			||||||
		if err != nil {
 | 
							if err != nil {
 | 
				
			||||||
			log.Fatalf("Error initializing: %s", err)
 | 
								log.Fatalf("Error initializing: %s", err)
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		id, err := strconv.Atoi(args[0])
 | 
							id, err := cmd.Flags().GetInt("identifier")
 | 
				
			||||||
		if err != nil {
 | 
							if err != nil {
 | 
				
			||||||
			log.Fatalf("Error converting ID to integer: %s", err)
 | 
								log.Fatalf("Error converting ID to integer: %s", err)
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
@ -176,47 +224,42 @@ var deleteNodeCmd = &cobra.Command{
 | 
				
			|||||||
	},
 | 
						},
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func sharingWorker(cmd *cobra.Command, args []string) (*headscale.Headscale, string, *headscale.Machine, *headscale.Namespace) {
 | 
				
			||||||
 | 
						namespaceStr, err := cmd.Flags().GetString("namespace")
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							log.Fatalf("Error getting namespace: %s", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						output, _ := cmd.Flags().GetString("output")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						h, err := getHeadscaleApp()
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							log.Fatalf("Error initializing: %s", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						namespace, err := h.GetNamespace(namespaceStr)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							log.Fatalf("Error fetching namespace %s: %s", namespaceStr, err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						id, err := cmd.Flags().GetInt("identifier")
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							log.Fatalf("Error converting ID to integer: %s", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						machine, err := h.GetMachineByID(uint64(id))
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							log.Fatalf("Error getting node: %s", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return h, output, machine, namespace
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
var shareMachineCmd = &cobra.Command{
 | 
					var shareMachineCmd = &cobra.Command{
 | 
				
			||||||
	Use:   "share ID namespace",
 | 
						Use:   "share",
 | 
				
			||||||
	Short: "Shares a node from the current namespace to the specified one",
 | 
						Short: "Shares a node from the current namespace to the specified one",
 | 
				
			||||||
	Args: func(cmd *cobra.Command, args []string) error {
 | 
					 | 
				
			||||||
		if len(args) < 2 {
 | 
					 | 
				
			||||||
			return fmt.Errorf("missing parameters")
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		return nil
 | 
					 | 
				
			||||||
	},
 | 
					 | 
				
			||||||
	Run: func(cmd *cobra.Command, args []string) {
 | 
						Run: func(cmd *cobra.Command, args []string) {
 | 
				
			||||||
		namespace, err := cmd.Flags().GetString("namespace")
 | 
							h, output, machine, namespace := sharingWorker(cmd, args)
 | 
				
			||||||
		if err != nil {
 | 
							err := h.AddSharedMachineToNamespace(machine, namespace)
 | 
				
			||||||
			log.Fatalf("Error getting namespace: %s", err)
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		output, _ := cmd.Flags().GetString("output")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		h, err := getHeadscaleApp()
 | 
					 | 
				
			||||||
		if err != nil {
 | 
					 | 
				
			||||||
			log.Fatalf("Error initializing: %s", err)
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		_, err = h.GetNamespace(namespace)
 | 
					 | 
				
			||||||
		if err != nil {
 | 
					 | 
				
			||||||
			log.Fatalf("Error fetching origin namespace: %s", err)
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		destinationNamespace, err := h.GetNamespace(args[1])
 | 
					 | 
				
			||||||
		if err != nil {
 | 
					 | 
				
			||||||
			log.Fatalf("Error fetching destination namespace: %s", err)
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		id, err := strconv.Atoi(args[0])
 | 
					 | 
				
			||||||
		if err != nil {
 | 
					 | 
				
			||||||
			log.Fatalf("Error converting ID to integer: %s", err)
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		machine, err := h.GetMachineByID(uint64(id))
 | 
					 | 
				
			||||||
		if err != nil {
 | 
					 | 
				
			||||||
			log.Fatalf("Error getting node: %s", err)
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		err = h.AddSharedMachineToNamespace(machine, destinationNamespace)
 | 
					 | 
				
			||||||
		if strings.HasPrefix(output, "json") {
 | 
							if strings.HasPrefix(output, "json") {
 | 
				
			||||||
			JsonOutput(map[string]string{"Result": "Node shared"}, err, output)
 | 
								JsonOutput(map[string]string{"Result": "Node shared"}, err, output)
 | 
				
			||||||
			return
 | 
								return
 | 
				
			||||||
@ -231,41 +274,11 @@ var shareMachineCmd = &cobra.Command{
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
var unshareMachineCmd = &cobra.Command{
 | 
					var unshareMachineCmd = &cobra.Command{
 | 
				
			||||||
	Use:   "unshare ID",
 | 
						Use:   "unshare",
 | 
				
			||||||
	Short: "Unshares a node from the specified namespace",
 | 
						Short: "Unshares a node from the specified namespace",
 | 
				
			||||||
	Args: func(cmd *cobra.Command, args []string) error {
 | 
					 | 
				
			||||||
		if len(args) < 1 {
 | 
					 | 
				
			||||||
			return fmt.Errorf("missing parameters")
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		return nil
 | 
					 | 
				
			||||||
	},
 | 
					 | 
				
			||||||
	Run: func(cmd *cobra.Command, args []string) {
 | 
						Run: func(cmd *cobra.Command, args []string) {
 | 
				
			||||||
		namespace, err := cmd.Flags().GetString("namespace")
 | 
							h, output, machine, namespace := sharingWorker(cmd, args)
 | 
				
			||||||
		if err != nil {
 | 
							err := h.RemoveSharedMachineFromNamespace(machine, namespace)
 | 
				
			||||||
			log.Fatalf("Error getting namespace: %s", err)
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		output, _ := cmd.Flags().GetString("output")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		h, err := getHeadscaleApp()
 | 
					 | 
				
			||||||
		if err != nil {
 | 
					 | 
				
			||||||
			log.Fatalf("Error initializing: %s", err)
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		n, err := h.GetNamespace(namespace)
 | 
					 | 
				
			||||||
		if err != nil {
 | 
					 | 
				
			||||||
			log.Fatalf("Error fetching namespace: %s", err)
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		id, err := strconv.Atoi(args[0])
 | 
					 | 
				
			||||||
		if err != nil {
 | 
					 | 
				
			||||||
			log.Fatalf("Error converting ID to integer: %s", err)
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		machine, err := h.GetMachineByID(uint64(id))
 | 
					 | 
				
			||||||
		if err != nil {
 | 
					 | 
				
			||||||
			log.Fatalf("Error getting node: %s", err)
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		err = h.RemoveSharedMachineFromNamespace(machine, n)
 | 
					 | 
				
			||||||
		if strings.HasPrefix(output, "json") {
 | 
							if strings.HasPrefix(output, "json") {
 | 
				
			||||||
			JsonOutput(map[string]string{"Result": "Node unshared"}, err, output)
 | 
								JsonOutput(map[string]string{"Result": "Node unshared"}, err, output)
 | 
				
			||||||
			return
 | 
								return
 | 
				
			||||||
@ -279,7 +292,7 @@ var unshareMachineCmd = &cobra.Command{
 | 
				
			|||||||
	},
 | 
						},
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func nodesToPtables(currentNamespace headscale.Namespace, machines []headscale.Machine) (pterm.TableData, error) {
 | 
					func nodesToPtables(currentNamespace *headscale.Namespace, machines []headscale.Machine) (pterm.TableData, error) {
 | 
				
			||||||
	d := pterm.TableData{{"ID", "Name", "NodeKey", "Namespace", "IP address", "Ephemeral", "Last seen", "Online"}}
 | 
						d := pterm.TableData{{"ID", "Name", "NodeKey", "Namespace", "IP address", "Ephemeral", "Last seen", "Online"}}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	for _, machine := range machines {
 | 
						for _, machine := range machines {
 | 
				
			||||||
@ -307,9 +320,10 @@ func nodesToPtables(currentNamespace headscale.Namespace, machines []headscale.M
 | 
				
			|||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		var namespace string
 | 
							var namespace string
 | 
				
			||||||
		if currentNamespace.ID == machine.NamespaceID {
 | 
							if (currentNamespace == nil) || (currentNamespace.ID == machine.NamespaceID) {
 | 
				
			||||||
			namespace = pterm.LightMagenta(machine.Namespace.Name)
 | 
								namespace = pterm.LightMagenta(machine.Namespace.Name)
 | 
				
			||||||
		} else {
 | 
							} else {
 | 
				
			||||||
 | 
								// Shared into this namespace
 | 
				
			||||||
			namespace = pterm.LightYellow(machine.Namespace.Name)
 | 
								namespace = pterm.LightYellow(machine.Namespace.Name)
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		d = append(d, []string{strconv.FormatUint(machine.ID, 10), machine.Name, nodeKey.ShortString(), namespace, machine.IPAddress, strconv.FormatBool(ephemeral), lastSeenTime, online})
 | 
							d = append(d, []string{strconv.FormatUint(machine.ID, 10), machine.Name, nodeKey.ShortString(), namespace, machine.IPAddress, strconv.FormatBool(ephemeral), lastSeenTime, online})
 | 
				
			||||||
 | 
				
			|||||||
@ -4,16 +4,16 @@ import (
 | 
				
			|||||||
	"encoding/json"
 | 
						"encoding/json"
 | 
				
			||||||
	"errors"
 | 
						"errors"
 | 
				
			||||||
	"fmt"
 | 
						"fmt"
 | 
				
			||||||
	"io"
 | 
						"net/url"
 | 
				
			||||||
	"os"
 | 
						"os"
 | 
				
			||||||
	"path/filepath"
 | 
						"path/filepath"
 | 
				
			||||||
 | 
						"regexp"
 | 
				
			||||||
	"strings"
 | 
						"strings"
 | 
				
			||||||
	"time"
 | 
						"time"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"github.com/juanfont/headscale"
 | 
						"github.com/juanfont/headscale"
 | 
				
			||||||
	"github.com/rs/zerolog/log"
 | 
						"github.com/rs/zerolog/log"
 | 
				
			||||||
	"github.com/spf13/viper"
 | 
						"github.com/spf13/viper"
 | 
				
			||||||
	"gopkg.in/yaml.v2"
 | 
					 | 
				
			||||||
	"inet.af/netaddr"
 | 
						"inet.af/netaddr"
 | 
				
			||||||
	"tailscale.com/tailcfg"
 | 
						"tailscale.com/tailcfg"
 | 
				
			||||||
	"tailscale.com/types/dnstype"
 | 
						"tailscale.com/types/dnstype"
 | 
				
			||||||
@ -51,21 +51,26 @@ func LoadConfig(path string) error {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	// Collect any validation errors and return them all at once
 | 
						// Collect any validation errors and return them all at once
 | 
				
			||||||
	var errorText string
 | 
						var errorText string
 | 
				
			||||||
	if (viper.GetString("tls_letsencrypt_hostname") != "") && ((viper.GetString("tls_cert_path") != "") || (viper.GetString("tls_key_path") != "")) {
 | 
						if (viper.GetString("tls_letsencrypt_hostname") != "") &&
 | 
				
			||||||
 | 
							((viper.GetString("tls_cert_path") != "") || (viper.GetString("tls_key_path") != "")) {
 | 
				
			||||||
		errorText += "Fatal config error: set either tls_letsencrypt_hostname or tls_cert_path/tls_key_path, not both\n"
 | 
							errorText += "Fatal config error: set either tls_letsencrypt_hostname or tls_cert_path/tls_key_path, not both\n"
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if (viper.GetString("tls_letsencrypt_hostname") != "") && (viper.GetString("tls_letsencrypt_challenge_type") == "TLS-ALPN-01") && (!strings.HasSuffix(viper.GetString("listen_addr"), ":443")) {
 | 
						if (viper.GetString("tls_letsencrypt_hostname") != "") &&
 | 
				
			||||||
 | 
							(viper.GetString("tls_letsencrypt_challenge_type") == "TLS-ALPN-01") &&
 | 
				
			||||||
 | 
							(!strings.HasSuffix(viper.GetString("listen_addr"), ":443")) {
 | 
				
			||||||
		// this is only a warning because there could be something sitting in front of headscale that redirects the traffic (e.g. an iptables rule)
 | 
							// this is only a warning because there could be something sitting in front of headscale that redirects the traffic (e.g. an iptables rule)
 | 
				
			||||||
		log.Warn().
 | 
							log.Warn().
 | 
				
			||||||
			Msg("Warning: when using tls_letsencrypt_hostname with TLS-ALPN-01 as challenge type, headscale must be reachable on port 443, i.e. listen_addr should probably end in :443")
 | 
								Msg("Warning: when using tls_letsencrypt_hostname with TLS-ALPN-01 as challenge type, headscale must be reachable on port 443, i.e. listen_addr should probably end in :443")
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if (viper.GetString("tls_letsencrypt_challenge_type") != "HTTP-01") && (viper.GetString("tls_letsencrypt_challenge_type") != "TLS-ALPN-01") {
 | 
						if (viper.GetString("tls_letsencrypt_challenge_type") != "HTTP-01") &&
 | 
				
			||||||
 | 
							(viper.GetString("tls_letsencrypt_challenge_type") != "TLS-ALPN-01") {
 | 
				
			||||||
		errorText += "Fatal config error: the only supported values for tls_letsencrypt_challenge_type are HTTP-01 and TLS-ALPN-01\n"
 | 
							errorText += "Fatal config error: the only supported values for tls_letsencrypt_challenge_type are HTTP-01 and TLS-ALPN-01\n"
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if !strings.HasPrefix(viper.GetString("server_url"), "http://") && !strings.HasPrefix(viper.GetString("server_url"), "https://") {
 | 
						if !strings.HasPrefix(viper.GetString("server_url"), "http://") &&
 | 
				
			||||||
 | 
							!strings.HasPrefix(viper.GetString("server_url"), "https://") {
 | 
				
			||||||
		errorText += "Fatal config error: server_url must start with https:// or http://\n"
 | 
							errorText += "Fatal config error: server_url must start with https:// or http://\n"
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	if errorText != "" {
 | 
						if errorText != "" {
 | 
				
			||||||
@ -73,7 +78,35 @@ func LoadConfig(path string) error {
 | 
				
			|||||||
	} else {
 | 
						} else {
 | 
				
			||||||
		return nil
 | 
							return nil
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func GetDERPConfig() headscale.DERPConfig {
 | 
				
			||||||
 | 
						urlStrs := viper.GetStringSlice("derp.urls")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						urls := make([]url.URL, len(urlStrs))
 | 
				
			||||||
 | 
						for index, urlStr := range urlStrs {
 | 
				
			||||||
 | 
							urlAddr, err := url.Parse(urlStr)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								log.Error().
 | 
				
			||||||
 | 
									Str("url", urlStr).
 | 
				
			||||||
 | 
									Err(err).
 | 
				
			||||||
 | 
									Msg("Failed to parse url, ignoring...")
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							urls[index] = *urlAddr
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						paths := viper.GetStringSlice("derp.paths")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						autoUpdate := viper.GetBool("derp.auto_update_enabled")
 | 
				
			||||||
 | 
						updateFrequency := viper.GetDuration("derp.update_frequency")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return headscale.DERPConfig{
 | 
				
			||||||
 | 
							URLs:            urls,
 | 
				
			||||||
 | 
							Paths:           paths,
 | 
				
			||||||
 | 
							AutoUpdate:      autoUpdate,
 | 
				
			||||||
 | 
							UpdateFrequency: updateFrequency,
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func GetDNSConfig() (*tailcfg.DNSConfig, string) {
 | 
					func GetDNSConfig() (*tailcfg.DNSConfig, string) {
 | 
				
			||||||
@ -171,33 +204,50 @@ func absPath(path string) string {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func getHeadscaleApp() (*headscale.Headscale, error) {
 | 
					func getHeadscaleApp() (*headscale.Headscale, error) {
 | 
				
			||||||
	derpPath := absPath(viper.GetString("derp_map_path"))
 | 
					 | 
				
			||||||
	derpMap, err := loadDerpMap(derpPath)
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		log.Error().
 | 
					 | 
				
			||||||
			Str("path", derpPath).
 | 
					 | 
				
			||||||
			Err(err).
 | 
					 | 
				
			||||||
			Msg("Could not load DERP servers map file")
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// Minimum inactivity time out is keepalive timeout (60s) plus a few seconds
 | 
						// Minimum inactivity time out is keepalive timeout (60s) plus a few seconds
 | 
				
			||||||
	// to avoid races
 | 
						// to avoid races
 | 
				
			||||||
	minInactivityTimeout, _ := time.ParseDuration("65s")
 | 
						minInactivityTimeout, _ := time.ParseDuration("65s")
 | 
				
			||||||
	if viper.GetDuration("ephemeral_node_inactivity_timeout") <= minInactivityTimeout {
 | 
						if viper.GetDuration("ephemeral_node_inactivity_timeout") <= minInactivityTimeout {
 | 
				
			||||||
		err = fmt.Errorf("ephemeral_node_inactivity_timeout (%s) is set too low, must be more than %s\n", viper.GetString("ephemeral_node_inactivity_timeout"), minInactivityTimeout)
 | 
							err := fmt.Errorf(
 | 
				
			||||||
 | 
								"ephemeral_node_inactivity_timeout (%s) is set too low, must be more than %s\n",
 | 
				
			||||||
 | 
								viper.GetString("ephemeral_node_inactivity_timeout"),
 | 
				
			||||||
 | 
								minInactivityTimeout,
 | 
				
			||||||
 | 
							)
 | 
				
			||||||
		return nil, err
 | 
							return nil, err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// maxMachineRegistrationDuration is the maximum time headscale will allow a client to (optionally) request for
 | 
				
			||||||
 | 
						// the machine key expiry time. RegisterRequests with Expiry times that are more than
 | 
				
			||||||
 | 
						// maxMachineRegistrationDuration in the future will be clamped to (now + maxMachineRegistrationDuration)
 | 
				
			||||||
 | 
						maxMachineRegistrationDuration, _ := time.ParseDuration(
 | 
				
			||||||
 | 
							"10h",
 | 
				
			||||||
 | 
						) // use 10h here because it is the length of a standard business day plus a small amount of leeway
 | 
				
			||||||
 | 
						if viper.GetDuration("max_machine_registration_duration") >= time.Second {
 | 
				
			||||||
 | 
							maxMachineRegistrationDuration = viper.GetDuration("max_machine_registration_duration")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// defaultMachineRegistrationDuration is the default time assigned to a machine registration if one is not
 | 
				
			||||||
 | 
						// specified by the tailscale client. It is the default amount of time a machine registration is valid for
 | 
				
			||||||
 | 
						// (ie the amount of time before the user has to re-authenticate when requesting a connection)
 | 
				
			||||||
 | 
						defaultMachineRegistrationDuration, _ := time.ParseDuration(
 | 
				
			||||||
 | 
							"8h",
 | 
				
			||||||
 | 
						) // use 8h here because it's the length of a standard business day
 | 
				
			||||||
 | 
						if viper.GetDuration("default_machine_registration_duration") >= time.Second {
 | 
				
			||||||
 | 
							defaultMachineRegistrationDuration = viper.GetDuration("default_machine_registration_duration")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	dnsConfig, baseDomain := GetDNSConfig()
 | 
						dnsConfig, baseDomain := GetDNSConfig()
 | 
				
			||||||
 | 
						derpConfig := GetDERPConfig()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	cfg := headscale.Config{
 | 
						cfg := headscale.Config{
 | 
				
			||||||
		ServerURL:      viper.GetString("server_url"),
 | 
							ServerURL:      viper.GetString("server_url"),
 | 
				
			||||||
		Addr:           viper.GetString("listen_addr"),
 | 
							Addr:           viper.GetString("listen_addr"),
 | 
				
			||||||
		PrivateKeyPath: absPath(viper.GetString("private_key_path")),
 | 
							PrivateKeyPath: absPath(viper.GetString("private_key_path")),
 | 
				
			||||||
		DerpMap:        derpMap,
 | 
					 | 
				
			||||||
		IPPrefix:       netaddr.MustParseIPPrefix(viper.GetString("ip_prefix")),
 | 
							IPPrefix:       netaddr.MustParseIPPrefix(viper.GetString("ip_prefix")),
 | 
				
			||||||
		BaseDomain:     baseDomain,
 | 
							BaseDomain:     baseDomain,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							DERP: derpConfig,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		EphemeralNodeInactivityTimeout: viper.GetDuration("ephemeral_node_inactivity_timeout"),
 | 
							EphemeralNodeInactivityTimeout: viper.GetDuration("ephemeral_node_inactivity_timeout"),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		DBtype: viper.GetString("db_type"),
 | 
							DBtype: viper.GetString("db_type"),
 | 
				
			||||||
@ -220,8 +270,19 @@ func getHeadscaleApp() (*headscale.Headscale, error) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
		ACMEEmail: viper.GetString("acme_email"),
 | 
							ACMEEmail: viper.GetString("acme_email"),
 | 
				
			||||||
		ACMEURL:   viper.GetString("acme_url"),
 | 
							ACMEURL:   viper.GetString("acme_url"),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							OIDC: headscale.OIDCConfig{
 | 
				
			||||||
 | 
								Issuer:       viper.GetString("oidc.issuer"),
 | 
				
			||||||
 | 
								ClientID:     viper.GetString("oidc.client_id"),
 | 
				
			||||||
 | 
								ClientSecret: viper.GetString("oidc.client_secret"),
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							MaxMachineRegistrationDuration:     maxMachineRegistrationDuration,
 | 
				
			||||||
 | 
							DefaultMachineRegistrationDuration: defaultMachineRegistrationDuration,
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						cfg.OIDC.MatchMap = loadOIDCMatchMap()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	h, err := headscale.NewHeadscale(cfg)
 | 
						h, err := headscale.NewHeadscale(cfg)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return nil, err
 | 
							return nil, err
 | 
				
			||||||
@ -243,21 +304,6 @@ func getHeadscaleApp() (*headscale.Headscale, error) {
 | 
				
			|||||||
	return h, nil
 | 
						return h, nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func loadDerpMap(path string) (*tailcfg.DERPMap, error) {
 | 
					 | 
				
			||||||
	derpFile, err := os.Open(path)
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		return nil, err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	defer derpFile.Close()
 | 
					 | 
				
			||||||
	var derpMap tailcfg.DERPMap
 | 
					 | 
				
			||||||
	b, err := io.ReadAll(derpFile)
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		return nil, err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	err = yaml.Unmarshal(b, &derpMap)
 | 
					 | 
				
			||||||
	return &derpMap, err
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func JsonOutput(result interface{}, errResult error, outputFormat string) {
 | 
					func JsonOutput(result interface{}, errResult error, outputFormat string) {
 | 
				
			||||||
	var j []byte
 | 
						var j []byte
 | 
				
			||||||
	var err error
 | 
						var err error
 | 
				
			||||||
@ -298,3 +344,15 @@ func HasJsonOutputFlag() bool {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
	return false
 | 
						return false
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// loadOIDCMatchMap is a wrapper around viper to verifies that the keys in
 | 
				
			||||||
 | 
					// the match map is valid regex strings.
 | 
				
			||||||
 | 
					func loadOIDCMatchMap() map[string]string {
 | 
				
			||||||
 | 
						strMap := viper.GetStringMapString("oidc.domain_map")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for oidcMatcher := range strMap {
 | 
				
			||||||
 | 
							_ = regexp.MustCompile(oidcMatcher)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return strMap
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -25,10 +25,9 @@ func (s *Suite) SetUpSuite(c *check.C) {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (s *Suite) TearDownSuite(c *check.C) {
 | 
					func (s *Suite) TearDownSuite(c *check.C) {
 | 
				
			||||||
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (*Suite) TestPostgresConfigLoading(c *check.C) {
 | 
					func (*Suite) TestConfigLoading(c *check.C) {
 | 
				
			||||||
	tmpDir, err := ioutil.TempDir("", "headscale")
 | 
						tmpDir, err := ioutil.TempDir("", "headscale")
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		c.Fatal(err)
 | 
							c.Fatal(err)
 | 
				
			||||||
@ -41,7 +40,7 @@ func (*Suite) TestPostgresConfigLoading(c *check.C) {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Symlink the example config file
 | 
						// Symlink the example config file
 | 
				
			||||||
	err = os.Symlink(filepath.Clean(path+"/../../config.yaml.postgres.example"), filepath.Join(tmpDir, "config.yaml"))
 | 
						err = os.Symlink(filepath.Clean(path+"/../../config-example.yaml"), filepath.Join(tmpDir, "config.yaml"))
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		c.Fatal(err)
 | 
							c.Fatal(err)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@ -53,40 +52,7 @@ func (*Suite) TestPostgresConfigLoading(c *check.C) {
 | 
				
			|||||||
	// Test that config file was interpreted correctly
 | 
						// Test that config file was interpreted correctly
 | 
				
			||||||
	c.Assert(viper.GetString("server_url"), check.Equals, "http://127.0.0.1:8080")
 | 
						c.Assert(viper.GetString("server_url"), check.Equals, "http://127.0.0.1:8080")
 | 
				
			||||||
	c.Assert(viper.GetString("listen_addr"), check.Equals, "0.0.0.0:8080")
 | 
						c.Assert(viper.GetString("listen_addr"), check.Equals, "0.0.0.0:8080")
 | 
				
			||||||
	c.Assert(viper.GetString("derp_map_path"), check.Equals, "derp.yaml")
 | 
						c.Assert(viper.GetStringSlice("derp.paths")[0], check.Equals, "derp-example.yaml")
 | 
				
			||||||
	c.Assert(viper.GetString("db_type"), check.Equals, "postgres")
 | 
					 | 
				
			||||||
	c.Assert(viper.GetString("db_port"), check.Equals, "5432")
 | 
					 | 
				
			||||||
	c.Assert(viper.GetString("tls_letsencrypt_hostname"), check.Equals, "")
 | 
					 | 
				
			||||||
	c.Assert(viper.GetString("tls_letsencrypt_listen"), check.Equals, ":http")
 | 
					 | 
				
			||||||
	c.Assert(viper.GetStringSlice("dns_config.nameservers")[0], check.Equals, "1.1.1.1")
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func (*Suite) TestSqliteConfigLoading(c *check.C) {
 | 
					 | 
				
			||||||
	tmpDir, err := ioutil.TempDir("", "headscale")
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		c.Fatal(err)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	defer os.RemoveAll(tmpDir)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	path, err := os.Getwd()
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		c.Fatal(err)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// Symlink the example config file
 | 
					 | 
				
			||||||
	err = os.Symlink(filepath.Clean(path+"/../../config.yaml.sqlite.example"), filepath.Join(tmpDir, "config.yaml"))
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		c.Fatal(err)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// Load example config, it should load without validation errors
 | 
					 | 
				
			||||||
	err = cli.LoadConfig(tmpDir)
 | 
					 | 
				
			||||||
	c.Assert(err, check.IsNil)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// Test that config file was interpreted correctly
 | 
					 | 
				
			||||||
	c.Assert(viper.GetString("server_url"), check.Equals, "http://127.0.0.1:8080")
 | 
					 | 
				
			||||||
	c.Assert(viper.GetString("listen_addr"), check.Equals, "0.0.0.0:8080")
 | 
					 | 
				
			||||||
	c.Assert(viper.GetString("derp_map_path"), check.Equals, "derp.yaml")
 | 
					 | 
				
			||||||
	c.Assert(viper.GetString("db_type"), check.Equals, "sqlite3")
 | 
						c.Assert(viper.GetString("db_type"), check.Equals, "sqlite3")
 | 
				
			||||||
	c.Assert(viper.GetString("db_path"), check.Equals, "db.sqlite")
 | 
						c.Assert(viper.GetString("db_path"), check.Equals, "db.sqlite")
 | 
				
			||||||
	c.Assert(viper.GetString("tls_letsencrypt_hostname"), check.Equals, "")
 | 
						c.Assert(viper.GetString("tls_letsencrypt_hostname"), check.Equals, "")
 | 
				
			||||||
@ -108,7 +74,7 @@ func (*Suite) TestDNSConfigLoading(c *check.C) {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Symlink the example config file
 | 
						// Symlink the example config file
 | 
				
			||||||
	err = os.Symlink(filepath.Clean(path+"/../../config.yaml.sqlite.example"), filepath.Join(tmpDir, "config.yaml"))
 | 
						err = os.Symlink(filepath.Clean(path+"/../../config-example.yaml"), filepath.Join(tmpDir, "config.yaml"))
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		c.Fatal(err)
 | 
							c.Fatal(err)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@ -128,7 +94,7 @@ func (*Suite) TestDNSConfigLoading(c *check.C) {
 | 
				
			|||||||
func writeConfig(c *check.C, tmpDir string, configYaml []byte) {
 | 
					func writeConfig(c *check.C, tmpDir string, configYaml []byte) {
 | 
				
			||||||
	// Populate a custom config file
 | 
						// Populate a custom config file
 | 
				
			||||||
	configFile := filepath.Join(tmpDir, "config.yaml")
 | 
						configFile := filepath.Join(tmpDir, "config.yaml")
 | 
				
			||||||
	err := ioutil.WriteFile(configFile, configYaml, 0644)
 | 
						err := ioutil.WriteFile(configFile, configYaml, 0o644)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		c.Fatalf("Couldn't write file %s", configFile)
 | 
							c.Fatalf("Couldn't write file %s", configFile)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@ -139,10 +105,12 @@ func (*Suite) TestTLSConfigValidation(c *check.C) {
 | 
				
			|||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		c.Fatal(err)
 | 
							c.Fatal(err)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	//defer os.RemoveAll(tmpDir)
 | 
						// defer os.RemoveAll(tmpDir)
 | 
				
			||||||
	fmt.Println(tmpDir)
 | 
						fmt.Println(tmpDir)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	configYaml := []byte("---\ntls_letsencrypt_hostname: \"example.com\"\ntls_letsencrypt_challenge_type: \"\"\ntls_cert_path: \"abc.pem\"")
 | 
						configYaml := []byte(
 | 
				
			||||||
 | 
							"---\ntls_letsencrypt_hostname: \"example.com\"\ntls_letsencrypt_challenge_type: \"\"\ntls_cert_path: \"abc.pem\"",
 | 
				
			||||||
 | 
						)
 | 
				
			||||||
	writeConfig(c, tmpDir, configYaml)
 | 
						writeConfig(c, tmpDir, configYaml)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Check configuration validation errors (1)
 | 
						// Check configuration validation errors (1)
 | 
				
			||||||
@ -150,13 +118,23 @@ func (*Suite) TestTLSConfigValidation(c *check.C) {
 | 
				
			|||||||
	c.Assert(err, check.NotNil)
 | 
						c.Assert(err, check.NotNil)
 | 
				
			||||||
	// check.Matches can not handle multiline strings
 | 
						// check.Matches can not handle multiline strings
 | 
				
			||||||
	tmp := strings.ReplaceAll(err.Error(), "\n", "***")
 | 
						tmp := strings.ReplaceAll(err.Error(), "\n", "***")
 | 
				
			||||||
	c.Assert(tmp, check.Matches, ".*Fatal config error: set either tls_letsencrypt_hostname or tls_cert_path/tls_key_path, not both.*")
 | 
						c.Assert(
 | 
				
			||||||
	c.Assert(tmp, check.Matches, ".*Fatal config error: the only supported values for tls_letsencrypt_challenge_type are.*")
 | 
							tmp,
 | 
				
			||||||
 | 
							check.Matches,
 | 
				
			||||||
 | 
							".*Fatal config error: set either tls_letsencrypt_hostname or tls_cert_path/tls_key_path, not both.*",
 | 
				
			||||||
 | 
						)
 | 
				
			||||||
 | 
						c.Assert(
 | 
				
			||||||
 | 
							tmp,
 | 
				
			||||||
 | 
							check.Matches,
 | 
				
			||||||
 | 
							".*Fatal config error: the only supported values for tls_letsencrypt_challenge_type are.*",
 | 
				
			||||||
 | 
						)
 | 
				
			||||||
	c.Assert(tmp, check.Matches, ".*Fatal config error: server_url must start with https:// or http://.*")
 | 
						c.Assert(tmp, check.Matches, ".*Fatal config error: server_url must start with https:// or http://.*")
 | 
				
			||||||
	fmt.Println(tmp)
 | 
						fmt.Println(tmp)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Check configuration validation errors (2)
 | 
						// Check configuration validation errors (2)
 | 
				
			||||||
	configYaml = []byte("---\nserver_url: \"http://127.0.0.1:8080\"\ntls_letsencrypt_hostname: \"example.com\"\ntls_letsencrypt_challenge_type: \"TLS-ALPN-01\"")
 | 
						configYaml = []byte(
 | 
				
			||||||
 | 
							"---\nserver_url: \"http://127.0.0.1:8080\"\ntls_letsencrypt_hostname: \"example.com\"\ntls_letsencrypt_challenge_type: \"TLS-ALPN-01\"",
 | 
				
			||||||
 | 
						)
 | 
				
			||||||
	writeConfig(c, tmpDir, configYaml)
 | 
						writeConfig(c, tmpDir, configYaml)
 | 
				
			||||||
	err = cli.LoadConfig(tmpDir)
 | 
						err = cli.LoadConfig(tmpDir)
 | 
				
			||||||
	c.Assert(err, check.IsNil)
 | 
						c.Assert(err, check.IsNil)
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										81
									
								
								config-example.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										81
									
								
								config-example.yaml
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,81 @@
 | 
				
			|||||||
 | 
					---
 | 
				
			||||||
 | 
					# The url clients will connect to.
 | 
				
			||||||
 | 
					# Typically this will be a domain.
 | 
				
			||||||
 | 
					server_url: http://127.0.0.1:8080
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Address to listen to / bind to on the server
 | 
				
			||||||
 | 
					listen_addr: 0.0.0.0:8080
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Path to WireGuard private key file
 | 
				
			||||||
 | 
					private_key_path: private.key
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					derp:
 | 
				
			||||||
 | 
					  # List of externally available DERP maps encoded in JSON
 | 
				
			||||||
 | 
					  urls:
 | 
				
			||||||
 | 
					    - https://controlplane.tailscale.com/derpmap/default
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  # Locally available DERP map files encoded in YAML
 | 
				
			||||||
 | 
					  paths:
 | 
				
			||||||
 | 
					    - derp-example.yaml
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  # If enabled, a worker will be set up to periodically
 | 
				
			||||||
 | 
					  # refresh the given sources and update the derpmap
 | 
				
			||||||
 | 
					  # will be set up.
 | 
				
			||||||
 | 
					  auto_update_enabled: true
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  # How often should we check for updates?
 | 
				
			||||||
 | 
					  update_frequency: 24h
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Disables the automatic check for updates on startup
 | 
				
			||||||
 | 
					disable_check_updates: false
 | 
				
			||||||
 | 
					ephemeral_node_inactivity_timeout: 30m
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# SQLite config
 | 
				
			||||||
 | 
					db_type: sqlite3
 | 
				
			||||||
 | 
					db_path: db.sqlite
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# # Postgres config
 | 
				
			||||||
 | 
					# db_type: postgres
 | 
				
			||||||
 | 
					# db_host: localhost
 | 
				
			||||||
 | 
					# db_port: 5432
 | 
				
			||||||
 | 
					# db_name: headscale
 | 
				
			||||||
 | 
					# db_user: foo
 | 
				
			||||||
 | 
					# db_pass: bar
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					acme_url: https://acme-v02.api.letsencrypt.org/directory
 | 
				
			||||||
 | 
					acme_email: ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					tls_letsencrypt_hostname: ""
 | 
				
			||||||
 | 
					tls_letsencrypt_listen: ":http"
 | 
				
			||||||
 | 
					tls_letsencrypt_cache_dir: ".cache"
 | 
				
			||||||
 | 
					tls_letsencrypt_challenge_type: HTTP-01
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					tls_cert_path: ""
 | 
				
			||||||
 | 
					tls_key_path: ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Path to a file containg ACL policies.
 | 
				
			||||||
 | 
					acl_policy_path: ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					dns_config:
 | 
				
			||||||
 | 
					  # Upstream DNS servers
 | 
				
			||||||
 | 
					  nameservers:
 | 
				
			||||||
 | 
					    - 1.1.1.1
 | 
				
			||||||
 | 
					  domains: []
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  magic_dns: true
 | 
				
			||||||
 | 
					  base_domain: example.com
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# headscale supports experimental OpenID connect support, 
 | 
				
			||||||
 | 
					# it is still being tested and might have some bugs, please
 | 
				
			||||||
 | 
					# help us test it.
 | 
				
			||||||
 | 
					# OpenID Connect
 | 
				
			||||||
 | 
					# oidc:
 | 
				
			||||||
 | 
					#   issuer: "https://your-oidc.issuer.com/path"
 | 
				
			||||||
 | 
					#   client_id: "your-oidc-client-id"
 | 
				
			||||||
 | 
					#   client_secret: "your-oidc-client-secret"
 | 
				
			||||||
 | 
					#
 | 
				
			||||||
 | 
					#   # Domain map is used to map incomming users (by their email) to 
 | 
				
			||||||
 | 
					#   # a namespace. The key can be a string, or regex.
 | 
				
			||||||
 | 
					#   domain_map:
 | 
				
			||||||
 | 
					#     ".*": default-namespace
 | 
				
			||||||
@ -1,30 +0,0 @@
 | 
				
			|||||||
---
 | 
					 | 
				
			||||||
server_url: http://127.0.0.1:8080
 | 
					 | 
				
			||||||
listen_addr: 0.0.0.0:8080
 | 
					 | 
				
			||||||
private_key_path: private.key
 | 
					 | 
				
			||||||
derp_map_path: derp.yaml
 | 
					 | 
				
			||||||
ephemeral_node_inactivity_timeout: 30m
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# Postgres config
 | 
					 | 
				
			||||||
db_type: postgres
 | 
					 | 
				
			||||||
db_host: localhost
 | 
					 | 
				
			||||||
db_port: 5432
 | 
					 | 
				
			||||||
db_name: headscale
 | 
					 | 
				
			||||||
db_user: foo
 | 
					 | 
				
			||||||
db_pass: bar
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
acme_url: https://acme-v02.api.letsencrypt.org/directory
 | 
					 | 
				
			||||||
acme_email: ''
 | 
					 | 
				
			||||||
tls_letsencrypt_hostname: ''
 | 
					 | 
				
			||||||
tls_letsencrypt_listen: ":http"
 | 
					 | 
				
			||||||
tls_letsencrypt_cache_dir: ".cache"
 | 
					 | 
				
			||||||
tls_letsencrypt_challenge_type: HTTP-01
 | 
					 | 
				
			||||||
tls_cert_path: ''
 | 
					 | 
				
			||||||
tls_key_path: ''
 | 
					 | 
				
			||||||
acl_policy_path: ''
 | 
					 | 
				
			||||||
dns_config:
 | 
					 | 
				
			||||||
  nameservers:
 | 
					 | 
				
			||||||
  - 1.1.1.1
 | 
					 | 
				
			||||||
  domains: []
 | 
					 | 
				
			||||||
  magic_dns: true
 | 
					 | 
				
			||||||
  base_domain: example.com
 | 
					 | 
				
			||||||
@ -1,26 +0,0 @@
 | 
				
			|||||||
---
 | 
					 | 
				
			||||||
server_url: http://127.0.0.1:8080
 | 
					 | 
				
			||||||
listen_addr: 0.0.0.0:8080
 | 
					 | 
				
			||||||
private_key_path: private.key
 | 
					 | 
				
			||||||
derp_map_path: derp.yaml
 | 
					 | 
				
			||||||
ephemeral_node_inactivity_timeout: 30m
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# SQLite config (uncomment it if you want to use SQLite)
 | 
					 | 
				
			||||||
db_type: sqlite3
 | 
					 | 
				
			||||||
db_path: db.sqlite
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
acme_url: https://acme-v02.api.letsencrypt.org/directory
 | 
					 | 
				
			||||||
acme_email: ''
 | 
					 | 
				
			||||||
tls_letsencrypt_hostname: ''
 | 
					 | 
				
			||||||
tls_letsencrypt_listen: ":http"
 | 
					 | 
				
			||||||
tls_letsencrypt_cache_dir: ".cache"
 | 
					 | 
				
			||||||
tls_letsencrypt_challenge_type: HTTP-01
 | 
					 | 
				
			||||||
tls_cert_path: ''
 | 
					 | 
				
			||||||
tls_key_path: ''
 | 
					 | 
				
			||||||
acl_policy_path: ''
 | 
					 | 
				
			||||||
dns_config:
 | 
					 | 
				
			||||||
  nameservers:
 | 
					 | 
				
			||||||
  - 1.1.1.1
 | 
					 | 
				
			||||||
  domains: []
 | 
					 | 
				
			||||||
  magic_dns: true
 | 
					 | 
				
			||||||
  base_domain: example.com
 | 
					 | 
				
			||||||
							
								
								
									
										15
									
								
								derp-example.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								derp-example.yaml
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,15 @@
 | 
				
			|||||||
 | 
					# If you plan to somehow use headscale, please deploy your own DERP infra: https://tailscale.com/kb/1118/custom-derp-servers/
 | 
				
			||||||
 | 
					regions: 
 | 
				
			||||||
 | 
					  900:
 | 
				
			||||||
 | 
					    regionid: 900
 | 
				
			||||||
 | 
					    regioncode: custom
 | 
				
			||||||
 | 
					    regionname: My Region
 | 
				
			||||||
 | 
					    nodes:
 | 
				
			||||||
 | 
					    - name: 1a
 | 
				
			||||||
 | 
					      regionid: 1
 | 
				
			||||||
 | 
					      hostname: myderp.mydomain.no
 | 
				
			||||||
 | 
					      ipv4: 123.123.123.123
 | 
				
			||||||
 | 
					      ipv6: "2604:a880:400:d1::828:b001"
 | 
				
			||||||
 | 
					      stunport: 0
 | 
				
			||||||
 | 
					      stunonly: false
 | 
				
			||||||
 | 
					      derptestport: 0
 | 
				
			||||||
							
								
								
									
										152
									
								
								derp.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										152
									
								
								derp.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,152 @@
 | 
				
			|||||||
 | 
					package headscale
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"encoding/json"
 | 
				
			||||||
 | 
						"io"
 | 
				
			||||||
 | 
						"io/ioutil"
 | 
				
			||||||
 | 
						"net/http"
 | 
				
			||||||
 | 
						"net/url"
 | 
				
			||||||
 | 
						"os"
 | 
				
			||||||
 | 
						"time"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"github.com/rs/zerolog/log"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"gopkg.in/yaml.v2"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"tailscale.com/tailcfg"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func loadDERPMapFromPath(path string) (*tailcfg.DERPMap, error) {
 | 
				
			||||||
 | 
						derpFile, err := os.Open(path)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						defer derpFile.Close()
 | 
				
			||||||
 | 
						var derpMap tailcfg.DERPMap
 | 
				
			||||||
 | 
						b, err := io.ReadAll(derpFile)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						err = yaml.Unmarshal(b, &derpMap)
 | 
				
			||||||
 | 
						return &derpMap, err
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func loadDERPMapFromURL(addr url.URL) (*tailcfg.DERPMap, error) {
 | 
				
			||||||
 | 
						client := http.Client{
 | 
				
			||||||
 | 
							Timeout: 10 * time.Second,
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						resp, err := client.Get(addr.String())
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						defer resp.Body.Close()
 | 
				
			||||||
 | 
						body, err := ioutil.ReadAll(resp.Body)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						var derpMap tailcfg.DERPMap
 | 
				
			||||||
 | 
						err = json.Unmarshal(body, &derpMap)
 | 
				
			||||||
 | 
						return &derpMap, err
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// mergeDERPMaps naively merges a list of DERPMaps into a single
 | 
				
			||||||
 | 
					// DERPMap, it will _only_ look at the Regions, an integer.
 | 
				
			||||||
 | 
					// If a region exists in two of the given DERPMaps, the region
 | 
				
			||||||
 | 
					// form the _last_ DERPMap will be preserved.
 | 
				
			||||||
 | 
					// An empty DERPMap list will result in a DERPMap with no regions
 | 
				
			||||||
 | 
					func mergeDERPMaps(derpMaps []*tailcfg.DERPMap) *tailcfg.DERPMap {
 | 
				
			||||||
 | 
						result := tailcfg.DERPMap{
 | 
				
			||||||
 | 
							OmitDefaultRegions: false,
 | 
				
			||||||
 | 
							Regions:            map[int]*tailcfg.DERPRegion{},
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for _, derpMap := range derpMaps {
 | 
				
			||||||
 | 
							for id, region := range derpMap.Regions {
 | 
				
			||||||
 | 
								result.Regions[id] = region
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return &result
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func GetDERPMap(cfg DERPConfig) *tailcfg.DERPMap {
 | 
				
			||||||
 | 
						derpMaps := make([]*tailcfg.DERPMap, 0)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for _, path := range cfg.Paths {
 | 
				
			||||||
 | 
							log.Debug().
 | 
				
			||||||
 | 
								Str("func", "GetDERPMap").
 | 
				
			||||||
 | 
								Str("path", path).
 | 
				
			||||||
 | 
								Msg("Loading DERPMap from path")
 | 
				
			||||||
 | 
							derpMap, err := loadDERPMapFromPath(path)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								log.Error().
 | 
				
			||||||
 | 
									Str("func", "GetDERPMap").
 | 
				
			||||||
 | 
									Str("path", path).
 | 
				
			||||||
 | 
									Err(err).
 | 
				
			||||||
 | 
									Msg("Could not load DERP map from path")
 | 
				
			||||||
 | 
								break
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							derpMaps = append(derpMaps, derpMap)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for _, addr := range cfg.URLs {
 | 
				
			||||||
 | 
							derpMap, err := loadDERPMapFromURL(addr)
 | 
				
			||||||
 | 
							log.Debug().
 | 
				
			||||||
 | 
								Str("func", "GetDERPMap").
 | 
				
			||||||
 | 
								Str("url", addr.String()).
 | 
				
			||||||
 | 
								Msg("Loading DERPMap from path")
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								log.Error().
 | 
				
			||||||
 | 
									Str("func", "GetDERPMap").
 | 
				
			||||||
 | 
									Str("url", addr.String()).
 | 
				
			||||||
 | 
									Err(err).
 | 
				
			||||||
 | 
									Msg("Could not load DERP map from path")
 | 
				
			||||||
 | 
								break
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							derpMaps = append(derpMaps, derpMap)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						derpMap := mergeDERPMaps(derpMaps)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						log.Trace().Interface("derpMap", derpMap).Msg("DERPMap loaded")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if len(derpMap.Regions) == 0 {
 | 
				
			||||||
 | 
							log.Warn().
 | 
				
			||||||
 | 
								Msg("DERP map is empty, not a single DERP map datasource was loaded correctly or contained a region")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return derpMap
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (h *Headscale) scheduledDERPMapUpdateWorker(cancelChan <-chan struct{}) {
 | 
				
			||||||
 | 
						log.Info().
 | 
				
			||||||
 | 
							Dur("frequency", h.cfg.DERP.UpdateFrequency).
 | 
				
			||||||
 | 
							Msg("Setting up a DERPMap update worker")
 | 
				
			||||||
 | 
						ticker := time.NewTicker(h.cfg.DERP.UpdateFrequency)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for {
 | 
				
			||||||
 | 
							select {
 | 
				
			||||||
 | 
							case <-cancelChan:
 | 
				
			||||||
 | 
								return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							case <-ticker.C:
 | 
				
			||||||
 | 
								log.Info().Msg("Fetching DERPMap updates")
 | 
				
			||||||
 | 
								h.DERPMap = GetDERPMap(h.cfg.DERP)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								namespaces, err := h.ListNamespaces()
 | 
				
			||||||
 | 
								if err != nil {
 | 
				
			||||||
 | 
									log.Error().
 | 
				
			||||||
 | 
										Err(err).
 | 
				
			||||||
 | 
										Msg("Failed to fetch namespaces")
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								for _, namespace := range *namespaces {
 | 
				
			||||||
 | 
									h.setLastStateChangeToNow(namespace.Name)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										146
									
								
								derp.yaml
									
									
									
									
									
								
							
							
						
						
									
										146
									
								
								derp.yaml
									
									
									
									
									
								
							@ -1,146 +0,0 @@
 | 
				
			|||||||
# This file contains some of the official Tailscale DERP servers, 
 | 
					 | 
				
			||||||
# shamelessly taken from https://github.com/tailscale/tailscale/blob/main/net/dnsfallback/dns-fallback-servers.json
 | 
					 | 
				
			||||||
#
 | 
					 | 
				
			||||||
# If you plan to somehow use headscale, please deploy your own DERP infra: https://tailscale.com/kb/1118/custom-derp-servers/
 | 
					 | 
				
			||||||
regions: 
 | 
					 | 
				
			||||||
  1:
 | 
					 | 
				
			||||||
    regionid: 1
 | 
					 | 
				
			||||||
    regioncode: nyc
 | 
					 | 
				
			||||||
    regionname: New York City
 | 
					 | 
				
			||||||
    nodes:
 | 
					 | 
				
			||||||
    - name: 1a
 | 
					 | 
				
			||||||
      regionid: 1
 | 
					 | 
				
			||||||
      hostname: derp1.tailscale.com
 | 
					 | 
				
			||||||
      ipv4: 159.89.225.99
 | 
					 | 
				
			||||||
      ipv6: "2604:a880:400:d1::828:b001"
 | 
					 | 
				
			||||||
      stunport: 0
 | 
					 | 
				
			||||||
      stunonly: false
 | 
					 | 
				
			||||||
      derptestport: 0
 | 
					 | 
				
			||||||
    - name: 1b
 | 
					 | 
				
			||||||
      regionid: 1
 | 
					 | 
				
			||||||
      hostname: derp1b.tailscale.com
 | 
					 | 
				
			||||||
      ipv4: 45.55.35.93
 | 
					 | 
				
			||||||
      ipv6: "2604:a880:800:a1::f:2001"
 | 
					 | 
				
			||||||
      stunport: 0
 | 
					 | 
				
			||||||
      stunonly: false
 | 
					 | 
				
			||||||
      derptestport: 0
 | 
					 | 
				
			||||||
  2:
 | 
					 | 
				
			||||||
    regionid: 2
 | 
					 | 
				
			||||||
    regioncode: sfo
 | 
					 | 
				
			||||||
    regionname: San Francisco
 | 
					 | 
				
			||||||
    nodes:
 | 
					 | 
				
			||||||
    - name: 2a
 | 
					 | 
				
			||||||
      regionid: 2
 | 
					 | 
				
			||||||
      hostname: derp2.tailscale.com
 | 
					 | 
				
			||||||
      ipv4: 167.172.206.31
 | 
					 | 
				
			||||||
      ipv6: "2604:a880:2:d1::c5:7001"
 | 
					 | 
				
			||||||
      stunport: 0
 | 
					 | 
				
			||||||
      stunonly: false
 | 
					 | 
				
			||||||
      derptestport: 0
 | 
					 | 
				
			||||||
    - name: 2b
 | 
					 | 
				
			||||||
      regionid: 2
 | 
					 | 
				
			||||||
      hostname: derp2b.tailscale.com
 | 
					 | 
				
			||||||
      ipv4: 64.227.106.23
 | 
					 | 
				
			||||||
      ipv6: "2604:a880:4:1d0::29:9000"
 | 
					 | 
				
			||||||
      stunport: 0
 | 
					 | 
				
			||||||
      stunonly: false
 | 
					 | 
				
			||||||
      derptestport: 0
 | 
					 | 
				
			||||||
  3:
 | 
					 | 
				
			||||||
    regionid: 3
 | 
					 | 
				
			||||||
    regioncode: sin
 | 
					 | 
				
			||||||
    regionname: Singapore
 | 
					 | 
				
			||||||
    nodes:
 | 
					 | 
				
			||||||
    - name: 3a
 | 
					 | 
				
			||||||
      regionid: 3
 | 
					 | 
				
			||||||
      hostname: derp3.tailscale.com
 | 
					 | 
				
			||||||
      ipv4: 68.183.179.66
 | 
					 | 
				
			||||||
      ipv6: "2400:6180:0:d1::67d:8001"
 | 
					 | 
				
			||||||
      stunport: 0
 | 
					 | 
				
			||||||
      stunonly: false
 | 
					 | 
				
			||||||
      derptestport: 0
 | 
					 | 
				
			||||||
  4:
 | 
					 | 
				
			||||||
    regionid: 4
 | 
					 | 
				
			||||||
    regioncode: fra
 | 
					 | 
				
			||||||
    regionname: Frankfurt
 | 
					 | 
				
			||||||
    nodes:
 | 
					 | 
				
			||||||
    - name: 4a
 | 
					 | 
				
			||||||
      regionid: 4
 | 
					 | 
				
			||||||
      hostname: derp4.tailscale.com
 | 
					 | 
				
			||||||
      ipv4: 167.172.182.26
 | 
					 | 
				
			||||||
      ipv6: "2a03:b0c0:3:e0::36e:900"
 | 
					 | 
				
			||||||
      stunport: 0
 | 
					 | 
				
			||||||
      stunonly: false
 | 
					 | 
				
			||||||
      derptestport: 0
 | 
					 | 
				
			||||||
    - name: 4b
 | 
					 | 
				
			||||||
      regionid: 4
 | 
					 | 
				
			||||||
      hostname: derp4b.tailscale.com
 | 
					 | 
				
			||||||
      ipv4: 157.230.25.0
 | 
					 | 
				
			||||||
      ipv6: "2a03:b0c0:3:e0::58f:3001"
 | 
					 | 
				
			||||||
      stunport: 0
 | 
					 | 
				
			||||||
      stunonly: false
 | 
					 | 
				
			||||||
      derptestport: 0
 | 
					 | 
				
			||||||
  5:
 | 
					 | 
				
			||||||
    regionid: 5
 | 
					 | 
				
			||||||
    regioncode: syd
 | 
					 | 
				
			||||||
    regionname: Sydney
 | 
					 | 
				
			||||||
    nodes:
 | 
					 | 
				
			||||||
    - name: 5a
 | 
					 | 
				
			||||||
      regionid: 5
 | 
					 | 
				
			||||||
      hostname: derp5.tailscale.com
 | 
					 | 
				
			||||||
      ipv4: 103.43.75.49
 | 
					 | 
				
			||||||
      ipv6: "2001:19f0:5801:10b7:5400:2ff:feaa:284c"
 | 
					 | 
				
			||||||
      stunport: 0
 | 
					 | 
				
			||||||
      stunonly: false
 | 
					 | 
				
			||||||
      derptestport: 0
 | 
					 | 
				
			||||||
  6:
 | 
					 | 
				
			||||||
    regionid: 6
 | 
					 | 
				
			||||||
    regioncode: blr
 | 
					 | 
				
			||||||
    regionname: Bangalore
 | 
					 | 
				
			||||||
    nodes:
 | 
					 | 
				
			||||||
    - name: 6a
 | 
					 | 
				
			||||||
      regionid: 6
 | 
					 | 
				
			||||||
      hostname: derp6.tailscale.com
 | 
					 | 
				
			||||||
      ipv4: 68.183.90.120
 | 
					 | 
				
			||||||
      ipv6: "2400:6180:100:d0::982:d001"
 | 
					 | 
				
			||||||
      stunport: 0
 | 
					 | 
				
			||||||
      stunonly: false
 | 
					 | 
				
			||||||
      derptestport: 0
 | 
					 | 
				
			||||||
  7:
 | 
					 | 
				
			||||||
    regionid: 7
 | 
					 | 
				
			||||||
    regioncode: tok
 | 
					 | 
				
			||||||
    regionname: Tokyo
 | 
					 | 
				
			||||||
    nodes:
 | 
					 | 
				
			||||||
    - name: 7a
 | 
					 | 
				
			||||||
      regionid: 7
 | 
					 | 
				
			||||||
      hostname: derp7.tailscale.com
 | 
					 | 
				
			||||||
      ipv4: 167.179.89.145
 | 
					 | 
				
			||||||
      ipv6: "2401:c080:1000:467f:5400:2ff:feee:22aa"
 | 
					 | 
				
			||||||
      stunport: 0
 | 
					 | 
				
			||||||
      stunonly: false
 | 
					 | 
				
			||||||
      derptestport: 0
 | 
					 | 
				
			||||||
  8:
 | 
					 | 
				
			||||||
    regionid: 8
 | 
					 | 
				
			||||||
    regioncode: lhr
 | 
					 | 
				
			||||||
    regionname: London
 | 
					 | 
				
			||||||
    nodes:
 | 
					 | 
				
			||||||
    - name: 8a
 | 
					 | 
				
			||||||
      regionid: 8
 | 
					 | 
				
			||||||
      hostname: derp8.tailscale.com
 | 
					 | 
				
			||||||
      ipv4: 167.71.139.179
 | 
					 | 
				
			||||||
      ipv6: "2a03:b0c0:1:e0::3cc:e001"
 | 
					 | 
				
			||||||
      stunport: 0
 | 
					 | 
				
			||||||
      stunonly: false
 | 
					 | 
				
			||||||
      derptestport: 0
 | 
					 | 
				
			||||||
  9:
 | 
					 | 
				
			||||||
    regionid: 9
 | 
					 | 
				
			||||||
    regioncode: sao
 | 
					 | 
				
			||||||
    regionname: São Paulo
 | 
					 | 
				
			||||||
    nodes:
 | 
					 | 
				
			||||||
    - name: 9a
 | 
					 | 
				
			||||||
      regionid: 9
 | 
					 | 
				
			||||||
      hostname: derp9.tailscale.com
 | 
					 | 
				
			||||||
      ipv4: 207.148.3.137
 | 
					 | 
				
			||||||
      ipv6: "2001:19f0:6401:1d9c:5400:2ff:feef:bb82"
 | 
					 | 
				
			||||||
      stunport: 0
 | 
					 | 
				
			||||||
      stunonly: false
 | 
					 | 
				
			||||||
      derptestport: 0
 | 
					 | 
				
			||||||
@ -97,7 +97,7 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
9. In the server, register your machine to a namespace with the CLI
 | 
					9. In the server, register your machine to a namespace with the CLI
 | 
				
			||||||
   ```shell
 | 
					   ```shell
 | 
				
			||||||
   headscale -n myfirstnamespace nodes register YOURMACHINEKEY
 | 
					   headscale -n myfirstnamespace nodes register -k YOURMACHINEKEY
 | 
				
			||||||
   ```
 | 
					   ```
 | 
				
			||||||
   or docker:
 | 
					   or docker:
 | 
				
			||||||
   ```shell
 | 
					   ```shell
 | 
				
			||||||
@ -106,11 +106,11 @@
 | 
				
			|||||||
     -v $(pwd)/config.json:/config.json \
 | 
					     -v $(pwd)/config.json:/config.json \
 | 
				
			||||||
     -v $(pwd)/derp.yaml:/derp.yaml \
 | 
					     -v $(pwd)/derp.yaml:/derp.yaml \
 | 
				
			||||||
     headscale/headscale:x.x.x \
 | 
					     headscale/headscale:x.x.x \
 | 
				
			||||||
     headscale -n myfirstnamespace nodes register YOURMACHINEKEY
 | 
					     headscale -n myfirstnamespace nodes register -k YOURMACHINEKEY
 | 
				
			||||||
   ```
 | 
					   ```
 | 
				
			||||||
   or if your server is already running in docker:
 | 
					   or if your server is already running in docker:
 | 
				
			||||||
   ```shell
 | 
					   ```shell
 | 
				
			||||||
   docker exec <container_name> headscale -n myfirstnamespace nodes register YOURMACHINEKEY
 | 
					   docker exec <container_name> headscale -n myfirstnamespace nodes register -k YOURMACHINEKEY
 | 
				
			||||||
   ```
 | 
					   ```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Alternatively, you can use Auth Keys to register your machines:
 | 
					Alternatively, you can use Auth Keys to register your machines:
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										702
									
								
								gen/go/v1/headscale.pb.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										702
									
								
								gen/go/v1/headscale.pb.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,702 @@
 | 
				
			|||||||
 | 
					// Code generated by protoc-gen-go. DO NOT EDIT.
 | 
				
			||||||
 | 
					// versions:
 | 
				
			||||||
 | 
					// 	protoc-gen-go v1.27.1
 | 
				
			||||||
 | 
					// 	protoc        v3.18.1
 | 
				
			||||||
 | 
					// source: v1/headscale.proto
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					package v1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						_ "github.com/infobloxopen/protoc-gen-gorm/options"
 | 
				
			||||||
 | 
						_ "google.golang.org/genproto/googleapis/api/annotations"
 | 
				
			||||||
 | 
						protoreflect "google.golang.org/protobuf/reflect/protoreflect"
 | 
				
			||||||
 | 
						protoimpl "google.golang.org/protobuf/runtime/protoimpl"
 | 
				
			||||||
 | 
						timestamppb "google.golang.org/protobuf/types/known/timestamppb"
 | 
				
			||||||
 | 
						reflect "reflect"
 | 
				
			||||||
 | 
						sync "sync"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const (
 | 
				
			||||||
 | 
						// Verify that this generated code is sufficiently up-to-date.
 | 
				
			||||||
 | 
						_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
 | 
				
			||||||
 | 
						// Verify that runtime/protoimpl is sufficiently up-to-date.
 | 
				
			||||||
 | 
						_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type RegisterMethod int32
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const (
 | 
				
			||||||
 | 
						RegisterMethod_AUTH_KEY RegisterMethod = 0
 | 
				
			||||||
 | 
						RegisterMethod_CLI      RegisterMethod = 1
 | 
				
			||||||
 | 
						RegisterMethod_OIDC     RegisterMethod = 2
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Enum value maps for RegisterMethod.
 | 
				
			||||||
 | 
					var (
 | 
				
			||||||
 | 
						RegisterMethod_name = map[int32]string{
 | 
				
			||||||
 | 
							0: "AUTH_KEY",
 | 
				
			||||||
 | 
							1: "CLI",
 | 
				
			||||||
 | 
							2: "OIDC",
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						RegisterMethod_value = map[string]int32{
 | 
				
			||||||
 | 
							"AUTH_KEY": 0,
 | 
				
			||||||
 | 
							"CLI":      1,
 | 
				
			||||||
 | 
							"OIDC":     2,
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (x RegisterMethod) Enum() *RegisterMethod {
 | 
				
			||||||
 | 
						p := new(RegisterMethod)
 | 
				
			||||||
 | 
						*p = x
 | 
				
			||||||
 | 
						return p
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (x RegisterMethod) String() string {
 | 
				
			||||||
 | 
						return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (RegisterMethod) Descriptor() protoreflect.EnumDescriptor {
 | 
				
			||||||
 | 
						return file_v1_headscale_proto_enumTypes[0].Descriptor()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (RegisterMethod) Type() protoreflect.EnumType {
 | 
				
			||||||
 | 
						return &file_v1_headscale_proto_enumTypes[0]
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (x RegisterMethod) Number() protoreflect.EnumNumber {
 | 
				
			||||||
 | 
						return protoreflect.EnumNumber(x)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Deprecated: Use RegisterMethod.Descriptor instead.
 | 
				
			||||||
 | 
					func (RegisterMethod) EnumDescriptor() ([]byte, []int) {
 | 
				
			||||||
 | 
						return file_v1_headscale_proto_rawDescGZIP(), []int{0}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type Namespace struct {
 | 
				
			||||||
 | 
						state         protoimpl.MessageState
 | 
				
			||||||
 | 
						sizeCache     protoimpl.SizeCache
 | 
				
			||||||
 | 
						unknownFields protoimpl.UnknownFields
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						Name string `protobuf:"bytes,1,opt,name=Name,proto3" json:"Name,omitempty"`
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (x *Namespace) Reset() {
 | 
				
			||||||
 | 
						*x = Namespace{}
 | 
				
			||||||
 | 
						if protoimpl.UnsafeEnabled {
 | 
				
			||||||
 | 
							mi := &file_v1_headscale_proto_msgTypes[0]
 | 
				
			||||||
 | 
							ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 | 
				
			||||||
 | 
							ms.StoreMessageInfo(mi)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (x *Namespace) String() string {
 | 
				
			||||||
 | 
						return protoimpl.X.MessageStringOf(x)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (*Namespace) ProtoMessage() {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (x *Namespace) ProtoReflect() protoreflect.Message {
 | 
				
			||||||
 | 
						mi := &file_v1_headscale_proto_msgTypes[0]
 | 
				
			||||||
 | 
						if protoimpl.UnsafeEnabled && x != nil {
 | 
				
			||||||
 | 
							ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 | 
				
			||||||
 | 
							if ms.LoadMessageInfo() == nil {
 | 
				
			||||||
 | 
								ms.StoreMessageInfo(mi)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							return ms
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return mi.MessageOf(x)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Deprecated: Use Namespace.ProtoReflect.Descriptor instead.
 | 
				
			||||||
 | 
					func (*Namespace) Descriptor() ([]byte, []int) {
 | 
				
			||||||
 | 
						return file_v1_headscale_proto_rawDescGZIP(), []int{0}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (x *Namespace) GetName() string {
 | 
				
			||||||
 | 
						if x != nil {
 | 
				
			||||||
 | 
							return x.Name
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return ""
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type PreAuthKey struct {
 | 
				
			||||||
 | 
						state         protoimpl.MessageState
 | 
				
			||||||
 | 
						sizeCache     protoimpl.SizeCache
 | 
				
			||||||
 | 
						unknownFields protoimpl.UnknownFields
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						ID          uint64                 `protobuf:"varint,1,opt,name=ID,proto3" json:"ID,omitempty"`
 | 
				
			||||||
 | 
						Key         string                 `protobuf:"bytes,2,opt,name=Key,proto3" json:"Key,omitempty"`
 | 
				
			||||||
 | 
						NamespaceID uint32                 `protobuf:"varint,3,opt,name=NamespaceID,proto3" json:"NamespaceID,omitempty"`
 | 
				
			||||||
 | 
						Namespace   *Namespace             `protobuf:"bytes,4,opt,name=Namespace,proto3" json:"Namespace,omitempty"`
 | 
				
			||||||
 | 
						Reusable    bool                   `protobuf:"varint,5,opt,name=Reusable,proto3" json:"Reusable,omitempty"`
 | 
				
			||||||
 | 
						Ephemeral   bool                   `protobuf:"varint,6,opt,name=Ephemeral,proto3" json:"Ephemeral,omitempty"`
 | 
				
			||||||
 | 
						Used        bool                   `protobuf:"varint,7,opt,name=Used,proto3" json:"Used,omitempty"`
 | 
				
			||||||
 | 
						CreatedAt   *timestamppb.Timestamp `protobuf:"bytes,8,opt,name=CreatedAt,proto3" json:"CreatedAt,omitempty"`
 | 
				
			||||||
 | 
						Expiration  *timestamppb.Timestamp `protobuf:"bytes,9,opt,name=Expiration,proto3" json:"Expiration,omitempty"`
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (x *PreAuthKey) Reset() {
 | 
				
			||||||
 | 
						*x = PreAuthKey{}
 | 
				
			||||||
 | 
						if protoimpl.UnsafeEnabled {
 | 
				
			||||||
 | 
							mi := &file_v1_headscale_proto_msgTypes[1]
 | 
				
			||||||
 | 
							ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 | 
				
			||||||
 | 
							ms.StoreMessageInfo(mi)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (x *PreAuthKey) String() string {
 | 
				
			||||||
 | 
						return protoimpl.X.MessageStringOf(x)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (*PreAuthKey) ProtoMessage() {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (x *PreAuthKey) ProtoReflect() protoreflect.Message {
 | 
				
			||||||
 | 
						mi := &file_v1_headscale_proto_msgTypes[1]
 | 
				
			||||||
 | 
						if protoimpl.UnsafeEnabled && x != nil {
 | 
				
			||||||
 | 
							ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 | 
				
			||||||
 | 
							if ms.LoadMessageInfo() == nil {
 | 
				
			||||||
 | 
								ms.StoreMessageInfo(mi)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							return ms
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return mi.MessageOf(x)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Deprecated: Use PreAuthKey.ProtoReflect.Descriptor instead.
 | 
				
			||||||
 | 
					func (*PreAuthKey) Descriptor() ([]byte, []int) {
 | 
				
			||||||
 | 
						return file_v1_headscale_proto_rawDescGZIP(), []int{1}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (x *PreAuthKey) GetID() uint64 {
 | 
				
			||||||
 | 
						if x != nil {
 | 
				
			||||||
 | 
							return x.ID
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return 0
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (x *PreAuthKey) GetKey() string {
 | 
				
			||||||
 | 
						if x != nil {
 | 
				
			||||||
 | 
							return x.Key
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return ""
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (x *PreAuthKey) GetNamespaceID() uint32 {
 | 
				
			||||||
 | 
						if x != nil {
 | 
				
			||||||
 | 
							return x.NamespaceID
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return 0
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (x *PreAuthKey) GetNamespace() *Namespace {
 | 
				
			||||||
 | 
						if x != nil {
 | 
				
			||||||
 | 
							return x.Namespace
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (x *PreAuthKey) GetReusable() bool {
 | 
				
			||||||
 | 
						if x != nil {
 | 
				
			||||||
 | 
							return x.Reusable
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return false
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (x *PreAuthKey) GetEphemeral() bool {
 | 
				
			||||||
 | 
						if x != nil {
 | 
				
			||||||
 | 
							return x.Ephemeral
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return false
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (x *PreAuthKey) GetUsed() bool {
 | 
				
			||||||
 | 
						if x != nil {
 | 
				
			||||||
 | 
							return x.Used
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return false
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (x *PreAuthKey) GetCreatedAt() *timestamppb.Timestamp {
 | 
				
			||||||
 | 
						if x != nil {
 | 
				
			||||||
 | 
							return x.CreatedAt
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (x *PreAuthKey) GetExpiration() *timestamppb.Timestamp {
 | 
				
			||||||
 | 
						if x != nil {
 | 
				
			||||||
 | 
							return x.Expiration
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type GetMachineRequest struct {
 | 
				
			||||||
 | 
						state         protoimpl.MessageState
 | 
				
			||||||
 | 
						sizeCache     protoimpl.SizeCache
 | 
				
			||||||
 | 
						unknownFields protoimpl.UnknownFields
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						MachineId uint64 `protobuf:"varint,1,opt,name=machine_id,json=machineId,proto3" json:"machine_id,omitempty"`
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (x *GetMachineRequest) Reset() {
 | 
				
			||||||
 | 
						*x = GetMachineRequest{}
 | 
				
			||||||
 | 
						if protoimpl.UnsafeEnabled {
 | 
				
			||||||
 | 
							mi := &file_v1_headscale_proto_msgTypes[2]
 | 
				
			||||||
 | 
							ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 | 
				
			||||||
 | 
							ms.StoreMessageInfo(mi)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (x *GetMachineRequest) String() string {
 | 
				
			||||||
 | 
						return protoimpl.X.MessageStringOf(x)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (*GetMachineRequest) ProtoMessage() {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (x *GetMachineRequest) ProtoReflect() protoreflect.Message {
 | 
				
			||||||
 | 
						mi := &file_v1_headscale_proto_msgTypes[2]
 | 
				
			||||||
 | 
						if protoimpl.UnsafeEnabled && x != nil {
 | 
				
			||||||
 | 
							ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 | 
				
			||||||
 | 
							if ms.LoadMessageInfo() == nil {
 | 
				
			||||||
 | 
								ms.StoreMessageInfo(mi)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							return ms
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return mi.MessageOf(x)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Deprecated: Use GetMachineRequest.ProtoReflect.Descriptor instead.
 | 
				
			||||||
 | 
					func (*GetMachineRequest) Descriptor() ([]byte, []int) {
 | 
				
			||||||
 | 
						return file_v1_headscale_proto_rawDescGZIP(), []int{2}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (x *GetMachineRequest) GetMachineId() uint64 {
 | 
				
			||||||
 | 
						if x != nil {
 | 
				
			||||||
 | 
							return x.MachineId
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return 0
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type Machine struct {
 | 
				
			||||||
 | 
						state         protoimpl.MessageState
 | 
				
			||||||
 | 
						sizeCache     protoimpl.SizeCache
 | 
				
			||||||
 | 
						unknownFields protoimpl.UnknownFields
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						ID                   uint64                 `protobuf:"varint,1,opt,name=ID,proto3" json:"ID,omitempty"`
 | 
				
			||||||
 | 
						MachineKey           string                 `protobuf:"bytes,2,opt,name=MachineKey,proto3" json:"MachineKey,omitempty"`
 | 
				
			||||||
 | 
						NodeKey              string                 `protobuf:"bytes,3,opt,name=NodeKey,proto3" json:"NodeKey,omitempty"`
 | 
				
			||||||
 | 
						DiscoKey             string                 `protobuf:"bytes,4,opt,name=DiscoKey,proto3" json:"DiscoKey,omitempty"`
 | 
				
			||||||
 | 
						IPAddress            string                 `protobuf:"bytes,5,opt,name=IPAddress,proto3" json:"IPAddress,omitempty"`
 | 
				
			||||||
 | 
						Name                 string                 `protobuf:"bytes,6,opt,name=Name,proto3" json:"Name,omitempty"`
 | 
				
			||||||
 | 
						NamespaceID          uint32                 `protobuf:"varint,7,opt,name=NamespaceID,proto3" json:"NamespaceID,omitempty"`
 | 
				
			||||||
 | 
						Registered           bool                   `protobuf:"varint,8,opt,name=Registered,proto3" json:"Registered,omitempty"`
 | 
				
			||||||
 | 
						RegisterMethod       RegisterMethod         `protobuf:"varint,9,opt,name=RegisterMethod,proto3,enum=headscale.v1.RegisterMethod" json:"RegisterMethod,omitempty"`
 | 
				
			||||||
 | 
						AuthKeyID            uint32                 `protobuf:"varint,10,opt,name=AuthKeyID,proto3" json:"AuthKeyID,omitempty"`
 | 
				
			||||||
 | 
						AuthKey              *PreAuthKey            `protobuf:"bytes,11,opt,name=AuthKey,proto3" json:"AuthKey,omitempty"`
 | 
				
			||||||
 | 
						LastSeen             *timestamppb.Timestamp `protobuf:"bytes,12,opt,name=LastSeen,proto3" json:"LastSeen,omitempty"`
 | 
				
			||||||
 | 
						LastSuccessfulUpdate *timestamppb.Timestamp `protobuf:"bytes,13,opt,name=LastSuccessfulUpdate,proto3" json:"LastSuccessfulUpdate,omitempty"`
 | 
				
			||||||
 | 
						Expiry               *timestamppb.Timestamp `protobuf:"bytes,14,opt,name=Expiry,proto3" json:"Expiry,omitempty"`
 | 
				
			||||||
 | 
						HostInfo             []byte                 `protobuf:"bytes,15,opt,name=HostInfo,proto3" json:"HostInfo,omitempty"`
 | 
				
			||||||
 | 
						Endpoints            []byte                 `protobuf:"bytes,16,opt,name=Endpoints,proto3" json:"Endpoints,omitempty"`
 | 
				
			||||||
 | 
						EnabledRoutes        []byte                 `protobuf:"bytes,17,opt,name=EnabledRoutes,proto3" json:"EnabledRoutes,omitempty"`
 | 
				
			||||||
 | 
						CreatedAt            *timestamppb.Timestamp `protobuf:"bytes,18,opt,name=CreatedAt,proto3" json:"CreatedAt,omitempty"`
 | 
				
			||||||
 | 
						UpdatedAt            *timestamppb.Timestamp `protobuf:"bytes,19,opt,name=UpdatedAt,proto3" json:"UpdatedAt,omitempty"`
 | 
				
			||||||
 | 
						DeletedAt            *timestamppb.Timestamp `protobuf:"bytes,20,opt,name=DeletedAt,proto3" json:"DeletedAt,omitempty"`
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (x *Machine) Reset() {
 | 
				
			||||||
 | 
						*x = Machine{}
 | 
				
			||||||
 | 
						if protoimpl.UnsafeEnabled {
 | 
				
			||||||
 | 
							mi := &file_v1_headscale_proto_msgTypes[3]
 | 
				
			||||||
 | 
							ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 | 
				
			||||||
 | 
							ms.StoreMessageInfo(mi)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (x *Machine) String() string {
 | 
				
			||||||
 | 
						return protoimpl.X.MessageStringOf(x)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (*Machine) ProtoMessage() {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (x *Machine) ProtoReflect() protoreflect.Message {
 | 
				
			||||||
 | 
						mi := &file_v1_headscale_proto_msgTypes[3]
 | 
				
			||||||
 | 
						if protoimpl.UnsafeEnabled && x != nil {
 | 
				
			||||||
 | 
							ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 | 
				
			||||||
 | 
							if ms.LoadMessageInfo() == nil {
 | 
				
			||||||
 | 
								ms.StoreMessageInfo(mi)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							return ms
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return mi.MessageOf(x)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Deprecated: Use Machine.ProtoReflect.Descriptor instead.
 | 
				
			||||||
 | 
					func (*Machine) Descriptor() ([]byte, []int) {
 | 
				
			||||||
 | 
						return file_v1_headscale_proto_rawDescGZIP(), []int{3}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (x *Machine) GetID() uint64 {
 | 
				
			||||||
 | 
						if x != nil {
 | 
				
			||||||
 | 
							return x.ID
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return 0
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (x *Machine) GetMachineKey() string {
 | 
				
			||||||
 | 
						if x != nil {
 | 
				
			||||||
 | 
							return x.MachineKey
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return ""
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (x *Machine) GetNodeKey() string {
 | 
				
			||||||
 | 
						if x != nil {
 | 
				
			||||||
 | 
							return x.NodeKey
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return ""
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (x *Machine) GetDiscoKey() string {
 | 
				
			||||||
 | 
						if x != nil {
 | 
				
			||||||
 | 
							return x.DiscoKey
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return ""
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (x *Machine) GetIPAddress() string {
 | 
				
			||||||
 | 
						if x != nil {
 | 
				
			||||||
 | 
							return x.IPAddress
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return ""
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (x *Machine) GetName() string {
 | 
				
			||||||
 | 
						if x != nil {
 | 
				
			||||||
 | 
							return x.Name
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return ""
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (x *Machine) GetNamespaceID() uint32 {
 | 
				
			||||||
 | 
						if x != nil {
 | 
				
			||||||
 | 
							return x.NamespaceID
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return 0
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (x *Machine) GetRegistered() bool {
 | 
				
			||||||
 | 
						if x != nil {
 | 
				
			||||||
 | 
							return x.Registered
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return false
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (x *Machine) GetRegisterMethod() RegisterMethod {
 | 
				
			||||||
 | 
						if x != nil {
 | 
				
			||||||
 | 
							return x.RegisterMethod
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return RegisterMethod_AUTH_KEY
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (x *Machine) GetAuthKeyID() uint32 {
 | 
				
			||||||
 | 
						if x != nil {
 | 
				
			||||||
 | 
							return x.AuthKeyID
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return 0
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (x *Machine) GetAuthKey() *PreAuthKey {
 | 
				
			||||||
 | 
						if x != nil {
 | 
				
			||||||
 | 
							return x.AuthKey
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (x *Machine) GetLastSeen() *timestamppb.Timestamp {
 | 
				
			||||||
 | 
						if x != nil {
 | 
				
			||||||
 | 
							return x.LastSeen
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (x *Machine) GetLastSuccessfulUpdate() *timestamppb.Timestamp {
 | 
				
			||||||
 | 
						if x != nil {
 | 
				
			||||||
 | 
							return x.LastSuccessfulUpdate
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (x *Machine) GetExpiry() *timestamppb.Timestamp {
 | 
				
			||||||
 | 
						if x != nil {
 | 
				
			||||||
 | 
							return x.Expiry
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (x *Machine) GetHostInfo() []byte {
 | 
				
			||||||
 | 
						if x != nil {
 | 
				
			||||||
 | 
							return x.HostInfo
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (x *Machine) GetEndpoints() []byte {
 | 
				
			||||||
 | 
						if x != nil {
 | 
				
			||||||
 | 
							return x.Endpoints
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (x *Machine) GetEnabledRoutes() []byte {
 | 
				
			||||||
 | 
						if x != nil {
 | 
				
			||||||
 | 
							return x.EnabledRoutes
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (x *Machine) GetCreatedAt() *timestamppb.Timestamp {
 | 
				
			||||||
 | 
						if x != nil {
 | 
				
			||||||
 | 
							return x.CreatedAt
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (x *Machine) GetUpdatedAt() *timestamppb.Timestamp {
 | 
				
			||||||
 | 
						if x != nil {
 | 
				
			||||||
 | 
							return x.UpdatedAt
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (x *Machine) GetDeletedAt() *timestamppb.Timestamp {
 | 
				
			||||||
 | 
						if x != nil {
 | 
				
			||||||
 | 
							return x.DeletedAt
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					var File_v1_headscale_proto protoreflect.FileDescriptor
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					var file_v1_headscale_proto_rawDesc = []byte{
 | 
				
			||||||
 | 
						0x0a, 0x12, 0x76, 0x31, 0x2f, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x70,
 | 
				
			||||||
 | 
						0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0c, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e,
 | 
				
			||||||
 | 
						0x76, 0x31, 0x1a, 0x1f, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f,
 | 
				
			||||||
 | 
						0x62, 0x75, 0x66, 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72,
 | 
				
			||||||
 | 
						0x6f, 0x74, 0x6f, 0x1a, 0x1c, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x61, 0x70, 0x69, 0x2f,
 | 
				
			||||||
 | 
						0x61, 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74,
 | 
				
			||||||
 | 
						0x6f, 0x1a, 0x12, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2f, 0x67, 0x6f, 0x72, 0x6d, 0x2e,
 | 
				
			||||||
 | 
						0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x1f, 0x0a, 0x09, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61,
 | 
				
			||||||
 | 
						0x63, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x4e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09,
 | 
				
			||||||
 | 
						0x52, 0x04, 0x4e, 0x61, 0x6d, 0x65, 0x22, 0xcb, 0x02, 0x0a, 0x0a, 0x50, 0x72, 0x65, 0x41, 0x75,
 | 
				
			||||||
 | 
						0x74, 0x68, 0x4b, 0x65, 0x79, 0x12, 0x0e, 0x0a, 0x02, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28,
 | 
				
			||||||
 | 
						0x04, 0x52, 0x02, 0x49, 0x44, 0x12, 0x10, 0x0a, 0x03, 0x4b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01,
 | 
				
			||||||
 | 
						0x28, 0x09, 0x52, 0x03, 0x4b, 0x65, 0x79, 0x12, 0x20, 0x0a, 0x0b, 0x4e, 0x61, 0x6d, 0x65, 0x73,
 | 
				
			||||||
 | 
						0x70, 0x61, 0x63, 0x65, 0x49, 0x44, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0b, 0x4e, 0x61,
 | 
				
			||||||
 | 
						0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x49, 0x44, 0x12, 0x35, 0x0a, 0x09, 0x4e, 0x61, 0x6d,
 | 
				
			||||||
 | 
						0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x68,
 | 
				
			||||||
 | 
						0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x4e, 0x61, 0x6d, 0x65,
 | 
				
			||||||
 | 
						0x73, 0x70, 0x61, 0x63, 0x65, 0x52, 0x09, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65,
 | 
				
			||||||
 | 
						0x12, 0x1a, 0x0a, 0x08, 0x52, 0x65, 0x75, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x18, 0x05, 0x20, 0x01,
 | 
				
			||||||
 | 
						0x28, 0x08, 0x52, 0x08, 0x52, 0x65, 0x75, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x12, 0x1c, 0x0a, 0x09,
 | 
				
			||||||
 | 
						0x45, 0x70, 0x68, 0x65, 0x6d, 0x65, 0x72, 0x61, 0x6c, 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52,
 | 
				
			||||||
 | 
						0x09, 0x45, 0x70, 0x68, 0x65, 0x6d, 0x65, 0x72, 0x61, 0x6c, 0x12, 0x12, 0x0a, 0x04, 0x55, 0x73,
 | 
				
			||||||
 | 
						0x65, 0x64, 0x18, 0x07, 0x20, 0x01, 0x28, 0x08, 0x52, 0x04, 0x55, 0x73, 0x65, 0x64, 0x12, 0x38,
 | 
				
			||||||
 | 
						0x0a, 0x09, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x18, 0x08, 0x20, 0x01, 0x28,
 | 
				
			||||||
 | 
						0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
 | 
				
			||||||
 | 
						0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x43,
 | 
				
			||||||
 | 
						0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x12, 0x3a, 0x0a, 0x0a, 0x45, 0x78, 0x70, 0x69,
 | 
				
			||||||
 | 
						0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67,
 | 
				
			||||||
 | 
						0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54,
 | 
				
			||||||
 | 
						0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0a, 0x45, 0x78, 0x70, 0x69, 0x72, 0x61,
 | 
				
			||||||
 | 
						0x74, 0x69, 0x6f, 0x6e, 0x22, 0x32, 0x0a, 0x11, 0x47, 0x65, 0x74, 0x4d, 0x61, 0x63, 0x68, 0x69,
 | 
				
			||||||
 | 
						0x6e, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x6d, 0x61, 0x63,
 | 
				
			||||||
 | 
						0x68, 0x69, 0x6e, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x09, 0x6d,
 | 
				
			||||||
 | 
						0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x49, 0x64, 0x22, 0xcd, 0x06, 0x0a, 0x07, 0x4d, 0x61, 0x63,
 | 
				
			||||||
 | 
						0x68, 0x69, 0x6e, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04,
 | 
				
			||||||
 | 
						0x52, 0x02, 0x49, 0x44, 0x12, 0x1e, 0x0a, 0x0a, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x4b,
 | 
				
			||||||
 | 
						0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e,
 | 
				
			||||||
 | 
						0x65, 0x4b, 0x65, 0x79, 0x12, 0x18, 0x0a, 0x07, 0x4e, 0x6f, 0x64, 0x65, 0x4b, 0x65, 0x79, 0x18,
 | 
				
			||||||
 | 
						0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x4e, 0x6f, 0x64, 0x65, 0x4b, 0x65, 0x79, 0x12, 0x1a,
 | 
				
			||||||
 | 
						0x0a, 0x08, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x4b, 0x65, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09,
 | 
				
			||||||
 | 
						0x52, 0x08, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x4b, 0x65, 0x79, 0x12, 0x1c, 0x0a, 0x09, 0x49, 0x50,
 | 
				
			||||||
 | 
						0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x49,
 | 
				
			||||||
 | 
						0x50, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x4e, 0x61, 0x6d, 0x65,
 | 
				
			||||||
 | 
						0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x20, 0x0a, 0x0b,
 | 
				
			||||||
 | 
						0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x49, 0x44, 0x18, 0x07, 0x20, 0x01, 0x28,
 | 
				
			||||||
 | 
						0x0d, 0x52, 0x0b, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x49, 0x44, 0x12, 0x1e,
 | 
				
			||||||
 | 
						0x0a, 0x0a, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x65, 0x64, 0x18, 0x08, 0x20, 0x01,
 | 
				
			||||||
 | 
						0x28, 0x08, 0x52, 0x0a, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x65, 0x64, 0x12, 0x44,
 | 
				
			||||||
 | 
						0x0a, 0x0e, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64,
 | 
				
			||||||
 | 
						0x18, 0x09, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1c, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61,
 | 
				
			||||||
 | 
						0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x4d, 0x65,
 | 
				
			||||||
 | 
						0x74, 0x68, 0x6f, 0x64, 0x52, 0x0e, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x4d, 0x65,
 | 
				
			||||||
 | 
						0x74, 0x68, 0x6f, 0x64, 0x12, 0x1c, 0x0a, 0x09, 0x41, 0x75, 0x74, 0x68, 0x4b, 0x65, 0x79, 0x49,
 | 
				
			||||||
 | 
						0x44, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x09, 0x41, 0x75, 0x74, 0x68, 0x4b, 0x65, 0x79,
 | 
				
			||||||
 | 
						0x49, 0x44, 0x12, 0x32, 0x0a, 0x07, 0x41, 0x75, 0x74, 0x68, 0x4b, 0x65, 0x79, 0x18, 0x0b, 0x20,
 | 
				
			||||||
 | 
						0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e,
 | 
				
			||||||
 | 
						0x76, 0x31, 0x2e, 0x50, 0x72, 0x65, 0x41, 0x75, 0x74, 0x68, 0x4b, 0x65, 0x79, 0x52, 0x07, 0x41,
 | 
				
			||||||
 | 
						0x75, 0x74, 0x68, 0x4b, 0x65, 0x79, 0x12, 0x36, 0x0a, 0x08, 0x4c, 0x61, 0x73, 0x74, 0x53, 0x65,
 | 
				
			||||||
 | 
						0x65, 0x6e, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c,
 | 
				
			||||||
 | 
						0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73,
 | 
				
			||||||
 | 
						0x74, 0x61, 0x6d, 0x70, 0x52, 0x08, 0x4c, 0x61, 0x73, 0x74, 0x53, 0x65, 0x65, 0x6e, 0x12, 0x4e,
 | 
				
			||||||
 | 
						0x0a, 0x14, 0x4c, 0x61, 0x73, 0x74, 0x53, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x66, 0x75, 0x6c,
 | 
				
			||||||
 | 
						0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67,
 | 
				
			||||||
 | 
						0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54,
 | 
				
			||||||
 | 
						0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x14, 0x4c, 0x61, 0x73, 0x74, 0x53, 0x75,
 | 
				
			||||||
 | 
						0x63, 0x63, 0x65, 0x73, 0x73, 0x66, 0x75, 0x6c, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x12, 0x32,
 | 
				
			||||||
 | 
						0x0a, 0x06, 0x45, 0x78, 0x70, 0x69, 0x72, 0x79, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a,
 | 
				
			||||||
 | 
						0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66,
 | 
				
			||||||
 | 
						0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x06, 0x45, 0x78, 0x70, 0x69,
 | 
				
			||||||
 | 
						0x72, 0x79, 0x12, 0x1a, 0x0a, 0x08, 0x48, 0x6f, 0x73, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x18, 0x0f,
 | 
				
			||||||
 | 
						0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x48, 0x6f, 0x73, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x1c,
 | 
				
			||||||
 | 
						0x0a, 0x09, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x73, 0x18, 0x10, 0x20, 0x01, 0x28,
 | 
				
			||||||
 | 
						0x0c, 0x52, 0x09, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x73, 0x12, 0x24, 0x0a, 0x0d,
 | 
				
			||||||
 | 
						0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x18, 0x11, 0x20,
 | 
				
			||||||
 | 
						0x01, 0x28, 0x0c, 0x52, 0x0d, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x52, 0x6f, 0x75, 0x74,
 | 
				
			||||||
 | 
						0x65, 0x73, 0x12, 0x38, 0x0a, 0x09, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x18,
 | 
				
			||||||
 | 
						0x12, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70,
 | 
				
			||||||
 | 
						0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d,
 | 
				
			||||||
 | 
						0x70, 0x52, 0x09, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x12, 0x38, 0x0a, 0x09,
 | 
				
			||||||
 | 
						0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x18, 0x13, 0x20, 0x01, 0x28, 0x0b, 0x32,
 | 
				
			||||||
 | 
						0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75,
 | 
				
			||||||
 | 
						0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x55, 0x70, 0x64,
 | 
				
			||||||
 | 
						0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x12, 0x38, 0x0a, 0x09, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65,
 | 
				
			||||||
 | 
						0x64, 0x41, 0x74, 0x18, 0x14, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67,
 | 
				
			||||||
 | 
						0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65,
 | 
				
			||||||
 | 
						0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x41, 0x74,
 | 
				
			||||||
 | 
						0x3a, 0x06, 0xba, 0xb9, 0x19, 0x02, 0x08, 0x01, 0x2a, 0x31, 0x0a, 0x0e, 0x52, 0x65, 0x67, 0x69,
 | 
				
			||||||
 | 
						0x73, 0x74, 0x65, 0x72, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x12, 0x0c, 0x0a, 0x08, 0x41, 0x55,
 | 
				
			||||||
 | 
						0x54, 0x48, 0x5f, 0x4b, 0x45, 0x59, 0x10, 0x00, 0x12, 0x07, 0x0a, 0x03, 0x43, 0x4c, 0x49, 0x10,
 | 
				
			||||||
 | 
						0x01, 0x12, 0x08, 0x0a, 0x04, 0x4f, 0x49, 0x44, 0x43, 0x10, 0x02, 0x32, 0x7e, 0x0a, 0x10, 0x48,
 | 
				
			||||||
 | 
						0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12,
 | 
				
			||||||
 | 
						0x6a, 0x0a, 0x0a, 0x47, 0x65, 0x74, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x12, 0x1f, 0x2e,
 | 
				
			||||||
 | 
						0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74,
 | 
				
			||||||
 | 
						0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x15,
 | 
				
			||||||
 | 
						0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x61,
 | 
				
			||||||
 | 
						0x63, 0x68, 0x69, 0x6e, 0x65, 0x22, 0x24, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1e, 0x12, 0x1c, 0x2f,
 | 
				
			||||||
 | 
						0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x2f, 0x7b,
 | 
				
			||||||
 | 
						0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x5f, 0x69, 0x64, 0x7d, 0x42, 0x29, 0x5a, 0x27, 0x67,
 | 
				
			||||||
 | 
						0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6a, 0x75, 0x61, 0x6e, 0x66, 0x6f,
 | 
				
			||||||
 | 
						0x6e, 0x74, 0x2f, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2f, 0x67, 0x65, 0x6e,
 | 
				
			||||||
 | 
						0x2f, 0x67, 0x6f, 0x2f, 0x76, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					var (
 | 
				
			||||||
 | 
						file_v1_headscale_proto_rawDescOnce sync.Once
 | 
				
			||||||
 | 
						file_v1_headscale_proto_rawDescData = file_v1_headscale_proto_rawDesc
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func file_v1_headscale_proto_rawDescGZIP() []byte {
 | 
				
			||||||
 | 
						file_v1_headscale_proto_rawDescOnce.Do(func() {
 | 
				
			||||||
 | 
							file_v1_headscale_proto_rawDescData = protoimpl.X.CompressGZIP(file_v1_headscale_proto_rawDescData)
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
						return file_v1_headscale_proto_rawDescData
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					var file_v1_headscale_proto_enumTypes = make([]protoimpl.EnumInfo, 1)
 | 
				
			||||||
 | 
					var file_v1_headscale_proto_msgTypes = make([]protoimpl.MessageInfo, 4)
 | 
				
			||||||
 | 
					var file_v1_headscale_proto_goTypes = []interface{}{
 | 
				
			||||||
 | 
						(RegisterMethod)(0),           // 0: headscale.v1.RegisterMethod
 | 
				
			||||||
 | 
						(*Namespace)(nil),             // 1: headscale.v1.Namespace
 | 
				
			||||||
 | 
						(*PreAuthKey)(nil),            // 2: headscale.v1.PreAuthKey
 | 
				
			||||||
 | 
						(*GetMachineRequest)(nil),     // 3: headscale.v1.GetMachineRequest
 | 
				
			||||||
 | 
						(*Machine)(nil),               // 4: headscale.v1.Machine
 | 
				
			||||||
 | 
						(*timestamppb.Timestamp)(nil), // 5: google.protobuf.Timestamp
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					var file_v1_headscale_proto_depIdxs = []int32{
 | 
				
			||||||
 | 
						1,  // 0: headscale.v1.PreAuthKey.Namespace:type_name -> headscale.v1.Namespace
 | 
				
			||||||
 | 
						5,  // 1: headscale.v1.PreAuthKey.CreatedAt:type_name -> google.protobuf.Timestamp
 | 
				
			||||||
 | 
						5,  // 2: headscale.v1.PreAuthKey.Expiration:type_name -> google.protobuf.Timestamp
 | 
				
			||||||
 | 
						0,  // 3: headscale.v1.Machine.RegisterMethod:type_name -> headscale.v1.RegisterMethod
 | 
				
			||||||
 | 
						2,  // 4: headscale.v1.Machine.AuthKey:type_name -> headscale.v1.PreAuthKey
 | 
				
			||||||
 | 
						5,  // 5: headscale.v1.Machine.LastSeen:type_name -> google.protobuf.Timestamp
 | 
				
			||||||
 | 
						5,  // 6: headscale.v1.Machine.LastSuccessfulUpdate:type_name -> google.protobuf.Timestamp
 | 
				
			||||||
 | 
						5,  // 7: headscale.v1.Machine.Expiry:type_name -> google.protobuf.Timestamp
 | 
				
			||||||
 | 
						5,  // 8: headscale.v1.Machine.CreatedAt:type_name -> google.protobuf.Timestamp
 | 
				
			||||||
 | 
						5,  // 9: headscale.v1.Machine.UpdatedAt:type_name -> google.protobuf.Timestamp
 | 
				
			||||||
 | 
						5,  // 10: headscale.v1.Machine.DeletedAt:type_name -> google.protobuf.Timestamp
 | 
				
			||||||
 | 
						3,  // 11: headscale.v1.HeadscaleService.GetMachine:input_type -> headscale.v1.GetMachineRequest
 | 
				
			||||||
 | 
						4,  // 12: headscale.v1.HeadscaleService.GetMachine:output_type -> headscale.v1.Machine
 | 
				
			||||||
 | 
						12, // [12:13] is the sub-list for method output_type
 | 
				
			||||||
 | 
						11, // [11:12] is the sub-list for method input_type
 | 
				
			||||||
 | 
						11, // [11:11] is the sub-list for extension type_name
 | 
				
			||||||
 | 
						11, // [11:11] is the sub-list for extension extendee
 | 
				
			||||||
 | 
						0,  // [0:11] is the sub-list for field type_name
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func init() { file_v1_headscale_proto_init() }
 | 
				
			||||||
 | 
					func file_v1_headscale_proto_init() {
 | 
				
			||||||
 | 
						if File_v1_headscale_proto != nil {
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if !protoimpl.UnsafeEnabled {
 | 
				
			||||||
 | 
							file_v1_headscale_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
 | 
				
			||||||
 | 
								switch v := v.(*Namespace); i {
 | 
				
			||||||
 | 
								case 0:
 | 
				
			||||||
 | 
									return &v.state
 | 
				
			||||||
 | 
								case 1:
 | 
				
			||||||
 | 
									return &v.sizeCache
 | 
				
			||||||
 | 
								case 2:
 | 
				
			||||||
 | 
									return &v.unknownFields
 | 
				
			||||||
 | 
								default:
 | 
				
			||||||
 | 
									return nil
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							file_v1_headscale_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
 | 
				
			||||||
 | 
								switch v := v.(*PreAuthKey); i {
 | 
				
			||||||
 | 
								case 0:
 | 
				
			||||||
 | 
									return &v.state
 | 
				
			||||||
 | 
								case 1:
 | 
				
			||||||
 | 
									return &v.sizeCache
 | 
				
			||||||
 | 
								case 2:
 | 
				
			||||||
 | 
									return &v.unknownFields
 | 
				
			||||||
 | 
								default:
 | 
				
			||||||
 | 
									return nil
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							file_v1_headscale_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
 | 
				
			||||||
 | 
								switch v := v.(*GetMachineRequest); i {
 | 
				
			||||||
 | 
								case 0:
 | 
				
			||||||
 | 
									return &v.state
 | 
				
			||||||
 | 
								case 1:
 | 
				
			||||||
 | 
									return &v.sizeCache
 | 
				
			||||||
 | 
								case 2:
 | 
				
			||||||
 | 
									return &v.unknownFields
 | 
				
			||||||
 | 
								default:
 | 
				
			||||||
 | 
									return nil
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							file_v1_headscale_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} {
 | 
				
			||||||
 | 
								switch v := v.(*Machine); i {
 | 
				
			||||||
 | 
								case 0:
 | 
				
			||||||
 | 
									return &v.state
 | 
				
			||||||
 | 
								case 1:
 | 
				
			||||||
 | 
									return &v.sizeCache
 | 
				
			||||||
 | 
								case 2:
 | 
				
			||||||
 | 
									return &v.unknownFields
 | 
				
			||||||
 | 
								default:
 | 
				
			||||||
 | 
									return nil
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						type x struct{}
 | 
				
			||||||
 | 
						out := protoimpl.TypeBuilder{
 | 
				
			||||||
 | 
							File: protoimpl.DescBuilder{
 | 
				
			||||||
 | 
								GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
 | 
				
			||||||
 | 
								RawDescriptor: file_v1_headscale_proto_rawDesc,
 | 
				
			||||||
 | 
								NumEnums:      1,
 | 
				
			||||||
 | 
								NumMessages:   4,
 | 
				
			||||||
 | 
								NumExtensions: 0,
 | 
				
			||||||
 | 
								NumServices:   1,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							GoTypes:           file_v1_headscale_proto_goTypes,
 | 
				
			||||||
 | 
							DependencyIndexes: file_v1_headscale_proto_depIdxs,
 | 
				
			||||||
 | 
							EnumInfos:         file_v1_headscale_proto_enumTypes,
 | 
				
			||||||
 | 
							MessageInfos:      file_v1_headscale_proto_msgTypes,
 | 
				
			||||||
 | 
						}.Build()
 | 
				
			||||||
 | 
						File_v1_headscale_proto = out.File
 | 
				
			||||||
 | 
						file_v1_headscale_proto_rawDesc = nil
 | 
				
			||||||
 | 
						file_v1_headscale_proto_goTypes = nil
 | 
				
			||||||
 | 
						file_v1_headscale_proto_depIdxs = nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										185
									
								
								gen/go/v1/headscale.pb.gw.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										185
									
								
								gen/go/v1/headscale.pb.gw.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,185 @@
 | 
				
			|||||||
 | 
					// Code generated by protoc-gen-grpc-gateway. DO NOT EDIT.
 | 
				
			||||||
 | 
					// source: v1/headscale.proto
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/*
 | 
				
			||||||
 | 
					Package v1 is a reverse proxy.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					It translates gRPC into RESTful JSON APIs.
 | 
				
			||||||
 | 
					*/
 | 
				
			||||||
 | 
					package v1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"context"
 | 
				
			||||||
 | 
						"io"
 | 
				
			||||||
 | 
						"net/http"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
 | 
				
			||||||
 | 
						"github.com/grpc-ecosystem/grpc-gateway/v2/utilities"
 | 
				
			||||||
 | 
						"google.golang.org/grpc"
 | 
				
			||||||
 | 
						"google.golang.org/grpc/codes"
 | 
				
			||||||
 | 
						"google.golang.org/grpc/grpclog"
 | 
				
			||||||
 | 
						"google.golang.org/grpc/metadata"
 | 
				
			||||||
 | 
						"google.golang.org/grpc/status"
 | 
				
			||||||
 | 
						"google.golang.org/protobuf/proto"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Suppress "imported and not used" errors
 | 
				
			||||||
 | 
					var _ codes.Code
 | 
				
			||||||
 | 
					var _ io.Reader
 | 
				
			||||||
 | 
					var _ status.Status
 | 
				
			||||||
 | 
					var _ = runtime.String
 | 
				
			||||||
 | 
					var _ = utilities.NewDoubleArray
 | 
				
			||||||
 | 
					var _ = metadata.Join
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func request_HeadscaleService_GetMachine_0(ctx context.Context, marshaler runtime.Marshaler, client HeadscaleServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
 | 
				
			||||||
 | 
						var protoReq GetMachineRequest
 | 
				
			||||||
 | 
						var metadata runtime.ServerMetadata
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						var (
 | 
				
			||||||
 | 
							val string
 | 
				
			||||||
 | 
							ok  bool
 | 
				
			||||||
 | 
							err error
 | 
				
			||||||
 | 
							_   = err
 | 
				
			||||||
 | 
						)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						val, ok = pathParams["machine_id"]
 | 
				
			||||||
 | 
						if !ok {
 | 
				
			||||||
 | 
							return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "machine_id")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						protoReq.MachineId, err = runtime.Uint64(val)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "machine_id", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						msg, err := client.GetMachine(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
 | 
				
			||||||
 | 
						return msg, metadata, err
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func local_request_HeadscaleService_GetMachine_0(ctx context.Context, marshaler runtime.Marshaler, server HeadscaleServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
 | 
				
			||||||
 | 
						var protoReq GetMachineRequest
 | 
				
			||||||
 | 
						var metadata runtime.ServerMetadata
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						var (
 | 
				
			||||||
 | 
							val string
 | 
				
			||||||
 | 
							ok  bool
 | 
				
			||||||
 | 
							err error
 | 
				
			||||||
 | 
							_   = err
 | 
				
			||||||
 | 
						)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						val, ok = pathParams["machine_id"]
 | 
				
			||||||
 | 
						if !ok {
 | 
				
			||||||
 | 
							return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "machine_id")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						protoReq.MachineId, err = runtime.Uint64(val)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "machine_id", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						msg, err := server.GetMachine(ctx, &protoReq)
 | 
				
			||||||
 | 
						return msg, metadata, err
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// RegisterHeadscaleServiceHandlerServer registers the http handlers for service HeadscaleService to "mux".
 | 
				
			||||||
 | 
					// UnaryRPC     :call HeadscaleServiceServer directly.
 | 
				
			||||||
 | 
					// StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906.
 | 
				
			||||||
 | 
					// Note that using this registration option will cause many gRPC library features to stop working. Consider using RegisterHeadscaleServiceHandlerFromEndpoint instead.
 | 
				
			||||||
 | 
					func RegisterHeadscaleServiceHandlerServer(ctx context.Context, mux *runtime.ServeMux, server HeadscaleServiceServer) error {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						mux.Handle("GET", pattern_HeadscaleService_GetMachine_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
 | 
				
			||||||
 | 
							ctx, cancel := context.WithCancel(req.Context())
 | 
				
			||||||
 | 
							defer cancel()
 | 
				
			||||||
 | 
							var stream runtime.ServerTransportStream
 | 
				
			||||||
 | 
							ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
 | 
				
			||||||
 | 
							inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
 | 
				
			||||||
 | 
							rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req, "/headscale.v1.HeadscaleService/GetMachine", runtime.WithHTTPPathPattern("/api/v1/machine/{machine_id}"))
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
 | 
				
			||||||
 | 
								return
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							resp, md, err := local_request_HeadscaleService_GetMachine_0(rctx, inboundMarshaler, server, req, pathParams)
 | 
				
			||||||
 | 
							md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
 | 
				
			||||||
 | 
							ctx = runtime.NewServerMetadataContext(ctx, md)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
 | 
				
			||||||
 | 
								return
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							forward_HeadscaleService_GetMachine_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// RegisterHeadscaleServiceHandlerFromEndpoint is same as RegisterHeadscaleServiceHandler but
 | 
				
			||||||
 | 
					// automatically dials to "endpoint" and closes the connection when "ctx" gets done.
 | 
				
			||||||
 | 
					func RegisterHeadscaleServiceHandlerFromEndpoint(ctx context.Context, mux *runtime.ServeMux, endpoint string, opts []grpc.DialOption) (err error) {
 | 
				
			||||||
 | 
						conn, err := grpc.Dial(endpoint, opts...)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						defer func() {
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								if cerr := conn.Close(); cerr != nil {
 | 
				
			||||||
 | 
									grpclog.Infof("Failed to close conn to %s: %v", endpoint, cerr)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								return
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							go func() {
 | 
				
			||||||
 | 
								<-ctx.Done()
 | 
				
			||||||
 | 
								if cerr := conn.Close(); cerr != nil {
 | 
				
			||||||
 | 
									grpclog.Infof("Failed to close conn to %s: %v", endpoint, cerr)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}()
 | 
				
			||||||
 | 
						}()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return RegisterHeadscaleServiceHandler(ctx, mux, conn)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// RegisterHeadscaleServiceHandler registers the http handlers for service HeadscaleService to "mux".
 | 
				
			||||||
 | 
					// The handlers forward requests to the grpc endpoint over "conn".
 | 
				
			||||||
 | 
					func RegisterHeadscaleServiceHandler(ctx context.Context, mux *runtime.ServeMux, conn *grpc.ClientConn) error {
 | 
				
			||||||
 | 
						return RegisterHeadscaleServiceHandlerClient(ctx, mux, NewHeadscaleServiceClient(conn))
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// RegisterHeadscaleServiceHandlerClient registers the http handlers for service HeadscaleService
 | 
				
			||||||
 | 
					// to "mux". The handlers forward requests to the grpc endpoint over the given implementation of "HeadscaleServiceClient".
 | 
				
			||||||
 | 
					// Note: the gRPC framework executes interceptors within the gRPC handler. If the passed in "HeadscaleServiceClient"
 | 
				
			||||||
 | 
					// doesn't go through the normal gRPC flow (creating a gRPC client etc.) then it will be up to the passed in
 | 
				
			||||||
 | 
					// "HeadscaleServiceClient" to call the correct interceptors.
 | 
				
			||||||
 | 
					func RegisterHeadscaleServiceHandlerClient(ctx context.Context, mux *runtime.ServeMux, client HeadscaleServiceClient) error {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						mux.Handle("GET", pattern_HeadscaleService_GetMachine_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
 | 
				
			||||||
 | 
							ctx, cancel := context.WithCancel(req.Context())
 | 
				
			||||||
 | 
							defer cancel()
 | 
				
			||||||
 | 
							inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
 | 
				
			||||||
 | 
							rctx, err := runtime.AnnotateContext(ctx, mux, req, "/headscale.v1.HeadscaleService/GetMachine", runtime.WithHTTPPathPattern("/api/v1/machine/{machine_id}"))
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
 | 
				
			||||||
 | 
								return
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							resp, md, err := request_HeadscaleService_GetMachine_0(rctx, inboundMarshaler, client, req, pathParams)
 | 
				
			||||||
 | 
							ctx = runtime.NewServerMetadataContext(ctx, md)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
 | 
				
			||||||
 | 
								return
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							forward_HeadscaleService_GetMachine_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					var (
 | 
				
			||||||
 | 
						pattern_HeadscaleService_GetMachine_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 1, 5, 3}, []string{"api", "v1", "machine", "machine_id"}, ""))
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					var (
 | 
				
			||||||
 | 
						forward_HeadscaleService_GetMachine_0 = runtime.ForwardResponseMessage
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
							
								
								
									
										101
									
								
								gen/go/v1/headscale_grpc.pb.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										101
									
								
								gen/go/v1/headscale_grpc.pb.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,101 @@
 | 
				
			|||||||
 | 
					// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					package v1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						context "context"
 | 
				
			||||||
 | 
						grpc "google.golang.org/grpc"
 | 
				
			||||||
 | 
						codes "google.golang.org/grpc/codes"
 | 
				
			||||||
 | 
						status "google.golang.org/grpc/status"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// This is a compile-time assertion to ensure that this generated file
 | 
				
			||||||
 | 
					// is compatible with the grpc package it is being compiled against.
 | 
				
			||||||
 | 
					// Requires gRPC-Go v1.32.0 or later.
 | 
				
			||||||
 | 
					const _ = grpc.SupportPackageIsVersion7
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// HeadscaleServiceClient is the client API for HeadscaleService service.
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
 | 
				
			||||||
 | 
					type HeadscaleServiceClient interface {
 | 
				
			||||||
 | 
						GetMachine(ctx context.Context, in *GetMachineRequest, opts ...grpc.CallOption) (*Machine, error)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type headscaleServiceClient struct {
 | 
				
			||||||
 | 
						cc grpc.ClientConnInterface
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func NewHeadscaleServiceClient(cc grpc.ClientConnInterface) HeadscaleServiceClient {
 | 
				
			||||||
 | 
						return &headscaleServiceClient{cc}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (c *headscaleServiceClient) GetMachine(ctx context.Context, in *GetMachineRequest, opts ...grpc.CallOption) (*Machine, error) {
 | 
				
			||||||
 | 
						out := new(Machine)
 | 
				
			||||||
 | 
						err := c.cc.Invoke(ctx, "/headscale.v1.HeadscaleService/GetMachine", in, out, opts...)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return out, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// HeadscaleServiceServer is the server API for HeadscaleService service.
 | 
				
			||||||
 | 
					// All implementations must embed UnimplementedHeadscaleServiceServer
 | 
				
			||||||
 | 
					// for forward compatibility
 | 
				
			||||||
 | 
					type HeadscaleServiceServer interface {
 | 
				
			||||||
 | 
						GetMachine(context.Context, *GetMachineRequest) (*Machine, error)
 | 
				
			||||||
 | 
						mustEmbedUnimplementedHeadscaleServiceServer()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// UnimplementedHeadscaleServiceServer must be embedded to have forward compatible implementations.
 | 
				
			||||||
 | 
					type UnimplementedHeadscaleServiceServer struct {
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (UnimplementedHeadscaleServiceServer) GetMachine(context.Context, *GetMachineRequest) (*Machine, error) {
 | 
				
			||||||
 | 
						return nil, status.Errorf(codes.Unimplemented, "method GetMachine not implemented")
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					func (UnimplementedHeadscaleServiceServer) mustEmbedUnimplementedHeadscaleServiceServer() {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// UnsafeHeadscaleServiceServer may be embedded to opt out of forward compatibility for this service.
 | 
				
			||||||
 | 
					// Use of this interface is not recommended, as added methods to HeadscaleServiceServer will
 | 
				
			||||||
 | 
					// result in compilation errors.
 | 
				
			||||||
 | 
					type UnsafeHeadscaleServiceServer interface {
 | 
				
			||||||
 | 
						mustEmbedUnimplementedHeadscaleServiceServer()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func RegisterHeadscaleServiceServer(s grpc.ServiceRegistrar, srv HeadscaleServiceServer) {
 | 
				
			||||||
 | 
						s.RegisterService(&HeadscaleService_ServiceDesc, srv)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func _HeadscaleService_GetMachine_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
 | 
				
			||||||
 | 
						in := new(GetMachineRequest)
 | 
				
			||||||
 | 
						if err := dec(in); err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if interceptor == nil {
 | 
				
			||||||
 | 
							return srv.(HeadscaleServiceServer).GetMachine(ctx, in)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						info := &grpc.UnaryServerInfo{
 | 
				
			||||||
 | 
							Server:     srv,
 | 
				
			||||||
 | 
							FullMethod: "/headscale.v1.HeadscaleService/GetMachine",
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						handler := func(ctx context.Context, req interface{}) (interface{}, error) {
 | 
				
			||||||
 | 
							return srv.(HeadscaleServiceServer).GetMachine(ctx, req.(*GetMachineRequest))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return interceptor(ctx, in, info, handler)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// HeadscaleService_ServiceDesc is the grpc.ServiceDesc for HeadscaleService service.
 | 
				
			||||||
 | 
					// It's only intended for direct use with grpc.RegisterService,
 | 
				
			||||||
 | 
					// and not to be introspected or modified (even as a copy)
 | 
				
			||||||
 | 
					var HeadscaleService_ServiceDesc = grpc.ServiceDesc{
 | 
				
			||||||
 | 
						ServiceName: "headscale.v1.HeadscaleService",
 | 
				
			||||||
 | 
						HandlerType: (*HeadscaleServiceServer)(nil),
 | 
				
			||||||
 | 
						Methods: []grpc.MethodDesc{
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								MethodName: "GetMachine",
 | 
				
			||||||
 | 
								Handler:    _HeadscaleService_GetMachine_Handler,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
						Streams:  []grpc.StreamDesc{},
 | 
				
			||||||
 | 
						Metadata: "v1/headscale.proto",
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										210
									
								
								gen/openapiv2/v1/headscale.swagger.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										210
									
								
								gen/openapiv2/v1/headscale.swagger.json
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,210 @@
 | 
				
			|||||||
 | 
					{
 | 
				
			||||||
 | 
					  "swagger": "2.0",
 | 
				
			||||||
 | 
					  "info": {
 | 
				
			||||||
 | 
					    "title": "v1/headscale.proto",
 | 
				
			||||||
 | 
					    "version": "version not set"
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  "tags": [
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					      "name": "HeadscaleService"
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  ],
 | 
				
			||||||
 | 
					  "consumes": [
 | 
				
			||||||
 | 
					    "application/json"
 | 
				
			||||||
 | 
					  ],
 | 
				
			||||||
 | 
					  "produces": [
 | 
				
			||||||
 | 
					    "application/json"
 | 
				
			||||||
 | 
					  ],
 | 
				
			||||||
 | 
					  "paths": {
 | 
				
			||||||
 | 
					    "/api/v1/machine/{machineId}": {
 | 
				
			||||||
 | 
					      "get": {
 | 
				
			||||||
 | 
					        "operationId": "HeadscaleService_GetMachine",
 | 
				
			||||||
 | 
					        "responses": {
 | 
				
			||||||
 | 
					          "200": {
 | 
				
			||||||
 | 
					            "description": "A successful response.",
 | 
				
			||||||
 | 
					            "schema": {
 | 
				
			||||||
 | 
					              "$ref": "#/definitions/v1Machine"
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          "default": {
 | 
				
			||||||
 | 
					            "description": "An unexpected error response.",
 | 
				
			||||||
 | 
					            "schema": {
 | 
				
			||||||
 | 
					              "$ref": "#/definitions/rpcStatus"
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "parameters": [
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            "name": "machineId",
 | 
				
			||||||
 | 
					            "in": "path",
 | 
				
			||||||
 | 
					            "required": true,
 | 
				
			||||||
 | 
					            "type": "string",
 | 
				
			||||||
 | 
					            "format": "uint64"
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        ],
 | 
				
			||||||
 | 
					        "tags": [
 | 
				
			||||||
 | 
					          "HeadscaleService"
 | 
				
			||||||
 | 
					        ]
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  "definitions": {
 | 
				
			||||||
 | 
					    "protobufAny": {
 | 
				
			||||||
 | 
					      "type": "object",
 | 
				
			||||||
 | 
					      "properties": {
 | 
				
			||||||
 | 
					        "@type": {
 | 
				
			||||||
 | 
					          "type": "string"
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      "additionalProperties": {}
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "rpcStatus": {
 | 
				
			||||||
 | 
					      "type": "object",
 | 
				
			||||||
 | 
					      "properties": {
 | 
				
			||||||
 | 
					        "code": {
 | 
				
			||||||
 | 
					          "type": "integer",
 | 
				
			||||||
 | 
					          "format": "int32"
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "message": {
 | 
				
			||||||
 | 
					          "type": "string"
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "details": {
 | 
				
			||||||
 | 
					          "type": "array",
 | 
				
			||||||
 | 
					          "items": {
 | 
				
			||||||
 | 
					            "$ref": "#/definitions/protobufAny"
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "v1Machine": {
 | 
				
			||||||
 | 
					      "type": "object",
 | 
				
			||||||
 | 
					      "properties": {
 | 
				
			||||||
 | 
					        "ID": {
 | 
				
			||||||
 | 
					          "type": "string",
 | 
				
			||||||
 | 
					          "format": "uint64"
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "MachineKey": {
 | 
				
			||||||
 | 
					          "type": "string"
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "NodeKey": {
 | 
				
			||||||
 | 
					          "type": "string"
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "DiscoKey": {
 | 
				
			||||||
 | 
					          "type": "string"
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "IPAddress": {
 | 
				
			||||||
 | 
					          "type": "string"
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "Name": {
 | 
				
			||||||
 | 
					          "type": "string"
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "NamespaceID": {
 | 
				
			||||||
 | 
					          "type": "integer",
 | 
				
			||||||
 | 
					          "format": "int64"
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "Registered": {
 | 
				
			||||||
 | 
					          "type": "boolean"
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "RegisterMethod": {
 | 
				
			||||||
 | 
					          "$ref": "#/definitions/v1RegisterMethod"
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "AuthKeyID": {
 | 
				
			||||||
 | 
					          "type": "integer",
 | 
				
			||||||
 | 
					          "format": "int64"
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "AuthKey": {
 | 
				
			||||||
 | 
					          "$ref": "#/definitions/v1PreAuthKey"
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "LastSeen": {
 | 
				
			||||||
 | 
					          "type": "string",
 | 
				
			||||||
 | 
					          "format": "date-time"
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "LastSuccessfulUpdate": {
 | 
				
			||||||
 | 
					          "type": "string",
 | 
				
			||||||
 | 
					          "format": "date-time"
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "Expiry": {
 | 
				
			||||||
 | 
					          "type": "string",
 | 
				
			||||||
 | 
					          "format": "date-time"
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "HostInfo": {
 | 
				
			||||||
 | 
					          "type": "string",
 | 
				
			||||||
 | 
					          "format": "byte"
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "Endpoints": {
 | 
				
			||||||
 | 
					          "type": "string",
 | 
				
			||||||
 | 
					          "format": "byte"
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "EnabledRoutes": {
 | 
				
			||||||
 | 
					          "type": "string",
 | 
				
			||||||
 | 
					          "format": "byte"
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "CreatedAt": {
 | 
				
			||||||
 | 
					          "type": "string",
 | 
				
			||||||
 | 
					          "format": "date-time"
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "UpdatedAt": {
 | 
				
			||||||
 | 
					          "type": "string",
 | 
				
			||||||
 | 
					          "format": "date-time"
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "DeletedAt": {
 | 
				
			||||||
 | 
					          "type": "string",
 | 
				
			||||||
 | 
					          "format": "date-time"
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "v1Namespace": {
 | 
				
			||||||
 | 
					      "type": "object",
 | 
				
			||||||
 | 
					      "properties": {
 | 
				
			||||||
 | 
					        "Name": {
 | 
				
			||||||
 | 
					          "type": "string"
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "v1PreAuthKey": {
 | 
				
			||||||
 | 
					      "type": "object",
 | 
				
			||||||
 | 
					      "properties": {
 | 
				
			||||||
 | 
					        "ID": {
 | 
				
			||||||
 | 
					          "type": "string",
 | 
				
			||||||
 | 
					          "format": "uint64"
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "Key": {
 | 
				
			||||||
 | 
					          "type": "string"
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "NamespaceID": {
 | 
				
			||||||
 | 
					          "type": "integer",
 | 
				
			||||||
 | 
					          "format": "int64"
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "Namespace": {
 | 
				
			||||||
 | 
					          "$ref": "#/definitions/v1Namespace"
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "Reusable": {
 | 
				
			||||||
 | 
					          "type": "boolean"
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "Ephemeral": {
 | 
				
			||||||
 | 
					          "type": "boolean"
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "Used": {
 | 
				
			||||||
 | 
					          "type": "boolean"
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "CreatedAt": {
 | 
				
			||||||
 | 
					          "type": "string",
 | 
				
			||||||
 | 
					          "format": "date-time"
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "Expiration": {
 | 
				
			||||||
 | 
					          "type": "string",
 | 
				
			||||||
 | 
					          "format": "date-time"
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "v1RegisterMethod": {
 | 
				
			||||||
 | 
					      "type": "string",
 | 
				
			||||||
 | 
					      "enum": [
 | 
				
			||||||
 | 
					        "AUTH_KEY",
 | 
				
			||||||
 | 
					        "CLI",
 | 
				
			||||||
 | 
					        "OIDC"
 | 
				
			||||||
 | 
					      ],
 | 
				
			||||||
 | 
					      "default": "AUTH_KEY"
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										13
									
								
								go.mod
									
									
									
									
									
								
							
							
						
						
									
										13
									
								
								go.mod
									
									
									
									
									
								
							@ -7,23 +7,28 @@ require (
 | 
				
			|||||||
	github.com/Microsoft/go-winio v0.5.0 // indirect
 | 
						github.com/Microsoft/go-winio v0.5.0 // indirect
 | 
				
			||||||
	github.com/cenkalti/backoff/v4 v4.1.1 // indirect
 | 
						github.com/cenkalti/backoff/v4 v4.1.1 // indirect
 | 
				
			||||||
	github.com/containerd/continuity v0.1.0 // indirect
 | 
						github.com/containerd/continuity v0.1.0 // indirect
 | 
				
			||||||
 | 
						github.com/coreos/go-oidc/v3 v3.1.0
 | 
				
			||||||
	github.com/docker/cli v20.10.8+incompatible // indirect
 | 
						github.com/docker/cli v20.10.8+incompatible // indirect
 | 
				
			||||||
	github.com/docker/docker v20.10.8+incompatible // indirect
 | 
						github.com/docker/docker v20.10.8+incompatible // indirect
 | 
				
			||||||
	github.com/efekarakus/termcolor v1.0.1
 | 
						github.com/efekarakus/termcolor v1.0.1
 | 
				
			||||||
	github.com/fatih/set v0.2.1 // indirect
 | 
						github.com/fatih/set v0.2.1
 | 
				
			||||||
	github.com/gin-gonic/gin v1.7.4
 | 
						github.com/gin-gonic/gin v1.7.4
 | 
				
			||||||
	github.com/gofrs/uuid v4.0.0+incompatible
 | 
						github.com/gofrs/uuid v4.0.0+incompatible
 | 
				
			||||||
	github.com/google/go-github v17.0.0+incompatible // indirect
 | 
						github.com/google/go-github v17.0.0+incompatible // indirect
 | 
				
			||||||
	github.com/google/go-querystring v1.1.0 // indirect
 | 
						github.com/google/go-querystring v1.1.0 // indirect
 | 
				
			||||||
 | 
						github.com/grpc-ecosystem/grpc-gateway/v2 v2.6.0
 | 
				
			||||||
	github.com/hako/durafmt v0.0.0-20210608085754-5c1018a4e16b
 | 
						github.com/hako/durafmt v0.0.0-20210608085754-5c1018a4e16b
 | 
				
			||||||
 | 
						github.com/infobloxopen/protoc-gen-gorm v1.0.1
 | 
				
			||||||
	github.com/klauspost/compress v1.13.5
 | 
						github.com/klauspost/compress v1.13.5
 | 
				
			||||||
	github.com/lib/pq v1.10.3 // indirect
 | 
						github.com/lib/pq v1.10.3 // indirect
 | 
				
			||||||
	github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6 // indirect
 | 
						github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6 // indirect
 | 
				
			||||||
	github.com/opencontainers/runc v1.0.2 // indirect
 | 
						github.com/opencontainers/runc v1.0.2 // indirect
 | 
				
			||||||
	github.com/ory/dockertest/v3 v3.7.0
 | 
						github.com/ory/dockertest/v3 v3.7.0
 | 
				
			||||||
 | 
						github.com/patrickmn/go-cache v2.1.0+incompatible
 | 
				
			||||||
	github.com/prometheus/client_golang v1.11.0
 | 
						github.com/prometheus/client_golang v1.11.0
 | 
				
			||||||
	github.com/pterm/pterm v0.12.30
 | 
						github.com/pterm/pterm v0.12.30
 | 
				
			||||||
	github.com/rs/zerolog v1.25.0
 | 
						github.com/rs/zerolog v1.25.0
 | 
				
			||||||
 | 
						github.com/soheilhy/cmux v0.1.5
 | 
				
			||||||
	github.com/spf13/cobra v1.2.1
 | 
						github.com/spf13/cobra v1.2.1
 | 
				
			||||||
	github.com/spf13/viper v1.8.1
 | 
						github.com/spf13/viper v1.8.1
 | 
				
			||||||
	github.com/stretchr/testify v1.7.0
 | 
						github.com/stretchr/testify v1.7.0
 | 
				
			||||||
@ -33,7 +38,13 @@ require (
 | 
				
			|||||||
	github.com/zsais/go-gin-prometheus v0.1.0
 | 
						github.com/zsais/go-gin-prometheus v0.1.0
 | 
				
			||||||
	golang.org/x/crypto v0.0.0-20210817164053-32db794688a5
 | 
						golang.org/x/crypto v0.0.0-20210817164053-32db794688a5
 | 
				
			||||||
	golang.org/x/net v0.0.0-20210913180222-943fd674d43e // indirect
 | 
						golang.org/x/net v0.0.0-20210913180222-943fd674d43e // indirect
 | 
				
			||||||
 | 
						golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f
 | 
				
			||||||
 | 
						golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
 | 
				
			||||||
	golang.org/x/sys v0.0.0-20210910150752-751e447fb3d0 // indirect
 | 
						golang.org/x/sys v0.0.0-20210910150752-751e447fb3d0 // indirect
 | 
				
			||||||
 | 
						google.golang.org/genproto v0.0.0-20210903162649-d08c68adba83
 | 
				
			||||||
 | 
						google.golang.org/grpc v1.40.0
 | 
				
			||||||
 | 
						google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0
 | 
				
			||||||
 | 
						google.golang.org/protobuf v1.27.1
 | 
				
			||||||
	gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c
 | 
						gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c
 | 
				
			||||||
	gopkg.in/yaml.v2 v2.4.0
 | 
						gopkg.in/yaml.v2 v2.4.0
 | 
				
			||||||
	gorm.io/datatypes v1.0.2
 | 
						gorm.io/datatypes v1.0.2
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										94
									
								
								go.sum
									
									
									
									
									
								
							
							
						
						
									
										94
									
								
								go.sum
									
									
									
									
									
								
							@ -38,6 +38,7 @@ cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0Zeo
 | 
				
			|||||||
cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
 | 
					cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
 | 
				
			||||||
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
 | 
					cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
 | 
				
			||||||
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
 | 
					cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
 | 
				
			||||||
 | 
					contrib.go.opencensus.io/exporter/ocagent v0.7.0/go.mod h1:IshRmMJBhDfFj5Y67nVhMYTTIze91RUeT73ipWKs/GY=
 | 
				
			||||||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
 | 
					dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
 | 
				
			||||||
github.com/AlecAivazis/survey/v2 v2.3.2 h1:TqTB+aDDCLYhf9/bD2TwSO8u8jDSmMUd2SUVO4gCnU8=
 | 
					github.com/AlecAivazis/survey/v2 v2.3.2 h1:TqTB+aDDCLYhf9/bD2TwSO8u8jDSmMUd2SUVO4gCnU8=
 | 
				
			||||||
github.com/AlecAivazis/survey/v2 v2.3.2/go.mod h1:TH2kPCDU3Kqq7pLbnCWwZXDBjnhZtmsCle5EiYDJ2fg=
 | 
					github.com/AlecAivazis/survey/v2 v2.3.2/go.mod h1:TH2kPCDU3Kqq7pLbnCWwZXDBjnhZtmsCle5EiYDJ2fg=
 | 
				
			||||||
@ -46,6 +47,7 @@ github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOEl
 | 
				
			|||||||
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
 | 
					github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
 | 
				
			||||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
 | 
					github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
 | 
				
			||||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
 | 
					github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
 | 
				
			||||||
 | 
					github.com/DATA-DOG/go-sqlmock v1.5.0/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM=
 | 
				
			||||||
github.com/Djarvur/go-err113 v0.0.0-20200511133814-5174e21577d5/go.mod h1:4UJr5HIiMZrwgkSPdsjy2uOQExX/WEILpIrO9UPGuXs=
 | 
					github.com/Djarvur/go-err113 v0.0.0-20200511133814-5174e21577d5/go.mod h1:4UJr5HIiMZrwgkSPdsjy2uOQExX/WEILpIrO9UPGuXs=
 | 
				
			||||||
github.com/Djarvur/go-err113 v0.1.0/go.mod h1:4UJr5HIiMZrwgkSPdsjy2uOQExX/WEILpIrO9UPGuXs=
 | 
					github.com/Djarvur/go-err113 v0.1.0/go.mod h1:4UJr5HIiMZrwgkSPdsjy2uOQExX/WEILpIrO9UPGuXs=
 | 
				
			||||||
github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0=
 | 
					github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0=
 | 
				
			||||||
@ -70,6 +72,7 @@ github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 h1:TngWCqHvy9oXAN6lEV
 | 
				
			|||||||
github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5/go.mod h1:lmUJ/7eu/Q8D7ML55dXQrVaamCz2vxCfdQBasLZfHKk=
 | 
					github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5/go.mod h1:lmUJ/7eu/Q8D7ML55dXQrVaamCz2vxCfdQBasLZfHKk=
 | 
				
			||||||
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
 | 
					github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
 | 
				
			||||||
github.com/OpenPeeDeeP/depguard v1.0.1/go.mod h1:xsIw86fROiiwelg+jB2uM9PiKihMMmUx/1V+TNhjQvM=
 | 
					github.com/OpenPeeDeeP/depguard v1.0.1/go.mod h1:xsIw86fROiiwelg+jB2uM9PiKihMMmUx/1V+TNhjQvM=
 | 
				
			||||||
 | 
					github.com/PuerkitoBio/goquery v1.5.1/go.mod h1:GsLWisAFVj4WgDibEWF4pvYnkVQBpKBKeU+7zCJoLcc=
 | 
				
			||||||
github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo=
 | 
					github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo=
 | 
				
			||||||
github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI=
 | 
					github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI=
 | 
				
			||||||
github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg=
 | 
					github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg=
 | 
				
			||||||
@ -84,6 +87,7 @@ github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRF
 | 
				
			|||||||
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
 | 
					github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
 | 
				
			||||||
github.com/alexbrainman/sspi v0.0.0-20210105120005-909beea2cc74/go.mod h1:cEWa1LVoE5KvSD9ONXsZrj0z6KqySlCCNKHlLzbqAt4=
 | 
					github.com/alexbrainman/sspi v0.0.0-20210105120005-909beea2cc74/go.mod h1:cEWa1LVoE5KvSD9ONXsZrj0z6KqySlCCNKHlLzbqAt4=
 | 
				
			||||||
github.com/andybalholm/brotli v1.0.0/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y=
 | 
					github.com/andybalholm/brotli v1.0.0/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y=
 | 
				
			||||||
 | 
					github.com/andybalholm/cascadia v1.1.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y=
 | 
				
			||||||
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
 | 
					github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
 | 
				
			||||||
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4=
 | 
					github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4=
 | 
				
			||||||
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
 | 
					github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
 | 
				
			||||||
@ -105,12 +109,14 @@ github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24
 | 
				
			|||||||
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
 | 
					github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
 | 
				
			||||||
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
 | 
					github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
 | 
				
			||||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
 | 
					github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
 | 
				
			||||||
 | 
					github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d/go.mod h1:6QX/PXZ00z/TKoufEY6K/a0k6AhaJrQKdFe6OfVXsa4=
 | 
				
			||||||
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
 | 
					github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
 | 
				
			||||||
github.com/bits-and-blooms/bitset v1.2.0/go.mod h1:gIdJ4wp64HaoK2YrL1Q5/N7Y16edYb8uY+O0FJTyyDA=
 | 
					github.com/bits-and-blooms/bitset v1.2.0/go.mod h1:gIdJ4wp64HaoK2YrL1Q5/N7Y16edYb8uY+O0FJTyyDA=
 | 
				
			||||||
github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84=
 | 
					github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84=
 | 
				
			||||||
github.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqOes/6LfM=
 | 
					github.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqOes/6LfM=
 | 
				
			||||||
github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb/go.mod h1:PkYb9DJNAwrSvRx5DYA+gUcOIgTGVMNkfSCbZM8cWpI=
 | 
					github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb/go.mod h1:PkYb9DJNAwrSvRx5DYA+gUcOIgTGVMNkfSCbZM8cWpI=
 | 
				
			||||||
github.com/bombsimon/wsl/v3 v3.1.0/go.mod h1:st10JtZYLE4D5sC7b8xV4zTKZwAQjCH/Hy2Pm1FNZIc=
 | 
					github.com/bombsimon/wsl/v3 v3.1.0/go.mod h1:st10JtZYLE4D5sC7b8xV4zTKZwAQjCH/Hy2Pm1FNZIc=
 | 
				
			||||||
 | 
					github.com/bufbuild/buf v0.37.0/go.mod h1:lQ1m2HkIaGOFba6w/aC3KYBHhKEOESP3gaAEpS3dAFM=
 | 
				
			||||||
github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ=
 | 
					github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ=
 | 
				
			||||||
github.com/cavaliercoder/go-cpio v0.0.0-20180626203310-925f9528c45e/go.mod h1:oDpT4efm8tSYHXV5tHSdRvBet/b/QzxZ+XyyPehvm3A=
 | 
					github.com/cavaliercoder/go-cpio v0.0.0-20180626203310-925f9528c45e/go.mod h1:oDpT4efm8tSYHXV5tHSdRvBet/b/QzxZ+XyyPehvm3A=
 | 
				
			||||||
github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4=
 | 
					github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4=
 | 
				
			||||||
@ -134,6 +140,7 @@ github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDk
 | 
				
			|||||||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
 | 
					github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
 | 
				
			||||||
github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
 | 
					github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
 | 
				
			||||||
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
 | 
					github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
 | 
				
			||||||
 | 
					github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
 | 
				
			||||||
github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I=
 | 
					github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I=
 | 
				
			||||||
github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ=
 | 
					github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ=
 | 
				
			||||||
github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8=
 | 
					github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8=
 | 
				
			||||||
@ -146,6 +153,8 @@ github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkE
 | 
				
			|||||||
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
 | 
					github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
 | 
				
			||||||
github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
 | 
					github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
 | 
				
			||||||
github.com/coreos/go-iptables v0.6.0/go.mod h1:Qe8Bv2Xik5FyTXwgIbLAnv2sWSBmvWdFETJConOQ//Q=
 | 
					github.com/coreos/go-iptables v0.6.0/go.mod h1:Qe8Bv2Xik5FyTXwgIbLAnv2sWSBmvWdFETJConOQ//Q=
 | 
				
			||||||
 | 
					github.com/coreos/go-oidc/v3 v3.1.0 h1:6avEvcdvTa1qYsOZ6I5PRkSYHzpTNWgKYmaJfaYbrRw=
 | 
				
			||||||
 | 
					github.com/coreos/go-oidc/v3 v3.1.0/go.mod h1:rEJ/idjfUyfkBit1eI1fvyr+64/g9dcKpAm8MJMesvo=
 | 
				
			||||||
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
 | 
					github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
 | 
				
			||||||
github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
 | 
					github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
 | 
				
			||||||
github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
 | 
					github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
 | 
				
			||||||
@ -168,9 +177,12 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
 | 
				
			|||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
 | 
					github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
 | 
				
			||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 | 
					github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 | 
				
			||||||
github.com/denis-tingajkin/go-header v0.3.1/go.mod h1:sq/2IxMhaZX+RRcgHfCRx/m0M5na0fBt4/CRe7Lrji0=
 | 
					github.com/denis-tingajkin/go-header v0.3.1/go.mod h1:sq/2IxMhaZX+RRcgHfCRx/m0M5na0fBt4/CRe7Lrji0=
 | 
				
			||||||
 | 
					github.com/denisenkom/go-mssqldb v0.0.0-20191124224453-732737034ffd/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU=
 | 
				
			||||||
 | 
					github.com/denisenkom/go-mssqldb v0.9.0/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU=
 | 
				
			||||||
github.com/denisenkom/go-mssqldb v0.10.0 h1:QykgLZBorFE95+gO3u9esLd0BmbvpWp0/waNNZfHBM8=
 | 
					github.com/denisenkom/go-mssqldb v0.10.0 h1:QykgLZBorFE95+gO3u9esLd0BmbvpWp0/waNNZfHBM8=
 | 
				
			||||||
github.com/denisenkom/go-mssqldb v0.10.0/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU=
 | 
					github.com/denisenkom/go-mssqldb v0.10.0/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU=
 | 
				
			||||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
 | 
					github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
 | 
				
			||||||
 | 
					github.com/dgrijalva/jwt-go v3.2.1-0.20200107013213-dc14462fd587+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
 | 
				
			||||||
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
 | 
					github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
 | 
				
			||||||
github.com/docker/cli v20.10.7+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
 | 
					github.com/docker/cli v20.10.7+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
 | 
				
			||||||
github.com/docker/cli v20.10.8+incompatible h1:/zO/6y9IOpcehE49yMRTV9ea0nBpb8OeqSskXLNfH1E=
 | 
					github.com/docker/cli v20.10.8+incompatible h1:/zO/6y9IOpcehE49yMRTV9ea0nBpb8OeqSskXLNfH1E=
 | 
				
			||||||
@ -199,7 +211,10 @@ github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1m
 | 
				
			|||||||
github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po=
 | 
					github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po=
 | 
				
			||||||
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
 | 
					github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
 | 
				
			||||||
github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
 | 
					github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
 | 
				
			||||||
 | 
					github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ=
 | 
				
			||||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
 | 
					github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
 | 
				
			||||||
 | 
					github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5 h1:Yzb9+7DPaBjB8zlTR87/ElzFsnQfuHnVUVqpZZIcV5Y=
 | 
				
			||||||
 | 
					github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5/go.mod h1:a2zkGnVExMxdzMo3M0Hi/3sEU+cWnZpSni0O6/Yb/P0=
 | 
				
			||||||
github.com/fanliao/go-promise v0.0.0-20141029170127-1890db352a72/go.mod h1:PjfxuH4FZdUyfMdtBio2lsRr1AKEaVPwelzuHuh8Lqc=
 | 
					github.com/fanliao/go-promise v0.0.0-20141029170127-1890db352a72/go.mod h1:PjfxuH4FZdUyfMdtBio2lsRr1AKEaVPwelzuHuh8Lqc=
 | 
				
			||||||
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
 | 
					github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
 | 
				
			||||||
github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
 | 
					github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
 | 
				
			||||||
@ -214,6 +229,7 @@ github.com/frankban/quicktest v1.13.0/go.mod h1:qLE0fzW0VuyUAJgPU19zByoIr0HtCHN/
 | 
				
			|||||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
 | 
					github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
 | 
				
			||||||
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
 | 
					github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
 | 
				
			||||||
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
 | 
					github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
 | 
				
			||||||
 | 
					github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
 | 
				
			||||||
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=
 | 
				
			||||||
@ -249,6 +265,7 @@ github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+
 | 
				
			|||||||
github.com/go-playground/validator/v10 v10.4.1 h1:pH2c5ADXtd66mxoE0Zm9SUhxE20r7aM3F26W0hOn+GE=
 | 
					github.com/go-playground/validator/v10 v10.4.1 h1:pH2c5ADXtd66mxoE0Zm9SUhxE20r7aM3F26W0hOn+GE=
 | 
				
			||||||
github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4=
 | 
					github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4=
 | 
				
			||||||
github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
 | 
					github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
 | 
				
			||||||
 | 
					github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
 | 
				
			||||||
github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE=
 | 
					github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE=
 | 
				
			||||||
github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
 | 
					github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
 | 
				
			||||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
 | 
					github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
 | 
				
			||||||
@ -278,6 +295,8 @@ github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69
 | 
				
			|||||||
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe h1:lXe2qZdvpiX5WZkZR4hgp4KJVfY3nMkvmwbVkpv1rVY=
 | 
					github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe h1:lXe2qZdvpiX5WZkZR4hgp4KJVfY3nMkvmwbVkpv1rVY=
 | 
				
			||||||
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
 | 
					github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
 | 
				
			||||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
 | 
					github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
 | 
				
			||||||
 | 
					github.com/golang/glog v1.0.0 h1:nfP3RFugxnNRyKgeWd4oI1nYvXpxrx8ck8ZrcizshdQ=
 | 
				
			||||||
 | 
					github.com/golang/glog v1.0.0/go.mod h1:EWib/APOK0SL3dFbYqvxE3UYd8E6s1ouQ7iEp/0LWV4=
 | 
				
			||||||
github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
 | 
					github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
 | 
				
			||||||
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
 | 
					github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
 | 
				
			||||||
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
 | 
					github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
 | 
				
			||||||
@ -370,6 +389,7 @@ github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaU
 | 
				
			|||||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=
 | 
					github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=
 | 
				
			||||||
github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
 | 
					github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
 | 
				
			||||||
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
 | 
					github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
 | 
				
			||||||
 | 
					github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
 | 
				
			||||||
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
 | 
					github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
 | 
				
			||||||
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
 | 
					github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
 | 
				
			||||||
github.com/gookit/color v1.3.1/go.mod h1:R3ogXq2B9rTbXoSHJ1HyUVAZ3poOJHpd9nQmyGZsfvQ=
 | 
					github.com/gookit/color v1.3.1/go.mod h1:R3ogXq2B9rTbXoSHJ1HyUVAZ3poOJHpd9nQmyGZsfvQ=
 | 
				
			||||||
@ -377,6 +397,7 @@ github.com/gookit/color v1.4.2 h1:tXy44JFSFkKnELV6WaMo/lLfu/meqITX3iAV52do7lk=
 | 
				
			|||||||
github.com/gookit/color v1.4.2/go.mod h1:fqRyamkC1W8uxl+lxCQxOT09l/vYfZ+QeiX3rKQHCoQ=
 | 
					github.com/gookit/color v1.4.2/go.mod h1:fqRyamkC1W8uxl+lxCQxOT09l/vYfZ+QeiX3rKQHCoQ=
 | 
				
			||||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
 | 
					github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
 | 
				
			||||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
 | 
					github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
 | 
				
			||||||
 | 
					github.com/gordonklaus/ineffassign v0.0.0-20200309095847-7953dde2c7bf/go.mod h1:cuNKsD1zp2v6XfE/orVX2QE1LC+i254ceGcVeDT3pTU=
 | 
				
			||||||
github.com/goreleaser/chglog v0.1.2/go.mod h1:tTZsFuSZK4epDXfjMkxzcGbrIOXprf0JFp47BjIr3B8=
 | 
					github.com/goreleaser/chglog v0.1.2/go.mod h1:tTZsFuSZK4epDXfjMkxzcGbrIOXprf0JFp47BjIr3B8=
 | 
				
			||||||
github.com/goreleaser/fileglob v0.3.1/go.mod h1:kNcPrPzjCp+Ox3jmXLU5QEsjhqrtLBm6OnXAif8KRl8=
 | 
					github.com/goreleaser/fileglob v0.3.1/go.mod h1:kNcPrPzjCp+Ox3jmXLU5QEsjhqrtLBm6OnXAif8KRl8=
 | 
				
			||||||
github.com/goreleaser/nfpm v1.10.3/go.mod h1:EEC7YD5wi+ol0MiAshpgPANBOkjXDl7wqTLVk68OBsk=
 | 
					github.com/goreleaser/nfpm v1.10.3/go.mod h1:EEC7YD5wi+ol0MiAshpgPANBOkjXDl7wqTLVk68OBsk=
 | 
				
			||||||
@ -394,10 +415,17 @@ github.com/gostaticanalysis/comment v1.3.0/go.mod h1:xMicKDx7XRXYdVwY9f9wQpDJVnq
 | 
				
			|||||||
github.com/gostaticanalysis/comment v1.4.1/go.mod h1:ih6ZxzTHLdadaiSnF5WY3dxUoXfXAlTaRzuaNDlSado=
 | 
					github.com/gostaticanalysis/comment v1.4.1/go.mod h1:ih6ZxzTHLdadaiSnF5WY3dxUoXfXAlTaRzuaNDlSado=
 | 
				
			||||||
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
 | 
					github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
 | 
				
			||||||
github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
 | 
					github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
 | 
				
			||||||
 | 
					github.com/grpc-ecosystem/go-grpc-middleware v1.2.2/go.mod h1:EaizFBKfUKtMIF5iaDEhniwNedqGo9FuLFzppDr3uwI=
 | 
				
			||||||
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
 | 
					github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
 | 
				
			||||||
github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
 | 
					github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
 | 
				
			||||||
github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
 | 
					github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
 | 
				
			||||||
 | 
					github.com/grpc-ecosystem/grpc-gateway v1.14.6/go.mod h1:zdiPV4Yse/1gnckTHtghG4GkDEdKCRJduHpTxT3/jcw=
 | 
				
			||||||
 | 
					github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo=
 | 
				
			||||||
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
 | 
					github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
 | 
				
			||||||
 | 
					github.com/grpc-ecosystem/grpc-gateway/v2 v2.3.0/go.mod h1:d2gYTOTUQklu06xp0AJYYmRdTVU1VKrqhkYfYag2L08=
 | 
				
			||||||
 | 
					github.com/grpc-ecosystem/grpc-gateway/v2 v2.4.0/go.mod h1:IOyTYjcIO0rkmnGBfJTL0NJ11exy/Tc2QEuv7hCXp24=
 | 
				
			||||||
 | 
					github.com/grpc-ecosystem/grpc-gateway/v2 v2.6.0 h1:rgxjzoDmDXw5q8HONgyHhBas4to0/XWRo/gPpJhsUNQ=
 | 
				
			||||||
 | 
					github.com/grpc-ecosystem/grpc-gateway/v2 v2.6.0/go.mod h1:qrJPVzv9YlhsrxJc3P/Q85nr0w1lIRikTl4JlhdDH5w=
 | 
				
			||||||
github.com/hako/durafmt v0.0.0-20210608085754-5c1018a4e16b h1:wDUNC2eKiL35DbLvsDhiblTUXHxcOPwQSCzi7xpQUN4=
 | 
					github.com/hako/durafmt v0.0.0-20210608085754-5c1018a4e16b h1:wDUNC2eKiL35DbLvsDhiblTUXHxcOPwQSCzi7xpQUN4=
 | 
				
			||||||
github.com/hako/durafmt v0.0.0-20210608085754-5c1018a4e16b/go.mod h1:VzxiSdG6j1pi7rwGm/xYI5RbtpBgM8sARDXlvEvxlu0=
 | 
					github.com/hako/durafmt v0.0.0-20210608085754-5c1018a4e16b/go.mod h1:VzxiSdG6j1pi7rwGm/xYI5RbtpBgM8sARDXlvEvxlu0=
 | 
				
			||||||
github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q=
 | 
					github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q=
 | 
				
			||||||
@ -441,6 +469,9 @@ github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH
 | 
				
			|||||||
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
 | 
					github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
 | 
				
			||||||
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
 | 
					github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
 | 
				
			||||||
github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo=
 | 
					github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo=
 | 
				
			||||||
 | 
					github.com/infobloxopen/atlas-app-toolkit v0.24.1-0.20210416193901-4c7518b07e08/go.mod h1:9BTHnpff654rY1J8KxSUOLJ+ZUDn2Vi3mmk26gQDo1M=
 | 
				
			||||||
 | 
					github.com/infobloxopen/protoc-gen-gorm v1.0.1 h1:IjvQ02gZSll+CjpWjxkLqrpxnvKAGfs5dXRJEpfZx2s=
 | 
				
			||||||
 | 
					github.com/infobloxopen/protoc-gen-gorm v1.0.1/go.mod h1:gTu86stnDQXwcNqLG9WNJfl3IPUIhxmGNqJ8z4826uo=
 | 
				
			||||||
github.com/insomniacslk/dhcp v0.0.0-20210621130208-1cac67f12b1e/go.mod h1:h+MxyHxRg9NH3terB1nfRIUaQEcI0XOVkdR9LNBlp8E=
 | 
					github.com/insomniacslk/dhcp v0.0.0-20210621130208-1cac67f12b1e/go.mod h1:h+MxyHxRg9NH3terB1nfRIUaQEcI0XOVkdR9LNBlp8E=
 | 
				
			||||||
github.com/jackc/chunkreader v1.0.0 h1:4s39bBR8ByfqH+DKm8rQA3E1LHZWB9XWcrz8fqaZbe0=
 | 
					github.com/jackc/chunkreader v1.0.0 h1:4s39bBR8ByfqH+DKm8rQA3E1LHZWB9XWcrz8fqaZbe0=
 | 
				
			||||||
github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo=
 | 
					github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo=
 | 
				
			||||||
@ -508,9 +539,13 @@ github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dv
 | 
				
			|||||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
 | 
					github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
 | 
				
			||||||
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
 | 
					github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
 | 
				
			||||||
github.com/jgautheron/goconst v0.0.0-20201117150253-ccae5bf973f3/go.mod h1:aAosetZ5zaeC/2EfMeRswtxUFBpe2Hr7HzkgX4fanO4=
 | 
					github.com/jgautheron/goconst v0.0.0-20201117150253-ccae5bf973f3/go.mod h1:aAosetZ5zaeC/2EfMeRswtxUFBpe2Hr7HzkgX4fanO4=
 | 
				
			||||||
 | 
					github.com/jhump/protoreflect v1.8.1/go.mod h1:7GcYQDdMU/O/BBrl/cX6PNHpXh6cenjd8pneu5yW7Tg=
 | 
				
			||||||
github.com/jingyugao/rowserrcheck v0.0.0-20191204022205-72ab7603b68a/go.mod h1:xRskid8CManxVta/ALEhJha/pweKBaVG6fWgc0yH25s=
 | 
					github.com/jingyugao/rowserrcheck v0.0.0-20191204022205-72ab7603b68a/go.mod h1:xRskid8CManxVta/ALEhJha/pweKBaVG6fWgc0yH25s=
 | 
				
			||||||
 | 
					github.com/jinzhu/gorm v1.9.16 h1:+IyIjPEABKRpsu/F8OvDPy9fyQlgsg2luMV2ZIH5i5o=
 | 
				
			||||||
 | 
					github.com/jinzhu/gorm v1.9.16/go.mod h1:G3LB3wezTOWM2ITLzPxEXgSkOXAntiLHS7UdBefADcs=
 | 
				
			||||||
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
 | 
					github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
 | 
				
			||||||
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
 | 
					github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
 | 
				
			||||||
 | 
					github.com/jinzhu/now v1.0.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
 | 
				
			||||||
github.com/jinzhu/now v1.1.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
 | 
					github.com/jinzhu/now v1.1.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
 | 
				
			||||||
github.com/jinzhu/now v1.1.2 h1:eVKgfIdy9b6zbWBMgFpfDPoAMifwSZagU9HmEU6zgiI=
 | 
					github.com/jinzhu/now v1.1.2 h1:eVKgfIdy9b6zbWBMgFpfDPoAMifwSZagU9HmEU6zgiI=
 | 
				
			||||||
github.com/jinzhu/now v1.1.2/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
 | 
					github.com/jinzhu/now v1.1.2/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
 | 
				
			||||||
@ -555,9 +590,11 @@ github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI
 | 
				
			|||||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
 | 
					github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
 | 
				
			||||||
github.com/klauspost/compress v1.10.7/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
 | 
					github.com/klauspost/compress v1.10.7/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
 | 
				
			||||||
github.com/klauspost/compress v1.11.0/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
 | 
					github.com/klauspost/compress v1.11.0/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
 | 
				
			||||||
 | 
					github.com/klauspost/compress v1.11.7/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
 | 
				
			||||||
github.com/klauspost/compress v1.12.2/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg=
 | 
					github.com/klauspost/compress v1.12.2/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg=
 | 
				
			||||||
github.com/klauspost/compress v1.13.5 h1:9O69jUPDcsT9fEm74W92rZL9FQY7rCdaXVneq+yyzl4=
 | 
					github.com/klauspost/compress v1.13.5 h1:9O69jUPDcsT9fEm74W92rZL9FQY7rCdaXVneq+yyzl4=
 | 
				
			||||||
github.com/klauspost/compress v1.13.5/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
 | 
					github.com/klauspost/compress v1.13.5/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
 | 
				
			||||||
 | 
					github.com/klauspost/pgzip v1.2.5/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs=
 | 
				
			||||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
 | 
					github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
 | 
				
			||||||
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
 | 
					github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
 | 
				
			||||||
github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
 | 
					github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
 | 
				
			||||||
@ -580,8 +617,10 @@ github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgx
 | 
				
			|||||||
github.com/lib/pq v0.0.0-20180327071824-d34b9ff171c2/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
 | 
					github.com/lib/pq v0.0.0-20180327071824-d34b9ff171c2/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
 | 
				
			||||||
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
 | 
					github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
 | 
				
			||||||
github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
 | 
					github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
 | 
				
			||||||
 | 
					github.com/lib/pq v1.1.1/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
 | 
				
			||||||
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
 | 
					github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
 | 
				
			||||||
github.com/lib/pq v1.3.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
 | 
					github.com/lib/pq v1.3.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
 | 
				
			||||||
 | 
					github.com/lib/pq v1.3.1-0.20200116171513-9eb3fc897d6f/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
 | 
				
			||||||
github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
 | 
					github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
 | 
				
			||||||
github.com/lib/pq v1.10.3 h1:v9QZf2Sn6AmjXtQeFpdoq/eaNtYP6IN+7lcrygsIAtg=
 | 
					github.com/lib/pq v1.10.3 h1:v9QZf2Sn6AmjXtQeFpdoq/eaNtYP6IN+7lcrygsIAtg=
 | 
				
			||||||
github.com/lib/pq v1.10.3/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
 | 
					github.com/lib/pq v1.10.3/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
 | 
				
			||||||
@ -591,6 +630,7 @@ github.com/logrusorgru/aurora v0.0.0-20181002194514-a7b3b318ed4e/go.mod h1:7rIyQ
 | 
				
			|||||||
github.com/lxn/walk v0.0.0-20210112085537-c389da54e794/go.mod h1:E23UucZGqpuUANJooIbHWCufXvOcT6E7Stq81gU+CSQ=
 | 
					github.com/lxn/walk v0.0.0-20210112085537-c389da54e794/go.mod h1:E23UucZGqpuUANJooIbHWCufXvOcT6E7Stq81gU+CSQ=
 | 
				
			||||||
github.com/lxn/win v0.0.0-20210218163916-a377121e959e/go.mod h1:KxxjdtRkfNoYDCUP5ryK7XJJNTnpC8atvtmTheChOtk=
 | 
					github.com/lxn/win v0.0.0-20210218163916-a377121e959e/go.mod h1:KxxjdtRkfNoYDCUP5ryK7XJJNTnpC8atvtmTheChOtk=
 | 
				
			||||||
github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ=
 | 
					github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ=
 | 
				
			||||||
 | 
					github.com/magefile/mage v1.10.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A=
 | 
				
			||||||
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
 | 
					github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
 | 
				
			||||||
github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
 | 
					github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
 | 
				
			||||||
github.com/magiconair/properties v1.8.4/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
 | 
					github.com/magiconair/properties v1.8.4/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
 | 
				
			||||||
@ -620,7 +660,9 @@ github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzp
 | 
				
			|||||||
github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU=
 | 
					github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU=
 | 
				
			||||||
github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
 | 
					github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
 | 
				
			||||||
github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
 | 
					github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
 | 
				
			||||||
 | 
					github.com/mattn/go-sqlite3 v1.14.0/go.mod h1:JIl7NbARA7phWnGvh0LKTyg7S9BA+6gx71ShQilpsus=
 | 
				
			||||||
github.com/mattn/go-sqlite3 v1.14.5/go.mod h1:WVKg1VTActs4Qso6iwGbiFih2UIHo0ENGwNd0Lj+XmI=
 | 
					github.com/mattn/go-sqlite3 v1.14.5/go.mod h1:WVKg1VTActs4Qso6iwGbiFih2UIHo0ENGwNd0Lj+XmI=
 | 
				
			||||||
 | 
					github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
 | 
				
			||||||
github.com/mattn/go-sqlite3 v1.14.8 h1:gDp86IdQsN/xWjIEmr9MF6o9mpksUgh0fu+9ByFxzIU=
 | 
					github.com/mattn/go-sqlite3 v1.14.8 h1:gDp86IdQsN/xWjIEmr9MF6o9mpksUgh0fu+9ByFxzIU=
 | 
				
			||||||
github.com/mattn/go-sqlite3 v1.14.8/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
 | 
					github.com/mattn/go-sqlite3 v1.14.8/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
 | 
				
			||||||
github.com/mattn/goveralls v0.0.2/go.mod h1:8d1ZMHsd7fW6IRPKQh46F2WRpyib5/X4FOpevwGNQEw=
 | 
					github.com/mattn/goveralls v0.0.2/go.mod h1:8d1ZMHsd7fW6IRPKQh46F2WRpyib5/X4FOpevwGNQEw=
 | 
				
			||||||
@ -690,6 +732,7 @@ github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OS
 | 
				
			|||||||
github.com/nbutton23/zxcvbn-go v0.0.0-20180912185939-ae427f1e4c1d/go.mod h1:o96djdrsSGy3AWPyBgZMAGfxZNfgntdJG+11KU4QvbU=
 | 
					github.com/nbutton23/zxcvbn-go v0.0.0-20180912185939-ae427f1e4c1d/go.mod h1:o96djdrsSGy3AWPyBgZMAGfxZNfgntdJG+11KU4QvbU=
 | 
				
			||||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
 | 
					github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
 | 
				
			||||||
github.com/nishanths/exhaustive v0.1.0/go.mod h1:S1j9110vxV1ECdCudXRkeMnFQ/DQk9ajLT0Uf2MYZQQ=
 | 
					github.com/nishanths/exhaustive v0.1.0/go.mod h1:S1j9110vxV1ECdCudXRkeMnFQ/DQk9ajLT0Uf2MYZQQ=
 | 
				
			||||||
 | 
					github.com/nishanths/predeclared v0.0.0-20200524104333-86fad755b4d3/go.mod h1:nt3d53pc1VYcphSCIaYAJtnPYnr3Zyn8fMq2wvPGPso=
 | 
				
			||||||
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
 | 
					github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
 | 
				
			||||||
github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs=
 | 
					github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs=
 | 
				
			||||||
github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA=
 | 
					github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA=
 | 
				
			||||||
@ -726,6 +769,8 @@ github.com/ory/dockertest/v3 v3.7.0 h1:Bijzonc69Ont3OU0a3TWKJ1Rzlh3TsDXP1JrTAkSm
 | 
				
			|||||||
github.com/ory/dockertest/v3 v3.7.0/go.mod h1:PvCCgnP7AfBZeVrzwiUTjZx/IUXlGLC1zQlUQrLIlUE=
 | 
					github.com/ory/dockertest/v3 v3.7.0/go.mod h1:PvCCgnP7AfBZeVrzwiUTjZx/IUXlGLC1zQlUQrLIlUE=
 | 
				
			||||||
github.com/pact-foundation/pact-go v1.0.4/go.mod h1:uExwJY4kCzNPcHRj+hCR/HBbOOIwwtUjcrb0b5/5kLM=
 | 
					github.com/pact-foundation/pact-go v1.0.4/go.mod h1:uExwJY4kCzNPcHRj+hCR/HBbOOIwwtUjcrb0b5/5kLM=
 | 
				
			||||||
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
 | 
					github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
 | 
				
			||||||
 | 
					github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=
 | 
				
			||||||
 | 
					github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
 | 
				
			||||||
github.com/pborman/getopt v1.1.0/go.mod h1:FxXoW1Re00sQG/+KIkuSqRL/LwQgSkv7uyac+STFsbk=
 | 
					github.com/pborman/getopt v1.1.0/go.mod h1:FxXoW1Re00sQG/+KIkuSqRL/LwQgSkv7uyac+STFsbk=
 | 
				
			||||||
github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k=
 | 
					github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k=
 | 
				
			||||||
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
 | 
					github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
 | 
				
			||||||
@ -745,6 +790,7 @@ github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE
 | 
				
			|||||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
 | 
					github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
 | 
				
			||||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 | 
					github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 | 
				
			||||||
github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA=
 | 
					github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA=
 | 
				
			||||||
 | 
					github.com/pkg/profile v1.5.0/go.mod h1:qBsxPvzyUincmltOk6iyRVxHYg4adc0OFOv72ZdLa18=
 | 
				
			||||||
github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI=
 | 
					github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI=
 | 
				
			||||||
github.com/pkg/sftp v1.13.0/go.mod h1:41g+FIPlQUTDCveupEmEA65IoiQFrtgCeDopC4ajGIM=
 | 
					github.com/pkg/sftp v1.13.0/go.mod h1:41g+FIPlQUTDCveupEmEA65IoiQFrtgCeDopC4ajGIM=
 | 
				
			||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 | 
					github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 | 
				
			||||||
@ -835,6 +881,7 @@ github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMB
 | 
				
			|||||||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
 | 
					github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
 | 
				
			||||||
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
 | 
					github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
 | 
				
			||||||
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
 | 
					github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
 | 
				
			||||||
 | 
					github.com/sirupsen/logrus v1.8.0/go.mod h1:4GuYW9TZmE769R5STWrRakJc4UqQ3+QQ95fyz7ENv1A=
 | 
				
			||||||
github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE=
 | 
					github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE=
 | 
				
			||||||
github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
 | 
					github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
 | 
				
			||||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
 | 
					github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
 | 
				
			||||||
@ -842,6 +889,8 @@ github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1
 | 
				
			|||||||
github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s=
 | 
					github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s=
 | 
				
			||||||
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
 | 
					github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
 | 
				
			||||||
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
 | 
					github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
 | 
				
			||||||
 | 
					github.com/soheilhy/cmux v0.1.5 h1:jjzc5WVemNEDTLwv9tlmemhC73tI08BNOIGwBOo10Js=
 | 
				
			||||||
 | 
					github.com/soheilhy/cmux v0.1.5/go.mod h1:T7TcVDs9LWfQgPlPsdngu6I6QIoyIFZDDC6sNE1GqG0=
 | 
				
			||||||
github.com/sonatard/noctx v0.0.1/go.mod h1:9D2D/EoULe8Yy2joDHJj7bv3sZoq9AaSb8B4lqBjiZI=
 | 
					github.com/sonatard/noctx v0.0.1/go.mod h1:9D2D/EoULe8Yy2joDHJj7bv3sZoq9AaSb8B4lqBjiZI=
 | 
				
			||||||
github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY=
 | 
					github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY=
 | 
				
			||||||
github.com/sourcegraph/go-diff v0.6.1/go.mod h1:iBszgVvyxdc8SFZ7gm69go2KDdt3ag071iBaWPF6cjs=
 | 
					github.com/sourcegraph/go-diff v0.6.1/go.mod h1:iBszgVvyxdc8SFZ7gm69go2KDdt3ag071iBaWPF6cjs=
 | 
				
			||||||
@ -856,6 +905,7 @@ github.com/spf13/cast v1.3.1 h1:nFm6S0SMdyzrzcmThSipiEubIDy8WEXKNZ0UOgiRpng=
 | 
				
			|||||||
github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
 | 
					github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
 | 
				
			||||||
github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
 | 
					github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
 | 
				
			||||||
github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE=
 | 
					github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE=
 | 
				
			||||||
 | 
					github.com/spf13/cobra v1.0.1-0.20201006035406-b97b5ead31f7/go.mod h1:yk5b0mALVusDL5fMM6Rd1wgnoO5jUPhwsQ6LQAJTidQ=
 | 
				
			||||||
github.com/spf13/cobra v1.1.1/go.mod h1:WnodtKOvamDL/PwE2M4iKs8aMDBZ5Q5klgD3qfVJQMI=
 | 
					github.com/spf13/cobra v1.1.1/go.mod h1:WnodtKOvamDL/PwE2M4iKs8aMDBZ5Q5klgD3qfVJQMI=
 | 
				
			||||||
github.com/spf13/cobra v1.2.1 h1:+KmjbUw1hriSNMF55oPrkZcb27aECyrj8V2ytv7kWDw=
 | 
					github.com/spf13/cobra v1.2.1 h1:+KmjbUw1hriSNMF55oPrkZcb27aECyrj8V2ytv7kWDw=
 | 
				
			||||||
github.com/spf13/cobra v1.2.1/go.mod h1:ExllRjgxM/piMAM+3tAZvg8fsklGAf3tPfi+i8t68Nk=
 | 
					github.com/spf13/cobra v1.2.1/go.mod h1:ExllRjgxM/piMAM+3tAZvg8fsklGAf3tPfi+i8t68Nk=
 | 
				
			||||||
@ -911,6 +961,7 @@ github.com/tomarrell/wrapcheck v0.0.0-20200807122107-df9e8bcb914d/go.mod h1:yiFB
 | 
				
			|||||||
github.com/tomarrell/wrapcheck v0.0.0-20201130113247-1683564d9756/go.mod h1:yiFB6fFoV7saXirUGfuK+cPtUh4NX/Hf5y2WC2lehu0=
 | 
					github.com/tomarrell/wrapcheck v0.0.0-20201130113247-1683564d9756/go.mod h1:yiFB6fFoV7saXirUGfuK+cPtUh4NX/Hf5y2WC2lehu0=
 | 
				
			||||||
github.com/tommy-muehle/go-mnd v1.3.1-0.20200224220436-e6f9a994e8fa/go.mod h1:dSUh0FtTP8VhvkL1S+gUR1OKd9ZnSaozuI6r3m6wOig=
 | 
					github.com/tommy-muehle/go-mnd v1.3.1-0.20200224220436-e6f9a994e8fa/go.mod h1:dSUh0FtTP8VhvkL1S+gUR1OKd9ZnSaozuI6r3m6wOig=
 | 
				
			||||||
github.com/toqueteos/webbrowser v1.2.0/go.mod h1:XWoZq4cyp9WeUeak7w7LXRUQf1F1ATJMir8RTqb4ayM=
 | 
					github.com/toqueteos/webbrowser v1.2.0/go.mod h1:XWoZq4cyp9WeUeak7w7LXRUQf1F1ATJMir8RTqb4ayM=
 | 
				
			||||||
 | 
					github.com/twitchtv/twirp v7.1.0+incompatible/go.mod h1:RRJoFSAmTEh2weEqWtpPE3vFK5YBhA6bqp2l1kfCC5A=
 | 
				
			||||||
github.com/u-root/uio v0.0.0-20210528114334-82958018845c/go.mod h1:LpEX5FO/cB+WF4TYGY1V5qktpaZLkKkSegbr0V4eYXA=
 | 
					github.com/u-root/uio v0.0.0-20210528114334-82958018845c/go.mod h1:LpEX5FO/cB+WF4TYGY1V5qktpaZLkKkSegbr0V4eYXA=
 | 
				
			||||||
github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
 | 
					github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
 | 
				
			||||||
github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo=
 | 
					github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo=
 | 
				
			||||||
@ -966,7 +1017,9 @@ go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
 | 
				
			|||||||
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
 | 
					go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
 | 
				
			||||||
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
 | 
					go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
 | 
				
			||||||
go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
 | 
					go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
 | 
				
			||||||
 | 
					go.opencensus.io v0.22.6/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E=
 | 
				
			||||||
go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E=
 | 
					go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E=
 | 
				
			||||||
 | 
					go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
 | 
				
			||||||
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
 | 
					go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
 | 
				
			||||||
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
 | 
					go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
 | 
				
			||||||
go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
 | 
					go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
 | 
				
			||||||
@ -980,6 +1033,7 @@ go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9E
 | 
				
			|||||||
go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
 | 
					go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
 | 
				
			||||||
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
 | 
					go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
 | 
				
			||||||
go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM=
 | 
					go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM=
 | 
				
			||||||
 | 
					go.uber.org/zap v1.16.0/go.mod h1:MA8QOfq0BHJwdXa996Y4dYkAqRKB8/1K1QMMZVaNZjQ=
 | 
				
			||||||
go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo=
 | 
					go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo=
 | 
				
			||||||
go4.org/intern v0.0.0-20210108033219-3eb7198706b2 h1:VFTf+jjIgsldaz/Mr00VaCSswHJrI2hIjQygE/W4IMg=
 | 
					go4.org/intern v0.0.0-20210108033219-3eb7198706b2 h1:VFTf+jjIgsldaz/Mr00VaCSswHJrI2hIjQygE/W4IMg=
 | 
				
			||||||
go4.org/intern v0.0.0-20210108033219-3eb7198706b2/go.mod h1:vLqJ+12kCw61iCWsPto0EOHhBS+o4rO5VIucbc9g2Cc=
 | 
					go4.org/intern v0.0.0-20210108033219-3eb7198706b2/go.mod h1:vLqJ+12kCw61iCWsPto0EOHhBS+o4rO5VIucbc9g2Cc=
 | 
				
			||||||
@ -1001,6 +1055,7 @@ golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8U
 | 
				
			|||||||
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 | 
					golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 | 
				
			||||||
golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 | 
					golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 | 
				
			||||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 | 
					golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 | 
				
			||||||
 | 
					golang.org/x/crypto v0.0.0-20191205180655-e7c4368fe9dd/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
 | 
				
			||||||
golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
 | 
					golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
 | 
				
			||||||
golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
 | 
					golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
 | 
				
			||||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
 | 
					golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
 | 
				
			||||||
@ -1025,6 +1080,7 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0
 | 
				
			|||||||
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
 | 
					golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
 | 
				
			||||||
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
 | 
					golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
 | 
				
			||||||
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
 | 
					golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
 | 
				
			||||||
 | 
					golang.org/x/exp v0.0.0-20200331195152-e8c3332aa8e5/go.mod h1:4M0jN8W1tt0AVLNr8HDosyJCDCDuyL9N9+3m7wDWgKw=
 | 
				
			||||||
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
 | 
					golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
 | 
				
			||||||
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
 | 
					golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
 | 
				
			||||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
 | 
					golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
 | 
				
			||||||
@ -1050,6 +1106,7 @@ golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 | 
				
			|||||||
golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 | 
					golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 | 
				
			||||||
golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 | 
					golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 | 
				
			||||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 | 
					golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 | 
				
			||||||
 | 
					golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 | 
				
			||||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 | 
					golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 | 
				
			||||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 | 
					golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 | 
				
			||||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 | 
					golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 | 
				
			||||||
@ -1073,6 +1130,7 @@ golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLL
 | 
				
			|||||||
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 | 
					golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 | 
				
			||||||
golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 | 
					golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 | 
				
			||||||
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 | 
					golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 | 
				
			||||||
 | 
					golang.org/x/net v0.0.0-20191002035440-2ec189313ef0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 | 
				
			||||||
golang.org/x/net v0.0.0-20191007182048-72f939374954/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 | 
					golang.org/x/net v0.0.0-20191007182048-72f939374954/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 | 
				
			||||||
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 | 
					golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 | 
				
			||||||
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 | 
					golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 | 
				
			||||||
@ -1081,7 +1139,9 @@ golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLL
 | 
				
			|||||||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 | 
					golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 | 
				
			||||||
golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 | 
					golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 | 
				
			||||||
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
 | 
					golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
 | 
				
			||||||
 | 
					golang.org/x/net v0.0.0-20200421231249-e086a090c8fd/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
 | 
				
			||||||
golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
 | 
					golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
 | 
				
			||||||
 | 
					golang.org/x/net v0.0.0-20200505041828-1ed23360d12c/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
 | 
				
			||||||
golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
 | 
					golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
 | 
				
			||||||
golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
 | 
					golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
 | 
				
			||||||
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
 | 
					golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
 | 
				
			||||||
@ -1094,6 +1154,7 @@ golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwY
 | 
				
			|||||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
 | 
					golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
 | 
				
			||||||
golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
 | 
					golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
 | 
				
			||||||
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
 | 
					golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
 | 
				
			||||||
 | 
					golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
 | 
				
			||||||
golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
 | 
					golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
 | 
				
			||||||
golang.org/x/net v0.0.0-20201216054612-986b41b23924/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
 | 
					golang.org/x/net v0.0.0-20201216054612-986b41b23924/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
 | 
				
			||||||
golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
 | 
					golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
 | 
				
			||||||
@ -1117,6 +1178,9 @@ golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ
 | 
				
			|||||||
golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
 | 
					golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
 | 
				
			||||||
golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
 | 
					golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
 | 
				
			||||||
golang.org/x/oauth2 v0.0.0-20210402161424-2e8d93401602/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
 | 
					golang.org/x/oauth2 v0.0.0-20210402161424-2e8d93401602/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
 | 
				
			||||||
 | 
					golang.org/x/oauth2 v0.0.0-20210427180440-81ed05c6b58c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
 | 
				
			||||||
 | 
					golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f h1:Qmd2pbz05z7z6lm0DrgQVVPuBm92jqujBKMHMOlOQEw=
 | 
				
			||||||
 | 
					golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
 | 
				
			||||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 | 
					golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 | 
				
			||||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 | 
					golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 | 
				
			||||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 | 
					golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 | 
				
			||||||
@ -1127,6 +1191,7 @@ golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJ
 | 
				
			|||||||
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 | 
					golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 | 
				
			||||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 | 
					golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 | 
				
			||||||
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 | 
					golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 | 
				
			||||||
 | 
					golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ=
 | 
				
			||||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 | 
					golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 | 
				
			||||||
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 | 
					golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 | 
				
			||||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 | 
					golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 | 
				
			||||||
@ -1176,6 +1241,7 @@ golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7w
 | 
				
			|||||||
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
					golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
				
			||||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
					golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
				
			||||||
golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
					golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
				
			||||||
 | 
					golang.org/x/sys v0.0.0-20200420163511-1957bb5e6d1f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
				
			||||||
golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
					golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
				
			||||||
golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
					golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
				
			||||||
golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
					golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
				
			||||||
@ -1304,11 +1370,13 @@ golang.org/x/tools v0.0.0-20200426102838-f3a5411a4c3b/go.mod h1:EkVYQZoAsY45+roY
 | 
				
			|||||||
golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
 | 
					golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
 | 
				
			||||||
golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
 | 
					golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
 | 
				
			||||||
golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
 | 
					golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
 | 
				
			||||||
 | 
					golang.org/x/tools v0.0.0-20200522201501-cb1345f3a375/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
 | 
				
			||||||
golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
 | 
					golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
 | 
				
			||||||
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
 | 
					golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
 | 
				
			||||||
golang.org/x/tools v0.0.0-20200622203043-20e05c1c8ffa/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
 | 
					golang.org/x/tools v0.0.0-20200622203043-20e05c1c8ffa/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
 | 
				
			||||||
golang.org/x/tools v0.0.0-20200624225443-88f3c62a19ff/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
 | 
					golang.org/x/tools v0.0.0-20200624225443-88f3c62a19ff/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
 | 
				
			||||||
golang.org/x/tools v0.0.0-20200625211823-6506e20df31f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
 | 
					golang.org/x/tools v0.0.0-20200625211823-6506e20df31f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
 | 
				
			||||||
 | 
					golang.org/x/tools v0.0.0-20200717024301-6ddee64345a6/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
 | 
				
			||||||
golang.org/x/tools v0.0.0-20200724022722-7017fd6b1305/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
 | 
					golang.org/x/tools v0.0.0-20200724022722-7017fd6b1305/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
 | 
				
			||||||
golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
 | 
					golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
 | 
				
			||||||
golang.org/x/tools v0.0.0-20200731060945-b5fad4ed8dd6/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
 | 
					golang.org/x/tools v0.0.0-20200731060945-b5fad4ed8dd6/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
 | 
				
			||||||
@ -1357,6 +1425,7 @@ google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/
 | 
				
			|||||||
google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
 | 
					google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
 | 
				
			||||||
google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
 | 
					google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
 | 
				
			||||||
google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
 | 
					google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
 | 
				
			||||||
 | 
					google.golang.org/api v0.25.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
 | 
				
			||||||
google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
 | 
					google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
 | 
				
			||||||
google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=
 | 
					google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=
 | 
				
			||||||
google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=
 | 
					google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=
 | 
				
			||||||
@ -1373,6 +1442,7 @@ google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7
 | 
				
			|||||||
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
 | 
					google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
 | 
				
			||||||
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
 | 
					google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
 | 
				
			||||||
google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
 | 
					google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
 | 
				
			||||||
 | 
					google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c=
 | 
				
			||||||
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
 | 
					google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
 | 
				
			||||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
 | 
					google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
 | 
				
			||||||
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
 | 
					google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
 | 
				
			||||||
@ -1396,26 +1466,34 @@ google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfG
 | 
				
			|||||||
google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
 | 
					google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
 | 
				
			||||||
google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
 | 
					google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
 | 
				
			||||||
google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
 | 
					google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
 | 
				
			||||||
 | 
					google.golang.org/genproto v0.0.0-20200423170343-7949de9c1215/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
 | 
				
			||||||
google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
 | 
					google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
 | 
				
			||||||
google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
 | 
					google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
 | 
				
			||||||
google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
 | 
					google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
 | 
				
			||||||
google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=
 | 
					google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=
 | 
				
			||||||
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
 | 
					google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
 | 
				
			||||||
 | 
					google.golang.org/genproto v0.0.0-20200527145253-8367513e4ece/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
 | 
				
			||||||
google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
 | 
					google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
 | 
				
			||||||
google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
 | 
					google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
 | 
				
			||||||
google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
 | 
					google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
 | 
				
			||||||
 | 
					google.golang.org/genproto v0.0.0-20200806141610-86f49bd18e98/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
 | 
				
			||||||
google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
 | 
					google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
 | 
				
			||||||
google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
 | 
					google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
 | 
				
			||||||
google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
 | 
					google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
 | 
				
			||||||
google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
 | 
					google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
 | 
				
			||||||
google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
 | 
					google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
 | 
				
			||||||
google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
 | 
					google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
 | 
				
			||||||
 | 
					google.golang.org/genproto v0.0.0-20210207032614-bba0dbe2a9ea/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
 | 
				
			||||||
google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
 | 
					google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
 | 
				
			||||||
 | 
					google.golang.org/genproto v0.0.0-20210224155714-063164c882e6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
 | 
				
			||||||
google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
 | 
					google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
 | 
				
			||||||
google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
 | 
					google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
 | 
				
			||||||
google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
 | 
					google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
 | 
				
			||||||
google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A=
 | 
					google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A=
 | 
				
			||||||
 | 
					google.golang.org/genproto v0.0.0-20210426193834-eac7f76ac494/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A=
 | 
				
			||||||
google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
 | 
					google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
 | 
				
			||||||
 | 
					google.golang.org/genproto v0.0.0-20210903162649-d08c68adba83 h1:3V2dxSZpz4zozWWUq36vUxXEKnSYitEH2LdsAx+RUmg=
 | 
				
			||||||
 | 
					google.golang.org/genproto v0.0.0-20210903162649-d08c68adba83/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
 | 
				
			||||||
google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
 | 
					google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
 | 
				
			||||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
 | 
					google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
 | 
				
			||||||
google.golang.org/grpc v1.20.0/go.mod h1:chYK+tFQF0nDUGJgXMSgLCQk3phJEuONr2DCgLDdAQM=
 | 
					google.golang.org/grpc v1.20.0/go.mod h1:chYK+tFQF0nDUGJgXMSgLCQk3phJEuONr2DCgLDdAQM=
 | 
				
			||||||
@ -1437,10 +1515,19 @@ google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM
 | 
				
			|||||||
google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0=
 | 
					google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0=
 | 
				
			||||||
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
 | 
					google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
 | 
				
			||||||
google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8=
 | 
					google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8=
 | 
				
			||||||
 | 
					google.golang.org/grpc v1.35.0-dev.0.20201218190559-666aea1fb34c/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
 | 
				
			||||||
google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
 | 
					google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
 | 
				
			||||||
google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
 | 
					google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
 | 
				
			||||||
google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
 | 
					google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
 | 
				
			||||||
 | 
					google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
 | 
				
			||||||
google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
 | 
					google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
 | 
				
			||||||
 | 
					google.golang.org/grpc v1.40.0 h1:AGJ0Ih4mHjSeibYkFGh1dD9KJ/eOtZ93I6hoHhukQ5Q=
 | 
				
			||||||
 | 
					google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=
 | 
				
			||||||
 | 
					google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.0.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw=
 | 
				
			||||||
 | 
					google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0 h1:M1YKkFIboKNieVO5DLUEVzQfGwJD30Nv2jfUgzb5UcE=
 | 
				
			||||||
 | 
					google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw=
 | 
				
			||||||
 | 
					google.golang.org/grpc/examples v0.0.0-20210309220351-d5b628860d4e/go.mod h1:Ly7ZA/ARzg8fnPU9TyZIxoz33sEUuWX7txiqs8lPTgE=
 | 
				
			||||||
 | 
					google.golang.org/grpc/examples v0.0.0-20210601155443-8bdcb4c9ab8d/go.mod h1:bF8wuZSAZTcbF7ZPKrDI/qY52toTP/yxLpRRY4Eu9Js=
 | 
				
			||||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
 | 
					google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
 | 
				
			||||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
 | 
					google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
 | 
				
			||||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
 | 
					google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
 | 
				
			||||||
@ -1451,9 +1538,12 @@ google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2
 | 
				
			|||||||
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
 | 
					google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
 | 
				
			||||||
google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
 | 
					google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
 | 
				
			||||||
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
 | 
					google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
 | 
				
			||||||
 | 
					google.golang.org/protobuf v1.25.1-0.20200805231151-a709e31e5d12/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
 | 
				
			||||||
 | 
					google.golang.org/protobuf v1.25.1-0.20201208041424-160c7477e0e8/go.mod h1:hFxJC2f0epmp1elRCiEGJTKAWbwxZ2nvqZdHl3FQXCY=
 | 
				
			||||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
 | 
					google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
 | 
				
			||||||
google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk=
 | 
					 | 
				
			||||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
 | 
					google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
 | 
				
			||||||
 | 
					google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ=
 | 
				
			||||||
 | 
					google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
 | 
				
			||||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
 | 
					gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
 | 
				
			||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 | 
					gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 | 
				
			||||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 | 
					gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 | 
				
			||||||
@ -1470,6 +1560,8 @@ gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
 | 
				
			|||||||
gopkg.in/ini.v1 v1.62.0 h1:duBzk771uxoUuOlyRLkHsygud9+5lrlGjdFBb4mSKDU=
 | 
					gopkg.in/ini.v1 v1.62.0 h1:duBzk771uxoUuOlyRLkHsygud9+5lrlGjdFBb4mSKDU=
 | 
				
			||||||
gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
 | 
					gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
 | 
				
			||||||
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
 | 
					gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
 | 
				
			||||||
 | 
					gopkg.in/square/go-jose.v2 v2.5.1 h1:7odma5RETjNHWJnR32wx8t+Io4djHE1PqxCFx3iiZ2w=
 | 
				
			||||||
 | 
					gopkg.in/square/go-jose.v2 v2.5.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
 | 
				
			||||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
 | 
					gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
 | 
				
			||||||
gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
 | 
					gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
 | 
				
			||||||
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
 | 
					gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										34
									
								
								grpcv1.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								grpcv1.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,34 @@
 | 
				
			|||||||
 | 
					//nolint
 | 
				
			||||||
 | 
					package headscale
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"context"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						apiV1 "github.com/juanfont/headscale/gen/go/v1"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type headscaleV1APIServer struct { // apiV1.HeadscaleServiceServer
 | 
				
			||||||
 | 
						apiV1.UnimplementedHeadscaleServiceServer
 | 
				
			||||||
 | 
						h *Headscale
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func newHeadscaleV1APIServer(h *Headscale) apiV1.HeadscaleServiceServer {
 | 
				
			||||||
 | 
						return headscaleV1APIServer{
 | 
				
			||||||
 | 
							h: h,
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (api headscaleV1APIServer) GetMachine(
 | 
				
			||||||
 | 
						ctx context.Context,
 | 
				
			||||||
 | 
						request *apiV1.GetMachineRequest,
 | 
				
			||||||
 | 
					) (*apiV1.Machine, error) {
 | 
				
			||||||
 | 
						m, err := api.h.GetMachineByID(request.MachineId)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// TODO(kradalby): Make this function actually do something
 | 
				
			||||||
 | 
						return &apiV1.Machine{Name: m.Name}, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (api headscaleV1APIServer) mustEmbedUnimplementedHeadscaleServiceServer() {}
 | 
				
			||||||
@ -230,7 +230,6 @@ func (s *IntegrationTestSuite) SetupSuite() {
 | 
				
			|||||||
		Name: "headscale",
 | 
							Name: "headscale",
 | 
				
			||||||
		Mounts: []string{
 | 
							Mounts: []string{
 | 
				
			||||||
			fmt.Sprintf("%s/integration_test/etc:/etc/headscale", currentPath),
 | 
								fmt.Sprintf("%s/integration_test/etc:/etc/headscale", currentPath),
 | 
				
			||||||
			fmt.Sprintf("%s/derp.yaml:/etc/headscale/derp.yaml", currentPath),
 | 
					 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
		Networks: []*dockertest.Network{&network},
 | 
							Networks: []*dockertest.Network{&network},
 | 
				
			||||||
		Cmd:      []string{"headscale", "serve"},
 | 
							Cmd:      []string{"headscale", "serve"},
 | 
				
			||||||
@ -289,7 +288,16 @@ func (s *IntegrationTestSuite) SetupSuite() {
 | 
				
			|||||||
		fmt.Printf("Creating pre auth key for %s\n", namespace)
 | 
							fmt.Printf("Creating pre auth key for %s\n", namespace)
 | 
				
			||||||
		authKey, err := executeCommand(
 | 
							authKey, err := executeCommand(
 | 
				
			||||||
			&headscale,
 | 
								&headscale,
 | 
				
			||||||
			[]string{"headscale", "--namespace", namespace, "preauthkeys", "create", "--reusable", "--expiration", "24h"},
 | 
								[]string{
 | 
				
			||||||
 | 
									"headscale",
 | 
				
			||||||
 | 
									"--namespace",
 | 
				
			||||||
 | 
									namespace,
 | 
				
			||||||
 | 
									"preauthkeys",
 | 
				
			||||||
 | 
									"create",
 | 
				
			||||||
 | 
									"--reusable",
 | 
				
			||||||
 | 
									"--expiration",
 | 
				
			||||||
 | 
									"24h",
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
			[]string{},
 | 
								[]string{},
 | 
				
			||||||
		)
 | 
							)
 | 
				
			||||||
		assert.Nil(s.T(), err)
 | 
							assert.Nil(s.T(), err)
 | 
				
			||||||
@ -298,7 +306,16 @@ func (s *IntegrationTestSuite) SetupSuite() {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
		fmt.Printf("Joining tailscale containers to headscale at %s\n", headscaleEndpoint)
 | 
							fmt.Printf("Joining tailscale containers to headscale at %s\n", headscaleEndpoint)
 | 
				
			||||||
		for hostname, tailscale := range scales.tailscales {
 | 
							for hostname, tailscale := range scales.tailscales {
 | 
				
			||||||
			command := []string{"tailscale", "up", "-login-server", headscaleEndpoint, "--authkey", strings.TrimSuffix(authKey, "\n"), "--hostname", hostname}
 | 
								command := []string{
 | 
				
			||||||
 | 
									"tailscale",
 | 
				
			||||||
 | 
									"up",
 | 
				
			||||||
 | 
									"-login-server",
 | 
				
			||||||
 | 
									headscaleEndpoint,
 | 
				
			||||||
 | 
									"--authkey",
 | 
				
			||||||
 | 
									strings.TrimSuffix(authKey, "\n"),
 | 
				
			||||||
 | 
									"--hostname",
 | 
				
			||||||
 | 
									hostname,
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			fmt.Println("Join command:", command)
 | 
								fmt.Println("Join command:", command)
 | 
				
			||||||
			fmt.Printf("Running join command for %s\n", hostname)
 | 
								fmt.Printf("Running join command for %s\n", hostname)
 | 
				
			||||||
@ -476,7 +493,7 @@ func (s *IntegrationTestSuite) TestSharedNodes() {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
		result, err := executeCommand(
 | 
							result, err := executeCommand(
 | 
				
			||||||
			&headscale,
 | 
								&headscale,
 | 
				
			||||||
			[]string{"headscale", "nodes", "share", "--namespace", "shared", fmt.Sprint(machine.ID), "main"},
 | 
								[]string{"headscale", "nodes", "share", "--identifier", fmt.Sprint(machine.ID), "--namespace", "main"},
 | 
				
			||||||
			[]string{},
 | 
								[]string{},
 | 
				
			||||||
		)
 | 
							)
 | 
				
			||||||
		assert.Nil(s.T(), err)
 | 
							assert.Nil(s.T(), err)
 | 
				
			||||||
@ -661,7 +678,13 @@ func (s *IntegrationTestSuite) TestMagicDNS() {
 | 
				
			|||||||
							fmt.Sprintf("%s.%s.headscale.net", peername, namespace),
 | 
												fmt.Sprintf("%s.%s.headscale.net", peername, namespace),
 | 
				
			||||||
						}
 | 
											}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
						fmt.Printf("Pinging using Hostname (magicdns) from %s (%s) to %s (%s)\n", hostname, ips[hostname], peername, ip)
 | 
											fmt.Printf(
 | 
				
			||||||
 | 
												"Pinging using Hostname (magicdns) from %s (%s) to %s (%s)\n",
 | 
				
			||||||
 | 
												hostname,
 | 
				
			||||||
 | 
												ips[hostname],
 | 
				
			||||||
 | 
												peername,
 | 
				
			||||||
 | 
												ip,
 | 
				
			||||||
 | 
											)
 | 
				
			||||||
						result, err := executeCommand(
 | 
											result, err := executeCommand(
 | 
				
			||||||
							&tailscale,
 | 
												&tailscale,
 | 
				
			||||||
							command,
 | 
												command,
 | 
				
			||||||
 | 
				
			|||||||
@ -1,19 +0,0 @@
 | 
				
			|||||||
{
 | 
					 | 
				
			||||||
  "server_url": "http://headscale:8080",
 | 
					 | 
				
			||||||
  "listen_addr": "0.0.0.0:8080",
 | 
					 | 
				
			||||||
  "private_key_path": "private.key",
 | 
					 | 
				
			||||||
  "derp_map_path": "derp.yaml",
 | 
					 | 
				
			||||||
  "ephemeral_node_inactivity_timeout": "30m",
 | 
					 | 
				
			||||||
  "db_type": "sqlite3",
 | 
					 | 
				
			||||||
  "db_path": "/tmp/integration_test_db.sqlite3",
 | 
					 | 
				
			||||||
  "acl_policy_path": "",
 | 
					 | 
				
			||||||
  "log_level": "trace",
 | 
					 | 
				
			||||||
  "dns_config": {
 | 
					 | 
				
			||||||
    "nameservers": [
 | 
					 | 
				
			||||||
      "1.1.1.1"
 | 
					 | 
				
			||||||
    ],
 | 
					 | 
				
			||||||
    "domains": [],
 | 
					 | 
				
			||||||
    "magic_dns": true,
 | 
					 | 
				
			||||||
    "base_domain": "headscale.net"
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
							
								
								
									
										20
									
								
								integration_test/etc/config.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								integration_test/etc/config.yaml
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,20 @@
 | 
				
			|||||||
 | 
					log_level: trace
 | 
				
			||||||
 | 
					acl_policy_path: ""
 | 
				
			||||||
 | 
					db_type: sqlite3
 | 
				
			||||||
 | 
					ephemeral_node_inactivity_timeout: 30m
 | 
				
			||||||
 | 
					dns_config:
 | 
				
			||||||
 | 
					  base_domain: headscale.net
 | 
				
			||||||
 | 
					  magic_dns: true
 | 
				
			||||||
 | 
					  domains: []
 | 
				
			||||||
 | 
					  nameservers:
 | 
				
			||||||
 | 
					    - 1.1.1.1
 | 
				
			||||||
 | 
					db_path: /tmp/integration_test_db.sqlite3
 | 
				
			||||||
 | 
					private_key_path: private.key
 | 
				
			||||||
 | 
					listen_addr: 0.0.0.0:8080
 | 
				
			||||||
 | 
					server_url: http://headscale:8080
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					derp:
 | 
				
			||||||
 | 
					  urls:
 | 
				
			||||||
 | 
					    - https://controlplane.tailscale.com/derpmap/default
 | 
				
			||||||
 | 
					  auto_update_enabled: false
 | 
				
			||||||
 | 
					  update_frequency: 1m
 | 
				
			||||||
							
								
								
									
										45
									
								
								machine.go
									
									
									
									
									
								
							
							
						
						
									
										45
									
								
								machine.go
									
									
									
									
									
								
							@ -36,6 +36,7 @@ type Machine struct {
 | 
				
			|||||||
	LastSeen             *time.Time
 | 
						LastSeen             *time.Time
 | 
				
			||||||
	LastSuccessfulUpdate *time.Time
 | 
						LastSuccessfulUpdate *time.Time
 | 
				
			||||||
	Expiry               *time.Time
 | 
						Expiry               *time.Time
 | 
				
			||||||
 | 
						RequestedExpiry      *time.Time
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	HostInfo      datatypes.JSON
 | 
						HostInfo      datatypes.JSON
 | 
				
			||||||
	Endpoints     datatypes.JSON
 | 
						Endpoints     datatypes.JSON
 | 
				
			||||||
@ -56,6 +57,38 @@ func (m Machine) isAlreadyRegistered() bool {
 | 
				
			|||||||
	return m.Registered
 | 
						return m.Registered
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// isExpired returns whether the machine registration has expired
 | 
				
			||||||
 | 
					func (m Machine) isExpired() bool {
 | 
				
			||||||
 | 
						return time.Now().UTC().After(*m.Expiry)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// If the Machine is expired, updateMachineExpiry updates the Machine Expiry time to the maximum allowed duration,
 | 
				
			||||||
 | 
					// or the default duration if no Expiry time was requested by the client. The expiry time here does not (yet) cause
 | 
				
			||||||
 | 
					// a client to be disconnected, however they will have to re-auth the machine if they attempt to reconnect after the
 | 
				
			||||||
 | 
					// expiry time.
 | 
				
			||||||
 | 
					func (h *Headscale) updateMachineExpiry(m *Machine) {
 | 
				
			||||||
 | 
						if m.isExpired() {
 | 
				
			||||||
 | 
							now := time.Now().UTC()
 | 
				
			||||||
 | 
							maxExpiry := now.Add(h.cfg.MaxMachineRegistrationDuration)         // calculate the maximum expiry
 | 
				
			||||||
 | 
							defaultExpiry := now.Add(h.cfg.DefaultMachineRegistrationDuration) // calculate the default expiry
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// clamp the expiry time of the machine registration to the maximum allowed, or use the default if none supplied
 | 
				
			||||||
 | 
							if maxExpiry.Before(*m.RequestedExpiry) {
 | 
				
			||||||
 | 
								log.Debug().
 | 
				
			||||||
 | 
									Msgf("Clamping registration expiry time to maximum: %v (%v)", maxExpiry, h.cfg.MaxMachineRegistrationDuration)
 | 
				
			||||||
 | 
								m.Expiry = &maxExpiry
 | 
				
			||||||
 | 
							} else if m.RequestedExpiry.IsZero() {
 | 
				
			||||||
 | 
								log.Debug().Msgf("Using default machine registration expiry time: %v (%v)", defaultExpiry, h.cfg.DefaultMachineRegistrationDuration)
 | 
				
			||||||
 | 
								m.Expiry = &defaultExpiry
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								log.Debug().Msgf("Using requested machine registration expiry time: %v", m.RequestedExpiry)
 | 
				
			||||||
 | 
								m.Expiry = m.RequestedExpiry
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							h.db.Save(&m)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (h *Headscale) getDirectPeers(m *Machine) (Machines, error) {
 | 
					func (h *Headscale) getDirectPeers(m *Machine) (Machines, error) {
 | 
				
			||||||
	log.Trace().
 | 
						log.Trace().
 | 
				
			||||||
		Str("func", "getDirectPeers").
 | 
							Str("func", "getDirectPeers").
 | 
				
			||||||
@ -326,7 +359,11 @@ func (ms MachinesP) String() string {
 | 
				
			|||||||
	return fmt.Sprintf("[ %s ](%d)", strings.Join(temp, ", "), len(temp))
 | 
						return fmt.Sprintf("[ %s ](%d)", strings.Join(temp, ", "), len(temp))
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (ms Machines) toNodes(baseDomain string, dnsConfig *tailcfg.DNSConfig, includeRoutes bool) ([]*tailcfg.Node, error) {
 | 
					func (ms Machines) toNodes(
 | 
				
			||||||
 | 
						baseDomain string,
 | 
				
			||||||
 | 
						dnsConfig *tailcfg.DNSConfig,
 | 
				
			||||||
 | 
						includeRoutes bool,
 | 
				
			||||||
 | 
					) ([]*tailcfg.Node, error) {
 | 
				
			||||||
	nodes := make([]*tailcfg.Node, len(ms))
 | 
						nodes := make([]*tailcfg.Node, len(ms))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	for index, machine := range ms {
 | 
						for index, machine := range ms {
 | 
				
			||||||
@ -446,8 +483,10 @@ func (m Machine) toNode(baseDomain string, dnsConfig *tailcfg.DNSConfig, include
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	n := tailcfg.Node{
 | 
						n := tailcfg.Node{
 | 
				
			||||||
		ID:         tailcfg.NodeID(m.ID),                               // this is the actual ID
 | 
							ID: tailcfg.NodeID(m.ID), // this is the actual ID
 | 
				
			||||||
		StableID:   tailcfg.StableNodeID(strconv.FormatUint(m.ID, 10)), // in headscale, unlike tailcontrol server, IDs are permanent
 | 
							StableID: tailcfg.StableNodeID(
 | 
				
			||||||
 | 
								strconv.FormatUint(m.ID, 10),
 | 
				
			||||||
 | 
							), // in headscale, unlike tailcontrol server, IDs are permanent
 | 
				
			||||||
		Name:       hostname,
 | 
							Name:       hostname,
 | 
				
			||||||
		User:       tailcfg.UserID(m.NamespaceID),
 | 
							User:       tailcfg.UserID(m.NamespaceID),
 | 
				
			||||||
		Key:        tailcfg.NodeKey(nKey),
 | 
							Key:        tailcfg.NodeKey(nKey),
 | 
				
			||||||
 | 
				
			|||||||
@ -246,6 +246,17 @@ func (n *Namespace) toUser() *tailcfg.User {
 | 
				
			|||||||
	return &u
 | 
						return &u
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (n *Namespace) toLogin() *tailcfg.Login {
 | 
				
			||||||
 | 
						l := tailcfg.Login{
 | 
				
			||||||
 | 
							ID:            tailcfg.LoginID(n.ID),
 | 
				
			||||||
 | 
							LoginName:     n.Name,
 | 
				
			||||||
 | 
							DisplayName:   n.Name,
 | 
				
			||||||
 | 
							ProfilePicURL: "",
 | 
				
			||||||
 | 
							Domain:        "headscale.net",
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return &l
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func getMapResponseUserProfiles(m Machine, peers Machines) []tailcfg.UserProfile {
 | 
					func getMapResponseUserProfiles(m Machine, peers Machines) []tailcfg.UserProfile {
 | 
				
			||||||
	namespaceMap := make(map[string]Namespace)
 | 
						namespaceMap := make(map[string]Namespace)
 | 
				
			||||||
	namespaceMap[m.Namespace.Name] = m.Namespace
 | 
						namespaceMap[m.Namespace.Name] = m.Namespace
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										228
									
								
								oidc.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										228
									
								
								oidc.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,228 @@
 | 
				
			|||||||
 | 
					package headscale
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"context"
 | 
				
			||||||
 | 
						"crypto/rand"
 | 
				
			||||||
 | 
						"encoding/hex"
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
						"net/http"
 | 
				
			||||||
 | 
						"regexp"
 | 
				
			||||||
 | 
						"strings"
 | 
				
			||||||
 | 
						"time"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"github.com/coreos/go-oidc/v3/oidc"
 | 
				
			||||||
 | 
						"github.com/gin-gonic/gin"
 | 
				
			||||||
 | 
						"github.com/patrickmn/go-cache"
 | 
				
			||||||
 | 
						"github.com/rs/zerolog/log"
 | 
				
			||||||
 | 
						"golang.org/x/oauth2"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type IDTokenClaims struct {
 | 
				
			||||||
 | 
						Name     string   `json:"name,omitempty"`
 | 
				
			||||||
 | 
						Groups   []string `json:"groups,omitempty"`
 | 
				
			||||||
 | 
						Email    string   `json:"email"`
 | 
				
			||||||
 | 
						Username string   `json:"preferred_username,omitempty"`
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (h *Headscale) initOIDC() error {
 | 
				
			||||||
 | 
						var err error
 | 
				
			||||||
 | 
						// grab oidc config if it hasn't been already
 | 
				
			||||||
 | 
						if h.oauth2Config == nil {
 | 
				
			||||||
 | 
							h.oidcProvider, err = oidc.NewProvider(context.Background(), h.cfg.OIDC.Issuer)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								log.Error().Msgf("Could not retrieve OIDC Config: %s", err.Error())
 | 
				
			||||||
 | 
								return err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							h.oauth2Config = &oauth2.Config{
 | 
				
			||||||
 | 
								ClientID:     h.cfg.OIDC.ClientID,
 | 
				
			||||||
 | 
								ClientSecret: h.cfg.OIDC.ClientSecret,
 | 
				
			||||||
 | 
								Endpoint:     h.oidcProvider.Endpoint(),
 | 
				
			||||||
 | 
								RedirectURL:  fmt.Sprintf("%s/oidc/callback", strings.TrimSuffix(h.cfg.ServerURL, "/")),
 | 
				
			||||||
 | 
								Scopes:       []string{oidc.ScopeOpenID, "profile", "email"},
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// init the state cache if it hasn't been already
 | 
				
			||||||
 | 
						if h.oidcStateCache == nil {
 | 
				
			||||||
 | 
							h.oidcStateCache = cache.New(time.Minute*5, time.Minute*10)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// RegisterOIDC redirects to the OIDC provider for authentication
 | 
				
			||||||
 | 
					// Puts machine key in cache so the callback can retrieve it using the oidc state param
 | 
				
			||||||
 | 
					// Listens in /oidc/register/:mKey
 | 
				
			||||||
 | 
					func (h *Headscale) RegisterOIDC(c *gin.Context) {
 | 
				
			||||||
 | 
						mKeyStr := c.Param("mkey")
 | 
				
			||||||
 | 
						if mKeyStr == "" {
 | 
				
			||||||
 | 
							c.String(http.StatusBadRequest, "Wrong params")
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						b := make([]byte, 16)
 | 
				
			||||||
 | 
						_, err := rand.Read(b)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							log.Error().Msg("could not read 16 bytes from rand")
 | 
				
			||||||
 | 
							c.String(http.StatusInternalServerError, "could not read 16 bytes from rand")
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						stateStr := hex.EncodeToString(b)[:32]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// place the machine key into the state cache, so it can be retrieved later
 | 
				
			||||||
 | 
						h.oidcStateCache.Set(stateStr, mKeyStr, time.Minute*5)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						authUrl := h.oauth2Config.AuthCodeURL(stateStr)
 | 
				
			||||||
 | 
						log.Debug().Msgf("Redirecting to %s for authentication", authUrl)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						c.Redirect(http.StatusFound, authUrl)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// OIDCCallback handles the callback from the OIDC endpoint
 | 
				
			||||||
 | 
					// Retrieves the mkey from the state cache and adds the machine to the users email namespace
 | 
				
			||||||
 | 
					// TODO: A confirmation page for new machines should be added to avoid phishing vulnerabilities
 | 
				
			||||||
 | 
					// TODO: Add groups information from OIDC tokens into machine HostInfo
 | 
				
			||||||
 | 
					// Listens in /oidc/callback
 | 
				
			||||||
 | 
					func (h *Headscale) OIDCCallback(c *gin.Context) {
 | 
				
			||||||
 | 
						code := c.Query("code")
 | 
				
			||||||
 | 
						state := c.Query("state")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if code == "" || state == "" {
 | 
				
			||||||
 | 
							c.String(http.StatusBadRequest, "Wrong params")
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						oauth2Token, err := h.oauth2Config.Exchange(context.Background(), code)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							c.String(http.StatusBadRequest, "Could not exchange code for token")
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						log.Debug().Msgf("AccessToken: %v", oauth2Token.AccessToken)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						rawIDToken, rawIDTokenOK := oauth2Token.Extra("id_token").(string)
 | 
				
			||||||
 | 
						if !rawIDTokenOK {
 | 
				
			||||||
 | 
							c.String(http.StatusBadRequest, "Could not extract ID Token")
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						verifier := h.oidcProvider.Verifier(&oidc.Config{ClientID: h.cfg.OIDC.ClientID})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						idToken, err := verifier.Verify(context.Background(), rawIDToken)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							c.String(http.StatusBadRequest, "Failed to verify id token: %s", err.Error())
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// TODO: we can use userinfo at some point to grab additional information about the user (groups membership, etc)
 | 
				
			||||||
 | 
						//userInfo, err := oidcProvider.UserInfo(context.Background(), oauth2.StaticTokenSource(oauth2Token))
 | 
				
			||||||
 | 
						//if err != nil {
 | 
				
			||||||
 | 
						//	c.String(http.StatusBadRequest, fmt.Sprintf("Failed to retrieve userinfo: %s", err))
 | 
				
			||||||
 | 
						//	return
 | 
				
			||||||
 | 
						//}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Extract custom claims
 | 
				
			||||||
 | 
						var claims IDTokenClaims
 | 
				
			||||||
 | 
						if err = idToken.Claims(&claims); err != nil {
 | 
				
			||||||
 | 
							c.String(http.StatusBadRequest, fmt.Sprintf("Failed to decode id token claims: %s", err))
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// retrieve machinekey from state cache
 | 
				
			||||||
 | 
						mKeyIf, mKeyFound := h.oidcStateCache.Get(state)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if !mKeyFound {
 | 
				
			||||||
 | 
							log.Error().Msg("requested machine state key expired before authorisation completed")
 | 
				
			||||||
 | 
							c.String(http.StatusBadRequest, "state has expired")
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						mKeyStr, mKeyOK := mKeyIf.(string)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if !mKeyOK {
 | 
				
			||||||
 | 
							log.Error().Msg("could not get machine key from cache")
 | 
				
			||||||
 | 
							c.String(http.StatusInternalServerError, "could not get machine key from cache")
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// retrieve machine information
 | 
				
			||||||
 | 
						m, err := h.GetMachineByMachineKey(mKeyStr)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							log.Error().Msg("machine key not found in database")
 | 
				
			||||||
 | 
							c.String(http.StatusInternalServerError, "could not get machine info from database")
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						now := time.Now().UTC()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if nsName, ok := h.getNamespaceFromEmail(claims.Email); ok {
 | 
				
			||||||
 | 
							// register the machine if it's new
 | 
				
			||||||
 | 
							if !m.Registered {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								log.Debug().Msg("Registering new machine after successful callback")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								ns, err := h.GetNamespace(nsName)
 | 
				
			||||||
 | 
								if err != nil {
 | 
				
			||||||
 | 
									ns, err = h.CreateNamespace(nsName)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									if err != nil {
 | 
				
			||||||
 | 
										log.Error().Msgf("could not create new namespace '%s'", claims.Email)
 | 
				
			||||||
 | 
										c.String(http.StatusInternalServerError, "could not create new namespace")
 | 
				
			||||||
 | 
										return
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								ip, err := h.getAvailableIP()
 | 
				
			||||||
 | 
								if err != nil {
 | 
				
			||||||
 | 
									c.String(http.StatusInternalServerError, "could not get an IP from the pool")
 | 
				
			||||||
 | 
									return
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								m.IPAddress = ip.String()
 | 
				
			||||||
 | 
								m.NamespaceID = ns.ID
 | 
				
			||||||
 | 
								m.Registered = true
 | 
				
			||||||
 | 
								m.RegisterMethod = "oidc"
 | 
				
			||||||
 | 
								m.LastSuccessfulUpdate = &now
 | 
				
			||||||
 | 
								h.db.Save(&m)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							h.updateMachineExpiry(m)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							c.Data(http.StatusOK, "text/html; charset=utf-8", []byte(fmt.Sprintf(`
 | 
				
			||||||
 | 
					<html>
 | 
				
			||||||
 | 
					<body>
 | 
				
			||||||
 | 
					<h1>headscale</h1>
 | 
				
			||||||
 | 
					<p>
 | 
				
			||||||
 | 
					    Authenticated as %s, you can now close this window.
 | 
				
			||||||
 | 
					</p>
 | 
				
			||||||
 | 
					</body>
 | 
				
			||||||
 | 
					</html>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					`, claims.Email)))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						log.Error().
 | 
				
			||||||
 | 
							Str("email", claims.Email).
 | 
				
			||||||
 | 
							Str("username", claims.Username).
 | 
				
			||||||
 | 
							Str("machine", m.Name).
 | 
				
			||||||
 | 
							Msg("Email could not be mapped to a namespace")
 | 
				
			||||||
 | 
						c.String(http.StatusBadRequest, "email from claim could not be mapped to a namespace")
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// getNamespaceFromEmail passes the users email through a list of "matchers"
 | 
				
			||||||
 | 
					// and iterates through them until it matches and returns a namespace.
 | 
				
			||||||
 | 
					// If no match is found, an empty string will be returned.
 | 
				
			||||||
 | 
					// TODO(kradalby): golang Maps key order is not stable, so this list is _not_ deterministic. Find a way to make the list of keys stable, preferably in the order presented in a users configuration.
 | 
				
			||||||
 | 
					func (h *Headscale) getNamespaceFromEmail(email string) (string, bool) {
 | 
				
			||||||
 | 
						for match, namespace := range h.cfg.OIDC.MatchMap {
 | 
				
			||||||
 | 
							regex := regexp.MustCompile(match)
 | 
				
			||||||
 | 
							if regex.MatchString(email) {
 | 
				
			||||||
 | 
								return namespace, true
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return "", false
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										174
									
								
								oidc_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										174
									
								
								oidc_test.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,174 @@
 | 
				
			|||||||
 | 
					package headscale
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"sync"
 | 
				
			||||||
 | 
						"testing"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"github.com/coreos/go-oidc/v3/oidc"
 | 
				
			||||||
 | 
						"github.com/patrickmn/go-cache"
 | 
				
			||||||
 | 
						"golang.org/x/oauth2"
 | 
				
			||||||
 | 
						"gorm.io/gorm"
 | 
				
			||||||
 | 
						"tailscale.com/tailcfg"
 | 
				
			||||||
 | 
						"tailscale.com/types/wgkey"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func TestHeadscale_getNamespaceFromEmail(t *testing.T) {
 | 
				
			||||||
 | 
						type fields struct {
 | 
				
			||||||
 | 
							cfg             Config
 | 
				
			||||||
 | 
							db              *gorm.DB
 | 
				
			||||||
 | 
							dbString        string
 | 
				
			||||||
 | 
							dbType          string
 | 
				
			||||||
 | 
							dbDebug         bool
 | 
				
			||||||
 | 
							publicKey       *wgkey.Key
 | 
				
			||||||
 | 
							privateKey      *wgkey.Private
 | 
				
			||||||
 | 
							aclPolicy       *ACLPolicy
 | 
				
			||||||
 | 
							aclRules        *[]tailcfg.FilterRule
 | 
				
			||||||
 | 
							lastStateChange sync.Map
 | 
				
			||||||
 | 
							oidcProvider    *oidc.Provider
 | 
				
			||||||
 | 
							oauth2Config    *oauth2.Config
 | 
				
			||||||
 | 
							oidcStateCache  *cache.Cache
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						type args struct {
 | 
				
			||||||
 | 
							email string
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						tests := []struct {
 | 
				
			||||||
 | 
							name   string
 | 
				
			||||||
 | 
							fields fields
 | 
				
			||||||
 | 
							args   args
 | 
				
			||||||
 | 
							want   string
 | 
				
			||||||
 | 
							want1  bool
 | 
				
			||||||
 | 
						}{
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name: "match all",
 | 
				
			||||||
 | 
								fields: fields{
 | 
				
			||||||
 | 
									cfg: Config{
 | 
				
			||||||
 | 
										OIDC: OIDCConfig{
 | 
				
			||||||
 | 
											MatchMap: map[string]string{
 | 
				
			||||||
 | 
												".*": "space",
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								args: args{
 | 
				
			||||||
 | 
									email: "test@example.no",
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								want:  "space",
 | 
				
			||||||
 | 
								want1: true,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name: "match user",
 | 
				
			||||||
 | 
								fields: fields{
 | 
				
			||||||
 | 
									cfg: Config{
 | 
				
			||||||
 | 
										OIDC: OIDCConfig{
 | 
				
			||||||
 | 
											MatchMap: map[string]string{
 | 
				
			||||||
 | 
												"specific@user\\.no": "user-namespace",
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								args: args{
 | 
				
			||||||
 | 
									email: "specific@user.no",
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								want:  "user-namespace",
 | 
				
			||||||
 | 
								want1: true,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name: "match domain",
 | 
				
			||||||
 | 
								fields: fields{
 | 
				
			||||||
 | 
									cfg: Config{
 | 
				
			||||||
 | 
										OIDC: OIDCConfig{
 | 
				
			||||||
 | 
											MatchMap: map[string]string{
 | 
				
			||||||
 | 
												".*@example\\.no": "example",
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								args: args{
 | 
				
			||||||
 | 
									email: "test@example.no",
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								want:  "example",
 | 
				
			||||||
 | 
								want1: true,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name: "multi match domain",
 | 
				
			||||||
 | 
								fields: fields{
 | 
				
			||||||
 | 
									cfg: Config{
 | 
				
			||||||
 | 
										OIDC: OIDCConfig{
 | 
				
			||||||
 | 
											MatchMap: map[string]string{
 | 
				
			||||||
 | 
												".*@example\\.no": "exammple",
 | 
				
			||||||
 | 
												".*@gmail\\.com":  "gmail",
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								args: args{
 | 
				
			||||||
 | 
									email: "someuser@gmail.com",
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								want:  "gmail",
 | 
				
			||||||
 | 
								want1: true,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name: "no match domain",
 | 
				
			||||||
 | 
								fields: fields{
 | 
				
			||||||
 | 
									cfg: Config{
 | 
				
			||||||
 | 
										OIDC: OIDCConfig{
 | 
				
			||||||
 | 
											MatchMap: map[string]string{
 | 
				
			||||||
 | 
												".*@dontknow.no": "never",
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								args: args{
 | 
				
			||||||
 | 
									email: "test@wedontknow.no",
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								want:  "",
 | 
				
			||||||
 | 
								want1: false,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name: "multi no match domain",
 | 
				
			||||||
 | 
								fields: fields{
 | 
				
			||||||
 | 
									cfg: Config{
 | 
				
			||||||
 | 
										OIDC: OIDCConfig{
 | 
				
			||||||
 | 
											MatchMap: map[string]string{
 | 
				
			||||||
 | 
												".*@dontknow.no":   "never",
 | 
				
			||||||
 | 
												".*@wedontknow.no": "other",
 | 
				
			||||||
 | 
												".*\\.no":          "stuffy",
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								args: args{
 | 
				
			||||||
 | 
									email: "tasy@nonofthem.com",
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								want:  "",
 | 
				
			||||||
 | 
								want1: false,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						//nolint
 | 
				
			||||||
 | 
						for _, tt := range tests {
 | 
				
			||||||
 | 
							t.Run(tt.name, func(t *testing.T) {
 | 
				
			||||||
 | 
								h := &Headscale{
 | 
				
			||||||
 | 
									cfg:             tt.fields.cfg,
 | 
				
			||||||
 | 
									db:              tt.fields.db,
 | 
				
			||||||
 | 
									dbString:        tt.fields.dbString,
 | 
				
			||||||
 | 
									dbType:          tt.fields.dbType,
 | 
				
			||||||
 | 
									dbDebug:         tt.fields.dbDebug,
 | 
				
			||||||
 | 
									publicKey:       tt.fields.publicKey,
 | 
				
			||||||
 | 
									privateKey:      tt.fields.privateKey,
 | 
				
			||||||
 | 
									aclPolicy:       tt.fields.aclPolicy,
 | 
				
			||||||
 | 
									aclRules:        tt.fields.aclRules,
 | 
				
			||||||
 | 
									lastStateChange: tt.fields.lastStateChange,
 | 
				
			||||||
 | 
									oidcProvider:    tt.fields.oidcProvider,
 | 
				
			||||||
 | 
									oauth2Config:    tt.fields.oauth2Config,
 | 
				
			||||||
 | 
									oidcStateCache:  tt.fields.oidcStateCache,
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								got, got1 := h.getNamespaceFromEmail(tt.args.email)
 | 
				
			||||||
 | 
								if got != tt.want {
 | 
				
			||||||
 | 
									t.Errorf("Headscale.getNamespaceFromEmail() got = %v, want %v", got, tt.want)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								if got1 != tt.want1 {
 | 
				
			||||||
 | 
									t.Errorf("Headscale.getNamespaceFromEmail() got1 = %v, want %v", got1, tt.want1)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										24
									
								
								proto/buf.lock
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								proto/buf.lock
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,24 @@
 | 
				
			|||||||
 | 
					# Generated by buf. DO NOT EDIT.
 | 
				
			||||||
 | 
					version: v1
 | 
				
			||||||
 | 
					deps:
 | 
				
			||||||
 | 
					  - remote: buf.build
 | 
				
			||||||
 | 
					    owner: googleapis
 | 
				
			||||||
 | 
					    repository: googleapis
 | 
				
			||||||
 | 
					    branch: main
 | 
				
			||||||
 | 
					    commit: cd101b0abb7b4404a0b1ecc1afd4ce10
 | 
				
			||||||
 | 
					    digest: b1-H4GHwHVHcJBbVPg-Cdmnx812reFCDQws_QoQ0W2hYQA=
 | 
				
			||||||
 | 
					    create_time: 2021-10-23T15:04:06.087748Z
 | 
				
			||||||
 | 
					  - remote: buf.build
 | 
				
			||||||
 | 
					    owner: grpc-ecosystem
 | 
				
			||||||
 | 
					    repository: grpc-gateway
 | 
				
			||||||
 | 
					    branch: main
 | 
				
			||||||
 | 
					    commit: ff83506eb9cc4cf8972f49ce87e6ed3e
 | 
				
			||||||
 | 
					    digest: b1-iLPHgLaoeWWinMiXXqPnxqE4BThtY3eSbswVGh9GOGI=
 | 
				
			||||||
 | 
					    create_time: 2021-10-23T16:26:52.283938Z
 | 
				
			||||||
 | 
					  - remote: buf.build
 | 
				
			||||||
 | 
					    owner: ufoundit-dev
 | 
				
			||||||
 | 
					    repository: protoc-gen-gorm
 | 
				
			||||||
 | 
					    branch: main
 | 
				
			||||||
 | 
					    commit: e2ecbaa0d37843298104bd29fd866df8
 | 
				
			||||||
 | 
					    digest: b1-SV9yKH_8P-IKTOlHZxP-bb0ALANYeEqH_mtPA0EWfLc=
 | 
				
			||||||
 | 
					    create_time: 2021-10-08T06:03:05.64876Z
 | 
				
			||||||
							
								
								
									
										12
									
								
								proto/buf.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								proto/buf.yaml
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,12 @@
 | 
				
			|||||||
 | 
					version: v1
 | 
				
			||||||
 | 
					lint:
 | 
				
			||||||
 | 
					  use:
 | 
				
			||||||
 | 
					    - DEFAULT
 | 
				
			||||||
 | 
					breaking:
 | 
				
			||||||
 | 
					  use:
 | 
				
			||||||
 | 
					    - FILE
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					deps:
 | 
				
			||||||
 | 
					  - buf.build/googleapis/googleapis
 | 
				
			||||||
 | 
					  - buf.build/grpc-ecosystem/grpc-gateway
 | 
				
			||||||
 | 
					  - buf.build/ufoundit-dev/protoc-gen-gorm
 | 
				
			||||||
							
								
								
									
										71
									
								
								proto/v1/headscale.proto
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										71
									
								
								proto/v1/headscale.proto
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,71 @@
 | 
				
			|||||||
 | 
					syntax = "proto3";
 | 
				
			||||||
 | 
					package headscale.v1;
 | 
				
			||||||
 | 
					option  go_package = "github.com/juanfont/headscale/gen/go/v1";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import "google/protobuf/timestamp.proto";
 | 
				
			||||||
 | 
					import "google/api/annotations.proto";
 | 
				
			||||||
 | 
					import "options/gorm.proto";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					enum RegisterMethod {
 | 
				
			||||||
 | 
					    AUTH_KEY = 0;
 | 
				
			||||||
 | 
					    CLI      = 1;
 | 
				
			||||||
 | 
					    OIDC     = 2;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					message Namespace {
 | 
				
			||||||
 | 
					    string Name = 1;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					message PreAuthKey {
 | 
				
			||||||
 | 
					    uint64    ID          = 1;
 | 
				
			||||||
 | 
					    string    Key         = 2;
 | 
				
			||||||
 | 
					    uint32    NamespaceID = 3;
 | 
				
			||||||
 | 
					    Namespace Namespace   = 4;
 | 
				
			||||||
 | 
					    bool      Reusable    = 5;
 | 
				
			||||||
 | 
					    bool      Ephemeral   = 6;
 | 
				
			||||||
 | 
					    bool      Used        = 7;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    google.protobuf.Timestamp CreatedAt  = 8;
 | 
				
			||||||
 | 
					    google.protobuf.Timestamp Expiration = 9;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					message GetMachineRequest {
 | 
				
			||||||
 | 
					    uint64 machine_id = 1;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					message Machine {
 | 
				
			||||||
 | 
					    option(gorm.opts).ormable = true;
 | 
				
			||||||
 | 
					    uint64 ID                 = 1;
 | 
				
			||||||
 | 
					    string MachineKey         = 2;
 | 
				
			||||||
 | 
					    string NodeKey            = 3;
 | 
				
			||||||
 | 
					    string DiscoKey           = 4;
 | 
				
			||||||
 | 
					    string IPAddress          = 5;
 | 
				
			||||||
 | 
					    string Name               = 6;
 | 
				
			||||||
 | 
					    uint32 NamespaceID        = 7;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    bool           Registered     = 8;
 | 
				
			||||||
 | 
					    RegisterMethod RegisterMethod = 9;
 | 
				
			||||||
 | 
					    uint32         AuthKeyID      = 10;
 | 
				
			||||||
 | 
					    PreAuthKey     AuthKey        = 11;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    google.protobuf.Timestamp LastSeen             = 12;
 | 
				
			||||||
 | 
					    google.protobuf.Timestamp LastSuccessfulUpdate = 13;
 | 
				
			||||||
 | 
					    google.protobuf.Timestamp Expiry               = 14;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    bytes HostInfo      = 15;
 | 
				
			||||||
 | 
					    bytes Endpoints     = 16;
 | 
				
			||||||
 | 
					    bytes EnabledRoutes = 17;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    google.protobuf.Timestamp CreatedAt = 18;
 | 
				
			||||||
 | 
					    google.protobuf.Timestamp UpdatedAt = 19;
 | 
				
			||||||
 | 
					    google.protobuf.Timestamp DeletedAt = 20;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Gin Router will prefix this with /api/v1
 | 
				
			||||||
 | 
					service HeadscaleService {
 | 
				
			||||||
 | 
					    rpc GetMachine(GetMachineRequest) returns(Machine) {
 | 
				
			||||||
 | 
					        option(google.api.http) = {
 | 
				
			||||||
 | 
					            get : "/api/v1/machine/{machine_id}"
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -43,7 +43,8 @@ func (h *Headscale) AddSharedMachineToNamespace(m *Machine, ns *Namespace) error
 | 
				
			|||||||
// RemoveSharedMachineFromNamespace removes a shared machine from a namespace
 | 
					// RemoveSharedMachineFromNamespace removes a shared machine from a namespace
 | 
				
			||||||
func (h *Headscale) RemoveSharedMachineFromNamespace(m *Machine, ns *Namespace) error {
 | 
					func (h *Headscale) RemoveSharedMachineFromNamespace(m *Machine, ns *Namespace) error {
 | 
				
			||||||
	if m.NamespaceID == ns.ID {
 | 
						if m.NamespaceID == ns.ID {
 | 
				
			||||||
		return errorSameNamespace
 | 
							// Can't unshare from primary namespace
 | 
				
			||||||
 | 
							return errorMachineNotShared
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	sharedMachine := SharedMachine{}
 | 
						sharedMachine := SharedMachine{}
 | 
				
			||||||
 | 
				
			|||||||
@ -86,6 +86,9 @@ func (s *Suite) TestUnshare(c *check.C) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	err = h.RemoveSharedMachineFromNamespace(m2, n1)
 | 
						err = h.RemoveSharedMachineFromNamespace(m2, n1)
 | 
				
			||||||
	c.Assert(err, check.Equals, errorMachineNotShared)
 | 
						c.Assert(err, check.Equals, errorMachineNotShared)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						err = h.RemoveSharedMachineFromNamespace(m1, n1)
 | 
				
			||||||
 | 
						c.Assert(err, check.Equals, errorMachineNotShared)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (s *Suite) TestAlreadyShared(c *check.C) {
 | 
					func (s *Suite) TestAlreadyShared(c *check.C) {
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										12
									
								
								tools.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								tools.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,12 @@
 | 
				
			|||||||
 | 
					//go:build tools
 | 
				
			||||||
 | 
					// +build tools
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					package tools
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						_ "github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-grpc-gateway"
 | 
				
			||||||
 | 
						_ "github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-openapiv2"
 | 
				
			||||||
 | 
						_ "github.com/infobloxopen/protoc-gen-gorm"
 | 
				
			||||||
 | 
						_ "google.golang.org/grpc/cmd/protoc-gen-go-grpc"
 | 
				
			||||||
 | 
						_ "google.golang.org/protobuf/cmd/protoc-gen-go"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
		Loading…
	
		Reference in New Issue
	
	Block a user