mirror of
				https://github.com/optim-enterprises-bv/vault.git
				synced 2025-10-30 18:17:55 +00:00 
			
		
		
		
	Automated rotate root support for AWS Auth and Secrets (#29497)
This commit is contained in:
		| @@ -199,6 +199,10 @@ func Backend(_ *logical.BackendConfig) (*backend, error) { | |||||||
| 		InitializeFunc: b.initialize, | 		InitializeFunc: b.initialize, | ||||||
| 		BackendType:    logical.TypeCredential, | 		BackendType:    logical.TypeCredential, | ||||||
| 		Clean:          b.cleanup, | 		Clean:          b.cleanup, | ||||||
|  | 		RotateCredential: func(ctx context.Context, request *logical.Request) error { | ||||||
|  | 			_, err := b.rotateRoot(ctx, request) | ||||||
|  | 			return err | ||||||
|  | 		}, | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	b.partitionToRegionMap = generatePartitionToRegionMap() | 	b.partitionToRegionMap = generatePartitionToRegionMap() | ||||||
|   | |||||||
| @@ -34,6 +34,7 @@ func TestBackend_CreateParseVerifyRoleTag(t *testing.T) { | |||||||
| 	config := logical.TestBackendConfig() | 	config := logical.TestBackendConfig() | ||||||
| 	storage := &logical.InmemStorage{} | 	storage := &logical.InmemStorage{} | ||||||
| 	config.StorageView = storage | 	config.StorageView = storage | ||||||
|  | 	config.System = &testSystemView{} | ||||||
|  |  | ||||||
| 	b, err := Backend(config) | 	b, err := Backend(config) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| @@ -260,6 +261,7 @@ func TestBackend_ConfigTidyIdentities(t *testing.T) { | |||||||
| 		config := logical.TestBackendConfig() | 		config := logical.TestBackendConfig() | ||||||
| 		storage := &logical.InmemStorage{} | 		storage := &logical.InmemStorage{} | ||||||
| 		config.StorageView = storage | 		config.StorageView = storage | ||||||
|  | 		config.System = &testSystemView{} | ||||||
|  |  | ||||||
| 		b, err := Backend(config) | 		b, err := Backend(config) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| @@ -317,6 +319,7 @@ func TestBackend_ConfigTidyRoleTags(t *testing.T) { | |||||||
| 		config := logical.TestBackendConfig() | 		config := logical.TestBackendConfig() | ||||||
| 		storage := &logical.InmemStorage{} | 		storage := &logical.InmemStorage{} | ||||||
| 		config.StorageView = storage | 		config.StorageView = storage | ||||||
|  | 		config.System = &testSystemView{} | ||||||
|  |  | ||||||
| 		b, err := Backend(config) | 		b, err := Backend(config) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| @@ -374,6 +377,7 @@ func TestBackend_TidyIdentities(t *testing.T) { | |||||||
| 		config := logical.TestBackendConfig() | 		config := logical.TestBackendConfig() | ||||||
| 		storage := &logical.InmemStorage{} | 		storage := &logical.InmemStorage{} | ||||||
| 		config.StorageView = storage | 		config.StorageView = storage | ||||||
|  | 		config.System = &testSystemView{} | ||||||
|  |  | ||||||
| 		b, err := Backend(config) | 		b, err := Backend(config) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| @@ -424,6 +428,7 @@ func TestBackend_TidyRoleTags(t *testing.T) { | |||||||
| 		config := logical.TestBackendConfig() | 		config := logical.TestBackendConfig() | ||||||
| 		storage := &logical.InmemStorage{} | 		storage := &logical.InmemStorage{} | ||||||
| 		config.StorageView = storage | 		config.StorageView = storage | ||||||
|  | 		config.System = &testSystemView{} | ||||||
|  |  | ||||||
| 		b, err := Backend(config) | 		b, err := Backend(config) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| @@ -473,6 +478,7 @@ func TestBackend_ConfigClient(t *testing.T) { | |||||||
| 	config := logical.TestBackendConfig() | 	config := logical.TestBackendConfig() | ||||||
| 	storage := &logical.InmemStorage{} | 	storage := &logical.InmemStorage{} | ||||||
| 	config.StorageView = storage | 	config.StorageView = storage | ||||||
|  | 	config.System = &testSystemView{} | ||||||
|  |  | ||||||
| 	b, err := Backend(config) | 	b, err := Backend(config) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| @@ -614,6 +620,7 @@ func TestBackend_pathConfigCertificate(t *testing.T) { | |||||||
| 	config := logical.TestBackendConfig() | 	config := logical.TestBackendConfig() | ||||||
| 	storage := &logical.InmemStorage{} | 	storage := &logical.InmemStorage{} | ||||||
| 	config.StorageView = storage | 	config.StorageView = storage | ||||||
|  | 	config.System = &testSystemView{} | ||||||
|  |  | ||||||
| 	b, err := Backend(config) | 	b, err := Backend(config) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| @@ -771,6 +778,8 @@ func TestBackend_parseAndVerifyRoleTagValue(t *testing.T) { | |||||||
| 	config := logical.TestBackendConfig() | 	config := logical.TestBackendConfig() | ||||||
| 	storage := &logical.InmemStorage{} | 	storage := &logical.InmemStorage{} | ||||||
| 	config.StorageView = storage | 	config.StorageView = storage | ||||||
|  | 	config.System = &testSystemView{} | ||||||
|  |  | ||||||
| 	b, err := Backend(config) | 	b, err := Backend(config) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		t.Fatal(err) | 		t.Fatal(err) | ||||||
| @@ -853,6 +862,8 @@ func TestBackend_PathRoleTag(t *testing.T) { | |||||||
| 	config := logical.TestBackendConfig() | 	config := logical.TestBackendConfig() | ||||||
| 	storage := &logical.InmemStorage{} | 	storage := &logical.InmemStorage{} | ||||||
| 	config.StorageView = storage | 	config.StorageView = storage | ||||||
|  | 	config.System = &testSystemView{} | ||||||
|  |  | ||||||
| 	b, err := Backend(config) | 	b, err := Backend(config) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		t.Fatal(err) | 		t.Fatal(err) | ||||||
| @@ -920,6 +931,8 @@ func TestBackend_PathBlacklistRoleTag(t *testing.T) { | |||||||
| 		storage := &logical.InmemStorage{} | 		storage := &logical.InmemStorage{} | ||||||
| 		config := logical.TestBackendConfig() | 		config := logical.TestBackendConfig() | ||||||
| 		config.StorageView = storage | 		config.StorageView = storage | ||||||
|  | 		config.System = &testSystemView{} | ||||||
|  |  | ||||||
| 		b, err := Backend(config) | 		b, err := Backend(config) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			t.Fatal(err) | 			t.Fatal(err) | ||||||
| @@ -1379,6 +1392,8 @@ func TestBackend_pathStsConfig(t *testing.T) { | |||||||
| 	config := logical.TestBackendConfig() | 	config := logical.TestBackendConfig() | ||||||
| 	storage := &logical.InmemStorage{} | 	storage := &logical.InmemStorage{} | ||||||
| 	config.StorageView = storage | 	config.StorageView = storage | ||||||
|  | 	config.System = &testSystemView{} | ||||||
|  |  | ||||||
| 	b, err := Backend(config) | 	b, err := Backend(config) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		t.Fatal(err) | 		t.Fatal(err) | ||||||
|   | |||||||
| @@ -14,9 +14,11 @@ import ( | |||||||
| 	"github.com/aws/aws-sdk-go/aws" | 	"github.com/aws/aws-sdk-go/aws" | ||||||
| 	"github.com/hashicorp/go-secure-stdlib/strutil" | 	"github.com/hashicorp/go-secure-stdlib/strutil" | ||||||
| 	"github.com/hashicorp/vault/sdk/framework" | 	"github.com/hashicorp/vault/sdk/framework" | ||||||
|  | 	"github.com/hashicorp/vault/sdk/helper/automatedrotationutil" | ||||||
| 	"github.com/hashicorp/vault/sdk/helper/pluginidentityutil" | 	"github.com/hashicorp/vault/sdk/helper/pluginidentityutil" | ||||||
| 	"github.com/hashicorp/vault/sdk/helper/pluginutil" | 	"github.com/hashicorp/vault/sdk/helper/pluginutil" | ||||||
| 	"github.com/hashicorp/vault/sdk/logical" | 	"github.com/hashicorp/vault/sdk/logical" | ||||||
|  | 	"github.com/hashicorp/vault/sdk/rotation" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| func (b *backend) pathConfigClient() *framework.Path { | func (b *backend) pathConfigClient() *framework.Path { | ||||||
| @@ -130,6 +132,7 @@ func (b *backend) pathConfigClient() *framework.Path { | |||||||
| 		HelpDescription: pathConfigClientHelpDesc, | 		HelpDescription: pathConfigClientHelpDesc, | ||||||
| 	} | 	} | ||||||
| 	pluginidentityutil.AddPluginIdentityTokenFields(p.Fields) | 	pluginidentityutil.AddPluginIdentityTokenFields(p.Fields) | ||||||
|  | 	automatedrotationutil.AddAutomatedRotationFields(p.Fields) | ||||||
|  |  | ||||||
| 	return p | 	return p | ||||||
| } | } | ||||||
| @@ -193,6 +196,7 @@ func (b *backend) pathConfigClientRead(ctx context.Context, req *logical.Request | |||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	clientConfig.PopulatePluginIdentityTokenData(configData) | 	clientConfig.PopulatePluginIdentityTokenData(configData) | ||||||
|  | 	clientConfig.PopulateAutomatedRotationData(configData) | ||||||
| 	return &logical.Response{ | 	return &logical.Response{ | ||||||
| 		Data: configData, | 		Data: configData, | ||||||
| 	}, nil | 	}, nil | ||||||
| @@ -363,6 +367,10 @@ func (b *backend) pathConfigClientCreateUpdate(ctx context.Context, req *logical | |||||||
| 		return logical.ErrorResponse(err.Error()), nil | 		return logical.ErrorResponse(err.Error()), nil | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	if err := configEntry.ParseAutomatedRotationFields(data); err != nil { | ||||||
|  | 		return logical.ErrorResponse(err.Error()), nil | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	// handle mutual exclusivity | 	// handle mutual exclusivity | ||||||
| 	if configEntry.IdentityTokenAudience != "" && configEntry.AccessKey != "" { | 	if configEntry.IdentityTokenAudience != "" && configEntry.AccessKey != "" { | ||||||
| 		return logical.ErrorResponse("only one of 'access_key' or 'identity_token_audience' can be set"), nil | 		return logical.ErrorResponse("only one of 'access_key' or 'identity_token_audience' can be set"), nil | ||||||
| @@ -384,6 +392,36 @@ func (b *backend) pathConfigClientCreateUpdate(ctx context.Context, req *logical | |||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	var performedRotationManagerOpern string | ||||||
|  | 	if configEntry.ShouldDeregisterRotationJob() { | ||||||
|  | 		performedRotationManagerOpern = "deregistration" | ||||||
|  | 		// Disable Automated Rotation and Deregister credentials if required | ||||||
|  | 		deregisterReq := &rotation.RotationJobDeregisterRequest{ | ||||||
|  | 			MountPoint: req.MountPoint, | ||||||
|  | 			ReqPath:    req.Path, | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		b.Logger().Debug("Deregistering rotation job", "mount", req.MountPoint+req.Path) | ||||||
|  | 		if err := b.System().DeregisterRotationJob(ctx, deregisterReq); err != nil { | ||||||
|  | 			return logical.ErrorResponse("error deregistering rotation job: %s", err), nil | ||||||
|  | 		} | ||||||
|  | 	} else if configEntry.ShouldRegisterRotationJob() { | ||||||
|  | 		performedRotationManagerOpern = "registration" | ||||||
|  | 		// Register the rotation job if it's required. | ||||||
|  | 		cfgReq := &rotation.RotationJobConfigureRequest{ | ||||||
|  | 			MountPoint:       req.MountPoint, | ||||||
|  | 			ReqPath:          req.Path, | ||||||
|  | 			RotationSchedule: configEntry.RotationSchedule, | ||||||
|  | 			RotationWindow:   configEntry.RotationWindow, | ||||||
|  | 			RotationPeriod:   configEntry.RotationPeriod, | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		b.Logger().Debug("Registering rotation job", "mount", req.MountPoint+req.Path) | ||||||
|  | 		if _, err = b.System().RegisterRotationJob(ctx, cfgReq); err != nil { | ||||||
|  | 			return logical.ErrorResponse("error registering rotation job: %s", err), nil | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	// Since this endpoint supports both create operation and update operation, | 	// Since this endpoint supports both create operation and update operation, | ||||||
| 	// the error checks for access_key and secret_key not being set are not present. | 	// the error checks for access_key and secret_key not being set are not present. | ||||||
| 	// This allows calling this endpoint multiple times to provide the values. | 	// This allows calling this endpoint multiple times to provide the values. | ||||||
| @@ -396,6 +434,10 @@ func (b *backend) pathConfigClientCreateUpdate(ctx context.Context, req *logical | |||||||
|  |  | ||||||
| 	if changedCreds || changedOtherConfig || req.Operation == logical.CreateOperation { | 	if changedCreds || changedOtherConfig || req.Operation == logical.CreateOperation { | ||||||
| 		if err := req.Storage.Put(ctx, entry); err != nil { | 		if err := req.Storage.Put(ctx, entry); err != nil { | ||||||
|  | 			if performedRotationManagerOpern != "" { | ||||||
|  | 				b.Logger().Error("write to storage failed but the rotation manager still succeeded.", | ||||||
|  | 					"operation", performedRotationManagerOpern, "mount", req.MountPoint, "path", req.Path) | ||||||
|  | 			} | ||||||
| 			return nil, err | 			return nil, err | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| @@ -424,6 +466,7 @@ func (b *backend) configClientToEntry(conf *clientConfig) (*logical.StorageEntry | |||||||
| // interact with the AWS EC2 API. | // interact with the AWS EC2 API. | ||||||
| type clientConfig struct { | type clientConfig struct { | ||||||
| 	pluginidentityutil.PluginIdentityTokenParams | 	pluginidentityutil.PluginIdentityTokenParams | ||||||
|  | 	automatedrotationutil.AutomatedRotationParams | ||||||
|  |  | ||||||
| 	AccessKey              string   `json:"access_key"` | 	AccessKey              string   `json:"access_key"` | ||||||
| 	SecretKey              string   `json:"secret_key"` | 	SecretKey              string   `json:"secret_key"` | ||||||
|   | |||||||
| @@ -7,9 +7,11 @@ import ( | |||||||
| 	"context" | 	"context" | ||||||
| 	"testing" | 	"testing" | ||||||
|  |  | ||||||
|  | 	"github.com/hashicorp/vault/sdk/helper/automatedrotationutil" | ||||||
| 	"github.com/hashicorp/vault/sdk/helper/pluginidentityutil" | 	"github.com/hashicorp/vault/sdk/helper/pluginidentityutil" | ||||||
| 	"github.com/hashicorp/vault/sdk/helper/pluginutil" | 	"github.com/hashicorp/vault/sdk/helper/pluginutil" | ||||||
| 	"github.com/hashicorp/vault/sdk/logical" | 	"github.com/hashicorp/vault/sdk/logical" | ||||||
|  | 	"github.com/hashicorp/vault/sdk/rotation" | ||||||
| 	"github.com/stretchr/testify/assert" | 	"github.com/stretchr/testify/assert" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| @@ -17,6 +19,7 @@ func TestBackend_pathConfigClient(t *testing.T) { | |||||||
| 	config := logical.TestBackendConfig() | 	config := logical.TestBackendConfig() | ||||||
| 	storage := &logical.InmemStorage{} | 	storage := &logical.InmemStorage{} | ||||||
| 	config.StorageView = storage | 	config.StorageView = storage | ||||||
|  | 	config.System = &testSystemView{} | ||||||
|  |  | ||||||
| 	b, err := Backend(config) | 	b, err := Backend(config) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| @@ -176,3 +179,11 @@ type testSystemView struct { | |||||||
| func (d testSystemView) GenerateIdentityToken(_ context.Context, _ *pluginutil.IdentityTokenRequest) (*pluginutil.IdentityTokenResponse, error) { | func (d testSystemView) GenerateIdentityToken(_ context.Context, _ *pluginutil.IdentityTokenRequest) (*pluginutil.IdentityTokenResponse, error) { | ||||||
| 	return nil, pluginidentityutil.ErrPluginWorkloadIdentityUnsupported | 	return nil, pluginidentityutil.ErrPluginWorkloadIdentityUnsupported | ||||||
| } | } | ||||||
|  |  | ||||||
|  | func (d testSystemView) RegisterRotationJob(_ context.Context, _ *rotation.RotationJobConfigureRequest) (string, error) { | ||||||
|  | 	return "", automatedrotationutil.ErrRotationManagerUnsupported | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (d testSystemView) DeregisterRotationJob(_ context.Context, _ *rotation.RotationJobDeregisterRequest) error { | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|   | |||||||
| @@ -42,6 +42,10 @@ func (b *backend) pathConfigRotateRoot() *framework.Path { | |||||||
| } | } | ||||||
|  |  | ||||||
| func (b *backend) pathConfigRotateRootUpdate(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) { | func (b *backend) pathConfigRotateRootUpdate(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) { | ||||||
|  | 	return b.rotateRoot(ctx, req) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (b *backend) rotateRoot(ctx context.Context, req *logical.Request) (*logical.Response, error) { | ||||||
| 	// First get the AWS key and secret and validate that we _can_ rotate them. | 	// First get the AWS key and secret and validate that we _can_ rotate them. | ||||||
| 	// We need the read lock here to prevent anything else from mutating it while we're using it. | 	// We need the read lock here to prevent anything else from mutating it while we're using it. | ||||||
| 	b.configMutex.Lock() | 	b.configMutex.Lock() | ||||||
|   | |||||||
| @@ -315,6 +315,8 @@ func TestBackend_pathLogin_NoClientConfig(t *testing.T) { | |||||||
| 	storage := new(logical.InmemStorage) | 	storage := new(logical.InmemStorage) | ||||||
| 	config := logical.TestBackendConfig() | 	config := logical.TestBackendConfig() | ||||||
| 	config.StorageView = storage | 	config.StorageView = storage | ||||||
|  | 	config.System = &testSystemView{} | ||||||
|  |  | ||||||
| 	b, err := Backend(config) | 	b, err := Backend(config) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		t.Fatal(err) | 		t.Fatal(err) | ||||||
| @@ -363,6 +365,8 @@ func TestBackend_pathLogin_IAMHeaders(t *testing.T) { | |||||||
| 	storage := &logical.InmemStorage{} | 	storage := &logical.InmemStorage{} | ||||||
| 	config := logical.TestBackendConfig() | 	config := logical.TestBackendConfig() | ||||||
| 	config.StorageView = storage | 	config.StorageView = storage | ||||||
|  | 	config.System = &testSystemView{} | ||||||
|  |  | ||||||
| 	b, err := Backend(config) | 	b, err := Backend(config) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		t.Fatal(err) | 		t.Fatal(err) | ||||||
| @@ -570,6 +574,8 @@ func TestBackend_pathLogin_IAMRoleResolution(t *testing.T) { | |||||||
| 	storage := &logical.InmemStorage{} | 	storage := &logical.InmemStorage{} | ||||||
| 	config := logical.TestBackendConfig() | 	config := logical.TestBackendConfig() | ||||||
| 	config.StorageView = storage | 	config.StorageView = storage | ||||||
|  | 	config.System = &testSystemView{} | ||||||
|  |  | ||||||
| 	b, err := Backend(config) | 	b, err := Backend(config) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		t.Fatal(err) | 		t.Fatal(err) | ||||||
| @@ -670,6 +676,8 @@ func TestBackend_defaultAliasMetadata(t *testing.T) { | |||||||
| 	storage := &logical.InmemStorage{} | 	storage := &logical.InmemStorage{} | ||||||
| 	config := logical.TestBackendConfig() | 	config := logical.TestBackendConfig() | ||||||
| 	config.StorageView = storage | 	config.StorageView = storage | ||||||
|  | 	config.System = &testSystemView{} | ||||||
|  |  | ||||||
| 	b, err := Backend(config) | 	b, err := Backend(config) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		t.Fatal(err) | 		t.Fatal(err) | ||||||
|   | |||||||
| @@ -10,8 +10,6 @@ import ( | |||||||
| 	"sync" | 	"sync" | ||||||
| 	"time" | 	"time" | ||||||
|  |  | ||||||
| 	"github.com/aws/aws-sdk-go/aws" |  | ||||||
| 	"github.com/aws/aws-sdk-go/service/iam" |  | ||||||
| 	"github.com/aws/aws-sdk-go/service/iam/iamiface" | 	"github.com/aws/aws-sdk-go/service/iam/iamiface" | ||||||
| 	"github.com/aws/aws-sdk-go/service/sts/stsiface" | 	"github.com/aws/aws-sdk-go/service/sts/stsiface" | ||||||
| 	"github.com/hashicorp/vault/sdk/framework" | 	"github.com/hashicorp/vault/sdk/framework" | ||||||
| @@ -77,89 +75,8 @@ func Backend(_ *logical.BackendConfig) *backend { | |||||||
| 			return nil | 			return nil | ||||||
| 		}, | 		}, | ||||||
| 		RotateCredential: func(ctx context.Context, req *logical.Request) error { | 		RotateCredential: func(ctx context.Context, req *logical.Request) error { | ||||||
| 			// the following code is a modified version of the rotate-root method | 			_, err := b.rotateRoot(ctx, req) | ||||||
| 			client, err := b.clientIAM(ctx, req.Storage) |  | ||||||
| 			if err != nil { |  | ||||||
| 			return err | 			return err | ||||||
| 			} |  | ||||||
| 			if client == nil { |  | ||||||
| 				return fmt.Errorf("nil IAM client") |  | ||||||
| 			} |  | ||||||
|  |  | ||||||
| 			b.clientMutex.Lock() |  | ||||||
| 			defer b.clientMutex.Unlock() |  | ||||||
|  |  | ||||||
| 			rawRootConfig, err := req.Storage.Get(ctx, "config/root") |  | ||||||
| 			if err != nil { |  | ||||||
| 				return err |  | ||||||
| 			} |  | ||||||
| 			if rawRootConfig == nil { |  | ||||||
| 				return fmt.Errorf("no configuration found for config/root") |  | ||||||
| 			} |  | ||||||
| 			var config rootConfig |  | ||||||
| 			if err := rawRootConfig.DecodeJSON(&config); err != nil { |  | ||||||
| 				return fmt.Errorf("error reading root configuration: %w", err) |  | ||||||
| 			} |  | ||||||
|  |  | ||||||
| 			if config.AccessKey == "" || config.SecretKey == "" { |  | ||||||
| 				return fmt.Errorf("cannot call config/rotate-root when either access_key or secret_key is empty") |  | ||||||
| 			} |  | ||||||
|  |  | ||||||
| 			var getUserInput iam.GetUserInput // empty input means get current user |  | ||||||
| 			getUserRes, err := client.GetUserWithContext(ctx, &getUserInput) |  | ||||||
| 			if err != nil { |  | ||||||
| 				return fmt.Errorf("error calling GetUser: %w", err) |  | ||||||
| 			} |  | ||||||
| 			if getUserRes == nil { |  | ||||||
| 				return fmt.Errorf("nil response from GetUser") |  | ||||||
| 			} |  | ||||||
| 			if getUserRes.User == nil { |  | ||||||
| 				return fmt.Errorf("nil user returned from GetUser") |  | ||||||
| 			} |  | ||||||
| 			if getUserRes.User.UserName == nil { |  | ||||||
| 				return fmt.Errorf("nil UserName returned from GetUser") |  | ||||||
| 			} |  | ||||||
|  |  | ||||||
| 			createAccessKeyInput := iam.CreateAccessKeyInput{ |  | ||||||
| 				UserName: getUserRes.User.UserName, |  | ||||||
| 			} |  | ||||||
| 			createAccessKeyRes, err := client.CreateAccessKeyWithContext(ctx, &createAccessKeyInput) |  | ||||||
| 			if err != nil { |  | ||||||
| 				return fmt.Errorf("error calling CreateAccessKey: %w", err) |  | ||||||
| 			} |  | ||||||
| 			if createAccessKeyRes.AccessKey == nil { |  | ||||||
| 				return fmt.Errorf("nil response from CreateAccessKey") |  | ||||||
| 			} |  | ||||||
| 			if createAccessKeyRes.AccessKey.AccessKeyId == nil || createAccessKeyRes.AccessKey.SecretAccessKey == nil { |  | ||||||
| 				return fmt.Errorf("nil AccessKeyId or SecretAccessKey returned from CreateAccessKey") |  | ||||||
| 			} |  | ||||||
|  |  | ||||||
| 			oldAccessKey := config.AccessKey |  | ||||||
|  |  | ||||||
| 			config.AccessKey = *createAccessKeyRes.AccessKey.AccessKeyId |  | ||||||
| 			config.SecretKey = *createAccessKeyRes.AccessKey.SecretAccessKey |  | ||||||
|  |  | ||||||
| 			newEntry, err := logical.StorageEntryJSON("config/root", config) |  | ||||||
| 			if err != nil { |  | ||||||
| 				return fmt.Errorf("error generating new config/root JSON: %w", err) |  | ||||||
| 			} |  | ||||||
| 			if err := req.Storage.Put(ctx, newEntry); err != nil { |  | ||||||
| 				return fmt.Errorf("error saving new config/root: %w", err) |  | ||||||
| 			} |  | ||||||
|  |  | ||||||
| 			b.iamClient = nil |  | ||||||
| 			b.stsClient = nil |  | ||||||
|  |  | ||||||
| 			deleteAccessKeyInput := iam.DeleteAccessKeyInput{ |  | ||||||
| 				AccessKeyId: aws.String(oldAccessKey), |  | ||||||
| 				UserName:    getUserRes.User.UserName, |  | ||||||
| 			} |  | ||||||
| 			_, err = client.DeleteAccessKeyWithContext(ctx, &deleteAccessKeyInput) |  | ||||||
| 			if err != nil { |  | ||||||
| 				return fmt.Errorf("error deleting old access key: %w", err) |  | ||||||
| 			} |  | ||||||
|  |  | ||||||
| 			return nil |  | ||||||
| 		}, | 		}, | ||||||
| 		BackendType: logical.TypeLogical, | 		BackendType: logical.TypeLogical, | ||||||
| 	} | 	} | ||||||
|   | |||||||
| @@ -62,7 +62,9 @@ func (m *mockIAMClient) CreateUserWithContext(_ aws.Context, input *iam.CreateUs | |||||||
| } | } | ||||||
|  |  | ||||||
| func getBackend(t *testing.T) logical.Backend { | func getBackend(t *testing.T) logical.Backend { | ||||||
| 	be, _ := Factory(context.Background(), logical.TestBackendConfig()) | 	cfg := logical.TestBackendConfig() | ||||||
|  | 	cfg.System = &testSystemView{} | ||||||
|  | 	be, _ := Factory(context.Background(), cfg) | ||||||
| 	return be | 	return be | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -109,11 +109,11 @@ func (b *backend) pathConfigRootRead(ctx context.Context, req *logical.Request, | |||||||
| 	b.clientMutex.RLock() | 	b.clientMutex.RLock() | ||||||
| 	defer b.clientMutex.RUnlock() | 	defer b.clientMutex.RUnlock() | ||||||
|  |  | ||||||
| 	config, exists, err := getConfigFromStorage(ctx, req) | 	config, err := getConfigFromStorage(ctx, req) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, err | 		return nil, err | ||||||
| 	} | 	} | ||||||
| 	if !exists { | 	if config == nil { | ||||||
| 		return nil, nil | 		return nil, nil | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| @@ -139,46 +139,90 @@ func (b *backend) pathConfigRootRead(ctx context.Context, req *logical.Request, | |||||||
| } | } | ||||||
|  |  | ||||||
| func (b *backend) pathConfigRootWrite(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) { | func (b *backend) pathConfigRootWrite(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) { | ||||||
| 	region := data.Get("region").(string) |  | ||||||
| 	iamendpoint := data.Get("iam_endpoint").(string) |  | ||||||
| 	stsendpoint := data.Get("sts_endpoint").(string) |  | ||||||
| 	stsregion := data.Get("sts_region").(string) |  | ||||||
| 	maxretries := data.Get("max_retries").(int) |  | ||||||
| 	roleARN := data.Get("role_arn").(string) |  | ||||||
| 	usernameTemplate := data.Get("username_template").(string) |  | ||||||
| 	if usernameTemplate == "" { |  | ||||||
| 		usernameTemplate = defaultUserNameTemplate |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	stsFallbackEndpoints := data.Get("sts_fallback_endpoints").([]string) |  | ||||||
| 	stsFallbackRegions := data.Get("sts_fallback_regions").([]string) |  | ||||||
|  |  | ||||||
| 	if len(stsFallbackEndpoints) != len(stsFallbackRegions) { |  | ||||||
| 		return logical.ErrorResponse("fallback endpoints and fallback regions must be the same length"), nil |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	b.clientMutex.Lock() | 	b.clientMutex.Lock() | ||||||
| 	defer b.clientMutex.Unlock() | 	defer b.clientMutex.Unlock() | ||||||
|  |  | ||||||
| 	// check for existing config | 	// check for existing config | ||||||
| 	previousCfg, previousCfgExists, err := getConfigFromStorage(ctx, req) | 	rc, err := getConfigFromStorage(ctx, req) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, err | 		return nil, err | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	rc := rootConfig{ | 	if rc == nil { | ||||||
| 		AccessKey:            data.Get("access_key").(string), | 		// Baseline | ||||||
| 		SecretKey:            data.Get("secret_key").(string), | 		rc = &rootConfig{} | ||||||
| 		IAMEndpoint:          iamendpoint, |  | ||||||
| 		STSEndpoint:          stsendpoint, |  | ||||||
| 		STSRegion:            stsregion, |  | ||||||
| 		STSFallbackEndpoints: stsFallbackEndpoints, |  | ||||||
| 		STSFallbackRegions:   stsFallbackRegions, |  | ||||||
| 		Region:               region, |  | ||||||
| 		MaxRetries:           maxretries, |  | ||||||
| 		UsernameTemplate:     usernameTemplate, |  | ||||||
| 		RoleARN:              roleARN, |  | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	if accessKey, ok := data.GetOk("access_key"); ok { | ||||||
|  | 		rc.AccessKey = accessKey.(string) | ||||||
|  | 	} else if req.Operation == logical.CreateOperation { | ||||||
|  | 		rc.AccessKey = data.Get("access_key").(string) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if secretKey, ok := data.GetOk("secret_key"); ok { | ||||||
|  | 		rc.SecretKey = secretKey.(string) | ||||||
|  | 	} else if req.Operation == logical.CreateOperation { | ||||||
|  | 		rc.SecretKey = data.Get("secret_key").(string) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if region, ok := data.GetOk("region"); ok { | ||||||
|  | 		rc.Region = region.(string) | ||||||
|  | 	} else if req.Operation == logical.CreateOperation { | ||||||
|  | 		rc.Region = data.Get("region").(string) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if iamEndpoint, ok := data.GetOk("iam_endpoint"); ok { | ||||||
|  | 		rc.IAMEndpoint = iamEndpoint.(string) | ||||||
|  | 	} else if req.Operation == logical.CreateOperation { | ||||||
|  | 		rc.IAMEndpoint = data.Get("iam_endpoint").(string) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if stsEndpoint, ok := data.GetOk("sts_endpoint"); ok { | ||||||
|  | 		rc.STSEndpoint = stsEndpoint.(string) | ||||||
|  | 	} else if req.Operation == logical.CreateOperation { | ||||||
|  | 		rc.STSEndpoint = data.Get("sts_endpoint").(string) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if stsRegion, ok := data.GetOk("sts_region"); ok { | ||||||
|  | 		rc.STSRegion = stsRegion.(string) | ||||||
|  | 	} else if req.Operation == logical.CreateOperation { | ||||||
|  | 		rc.STSRegion = data.Get("sts_region").(string) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if maxRetries, ok := data.GetOk("max_retries"); ok { | ||||||
|  | 		rc.MaxRetries = maxRetries.(int) | ||||||
|  | 	} else if req.Operation == logical.CreateOperation { | ||||||
|  | 		rc.MaxRetries = data.Get("max_retries").(int) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if roleARN, ok := data.GetOk("role_arn"); ok { | ||||||
|  | 		rc.RoleARN = roleARN.(string) | ||||||
|  | 	} else if req.Operation == logical.CreateOperation { | ||||||
|  | 		rc.RoleARN = data.Get("role_arn").(string) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if stsFallbackEndpoints, ok := data.GetOk("sts_fallback_endpoints"); ok { | ||||||
|  | 		rc.STSFallbackEndpoints = stsFallbackEndpoints.([]string) | ||||||
|  | 	} else if req.Operation == logical.CreateOperation { | ||||||
|  | 		rc.STSFallbackEndpoints = data.Get("sts_fallback_endpoints").([]string) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if stsFallbackRegions, ok := data.GetOk("sts_fallback_regions"); ok { | ||||||
|  | 		rc.STSFallbackRegions = stsFallbackRegions.([]string) | ||||||
|  | 	} else if req.Operation == logical.CreateOperation { | ||||||
|  | 		rc.STSFallbackRegions = data.Get("sts_fallback_regions").([]string) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	usernameTemplate := data.Get("username_template").(string) | ||||||
|  | 	if usernameTemplate == "" { | ||||||
|  | 		usernameTemplate = defaultUserNameTemplate | ||||||
|  | 	} | ||||||
|  | 	rc.UsernameTemplate = usernameTemplate | ||||||
|  |  | ||||||
|  | 	if len(rc.STSFallbackEndpoints) != len(rc.STSFallbackRegions) { | ||||||
|  | 		return logical.ErrorResponse("fallback endpoints and fallback regions must be the same length"), nil | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	if err := rc.ParsePluginIdentityTokenFields(data); err != nil { | 	if err := rc.ParsePluginIdentityTokenFields(data); err != nil { | ||||||
| 		return logical.ErrorResponse(err.Error()), nil | 		return logical.ErrorResponse(err.Error()), nil | ||||||
| 	} | 	} | ||||||
| @@ -206,48 +250,42 @@ func (b *backend) pathConfigRootWrite(ctx context.Context, req *logical.Request, | |||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	// Save the initial config only if it does not already exist | 	var performedRotationManagerOpern string | ||||||
| 	if !previousCfgExists { | 	if rc.ShouldDeregisterRotationJob() { | ||||||
| 		if err := putConfigToStorage(ctx, req, &rc); err != nil { | 		performedRotationManagerOpern = "deregistration" | ||||||
| 			return nil, err | 		// Disable Automated Rotation and Deregister credentials if required | ||||||
| 		} | 		deregisterReq := &rotation.RotationJobDeregisterRequest{ | ||||||
|  | 			MountPoint: req.MountPoint, | ||||||
|  | 			ReqPath:    req.Path, | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 	// Now that the root config is set up, register the rotation job if it required | 		b.Logger().Debug("Deregistering rotation job", "mount", req.MountPoint+req.Path) | ||||||
| 	if rc.ShouldRegisterRotationJob() { | 		if err := b.System().DeregisterRotationJob(ctx, deregisterReq); err != nil { | ||||||
|  | 			return logical.ErrorResponse("error deregistering rotation job: %s", err), nil | ||||||
|  | 		} | ||||||
|  | 	} else if rc.ShouldRegisterRotationJob() { | ||||||
|  | 		performedRotationManagerOpern = "registration" | ||||||
|  | 		// Register the rotation job if it's required. | ||||||
| 		cfgReq := &rotation.RotationJobConfigureRequest{ | 		cfgReq := &rotation.RotationJobConfigureRequest{ | ||||||
| 			Name:             rootRotationJobName, | 			MountPoint:       req.MountPoint, | ||||||
| 			MountType:        req.MountType, |  | ||||||
| 			ReqPath:          req.Path, | 			ReqPath:          req.Path, | ||||||
| 			RotationSchedule: rc.RotationSchedule, | 			RotationSchedule: rc.RotationSchedule, | ||||||
| 			RotationWindow:   rc.RotationWindow, | 			RotationWindow:   rc.RotationWindow, | ||||||
| 			RotationPeriod:   rc.RotationPeriod, | 			RotationPeriod:   rc.RotationPeriod, | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		_, err = b.System().RegisterRotationJob(ctx, cfgReq) | 		b.Logger().Debug("Registering rotation job", "mount", req.MountPoint+req.Path) | ||||||
| 		if err != nil { | 		if _, err = b.System().RegisterRotationJob(ctx, cfgReq); err != nil { | ||||||
| 			return logical.ErrorResponse("error registering rotation job: %s", err), nil | 			return logical.ErrorResponse("error registering rotation job: %s", err), nil | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	// Disable Automated Rotation and Deregister credentials if required | 	// Save the config | ||||||
| 	if rc.DisableAutomatedRotation { | 	if err := putConfigToStorage(ctx, req, rc); err != nil { | ||||||
| 		// Ensure de-registering only occurs on updates and if | 		if performedRotationManagerOpern != "" { | ||||||
| 		// a credential has actually been registered (rotation_period or rotation_schedule is set) | 			b.Logger().Error("write to storage failed but the rotation manager still succeeded.", | ||||||
| 		deregisterReq := &rotation.RotationJobDeregisterRequest{ | 				"operation", performedRotationManagerOpern, "mount", req.MountPoint, "path", req.Path) | ||||||
| 			MountType: req.MountType, |  | ||||||
| 			ReqPath:   req.Path, |  | ||||||
| 		} | 		} | ||||||
| 		if previousCfgExists && previousCfg.ShouldRegisterRotationJob() { |  | ||||||
| 			err := b.System().DeregisterRotationJob(ctx, deregisterReq) |  | ||||||
| 			if err != nil { |  | ||||||
| 				return logical.ErrorResponse("error de-registering rotation job: %s", err), nil |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	// update config entry with rotation ID |  | ||||||
| 	if err := putConfigToStorage(ctx, req, &rc); err != nil { |  | ||||||
| 		return nil, err | 		return nil, err | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| @@ -259,22 +297,22 @@ func (b *backend) pathConfigRootWrite(ctx context.Context, req *logical.Request, | |||||||
| 	return nil, nil | 	return nil, nil | ||||||
| } | } | ||||||
|  |  | ||||||
| func getConfigFromStorage(ctx context.Context, req *logical.Request) (*rootConfig, bool, error) { | func getConfigFromStorage(ctx context.Context, req *logical.Request) (*rootConfig, error) { | ||||||
| 	entry, err := req.Storage.Get(ctx, "config/root") | 	entry, err := req.Storage.Get(ctx, "config/root") | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, false, err | 		return nil, err | ||||||
| 	} | 	} | ||||||
| 	if entry == nil { | 	if entry == nil { | ||||||
| 		return nil, false, nil | 		return nil, nil | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	var config rootConfig | 	var config rootConfig | ||||||
|  |  | ||||||
| 	if err := entry.DecodeJSON(&config); err != nil { | 	if err := entry.DecodeJSON(&config); err != nil { | ||||||
| 		return nil, false, err | 		return nil, err | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	return &config, true, nil | 	return &config, nil | ||||||
| } | } | ||||||
|  |  | ||||||
| func putConfigToStorage(ctx context.Context, req *logical.Request, rc *rootConfig) error { | func putConfigToStorage(ctx context.Context, req *logical.Request, rc *rootConfig) error { | ||||||
|   | |||||||
| @@ -21,6 +21,7 @@ import ( | |||||||
| func TestBackend_PathConfigRoot(t *testing.T) { | func TestBackend_PathConfigRoot(t *testing.T) { | ||||||
| 	config := logical.TestBackendConfig() | 	config := logical.TestBackendConfig() | ||||||
| 	config.StorageView = &logical.InmemStorage{} | 	config.StorageView = &logical.InmemStorage{} | ||||||
|  | 	config.System = &testSystemView{} | ||||||
|  |  | ||||||
| 	b := Backend(config) | 	b := Backend(config) | ||||||
| 	if err := b.Setup(context.Background(), config); err != nil { | 	if err := b.Setup(context.Background(), config); err != nil { | ||||||
| @@ -280,8 +281,8 @@ func TestBackend_PathConfigRoot_RegisterRootRotation(t *testing.T) { | |||||||
| 	configData := map[string]interface{}{ | 	configData := map[string]interface{}{ | ||||||
| 		"access_key":        "access-key", | 		"access_key":        "access-key", | ||||||
| 		"secret_key":        "secret-key", | 		"secret_key":        "secret-key", | ||||||
| 		"rotation_schedule": "*/30 * * * * *", | 		"rotation_schedule": "*/1 * * * *", | ||||||
| 		"rotation_window":   60, | 		"rotation_window":   120, | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	configReq := &logical.Request{ | 	configReq := &logical.Request{ | ||||||
| @@ -308,3 +309,7 @@ func (d testSystemView) GenerateIdentityToken(_ context.Context, _ *pluginutil.I | |||||||
| func (d testSystemView) RegisterRotationJob(_ context.Context, _ *rotation.RotationJobConfigureRequest) (string, error) { | func (d testSystemView) RegisterRotationJob(_ context.Context, _ *rotation.RotationJobConfigureRequest) (string, error) { | ||||||
| 	return "", automatedrotationutil.ErrRotationManagerUnsupported | 	return "", automatedrotationutil.ErrRotationManagerUnsupported | ||||||
| } | } | ||||||
|  |  | ||||||
|  | func (d testSystemView) DeregisterRotationJob(_ context.Context, _ *rotation.RotationJobDeregisterRequest) error { | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|   | |||||||
| @@ -37,6 +37,10 @@ func pathConfigRotateRoot(b *backend) *framework.Path { | |||||||
| } | } | ||||||
|  |  | ||||||
| func (b *backend) pathConfigRotateRootUpdate(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) { | func (b *backend) pathConfigRotateRootUpdate(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) { | ||||||
|  | 	return b.rotateRoot(ctx, req) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (b *backend) rotateRoot(ctx context.Context, req *logical.Request) (*logical.Response, error) { | ||||||
| 	// have to get the client config first because that takes out a read lock | 	// have to get the client config first because that takes out a read lock | ||||||
| 	client, err := b.clientIAM(ctx, req.Storage) | 	client, err := b.clientIAM(ctx, req.Storage) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
|   | |||||||
| @@ -120,6 +120,8 @@ func TestGenUsername(t *testing.T) { | |||||||
| func TestReadConfig_DefaultTemplate(t *testing.T) { | func TestReadConfig_DefaultTemplate(t *testing.T) { | ||||||
| 	config := logical.TestBackendConfig() | 	config := logical.TestBackendConfig() | ||||||
| 	config.StorageView = &logical.InmemStorage{} | 	config.StorageView = &logical.InmemStorage{} | ||||||
|  | 	config.System = &testSystemView{} | ||||||
|  |  | ||||||
| 	b := Backend(config) | 	b := Backend(config) | ||||||
| 	if err := b.Setup(context.Background(), config); err != nil { | 	if err := b.Setup(context.Background(), config); err != nil { | ||||||
| 		t.Fatal(err) | 		t.Fatal(err) | ||||||
| @@ -164,6 +166,8 @@ func TestReadConfig_DefaultTemplate(t *testing.T) { | |||||||
| func TestReadConfig_CustomTemplate(t *testing.T) { | func TestReadConfig_CustomTemplate(t *testing.T) { | ||||||
| 	config := logical.TestBackendConfig() | 	config := logical.TestBackendConfig() | ||||||
| 	config.StorageView = &logical.InmemStorage{} | 	config.StorageView = &logical.InmemStorage{} | ||||||
|  | 	config.System = &testSystemView{} | ||||||
|  |  | ||||||
| 	b := Backend(config) | 	b := Backend(config) | ||||||
| 	if err := b.Setup(context.Background(), config); err != nil { | 	if err := b.Setup(context.Background(), config); err != nil { | ||||||
| 		t.Fatal(err) | 		t.Fatal(err) | ||||||
|   | |||||||
							
								
								
									
										5
									
								
								changelog/29497.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								changelog/29497.txt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,5 @@ | |||||||
|  | ```release-note:feature | ||||||
|  | **Automated Root Rotation**: Adds Automated Root Rotation capabilities to the AWS Auth, AWS Secrets | ||||||
|  | and DB Secrets plugins. This allows plugin users to automate their root credential rotations based | ||||||
|  | on configurable schedules/periods via the Rotation Manager. Note: Enterprise only. | ||||||
|  | ``` | ||||||
| @@ -8,6 +8,7 @@ import ( | |||||||
| 	"fmt" | 	"fmt" | ||||||
|  |  | ||||||
| 	"github.com/hashicorp/vault/sdk/framework" | 	"github.com/hashicorp/vault/sdk/framework" | ||||||
|  | 	"github.com/hashicorp/vault/sdk/rotation" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| var ( | var ( | ||||||
| @@ -41,6 +42,14 @@ func (p *AutomatedRotationParams) ParseAutomatedRotationFields(d *framework.Fiel | |||||||
| 			return ErrRotationMutuallyExclusiveFields | 			return ErrRotationMutuallyExclusiveFields | ||||||
| 		} | 		} | ||||||
| 		p.RotationSchedule = rotationScheduleRaw.(string) | 		p.RotationSchedule = rotationScheduleRaw.(string) | ||||||
|  |  | ||||||
|  | 		// parse schedule to ensure it is valid | ||||||
|  | 		if p.RotationSchedule != "" { | ||||||
|  | 			_, err := rotation.DefaultScheduler.Parse(p.RotationSchedule) | ||||||
|  | 			if err != nil { | ||||||
|  | 				return fmt.Errorf("failed to parse provided rotation_schedule: %w", err) | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if windowOk { | 	if windowOk { | ||||||
| @@ -75,6 +84,10 @@ func (p *AutomatedRotationParams) ShouldRegisterRotationJob() bool { | |||||||
| 	return p.RotationSchedule != "" || p.RotationPeriod != 0 | 	return p.RotationSchedule != "" || p.RotationPeriod != 0 | ||||||
| } | } | ||||||
|  |  | ||||||
|  | func (p *AutomatedRotationParams) ShouldDeregisterRotationJob() bool { | ||||||
|  | 	return p.DisableAutomatedRotation || (p.RotationSchedule == "" && p.RotationPeriod == 0) | ||||||
|  | } | ||||||
|  |  | ||||||
| // AddAutomatedRotationFields adds plugin identity token fields to the given | // AddAutomatedRotationFields adds plugin identity token fields to the given | ||||||
| // field schema map. | // field schema map. | ||||||
| func AddAutomatedRotationFields(m map[string]*framework.FieldSchema) { | func AddAutomatedRotationFields(m map[string]*framework.FieldSchema) { | ||||||
|   | |||||||
| @@ -41,13 +41,13 @@ func TestParseAutomatedRotationFields(t *testing.T) { | |||||||
| 			name: "basic", | 			name: "basic", | ||||||
| 			data: &framework.FieldData{ | 			data: &framework.FieldData{ | ||||||
| 				Raw: map[string]interface{}{ | 				Raw: map[string]interface{}{ | ||||||
| 					"rotation_schedule": "*/15 * * * * *", | 					"rotation_schedule": "*/15 * * * *", | ||||||
| 					"rotation_window":   60, | 					"rotation_window":   60, | ||||||
| 				}, | 				}, | ||||||
| 				Schema: schemaMap, | 				Schema: schemaMap, | ||||||
| 			}, | 			}, | ||||||
| 			expectedParams: &AutomatedRotationParams{ | 			expectedParams: &AutomatedRotationParams{ | ||||||
| 				RotationSchedule:         "*/15 * * * * *", | 				RotationSchedule:         "*/15 * * * *", | ||||||
| 				RotationWindow:           60, | 				RotationWindow:           60, | ||||||
| 				RotationPeriod:           0, | 				RotationPeriod:           0, | ||||||
| 				DisableAutomatedRotation: false, | 				DisableAutomatedRotation: false, | ||||||
| @@ -57,7 +57,7 @@ func TestParseAutomatedRotationFields(t *testing.T) { | |||||||
| 			name: "mutually-exclusive", | 			name: "mutually-exclusive", | ||||||
| 			data: &framework.FieldData{ | 			data: &framework.FieldData{ | ||||||
| 				Raw: map[string]interface{}{ | 				Raw: map[string]interface{}{ | ||||||
| 					"rotation_schedule": "*/15 * * * * *", | 					"rotation_schedule": "*/15 * * * *", | ||||||
| 					"rotation_period":   15, | 					"rotation_period":   15, | ||||||
| 					"rotation_window":   60, | 					"rotation_window":   60, | ||||||
| 				}, | 				}, | ||||||
| @@ -65,6 +65,16 @@ func TestParseAutomatedRotationFields(t *testing.T) { | |||||||
| 			}, | 			}, | ||||||
| 			expectedError: ErrRotationMutuallyExclusiveFields.Error(), | 			expectedError: ErrRotationMutuallyExclusiveFields.Error(), | ||||||
| 		}, | 		}, | ||||||
|  | 		{ | ||||||
|  | 			name: "bad-schedule", | ||||||
|  | 			data: &framework.FieldData{ | ||||||
|  | 				Raw: map[string]interface{}{ | ||||||
|  | 					"rotation_schedule": "random string", | ||||||
|  | 				}, | ||||||
|  | 				Schema: schemaMap, | ||||||
|  | 			}, | ||||||
|  | 			expectedError: "failed to parse provided rotation_schedule", | ||||||
|  | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name: "incompatible-fields", | 			name: "incompatible-fields", | ||||||
| 			data: &framework.FieldData{ | 			data: &framework.FieldData{ | ||||||
| @@ -82,13 +92,17 @@ func TestParseAutomatedRotationFields(t *testing.T) { | |||||||
| 		t.Run(tt.name, func(t *testing.T) { | 		t.Run(tt.name, func(t *testing.T) { | ||||||
| 			p := &AutomatedRotationParams{} | 			p := &AutomatedRotationParams{} | ||||||
| 			err := p.ParseAutomatedRotationFields(tt.data) | 			err := p.ParseAutomatedRotationFields(tt.data) | ||||||
|  | 			if err != nil { | ||||||
| 			if err != nil && !strings.Contains(err.Error(), tt.expectedError) { | 				if tt.expectedError == "" { | ||||||
|  | 					t.Errorf("expected no error but received an error: %s", err) | ||||||
|  | 				} | ||||||
|  | 				if !strings.Contains(err.Error(), tt.expectedError) { | ||||||
| 					t.Errorf("ParseAutomatedRotationFields() error = %v, expected %s", err, tt.expectedError) | 					t.Errorf("ParseAutomatedRotationFields() error = %v, expected %s", err, tt.expectedError) | ||||||
| 				} | 				} | ||||||
|  | 			} | ||||||
|  |  | ||||||
| 			if err == nil && !reflect.DeepEqual(tt.expectedParams, p) { | 			if err == nil && !reflect.DeepEqual(tt.expectedParams, p) { | ||||||
| 				t.Errorf("ParseAutomatedRotationFields() error comparing params; got %v, expected %v", tt.expectedParams, p) | 				t.Errorf("ParseAutomatedRotationFields() error comparing params; got %v, expected %v", p, tt.expectedParams) | ||||||
| 			} | 			} | ||||||
| 		}) | 		}) | ||||||
| 	} | 	} | ||||||
| @@ -103,13 +117,13 @@ func TestPopulateAutomatedRotationData(t *testing.T) { | |||||||
| 		{ | 		{ | ||||||
| 			name: "basic", | 			name: "basic", | ||||||
| 			expected: map[string]interface{}{ | 			expected: map[string]interface{}{ | ||||||
| 				"rotation_schedule":          "*/15 * * * * *", | 				"rotation_schedule":          "*/15 * * * *", | ||||||
| 				"rotation_window":            60, | 				"rotation_window":            60, | ||||||
| 				"rotation_period":            0, | 				"rotation_period":            0, | ||||||
| 				"disable_automated_rotation": false, | 				"disable_automated_rotation": false, | ||||||
| 			}, | 			}, | ||||||
| 			inputParams: &AutomatedRotationParams{ | 			inputParams: &AutomatedRotationParams{ | ||||||
| 				RotationSchedule:         "*/15 * * * * *", | 				RotationSchedule:         "*/15 * * * *", | ||||||
| 				RotationWindow:           60, | 				RotationWindow:           60, | ||||||
| 				RotationPeriod:           0, | 				RotationPeriod:           0, | ||||||
| 				DisableAutomatedRotation: false, | 				DisableAutomatedRotation: false, | ||||||
| @@ -129,3 +143,109 @@ func TestPopulateAutomatedRotationData(t *testing.T) { | |||||||
| 		}) | 		}) | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
|  | func TestShouldRegisterRotationJob(t *testing.T) { | ||||||
|  | 	tests := []struct { | ||||||
|  | 		name        string | ||||||
|  | 		inputParams *AutomatedRotationParams | ||||||
|  | 		expected    bool | ||||||
|  | 	}{ | ||||||
|  | 		{ | ||||||
|  | 			name:     "false", | ||||||
|  | 			expected: false, | ||||||
|  | 			inputParams: &AutomatedRotationParams{ | ||||||
|  | 				RotationSchedule:         "", | ||||||
|  | 				RotationWindow:           60, | ||||||
|  | 				RotationPeriod:           0, | ||||||
|  | 				DisableAutomatedRotation: false, | ||||||
|  | 			}, | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			name:     "true-schedule", | ||||||
|  | 			expected: true, | ||||||
|  | 			inputParams: &AutomatedRotationParams{ | ||||||
|  | 				RotationSchedule:         "*/15 * * * *", | ||||||
|  | 				RotationWindow:           0, | ||||||
|  | 				RotationPeriod:           0, | ||||||
|  | 				DisableAutomatedRotation: false, | ||||||
|  | 			}, | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			name:     "true-period", | ||||||
|  | 			expected: true, | ||||||
|  | 			inputParams: &AutomatedRotationParams{ | ||||||
|  | 				RotationSchedule:         "", | ||||||
|  | 				RotationWindow:           0, | ||||||
|  | 				RotationPeriod:           5, | ||||||
|  | 				DisableAutomatedRotation: false, | ||||||
|  | 			}, | ||||||
|  | 		}, | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	for _, tt := range tests { | ||||||
|  | 		t.Run(tt.name, func(t *testing.T) { | ||||||
|  | 			out := tt.inputParams.ShouldRegisterRotationJob() | ||||||
|  | 			if out != tt.expected { | ||||||
|  | 				t.Errorf("ShouldRegisterRotationJob() output = %v, expected: %v", out, tt.expected) | ||||||
|  | 			} | ||||||
|  | 		}) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func TestShouldDeregisterRotationJob(t *testing.T) { | ||||||
|  | 	tests := []struct { | ||||||
|  | 		name        string | ||||||
|  | 		inputParams *AutomatedRotationParams | ||||||
|  | 		expected    bool | ||||||
|  | 	}{ | ||||||
|  | 		{ | ||||||
|  | 			name:     "zero out schedule and period", | ||||||
|  | 			expected: true, | ||||||
|  | 			inputParams: &AutomatedRotationParams{ | ||||||
|  | 				RotationSchedule:         "", | ||||||
|  | 				RotationWindow:           60, | ||||||
|  | 				RotationPeriod:           0, | ||||||
|  | 				DisableAutomatedRotation: false, | ||||||
|  | 			}, | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			name:     "false-schedule set", | ||||||
|  | 			expected: false, | ||||||
|  | 			inputParams: &AutomatedRotationParams{ | ||||||
|  | 				RotationSchedule:         "*/15 * * * *", | ||||||
|  | 				RotationWindow:           0, | ||||||
|  | 				RotationPeriod:           0, | ||||||
|  | 				DisableAutomatedRotation: false, | ||||||
|  | 			}, | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			name:     "false-period set", | ||||||
|  | 			expected: false, | ||||||
|  | 			inputParams: &AutomatedRotationParams{ | ||||||
|  | 				RotationSchedule:         "", | ||||||
|  | 				RotationWindow:           0, | ||||||
|  | 				RotationPeriod:           5, | ||||||
|  | 				DisableAutomatedRotation: false, | ||||||
|  | 			}, | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			name:     "true-disable set", | ||||||
|  | 			expected: true, | ||||||
|  | 			inputParams: &AutomatedRotationParams{ | ||||||
|  | 				RotationSchedule:         "*/15 * * * *", | ||||||
|  | 				RotationWindow:           0, | ||||||
|  | 				RotationPeriod:           0, | ||||||
|  | 				DisableAutomatedRotation: true, | ||||||
|  | 			}, | ||||||
|  | 		}, | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	for _, tt := range tests { | ||||||
|  | 		t.Run(tt.name, func(t *testing.T) { | ||||||
|  | 			out := tt.inputParams.ShouldDeregisterRotationJob() | ||||||
|  | 			if out != tt.expected { | ||||||
|  | 				t.Errorf("ShouldRegisterRotationJob() output = %v, expected: %v", out, tt.expected) | ||||||
|  | 			} | ||||||
|  | 		}) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|   | |||||||
| @@ -231,7 +231,7 @@ func (s *gRPCSystemViewClient) RegisterRotationJob(ctx context.Context, req *rot | |||||||
| 	cfgReq := &pb.RegisterRotationJobRequest{ | 	cfgReq := &pb.RegisterRotationJobRequest{ | ||||||
| 		Job: &pb.RotationJobInput{ | 		Job: &pb.RotationJobInput{ | ||||||
| 			Name:             req.Name, | 			Name:             req.Name, | ||||||
| 			MountType:        req.MountType, | 			MountPoint:       req.MountPoint, | ||||||
| 			Path:             req.ReqPath, | 			Path:             req.ReqPath, | ||||||
| 			RotationSchedule: req.RotationSchedule, | 			RotationSchedule: req.RotationSchedule, | ||||||
| 			RotationWindow:   int64(req.RotationWindow), | 			RotationWindow:   int64(req.RotationWindow), | ||||||
| @@ -248,7 +248,7 @@ func (s *gRPCSystemViewClient) RegisterRotationJob(ctx context.Context, req *rot | |||||||
| func (s *gRPCSystemViewClient) DeregisterRotationJob(ctx context.Context, req *rotation.RotationJobDeregisterRequest) error { | func (s *gRPCSystemViewClient) DeregisterRotationJob(ctx context.Context, req *rotation.RotationJobDeregisterRequest) error { | ||||||
| 	_, err := s.client.DeregisterRotationJob(ctx, &pb.DeregisterRotationJobRequest{ | 	_, err := s.client.DeregisterRotationJob(ctx, &pb.DeregisterRotationJobRequest{ | ||||||
| 		Req: &pb.DeregisterRotationRequestInput{ | 		Req: &pb.DeregisterRotationRequestInput{ | ||||||
| 			MountType: req.MountType, | 			MountPoint: req.MountPoint, | ||||||
| 			ReqPath:    req.ReqPath, | 			ReqPath:    req.ReqPath, | ||||||
| 		}, | 		}, | ||||||
| 	}) | 	}) | ||||||
| @@ -469,7 +469,7 @@ func (s *gRPCSystemViewServer) RegisterRotationJob(ctx context.Context, req *pb. | |||||||
|  |  | ||||||
| 	cfgReq := &rotation.RotationJobConfigureRequest{ | 	cfgReq := &rotation.RotationJobConfigureRequest{ | ||||||
| 		Name:             req.Job.Name, | 		Name:             req.Job.Name, | ||||||
| 		MountType:        req.Job.MountType, | 		MountPoint:       req.Job.MountPoint, | ||||||
| 		ReqPath:          req.Job.Path, | 		ReqPath:          req.Job.Path, | ||||||
| 		RotationSchedule: req.Job.RotationSchedule, | 		RotationSchedule: req.Job.RotationSchedule, | ||||||
| 		RotationWindow:   int(req.Job.RotationWindow), | 		RotationWindow:   int(req.Job.RotationWindow), | ||||||
| @@ -493,7 +493,7 @@ func (s *gRPCSystemViewServer) DeregisterRotationJob(ctx context.Context, req *p | |||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	cfgReq := &rotation.RotationJobDeregisterRequest{ | 	cfgReq := &rotation.RotationJobDeregisterRequest{ | ||||||
| 		MountType: req.Req.MountType, | 		MountPoint: req.Req.MountPoint, | ||||||
| 		ReqPath:    req.Req.ReqPath, | 		ReqPath:    req.Req.ReqPath, | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|   | |||||||
| @@ -3310,7 +3310,7 @@ func (x *RegisterRotationJobResponse) GetErr() string { | |||||||
| type RotationJobInput struct { | type RotationJobInput struct { | ||||||
| 	state            protoimpl.MessageState `protogen:"open.v1"` | 	state            protoimpl.MessageState `protogen:"open.v1"` | ||||||
| 	Name             string                 `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` | 	Name             string                 `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` | ||||||
| 	MountType        string                 `protobuf:"bytes,2,opt,name=mount_type,json=mountType,proto3" json:"mount_type,omitempty"` | 	MountPoint       string                 `protobuf:"bytes,2,opt,name=mount_point,json=mountPoint,proto3" json:"mount_point,omitempty"` | ||||||
| 	Path             string                 `protobuf:"bytes,3,opt,name=path,proto3" json:"path,omitempty"` | 	Path             string                 `protobuf:"bytes,3,opt,name=path,proto3" json:"path,omitempty"` | ||||||
| 	RotationSchedule string                 `protobuf:"bytes,4,opt,name=rotation_schedule,json=rotationSchedule,proto3" json:"rotation_schedule,omitempty"` | 	RotationSchedule string                 `protobuf:"bytes,4,opt,name=rotation_schedule,json=rotationSchedule,proto3" json:"rotation_schedule,omitempty"` | ||||||
| 	RotationWindow   int64                  `protobuf:"varint,5,opt,name=rotation_window,json=rotationWindow,proto3" json:"rotation_window,omitempty"` | 	RotationWindow   int64                  `protobuf:"varint,5,opt,name=rotation_window,json=rotationWindow,proto3" json:"rotation_window,omitempty"` | ||||||
| @@ -3356,9 +3356,9 @@ func (x *RotationJobInput) GetName() string { | |||||||
| 	return "" | 	return "" | ||||||
| } | } | ||||||
|  |  | ||||||
| func (x *RotationJobInput) GetMountType() string { | func (x *RotationJobInput) GetMountPoint() string { | ||||||
| 	if x != nil { | 	if x != nil { | ||||||
| 		return x.MountType | 		return x.MountPoint | ||||||
| 	} | 	} | ||||||
| 	return "" | 	return "" | ||||||
| } | } | ||||||
| @@ -3393,7 +3393,7 @@ func (x *RotationJobInput) GetRotationPeriod() int64 { | |||||||
|  |  | ||||||
| type DeregisterRotationRequestInput struct { | type DeregisterRotationRequestInput struct { | ||||||
| 	state         protoimpl.MessageState `protogen:"open.v1"` | 	state         protoimpl.MessageState `protogen:"open.v1"` | ||||||
| 	MountType     string                 `protobuf:"bytes,1,opt,name=mount_type,json=mountType,proto3" json:"mount_type,omitempty"` | 	MountPoint    string                 `protobuf:"bytes,1,opt,name=mount_point,json=mountPoint,proto3" json:"mount_point,omitempty"` | ||||||
| 	ReqPath       string                 `protobuf:"bytes,2,opt,name=req_path,json=reqPath,proto3" json:"req_path,omitempty"` | 	ReqPath       string                 `protobuf:"bytes,2,opt,name=req_path,json=reqPath,proto3" json:"req_path,omitempty"` | ||||||
| 	unknownFields protoimpl.UnknownFields | 	unknownFields protoimpl.UnknownFields | ||||||
| 	sizeCache     protoimpl.SizeCache | 	sizeCache     protoimpl.SizeCache | ||||||
| @@ -3429,9 +3429,9 @@ func (*DeregisterRotationRequestInput) Descriptor() ([]byte, []int) { | |||||||
| 	return file_sdk_plugin_pb_backend_proto_rawDescGZIP(), []int{52} | 	return file_sdk_plugin_pb_backend_proto_rawDescGZIP(), []int{52} | ||||||
| } | } | ||||||
|  |  | ||||||
| func (x *DeregisterRotationRequestInput) GetMountType() string { | func (x *DeregisterRotationRequestInput) GetMountPoint() string { | ||||||
| 	if x != nil { | 	if x != nil { | ||||||
| 		return x.MountType | 		return x.MountPoint | ||||||
| 	} | 	} | ||||||
| 	return "" | 	return "" | ||||||
| } | } | ||||||
| @@ -4241,198 +4241,198 @@ var file_sdk_plugin_pb_backend_proto_rawDesc = []byte{ | |||||||
| 	0x0a, 0x0b, 0x72, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, | 	0x0a, 0x0b, 0x72, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, | ||||||
| 	0x01, 0x28, 0x09, 0x52, 0x0a, 0x72, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, | 	0x01, 0x28, 0x09, 0x52, 0x0a, 0x72, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, | ||||||
| 	0x10, 0x0a, 0x03, 0x65, 0x72, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x65, 0x72, | 	0x10, 0x0a, 0x03, 0x65, 0x72, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x65, 0x72, | ||||||
| 	0x72, 0x22, 0xd8, 0x01, 0x0a, 0x10, 0x52, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4a, 0x6f, | 	0x72, 0x22, 0xda, 0x01, 0x0a, 0x10, 0x52, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4a, 0x6f, | ||||||
| 	0x62, 0x49, 0x6e, 0x70, 0x75, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, | 	0x62, 0x49, 0x6e, 0x70, 0x75, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, | ||||||
| 	0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x6d, 0x6f, | 	0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x6d, 0x6f, | ||||||
| 	0x75, 0x6e, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, | 	0x75, 0x6e, 0x74, 0x5f, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, | ||||||
| 	0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x61, 0x74, | 	0x0a, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x70, | ||||||
| 	0x68, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x70, 0x61, 0x74, 0x68, 0x12, 0x2b, 0x0a, | 	0x61, 0x74, 0x68, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x70, 0x61, 0x74, 0x68, 0x12, | ||||||
| 	0x11, 0x72, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x73, 0x63, 0x68, 0x65, 0x64, 0x75, | 	0x2b, 0x0a, 0x11, 0x72, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x73, 0x63, 0x68, 0x65, | ||||||
| 	0x6c, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x72, 0x6f, 0x74, 0x61, 0x74, 0x69, | 	0x64, 0x75, 0x6c, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x72, 0x6f, 0x74, 0x61, | ||||||
| 	0x6f, 0x6e, 0x53, 0x63, 0x68, 0x65, 0x64, 0x75, 0x6c, 0x65, 0x12, 0x27, 0x0a, 0x0f, 0x72, 0x6f, | 	0x74, 0x69, 0x6f, 0x6e, 0x53, 0x63, 0x68, 0x65, 0x64, 0x75, 0x6c, 0x65, 0x12, 0x27, 0x0a, 0x0f, | ||||||
| 	0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x77, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x18, 0x05, 0x20, | 	0x72, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x77, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x18, | ||||||
| 	0x01, 0x28, 0x03, 0x52, 0x0e, 0x72, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x57, 0x69, 0x6e, | 	0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x72, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x57, | ||||||
| 	0x64, 0x6f, 0x77, 0x12, 0x27, 0x0a, 0x0f, 0x72, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, | 	0x69, 0x6e, 0x64, 0x6f, 0x77, 0x12, 0x27, 0x0a, 0x0f, 0x72, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, | ||||||
| 	0x70, 0x65, 0x72, 0x69, 0x6f, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x72, 0x6f, | 	0x6e, 0x5f, 0x70, 0x65, 0x72, 0x69, 0x6f, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, | ||||||
| 	0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x50, 0x65, 0x72, 0x69, 0x6f, 0x64, 0x22, 0x5a, 0x0a, 0x1e, | 	0x72, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x50, 0x65, 0x72, 0x69, 0x6f, 0x64, 0x22, 0x5c, | ||||||
|  | 	0x0a, 0x1e, 0x44, 0x65, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x52, 0x6f, 0x74, 0x61, | ||||||
|  | 	0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x6e, 0x70, 0x75, 0x74, | ||||||
|  | 	0x12, 0x1f, 0x0a, 0x0b, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, | ||||||
|  | 	0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x50, 0x6f, 0x69, 0x6e, | ||||||
|  | 	0x74, 0x12, 0x19, 0x0a, 0x08, 0x72, 0x65, 0x71, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x18, 0x02, 0x20, | ||||||
|  | 	0x01, 0x28, 0x09, 0x52, 0x07, 0x72, 0x65, 0x71, 0x50, 0x61, 0x74, 0x68, 0x22, 0x54, 0x0a, 0x1c, | ||||||
| 	0x44, 0x65, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x52, 0x6f, 0x74, 0x61, 0x74, 0x69, | 	0x44, 0x65, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x52, 0x6f, 0x74, 0x61, 0x74, 0x69, | ||||||
| 	0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x6e, 0x70, 0x75, 0x74, 0x12, 0x1d, | 	0x6f, 0x6e, 0x4a, 0x6f, 0x62, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x34, 0x0a, 0x03, | ||||||
| 	0x0a, 0x0a, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, | 	0x72, 0x65, 0x71, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x70, 0x62, 0x2e, 0x44, | ||||||
| 	0x28, 0x09, 0x52, 0x09, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x19, 0x0a, | 	0x65, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x52, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, | ||||||
| 	0x08, 0x72, 0x65, 0x71, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, | 	0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x6e, 0x70, 0x75, 0x74, 0x52, 0x03, 0x72, | ||||||
| 	0x07, 0x72, 0x65, 0x71, 0x50, 0x61, 0x74, 0x68, 0x22, 0x54, 0x0a, 0x1c, 0x44, 0x65, 0x72, 0x65, | 	0x65, 0x71, 0x22, 0x8e, 0x01, 0x0a, 0x0a, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, | ||||||
|  | 	0x6e, 0x12, 0x1f, 0x0a, 0x0b, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x5f, 0x61, 0x64, 0x64, 0x72, | ||||||
|  | 	0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x41, 0x64, | ||||||
|  | 	0x64, 0x72, 0x12, 0x1f, 0x0a, 0x0b, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x5f, 0x70, 0x6f, 0x72, | ||||||
|  | 	0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0a, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x50, | ||||||
|  | 	0x6f, 0x72, 0x74, 0x12, 0x3e, 0x0a, 0x10, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, | ||||||
|  | 	0x6e, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, | ||||||
|  | 	0x70, 0x62, 0x2e, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x61, | ||||||
|  | 	0x74, 0x65, 0x52, 0x0f, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, | ||||||
|  | 	0x61, 0x74, 0x65, 0x22, 0xbb, 0x04, 0x0a, 0x0f, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, | ||||||
|  | 	0x6f, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, | ||||||
|  | 	0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, | ||||||
|  | 	0x6e, 0x12, 0x2d, 0x0a, 0x12, 0x68, 0x61, 0x6e, 0x64, 0x73, 0x68, 0x61, 0x6b, 0x65, 0x5f, 0x63, | ||||||
|  | 	0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x11, 0x68, | ||||||
|  | 	0x61, 0x6e, 0x64, 0x73, 0x68, 0x61, 0x6b, 0x65, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, | ||||||
|  | 	0x12, 0x1d, 0x0a, 0x0a, 0x64, 0x69, 0x64, 0x5f, 0x72, 0x65, 0x73, 0x75, 0x6d, 0x65, 0x18, 0x03, | ||||||
|  | 	0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x64, 0x69, 0x64, 0x52, 0x65, 0x73, 0x75, 0x6d, 0x65, 0x12, | ||||||
|  | 	0x21, 0x0a, 0x0c, 0x63, 0x69, 0x70, 0x68, 0x65, 0x72, 0x5f, 0x73, 0x75, 0x69, 0x74, 0x65, 0x18, | ||||||
|  | 	0x04, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0b, 0x63, 0x69, 0x70, 0x68, 0x65, 0x72, 0x53, 0x75, 0x69, | ||||||
|  | 	0x74, 0x65, 0x12, 0x2f, 0x0a, 0x13, 0x6e, 0x65, 0x67, 0x6f, 0x74, 0x69, 0x61, 0x74, 0x65, 0x64, | ||||||
|  | 	0x5f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, | ||||||
|  | 	0x12, 0x6e, 0x65, 0x67, 0x6f, 0x74, 0x69, 0x61, 0x74, 0x65, 0x64, 0x50, 0x72, 0x6f, 0x74, 0x6f, | ||||||
|  | 	0x63, 0x6f, 0x6c, 0x12, 0x41, 0x0a, 0x1d, 0x6e, 0x65, 0x67, 0x6f, 0x74, 0x69, 0x61, 0x74, 0x65, | ||||||
|  | 	0x64, 0x5f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x5f, 0x69, 0x73, 0x5f, 0x6d, 0x75, | ||||||
|  | 	0x74, 0x75, 0x61, 0x6c, 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x1a, 0x6e, 0x65, 0x67, 0x6f, | ||||||
|  | 	0x74, 0x69, 0x61, 0x74, 0x65, 0x64, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x49, 0x73, | ||||||
|  | 	0x4d, 0x75, 0x74, 0x75, 0x61, 0x6c, 0x12, 0x1f, 0x0a, 0x0b, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, | ||||||
|  | 	0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x73, 0x65, 0x72, | ||||||
|  | 	0x76, 0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x41, 0x0a, 0x11, 0x70, 0x65, 0x65, 0x72, 0x5f, | ||||||
|  | 	0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x73, 0x18, 0x08, 0x20, 0x01, | ||||||
|  | 	0x28, 0x0b, 0x32, 0x14, 0x2e, 0x70, 0x62, 0x2e, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, | ||||||
|  | 	0x61, 0x74, 0x65, 0x43, 0x68, 0x61, 0x69, 0x6e, 0x52, 0x10, 0x70, 0x65, 0x65, 0x72, 0x43, 0x65, | ||||||
|  | 	0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x73, 0x12, 0x3d, 0x0a, 0x0f, 0x76, 0x65, | ||||||
|  | 	0x72, 0x69, 0x66, 0x69, 0x65, 0x64, 0x5f, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x73, 0x18, 0x09, 0x20, | ||||||
|  | 	0x03, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x70, 0x62, 0x2e, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, | ||||||
|  | 	0x63, 0x61, 0x74, 0x65, 0x43, 0x68, 0x61, 0x69, 0x6e, 0x52, 0x0e, 0x76, 0x65, 0x72, 0x69, 0x66, | ||||||
|  | 	0x69, 0x65, 0x64, 0x43, 0x68, 0x61, 0x69, 0x6e, 0x73, 0x12, 0x42, 0x0a, 0x1d, 0x73, 0x69, 0x67, | ||||||
|  | 	0x6e, 0x65, 0x64, 0x5f, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x5f, | ||||||
|  | 	0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x73, 0x18, 0x0a, 0x20, 0x03, 0x28, 0x0c, | ||||||
|  | 	0x52, 0x1b, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, | ||||||
|  | 	0x61, 0x74, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x73, 0x12, 0x23, 0x0a, | ||||||
|  | 	0x0d, 0x6f, 0x63, 0x73, 0x70, 0x5f, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x18, 0x0b, | ||||||
|  | 	0x20, 0x01, 0x28, 0x0c, 0x52, 0x0c, 0x6f, 0x63, 0x73, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, | ||||||
|  | 	0x73, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x74, 0x6c, 0x73, 0x5f, 0x75, 0x6e, 0x69, 0x71, 0x75, 0x65, | ||||||
|  | 	0x18, 0x0c, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x74, 0x6c, 0x73, 0x55, 0x6e, 0x69, 0x71, 0x75, | ||||||
|  | 	0x65, 0x22, 0x2a, 0x0a, 0x0b, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, | ||||||
|  | 	0x12, 0x1b, 0x0a, 0x09, 0x61, 0x73, 0x6e, 0x31, 0x5f, 0x64, 0x61, 0x74, 0x61, 0x18, 0x01, 0x20, | ||||||
|  | 	0x01, 0x28, 0x0c, 0x52, 0x08, 0x61, 0x73, 0x6e, 0x31, 0x44, 0x61, 0x74, 0x61, 0x22, 0x47, 0x0a, | ||||||
|  | 	0x10, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x43, 0x68, 0x61, 0x69, | ||||||
|  | 	0x6e, 0x12, 0x33, 0x0a, 0x0c, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, | ||||||
|  | 	0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x70, 0x62, 0x2e, 0x43, 0x65, 0x72, | ||||||
|  | 	0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x52, 0x0c, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, | ||||||
|  | 	0x69, 0x63, 0x61, 0x74, 0x65, 0x73, 0x22, 0x5b, 0x0a, 0x10, 0x53, 0x65, 0x6e, 0x64, 0x45, 0x76, | ||||||
|  | 	0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x65, 0x76, | ||||||
|  | 	0x65, 0x6e, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, | ||||||
|  | 	0x65, 0x76, 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x28, 0x0a, 0x05, 0x65, 0x76, 0x65, | ||||||
|  | 	0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x6c, 0x6f, 0x67, 0x69, 0x63, | ||||||
|  | 	0x61, 0x6c, 0x2e, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x44, 0x61, 0x74, 0x61, 0x52, 0x05, 0x65, 0x76, | ||||||
|  | 	0x65, 0x6e, 0x74, 0x32, 0xa5, 0x03, 0x0a, 0x07, 0x42, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x12, | ||||||
|  | 	0x3e, 0x0a, 0x0d, 0x48, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, | ||||||
|  | 	0x12, 0x15, 0x2e, 0x70, 0x62, 0x2e, 0x48, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x52, 0x65, 0x71, 0x75, | ||||||
|  | 	0x65, 0x73, 0x74, 0x41, 0x72, 0x67, 0x73, 0x1a, 0x16, 0x2e, 0x70, 0x62, 0x2e, 0x48, 0x61, 0x6e, | ||||||
|  | 	0x64, 0x6c, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, | ||||||
|  | 	0x30, 0x0a, 0x0c, 0x53, 0x70, 0x65, 0x63, 0x69, 0x61, 0x6c, 0x50, 0x61, 0x74, 0x68, 0x73, 0x12, | ||||||
|  | 	0x09, 0x2e, 0x70, 0x62, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x15, 0x2e, 0x70, 0x62, 0x2e, | ||||||
|  | 	0x53, 0x70, 0x65, 0x63, 0x69, 0x61, 0x6c, 0x50, 0x61, 0x74, 0x68, 0x73, 0x52, 0x65, 0x70, 0x6c, | ||||||
|  | 	0x79, 0x12, 0x53, 0x0a, 0x14, 0x48, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x45, 0x78, 0x69, 0x73, 0x74, | ||||||
|  | 	0x65, 0x6e, 0x63, 0x65, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x12, 0x1c, 0x2e, 0x70, 0x62, 0x2e, 0x48, | ||||||
|  | 	0x61, 0x6e, 0x64, 0x6c, 0x65, 0x45, 0x78, 0x69, 0x73, 0x74, 0x65, 0x6e, 0x63, 0x65, 0x43, 0x68, | ||||||
|  | 	0x65, 0x63, 0x6b, 0x41, 0x72, 0x67, 0x73, 0x1a, 0x1d, 0x2e, 0x70, 0x62, 0x2e, 0x48, 0x61, 0x6e, | ||||||
|  | 	0x64, 0x6c, 0x65, 0x45, 0x78, 0x69, 0x73, 0x74, 0x65, 0x6e, 0x63, 0x65, 0x43, 0x68, 0x65, 0x63, | ||||||
|  | 	0x6b, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x1f, 0x0a, 0x07, 0x43, 0x6c, 0x65, 0x61, 0x6e, 0x75, | ||||||
|  | 	0x70, 0x12, 0x09, 0x2e, 0x70, 0x62, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x09, 0x2e, 0x70, | ||||||
|  | 	0x62, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x31, 0x0a, 0x0d, 0x49, 0x6e, 0x76, 0x61, 0x6c, | ||||||
|  | 	0x69, 0x64, 0x61, 0x74, 0x65, 0x4b, 0x65, 0x79, 0x12, 0x15, 0x2e, 0x70, 0x62, 0x2e, 0x49, 0x6e, | ||||||
|  | 	0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x65, 0x4b, 0x65, 0x79, 0x41, 0x72, 0x67, 0x73, 0x1a, | ||||||
|  | 	0x09, 0x2e, 0x70, 0x62, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x26, 0x0a, 0x05, 0x53, 0x65, | ||||||
|  | 	0x74, 0x75, 0x70, 0x12, 0x0d, 0x2e, 0x70, 0x62, 0x2e, 0x53, 0x65, 0x74, 0x75, 0x70, 0x41, 0x72, | ||||||
|  | 	0x67, 0x73, 0x1a, 0x0e, 0x2e, 0x70, 0x62, 0x2e, 0x53, 0x65, 0x74, 0x75, 0x70, 0x52, 0x65, 0x70, | ||||||
|  | 	0x6c, 0x79, 0x12, 0x35, 0x0a, 0x0a, 0x49, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x6c, 0x69, 0x7a, 0x65, | ||||||
|  | 	0x12, 0x12, 0x2e, 0x70, 0x62, 0x2e, 0x49, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x6c, 0x69, 0x7a, 0x65, | ||||||
|  | 	0x41, 0x72, 0x67, 0x73, 0x1a, 0x13, 0x2e, 0x70, 0x62, 0x2e, 0x49, 0x6e, 0x69, 0x74, 0x69, 0x61, | ||||||
|  | 	0x6c, 0x69, 0x7a, 0x65, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x20, 0x0a, 0x04, 0x54, 0x79, 0x70, | ||||||
|  | 	0x65, 0x12, 0x09, 0x2e, 0x70, 0x62, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x0d, 0x2e, 0x70, | ||||||
|  | 	0x62, 0x2e, 0x54, 0x79, 0x70, 0x65, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x32, 0xd5, 0x01, 0x0a, 0x07, | ||||||
|  | 	0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x12, 0x31, 0x0a, 0x04, 0x4c, 0x69, 0x73, 0x74, 0x12, | ||||||
|  | 	0x13, 0x2e, 0x70, 0x62, 0x2e, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x4c, 0x69, 0x73, 0x74, | ||||||
|  | 	0x41, 0x72, 0x67, 0x73, 0x1a, 0x14, 0x2e, 0x70, 0x62, 0x2e, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, | ||||||
|  | 	0x65, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x2e, 0x0a, 0x03, 0x47, 0x65, | ||||||
|  | 	0x74, 0x12, 0x12, 0x2e, 0x70, 0x62, 0x2e, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x47, 0x65, | ||||||
|  | 	0x74, 0x41, 0x72, 0x67, 0x73, 0x1a, 0x13, 0x2e, 0x70, 0x62, 0x2e, 0x53, 0x74, 0x6f, 0x72, 0x61, | ||||||
|  | 	0x67, 0x65, 0x47, 0x65, 0x74, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x2e, 0x0a, 0x03, 0x50, 0x75, | ||||||
|  | 	0x74, 0x12, 0x12, 0x2e, 0x70, 0x62, 0x2e, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x50, 0x75, | ||||||
|  | 	0x74, 0x41, 0x72, 0x67, 0x73, 0x1a, 0x13, 0x2e, 0x70, 0x62, 0x2e, 0x53, 0x74, 0x6f, 0x72, 0x61, | ||||||
|  | 	0x67, 0x65, 0x50, 0x75, 0x74, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x37, 0x0a, 0x06, 0x44, 0x65, | ||||||
|  | 	0x6c, 0x65, 0x74, 0x65, 0x12, 0x15, 0x2e, 0x70, 0x62, 0x2e, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, | ||||||
|  | 	0x65, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x41, 0x72, 0x67, 0x73, 0x1a, 0x16, 0x2e, 0x70, 0x62, | ||||||
|  | 	0x2e, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, | ||||||
|  | 	0x70, 0x6c, 0x79, 0x32, 0xdd, 0x07, 0x0a, 0x0a, 0x53, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x56, 0x69, | ||||||
|  | 	0x65, 0x77, 0x12, 0x2a, 0x0a, 0x0f, 0x44, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x4c, 0x65, 0x61, | ||||||
|  | 	0x73, 0x65, 0x54, 0x54, 0x4c, 0x12, 0x09, 0x2e, 0x70, 0x62, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, | ||||||
|  | 	0x1a, 0x0c, 0x2e, 0x70, 0x62, 0x2e, 0x54, 0x54, 0x4c, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x26, | ||||||
|  | 	0x0a, 0x0b, 0x4d, 0x61, 0x78, 0x4c, 0x65, 0x61, 0x73, 0x65, 0x54, 0x54, 0x4c, 0x12, 0x09, 0x2e, | ||||||
|  | 	0x70, 0x62, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x0c, 0x2e, 0x70, 0x62, 0x2e, 0x54, 0x54, | ||||||
|  | 	0x4c, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x26, 0x0a, 0x07, 0x54, 0x61, 0x69, 0x6e, 0x74, 0x65, | ||||||
|  | 	0x64, 0x12, 0x09, 0x2e, 0x70, 0x62, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x10, 0x2e, 0x70, | ||||||
|  | 	0x62, 0x2e, 0x54, 0x61, 0x69, 0x6e, 0x74, 0x65, 0x64, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x36, | ||||||
|  | 	0x0a, 0x0f, 0x43, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x67, 0x44, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, | ||||||
|  | 	0x64, 0x12, 0x09, 0x2e, 0x70, 0x62, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x18, 0x2e, 0x70, | ||||||
|  | 	0x62, 0x2e, 0x43, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x67, 0x44, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, | ||||||
|  | 	0x64, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x38, 0x0a, 0x10, 0x52, 0x65, 0x70, 0x6c, 0x69, 0x63, | ||||||
|  | 	0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x09, 0x2e, 0x70, 0x62, 0x2e, | ||||||
|  | 	0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x19, 0x2e, 0x70, 0x62, 0x2e, 0x52, 0x65, 0x70, 0x6c, 0x69, | ||||||
|  | 	0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x65, 0x70, 0x6c, 0x79, | ||||||
|  | 	0x12, 0x47, 0x0a, 0x10, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x57, 0x72, 0x61, 0x70, | ||||||
|  | 	0x44, 0x61, 0x74, 0x61, 0x12, 0x18, 0x2e, 0x70, 0x62, 0x2e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, | ||||||
|  | 	0x73, 0x65, 0x57, 0x72, 0x61, 0x70, 0x44, 0x61, 0x74, 0x61, 0x41, 0x72, 0x67, 0x73, 0x1a, 0x19, | ||||||
|  | 	0x2e, 0x70, 0x62, 0x2e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x57, 0x72, 0x61, 0x70, | ||||||
|  | 	0x44, 0x61, 0x74, 0x61, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x30, 0x0a, 0x0c, 0x4d, 0x6c, 0x6f, | ||||||
|  | 	0x63, 0x6b, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x09, 0x2e, 0x70, 0x62, 0x2e, 0x45, | ||||||
|  | 	0x6d, 0x70, 0x74, 0x79, 0x1a, 0x15, 0x2e, 0x70, 0x62, 0x2e, 0x4d, 0x6c, 0x6f, 0x63, 0x6b, 0x45, | ||||||
|  | 	0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x2c, 0x0a, 0x0a, 0x4c, | ||||||
|  | 	0x6f, 0x63, 0x61, 0x6c, 0x4d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x09, 0x2e, 0x70, 0x62, 0x2e, 0x45, | ||||||
|  | 	0x6d, 0x70, 0x74, 0x79, 0x1a, 0x13, 0x2e, 0x70, 0x62, 0x2e, 0x4c, 0x6f, 0x63, 0x61, 0x6c, 0x4d, | ||||||
|  | 	0x6f, 0x75, 0x6e, 0x74, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x35, 0x0a, 0x0a, 0x45, 0x6e, 0x74, | ||||||
|  | 	0x69, 0x74, 0x79, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x12, 0x2e, 0x70, 0x62, 0x2e, 0x45, 0x6e, 0x74, | ||||||
|  | 	0x69, 0x74, 0x79, 0x49, 0x6e, 0x66, 0x6f, 0x41, 0x72, 0x67, 0x73, 0x1a, 0x13, 0x2e, 0x70, 0x62, | ||||||
|  | 	0x2e, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x70, 0x6c, 0x79, | ||||||
|  | 	0x12, 0x2a, 0x0a, 0x09, 0x50, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x45, 0x6e, 0x76, 0x12, 0x09, 0x2e, | ||||||
|  | 	0x70, 0x62, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x12, 0x2e, 0x70, 0x62, 0x2e, 0x50, 0x6c, | ||||||
|  | 	0x75, 0x67, 0x69, 0x6e, 0x45, 0x6e, 0x76, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x3f, 0x0a, 0x0f, | ||||||
|  | 	0x47, 0x72, 0x6f, 0x75, 0x70, 0x73, 0x46, 0x6f, 0x72, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x12, | ||||||
|  | 	0x12, 0x2e, 0x70, 0x62, 0x2e, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x49, 0x6e, 0x66, 0x6f, 0x41, | ||||||
|  | 	0x72, 0x67, 0x73, 0x1a, 0x18, 0x2e, 0x70, 0x62, 0x2e, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x73, 0x46, | ||||||
|  | 	0x6f, 0x72, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x68, 0x0a, | ||||||
|  | 	0x1a, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, | ||||||
|  | 	0x64, 0x46, 0x72, 0x6f, 0x6d, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x12, 0x25, 0x2e, 0x70, 0x62, | ||||||
|  | 	0x2e, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, | ||||||
|  | 	0x64, 0x46, 0x72, 0x6f, 0x6d, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, | ||||||
|  | 	0x73, 0x74, 0x1a, 0x23, 0x2e, 0x70, 0x62, 0x2e, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, | ||||||
|  | 	0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x46, 0x72, 0x6f, 0x6d, 0x50, 0x6f, 0x6c, 0x69, | ||||||
|  | 	0x63, 0x79, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x2e, 0x0a, 0x0b, 0x43, 0x6c, 0x75, 0x73, 0x74, | ||||||
|  | 	0x65, 0x72, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x09, 0x2e, 0x70, 0x62, 0x2e, 0x45, 0x6d, 0x70, 0x74, | ||||||
|  | 	0x79, 0x1a, 0x14, 0x2e, 0x70, 0x62, 0x2e, 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x49, 0x6e, | ||||||
|  | 	0x66, 0x6f, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x5c, 0x0a, 0x15, 0x47, 0x65, 0x6e, 0x65, 0x72, | ||||||
|  | 	0x61, 0x74, 0x65, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x54, 0x6f, 0x6b, 0x65, 0x6e, | ||||||
|  | 	0x12, 0x20, 0x2e, 0x70, 0x62, 0x2e, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x49, 0x64, | ||||||
|  | 	0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, | ||||||
|  | 	0x73, 0x74, 0x1a, 0x21, 0x2e, 0x70, 0x62, 0x2e, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, | ||||||
|  | 	0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x52, 0x65, 0x73, | ||||||
|  | 	0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x56, 0x0a, 0x13, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, | ||||||
|  | 	0x72, 0x52, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4a, 0x6f, 0x62, 0x12, 0x1e, 0x2e, 0x70, | ||||||
|  | 	0x62, 0x2e, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x52, 0x6f, 0x74, 0x61, 0x74, 0x69, | ||||||
|  | 	0x6f, 0x6e, 0x4a, 0x6f, 0x62, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x70, | ||||||
|  | 	0x62, 0x2e, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x52, 0x6f, 0x74, 0x61, 0x74, 0x69, | ||||||
|  | 	0x6f, 0x6e, 0x4a, 0x6f, 0x62, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x44, 0x0a, | ||||||
|  | 	0x15, 0x44, 0x65, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x52, 0x6f, 0x74, 0x61, 0x74, | ||||||
|  | 	0x69, 0x6f, 0x6e, 0x4a, 0x6f, 0x62, 0x12, 0x20, 0x2e, 0x70, 0x62, 0x2e, 0x44, 0x65, 0x72, 0x65, | ||||||
| 	0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x52, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4a, 0x6f, | 	0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x52, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4a, 0x6f, | ||||||
| 	0x62, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x34, 0x0a, 0x03, 0x72, 0x65, 0x71, 0x18, | 	0x62, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x09, 0x2e, 0x70, 0x62, 0x2e, 0x45, 0x6d, | ||||||
| 	0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x70, 0x62, 0x2e, 0x44, 0x65, 0x72, 0x65, 0x67, | 	0x70, 0x74, 0x79, 0x32, 0x36, 0x0a, 0x06, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x2c, 0x0a, | ||||||
| 	0x69, 0x73, 0x74, 0x65, 0x72, 0x52, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, | 	0x09, 0x53, 0x65, 0x6e, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x14, 0x2e, 0x70, 0x62, 0x2e, | ||||||
| 	0x75, 0x65, 0x73, 0x74, 0x49, 0x6e, 0x70, 0x75, 0x74, 0x52, 0x03, 0x72, 0x65, 0x71, 0x22, 0x8e, | 	0x53, 0x65, 0x6e, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, | ||||||
| 	0x01, 0x0a, 0x0a, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1f, 0x0a, | 	0x1a, 0x09, 0x2e, 0x70, 0x62, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x42, 0x2a, 0x5a, 0x28, 0x67, | ||||||
| 	0x0b, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x18, 0x01, 0x20, 0x01, | 	0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, | ||||||
| 	0x28, 0x09, 0x52, 0x0a, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x41, 0x64, 0x64, 0x72, 0x12, 0x1f, | 	0x6f, 0x72, 0x70, 0x2f, 0x76, 0x61, 0x75, 0x6c, 0x74, 0x2f, 0x73, 0x64, 0x6b, 0x2f, 0x70, 0x6c, | ||||||
| 	0x0a, 0x0b, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x5f, 0x70, 0x6f, 0x72, 0x74, 0x18, 0x03, 0x20, | 	0x75, 0x67, 0x69, 0x6e, 0x2f, 0x70, 0x62, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, | ||||||
| 	0x01, 0x28, 0x05, 0x52, 0x0a, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x50, 0x6f, 0x72, 0x74, 0x12, |  | ||||||
| 	0x3e, 0x0a, 0x10, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x73, 0x74, |  | ||||||
| 	0x61, 0x74, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x70, 0x62, 0x2e, 0x43, |  | ||||||
| 	0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x0f, |  | ||||||
| 	0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x65, 0x22, |  | ||||||
| 	0xbb, 0x04, 0x0a, 0x0f, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, |  | ||||||
| 	0x61, 0x74, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x01, |  | ||||||
| 	0x20, 0x01, 0x28, 0x0d, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x2d, 0x0a, |  | ||||||
| 	0x12, 0x68, 0x61, 0x6e, 0x64, 0x73, 0x68, 0x61, 0x6b, 0x65, 0x5f, 0x63, 0x6f, 0x6d, 0x70, 0x6c, |  | ||||||
| 	0x65, 0x74, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x11, 0x68, 0x61, 0x6e, 0x64, 0x73, |  | ||||||
| 	0x68, 0x61, 0x6b, 0x65, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x12, 0x1d, 0x0a, 0x0a, |  | ||||||
| 	0x64, 0x69, 0x64, 0x5f, 0x72, 0x65, 0x73, 0x75, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, |  | ||||||
| 	0x52, 0x09, 0x64, 0x69, 0x64, 0x52, 0x65, 0x73, 0x75, 0x6d, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x63, |  | ||||||
| 	0x69, 0x70, 0x68, 0x65, 0x72, 0x5f, 0x73, 0x75, 0x69, 0x74, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, |  | ||||||
| 	0x0d, 0x52, 0x0b, 0x63, 0x69, 0x70, 0x68, 0x65, 0x72, 0x53, 0x75, 0x69, 0x74, 0x65, 0x12, 0x2f, |  | ||||||
| 	0x0a, 0x13, 0x6e, 0x65, 0x67, 0x6f, 0x74, 0x69, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x70, 0x72, 0x6f, |  | ||||||
| 	0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x12, 0x6e, 0x65, 0x67, |  | ||||||
| 	0x6f, 0x74, 0x69, 0x61, 0x74, 0x65, 0x64, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x12, |  | ||||||
| 	0x41, 0x0a, 0x1d, 0x6e, 0x65, 0x67, 0x6f, 0x74, 0x69, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x70, 0x72, |  | ||||||
| 	0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x5f, 0x69, 0x73, 0x5f, 0x6d, 0x75, 0x74, 0x75, 0x61, 0x6c, |  | ||||||
| 	0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x1a, 0x6e, 0x65, 0x67, 0x6f, 0x74, 0x69, 0x61, 0x74, |  | ||||||
| 	0x65, 0x64, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x49, 0x73, 0x4d, 0x75, 0x74, 0x75, |  | ||||||
| 	0x61, 0x6c, 0x12, 0x1f, 0x0a, 0x0b, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x5f, 0x6e, 0x61, 0x6d, |  | ||||||
| 	0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x4e, |  | ||||||
| 	0x61, 0x6d, 0x65, 0x12, 0x41, 0x0a, 0x11, 0x70, 0x65, 0x65, 0x72, 0x5f, 0x63, 0x65, 0x72, 0x74, |  | ||||||
| 	0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x73, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, |  | ||||||
| 	0x2e, 0x70, 0x62, 0x2e, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x43, |  | ||||||
| 	0x68, 0x61, 0x69, 0x6e, 0x52, 0x10, 0x70, 0x65, 0x65, 0x72, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, |  | ||||||
| 	0x69, 0x63, 0x61, 0x74, 0x65, 0x73, 0x12, 0x3d, 0x0a, 0x0f, 0x76, 0x65, 0x72, 0x69, 0x66, 0x69, |  | ||||||
| 	0x65, 0x64, 0x5f, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x73, 0x18, 0x09, 0x20, 0x03, 0x28, 0x0b, 0x32, |  | ||||||
| 	0x14, 0x2e, 0x70, 0x62, 0x2e, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, |  | ||||||
| 	0x43, 0x68, 0x61, 0x69, 0x6e, 0x52, 0x0e, 0x76, 0x65, 0x72, 0x69, 0x66, 0x69, 0x65, 0x64, 0x43, |  | ||||||
| 	0x68, 0x61, 0x69, 0x6e, 0x73, 0x12, 0x42, 0x0a, 0x1d, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x5f, |  | ||||||
| 	0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x5f, 0x74, 0x69, 0x6d, 0x65, |  | ||||||
| 	0x73, 0x74, 0x61, 0x6d, 0x70, 0x73, 0x18, 0x0a, 0x20, 0x03, 0x28, 0x0c, 0x52, 0x1b, 0x73, 0x69, |  | ||||||
| 	0x67, 0x6e, 0x65, 0x64, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x54, |  | ||||||
| 	0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x73, 0x12, 0x23, 0x0a, 0x0d, 0x6f, 0x63, 0x73, |  | ||||||
| 	0x70, 0x5f, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x0c, |  | ||||||
| 	0x52, 0x0c, 0x6f, 0x63, 0x73, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1d, |  | ||||||
| 	0x0a, 0x0a, 0x74, 0x6c, 0x73, 0x5f, 0x75, 0x6e, 0x69, 0x71, 0x75, 0x65, 0x18, 0x0c, 0x20, 0x01, |  | ||||||
| 	0x28, 0x0c, 0x52, 0x09, 0x74, 0x6c, 0x73, 0x55, 0x6e, 0x69, 0x71, 0x75, 0x65, 0x22, 0x2a, 0x0a, |  | ||||||
| 	0x0b, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x12, 0x1b, 0x0a, 0x09, |  | ||||||
| 	0x61, 0x73, 0x6e, 0x31, 0x5f, 0x64, 0x61, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, |  | ||||||
| 	0x08, 0x61, 0x73, 0x6e, 0x31, 0x44, 0x61, 0x74, 0x61, 0x22, 0x47, 0x0a, 0x10, 0x43, 0x65, 0x72, |  | ||||||
| 	0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x43, 0x68, 0x61, 0x69, 0x6e, 0x12, 0x33, 0x0a, |  | ||||||
| 	0x0c, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x73, 0x18, 0x01, 0x20, |  | ||||||
| 	0x03, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x70, 0x62, 0x2e, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, |  | ||||||
| 	0x63, 0x61, 0x74, 0x65, 0x52, 0x0c, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, |  | ||||||
| 	0x65, 0x73, 0x22, 0x5b, 0x0a, 0x10, 0x53, 0x65, 0x6e, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x52, |  | ||||||
| 	0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x5f, |  | ||||||
| 	0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x65, 0x76, 0x65, 0x6e, |  | ||||||
| 	0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x28, 0x0a, 0x05, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x18, 0x02, |  | ||||||
| 	0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x6c, 0x6f, 0x67, 0x69, 0x63, 0x61, 0x6c, 0x2e, 0x45, |  | ||||||
| 	0x76, 0x65, 0x6e, 0x74, 0x44, 0x61, 0x74, 0x61, 0x52, 0x05, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x32, |  | ||||||
| 	0xa5, 0x03, 0x0a, 0x07, 0x42, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x12, 0x3e, 0x0a, 0x0d, 0x48, |  | ||||||
| 	0x61, 0x6e, 0x64, 0x6c, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x15, 0x2e, 0x70, |  | ||||||
| 	0x62, 0x2e, 0x48, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x41, |  | ||||||
| 	0x72, 0x67, 0x73, 0x1a, 0x16, 0x2e, 0x70, 0x62, 0x2e, 0x48, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x52, |  | ||||||
| 	0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x30, 0x0a, 0x0c, 0x53, |  | ||||||
| 	0x70, 0x65, 0x63, 0x69, 0x61, 0x6c, 0x50, 0x61, 0x74, 0x68, 0x73, 0x12, 0x09, 0x2e, 0x70, 0x62, |  | ||||||
| 	0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x15, 0x2e, 0x70, 0x62, 0x2e, 0x53, 0x70, 0x65, 0x63, |  | ||||||
| 	0x69, 0x61, 0x6c, 0x50, 0x61, 0x74, 0x68, 0x73, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x53, 0x0a, |  | ||||||
| 	0x14, 0x48, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x45, 0x78, 0x69, 0x73, 0x74, 0x65, 0x6e, 0x63, 0x65, |  | ||||||
| 	0x43, 0x68, 0x65, 0x63, 0x6b, 0x12, 0x1c, 0x2e, 0x70, 0x62, 0x2e, 0x48, 0x61, 0x6e, 0x64, 0x6c, |  | ||||||
| 	0x65, 0x45, 0x78, 0x69, 0x73, 0x74, 0x65, 0x6e, 0x63, 0x65, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x41, |  | ||||||
| 	0x72, 0x67, 0x73, 0x1a, 0x1d, 0x2e, 0x70, 0x62, 0x2e, 0x48, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x45, |  | ||||||
| 	0x78, 0x69, 0x73, 0x74, 0x65, 0x6e, 0x63, 0x65, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x52, 0x65, 0x70, |  | ||||||
| 	0x6c, 0x79, 0x12, 0x1f, 0x0a, 0x07, 0x43, 0x6c, 0x65, 0x61, 0x6e, 0x75, 0x70, 0x12, 0x09, 0x2e, |  | ||||||
| 	0x70, 0x62, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x09, 0x2e, 0x70, 0x62, 0x2e, 0x45, 0x6d, |  | ||||||
| 	0x70, 0x74, 0x79, 0x12, 0x31, 0x0a, 0x0d, 0x49, 0x6e, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, |  | ||||||
| 	0x65, 0x4b, 0x65, 0x79, 0x12, 0x15, 0x2e, 0x70, 0x62, 0x2e, 0x49, 0x6e, 0x76, 0x61, 0x6c, 0x69, |  | ||||||
| 	0x64, 0x61, 0x74, 0x65, 0x4b, 0x65, 0x79, 0x41, 0x72, 0x67, 0x73, 0x1a, 0x09, 0x2e, 0x70, 0x62, |  | ||||||
| 	0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x26, 0x0a, 0x05, 0x53, 0x65, 0x74, 0x75, 0x70, 0x12, |  | ||||||
| 	0x0d, 0x2e, 0x70, 0x62, 0x2e, 0x53, 0x65, 0x74, 0x75, 0x70, 0x41, 0x72, 0x67, 0x73, 0x1a, 0x0e, |  | ||||||
| 	0x2e, 0x70, 0x62, 0x2e, 0x53, 0x65, 0x74, 0x75, 0x70, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x35, |  | ||||||
| 	0x0a, 0x0a, 0x49, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x12, 0x12, 0x2e, 0x70, |  | ||||||
| 	0x62, 0x2e, 0x49, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x41, 0x72, 0x67, 0x73, |  | ||||||
| 	0x1a, 0x13, 0x2e, 0x70, 0x62, 0x2e, 0x49, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x6c, 0x69, 0x7a, 0x65, |  | ||||||
| 	0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x20, 0x0a, 0x04, 0x54, 0x79, 0x70, 0x65, 0x12, 0x09, 0x2e, |  | ||||||
| 	0x70, 0x62, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x0d, 0x2e, 0x70, 0x62, 0x2e, 0x54, 0x79, |  | ||||||
| 	0x70, 0x65, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x32, 0xd5, 0x01, 0x0a, 0x07, 0x53, 0x74, 0x6f, 0x72, |  | ||||||
| 	0x61, 0x67, 0x65, 0x12, 0x31, 0x0a, 0x04, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x13, 0x2e, 0x70, 0x62, |  | ||||||
| 	0x2e, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x72, 0x67, 0x73, |  | ||||||
| 	0x1a, 0x14, 0x2e, 0x70, 0x62, 0x2e, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x4c, 0x69, 0x73, |  | ||||||
| 	0x74, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x2e, 0x0a, 0x03, 0x47, 0x65, 0x74, 0x12, 0x12, 0x2e, |  | ||||||
| 	0x70, 0x62, 0x2e, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x47, 0x65, 0x74, 0x41, 0x72, 0x67, |  | ||||||
| 	0x73, 0x1a, 0x13, 0x2e, 0x70, 0x62, 0x2e, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x47, 0x65, |  | ||||||
| 	0x74, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x2e, 0x0a, 0x03, 0x50, 0x75, 0x74, 0x12, 0x12, 0x2e, |  | ||||||
| 	0x70, 0x62, 0x2e, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x50, 0x75, 0x74, 0x41, 0x72, 0x67, |  | ||||||
| 	0x73, 0x1a, 0x13, 0x2e, 0x70, 0x62, 0x2e, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x50, 0x75, |  | ||||||
| 	0x74, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x37, 0x0a, 0x06, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, |  | ||||||
| 	0x12, 0x15, 0x2e, 0x70, 0x62, 0x2e, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x44, 0x65, 0x6c, |  | ||||||
| 	0x65, 0x74, 0x65, 0x41, 0x72, 0x67, 0x73, 0x1a, 0x16, 0x2e, 0x70, 0x62, 0x2e, 0x53, 0x74, 0x6f, |  | ||||||
| 	0x72, 0x61, 0x67, 0x65, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x32, |  | ||||||
| 	0xdd, 0x07, 0x0a, 0x0a, 0x53, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x56, 0x69, 0x65, 0x77, 0x12, 0x2a, |  | ||||||
| 	0x0a, 0x0f, 0x44, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x4c, 0x65, 0x61, 0x73, 0x65, 0x54, 0x54, |  | ||||||
| 	0x4c, 0x12, 0x09, 0x2e, 0x70, 0x62, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x0c, 0x2e, 0x70, |  | ||||||
| 	0x62, 0x2e, 0x54, 0x54, 0x4c, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x26, 0x0a, 0x0b, 0x4d, 0x61, |  | ||||||
| 	0x78, 0x4c, 0x65, 0x61, 0x73, 0x65, 0x54, 0x54, 0x4c, 0x12, 0x09, 0x2e, 0x70, 0x62, 0x2e, 0x45, |  | ||||||
| 	0x6d, 0x70, 0x74, 0x79, 0x1a, 0x0c, 0x2e, 0x70, 0x62, 0x2e, 0x54, 0x54, 0x4c, 0x52, 0x65, 0x70, |  | ||||||
| 	0x6c, 0x79, 0x12, 0x26, 0x0a, 0x07, 0x54, 0x61, 0x69, 0x6e, 0x74, 0x65, 0x64, 0x12, 0x09, 0x2e, |  | ||||||
| 	0x70, 0x62, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x10, 0x2e, 0x70, 0x62, 0x2e, 0x54, 0x61, |  | ||||||
| 	0x69, 0x6e, 0x74, 0x65, 0x64, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x36, 0x0a, 0x0f, 0x43, 0x61, |  | ||||||
| 	0x63, 0x68, 0x69, 0x6e, 0x67, 0x44, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x09, 0x2e, |  | ||||||
| 	0x70, 0x62, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x18, 0x2e, 0x70, 0x62, 0x2e, 0x43, 0x61, |  | ||||||
| 	0x63, 0x68, 0x69, 0x6e, 0x67, 0x44, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x52, 0x65, 0x70, |  | ||||||
| 	0x6c, 0x79, 0x12, 0x38, 0x0a, 0x10, 0x52, 0x65, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, |  | ||||||
| 	0x6e, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x09, 0x2e, 0x70, 0x62, 0x2e, 0x45, 0x6d, 0x70, 0x74, |  | ||||||
| 	0x79, 0x1a, 0x19, 0x2e, 0x70, 0x62, 0x2e, 0x52, 0x65, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, |  | ||||||
| 	0x6f, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x47, 0x0a, 0x10, |  | ||||||
| 	0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x57, 0x72, 0x61, 0x70, 0x44, 0x61, 0x74, 0x61, |  | ||||||
| 	0x12, 0x18, 0x2e, 0x70, 0x62, 0x2e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x57, 0x72, |  | ||||||
| 	0x61, 0x70, 0x44, 0x61, 0x74, 0x61, 0x41, 0x72, 0x67, 0x73, 0x1a, 0x19, 0x2e, 0x70, 0x62, 0x2e, |  | ||||||
| 	0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x57, 0x72, 0x61, 0x70, 0x44, 0x61, 0x74, 0x61, |  | ||||||
| 	0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x30, 0x0a, 0x0c, 0x4d, 0x6c, 0x6f, 0x63, 0x6b, 0x45, 0x6e, |  | ||||||
| 	0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x09, 0x2e, 0x70, 0x62, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, |  | ||||||
| 	0x1a, 0x15, 0x2e, 0x70, 0x62, 0x2e, 0x4d, 0x6c, 0x6f, 0x63, 0x6b, 0x45, 0x6e, 0x61, 0x62, 0x6c, |  | ||||||
| 	0x65, 0x64, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x2c, 0x0a, 0x0a, 0x4c, 0x6f, 0x63, 0x61, 0x6c, |  | ||||||
| 	0x4d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x09, 0x2e, 0x70, 0x62, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, |  | ||||||
| 	0x1a, 0x13, 0x2e, 0x70, 0x62, 0x2e, 0x4c, 0x6f, 0x63, 0x61, 0x6c, 0x4d, 0x6f, 0x75, 0x6e, 0x74, |  | ||||||
| 	0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x35, 0x0a, 0x0a, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x49, |  | ||||||
| 	0x6e, 0x66, 0x6f, 0x12, 0x12, 0x2e, 0x70, 0x62, 0x2e, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x49, |  | ||||||
| 	0x6e, 0x66, 0x6f, 0x41, 0x72, 0x67, 0x73, 0x1a, 0x13, 0x2e, 0x70, 0x62, 0x2e, 0x45, 0x6e, 0x74, |  | ||||||
| 	0x69, 0x74, 0x79, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x2a, 0x0a, 0x09, |  | ||||||
| 	0x50, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x45, 0x6e, 0x76, 0x12, 0x09, 0x2e, 0x70, 0x62, 0x2e, 0x45, |  | ||||||
| 	0x6d, 0x70, 0x74, 0x79, 0x1a, 0x12, 0x2e, 0x70, 0x62, 0x2e, 0x50, 0x6c, 0x75, 0x67, 0x69, 0x6e, |  | ||||||
| 	0x45, 0x6e, 0x76, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x3f, 0x0a, 0x0f, 0x47, 0x72, 0x6f, 0x75, |  | ||||||
| 	0x70, 0x73, 0x46, 0x6f, 0x72, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x12, 0x12, 0x2e, 0x70, 0x62, |  | ||||||
| 	0x2e, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x49, 0x6e, 0x66, 0x6f, 0x41, 0x72, 0x67, 0x73, 0x1a, |  | ||||||
| 	0x18, 0x2e, 0x70, 0x62, 0x2e, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x73, 0x46, 0x6f, 0x72, 0x45, 0x6e, |  | ||||||
| 	0x74, 0x69, 0x74, 0x79, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x68, 0x0a, 0x1a, 0x47, 0x65, 0x6e, |  | ||||||
| 	0x65, 0x72, 0x61, 0x74, 0x65, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x46, 0x72, 0x6f, |  | ||||||
| 	0x6d, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x12, 0x25, 0x2e, 0x70, 0x62, 0x2e, 0x47, 0x65, 0x6e, |  | ||||||
| 	0x65, 0x72, 0x61, 0x74, 0x65, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x46, 0x72, 0x6f, |  | ||||||
| 	0x6d, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x23, |  | ||||||
| 	0x2e, 0x70, 0x62, 0x2e, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x50, 0x61, 0x73, 0x73, |  | ||||||
| 	0x77, 0x6f, 0x72, 0x64, 0x46, 0x72, 0x6f, 0x6d, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x52, 0x65, |  | ||||||
| 	0x70, 0x6c, 0x79, 0x12, 0x2e, 0x0a, 0x0b, 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x49, 0x6e, |  | ||||||
| 	0x66, 0x6f, 0x12, 0x09, 0x2e, 0x70, 0x62, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x14, 0x2e, |  | ||||||
| 	0x70, 0x62, 0x2e, 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, |  | ||||||
| 	0x70, 0x6c, 0x79, 0x12, 0x5c, 0x0a, 0x15, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x49, |  | ||||||
| 	0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x20, 0x2e, 0x70, |  | ||||||
| 	0x62, 0x2e, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, |  | ||||||
| 	0x74, 0x79, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x21, |  | ||||||
| 	0x2e, 0x70, 0x62, 0x2e, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x49, 0x64, 0x65, 0x6e, |  | ||||||
| 	0x74, 0x69, 0x74, 0x79, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, |  | ||||||
| 	0x65, 0x12, 0x56, 0x0a, 0x13, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x52, 0x6f, 0x74, |  | ||||||
| 	0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4a, 0x6f, 0x62, 0x12, 0x1e, 0x2e, 0x70, 0x62, 0x2e, 0x52, 0x65, |  | ||||||
| 	0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x52, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4a, 0x6f, |  | ||||||
| 	0x62, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x70, 0x62, 0x2e, 0x52, 0x65, |  | ||||||
| 	0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x52, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4a, 0x6f, |  | ||||||
| 	0x62, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x44, 0x0a, 0x15, 0x44, 0x65, 0x72, |  | ||||||
| 	0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x52, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4a, |  | ||||||
| 	0x6f, 0x62, 0x12, 0x20, 0x2e, 0x70, 0x62, 0x2e, 0x44, 0x65, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, |  | ||||||
| 	0x65, 0x72, 0x52, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4a, 0x6f, 0x62, 0x52, 0x65, 0x71, |  | ||||||
| 	0x75, 0x65, 0x73, 0x74, 0x1a, 0x09, 0x2e, 0x70, 0x62, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x32, |  | ||||||
| 	0x36, 0x0a, 0x06, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x2c, 0x0a, 0x09, 0x53, 0x65, 0x6e, |  | ||||||
| 	0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x14, 0x2e, 0x70, 0x62, 0x2e, 0x53, 0x65, 0x6e, 0x64, |  | ||||||
| 	0x45, 0x76, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x09, 0x2e, 0x70, |  | ||||||
| 	0x62, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x42, 0x2a, 0x5a, 0x28, 0x67, 0x69, 0x74, 0x68, 0x75, |  | ||||||
| 	0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2f, |  | ||||||
| 	0x76, 0x61, 0x75, 0x6c, 0x74, 0x2f, 0x73, 0x64, 0x6b, 0x2f, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, |  | ||||||
| 	0x2f, 0x70, 0x62, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, |  | ||||||
| } | } | ||||||
|  |  | ||||||
| var ( | var ( | ||||||
|   | |||||||
| @@ -618,7 +618,7 @@ message RegisterRotationJobResponse { | |||||||
|  |  | ||||||
| message RotationJobInput { | message RotationJobInput { | ||||||
|   string name = 1; |   string name = 1; | ||||||
|   string mount_type = 2; |   string mount_point = 2; | ||||||
|   string path = 3; |   string path = 3; | ||||||
|   string rotation_schedule = 4; |   string rotation_schedule = 4; | ||||||
|   int64 rotation_window = 5; |   int64 rotation_window = 5; | ||||||
| @@ -626,7 +626,7 @@ message RotationJobInput { | |||||||
| } | } | ||||||
|  |  | ||||||
| message DeregisterRotationRequestInput { | message DeregisterRotationRequestInput { | ||||||
|   string mount_type = 1; |   string mount_point = 1; | ||||||
|   string req_path = 2; |   string req_path = 2; | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -26,13 +26,13 @@ type RotationJob struct { | |||||||
| 	// For requests, this will always be blank. | 	// For requests, this will always be blank. | ||||||
| 	RotationID string `sentinel:""` | 	RotationID string `sentinel:""` | ||||||
| 	Path       string | 	Path       string | ||||||
| 	MountType  string | 	MountPoint string | ||||||
| 	Name       string | 	Name       string | ||||||
| } | } | ||||||
|  |  | ||||||
| type RotationJobConfigureRequest struct { | type RotationJobConfigureRequest struct { | ||||||
| 	Name             string | 	Name             string | ||||||
| 	MountType        string | 	MountPoint       string | ||||||
| 	ReqPath          string | 	ReqPath          string | ||||||
| 	RotationSchedule string | 	RotationSchedule string | ||||||
| 	RotationWindow   int | 	RotationWindow   int | ||||||
| @@ -40,12 +40,23 @@ type RotationJobConfigureRequest struct { | |||||||
| } | } | ||||||
|  |  | ||||||
| type RotationJobDeregisterRequest struct { | type RotationJobDeregisterRequest struct { | ||||||
| 	MountType string | 	MountPoint string | ||||||
| 	ReqPath    string | 	ReqPath    string | ||||||
| } | } | ||||||
|  |  | ||||||
| func (s *RotationJob) Validate() error { | func (s *RotationJob) Validate() error { | ||||||
| 	// TODO: validation? | 	if s.MountPoint == "" { | ||||||
|  | 		return fmt.Errorf("MountPoint is required") | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if s.Path == "" { | ||||||
|  | 		return fmt.Errorf("ReqPath is required") | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if s.Schedule.RotationSchedule == "" && s.Schedule.RotationPeriod == 0 { | ||||||
|  | 		return fmt.Errorf("RotationSchedule or RotationPeriod is required to set up rotation job") | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -72,7 +83,7 @@ func newRotationJob(configRequest *RotationJobConfigureRequest) (*RotationJob, e | |||||||
| 		RotationOptions: RotationOptions{ | 		RotationOptions: RotationOptions{ | ||||||
| 			Schedule: rs, | 			Schedule: rs, | ||||||
| 		}, | 		}, | ||||||
| 		MountType: configRequest.MountType, | 		MountPoint: configRequest.MountPoint, | ||||||
| 		Path:       configRequest.ReqPath, | 		Path:       configRequest.ReqPath, | ||||||
| 		Name:       configRequest.Name, | 		Name:       configRequest.Name, | ||||||
| 	}, nil | 	}, nil | ||||||
| @@ -84,6 +95,11 @@ func ConfigureRotationJob(configRequest *RotationJobConfigureRequest) (*Rotation | |||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, err | 		return nil, err | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	if err := rotationJob.Validate(); err != nil { | ||||||
|  | 		return nil, fmt.Errorf("error validating rotation job: %s", err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	// Expect rotation job to exist here | 	// Expect rotation job to exist here | ||||||
| 	if rotationJob == nil { | 	if rotationJob == nil { | ||||||
| 		return nil, fmt.Errorf("rotation credential was nil; expected non-nil value") | 		return nil, fmt.Errorf("rotation credential was nil; expected non-nil value") | ||||||
|   | |||||||
							
								
								
									
										71
									
								
								sdk/rotation/rotation_job_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										71
									
								
								sdk/rotation/rotation_job_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,71 @@ | |||||||
|  | // Copyright (c) HashiCorp, Inc. | ||||||
|  | // SPDX-License-Identifier: MPL-2.0 | ||||||
|  |  | ||||||
|  | package rotation | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"reflect" | ||||||
|  | 	"strings" | ||||||
|  | 	"testing" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | func TestConfigureRotationJob(t *testing.T) { | ||||||
|  | 	tests := []struct { | ||||||
|  | 		name          string | ||||||
|  | 		req           *RotationJobConfigureRequest | ||||||
|  | 		expected      RotationJob | ||||||
|  | 		expectedError string | ||||||
|  | 	}{ | ||||||
|  | 		{ | ||||||
|  | 			name: "no rotation params", | ||||||
|  | 			req: &RotationJobConfigureRequest{ | ||||||
|  | 				MountPoint:       "aws", | ||||||
|  | 				ReqPath:          "config/root", | ||||||
|  | 				RotationSchedule: "", | ||||||
|  | 				RotationWindow:   60, | ||||||
|  | 				RotationPeriod:   0, | ||||||
|  | 			}, | ||||||
|  | 			expectedError: "RotationSchedule or RotationPeriod is required to set up rotation job", | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			name: "no mount point", | ||||||
|  | 			req: &RotationJobConfigureRequest{ | ||||||
|  | 				MountPoint:       "", | ||||||
|  | 				ReqPath:          "config/root", | ||||||
|  | 				RotationSchedule: "", | ||||||
|  | 				RotationWindow:   60, | ||||||
|  | 				RotationPeriod:   5, | ||||||
|  | 			}, | ||||||
|  | 			expectedError: "MountPoint is required", | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			name: "no req path", | ||||||
|  | 			req: &RotationJobConfigureRequest{ | ||||||
|  | 				MountPoint:       "aws", | ||||||
|  | 				ReqPath:          "", | ||||||
|  | 				RotationSchedule: "", | ||||||
|  | 				RotationWindow:   60, | ||||||
|  | 				RotationPeriod:   5, | ||||||
|  | 			}, | ||||||
|  | 			expectedError: "ReqPath is required", | ||||||
|  | 		}, | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	for _, tt := range tests { | ||||||
|  | 		t.Run(tt.name, func(t *testing.T) { | ||||||
|  | 			out, err := ConfigureRotationJob(tt.req) | ||||||
|  | 			if err != nil { | ||||||
|  | 				if tt.expectedError == "" { | ||||||
|  | 					t.Errorf("expected no error but received an error: %s", err) | ||||||
|  | 				} | ||||||
|  | 				if !strings.Contains(err.Error(), tt.expectedError) { | ||||||
|  | 					t.Errorf("TestConfigureRotationJob() error = %v, expected %s", err, tt.expectedError) | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			if err == nil && !reflect.DeepEqual(tt.expected, out) { | ||||||
|  | 				t.Errorf("TestConfigureRotationJob() error comparing params; got %v, expected %v", out, tt.expected) | ||||||
|  | 			} | ||||||
|  | 		}) | ||||||
|  | 	} | ||||||
|  | } | ||||||
| @@ -13,7 +13,7 @@ import ( | |||||||
| const ( | const ( | ||||||
| 	// Minimum allowed value for rotation_window | 	// Minimum allowed value for rotation_window | ||||||
| 	minRotationWindowSeconds = 3600 | 	minRotationWindowSeconds = 3600 | ||||||
| 	parseOptions             = cron.Second | cron.Minute | cron.Hour | cron.Dom | cron.Month | cron.Dow | 	parseOptions             = cron.Minute | cron.Hour | cron.Dom | cron.Month | cron.Dow | ||||||
| ) | ) | ||||||
|  |  | ||||||
| // RotationSchedule holds the parsed and unparsed versions of the schedule, along with the projected next rotation time. | // RotationSchedule holds the parsed and unparsed versions of the schedule, along with the projected next rotation time. | ||||||
|   | |||||||
| @@ -29,5 +29,5 @@ func (c *Core) RegisterRotationJob(_ context.Context, _ *rotation.RotationJob) ( | |||||||
| } | } | ||||||
|  |  | ||||||
| func (c *Core) DeregisterRotationJob(_ context.Context, _ *rotation.RotationJobDeregisterRequest) error { | func (c *Core) DeregisterRotationJob(_ context.Context, _ *rotation.RotationJobDeregisterRequest) error { | ||||||
| 	return automatedrotationutil.ErrRotationManagerUnsupported | 	return nil | ||||||
| } | } | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 vinay-gopalan
					vinay-gopalan