diff --git a/.dockerignore b/.dockerignore
index f90134b3..057a20e7 100644
--- a/.dockerignore
+++ b/.dockerignore
@@ -15,3 +15,4 @@ README.md
LICENSE
.vscode
+*.sock
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index f1773af9..ca4d4cf2 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -27,7 +27,7 @@ jobs:
sudo apt update
sudo apt install -y make
- - name: Run lint
+ - name: Run build
run: make build
- uses: actions/upload-artifact@v2
diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml
index c0286571..6b561d24 100644
--- a/.github/workflows/lint.yml
+++ b/.github/workflows/lint.yml
@@ -18,22 +18,3 @@ jobs:
# below, but it's still much faster in the end than installing
# golangci-lint manually in the `Run lint` step.
- 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
diff --git a/.golangci.yaml b/.golangci.yaml
new file mode 100644
index 00000000..a97c2bb5
--- /dev/null
+++ b/.golangci.yaml
@@ -0,0 +1,7 @@
+---
+run:
+ timeout: 5m
+
+issues:
+ skip-dirs:
+ - gen
diff --git a/Dockerfile b/Dockerfile
index 6e216aad..9590070b 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,11 +1,13 @@
+FROM bufbuild/buf:1.0.0-rc6 as buf
+
FROM golang:1.17.1-bullseye AS build
ENV GOPATH /go
+WORKDIR /go/src/headscale
COPY go.mod go.sum /go/src/headscale/
-WORKDIR /go/src/headscale
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 test -e /go/bin/headscale
diff --git a/Makefile b/Makefile
index 755253fc..92beaefb 100644
--- a/Makefile
+++ b/Makefile
@@ -19,9 +19,21 @@ coverprofile_html:
go tool cover -html=coverage.out
lint:
- golint
- golangci-lint run --timeout 5m
+ golangci-lint run --fix
+
+proto-lint:
+ cd proto/ && buf lint
compress: build
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
diff --git a/README.md b/README.md
index a3c09396..709f0ad5 100644
--- a/README.md
+++ b/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.
+**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
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] Support for alternative IP ranges in the tailnets (default Tailscale's 100.64.0.0/10)
- [x] DNS (passing DNS servers to nodes)
+- [x] Single-Sign-On (via Open ID Connect)
- [x] Share nodes between namespaces
- [x] MagicDNS (see `docs/`)
@@ -47,7 +50,6 @@ headscale implements this coordination server.
Suggestions/PRs welcomed!
-
## Running headscale
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.
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
@@ -91,6 +127,13 @@ Please have a look at the documentation under [`docs/`](docs/).
ohdearaugustin
+
+
+
+
+ unreality
+
+
@@ -98,6 +141,8 @@ Please have a look at the documentation under [`docs/`](docs/).
Aaron Bieber
+
+
@@ -105,8 +150,6 @@ Please have a look at the documentation under [`docs/`](docs/).
Paul Tötterman
-
-
@@ -142,6 +185,8 @@ Please have a look at the documentation under [`docs/`](docs/).
Felix Kronlage-Dammers
+
+
@@ -149,8 +194,6 @@ Please have a look at the documentation under [`docs/`](docs/).
Felix Yan
-
-
@@ -186,6 +229,8 @@ Please have a look at the documentation under [`docs/`](docs/).
Tjerk Woudsma
+
+
@@ -193,8 +238,6 @@ Please have a look at the documentation under [`docs/`](docs/).
Zakhar Bessarab
-
-
diff --git a/api.go b/api.go
index a31cf529..ad87a7e9 100644
--- a/api.go
+++ b/api.go
@@ -7,6 +7,7 @@ import (
"fmt"
"io"
"net/http"
+ "strings"
"time"
"github.com/rs/zerolog/log"
@@ -43,7 +44,7 @@ func (h *Headscale) RegisterWebAPI(c *gin.Context) {
- headscale -n NAMESPACE nodes register %s
+ headscale -n NAMESPACE nodes register -k %s
@@ -64,7 +65,7 @@ func (h *Headscale) RegistrationHandler(c *gin.Context) {
Str("handler", "Registration").
Err(err).
Msg("Cannot parse machine key")
- machineRegistrations.WithLabelValues("unkown", "web", "error", "unknown").Inc()
+ machineRegistrations.WithLabelValues("unknown", "web", "error", "unknown").Inc()
c.String(http.StatusInternalServerError, "Sad!")
return
}
@@ -75,37 +76,33 @@ func (h *Headscale) RegistrationHandler(c *gin.Context) {
Str("handler", "Registration").
Err(err).
Msg("Cannot decode message")
- machineRegistrations.WithLabelValues("unkown", "web", "error", "unknown").Inc()
+ machineRegistrations.WithLabelValues("unknown", "web", "error", "unknown").Inc()
c.String(http.StatusInternalServerError, "Very sad!")
return
}
now := time.Now().UTC()
- var m Machine
- if result := h.db.Preload("Namespace").First(&m, "machine_key = ?", mKey.HexString()); errors.Is(
- result.Error,
- gorm.ErrRecordNotFound,
- ) {
+ m, err := h.GetMachineByMachineKey(mKey.HexString())
+ if errors.Is(err, gorm.ErrRecordNotFound) {
log.Info().Str("machine", req.Hostinfo.Hostname).Msg("New machine")
- m = Machine{
- Expiry: &req.Expiry,
- MachineKey: mKey.HexString(),
- Name: req.Hostinfo.Hostname,
- NodeKey: wgkey.Key(req.NodeKey).HexString(),
- LastSuccessfulUpdate: &now,
+ newMachine := Machine{
+ Expiry: &time.Time{},
+ MachineKey: mKey.HexString(),
+ Name: req.Hostinfo.Hostname,
}
- if err := h.db.Create(&m).Error; err != nil {
+ if err := h.db.Create(&newMachine).Error; err != nil {
log.Error().
Str("handler", "Registration").
Err(err).
Msg("Could not create row")
- machineRegistrations.WithLabelValues("unkown", "web", "error", m.Namespace.Name).Inc()
+ machineRegistrations.WithLabelValues("unknown", "web", "error", m.Namespace.Name).Inc()
return
}
+ m = &newMachine
}
if !m.Registered && req.Auth.AuthKey != "" {
- h.handleAuthKey(c, h.db, mKey, req, m)
+ h.handleAuthKey(c, h.db, mKey, req, *m)
return
}
@@ -113,7 +110,36 @@ func (h *Headscale) RegistrationHandler(c *gin.Context) {
// We have the updated key!
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().
Str("handler", "Registration").
Str("machine", m.Name).
@@ -122,6 +148,8 @@ func (h *Headscale) RegistrationHandler(c *gin.Context) {
resp.AuthURL = ""
resp.MachineAuthorized = true
resp.User = *m.Namespace.toUser()
+ resp.Login = *m.Namespace.toLogin()
+
respBody, err := encode(resp, &mKey, h.privateKey)
if err != nil {
log.Error().
@@ -137,12 +165,30 @@ func (h *Headscale) RegistrationHandler(c *gin.Context) {
return
}
+ // The client has registered before, but has expired
log.Debug().
Str("handler", "Registration").
Str("machine", m.Name).
- Msg("Not registered and not NodeKey rotation. Sending a authurl to register")
- resp.AuthURL = fmt.Sprintf("%s/register?key=%s",
- h.cfg.ServerURL, mKey.HexString())
+ Msg("Machine registration has expired. Sending a authurl to register")
+
+ 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)
if err != nil {
log.Error().
@@ -158,8 +204,8 @@ func (h *Headscale) RegistrationHandler(c *gin.Context) {
return
}
- // The NodeKey we have matches OldNodeKey, which means this is a refresh after an key expiration
- if m.NodeKey == wgkey.Key(req.OldNodeKey).HexString() {
+ // The NodeKey we have matches OldNodeKey, which means this is a refresh after a key expiration
+ if m.NodeKey == wgkey.Key(req.OldNodeKey).HexString() && m.Expiry.UTC().After(now) {
log.Debug().
Str("handler", "Registration").
Str("machine", m.Name).
@@ -182,35 +228,23 @@ func (h *Headscale) RegistrationHandler(c *gin.Context) {
return
}
- // We arrive here after a client is restarted without finalizing the authentication flow or
- // 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
- }
-
+ // The machine registration is new, redirect the client to the registration URL
log.Debug().
Str("handler", "Registration").
Str("machine", m.Name).
Msg("The node is sending us a new NodeKey, sending auth url")
- 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())
+ }
+
+ // 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)
if err != nil {
log.Error().
diff --git a/app.go b/app.go
index 546eb866..1f456665 100644
--- a/app.go
+++ b/app.go
@@ -1,22 +1,42 @@
package headscale
import (
+ "context"
+ "crypto/tls"
"errors"
"fmt"
+ "io"
+ "net"
"net/http"
"net/url"
"os"
+ "os/signal"
"sort"
"strings"
"sync"
+ "syscall"
"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/grpc-ecosystem/grpc-gateway/v2/runtime"
+ apiV1 "github.com/juanfont/headscale/gen/go/headscale/v1"
+ "github.com/rs/zerolog/log"
+ "github.com/soheilhy/cmux"
ginprometheus "github.com/zsais/go-gin-prometheus"
"golang.org/x/crypto/acme"
"golang.org/x/crypto/acme/autocert"
+ "golang.org/x/sync/errgroup"
+ "google.golang.org/grpc"
+ "google.golang.org/grpc/codes"
+ "google.golang.org/grpc/credentials"
+ "google.golang.org/grpc/metadata"
+ "google.golang.org/grpc/peer"
+ "google.golang.org/grpc/reflection"
+ "google.golang.org/grpc/status"
"gorm.io/gorm"
"inet.af/netaddr"
"tailscale.com/tailcfg"
@@ -24,7 +44,11 @@ import (
"tailscale.com/types/wgkey"
)
-// Config contains the initial Headscale configuration
+const (
+ AUTH_PREFIX = "Bearer "
+)
+
+// Config contains the initial Headscale configuration.
type Config struct {
ServerURL string
Addr string
@@ -55,6 +79,20 @@ type Config struct {
ACMEEmail string
DNSConfig *tailcfg.DNSConfig
+
+ UnixSocket string
+
+ OIDC OIDCConfig
+
+ MaxMachineRegistrationDuration time.Duration
+ DefaultMachineRegistrationDuration time.Duration
+}
+
+type OIDCConfig struct {
+ Issuer string
+ ClientID string
+ ClientSecret string
+ MatchMap map[string]string
}
type DERPConfig struct {
@@ -64,7 +102,7 @@ type DERPConfig struct {
UpdateFrequency time.Duration
}
-// Headscale represents the base app of the service
+// Headscale represents the base app of the service.
type Headscale struct {
cfg Config
db *gorm.DB
@@ -80,14 +118,19 @@ type Headscale struct {
aclRules *[]tailcfg.FilterRule
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) {
content, err := os.ReadFile(cfg.PrivateKeyPath)
if err != nil {
return nil, err
}
+
privKey, err := wgkey.ParsePrivate(string(content))
if err != nil {
return nil, err
@@ -119,6 +162,13 @@ func NewHeadscale(cfg Config) (*Headscale, error) {
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
magicDNSDomains, err := generateMagicDNSRootDomains(h.cfg.IPPrefix, h.cfg.BaseDomain)
if err != nil {
@@ -136,14 +186,14 @@ func NewHeadscale(cfg Config) (*Headscale, error) {
return &h, nil
}
-// Redirect to our TLS url
+// Redirect to our TLS url.
func (h *Headscale) redirect(w http.ResponseWriter, req *http.Request) {
target := h.cfg.ServerURL + req.URL.RequestURI()
http.Redirect(w, req, target, http.StatusFound)
}
// 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) {
ticker := time.NewTicker(time.Duration(milliSeconds) * time.Millisecond)
for range ticker.C {
@@ -155,18 +205,23 @@ func (h *Headscale) expireEphemeralNodesWorker() {
namespaces, err := h.ListNamespaces()
if err != nil {
log.Error().Err(err).Msg("Error listing namespaces")
+
return
}
+
for _, ns := range *namespaces {
machines, err := h.ListMachinesInNamespace(ns.Name)
if err != nil {
log.Error().Err(err).Str("namespace", ns.Name).Msg("Error listing machines in namespace")
+
return
}
+
for _, m := range *machines {
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")
+
err = h.db.Unscoped().Delete(m).Error
if err != nil {
log.Error().
@@ -176,12 +231,13 @@ func (h *Headscale) expireEphemeralNodesWorker() {
}
}
}
+
h.setLastStateChangeToNow(ns.Name)
}
}
// 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) {
ticker := time.NewTicker(time.Duration(milliSeconds) * time.Millisecond)
for range ticker.C {
@@ -194,24 +250,184 @@ func (h *Headscale) watchForKVUpdatesWorker() {
// more functions will come here in the future
}
-// Serve launches a GIN server with the Headscale API
+func (h *Headscale) grpcAuthenticationInterceptor(ctx context.Context,
+ req interface{},
+ info *grpc.UnaryServerInfo,
+ handler grpc.UnaryHandler) (interface{}, error) {
+
+ // Check if the request is coming from the on-server client.
+ // This is not secure, but it is to maintain maintainability
+ // with the "legacy" database-based client
+ // It is also neede for grpc-gateway to be able to connect to
+ // the server
+ p, _ := peer.FromContext(ctx)
+
+ log.Trace().Caller().Str("client_address", p.Addr.String()).Msg("Client is trying to authenticate")
+
+ md, ok := metadata.FromIncomingContext(ctx)
+ if !ok {
+ log.Error().Caller().Str("client_address", p.Addr.String()).Msg("Retrieving metadata is failed")
+ return ctx, status.Errorf(codes.InvalidArgument, "Retrieving metadata is failed")
+ }
+
+ authHeader, ok := md["authorization"]
+ if !ok {
+ log.Error().Caller().Str("client_address", p.Addr.String()).Msg("Authorization token is not supplied")
+ return ctx, status.Errorf(codes.Unauthenticated, "Authorization token is not supplied")
+ }
+
+ token := authHeader[0]
+
+ if !strings.HasPrefix(token, AUTH_PREFIX) {
+ log.Error().
+ Caller().
+ Str("client_address", p.Addr.String()).
+ Msg(`missing "Bearer " prefix in "Authorization" header`)
+ return ctx, status.Error(codes.Unauthenticated, `missing "Bearer " prefix in "Authorization" header`)
+ }
+
+ // TODO(kradalby): Implement API key backend:
+ // - Table in the DB
+ // - Key name
+ // - Encrypted
+ // - Expiry
+ //
+ // Currently all other than localhost traffic is unauthorized, this is intentional to allow
+ // us to make use of gRPC for our CLI, but not having to implement any of the remote capabilities
+ // and API key auth
+ return ctx, status.Error(codes.Unauthenticated, "Authentication is not implemented yet")
+
+ //if strings.TrimPrefix(token, AUTH_PREFIX) != a.Token {
+ // log.Error().Caller().Str("client_address", p.Addr.String()).Msg("invalid token")
+ // return ctx, status.Error(codes.Unauthenticated, "invalid token")
+ //}
+
+ // return handler(ctx, req)
+}
+
+func (h *Headscale) httpAuthenticationMiddleware(c *gin.Context) {
+ log.Trace().
+ Caller().
+ Str("client_address", c.ClientIP()).
+ Msg("HTTP authentication invoked")
+
+ authHeader := c.GetHeader("authorization")
+
+ if !strings.HasPrefix(authHeader, AUTH_PREFIX) {
+ log.Error().
+ Caller().
+ Str("client_address", c.ClientIP()).
+ Msg(`missing "Bearer " prefix in "Authorization" header`)
+ c.AbortWithStatus(http.StatusUnauthorized)
+
+ return
+ }
+
+ c.AbortWithStatus(http.StatusUnauthorized)
+
+ // TODO(kradalby): Implement API key backend
+ // Currently all traffic is unauthorized, this is intentional to allow
+ // us to make use of gRPC for our CLI, but not having to implement any of the remote capabilities
+ // and API key auth
+ //
+ // if strings.TrimPrefix(authHeader, AUTH_PREFIX) != a.Token {
+ // log.Error().Caller().Str("client_address", c.ClientIP()).Msg("invalid token")
+ // c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error", "unauthorized"})
+
+ // return
+ // }
+
+ // c.Next()
+}
+
+// Serve launches a GIN server with the Headscale API.
func (h *Headscale) Serve() error {
+ var err error
+
+ ctx := context.Background()
+ ctx, cancel := context.WithCancel(ctx)
+
+ defer cancel()
+
+ socketListener, err := net.Listen("unix", h.cfg.UnixSocket)
+ if err != nil {
+ panic(err)
+ }
+
+ // Handle common process-killing signals so we can gracefully shut down:
+ sigc := make(chan os.Signal, 1)
+ signal.Notify(sigc, os.Interrupt, syscall.SIGTERM)
+ go func(c chan os.Signal) {
+ // Wait for a SIGINT or SIGKILL:
+ sig := <-c
+ log.Printf("Caught signal %s: shutting down.", sig)
+ // Stop listening (and unlink the socket if unix type):
+ socketListener.Close()
+ // And we're done:
+ os.Exit(0)
+ }(sigc)
+
+ networkListener, 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(networkListener)
+ // Match gRPC requests here
+ grpcListener := m.MatchWithWriters(
+ cmux.HTTP2MatchHeaderFieldSendSettings("content-type", "application/grpc"),
+ cmux.HTTP2MatchHeaderFieldSendSettings("content-type", "application/grpc+proto"),
+ )
+ // Otherwise match regular http requests.
+ httpListener := m.Match(cmux.Any())
+
+ grpcGatewayMux := runtime.NewServeMux()
+
+ // Make the grpc-gateway connect to grpc over socket
+ grpcGatewayConn, err := grpc.Dial(
+ h.cfg.UnixSocket,
+ []grpc.DialOption{
+ grpc.WithInsecure(),
+ grpc.WithContextDialer(GrpcSocketDialer),
+ }...,
+ )
+ if err != nil {
+ return err
+ }
+
+ // Connect to the gRPC server over localhost to skip
+ // the authentication.
+ err = apiV1.RegisterHeadscaleServiceHandler(ctx, grpcGatewayMux, grpcGatewayConn)
+ if err != nil {
+ return err
+ }
+
r := gin.Default()
p := ginprometheus.NewPrometheus("gin")
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("/register", h.RegisterWebAPI)
r.POST("/machine/:id/map", h.PollNetMapHandler)
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/:platform", h.ApplePlatformConfig)
- var err error
+ r.GET("/swagger", SwaggerUI)
+ r.GET("/swagger/v1/openapiv2.json", SwaggerAPIv1)
- go h.watchForKVUpdates(5000)
- go h.expireEphemeralNodes(5000)
+ api := r.Group("/api")
+ api.Use(h.httpAuthenticationMiddleware)
+ {
+ api.Any("/v1/*any", gin.WrapF(grpcGatewayMux.ServeHTTP))
+ }
+
+ r.NoRoute(stdoutHandler)
// Fetch an initial DERP Map before we start serving
h.DERPMap = GetDERPMap(h.cfg.DERP)
@@ -222,7 +438,12 @@ func (h *Headscale) Serve() error {
go h.scheduledDERPMapUpdateWorker(derpMapCancelChannel)
}
- s := &http.Server{
+ // I HATE THIS
+ updateMillisecondsWait := int64(5000)
+ go h.watchForKVUpdates(updateMillisecondsWait)
+ go h.expireEphemeralNodes(updateMillisecondsWait)
+
+ httpServer := &http.Server{
Addr: h.cfg.Addr,
Handler: r,
ReadTimeout: 30 * time.Second,
@@ -233,6 +454,59 @@ func (h *Headscale) Serve() error {
WriteTimeout: 0,
}
+ grpcOptions := []grpc.ServerOption{
+ grpc.UnaryInterceptor(
+ h.grpcAuthenticationInterceptor,
+ ),
+ }
+
+ 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
+
+ grpcOptions = append(grpcOptions, grpc.Creds(credentials.NewTLS(tlsConfig)))
+ }
+
+ grpcServer := grpc.NewServer(grpcOptions...)
+
+ // Start the local gRPC server without TLS and without authentication
+ grpcSocket := grpc.NewServer()
+
+ apiV1.RegisterHeadscaleServiceServer(grpcServer, newHeadscaleV1APIServer(h))
+ apiV1.RegisterHeadscaleServiceServer(grpcSocket, newHeadscaleV1APIServer(h))
+ reflection.Register(grpcServer)
+ reflection.Register(grpcSocket)
+
+ g := new(errgroup.Group)
+
+ g.Go(func() error { return grpcSocket.Serve(socketListener) })
+
+ // TODO(kradalby): Verify if we need the same TLS setup for gRPC as HTTP
+ g.Go(func() error { return grpcServer.Serve(grpcListener) })
+
+ if tlsConfig != nil {
+ g.Go(func() error {
+ tlsl := tls.NewListener(httpListener, tlsConfig)
+ return httpServer.Serve(tlsl)
+ })
+ } else {
+ 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 !strings.HasPrefix(h.cfg.ServerURL, "https://") {
log.Warn().Msg("Listening with TLS but ServerURL does not start with https://")
@@ -248,13 +522,11 @@ func (h *Headscale) Serve() error {
Email: h.cfg.ACMEEmail,
}
- s.TLSConfig = m.TLSConfig()
-
if h.cfg.TLSLetsEncryptChallengeType == "TLS-ALPN-01" {
// Configuration via autocert with TLS-ALPN-01 (https://tools.ietf.org/html/rfc8737)
// The RFC requires that the validation is done on port 443; in other words, headscale
// must be reachable on port 443.
- err = s.ListenAndServeTLS("", "")
+ return m.TLSConfig(), nil
} else if h.cfg.TLSLetsEncryptChallengeType == "HTTP-01" {
// Configuration via autocert with HTTP-01. This requires listening on
// port 80 for the certificate validation in addition to the headscale
@@ -264,22 +536,30 @@ func (h *Headscale) Serve() error {
Err(http.ListenAndServe(h.cfg.TLSLetsEncryptListen, m.HTTPHandler(http.HandlerFunc(h.redirect)))).
Msg("failed to set up a HTTP server")
}()
- err = s.ListenAndServeTLS("", "")
+
+ return m.TLSConfig(), nil
} else {
- return errors.New("unknown value for TLSLetsEncryptChallengeType")
+ return nil, errors.New("unknown value for TLSLetsEncryptChallengeType")
}
} else if h.cfg.TLSCertPath == "" {
if !strings.HasPrefix(h.cfg.ServerURL, "http://") {
log.Warn().Msg("Listening without TLS but ServerURL does not start with http://")
}
- err = s.ListenAndServe()
+
+ return nil, nil
} else {
if !strings.HasPrefix(h.cfg.ServerURL, "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) {
@@ -311,3 +591,14 @@ func (h *Headscale) getLastStateChange(namespaces ...string) time.Time {
return times[0]
}
}
+
+func stdoutHandler(c *gin.Context) {
+ b, _ := io.ReadAll(c.Request.Body)
+
+ log.Trace().
+ Interface("header", c.Request.Header).
+ Interface("proto", c.Request.Proto).
+ Interface("url", c.Request.URL).
+ Bytes("body", b).
+ Msg("Request did not match")
+}
diff --git a/buf.gen.yaml b/buf.gen.yaml
new file mode 100644
index 00000000..d7b832ab
--- /dev/null
+++ b/buf.gen.yaml
@@ -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
diff --git a/cli.go b/cli.go
index 9c5b66e5..8610b334 100644
--- a/cli.go
+++ b/cli.go
@@ -23,6 +23,8 @@ func (h *Headscale) RegisterMachine(key string, namespace string) (*Machine, err
return nil, errors.New("Machine not found")
}
+ h.updateMachineExpiry(&m) // update the machine's expiry before bailing if its already registered
+
if m.isAlreadyRegistered() {
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.RegisterMethod = "cli"
h.db.Save(&m)
+
return &m, nil
}
diff --git a/cli_test.go b/cli_test.go
index 528a115e..291b5df1 100644
--- a/cli_test.go
+++ b/cli_test.go
@@ -1,6 +1,8 @@
package headscale
import (
+ "time"
+
"gopkg.in/check.v1"
)
@@ -8,14 +10,18 @@ func (s *Suite) TestRegisterMachine(c *check.C) {
n, err := h.CreateNamespace("test")
c.Assert(err, check.IsNil)
+ now := time.Now().UTC()
+
m := Machine{
- ID: 0,
- MachineKey: "8ce002a935f8c394e55e78fbbb410576575ff8ec5cfa2e627e4b807f1be15b0e",
- NodeKey: "bar",
- DiscoKey: "faa",
- Name: "testmachine",
- NamespaceID: n.ID,
- IPAddress: "10.0.0.1",
+ ID: 0,
+ MachineKey: "8ce002a935f8c394e55e78fbbb410576575ff8ec5cfa2e627e4b807f1be15b0e",
+ NodeKey: "bar",
+ DiscoKey: "faa",
+ Name: "testmachine",
+ NamespaceID: n.ID,
+ IPAddress: "10.0.0.1",
+ Expiry: &now,
+ RequestedExpiry: &now,
}
h.db.Save(&m)
diff --git a/cmd/headscale/cli/namespaces.go b/cmd/headscale/cli/namespaces.go
index 42870370..1c4e3762 100644
--- a/cmd/headscale/cli/namespaces.go
+++ b/cmd/headscale/cli/namespaces.go
@@ -1,12 +1,15 @@
package cli
import (
+ "context"
"fmt"
- "log"
"strconv"
"strings"
+ "time"
+ apiV1 "github.com/juanfont/headscale/gen/go/headscale/v1"
"github.com/pterm/pterm"
+ "github.com/rs/zerolog/log"
"github.com/spf13/cobra"
)
@@ -34,13 +37,21 @@ var createNamespaceCmd = &cobra.Command{
},
Run: func(cmd *cobra.Command, args []string) {
o, _ := cmd.Flags().GetString("output")
- h, err := getHeadscaleApp()
- if err != nil {
- log.Fatalf("Error initializing: %s", err)
- }
- namespace, err := h.CreateNamespace(args[0])
+
+ ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
+ defer cancel()
+
+ client, conn := getHeadscaleGRPCClient(ctx)
+ defer conn.Close()
+
+ log.Trace().Interface("client", client).Msg("Obtained gRPC client")
+
+ request := &apiV1.CreateNamespaceRequest{Name: args[0]}
+
+ log.Trace().Interface("request", request).Msg("Sending CreateNamespace request")
+ response, err := client.CreateNamespace(ctx, request)
if strings.HasPrefix(o, "json") {
- JsonOutput(namespace, err, o)
+ JsonOutput(response.Name, err, o)
return
}
if err != nil {
@@ -64,7 +75,7 @@ var destroyNamespaceCmd = &cobra.Command{
o, _ := cmd.Flags().GetString("output")
h, err := getHeadscaleApp()
if err != nil {
- log.Fatalf("Error initializing: %s", err)
+ log.Fatal().Err(err).Msgf("Error initializing: %s", err)
}
err = h.DestroyNamespace(args[0])
if strings.HasPrefix(o, "json") {
@@ -86,7 +97,7 @@ var listNamespacesCmd = &cobra.Command{
o, _ := cmd.Flags().GetString("output")
h, err := getHeadscaleApp()
if err != nil {
- log.Fatalf("Error initializing: %s", err)
+ log.Fatal().Err(err).Msgf("Error initializing: %s", err)
}
namespaces, err := h.ListNamespaces()
if strings.HasPrefix(o, "json") {
@@ -100,11 +111,14 @@ var listNamespacesCmd = &cobra.Command{
d := pterm.TableData{{"ID", "Name", "Created"}}
for _, n := range *namespaces {
- d = append(d, []string{strconv.FormatUint(uint64(n.ID), 10), n.Name, n.CreatedAt.Format("2006-01-02 15:04:05")})
+ d = append(
+ d,
+ []string{strconv.FormatUint(uint64(n.ID), 10), n.Name, n.CreatedAt.Format("2006-01-02 15:04:05")},
+ )
}
err = pterm.DefaultTable.WithHasHeader().WithData(d).Render()
if err != nil {
- log.Fatal(err)
+ log.Fatal().Err(err).Msg("")
}
},
}
@@ -122,7 +136,7 @@ var renameNamespaceCmd = &cobra.Command{
o, _ := cmd.Flags().GetString("output")
h, err := getHeadscaleApp()
if err != nil {
- log.Fatalf("Error initializing: %s", err)
+ log.Fatal().Err(err).Msgf("Error initializing: %s", err)
}
err = h.RenameNamespace(args[0], args[1])
if strings.HasPrefix(o, "json") {
diff --git a/cmd/headscale/cli/nodes.go b/cmd/headscale/cli/nodes.go
index c44aa5ed..cdf37efb 100644
--- a/cmd/headscale/cli/nodes.go
+++ b/cmd/headscale/cli/nodes.go
@@ -17,15 +17,50 @@ import (
func init() {
rootCmd.AddCommand(nodeCmd)
- nodeCmd.PersistentFlags().StringP("namespace", "n", "", "Namespace")
- err := nodeCmd.MarkPersistentFlagRequired("namespace")
+ listNodesCmd.Flags().StringP("namespace", "n", "", "Filter by 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 {
log.Fatalf(err.Error())
}
- nodeCmd.AddCommand(listNodesCmd)
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)
+
+ 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)
+
+ 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)
}
@@ -35,14 +70,8 @@ var nodeCmd = &cobra.Command{
}
var registerNodeCmd = &cobra.Command{
- Use: "register machineID",
+ Use: "register",
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) {
n, err := cmd.Flags().GetString("namespace")
if err != nil {
@@ -54,7 +83,11 @@ var registerNodeCmd = &cobra.Command{
if err != nil {
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") {
JsonOutput(m, err, o)
return
@@ -69,7 +102,7 @@ var registerNodeCmd = &cobra.Command{
var listNodesCmd = &cobra.Command{
Use: "list",
- Short: "List the nodes in a given namespace",
+ Short: "List nodes",
Run: func(cmd *cobra.Command, args []string) {
n, err := cmd.Flags().GetString("namespace")
if err != nil {
@@ -82,23 +115,44 @@ var listNodesCmd = &cobra.Command{
log.Fatalf("Error initializing: %s", err)
}
- namespace, err := h.GetNamespace(n)
- if err != nil {
- log.Fatalf("Error fetching namespace: %s", err)
+ var namespaces []headscale.Namespace
+ var namespace *headscale.Namespace
+ 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)
- if err != nil {
- log.Fatalf("Error fetching machines: %s", err)
+ var allMachines []headscale.Machine
+ for _, namespace := range namespaces {
+ 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)
- if err != nil {
- log.Fatalf("Error fetching shared machines: %s", err)
+ // listing sharedMachines is only relevant when a particular namespace is
+ // requested
+ if sharedMachines != nil {
+ allMachines = append(allMachines, *sharedMachines...)
}
- allMachines := append(*machines, *sharedMachines...)
-
if strings.HasPrefix(o, "json") {
JsonOutput(allMachines, err, o)
return
@@ -108,7 +162,7 @@ var listNodesCmd = &cobra.Command{
log.Fatalf("Error getting nodes: %s", err)
}
- d, err := nodesToPtables(*namespace, allMachines)
+ d, err := nodesToPtables(namespace, allMachines)
if err != nil {
log.Fatalf("Error converting to table: %s", err)
}
@@ -121,21 +175,15 @@ var listNodesCmd = &cobra.Command{
}
var deleteNodeCmd = &cobra.Command{
- Use: "delete ID",
+ Use: "delete",
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) {
output, _ := cmd.Flags().GetString("output")
h, err := getHeadscaleApp()
if err != nil {
log.Fatalf("Error initializing: %s", err)
}
- id, err := strconv.Atoi(args[0])
+ id, err := cmd.Flags().GetInt("identifier")
if err != nil {
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{
- Use: "share ID namespace",
+ Use: "share",
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) {
- namespace, 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)
- }
-
- _, 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)
+ h, output, machine, namespace := sharingWorker(cmd, args)
+ err := h.AddSharedMachineToNamespace(machine, namespace)
if strings.HasPrefix(output, "json") {
JsonOutput(map[string]string{"Result": "Node shared"}, err, output)
return
@@ -231,41 +274,11 @@ var shareMachineCmd = &cobra.Command{
}
var unshareMachineCmd = &cobra.Command{
- Use: "unshare ID",
+ Use: "unshare",
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) {
- namespace, 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)
- }
-
- 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)
+ h, output, machine, namespace := sharingWorker(cmd, args)
+ err := h.RemoveSharedMachineFromNamespace(machine, namespace)
if strings.HasPrefix(output, "json") {
JsonOutput(map[string]string{"Result": "Node unshared"}, err, output)
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"}}
for _, machine := range machines {
@@ -307,9 +320,10 @@ func nodesToPtables(currentNamespace headscale.Namespace, machines []headscale.M
}
var namespace string
- if currentNamespace.ID == machine.NamespaceID {
+ if (currentNamespace == nil) || (currentNamespace.ID == machine.NamespaceID) {
namespace = pterm.LightMagenta(machine.Namespace.Name)
} else {
+ // Shared into this namespace
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})
diff --git a/cmd/headscale/cli/utils.go b/cmd/headscale/cli/utils.go
index 0768e1eb..cd467697 100644
--- a/cmd/headscale/cli/utils.go
+++ b/cmd/headscale/cli/utils.go
@@ -1,18 +1,23 @@
package cli
import (
+ "context"
"encoding/json"
"errors"
"fmt"
"net/url"
"os"
"path/filepath"
+ "regexp"
+ "strconv"
"strings"
"time"
"github.com/juanfont/headscale"
+ apiV1 "github.com/juanfont/headscale/gen/go/headscale/v1"
"github.com/rs/zerolog/log"
"github.com/spf13/viper"
+ "google.golang.org/grpc"
"inet.af/netaddr"
"tailscale.com/tailcfg"
"tailscale.com/types/dnstype"
@@ -43,6 +48,8 @@ func LoadConfig(path string) error {
viper.SetDefault("dns_config", nil)
+ viper.SetDefault("unix_socket", "/var/run/headscale.sock")
+
err := viper.ReadInConfig()
if err != nil {
return fmt.Errorf("Fatal error reading config file: %s \n", err)
@@ -202,23 +209,31 @@ func absPath(path string) string {
return path
}
-func getHeadscaleApp() (*headscale.Headscale, error) {
- // Minimum inactivity time out is keepalive timeout (60s) plus a few seconds
- // to avoid races
- minInactivityTimeout, _ := time.ParseDuration("65s")
- 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,
- )
- return nil, err
+func getHeadscaleConfig() headscale.Config {
+ // 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()
derpConfig := GetDERPConfig()
- cfg := headscale.Config{
+ return headscale.Config{
ServerURL: viper.GetString("server_url"),
Addr: viper.GetString("listen_addr"),
PrivateKeyPath: absPath(viper.GetString("private_key_path")),
@@ -249,7 +264,36 @@ func getHeadscaleApp() (*headscale.Headscale, error) {
ACMEEmail: viper.GetString("acme_email"),
ACMEURL: viper.GetString("acme_url"),
+
+ UnixSocket: viper.GetString("unix_socket"),
+
+ OIDC: headscale.OIDCConfig{
+ Issuer: viper.GetString("oidc.issuer"),
+ ClientID: viper.GetString("oidc.client_id"),
+ ClientSecret: viper.GetString("oidc.client_secret"),
+ },
+
+ MaxMachineRegistrationDuration: maxMachineRegistrationDuration,
+ DefaultMachineRegistrationDuration: defaultMachineRegistrationDuration,
}
+}
+
+func getHeadscaleApp() (*headscale.Headscale, error) {
+ // Minimum inactivity time out is keepalive timeout (60s) plus a few seconds
+ // to avoid races
+ minInactivityTimeout, _ := time.ParseDuration("65s")
+ 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,
+ )
+ return nil, err
+ }
+
+ cfg := getHeadscaleConfig()
+
+ cfg.OIDC.MatchMap = loadOIDCMatchMap()
h, err := headscale.NewHeadscale(cfg)
if err != nil {
@@ -272,6 +316,65 @@ func getHeadscaleApp() (*headscale.Headscale, error) {
return h, nil
}
+func getHeadscaleGRPCClient(ctx context.Context) (apiV1.HeadscaleServiceClient, *grpc.ClientConn) {
+ grpcOptions := []grpc.DialOption{
+ grpc.WithBlock(),
+ }
+
+ address := os.Getenv("HEADSCALE_ADDRESS")
+
+ // If the address is not set, we assume that we are on the server hosting headscale.
+ if address == "" {
+
+ cfg := getHeadscaleConfig()
+
+ log.Debug().
+ Str("socket", cfg.UnixSocket).
+ Msgf("HEADSCALE_ADDRESS environment is not set, connecting to unix socket.")
+
+ address = cfg.UnixSocket
+
+ grpcOptions = append(
+ grpcOptions,
+ grpc.WithInsecure(),
+ grpc.WithContextDialer(headscale.GrpcSocketDialer),
+ )
+ } else {
+ // If we are not connecting to a local server, require an API key for authentication
+ apiKey := os.Getenv("HEADSCALE_API_KEY")
+ if apiKey == "" {
+ log.Fatal().Msgf("HEADSCALE_API_KEY environment variable needs to be set.")
+ }
+ grpcOptions = append(grpcOptions,
+ grpc.WithPerRPCCredentials(tokenAuth{
+ token: apiKey,
+ }),
+ )
+
+ insecureStr := os.Getenv("HEADSCALE_INSECURE")
+ if insecureStr != "" {
+ insecure, err := strconv.ParseBool(insecureStr)
+ if err != nil {
+ log.Fatal().Err(err).Msgf("Failed to parse HEADSCALE_INSECURE: %v", err)
+ }
+
+ if insecure {
+ grpcOptions = append(grpcOptions, grpc.WithInsecure())
+ }
+ }
+ }
+
+ log.Trace().Caller().Str("address", address).Msg("Connecting via gRPC")
+ conn, err := grpc.DialContext(ctx, address, grpcOptions...)
+ if err != nil {
+ log.Fatal().Err(err).Msgf("Could not connect: %v", err)
+ }
+
+ client := apiV1.NewHeadscaleServiceClient(conn)
+
+ return client, conn
+}
+
func JsonOutput(result interface{}, errResult error, outputFormat string) {
var j []byte
var err error
@@ -312,3 +415,30 @@ func HasJsonOutputFlag() bool {
}
return false
}
+
+type tokenAuth struct {
+ token string
+}
+
+// Return value is mapped to request headers.
+func (t tokenAuth) GetRequestMetadata(ctx context.Context, in ...string) (map[string]string, error) {
+ return map[string]string{
+ "authorization": "Bearer " + t.token,
+ }, nil
+}
+
+func (tokenAuth) RequireTransportSecurity() bool {
+ return true
+}
+
+// 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
+}
diff --git a/cmd/headscale/headscale_test.go b/cmd/headscale/headscale_test.go
index eff78496..e3a5713f 100644
--- a/cmd/headscale/headscale_test.go
+++ b/cmd/headscale/headscale_test.go
@@ -27,7 +27,7 @@ func (s *Suite) SetUpSuite(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")
if err != nil {
c.Fatal(err)
@@ -40,39 +40,7 @@ func (*Suite) TestPostgresConfigLoading(c *check.C) {
}
// Symlink the example config file
- err = os.Symlink(filepath.Clean(path+"/../../config.yaml.postgres.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("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"))
+ err = os.Symlink(filepath.Clean(path+"/../../config-example.yaml"), filepath.Join(tmpDir, "config.yaml"))
if err != nil {
c.Fatal(err)
}
@@ -106,7 +74,7 @@ func (*Suite) TestDNSConfigLoading(c *check.C) {
}
// 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 {
c.Fatal(err)
}
diff --git a/config-example.yaml b/config-example.yaml
new file mode 100644
index 00000000..60369306
--- /dev/null
+++ b/config-example.yaml
@@ -0,0 +1,84 @@
+---
+# 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
+
+# Unix socket used for the CLI to connect without authentication
+# Note: for local development, you probably want to change this to:
+# unix_socket: ./headscale.sock
+unix_socket: /var/run/headscale.sock
+# 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
diff --git a/config.yaml.postgres.example b/config.yaml.postgres.example
deleted file mode 100644
index 920bdaaa..00000000
--- a/config.yaml.postgres.example
+++ /dev/null
@@ -1,29 +0,0 @@
----
-server_url: http://127.0.0.1:8080
-listen_addr: 0.0.0.0:8080
-private_key_path: private.key
-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
diff --git a/config.yaml.sqlite.example b/config.yaml.sqlite.example
deleted file mode 100644
index 411a2a77..00000000
--- a/config.yaml.sqlite.example
+++ /dev/null
@@ -1,43 +0,0 @@
----
-log_level: info
-server_url: http://127.0.0.1:8080
-listen_addr: 0.0.0.0:8080
-private_key_path: private.key
-ephemeral_node_inactivity_timeout: 30m
-
-# SQLite config (uncomment it if you want to use SQLite)
-db_type: sqlite3
-db_path: db.sqlite
-
-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
-
-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
diff --git a/docs/Running.md b/docs/Running.md
index 2193c107..b784aba9 100644
--- a/docs/Running.md
+++ b/docs/Running.md
@@ -101,18 +101,18 @@ If you used tailscale.com before in your nodes, make sure you clear the tailscal
3. In the server, register your machine to a namespace with the CLI
```shell
- headscale -n myfirstnamespace nodes register YOURMACHINEKEY
+ headscale -n myfirstnamespace nodes register -k YOURMACHINEKEY
```
or docker:
```shell
docker run \
-v $(pwd)/config:/etc/headscale/ \
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:
```shell
- docker exec headscale -n myfirstnamespace nodes register YOURMACHINEKEY
+ docker exec headscale -n myfirstnamespace nodes register -k YOURMACHINEKEY
```
### Alternative: adding node with AUTHKEY
diff --git a/gen/go/headscale/v1/headscale.pb.go b/gen/go/headscale/v1/headscale.pb.go
new file mode 100644
index 00000000..e050e931
--- /dev/null
+++ b/gen/go/headscale/v1/headscale.pb.go
@@ -0,0 +1,807 @@
+// Code generated by protoc-gen-go. DO NOT EDIT.
+// versions:
+// protoc-gen-go v1.27.1
+// protoc v3.18.1
+// source: headscale/v1/headscale.proto
+
+package v1
+
+import (
+ _ "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_REGISTER_METHOD_UNSPECIFIED RegisterMethod = 0
+ RegisterMethod_REGISTER_METHOD_AUTH_KEY RegisterMethod = 1
+ RegisterMethod_REGISTER_METHOD_CLI RegisterMethod = 2
+ RegisterMethod_REGISTER_METHOD_OIDC RegisterMethod = 3
+)
+
+// Enum value maps for RegisterMethod.
+var (
+ RegisterMethod_name = map[int32]string{
+ 0: "REGISTER_METHOD_UNSPECIFIED",
+ 1: "REGISTER_METHOD_AUTH_KEY",
+ 2: "REGISTER_METHOD_CLI",
+ 3: "REGISTER_METHOD_OIDC",
+ }
+ RegisterMethod_value = map[string]int32{
+ "REGISTER_METHOD_UNSPECIFIED": 0,
+ "REGISTER_METHOD_AUTH_KEY": 1,
+ "REGISTER_METHOD_CLI": 2,
+ "REGISTER_METHOD_OIDC": 3,
+ }
+)
+
+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_headscale_v1_headscale_proto_enumTypes[0].Descriptor()
+}
+
+func (RegisterMethod) Type() protoreflect.EnumType {
+ return &file_headscale_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_headscale_v1_headscale_proto_rawDescGZIP(), []int{0}
+}
+
+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_headscale_v1_headscale_proto_msgTypes[0]
+ 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_headscale_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 GetMachineRequest.ProtoReflect.Descriptor instead.
+func (*GetMachineRequest) Descriptor() ([]byte, []int) {
+ return file_headscale_v1_headscale_proto_rawDescGZIP(), []int{0}
+}
+
+func (x *GetMachineRequest) GetMachineId() uint64 {
+ if x != nil {
+ return x.MachineId
+ }
+ return 0
+}
+
+type GetMachineResponse 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=machine_key,json=machineKey,proto3" json:"machine_key,omitempty"`
+ NodeKey string `protobuf:"bytes,3,opt,name=node_key,json=nodeKey,proto3" json:"node_key,omitempty"`
+ DiscoKey string `protobuf:"bytes,4,opt,name=disco_key,json=discoKey,proto3" json:"disco_key,omitempty"`
+ IpAddress string `protobuf:"bytes,5,opt,name=ip_address,json=ipAddress,proto3" json:"ip_address,omitempty"`
+ Name string `protobuf:"bytes,6,opt,name=name,proto3" json:"name,omitempty"`
+ NamespaceId uint32 `protobuf:"varint,7,opt,name=namespace_id,json=namespaceId,proto3" json:"namespace_id,omitempty"`
+ Registered bool `protobuf:"varint,8,opt,name=registered,proto3" json:"registered,omitempty"`
+ RegisterMethod RegisterMethod `protobuf:"varint,9,opt,name=register_method,json=registerMethod,proto3,enum=headscale.v1.RegisterMethod" json:"register_method,omitempty"`
+ AuthKeyId uint32 `protobuf:"varint,10,opt,name=auth_key_id,json=authKeyId,proto3" json:"auth_key_id,omitempty"` // PreAuthKey auth_key = 11;
+ LastSeen *timestamppb.Timestamp `protobuf:"bytes,12,opt,name=last_seen,json=lastSeen,proto3" json:"last_seen,omitempty"`
+ LastSuccessfulUpdate *timestamppb.Timestamp `protobuf:"bytes,13,opt,name=last_successful_update,json=lastSuccessfulUpdate,proto3" json:"last_successful_update,omitempty"`
+ Expiry *timestamppb.Timestamp `protobuf:"bytes,14,opt,name=expiry,proto3" json:"expiry,omitempty"`
+}
+
+func (x *GetMachineResponse) Reset() {
+ *x = GetMachineResponse{}
+ if protoimpl.UnsafeEnabled {
+ mi := &file_headscale_v1_headscale_proto_msgTypes[1]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+ }
+}
+
+func (x *GetMachineResponse) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*GetMachineResponse) ProtoMessage() {}
+
+func (x *GetMachineResponse) ProtoReflect() protoreflect.Message {
+ mi := &file_headscale_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 GetMachineResponse.ProtoReflect.Descriptor instead.
+func (*GetMachineResponse) Descriptor() ([]byte, []int) {
+ return file_headscale_v1_headscale_proto_rawDescGZIP(), []int{1}
+}
+
+func (x *GetMachineResponse) GetId() uint64 {
+ if x != nil {
+ return x.Id
+ }
+ return 0
+}
+
+func (x *GetMachineResponse) GetMachineKey() string {
+ if x != nil {
+ return x.MachineKey
+ }
+ return ""
+}
+
+func (x *GetMachineResponse) GetNodeKey() string {
+ if x != nil {
+ return x.NodeKey
+ }
+ return ""
+}
+
+func (x *GetMachineResponse) GetDiscoKey() string {
+ if x != nil {
+ return x.DiscoKey
+ }
+ return ""
+}
+
+func (x *GetMachineResponse) GetIpAddress() string {
+ if x != nil {
+ return x.IpAddress
+ }
+ return ""
+}
+
+func (x *GetMachineResponse) GetName() string {
+ if x != nil {
+ return x.Name
+ }
+ return ""
+}
+
+func (x *GetMachineResponse) GetNamespaceId() uint32 {
+ if x != nil {
+ return x.NamespaceId
+ }
+ return 0
+}
+
+func (x *GetMachineResponse) GetRegistered() bool {
+ if x != nil {
+ return x.Registered
+ }
+ return false
+}
+
+func (x *GetMachineResponse) GetRegisterMethod() RegisterMethod {
+ if x != nil {
+ return x.RegisterMethod
+ }
+ return RegisterMethod_REGISTER_METHOD_UNSPECIFIED
+}
+
+func (x *GetMachineResponse) GetAuthKeyId() uint32 {
+ if x != nil {
+ return x.AuthKeyId
+ }
+ return 0
+}
+
+func (x *GetMachineResponse) GetLastSeen() *timestamppb.Timestamp {
+ if x != nil {
+ return x.LastSeen
+ }
+ return nil
+}
+
+func (x *GetMachineResponse) GetLastSuccessfulUpdate() *timestamppb.Timestamp {
+ if x != nil {
+ return x.LastSuccessfulUpdate
+ }
+ return nil
+}
+
+func (x *GetMachineResponse) GetExpiry() *timestamppb.Timestamp {
+ if x != nil {
+ return x.Expiry
+ }
+ return nil
+}
+
+type CreateNamespaceRequest struct {
+ state protoimpl.MessageState
+ sizeCache protoimpl.SizeCache
+ unknownFields protoimpl.UnknownFields
+
+ Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
+}
+
+func (x *CreateNamespaceRequest) Reset() {
+ *x = CreateNamespaceRequest{}
+ if protoimpl.UnsafeEnabled {
+ mi := &file_headscale_v1_headscale_proto_msgTypes[2]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+ }
+}
+
+func (x *CreateNamespaceRequest) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*CreateNamespaceRequest) ProtoMessage() {}
+
+func (x *CreateNamespaceRequest) ProtoReflect() protoreflect.Message {
+ mi := &file_headscale_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 CreateNamespaceRequest.ProtoReflect.Descriptor instead.
+func (*CreateNamespaceRequest) Descriptor() ([]byte, []int) {
+ return file_headscale_v1_headscale_proto_rawDescGZIP(), []int{2}
+}
+
+func (x *CreateNamespaceRequest) GetName() string {
+ if x != nil {
+ return x.Name
+ }
+ return ""
+}
+
+type CreateNamespaceResponse struct {
+ state protoimpl.MessageState
+ sizeCache protoimpl.SizeCache
+ unknownFields protoimpl.UnknownFields
+
+ Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
+}
+
+func (x *CreateNamespaceResponse) Reset() {
+ *x = CreateNamespaceResponse{}
+ if protoimpl.UnsafeEnabled {
+ mi := &file_headscale_v1_headscale_proto_msgTypes[3]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+ }
+}
+
+func (x *CreateNamespaceResponse) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*CreateNamespaceResponse) ProtoMessage() {}
+
+func (x *CreateNamespaceResponse) ProtoReflect() protoreflect.Message {
+ mi := &file_headscale_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 CreateNamespaceResponse.ProtoReflect.Descriptor instead.
+func (*CreateNamespaceResponse) Descriptor() ([]byte, []int) {
+ return file_headscale_v1_headscale_proto_rawDescGZIP(), []int{3}
+}
+
+func (x *CreateNamespaceResponse) GetName() string {
+ if x != nil {
+ return x.Name
+ }
+ return ""
+}
+
+type DeleteNamespaceRequest struct {
+ state protoimpl.MessageState
+ sizeCache protoimpl.SizeCache
+ unknownFields protoimpl.UnknownFields
+
+ Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
+}
+
+func (x *DeleteNamespaceRequest) Reset() {
+ *x = DeleteNamespaceRequest{}
+ if protoimpl.UnsafeEnabled {
+ mi := &file_headscale_v1_headscale_proto_msgTypes[4]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+ }
+}
+
+func (x *DeleteNamespaceRequest) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*DeleteNamespaceRequest) ProtoMessage() {}
+
+func (x *DeleteNamespaceRequest) ProtoReflect() protoreflect.Message {
+ mi := &file_headscale_v1_headscale_proto_msgTypes[4]
+ 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 DeleteNamespaceRequest.ProtoReflect.Descriptor instead.
+func (*DeleteNamespaceRequest) Descriptor() ([]byte, []int) {
+ return file_headscale_v1_headscale_proto_rawDescGZIP(), []int{4}
+}
+
+func (x *DeleteNamespaceRequest) GetName() string {
+ if x != nil {
+ return x.Name
+ }
+ return ""
+}
+
+type DeleteNamespaceResponse struct {
+ state protoimpl.MessageState
+ sizeCache protoimpl.SizeCache
+ unknownFields protoimpl.UnknownFields
+}
+
+func (x *DeleteNamespaceResponse) Reset() {
+ *x = DeleteNamespaceResponse{}
+ if protoimpl.UnsafeEnabled {
+ mi := &file_headscale_v1_headscale_proto_msgTypes[5]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+ }
+}
+
+func (x *DeleteNamespaceResponse) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*DeleteNamespaceResponse) ProtoMessage() {}
+
+func (x *DeleteNamespaceResponse) ProtoReflect() protoreflect.Message {
+ mi := &file_headscale_v1_headscale_proto_msgTypes[5]
+ 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 DeleteNamespaceResponse.ProtoReflect.Descriptor instead.
+func (*DeleteNamespaceResponse) Descriptor() ([]byte, []int) {
+ return file_headscale_v1_headscale_proto_rawDescGZIP(), []int{5}
+}
+
+type ListNamespacesRequest struct {
+ state protoimpl.MessageState
+ sizeCache protoimpl.SizeCache
+ unknownFields protoimpl.UnknownFields
+}
+
+func (x *ListNamespacesRequest) Reset() {
+ *x = ListNamespacesRequest{}
+ if protoimpl.UnsafeEnabled {
+ mi := &file_headscale_v1_headscale_proto_msgTypes[6]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+ }
+}
+
+func (x *ListNamespacesRequest) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*ListNamespacesRequest) ProtoMessage() {}
+
+func (x *ListNamespacesRequest) ProtoReflect() protoreflect.Message {
+ mi := &file_headscale_v1_headscale_proto_msgTypes[6]
+ 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 ListNamespacesRequest.ProtoReflect.Descriptor instead.
+func (*ListNamespacesRequest) Descriptor() ([]byte, []int) {
+ return file_headscale_v1_headscale_proto_rawDescGZIP(), []int{6}
+}
+
+type ListNamespacesResponse struct {
+ state protoimpl.MessageState
+ sizeCache protoimpl.SizeCache
+ unknownFields protoimpl.UnknownFields
+
+ Namespaces []string `protobuf:"bytes,1,rep,name=namespaces,proto3" json:"namespaces,omitempty"`
+}
+
+func (x *ListNamespacesResponse) Reset() {
+ *x = ListNamespacesResponse{}
+ if protoimpl.UnsafeEnabled {
+ mi := &file_headscale_v1_headscale_proto_msgTypes[7]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+ }
+}
+
+func (x *ListNamespacesResponse) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*ListNamespacesResponse) ProtoMessage() {}
+
+func (x *ListNamespacesResponse) ProtoReflect() protoreflect.Message {
+ mi := &file_headscale_v1_headscale_proto_msgTypes[7]
+ 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 ListNamespacesResponse.ProtoReflect.Descriptor instead.
+func (*ListNamespacesResponse) Descriptor() ([]byte, []int) {
+ return file_headscale_v1_headscale_proto_rawDescGZIP(), []int{7}
+}
+
+func (x *ListNamespacesResponse) GetNamespaces() []string {
+ if x != nil {
+ return x.Namespaces
+ }
+ return nil
+}
+
+var File_headscale_v1_headscale_proto protoreflect.FileDescriptor
+
+var file_headscale_v1_headscale_proto_rawDesc = []byte{
+ 0x0a, 0x1c, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2f, 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, 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,
+ 0x99, 0x04, 0x0a, 0x12, 0x47, 0x65, 0x74, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x52, 0x65,
+ 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01,
+ 0x28, 0x04, 0x52, 0x02, 0x69, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e,
+ 0x65, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x6d, 0x61, 0x63,
+ 0x68, 0x69, 0x6e, 0x65, 0x4b, 0x65, 0x79, 0x12, 0x19, 0x0a, 0x08, 0x6e, 0x6f, 0x64, 0x65, 0x5f,
+ 0x6b, 0x65, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6e, 0x6f, 0x64, 0x65, 0x4b,
+ 0x65, 0x79, 0x12, 0x1b, 0x0a, 0x09, 0x64, 0x69, 0x73, 0x63, 0x6f, 0x5f, 0x6b, 0x65, 0x79, 0x18,
+ 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x64, 0x69, 0x73, 0x63, 0x6f, 0x4b, 0x65, 0x79, 0x12,
+ 0x1d, 0x0a, 0x0a, 0x69, 0x70, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x05, 0x20,
+ 0x01, 0x28, 0x09, 0x52, 0x09, 0x69, 0x70, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x12,
+ 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61,
+ 0x6d, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f,
+ 0x69, 0x64, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0b, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70,
+ 0x61, 0x63, 0x65, 0x49, 0x64, 0x12, 0x1e, 0x0a, 0x0a, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65,
+ 0x72, 0x65, 0x64, 0x18, 0x08, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x72, 0x65, 0x67, 0x69, 0x73,
+ 0x74, 0x65, 0x72, 0x65, 0x64, 0x12, 0x45, 0x0a, 0x0f, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65,
+ 0x72, 0x5f, 0x6d, 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, 0x72, 0x65,
+ 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x12, 0x1e, 0x0a, 0x0b,
+ 0x61, 0x75, 0x74, 0x68, 0x5f, 0x6b, 0x65, 0x79, 0x5f, 0x69, 0x64, 0x18, 0x0a, 0x20, 0x01, 0x28,
+ 0x0d, 0x52, 0x09, 0x61, 0x75, 0x74, 0x68, 0x4b, 0x65, 0x79, 0x49, 0x64, 0x12, 0x37, 0x0a, 0x09,
+ 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x73, 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, 0x6c, 0x61, 0x73,
+ 0x74, 0x53, 0x65, 0x65, 0x6e, 0x12, 0x50, 0x0a, 0x16, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x73, 0x75,
+ 0x63, 0x63, 0x65, 0x73, 0x73, 0x66, 0x75, 0x6c, 0x5f, 0x75, 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, 0x6c, 0x61, 0x73, 0x74, 0x53, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x66, 0x75,
+ 0x6c, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x12, 0x32, 0x0a, 0x06, 0x65, 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, 0x65, 0x78, 0x70, 0x69, 0x72, 0x79, 0x22, 0x2c, 0x0a, 0x16, 0x43,
+ 0x72, 0x65, 0x61, 0x74, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x52, 0x65,
+ 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20,
+ 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x22, 0x2d, 0x0a, 0x17, 0x43, 0x72, 0x65,
+ 0x61, 0x74, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70,
+ 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01,
+ 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x22, 0x2c, 0x0a, 0x16, 0x44, 0x65, 0x6c, 0x65,
+ 0x74, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65,
+ 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09,
+ 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x22, 0x19, 0x0a, 0x17, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65,
+ 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,
+ 0x65, 0x22, 0x17, 0x0a, 0x15, 0x4c, 0x69, 0x73, 0x74, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61,
+ 0x63, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x38, 0x0a, 0x16, 0x4c, 0x69,
+ 0x73, 0x74, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70,
+ 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1e, 0x0a, 0x0a, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63,
+ 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0a, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70,
+ 0x61, 0x63, 0x65, 0x73, 0x2a, 0x82, 0x01, 0x0a, 0x0e, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65,
+ 0x72, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x12, 0x1f, 0x0a, 0x1b, 0x52, 0x45, 0x47, 0x49, 0x53,
+ 0x54, 0x45, 0x52, 0x5f, 0x4d, 0x45, 0x54, 0x48, 0x4f, 0x44, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45,
+ 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x1c, 0x0a, 0x18, 0x52, 0x45, 0x47, 0x49,
+ 0x53, 0x54, 0x45, 0x52, 0x5f, 0x4d, 0x45, 0x54, 0x48, 0x4f, 0x44, 0x5f, 0x41, 0x55, 0x54, 0x48,
+ 0x5f, 0x4b, 0x45, 0x59, 0x10, 0x01, 0x12, 0x17, 0x0a, 0x13, 0x52, 0x45, 0x47, 0x49, 0x53, 0x54,
+ 0x45, 0x52, 0x5f, 0x4d, 0x45, 0x54, 0x48, 0x4f, 0x44, 0x5f, 0x43, 0x4c, 0x49, 0x10, 0x02, 0x12,
+ 0x18, 0x0a, 0x14, 0x52, 0x45, 0x47, 0x49, 0x53, 0x54, 0x45, 0x52, 0x5f, 0x4d, 0x45, 0x54, 0x48,
+ 0x4f, 0x44, 0x5f, 0x4f, 0x49, 0x44, 0x43, 0x10, 0x03, 0x32, 0xfa, 0x03, 0x0a, 0x10, 0x48, 0x65,
+ 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x75,
+ 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, 0x20, 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, 0x73, 0x70, 0x6f, 0x6e, 0x73, 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, 0x12, 0x7c, 0x0a, 0x0f, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x4e,
+ 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, 0x24, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73,
+ 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x4e, 0x61,
+ 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x25,
+ 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72,
+ 0x65, 0x61, 0x74, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x52, 0x65, 0x73,
+ 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1c, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x16, 0x22, 0x11, 0x2f,
+ 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65,
+ 0x3a, 0x01, 0x2a, 0x12, 0x79, 0x0a, 0x0f, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x4e, 0x61, 0x6d,
+ 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, 0x24, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61,
+ 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x4e, 0x61, 0x6d, 0x65,
+ 0x73, 0x70, 0x61, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x25, 0x2e, 0x68,
+ 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65,
+ 0x74, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f,
+ 0x6e, 0x73, 0x65, 0x22, 0x19, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x13, 0x2a, 0x11, 0x2f, 0x61, 0x70,
+ 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, 0x76,
+ 0x0a, 0x0e, 0x4c, 0x69, 0x73, 0x74, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x73,
+ 0x12, 0x23, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e,
+ 0x4c, 0x69, 0x73, 0x74, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x73, 0x52, 0x65,
+ 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x24, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c,
+ 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61,
+ 0x63, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x19, 0x82, 0xd3, 0xe4,
+ 0x93, 0x02, 0x13, 0x12, 0x11, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x6e, 0x61, 0x6d,
+ 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 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_headscale_v1_headscale_proto_rawDescOnce sync.Once
+ file_headscale_v1_headscale_proto_rawDescData = file_headscale_v1_headscale_proto_rawDesc
+)
+
+func file_headscale_v1_headscale_proto_rawDescGZIP() []byte {
+ file_headscale_v1_headscale_proto_rawDescOnce.Do(func() {
+ file_headscale_v1_headscale_proto_rawDescData = protoimpl.X.CompressGZIP(file_headscale_v1_headscale_proto_rawDescData)
+ })
+ return file_headscale_v1_headscale_proto_rawDescData
+}
+
+var file_headscale_v1_headscale_proto_enumTypes = make([]protoimpl.EnumInfo, 1)
+var file_headscale_v1_headscale_proto_msgTypes = make([]protoimpl.MessageInfo, 8)
+var file_headscale_v1_headscale_proto_goTypes = []interface{}{
+ (RegisterMethod)(0), // 0: headscale.v1.RegisterMethod
+ (*GetMachineRequest)(nil), // 1: headscale.v1.GetMachineRequest
+ (*GetMachineResponse)(nil), // 2: headscale.v1.GetMachineResponse
+ (*CreateNamespaceRequest)(nil), // 3: headscale.v1.CreateNamespaceRequest
+ (*CreateNamespaceResponse)(nil), // 4: headscale.v1.CreateNamespaceResponse
+ (*DeleteNamespaceRequest)(nil), // 5: headscale.v1.DeleteNamespaceRequest
+ (*DeleteNamespaceResponse)(nil), // 6: headscale.v1.DeleteNamespaceResponse
+ (*ListNamespacesRequest)(nil), // 7: headscale.v1.ListNamespacesRequest
+ (*ListNamespacesResponse)(nil), // 8: headscale.v1.ListNamespacesResponse
+ (*timestamppb.Timestamp)(nil), // 9: google.protobuf.Timestamp
+}
+var file_headscale_v1_headscale_proto_depIdxs = []int32{
+ 0, // 0: headscale.v1.GetMachineResponse.register_method:type_name -> headscale.v1.RegisterMethod
+ 9, // 1: headscale.v1.GetMachineResponse.last_seen:type_name -> google.protobuf.Timestamp
+ 9, // 2: headscale.v1.GetMachineResponse.last_successful_update:type_name -> google.protobuf.Timestamp
+ 9, // 3: headscale.v1.GetMachineResponse.expiry:type_name -> google.protobuf.Timestamp
+ 1, // 4: headscale.v1.HeadscaleService.GetMachine:input_type -> headscale.v1.GetMachineRequest
+ 3, // 5: headscale.v1.HeadscaleService.CreateNamespace:input_type -> headscale.v1.CreateNamespaceRequest
+ 5, // 6: headscale.v1.HeadscaleService.DeleteNamespace:input_type -> headscale.v1.DeleteNamespaceRequest
+ 7, // 7: headscale.v1.HeadscaleService.ListNamespaces:input_type -> headscale.v1.ListNamespacesRequest
+ 2, // 8: headscale.v1.HeadscaleService.GetMachine:output_type -> headscale.v1.GetMachineResponse
+ 4, // 9: headscale.v1.HeadscaleService.CreateNamespace:output_type -> headscale.v1.CreateNamespaceResponse
+ 6, // 10: headscale.v1.HeadscaleService.DeleteNamespace:output_type -> headscale.v1.DeleteNamespaceResponse
+ 8, // 11: headscale.v1.HeadscaleService.ListNamespaces:output_type -> headscale.v1.ListNamespacesResponse
+ 8, // [8:12] is the sub-list for method output_type
+ 4, // [4:8] is the sub-list for method input_type
+ 4, // [4:4] is the sub-list for extension type_name
+ 4, // [4:4] is the sub-list for extension extendee
+ 0, // [0:4] is the sub-list for field type_name
+}
+
+func init() { file_headscale_v1_headscale_proto_init() }
+func file_headscale_v1_headscale_proto_init() {
+ if File_headscale_v1_headscale_proto != nil {
+ return
+ }
+ if !protoimpl.UnsafeEnabled {
+ file_headscale_v1_headscale_proto_msgTypes[0].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_headscale_v1_headscale_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
+ switch v := v.(*GetMachineResponse); i {
+ case 0:
+ return &v.state
+ case 1:
+ return &v.sizeCache
+ case 2:
+ return &v.unknownFields
+ default:
+ return nil
+ }
+ }
+ file_headscale_v1_headscale_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
+ switch v := v.(*CreateNamespaceRequest); i {
+ case 0:
+ return &v.state
+ case 1:
+ return &v.sizeCache
+ case 2:
+ return &v.unknownFields
+ default:
+ return nil
+ }
+ }
+ file_headscale_v1_headscale_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} {
+ switch v := v.(*CreateNamespaceResponse); i {
+ case 0:
+ return &v.state
+ case 1:
+ return &v.sizeCache
+ case 2:
+ return &v.unknownFields
+ default:
+ return nil
+ }
+ }
+ file_headscale_v1_headscale_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} {
+ switch v := v.(*DeleteNamespaceRequest); i {
+ case 0:
+ return &v.state
+ case 1:
+ return &v.sizeCache
+ case 2:
+ return &v.unknownFields
+ default:
+ return nil
+ }
+ }
+ file_headscale_v1_headscale_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} {
+ switch v := v.(*DeleteNamespaceResponse); i {
+ case 0:
+ return &v.state
+ case 1:
+ return &v.sizeCache
+ case 2:
+ return &v.unknownFields
+ default:
+ return nil
+ }
+ }
+ file_headscale_v1_headscale_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} {
+ switch v := v.(*ListNamespacesRequest); i {
+ case 0:
+ return &v.state
+ case 1:
+ return &v.sizeCache
+ case 2:
+ return &v.unknownFields
+ default:
+ return nil
+ }
+ }
+ file_headscale_v1_headscale_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} {
+ switch v := v.(*ListNamespacesResponse); 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_headscale_v1_headscale_proto_rawDesc,
+ NumEnums: 1,
+ NumMessages: 8,
+ NumExtensions: 0,
+ NumServices: 1,
+ },
+ GoTypes: file_headscale_v1_headscale_proto_goTypes,
+ DependencyIndexes: file_headscale_v1_headscale_proto_depIdxs,
+ EnumInfos: file_headscale_v1_headscale_proto_enumTypes,
+ MessageInfos: file_headscale_v1_headscale_proto_msgTypes,
+ }.Build()
+ File_headscale_v1_headscale_proto = out.File
+ file_headscale_v1_headscale_proto_rawDesc = nil
+ file_headscale_v1_headscale_proto_goTypes = nil
+ file_headscale_v1_headscale_proto_depIdxs = nil
+}
diff --git a/gen/go/headscale/v1/headscale.pb.gw.go b/gen/go/headscale/v1/headscale.pb.gw.go
new file mode 100644
index 00000000..226afe50
--- /dev/null
+++ b/gen/go/headscale/v1/headscale.pb.gw.go
@@ -0,0 +1,414 @@
+// Code generated by protoc-gen-grpc-gateway. DO NOT EDIT.
+// source: headscale/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
+
+}
+
+func request_HeadscaleService_CreateNamespace_0(ctx context.Context, marshaler runtime.Marshaler, client HeadscaleServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
+ var protoReq CreateNamespaceRequest
+ var metadata runtime.ServerMetadata
+
+ newReader, berr := utilities.IOReaderFactory(req.Body)
+ if berr != nil {
+ return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
+ }
+ if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
+ return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
+ }
+
+ msg, err := client.CreateNamespace(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
+ return msg, metadata, err
+
+}
+
+func local_request_HeadscaleService_CreateNamespace_0(ctx context.Context, marshaler runtime.Marshaler, server HeadscaleServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
+ var protoReq CreateNamespaceRequest
+ var metadata runtime.ServerMetadata
+
+ newReader, berr := utilities.IOReaderFactory(req.Body)
+ if berr != nil {
+ return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
+ }
+ if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
+ return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
+ }
+
+ msg, err := server.CreateNamespace(ctx, &protoReq)
+ return msg, metadata, err
+
+}
+
+var (
+ filter_HeadscaleService_DeleteNamespace_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)}
+)
+
+func request_HeadscaleService_DeleteNamespace_0(ctx context.Context, marshaler runtime.Marshaler, client HeadscaleServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
+ var protoReq DeleteNamespaceRequest
+ var metadata runtime.ServerMetadata
+
+ if err := req.ParseForm(); err != nil {
+ return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
+ }
+ if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_HeadscaleService_DeleteNamespace_0); err != nil {
+ return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
+ }
+
+ msg, err := client.DeleteNamespace(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
+ return msg, metadata, err
+
+}
+
+func local_request_HeadscaleService_DeleteNamespace_0(ctx context.Context, marshaler runtime.Marshaler, server HeadscaleServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
+ var protoReq DeleteNamespaceRequest
+ var metadata runtime.ServerMetadata
+
+ if err := req.ParseForm(); err != nil {
+ return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
+ }
+ if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_HeadscaleService_DeleteNamespace_0); err != nil {
+ return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
+ }
+
+ msg, err := server.DeleteNamespace(ctx, &protoReq)
+ return msg, metadata, err
+
+}
+
+func request_HeadscaleService_ListNamespaces_0(ctx context.Context, marshaler runtime.Marshaler, client HeadscaleServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
+ var protoReq ListNamespacesRequest
+ var metadata runtime.ServerMetadata
+
+ msg, err := client.ListNamespaces(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
+ return msg, metadata, err
+
+}
+
+func local_request_HeadscaleService_ListNamespaces_0(ctx context.Context, marshaler runtime.Marshaler, server HeadscaleServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
+ var protoReq ListNamespacesRequest
+ var metadata runtime.ServerMetadata
+
+ msg, err := server.ListNamespaces(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()...)
+
+ })
+
+ mux.Handle("POST", pattern_HeadscaleService_CreateNamespace_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/CreateNamespace", runtime.WithHTTPPathPattern("/api/v1/namespace"))
+ if err != nil {
+ runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
+ return
+ }
+ resp, md, err := local_request_HeadscaleService_CreateNamespace_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_CreateNamespace_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
+
+ })
+
+ mux.Handle("DELETE", pattern_HeadscaleService_DeleteNamespace_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/DeleteNamespace", runtime.WithHTTPPathPattern("/api/v1/namespace"))
+ if err != nil {
+ runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
+ return
+ }
+ resp, md, err := local_request_HeadscaleService_DeleteNamespace_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_DeleteNamespace_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
+
+ })
+
+ mux.Handle("GET", pattern_HeadscaleService_ListNamespaces_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/ListNamespaces", runtime.WithHTTPPathPattern("/api/v1/namespace"))
+ if err != nil {
+ runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
+ return
+ }
+ resp, md, err := local_request_HeadscaleService_ListNamespaces_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_ListNamespaces_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()...)
+
+ })
+
+ mux.Handle("POST", pattern_HeadscaleService_CreateNamespace_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/CreateNamespace", runtime.WithHTTPPathPattern("/api/v1/namespace"))
+ if err != nil {
+ runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
+ return
+ }
+ resp, md, err := request_HeadscaleService_CreateNamespace_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_CreateNamespace_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
+
+ })
+
+ mux.Handle("DELETE", pattern_HeadscaleService_DeleteNamespace_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/DeleteNamespace", runtime.WithHTTPPathPattern("/api/v1/namespace"))
+ if err != nil {
+ runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
+ return
+ }
+ resp, md, err := request_HeadscaleService_DeleteNamespace_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_DeleteNamespace_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
+
+ })
+
+ mux.Handle("GET", pattern_HeadscaleService_ListNamespaces_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/ListNamespaces", runtime.WithHTTPPathPattern("/api/v1/namespace"))
+ if err != nil {
+ runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
+ return
+ }
+ resp, md, err := request_HeadscaleService_ListNamespaces_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_ListNamespaces_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"}, ""))
+
+ pattern_HeadscaleService_CreateNamespace_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"api", "v1", "namespace"}, ""))
+
+ pattern_HeadscaleService_DeleteNamespace_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"api", "v1", "namespace"}, ""))
+
+ pattern_HeadscaleService_ListNamespaces_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"api", "v1", "namespace"}, ""))
+)
+
+var (
+ forward_HeadscaleService_GetMachine_0 = runtime.ForwardResponseMessage
+
+ forward_HeadscaleService_CreateNamespace_0 = runtime.ForwardResponseMessage
+
+ forward_HeadscaleService_DeleteNamespace_0 = runtime.ForwardResponseMessage
+
+ forward_HeadscaleService_ListNamespaces_0 = runtime.ForwardResponseMessage
+)
diff --git a/gen/go/headscale/v1/headscale_grpc.pb.go b/gen/go/headscale/v1/headscale_grpc.pb.go
new file mode 100644
index 00000000..a613e8e9
--- /dev/null
+++ b/gen/go/headscale/v1/headscale_grpc.pb.go
@@ -0,0 +1,209 @@
+// 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) (*GetMachineResponse, error)
+ CreateNamespace(ctx context.Context, in *CreateNamespaceRequest, opts ...grpc.CallOption) (*CreateNamespaceResponse, error)
+ DeleteNamespace(ctx context.Context, in *DeleteNamespaceRequest, opts ...grpc.CallOption) (*DeleteNamespaceResponse, error)
+ ListNamespaces(ctx context.Context, in *ListNamespacesRequest, opts ...grpc.CallOption) (*ListNamespacesResponse, 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) (*GetMachineResponse, error) {
+ out := new(GetMachineResponse)
+ err := c.cc.Invoke(ctx, "/headscale.v1.HeadscaleService/GetMachine", in, out, opts...)
+ if err != nil {
+ return nil, err
+ }
+ return out, nil
+}
+
+func (c *headscaleServiceClient) CreateNamespace(ctx context.Context, in *CreateNamespaceRequest, opts ...grpc.CallOption) (*CreateNamespaceResponse, error) {
+ out := new(CreateNamespaceResponse)
+ err := c.cc.Invoke(ctx, "/headscale.v1.HeadscaleService/CreateNamespace", in, out, opts...)
+ if err != nil {
+ return nil, err
+ }
+ return out, nil
+}
+
+func (c *headscaleServiceClient) DeleteNamespace(ctx context.Context, in *DeleteNamespaceRequest, opts ...grpc.CallOption) (*DeleteNamespaceResponse, error) {
+ out := new(DeleteNamespaceResponse)
+ err := c.cc.Invoke(ctx, "/headscale.v1.HeadscaleService/DeleteNamespace", in, out, opts...)
+ if err != nil {
+ return nil, err
+ }
+ return out, nil
+}
+
+func (c *headscaleServiceClient) ListNamespaces(ctx context.Context, in *ListNamespacesRequest, opts ...grpc.CallOption) (*ListNamespacesResponse, error) {
+ out := new(ListNamespacesResponse)
+ err := c.cc.Invoke(ctx, "/headscale.v1.HeadscaleService/ListNamespaces", 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) (*GetMachineResponse, error)
+ CreateNamespace(context.Context, *CreateNamespaceRequest) (*CreateNamespaceResponse, error)
+ DeleteNamespace(context.Context, *DeleteNamespaceRequest) (*DeleteNamespaceResponse, error)
+ ListNamespaces(context.Context, *ListNamespacesRequest) (*ListNamespacesResponse, error)
+ mustEmbedUnimplementedHeadscaleServiceServer()
+}
+
+// UnimplementedHeadscaleServiceServer must be embedded to have forward compatible implementations.
+type UnimplementedHeadscaleServiceServer struct {
+}
+
+func (UnimplementedHeadscaleServiceServer) GetMachine(context.Context, *GetMachineRequest) (*GetMachineResponse, error) {
+ return nil, status.Errorf(codes.Unimplemented, "method GetMachine not implemented")
+}
+func (UnimplementedHeadscaleServiceServer) CreateNamespace(context.Context, *CreateNamespaceRequest) (*CreateNamespaceResponse, error) {
+ return nil, status.Errorf(codes.Unimplemented, "method CreateNamespace not implemented")
+}
+func (UnimplementedHeadscaleServiceServer) DeleteNamespace(context.Context, *DeleteNamespaceRequest) (*DeleteNamespaceResponse, error) {
+ return nil, status.Errorf(codes.Unimplemented, "method DeleteNamespace not implemented")
+}
+func (UnimplementedHeadscaleServiceServer) ListNamespaces(context.Context, *ListNamespacesRequest) (*ListNamespacesResponse, error) {
+ return nil, status.Errorf(codes.Unimplemented, "method ListNamespaces 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)
+}
+
+func _HeadscaleService_CreateNamespace_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
+ in := new(CreateNamespaceRequest)
+ if err := dec(in); err != nil {
+ return nil, err
+ }
+ if interceptor == nil {
+ return srv.(HeadscaleServiceServer).CreateNamespace(ctx, in)
+ }
+ info := &grpc.UnaryServerInfo{
+ Server: srv,
+ FullMethod: "/headscale.v1.HeadscaleService/CreateNamespace",
+ }
+ handler := func(ctx context.Context, req interface{}) (interface{}, error) {
+ return srv.(HeadscaleServiceServer).CreateNamespace(ctx, req.(*CreateNamespaceRequest))
+ }
+ return interceptor(ctx, in, info, handler)
+}
+
+func _HeadscaleService_DeleteNamespace_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
+ in := new(DeleteNamespaceRequest)
+ if err := dec(in); err != nil {
+ return nil, err
+ }
+ if interceptor == nil {
+ return srv.(HeadscaleServiceServer).DeleteNamespace(ctx, in)
+ }
+ info := &grpc.UnaryServerInfo{
+ Server: srv,
+ FullMethod: "/headscale.v1.HeadscaleService/DeleteNamespace",
+ }
+ handler := func(ctx context.Context, req interface{}) (interface{}, error) {
+ return srv.(HeadscaleServiceServer).DeleteNamespace(ctx, req.(*DeleteNamespaceRequest))
+ }
+ return interceptor(ctx, in, info, handler)
+}
+
+func _HeadscaleService_ListNamespaces_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
+ in := new(ListNamespacesRequest)
+ if err := dec(in); err != nil {
+ return nil, err
+ }
+ if interceptor == nil {
+ return srv.(HeadscaleServiceServer).ListNamespaces(ctx, in)
+ }
+ info := &grpc.UnaryServerInfo{
+ Server: srv,
+ FullMethod: "/headscale.v1.HeadscaleService/ListNamespaces",
+ }
+ handler := func(ctx context.Context, req interface{}) (interface{}, error) {
+ return srv.(HeadscaleServiceServer).ListNamespaces(ctx, req.(*ListNamespacesRequest))
+ }
+ 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,
+ },
+ {
+ MethodName: "CreateNamespace",
+ Handler: _HeadscaleService_CreateNamespace_Handler,
+ },
+ {
+ MethodName: "DeleteNamespace",
+ Handler: _HeadscaleService_DeleteNamespace_Handler,
+ },
+ {
+ MethodName: "ListNamespaces",
+ Handler: _HeadscaleService_ListNamespaces_Handler,
+ },
+ },
+ Streams: []grpc.StreamDesc{},
+ Metadata: "headscale/v1/headscale.proto",
+}
diff --git a/gen/openapiv2/headscale/v1/headscale.swagger.json b/gen/openapiv2/headscale/v1/headscale.swagger.json
new file mode 100644
index 00000000..567a921f
--- /dev/null
+++ b/gen/openapiv2/headscale/v1/headscale.swagger.json
@@ -0,0 +1,250 @@
+{
+ "swagger": "2.0",
+ "info": {
+ "title": "headscale/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/v1GetMachineResponse"
+ }
+ },
+ "default": {
+ "description": "An unexpected error response.",
+ "schema": {
+ "$ref": "#/definitions/rpcStatus"
+ }
+ }
+ },
+ "parameters": [
+ {
+ "name": "machineId",
+ "in": "path",
+ "required": true,
+ "type": "string",
+ "format": "uint64"
+ }
+ ],
+ "tags": [
+ "HeadscaleService"
+ ]
+ }
+ },
+ "/api/v1/namespace": {
+ "get": {
+ "operationId": "HeadscaleService_ListNamespaces",
+ "responses": {
+ "200": {
+ "description": "A successful response.",
+ "schema": {
+ "$ref": "#/definitions/v1ListNamespacesResponse"
+ }
+ },
+ "default": {
+ "description": "An unexpected error response.",
+ "schema": {
+ "$ref": "#/definitions/rpcStatus"
+ }
+ }
+ },
+ "tags": [
+ "HeadscaleService"
+ ]
+ },
+ "delete": {
+ "operationId": "HeadscaleService_DeleteNamespace",
+ "responses": {
+ "200": {
+ "description": "A successful response.",
+ "schema": {
+ "$ref": "#/definitions/v1DeleteNamespaceResponse"
+ }
+ },
+ "default": {
+ "description": "An unexpected error response.",
+ "schema": {
+ "$ref": "#/definitions/rpcStatus"
+ }
+ }
+ },
+ "parameters": [
+ {
+ "name": "name",
+ "in": "query",
+ "required": false,
+ "type": "string"
+ }
+ ],
+ "tags": [
+ "HeadscaleService"
+ ]
+ },
+ "post": {
+ "operationId": "HeadscaleService_CreateNamespace",
+ "responses": {
+ "200": {
+ "description": "A successful response.",
+ "schema": {
+ "$ref": "#/definitions/v1CreateNamespaceResponse"
+ }
+ },
+ "default": {
+ "description": "An unexpected error response.",
+ "schema": {
+ "$ref": "#/definitions/rpcStatus"
+ }
+ }
+ },
+ "parameters": [
+ {
+ "name": "body",
+ "in": "body",
+ "required": true,
+ "schema": {
+ "$ref": "#/definitions/v1CreateNamespaceRequest"
+ }
+ }
+ ],
+ "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"
+ }
+ }
+ }
+ },
+ "v1CreateNamespaceRequest": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string"
+ }
+ }
+ },
+ "v1CreateNamespaceResponse": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string"
+ }
+ }
+ },
+ "v1DeleteNamespaceResponse": {
+ "type": "object"
+ },
+ "v1GetMachineResponse": {
+ "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"
+ },
+ "lastSeen": {
+ "type": "string",
+ "format": "date-time"
+ },
+ "lastSuccessfulUpdate": {
+ "type": "string",
+ "format": "date-time"
+ },
+ "expiry": {
+ "type": "string",
+ "format": "date-time"
+ }
+ }
+ },
+ "v1ListNamespacesResponse": {
+ "type": "object",
+ "properties": {
+ "namespaces": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ }
+ }
+ },
+ "v1RegisterMethod": {
+ "type": "string",
+ "enum": [
+ "REGISTER_METHOD_UNSPECIFIED",
+ "REGISTER_METHOD_AUTH_KEY",
+ "REGISTER_METHOD_CLI",
+ "REGISTER_METHOD_OIDC"
+ ],
+ "default": "REGISTER_METHOD_UNSPECIFIED"
+ }
+ }
+}
diff --git a/go.mod b/go.mod
index 65165e0d..296def85 100644
--- a/go.mod
+++ b/go.mod
@@ -7,23 +7,28 @@ require (
github.com/Microsoft/go-winio v0.5.0 // indirect
github.com/cenkalti/backoff/v4 v4.1.1 // 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/docker v20.10.8+incompatible // indirect
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/gofrs/uuid v4.0.0+incompatible
github.com/google/go-github v17.0.0+incompatible // 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/infobloxopen/protoc-gen-gorm v1.0.1
github.com/klauspost/compress v1.13.5
github.com/lib/pq v1.10.3 // indirect
github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6 // indirect
github.com/opencontainers/runc v1.0.2 // indirect
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/pterm/pterm v0.12.30
github.com/rs/zerolog v1.25.0
+ github.com/soheilhy/cmux v0.1.5
github.com/spf13/cobra v1.2.1
github.com/spf13/viper v1.8.1
github.com/stretchr/testify v1.7.0
@@ -33,7 +38,13 @@ require (
github.com/zsais/go-gin-prometheus v0.1.0
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5
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
+ 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/yaml.v2 v2.4.0
gorm.io/datatypes v1.0.2
diff --git a/go.sum b/go.sum
index b429ca95..96eb50bd 100644
--- a/go.sum
+++ b/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.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
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=
github.com/AlecAivazis/survey/v2 v2.3.2 h1:TqTB+aDDCLYhf9/bD2TwSO8u8jDSmMUd2SUVO4gCnU8=
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/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/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.1.0/go.mod h1:4UJr5HIiMZrwgkSPdsjy2uOQExX/WEILpIrO9UPGuXs=
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/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/PuerkitoBio/goquery v1.5.1/go.mod h1:GsLWisAFVj4WgDibEWF4pvYnkVQBpKBKeU+7zCJoLcc=
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/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/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/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-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4=
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.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
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/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.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqOes/6LfM=
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/bufbuild/buf v0.37.0/go.mod h1:lQ1m2HkIaGOFba6w/aC3KYBHhKEOESP3gaAEpS3dAFM=
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/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-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/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/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ=
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.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
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.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
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/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/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/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.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/docker/cli v20.10.7+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
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.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.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ=
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/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
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.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
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/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=
@@ -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/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.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/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
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/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
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-20190129154638-5b532d6fd5ef/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/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.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.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
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/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/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/fileglob v0.3.1/go.mod h1:kNcPrPzjCp+Ox3jmXLU5QEsjhqrtLBm6OnXAif8KRl8=
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/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.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/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.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/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/go.mod h1:VzxiSdG6j1pi7rwGm/xYI5RbtpBgM8sARDXlvEvxlu0=
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/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
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/jackc/chunkreader v1.0.0 h1:4s39bBR8ByfqH+DKm8rQA3E1LHZWB9XWcrz8fqaZbe0=
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/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/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/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/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.2 h1:eVKgfIdy9b6zbWBMgFpfDPoAMifwSZagU9HmEU6zgiI=
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/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.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.13.5 h1:9O69jUPDcsT9fEm74W92rZL9FQY7rCdaXVneq+yyzl4=
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.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=
@@ -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 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.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.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.3 h1:v9QZf2Sn6AmjXtQeFpdoq/eaNtYP6IN+7lcrygsIAtg=
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/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/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.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
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/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
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.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/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
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/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/predeclared v0.0.0-20200524104333-86fad755b4d3/go.mod h1:nt3d53pc1VYcphSCIaYAJtnPYnr3Zyn8fMq2wvPGPso=
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/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/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/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/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k=
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/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
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.13.0/go.mod h1:41g+FIPlQUTDCveupEmEA65IoiQFrtgCeDopC4ajGIM=
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.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
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/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
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/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.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/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY=
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/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.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.2.1 h1:+KmjbUw1hriSNMF55oPrkZcb27aECyrj8V2ytv7kWDw=
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/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/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/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
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.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
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.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.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
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.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
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=
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=
@@ -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-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-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-20200323165209-0ec3e9974c59/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-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-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-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
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.1/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-20180826012351-8a410e7b638d/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-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-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-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=
@@ -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-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-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-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-20200513185701-a91f0712d120/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-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-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-20201216054612-986b41b23924/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-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-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-20181108010431-42b317875d0f/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-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-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ=
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-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-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-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-20200511232937-7e40ca221e25/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-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-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-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-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-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-20200729194436-6467de6f59a7/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.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.25.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.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.5/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/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
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-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-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-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-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-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-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-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-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-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-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-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-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-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-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.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
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.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
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.36.0/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.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-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
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.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.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 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk=
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/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=
@@ -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/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
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/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
diff --git a/grpcv1.go b/grpcv1.go
new file mode 100644
index 00000000..08c977b8
--- /dev/null
+++ b/grpcv1.go
@@ -0,0 +1,75 @@
+//nolint
+package headscale
+
+import (
+ "context"
+
+ apiV1 "github.com/juanfont/headscale/gen/go/headscale/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.GetMachineResponse, error) {
+ // m, err := api.h.GetMachineByID(request.MachineId)
+ // if err != nil {
+ // return nil, err
+ // }
+
+ // TODO(kradalby): Make this function actually do something
+ return &apiV1.GetMachineResponse{Name: "test"}, nil
+}
+
+func (api headscaleV1APIServer) CreateNamespace(
+ ctx context.Context,
+ request *apiV1.CreateNamespaceRequest,
+) (*apiV1.CreateNamespaceResponse, error) {
+ namespace, err := api.h.CreateNamespace(request.Name)
+ if err != nil {
+ return nil, err
+ }
+
+ return &apiV1.CreateNamespaceResponse{Name: namespace.Name}, nil
+}
+
+func (api headscaleV1APIServer) DeleteNamespace(
+ ctx context.Context,
+ request *apiV1.DeleteNamespaceRequest,
+) (*apiV1.DeleteNamespaceResponse, error) {
+ err := api.h.DestroyNamespace(request.Name)
+ if err != nil {
+ return nil, err
+ }
+
+ return &apiV1.DeleteNamespaceResponse{}, nil
+}
+
+func (api headscaleV1APIServer) ListNamespaces(
+ ctx context.Context,
+ request *apiV1.ListNamespacesRequest,
+) (*apiV1.ListNamespacesResponse, error) {
+ namespaces, err := api.h.ListNamespaces()
+ if err != nil {
+ return nil, err
+ }
+
+ response := make([]string, len(*namespaces))
+ for index, namespace := range *namespaces {
+ response[index] = namespace.Name
+ }
+
+ return &apiV1.ListNamespacesResponse{Namespaces: response}, nil
+}
+
+func (api headscaleV1APIServer) mustEmbedUnimplementedHeadscaleServiceServer() {}
diff --git a/integration_test.go b/integration_test.go
index 53092423..1f30bcab 100644
--- a/integration_test.go
+++ b/integration_test.go
@@ -39,7 +39,7 @@ var (
headscale dockertest.Resource
)
-var tailscaleVersions = []string{"1.14.3", "1.12.3"}
+var tailscaleVersions = []string{"1.16.2", "1.14.3", "1.12.3"}
type TestNamespace struct {
count int
@@ -99,26 +99,48 @@ func executeCommand(resource *dockertest.Resource, cmd []string, env []string) (
var stdout bytes.Buffer
var stderr bytes.Buffer
- exitCode, err := resource.Exec(
- cmd,
- dockertest.ExecOptions{
- Env: env,
- StdOut: &stdout,
- StdErr: &stderr,
- },
- )
- if err != nil {
- return "", err
+ // TODO(kradalby): Make configurable
+ timeout := 10 * time.Second
+
+ type result struct {
+ exitCode int
+ err error
}
- if exitCode != 0 {
- fmt.Println("Command: ", cmd)
- fmt.Println("stdout: ", stdout.String())
- fmt.Println("stderr: ", stderr.String())
- return "", fmt.Errorf("command failed with: %s", stderr.String())
- }
+ resultChan := make(chan result, 1)
- return stdout.String(), nil
+ // Run your long running function in it's own goroutine and pass back it's
+ // response into our channel.
+ go func() {
+ exitCode, err := resource.Exec(
+ cmd,
+ dockertest.ExecOptions{
+ Env: env,
+ StdOut: &stdout,
+ StdErr: &stderr,
+ },
+ )
+ resultChan <- result{exitCode, err}
+ }()
+
+ // Listen on our channel AND a timeout channel - which ever happens first.
+ select {
+ case res := <-resultChan:
+ if res.err != nil {
+ return "", res.err
+ }
+
+ if res.exitCode != 0 {
+ fmt.Println("Command: ", cmd)
+ fmt.Println("stdout: ", stdout.String())
+ fmt.Println("stderr: ", stderr.String())
+ return "", fmt.Errorf("command failed with: %s", stderr.String())
+ }
+
+ return stdout.String(), nil
+ case <-time.After(timeout):
+ return "", fmt.Errorf("command timed out after %s", timeout)
+ }
}
func saveLog(resource *dockertest.Resource, basePath string) error {
@@ -282,8 +304,8 @@ func (s *IntegrationTestSuite) SetupSuite() {
[]string{"headscale", "namespaces", "create", namespace},
[]string{},
)
- assert.Nil(s.T(), err)
fmt.Println("headscale create namespace result: ", result)
+ assert.Nil(s.T(), err)
fmt.Printf("Creating pre auth key for %s\n", namespace)
authKey, err := executeCommand(
@@ -493,7 +515,7 @@ func (s *IntegrationTestSuite) TestSharedNodes() {
result, err := executeCommand(
&headscale,
- []string{"headscale", "nodes", "share", "--namespace", "shared", fmt.Sprint(machine.ID), "main"},
+ []string{"headscale", "nodes", "share", "--identifier", fmt.Sprint(machine.ID), "--namespace", "main"},
[]string{},
)
assert.Nil(s.T(), err)
diff --git a/machine.go b/machine.go
index 8986ac92..ccd30e3e 100644
--- a/machine.go
+++ b/machine.go
@@ -36,6 +36,7 @@ type Machine struct {
LastSeen *time.Time
LastSuccessfulUpdate *time.Time
Expiry *time.Time
+ RequestedExpiry *time.Time
HostInfo datatypes.JSON
Endpoints datatypes.JSON
@@ -56,6 +57,38 @@ func (m Machine) isAlreadyRegistered() bool {
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) {
log.Trace().
Str("func", "getDirectPeers").
@@ -326,7 +359,11 @@ func (ms MachinesP) String() string {
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))
for index, machine := range ms {
@@ -446,8 +483,10 @@ func (m Machine) toNode(baseDomain string, dnsConfig *tailcfg.DNSConfig, include
}
n := tailcfg.Node{
- 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
+ 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
Name: hostname,
User: tailcfg.UserID(m.NamespaceID),
Key: tailcfg.NodeKey(nKey),
diff --git a/namespaces.go b/namespaces.go
index c350e8c8..d7c1e035 100644
--- a/namespaces.go
+++ b/namespaces.go
@@ -246,6 +246,17 @@ func (n *Namespace) toUser() *tailcfg.User {
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 {
namespaceMap := make(map[string]Namespace)
namespaceMap[m.Namespace.Name] = m.Namespace
diff --git a/oidc.go b/oidc.go
new file mode 100644
index 00000000..51c443db
--- /dev/null
+++ b/oidc.go
@@ -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(`
+
+
+headscale
+
+ Authenticated as %s, you can now close this window.
+
+
+
+
+`, 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
+}
diff --git a/oidc_test.go b/oidc_test.go
new file mode 100644
index 00000000..b501ff14
--- /dev/null
+++ b/oidc_test.go
@@ -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)
+ }
+ })
+ }
+}
diff --git a/proto/buf.lock b/proto/buf.lock
new file mode 100644
index 00000000..03cd7b89
--- /dev/null
+++ b/proto/buf.lock
@@ -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
diff --git a/proto/buf.yaml b/proto/buf.yaml
new file mode 100644
index 00000000..7e524ba0
--- /dev/null
+++ b/proto/buf.yaml
@@ -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
diff --git a/proto/headscale/v1/headscale.proto b/proto/headscale/v1/headscale.proto
new file mode 100644
index 00000000..2e6b0a67
--- /dev/null
+++ b/proto/headscale/v1/headscale.proto
@@ -0,0 +1,106 @@
+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";
+
+enum RegisterMethod {
+ REGISTER_METHOD_UNSPECIFIED = 0;
+ REGISTER_METHOD_AUTH_KEY = 1;
+ REGISTER_METHOD_CLI = 2;
+ REGISTER_METHOD_OIDC = 3;
+}
+
+// message PreAuthKey {
+// uint64 id = 1;
+// string key = 2;
+// uint32 namespace_id = 3;
+// Namespace namespace = 4;
+// bool reusable = 5;
+// bool ephemeral = 6;
+// bool used = 7;
+//
+// google.protobuf.Timestamp created_at = 8;
+// google.protobuf.Timestamp expiration = 9;
+// }
+
+message GetMachineRequest {
+ uint64 machine_id = 1;
+}
+
+message GetMachineResponse {
+ uint64 id = 1;
+ string machine_key = 2;
+ string node_key = 3;
+ string disco_key = 4;
+ string ip_address = 5;
+ string name = 6;
+ uint32 namespace_id = 7;
+
+ bool registered = 8;
+ RegisterMethod register_method = 9;
+ uint32 auth_key_id = 10;
+ // PreAuthKey auth_key = 11;
+
+ google.protobuf.Timestamp last_seen = 12;
+ google.protobuf.Timestamp last_successful_update = 13;
+ google.protobuf.Timestamp expiry = 14;
+
+ // bytes host_info = 15;
+ // bytes endpoints = 16;
+ // bytes enabled_routes = 17;
+
+ // google.protobuf.Timestamp created_at = 18;
+ // google.protobuf.Timestamp updated_at = 19;
+ // google.protobuf.Timestamp deleted_at = 20;
+}
+
+message CreateNamespaceRequest {
+ string name = 1;
+}
+
+message CreateNamespaceResponse {
+ string name = 1;
+}
+
+message DeleteNamespaceRequest {
+ string name = 1;
+}
+
+message DeleteNamespaceResponse {
+}
+
+message ListNamespacesRequest {
+}
+
+message ListNamespacesResponse {
+ repeated string namespaces = 1;
+}
+
+service HeadscaleService {
+ rpc GetMachine(GetMachineRequest) returns(GetMachineResponse) {
+ option(google.api.http) = {
+ get : "/api/v1/machine/{machine_id}"
+ };
+ }
+
+ rpc CreateNamespace(CreateNamespaceRequest) returns(CreateNamespaceResponse) {
+ option(google.api.http) = {
+ post : "/api/v1/namespace"
+ body : "*"
+ };
+ }
+
+ rpc DeleteNamespace(DeleteNamespaceRequest) returns(DeleteNamespaceResponse) {
+ option(google.api.http) = {
+ delete : "/api/v1/namespace"
+ };
+ }
+
+ rpc ListNamespaces(ListNamespacesRequest) returns(ListNamespacesResponse) {
+ option(google.api.http) = {
+ get : "/api/v1/namespace"
+ };
+ }
+}
diff --git a/sharing.go b/sharing.go
index 879ed06f..5f6a8f45 100644
--- a/sharing.go
+++ b/sharing.go
@@ -43,7 +43,8 @@ func (h *Headscale) AddSharedMachineToNamespace(m *Machine, ns *Namespace) error
// RemoveSharedMachineFromNamespace removes a shared machine from a namespace
func (h *Headscale) RemoveSharedMachineFromNamespace(m *Machine, ns *Namespace) error {
if m.NamespaceID == ns.ID {
- return errorSameNamespace
+ // Can't unshare from primary namespace
+ return errorMachineNotShared
}
sharedMachine := SharedMachine{}
diff --git a/sharing_test.go b/sharing_test.go
index 140b05f2..1133fd92 100644
--- a/sharing_test.go
+++ b/sharing_test.go
@@ -86,6 +86,9 @@ func (s *Suite) TestUnshare(c *check.C) {
err = h.RemoveSharedMachineFromNamespace(m2, n1)
c.Assert(err, check.Equals, errorMachineNotShared)
+
+ err = h.RemoveSharedMachineFromNamespace(m1, n1)
+ c.Assert(err, check.Equals, errorMachineNotShared)
}
func (s *Suite) TestAlreadyShared(c *check.C) {
diff --git a/swagger.go b/swagger.go
new file mode 100644
index 00000000..17f57697
--- /dev/null
+++ b/swagger.go
@@ -0,0 +1,65 @@
+package headscale
+
+import (
+ "bytes"
+ _ "embed"
+ "net/http"
+ "text/template"
+
+ "github.com/rs/zerolog/log"
+
+ "github.com/gin-gonic/gin"
+)
+
+//go:embed gen/openapiv2/headscale/v1/headscale.swagger.json
+var apiV1JSON []byte
+
+func SwaggerUI(c *gin.Context) {
+ t := template.Must(template.New("swagger").Parse(`
+
+
+
+
+
+
+
+
+
+
+
+`))
+
+ var payload bytes.Buffer
+ if err := t.Execute(&payload, struct{}{}); err != nil {
+ log.Error().
+ Caller().
+ Err(err).
+ Msg("Could not render Swagger")
+ c.Data(http.StatusInternalServerError, "text/html; charset=utf-8", []byte("Could not render Swagger"))
+ return
+ }
+
+ c.Data(http.StatusOK, "text/html; charset=utf-8", payload.Bytes())
+}
+
+func SwaggerAPIv1(c *gin.Context) {
+ c.Data(http.StatusOK, "application/json; charset=utf-8", apiV1JSON)
+}
diff --git a/tools.go b/tools.go
new file mode 100644
index 00000000..287c1230
--- /dev/null
+++ b/tools.go
@@ -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"
+)
diff --git a/utils.go b/utils.go
index cbe1d870..555227ec 100644
--- a/utils.go
+++ b/utils.go
@@ -6,10 +6,12 @@
package headscale
import (
+ "context"
"crypto/rand"
"encoding/json"
"fmt"
"io"
+ "net"
"strings"
"golang.org/x/crypto/nacl/box"
@@ -156,3 +158,8 @@ func tailNodesToString(nodes []*tailcfg.Node) string {
func tailMapResponseToString(resp tailcfg.MapResponse) string {
return fmt.Sprintf("{ Node: %s, Peers: %s }", resp.Node.Name, tailNodesToString(resp.Peers))
}
+
+func GrpcSocketDialer(ctx context.Context, addr string) (net.Conn, error) {
+ var d net.Dialer
+ return d.DialContext(ctx, "unix", addr)
+}