From 9dc20580c781e24206bdfcebb14761e07621bc6b Mon Sep 17 00:00:00 2001 From: Kristoffer Dalby Date: Tue, 25 Jan 2022 22:11:05 +0000 Subject: [PATCH 01/61] 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/61] 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/61] 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/61] 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/61] 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/61] 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/61] 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/61] 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/61] 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/61] 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/61] 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/61] 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/61] 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/61] 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/61] 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/61] 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/61] 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/61] 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/61] 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 150652e939dc383619cd112673f3f18d12d3ee10 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ho=C3=A0ng=20=C4=90=E1=BB=A9c=20Hi=E1=BA=BFu?= Date: Fri, 11 Feb 2022 13:46:36 +0700 Subject: [PATCH 20/61] poll: fix swapped machine<->namespace labels --- poll.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/poll.go b/poll.go index bcd6c33f..dd3956fb 100644 --- a/poll.go +++ b/poll.go @@ -193,7 +193,7 @@ func (h *Headscale) PollNetMapHandler(ctx *gin.Context) { // It sounds like we should update the nodes when we have received a endpoint update // even tho the comments in the tailscale code dont explicitly say so. - updateRequestsFromNode.WithLabelValues(machine.Name, machine.Namespace.Name, "endpoint-update"). + updateRequestsFromNode.WithLabelValues(machine.Namespace.Name, machine.Name, "endpoint-update"). Inc() updateChan <- struct{}{} @@ -222,7 +222,7 @@ func (h *Headscale) PollNetMapHandler(ctx *gin.Context) { Str("handler", "PollNetMap"). Str("machine", machine.Name). Msg("Notifying peers") - updateRequestsFromNode.WithLabelValues(machine.Name, machine.Namespace.Name, "full-update"). + updateRequestsFromNode.WithLabelValues(machine.Namespace.Name, machine.Name, "full-update"). Inc() updateChan <- struct{}{} @@ -413,7 +413,7 @@ func (h *Headscale) PollNetMapStream( Str("machine", machine.Name). Str("channel", "update"). Msg("Received a request for update") - updateRequestsReceivedOnChannel.WithLabelValues(machine.Name, machine.Namespace.Name). + updateRequestsReceivedOnChannel.WithLabelValues(machine.Namespace.Name, machine.Name). Inc() if h.isOutdated(machine) { var lastUpdate time.Time @@ -443,7 +443,7 @@ func (h *Headscale) PollNetMapStream( Str("channel", "update"). Err(err). Msg("Could not write the map response") - updateRequestsSentToNode.WithLabelValues(machine.Name, machine.Namespace.Name, "failed"). + updateRequestsSentToNode.WithLabelValues(machine.Namespace.Name, machine.Name, "failed"). Inc() return false @@ -453,7 +453,7 @@ func (h *Headscale) PollNetMapStream( Str("machine", machine.Name). Str("channel", "update"). Msg("Updated Map has been sent") - updateRequestsSentToNode.WithLabelValues(machine.Name, machine.Namespace.Name, "success"). + updateRequestsSentToNode.WithLabelValues(machine.Namespace.Name, machine.Name, "success"). Inc() // Keep track of the last successful update, @@ -582,7 +582,7 @@ func (h *Headscale) scheduledPollWorker( Str("func", "scheduledPollWorker"). Str("machine", machine.Name). Msg("Sending update request") - updateRequestsFromNode.WithLabelValues(machine.Name, machine.Namespace.Name, "scheduled-update"). + updateRequestsFromNode.WithLabelValues(machine.Namespace.Name, machine.Name, "scheduled-update"). Inc() updateChan <- struct{}{} } From 66ff34c2ddb2ca34dcee3a3aa49b14754aeb0591 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ho=C3=A0ng=20=C4=90=E1=BB=A9c=20Hi=E1=BA=BFu?= Date: Fri, 11 Feb 2022 13:49:09 +0700 Subject: [PATCH 21/61] apply changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6c74144e..a4a56faf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ **Changes**: - `ip_prefix` is now superseded by `ip_prefixes` in the configuration [#208](https://github.com/juanfont/headscale/pull/208) +- fix swapped machine<->namespace labels in `/metrics` **0.12.4 (2022-01-29):** From d9aaa0bdfc441e22de13ad1e5e18010b989c68e2 Mon Sep 17 00:00:00 2001 From: Kristoffer Dalby Date: Fri, 11 Feb 2022 08:26:22 +0000 Subject: [PATCH 22/61] Add docs on how to set up Windows clients --- README.md | 2 +- docs/windows-client.md | 42 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 43 insertions(+), 1 deletion(-) create mode 100644 docs/windows-client.md diff --git a/README.md b/README.md index 9a599d3d..491450a5 100644 --- a/README.md +++ b/README.md @@ -42,7 +42,7 @@ headscale implements this coordination server. | Linux | Yes | | OpenBSD | Yes | | macOS | Yes (see `/apple` on your headscale for more information) | -| Windows | Yes | +| Windows | Yes [docs](./docs/windows-client.md) | | Android | [You need to compile the client yourself](https://github.com/juanfont/headscale/issues/58#issuecomment-885255270) | | iOS | Not yet | diff --git a/docs/windows-client.md b/docs/windows-client.md new file mode 100644 index 00000000..2c8ecd56 --- /dev/null +++ b/docs/windows-client.md @@ -0,0 +1,42 @@ +# Connecting a Windows client + +## Goal + +This documentation has the goal of showing how a user can use the official Windows [Tailscale](https://tailscale.com) client with `headscale`. + +## Add registry keys + +To make the Windows client behave as expected and to run well with `headscale`, two registry keys **must** be set: + +- `HKLM:\SOFTWARE\Tailscale IPN\UnattendedMode` must be set to `always` to allow Tailscale to run properly in the background +- `HKLM:\SOFTWARE\Tailscale IPN\LoginURL` must be set to `` to ensure Tailscale contacts the correct control server. + +The Tailscale Windows client has been observed to reset its configuration on logout/reboot and these two keys [resolves that issue](https://github.com/tailscale/tailscale/issues/2798). + +For a guide on how to edit registry keys, [check out Computer Hope](https://www.computerhope.com/issues/ch001348.htm). + +## Installation + +Download the [Official Windows Client](https://tailscale.com/download/windows) and install it. + +When the installation has finished, start Tailscale and log in (you might have to click the icon in the system tray). + +The log in should open a browser Window and direct you to your `headscale` instance. + +## Troubleshooting + +If you are seeing repeated messages like: + +``` +[GIN] 2022/02/10 - 16:39:34 | 200 | 1.105306ms | 127.0.0.1 | POST "/machine/redacted" +``` + +in your `headscale` output, turn on `DEBUG` logging and look for: + +``` +2022-02-11T00:59:29Z DBG Machine registration has expired. Sending a authurl to register machine=redacted +``` + +This typically means that the register keys above was not set appropriatly. + +Ensure they are set correctly, delete Tailscale APP_DATA folder and try to connect again. From ba8afdb7bea3bacae0f26dccd0b4166ac0c49923 Mon Sep 17 00:00:00 2001 From: Kristoffer Dalby Date: Fri, 11 Feb 2022 08:39:00 +0000 Subject: [PATCH 23/61] Upgrade to tailscale 1.20.4 --- go.mod | 2 +- go.sum | 2 + integration_test.go | 114 +++++++++++++++++++++++--------------------- 3 files changed, 62 insertions(+), 56 deletions(-) diff --git a/go.mod b/go.mod index 8683c324..6de61f92 100644 --- a/go.mod +++ b/go.mod @@ -40,7 +40,7 @@ require ( gorm.io/driver/sqlite v1.1.5 gorm.io/gorm v1.21.15 inet.af/netaddr v0.0.0-20211027220019-c74959edd3b6 - tailscale.com v1.20.3 + tailscale.com v1.20.4 ) require ( diff --git a/go.sum b/go.sum index 86642b87..078111e2 100644 --- a/go.sum +++ b/go.sum @@ -1411,3 +1411,5 @@ sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU= tailscale.com v1.20.3 h1:C3g2AgmQaOi0YT5dAal9mslugPXMxwj0EXY7YfL2QrA= tailscale.com v1.20.3/go.mod h1:kjVy3ji2OH5lZhPLIIRacoY3CN4Bo3Yyb2mtoM8nfJ4= +tailscale.com v1.20.4 h1:7cl/Q2Sbo2Jb2dX7zA+Exbbl7DT5UGZ4iGhQ2xj23X0= +tailscale.com v1.20.4/go.mod h1:kjVy3ji2OH5lZhPLIIRacoY3CN4Bo3Yyb2mtoM8nfJ4= diff --git a/integration_test.go b/integration_test.go index ee89ef28..43845b61 100644 --- a/integration_test.go +++ b/integration_test.go @@ -28,7 +28,7 @@ import ( "tailscale.com/ipn/ipnstate" ) -var tailscaleVersions = []string{"1.20.2", "1.18.2", "1.16.2", "1.14.3", "1.12.3"} +var tailscaleVersions = []string{"1.20.4", "1.18.2", "1.16.2", "1.14.3", "1.12.3"} type TestNamespace struct { count int @@ -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 2357fb6f80acf9a5a09fb5503bdbec373c31596b Mon Sep 17 00:00:00 2001 From: Kristoffer Dalby Date: Fri, 11 Feb 2022 08:43:31 +0000 Subject: [PATCH 24/61] Upgrade all dependencies --- go.mod | 83 +++++++++++----------- go.sum | 214 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 257 insertions(+), 40 deletions(-) diff --git a/go.mod b/go.mod index 6de61f92..bbba4646 100644 --- a/go.mod +++ b/go.mod @@ -7,38 +7,38 @@ require ( github.com/coreos/go-oidc/v3 v3.1.0 github.com/efekarakus/termcolor v1.0.1 github.com/fatih/set v0.2.1 - github.com/gin-gonic/gin v1.7.4 - github.com/gofrs/uuid v4.1.0+incompatible + github.com/gin-gonic/gin v1.7.7 + github.com/gofrs/uuid v4.2.0+incompatible github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 - github.com/grpc-ecosystem/grpc-gateway/v2 v2.6.0 - github.com/infobloxopen/protoc-gen-gorm v1.0.1 - github.com/klauspost/compress v1.13.6 - github.com/ory/dockertest/v3 v3.7.0 + github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.3 + github.com/infobloxopen/protoc-gen-gorm v1.1.0 + github.com/klauspost/compress v1.14.2 + github.com/ory/dockertest/v3 v3.8.1 github.com/patrickmn/go-cache v2.1.0+incompatible github.com/philip-bui/grpc-zerolog v1.0.1 - github.com/prometheus/client_golang v1.11.0 - github.com/pterm/pterm v0.12.30 - github.com/rs/zerolog v1.26.0 + github.com/prometheus/client_golang v1.12.1 + github.com/pterm/pterm v0.12.36 + github.com/rs/zerolog v1.26.1 github.com/soheilhy/cmux v0.1.5 - github.com/spf13/cobra v1.2.1 - github.com/spf13/viper v1.9.0 + github.com/spf13/cobra v1.3.0 + github.com/spf13/viper v1.10.1 github.com/stretchr/testify v1.7.0 - github.com/tailscale/hujson v0.0.0-20211105212140-3a0adc019d83 + github.com/tailscale/hujson v0.0.0-20211215203138-ffd971c5f362 github.com/tcnksm/go-latest v0.0.0-20170313132115-e3007ae9052e github.com/zsais/go-gin-prometheus v0.1.0 - golang.org/x/crypto v0.0.0-20211202192323-5770296d904e + golang.org/x/crypto v0.0.0-20220210151621-f4118a5b28e2 golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 golang.org/x/sync v0.0.0-20210220032951-036812b2e83c - google.golang.org/genproto v0.0.0-20211104193956-4c6863e31247 - google.golang.org/grpc v1.42.0 - google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0 + google.golang.org/genproto v0.0.0-20220210181026-6fee9acbd336 + google.golang.org/grpc v1.44.0 + google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.2.0 google.golang.org/protobuf v1.27.1 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c gopkg.in/yaml.v2 v2.4.0 - gorm.io/datatypes v1.0.2 - gorm.io/driver/postgres v1.1.1 - gorm.io/driver/sqlite v1.1.5 - gorm.io/gorm v1.21.15 + gorm.io/datatypes v1.0.5 + gorm.io/driver/postgres v1.2.3 + gorm.io/driver/sqlite v1.2.6 + gorm.io/gorm v1.22.5 inet.af/netaddr v0.0.0-20211027220019-c74959edd3b6 tailscale.com v1.20.4 ) @@ -49,12 +49,12 @@ require ( github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 // indirect github.com/atomicgo/cursor v0.0.1 // indirect github.com/beorn7/perks v1.0.1 // indirect - github.com/cenkalti/backoff/v4 v4.1.1 // indirect + github.com/cenkalti/backoff/v4 v4.1.2 // indirect github.com/cespare/xxhash/v2 v2.1.2 // indirect - github.com/containerd/continuity v0.1.0 // indirect + github.com/containerd/continuity v0.2.2 // indirect github.com/davecgh/go-spew v1.1.1 // indirect - github.com/docker/cli v20.10.8+incompatible // indirect - github.com/docker/docker v20.10.8+incompatible // indirect + github.com/docker/cli v20.10.12+incompatible // indirect + github.com/docker/docker v20.10.12+incompatible // indirect github.com/docker/go-connections v0.4.0 // indirect github.com/docker/go-units v0.4.0 // indirect github.com/fsnotify/fsnotify v1.5.1 // indirect @@ -62,29 +62,30 @@ require ( github.com/gin-contrib/sse v0.1.0 // indirect github.com/go-playground/locales v0.14.0 // indirect github.com/go-playground/universal-translator v0.18.0 // indirect - github.com/go-playground/validator/v10 v10.9.0 // indirect + github.com/go-playground/validator/v10 v10.10.0 // indirect + github.com/go-sql-driver/mysql v1.6.0 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/glog v1.0.0 // indirect github.com/golang/protobuf v1.5.2 // indirect github.com/google/go-github v17.0.0+incompatible // indirect github.com/google/go-querystring v1.1.0 // indirect github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect - github.com/gookit/color v1.4.2 // indirect - github.com/hashicorp/go-version v1.2.0 // indirect + github.com/gookit/color v1.5.0 // indirect + github.com/hashicorp/go-version v1.4.0 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/imdario/mergo v0.3.12 // indirect github.com/inconshreveable/mousetrap v1.0.0 // indirect github.com/jackc/chunkreader/v2 v2.0.1 // indirect - github.com/jackc/pgconn v1.10.0 // indirect + github.com/jackc/pgconn v1.11.0 // indirect github.com/jackc/pgio v1.0.0 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect - github.com/jackc/pgproto3/v2 v2.1.1 // indirect + github.com/jackc/pgproto3/v2 v2.2.0 // indirect github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b // indirect - github.com/jackc/pgtype v1.8.1 // indirect - github.com/jackc/pgx/v4 v4.13.0 // indirect + github.com/jackc/pgtype v1.10.0 // indirect + github.com/jackc/pgx/v4 v4.15.0 // indirect github.com/jinzhu/gorm v1.9.16 // indirect github.com/jinzhu/inflection v1.0.0 // indirect - github.com/jinzhu/now v1.1.2 // indirect + github.com/jinzhu/now v1.1.4 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect github.com/kr/pretty v0.3.0 // indirect @@ -95,16 +96,16 @@ require ( github.com/mattn/go-colorable v0.1.12 // indirect github.com/mattn/go-isatty v0.0.14 // indirect github.com/mattn/go-runewidth v0.0.13 // indirect - github.com/mattn/go-sqlite3 v1.14.8 // indirect + github.com/mattn/go-sqlite3 v1.14.11 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect - github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b // indirect + github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect github.com/mitchellh/mapstructure v1.4.3 // indirect github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.0.2 // indirect - github.com/opencontainers/runc v1.0.3 // indirect + github.com/opencontainers/runc v1.1.0 // indirect github.com/pelletier/go-toml v1.9.4 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect @@ -112,9 +113,9 @@ require ( github.com/prometheus/common v0.32.1 // indirect github.com/prometheus/procfs v0.7.3 // indirect github.com/rivo/uniseg v0.2.0 // indirect - github.com/rogpeppe/go-internal v1.8.1-0.20211023094830-115ce09fd6b4 // indirect + github.com/rogpeppe/go-internal v1.8.1 // indirect github.com/sirupsen/logrus v1.8.1 // indirect - github.com/spf13/afero v1.6.0 // indirect + github.com/spf13/afero v1.8.1 // indirect github.com/spf13/cast v1.4.1 // indirect github.com/spf13/jwalterweatherman v1.1.0 // indirect github.com/spf13/pflag v1.0.5 // indirect @@ -127,12 +128,14 @@ require ( go4.org/intern v0.0.0-20211027215823-ae77deb06f29 // indirect go4.org/mem v0.0.0-20210711025021-927187094b94 // indirect go4.org/unsafe/assume-no-moving-gc v0.0.0-20211027215541-db492cf91b37 // indirect - golang.org/x/net v0.0.0-20211205041911-012df41ee64c // indirect - golang.org/x/sys v0.0.0-20211205182925-97ca703d548d // indirect + golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd // indirect + golang.org/x/sys v0.0.0-20220209214540-3681064d5158 // indirect golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect golang.org/x/text v0.3.7 // indirect google.golang.org/appengine v1.6.7 // indirect - gopkg.in/ini.v1 v1.66.2 // indirect + gopkg.in/ini.v1 v1.66.4 // indirect gopkg.in/square/go-jose.v2 v2.6.0 // indirect gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect + gorm.io/driver/mysql v1.2.3 // indirect + sigs.k8s.io/yaml v1.3.0 // indirect ) diff --git a/go.sum b/go.sum index 078111e2..28145899 100644 --- a/go.sum +++ b/go.sum @@ -1,9 +1,11 @@ bazil.org/fuse v0.0.0-20160811212531-371fbbdaa898/go.mod h1:Xbm+BRKSBEpa4q4hTSxohYNQpsxXPbPry4JJWOB3LB8= +bazil.org/fuse v0.0.0-20200407214033-5883e5a4b512/go.mod h1:FbcW6z/2VytnFDhZfumh8Ss8zxHE6qpMP5sHTRe0EaM= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.44.3/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= @@ -16,6 +18,7 @@ cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOY cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= +cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY= cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg= cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8= cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0= @@ -24,6 +27,10 @@ cloud.google.com/go v0.84.0/go.mod h1:RazrYuxIK6Kb7YrzzhPoLmCVzl7Sup4NrbKPg8KHSU cloud.google.com/go v0.87.0/go.mod h1:TpDYlFy7vuLzZMMZ+B6iRiELaY7z/gJPaqbMx6mlWcY= cloud.google.com/go v0.90.0/go.mod h1:kRX0mNRHe0e2rC6oNakvwQqzyDmg57xJ+SZU1eT2aDQ= cloud.google.com/go v0.93.3/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+YI= +cloud.google.com/go v0.94.1/go.mod h1:qAlAugsXlC+JWO+Bke5vCtc9ONxjQT3drlTTnAplMW4= +cloud.google.com/go v0.97.0/go.mod h1:GF7l59pYBVlXQIBLx3a761cZ41F9bBH3JUlihCt2Udc= +cloud.google.com/go v0.98.0/go.mod h1:ua6Ush4NALrHk5QXDWnjvZHN93OuF0HfuEPq9I1X0cM= +cloud.google.com/go v0.99.0/go.mod h1:w0Xx2nLzqWJPuozYQX+hFfCSI8WioryfRDzkoI/Y2ZA= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= @@ -34,6 +41,7 @@ cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7 cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= cloud.google.com/go/firestore v1.6.0/go.mod h1:afJwI0vaXwAG54kI7A//lP/lSPDkQORQuMkv56TxEPU= +cloud.google.com/go/firestore v1.6.1/go.mod h1:asNXNOzBdyVQmEU+ggO8UPodTkEVFW5Qx+rwHnAz+EY= cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= @@ -43,6 +51,7 @@ cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0Zeo cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= +cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo= contrib.go.opencensus.io/exporter/ocagent v0.7.0/go.mod h1:IshRmMJBhDfFj5Y67nVhMYTTIze91RUeT73ipWKs/GY= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/AlecAivazis/survey/v2 v2.3.2 h1:TqTB+aDDCLYhf9/bD2TwSO8u8jDSmMUd2SUVO4gCnU8= @@ -53,10 +62,15 @@ github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg6 github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/DATA-DOG/go-sqlmock v1.5.0/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= +github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= github.com/MarvinJWendt/testza v0.1.0/go.mod h1:7AxNvlfeHP7Z/hDQ5JtE3OKYT3XFUeLCDE2DQninSqs= github.com/MarvinJWendt/testza v0.2.1 h1:eitywm1lzygA2KCyn55jFVdOaXj5I9LeOsLNeifd2Kw= github.com/MarvinJWendt/testza v0.2.1/go.mod h1:God7bhG8n6uQxwdScay+gjm9/LnO4D3kkcZX4hv9Rp8= +github.com/MarvinJWendt/testza v0.2.8/go.mod h1:nwIcjmr0Zz+Rcwfh3/4UhBp7ePKVhuBExvZqnKYWlII= +github.com/MarvinJWendt/testza v0.2.10/go.mod h1:pd+VWsoGUiFtq+hRKSU1Bktnn+DMCSrDrXDpX2bG66k= +github.com/MarvinJWendt/testza v0.2.12 h1:/PRp/BF+27t2ZxynTiqj0nyND5PbOtfJS0SuTuxmgeg= +github.com/MarvinJWendt/testza v0.2.12/go.mod h1:JOIegYyV7rX+7VZ9r77L/eH6CfJHHzXjB69adAhzZkI= github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww= github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc= github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= @@ -85,6 +99,7 @@ github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= +github.com/armon/go-metrics v0.3.10/go.mod h1:4O98XIr/9W0sxpJ8UaYkvjk10Iff7SnFrb4QAOwNTFc= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A= @@ -108,16 +123,24 @@ github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QH github.com/cenkalti/backoff/v4 v4.1.0/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= github.com/cenkalti/backoff/v4 v4.1.1 h1:G2HAfAmvm/GcKan2oOQpBXOd2tT2G57ZnZGWa1PxPBQ= github.com/cenkalti/backoff/v4 v4.1.1/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= +github.com/cenkalti/backoff/v4 v4.1.2 h1:6Yo7N8UP2K6LWZnW94DLVSSrbobcWdVzAYOisuDPIFo= +github.com/cenkalti/backoff/v4 v4.1.2/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/census-instrumentation/opencensus-proto v0.3.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE= github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/checkpoint-restore/go-criu/v5 v5.0.0/go.mod h1:cfwC0EG7HMUenopBsUf9d89JlCLQIfgVcNsNN0t6T2M= +github.com/checkpoint-restore/go-criu/v5 v5.3.0/go.mod h1:E/eQpaFtUKGOOSEBZgmKAcn+zUUwWxqcaKZlF54wK8E= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/cilium/ebpf v0.6.2/go.mod h1:4tRaxcgiL706VnOzHOdBlY8IEAIdxINsQBcU4xJJXRs= +github.com/cilium/ebpf v0.7.0/go.mod h1:/oI2+1shJiTGAMgl6/RgJr36Eo1jzrRcAWbcXO2usCA= +github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag= +github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I= github.com/clbanning/x2j v0.0.0-20191024224557-825249438eec/go.mod h1:jMjuTZXRI4dUb/I5gc9Hdhagfvm9+RyrPryS/auMzxE= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= @@ -127,15 +150,20 @@ github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XP github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20211130200136-a8f946100490/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I= github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI= github.com/containerd/console v1.0.2/go.mod h1:ytZPjGgY2oeTkAONYafi2kSj0aYggsf8acV1PGKCbzQ= +github.com/containerd/console v1.0.3/go.mod h1:7LqA/THxQ86k76b8c/EMSiaJ3h1eZkMkXar0TQ1gf3U= github.com/containerd/continuity v0.0.0-20190827140505-75bee3e2ccb6/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= github.com/containerd/continuity v0.1.0 h1:UFRRY5JemiAhPZrr/uE0n8fMTLcZsUvySPr1+D7pgr8= github.com/containerd/continuity v0.1.0/go.mod h1:ICJu0PwR54nI0yPEnJ6jcS+J7CZAUXrLh8lPo2knzsM= +github.com/containerd/continuity v0.2.2 h1:QSqfxcn8c+12slxwu00AtzXrsami0MJb/MQs9lOLHLA= +github.com/containerd/continuity v0.2.2/go.mod h1:pWygW9u7LtS1o4N/Tn0FoCFDIXZ7rxcMX7HX1Dmibvk= github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= @@ -151,15 +179,18 @@ github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfc github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.17 h1:QeVUsEDNrLBW4tMgZHvxy18sKtr6VI492kBhUfhDJNI= github.com/cyphar/filepath-securejoin v0.2.2/go.mod h1:FpkQEhXnPnOthhzymB7CGsFk2G9VLXONKD9G7QGMM+4= +github.com/cyphar/filepath-securejoin v0.2.3/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/denisenkom/go-mssqldb v0.0.0-20191124224453-732737034ffd/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU= +github.com/denisenkom/go-mssqldb v0.9.0 h1:RSohk2RsiZqLZ0zCjtfn3S4Gp4exhpBWHyQ7D0yGjAk= github.com/denisenkom/go-mssqldb v0.9.0/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU= github.com/denisenkom/go-mssqldb v0.10.0 h1:QykgLZBorFE95+gO3u9esLd0BmbvpWp0/waNNZfHBM8= github.com/denisenkom/go-mssqldb v0.10.0/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU= @@ -169,9 +200,14 @@ github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8 github.com/docker/cli v20.10.7+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= github.com/docker/cli v20.10.8+incompatible h1:/zO/6y9IOpcehE49yMRTV9ea0nBpb8OeqSskXLNfH1E= github.com/docker/cli v20.10.8+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= +github.com/docker/cli v20.10.11+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= +github.com/docker/cli v20.10.12+incompatible h1:lZlz0uzG+GH+c0plStMUdF/qk3ppmgnswpR5EbqzVGA= +github.com/docker/cli v20.10.12+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= github.com/docker/docker v20.10.7+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker v20.10.8+incompatible h1:RVqD337BgQicVCzYrrlhLDWhq6OAD2PJDUg2LsEUvKM= github.com/docker/docker v20.10.8+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker v20.10.12+incompatible h1:CEeNmFM0QZIsJCZKMkZx0ZcahTiewkrgiwfYD+dfl1U= +github.com/docker/docker v20.10.12+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= github.com/docker/go-units v0.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw= @@ -194,11 +230,14 @@ github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.m github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= +github.com/envoyproxy/go-control-plane v0.10.1/go.mod h1:AY7fTTXNdv/aJ2O5jwpxAPOWUZ7hQAEvzN5Pf27BkQQ= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/envoyproxy/protoc-gen-validate v0.6.2/go.mod h1:2t7qjJNvHPx8IjnBOzl9E9/baC+qXE/TeeyBRzgJDws= github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5 h1:Yzb9+7DPaBjB8zlTR87/ElzFsnQfuHnVUVqpZZIcV5Y= github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5/go.mod h1:a2zkGnVExMxdzMo3M0Hi/3sEU+cWnZpSni0O6/Yb/P0= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= +github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= github.com/fatih/set v0.2.1 h1:nn2CaJyknWE/6txyUDGwysr3G5QC6xWB/PtVjPBbeaA= github.com/fatih/set v0.2.1/go.mod h1:+RKtMCH+favT2+3YecHGxcc0b4KyVWA1QWWJUs4E0CI= github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4= @@ -214,6 +253,8 @@ github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= github.com/gin-gonic/gin v1.7.4 h1:QmUZXrvJ9qZ3GfWvQ+2wnW/1ePrTEJqPKMYEU3lD/DM= github.com/gin-gonic/gin v1.7.4/go.mod h1:jD2toBW3GZUr5UMcdrwQA10I7RuaFOl/SGeDjXkfUtY= +github.com/gin-gonic/gin v1.7.7 h1:3DoBmSbJbZAWqXJC3SLjAPfutPJJRN1U5pALB7EeTTs= +github.com/gin-gonic/gin v1.7.7/go.mod h1:axIBovoeJpVj8S3BwE0uPMTeReE4+AfFtqpqaZ1qq1U= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= @@ -235,17 +276,22 @@ github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4= github.com/go-playground/validator/v10 v10.9.0 h1:NgTtmN58D0m8+UuxtYmGztBJB7VnPgjj221I1QHci2A= github.com/go-playground/validator/v10 v10.9.0/go.mod h1:74x4gJWsvQexRdW8Pn3dXSGrTK4nAUsbPlLADvpJkos= +github.com/go-playground/validator/v10 v10.10.0 h1:I7mrTYv78z8k8VXa/qJlOlEXn/nBh+BF8dHX5nt/dr0= +github.com/go-playground/validator/v10 v10.10.0/go.mod h1:74x4gJWsvQexRdW8Pn3dXSGrTK4nAUsbPlLADvpJkos= github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE= github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/godbus/dbus/v5 v5.0.6/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gofrs/flock v0.8.0/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/gofrs/uuid v4.1.0+incompatible h1:sIa2eCvUTwgjbqXrPLfNwUf9S3i3mpH1O1atV+iL/Wk= github.com/gofrs/uuid v4.1.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= +github.com/gofrs/uuid v4.2.0+incompatible h1:yyYWMnhkhrKwwr8gAOcOCYxOOscHgDS9yZgBrnJfGa0= +github.com/gofrs/uuid v4.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= @@ -262,6 +308,7 @@ github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4er github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= @@ -306,6 +353,7 @@ github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.7 h1:81/ik6ipDQS2aGcBfIN5dHDB36BwrStyeAQquSYCV4o= github.com/google/go-github v17.0.0+incompatible h1:N0LgJ1j65A7kfXrZnUDaYCs/Sf4rEjNlfyDHW9dolSY= github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ= github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= @@ -324,6 +372,7 @@ github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hf github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= @@ -338,8 +387,12 @@ github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+ github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0= +github.com/googleapis/gax-go/v2 v2.1.1/go.mod h1:hddJymUZASv3XPyGkUpKj8pPO47Rmb0eJc8R6ouapiM= +github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= github.com/gookit/color v1.4.2 h1:tXy44JFSFkKnELV6WaMo/lLfu/meqITX3iAV52do7lk= github.com/gookit/color v1.4.2/go.mod h1:fqRyamkC1W8uxl+lxCQxOT09l/vYfZ+QeiX3rKQHCoQ= +github.com/gookit/color v1.5.0 h1:1Opow3+BWDwqor78DcJkJCIwnkviFi+rrOANki9BUFw= +github.com/gookit/color v1.5.0/go.mod h1:43aQb+Zerm/BWh2GnrgOQm7ffz7tvQXEKV6BFMl7wAo= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gordonklaus/ineffassign v0.0.0-20200309095847-7953dde2c7bf/go.mod h1:cuNKsD1zp2v6XfE/orVX2QE1LC+i254ceGcVeDT3pTU= github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= @@ -357,24 +410,33 @@ github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgf github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/grpc-ecosystem/grpc-gateway v1.14.6/go.mod h1:zdiPV4Yse/1gnckTHtghG4GkDEdKCRJduHpTxT3/jcw= +github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= github.com/grpc-ecosystem/grpc-gateway/v2 v2.3.0/go.mod h1:d2gYTOTUQklu06xp0AJYYmRdTVU1VKrqhkYfYag2L08= github.com/grpc-ecosystem/grpc-gateway/v2 v2.4.0/go.mod h1:IOyTYjcIO0rkmnGBfJTL0NJ11exy/Tc2QEuv7hCXp24= github.com/grpc-ecosystem/grpc-gateway/v2 v2.6.0 h1:rgxjzoDmDXw5q8HONgyHhBas4to0/XWRo/gPpJhsUNQ= github.com/grpc-ecosystem/grpc-gateway/v2 v2.6.0/go.mod h1:qrJPVzv9YlhsrxJc3P/Q85nr0w1lIRikTl4JlhdDH5w= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.3 h1:I8MsauTJQXZ8df8qJvEln0kYNc3bSapuaSsEsnFdEFU= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.3/go.mod h1:lZdb/YAJUSj9OqrCHs2ihjtoO3+xK3G53wTYXFWRGDo= github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= github.com/hashicorp/consul/api v1.3.0/go.mod h1:MmDNSzIMUjNpY/mQ398R4bk2FnqQLoPndWW5VkKPlCE= github.com/hashicorp/consul/api v1.10.1/go.mod h1:XjsvQN+RJGWI2TWy1/kqaE16HrR2J/FWgkYjdZQsX9M= +github.com/hashicorp/consul/api v1.11.0/go.mod h1:XjsvQN+RJGWI2TWy1/kqaE16HrR2J/FWgkYjdZQsX9M= github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= github.com/hashicorp/consul/sdk v0.3.0/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= github.com/hashicorp/consul/sdk v0.8.0/go.mod h1:GBvyrGALthsZObzUGsfgHZQDXjg4lOjagTIwIR1vPms= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= github.com/hashicorp/go-hclog v0.12.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= +github.com/hashicorp/go-hclog v1.0.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-immutable-radix v1.3.1/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA= +github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs= github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= @@ -383,22 +445,29 @@ github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/b github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-version v1.2.0 h1:3vNe/fWF5CBgRIguda1meWhsZHy3m8gCJ5wx+dIzX/E= github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/hashicorp/go-version v1.4.0 h1:aAQzgqIrRKRa7w75CKpbBxYsmUoPjzVm1W59ca1L0J4= +github.com/hashicorp/go-version v1.4.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= github.com/hashicorp/mdns v1.0.1/go.mod h1:4gW7WsVCke5TE7EPeYliwHlRUyBtfCwuFwuMg2DmyNY= +github.com/hashicorp/mdns v1.0.4/go.mod h1:mtBihi+LeNXGtG8L9dX59gAEa12BDtBQSp4v/YAJqrc= github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= github.com/hashicorp/memberlist v0.2.2/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE= +github.com/hashicorp/memberlist v0.3.0/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE= github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= github.com/hashicorp/serf v0.9.5/go.mod h1:UWDWwZeL5cuWDJdl0C6wrvrUwEqtQ4ZKBKKENpqIUyk= +github.com/hashicorp/serf v0.9.6/go.mod h1:TXZNMjZQijwlDvp+r0b63xZ45H7JmCmgg4gpTwn9UV4= github.com/hinshun/vt10x v0.0.0-20180616224451-1954e6464174 h1:WlZsjVhE8Af9IcZDGgJGQpNflI3+MJSBhsgT5PCtzBQ= github.com/hinshun/vt10x v0.0.0-20180616224451-1954e6464174/go.mod h1:DqJ97dSdRW1W22yXSB90986pcOyQ7r45iio1KN2ez1A= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg= +github.com/iancoleman/strcase v0.2.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU= @@ -409,6 +478,9 @@ github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod github.com/infobloxopen/atlas-app-toolkit v0.24.1-0.20210416193901-4c7518b07e08/go.mod h1:9BTHnpff654rY1J8KxSUOLJ+ZUDn2Vi3mmk26gQDo1M= github.com/infobloxopen/protoc-gen-gorm v1.0.1 h1:IjvQ02gZSll+CjpWjxkLqrpxnvKAGfs5dXRJEpfZx2s= github.com/infobloxopen/protoc-gen-gorm v1.0.1/go.mod h1:gTu86stnDQXwcNqLG9WNJfl3IPUIhxmGNqJ8z4826uo= +github.com/infobloxopen/protoc-gen-gorm v1.1.0 h1:l6JKEkqMTFbtoGIfQmh/aOy7KfljgX4ql772LtG4kas= +github.com/infobloxopen/protoc-gen-gorm v1.1.0/go.mod h1:ohzLmmFMWQztw2RBHunfjKSCjTPUW4JvbgU1Mdazwxg= +github.com/jackc/chunkreader v1.0.0 h1:4s39bBR8ByfqH+DKm8rQA3E1LHZWB9XWcrz8fqaZbe0= github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo= github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= github.com/jackc/chunkreader/v2 v2.0.1 h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki7MGG8= @@ -425,6 +497,9 @@ github.com/jackc/pgconn v1.9.0/go.mod h1:YctiPyvzfU11JFxoXokUOOKQXQmDMoJL9vJzHH8 github.com/jackc/pgconn v1.9.1-0.20210724152538-d89c8390a530/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI= github.com/jackc/pgconn v1.10.0 h1:4EYhlDVEMsJ30nNj0mmgwIUXoq7e9sMJrVC2ED6QlCU= github.com/jackc/pgconn v1.10.0/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI= +github.com/jackc/pgconn v1.10.1/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI= +github.com/jackc/pgconn v1.11.0 h1:HiHArx4yFbwl91X3qqIHtUFoiIfLNJXCQRsnzkiwwaQ= +github.com/jackc/pgconn v1.11.0/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI= github.com/jackc/pgio v1.0.0 h1:g12B9UwVnzGhueNavwioyEEpAmqMe1E/BN9ES+8ovkE= github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8= github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE= @@ -433,6 +508,7 @@ github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65 h1:DadwsjnMwFjfWc9y5W github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65/go.mod h1:5R2h2EEX+qri8jOWMbJCtaPWkrrNc7OHwsp2TCqp7ak= github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= +github.com/jackc/pgproto3 v1.1.0 h1:FYYE4yRw+AgI8wXIinMlNjBbp/UitDJwfj5LqqewP1A= github.com/jackc/pgproto3 v1.1.0/go.mod h1:eR5FA3leWg7p9aeAqi37XOTgTIbkABlvcPB3E5rlc78= github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190420180111-c116219b62db/go.mod h1:bhq50y+xrl9n5mRYyCBFKkpRVTLYJVWeCc+mEAI3yXA= github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190609003834-432c2951c711/go.mod h1:uH0AWtUmuShn0bcesswc4aBTWGvw0cAxIJp+6OB//Wg= @@ -442,6 +518,8 @@ github.com/jackc/pgproto3/v2 v2.0.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwX github.com/jackc/pgproto3/v2 v2.0.6/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= github.com/jackc/pgproto3/v2 v2.1.1 h1:7PQ/4gLoqnl87ZxL7xjO0DR5gYuviDCZxQJsUlFW1eI= github.com/jackc/pgproto3/v2 v2.1.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= +github.com/jackc/pgproto3/v2 v2.2.0 h1:r7JypeP2D3onoQTCxWdTpCtJ4D+qpKr0TxvoyMhZ5ns= +github.com/jackc/pgproto3/v2 v2.2.0/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= github.com/jackc/pgservicefile v0.0.0-20200307190119-3430c5407db8/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E= github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b h1:C8S2+VttkHFdOOCXJe+YGfa4vHYwlt4Zx+IVXQ97jYg= github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E= @@ -455,6 +533,9 @@ github.com/jackc/pgtype v1.7.0/go.mod h1:ZnHF+rMePVqDKaOfJVI4Q8IVvAQMryDlDkZnKOI github.com/jackc/pgtype v1.8.1-0.20210724151600-32e20a603178/go.mod h1:C516IlIV9NKqfsMCXTdChteoXmwgUceqaLfjg2e3NlM= github.com/jackc/pgtype v1.8.1 h1:9k0IXtdJXHJbyAWQgbWr1lU+MEhPXZz6RIXxfR5oxXs= github.com/jackc/pgtype v1.8.1/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4= +github.com/jackc/pgtype v1.9.0/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4= +github.com/jackc/pgtype v1.10.0 h1:ILnBWrRMSXGczYvmkYD6PsYyVFUNLTnIUJHHDLmqk38= +github.com/jackc/pgtype v1.10.0/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4= github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y= github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM= github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc= @@ -465,11 +546,16 @@ github.com/jackc/pgx/v4 v4.11.0/go.mod h1:i62xJgdrtVDsnL3U8ekyrQXEwGNTRoG7/8r+CI github.com/jackc/pgx/v4 v4.12.1-0.20210724153913-640aa07df17c/go.mod h1:1QD0+tgSXP7iUjYm9C1NxKhny7lq6ee99u/z+IHFcgs= github.com/jackc/pgx/v4 v4.13.0 h1:JCjhT5vmhMAf/YwBHLvrBn4OGdIQBiFG6ym8Zmdx570= github.com/jackc/pgx/v4 v4.13.0/go.mod h1:9P4X524sErlaxj0XSGZk7s+LD0eOyu1ZDUrrpznYDF0= +github.com/jackc/pgx/v4 v4.14.0/go.mod h1:jT3ibf/A0ZVCp89rtCIN0zCJxcE74ypROmHEZYsG/j8= +github.com/jackc/pgx/v4 v4.15.0 h1:B7dTkXsdILD3MF987WGGCcg+tvLW6bZJdEcqVFeU//w= +github.com/jackc/pgx/v4 v4.15.0/go.mod h1:D/zyOyXiaM1TmVWnOM18p0xdDtdakRBa0RsVGI3U3bw= github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= github.com/jackc/puddle v1.1.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= github.com/jackc/puddle v1.1.1/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= +github.com/jackc/puddle v1.2.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= +github.com/jackc/puddle v1.2.1/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= github.com/jhump/protoreflect v1.8.1/go.mod h1:7GcYQDdMU/O/BBrl/cX6PNHpXh6cenjd8pneu5yW7Tg= github.com/jinzhu/gorm v1.9.16 h1:+IyIjPEABKRpsu/F8OvDPy9fyQlgsg2luMV2ZIH5i5o= github.com/jinzhu/gorm v1.9.16/go.mod h1:G3LB3wezTOWM2ITLzPxEXgSkOXAntiLHS7UdBefADcs= @@ -479,6 +565,9 @@ github.com/jinzhu/now v1.0.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/ github.com/jinzhu/now v1.1.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= github.com/jinzhu/now v1.1.2 h1:eVKgfIdy9b6zbWBMgFpfDPoAMifwSZagU9HmEU6zgiI= github.com/jinzhu/now v1.1.2/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= +github.com/jinzhu/now v1.1.3/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= +github.com/jinzhu/now v1.1.4 h1:tHnRBy1i5F2Dh8BAFxqFzxKqqvezXrL2OW1TnX+Mlas= +github.com/jinzhu/now v1.1.4/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= @@ -503,6 +592,9 @@ github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+o github.com/klauspost/compress v1.11.7/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= github.com/klauspost/compress v1.13.6 h1:P76CopJELS0TiO2mebmnzgWaajssP/EszplttgQxcgc= github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= +github.com/klauspost/compress v1.14.2 h1:S0OHlFk/Gbon/yauFJ4FfJJF5V0fc5HbBTJazi28pRw= +github.com/klauspost/compress v1.14.2/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= +github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/pgzip v1.2.5/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= @@ -536,6 +628,7 @@ github.com/lib/pq v1.10.3 h1:v9QZf2Sn6AmjXtQeFpdoq/eaNtYP6IN+7lcrygsIAtg= github.com/lib/pq v1.10.3/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM= github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4= +github.com/lyft/protoc-gen-star v0.5.3/go.mod h1:V0xaHgaf5oCCqmcxYcWiDfTiKsZsRc87/1qhoTACD8w= github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ= github.com/magefile/mage v1.10.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= @@ -547,6 +640,7 @@ github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcncea github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40= github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= @@ -568,12 +662,18 @@ github.com/mattn/go-sqlite3 v1.14.5/go.mod h1:WVKg1VTActs4Qso6iwGbiFih2UIHo0ENGw github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= github.com/mattn/go-sqlite3 v1.14.8 h1:gDp86IdQsN/xWjIEmr9MF6o9mpksUgh0fu+9ByFxzIU= github.com/mattn/go-sqlite3 v1.14.8/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= +github.com/mattn/go-sqlite3 v1.14.9/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= +github.com/mattn/go-sqlite3 v1.14.11 h1:gt+cp9c0XGqe9S/wAHTL3n/7MqY+siPWgWJgqdsFrzQ= +github.com/mattn/go-sqlite3 v1.14.11/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b h1:j7+1HpAFS1zy5+Q4qx1fWh90gTKwiN4QCGoY9TWyyO4= github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= +github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d h1:5PJl274Y63IEHC+7izoQE9x6ikvDFZS2mDVS3drnohI= +github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso= +github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI= github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= github.com/mitchellh/cli v1.1.0/go.mod h1:xcISNoH86gajksDmfB23e/pu+B+GeFRMYmoHXxx3xhI= github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= @@ -588,6 +688,7 @@ github.com/mitchellh/mapstructure v1.4.2/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RR github.com/mitchellh/mapstructure v1.4.3 h1:OVowDSCllw/YjdLkam3/sm7wEtOy59d8ndGgCcyj8cs= github.com/mitchellh/mapstructure v1.4.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/moby/sys/mountinfo v0.4.1/go.mod h1:rEr8tzG/lsIZHBtN/JjGG+LMYx9eXgW2JI+6q0qou+A= +github.com/moby/sys/mountinfo v0.5.0/go.mod h1:3bMD3Rg+zkqx8MRYPi7Pyb0Ie97QEBmdxbhnCLlSvSU= github.com/moby/term v0.0.0-20201216013528-df9cb8a40635/go.mod h1:FBS0z0QWA44HXygs7VXDUOGoN/1TV3RuWkLO04am3wc= github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6 h1:dcztxKSvZ4Id8iPpHERQBbIJfabdt4wUm5qy3wOL2Zc= github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6/go.mod h1:E2VnQOmVuvZB6UYnnDB0qG5Nq/1tD9acaOpo6xmt0Kw= @@ -624,10 +725,14 @@ github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zM github.com/opencontainers/image-spec v1.0.2 h1:9yCKha/T5XdGtO0q9Q9a6T5NUCsTn/DrBg0D7ufOcFM= github.com/opencontainers/image-spec v1.0.2/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= github.com/opencontainers/runc v1.0.0-rc9/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= +github.com/opencontainers/runc v1.0.2/go.mod h1:aTaHFFwQXuA71CiyxOdFFIorAoemI04suvGRQFzWTD0= github.com/opencontainers/runc v1.0.3 h1:1hbqejyQWCJBvtKAfdO0b1FmaEf2z/bxnjqbARass5k= github.com/opencontainers/runc v1.0.3/go.mod h1:aTaHFFwQXuA71CiyxOdFFIorAoemI04suvGRQFzWTD0= +github.com/opencontainers/runc v1.1.0 h1:O9+X96OcDjkmmZyfaG996kV7yq8HsoU2h1XRRQcefG8= +github.com/opencontainers/runc v1.1.0/go.mod h1:Tj1hFw6eFWp/o33uxGf5yF2BX5yz2Z6iptFpuvbbKqc= github.com/opencontainers/runtime-spec v1.0.3-0.20210326190908-1c3f411f0417/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= github.com/opencontainers/selinux v1.8.2/go.mod h1:MUIHuUEvKB1wtJjQdOyYRgOnLD2xAPP8dBsCoU0KuF8= +github.com/opencontainers/selinux v1.10.0/go.mod h1:2i0OySw99QjzBBQByd1Gr9gSjvuho1lHsJxIJ3gGbJI= github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492/go.mod h1:Ngi6UdF0k5OKD5t5wlmGhe/EDKPoUM3BXZSSfIuJbis= github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74= github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= @@ -638,8 +743,11 @@ github.com/openzipkin/zipkin-go v0.2.1/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnh github.com/openzipkin/zipkin-go v0.2.2/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= github.com/ory/dockertest/v3 v3.7.0 h1:Bijzonc69Ont3OU0a3TWKJ1Rzlh3TsDXP1JrTAkSmsM= github.com/ory/dockertest/v3 v3.7.0/go.mod h1:PvCCgnP7AfBZeVrzwiUTjZx/IUXlGLC1zQlUQrLIlUE= +github.com/ory/dockertest/v3 v3.8.1 h1:vU/8d1We4qIad2YM0kOwRVtnyue7ExvacPiw1yDm17g= +github.com/ory/dockertest/v3 v3.8.1/go.mod h1:wSRQ3wmkz+uSARYMk7kVJFDBGm8x5gSxIhI7NDc+BAQ= github.com/pact-foundation/pact-go v1.0.4/go.mod h1:uExwJY4kCzNPcHRj+hCR/HBbOOIwwtUjcrb0b5/5kLM= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= +github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= @@ -660,6 +768,7 @@ github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA= github.com/pkg/profile v1.5.0/go.mod h1:qBsxPvzyUincmltOk6iyRVxHYg4adc0OFOv72ZdLa18= github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI= +github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= @@ -669,9 +778,12 @@ github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= github.com/prometheus/client_golang v1.3.0/go.mod h1:hJaj2vgQTGQmVCsAACORcieXFeDPbaTKGT+JTgUa3og= +github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU= github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= github.com/prometheus/client_golang v1.11.0 h1:HNkLOAEQMIDv/K+04rukrLx6ch7msSRwf3/SASFAGtQ= github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= +github.com/prometheus/client_golang v1.12.1 h1:ZiaPsmm9uiBeaSMRznKsCDNtPCS0T3JVDGF+06gjBzk= +github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= @@ -684,6 +796,7 @@ github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y8 github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA= +github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4= github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= github.com/prometheus/common v0.32.1 h1:hWIdL3N2HoUx3B8j3YN9mWor0qhY/NlEKZEaXxuIRh4= @@ -702,6 +815,10 @@ github.com/pterm/pterm v0.12.27/go.mod h1:PhQ89w4i95rhgE+xedAoqous6K9X+r6aSOI2eF github.com/pterm/pterm v0.12.29/go.mod h1:WI3qxgvoQFFGKGjGnJR849gU0TsEOvKn5Q8LlY1U7lg= github.com/pterm/pterm v0.12.30 h1:ZfXzqtOJVKZ2Uhd+L5o6jmbO44PH3Mee4mxq303nh1Y= github.com/pterm/pterm v0.12.30/go.mod h1:MOqLIyMOgmTDz9yorcYbcw+HsgoZo3BQfg2wtl3HEFE= +github.com/pterm/pterm v0.12.31/go.mod h1:32ZAWZVXD7ZfG0s8qqHXePte42kdz8ECtRyEejaWgXU= +github.com/pterm/pterm v0.12.33/go.mod h1:x+h2uL+n7CP/rel9+bImHD5lF3nM9vJj80k9ybiiTTE= +github.com/pterm/pterm v0.12.36 h1:Ui5zZj7xA8lXR0CxWXlKGCQMW1cZVUMOS8jEXs6ur/g= +github.com/pterm/pterm v0.12.36/go.mod h1:NjiL09hFhT/vWjQHSj1athJpx6H8cjpHXNAK5bUw8T8= github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= @@ -712,19 +829,26 @@ github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTE github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= github.com/rogpeppe/go-internal v1.8.1-0.20211023094830-115ce09fd6b4 h1:Ha8xCaq6ln1a+R91Km45Oq6lPXj2Mla6CRJYcuV2h1w= github.com/rogpeppe/go-internal v1.8.1-0.20211023094830-115ce09fd6b4/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4nPKWu0nJ5d+o= +github.com/rogpeppe/go-internal v1.8.1 h1:geMPLpDpQOgVyCg5z5GoRwLHepNdb71NXb67XFkP+Eg= +github.com/rogpeppe/go-internal v1.8.1/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4nPKWu0nJ5d+o= github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= github.com/rs/xid v1.3.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU= github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc= github.com/rs/zerolog v1.26.0 h1:ORM4ibhEZeTeQlCojCK2kPz1ogAY4bGs4tD+SaAdGaE= github.com/rs/zerolog v1.26.0/go.mod h1:yBiM87lvSqX8h0Ww4sdzNSkVYZ8dL2xjZJG1lAuGZEo= +github.com/rs/zerolog v1.26.1 h1:/ihwxqH+4z8UxyI70wM1z9yCvkWcfz/a3mj48k/Zngc= +github.com/rs/zerolog v1.26.1/go.mod h1:/wSSJWX7lVrsOwlbyTRSOJvqRlc+WjWlfes+CiJ+tmc= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/sagikazarmark/crypt v0.1.0/go.mod h1:B/mN0msZuINBtQ1zZLEQcegFJJf9vnYIR88KRMEuODE= +github.com/sagikazarmark/crypt v0.3.0/go.mod h1:uD/D+6UF4SrIR1uGEv7bBNkNqLGqUr43MRiaGWX1Nig= github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E= github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/seccomp/libseccomp-golang v0.9.1/go.mod h1:GbW5+tmTXfcxTToHLXlScSlAvWlF4P2Ca7zGrPiEpWo= +github.com/seccomp/libseccomp-golang v0.9.2-0.20210429002308-3879420cc921/go.mod h1:JA8cRccbGaA1s33RQf7Y1+q9gHmZX1yB/z9WDN1C6fg= github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4= github.com/shopspring/decimal v0.0.0-20200227202807-02e2044944cc/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ= @@ -746,8 +870,11 @@ github.com/soheilhy/cmux v0.1.5/go.mod h1:T7TcVDs9LWfQgPlPsdngu6I6QIoyIFZDDC6sNE github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= +github.com/spf13/afero v1.3.3/go.mod h1:5KUK8ByomD5Ti5Artl0RtHeI5pTF7MIDuXL3yY520V4= github.com/spf13/afero v1.6.0 h1:xoax2sJ2DT8S8xA2paPFjDCScCNeWsg75VG0DLRreiY= github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I= +github.com/spf13/afero v1.8.1 h1:izYHOT71f9iZ7iq37Uqjael60/vYC6vMtzedudZ0zEk= +github.com/spf13/afero v1.8.1/go.mod h1:CtAatgMJh6bJEIs48Ay/FOnkljP3WeGUG0MC1RfAqwo= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cast v1.4.1 h1:s0hze+J0196ZfEMTs80N7UlFt0BDuQ7Q+JDnHiMWKdA= @@ -757,6 +884,8 @@ github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHN github.com/spf13/cobra v1.0.1-0.20201006035406-b97b5ead31f7/go.mod h1:yk5b0mALVusDL5fMM6Rd1wgnoO5jUPhwsQ6LQAJTidQ= github.com/spf13/cobra v1.2.1 h1:+KmjbUw1hriSNMF55oPrkZcb27aECyrj8V2ytv7kWDw= github.com/spf13/cobra v1.2.1/go.mod h1:ExllRjgxM/piMAM+3tAZvg8fsklGAf3tPfi+i8t68Nk= +github.com/spf13/cobra v1.3.0 h1:R7cSvGu+Vv+qX0gW5R/85dx2kmmJT5z5NM8ifdYjdn0= +github.com/spf13/cobra v1.3.0/go.mod h1:BrRVncBjOJa/eUcVVm9CE+oC6as8k+VYr4NY7WCi9V4= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= @@ -769,6 +898,9 @@ github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5q github.com/spf13/viper v1.8.1/go.mod h1:o0Pch8wJ9BVSWGQMbra6iw0oQ5oktSIBaujf1rJH9Ns= github.com/spf13/viper v1.9.0 h1:yR6EXjTp0y0cLN8OZg1CRZmOBdI88UcGkhgyJhu6nZk= github.com/spf13/viper v1.9.0/go.mod h1:+i6ajR7OX2XaiBkrcZJFK21htRk7eDeLg7+O6bhUPP4= +github.com/spf13/viper v1.10.0/go.mod h1:SoyBPwAtKDzypXNDFKN5kzH7ppppbGZtls1UpIy5AsM= +github.com/spf13/viper v1.10.1 h1:nuJZuYpG7gTj/XqiUwg8bA0cp1+M2mC3J4g5luUYBKk= +github.com/spf13/viper v1.10.1/go.mod h1:IGlFPqhNAPKRxohIzWpI5QEy4kuI7tcl5WvR+8qy1rU= github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI= @@ -789,13 +921,18 @@ github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69 github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= github.com/tailscale/hujson v0.0.0-20211105212140-3a0adc019d83 h1:f7nwzdAHTUUOJjHZuDvLz9CEAlUM228amCRvwzlPvsA= github.com/tailscale/hujson v0.0.0-20211105212140-3a0adc019d83/go.mod h1:iTDXJsA6A2wNNjurgic2rk+is6uzU4U2NLm4T+edr6M= +github.com/tailscale/hujson v0.0.0-20211215203138-ffd971c5f362 h1:xx7EMpWIKUrMMg+QanclF7bj8QTH/XYdQb/eplkmkgw= +github.com/tailscale/hujson v0.0.0-20211215203138-ffd971c5f362/go.mod h1:iTDXJsA6A2wNNjurgic2rk+is6uzU4U2NLm4T+edr6M= github.com/tcnksm/go-latest v0.0.0-20170313132115-e3007ae9052e h1:IWllFTiDjjLIf2oeKxpIUmtiDV5sn71VgeQgg6vcE7k= github.com/tcnksm/go-latest v0.0.0-20170313132115-e3007ae9052e/go.mod h1:d7u6HkTYKSv5m6MCKkOQlHwaShTMl3HjqSGW3XtVhXM= github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= +github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= +github.com/tv42/httpunix v0.0.0-20191220191345-2ba4b9c3382c/go.mod h1:hzIxponao9Kjc7aWznkXaL4U4TWaDSs8zcsY4Ka08nM= github.com/twitchtv/twirp v7.1.0+incompatible/go.mod h1:RRJoFSAmTEh2weEqWtpPE3vFK5YBhA6bqp2l1kfCC5A= github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= +github.com/ugorji/go v1.2.6 h1:tGiWC9HENWE2tqYycIqFTNorMmFRVhNwCpDOpWqnk8E= github.com/ugorji/go v1.2.6/go.mod h1:anCg0y61KIhDlPZmnH+so+RQbysYVyDko0IMgJv0Nn0= github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= github.com/ugorji/go/codec v1.2.6 h1:7kbGefxLoDBuYXOms4yD7223OpNMMPNPZxXk5TvFcyQ= @@ -828,8 +965,11 @@ go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg= go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs= +go.etcd.io/etcd/api/v3 v3.5.1/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs= go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= +go.etcd.io/etcd/client/pkg/v3 v3.5.1/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ= +go.etcd.io/etcd/client/v2 v2.305.1/go.mod h1:pMEacxZW7o8pg4CrFE7pquyCJJzZvkvdD2RibOCCCGs= go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= @@ -880,11 +1020,17 @@ golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= +golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20211202192323-5770296d904e h1:MUP6MR3rJ7Gk9LEia0LP2ytiH6MuCfs7qYz+47jGdD8= golang.org/x/crypto v0.0.0-20211202192323-5770296d904e/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.0.0-20211215165025-cf75a172585e/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= +golang.org/x/crypto v0.0.0-20220210151621-f4118a5b28e2 h1:XdAboW3BNMv9ocSCOk/u1MFioZGzCNkiJZ19v9Oe3Ig= +golang.org/x/crypto v0.0.0-20220210151621-f4118a5b28e2/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -921,6 +1067,7 @@ golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.5.0/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -971,11 +1118,16 @@ golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= +golang.org/x/net v0.0.0-20210410081132-afb366fc7cd1/go.mod h1:9tjilg8BloeKEkVJvy7fQ90B1CfIiPueXVOjqfkSzI8= golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211205041911-012df41ee64c h1:7SfqwP5fxEtl/P02w5IhKc86ziJ+A25yFrkVgoy2FT8= golang.org/x/net v0.0.0-20211205041911-012df41ee64c/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220114011407-0dd24b26b47d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd h1:O7DYs+zxREGLKzKoMQrtrEacpb0ZVXA5rIwylE2Xchk= +golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -993,6 +1145,7 @@ golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20211005180243-6b3c2da341f1/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 h1:RerP+noqYHUQ8CMRcPlC2nvTa4dcBIjegkuWdcUDuqg= golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -1038,6 +1191,7 @@ golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20191113165036-4c7a9d0fe056/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191115151921-52ab43148777/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191210023423-ac6580df4449/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191220142924-d4481acd189f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1069,12 +1223,15 @@ golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210426230700-d19ff857e887/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -1085,10 +1242,21 @@ golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210816183151-1e6c022a8912/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210906170528-6f6e22806c34/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211013075003-97ac67df715c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211116061358-0a5406a5449c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211205182925-97ca703d548d h1:FjkYO/PPp4Wi0EAUOVLxePm7qVW4r4ctbWpURyuOD0E= golang.org/x/sys v0.0.0-20211205182925-97ca703d548d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220209214540-3681064d5158 h1:rm+CHSpPEEW2IsXUib1ThaHIjuBVZjxNgSKmBLFfD4c= +golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= @@ -1170,6 +1338,7 @@ golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4f golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= @@ -1213,7 +1382,12 @@ google.golang.org/api v0.48.0/go.mod h1:71Pr1vy+TAZRPkPs/xlCf5SsU8WjuAWv1Pfjbtuk google.golang.org/api v0.50.0/go.mod h1:4bNT5pAuq5ji4SRZm+5QIkjny9JAyVD/3gaSihNefaw= google.golang.org/api v0.51.0/go.mod h1:t4HdrdoNgyN5cbEfm7Lum0lcLDLiise1F8qDKX00sOU= google.golang.org/api v0.54.0/go.mod h1:7C4bFFOvVDGXjfDTAsgGwDgAxRDeQ4X8NvUedIt6z3k= +google.golang.org/api v0.55.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE= google.golang.org/api v0.56.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE= +google.golang.org/api v0.57.0/go.mod h1:dVPlbZyBo2/OjBpmvNdpn2GRm6rPy75jyU7bmhdrMgI= +google.golang.org/api v0.59.0/go.mod h1:sT2boj7M9YJxZzgeZqXogmhfmRWDtPzT31xkieUbuZU= +google.golang.org/api v0.61.0/go.mod h1:xQRti5UdCmoCEqFxcz93fTl338AVqDgyaDRuOZ3hg9I= +google.golang.org/api v0.62.0/go.mod h1:dKmwPCydfsad4qCH08MSdgWjfHOyfpd4VtDGgRFdavw= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= @@ -1262,9 +1436,11 @@ google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6D google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210207032614-bba0dbe2a9ea/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210224155714-063164c882e6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= @@ -1282,9 +1458,22 @@ google.golang.org/genproto v0.0.0-20210805201207-89edb61ffb67/go.mod h1:ob2IJxKr google.golang.org/genproto v0.0.0-20210813162853-db860fec028c/go.mod h1:cFeNkxwySK631ADgubI+/XFU/xp8FD5KIVV4rj8UC5w= google.golang.org/genproto v0.0.0-20210821163610-241b8fcbd6c8/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= google.golang.org/genproto v0.0.0-20210828152312-66f60bf46e71/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210831024726-fe130286e0e2/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= google.golang.org/genproto v0.0.0-20210903162649-d08c68adba83/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210909211513-a8c4777a87af/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210924002016-3dee208752a0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20211008145708-270636b82663/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20211028162531-8db9c33dc351/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= google.golang.org/genproto v0.0.0-20211104193956-4c6863e31247 h1:ZONpjmFT5e+I/0/xE3XXbG5OIvX2hRYzol04MhKBl2E= google.golang.org/genproto v0.0.0-20211104193956-4c6863e31247/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20211118181313-81c1377c94b1/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20211129164237-f09f9a12af12/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20211203200212-54befc351ae9/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20211206160659-862468c7d6e0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20220118154757-00ab72f36ad5/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20220210181026-6fee9acbd336 h1:RK2ysGpQApbI6U7xn+ROT2rrm08lE/t8AcGqG8XI1CY= +google.golang.org/genproto v0.0.0-20220210181026-6fee9acbd336/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.0/go.mod h1:chYK+tFQF0nDUGJgXMSgLCQk3phJEuONr2DCgLDdAQM= @@ -1316,11 +1505,17 @@ google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQ google.golang.org/grpc v1.39.0/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= google.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= +google.golang.org/grpc v1.40.1/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= google.golang.org/grpc v1.42.0 h1:XT2/MFpuPFsEX2fWh3YQtHkZ+WYZFQRfaUgLZYj/p6A= google.golang.org/grpc v1.42.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= +google.golang.org/grpc v1.43.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= +google.golang.org/grpc v1.44.0 h1:weqSxi/TMs1SqFRMHCtBgXRs8k3X39QIDEZ0pRcttUg= +google.golang.org/grpc v1.44.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.0.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0 h1:M1YKkFIboKNieVO5DLUEVzQfGwJD30Nv2jfUgzb5UcE= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= +google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.2.0 h1:TLkBREm4nIsEcexnCjgQd5GQWaHcqMzwQV0TX9pq8S0= +google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.2.0/go.mod h1:DNq5QpG7LJqD2AamLZ7zvKE0DEpVl2BSEVjFycAAjRY= google.golang.org/grpc/examples v0.0.0-20210309220351-d5b628860d4e/go.mod h1:Ly7ZA/ARzg8fnPU9TyZIxoz33sEUuWX7txiqs8lPTgE= google.golang.org/grpc/examples v0.0.0-20210601155443-8bdcb4c9ab8d/go.mod h1:bF8wuZSAZTcbF7ZPKrDI/qY52toTP/yxLpRRY4Eu9Js= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= @@ -1355,6 +1550,8 @@ gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.63.2/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.66.2 h1:XfR1dOYubytKy4Shzc2LHrrGhU0lDCfDGG1yLPmpgsI= gopkg.in/ini.v1 v1.66.2/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/ini.v1 v1.66.4 h1:SsAcf+mM7mRZo2nJNGt8mZCjG8ZRaNGMURJw7BsIST4= +gopkg.in/ini.v1 v1.66.4/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= gopkg.in/square/go-jose.v2 v2.5.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= gopkg.in/square/go-jose.v2 v2.6.0 h1:NGk74WTnPKBNUhNzQX7PYcTLUjoq7mzKk2OKbvwk2iI= @@ -1376,14 +1573,23 @@ gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gorm.io/datatypes v1.0.2 h1:ChZ5VfWGB23qEr1kZosidvG9CF9HIczwoxLhBS7Ebs4= gorm.io/datatypes v1.0.2/go.mod h1:1O1JVE4grFGcQTOGQbIBitiXUP6Sv84/KZU7eWeUv1k= +gorm.io/datatypes v1.0.5 h1:3vHCfg4Bz8SDx83zE+ASskF+g/j0kWrcKrY9jFUyAl0= +gorm.io/datatypes v1.0.5/go.mod h1:acG/OHGwod+1KrbwPL1t+aavb7jOBOETeyl5M8K5VQs= gorm.io/driver/mysql v1.1.2 h1:OofcyE2lga734MxwcCW9uB4mWNXMr50uaGRVwQL2B0M= gorm.io/driver/mysql v1.1.2/go.mod h1:4P/X9vSc3WTrhTLZ259cpFd6xKNYiSSdSZngkSBGIMM= +gorm.io/driver/mysql v1.2.2/go.mod h1:qsiz+XcAyMrS6QY+X3M9R6b/lKM1imKmcuK9kac5LTo= +gorm.io/driver/mysql v1.2.3 h1:cZqzlOfg5Kf1VIdLC1D9hT6Cy9BgxhExLj/2tIgUe7Y= +gorm.io/driver/mysql v1.2.3/go.mod h1:qsiz+XcAyMrS6QY+X3M9R6b/lKM1imKmcuK9kac5LTo= gorm.io/driver/postgres v1.1.0/go.mod h1:hXQIwafeRjJvUm+OMxcFWyswJ/vevcpPLlGocwAwuqw= gorm.io/driver/postgres v1.1.1 h1:tWLmqYCyaoh89fi7DhM6QggujrOnmfo3H98AzgNAAu0= gorm.io/driver/postgres v1.1.1/go.mod h1:tpe2xN7aCst1NUdYyWQyxPtnHC+Zfp6NEux9PXD1OU0= +gorm.io/driver/postgres v1.2.3 h1:f4t0TmNMy9gh3TU2PX+EppoA6YsgFnyq8Ojtddb42To= +gorm.io/driver/postgres v1.2.3/go.mod h1:pJV6RgYQPG47aM1f0QeOzFH9HxQc8JcmAgjRCgS0wjs= gorm.io/driver/sqlite v1.1.4/go.mod h1:mJCeTFr7+crvS+TRnWc5Z3UvwxUN1BGBLMrf5LA9DYw= gorm.io/driver/sqlite v1.1.5 h1:JU8G59VyKu1x1RMQgjefQnkZjDe9wHc1kARDZPu5dZs= gorm.io/driver/sqlite v1.1.5/go.mod h1:NpaYMcVKEh6vLJ47VP6T7Weieu4H1Drs3dGD/K6GrGc= +gorm.io/driver/sqlite v1.2.6 h1:SStaH/b+280M7C8vXeZLz/zo9cLQmIGwwj3cSj7p6l4= +gorm.io/driver/sqlite v1.2.6/go.mod h1:gyoX0vHiiwi0g49tv+x2E7l8ksauLK0U/gShcdUsjWY= gorm.io/driver/sqlserver v1.0.9 h1:P7Dm/BKqsrOjyhRSnLXvG2g1W/eJUgxdrdBwgJw3tEg= gorm.io/driver/sqlserver v1.0.9/go.mod h1:iBdxY2CepkTt9Q1r84RbZA1qCai300Qlp8kQf9qE9II= gorm.io/gorm v1.20.7/go.mod h1:0HFTzE/SqkGTzK6TlDPPQbAYCluiVvhzoA1+aVyzenw= @@ -1392,8 +1598,14 @@ gorm.io/gorm v1.21.12/go.mod h1:F+OptMscr0P2F2qU97WT1WimdH9GaQPoDW7AYd5i2Y0= gorm.io/gorm v1.21.14/go.mod h1:F+OptMscr0P2F2qU97WT1WimdH9GaQPoDW7AYd5i2Y0= gorm.io/gorm v1.21.15 h1:gAyaDoPw0lCyrSFWhBlahbUA1U4P5RViC1uIqoB+1Rk= gorm.io/gorm v1.21.15/go.mod h1:F+OptMscr0P2F2qU97WT1WimdH9GaQPoDW7AYd5i2Y0= +gorm.io/gorm v1.22.3/go.mod h1:F+OptMscr0P2F2qU97WT1WimdH9GaQPoDW7AYd5i2Y0= +gorm.io/gorm v1.22.4/go.mod h1:1aeVC+pe9ZmvKZban/gW4QPra7PRoTEssyc922qCAkk= +gorm.io/gorm v1.22.5 h1:lYREBgc02Be/5lSCTuysZZDb6ffL2qrat6fg9CFbvXU= +gorm.io/gorm v1.22.5/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk= gotest.tools/v3 v3.0.2 h1:kG1BFyqVHuQoVQiR1bWGnfz/fmHvvuiSPIV7rvl360E= gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk= +gotest.tools/v3 v3.0.3 h1:4AuOwCGf4lLR9u3YOe2awrHygurzhO/HeQ6laiA6Sx0= +gotest.tools/v3 v3.0.3/go.mod h1:Z7Lb0S5l+klDB31fvDQX8ss/FlKDxtlFlw3Oa8Ymbl8= honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= @@ -1408,6 +1620,8 @@ rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8 rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= +sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo= +sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8= sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU= tailscale.com v1.20.3 h1:C3g2AgmQaOi0YT5dAal9mslugPXMxwj0EXY7YfL2QrA= tailscale.com v1.20.3/go.mod h1:kjVy3ji2OH5lZhPLIIRacoY3CN4Bo3Yyb2mtoM8nfJ4= From 1d40de309519d010d1e7ba849a9a91b78933c80a Mon Sep 17 00:00:00 2001 From: Kristoffer Dalby Date: Fri, 11 Feb 2022 08:45:02 +0000 Subject: [PATCH 25/61] Update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6c74144e..330636f0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ **Changes**: - `ip_prefix` is now superseded by `ip_prefixes` in the configuration [#208](https://github.com/juanfont/headscale/pull/208) +- Upgrade `tailscale` (1.20.4) and other dependencies to latest [#314](https://github.com/juanfont/headscale/pull/314) **0.12.4 (2022-01-29):** From 1b47ddd5836ba07845e527e2c8b9ee8580658fb1 Mon Sep 17 00:00:00 2001 From: Kristoffer Dalby Date: Fri, 11 Feb 2022 18:36:53 +0000 Subject: [PATCH 26/61] Improve the windows client docs as per discord recommendations --- docs/images/windows-registry.png | Bin 0 -> 103356 bytes docs/windows-client.md | 16 ++++++++++++---- 2 files changed, 12 insertions(+), 4 deletions(-) create mode 100644 docs/images/windows-registry.png diff --git a/docs/images/windows-registry.png b/docs/images/windows-registry.png new file mode 100644 index 0000000000000000000000000000000000000000..1324ca6c4d8e8e486d46569aabce5e2acd870ebc GIT binary patch literal 103356 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�|5G0Evwu)ZTnT1_5}8J_AWW^smEmdF=x>! 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 n`YyK(CSGE(GbNzzS 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 uOEzyfEYW;X` to ensure Tailscale contacts the correct control server. +- `HKLM:\SOFTWARE\Tailscale IPN\UnattendedMode` must be set to `always` as a `string` type, to allow Tailscale to run properly in the background +- `HKLM:\SOFTWARE\Tailscale IPN\LoginURL` must be set to `` as a `string` type, to ensure Tailscale contacts the correct control server. + +![windows-registry](./images/windows-registry.png) The Tailscale Windows client has been observed to reset its configuration on logout/reboot and these two keys [resolves that issue](https://github.com/tailscale/tailscale/issues/2798). @@ -37,6 +39,12 @@ in your `headscale` output, turn on `DEBUG` logging and look for: 2022-02-11T00:59:29Z DBG Machine registration has expired. Sending a authurl to register machine=redacted ``` -This typically means that the register keys above was not set appropriatly. +This typically means that the registry keys above was not set appropriately. -Ensure they are set correctly, delete Tailscale APP_DATA folder and try to connect again. +To reset and try again, it is important to do the following: + +1. Ensure the registry keys from the previous guide is correctly set. +2. Shut down the Tailscale service (or the client running in the tray) +3. Delete Tailscale Application data folder, located at `C:\Users\\AppData\Local\Tailscale` and try to connect again. +4. Ensure the Windows node is deleted from headscale (to ensure fresh setup) +5. Start Tailscale on the windows machine and retry the login. From dd8bae8c619e2453d01a41fc52706789702f639d Mon Sep 17 00:00:00 2001 From: Kristoffer Dalby Date: Fri, 11 Feb 2022 18:39:41 +0000 Subject: [PATCH 27/61] Add link from the docs readme --- docs/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/README.md b/docs/README.md index f42d67de..f8824a97 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) +- [Using a Windows client with headscale](windows-client.md) ### References From 8853ccd5b4b34b4a4a7085e3ab0d6fbe632cf9f8 Mon Sep 17 00:00:00 2001 From: Kristoffer Dalby Date: Sat, 12 Feb 2022 13:25:27 +0000 Subject: [PATCH 28/61] 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 29/61] 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 30/61] 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 31/61] 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 32/61] 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 33/61] 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 34/61] 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 35/61] 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 36/61] 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 37/61] 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 38/61] 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 39/61] 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 40/61] 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 41/61] 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 42/61] 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 43/61] 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 2bc8051ae52f7c48bfcf35d893a2b5563e7ffb2c Mon Sep 17 00:00:00 2001 From: Kristoffer Dalby Date: Sat, 12 Feb 2022 20:46:05 +0000 Subject: [PATCH 44/61] Remove kv-namespace-worker This commit removes the namespace kv worker and related code, now that we talk over gRPC to the server, and not directly to the DB, we should not need this anymore. --- app.go | 16 ---------------- machine_test.go | 13 ------------- namespaces.go | 38 -------------------------------------- 3 files changed, 67 deletions(-) diff --git a/app.go b/app.go index bc7491ce..22b4ceee 100644 --- a/app.go +++ b/app.go @@ -269,20 +269,6 @@ func (h *Headscale) expireEphemeralNodesWorker() { } } -// WatchForKVUpdates checks the KV DB table for requests to perform tailnet upgrades -// This is a way to communitate the CLI with the headscale server. -func (h *Headscale) watchForKVUpdates(milliSeconds int64) { - ticker := time.NewTicker(time.Duration(milliSeconds) * time.Millisecond) - for range ticker.C { - h.watchForKVUpdatesWorker() - } -} - -func (h *Headscale) watchForKVUpdatesWorker() { - h.checkForNamespacesPendingUpdates() - // more functions will come here in the future -} - func (h *Headscale) grpcAuthenticationInterceptor(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, @@ -521,8 +507,6 @@ func (h *Headscale) Serve() error { go h.scheduledDERPMapUpdateWorker(derpMapCancelChannel) } - // I HATE THIS - go h.watchForKVUpdates(updateInterval) go h.expireEphemeralNodes(updateInterval) httpServer := &http.Server{ diff --git a/machine_test.go b/machine_test.go index 1cc4d40c..ff1dc914 100644 --- a/machine_test.go +++ b/machine_test.go @@ -1,7 +1,6 @@ package headscale import ( - "encoding/json" "strconv" "time" @@ -88,18 +87,6 @@ func (s *Suite) TestDeleteMachine(c *check.C) { err = app.DeleteMachine(&machine) c.Assert(err, check.IsNil) - namespacesPendingUpdates, err := app.getValue("namespaces_pending_updates") - c.Assert(err, check.IsNil) - - names := []string{} - err = json.Unmarshal([]byte(namespacesPendingUpdates), &names) - c.Assert(err, check.IsNil) - c.Assert(names, check.DeepEquals, []string{namespace.Name}) - - app.checkForNamespacesPendingUpdates() - - namespacesPendingUpdates, _ = app.getValue("namespaces_pending_updates") - c.Assert(namespacesPendingUpdates, check.Equals, "") _, err = app.GetMachine(namespace.Name, "testmachine") c.Assert(err, check.NotNil) } diff --git a/namespaces.go b/namespaces.go index e512068d..223455af 100644 --- a/namespaces.go +++ b/namespaces.go @@ -235,44 +235,6 @@ func (h *Headscale) RequestMapUpdates(namespaceID uint) error { return h.setValue("namespaces_pending_updates", string(data)) } -func (h *Headscale) checkForNamespacesPendingUpdates() { - namespacesPendingUpdates, err := h.getValue("namespaces_pending_updates") - if err != nil { - return - } - if namespacesPendingUpdates == "" { - return - } - - namespaces := []string{} - err = json.Unmarshal([]byte(namespacesPendingUpdates), &namespaces) - if err != nil { - return - } - for _, namespace := range namespaces { - log.Trace(). - Str("func", "RequestMapUpdates"). - Str("machine", namespace). - Msg("Sending updates to nodes in namespacespace") - h.setLastStateChangeToNow(namespace) - } - newPendingUpdateValue, err := h.getValue("namespaces_pending_updates") - if err != nil { - return - } - if namespacesPendingUpdates == newPendingUpdateValue { // only clear when no changes, so we notified everybody - err = h.setValue("namespaces_pending_updates", "") - if err != nil { - log.Error(). - Str("func", "checkForNamespacesPendingUpdates"). - Err(err). - Msg("Could not save to KV") - - return - } - } -} - func (n *Namespace) toUser() *tailcfg.User { user := tailcfg.User{ ID: tailcfg.UserID(n.ID), From 6fa0903a8e05a0d8d98068c4ca281fe5c4495c98 Mon Sep 17 00:00:00 2001 From: Kristoffer Dalby Date: Sat, 12 Feb 2022 20:50:17 +0000 Subject: [PATCH 45/61] Update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index aa06be0c..045a394f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ - `ip_prefix` is now superseded by `ip_prefixes` in the configuration [#208](https://github.com/juanfont/headscale/pull/208) - Upgrade `tailscale` (1.20.4) and other dependencies to latest [#314](https://github.com/juanfont/headscale/pull/314) - fix swapped machine<->namespace labels in `/metrics` [#312](https://github.com/juanfont/headscale/pull/312) +- remove key-value based update mechanism for namespace changes [#316](https://github.com/juanfont/headscale/pull/316) **0.12.4 (2022-01-29):** From bb80b679bc561681ed68880d383c5660752deb6f Mon Sep 17 00:00:00 2001 From: Kristoffer Dalby Date: Sat, 12 Feb 2022 21:04:00 +0000 Subject: [PATCH 46/61] Remove RequestMapUpdates function --- machine.go | 15 +++++--------- namespaces.go | 55 --------------------------------------------------- routes.go | 5 ----- sharing.go | 5 ----- 4 files changed, 5 insertions(+), 75 deletions(-) diff --git a/machine.go b/machine.go index 64fd11b8..2b462985 100644 --- a/machine.go +++ b/machine.go @@ -353,13 +353,12 @@ func (h *Headscale) DeleteMachine(machine *Machine) error { } machine.Registered = false - namespaceID := machine.NamespaceID h.db.Save(&machine) // we mark it as unregistered, just in case if err := h.db.Delete(&machine).Error; err != nil { return err } - return h.RequestMapUpdates(namespaceID) + return nil } func (h *Headscale) TouchMachine(machine *Machine) error { @@ -377,12 +376,11 @@ func (h *Headscale) HardDeleteMachine(machine *Machine) error { return err } - namespaceID := machine.NamespaceID if err := h.db.Unscoped().Delete(&machine).Error; err != nil { return err } - return h.RequestMapUpdates(namespaceID) + return nil } // GetHostInfo returns a Hostinfo struct for the machine. @@ -530,7 +528,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{} @@ -862,11 +862,6 @@ func (h *Headscale) EnableRoutes(machine *Machine, routeStrs ...string) error { machine.EnabledRoutes = datatypes.JSON(routes) h.db.Save(&machine) - err = h.RequestMapUpdates(machine.NamespaceID) - if err != nil { - return err - } - return nil } diff --git a/namespaces.go b/namespaces.go index 223455af..bdd440cf 100644 --- a/namespaces.go +++ b/namespaces.go @@ -1,9 +1,7 @@ package headscale import ( - "encoding/json" "errors" - "fmt" "strconv" "time" @@ -104,11 +102,6 @@ func (h *Headscale) RenameNamespace(oldName, newName string) error { return result.Error } - err = h.RequestMapUpdates(oldNamespace.ID) - if err != nil { - return err - } - return nil } @@ -187,54 +180,6 @@ func (h *Headscale) SetMachineNamespace(machine *Machine, namespaceName string) return nil } -// TODO(kradalby): Remove the need for this. -// RequestMapUpdates signals the KV worker to update the maps for this namespace. -func (h *Headscale) RequestMapUpdates(namespaceID uint) error { - namespace := Namespace{} - if err := h.db.First(&namespace, namespaceID).Error; err != nil { - return err - } - - namespacesPendingUpdates, err := h.getValue("namespaces_pending_updates") - if err != nil || namespacesPendingUpdates == "" { - err = h.setValue( - "namespaces_pending_updates", - fmt.Sprintf(`["%s"]`, namespace.Name), - ) - if err != nil { - return err - } - - return nil - } - names := []string{} - err = json.Unmarshal([]byte(namespacesPendingUpdates), &names) - if err != nil { - err = h.setValue( - "namespaces_pending_updates", - fmt.Sprintf(`["%s"]`, namespace.Name), - ) - if err != nil { - return err - } - - return nil - } - - names = append(names, namespace.Name) - data, err := json.Marshal(names) - if err != nil { - log.Error(). - Str("func", "RequestMapUpdates"). - Err(err). - Msg("Could not marshal namespaces_pending_updates") - - return err - } - - return h.setValue("namespaces_pending_updates", string(data)) -} - func (n *Namespace) toUser() *tailcfg.User { user := tailcfg.User{ ID: tailcfg.UserID(n.ID), diff --git a/routes.go b/routes.go index 448095a5..0065a03f 100644 --- a/routes.go +++ b/routes.go @@ -143,10 +143,5 @@ func (h *Headscale) EnableNodeRoute( machine.EnabledRoutes = datatypes.JSON(routes) h.db.Save(&machine) - err = h.RequestMapUpdates(machine.NamespaceID) - if err != nil { - return err - } - return nil } diff --git a/sharing.go b/sharing.go index be1689d5..caac5319 100644 --- a/sharing.go +++ b/sharing.go @@ -67,11 +67,6 @@ func (h *Headscale) RemoveSharedMachineFromNamespace( return errMachineNotShared } - err := h.RequestMapUpdates(namespace.ID) - if err != nil { - return err - } - return nil } From 0018a78d5a146d3fbd1e0157e9dc8809fd0ed544 Mon Sep 17 00:00:00 2001 From: Kristoffer Dalby Date: Sun, 13 Feb 2022 08:41:49 +0000 Subject: [PATCH 47/61] 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 48/61] 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 49/61] 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 50/61] 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: From 9f8034947141da706aa743caeda679d85c17ea7d Mon Sep 17 00:00:00 2001 From: Kristoffer Dalby Date: Sun, 13 Feb 2022 11:02:31 +0000 Subject: [PATCH 51/61] Add sponsorship button This commit adds a sponsor/funding section to headscale. @juanfont and I have discussed this and this arrangement is agreed upon and hopefully this can bring us to a place in the future were even more features and prioritization can be put upon the project. --- .github/FUNDING.yml | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .github/FUNDING.yml diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 00000000..16fba08e --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,2 @@ +ko_fi: kradalby +github: [kradalby] From f30ee3d2df705b1573f9ae539f74f5263c35288c Mon Sep 17 00:00:00 2001 From: Kristoffer Dalby Date: Sun, 13 Feb 2022 11:07:45 +0000 Subject: [PATCH 52/61] Add note about support in readme --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index 491450a5..cf246544 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,12 @@ The control server works as an exchange point of Wireguard public keys for the n headscale implements this coordination server. +## Support + +If you like `headscale` and find it useful, there is sponsorship and donation buttons available in the repo. + +If you would like to sponsor features, bugs or prioritisation, reach out to one of the maintainers. + ## Status - [x] Base functionality (nodes can communicate with each other) From b2889bc355140913ca3c80159b232030442ea4a0 Mon Sep 17 00:00:00 2001 From: ohdearaugustin Date: Mon, 14 Feb 2022 21:24:51 +0100 Subject: [PATCH 53/61] github/workflows: set specific go version --- .github/workflows/build.yml | 2 +- .github/workflows/release.yml | 2 +- .github/workflows/test-integration.yml | 2 +- .github/workflows/test.yml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index d92e9e6e..37423c38 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -31,7 +31,7 @@ jobs: if: steps.changed-files.outputs.any_changed == 'true' uses: actions/setup-go@v2 with: - go-version: "1.17" + go-version: "1.17.7" - name: Install dependencies if: steps.changed-files.outputs.any_changed == 'true' diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index c9a40bc0..8999f7bf 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -18,7 +18,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v2 with: - go-version: 1.17 + go-version: 1.17.7 - name: Install dependencies run: | diff --git a/.github/workflows/test-integration.yml b/.github/workflows/test-integration.yml index 9f526f97..d9c52c7b 100644 --- a/.github/workflows/test-integration.yml +++ b/.github/workflows/test-integration.yml @@ -25,7 +25,7 @@ jobs: if: steps.changed-files.outputs.any_changed == 'true' uses: actions/setup-go@v2 with: - go-version: "1.17" + go-version: "1.17.7" - name: Run Integration tests if: steps.changed-files.outputs.any_changed == 'true' diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 9ce8a779..663220be 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -25,7 +25,7 @@ jobs: if: steps.changed-files.outputs.any_changed == 'true' uses: actions/setup-go@v2 with: - go-version: "1.17" + go-version: "1.17.7" - name: Install dependencies if: steps.changed-files.outputs.any_changed == 'true' From 0b9dd19ec7fc8d4a7b5bfb8c684c1ed266eb185a Mon Sep 17 00:00:00 2001 From: ohdearaugustin Date: Mon, 14 Feb 2022 21:25:11 +0100 Subject: [PATCH 54/61] Dockerfiles: update go version to 1.17.7 --- Dockerfile | 2 +- Dockerfile.alpine | 2 +- Dockerfile.debug | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Dockerfile b/Dockerfile index 050439b5..6e577753 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,5 @@ # Builder image -FROM docker.io/golang:1.17.1-bullseye AS build +FROM docker.io/golang:1.17.7-bullseye AS build ENV GOPATH /go WORKDIR /go/src/headscale diff --git a/Dockerfile.alpine b/Dockerfile.alpine index a75bbfea..b07e1904 100644 --- a/Dockerfile.alpine +++ b/Dockerfile.alpine @@ -1,5 +1,5 @@ # Builder image -FROM docker.io/golang:1.17.1-alpine AS build +FROM docker.io/golang:1.17.7-alpine AS build ENV GOPATH /go WORKDIR /go/src/headscale diff --git a/Dockerfile.debug b/Dockerfile.debug index b81ee67e..3d2675f9 100644 --- a/Dockerfile.debug +++ b/Dockerfile.debug @@ -1,5 +1,5 @@ # Builder image -FROM docker.io/golang:1.17.1-bullseye AS build +FROM docker.io/golang:1.17.7-bullseye AS build ENV GOPATH /go WORKDIR /go/src/headscale From 61bfa79be2313e7458a7379b5dd6d1836356f20e Mon Sep 17 00:00:00 2001 From: Tanner <97977342+m-tanner-dev0@users.noreply.github.com> Date: Thu, 17 Feb 2022 17:55:40 -0800 Subject: [PATCH 55/61] Update README.md change flippant language --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index cf246544..1d86380e 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ Join our [Discord](https://discord.gg/XcQxk2VHjx) server for a chat. ## Overview -Tailscale is [a modern VPN](https://tailscale.com/) built on top of [Wireguard](https://www.wireguard.com/). It [works like an overlay network](https://tailscale.com/blog/how-tailscale-works/) between the computers of your networks - using all kinds of [NAT traversal sorcery](https://tailscale.com/blog/how-nat-traversal-works/). +Tailscale is [a modern VPN](https://tailscale.com/) built on top of [Wireguard](https://www.wireguard.com/). It [works like an overlay network](https://tailscale.com/blog/how-tailscale-works/) between the computers of your networks - using [NAT traversal](https://tailscale.com/blog/how-nat-traversal-works/). Everything in Tailscale is Open Source, except the GUI clients for proprietary OS (Windows and macOS/iOS), and the 'coordination/control server'. @@ -52,7 +52,7 @@ If you would like to sponsor features, bugs or prioritisation, reach out to one | Android | [You need to compile the client yourself](https://github.com/juanfont/headscale/issues/58#issuecomment-885255270) | | iOS | Not yet | -## Roadmap 🤷 +## Roadmap Suggestions/PRs welcomed! From 5fbef07627d92602659f3a41fd1e5f00a84cdb9e Mon Sep 17 00:00:00 2001 From: Kristoffer Dalby Date: Fri, 18 Feb 2022 18:54:27 +0000 Subject: [PATCH 56/61] Update changelog for 0.13.0 --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f6e44161..70bda12d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ **TBD (TBD):** -**0.13.0 (2022-xx-xx):** +**0.13.0 (2022-02-18):** **Features**: From 7916fa8b45a0540b1e18b36e5280d5595705644b Mon Sep 17 00:00:00 2001 From: Kristoffer Dalby Date: Fri, 18 Feb 2022 19:57:03 +0000 Subject: [PATCH 57/61] Add ohdearaugustin to CODEOWNERS for config and docs --- .github/CODEOWNERS | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 .github/CODEOWNERS diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 00000000..565684fe --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,9 @@ +* @juanfont @kradalby + +*.md @ohdearaugustin +*.yml @ohdearaugustin +*.yaml @ohdearaugustin +Dockerfile* @ohdearaugustin +.goreleaser.yaml @ohdearaugustin +/docs/ @ohdearaugustin +/.github/workflows/ @ohdearaugustin From a6b7bc5939bbf6e638b38f0d8d81b251845447e1 Mon Sep 17 00:00:00 2001 From: e-zk Date: Sun, 20 Feb 2022 03:14:51 +1000 Subject: [PATCH 58/61] Fix spelling error --- docs/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/README.md b/docs/README.md index 1ac15221..89c74a79 100644 --- a/docs/README.md +++ b/docs/README.md @@ -10,7 +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) +- [Control headscale remotely](remote-cli.md) - [Using a Windows client with headscale](windows-client.md) ### References From e9f13b603128d9536f75be48199cda5f1f222070 Mon Sep 17 00:00:00 2001 From: ohdearaugustin Date: Sat, 19 Feb 2022 20:28:08 +0100 Subject: [PATCH 59/61] CODEOWNER: add renovate config ohdearaugustin --- .github/CODEOWNERS | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 565684fe..fa1c06da 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -7,3 +7,4 @@ Dockerfile* @ohdearaugustin .goreleaser.yaml @ohdearaugustin /docs/ @ohdearaugustin /.github/workflows/ @ohdearaugustin +/.github/renovate.json @ohdearaugustin From 8798efd3536bb7ee7883de909f69c12b3715f06a Mon Sep 17 00:00:00 2001 From: ohdearaugustin Date: Sat, 19 Feb 2022 21:04:01 +0100 Subject: [PATCH 60/61] contributor: set specific version --- .github/workflows/contributors.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/contributors.yml b/.github/workflows/contributors.yml index e27b6eda..4a73009b 100644 --- a/.github/workflows/contributors.yml +++ b/.github/workflows/contributors.yml @@ -4,13 +4,13 @@ on: push: branches: - main - + workflow_dispatch: jobs: add-contributors: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - - uses: BobAnkh/add-contributors@master + - uses: BobAnkh/add-contributors@v0.2.2 with: CONTRIBUTOR: "## Contributors" COLUMN_PER_ROW: "6" From 5245f1accc5fd7f85ecbe6f4e59599659f3b72d8 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sat, 19 Feb 2022 22:48:26 +0000 Subject: [PATCH 61/61] docs(README): update contributors --- README.md | 181 ++++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 168 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 1d86380e..e7ba6649 100644 --- a/README.md +++ b/README.md @@ -122,13 +122,6 @@ make build - + + + + + - - + + + + + + + + + + + + + - - + + + + + + + + + + - - + + + + + + + +
- - Juan -
- Juan Font -
-
Kristoffer @@ -136,6 +129,13 @@ make build Kristoffer Dalby + + Juan +
+ Juan Font +
+
Ward @@ -150,6 +150,13 @@ make build ohdearaugustin + + Alessandro +
+ Alessandro (Ale) Segala +
+
unreality/ @@ -157,6 +164,15 @@ make build unreality
+ + Eugen +
+ Eugen Biegler +
+
Aaron @@ -164,8 +180,27 @@ make build Aaron Bieber
+ + Fernando +
+ Fernando De Lucchi +
+
+ + Hoàng +
+ Hoàng Đức Hiếu +
+
+ + Michael +
+ Michael G. +
+
Paul @@ -173,6 +208,8 @@ make build Paul Tötterman
Casey @@ -187,6 +224,20 @@ make build Silver Bullet + + Stefan +
+ Stefan Majer +
+
+ + lachy-2849/ +
+ lachy-2849 +
+
thomas/ @@ -194,6 +245,29 @@ make build thomas + + Abraham +
+ Abraham Ingersoll +
+
+ + Adrien +
+ Adrien Raffin-Caboisse +
+
+ + Artem +
+ Artem Klevtsov +
+
Arthur @@ -201,6 +275,13 @@ make build Arthur Woimbée + + Bryan +
+ Bryan Stenson +
+
Felix @@ -208,8 +289,6 @@ make build Felix Kronlage-Dammers
Felix @@ -217,6 +296,43 @@ make build Felix Yan
+ + JJGadgets/ +
+ JJGadgets +
+
+ + Jim +
+ Jim Tittsler +
+
+ + Pierre +
+ Pierre Carru +
+
+ + rcursaru/ +
+ rcursaru +
+
+ + Ryan +
+ Ryan Fowler +
+
Shaanan @@ -224,6 +340,15 @@ make build Shaanan Cohney
+ + Tanner/ +
+ Tanner +
+
Teteros/ @@ -252,8 +377,6 @@ make build Tjerk Woudsma
Zakhar @@ -261,6 +384,15 @@ make build Zakhar Bessarab
+ + ZiYuan/ +
+ ZiYuan +
+
derelm/ @@ -268,6 +400,13 @@ make build derelm + + e-zk/ +
+ e-zk +
+
ignoramous/ @@ -275,6 +414,22 @@ make build ignoramous + + lion24/ +
+ lion24 +
+
+ + Wakeful-Cloud/ +
+ Wakeful-Cloud +
+
zy/