From 9dc20580c781e24206bdfcebb14761e07621bc6b Mon Sep 17 00:00:00 2001 From: Kristoffer Dalby Date: Tue, 25 Jan 2022 22:11:05 +0000 Subject: [PATCH 01/39] Add api key data model and helpers This commits introduces a new data model for holding api keys for the API. The keys are stored in the database with a prefix and a hash and bcrypt with 10 passes is used to store the hash and it is "one way safe". Api keys have an expiry logic similar to pre auth keys. A key cannot be retrieved after it has created, only verified. --- api_key.go | 164 ++++++++++++++++++++++++++++++++++++++++++++++++ api_key_test.go | 89 ++++++++++++++++++++++++++ 2 files changed, 253 insertions(+) create mode 100644 api_key.go create mode 100644 api_key_test.go 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) +} From 70d82ea18489a7875dff5668f6dce5335a5cc3fa Mon Sep 17 00:00:00 2001 From: Kristoffer Dalby Date: Tue, 25 Jan 2022 22:11:05 +0000 Subject: [PATCH 02/39] Add migration for new data model --- db.go | 5 +++++ 1 file changed, 5 insertions(+) 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 From b8e9024845d4b86f4d8a525522bb7c9226e60f8b Mon Sep 17 00:00:00 2001 From: Kristoffer Dalby Date: Tue, 25 Jan 2022 22:11:15 +0000 Subject: [PATCH 03/39] Add proto model for api key --- proto/headscale/v1/apikey.proto | 35 ++++++++++++++++++++++++++++++ proto/headscale/v1/headscale.proto | 23 ++++++++++++++++++++ proto/headscale/v1/machine.proto | 14 ++++++------ 3 files changed, 65 insertions(+), 7 deletions(-) create mode 100644 proto/headscale/v1/apikey.proto 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; From b1a9b1ada1c49023ae72a2210089dd486f1524ef Mon Sep 17 00:00:00 2001 From: Kristoffer Dalby Date: Tue, 25 Jan 2022 22:11:15 +0000 Subject: [PATCH 04/39] Generate code from proto --- gen/go/headscale/v1/apikey.pb.go | 559 ++++++++++++++++++ gen/go/headscale/v1/device.pb.go | 7 +- gen/go/headscale/v1/headscale.pb.go | 423 +++++++------ gen/go/headscale/v1/headscale.pb.gw.go | 227 +++++++ gen/go/headscale/v1/headscale_grpc.pb.go | 111 ++++ gen/go/headscale/v1/machine.pb.go | 7 +- gen/go/headscale/v1/namespace.pb.go | 7 +- gen/go/headscale/v1/preauthkey.pb.go | 7 +- gen/go/headscale/v1/routes.pb.go | 7 +- .../headscale/v1/apikey.swagger.json | 43 ++ .../headscale/v1/headscale.swagger.json | 148 +++++ 11 files changed, 1338 insertions(+), 208 deletions(-) create mode 100644 gen/go/headscale/v1/apikey.pb.go create mode 100644 gen/openapiv2/headscale/v1/apikey.swagger.json 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": { From f9137f3bb0d8c89ff7b39aad91afecb30c6bc567 Mon Sep 17 00:00:00 2001 From: Kristoffer Dalby Date: Tue, 25 Jan 2022 22:11:15 +0000 Subject: [PATCH 05/39] Create helper functions around gRPC interface --- grpcv1.go | 56 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) 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, From b4259fcd7933ee4423f98fe508c023dd4bcf2273 Mon Sep 17 00:00:00 2001 From: Kristoffer Dalby Date: Tue, 25 Jan 2022 22:11:15 +0000 Subject: [PATCH 06/39] Add helper function for colouring expiries --- cmd/headscale/cli/pterm_style.go | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 cmd/headscale/cli/pterm_style.go diff --git a/cmd/headscale/cli/pterm_style.go b/cmd/headscale/cli/pterm_style.go new file mode 100644 index 00000000..e2678182 --- /dev/null +++ b/cmd/headscale/cli/pterm_style.go @@ -0,0 +1,20 @@ +package cli + +import ( + "time" + + "github.com/pterm/pterm" +) + +func ColourTime(date time.Time) string { + dateStr := date.Format("2006-01-02 15:04:05") + now := time.Now() + + if date.After(now) { + dateStr = pterm.LightGreen(dateStr) + } else { + dateStr = pterm.LightRed(dateStr) + } + + return dateStr +} From 1fd57a3375ac20590e1a588ffb7122976107ffd0 Mon Sep 17 00:00:00 2001 From: Kristoffer Dalby Date: Tue, 25 Jan 2022 22:11:15 +0000 Subject: [PATCH 07/39] Add apikeys command to create, list and expire --- cmd/headscale/cli/api_key.go | 183 +++++++++++++++++++++++++++++++++++ 1 file changed, 183 insertions(+) create mode 100644 cmd/headscale/cli/api_key.go diff --git a/cmd/headscale/cli/api_key.go b/cmd/headscale/cli/api_key.go new file mode 100644 index 00000000..fcce6905 --- /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) + }, +} From 6e14fdf0d3efa431b076180bdab465fb2e102641 Mon Sep 17 00:00:00 2001 From: Kristoffer Dalby Date: Tue, 25 Jan 2022 22:11:15 +0000 Subject: [PATCH 08/39] More reusable stuff in cli --- cmd/headscale/cli/preauthkeys.go | 2 +- cmd/headscale/cli/utils.go | 10 ++++++---- 2 files changed, 7 insertions(+), 5 deletions(-) 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/utils.go b/cmd/headscale/cli/utils.go index 258acf33..9e2c54cf 100644 --- a/cmd/headscale/cli/utils.go +++ b/cmd/headscale/cli/utils.go @@ -26,7 +26,8 @@ import ( ) const ( - PermissionFallback = 0o700 + PermissionFallback = 0o700 + HeadscaleDateTimeFormat = "2006-01-02 15:04:05" ) func LoadConfig(path string) error { @@ -270,7 +271,8 @@ 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{ @@ -400,7 +402,7 @@ func getHeadscaleCLIClient() (context.Context, v1.HeadscaleServiceClient, *grpc. // 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{ @@ -416,7 +418,7 @@ func getHeadscaleCLIClient() (context.Context, v1.HeadscaleServiceClient, *grpc. 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) From 05db1b710935d0b5e1b09f52b2cf2ed88b40f810 Mon Sep 17 00:00:00 2001 From: Kristoffer Dalby Date: Tue, 25 Jan 2022 22:11:15 +0000 Subject: [PATCH 09/39] Formatting and improving logs for config loading --- cmd/headscale/headscale.go | 2 +- cmd/headscale/headscale_test.go | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) 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) { From e8e573de627323620b64ebd13435ba0d6faa2a8a Mon Sep 17 00:00:00 2001 From: Kristoffer Dalby Date: Tue, 25 Jan 2022 22:11:15 +0000 Subject: [PATCH 10/39] Add apikeys command integration test --- integration_cli_test.go | 145 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 145 insertions(+) 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()), + ) + } + } +} From 8218ef96ef40828308e660ce9512bc5062553ddc Mon Sep 17 00:00:00 2001 From: Kristoffer Dalby Date: Tue, 25 Jan 2022 22:11:15 +0000 Subject: [PATCH 11/39] Formatting of integration tests --- integration_common_test.go | 9 +-- integration_test.go | 112 +++++++++++++++++++------------------ 2 files changed, 63 insertions(+), 58 deletions(-) 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/integration_test.go b/integration_test.go index ee89ef28..c5bea3d3 100644 --- a/integration_test.go +++ b/integration_test.go @@ -375,7 +375,7 @@ func (s *IntegrationTestSuite) TestGetIpAddresses() { ips, err := getIPs(scales.tailscales) assert.Nil(s.T(), err) - for hostname, _ := range scales.tailscales { + for hostname := range scales.tailscales { ips := ips[hostname] for _, ip := range ips { s.T().Run(hostname, func(t *testing.T) { @@ -464,32 +464,33 @@ func (s *IntegrationTestSuite) TestPingAllPeersByAddress() { if peername == hostname { continue } - s.T().Run(fmt.Sprintf("%s-%s-%d", hostname, peername, i), func(t *testing.T) { - // We are only interested in "direct ping" which means what we - // might need a couple of more attempts before reaching the node. - command := []string{ - "tailscale", "ping", - "--timeout=1s", - "--c=10", - "--until-direct=true", - ip.String(), - } + s.T(). + Run(fmt.Sprintf("%s-%s-%d", hostname, peername, i), func(t *testing.T) { + // We are only interested in "direct ping" which means what we + // might need a couple of more attempts before reaching the node. + command := []string{ + "tailscale", "ping", + "--timeout=1s", + "--c=10", + "--until-direct=true", + ip.String(), + } - fmt.Printf( - "Pinging from %s to %s (%s)\n", - hostname, - peername, - ip, - ) - result, err := ExecuteCommand( - &tailscale, - command, - []string{}, - ) - assert.Nil(t, err) - fmt.Printf("Result for %s: %s\n", hostname, result) - assert.Contains(t, result, "pong") - }) + fmt.Printf( + "Pinging from %s to %s (%s)\n", + hostname, + peername, + ip, + ) + result, err := ExecuteCommand( + &tailscale, + command, + []string{}, + ) + assert.Nil(t, err) + fmt.Printf("Result for %s: %s\n", hostname, result) + assert.Contains(t, result, "pong") + }) } } } @@ -569,32 +570,33 @@ func (s *IntegrationTestSuite) TestSharedNodes() { if peername == hostname { continue } - s.T().Run(fmt.Sprintf("%s-%s-%d", hostname, peername, i), func(t *testing.T) { - // We are only interested in "direct ping" which means what we - // might need a couple of more attempts before reaching the node. - command := []string{ - "tailscale", "ping", - "--timeout=15s", - "--c=20", - "--until-direct=true", - ip.String(), - } + s.T(). + Run(fmt.Sprintf("%s-%s-%d", hostname, peername, i), func(t *testing.T) { + // We are only interested in "direct ping" which means what we + // might need a couple of more attempts before reaching the node. + command := []string{ + "tailscale", "ping", + "--timeout=15s", + "--c=20", + "--until-direct=true", + ip.String(), + } - fmt.Printf( - "Pinging from %s to %s (%s)\n", - hostname, - peername, - ip, - ) - result, err := ExecuteCommand( - &tailscale, - command, - []string{}, - ) - assert.Nil(t, err) - fmt.Printf("Result for %s: %s\n", hostname, result) - assert.Contains(t, result, "pong") - }) + fmt.Printf( + "Pinging from %s to %s (%s)\n", + hostname, + peername, + ip, + ) + result, err := ExecuteCommand( + &tailscale, + command, + []string{}, + ) + assert.Nil(t, err) + fmt.Printf("Result for %s: %s\n", hostname, result) + assert.Contains(t, result, "pong") + }) } } } @@ -625,7 +627,7 @@ func (s *IntegrationTestSuite) TestTailDrop() { []string{}, ) assert.Nil(s.T(), err) - for peername, _ := range ips { + for peername := range ips { if peername == hostname { continue } @@ -705,7 +707,7 @@ func (s *IntegrationTestSuite) TestPingAllPeersByHostname() { ips, err := getIPs(scales.tailscales) assert.Nil(s.T(), err) for hostname, tailscale := range scales.tailscales { - for peername, _ := range ips { + for peername := range ips { if peername == hostname { continue } @@ -774,7 +776,9 @@ func (s *IntegrationTestSuite) TestMagicDNS() { } } -func getIPs(tailscales map[string]dockertest.Resource) (map[string][]netaddr.IP, error) { +func getIPs( + tailscales map[string]dockertest.Resource, +) (map[string][]netaddr.IP, error) { ips := make(map[string][]netaddr.IP) for hostname, tailscale := range tailscales { command := []string{"tailscale", "ip"} From 3393363a67c17186a54bee53f30d5c4feacd3feb Mon Sep 17 00:00:00 2001 From: Kristoffer Dalby Date: Tue, 25 Jan 2022 22:11:15 +0000 Subject: [PATCH 12/39] Add safe random hash generators --- utils.go | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/utils.go b/utils.go index 9e85599f..562cede6 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,28 @@ 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) { + b := make([]byte, n) + _, err := rand.Read(b) + // Note that err == nil only if we read len(b) bytes. + if err != nil { + return nil, err + } + + return b, 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 +} From a730f007d840672a084a573d0657753db7bdbb4e Mon Sep 17 00:00:00 2001 From: Kristoffer Dalby Date: Tue, 25 Jan 2022 22:11:15 +0000 Subject: [PATCH 13/39] Formatting of DNS files --- dns.go | 11 +++++++++-- dns_test.go | 6 +++++- 2 files changed, 14 insertions(+), 3 deletions(-) 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) { From a6e22387fd886771832f6d99437ba536a09d617b Mon Sep 17 00:00:00 2001 From: Kristoffer Dalby Date: Tue, 25 Jan 2022 22:11:15 +0000 Subject: [PATCH 14/39] Formatting of machine.go --- machine.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/machine.go b/machine.go index 64fd11b8..f1f72438 100644 --- a/machine.go +++ b/machine.go @@ -530,7 +530,9 @@ func (machine Machine) toNode( addrs = append(addrs, ip) } - allowedIPs := append([]netaddr.IPPrefix{}, addrs...) // we append the node own IP, as it is required by the clients + allowedIPs := append( + []netaddr.IPPrefix{}, + addrs...) // we append the node own IP, as it is required by the clients if includeRoutes { routesStr := []string{} From 00c69ce50cf97df6f89ec1c56d7ac05f30b7e576 Mon Sep 17 00:00:00 2001 From: Kristoffer Dalby Date: Tue, 25 Jan 2022 22:11:15 +0000 Subject: [PATCH 15/39] Enable remote gRPC and HTTP API This commit enables the existing gRPC and HTTP API from remote locations as long as the user can provide a valid API key. This allows users to control their headscale with the CLI from a workstation. :tada: --- app.go | 70 ++++++++++++++++++++++++++++++++++------------------------ 1 file changed, 41 insertions(+), 29 deletions(-) diff --git a/app.go b/app.go index bc7491ce..61d67ade 100644 --- a/app.go +++ b/app.go @@ -339,26 +339,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) { @@ -381,19 +381,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 @@ -630,6 +641,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") }() From fa197cc18364e0e211811563e6d26c988cdd4a54 Mon Sep 17 00:00:00 2001 From: Kristoffer Dalby Date: Tue, 25 Jan 2022 22:11:15 +0000 Subject: [PATCH 16/39] Add docs for remote access --- docs/remote-cli.md | 85 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 85 insertions(+) create mode 100644 docs/remote-cli.md diff --git a/docs/remote-cli.md b/docs/remote-cli.md new file mode 100644 index 00000000..55193620 --- /dev/null +++ b/docs/remote-cli.md @@ -0,0 +1,85 @@ +# 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. + +## 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="" +``` + +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. + +## 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 + - If it is not valid, set the environment variable `HEADSCALE_CLI_INSECURE=true` to allow insecure certs. From bae7ba46de8e014dbc6c2ce29c304fc6537ab7c1 Mon Sep 17 00:00:00 2001 From: Kristoffer Dalby Date: Tue, 25 Jan 2022 22:11:15 +0000 Subject: [PATCH 17/39] Update changelog --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6c74144e..cfa96c32 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**: From 56b6528e3b3125d460437c22529d381b7f311b7d Mon Sep 17 00:00:00 2001 From: Kristoffer Dalby Date: Tue, 25 Jan 2022 22:11:15 +0000 Subject: [PATCH 18/39] Run prettier --- docs/remote-cli.md | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/docs/remote-cli.md b/docs/remote-cli.md index 55193620..3d4bbafb 100644 --- a/docs/remote-cli.md +++ b/docs/remote-cli.md @@ -13,7 +13,7 @@ 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 +## Create an API key We need to create an API key to authenticate our remote `headscale` when using it from our workstation. @@ -23,7 +23,7 @@ To create a API key, log into your `headscale` server and generate a key: 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, +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: @@ -38,7 +38,6 @@ and to expire a key: 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): @@ -59,7 +58,7 @@ export HEADSCALE_CLI_API_KEY="" ``` 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). +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. @@ -81,5 +80,5 @@ 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 +- Verify that your TLS certificate is valid - If it is not valid, set the environment variable `HEADSCALE_CLI_INSECURE=true` to allow insecure certs. From 537cd35cb2a408da9354c85d88fc03b6398c0792 Mon Sep 17 00:00:00 2001 From: Kristoffer Dalby Date: Tue, 25 Jan 2022 22:22:15 +0000 Subject: [PATCH 19/39] Try to add the grpc cert correctly --- app.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/app.go b/app.go index 61d67ade..08815889 100644 --- a/app.go +++ b/app.go @@ -572,7 +572,11 @@ func (h *Headscale) Serve() error { if tlsConfig != nil { httpServer.TLSConfig = tlsConfig - grpcOptions = append(grpcOptions, grpc.Creds(credentials.NewTLS(tlsConfig))) + // grpcOptions = append(grpcOptions, grpc.Creds(credentials.NewTLS(tlsConfig))) + grpcOptions = append( + grpcOptions, + grpc.Creds(credentials.NewServerTLSFromCert(&tlsConfig.Certificates[0])), + ) } grpcServer := grpc.NewServer(grpcOptions...) From 8853ccd5b4b34b4a4a7085e3ab0d6fbe632cf9f8 Mon Sep 17 00:00:00 2001 From: Kristoffer Dalby Date: Sat, 12 Feb 2022 13:25:27 +0000 Subject: [PATCH 20/39] Terminate tls immediatly, mux after --- app.go | 178 +++++++++++++++++++++++++++++++-------------------------- 1 file changed, 97 insertions(+), 81 deletions(-) diff --git a/app.go b/app.go index 08815889..b7d71c32 100644 --- a/app.go +++ b/app.go @@ -35,7 +35,7 @@ import ( "golang.org/x/sync/errgroup" "google.golang.org/grpc" "google.golang.org/grpc/codes" - "google.golang.org/grpc/credentials" + // "google.golang.org/grpc/credentials" "google.golang.org/grpc/metadata" "google.golang.org/grpc/peer" "google.golang.org/grpc/reflection" @@ -418,14 +418,65 @@ 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 - ctx := context.Background() - ctx, cancel := context.WithCancel(ctx) + // Fetch an initial DERP Map before we start serving + h.DERPMap = GetDERPMap(h.cfg.DERP) - defer cancel() + if h.cfg.DERP.AutoUpdate { + derpMapCancelChannel := make(chan struct{}) + defer func() { derpMapCancelChannel <- struct{}{} }() + go h.scheduledDERPMapUpdateWorker(derpMapCancelChannel) + } + + // I HATE THIS + go h.watchForKVUpdates(updateInterval) + go h.expireEphemeralNodes(updateInterval) + + if zl.GlobalLevel() == zl.TraceLevel { + zerolog.RespLog = true + } else { + zerolog.RespLog = false + } + + // + // + // Set up LOCAL listeners + // err = h.ensureUnixSocketIsAbsent() if err != nil { @@ -455,7 +506,35 @@ func (h *Headscale) Serve() error { os.Exit(0) }(sigc) - networkListener, err := net.Listen("tcp", h.cfg.Addr) + // + // + // Set up REMOTE listeners + // + ctx := context.Background() + ctx, cancel := context.WithCancel(ctx) + + defer cancel() + + var networkListener net.Listener + + tlsConfig, err := h.getTLSSettings() + if err != nil { + log.Error().Err(err).Msg("Failed to set up TLS configuration") + + return err + } + + // if tlsConfig != nil { + // httpServer.TLSConfig = tlsConfig + // + // grpcOptions = append(grpcOptions, grpc.Creds(credentials.NewTLS(tlsConfig))) + // } + + if tlsConfig != nil { + networkListener, err = tls.Listen("tcp", h.cfg.Addr, tlsConfig) + } else { + networkListener, err = net.Listen("tcp", h.cfg.Addr) + } if err != nil { return fmt.Errorf("failed to bind to TCP address: %w", err) } @@ -463,6 +542,7 @@ func (h *Headscale) Serve() error { // 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"), @@ -495,46 +575,7 @@ func (h *Headscale) Serve() error { return err } - 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(grpcGatewayMux.ServeHTTP)) - } - - router.NoRoute(stdoutHandler) - - // Fetch an initial DERP Map before we start serving - h.DERPMap = GetDERPMap(h.cfg.DERP) - - if h.cfg.DERP.AutoUpdate { - derpMapCancelChannel := make(chan struct{}) - defer func() { derpMapCancelChannel <- struct{}{} }() - go h.scheduledDERPMapUpdateWorker(derpMapCancelChannel) - } - - // I HATE THIS - go h.watchForKVUpdates(updateInterval) - go h.expireEphemeralNodes(updateInterval) + router := h.createRouter(grpcGatewayMux) httpServer := &http.Server{ Addr: h.cfg.Addr, @@ -547,12 +588,6 @@ 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( @@ -562,23 +597,6 @@ func (h *Headscale) Serve() error { ), } - tlsConfig, err := h.getTLSSettings() - if err != nil { - log.Error().Err(err).Msg("Failed to set up TLS configuration") - - return err - } - - if tlsConfig != nil { - httpServer.TLSConfig = tlsConfig - - // grpcOptions = append(grpcOptions, grpc.Creds(credentials.NewTLS(tlsConfig))) - grpcOptions = append( - grpcOptions, - grpc.Creds(credentials.NewServerTLSFromCert(&tlsConfig.Certificates[0])), - ) - } - grpcServer := grpc.NewServer(grpcOptions...) // Start the local gRPC server without TLS and without authentication @@ -592,22 +610,20 @@ func (h *Headscale) Serve() error { 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) - }) - } else { - errorGroup.Go(func() error { return httpServer.Serve(httpListener) }) - } - + errorGroup.Go(func() error { return httpServer.Serve(httpListener) }) errorGroup.Go(func() error { return networkMutex.Serve() }) + // if tlsConfig != nil { + // errorGroup.Go(func() error { + // tlsl := tls.NewListener(httpListener, tlsConfig) + // + // return httpServer.Serve(tlsl) + // }) + // } else { + // errorGroup.Go(func() error { return httpServer.Serve(httpListener) }) + // } + log.Info(). Msgf("listening and serving (multiplexed HTTP and gRPC) on: %s", h.cfg.Addr) From 2aba37d2efc1d259f7dcd0088da0bb5f0c7924bc Mon Sep 17 00:00:00 2001 From: Kristoffer Dalby Date: Sat, 12 Feb 2022 14:42:23 +0000 Subject: [PATCH 21/39] Try to support plaintext http2 after termination --- app.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/app.go b/app.go index b7d71c32..6efeec90 100644 --- a/app.go +++ b/app.go @@ -31,10 +31,13 @@ import ( ginprometheus "github.com/zsais/go-gin-prometheus" "golang.org/x/crypto/acme" "golang.org/x/crypto/acme/autocert" + "golang.org/x/net/http2" + "golang.org/x/net/http2/h2c" "golang.org/x/oauth2" "golang.org/x/sync/errgroup" "google.golang.org/grpc" "google.golang.org/grpc/codes" + // "google.golang.org/grpc/credentials" "google.golang.org/grpc/metadata" "google.golang.org/grpc/peer" @@ -577,9 +580,11 @@ func (h *Headscale) Serve() error { router := h.createRouter(grpcGatewayMux) + h2s := &http2.Server{} + httpServer := &http.Server{ Addr: h.cfg.Addr, - Handler: router, + Handler: h2c.NewHandler(router, h2s), ReadTimeout: HTTPReadTimeout, // Go does not handle timeouts in HTTP very well, and there is // no good way to handle streaming timeouts, therefore we need to From 811d3d510c6788779c9fddd25a2523e9329bb510 Mon Sep 17 00:00:00 2001 From: Kristoffer Dalby Date: Sat, 12 Feb 2022 16:14:33 +0000 Subject: [PATCH 22/39] Add grpc_listen_addr config option --- cmd/headscale/cli/utils.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/cmd/headscale/cli/utils.go b/cmd/headscale/cli/utils.go index 9e2c54cf..eca43e80 100644 --- a/cmd/headscale/cli/utils.go +++ b/cmd/headscale/cli/utils.go @@ -55,6 +55,8 @@ func LoadConfig(path string) error { viper.SetDefault("unix_socket", "/var/run/headscale.sock") viper.SetDefault("unix_socket_permission", "0o770") + viper.SetDefault("grpc_listen_addr", ":50443") + viper.SetDefault("cli.insecure", false) viper.SetDefault("cli.timeout", "5s") @@ -278,6 +280,7 @@ func getHeadscaleConfig() headscale.Config { return headscale.Config{ ServerURL: viper.GetString("server_url"), Addr: viper.GetString("listen_addr"), + GRPCAddr: viper.GetString("grpc_listen_addr"), IPPrefixes: prefixes, PrivateKeyPath: absPath(viper.GetString("private_key_path")), BaseDomain: baseDomain, From bfc6f6e0eb52a348b7702ed7630ef1f60397dd85 Mon Sep 17 00:00:00 2001 From: Kristoffer Dalby Date: Sat, 12 Feb 2022 16:15:26 +0000 Subject: [PATCH 23/39] Split grpc and http --- app.go | 105 ++++++++++++++++++++++++++------------------------------- 1 file changed, 47 insertions(+), 58 deletions(-) diff --git a/app.go b/app.go index 6efeec90..bf07172b 100644 --- a/app.go +++ b/app.go @@ -27,12 +27,9 @@ 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" - "golang.org/x/net/http2" - "golang.org/x/net/http2/h2c" "golang.org/x/oauth2" "golang.org/x/sync/errgroup" "google.golang.org/grpc" @@ -71,6 +68,7 @@ const ( type Config struct { ServerURL string Addr string + GRPCAddr string EphemeralNodeInactivityTimeout time.Duration IPPrefixes []netaddr.IPPrefix PrivateKeyPath string @@ -518,8 +516,6 @@ func (h *Headscale) Serve() error { defer cancel() - var networkListener net.Listener - tlsConfig, err := h.getTLSSettings() if err != nil { log.Error().Err(err).Msg("Failed to set up TLS configuration") @@ -527,35 +523,22 @@ func (h *Headscale) Serve() error { return err } - // if tlsConfig != nil { - // httpServer.TLSConfig = tlsConfig + // var httpListener net.Listener // - // grpcOptions = append(grpcOptions, grpc.Creds(credentials.NewTLS(tlsConfig))) + // if tlsConfig != nil { + // httpListener, err = tls.Listen("tcp", h.cfg.Addr, tlsConfig) + // } else { + // httpListener, err = net.Listen("tcp", h.cfg.Addr) // } + // if err != nil { + // return fmt.Errorf("failed to bind to TCP address: %w", err) + // } + // - if tlsConfig != nil { - networkListener, err = tls.Listen("tcp", h.cfg.Addr, tlsConfig) - } else { - 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()) + // + // + // gRPC setup + // grpcGatewayMux := runtime.NewServeMux() @@ -578,21 +561,6 @@ func (h *Headscale) Serve() error { return err } - router := h.createRouter(grpcGatewayMux) - - h2s := &http2.Server{} - - httpServer := &http.Server{ - Addr: h.cfg.Addr, - Handler: h2c.NewHandler(router, h2s), - ReadTimeout: HTTPReadTimeout, - // Go does not handle timeouts in HTTP very well, and there is - // no good way to handle streaming timeouts, therefore we need to - // keep this at unlimited and be careful to clean up connections - // https://blog.cloudflare.com/the-complete-guide-to-golang-net-http-timeouts/#aboutstreaming - WriteTimeout: 0, - } - grpcOptions := []grpc.ServerOption{ grpc.UnaryInterceptor( grpc_middleware.ChainUnaryServer( @@ -612,22 +580,43 @@ func (h *Headscale) Serve() error { reflection.Register(grpcServer) reflection.Register(grpcSocket) + var grpcListener net.Listener + if tlsConfig != nil { + grpcListener, err = tls.Listen("tcp", h.cfg.GRPCAddr, tlsConfig) + } else { + grpcListener, err = net.Listen("tcp", h.cfg.GRPCAddr) + } + if err != nil { + return fmt.Errorf("failed to bind to TCP address: %w", err) + } + + // + // + // HTTP setup + // + + router := h.createRouter(grpcGatewayMux) + + httpServer := &http.Server{ + Addr: h.cfg.Addr, + Handler: router, + ReadTimeout: HTTPReadTimeout, + // Go does not handle timeouts in HTTP very well, and there is + // no good way to handle streaming timeouts, therefore we need to + // keep this at unlimited and be careful to clean up connections + // https://blog.cloudflare.com/the-complete-guide-to-golang-net-http-timeouts/#aboutstreaming + WriteTimeout: 0, + } + + if tlsConfig != nil { + httpServer.TLSConfig = tlsConfig + } + errorGroup := new(errgroup.Group) errorGroup.Go(func() error { return grpcSocket.Serve(socketListener) }) errorGroup.Go(func() error { return grpcServer.Serve(grpcListener) }) - errorGroup.Go(func() error { return httpServer.Serve(httpListener) }) - errorGroup.Go(func() error { return networkMutex.Serve() }) - - // if tlsConfig != nil { - // errorGroup.Go(func() error { - // tlsl := tls.NewListener(httpListener, tlsConfig) - // - // return httpServer.Serve(tlsl) - // }) - // } else { - // errorGroup.Go(func() error { return httpServer.Serve(httpListener) }) - // } + errorGroup.Go(func() error { return httpServer.ListenAndServe() }) log.Info(). Msgf("listening and serving (multiplexed HTTP and gRPC) on: %s", h.cfg.Addr) From 59e48993f2f7ebdbb24ab063cebfa1eaba46a288 Mon Sep 17 00:00:00 2001 From: Kristoffer Dalby Date: Sat, 12 Feb 2022 16:33:18 +0000 Subject: [PATCH 24/39] Change the http listener --- app.go | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/app.go b/app.go index bf07172b..987e64e4 100644 --- a/app.go +++ b/app.go @@ -608,15 +608,22 @@ func (h *Headscale) Serve() error { WriteTimeout: 0, } + var httpListener net.Listener if tlsConfig != nil { httpServer.TLSConfig = tlsConfig + httpListener, err = tls.Listen("tcp", h.cfg.Addr, tlsConfig) + } else { + httpListener, err = net.Listen("tcp", h.cfg.Addr) + } + if err != nil { + return fmt.Errorf("failed to bind to TCP address: %w", err) } errorGroup := new(errgroup.Group) errorGroup.Go(func() error { return grpcSocket.Serve(socketListener) }) errorGroup.Go(func() error { return grpcServer.Serve(grpcListener) }) - errorGroup.Go(func() error { return httpServer.ListenAndServe() }) + errorGroup.Go(func() error { return httpServer.Serve(httpListener) }) log.Info(). Msgf("listening and serving (multiplexed HTTP and gRPC) on: %s", h.cfg.Addr) From 30a2ccd9758c96efa42e2f9691f151be480ec5a6 Mon Sep 17 00:00:00 2001 From: Kristoffer Dalby Date: Sat, 12 Feb 2022 17:05:30 +0000 Subject: [PATCH 25/39] Add tls certs as creds for grpc --- app.go | 116 +++++++++++++++++++++++++++++---------------------------- 1 file changed, 59 insertions(+), 57 deletions(-) diff --git a/app.go b/app.go index 987e64e4..8d228b48 100644 --- a/app.go +++ b/app.go @@ -34,6 +34,8 @@ import ( "golang.org/x/sync/errgroup" "google.golang.org/grpc" "google.golang.org/grpc/codes" + "google.golang.org/grpc/credentials" + "google.golang.org/grpc/internal/credentials" // "google.golang.org/grpc/credentials" "google.golang.org/grpc/metadata" @@ -474,6 +476,13 @@ func (h *Headscale) Serve() error { 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 @@ -507,39 +516,6 @@ func (h *Headscale) Serve() error { os.Exit(0) }(sigc) - // - // - // Set up REMOTE listeners - // - ctx := context.Background() - ctx, cancel := context.WithCancel(ctx) - - defer cancel() - - 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 { - // httpListener, err = tls.Listen("tcp", h.cfg.Addr, tlsConfig) - // } else { - // httpListener, err = net.Listen("tcp", h.cfg.Addr) - // } - // if err != nil { - // return fmt.Errorf("failed to bind to TCP address: %w", err) - // } - // - - // - // - // gRPC setup - // - grpcGatewayMux := runtime.NewServeMux() // Make the grpc-gateway connect to grpc over socket @@ -561,33 +537,63 @@ func (h *Headscale) Serve() error { return err } - grpcOptions := []grpc.ServerOption{ - grpc.UnaryInterceptor( - grpc_middleware.ChainUnaryServer( - h.grpcAuthenticationInterceptor, - zerolog.NewUnaryServerInterceptor(), - ), - ), - } - - 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) - var grpcListener net.Listener - if tlsConfig != nil { - grpcListener, err = tls.Listen("tcp", h.cfg.GRPCAddr, tlsConfig) - } else { - grpcListener, err = net.Listen("tcp", h.cfg.GRPCAddr) - } + errorGroup.Go(func() error { return grpcSocket.Serve(socketListener) }) + + // + // + // Set up REMOTE listeners + // + + tlsConfig, err := h.getTLSSettings() if err != nil { - return fmt.Errorf("failed to bind to TCP address: %w", err) + log.Error().Err(err).Msg("Failed to set up TLS configuration") + + return err + } + + // + // + // gRPC setup + // + + // If TLS has been enabled, set up the remote gRPC server + if tlsConfig != nil { + log.Info().Msgf("Enabling remote gRPC at %s", h.cfg.GRPCAddr) + + grpcOptions := []grpc.ServerOption{ + grpc.UnaryInterceptor( + grpc_middleware.ChainUnaryServer( + h.grpcAuthenticationInterceptor, + zerolog.NewUnaryServerInterceptor(), + ), + ), + grpc.Creds(credentials.NewTLS(tlsConfig)), + } + + grpcServer := grpc.NewServer(grpcOptions...) + + v1.RegisterHeadscaleServiceServer(grpcServer, newHeadscaleV1APIServer(h)) + reflection.Register(grpcServer) + + var grpcListener net.Listener + // if tlsConfig != nil { + // grpcListener, err = tls.Listen("tcp", h.cfg.GRPCAddr, tlsConfig) + // } else { + 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) }) + } else { + log.Info().Msg("TLS is not configured, not enabling remote gRPC") } // @@ -619,10 +625,6 @@ func (h *Headscale) Serve() error { return fmt.Errorf("failed to bind to TCP address: %w", err) } - errorGroup := new(errgroup.Group) - - errorGroup.Go(func() error { return grpcSocket.Serve(socketListener) }) - errorGroup.Go(func() error { return grpcServer.Serve(grpcListener) }) errorGroup.Go(func() error { return httpServer.Serve(httpListener) }) log.Info(). From 531298fa593181f0a07cc860ed9ba15c37a04850 Mon Sep 17 00:00:00 2001 From: Kristoffer Dalby Date: Sat, 12 Feb 2022 17:13:51 +0000 Subject: [PATCH 26/39] Fix import --- app.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/app.go b/app.go index 8d228b48..0444247f 100644 --- a/app.go +++ b/app.go @@ -35,9 +35,6 @@ import ( "google.golang.org/grpc" "google.golang.org/grpc/codes" "google.golang.org/grpc/credentials" - "google.golang.org/grpc/internal/credentials" - - // "google.golang.org/grpc/credentials" "google.golang.org/grpc/metadata" "google.golang.org/grpc/peer" "google.golang.org/grpc/reflection" From c73b57e7dcbbfef7dee166333bc37b1349e20854 Mon Sep 17 00:00:00 2001 From: Kristoffer Dalby Date: Sat, 12 Feb 2022 19:08:33 +0000 Subject: [PATCH 27/39] Use undeprecated method for insecure --- cmd/headscale/cli/utils.go | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/cmd/headscale/cli/utils.go b/cmd/headscale/cli/utils.go index eca43e80..e3e1758e 100644 --- a/cmd/headscale/cli/utils.go +++ b/cmd/headscale/cli/utils.go @@ -19,6 +19,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" @@ -398,7 +400,7 @@ func getHeadscaleCLIClient() (context.Context, v1.HeadscaleServiceClient, *grpc. grpcOptions = append( grpcOptions, - grpc.WithInsecure(), + grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithContextDialer(headscale.GrpcSocketDialer), ) } else { @@ -414,7 +416,13 @@ func getHeadscaleCLIClient() (context.Context, v1.HeadscaleServiceClient, *grpc. ) if cfg.CLI.Insecure { - grpcOptions = append(grpcOptions, grpc.WithInsecure()) + grpcOptions = append(grpcOptions, + grpc.WithTransportCredentials(insecure.NewCredentials()), + ) + } else { + grpcOptions = append(grpcOptions, + grpc.WithTransportCredentials(credentials.NewClientTLSFromCert(nil, "")), + ) } } @@ -492,7 +500,7 @@ func (t tokenAuth) GetRequestMetadata( } func (tokenAuth) RequireTransportSecurity() bool { - return true + return false } // loadOIDCMatchMap is a wrapper around viper to verifies that the keys in From e18078d7f8a001c88e173c4c944c7e4f365a14ce Mon Sep 17 00:00:00 2001 From: Kristoffer Dalby Date: Sat, 12 Feb 2022 19:08:41 +0000 Subject: [PATCH 28/39] Rename j --- cmd/headscale/cli/utils.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/cmd/headscale/cli/utils.go b/cmd/headscale/cli/utils.go index e3e1758e..6a95e271 100644 --- a/cmd/headscale/cli/utils.go +++ b/cmd/headscale/cli/utils.go @@ -438,21 +438,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) } @@ -464,7 +464,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) { From 58bfea4e6459927dff5264c83c5d4bc6dcc6bbd6 Mon Sep 17 00:00:00 2001 From: Kristoffer Dalby Date: Sat, 12 Feb 2022 19:08:59 +0000 Subject: [PATCH 29/39] Update examples and docs --- config-example.yaml | 7 +++++++ docs/remote-cli.md | 10 +++++++++- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/config-example.yaml b/config-example.yaml index 940fe57f..4fc06c97 100644 --- a/config-example.yaml +++ b/config-example.yaml @@ -16,6 +16,13 @@ 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 + # Private key used encrypt the traffic between headscale # and Tailscale clients. # The private key file which will be diff --git a/docs/remote-cli.md b/docs/remote-cli.md index 3d4bbafb..1a1dc1de 100644 --- a/docs/remote-cli.md +++ b/docs/remote-cli.md @@ -7,6 +7,7 @@ - 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 @@ -53,10 +54,17 @@ chmod +x /usr/local/bin/headscale 4. Configure the CLI through Environment Variables ```shell -export HEADSCALE_CLI_ADDRESS="" +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). From 4078e75b50d46b2c162c7d8201713ca7e2fc1c53 Mon Sep 17 00:00:00 2001 From: Kristoffer Dalby Date: Sat, 12 Feb 2022 19:30:25 +0000 Subject: [PATCH 30/39] Correct log message --- app.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/app.go b/app.go index 0444247f..aad8156b 100644 --- a/app.go +++ b/app.go @@ -589,6 +589,9 @@ func (h *Headscale) Serve() error { } errorGroup.Go(func() error { return grpcServer.Serve(grpcListener) }) + + log.Info(). + Msgf("listening and serving gRPC on: %s", h.cfg.GRPCAddr) } else { log.Info().Msg("TLS is not configured, not enabling remote gRPC") } @@ -625,7 +628,7 @@ func (h *Headscale) Serve() error { 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() } From 315ff9daf064d9ac78af14fa245ebb0136e24863 Mon Sep 17 00:00:00 2001 From: Kristoffer Dalby Date: Sat, 12 Feb 2022 19:35:55 +0000 Subject: [PATCH 31/39] Remove insecure, only allow valid certs --- app.go | 7 +++---- cmd/headscale/cli/utils.go | 21 +++++---------------- docs/remote-cli.md | 4 ++-- 3 files changed, 10 insertions(+), 22 deletions(-) diff --git a/app.go b/app.go index aad8156b..4922fba8 100644 --- a/app.go +++ b/app.go @@ -119,10 +119,9 @@ type DERPConfig struct { } type CLIConfig struct { - Address string - APIKey string - Insecure bool - Timeout time.Duration + Address string + APIKey string + Timeout time.Duration } // Headscale represents the base app of the service. diff --git a/cmd/headscale/cli/utils.go b/cmd/headscale/cli/utils.go index 6a95e271..072e3040 100644 --- a/cmd/headscale/cli/utils.go +++ b/cmd/headscale/cli/utils.go @@ -59,7 +59,6 @@ func LoadConfig(path string) error { viper.SetDefault("grpc_listen_addr", ":50443") - viper.SetDefault("cli.insecure", false) viper.SetDefault("cli.timeout", "5s") if err := viper.ReadInConfig(); err != nil { @@ -326,10 +325,9 @@ 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"), + Address: viper.GetString("cli.address"), + APIKey: viper.GetString("cli.api_key"), + Timeout: viper.GetDuration("cli.timeout"), }, } } @@ -413,17 +411,8 @@ func getHeadscaleCLIClient() (context.Context, v1.HeadscaleServiceClient, *grpc. grpc.WithPerRPCCredentials(tokenAuth{ token: apiKey, }), + grpc.WithTransportCredentials(credentials.NewClientTLSFromCert(nil, "")), ) - - if cfg.CLI.Insecure { - grpcOptions = append(grpcOptions, - grpc.WithTransportCredentials(insecure.NewCredentials()), - ) - } else { - grpcOptions = append(grpcOptions, - grpc.WithTransportCredentials(credentials.NewClientTLSFromCert(nil, "")), - ) - } } log.Trace().Caller().Str("address", address).Msg("Connecting via gRPC") @@ -500,7 +489,7 @@ func (t tokenAuth) GetRequestMetadata( } func (tokenAuth) RequireTransportSecurity() bool { - return false + return true } // loadOIDCMatchMap is a wrapper around viper to verifies that the keys in diff --git a/docs/remote-cli.md b/docs/remote-cli.md index 1a1dc1de..adcced78 100644 --- a/docs/remote-cli.md +++ b/docs/remote-cli.md @@ -88,5 +88,5 @@ 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 - - If it is not valid, set the environment variable `HEADSCALE_CLI_INSECURE=true` to allow insecure certs. +- 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. From 2fbcc38f8f1066966b848d51a0e10c3ce9c3468c Mon Sep 17 00:00:00 2001 From: Kristoffer Dalby Date: Sat, 12 Feb 2022 19:36:43 +0000 Subject: [PATCH 32/39] Emph trusted cert --- docs/remote-cli.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/remote-cli.md b/docs/remote-cli.md index adcced78..5ff2f4a0 100644 --- a/docs/remote-cli.md +++ b/docs/remote-cli.md @@ -5,7 +5,7 @@ - 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 +- `headscale` _must_ be served over TLS/HTTPS with a _trusted_ certificate - Remote access does _not_ support unencrypted traffic. - Port `50443` must be open in the firewall (or port overriden by `grpc_listen_addr` option) From ead8b68a03370e1fbb991ebf152a4125e275fc11 Mon Sep 17 00:00:00 2001 From: Kristoffer Dalby Date: Sat, 12 Feb 2022 19:42:55 +0000 Subject: [PATCH 33/39] Fix lint --- cmd/headscale/cli/api_key.go | 10 +++++----- cmd/headscale/cli/pterm_style.go | 3 +-- utils.go | 9 +++++---- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/cmd/headscale/cli/api_key.go b/cmd/headscale/cli/api_key.go index fcce6905..975149ff 100644 --- a/cmd/headscale/cli/api_key.go +++ b/cmd/headscale/cli/api_key.go @@ -14,8 +14,8 @@ import ( ) const ( - // 90 days - DefaultApiKeyExpiry = 90 * 24 * time.Hour + // 90 days. + DefaultAPIKeyExpiry = 90 * 24 * time.Hour ) func init() { @@ -23,7 +23,7 @@ func init() { apiKeysCmd.AddCommand(listAPIKeys) createAPIKeyCmd.Flags(). - DurationP("expiration", "e", DefaultApiKeyExpiry, "Human-readable expiration of the key (30m, 24h, 365d...)") + DurationP("expiration", "e", DefaultAPIKeyExpiry, "Human-readable expiration of the key (30m, 24h, 365d...)") apiKeysCmd.AddCommand(createAPIKeyCmd) @@ -104,8 +104,8 @@ 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. +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") diff --git a/cmd/headscale/cli/pterm_style.go b/cmd/headscale/cli/pterm_style.go index e2678182..85fd050b 100644 --- a/cmd/headscale/cli/pterm_style.go +++ b/cmd/headscale/cli/pterm_style.go @@ -8,9 +8,8 @@ import ( func ColourTime(date time.Time) string { dateStr := date.Format("2006-01-02 15:04:05") - now := time.Now() - if date.After(now) { + if date.After(time.Now()) { dateStr = pterm.LightGreen(dateStr) } else { dateStr = pterm.LightRed(dateStr) diff --git a/utils.go b/utils.go index 562cede6..a6be8cc4 100644 --- a/utils.go +++ b/utils.go @@ -286,14 +286,14 @@ func containsIPPrefix(prefixes []netaddr.IPPrefix, prefix netaddr.IPPrefix) bool // number generator fails to function correctly, in which // case the caller should not continue. func GenerateRandomBytes(n int) ([]byte, error) { - b := make([]byte, n) - _, err := rand.Read(b) + bytes := make([]byte, n) + // Note that err == nil only if we read len(b) bytes. - if err != nil { + if _, err := rand.Read(bytes); err != nil { return nil, err } - return b, nil + return bytes, nil } // GenerateRandomStringURLSafe returns a URL-safe, base64 encoded @@ -303,5 +303,6 @@ func GenerateRandomBytes(n int) ([]byte, error) { // case the caller should not continue. func GenerateRandomStringURLSafe(n int) (string, error) { b, err := GenerateRandomBytes(n) + return base64.RawURLEncoding.EncodeToString(b), err } From d79ccfc05a00c470def605f4e70bc69844d30d97 Mon Sep 17 00:00:00 2001 From: Kristoffer Dalby Date: Sat, 12 Feb 2022 19:48:05 +0000 Subject: [PATCH 34/39] Add comment on why grpc is on its own port, replace deprecated --- app.go | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/app.go b/app.go index 4922fba8..126ed861 100644 --- a/app.go +++ b/app.go @@ -35,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" @@ -518,7 +519,7 @@ func (h *Headscale) Serve() error { grpcGatewayConn, err := grpc.Dial( h.cfg.UnixSocket, []grpc.DialOption{ - grpc.WithInsecure(), + grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithContextDialer(GrpcSocketDialer), }..., ) @@ -558,6 +559,13 @@ func (h *Headscale) Serve() error { // gRPC setup // + // 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 TLS has been enabled, set up the remote gRPC server if tlsConfig != nil { log.Info().Msgf("Enabling remote gRPC at %s", h.cfg.GRPCAddr) From 4841e16386b8f8dff6b6f220ab3664dcd55269ae Mon Sep 17 00:00:00 2001 From: Kristoffer Dalby Date: Sat, 12 Feb 2022 20:39:42 +0000 Subject: [PATCH 35/39] Add remote control doc --- docs/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/README.md b/docs/README.md index f42d67de..cc3b3bae 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) ### References From 0018a78d5a146d3fbd1e0157e9dc8809fd0ed544 Mon Sep 17 00:00:00 2001 From: Kristoffer Dalby Date: Sun, 13 Feb 2022 08:41:49 +0000 Subject: [PATCH 36/39] Add insecure option Add option to not _validate_ if the certificate served from headscale is trusted. --- app.go | 7 ++++--- cmd/headscale/cli/utils.go | 25 +++++++++++++++++++++---- docs/remote-cli.md | 5 +++-- 3 files changed, 28 insertions(+), 9 deletions(-) diff --git a/app.go b/app.go index 126ed861..0ad33605 100644 --- a/app.go +++ b/app.go @@ -120,9 +120,10 @@ type DERPConfig struct { } type CLIConfig struct { - Address string - APIKey string - Timeout time.Duration + Address string + APIKey string + Timeout time.Duration + Insecure bool } // Headscale represents the base app of the service. diff --git a/cmd/headscale/cli/utils.go b/cmd/headscale/cli/utils.go index 072e3040..84ff977b 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" @@ -60,6 +61,7 @@ func LoadConfig(path string) error { viper.SetDefault("grpc_listen_addr", ":50443") 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) @@ -325,9 +327,10 @@ func getHeadscaleConfig() headscale.Config { }, CLI: headscale.CLIConfig{ - Address: viper.GetString("cli.address"), - APIKey: viper.GetString("cli.api_key"), - Timeout: viper.GetDuration("cli.timeout"), + Address: viper.GetString("cli.address"), + APIKey: viper.GetString("cli.api_key"), + Timeout: viper.GetDuration("cli.timeout"), + Insecure: viper.GetBool("cli.insecure"), }, } } @@ -411,8 +414,22 @@ func getHeadscaleCLIClient() (context.Context, v1.HeadscaleServiceClient, *grpc. grpc.WithPerRPCCredentials(tokenAuth{ token: apiKey, }), - grpc.WithTransportCredentials(credentials.NewClientTLSFromCert(nil, "")), ) + + if cfg.CLI.Insecure { + tlsConfig := &tls.Config{ + 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") diff --git a/docs/remote-cli.md b/docs/remote-cli.md index 5ff2f4a0..5ba27ee0 100644 --- a/docs/remote-cli.md +++ b/docs/remote-cli.md @@ -5,7 +5,7 @@ - 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 with a _trusted_ certificate +- `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) @@ -89,4 +89,5 @@ 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. + - 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 From c3b68adfed83e85c5c3c0237fbcc3573935d5128 Mon Sep 17 00:00:00 2001 From: Kristoffer Dalby Date: Sun, 13 Feb 2022 08:46:35 +0000 Subject: [PATCH 37/39] Fix lint --- cmd/headscale/cli/utils.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/cmd/headscale/cli/utils.go b/cmd/headscale/cli/utils.go index 84ff977b..9a3f84d8 100644 --- a/cmd/headscale/cli/utils.go +++ b/cmd/headscale/cli/utils.go @@ -418,13 +418,15 @@ func getHeadscaleCLIClient() (context.Context, v1.HeadscaleServiceClient, *grpc. if cfg.CLI.Insecure { 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, "")), From 4e547963849f48de60ad9eeeb00dd1089d22662a Mon Sep 17 00:00:00 2001 From: Kristoffer Dalby Date: Sun, 13 Feb 2022 09:08:46 +0000 Subject: [PATCH 38/39] Allow gRPC server to run insecure --- app.go | 22 +++++++++++----------- cmd/headscale/cli/utils.go | 9 ++++++--- config-example.yaml | 6 ++++++ 3 files changed, 23 insertions(+), 14 deletions(-) diff --git a/app.go b/app.go index 0ad33605..ba87fcc7 100644 --- a/app.go +++ b/app.go @@ -69,6 +69,7 @@ type Config struct { ServerURL string Addr string GRPCAddr string + GRPCAllowInsecure bool EphemeralNodeInactivityTimeout time.Duration IPPrefixes []netaddr.IPPrefix PrivateKeyPath string @@ -567,8 +568,7 @@ func (h *Headscale) Serve() error { // https://github.com/soheilhy/cmux/issues/68 // https://github.com/soheilhy/cmux/issues/91 - // If TLS has been enabled, set up the remote gRPC server - if tlsConfig != nil { + if tlsConfig != nil || h.cfg.GRPCAllowInsecure { log.Info().Msgf("Enabling remote gRPC at %s", h.cfg.GRPCAddr) grpcOptions := []grpc.ServerOption{ @@ -578,7 +578,14 @@ func (h *Headscale) Serve() error { zerolog.NewUnaryServerInterceptor(), ), ), - grpc.Creds(credentials.NewTLS(tlsConfig)), + } + + if tlsConfig != nil { + grpcOptions = append(grpcOptions, + grpc.Creds(credentials.NewTLS(tlsConfig)), + ) + } else { + log.Warn().Msg("gRPC is running without security") } grpcServer := grpc.NewServer(grpcOptions...) @@ -586,12 +593,7 @@ func (h *Headscale) Serve() error { v1.RegisterHeadscaleServiceServer(grpcServer, newHeadscaleV1APIServer(h)) reflection.Register(grpcServer) - var grpcListener net.Listener - // if tlsConfig != nil { - // grpcListener, err = tls.Listen("tcp", h.cfg.GRPCAddr, tlsConfig) - // } else { - grpcListener, err = net.Listen("tcp", h.cfg.GRPCAddr) - // } + grpcListener, err := net.Listen("tcp", h.cfg.GRPCAddr) if err != nil { return fmt.Errorf("failed to bind to TCP address: %w", err) } @@ -600,8 +602,6 @@ func (h *Headscale) Serve() error { log.Info(). Msgf("listening and serving gRPC on: %s", h.cfg.GRPCAddr) - } else { - log.Info().Msg("TLS is not configured, not enabling remote gRPC") } // diff --git a/cmd/headscale/cli/utils.go b/cmd/headscale/cli/utils.go index 9a3f84d8..85dcae71 100644 --- a/cmd/headscale/cli/utils.go +++ b/cmd/headscale/cli/utils.go @@ -59,6 +59,7 @@ func LoadConfig(path string) error { viper.SetDefault("unix_socket_permission", "0o770") viper.SetDefault("grpc_listen_addr", ":50443") + viper.SetDefault("grpc_allow_insecure", false) viper.SetDefault("cli.timeout", "5s") viper.SetDefault("cli.insecure", false) @@ -281,9 +282,11 @@ func getHeadscaleConfig() headscale.Config { } return headscale.Config{ - ServerURL: viper.GetString("server_url"), - Addr: viper.GetString("listen_addr"), - GRPCAddr: viper.GetString("grpc_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, diff --git a/config-example.yaml b/config-example.yaml index 4fc06c97..ba0c653f 100644 --- a/config-example.yaml +++ b/config-example.yaml @@ -23,6 +23,12 @@ listen_addr: 0.0.0.0:8080 # 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 From 14b23544e42a0ef3e773fa7efd3703dbf130396d Mon Sep 17 00:00:00 2001 From: Kristoffer Dalby Date: Sun, 13 Feb 2022 09:48:33 +0000 Subject: [PATCH 39/39] Add note about running grpc behind a proxy and combining ports --- docs/remote-cli.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/docs/remote-cli.md b/docs/remote-cli.md index 5ba27ee0..3b1dc845 100644 --- a/docs/remote-cli.md +++ b/docs/remote-cli.md @@ -82,6 +82,13 @@ 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: