diff --git a/CHANGELOG.md b/CHANGELOG.md index 045a394f..f6e44161 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,9 @@ **Features**: - Add IPv6 support to the prefix assigned to namespaces +- Add API Key support + - Enable remote control of `headscale` via CLI [docs](docs/remote-cli.md) + - Enable HTTP API (beta, subject to change) **Changes**: diff --git a/api_key.go b/api_key.go new file mode 100644 index 00000000..a968b260 --- /dev/null +++ b/api_key.go @@ -0,0 +1,164 @@ +package headscale + +import ( + "fmt" + "strings" + "time" + + v1 "github.com/juanfont/headscale/gen/go/headscale/v1" + "golang.org/x/crypto/bcrypt" + "google.golang.org/protobuf/types/known/timestamppb" +) + +const ( + apiPrefixLength = 7 + apiKeyLength = 32 + apiKeyParts = 2 + + errAPIKeyFailedToParse = Error("Failed to parse ApiKey") +) + +// APIKey describes the datamodel for API keys used to remotely authenticate with +// headscale. +type APIKey struct { + ID uint64 `gorm:"primary_key"` + Prefix string `gorm:"uniqueIndex"` + Hash []byte + + CreatedAt *time.Time + Expiration *time.Time + LastSeen *time.Time +} + +// CreateAPIKey creates a new ApiKey in a namespace, and returns it. +func (h *Headscale) CreateAPIKey( + expiration *time.Time, +) (string, *APIKey, error) { + prefix, err := GenerateRandomStringURLSafe(apiPrefixLength) + if err != nil { + return "", nil, err + } + + toBeHashed, err := GenerateRandomStringURLSafe(apiKeyLength) + if err != nil { + return "", nil, err + } + + // Key to return to user, this will only be visible _once_ + keyStr := prefix + "." + toBeHashed + + hash, err := bcrypt.GenerateFromPassword([]byte(toBeHashed), bcrypt.DefaultCost) + if err != nil { + return "", nil, err + } + + key := APIKey{ + Prefix: prefix, + Hash: hash, + Expiration: expiration, + } + h.db.Save(&key) + + return keyStr, &key, nil +} + +// ListAPIKeys returns the list of ApiKeys for a namespace. +func (h *Headscale) ListAPIKeys() ([]APIKey, error) { + keys := []APIKey{} + if err := h.db.Find(&keys).Error; err != nil { + return nil, err + } + + return keys, nil +} + +// GetAPIKey returns a ApiKey for a given key. +func (h *Headscale) GetAPIKey(prefix string) (*APIKey, error) { + key := APIKey{} + if result := h.db.First(&key, "prefix = ?", prefix); result.Error != nil { + return nil, result.Error + } + + return &key, nil +} + +// GetAPIKeyByID returns a ApiKey for a given id. +func (h *Headscale) GetAPIKeyByID(id uint64) (*APIKey, error) { + key := APIKey{} + if result := h.db.Find(&APIKey{ID: id}).First(&key); result.Error != nil { + return nil, result.Error + } + + return &key, nil +} + +// DestroyAPIKey destroys a ApiKey. Returns error if the ApiKey +// does not exist. +func (h *Headscale) DestroyAPIKey(key APIKey) error { + if result := h.db.Unscoped().Delete(key); result.Error != nil { + return result.Error + } + + return nil +} + +// ExpireAPIKey marks a ApiKey as expired. +func (h *Headscale) ExpireAPIKey(key *APIKey) error { + if err := h.db.Model(&key).Update("Expiration", time.Now()).Error; err != nil { + return err + } + + return nil +} + +func (h *Headscale) ValidateAPIKey(keyStr string) (bool, error) { + prefix, hash, err := splitAPIKey(keyStr) + if err != nil { + return false, fmt.Errorf("failed to validate api key: %w", err) + } + + key, err := h.GetAPIKey(prefix) + if err != nil { + return false, fmt.Errorf("failed to validate api key: %w", err) + } + + if key.Expiration.Before(time.Now()) { + return false, nil + } + + if err := bcrypt.CompareHashAndPassword(key.Hash, []byte(hash)); err != nil { + return false, err + } + + return true, nil +} + +func splitAPIKey(key string) (string, string, error) { + parts := strings.Split(key, ".") + if len(parts) != apiKeyParts { + return "", "", errAPIKeyFailedToParse + } + + return parts[0], parts[1], nil +} + +func (key *APIKey) toProto() *v1.ApiKey { + protoKey := v1.ApiKey{ + Id: key.ID, + Prefix: key.Prefix, + } + + if key.Expiration != nil { + protoKey.Expiration = timestamppb.New(*key.Expiration) + } + + if key.CreatedAt != nil { + protoKey.CreatedAt = timestamppb.New(*key.CreatedAt) + } + + if key.LastSeen != nil { + protoKey.LastSeen = timestamppb.New(*key.LastSeen) + } + + return &protoKey +} diff --git a/api_key_test.go b/api_key_test.go new file mode 100644 index 00000000..2ddbbbc0 --- /dev/null +++ b/api_key_test.go @@ -0,0 +1,89 @@ +package headscale + +import ( + "time" + + "gopkg.in/check.v1" +) + +func (*Suite) TestCreateAPIKey(c *check.C) { + apiKeyStr, apiKey, err := app.CreateAPIKey(nil) + c.Assert(err, check.IsNil) + c.Assert(apiKey, check.NotNil) + + // Did we get a valid key? + c.Assert(apiKey.Prefix, check.NotNil) + c.Assert(apiKey.Hash, check.NotNil) + c.Assert(apiKeyStr, check.Not(check.Equals), "") + + _, err = app.ListAPIKeys() + c.Assert(err, check.IsNil) + + keys, err := app.ListAPIKeys() + c.Assert(err, check.IsNil) + c.Assert(len(keys), check.Equals, 1) +} + +func (*Suite) TestAPIKeyDoesNotExist(c *check.C) { + key, err := app.GetAPIKey("does-not-exist") + c.Assert(err, check.NotNil) + c.Assert(key, check.IsNil) +} + +func (*Suite) TestValidateAPIKeyOk(c *check.C) { + nowPlus2 := time.Now().Add(2 * time.Hour) + apiKeyStr, apiKey, err := app.CreateAPIKey(&nowPlus2) + c.Assert(err, check.IsNil) + c.Assert(apiKey, check.NotNil) + + valid, err := app.ValidateAPIKey(apiKeyStr) + c.Assert(err, check.IsNil) + c.Assert(valid, check.Equals, true) +} + +func (*Suite) TestValidateAPIKeyNotOk(c *check.C) { + nowMinus2 := time.Now().Add(time.Duration(-2) * time.Hour) + apiKeyStr, apiKey, err := app.CreateAPIKey(&nowMinus2) + c.Assert(err, check.IsNil) + c.Assert(apiKey, check.NotNil) + + valid, err := app.ValidateAPIKey(apiKeyStr) + c.Assert(err, check.IsNil) + c.Assert(valid, check.Equals, false) + + now := time.Now() + apiKeyStrNow, apiKey, err := app.CreateAPIKey(&now) + c.Assert(err, check.IsNil) + c.Assert(apiKey, check.NotNil) + + validNow, err := app.ValidateAPIKey(apiKeyStrNow) + c.Assert(err, check.IsNil) + c.Assert(validNow, check.Equals, false) + + validSilly, err := app.ValidateAPIKey("nota.validkey") + c.Assert(err, check.NotNil) + c.Assert(validSilly, check.Equals, false) + + validWithErr, err := app.ValidateAPIKey("produceerrorkey") + c.Assert(err, check.NotNil) + c.Assert(validWithErr, check.Equals, false) +} + +func (*Suite) TestExpireAPIKey(c *check.C) { + nowPlus2 := time.Now().Add(2 * time.Hour) + apiKeyStr, apiKey, err := app.CreateAPIKey(&nowPlus2) + c.Assert(err, check.IsNil) + c.Assert(apiKey, check.NotNil) + + valid, err := app.ValidateAPIKey(apiKeyStr) + c.Assert(err, check.IsNil) + c.Assert(valid, check.Equals, true) + + err = app.ExpireAPIKey(apiKey) + c.Assert(err, check.IsNil) + c.Assert(apiKey.Expiration, check.NotNil) + + notValid, err := app.ValidateAPIKey(apiKeyStr) + c.Assert(err, check.IsNil) + c.Assert(notValid, check.Equals, false) +} diff --git a/app.go b/app.go index 22b4ceee..ac350ecf 100644 --- a/app.go +++ b/app.go @@ -27,7 +27,6 @@ import ( zerolog "github.com/philip-bui/grpc-zerolog" zl "github.com/rs/zerolog" "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" @@ -36,6 +35,7 @@ import ( "google.golang.org/grpc" "google.golang.org/grpc/codes" "google.golang.org/grpc/credentials" + "google.golang.org/grpc/credentials/insecure" "google.golang.org/grpc/metadata" "google.golang.org/grpc/peer" "google.golang.org/grpc/reflection" @@ -68,6 +68,8 @@ const ( type Config struct { ServerURL string Addr string + GRPCAddr string + GRPCAllowInsecure bool EphemeralNodeInactivityTimeout time.Duration IPPrefixes []netaddr.IPPrefix PrivateKeyPath string @@ -121,8 +123,8 @@ type DERPConfig struct { type CLIConfig struct { Address string APIKey string - Insecure bool Timeout time.Duration + Insecure bool } // Headscale represents the base app of the service. @@ -325,26 +327,26 @@ func (h *Headscale) grpcAuthenticationInterceptor(ctx context.Context, ) } - // 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", - ) + valid, err := h.ValidateAPIKey(strings.TrimPrefix(token, AuthPrefix)) + if err != nil { + log.Error(). + Caller(). + Err(err). + Str("client_address", client.Addr.String()). + Msg("failed to validate token") - // 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 ctx, status.Error(codes.Internal, "failed to validate token") + } - // return handler(ctx, req) + if !valid { + log.Info(). + Str("client_address", client.Addr.String()). + Msg("invalid token") + + return ctx, status.Error(codes.Unauthenticated, "invalid token") + } + + return handler(ctx, req) } func (h *Headscale) httpAuthenticationMiddleware(ctx *gin.Context) { @@ -367,19 +369,30 @@ func (h *Headscale) httpAuthenticationMiddleware(ctx *gin.Context) { ctx.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"}) + valid, err := h.ValidateAPIKey(strings.TrimPrefix(authHeader, AuthPrefix)) + if err != nil { + log.Error(). + Caller(). + Err(err). + Str("client_address", ctx.ClientIP()). + Msg("failed to validate token") - // return - // } + ctx.AbortWithStatus(http.StatusInternalServerError) - // c.Next() + return + } + + if !valid { + log.Info(). + Str("client_address", ctx.ClientIP()). + Msg("invalid token") + + ctx.AbortWithStatus(http.StatusUnauthorized) + + return + } + + ctx.Next() } // ensureUnixSocketIsAbsent will check if the given path for headscales unix socket is clear @@ -393,15 +406,71 @@ func (h *Headscale) ensureUnixSocketIsAbsent() error { return os.Remove(h.cfg.UnixSocket) } +func (h *Headscale) createRouter(grpcMux *runtime.ServeMux) *gin.Engine { + router := gin.Default() + + prometheus := ginprometheus.NewPrometheus("gin") + prometheus.Use(router) + + router.GET( + "/health", + func(c *gin.Context) { c.JSON(http.StatusOK, gin.H{"healthy": "ok"}) }, + ) + router.GET("/key", h.KeyHandler) + router.GET("/register", h.RegisterWebAPI) + router.POST("/machine/:id/map", h.PollNetMapHandler) + router.POST("/machine/:id", h.RegistrationHandler) + router.GET("/oidc/register/:mkey", h.RegisterOIDC) + router.GET("/oidc/callback", h.OIDCCallback) + router.GET("/apple", h.AppleMobileConfig) + router.GET("/apple/:platform", h.ApplePlatformConfig) + router.GET("/swagger", SwaggerUI) + router.GET("/swagger/v1/openapiv2.json", SwaggerAPIv1) + + api := router.Group("/api") + api.Use(h.httpAuthenticationMiddleware) + { + api.Any("/v1/*any", gin.WrapF(grpcMux.ServeHTTP)) + } + + router.NoRoute(stdoutHandler) + + return router +} + // Serve launches a GIN server with the Headscale API. func (h *Headscale) Serve() error { var err error + // Fetch an initial DERP Map before we start serving + h.DERPMap = GetDERPMap(h.cfg.DERP) + + if h.cfg.DERP.AutoUpdate { + derpMapCancelChannel := make(chan struct{}) + defer func() { derpMapCancelChannel <- struct{}{} }() + go h.scheduledDERPMapUpdateWorker(derpMapCancelChannel) + } + + go h.expireEphemeralNodes(updateInterval) + + if zl.GlobalLevel() == zl.TraceLevel { + zerolog.RespLog = true + } else { + zerolog.RespLog = false + } + + // Prepare group for running listeners + errorGroup := new(errgroup.Group) + ctx := context.Background() ctx, cancel := context.WithCancel(ctx) - defer cancel() + // + // + // Set up LOCAL listeners + // + err = h.ensureUnixSocketIsAbsent() if err != nil { return fmt.Errorf("unable to remove old socket file: %w", err) @@ -430,32 +499,13 @@ func (h *Headscale) Serve() error { os.Exit(0) }(sigc) - networkListener, err := net.Listen("tcp", h.cfg.Addr) - if err != nil { - return fmt.Errorf("failed to bind to TCP address: %w", 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. - networkMutex := cmux.New(networkListener) - // Match gRPC requests here - grpcListener := networkMutex.MatchWithWriters( - cmux.HTTP2MatchHeaderFieldSendSettings("content-type", "application/grpc"), - cmux.HTTP2MatchHeaderFieldSendSettings( - "content-type", - "application/grpc+proto", - ), - ) - // Otherwise match regular http requests. - httpListener := networkMutex.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.WithTransportCredentials(insecure.NewCredentials()), grpc.WithContextDialer(GrpcSocketDialer), }..., ) @@ -470,44 +520,80 @@ func (h *Headscale) Serve() error { return err } - router := gin.Default() + // Start the local gRPC server without TLS and without authentication + grpcSocket := grpc.NewServer(zerolog.UnaryInterceptor()) - prometheus := ginprometheus.NewPrometheus("gin") - prometheus.Use(router) + v1.RegisterHeadscaleServiceServer(grpcSocket, newHeadscaleV1APIServer(h)) + reflection.Register(grpcSocket) - router.GET( - "/health", - func(c *gin.Context) { c.JSON(http.StatusOK, gin.H{"healthy": "ok"}) }, - ) - router.GET("/key", h.KeyHandler) - router.GET("/register", h.RegisterWebAPI) - router.POST("/machine/:id/map", h.PollNetMapHandler) - router.POST("/machine/:id", h.RegistrationHandler) - router.GET("/oidc/register/:mkey", h.RegisterOIDC) - router.GET("/oidc/callback", h.OIDCCallback) - router.GET("/apple", h.AppleMobileConfig) - router.GET("/apple/:platform", h.ApplePlatformConfig) - router.GET("/swagger", SwaggerUI) - router.GET("/swagger/v1/openapiv2.json", SwaggerAPIv1) + errorGroup.Go(func() error { return grpcSocket.Serve(socketListener) }) - api := router.Group("/api") - api.Use(h.httpAuthenticationMiddleware) - { - api.Any("/v1/*any", gin.WrapF(grpcGatewayMux.ServeHTTP)) + // + // + // Set up REMOTE listeners + // + + tlsConfig, err := h.getTLSSettings() + if err != nil { + log.Error().Err(err).Msg("Failed to set up TLS configuration") + + return err } - router.NoRoute(stdoutHandler) + // + // + // gRPC setup + // - // Fetch an initial DERP Map before we start serving - h.DERPMap = GetDERPMap(h.cfg.DERP) + // We are sadly not able to run gRPC and HTTPS (2.0) on the same + // port because the connection mux does not support matching them + // since they are so similar. There is multiple issues open and we + // can revisit this if changes: + // https://github.com/soheilhy/cmux/issues/68 + // https://github.com/soheilhy/cmux/issues/91 - if h.cfg.DERP.AutoUpdate { - derpMapCancelChannel := make(chan struct{}) - defer func() { derpMapCancelChannel <- struct{}{} }() - go h.scheduledDERPMapUpdateWorker(derpMapCancelChannel) + if tlsConfig != nil || h.cfg.GRPCAllowInsecure { + log.Info().Msgf("Enabling remote gRPC at %s", h.cfg.GRPCAddr) + + grpcOptions := []grpc.ServerOption{ + grpc.UnaryInterceptor( + grpc_middleware.ChainUnaryServer( + h.grpcAuthenticationInterceptor, + zerolog.NewUnaryServerInterceptor(), + ), + ), + } + + if tlsConfig != nil { + grpcOptions = append(grpcOptions, + grpc.Creds(credentials.NewTLS(tlsConfig)), + ) + } else { + log.Warn().Msg("gRPC is running without security") + } + + grpcServer := grpc.NewServer(grpcOptions...) + + v1.RegisterHeadscaleServiceServer(grpcServer, newHeadscaleV1APIServer(h)) + reflection.Register(grpcServer) + + grpcListener, err := net.Listen("tcp", h.cfg.GRPCAddr) + if err != nil { + return fmt.Errorf("failed to bind to TCP address: %w", err) + } + + errorGroup.Go(func() error { return grpcServer.Serve(grpcListener) }) + + log.Info(). + Msgf("listening and serving gRPC on: %s", h.cfg.GRPCAddr) } - go h.expireEphemeralNodes(updateInterval) + // + // + // HTTP setup + // + + router := h.createRouter(grpcGatewayMux) httpServer := &http.Server{ Addr: h.cfg.Addr, @@ -520,65 +606,21 @@ func (h *Headscale) Serve() error { WriteTimeout: 0, } - if zl.GlobalLevel() == zl.TraceLevel { - zerolog.RespLog = true - } else { - zerolog.RespLog = false - } - - grpcOptions := []grpc.ServerOption{ - grpc.UnaryInterceptor( - grpc_middleware.ChainUnaryServer( - h.grpcAuthenticationInterceptor, - zerolog.NewUnaryServerInterceptor(), - ), - ), - } - - tlsConfig, err := h.getTLSSettings() - if err != nil { - log.Error().Err(err).Msg("Failed to set up TLS configuration") - - return err - } - + var httpListener net.Listener 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(zerolog.UnaryInterceptor()) - - v1.RegisterHeadscaleServiceServer(grpcServer, newHeadscaleV1APIServer(h)) - v1.RegisterHeadscaleServiceServer(grpcSocket, newHeadscaleV1APIServer(h)) - reflection.Register(grpcServer) - reflection.Register(grpcSocket) - - errorGroup := new(errgroup.Group) - - errorGroup.Go(func() error { return grpcSocket.Serve(socketListener) }) - - // TODO(kradalby): Verify if we need the same TLS setup for gRPC as HTTP - errorGroup.Go(func() error { return grpcServer.Serve(grpcListener) }) - - if tlsConfig != nil { - errorGroup.Go(func() error { - tlsl := tls.NewListener(httpListener, tlsConfig) - - return httpServer.Serve(tlsl) - }) + httpListener, err = tls.Listen("tcp", h.cfg.Addr, tlsConfig) } else { - errorGroup.Go(func() error { return httpServer.Serve(httpListener) }) + httpListener, err = net.Listen("tcp", h.cfg.Addr) + } + if err != nil { + return fmt.Errorf("failed to bind to TCP address: %w", err) } - errorGroup.Go(func() error { return networkMutex.Serve() }) + errorGroup.Go(func() error { return httpServer.Serve(httpListener) }) log.Info(). - Msgf("listening and serving (multiplexed HTTP and gRPC) on: %s", h.cfg.Addr) + Msgf("listening and serving HTTP on: %s", h.cfg.Addr) return errorGroup.Wait() } @@ -614,6 +656,7 @@ func (h *Headscale) getTLSSettings() (*tls.Config, error) { // service, which can be configured to run on any other port. go func() { log.Fatal(). + Caller(). Err(http.ListenAndServe(h.cfg.TLSLetsEncryptListen, certManager.HTTPHandler(http.HandlerFunc(h.redirect)))). Msg("failed to set up a HTTP server") }() diff --git a/cmd/headscale/cli/api_key.go b/cmd/headscale/cli/api_key.go new file mode 100644 index 00000000..975149ff --- /dev/null +++ b/cmd/headscale/cli/api_key.go @@ -0,0 +1,183 @@ +package cli + +import ( + "fmt" + "strconv" + "time" + + "github.com/juanfont/headscale" + v1 "github.com/juanfont/headscale/gen/go/headscale/v1" + "github.com/pterm/pterm" + "github.com/rs/zerolog/log" + "github.com/spf13/cobra" + "google.golang.org/protobuf/types/known/timestamppb" +) + +const ( + // 90 days. + DefaultAPIKeyExpiry = 90 * 24 * time.Hour +) + +func init() { + rootCmd.AddCommand(apiKeysCmd) + apiKeysCmd.AddCommand(listAPIKeys) + + createAPIKeyCmd.Flags(). + DurationP("expiration", "e", DefaultAPIKeyExpiry, "Human-readable expiration of the key (30m, 24h, 365d...)") + + apiKeysCmd.AddCommand(createAPIKeyCmd) + + expireAPIKeyCmd.Flags().StringP("prefix", "p", "", "ApiKey prefix") + err := expireAPIKeyCmd.MarkFlagRequired("prefix") + if err != nil { + log.Fatal().Err(err).Msg("") + } + apiKeysCmd.AddCommand(expireAPIKeyCmd) +} + +var apiKeysCmd = &cobra.Command{ + Use: "apikeys", + Short: "Handle the Api keys in Headscale", +} + +var listAPIKeys = &cobra.Command{ + Use: "list", + Short: "List the Api keys for headscale", + Run: func(cmd *cobra.Command, args []string) { + output, _ := cmd.Flags().GetString("output") + + ctx, client, conn, cancel := getHeadscaleCLIClient() + defer cancel() + defer conn.Close() + + request := &v1.ListApiKeysRequest{} + + response, err := client.ListApiKeys(ctx, request) + if err != nil { + ErrorOutput( + err, + fmt.Sprintf("Error getting the list of keys: %s", err), + output, + ) + + return + } + + if output != "" { + SuccessOutput(response.ApiKeys, "", output) + + return + } + + tableData := pterm.TableData{ + {"ID", "Prefix", "Expiration", "Created"}, + } + for _, key := range response.ApiKeys { + expiration := "-" + + if key.GetExpiration() != nil { + expiration = ColourTime(key.Expiration.AsTime()) + } + + tableData = append(tableData, []string{ + strconv.FormatUint(key.GetId(), headscale.Base10), + key.GetPrefix(), + expiration, + key.GetCreatedAt().AsTime().Format(HeadscaleDateTimeFormat), + }) + + } + err = pterm.DefaultTable.WithHasHeader().WithData(tableData).Render() + if err != nil { + ErrorOutput( + err, + fmt.Sprintf("Failed to render pterm table: %s", err), + output, + ) + + return + } + }, +} + +var createAPIKeyCmd = &cobra.Command{ + Use: "create", + Short: "Creates a new Api key", + Long: ` +Creates a new Api key, the Api key is only visible on creation +and cannot be retrieved again. +If you loose a key, create a new one and revoke (expire) the old one.`, + Run: func(cmd *cobra.Command, args []string) { + output, _ := cmd.Flags().GetString("output") + + log.Trace(). + Msg("Preparing to create ApiKey") + + request := &v1.CreateApiKeyRequest{} + + duration, _ := cmd.Flags().GetDuration("expiration") + expiration := time.Now().UTC().Add(duration) + + log.Trace().Dur("expiration", duration).Msg("expiration has been set") + + request.Expiration = timestamppb.New(expiration) + + ctx, client, conn, cancel := getHeadscaleCLIClient() + defer cancel() + defer conn.Close() + + response, err := client.CreateApiKey(ctx, request) + if err != nil { + ErrorOutput( + err, + fmt.Sprintf("Cannot create Api Key: %s\n", err), + output, + ) + + return + } + + SuccessOutput(response.ApiKey, response.ApiKey, output) + }, +} + +var expireAPIKeyCmd = &cobra.Command{ + Use: "expire", + Short: "Expire an ApiKey", + Aliases: []string{"revoke"}, + Run: func(cmd *cobra.Command, args []string) { + output, _ := cmd.Flags().GetString("output") + + prefix, err := cmd.Flags().GetString("prefix") + if err != nil { + ErrorOutput( + err, + fmt.Sprintf("Error getting prefix from CLI flag: %s", err), + output, + ) + + return + } + + ctx, client, conn, cancel := getHeadscaleCLIClient() + defer cancel() + defer conn.Close() + + request := &v1.ExpireApiKeyRequest{ + Prefix: prefix, + } + + response, err := client.ExpireApiKey(ctx, request) + if err != nil { + ErrorOutput( + err, + fmt.Sprintf("Cannot expire Api Key: %s\n", err), + output, + ) + + return + } + + SuccessOutput(response, "Key expired", output) + }, +} diff --git a/cmd/headscale/cli/preauthkeys.go b/cmd/headscale/cli/preauthkeys.go index 5342085c..580184f1 100644 --- a/cmd/headscale/cli/preauthkeys.go +++ b/cmd/headscale/cli/preauthkeys.go @@ -83,7 +83,7 @@ var listPreAuthKeys = &cobra.Command{ for _, key := range response.PreAuthKeys { expiration := "-" if key.GetExpiration() != nil { - expiration = key.Expiration.AsTime().Format("2006-01-02 15:04:05") + expiration = ColourTime(key.Expiration.AsTime()) } var reusable string diff --git a/cmd/headscale/cli/pterm_style.go b/cmd/headscale/cli/pterm_style.go new file mode 100644 index 00000000..85fd050b --- /dev/null +++ b/cmd/headscale/cli/pterm_style.go @@ -0,0 +1,19 @@ +package cli + +import ( + "time" + + "github.com/pterm/pterm" +) + +func ColourTime(date time.Time) string { + dateStr := date.Format("2006-01-02 15:04:05") + + if date.After(time.Now()) { + dateStr = pterm.LightGreen(dateStr) + } else { + dateStr = pterm.LightRed(dateStr) + } + + return dateStr +} diff --git a/cmd/headscale/cli/utils.go b/cmd/headscale/cli/utils.go index 258acf33..85dcae71 100644 --- a/cmd/headscale/cli/utils.go +++ b/cmd/headscale/cli/utils.go @@ -2,6 +2,7 @@ package cli import ( "context" + "crypto/tls" "encoding/json" "errors" "fmt" @@ -19,6 +20,8 @@ import ( "github.com/rs/zerolog/log" "github.com/spf13/viper" "google.golang.org/grpc" + "google.golang.org/grpc/credentials" + "google.golang.org/grpc/credentials/insecure" "gopkg.in/yaml.v2" "inet.af/netaddr" "tailscale.com/tailcfg" @@ -26,7 +29,8 @@ import ( ) const ( - PermissionFallback = 0o700 + PermissionFallback = 0o700 + HeadscaleDateTimeFormat = "2006-01-02 15:04:05" ) func LoadConfig(path string) error { @@ -54,8 +58,11 @@ func LoadConfig(path string) error { viper.SetDefault("unix_socket", "/var/run/headscale.sock") viper.SetDefault("unix_socket_permission", "0o770") - viper.SetDefault("cli.insecure", false) + viper.SetDefault("grpc_listen_addr", ":50443") + viper.SetDefault("grpc_allow_insecure", false) + viper.SetDefault("cli.timeout", "5s") + viper.SetDefault("cli.insecure", false) if err := viper.ReadInConfig(); err != nil { return fmt.Errorf("fatal error reading config file: %w", err) @@ -270,12 +277,16 @@ func getHeadscaleConfig() headscale.Config { if len(prefixes) < 1 { prefixes = append(prefixes, netaddr.MustParseIPPrefix("100.64.0.0/10")) - log.Warn().Msgf("'ip_prefixes' not configured, falling back to default: %v", prefixes) + log.Warn(). + Msgf("'ip_prefixes' not configured, falling back to default: %v", prefixes) } return headscale.Config{ - ServerURL: viper.GetString("server_url"), - Addr: viper.GetString("listen_addr"), + ServerURL: viper.GetString("server_url"), + Addr: viper.GetString("listen_addr"), + GRPCAddr: viper.GetString("grpc_listen_addr"), + GRPCAllowInsecure: viper.GetBool("grpc_allow_insecure"), + IPPrefixes: prefixes, PrivateKeyPath: absPath(viper.GetString("private_key_path")), BaseDomain: baseDomain, @@ -321,8 +332,8 @@ func getHeadscaleConfig() headscale.Config { CLI: headscale.CLIConfig{ Address: viper.GetString("cli.address"), APIKey: viper.GetString("cli.api_key"), - Insecure: viper.GetBool("cli.insecure"), Timeout: viper.GetDuration("cli.timeout"), + Insecure: viper.GetBool("cli.insecure"), }, } } @@ -393,14 +404,14 @@ func getHeadscaleCLIClient() (context.Context, v1.HeadscaleServiceClient, *grpc. grpcOptions = append( grpcOptions, - grpc.WithInsecure(), + grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithContextDialer(headscale.GrpcSocketDialer), ) } else { // If we are not connecting to a local server, require an API key for authentication apiKey := cfg.CLI.APIKey if apiKey == "" { - log.Fatal().Msgf("HEADSCALE_CLI_API_KEY environment variable needs to be set.") + log.Fatal().Caller().Msgf("HEADSCALE_CLI_API_KEY environment variable needs to be set.") } grpcOptions = append(grpcOptions, grpc.WithPerRPCCredentials(tokenAuth{ @@ -409,14 +420,27 @@ func getHeadscaleCLIClient() (context.Context, v1.HeadscaleServiceClient, *grpc. ) if cfg.CLI.Insecure { - grpcOptions = append(grpcOptions, grpc.WithInsecure()) + tlsConfig := &tls.Config{ + // turn of gosec as we are intentionally setting + // insecure. + //nolint:gosec + InsecureSkipVerify: true, + } + + grpcOptions = append(grpcOptions, + grpc.WithTransportCredentials(credentials.NewTLS(tlsConfig)), + ) + } else { + grpcOptions = append(grpcOptions, + grpc.WithTransportCredentials(credentials.NewClientTLSFromCert(nil, "")), + ) } } 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) + log.Fatal().Caller().Err(err).Msgf("Could not connect: %v", err) } client := v1.NewHeadscaleServiceClient(conn) @@ -425,21 +449,21 @@ func getHeadscaleCLIClient() (context.Context, v1.HeadscaleServiceClient, *grpc. } func SuccessOutput(result interface{}, override string, outputFormat string) { - var j []byte + var jsonBytes []byte var err error switch outputFormat { case "json": - j, err = json.MarshalIndent(result, "", "\t") + jsonBytes, err = json.MarshalIndent(result, "", "\t") if err != nil { log.Fatal().Err(err) } case "json-line": - j, err = json.Marshal(result) + jsonBytes, err = json.Marshal(result) if err != nil { log.Fatal().Err(err) } case "yaml": - j, err = yaml.Marshal(result) + jsonBytes, err = yaml.Marshal(result) if err != nil { log.Fatal().Err(err) } @@ -451,7 +475,7 @@ func SuccessOutput(result interface{}, override string, outputFormat string) { } //nolint - fmt.Println(string(j)) + fmt.Println(string(jsonBytes)) } func ErrorOutput(errResult error, override string, outputFormat string) { diff --git a/cmd/headscale/headscale.go b/cmd/headscale/headscale.go index d6bf2166..600b186e 100644 --- a/cmd/headscale/headscale.go +++ b/cmd/headscale/headscale.go @@ -44,7 +44,7 @@ func main() { }) if err := cli.LoadConfig(""); err != nil { - log.Fatal().Err(err) + log.Fatal().Caller().Err(err) } machineOutput := cli.HasMachineOutputFlag() diff --git a/cmd/headscale/headscale_test.go b/cmd/headscale/headscale_test.go index 218e458e..5ab46e00 100644 --- a/cmd/headscale/headscale_test.go +++ b/cmd/headscale/headscale_test.go @@ -61,7 +61,11 @@ func (*Suite) TestConfigLoading(c *check.C) { c.Assert(viper.GetString("tls_letsencrypt_listen"), check.Equals, ":http") c.Assert(viper.GetString("tls_letsencrypt_challenge_type"), check.Equals, "HTTP-01") c.Assert(viper.GetStringSlice("dns_config.nameservers")[0], check.Equals, "1.1.1.1") - c.Assert(cli.GetFileMode("unix_socket_permission"), check.Equals, fs.FileMode(0o770)) + c.Assert( + cli.GetFileMode("unix_socket_permission"), + check.Equals, + fs.FileMode(0o770), + ) } func (*Suite) TestDNSConfigLoading(c *check.C) { diff --git a/config-example.yaml b/config-example.yaml index 940fe57f..ba0c653f 100644 --- a/config-example.yaml +++ b/config-example.yaml @@ -16,6 +16,19 @@ server_url: http://127.0.0.1:8080 # listen_addr: 0.0.0.0:8080 +# Address to listen for gRPC. +# gRPC is used for controlling a headscale server +# remotely with the CLI +# Note: Remote access _only_ works if you have +# valid certificates. +grpc_listen_addr: 0.0.0.0:50443 + +# Allow the gRPC admin interface to run in INSECURE +# mode. This is not recommended as the traffic will +# be unencrypted. Only enable if you know what you +# are doing. +grpc_allow_insecure: false + # Private key used encrypt the traffic between headscale # and Tailscale clients. # The private key file which will be diff --git a/db.go b/db.go index 7b777863..1b53dc88 100644 --- a/db.go +++ b/db.go @@ -58,6 +58,11 @@ func (h *Headscale) initDB() error { return err } + err = db.AutoMigrate(&APIKey{}) + if err != nil { + return err + } + err = h.setValue("db_version", dbVersion) return err diff --git a/dns.go b/dns.go index df896090..8ecd993c 100644 --- a/dns.go +++ b/dns.go @@ -51,7 +51,12 @@ func generateMagicDNSRootDomains(ipPrefixes []netaddr.IPPrefix) []dnsname.FQDN { generateDNSRoot = generateIPv6DNSRootDomain default: - panic(fmt.Sprintf("unsupported IP version with address length %d", ipPrefix.IP().BitLen())) + panic( + fmt.Sprintf( + "unsupported IP version with address length %d", + ipPrefix.IP().BitLen(), + ), + ) } fqdns = append(fqdns, generateDNSRoot(ipPrefix)...) @@ -115,7 +120,9 @@ func generateIPv6DNSRootDomain(ipPrefix netaddr.IPPrefix) []dnsname.FQDN { // function is called only once over the lifetime of a server process. prefixConstantParts := []string{} for i := 0; i < maskBits/nibbleLen; i++ { - prefixConstantParts = append([]string{string(nibbleStr[i])}, prefixConstantParts...) + prefixConstantParts = append( + []string{string(nibbleStr[i])}, + prefixConstantParts...) } makeDomain := func(variablePrefix ...string) (dnsname.FQDN, error) { diff --git a/dns_test.go b/dns_test.go index f8f7fb96..80ee83bb 100644 --- a/dns_test.go +++ b/dns_test.go @@ -81,7 +81,11 @@ func (s *Suite) TestMagicDNSRootDomainsIPv6Single(c *check.C) { domains := generateMagicDNSRootDomains(prefixes) c.Assert(len(domains), check.Equals, 1) - c.Assert(domains[0].WithTrailingDot(), check.Equals, "0.e.1.a.c.5.1.1.a.7.d.f.ip6.arpa.") + c.Assert( + domains[0].WithTrailingDot(), + check.Equals, + "0.e.1.a.c.5.1.1.a.7.d.f.ip6.arpa.", + ) } func (s *Suite) TestMagicDNSRootDomainsIPv6SingleMultiple(c *check.C) { diff --git a/docs/README.md b/docs/README.md index f8824a97..1ac15221 100644 --- a/docs/README.md +++ b/docs/README.md @@ -10,6 +10,7 @@ please ask on [Discord](https://discord.gg/XcQxk2VHjx) instead of opening an Iss ### How-to - [Running headscale on Linux](running-headscale-linux.md) +- [Control headscale remotly](remote-cli.md) - [Using a Windows client with headscale](windows-client.md) ### References diff --git a/docs/remote-cli.md b/docs/remote-cli.md new file mode 100644 index 00000000..3b1dc845 --- /dev/null +++ b/docs/remote-cli.md @@ -0,0 +1,100 @@ +# Controlling `headscale` with remote CLI + +## Prerequisit + +- A workstation to run `headscale` (could be Linux, macOS, other supported platforms) +- A `headscale` server (version `0.13.0` or newer) +- Access to create API keys (local access to the `headscale` server) +- `headscale` _must_ be served over TLS/HTTPS + - Remote access does _not_ support unencrypted traffic. +- Port `50443` must be open in the firewall (or port overriden by `grpc_listen_addr` option) + +## Goal + +This documentation has the goal of showing a user how-to set control a `headscale` instance +from a remote machine with the `headscale` command line binary. + +## Create an API key + +We need to create an API key to authenticate our remote `headscale` when using it from our workstation. + +To create a API key, log into your `headscale` server and generate a key: + +```shell +headscale apikeys create --expiration 90d +``` + +Copy the output of the command and save it for later. Please not that you can not retrieve a key again, +if the key is lost, expire the old one, and create a new key. + +To list the keys currently assosicated with the server: + +```shell +headscale apikeys list +``` + +and to expire a key: + +```shell +headscale apikeys expire --prefix "" +``` + +## Download and configure `headscale` + +1. Download the latest [`headscale` binary from GitHub's release page](https://github.com/juanfont/headscale/releases): + +2. Put the binary somewhere in your `PATH`, e.g. `/usr/local/bin/headcale` + +3. Make `headscale` executable: + +```shell +chmod +x /usr/local/bin/headscale +``` + +4. Configure the CLI through Environment Variables + +```shell +export HEADSCALE_CLI_ADDRESS=":" +export HEADSCALE_CLI_API_KEY="" +``` + +for example: + +```shell +export HEADSCALE_CLI_ADDRESS="headscale.example.com:50443" +export HEADSCALE_CLI_API_KEY="abcde12345" +``` + +This will tell the `headscale` binary to connect to a remote instance, instead of looking +for a local instance (which is what it does on the server). + +The API key is needed to make sure that your are allowed to access the server. The key is _not_ +needed when running directly on the server, as the connection is local. + +5. Test the connection + +Let us run the headscale command to verify that we can connect by listing our nodes: + +```shell +headscale nodes list +``` + +You should now be able to see a list of your nodes from your workstation, and you can +now control the `headscale` server from your workstation. + +## Behind a proxy + +It is possible to run the gRPC remote endpoint behind a reverse proxy, like Nginx, and have it run on the _same_ port as `headscale`. + +While this is _not a supported_ feature, an example on how this can be set up on +[NixOS is shown here](https://github.com/kradalby/dotfiles/blob/4489cdbb19cddfbfae82cd70448a38fde5a76711/machines/headscale.oracldn/headscale.nix#L61-L91). + +## Troubleshooting + +Checklist: + +- Make sure you have the _same_ `headscale` version on your server and workstation +- Make sure you use version `0.13.0` or newer. +- Verify that your TLS certificate is valid and trusted + - If you do not have access to a trusted certificate (e.g. from Let's Encrypt), add your self signed certificate to the trust store of your OS or + - Set `HEADSCALE_CLI_INSECURE` to 0 in your environement diff --git a/gen/go/headscale/v1/apikey.pb.go b/gen/go/headscale/v1/apikey.pb.go new file mode 100644 index 00000000..ace8b18c --- /dev/null +++ b/gen/go/headscale/v1/apikey.pb.go @@ -0,0 +1,559 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.27.1 +// protoc (unknown) +// source: headscale/v1/apikey.proto + +package v1 + +import ( + reflect "reflect" + sync "sync" + + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + timestamppb "google.golang.org/protobuf/types/known/timestamppb" +) + +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 ApiKey struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Id uint64 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"` + Prefix string `protobuf:"bytes,2,opt,name=prefix,proto3" json:"prefix,omitempty"` + Expiration *timestamppb.Timestamp `protobuf:"bytes,3,opt,name=expiration,proto3" json:"expiration,omitempty"` + CreatedAt *timestamppb.Timestamp `protobuf:"bytes,4,opt,name=created_at,json=createdAt,proto3" json:"created_at,omitempty"` + LastSeen *timestamppb.Timestamp `protobuf:"bytes,5,opt,name=last_seen,json=lastSeen,proto3" json:"last_seen,omitempty"` +} + +func (x *ApiKey) Reset() { + *x = ApiKey{} + if protoimpl.UnsafeEnabled { + mi := &file_headscale_v1_apikey_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ApiKey) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ApiKey) ProtoMessage() {} + +func (x *ApiKey) ProtoReflect() protoreflect.Message { + mi := &file_headscale_v1_apikey_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 ApiKey.ProtoReflect.Descriptor instead. +func (*ApiKey) Descriptor() ([]byte, []int) { + return file_headscale_v1_apikey_proto_rawDescGZIP(), []int{0} +} + +func (x *ApiKey) GetId() uint64 { + if x != nil { + return x.Id + } + return 0 +} + +func (x *ApiKey) GetPrefix() string { + if x != nil { + return x.Prefix + } + return "" +} + +func (x *ApiKey) GetExpiration() *timestamppb.Timestamp { + if x != nil { + return x.Expiration + } + return nil +} + +func (x *ApiKey) GetCreatedAt() *timestamppb.Timestamp { + if x != nil { + return x.CreatedAt + } + return nil +} + +func (x *ApiKey) GetLastSeen() *timestamppb.Timestamp { + if x != nil { + return x.LastSeen + } + return nil +} + +type CreateApiKeyRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Expiration *timestamppb.Timestamp `protobuf:"bytes,1,opt,name=expiration,proto3" json:"expiration,omitempty"` +} + +func (x *CreateApiKeyRequest) Reset() { + *x = CreateApiKeyRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_headscale_v1_apikey_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *CreateApiKeyRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CreateApiKeyRequest) ProtoMessage() {} + +func (x *CreateApiKeyRequest) ProtoReflect() protoreflect.Message { + mi := &file_headscale_v1_apikey_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 CreateApiKeyRequest.ProtoReflect.Descriptor instead. +func (*CreateApiKeyRequest) Descriptor() ([]byte, []int) { + return file_headscale_v1_apikey_proto_rawDescGZIP(), []int{1} +} + +func (x *CreateApiKeyRequest) GetExpiration() *timestamppb.Timestamp { + if x != nil { + return x.Expiration + } + return nil +} + +type CreateApiKeyResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + ApiKey string `protobuf:"bytes,1,opt,name=api_key,json=apiKey,proto3" json:"api_key,omitempty"` +} + +func (x *CreateApiKeyResponse) Reset() { + *x = CreateApiKeyResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_headscale_v1_apikey_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *CreateApiKeyResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CreateApiKeyResponse) ProtoMessage() {} + +func (x *CreateApiKeyResponse) ProtoReflect() protoreflect.Message { + mi := &file_headscale_v1_apikey_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 CreateApiKeyResponse.ProtoReflect.Descriptor instead. +func (*CreateApiKeyResponse) Descriptor() ([]byte, []int) { + return file_headscale_v1_apikey_proto_rawDescGZIP(), []int{2} +} + +func (x *CreateApiKeyResponse) GetApiKey() string { + if x != nil { + return x.ApiKey + } + return "" +} + +type ExpireApiKeyRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Prefix string `protobuf:"bytes,1,opt,name=prefix,proto3" json:"prefix,omitempty"` +} + +func (x *ExpireApiKeyRequest) Reset() { + *x = ExpireApiKeyRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_headscale_v1_apikey_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ExpireApiKeyRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ExpireApiKeyRequest) ProtoMessage() {} + +func (x *ExpireApiKeyRequest) ProtoReflect() protoreflect.Message { + mi := &file_headscale_v1_apikey_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 ExpireApiKeyRequest.ProtoReflect.Descriptor instead. +func (*ExpireApiKeyRequest) Descriptor() ([]byte, []int) { + return file_headscale_v1_apikey_proto_rawDescGZIP(), []int{3} +} + +func (x *ExpireApiKeyRequest) GetPrefix() string { + if x != nil { + return x.Prefix + } + return "" +} + +type ExpireApiKeyResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *ExpireApiKeyResponse) Reset() { + *x = ExpireApiKeyResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_headscale_v1_apikey_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ExpireApiKeyResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ExpireApiKeyResponse) ProtoMessage() {} + +func (x *ExpireApiKeyResponse) ProtoReflect() protoreflect.Message { + mi := &file_headscale_v1_apikey_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 ExpireApiKeyResponse.ProtoReflect.Descriptor instead. +func (*ExpireApiKeyResponse) Descriptor() ([]byte, []int) { + return file_headscale_v1_apikey_proto_rawDescGZIP(), []int{4} +} + +type ListApiKeysRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *ListApiKeysRequest) Reset() { + *x = ListApiKeysRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_headscale_v1_apikey_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ListApiKeysRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListApiKeysRequest) ProtoMessage() {} + +func (x *ListApiKeysRequest) ProtoReflect() protoreflect.Message { + mi := &file_headscale_v1_apikey_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 ListApiKeysRequest.ProtoReflect.Descriptor instead. +func (*ListApiKeysRequest) Descriptor() ([]byte, []int) { + return file_headscale_v1_apikey_proto_rawDescGZIP(), []int{5} +} + +type ListApiKeysResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + ApiKeys []*ApiKey `protobuf:"bytes,1,rep,name=api_keys,json=apiKeys,proto3" json:"api_keys,omitempty"` +} + +func (x *ListApiKeysResponse) Reset() { + *x = ListApiKeysResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_headscale_v1_apikey_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ListApiKeysResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListApiKeysResponse) ProtoMessage() {} + +func (x *ListApiKeysResponse) ProtoReflect() protoreflect.Message { + mi := &file_headscale_v1_apikey_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 ListApiKeysResponse.ProtoReflect.Descriptor instead. +func (*ListApiKeysResponse) Descriptor() ([]byte, []int) { + return file_headscale_v1_apikey_proto_rawDescGZIP(), []int{6} +} + +func (x *ListApiKeysResponse) GetApiKeys() []*ApiKey { + if x != nil { + return x.ApiKeys + } + return nil +} + +var File_headscale_v1_apikey_proto protoreflect.FileDescriptor + +var file_headscale_v1_apikey_proto_rawDesc = []byte{ + 0x0a, 0x19, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2f, 0x76, 0x31, 0x2f, 0x61, + 0x70, 0x69, 0x6b, 0x65, 0x79, 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, 0x22, 0xe0, 0x01, 0x0a, 0x06, 0x41, + 0x70, 0x69, 0x4b, 0x65, 0x79, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x04, 0x52, 0x02, 0x69, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x70, 0x72, 0x65, 0x66, 0x69, 0x78, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x70, 0x72, 0x65, 0x66, 0x69, 0x78, 0x12, 0x3a, 0x0a, + 0x0a, 0x65, 0x78, 0x70, 0x69, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0a, 0x65, + 0x78, 0x70, 0x69, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x39, 0x0a, 0x0a, 0x63, 0x72, 0x65, + 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, + 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, + 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x63, 0x72, 0x65, 0x61, 0x74, + 0x65, 0x64, 0x41, 0x74, 0x12, 0x37, 0x0a, 0x09, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x73, 0x65, 0x65, + 0x6e, 0x18, 0x05, 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, 0x22, 0x51, 0x0a, + 0x13, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x41, 0x70, 0x69, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x12, 0x3a, 0x0a, 0x0a, 0x65, 0x78, 0x70, 0x69, 0x72, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, + 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, + 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0a, 0x65, 0x78, 0x70, 0x69, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x22, 0x2f, 0x0a, 0x14, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x41, 0x70, 0x69, 0x4b, 0x65, 0x79, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x17, 0x0a, 0x07, 0x61, 0x70, 0x69, 0x5f, + 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x61, 0x70, 0x69, 0x4b, 0x65, + 0x79, 0x22, 0x2d, 0x0a, 0x13, 0x45, 0x78, 0x70, 0x69, 0x72, 0x65, 0x41, 0x70, 0x69, 0x4b, 0x65, + 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x70, 0x72, 0x65, 0x66, + 0x69, 0x78, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x70, 0x72, 0x65, 0x66, 0x69, 0x78, + 0x22, 0x16, 0x0a, 0x14, 0x45, 0x78, 0x70, 0x69, 0x72, 0x65, 0x41, 0x70, 0x69, 0x4b, 0x65, 0x79, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x14, 0x0a, 0x12, 0x4c, 0x69, 0x73, 0x74, + 0x41, 0x70, 0x69, 0x4b, 0x65, 0x79, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x46, + 0x0a, 0x13, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x70, 0x69, 0x4b, 0x65, 0x79, 0x73, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2f, 0x0a, 0x08, 0x61, 0x70, 0x69, 0x5f, 0x6b, 0x65, 0x79, + 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, + 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x70, 0x69, 0x4b, 0x65, 0x79, 0x52, 0x07, 0x61, + 0x70, 0x69, 0x4b, 0x65, 0x79, 0x73, 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_apikey_proto_rawDescOnce sync.Once + file_headscale_v1_apikey_proto_rawDescData = file_headscale_v1_apikey_proto_rawDesc +) + +func file_headscale_v1_apikey_proto_rawDescGZIP() []byte { + file_headscale_v1_apikey_proto_rawDescOnce.Do(func() { + file_headscale_v1_apikey_proto_rawDescData = protoimpl.X.CompressGZIP(file_headscale_v1_apikey_proto_rawDescData) + }) + return file_headscale_v1_apikey_proto_rawDescData +} + +var file_headscale_v1_apikey_proto_msgTypes = make([]protoimpl.MessageInfo, 7) +var file_headscale_v1_apikey_proto_goTypes = []interface{}{ + (*ApiKey)(nil), // 0: headscale.v1.ApiKey + (*CreateApiKeyRequest)(nil), // 1: headscale.v1.CreateApiKeyRequest + (*CreateApiKeyResponse)(nil), // 2: headscale.v1.CreateApiKeyResponse + (*ExpireApiKeyRequest)(nil), // 3: headscale.v1.ExpireApiKeyRequest + (*ExpireApiKeyResponse)(nil), // 4: headscale.v1.ExpireApiKeyResponse + (*ListApiKeysRequest)(nil), // 5: headscale.v1.ListApiKeysRequest + (*ListApiKeysResponse)(nil), // 6: headscale.v1.ListApiKeysResponse + (*timestamppb.Timestamp)(nil), // 7: google.protobuf.Timestamp +} +var file_headscale_v1_apikey_proto_depIdxs = []int32{ + 7, // 0: headscale.v1.ApiKey.expiration:type_name -> google.protobuf.Timestamp + 7, // 1: headscale.v1.ApiKey.created_at:type_name -> google.protobuf.Timestamp + 7, // 2: headscale.v1.ApiKey.last_seen:type_name -> google.protobuf.Timestamp + 7, // 3: headscale.v1.CreateApiKeyRequest.expiration:type_name -> google.protobuf.Timestamp + 0, // 4: headscale.v1.ListApiKeysResponse.api_keys:type_name -> headscale.v1.ApiKey + 5, // [5:5] is the sub-list for method output_type + 5, // [5:5] is the sub-list for method input_type + 5, // [5:5] is the sub-list for extension type_name + 5, // [5:5] is the sub-list for extension extendee + 0, // [0:5] is the sub-list for field type_name +} + +func init() { file_headscale_v1_apikey_proto_init() } +func file_headscale_v1_apikey_proto_init() { + if File_headscale_v1_apikey_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_headscale_v1_apikey_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ApiKey); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_headscale_v1_apikey_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*CreateApiKeyRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_headscale_v1_apikey_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*CreateApiKeyResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_headscale_v1_apikey_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ExpireApiKeyRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_headscale_v1_apikey_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ExpireApiKeyResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_headscale_v1_apikey_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ListApiKeysRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_headscale_v1_apikey_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ListApiKeysResponse); 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_apikey_proto_rawDesc, + NumEnums: 0, + NumMessages: 7, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_headscale_v1_apikey_proto_goTypes, + DependencyIndexes: file_headscale_v1_apikey_proto_depIdxs, + MessageInfos: file_headscale_v1_apikey_proto_msgTypes, + }.Build() + File_headscale_v1_apikey_proto = out.File + file_headscale_v1_apikey_proto_rawDesc = nil + file_headscale_v1_apikey_proto_goTypes = nil + file_headscale_v1_apikey_proto_depIdxs = nil +} diff --git a/gen/go/headscale/v1/device.pb.go b/gen/go/headscale/v1/device.pb.go index a3b5911a..58792512 100644 --- a/gen/go/headscale/v1/device.pb.go +++ b/gen/go/headscale/v1/device.pb.go @@ -1,17 +1,18 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.27.1 -// protoc v3.17.3 +// protoc (unknown) // source: headscale/v1/device.proto package v1 import ( + reflect "reflect" + sync "sync" + 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 ( diff --git a/gen/go/headscale/v1/headscale.pb.go b/gen/go/headscale/v1/headscale.pb.go index ad8a50d7..7799082d 100644 --- a/gen/go/headscale/v1/headscale.pb.go +++ b/gen/go/headscale/v1/headscale.pb.go @@ -1,16 +1,17 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.27.1 -// protoc v3.17.3 +// protoc (unknown) // source: headscale/v1/headscale.proto package v1 import ( + reflect "reflect" + _ "google.golang.org/genproto/googleapis/api/annotations" protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" - reflect "reflect" ) const ( @@ -34,162 +35,185 @@ var file_headscale_v1_headscale_proto_rawDesc = []byte{ 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1a, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2f, 0x76, 0x31, 0x2f, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x19, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2f, 0x76, - 0x31, 0x2f, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x32, 0xf4, - 0x12, 0x0a, 0x10, 0x48, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x53, 0x65, 0x72, 0x76, - 0x69, 0x63, 0x65, 0x12, 0x77, 0x0a, 0x0c, 0x47, 0x65, 0x74, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, - 0x61, 0x63, 0x65, 0x12, 0x21, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, - 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x22, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, - 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, - 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x20, 0x82, 0xd3, 0xe4, 0x93, - 0x02, 0x1a, 0x12, 0x18, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x6e, 0x61, 0x6d, 0x65, - 0x73, 0x70, 0x61, 0x63, 0x65, 0x2f, 0x7b, 0x6e, 0x61, 0x6d, 0x65, 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, 0x96, 0x01, 0x0a, 0x0f, 0x52, - 0x65, 0x6e, 0x61, 0x6d, 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, 0x52, 0x65, - 0x6e, 0x61, 0x6d, 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, 0x52, 0x65, 0x6e, 0x61, 0x6d, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, - 0x61, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x36, 0x82, 0xd3, 0xe4, - 0x93, 0x02, 0x30, 0x22, 0x2e, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x6e, 0x61, 0x6d, - 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x2f, 0x7b, 0x6f, 0x6c, 0x64, 0x5f, 0x6e, 0x61, 0x6d, 0x65, - 0x7d, 0x2f, 0x72, 0x65, 0x6e, 0x61, 0x6d, 0x65, 0x2f, 0x7b, 0x6e, 0x65, 0x77, 0x5f, 0x6e, 0x61, - 0x6d, 0x65, 0x7d, 0x12, 0x80, 0x01, 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, 0x20, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1a, 0x2a, 0x18, 0x2f, 0x61, - 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x2f, - 0x7b, 0x6e, 0x61, 0x6d, 0x65, 0x7d, 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, 0x12, 0x80, - 0x01, 0x0a, 0x10, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x50, 0x72, 0x65, 0x41, 0x75, 0x74, 0x68, - 0x4b, 0x65, 0x79, 0x12, 0x25, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, - 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x50, 0x72, 0x65, 0x41, 0x75, 0x74, 0x68, - 0x4b, 0x65, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x26, 0x2e, 0x68, 0x65, 0x61, + 0x31, 0x2f, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x19, + 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2f, 0x76, 0x31, 0x2f, 0x61, 0x70, 0x69, + 0x6b, 0x65, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x32, 0xcb, 0x15, 0x0a, 0x10, 0x48, 0x65, + 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x77, + 0x0a, 0x0c, 0x47, 0x65, 0x74, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, 0x21, + 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, + 0x74, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x22, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, + 0x2e, 0x47, 0x65, 0x74, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x20, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1a, 0x12, 0x18, 0x2f, + 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, + 0x2f, 0x7b, 0x6e, 0x61, 0x6d, 0x65, 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, - 0x50, 0x72, 0x65, 0x41, 0x75, 0x74, 0x68, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x22, 0x1d, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x17, 0x22, 0x12, 0x2f, 0x61, 0x70, 0x69, - 0x2f, 0x76, 0x31, 0x2f, 0x70, 0x72, 0x65, 0x61, 0x75, 0x74, 0x68, 0x6b, 0x65, 0x79, 0x3a, 0x01, - 0x2a, 0x12, 0x87, 0x01, 0x0a, 0x10, 0x45, 0x78, 0x70, 0x69, 0x72, 0x65, 0x50, 0x72, 0x65, 0x41, - 0x75, 0x74, 0x68, 0x4b, 0x65, 0x79, 0x12, 0x25, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, - 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x45, 0x78, 0x70, 0x69, 0x72, 0x65, 0x50, 0x72, 0x65, 0x41, - 0x75, 0x74, 0x68, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x26, 0x2e, - 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x45, 0x78, 0x70, - 0x69, 0x72, 0x65, 0x50, 0x72, 0x65, 0x41, 0x75, 0x74, 0x68, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x24, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1e, 0x22, 0x19, 0x2f, - 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x70, 0x72, 0x65, 0x61, 0x75, 0x74, 0x68, 0x6b, 0x65, - 0x79, 0x2f, 0x65, 0x78, 0x70, 0x69, 0x72, 0x65, 0x3a, 0x01, 0x2a, 0x12, 0x7a, 0x0a, 0x0f, 0x4c, - 0x69, 0x73, 0x74, 0x50, 0x72, 0x65, 0x41, 0x75, 0x74, 0x68, 0x4b, 0x65, 0x79, 0x73, 0x12, 0x24, - 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, - 0x73, 0x74, 0x50, 0x72, 0x65, 0x41, 0x75, 0x74, 0x68, 0x4b, 0x65, 0x79, 0x73, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x1a, 0x25, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, - 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x72, 0x65, 0x41, 0x75, 0x74, 0x68, 0x4b, - 0x65, 0x79, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1a, 0x82, 0xd3, 0xe4, - 0x93, 0x02, 0x14, 0x12, 0x12, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x70, 0x72, 0x65, - 0x61, 0x75, 0x74, 0x68, 0x6b, 0x65, 0x79, 0x12, 0x89, 0x01, 0x0a, 0x12, 0x44, 0x65, 0x62, 0x75, - 0x67, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x12, 0x27, - 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, - 0x62, 0x75, 0x67, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x28, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, - 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x62, 0x75, 0x67, 0x43, 0x72, 0x65, 0x61, - 0x74, 0x65, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x22, 0x20, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1a, 0x22, 0x15, 0x2f, 0x61, 0x70, 0x69, 0x2f, - 0x76, 0x31, 0x2f, 0x64, 0x65, 0x62, 0x75, 0x67, 0x2f, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, - 0x3a, 0x01, 0x2a, 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, 0x80, 0x01, 0x0a, 0x0f, 0x52, - 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x12, 0x24, - 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, - 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x1a, 0x25, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, - 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x4d, 0x61, 0x63, 0x68, - 0x69, 0x6e, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x20, 0x82, 0xd3, 0xe4, - 0x93, 0x02, 0x1a, 0x22, 0x18, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x6d, 0x61, 0x63, - 0x68, 0x69, 0x6e, 0x65, 0x2f, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x12, 0x7e, 0x0a, - 0x0d, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x12, 0x22, - 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, - 0x6c, 0x65, 0x74, 0x65, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x1a, 0x23, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, - 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x24, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1e, 0x2a, - 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, 0x85, 0x01, - 0x0a, 0x0d, 0x45, 0x78, 0x70, 0x69, 0x72, 0x65, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x12, - 0x22, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x45, - 0x78, 0x70, 0x69, 0x72, 0x65, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, - 0x76, 0x31, 0x2e, 0x45, 0x78, 0x70, 0x69, 0x72, 0x65, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x2b, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x25, - 0x22, 0x23, 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, 0x2f, 0x65, - 0x78, 0x70, 0x69, 0x72, 0x65, 0x12, 0x6e, 0x0a, 0x0c, 0x4c, 0x69, 0x73, 0x74, 0x4d, 0x61, 0x63, - 0x68, 0x69, 0x6e, 0x65, 0x73, 0x12, 0x21, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, - 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, - 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x22, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, - 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x4d, 0x61, 0x63, 0x68, - 0x69, 0x6e, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x17, 0x82, 0xd3, - 0xe4, 0x93, 0x02, 0x11, 0x12, 0x0f, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x6d, 0x61, - 0x63, 0x68, 0x69, 0x6e, 0x65, 0x12, 0x8d, 0x01, 0x0a, 0x0c, 0x53, 0x68, 0x61, 0x72, 0x65, 0x4d, - 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x12, 0x21, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, - 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x68, 0x61, 0x72, 0x65, 0x4d, 0x61, 0x63, 0x68, 0x69, - 0x6e, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x22, 0x2e, 0x68, 0x65, 0x61, 0x64, - 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x68, 0x61, 0x72, 0x65, 0x4d, 0x61, - 0x63, 0x68, 0x69, 0x6e, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x36, 0x82, - 0xd3, 0xe4, 0x93, 0x02, 0x30, 0x22, 0x2e, 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, 0x2f, 0x73, 0x68, 0x61, 0x72, 0x65, 0x2f, 0x7b, 0x6e, 0x61, 0x6d, 0x65, 0x73, - 0x70, 0x61, 0x63, 0x65, 0x7d, 0x12, 0x95, 0x01, 0x0a, 0x0e, 0x55, 0x6e, 0x73, 0x68, 0x61, 0x72, - 0x65, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x12, 0x23, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, - 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x6e, 0x73, 0x68, 0x61, 0x72, 0x65, 0x4d, - 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x24, 0x2e, - 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x6e, 0x73, - 0x68, 0x61, 0x72, 0x65, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x22, 0x38, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x32, 0x22, 0x30, 0x2f, 0x61, 0x70, + 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, 0x96, 0x01, 0x0a, 0x0f, 0x52, 0x65, 0x6e, 0x61, 0x6d, 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, 0x52, 0x65, 0x6e, 0x61, 0x6d, 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, 0x52, + 0x65, 0x6e, 0x61, 0x6d, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x36, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x30, 0x22, 0x2e, + 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, + 0x65, 0x2f, 0x7b, 0x6f, 0x6c, 0x64, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x7d, 0x2f, 0x72, 0x65, 0x6e, + 0x61, 0x6d, 0x65, 0x2f, 0x7b, 0x6e, 0x65, 0x77, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x7d, 0x12, 0x80, + 0x01, 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, + 0x20, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1a, 0x2a, 0x18, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, + 0x2f, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x2f, 0x7b, 0x6e, 0x61, 0x6d, 0x65, + 0x7d, 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, 0x12, 0x80, 0x01, 0x0a, 0x10, 0x43, 0x72, + 0x65, 0x61, 0x74, 0x65, 0x50, 0x72, 0x65, 0x41, 0x75, 0x74, 0x68, 0x4b, 0x65, 0x79, 0x12, 0x25, + 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, + 0x65, 0x61, 0x74, 0x65, 0x50, 0x72, 0x65, 0x41, 0x75, 0x74, 0x68, 0x4b, 0x65, 0x79, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x26, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, + 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x50, 0x72, 0x65, 0x41, 0x75, + 0x74, 0x68, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1d, 0x82, + 0xd3, 0xe4, 0x93, 0x02, 0x17, 0x22, 0x12, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x70, + 0x72, 0x65, 0x61, 0x75, 0x74, 0x68, 0x6b, 0x65, 0x79, 0x3a, 0x01, 0x2a, 0x12, 0x87, 0x01, 0x0a, + 0x10, 0x45, 0x78, 0x70, 0x69, 0x72, 0x65, 0x50, 0x72, 0x65, 0x41, 0x75, 0x74, 0x68, 0x4b, 0x65, + 0x79, 0x12, 0x25, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, + 0x2e, 0x45, 0x78, 0x70, 0x69, 0x72, 0x65, 0x50, 0x72, 0x65, 0x41, 0x75, 0x74, 0x68, 0x4b, 0x65, + 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x26, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, + 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x45, 0x78, 0x70, 0x69, 0x72, 0x65, 0x50, 0x72, + 0x65, 0x41, 0x75, 0x74, 0x68, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x22, 0x24, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1e, 0x22, 0x19, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, + 0x31, 0x2f, 0x70, 0x72, 0x65, 0x61, 0x75, 0x74, 0x68, 0x6b, 0x65, 0x79, 0x2f, 0x65, 0x78, 0x70, + 0x69, 0x72, 0x65, 0x3a, 0x01, 0x2a, 0x12, 0x7a, 0x0a, 0x0f, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x72, + 0x65, 0x41, 0x75, 0x74, 0x68, 0x4b, 0x65, 0x79, 0x73, 0x12, 0x24, 0x2e, 0x68, 0x65, 0x61, 0x64, + 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x72, 0x65, + 0x41, 0x75, 0x74, 0x68, 0x4b, 0x65, 0x79, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x25, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x4c, + 0x69, 0x73, 0x74, 0x50, 0x72, 0x65, 0x41, 0x75, 0x74, 0x68, 0x4b, 0x65, 0x79, 0x73, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1a, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x14, 0x12, 0x12, + 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x70, 0x72, 0x65, 0x61, 0x75, 0x74, 0x68, 0x6b, + 0x65, 0x79, 0x12, 0x89, 0x01, 0x0a, 0x12, 0x44, 0x65, 0x62, 0x75, 0x67, 0x43, 0x72, 0x65, 0x61, + 0x74, 0x65, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x12, 0x27, 0x2e, 0x68, 0x65, 0x61, 0x64, + 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x62, 0x75, 0x67, 0x43, 0x72, + 0x65, 0x61, 0x74, 0x65, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x28, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, + 0x31, 0x2e, 0x44, 0x65, 0x62, 0x75, 0x67, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x4d, 0x61, 0x63, + 0x68, 0x69, 0x6e, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x20, 0x82, 0xd3, + 0xe4, 0x93, 0x02, 0x1a, 0x22, 0x15, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x64, 0x65, + 0x62, 0x75, 0x67, 0x2f, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x3a, 0x01, 0x2a, 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, 0x80, 0x01, 0x0a, 0x0f, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, + 0x65, 0x72, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x12, 0x24, 0x2e, 0x68, 0x65, 0x61, 0x64, + 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, + 0x72, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x25, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x52, + 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x20, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1a, 0x22, 0x18, + 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x2f, + 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x12, 0x7e, 0x0a, 0x0d, 0x44, 0x65, 0x6c, 0x65, + 0x74, 0x65, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x12, 0x22, 0x2e, 0x68, 0x65, 0x61, 0x64, + 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x4d, + 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e, + 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x6c, + 0x65, 0x74, 0x65, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x22, 0x24, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1e, 0x2a, 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, 0x85, 0x01, 0x0a, 0x0d, 0x45, 0x78, 0x70, + 0x69, 0x72, 0x65, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x12, 0x22, 0x2e, 0x68, 0x65, 0x61, + 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x45, 0x78, 0x70, 0x69, 0x72, 0x65, + 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x23, + 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x45, 0x78, + 0x70, 0x69, 0x72, 0x65, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x22, 0x2b, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x25, 0x22, 0x23, 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, 0x2f, 0x75, 0x6e, 0x73, 0x68, 0x61, 0x72, - 0x65, 0x2f, 0x7b, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x7d, 0x12, 0x8b, 0x01, - 0x0a, 0x0f, 0x47, 0x65, 0x74, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x52, 0x6f, 0x75, 0x74, - 0x65, 0x12, 0x24, 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, 0x6f, 0x75, 0x74, 0x65, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x25, 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, 0x6f, 0x75, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x2b, - 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x25, 0x12, 0x23, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, + 0x63, 0x68, 0x69, 0x6e, 0x65, 0x5f, 0x69, 0x64, 0x7d, 0x2f, 0x65, 0x78, 0x70, 0x69, 0x72, 0x65, + 0x12, 0x6e, 0x0a, 0x0c, 0x4c, 0x69, 0x73, 0x74, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x73, + 0x12, 0x21, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, + 0x4c, 0x69, 0x73, 0x74, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x1a, 0x22, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, + 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x73, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x17, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x11, 0x12, + 0x0f, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, + 0x12, 0x8d, 0x01, 0x0a, 0x0c, 0x53, 0x68, 0x61, 0x72, 0x65, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, + 0x65, 0x12, 0x21, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, + 0x2e, 0x53, 0x68, 0x61, 0x72, 0x65, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x1a, 0x22, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, + 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x68, 0x61, 0x72, 0x65, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x36, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x30, + 0x22, 0x2e, 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, 0x2f, 0x73, + 0x68, 0x61, 0x72, 0x65, 0x2f, 0x7b, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x7d, + 0x12, 0x95, 0x01, 0x0a, 0x0e, 0x55, 0x6e, 0x73, 0x68, 0x61, 0x72, 0x65, 0x4d, 0x61, 0x63, 0x68, + 0x69, 0x6e, 0x65, 0x12, 0x23, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, + 0x76, 0x31, 0x2e, 0x55, 0x6e, 0x73, 0x68, 0x61, 0x72, 0x65, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, + 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x24, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, + 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x6e, 0x73, 0x68, 0x61, 0x72, 0x65, 0x4d, + 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x38, + 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x32, 0x22, 0x30, 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, 0x2f, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x12, 0x97, 0x01, 0x0a, 0x13, - 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x52, 0x6f, 0x75, - 0x74, 0x65, 0x73, 0x12, 0x28, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, - 0x76, 0x31, 0x2e, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, - 0x52, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x29, 0x2e, - 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x45, 0x6e, 0x61, - 0x62, 0x6c, 0x65, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x73, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x2b, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x25, - 0x22, 0x23, 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, 0x2f, 0x72, - 0x6f, 0x75, 0x74, 0x65, 0x73, 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, + 0x5f, 0x69, 0x64, 0x7d, 0x2f, 0x75, 0x6e, 0x73, 0x68, 0x61, 0x72, 0x65, 0x2f, 0x7b, 0x6e, 0x61, + 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x7d, 0x12, 0x8b, 0x01, 0x0a, 0x0f, 0x47, 0x65, 0x74, + 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x12, 0x24, 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, 0x6f, 0x75, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x25, 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, 0x6f, 0x75, 0x74, + 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x2b, 0x82, 0xd3, 0xe4, 0x93, 0x02, + 0x25, 0x12, 0x23, 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, 0x2f, + 0x72, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x12, 0x97, 0x01, 0x0a, 0x13, 0x45, 0x6e, 0x61, 0x62, 0x6c, + 0x65, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x12, 0x28, + 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x45, 0x6e, + 0x61, 0x62, 0x6c, 0x65, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x52, 0x6f, 0x75, 0x74, 0x65, + 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x29, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, + 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x4d, 0x61, + 0x63, 0x68, 0x69, 0x6e, 0x65, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x22, 0x2b, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x25, 0x22, 0x23, 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, 0x2f, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x73, + 0x12, 0x70, 0x0a, 0x0c, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x41, 0x70, 0x69, 0x4b, 0x65, 0x79, + 0x12, 0x21, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, + 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x41, 0x70, 0x69, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x1a, 0x22, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, + 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x41, 0x70, 0x69, 0x4b, 0x65, 0x79, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x19, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x13, 0x22, + 0x0e, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x61, 0x70, 0x69, 0x6b, 0x65, 0x79, 0x3a, + 0x01, 0x2a, 0x12, 0x77, 0x0a, 0x0c, 0x45, 0x78, 0x70, 0x69, 0x72, 0x65, 0x41, 0x70, 0x69, 0x4b, + 0x65, 0x79, 0x12, 0x21, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, + 0x31, 0x2e, 0x45, 0x78, 0x70, 0x69, 0x72, 0x65, 0x41, 0x70, 0x69, 0x4b, 0x65, 0x79, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x22, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, + 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x45, 0x78, 0x70, 0x69, 0x72, 0x65, 0x41, 0x70, 0x69, 0x4b, 0x65, + 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x20, 0x82, 0xd3, 0xe4, 0x93, 0x02, + 0x1a, 0x22, 0x15, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x61, 0x70, 0x69, 0x6b, 0x65, + 0x79, 0x2f, 0x65, 0x78, 0x70, 0x69, 0x72, 0x65, 0x3a, 0x01, 0x2a, 0x12, 0x6a, 0x0a, 0x0b, 0x4c, + 0x69, 0x73, 0x74, 0x41, 0x70, 0x69, 0x4b, 0x65, 0x79, 0x73, 0x12, 0x20, 0x2e, 0x68, 0x65, 0x61, + 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x70, + 0x69, 0x4b, 0x65, 0x79, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x21, 0x2e, 0x68, + 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, + 0x41, 0x70, 0x69, 0x4b, 0x65, 0x79, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, + 0x16, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x10, 0x12, 0x0e, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, + 0x2f, 0x61, 0x70, 0x69, 0x6b, 0x65, 0x79, 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_goTypes = []interface{}{ @@ -211,24 +235,30 @@ var file_headscale_v1_headscale_proto_goTypes = []interface{}{ (*UnshareMachineRequest)(nil), // 15: headscale.v1.UnshareMachineRequest (*GetMachineRouteRequest)(nil), // 16: headscale.v1.GetMachineRouteRequest (*EnableMachineRoutesRequest)(nil), // 17: headscale.v1.EnableMachineRoutesRequest - (*GetNamespaceResponse)(nil), // 18: headscale.v1.GetNamespaceResponse - (*CreateNamespaceResponse)(nil), // 19: headscale.v1.CreateNamespaceResponse - (*RenameNamespaceResponse)(nil), // 20: headscale.v1.RenameNamespaceResponse - (*DeleteNamespaceResponse)(nil), // 21: headscale.v1.DeleteNamespaceResponse - (*ListNamespacesResponse)(nil), // 22: headscale.v1.ListNamespacesResponse - (*CreatePreAuthKeyResponse)(nil), // 23: headscale.v1.CreatePreAuthKeyResponse - (*ExpirePreAuthKeyResponse)(nil), // 24: headscale.v1.ExpirePreAuthKeyResponse - (*ListPreAuthKeysResponse)(nil), // 25: headscale.v1.ListPreAuthKeysResponse - (*DebugCreateMachineResponse)(nil), // 26: headscale.v1.DebugCreateMachineResponse - (*GetMachineResponse)(nil), // 27: headscale.v1.GetMachineResponse - (*RegisterMachineResponse)(nil), // 28: headscale.v1.RegisterMachineResponse - (*DeleteMachineResponse)(nil), // 29: headscale.v1.DeleteMachineResponse - (*ExpireMachineResponse)(nil), // 30: headscale.v1.ExpireMachineResponse - (*ListMachinesResponse)(nil), // 31: headscale.v1.ListMachinesResponse - (*ShareMachineResponse)(nil), // 32: headscale.v1.ShareMachineResponse - (*UnshareMachineResponse)(nil), // 33: headscale.v1.UnshareMachineResponse - (*GetMachineRouteResponse)(nil), // 34: headscale.v1.GetMachineRouteResponse - (*EnableMachineRoutesResponse)(nil), // 35: headscale.v1.EnableMachineRoutesResponse + (*CreateApiKeyRequest)(nil), // 18: headscale.v1.CreateApiKeyRequest + (*ExpireApiKeyRequest)(nil), // 19: headscale.v1.ExpireApiKeyRequest + (*ListApiKeysRequest)(nil), // 20: headscale.v1.ListApiKeysRequest + (*GetNamespaceResponse)(nil), // 21: headscale.v1.GetNamespaceResponse + (*CreateNamespaceResponse)(nil), // 22: headscale.v1.CreateNamespaceResponse + (*RenameNamespaceResponse)(nil), // 23: headscale.v1.RenameNamespaceResponse + (*DeleteNamespaceResponse)(nil), // 24: headscale.v1.DeleteNamespaceResponse + (*ListNamespacesResponse)(nil), // 25: headscale.v1.ListNamespacesResponse + (*CreatePreAuthKeyResponse)(nil), // 26: headscale.v1.CreatePreAuthKeyResponse + (*ExpirePreAuthKeyResponse)(nil), // 27: headscale.v1.ExpirePreAuthKeyResponse + (*ListPreAuthKeysResponse)(nil), // 28: headscale.v1.ListPreAuthKeysResponse + (*DebugCreateMachineResponse)(nil), // 29: headscale.v1.DebugCreateMachineResponse + (*GetMachineResponse)(nil), // 30: headscale.v1.GetMachineResponse + (*RegisterMachineResponse)(nil), // 31: headscale.v1.RegisterMachineResponse + (*DeleteMachineResponse)(nil), // 32: headscale.v1.DeleteMachineResponse + (*ExpireMachineResponse)(nil), // 33: headscale.v1.ExpireMachineResponse + (*ListMachinesResponse)(nil), // 34: headscale.v1.ListMachinesResponse + (*ShareMachineResponse)(nil), // 35: headscale.v1.ShareMachineResponse + (*UnshareMachineResponse)(nil), // 36: headscale.v1.UnshareMachineResponse + (*GetMachineRouteResponse)(nil), // 37: headscale.v1.GetMachineRouteResponse + (*EnableMachineRoutesResponse)(nil), // 38: headscale.v1.EnableMachineRoutesResponse + (*CreateApiKeyResponse)(nil), // 39: headscale.v1.CreateApiKeyResponse + (*ExpireApiKeyResponse)(nil), // 40: headscale.v1.ExpireApiKeyResponse + (*ListApiKeysResponse)(nil), // 41: headscale.v1.ListApiKeysResponse } var file_headscale_v1_headscale_proto_depIdxs = []int32{ 0, // 0: headscale.v1.HeadscaleService.GetNamespace:input_type -> headscale.v1.GetNamespaceRequest @@ -249,26 +279,32 @@ var file_headscale_v1_headscale_proto_depIdxs = []int32{ 15, // 15: headscale.v1.HeadscaleService.UnshareMachine:input_type -> headscale.v1.UnshareMachineRequest 16, // 16: headscale.v1.HeadscaleService.GetMachineRoute:input_type -> headscale.v1.GetMachineRouteRequest 17, // 17: headscale.v1.HeadscaleService.EnableMachineRoutes:input_type -> headscale.v1.EnableMachineRoutesRequest - 18, // 18: headscale.v1.HeadscaleService.GetNamespace:output_type -> headscale.v1.GetNamespaceResponse - 19, // 19: headscale.v1.HeadscaleService.CreateNamespace:output_type -> headscale.v1.CreateNamespaceResponse - 20, // 20: headscale.v1.HeadscaleService.RenameNamespace:output_type -> headscale.v1.RenameNamespaceResponse - 21, // 21: headscale.v1.HeadscaleService.DeleteNamespace:output_type -> headscale.v1.DeleteNamespaceResponse - 22, // 22: headscale.v1.HeadscaleService.ListNamespaces:output_type -> headscale.v1.ListNamespacesResponse - 23, // 23: headscale.v1.HeadscaleService.CreatePreAuthKey:output_type -> headscale.v1.CreatePreAuthKeyResponse - 24, // 24: headscale.v1.HeadscaleService.ExpirePreAuthKey:output_type -> headscale.v1.ExpirePreAuthKeyResponse - 25, // 25: headscale.v1.HeadscaleService.ListPreAuthKeys:output_type -> headscale.v1.ListPreAuthKeysResponse - 26, // 26: headscale.v1.HeadscaleService.DebugCreateMachine:output_type -> headscale.v1.DebugCreateMachineResponse - 27, // 27: headscale.v1.HeadscaleService.GetMachine:output_type -> headscale.v1.GetMachineResponse - 28, // 28: headscale.v1.HeadscaleService.RegisterMachine:output_type -> headscale.v1.RegisterMachineResponse - 29, // 29: headscale.v1.HeadscaleService.DeleteMachine:output_type -> headscale.v1.DeleteMachineResponse - 30, // 30: headscale.v1.HeadscaleService.ExpireMachine:output_type -> headscale.v1.ExpireMachineResponse - 31, // 31: headscale.v1.HeadscaleService.ListMachines:output_type -> headscale.v1.ListMachinesResponse - 32, // 32: headscale.v1.HeadscaleService.ShareMachine:output_type -> headscale.v1.ShareMachineResponse - 33, // 33: headscale.v1.HeadscaleService.UnshareMachine:output_type -> headscale.v1.UnshareMachineResponse - 34, // 34: headscale.v1.HeadscaleService.GetMachineRoute:output_type -> headscale.v1.GetMachineRouteResponse - 35, // 35: headscale.v1.HeadscaleService.EnableMachineRoutes:output_type -> headscale.v1.EnableMachineRoutesResponse - 18, // [18:36] is the sub-list for method output_type - 0, // [0:18] is the sub-list for method input_type + 18, // 18: headscale.v1.HeadscaleService.CreateApiKey:input_type -> headscale.v1.CreateApiKeyRequest + 19, // 19: headscale.v1.HeadscaleService.ExpireApiKey:input_type -> headscale.v1.ExpireApiKeyRequest + 20, // 20: headscale.v1.HeadscaleService.ListApiKeys:input_type -> headscale.v1.ListApiKeysRequest + 21, // 21: headscale.v1.HeadscaleService.GetNamespace:output_type -> headscale.v1.GetNamespaceResponse + 22, // 22: headscale.v1.HeadscaleService.CreateNamespace:output_type -> headscale.v1.CreateNamespaceResponse + 23, // 23: headscale.v1.HeadscaleService.RenameNamespace:output_type -> headscale.v1.RenameNamespaceResponse + 24, // 24: headscale.v1.HeadscaleService.DeleteNamespace:output_type -> headscale.v1.DeleteNamespaceResponse + 25, // 25: headscale.v1.HeadscaleService.ListNamespaces:output_type -> headscale.v1.ListNamespacesResponse + 26, // 26: headscale.v1.HeadscaleService.CreatePreAuthKey:output_type -> headscale.v1.CreatePreAuthKeyResponse + 27, // 27: headscale.v1.HeadscaleService.ExpirePreAuthKey:output_type -> headscale.v1.ExpirePreAuthKeyResponse + 28, // 28: headscale.v1.HeadscaleService.ListPreAuthKeys:output_type -> headscale.v1.ListPreAuthKeysResponse + 29, // 29: headscale.v1.HeadscaleService.DebugCreateMachine:output_type -> headscale.v1.DebugCreateMachineResponse + 30, // 30: headscale.v1.HeadscaleService.GetMachine:output_type -> headscale.v1.GetMachineResponse + 31, // 31: headscale.v1.HeadscaleService.RegisterMachine:output_type -> headscale.v1.RegisterMachineResponse + 32, // 32: headscale.v1.HeadscaleService.DeleteMachine:output_type -> headscale.v1.DeleteMachineResponse + 33, // 33: headscale.v1.HeadscaleService.ExpireMachine:output_type -> headscale.v1.ExpireMachineResponse + 34, // 34: headscale.v1.HeadscaleService.ListMachines:output_type -> headscale.v1.ListMachinesResponse + 35, // 35: headscale.v1.HeadscaleService.ShareMachine:output_type -> headscale.v1.ShareMachineResponse + 36, // 36: headscale.v1.HeadscaleService.UnshareMachine:output_type -> headscale.v1.UnshareMachineResponse + 37, // 37: headscale.v1.HeadscaleService.GetMachineRoute:output_type -> headscale.v1.GetMachineRouteResponse + 38, // 38: headscale.v1.HeadscaleService.EnableMachineRoutes:output_type -> headscale.v1.EnableMachineRoutesResponse + 39, // 39: headscale.v1.HeadscaleService.CreateApiKey:output_type -> headscale.v1.CreateApiKeyResponse + 40, // 40: headscale.v1.HeadscaleService.ExpireApiKey:output_type -> headscale.v1.ExpireApiKeyResponse + 41, // 41: headscale.v1.HeadscaleService.ListApiKeys:output_type -> headscale.v1.ListApiKeysResponse + 21, // [21:42] is the sub-list for method output_type + 0, // [0:21] is the sub-list for method input_type 0, // [0:0] is the sub-list for extension type_name 0, // [0:0] is the sub-list for extension extendee 0, // [0:0] is the sub-list for field type_name @@ -283,6 +319,7 @@ func file_headscale_v1_headscale_proto_init() { file_headscale_v1_preauthkey_proto_init() file_headscale_v1_machine_proto_init() file_headscale_v1_routes_proto_init() + file_headscale_v1_apikey_proto_init() type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ diff --git a/gen/go/headscale/v1/headscale.pb.gw.go b/gen/go/headscale/v1/headscale.pb.gw.go index 41502c8f..b245b895 100644 --- a/gen/go/headscale/v1/headscale.pb.gw.go +++ b/gen/go/headscale/v1/headscale.pb.gw.go @@ -891,6 +891,92 @@ func local_request_HeadscaleService_EnableMachineRoutes_0(ctx context.Context, m } +func request_HeadscaleService_CreateApiKey_0(ctx context.Context, marshaler runtime.Marshaler, client HeadscaleServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq CreateApiKeyRequest + 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.CreateApiKey(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_HeadscaleService_CreateApiKey_0(ctx context.Context, marshaler runtime.Marshaler, server HeadscaleServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq CreateApiKeyRequest + 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.CreateApiKey(ctx, &protoReq) + return msg, metadata, err + +} + +func request_HeadscaleService_ExpireApiKey_0(ctx context.Context, marshaler runtime.Marshaler, client HeadscaleServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq ExpireApiKeyRequest + 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.ExpireApiKey(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_HeadscaleService_ExpireApiKey_0(ctx context.Context, marshaler runtime.Marshaler, server HeadscaleServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq ExpireApiKeyRequest + 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.ExpireApiKey(ctx, &protoReq) + return msg, metadata, err + +} + +func request_HeadscaleService_ListApiKeys_0(ctx context.Context, marshaler runtime.Marshaler, client HeadscaleServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq ListApiKeysRequest + var metadata runtime.ServerMetadata + + msg, err := client.ListApiKeys(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_HeadscaleService_ListApiKeys_0(ctx context.Context, marshaler runtime.Marshaler, server HeadscaleServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq ListApiKeysRequest + var metadata runtime.ServerMetadata + + msg, err := server.ListApiKeys(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. @@ -1311,6 +1397,75 @@ func RegisterHeadscaleServiceHandlerServer(ctx context.Context, mux *runtime.Ser }) + mux.Handle("POST", pattern_HeadscaleService_CreateApiKey_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/CreateApiKey", runtime.WithHTTPPathPattern("/api/v1/apikey")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_HeadscaleService_CreateApiKey_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_CreateApiKey_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("POST", pattern_HeadscaleService_ExpireApiKey_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/ExpireApiKey", runtime.WithHTTPPathPattern("/api/v1/apikey/expire")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_HeadscaleService_ExpireApiKey_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_ExpireApiKey_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_HeadscaleService_ListApiKeys_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/ListApiKeys", runtime.WithHTTPPathPattern("/api/v1/apikey")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_HeadscaleService_ListApiKeys_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_ListApiKeys_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + return nil } @@ -1712,6 +1867,66 @@ func RegisterHeadscaleServiceHandlerClient(ctx context.Context, mux *runtime.Ser }) + mux.Handle("POST", pattern_HeadscaleService_CreateApiKey_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/CreateApiKey", runtime.WithHTTPPathPattern("/api/v1/apikey")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_HeadscaleService_CreateApiKey_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_CreateApiKey_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("POST", pattern_HeadscaleService_ExpireApiKey_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/ExpireApiKey", runtime.WithHTTPPathPattern("/api/v1/apikey/expire")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_HeadscaleService_ExpireApiKey_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_ExpireApiKey_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_HeadscaleService_ListApiKeys_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/ListApiKeys", runtime.WithHTTPPathPattern("/api/v1/apikey")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_HeadscaleService_ListApiKeys_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_ListApiKeys_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + return nil } @@ -1751,6 +1966,12 @@ var ( pattern_HeadscaleService_GetMachineRoute_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 1, 5, 3, 2, 4}, []string{"api", "v1", "machine", "machine_id", "routes"}, "")) pattern_HeadscaleService_EnableMachineRoutes_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 1, 5, 3, 2, 4}, []string{"api", "v1", "machine", "machine_id", "routes"}, "")) + + pattern_HeadscaleService_CreateApiKey_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"api", "v1", "apikey"}, "")) + + pattern_HeadscaleService_ExpireApiKey_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"api", "v1", "apikey", "expire"}, "")) + + pattern_HeadscaleService_ListApiKeys_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"api", "v1", "apikey"}, "")) ) var ( @@ -1789,4 +2010,10 @@ var ( forward_HeadscaleService_GetMachineRoute_0 = runtime.ForwardResponseMessage forward_HeadscaleService_EnableMachineRoutes_0 = runtime.ForwardResponseMessage + + forward_HeadscaleService_CreateApiKey_0 = runtime.ForwardResponseMessage + + forward_HeadscaleService_ExpireApiKey_0 = runtime.ForwardResponseMessage + + forward_HeadscaleService_ListApiKeys_0 = runtime.ForwardResponseMessage ) diff --git a/gen/go/headscale/v1/headscale_grpc.pb.go b/gen/go/headscale/v1/headscale_grpc.pb.go index ab6cb70c..c75a36c5 100644 --- a/gen/go/headscale/v1/headscale_grpc.pb.go +++ b/gen/go/headscale/v1/headscale_grpc.pb.go @@ -4,6 +4,7 @@ package v1 import ( context "context" + grpc "google.golang.org/grpc" codes "google.golang.org/grpc/codes" status "google.golang.org/grpc/status" @@ -40,6 +41,10 @@ type HeadscaleServiceClient interface { // --- Route start --- GetMachineRoute(ctx context.Context, in *GetMachineRouteRequest, opts ...grpc.CallOption) (*GetMachineRouteResponse, error) EnableMachineRoutes(ctx context.Context, in *EnableMachineRoutesRequest, opts ...grpc.CallOption) (*EnableMachineRoutesResponse, error) + // --- ApiKeys start --- + CreateApiKey(ctx context.Context, in *CreateApiKeyRequest, opts ...grpc.CallOption) (*CreateApiKeyResponse, error) + ExpireApiKey(ctx context.Context, in *ExpireApiKeyRequest, opts ...grpc.CallOption) (*ExpireApiKeyResponse, error) + ListApiKeys(ctx context.Context, in *ListApiKeysRequest, opts ...grpc.CallOption) (*ListApiKeysResponse, error) } type headscaleServiceClient struct { @@ -212,6 +217,33 @@ func (c *headscaleServiceClient) EnableMachineRoutes(ctx context.Context, in *En return out, nil } +func (c *headscaleServiceClient) CreateApiKey(ctx context.Context, in *CreateApiKeyRequest, opts ...grpc.CallOption) (*CreateApiKeyResponse, error) { + out := new(CreateApiKeyResponse) + err := c.cc.Invoke(ctx, "/headscale.v1.HeadscaleService/CreateApiKey", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *headscaleServiceClient) ExpireApiKey(ctx context.Context, in *ExpireApiKeyRequest, opts ...grpc.CallOption) (*ExpireApiKeyResponse, error) { + out := new(ExpireApiKeyResponse) + err := c.cc.Invoke(ctx, "/headscale.v1.HeadscaleService/ExpireApiKey", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *headscaleServiceClient) ListApiKeys(ctx context.Context, in *ListApiKeysRequest, opts ...grpc.CallOption) (*ListApiKeysResponse, error) { + out := new(ListApiKeysResponse) + err := c.cc.Invoke(ctx, "/headscale.v1.HeadscaleService/ListApiKeys", 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 @@ -238,6 +270,10 @@ type HeadscaleServiceServer interface { // --- Route start --- GetMachineRoute(context.Context, *GetMachineRouteRequest) (*GetMachineRouteResponse, error) EnableMachineRoutes(context.Context, *EnableMachineRoutesRequest) (*EnableMachineRoutesResponse, error) + // --- ApiKeys start --- + CreateApiKey(context.Context, *CreateApiKeyRequest) (*CreateApiKeyResponse, error) + ExpireApiKey(context.Context, *ExpireApiKeyRequest) (*ExpireApiKeyResponse, error) + ListApiKeys(context.Context, *ListApiKeysRequest) (*ListApiKeysResponse, error) mustEmbedUnimplementedHeadscaleServiceServer() } @@ -299,6 +335,15 @@ func (UnimplementedHeadscaleServiceServer) GetMachineRoute(context.Context, *Get func (UnimplementedHeadscaleServiceServer) EnableMachineRoutes(context.Context, *EnableMachineRoutesRequest) (*EnableMachineRoutesResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method EnableMachineRoutes not implemented") } +func (UnimplementedHeadscaleServiceServer) CreateApiKey(context.Context, *CreateApiKeyRequest) (*CreateApiKeyResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method CreateApiKey not implemented") +} +func (UnimplementedHeadscaleServiceServer) ExpireApiKey(context.Context, *ExpireApiKeyRequest) (*ExpireApiKeyResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method ExpireApiKey not implemented") +} +func (UnimplementedHeadscaleServiceServer) ListApiKeys(context.Context, *ListApiKeysRequest) (*ListApiKeysResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method ListApiKeys not implemented") +} func (UnimplementedHeadscaleServiceServer) mustEmbedUnimplementedHeadscaleServiceServer() {} // UnsafeHeadscaleServiceServer may be embedded to opt out of forward compatibility for this service. @@ -636,6 +681,60 @@ func _HeadscaleService_EnableMachineRoutes_Handler(srv interface{}, ctx context. return interceptor(ctx, in, info, handler) } +func _HeadscaleService_CreateApiKey_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(CreateApiKeyRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(HeadscaleServiceServer).CreateApiKey(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/headscale.v1.HeadscaleService/CreateApiKey", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(HeadscaleServiceServer).CreateApiKey(ctx, req.(*CreateApiKeyRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _HeadscaleService_ExpireApiKey_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ExpireApiKeyRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(HeadscaleServiceServer).ExpireApiKey(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/headscale.v1.HeadscaleService/ExpireApiKey", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(HeadscaleServiceServer).ExpireApiKey(ctx, req.(*ExpireApiKeyRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _HeadscaleService_ListApiKeys_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ListApiKeysRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(HeadscaleServiceServer).ListApiKeys(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/headscale.v1.HeadscaleService/ListApiKeys", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(HeadscaleServiceServer).ListApiKeys(ctx, req.(*ListApiKeysRequest)) + } + 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) @@ -715,6 +814,18 @@ var HeadscaleService_ServiceDesc = grpc.ServiceDesc{ MethodName: "EnableMachineRoutes", Handler: _HeadscaleService_EnableMachineRoutes_Handler, }, + { + MethodName: "CreateApiKey", + Handler: _HeadscaleService_CreateApiKey_Handler, + }, + { + MethodName: "ExpireApiKey", + Handler: _HeadscaleService_ExpireApiKey_Handler, + }, + { + MethodName: "ListApiKeys", + Handler: _HeadscaleService_ListApiKeys_Handler, + }, }, Streams: []grpc.StreamDesc{}, Metadata: "headscale/v1/headscale.proto", diff --git a/gen/go/headscale/v1/machine.pb.go b/gen/go/headscale/v1/machine.pb.go index 07613697..5f12c6e5 100644 --- a/gen/go/headscale/v1/machine.pb.go +++ b/gen/go/headscale/v1/machine.pb.go @@ -1,17 +1,18 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.27.1 -// protoc v3.17.3 +// protoc (unknown) // source: headscale/v1/machine.proto package v1 import ( + reflect "reflect" + sync "sync" + 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 ( diff --git a/gen/go/headscale/v1/namespace.pb.go b/gen/go/headscale/v1/namespace.pb.go index 341f8ca0..0e0f8279 100644 --- a/gen/go/headscale/v1/namespace.pb.go +++ b/gen/go/headscale/v1/namespace.pb.go @@ -1,17 +1,18 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.27.1 -// protoc v3.17.3 +// protoc (unknown) // source: headscale/v1/namespace.proto package v1 import ( + reflect "reflect" + sync "sync" + 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 ( diff --git a/gen/go/headscale/v1/preauthkey.pb.go b/gen/go/headscale/v1/preauthkey.pb.go index 1169a89a..056e0f39 100644 --- a/gen/go/headscale/v1/preauthkey.pb.go +++ b/gen/go/headscale/v1/preauthkey.pb.go @@ -1,17 +1,18 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.27.1 -// protoc v3.17.3 +// protoc (unknown) // source: headscale/v1/preauthkey.proto package v1 import ( + reflect "reflect" + sync "sync" + 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 ( diff --git a/gen/go/headscale/v1/routes.pb.go b/gen/go/headscale/v1/routes.pb.go index ba40856d..12510f39 100644 --- a/gen/go/headscale/v1/routes.pb.go +++ b/gen/go/headscale/v1/routes.pb.go @@ -1,16 +1,17 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.27.1 -// protoc v3.17.3 +// protoc (unknown) // source: headscale/v1/routes.proto package v1 import ( - protoreflect "google.golang.org/protobuf/reflect/protoreflect" - protoimpl "google.golang.org/protobuf/runtime/protoimpl" reflect "reflect" sync "sync" + + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" ) const ( diff --git a/gen/openapiv2/headscale/v1/apikey.swagger.json b/gen/openapiv2/headscale/v1/apikey.swagger.json new file mode 100644 index 00000000..0d4ebbe9 --- /dev/null +++ b/gen/openapiv2/headscale/v1/apikey.swagger.json @@ -0,0 +1,43 @@ +{ + "swagger": "2.0", + "info": { + "title": "headscale/v1/apikey.proto", + "version": "version not set" + }, + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "paths": {}, + "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" + } + } + } + } + } +} diff --git a/gen/openapiv2/headscale/v1/headscale.swagger.json b/gen/openapiv2/headscale/v1/headscale.swagger.json index f1635ac2..d91d0baf 100644 --- a/gen/openapiv2/headscale/v1/headscale.swagger.json +++ b/gen/openapiv2/headscale/v1/headscale.swagger.json @@ -16,6 +16,91 @@ "application/json" ], "paths": { + "/api/v1/apikey": { + "get": { + "operationId": "HeadscaleService_ListApiKeys", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/v1ListApiKeysResponse" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + } + }, + "tags": [ + "HeadscaleService" + ] + }, + "post": { + "summary": "--- ApiKeys start ---", + "operationId": "HeadscaleService_CreateApiKey", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/v1CreateApiKeyResponse" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + } + }, + "parameters": [ + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/v1CreateApiKeyRequest" + } + } + ], + "tags": [ + "HeadscaleService" + ] + } + }, + "/api/v1/apikey/expire": { + "post": { + "operationId": "HeadscaleService_ExpireApiKey", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/v1ExpireApiKeyResponse" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + } + }, + "parameters": [ + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/v1ExpireApiKeyRequest" + } + } + ], + "tags": [ + "HeadscaleService" + ] + } + }, "/api/v1/debug/machine": { "post": { "summary": "--- Machine start ---", @@ -596,6 +681,47 @@ } } }, + "v1ApiKey": { + "type": "object", + "properties": { + "id": { + "type": "string", + "format": "uint64" + }, + "prefix": { + "type": "string" + }, + "expiration": { + "type": "string", + "format": "date-time" + }, + "createdAt": { + "type": "string", + "format": "date-time" + }, + "lastSeen": { + "type": "string", + "format": "date-time" + } + } + }, + "v1CreateApiKeyRequest": { + "type": "object", + "properties": { + "expiration": { + "type": "string", + "format": "date-time" + } + } + }, + "v1CreateApiKeyResponse": { + "type": "object", + "properties": { + "apiKey": { + "type": "string" + } + } + }, "v1CreateNamespaceRequest": { "type": "object", "properties": { @@ -680,6 +806,17 @@ } } }, + "v1ExpireApiKeyRequest": { + "type": "object", + "properties": { + "prefix": { + "type": "string" + } + } + }, + "v1ExpireApiKeyResponse": { + "type": "object" + }, "v1ExpireMachineResponse": { "type": "object", "properties": { @@ -726,6 +863,17 @@ } } }, + "v1ListApiKeysResponse": { + "type": "object", + "properties": { + "apiKeys": { + "type": "array", + "items": { + "$ref": "#/definitions/v1ApiKey" + } + } + } + }, "v1ListMachinesResponse": { "type": "object", "properties": { diff --git a/grpcv1.go b/grpcv1.go index 1850ce7a..9762d22b 100644 --- a/grpcv1.go +++ b/grpcv1.go @@ -349,6 +349,62 @@ func (api headscaleV1APIServer) EnableMachineRoutes( }, nil } +func (api headscaleV1APIServer) CreateApiKey( + ctx context.Context, + request *v1.CreateApiKeyRequest, +) (*v1.CreateApiKeyResponse, error) { + var expiration time.Time + if request.GetExpiration() != nil { + expiration = request.GetExpiration().AsTime() + } + + apiKey, _, err := api.h.CreateAPIKey( + &expiration, + ) + if err != nil { + return nil, err + } + + return &v1.CreateApiKeyResponse{ApiKey: apiKey}, nil +} + +func (api headscaleV1APIServer) ExpireApiKey( + ctx context.Context, + request *v1.ExpireApiKeyRequest, +) (*v1.ExpireApiKeyResponse, error) { + var apiKey *APIKey + var err error + + apiKey, err = api.h.GetAPIKey(request.Prefix) + if err != nil { + return nil, err + } + + err = api.h.ExpireAPIKey(apiKey) + if err != nil { + return nil, err + } + + return &v1.ExpireApiKeyResponse{}, nil +} + +func (api headscaleV1APIServer) ListApiKeys( + ctx context.Context, + request *v1.ListApiKeysRequest, +) (*v1.ListApiKeysResponse, error) { + apiKeys, err := api.h.ListAPIKeys() + if err != nil { + return nil, err + } + + response := make([]*v1.ApiKey, len(apiKeys)) + for index, key := range apiKeys { + response[index] = key.toProto() + } + + return &v1.ListApiKeysResponse{ApiKeys: response}, nil +} + // The following service calls are for testing and debugging func (api headscaleV1APIServer) DebugCreateMachine( ctx context.Context, diff --git a/integration_cli_test.go b/integration_cli_test.go index 0278da0d..818be911 100644 --- a/integration_cli_test.go +++ b/integration_cli_test.go @@ -1193,3 +1193,148 @@ func (s *IntegrationCLITestSuite) TestRouteCommand() { "route (route-machine) is not available on node", ) } + +func (s *IntegrationCLITestSuite) TestApiKeyCommand() { + count := 5 + + keys := make([]string, count) + + for i := 0; i < count; i++ { + apiResult, err := ExecuteCommand( + &s.headscale, + []string{ + "headscale", + "apikeys", + "create", + "--expiration", + "24h", + "--output", + "json", + }, + []string{}, + ) + assert.Nil(s.T(), err) + assert.NotEmpty(s.T(), apiResult) + + // var apiKey v1.ApiKey + // err = json.Unmarshal([]byte(apiResult), &apiKey) + // assert.Nil(s.T(), err) + + keys[i] = apiResult + } + + assert.Len(s.T(), keys, 5) + + // Test list of keys + listResult, err := ExecuteCommand( + &s.headscale, + []string{ + "headscale", + "apikeys", + "list", + "--output", + "json", + }, + []string{}, + ) + assert.Nil(s.T(), err) + + var listedApiKeys []v1.ApiKey + err = json.Unmarshal([]byte(listResult), &listedApiKeys) + assert.Nil(s.T(), err) + + assert.Len(s.T(), listedApiKeys, 5) + + assert.Equal(s.T(), uint64(1), listedApiKeys[0].Id) + assert.Equal(s.T(), uint64(2), listedApiKeys[1].Id) + assert.Equal(s.T(), uint64(3), listedApiKeys[2].Id) + assert.Equal(s.T(), uint64(4), listedApiKeys[3].Id) + assert.Equal(s.T(), uint64(5), listedApiKeys[4].Id) + + assert.NotEmpty(s.T(), listedApiKeys[0].Prefix) + assert.NotEmpty(s.T(), listedApiKeys[1].Prefix) + assert.NotEmpty(s.T(), listedApiKeys[2].Prefix) + assert.NotEmpty(s.T(), listedApiKeys[3].Prefix) + assert.NotEmpty(s.T(), listedApiKeys[4].Prefix) + + assert.True(s.T(), listedApiKeys[0].Expiration.AsTime().After(time.Now())) + assert.True(s.T(), listedApiKeys[1].Expiration.AsTime().After(time.Now())) + assert.True(s.T(), listedApiKeys[2].Expiration.AsTime().After(time.Now())) + assert.True(s.T(), listedApiKeys[3].Expiration.AsTime().After(time.Now())) + assert.True(s.T(), listedApiKeys[4].Expiration.AsTime().After(time.Now())) + + assert.True( + s.T(), + listedApiKeys[0].Expiration.AsTime().Before(time.Now().Add(time.Hour*26)), + ) + assert.True( + s.T(), + listedApiKeys[1].Expiration.AsTime().Before(time.Now().Add(time.Hour*26)), + ) + assert.True( + s.T(), + listedApiKeys[2].Expiration.AsTime().Before(time.Now().Add(time.Hour*26)), + ) + assert.True( + s.T(), + listedApiKeys[3].Expiration.AsTime().Before(time.Now().Add(time.Hour*26)), + ) + assert.True( + s.T(), + listedApiKeys[4].Expiration.AsTime().Before(time.Now().Add(time.Hour*26)), + ) + + expiredPrefixes := make(map[string]bool) + + // Expire three keys + for i := 0; i < 3; i++ { + _, err := ExecuteCommand( + &s.headscale, + []string{ + "headscale", + "apikeys", + "expire", + "--prefix", + listedApiKeys[i].Prefix, + }, + []string{}, + ) + assert.Nil(s.T(), err) + + expiredPrefixes[listedApiKeys[i].Prefix] = true + } + + // Test list pre auth keys after expire + listAfterExpireResult, err := ExecuteCommand( + &s.headscale, + []string{ + "headscale", + "apikeys", + "list", + "--output", + "json", + }, + []string{}, + ) + assert.Nil(s.T(), err) + + var listedAfterExpireApiKeys []v1.ApiKey + err = json.Unmarshal([]byte(listAfterExpireResult), &listedAfterExpireApiKeys) + assert.Nil(s.T(), err) + + for index := range listedAfterExpireApiKeys { + if _, ok := expiredPrefixes[listedAfterExpireApiKeys[index].Prefix]; ok { + // Expired + assert.True( + s.T(), + listedAfterExpireApiKeys[index].Expiration.AsTime().Before(time.Now()), + ) + } else { + // Not expired + assert.False( + s.T(), + listedAfterExpireApiKeys[index].Expiration.AsTime().Before(time.Now()), + ) + } + } +} diff --git a/integration_common_test.go b/integration_common_test.go index 94291fc6..de304d0d 100644 --- a/integration_common_test.go +++ b/integration_common_test.go @@ -8,16 +8,17 @@ import ( "fmt" "time" - "inet.af/netaddr" - "github.com/ory/dockertest/v3" "github.com/ory/dockertest/v3/docker" + "inet.af/netaddr" ) const DOCKER_EXECUTE_TIMEOUT = 10 * time.Second -var IpPrefix4 = netaddr.MustParseIPPrefix("100.64.0.0/10") -var IpPrefix6 = netaddr.MustParseIPPrefix("fd7a:115c:a1e0::/48") +var ( + IpPrefix4 = netaddr.MustParseIPPrefix("100.64.0.0/10") + IpPrefix6 = netaddr.MustParseIPPrefix("fd7a:115c:a1e0::/48") +) type ExecuteCommandConfig struct { timeout time.Duration diff --git a/proto/headscale/v1/apikey.proto b/proto/headscale/v1/apikey.proto new file mode 100644 index 00000000..749e5c22 --- /dev/null +++ b/proto/headscale/v1/apikey.proto @@ -0,0 +1,35 @@ +syntax = "proto3"; +package headscale.v1; +option go_package = "github.com/juanfont/headscale/gen/go/v1"; + +import "google/protobuf/timestamp.proto"; + +message ApiKey { + uint64 id = 1; + string prefix = 2; + google.protobuf.Timestamp expiration = 3; + google.protobuf.Timestamp created_at = 4; + google.protobuf.Timestamp last_seen = 5; +} + +message CreateApiKeyRequest { + google.protobuf.Timestamp expiration = 1; +} + +message CreateApiKeyResponse { + string api_key = 1; +} + +message ExpireApiKeyRequest { + string prefix = 1; +} + +message ExpireApiKeyResponse { +} + +message ListApiKeysRequest { +} + +message ListApiKeysResponse { + repeated ApiKey api_keys = 1; +} diff --git a/proto/headscale/v1/headscale.proto b/proto/headscale/v1/headscale.proto index f7332a88..3cbbb8ed 100644 --- a/proto/headscale/v1/headscale.proto +++ b/proto/headscale/v1/headscale.proto @@ -8,6 +8,7 @@ import "headscale/v1/namespace.proto"; import "headscale/v1/preauthkey.proto"; import "headscale/v1/machine.proto"; import "headscale/v1/routes.proto"; +import "headscale/v1/apikey.proto"; // import "headscale/v1/device.proto"; service HeadscaleService { @@ -131,6 +132,28 @@ service HeadscaleService { } // --- Route end --- + // --- ApiKeys start --- + rpc CreateApiKey(CreateApiKeyRequest) returns (CreateApiKeyResponse) { + option (google.api.http) = { + post: "/api/v1/apikey" + body: "*" + }; + } + + rpc ExpireApiKey(ExpireApiKeyRequest) returns (ExpireApiKeyResponse) { + option (google.api.http) = { + post: "/api/v1/apikey/expire" + body: "*" + }; + } + + rpc ListApiKeys(ListApiKeysRequest) returns (ListApiKeysResponse) { + option (google.api.http) = { + get: "/api/v1/apikey" + }; + } + // --- ApiKeys end --- + // Implement Tailscale API // rpc GetDevice(GetDeviceRequest) returns(GetDeviceResponse) { // option(google.api.http) = { diff --git a/proto/headscale/v1/machine.proto b/proto/headscale/v1/machine.proto index 9b032122..47664e15 100644 --- a/proto/headscale/v1/machine.proto +++ b/proto/headscale/v1/machine.proto @@ -14,13 +14,13 @@ enum RegisterMethod { } message Machine { - uint64 id = 1; - string machine_key = 2; - string node_key = 3; - string disco_key = 4; - repeated string ip_addresses = 5; - string name = 6; - Namespace namespace = 7; + uint64 id = 1; + string machine_key = 2; + string node_key = 3; + string disco_key = 4; + repeated string ip_addresses = 5; + string name = 6; + Namespace namespace = 7; bool registered = 8; RegisterMethod register_method = 9; diff --git a/utils.go b/utils.go index 9e85599f..a6be8cc4 100644 --- a/utils.go +++ b/utils.go @@ -7,6 +7,8 @@ package headscale import ( "context" + "crypto/rand" + "encoding/base64" "encoding/json" "fmt" "net" @@ -278,3 +280,29 @@ func containsIPPrefix(prefixes []netaddr.IPPrefix, prefix netaddr.IPPrefix) bool return false } + +// GenerateRandomBytes returns securely generated random bytes. +// It will return an error if the system's secure random +// number generator fails to function correctly, in which +// case the caller should not continue. +func GenerateRandomBytes(n int) ([]byte, error) { + bytes := make([]byte, n) + + // Note that err == nil only if we read len(b) bytes. + if _, err := rand.Read(bytes); err != nil { + return nil, err + } + + return bytes, nil +} + +// GenerateRandomStringURLSafe returns a URL-safe, base64 encoded +// securely generated random string. +// It will return an error if the system's secure random +// number generator fails to function correctly, in which +// case the caller should not continue. +func GenerateRandomStringURLSafe(n int) (string, error) { + b, err := GenerateRandomBytes(n) + + return base64.RawURLEncoding.EncodeToString(b), err +}