merge master

This commit is contained in:
Kit Patella 2020-11-16 10:46:53 -08:00
commit 374748dafc
224 changed files with 6985 additions and 2616 deletions

3
.changelog/9007.txt Normal file
View File

@ -0,0 +1,3 @@
```release-note:improvement
server: break up Intention.Apply monolithic method
```

3
.changelog/9098.txt Normal file
View File

@ -0,0 +1,3 @@
```release-note:feature
cli: snapshot inspect command provides KV usage breakdown
```

3
.changelog/9101.txt Normal file
View File

@ -0,0 +1,3 @@
```release-note:feature
agent: return the default ACL policy to callers as a header
```

3
.changelog/9119.txt Normal file
View File

@ -0,0 +1,3 @@
```release-note:improvement
chore: update to Go 1.14.11 with mitigation for [golang/go#42138](https://github.com/golang/go/issues/42138)
```

3
.changelog/9142.txt Normal file
View File

@ -0,0 +1,3 @@
```release-note:feature
autopilot: Added a new `consul operator autopilot state` command to retrieve and view the Autopilot state from consul.
```

3
.changelog/9151.txt Normal file
View File

@ -0,0 +1,3 @@
```release-note:improvement
server: remove config entry CAS in legacy intention API bridge code
```

3
.changelog/9156.txt Normal file
View File

@ -0,0 +1,3 @@
```release-note:bug
namespace: **(Enterprise Only)** Fixed a bug that could case snapshot restoration to fail when it contained a namespace marked for deletion while still containing other resources in that namespace.
```

3
.changelog/9181.txt Normal file
View File

@ -0,0 +1,3 @@
```release-note:deprecation
telemetry: the disable_compat_1.9 config will cover more metrics deprecations in future 1.9 point releases. These metrics will be emitted twice for backwards compatibility - if the flag is true, only the new metric name will be written.
```

18
.changelog/9186.txt Normal file
View File

@ -0,0 +1,18 @@
```release-note:bug
server: skip deleted and deleting namespaces when migrating intentions to config entries
```
```release-note:breaking-change
server: **(Enterprise only)** Pre-existing intentions defined with
non-existent destination namespaces were non-functional and are erased during
the upgrade process. This should not matter as these intentions had nothing to
enforce.
```
```release-note:breaking-change
server: **(OSS only)** Pre-existing intentions defined with either a source or
destination namespace value that is not "default" are rewritten or deleted
during the upgrade process. Wildcards first attempt to downgrade to "default"
unless an intention already exists, otherwise these non-functional intentions
are deleted.
```

3
.changelog/_666.txt Normal file
View File

@ -0,0 +1,3 @@
```release-note:bug
namespace: **(Enterprise Only)** Fixed an issue where namespaced services and checks were not being deleted when the containing namespace was deleted.
```

View File

@ -3,7 +3,7 @@ version: 2
references: references:
images: images:
go: &GOLANG_IMAGE docker.mirror.hashicorp.services/circleci/golang:1.14.11 go: &GOLANG_IMAGE docker.mirror.hashicorp.services/circleci/golang:1.15.5
ember: &EMBER_IMAGE docker.mirror.hashicorp.services/circleci/node:12-browsers ember: &EMBER_IMAGE docker.mirror.hashicorp.services/circleci/node:12-browsers
paths: paths:
@ -243,7 +243,7 @@ jobs:
-tags="$GOTAGS" -p 2 \ -tags="$GOTAGS" -p 2 \
-race -gcflags=all=-d=checkptr=0 \ -race -gcflags=all=-d=checkptr=0 \
./agent/{ae,cache,cache-types,checks,config,pool,proxycfg,router}/... \ ./agent/{ae,cache,cache-types,checks,config,pool,proxycfg,router}/... \
./agent/consul/{authmethod,autopilot,fsm,state,stream}/... \ ./agent/consul/{authmethod,fsm,state,stream}/... \
./agent/{grpc,rpc,rpcclient,submatview}/... \ ./agent/{grpc,rpc,rpcclient,submatview}/... \
./snapshot ./snapshot
@ -344,7 +344,7 @@ jobs:
<<: *build-distros <<: *build-distros
environment: environment:
<<: *build-env <<: *build-env
XC_OS: "darwin freebsd linux windows" XC_OS: "freebsd linux windows"
XC_ARCH: "386" XC_ARCH: "386"
# build all amd64 architecture supported OS binaries # build all amd64 architecture supported OS binaries
@ -821,7 +821,7 @@ jobs:
# only runs on master: checks latest commit to see if the PR associated has a backport/* or docs* label to cherry-pick # only runs on master: checks latest commit to see if the PR associated has a backport/* or docs* label to cherry-pick
cherry-picker: cherry-picker:
docker: docker:
- image: docker.mirror.hashicorp.services/alpine:3.11 - image: docker.mirror.hashicorp.services/alpine:3.12
steps: steps:
- run: apk add --no-cache --no-progress git bash curl ncurses jq openssh-client - run: apk add --no-cache --no-progress git bash curl ncurses jq openssh-client
- checkout - checkout
@ -833,7 +833,7 @@ jobs:
trigger-oss-merge: trigger-oss-merge:
docker: docker:
- image: docker.mirror.hashicorp.services/alpine:3.11 - image: docker.mirror.hashicorp.services/alpine:3.12
steps: steps:
- run: apk add --no-cache --no-progress curl jq - run: apk add --no-cache --no-progress curl jq
- run: - run:
@ -894,6 +894,7 @@ workflows:
branches: branches:
only: only:
- master - master
- /release\/\d+\.\d+\.x$/
requires: requires:
- build-static-assets - build-static-assets
- dev-build: - dev-build:

View File

@ -133,7 +133,7 @@ pr_number=$(echo "$resp" | jq '.items[].number')
# comment on the PR with the build number to make it easy to re-run the job when # comment on the PR with the build number to make it easy to re-run the job when
# cherry-pick labels are added in the future # cherry-pick labels are added in the future
github_message=":cherries: Starting backport cherry picking.\n\nTo cherry-pick post-merge, add backport labels and re-run ${CIRCLE_BUILD_URL}." github_message=":cherries: If backport labels were added before merging, cherry-picking will start automatically.\n\nTo retroactively trigger a backport after merging, add backport labels and re-run ${CIRCLE_BUILD_URL}."
curl -f -s -H "Authorization: token ${GITHUB_TOKEN}" \ curl -f -s -H "Authorization: token ${GITHUB_TOKEN}" \
-X POST \ -X POST \
-d "{ \"body\": \"${github_message}\"}" \ -d "{ \"body\": \"${github_message}\"}" \

View File

@ -1,5 +1,25 @@
## UNRELEASED ## UNRELEASED
## 1.9.0-beta3 (November 10, 2020)
BREAKING CHANGES:
* connect: Switch the default gateway port from 443 to 8443 to avoid assumption of Envoy running as root. [[GH-9113](https://github.com/hashicorp/consul/issues/9113)]
* raft: Raft protocol v2 is no longer supported. If currently using protocol v2 then an intermediate upgrade to a version supporting both v2 and v3 protocols will be necessary (1.0.0 - 1.8.x). Note that the Raft protocol configured with the `raft_protocol` setting and the Consul RPC protocol configured with the `protocol` setting and output by the `consul version` command are distinct and supported Consul RPC protocol versions are not altered. [[GH-9103](https://github.com/hashicorp/consul/issues/9103)]
FEATURES:
* autopilot: A new `/v1/operator/autopilot/state` HTTP API was created to give greater visibility into what autopilot is doing and how it has classified all the servers it is tracking. [[GH-9103](https://github.com/hashicorp/consul/issues/9103)]
IMPROVEMENTS:
* autopilot: **(Enterprise Only)** Autopilot now supports using both Redundancy Zones and Automated Upgrades together. [[GH-9103](https://github.com/hashicorp/consul/issues/9103)]
* chore: update to Go 1.14.11 with mitigation for [golang/go#42138](https://github.com/golang/go/issues/42138) [[GH-9119](https://github.com/hashicorp/consul/issues/9119)]
BUG FIXES:
* autopilot: **(Enterprise Only)** Previously servers in other zones would not be promoted when all servers in a second zone had failed. Now the actual behavior matches the docs and autopilot will promote a healthy non-voter from any zone to replace failure of an entire zone. [[GH-9103](https://github.com/hashicorp/consul/issues/9103)]
## 1.9.0-beta2 (November 07, 2020) ## 1.9.0-beta2 (November 07, 2020)
BREAKING CHANGES: BREAKING CHANGES:

View File

@ -77,8 +77,8 @@ type RuntimeConfig struct {
// ACLDefaultPolicy is used to control the ACL interaction when // ACLDefaultPolicy is used to control the ACL interaction when
// there is no defined policy. This can be "allow" which means // there is no defined policy. This can be "allow" which means
// ACLs are used to black-list, or "deny" which means ACLs are // ACLs are used to deny-list, or "deny" which means ACLs are
// white-lists. // allow-lists.
// //
// hcl: acl.default_policy = ("allow"|"deny") // hcl: acl.default_policy = ("allow"|"deny")
ACLDefaultPolicy string ACLDefaultPolicy string

View File

@ -268,8 +268,8 @@ type Config struct {
// ACLDefaultPolicy is used to control the ACL interaction when // ACLDefaultPolicy is used to control the ACL interaction when
// there is no defined policy. This can be "allow" which means // there is no defined policy. This can be "allow" which means
// ACLs are used to black-list, or "deny" which means ACLs are // ACLs are used to deny-list, or "deny" which means ACLs are
// white-lists. // allow-lists.
ACLDefaultPolicy string ACLDefaultPolicy string
// ACLDownPolicy controls the behavior of ACLs if the ACLDatacenter // ACLDownPolicy controls the behavior of ACLs if the ACLDatacenter

View File

@ -48,10 +48,6 @@ type ConfigEntry struct {
// Apply does an upsert of the given config entry. // Apply does an upsert of the given config entry.
func (c *ConfigEntry) Apply(args *structs.ConfigEntryRequest, reply *bool) error { func (c *ConfigEntry) Apply(args *structs.ConfigEntryRequest, reply *bool) error {
return c.applyInternal(args, reply, nil)
}
func (c *ConfigEntry) applyInternal(args *structs.ConfigEntryRequest, reply *bool, normalizeAndValidateFn func(structs.ConfigEntry) error) error {
if err := c.srv.validateEnterpriseRequest(args.Entry.GetEnterpriseMeta(), true); err != nil { if err := c.srv.validateEnterpriseRequest(args.Entry.GetEnterpriseMeta(), true); err != nil {
return err return err
} }
@ -76,17 +72,11 @@ func (c *ConfigEntry) applyInternal(args *structs.ConfigEntryRequest, reply *boo
} }
// Normalize and validate the incoming config entry as if it came from a user. // Normalize and validate the incoming config entry as if it came from a user.
if normalizeAndValidateFn == nil { if err := args.Entry.Normalize(); err != nil {
if err := args.Entry.Normalize(); err != nil { return err
return err }
} if err := args.Entry.Validate(); err != nil {
if err := args.Entry.Validate(); err != nil { return err
return err
}
} else {
if err := normalizeAndValidateFn(args.Entry); err != nil {
return err
}
} }
if authz != nil && !args.Entry.CanWrite(authz) { if authz != nil && !args.Entry.CanWrite(authz) {
@ -483,6 +473,11 @@ func (c *ConfigEntry) ResolveServiceConfig(args *structs.ServiceConfigRequest, r
func (c *ConfigEntry) preflightCheck(kind string) error { func (c *ConfigEntry) preflightCheck(kind string) error {
switch kind { switch kind {
case structs.ServiceIntentions: case structs.ServiceIntentions:
// Exit early if Connect hasn't been enabled.
if !c.srv.config.ConnectEnabled {
return ErrConnectNotEnabled
}
usingConfigEntries, err := c.srv.fsm.State().AreIntentionsInConfigEntries() usingConfigEntries, err := c.srv.fsm.State().AreIntentionsInConfigEntries()
if err != nil { if err != nil {
return fmt.Errorf("system metadata lookup failed: %v", err) return fmt.Errorf("system metadata lookup failed: %v", err)

View File

@ -382,6 +382,11 @@ func (c *FSM) applyIntentionOperation(buf []byte, index uint64) interface{} {
[]metrics.Label{{Name: "op", Value: string(req.Op)}}) []metrics.Label{{Name: "op", Value: string(req.Op)}})
defer metrics.MeasureSinceWithLabels([]string{"fsm", "intention"}, time.Now(), defer metrics.MeasureSinceWithLabels([]string{"fsm", "intention"}, time.Now(),
[]metrics.Label{{Name: "op", Value: string(req.Op)}}) []metrics.Label{{Name: "op", Value: string(req.Op)}})
if req.Mutation != nil {
return c.state.IntentionMutation(index, req.Op, req.Mutation)
}
switch req.Op { switch req.Op {
case structs.IntentionOpCreate, structs.IntentionOpUpdate: case structs.IntentionOpCreate, structs.IntentionOpUpdate:
//nolint:staticcheck //nolint:staticcheck

View File

@ -33,22 +33,11 @@ var (
ErrIntentionNotFound = errors.New("Intention not found") ErrIntentionNotFound = errors.New("Intention not found")
) )
// NewIntentionEndpoint returns a new Intention endpoint.
func NewIntentionEndpoint(srv *Server, logger hclog.Logger) *Intention {
return &Intention{
srv: srv,
logger: logger,
configEntryEndpoint: &ConfigEntry{srv},
}
}
// Intention manages the Connect intentions. // Intention manages the Connect intentions.
type Intention struct { type Intention struct {
// srv is a pointer back to the server. // srv is a pointer back to the server.
srv *Server srv *Server
logger hclog.Logger logger hclog.Logger
configEntryEndpoint *ConfigEntry
} }
func (s *Intention) checkIntentionID(id string) (bool, error) { func (s *Intention) checkIntentionID(id string) (bool, error) {
@ -62,25 +51,139 @@ func (s *Intention) checkIntentionID(id string) (bool, error) {
return true, nil return true, nil
} }
// prepareApplyCreate validates that the requester has permissions to create var ErrIntentionsNotUpgradedYet = errors.New("Intentions are read only while being upgraded to config entries")
// the new intention, generates a new uuid for the intention and generally
// validates that the request is well-formed // legacyUpgradeCheck fast fails a write request using the legacy intention
// // RPCs if the system is known to be mid-upgrade. This is purely a perf
// Returns an existing service-intentions config entry for this destination if // optimization and the actual real enforcement happens in the FSM. It would be
// one exists. // wasteful to round trip all the way through raft to have it fail for
func (s *Intention) prepareApplyCreate( // known-up-front reasons, hence why we check it twice.
ident structs.ACLIdentity, func (s *Intention) legacyUpgradeCheck() error {
usingConfigEntries, err := s.srv.fsm.State().AreIntentionsInConfigEntries()
if err != nil {
return fmt.Errorf("system metadata lookup failed: %v", err)
}
if !usingConfigEntries {
return ErrIntentionsNotUpgradedYet
}
return nil
}
// Apply creates or updates an intention in the data store.
func (s *Intention) Apply(args *structs.IntentionRequest, reply *string) error {
// Exit early if Connect hasn't been enabled.
if !s.srv.config.ConnectEnabled {
return ErrConnectNotEnabled
}
// Ensure that all service-intentions config entry writes go to the primary
// datacenter. These will then be replicated to all the other datacenters.
args.Datacenter = s.srv.config.PrimaryDatacenter
if done, err := s.srv.ForwardRPC("Intention.Apply", args, args, reply); done {
return err
}
defer metrics.MeasureSince([]string{"consul", "intention", "apply"}, time.Now())
defer metrics.MeasureSince([]string{"intention", "apply"}, time.Now())
if err := s.legacyUpgradeCheck(); err != nil {
return err
}
if args.Mutation != nil {
return fmt.Errorf("Mutation field is internal only and must not be set via RPC")
}
// Always set a non-nil intention to avoid nil-access below
if args.Intention == nil {
args.Intention = &structs.Intention{}
}
// Get the ACL token for the request for the checks below.
var entMeta structs.EnterpriseMeta
ident, authz, err := s.srv.ResolveTokenIdentityAndDefaultMeta(args.Token, &entMeta, nil)
if err != nil {
return err
}
var accessorID string
if ident != nil {
accessorID = ident.ID()
}
var (
mut *structs.IntentionMutation
legacyWrite bool
)
switch args.Op {
case structs.IntentionOpCreate:
legacyWrite = true
mut, err = s.computeApplyChangesLegacyCreate(accessorID, authz, &entMeta, args)
case structs.IntentionOpUpdate:
legacyWrite = true
mut, err = s.computeApplyChangesLegacyUpdate(accessorID, authz, &entMeta, args)
case structs.IntentionOpUpsert:
legacyWrite = false
mut, err = s.computeApplyChangesUpsert(accessorID, authz, &entMeta, args)
case structs.IntentionOpDelete:
if args.Intention.ID == "" {
legacyWrite = false
mut, err = s.computeApplyChangesDelete(accessorID, authz, &entMeta, args)
} else {
legacyWrite = true
mut, err = s.computeApplyChangesLegacyDelete(accessorID, authz, &entMeta, args)
}
case structs.IntentionOpDeleteAll:
// This is an internal operation initiated by the leader and is not
// exposed for general RPC use.
return fmt.Errorf("Invalid Intention operation: %v", args.Op)
default:
return fmt.Errorf("Invalid Intention operation: %v", args.Op)
}
if err != nil {
return err
}
if legacyWrite {
*reply = args.Intention.ID
} else {
*reply = ""
}
// Switch to the config entry manipulating flavor:
args.Mutation = mut
args.Intention = nil
resp, err := s.srv.raftApply(structs.IntentionRequestType, args)
if err != nil {
return err
}
if respErr, ok := resp.(error); ok {
return respErr
}
return nil
}
func (s *Intention) computeApplyChangesLegacyCreate(
accessorID string,
authz acl.Authorizer, authz acl.Authorizer,
entMeta *structs.EnterpriseMeta, entMeta *structs.EnterpriseMeta,
args *structs.IntentionRequest, args *structs.IntentionRequest,
) (*structs.ServiceIntentionsConfigEntry, error) { ) (*structs.IntentionMutation, error) {
// This variant is just for legacy UUID-based intentions.
args.Intention.DefaultNamespaces(entMeta)
if !args.Intention.CanWrite(authz) { if !args.Intention.CanWrite(authz) {
var accessorID string sn := args.Intention.SourceServiceName()
if ident != nil { dn := args.Intention.DestinationServiceName()
accessorID = ident.ID()
}
// todo(kit) Migrate intention access denial logging over to audit logging when we implement it // todo(kit) Migrate intention access denial logging over to audit logging when we implement it
s.logger.Warn("Intention creation denied due to ACLs", "intention", args.Intention.ID, "accessorID", accessorID) s.logger.Warn("Intention creation denied due to ACLs",
"source", sn.String(),
"destination", dn.String(),
"accessorID", accessorID)
return nil, acl.ErrPermissionDenied return nil, acl.ErrPermissionDenied
} }
@ -106,8 +209,6 @@ func (s *Intention) prepareApplyCreate(
args.Intention.SourceType = structs.IntentionSourceConsul args.Intention.SourceType = structs.IntentionSourceConsul
} }
args.Intention.DefaultNamespaces(entMeta)
if err := s.validateEnterpriseIntention(args.Intention); err != nil { if err := s.validateEnterpriseIntention(args.Intention); err != nil {
return nil, err return nil, err
} }
@ -117,74 +218,59 @@ func (s *Intention) prepareApplyCreate(
return nil, err return nil, err
} }
_, configEntry, err := s.srv.fsm.State().ConfigEntry(nil, structs.ServiceIntentions, args.Intention.DestinationName, args.Intention.DestinationEnterpriseMeta()) // NOTE: if the append of this source causes a duplicate source name the
if err != nil { // config entry validation will fail so we don't have to check that
return nil, fmt.Errorf("service-intentions config entry lookup failed: %v", err) // explicitly here.
} else if configEntry == nil {
return nil, nil mut := &structs.IntentionMutation{
Destination: args.Intention.DestinationServiceName(),
Value: args.Intention.ToSourceIntention(true),
} }
return configEntry.(*structs.ServiceIntentionsConfigEntry), nil // Set the created/updated times. If this is an update instead of an insert
// the UpdateOver() will fix it up appropriately.
now := time.Now().UTC()
mut.Value.LegacyCreateTime = timePointer(now)
mut.Value.LegacyUpdateTime = timePointer(now)
return mut, nil
} }
// prepareApplyUpdateLegacy validates that the requester has permissions on both the updated and existing func (s *Intention) computeApplyChangesLegacyUpdate(
// intention as well as generally validating that the request is well-formed accessorID string,
//
// Returns an existing service-intentions config entry for this destination if
// one exists.
func (s *Intention) prepareApplyUpdateLegacy(
ident structs.ACLIdentity,
authz acl.Authorizer, authz acl.Authorizer,
entMeta *structs.EnterpriseMeta, entMeta *structs.EnterpriseMeta,
args *structs.IntentionRequest, args *structs.IntentionRequest,
) (*structs.ServiceIntentionsConfigEntry, error) { ) (*structs.IntentionMutation, error) {
if !args.Intention.CanWrite(authz) { // This variant is just for legacy UUID-based intentions.
var accessorID string
if ident != nil {
accessorID = ident.ID()
}
// todo(kit) Migrate intention access denial logging over to audit logging when we implement it
s.logger.Warn("Update operation on intention denied due to ACLs", "intention", args.Intention.ID, "accessorID", accessorID)
return nil, acl.ErrPermissionDenied
}
_, configEntry, ixn, err := s.srv.fsm.State().IntentionGet(nil, args.Intention.ID) _, _, ixn, err := s.srv.fsm.State().IntentionGet(nil, args.Intention.ID)
if err != nil { if err != nil {
return nil, fmt.Errorf("Intention lookup failed: %v", err) return nil, fmt.Errorf("Intention lookup failed: %v", err)
} }
if ixn == nil || configEntry == nil { if ixn == nil {
return nil, fmt.Errorf("Cannot modify non-existent intention: '%s'", args.Intention.ID) return nil, fmt.Errorf("Cannot modify non-existent intention: '%s'", args.Intention.ID)
} }
// Perform the ACL check that we have write to the old intention too,
// which must be true to perform any rename. This is the only ACL enforcement
// done for deletions and a secondary enforcement for updates.
if !ixn.CanWrite(authz) { if !ixn.CanWrite(authz) {
var accessorID string
if ident != nil {
accessorID = ident.ID()
}
// todo(kit) Migrate intention access denial logging over to audit logging when we implement it // todo(kit) Migrate intention access denial logging over to audit logging when we implement it
s.logger.Warn("Update operation on intention denied due to ACLs", "intention", args.Intention.ID, "accessorID", accessorID) s.logger.Warn("Update operation on intention denied due to ACLs", "intention", args.Intention.ID, "accessorID", accessorID)
return nil, acl.ErrPermissionDenied return nil, acl.ErrPermissionDenied
} }
args.Intention.DefaultNamespaces(entMeta)
// Prior to v1.9.0 renames of the destination side of an intention were // Prior to v1.9.0 renames of the destination side of an intention were
// allowed, but that behavior doesn't work anymore. // allowed, but that behavior doesn't work anymore.
if ixn.DestinationServiceName() != args.Intention.DestinationServiceName() { if ixn.DestinationServiceName() != args.Intention.DestinationServiceName() {
return nil, fmt.Errorf("Cannot modify DestinationNS or DestinationName for an intention once it exists.") return nil, fmt.Errorf("Cannot modify DestinationNS or DestinationName for an intention once it exists.")
} }
// We always update the updatedat field.
args.Intention.UpdatedAt = time.Now().UTC()
// Default source type // Default source type
if args.Intention.SourceType == "" { if args.Intention.SourceType == "" {
args.Intention.SourceType = structs.IntentionSourceConsul args.Intention.SourceType = structs.IntentionSourceConsul
} }
args.Intention.DefaultNamespaces(entMeta)
if err := s.validateEnterpriseIntention(args.Intention); err != nil { if err := s.validateEnterpriseIntention(args.Intention); err != nil {
return nil, err return nil, err
} }
@ -196,329 +282,149 @@ func (s *Intention) prepareApplyUpdateLegacy(
return nil, err return nil, err
} }
return configEntry, nil mut := &structs.IntentionMutation{
ID: args.Intention.ID,
Value: args.Intention.ToSourceIntention(true),
}
// Set the created/updated times. If this is an update instead of an insert
// the UpdateOver() will fix it up appropriately.
now := time.Now().UTC()
mut.Value.LegacyCreateTime = timePointer(now)
mut.Value.LegacyUpdateTime = timePointer(now)
return mut, nil
} }
// prepareApplyDeleteLegacy ensures that the intention specified by the ID in the request exists func (s *Intention) computeApplyChangesUpsert(
// and that the requester is authorized to delete it accessorID string,
//
// Returns an existing service-intentions config entry for this destination if
// one exists.
func (s *Intention) prepareApplyDeleteLegacy(
ident structs.ACLIdentity,
authz acl.Authorizer, authz acl.Authorizer,
entMeta *structs.EnterpriseMeta,
args *structs.IntentionRequest, args *structs.IntentionRequest,
) (*structs.ServiceIntentionsConfigEntry, error) { ) (*structs.IntentionMutation, error) {
// If this is not a create, then we have to verify the ID. // This variant is just for config-entry based intentions.
_, configEntry, ixn, err := s.srv.fsm.State().IntentionGet(nil, args.Intention.ID)
if args.Intention.ID != "" {
// This is a new-style only endpoint
return nil, fmt.Errorf("ID must not be specified")
}
args.Intention.DefaultNamespaces(entMeta)
if !args.Intention.CanWrite(authz) {
sn := args.Intention.SourceServiceName()
dn := args.Intention.DestinationServiceName()
// todo(kit) Migrate intention access denial logging over to audit logging when we implement it
s.logger.Warn("Intention upsert denied due to ACLs",
"source", sn.String(),
"destination", dn.String(),
"accessorID", accessorID)
return nil, acl.ErrPermissionDenied
}
_, prevEntry, err := s.srv.fsm.State().ConfigEntry(nil, structs.ServiceIntentions, args.Intention.DestinationName, args.Intention.DestinationEnterpriseMeta())
if err != nil { if err != nil {
return nil, fmt.Errorf("Intention lookup failed: %v", err) return nil, fmt.Errorf("Intention lookup failed: %v", err)
} }
if ixn == nil || configEntry == nil {
if prevEntry == nil {
// Meta is NOT permitted here, as it would need to be persisted on
// the enclosing config entry.
if len(args.Intention.Meta) > 0 {
return nil, fmt.Errorf("Meta must not be specified")
}
} else {
if len(args.Intention.Meta) > 0 {
// Meta is NOT permitted here, but there is one exception. If
// you are updating a previous record, but that record lives
// within a config entry that itself has Meta, then you may
// incidentally ship the Meta right back to consul.
//
// In that case if Meta is provided, it has to be a perfect
// match for what is already on the enclosing config entry so
// it's safe to discard.
if !equalStringMaps(prevEntry.GetMeta(), args.Intention.Meta) {
return nil, fmt.Errorf("Meta must not be specified, or should be unchanged during an update.")
}
// Now it is safe to discard
args.Intention.Meta = nil
}
}
return &structs.IntentionMutation{
Destination: args.Intention.DestinationServiceName(),
Source: args.Intention.SourceServiceName(),
Value: args.Intention.ToSourceIntention(false),
}, nil
}
func (s *Intention) computeApplyChangesLegacyDelete(
accessorID string,
authz acl.Authorizer,
entMeta *structs.EnterpriseMeta,
args *structs.IntentionRequest,
) (*structs.IntentionMutation, error) {
_, _, ixn, err := s.srv.fsm.State().IntentionGet(nil, args.Intention.ID)
if err != nil {
return nil, fmt.Errorf("Intention lookup failed: %v", err)
}
if ixn == nil {
return nil, fmt.Errorf("Cannot delete non-existent intention: '%s'", args.Intention.ID) return nil, fmt.Errorf("Cannot delete non-existent intention: '%s'", args.Intention.ID)
} }
// Perform the ACL check that we have write to the old intention. This is
// the only ACL enforcement done for deletions and a secondary enforcement
// for updates.
if !ixn.CanWrite(authz) { if !ixn.CanWrite(authz) {
var accessorID string
if ident != nil {
accessorID = ident.ID()
}
// todo(kit) Migrate intention access denial logging over to audit logging when we implement it // todo(kit) Migrate intention access denial logging over to audit logging when we implement it
s.logger.Warn("Deletion operation on intention denied due to ACLs", "intention", args.Intention.ID, "accessorID", accessorID) s.logger.Warn("Deletion operation on intention denied due to ACLs", "intention", args.Intention.ID, "accessorID", accessorID)
return nil, acl.ErrPermissionDenied return nil, acl.ErrPermissionDenied
} }
return configEntry, nil return &structs.IntentionMutation{
ID: args.Intention.ID,
}, nil
} }
var ErrIntentionsNotUpgradedYet = errors.New("Intentions are read only while being upgraded to config entries") func (s *Intention) computeApplyChangesDelete(
accessorID string,
// legacyUpgradeCheck fast fails a write request using the legacy intention authz acl.Authorizer,
// RPCs if the system is known to be mid-upgrade. This is purely a perf entMeta *structs.EnterpriseMeta,
// optimization and the actual real enforcement happens in the FSM. It would be
// wasteful to round trip all the way through raft to have it fail for
// known-up-front reasons, hence why we check it twice.
func (s *Intention) legacyUpgradeCheck() error {
usingConfigEntries, err := s.srv.fsm.State().AreIntentionsInConfigEntries()
if err != nil {
return fmt.Errorf("system metadata lookup failed: %v", err)
}
if !usingConfigEntries {
return ErrIntentionsNotUpgradedYet
}
return nil
}
// Apply creates or updates an intention in the data store.
func (s *Intention) Apply(
args *structs.IntentionRequest, args *structs.IntentionRequest,
reply *string) error { ) (*structs.IntentionMutation, error) {
args.Intention.DefaultNamespaces(entMeta)
// Ensure that all service-intentions config entry writes go to the primary
// datacenter. These will then be replicated to all the other datacenters.
args.Datacenter = s.srv.config.PrimaryDatacenter
if done, err := s.srv.ForwardRPC("Intention.Apply", args, args, reply); done {
return err
}
defer metrics.MeasureSince([]string{"consul", "intention", "apply"}, time.Now())
defer metrics.MeasureSince([]string{"intention", "apply"}, time.Now())
if err := s.legacyUpgradeCheck(); err != nil {
return err
}
// Always set a non-nil intention to avoid nil-access below
if args.Intention == nil {
args.Intention = &structs.Intention{}
}
// Get the ACL token for the request for the checks below.
var entMeta structs.EnterpriseMeta
ident, authz, err := s.srv.ResolveTokenIdentityAndDefaultMeta(args.Token, &entMeta, nil)
if err != nil {
return err
}
var (
prevEntry *structs.ServiceIntentionsConfigEntry
upsertEntry *structs.ServiceIntentionsConfigEntry
legacyWrite bool
noop bool
)
switch args.Op {
case structs.IntentionOpCreate:
legacyWrite = true
// This variant is just for legacy UUID-based intentions.
prevEntry, err = s.prepareApplyCreate(ident, authz, &entMeta, args)
if err != nil {
return err
}
if prevEntry == nil {
upsertEntry = args.Intention.ToConfigEntry(true)
} else {
upsertEntry = prevEntry.Clone()
upsertEntry.Sources = append(upsertEntry.Sources, args.Intention.ToSourceIntention(true))
}
case structs.IntentionOpUpdate:
// This variant is just for legacy UUID-based intentions.
legacyWrite = true
prevEntry, err = s.prepareApplyUpdateLegacy(ident, authz, &entMeta, args)
if err != nil {
return err
}
upsertEntry = prevEntry.Clone()
for i, src := range upsertEntry.Sources {
if src.LegacyID == args.Intention.ID {
upsertEntry.Sources[i] = args.Intention.ToSourceIntention(true)
break
}
}
case structs.IntentionOpUpsert:
// This variant is just for config-entry based intentions.
legacyWrite = false
if args.Intention.ID != "" {
// This is a new-style only endpoint
return fmt.Errorf("ID must not be specified")
}
args.Intention.DefaultNamespaces(&entMeta)
prevEntry, err = s.getServiceIntentionsConfigEntry(args.Intention.DestinationName, args.Intention.DestinationEnterpriseMeta())
if err != nil {
return err
}
if !args.Intention.CanWrite(authz) {
sn := args.Intention.SourceServiceName() sn := args.Intention.SourceServiceName()
dn := args.Intention.DestinationServiceName()
// TODO(intentions): have service-intentions validation functions // todo(kit) Migrate intention access denial logging over to audit logging when we implement it
// return structured errors so that we can rewrite the field prefix s.logger.Warn("Intention delete denied due to ACLs",
// here so that the validation errors are not misleading. "source", sn.String(),
if prevEntry == nil { "destination", dn.String(),
// Meta is NOT permitted here, as it would need to be persisted on "accessorID", accessorID)
// the enclosing config entry. return nil, acl.ErrPermissionDenied
if len(args.Intention.Meta) > 0 {
return fmt.Errorf("Meta must not be specified")
}
upsertEntry = args.Intention.ToConfigEntry(false)
} else {
upsertEntry = prevEntry.Clone()
if len(args.Intention.Meta) > 0 {
// Meta is NOT permitted here, but there is one exception. If
// you are updating a previous record, but that record lives
// within a config entry that itself has Meta, then you may
// incidentally ship the Meta right back to consul.
//
// In that case if Meta is provided, it has to be a perfect
// match for what is already on the enclosing config entry so
// it's safe to discard.
if !equalStringMaps(upsertEntry.Meta, args.Intention.Meta) {
return fmt.Errorf("Meta must not be specified, or should be unchanged during an update.")
}
// Now it is safe to discard
args.Intention.Meta = nil
}
found := false
for i, src := range upsertEntry.Sources {
if src.SourceServiceName() == sn {
upsertEntry.Sources[i] = args.Intention.ToSourceIntention(false)
found = true
break
}
}
if !found {
upsertEntry.Sources = append(upsertEntry.Sources, args.Intention.ToSourceIntention(false))
}
}
case structs.IntentionOpDelete:
// There are two ways to get this request:
//
// 1) legacy: the ID field is populated
// 2) config-entry: the ID field is NOT populated
if args.Intention.ID == "" {
// config-entry style: no LegacyID
legacyWrite = false
args.Intention.DefaultNamespaces(&entMeta)
prevEntry, err = s.getServiceIntentionsConfigEntry(args.Intention.DestinationName, args.Intention.DestinationEnterpriseMeta())
if err != nil {
return err
}
// NOTE: validation errors may be misleading!
noop = true
if prevEntry != nil {
sn := args.Intention.SourceServiceName()
upsertEntry = prevEntry.Clone()
for i, src := range upsertEntry.Sources {
if src.SourceServiceName() == sn {
// Delete slice element: https://github.com/golang/go/wiki/SliceTricks#delete
// a = append(a[:i], a[i+1:]...)
upsertEntry.Sources = append(upsertEntry.Sources[:i], upsertEntry.Sources[i+1:]...)
if len(upsertEntry.Sources) == 0 {
upsertEntry.Sources = nil
}
noop = false
break
}
}
}
} else {
// legacy style: LegacyID required
legacyWrite = true
prevEntry, err = s.prepareApplyDeleteLegacy(ident, authz, args)
if err != nil {
return err
}
upsertEntry = prevEntry.Clone()
for i, src := range upsertEntry.Sources {
if src.LegacyID == args.Intention.ID {
// Delete slice element: https://github.com/golang/go/wiki/SliceTricks#delete
// a = append(a[:i], a[i+1:]...)
upsertEntry.Sources = append(upsertEntry.Sources[:i], upsertEntry.Sources[i+1:]...)
if len(upsertEntry.Sources) == 0 {
upsertEntry.Sources = nil
}
break
}
}
}
case structs.IntentionOpDeleteAll:
// This is an internal operation initiated by the leader and is not
// exposed for general RPC use.
fallthrough
default:
return fmt.Errorf("Invalid Intention operation: %v", args.Op)
} }
if !noop && prevEntry != nil && legacyWrite && !prevEntry.LegacyIDFieldsAreAllSet() { // Pre-flight to avoid pointless raft operations.
sn := prevEntry.DestinationServiceName() _, _, ixn, err := s.srv.fsm.State().IntentionGetExact(nil, args.Intention.ToExact())
return fmt.Errorf("cannot use legacy intention API to edit intentions with a destination of %q after editing them via a service-intentions config entry", sn.String()) if err != nil {
return nil, fmt.Errorf("Intention lookup failed: %v", err)
}
if ixn == nil {
return nil, nil
} }
// setup the reply which will have been filled in by one of the preparedApply* funcs return &structs.IntentionMutation{
if legacyWrite { Destination: args.Intention.DestinationServiceName(),
*reply = args.Intention.ID Source: args.Intention.SourceServiceName(),
} else { }, nil
*reply = ""
}
if noop {
return nil
}
// Commit indirectly by invoking the other RPC handler directly.
configReq := &structs.ConfigEntryRequest{
Datacenter: args.Datacenter,
WriteRequest: args.WriteRequest,
}
if upsertEntry == nil || len(upsertEntry.Sources) == 0 {
configReq.Op = structs.ConfigEntryDelete
configReq.Entry = &structs.ServiceIntentionsConfigEntry{
Kind: structs.ServiceIntentions,
Name: prevEntry.Name,
EnterpriseMeta: prevEntry.EnterpriseMeta,
}
var ignored struct{}
return s.configEntryEndpoint.Delete(configReq, &ignored)
} else {
// Update config entry CAS
configReq.Op = structs.ConfigEntryUpsertCAS
configReq.Entry = upsertEntry
var normalizeAndValidateFn func(raw structs.ConfigEntry) error
if legacyWrite {
normalizeAndValidateFn = func(raw structs.ConfigEntry) error {
entry := raw.(*structs.ServiceIntentionsConfigEntry)
if err := entry.LegacyNormalize(); err != nil {
return err
}
return entry.LegacyValidate()
}
}
var applied bool
err := s.configEntryEndpoint.applyInternal(configReq, &applied, normalizeAndValidateFn)
if err != nil {
return err
}
if !applied {
return fmt.Errorf("config entry failed to persist due to CAS failure: kind=%q, name=%q", upsertEntry.Kind, upsertEntry.Name)
}
return nil
}
} }
// Get returns a single intention by ID. // Get returns a single intention by ID.
func (s *Intention) Get( func (s *Intention) Get(args *structs.IntentionQueryRequest, reply *structs.IndexedIntentions) error {
args *structs.IntentionQueryRequest, // Exit early if Connect hasn't been enabled.
reply *structs.IndexedIntentions) error { if !s.srv.config.ConnectEnabled {
return ErrConnectNotEnabled
}
// Forward if necessary // Forward if necessary
if done, err := s.srv.ForwardRPC("Intention.Get", args, args, reply); done { if done, err := s.srv.ForwardRPC("Intention.Get", args, args, reply); done {
return err return err
@ -591,9 +497,12 @@ func (s *Intention) Get(
} }
// List returns all the intentions. // List returns all the intentions.
func (s *Intention) List( func (s *Intention) List(args *structs.IntentionListRequest, reply *structs.IndexedIntentions) error {
args *structs.IntentionListRequest, // Exit early if Connect hasn't been enabled.
reply *structs.IndexedIntentions) error { if !s.srv.config.ConnectEnabled {
return ErrConnectNotEnabled
}
// Forward if necessary // Forward if necessary
if done, err := s.srv.ForwardRPC("Intention.List", args, args, reply); done { if done, err := s.srv.ForwardRPC("Intention.List", args, args, reply); done {
return err return err
@ -658,9 +567,12 @@ func (s *Intention) List(
} }
// Match returns the set of intentions that match the given source/destination. // Match returns the set of intentions that match the given source/destination.
func (s *Intention) Match( func (s *Intention) Match(args *structs.IntentionQueryRequest, reply *structs.IndexedIntentionMatches) error {
args *structs.IntentionQueryRequest, // Exit early if Connect hasn't been enabled.
reply *structs.IndexedIntentionMatches) error { if !s.srv.config.ConnectEnabled {
return ErrConnectNotEnabled
}
// Forward if necessary // Forward if necessary
if done, err := s.srv.ForwardRPC("Intention.Match", args, args, reply); done { if done, err := s.srv.ForwardRPC("Intention.Match", args, args, reply); done {
return err return err
@ -729,9 +641,12 @@ func (s *Intention) Match(
// Note: Whenever the logic for this method is changed, you should take // Note: Whenever the logic for this method is changed, you should take
// a look at the agent authorize endpoint (agent/agent_endpoint.go) since // a look at the agent authorize endpoint (agent/agent_endpoint.go) since
// the logic there is similar. // the logic there is similar.
func (s *Intention) Check( func (s *Intention) Check(args *structs.IntentionQueryRequest, reply *structs.IntentionQueryCheckResponse) error {
args *structs.IntentionQueryRequest, // Exit early if Connect hasn't been enabled.
reply *structs.IntentionQueryCheckResponse) error { if !s.srv.config.ConnectEnabled {
return ErrConnectNotEnabled
}
// Forward maybe // Forward maybe
if done, err := s.srv.ForwardRPC("Intention.Check", args, args, reply); done { if done, err := s.srv.ForwardRPC("Intention.Check", args, args, reply); done {
return err return err
@ -851,23 +766,6 @@ func (s *Intention) validateEnterpriseIntention(ixn *structs.Intention) error {
return nil return nil
} }
func (s *Intention) getServiceIntentionsConfigEntry(name string, entMeta *structs.EnterpriseMeta) (*structs.ServiceIntentionsConfigEntry, error) {
_, raw, err := s.srv.fsm.State().ConfigEntry(nil, structs.ServiceIntentions, name, entMeta)
if err != nil {
return nil, fmt.Errorf("Intention lookup failed: %v", err)
}
if raw == nil {
return nil, nil
}
configEntry, ok := raw.(*structs.ServiceIntentionsConfigEntry)
if !ok {
return nil, fmt.Errorf("invalid service config type %T", raw)
}
return configEntry, nil
}
func equalStringMaps(a, b map[string]string) bool { func equalStringMaps(a, b map[string]string) bool {
if len(a) != len(b) { if len(a) != len(b) {
return false return false

View File

@ -1033,18 +1033,32 @@ func (s *Server) bootstrapConfigEntries(entries []structs.ConfigEntry) error {
state := s.fsm.State() state := s.fsm.State()
// Do a quick preflight check to see if someone is trying to upgrade from // Do some quick preflight checks to see if someone is doing something
// an older pre-1.9.0 version of consul with intentions AND are trying to // that's not allowed at this time:
// bootstrap a service-intentions config entry at the same time. //
// - Trying to upgrade from an older pre-1.9.0 version of consul with
// intentions AND are trying to bootstrap a service-intentions config entry
// at the same time.
//
// - Trying to insert service-intentions config entries when connect is
// disabled.
usingConfigEntries, err := s.fsm.State().AreIntentionsInConfigEntries() usingConfigEntries, err := s.fsm.State().AreIntentionsInConfigEntries()
if err != nil { if err != nil {
return fmt.Errorf("Failed to determine if we are migrating intentions yet: %v", err) return fmt.Errorf("Failed to determine if we are migrating intentions yet: %v", err)
} }
if !usingConfigEntries {
if !usingConfigEntries || !s.config.ConnectEnabled {
for _, entry := range entries { for _, entry := range entries {
if entry.GetKind() == structs.ServiceIntentions { if entry.GetKind() == structs.ServiceIntentions {
return fmt.Errorf("Refusing to apply configuration entry %q / %q because intentions are still being migrated to config entries: %v", if !s.config.ConnectEnabled {
entry.GetKind(), entry.GetName(), err) return fmt.Errorf("Refusing to apply configuration entry %q / %q because Connect must be enabled to bootstrap intentions",
entry.GetKind(), entry.GetName())
}
if !usingConfigEntries {
return fmt.Errorf("Refusing to apply configuration entry %q / %q because intentions are still being migrated to config entries",
entry.GetKind(), entry.GetName())
}
} }
} }
} }

View File

@ -100,6 +100,11 @@ func (s *Server) legacyIntentionMigration(ctx context.Context) error {
return err return err
} }
entries, err = s.filterMigratedLegacyIntentions(entries)
if err != nil {
return err
}
// Totally cheat and repurpose one part of config entry replication // Totally cheat and repurpose one part of config entry replication
// here so we automatically get our writes rate limited. // here so we automatically get our writes rate limited.
_, err = s.reconcileLocalConfig(ctx, entries, structs.ConfigEntryUpsert) _, err = s.reconcileLocalConfig(ctx, entries, structs.ConfigEntryUpsert)

View File

@ -84,3 +84,7 @@ func migrateIntentionsToConfigEntries(ixns structs.Intentions) []*structs.Servic
return structs.MigrateIntentions(output) return structs.MigrateIntentions(output)
} }
func (s *Server) filterMigratedLegacyIntentions(entries []structs.ConfigEntry) ([]structs.ConfigEntry, error) {
return entries, nil
}

View File

@ -11,6 +11,13 @@ import (
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
func testLeader_LegacyIntentionMigrationHookEnterprise(_ *testing.T, _ *Server, _ bool) {
}
func appendLegacyIntentionsForMigrationTestEnterprise(_ *testing.T, _ *Server, ixns []*structs.Intention) []*structs.Intention {
return ixns
}
func TestMigrateIntentionsToConfigEntries(t *testing.T) { func TestMigrateIntentionsToConfigEntries(t *testing.T) {
compare := func(t *testing.T, got structs.Intentions, expect [][]string) { compare := func(t *testing.T, got structs.Intentions, expect [][]string) {
t.Helper() t.Helper()

View File

@ -425,6 +425,11 @@ func TestLeader_LegacyIntentionMigration(t *testing.T) {
makeIxn("contractor", "*", false), makeIxn("contractor", "*", false),
makeIxn("*", "*", true), makeIxn("*", "*", true),
} }
ixns = appendLegacyIntentionsForMigrationTestEnterprise(t, s1pre, ixns)
testLeader_LegacyIntentionMigrationHookEnterprise(t, s1pre, true)
var retained []*structs.Intention
for _, ixn := range ixns { for _, ixn := range ixns {
ixn2 := *ixn ixn2 := *ixn
resp, err := s1pre.raftApply(structs.IntentionRequestType, &structs.IntentionRequest{ resp, err := s1pre.raftApply(structs.IntentionRequestType, &structs.IntentionRequest{
@ -435,6 +440,10 @@ func TestLeader_LegacyIntentionMigration(t *testing.T) {
if respErr, ok := resp.(error); ok { if respErr, ok := resp.(error); ok {
t.Fatalf("respErr: %v", respErr) t.Fatalf("respErr: %v", respErr)
} }
if _, present := ixn.Meta["unit-test-discarded"]; !present {
retained = append(retained, ixn)
}
} }
mapify := func(ixns []*structs.Intention) map[string]*structs.Intention { mapify := func(ixns []*structs.Intention) map[string]*structs.Intention {
@ -465,7 +474,7 @@ func TestLeader_LegacyIntentionMigration(t *testing.T) {
for k, expectV := range expect { for k, expectV := range expect {
gotV, ok := gotM[k] gotV, ok := gotM[k]
if !ok { if !ok {
r.Errorf("results are missing key %q", k) r.Errorf("results are missing key %q: %v", k, expectV)
continue continue
} }
@ -483,8 +492,14 @@ func TestLeader_LegacyIntentionMigration(t *testing.T) {
} }
expectM := mapify(ixns) expectM := mapify(ixns)
checkIntentions(t, s1pre, false, expectM) expectRetainedM := mapify(retained)
checkIntentions(t, s1pre, true, expectM)
require.True(t, t.Run("check initial intentions", func(t *testing.T) {
checkIntentions(t, s1pre, false, expectM)
}))
require.True(t, t.Run("check initial legacy intentions", func(t *testing.T) {
checkIntentions(t, s1pre, true, expectM)
}))
// Shutdown s1pre and restart it to trigger migration. // Shutdown s1pre and restart it to trigger migration.
s1pre.Shutdown() s1pre.Shutdown()
@ -500,8 +515,7 @@ func TestLeader_LegacyIntentionMigration(t *testing.T) {
testrpc.WaitForLeader(t, s1.RPC, "dc1") testrpc.WaitForLeader(t, s1.RPC, "dc1")
// check that all 7 intentions are present before migration testLeader_LegacyIntentionMigrationHookEnterprise(t, s1, false)
checkIntentions(t, s1, false, expectM)
// Wait until the migration routine is complete. // Wait until the migration routine is complete.
retry.Run(t, func(r *retry.R) { retry.Run(t, func(r *retry.R) {
@ -513,9 +527,13 @@ func TestLeader_LegacyIntentionMigration(t *testing.T) {
}) })
// check that all 7 intentions are present the general way after migration // check that all 7 intentions are present the general way after migration
checkIntentions(t, s1, false, expectM) require.True(t, t.Run("check migrated intentions", func(t *testing.T) {
// check that no intentions exist in the legacy table checkIntentions(t, s1, false, expectRetainedM)
checkIntentions(t, s1, true, map[string]*structs.Intention{}) }))
require.True(t, t.Run("check migrated legacy intentions", func(t *testing.T) {
// check that no intentions exist in the legacy table
checkIntentions(t, s1, true, map[string]*structs.Intention{})
}))
mapifyConfigs := func(entries interface{}) map[structs.ConfigEntryKindName]*structs.ServiceIntentionsConfigEntry { mapifyConfigs := func(entries interface{}) map[structs.ConfigEntryKindName]*structs.ServiceIntentionsConfigEntry {
m := make(map[structs.ConfigEntryKindName]*structs.ServiceIntentionsConfigEntry) m := make(map[structs.ConfigEntryKindName]*structs.ServiceIntentionsConfigEntry)
@ -541,7 +559,7 @@ func TestLeader_LegacyIntentionMigration(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
gotConfigsM := mapifyConfigs(gotConfigs) gotConfigsM := mapifyConfigs(gotConfigs)
expectConfigs := structs.MigrateIntentions(ixns) expectConfigs := structs.MigrateIntentions(retained)
for _, entry := range expectConfigs { for _, entry := range expectConfigs {
require.NoError(t, entry.LegacyNormalize()) // tidy them up the same way the write would require.NoError(t, entry.LegacyNormalize()) // tidy them up the same way the write would
} }

View File

@ -12,8 +12,17 @@ import (
type LeaderRoutine func(ctx context.Context) error type LeaderRoutine func(ctx context.Context) error
type leaderRoutine struct { type leaderRoutine struct {
running bool cancel context.CancelFunc
cancel context.CancelFunc stoppedCh chan struct{} // closed when no longer running
}
func (r *leaderRoutine) running() bool {
select {
case <-r.stoppedCh:
return false
default:
return true
}
} }
type LeaderRoutineManager struct { type LeaderRoutineManager struct {
@ -41,7 +50,7 @@ func (m *LeaderRoutineManager) IsRunning(name string) bool {
defer m.lock.Unlock() defer m.lock.Unlock()
if routine, ok := m.routines[name]; ok { if routine, ok := m.routines[name]; ok {
return routine.running return routine.running()
} }
return false return false
@ -55,7 +64,7 @@ func (m *LeaderRoutineManager) StartWithContext(parentCtx context.Context, name
m.lock.Lock() m.lock.Lock()
defer m.lock.Unlock() defer m.lock.Unlock()
if instance, ok := m.routines[name]; ok && instance.running { if instance, ok := m.routines[name]; ok && instance.running() {
return nil return nil
} }
@ -65,11 +74,15 @@ func (m *LeaderRoutineManager) StartWithContext(parentCtx context.Context, name
ctx, cancel := context.WithCancel(parentCtx) ctx, cancel := context.WithCancel(parentCtx)
instance := &leaderRoutine{ instance := &leaderRoutine{
running: true, cancel: cancel,
cancel: cancel, stoppedCh: make(chan struct{}),
} }
go func() { go func() {
defer func() {
close(instance.stoppedCh)
}()
err := routine(ctx) err := routine(ctx)
if err != nil && err != context.DeadlineExceeded && err != context.Canceled { if err != nil && err != context.DeadlineExceeded && err != context.Canceled {
m.logger.Error("routine exited with error", m.logger.Error("routine exited with error",
@ -79,10 +92,6 @@ func (m *LeaderRoutineManager) StartWithContext(parentCtx context.Context, name
} else { } else {
m.logger.Debug("stopped routine", "routine", name) m.logger.Debug("stopped routine", "routine", name)
} }
m.lock.Lock()
instance.running = false
m.lock.Unlock()
}() }()
m.routines[name] = instance m.routines[name] = instance
@ -90,7 +99,19 @@ func (m *LeaderRoutineManager) StartWithContext(parentCtx context.Context, name
return nil return nil
} }
func (m *LeaderRoutineManager) Stop(name string) error { func (m *LeaderRoutineManager) Stop(name string) <-chan struct{} {
instance := m.stopInstance(name)
if instance == nil {
// Fabricate a closed channel so it won't block forever.
ch := make(chan struct{})
close(ch)
return ch
}
return instance.stoppedCh
}
func (m *LeaderRoutineManager) stopInstance(name string) *leaderRoutine {
m.lock.Lock() m.lock.Lock()
defer m.lock.Unlock() defer m.lock.Unlock()
@ -100,15 +121,16 @@ func (m *LeaderRoutineManager) Stop(name string) error {
return nil return nil
} }
if !instance.running { if !instance.running() {
return nil return instance
} }
m.logger.Debug("stopping routine", "routine", name) m.logger.Debug("stopping routine", "routine", name)
instance.cancel() instance.cancel()
delete(m.routines, name) delete(m.routines, name)
return nil
return instance
} }
func (m *LeaderRoutineManager) StopAll() { func (m *LeaderRoutineManager) StopAll() {
@ -116,7 +138,7 @@ func (m *LeaderRoutineManager) StopAll() {
defer m.lock.Unlock() defer m.lock.Unlock()
for name, routine := range m.routines { for name, routine := range m.routines {
if !routine.running { if !routine.running() {
continue continue
} }
m.logger.Debug("stopping routine", "routine", name) m.logger.Debug("stopping routine", "routine", name)

View File

@ -36,7 +36,9 @@ func TestLeaderRoutineManager(t *testing.T) {
require.Equal(r, uint32(1), atomic.LoadUint32(&runs)) require.Equal(r, uint32(1), atomic.LoadUint32(&runs))
require.Equal(r, uint32(1), atomic.LoadUint32(&running)) require.Equal(r, uint32(1), atomic.LoadUint32(&running))
}) })
require.NoError(t, mgr.Stop("run")) doneCh := mgr.Stop("run")
require.NotNil(t, doneCh)
<-doneCh
// ensure the background go routine was actually cancelled // ensure the background go routine was actually cancelled
retry.Run(t, func(r *retry.R) { retry.Run(t, func(r *retry.R) {
@ -51,7 +53,10 @@ func TestLeaderRoutineManager(t *testing.T) {
require.Equal(r, uint32(1), atomic.LoadUint32(&running)) require.Equal(r, uint32(1), atomic.LoadUint32(&running))
}) })
require.NoError(t, mgr.Stop("run")) doneCh = mgr.Stop("run")
require.NotNil(t, doneCh)
<-doneCh
retry.Run(t, func(r *retry.R) { retry.Run(t, func(r *retry.R) {
require.Equal(r, uint32(0), atomic.LoadUint32(&running)) require.Equal(r, uint32(0), atomic.LoadUint32(&running))
}) })

View File

@ -1230,63 +1230,133 @@ func TestLeader_ConfigEntryBootstrap(t *testing.T) {
func TestLeader_ConfigEntryBootstrap_Fail(t *testing.T) { func TestLeader_ConfigEntryBootstrap_Fail(t *testing.T) {
t.Parallel() t.Parallel()
pr, pw := io.Pipe() type testcase struct {
defer pw.Close() name string
entries []structs.ConfigEntry
serverCB func(c *Config)
expectMessage string
}
ch := make(chan string, 1) cases := []testcase{
go func() { {
defer pr.Close() name: "service-splitter without L7 protocol",
scan := bufio.NewScanner(pr) entries: []structs.ConfigEntry{
for scan.Scan() { &structs.ServiceSplitterConfigEntry{
line := scan.Text() Kind: structs.ServiceSplitter,
Name: "web",
if strings.Contains(line, "failed to establish leadership") { Splits: []structs.ServiceSplit{
ch <- "" {Weight: 100, Service: "web"},
return },
} },
if strings.Contains(line, "successfully established leadership") {
ch <- "leadership should not have gotten here if config entries properly failed"
return
}
}
if scan.Err() != nil {
ch <- fmt.Sprintf("ERROR: %v", scan.Err())
} else {
ch <- "should not get here"
}
}()
_, config := testServerConfig(t)
config.Build = "1.6.0"
config.ConfigEntryBootstrap = []structs.ConfigEntry{
&structs.ServiceSplitterConfigEntry{
Kind: structs.ServiceSplitter,
Name: "web",
Splits: []structs.ServiceSplit{
{Weight: 100, Service: "web"},
}, },
expectMessage: `Failed to apply configuration entry "service-splitter" / "web": discovery chain "web" uses a protocol "tcp" that does not permit advanced routing or splitting behavior"`,
},
{
name: "service-intentions without migration",
entries: []structs.ConfigEntry{
&structs.ServiceIntentionsConfigEntry{
Kind: structs.ServiceIntentions,
Name: "web",
Sources: []*structs.SourceIntention{
{
Name: "debug",
Action: structs.IntentionActionAllow,
},
},
},
},
serverCB: func(c *Config) {
c.OverrideInitialSerfTags = func(tags map[string]string) {
tags["ft_si"] = "0"
}
},
expectMessage: `Refusing to apply configuration entry "service-intentions" / "web" because intentions are still being migrated to config entries`,
},
{
name: "service-intentions without Connect",
entries: []structs.ConfigEntry{
&structs.ServiceIntentionsConfigEntry{
Kind: structs.ServiceIntentions,
Name: "web",
Sources: []*structs.SourceIntention{
{
Name: "debug",
Action: structs.IntentionActionAllow,
},
},
},
},
serverCB: func(c *Config) {
c.ConnectEnabled = false
},
expectMessage: `Refusing to apply configuration entry "service-intentions" / "web" because Connect must be enabled to bootstrap intentions"`,
}, },
} }
logger := hclog.NewInterceptLogger(&hclog.LoggerOptions{ for _, tc := range cases {
Name: config.NodeName, tc := tc
Level: hclog.Debug, t.Run(tc.name, func(t *testing.T) {
Output: io.MultiWriter(pw, testutil.NewLogBuffer(t)), pr, pw := io.Pipe()
}) defer pw.Close()
deps := newDefaultDeps(t, config) var (
deps.Logger = logger ch = make(chan string, 1)
applyErrorLine string
)
go func() {
defer pr.Close()
scan := bufio.NewScanner(pr)
for scan.Scan() {
line := scan.Text()
srv, err := NewServer(config, deps) if strings.Contains(line, "failed to establish leadership") {
require.NoError(t, err) applyErrorLine = line
defer srv.Shutdown() ch <- ""
return
}
if strings.Contains(line, "successfully established leadership") {
ch <- "leadership should not have gotten here if config entries properly failed"
return
}
}
select { if scan.Err() != nil {
case result := <-ch: ch <- fmt.Sprintf("ERROR: %v", scan.Err())
require.Empty(t, result) } else {
case <-time.After(time.Second): ch <- "should not get here"
t.Fatal("timeout waiting for a result from tailing logs") }
}()
_, config := testServerConfig(t)
config.Build = "1.6.0"
config.ConfigEntryBootstrap = tc.entries
if tc.serverCB != nil {
tc.serverCB(config)
}
logger := hclog.NewInterceptLogger(&hclog.LoggerOptions{
Name: config.NodeName,
Level: hclog.Debug,
Output: io.MultiWriter(pw, testutil.NewLogBuffer(t)),
})
deps := newDefaultDeps(t, config)
deps.Logger = logger
srv, err := NewServer(config, deps)
require.NoError(t, err)
defer srv.Shutdown()
select {
case result := <-ch:
require.Empty(t, result)
if tc.expectMessage != "" {
require.Contains(t, applyErrorLine, tc.expectMessage)
}
case <-time.After(time.Second):
t.Fatal("timeout waiting for a result from tailing logs")
}
})
} }
} }

View File

@ -11,7 +11,7 @@ func init() {
registerEndpoint(func(s *Server) interface{} { return &FederationState{s} }) registerEndpoint(func(s *Server) interface{} { return &FederationState{s} })
registerEndpoint(func(s *Server) interface{} { return &DiscoveryChain{s} }) registerEndpoint(func(s *Server) interface{} { return &DiscoveryChain{s} })
registerEndpoint(func(s *Server) interface{} { return &Health{s} }) registerEndpoint(func(s *Server) interface{} { return &Health{s} })
registerEndpoint(func(s *Server) interface{} { return NewIntentionEndpoint(s, s.loggers.Named(logging.Intentions)) }) registerEndpoint(func(s *Server) interface{} { return &Intention{s, s.loggers.Named(logging.Intentions)} })
registerEndpoint(func(s *Server) interface{} { return &Internal{s, s.loggers.Named(logging.Internal)} }) registerEndpoint(func(s *Server) interface{} { return &Internal{s, s.loggers.Named(logging.Internal)} })
registerEndpoint(func(s *Server) interface{} { return &KVS{s, s.loggers.Named(logging.KV)} }) registerEndpoint(func(s *Server) interface{} { return &KVS{s, s.loggers.Named(logging.KV)} })
registerEndpoint(func(s *Server) interface{} { return &Operator{s, s.loggers.Named(logging.Operator)} }) registerEndpoint(func(s *Server) interface{} { return &Operator{s, s.loggers.Named(logging.Operator)} })

View File

@ -925,7 +925,7 @@ func TestSession_Apply_BadTTL(t *testing.T) {
if err == nil { if err == nil {
t.Fatal("expected error") t.Fatal("expected error")
} }
if err.Error() != "Session TTL '10z' invalid: time: unknown unit z in duration 10z" { if err.Error() != `Session TTL '10z' invalid: time: unknown unit "z" in duration "10z"` {
t.Fatalf("incorrect error message: %s", err.Error()) t.Fatalf("incorrect error message: %s", err.Error())
} }

View File

@ -263,7 +263,7 @@ func (s *Snapshot) Checks(node string) (memdb.ResultIterator, error) {
// performed within a single transaction to avoid race conditions on state // performed within a single transaction to avoid race conditions on state
// updates. // updates.
func (s *Restore) Registration(idx uint64, req *structs.RegisterRequest) error { func (s *Restore) Registration(idx uint64, req *structs.RegisterRequest) error {
return s.store.ensureRegistrationTxn(s.tx, idx, true, req) return s.store.ensureRegistrationTxn(s.tx, idx, true, req, true)
} }
// EnsureRegistration is used to make sure a node, service, and check // EnsureRegistration is used to make sure a node, service, and check
@ -273,7 +273,7 @@ func (s *Store) EnsureRegistration(idx uint64, req *structs.RegisterRequest) err
tx := s.db.WriteTxn(idx) tx := s.db.WriteTxn(idx)
defer tx.Abort() defer tx.Abort()
if err := s.ensureRegistrationTxn(tx, idx, false, req); err != nil { if err := s.ensureRegistrationTxn(tx, idx, false, req, false); err != nil {
return err return err
} }
@ -294,8 +294,8 @@ func (s *Store) ensureCheckIfNodeMatches(tx WriteTxn, idx uint64, preserveIndexe
// ensureRegistrationTxn is used to make sure a node, service, and check // ensureRegistrationTxn is used to make sure a node, service, and check
// registration is performed within a single transaction to avoid race // registration is performed within a single transaction to avoid race
// conditions on state updates. // conditions on state updates.
func (s *Store) ensureRegistrationTxn(tx WriteTxn, idx uint64, preserveIndexes bool, req *structs.RegisterRequest) error { func (s *Store) ensureRegistrationTxn(tx WriteTxn, idx uint64, preserveIndexes bool, req *structs.RegisterRequest, restore bool) error {
if _, err := validateRegisterRequestTxn(tx, req); err != nil { if _, err := validateRegisterRequestTxn(tx, req, restore); err != nil {
return err return err
} }

View File

@ -40,7 +40,7 @@ func TestServiceHealthEventsFromChanges(t *testing.T) {
Name: "service reg, new node", Name: "service reg, new node",
Mutate: func(s *Store, tx *txn) error { Mutate: func(s *Store, tx *txn) error {
return s.ensureRegistrationTxn(tx, tx.Index, false, return s.ensureRegistrationTxn(tx, tx.Index, false,
testServiceRegistration(t, "web")) testServiceRegistration(t, "web"), false)
}, },
WantEvents: []stream.Event{ WantEvents: []stream.Event{
testServiceHealthEvent(t, "web"), testServiceHealthEvent(t, "web"),
@ -51,11 +51,11 @@ func TestServiceHealthEventsFromChanges(t *testing.T) {
Name: "service reg, existing node", Name: "service reg, existing node",
Setup: func(s *Store, tx *txn) error { Setup: func(s *Store, tx *txn) error {
return s.ensureRegistrationTxn(tx, tx.Index, false, return s.ensureRegistrationTxn(tx, tx.Index, false,
testServiceRegistration(t, "db")) testServiceRegistration(t, "db"), false)
}, },
Mutate: func(s *Store, tx *txn) error { Mutate: func(s *Store, tx *txn) error {
return s.ensureRegistrationTxn(tx, tx.Index, false, return s.ensureRegistrationTxn(tx, tx.Index, false,
testServiceRegistration(t, "web")) testServiceRegistration(t, "web"), false)
}, },
WantEvents: []stream.Event{ WantEvents: []stream.Event{
// Should only publish new service // Should only publish new service
@ -67,11 +67,11 @@ func TestServiceHealthEventsFromChanges(t *testing.T) {
Name: "service dereg, existing node", Name: "service dereg, existing node",
Setup: func(s *Store, tx *txn) error { Setup: func(s *Store, tx *txn) error {
if err := s.ensureRegistrationTxn(tx, tx.Index, false, if err := s.ensureRegistrationTxn(tx, tx.Index, false,
testServiceRegistration(t, "db")); err != nil { testServiceRegistration(t, "db"), false); err != nil {
return err return err
} }
if err := s.ensureRegistrationTxn(tx, tx.Index, false, if err := s.ensureRegistrationTxn(tx, tx.Index, false,
testServiceRegistration(t, "web")); err != nil { testServiceRegistration(t, "web"), false); err != nil {
return err return err
} }
return nil return nil
@ -88,10 +88,10 @@ func TestServiceHealthEventsFromChanges(t *testing.T) {
{ {
Name: "node dereg", Name: "node dereg",
Setup: func(s *Store, tx *txn) error { Setup: func(s *Store, tx *txn) error {
if err := s.ensureRegistrationTxn(tx, tx.Index, false, testServiceRegistration(t, "db")); err != nil { if err := s.ensureRegistrationTxn(tx, tx.Index, false, testServiceRegistration(t, "db"), false); err != nil {
return err return err
} }
if err := s.ensureRegistrationTxn(tx, tx.Index, false, testServiceRegistration(t, "web")); err != nil { if err := s.ensureRegistrationTxn(tx, tx.Index, false, testServiceRegistration(t, "web"), false); err != nil {
return err return err
} }
return nil return nil
@ -109,7 +109,7 @@ func TestServiceHealthEventsFromChanges(t *testing.T) {
{ {
Name: "connect native reg, new node", Name: "connect native reg, new node",
Mutate: func(s *Store, tx *txn) error { Mutate: func(s *Store, tx *txn) error {
return s.ensureRegistrationTxn(tx, tx.Index, false, testServiceRegistration(t, "web", regConnectNative)) return s.ensureRegistrationTxn(tx, tx.Index, false, testServiceRegistration(t, "web", regConnectNative), false)
}, },
WantEvents: []stream.Event{ WantEvents: []stream.Event{
// We should see both a regular service health event as well as a connect // We should see both a regular service health event as well as a connect
@ -122,10 +122,10 @@ func TestServiceHealthEventsFromChanges(t *testing.T) {
{ {
Name: "connect native reg, existing node", Name: "connect native reg, existing node",
Setup: func(s *Store, tx *txn) error { Setup: func(s *Store, tx *txn) error {
return s.ensureRegistrationTxn(tx, tx.Index, false, testServiceRegistration(t, "db")) return s.ensureRegistrationTxn(tx, tx.Index, false, testServiceRegistration(t, "db"), false)
}, },
Mutate: func(s *Store, tx *txn) error { Mutate: func(s *Store, tx *txn) error {
return s.ensureRegistrationTxn(tx, tx.Index, false, testServiceRegistration(t, "web", regConnectNative)) return s.ensureRegistrationTxn(tx, tx.Index, false, testServiceRegistration(t, "web", regConnectNative), false)
}, },
WantEvents: []stream.Event{ WantEvents: []stream.Event{
// We should see both a regular service health event as well as a connect // We should see both a regular service health event as well as a connect
@ -143,11 +143,11 @@ func TestServiceHealthEventsFromChanges(t *testing.T) {
{ {
Name: "connect native dereg, existing node", Name: "connect native dereg, existing node",
Setup: func(s *Store, tx *txn) error { Setup: func(s *Store, tx *txn) error {
if err := s.ensureRegistrationTxn(tx, tx.Index, false, testServiceRegistration(t, "db")); err != nil { if err := s.ensureRegistrationTxn(tx, tx.Index, false, testServiceRegistration(t, "db"), false); err != nil {
return err return err
} }
return s.ensureRegistrationTxn(tx, tx.Index, false, testServiceRegistration(t, "web", regConnectNative)) return s.ensureRegistrationTxn(tx, tx.Index, false, testServiceRegistration(t, "web", regConnectNative), false)
}, },
Mutate: func(s *Store, tx *txn) error { Mutate: func(s *Store, tx *txn) error {
return s.deleteServiceTxn(tx, tx.Index, "node1", "web", nil) return s.deleteServiceTxn(tx, tx.Index, "node1", "web", nil)
@ -162,10 +162,10 @@ func TestServiceHealthEventsFromChanges(t *testing.T) {
{ {
Name: "connect sidecar reg, new node", Name: "connect sidecar reg, new node",
Mutate: func(s *Store, tx *txn) error { Mutate: func(s *Store, tx *txn) error {
if err := s.ensureRegistrationTxn(tx, tx.Index, false, testServiceRegistration(t, "web")); err != nil { if err := s.ensureRegistrationTxn(tx, tx.Index, false, testServiceRegistration(t, "web"), false); err != nil {
return err return err
} }
return s.ensureRegistrationTxn(tx, tx.Index, false, testServiceRegistration(t, "web", regSidecar)) return s.ensureRegistrationTxn(tx, tx.Index, false, testServiceRegistration(t, "web", regSidecar), false)
}, },
WantEvents: []stream.Event{ WantEvents: []stream.Event{
// We should see both a regular service health event for the web service // We should see both a regular service health event for the web service
@ -180,15 +180,15 @@ func TestServiceHealthEventsFromChanges(t *testing.T) {
Name: "connect sidecar reg, existing node", Name: "connect sidecar reg, existing node",
Setup: func(s *Store, tx *txn) error { Setup: func(s *Store, tx *txn) error {
if err := s.ensureRegistrationTxn(tx, tx.Index, false, if err := s.ensureRegistrationTxn(tx, tx.Index, false,
testServiceRegistration(t, "db")); err != nil { testServiceRegistration(t, "db"), false); err != nil {
return err return err
} }
return s.ensureRegistrationTxn(tx, tx.Index, false, return s.ensureRegistrationTxn(tx, tx.Index, false,
testServiceRegistration(t, "web")) testServiceRegistration(t, "web"), false)
}, },
Mutate: func(s *Store, tx *txn) error { Mutate: func(s *Store, tx *txn) error {
return s.ensureRegistrationTxn(tx, tx.Index, false, return s.ensureRegistrationTxn(tx, tx.Index, false,
testServiceRegistration(t, "web", regSidecar)) testServiceRegistration(t, "web", regSidecar), false)
}, },
WantEvents: []stream.Event{ WantEvents: []stream.Event{
// We should see both a regular service health event for the proxy // We should see both a regular service health event for the proxy
@ -202,15 +202,15 @@ func TestServiceHealthEventsFromChanges(t *testing.T) {
Name: "connect sidecar dereg, existing node", Name: "connect sidecar dereg, existing node",
Setup: func(s *Store, tx *txn) error { Setup: func(s *Store, tx *txn) error {
if err := s.ensureRegistrationTxn(tx, tx.Index, false, if err := s.ensureRegistrationTxn(tx, tx.Index, false,
testServiceRegistration(t, "db")); err != nil { testServiceRegistration(t, "db"), false); err != nil {
return err return err
} }
if err := s.ensureRegistrationTxn(tx, tx.Index, false, if err := s.ensureRegistrationTxn(tx, tx.Index, false,
testServiceRegistration(t, "web")); err != nil { testServiceRegistration(t, "web"), false); err != nil {
return err return err
} }
return s.ensureRegistrationTxn(tx, tx.Index, false, return s.ensureRegistrationTxn(tx, tx.Index, false,
testServiceRegistration(t, "web", regSidecar)) testServiceRegistration(t, "web", regSidecar), false)
}, },
Mutate: func(s *Store, tx *txn) error { Mutate: func(s *Store, tx *txn) error {
// Delete only the sidecar // Delete only the sidecar
@ -227,20 +227,20 @@ func TestServiceHealthEventsFromChanges(t *testing.T) {
Name: "connect sidecar mutate svc", Name: "connect sidecar mutate svc",
Setup: func(s *Store, tx *txn) error { Setup: func(s *Store, tx *txn) error {
if err := s.ensureRegistrationTxn(tx, tx.Index, false, if err := s.ensureRegistrationTxn(tx, tx.Index, false,
testServiceRegistration(t, "db")); err != nil { testServiceRegistration(t, "db"), false); err != nil {
return err return err
} }
if err := s.ensureRegistrationTxn(tx, tx.Index, false, if err := s.ensureRegistrationTxn(tx, tx.Index, false,
testServiceRegistration(t, "web")); err != nil { testServiceRegistration(t, "web"), false); err != nil {
return err return err
} }
return s.ensureRegistrationTxn(tx, tx.Index, false, return s.ensureRegistrationTxn(tx, tx.Index, false,
testServiceRegistration(t, "web", regSidecar)) testServiceRegistration(t, "web", regSidecar), false)
}, },
Mutate: func(s *Store, tx *txn) error { Mutate: func(s *Store, tx *txn) error {
// Change port of the target service instance // Change port of the target service instance
return s.ensureRegistrationTxn(tx, tx.Index, false, return s.ensureRegistrationTxn(tx, tx.Index, false,
testServiceRegistration(t, "web", regMutatePort)) testServiceRegistration(t, "web", regMutatePort), false)
}, },
WantEvents: []stream.Event{ WantEvents: []stream.Event{
// We should see the service topic update but not connect since proxy // We should see the service topic update but not connect since proxy
@ -258,20 +258,20 @@ func TestServiceHealthEventsFromChanges(t *testing.T) {
Name: "connect sidecar mutate sidecar", Name: "connect sidecar mutate sidecar",
Setup: func(s *Store, tx *txn) error { Setup: func(s *Store, tx *txn) error {
if err := s.ensureRegistrationTxn(tx, tx.Index, false, if err := s.ensureRegistrationTxn(tx, tx.Index, false,
testServiceRegistration(t, "db")); err != nil { testServiceRegistration(t, "db"), false); err != nil {
return err return err
} }
if err := s.ensureRegistrationTxn(tx, tx.Index, false, if err := s.ensureRegistrationTxn(tx, tx.Index, false,
testServiceRegistration(t, "web")); err != nil { testServiceRegistration(t, "web"), false); err != nil {
return err return err
} }
return s.ensureRegistrationTxn(tx, tx.Index, false, return s.ensureRegistrationTxn(tx, tx.Index, false,
testServiceRegistration(t, "web", regSidecar)) testServiceRegistration(t, "web", regSidecar), false)
}, },
Mutate: func(s *Store, tx *txn) error { Mutate: func(s *Store, tx *txn) error {
// Change port of the sidecar service instance // Change port of the sidecar service instance
return s.ensureRegistrationTxn(tx, tx.Index, false, return s.ensureRegistrationTxn(tx, tx.Index, false,
testServiceRegistration(t, "web", regSidecar, regMutatePort)) testServiceRegistration(t, "web", regSidecar, regMutatePort), false)
}, },
WantEvents: []stream.Event{ WantEvents: []stream.Event{
// We should see the proxy service topic update and a connect update // We should see the proxy service topic update and a connect update
@ -295,24 +295,24 @@ func TestServiceHealthEventsFromChanges(t *testing.T) {
Name: "connect sidecar rename service", Name: "connect sidecar rename service",
Setup: func(s *Store, tx *txn) error { Setup: func(s *Store, tx *txn) error {
if err := s.ensureRegistrationTxn(tx, tx.Index, false, if err := s.ensureRegistrationTxn(tx, tx.Index, false,
testServiceRegistration(t, "db")); err != nil { testServiceRegistration(t, "db"), false); err != nil {
return err return err
} }
if err := s.ensureRegistrationTxn(tx, tx.Index, false, if err := s.ensureRegistrationTxn(tx, tx.Index, false,
testServiceRegistration(t, "web")); err != nil { testServiceRegistration(t, "web"), false); err != nil {
return err return err
} }
return s.ensureRegistrationTxn(tx, tx.Index, false, return s.ensureRegistrationTxn(tx, tx.Index, false,
testServiceRegistration(t, "web", regSidecar)) testServiceRegistration(t, "web", regSidecar), false)
}, },
Mutate: func(s *Store, tx *txn) error { Mutate: func(s *Store, tx *txn) error {
// Change service name but not ID, update proxy too // Change service name but not ID, update proxy too
if err := s.ensureRegistrationTxn(tx, tx.Index, false, if err := s.ensureRegistrationTxn(tx, tx.Index, false,
testServiceRegistration(t, "web", regRenameService)); err != nil { testServiceRegistration(t, "web", regRenameService), false); err != nil {
return err return err
} }
return s.ensureRegistrationTxn(tx, tx.Index, false, return s.ensureRegistrationTxn(tx, tx.Index, false,
testServiceRegistration(t, "web", regSidecar, regRenameService)) testServiceRegistration(t, "web", regSidecar, regRenameService), false)
}, },
WantEvents: []stream.Event{ WantEvents: []stream.Event{
// We should see events to deregister the old service instance and the // We should see events to deregister the old service instance and the
@ -353,25 +353,25 @@ func TestServiceHealthEventsFromChanges(t *testing.T) {
Setup: func(s *Store, tx *txn) error { Setup: func(s *Store, tx *txn) error {
// Register a web_changed service // Register a web_changed service
if err := s.ensureRegistrationTxn(tx, tx.Index, false, if err := s.ensureRegistrationTxn(tx, tx.Index, false,
testServiceRegistration(t, "web_changed")); err != nil { testServiceRegistration(t, "web_changed"), false); err != nil {
return err return err
} }
// Also a web // Also a web
if err := s.ensureRegistrationTxn(tx, tx.Index, false, if err := s.ensureRegistrationTxn(tx, tx.Index, false,
testServiceRegistration(t, "web")); err != nil { testServiceRegistration(t, "web"), false); err != nil {
return err return err
} }
// And a sidecar initially for web, will be moved to target web_changed // And a sidecar initially for web, will be moved to target web_changed
// in Mutate. // in Mutate.
return s.ensureRegistrationTxn(tx, tx.Index, false, return s.ensureRegistrationTxn(tx, tx.Index, false,
testServiceRegistration(t, "web", regSidecar)) testServiceRegistration(t, "web", regSidecar), false)
}, },
Mutate: func(s *Store, tx *txn) error { Mutate: func(s *Store, tx *txn) error {
// Change only the destination service of the proxy without a service // Change only the destination service of the proxy without a service
// rename or deleting and recreating the proxy. This is far fetched but // rename or deleting and recreating the proxy. This is far fetched but
// still valid. // still valid.
return s.ensureRegistrationTxn(tx, tx.Index, false, return s.ensureRegistrationTxn(tx, tx.Index, false,
testServiceRegistration(t, "web", regSidecar, regRenameService)) testServiceRegistration(t, "web", regSidecar, regRenameService), false)
}, },
WantEvents: []stream.Event{ WantEvents: []stream.Event{
// We should only see service health events for the sidecar service // We should only see service health events for the sidecar service
@ -405,17 +405,17 @@ func TestServiceHealthEventsFromChanges(t *testing.T) {
Setup: func(s *Store, tx *txn) error { Setup: func(s *Store, tx *txn) error {
// Register a db service // Register a db service
if err := s.ensureRegistrationTxn(tx, tx.Index, false, if err := s.ensureRegistrationTxn(tx, tx.Index, false,
testServiceRegistration(t, "db")); err != nil { testServiceRegistration(t, "db"), false); err != nil {
return err return err
} }
// Also a web // Also a web
if err := s.ensureRegistrationTxn(tx, tx.Index, false, if err := s.ensureRegistrationTxn(tx, tx.Index, false,
testServiceRegistration(t, "web")); err != nil { testServiceRegistration(t, "web"), false); err != nil {
return err return err
} }
// With a connect sidecar // With a connect sidecar
if err := s.ensureRegistrationTxn(tx, tx.Index, false, if err := s.ensureRegistrationTxn(tx, tx.Index, false,
testServiceRegistration(t, "web", regSidecar)); err != nil { testServiceRegistration(t, "web", regSidecar), false); err != nil {
return err return err
} }
return nil return nil
@ -423,7 +423,7 @@ func TestServiceHealthEventsFromChanges(t *testing.T) {
Mutate: func(s *Store, tx *txn) error { Mutate: func(s *Store, tx *txn) error {
// Change only the node meta. // Change only the node meta.
return s.ensureRegistrationTxn(tx, tx.Index, false, return s.ensureRegistrationTxn(tx, tx.Index, false,
testNodeRegistration(t, regNodeMeta)) testNodeRegistration(t, regNodeMeta), false)
}, },
WantEvents: []stream.Event{ WantEvents: []stream.Event{
// We should see updates for all services and a connect update for the // We should see updates for all services and a connect update for the
@ -463,17 +463,17 @@ func TestServiceHealthEventsFromChanges(t *testing.T) {
Setup: func(s *Store, tx *txn) error { Setup: func(s *Store, tx *txn) error {
// Register a db service // Register a db service
if err := s.ensureRegistrationTxn(tx, tx.Index, false, if err := s.ensureRegistrationTxn(tx, tx.Index, false,
testServiceRegistration(t, "db")); err != nil { testServiceRegistration(t, "db"), false); err != nil {
return err return err
} }
// Also a web // Also a web
if err := s.ensureRegistrationTxn(tx, tx.Index, false, if err := s.ensureRegistrationTxn(tx, tx.Index, false,
testServiceRegistration(t, "web")); err != nil { testServiceRegistration(t, "web"), false); err != nil {
return err return err
} }
// With a connect sidecar // With a connect sidecar
if err := s.ensureRegistrationTxn(tx, tx.Index, false, if err := s.ensureRegistrationTxn(tx, tx.Index, false,
testServiceRegistration(t, "web", regSidecar)); err != nil { testServiceRegistration(t, "web", regSidecar), false); err != nil {
return err return err
} }
return nil return nil
@ -485,17 +485,17 @@ func TestServiceHealthEventsFromChanges(t *testing.T) {
// services registered afterwards. // services registered afterwards.
// Register a db service // Register a db service
if err := s.ensureRegistrationTxn(tx, tx.Index, false, if err := s.ensureRegistrationTxn(tx, tx.Index, false,
testServiceRegistration(t, "db", regRenameNode)); err != nil { testServiceRegistration(t, "db", regRenameNode), false); err != nil {
return err return err
} }
// Also a web // Also a web
if err := s.ensureRegistrationTxn(tx, tx.Index, false, if err := s.ensureRegistrationTxn(tx, tx.Index, false,
testServiceRegistration(t, "web", regRenameNode)); err != nil { testServiceRegistration(t, "web", regRenameNode), false); err != nil {
return err return err
} }
// With a connect sidecar // With a connect sidecar
if err := s.ensureRegistrationTxn(tx, tx.Index, false, if err := s.ensureRegistrationTxn(tx, tx.Index, false,
testServiceRegistration(t, "web", regSidecar, regRenameNode)); err != nil { testServiceRegistration(t, "web", regSidecar, regRenameNode), false); err != nil {
return err return err
} }
return nil return nil
@ -540,17 +540,17 @@ func TestServiceHealthEventsFromChanges(t *testing.T) {
Setup: func(s *Store, tx *txn) error { Setup: func(s *Store, tx *txn) error {
// Register a db service // Register a db service
if err := s.ensureRegistrationTxn(tx, tx.Index, false, if err := s.ensureRegistrationTxn(tx, tx.Index, false,
testServiceRegistration(t, "db")); err != nil { testServiceRegistration(t, "db"), false); err != nil {
return err return err
} }
// Also a web // Also a web
if err := s.ensureRegistrationTxn(tx, tx.Index, false, if err := s.ensureRegistrationTxn(tx, tx.Index, false,
testServiceRegistration(t, "web")); err != nil { testServiceRegistration(t, "web"), false); err != nil {
return err return err
} }
// With a connect sidecar // With a connect sidecar
if err := s.ensureRegistrationTxn(tx, tx.Index, false, if err := s.ensureRegistrationTxn(tx, tx.Index, false,
testServiceRegistration(t, "web", regSidecar)); err != nil { testServiceRegistration(t, "web", regSidecar), false); err != nil {
return err return err
} }
return nil return nil
@ -558,7 +558,7 @@ func TestServiceHealthEventsFromChanges(t *testing.T) {
Mutate: func(s *Store, tx *txn) error { Mutate: func(s *Store, tx *txn) error {
// Change only the node-level check status // Change only the node-level check status
if err := s.ensureRegistrationTxn(tx, tx.Index, false, if err := s.ensureRegistrationTxn(tx, tx.Index, false,
testServiceRegistration(t, "web", regNodeCheckFail)); err != nil { testServiceRegistration(t, "web", regNodeCheckFail), false); err != nil {
return err return err
} }
return nil return nil
@ -600,17 +600,17 @@ func TestServiceHealthEventsFromChanges(t *testing.T) {
Setup: func(s *Store, tx *txn) error { Setup: func(s *Store, tx *txn) error {
// Register a db service // Register a db service
if err := s.ensureRegistrationTxn(tx, tx.Index, false, if err := s.ensureRegistrationTxn(tx, tx.Index, false,
testServiceRegistration(t, "db")); err != nil { testServiceRegistration(t, "db"), false); err != nil {
return err return err
} }
// Also a web // Also a web
if err := s.ensureRegistrationTxn(tx, tx.Index, false, if err := s.ensureRegistrationTxn(tx, tx.Index, false,
testServiceRegistration(t, "web")); err != nil { testServiceRegistration(t, "web"), false); err != nil {
return err return err
} }
// With a connect sidecar // With a connect sidecar
if err := s.ensureRegistrationTxn(tx, tx.Index, false, if err := s.ensureRegistrationTxn(tx, tx.Index, false,
testServiceRegistration(t, "web", regSidecar)); err != nil { testServiceRegistration(t, "web", regSidecar), false); err != nil {
return err return err
} }
return nil return nil
@ -618,7 +618,7 @@ func TestServiceHealthEventsFromChanges(t *testing.T) {
Mutate: func(s *Store, tx *txn) error { Mutate: func(s *Store, tx *txn) error {
// Change the service-level check status // Change the service-level check status
if err := s.ensureRegistrationTxn(tx, tx.Index, false, if err := s.ensureRegistrationTxn(tx, tx.Index, false,
testServiceRegistration(t, "web", regServiceCheckFail)); err != nil { testServiceRegistration(t, "web", regServiceCheckFail), false); err != nil {
return err return err
} }
// Also change the service-level check status for the proxy. This is // Also change the service-level check status for the proxy. This is
@ -626,7 +626,7 @@ func TestServiceHealthEventsFromChanges(t *testing.T) {
// - the proxies check would get updated at roughly the same time as the // - the proxies check would get updated at roughly the same time as the
// target service check updates. // target service check updates.
if err := s.ensureRegistrationTxn(tx, tx.Index, false, if err := s.ensureRegistrationTxn(tx, tx.Index, false,
testServiceRegistration(t, "web", regSidecar, regServiceCheckFail)); err != nil { testServiceRegistration(t, "web", regSidecar, regServiceCheckFail), false); err != nil {
return err return err
} }
return nil return nil
@ -663,17 +663,17 @@ func TestServiceHealthEventsFromChanges(t *testing.T) {
Setup: func(s *Store, tx *txn) error { Setup: func(s *Store, tx *txn) error {
// Register a db service // Register a db service
if err := s.ensureRegistrationTxn(tx, tx.Index, false, if err := s.ensureRegistrationTxn(tx, tx.Index, false,
testServiceRegistration(t, "db")); err != nil { testServiceRegistration(t, "db"), false); err != nil {
return err return err
} }
// Also a web // Also a web
if err := s.ensureRegistrationTxn(tx, tx.Index, false, if err := s.ensureRegistrationTxn(tx, tx.Index, false,
testServiceRegistration(t, "web")); err != nil { testServiceRegistration(t, "web"), false); err != nil {
return err return err
} }
// With a connect sidecar // With a connect sidecar
if err := s.ensureRegistrationTxn(tx, tx.Index, false, if err := s.ensureRegistrationTxn(tx, tx.Index, false,
testServiceRegistration(t, "web", regSidecar)); err != nil { testServiceRegistration(t, "web", regSidecar), false); err != nil {
return err return err
} }
return nil return nil
@ -717,17 +717,17 @@ func TestServiceHealthEventsFromChanges(t *testing.T) {
Setup: func(s *Store, tx *txn) error { Setup: func(s *Store, tx *txn) error {
// Register a db service // Register a db service
if err := s.ensureRegistrationTxn(tx, tx.Index, false, if err := s.ensureRegistrationTxn(tx, tx.Index, false,
testServiceRegistration(t, "db")); err != nil { testServiceRegistration(t, "db"), false); err != nil {
return err return err
} }
// Also a web // Also a web
if err := s.ensureRegistrationTxn(tx, tx.Index, false, if err := s.ensureRegistrationTxn(tx, tx.Index, false,
testServiceRegistration(t, "web")); err != nil { testServiceRegistration(t, "web"), false); err != nil {
return err return err
} }
// With a connect sidecar // With a connect sidecar
if err := s.ensureRegistrationTxn(tx, tx.Index, false, if err := s.ensureRegistrationTxn(tx, tx.Index, false,
testServiceRegistration(t, "web", regSidecar)); err != nil { testServiceRegistration(t, "web", regSidecar), false); err != nil {
return err return err
} }
return nil return nil
@ -777,19 +777,19 @@ func TestServiceHealthEventsFromChanges(t *testing.T) {
// Register a db service // Register a db service
if err := s.ensureRegistrationTxn(tx, tx.Index, false, if err := s.ensureRegistrationTxn(tx, tx.Index, false,
testServiceRegistration(t, "db")); err != nil { testServiceRegistration(t, "db"), false); err != nil {
return err return err
} }
// Node2 // Node2
// Also a web // Also a web
if err := s.ensureRegistrationTxn(tx, tx.Index, false, if err := s.ensureRegistrationTxn(tx, tx.Index, false,
testServiceRegistration(t, "web", regNode2)); err != nil { testServiceRegistration(t, "web", regNode2), false); err != nil {
return err return err
} }
// With a connect sidecar // With a connect sidecar
if err := s.ensureRegistrationTxn(tx, tx.Index, false, if err := s.ensureRegistrationTxn(tx, tx.Index, false,
testServiceRegistration(t, "web", regSidecar, regNode2)); err != nil { testServiceRegistration(t, "web", regSidecar, regNode2), false); err != nil {
return err return err
} }
@ -808,17 +808,17 @@ func TestServiceHealthEventsFromChanges(t *testing.T) {
// Register those on node1 // Register those on node1
if err := s.ensureRegistrationTxn(tx, tx.Index, false, if err := s.ensureRegistrationTxn(tx, tx.Index, false,
testServiceRegistration(t, "web")); err != nil { testServiceRegistration(t, "web"), false); err != nil {
return err return err
} }
if err := s.ensureRegistrationTxn(tx, tx.Index, false, if err := s.ensureRegistrationTxn(tx, tx.Index, false,
testServiceRegistration(t, "web", regSidecar)); err != nil { testServiceRegistration(t, "web", regSidecar), false); err != nil {
return err return err
} }
// And for good measure, add a new connect-native service to node2 // And for good measure, add a new connect-native service to node2
if err := s.ensureRegistrationTxn(tx, tx.Index, false, if err := s.ensureRegistrationTxn(tx, tx.Index, false,
testServiceRegistration(t, "api", regConnectNative, regNode2)); err != nil { testServiceRegistration(t, "api", regConnectNative, regNode2), false); err != nil {
return err return err
} }

View File

@ -323,7 +323,7 @@ func catalogChecksForNodeService(tx ReadTxn, node string, service string, entMet
return tx.Get("checks", "node_service", node, service) return tx.Get("checks", "node_service", node, service)
} }
func validateRegisterRequestTxn(_ ReadTxn, _ *structs.RegisterRequest) (*structs.EnterpriseMeta, error) { func validateRegisterRequestTxn(_ ReadTxn, _ *structs.RegisterRequest, _ bool) (*structs.EnterpriseMeta, error) {
return nil, nil return nil, nil
} }

View File

@ -180,7 +180,7 @@ func (s *Store) EnsureConfigEntry(idx uint64, conf structs.ConfigEntry, entMeta
} }
// ensureConfigEntryTxn upserts a config entry inside of a transaction. // ensureConfigEntryTxn upserts a config entry inside of a transaction.
func ensureConfigEntryTxn(tx *txn, idx uint64, conf structs.ConfigEntry, entMeta *structs.EnterpriseMeta) error { func ensureConfigEntryTxn(tx WriteTxn, idx uint64, conf structs.ConfigEntry, entMeta *structs.EnterpriseMeta) error {
// Check for existing configuration. // Check for existing configuration.
existing, err := firstConfigEntryWithTxn(tx, conf.GetKind(), conf.GetName(), entMeta) existing, err := firstConfigEntryWithTxn(tx, conf.GetKind(), conf.GetName(), entMeta)
if err != nil { if err != nil {
@ -254,6 +254,14 @@ func (s *Store) DeleteConfigEntry(idx uint64, kind, name string, entMeta *struct
tx := s.db.WriteTxn(idx) tx := s.db.WriteTxn(idx)
defer tx.Abort() defer tx.Abort()
if err := deleteConfigEntryTxn(tx, idx, kind, name, entMeta); err != nil {
return err
}
return tx.Commit()
}
func deleteConfigEntryTxn(tx WriteTxn, idx uint64, kind, name string, entMeta *structs.EnterpriseMeta) error {
// Try to retrieve the existing config entry. // Try to retrieve the existing config entry.
existing, err := firstConfigEntryWithTxn(tx, kind, name, entMeta) existing, err := firstConfigEntryWithTxn(tx, kind, name, entMeta)
if err != nil { if err != nil {
@ -298,10 +306,10 @@ func (s *Store) DeleteConfigEntry(idx uint64, kind, name string, entMeta *struct
return fmt.Errorf("failed updating index: %s", err) return fmt.Errorf("failed updating index: %s", err)
} }
return tx.Commit() return nil
} }
func insertConfigEntryWithTxn(tx *txn, idx uint64, conf structs.ConfigEntry) error { func insertConfigEntryWithTxn(tx WriteTxn, idx uint64, conf structs.ConfigEntry) error {
if conf == nil { if conf == nil {
return fmt.Errorf("cannot insert nil config entry") return fmt.Errorf("cannot insert nil config entry")
} }

View File

@ -207,6 +207,294 @@ func (s *Store) legacyIntentionsListTxn(tx ReadTxn, ws memdb.WatchSet, entMeta *
var ErrLegacyIntentionsAreDisabled = errors.New("Legacy intention modifications are disabled after the config entry migration.") var ErrLegacyIntentionsAreDisabled = errors.New("Legacy intention modifications are disabled after the config entry migration.")
func (s *Store) IntentionMutation(idx uint64, op structs.IntentionOp, mut *structs.IntentionMutation) error {
tx := s.db.WriteTxn(idx)
defer tx.Abort()
usingConfigEntries, err := areIntentionsInConfigEntries(tx, nil)
if err != nil {
return err
}
if !usingConfigEntries {
return errors.New("state: IntentionMutation() is not allowed when intentions are not stored in config entries")
}
switch op {
case structs.IntentionOpCreate:
if err := s.intentionMutationLegacyCreate(tx, idx, mut.Destination, mut.Value); err != nil {
return err
}
case structs.IntentionOpUpdate:
if err := s.intentionMutationLegacyUpdate(tx, idx, mut.ID, mut.Value); err != nil {
return err
}
case structs.IntentionOpDelete:
if mut.ID == "" {
if err := s.intentionMutationDelete(tx, idx, mut.Destination, mut.Source); err != nil {
return err
}
} else {
if err := s.intentionMutationLegacyDelete(tx, idx, mut.ID); err != nil {
return err
}
}
case structs.IntentionOpUpsert:
if err := s.intentionMutationUpsert(tx, idx, mut.Destination, mut.Source, mut.Value); err != nil {
return err
}
case structs.IntentionOpDeleteAll:
// This is an internal operation initiated by the leader and is not
// exposed for general RPC use.
return fmt.Errorf("Invalid Intention mutation operation '%s'", op)
default:
return fmt.Errorf("Invalid Intention mutation operation '%s'", op)
}
return tx.Commit()
}
func (s *Store) intentionMutationLegacyCreate(
tx WriteTxn,
idx uint64,
dest structs.ServiceName,
value *structs.SourceIntention,
) error {
_, configEntry, err := configEntryTxn(tx, nil, structs.ServiceIntentions, dest.Name, &dest.EnterpriseMeta)
if err != nil {
return fmt.Errorf("service-intentions config entry lookup failed: %v", err)
}
var upsertEntry *structs.ServiceIntentionsConfigEntry
if configEntry == nil {
upsertEntry = &structs.ServiceIntentionsConfigEntry{
Kind: structs.ServiceIntentions,
Name: dest.Name,
EnterpriseMeta: dest.EnterpriseMeta,
Sources: []*structs.SourceIntention{value},
}
} else {
prevEntry := configEntry.(*structs.ServiceIntentionsConfigEntry)
if err := checkLegacyIntentionApplyAllowed(prevEntry); err != nil {
return err
}
upsertEntry = prevEntry.Clone()
upsertEntry.Sources = append(upsertEntry.Sources, value)
}
if err := upsertEntry.LegacyNormalize(); err != nil {
return err
}
if err := upsertEntry.LegacyValidate(); err != nil {
return err
}
if err := ensureConfigEntryTxn(tx, idx, upsertEntry, upsertEntry.GetEnterpriseMeta()); err != nil {
return err
}
return nil
}
func (s *Store) intentionMutationLegacyUpdate(
tx WriteTxn,
idx uint64,
legacyID string,
value *structs.SourceIntention,
) error {
// This variant is just for legacy UUID-based intentions.
_, prevEntry, ixn, err := s.IntentionGet(nil, legacyID)
if err != nil {
return fmt.Errorf("Intention lookup failed: %v", err)
}
if ixn == nil || prevEntry == nil {
return fmt.Errorf("Cannot modify non-existent intention: '%s'", legacyID)
}
if err := checkLegacyIntentionApplyAllowed(prevEntry); err != nil {
return err
}
upsertEntry := prevEntry.Clone()
foundMatch := upsertEntry.UpdateSourceByLegacyID(
legacyID,
value,
)
if !foundMatch {
return fmt.Errorf("Cannot modify non-existent intention: '%s'", legacyID)
}
if err := upsertEntry.LegacyNormalize(); err != nil {
return err
}
if err := upsertEntry.LegacyValidate(); err != nil {
return err
}
if err := ensureConfigEntryTxn(tx, idx, upsertEntry, upsertEntry.GetEnterpriseMeta()); err != nil {
return err
}
return nil
}
func (s *Store) intentionMutationDelete(
tx WriteTxn,
idx uint64,
dest structs.ServiceName,
src structs.ServiceName,
) error {
_, configEntry, err := configEntryTxn(tx, nil, structs.ServiceIntentions, dest.Name, &dest.EnterpriseMeta)
if err != nil {
return fmt.Errorf("service-intentions config entry lookup failed: %v", err)
}
if configEntry == nil {
return nil
}
prevEntry := configEntry.(*structs.ServiceIntentionsConfigEntry)
upsertEntry := prevEntry.Clone()
deleted := upsertEntry.DeleteSourceByName(src)
if !deleted {
return nil
}
if upsertEntry == nil || len(upsertEntry.Sources) == 0 {
return deleteConfigEntryTxn(
tx,
idx,
structs.ServiceIntentions,
dest.Name,
&dest.EnterpriseMeta,
)
}
if err := upsertEntry.Normalize(); err != nil {
return err
}
if err := upsertEntry.Validate(); err != nil {
return err
}
if err := ensureConfigEntryTxn(tx, idx, upsertEntry, upsertEntry.GetEnterpriseMeta()); err != nil {
return err
}
return nil
}
func (s *Store) intentionMutationLegacyDelete(
tx WriteTxn,
idx uint64,
legacyID string,
) error {
_, prevEntry, ixn, err := s.IntentionGet(nil, legacyID)
if err != nil {
return fmt.Errorf("Intention lookup failed: %v", err)
}
if ixn == nil || prevEntry == nil {
return fmt.Errorf("Cannot delete non-existent intention: '%s'", legacyID)
}
if err := checkLegacyIntentionApplyAllowed(prevEntry); err != nil {
return err
}
upsertEntry := prevEntry.Clone()
deleted := upsertEntry.DeleteSourceByLegacyID(legacyID)
if !deleted {
return fmt.Errorf("Cannot delete non-existent intention: '%s'", legacyID)
}
if upsertEntry == nil || len(upsertEntry.Sources) == 0 {
return deleteConfigEntryTxn(
tx,
idx,
structs.ServiceIntentions,
prevEntry.Name,
&prevEntry.EnterpriseMeta,
)
}
if err := upsertEntry.LegacyNormalize(); err != nil {
return err
}
if err := upsertEntry.LegacyValidate(); err != nil {
return err
}
if err := ensureConfigEntryTxn(tx, idx, upsertEntry, upsertEntry.GetEnterpriseMeta()); err != nil {
return err
}
return nil
}
func (s *Store) intentionMutationUpsert(
tx WriteTxn,
idx uint64,
dest structs.ServiceName,
src structs.ServiceName,
value *structs.SourceIntention,
) error {
// This variant is just for config-entry based intentions.
_, configEntry, err := configEntryTxn(tx, nil, structs.ServiceIntentions, dest.Name, &dest.EnterpriseMeta)
if err != nil {
return fmt.Errorf("service-intentions config entry lookup failed: %v", err)
}
var prevEntry *structs.ServiceIntentionsConfigEntry
if configEntry != nil {
prevEntry = configEntry.(*structs.ServiceIntentionsConfigEntry)
}
var upsertEntry *structs.ServiceIntentionsConfigEntry
if prevEntry == nil {
upsertEntry = &structs.ServiceIntentionsConfigEntry{
Kind: structs.ServiceIntentions,
Name: dest.Name,
EnterpriseMeta: dest.EnterpriseMeta,
Sources: []*structs.SourceIntention{value},
}
} else {
upsertEntry = prevEntry.Clone()
upsertEntry.UpsertSourceByName(src, value)
}
if err := upsertEntry.Normalize(); err != nil {
return err
}
if err := upsertEntry.Validate(); err != nil {
return err
}
if err := ensureConfigEntryTxn(tx, idx, upsertEntry, upsertEntry.GetEnterpriseMeta()); err != nil {
return err
}
return nil
}
func checkLegacyIntentionApplyAllowed(prevEntry *structs.ServiceIntentionsConfigEntry) error {
if prevEntry == nil {
return nil
}
if prevEntry.LegacyIDFieldsAreAllSet() {
return nil
}
sn := prevEntry.DestinationServiceName()
return fmt.Errorf("cannot use legacy intention API to edit intentions with a destination of %q after editing them via a service-intentions config entry", sn.String())
}
// LegacyIntentionSet creates or updates an intention. // LegacyIntentionSet creates or updates an intention.
// //
// Deprecated: Edit service-intentions config entries directly. // Deprecated: Edit service-intentions config entries directly.

View File

@ -13,6 +13,14 @@ import (
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
var (
testLocation = time.FixedZone("UTC-8", -8*60*60)
testTimeA = time.Date(1955, 11, 5, 6, 15, 0, 0, testLocation)
testTimeB = time.Date(1985, 10, 26, 1, 35, 0, 0, testLocation)
testTimeC = time.Date(2015, 10, 21, 16, 29, 0, 0, testLocation)
)
func testBothIntentionFormats(t *testing.T, f func(t *testing.T, s *Store, legacy bool)) { func testBothIntentionFormats(t *testing.T, f func(t *testing.T, s *Store, legacy bool)) {
t.Helper() t.Helper()
@ -72,6 +80,8 @@ func TestStore_IntentionSetGet_basic(t *testing.T) {
DestinationNS: "default", DestinationNS: "default",
DestinationName: "web", DestinationName: "web",
Meta: map[string]string{}, Meta: map[string]string{},
CreatedAt: testTimeA,
UpdatedAt: testTimeA,
} }
// Inserting a with empty ID is disallowed. // Inserting a with empty ID is disallowed.
@ -89,6 +99,8 @@ func TestStore_IntentionSetGet_basic(t *testing.T) {
DestinationNS: "default", DestinationNS: "default",
DestinationName: "web", DestinationName: "web",
Meta: map[string]string{}, Meta: map[string]string{},
CreatedAt: testTimeA,
UpdatedAt: testTimeA,
RaftIndex: structs.RaftIndex{ RaftIndex: structs.RaftIndex{
CreateIndex: lastIndex, CreateIndex: lastIndex,
ModifyIndex: lastIndex, ModifyIndex: lastIndex,
@ -103,10 +115,12 @@ func TestStore_IntentionSetGet_basic(t *testing.T) {
Name: "web", Name: "web",
Sources: []*structs.SourceIntention{ Sources: []*structs.SourceIntention{
{ {
LegacyID: srcID, LegacyID: srcID,
Name: "*", Name: "*",
Action: structs.IntentionActionAllow, Action: structs.IntentionActionAllow,
LegacyMeta: map[string]string{}, LegacyMeta: map[string]string{},
LegacyCreateTime: &testTimeA,
LegacyUpdateTime: &testTimeA,
}, },
}, },
} }
@ -258,6 +272,534 @@ func TestStore_LegacyIntentionDelete_failsAfterUpgrade(t *testing.T) {
testutil.RequireErrorContains(t, err, ErrLegacyIntentionsAreDisabled.Error()) testutil.RequireErrorContains(t, err, ErrLegacyIntentionsAreDisabled.Error())
} }
func TestStore_IntentionMutation(t *testing.T) {
testBothIntentionFormats(t, func(t *testing.T, s *Store, legacy bool) {
if legacy {
mut := &structs.IntentionMutation{}
err := s.IntentionMutation(1, structs.IntentionOpCreate, mut)
testutil.RequireErrorContains(t, err, "state: IntentionMutation() is not allowed when intentions are not stored in config entries")
} else {
testStore_IntentionMutation(t, s)
}
})
}
func testStore_IntentionMutation(t *testing.T, s *Store) {
lastIndex := uint64(1)
defaultEntMeta := structs.DefaultEnterpriseMeta()
var (
id1 = testUUID()
id2 = testUUID()
id3 = testUUID()
)
eqEntry := func(t *testing.T, expect, got *structs.ServiceIntentionsConfigEntry) {
t.Helper()
// Zero out some fields for comparison.
got = got.Clone()
got.RaftIndex = structs.RaftIndex{}
for _, src := range got.Sources {
src.LegacyCreateTime = nil
src.LegacyUpdateTime = nil
if len(src.LegacyMeta) == 0 {
src.LegacyMeta = nil
}
}
require.Equal(t, expect, got)
}
// Try to create an intention without an ID to prove that LegacyValidate is being called.
testutil.RequireErrorContains(t, s.IntentionMutation(lastIndex, structs.IntentionOpCreate, &structs.IntentionMutation{
Destination: structs.NewServiceName("api", defaultEntMeta),
Value: &structs.SourceIntention{
Name: "web",
EnterpriseMeta: *defaultEntMeta,
Action: structs.IntentionActionAllow,
LegacyCreateTime: &testTimeA,
LegacyUpdateTime: &testTimeA,
},
}), `Sources[0].LegacyID must be set`)
// Create intention and create config entry
{
require.NoError(t, s.IntentionMutation(lastIndex, structs.IntentionOpCreate, &structs.IntentionMutation{
Destination: structs.NewServiceName("api", defaultEntMeta),
Value: &structs.SourceIntention{
Name: "web",
EnterpriseMeta: *defaultEntMeta,
Action: structs.IntentionActionAllow,
LegacyID: id1,
LegacyCreateTime: &testTimeA,
LegacyUpdateTime: &testTimeA,
},
}))
// Ensure it's there now.
idx, entry, ixn, err := s.IntentionGet(nil, id1)
require.NoError(t, err)
require.NotNil(t, entry)
require.NotNil(t, ixn)
require.Equal(t, lastIndex, idx)
eqEntry(t, &structs.ServiceIntentionsConfigEntry{
Kind: structs.ServiceIntentions,
Name: "api",
EnterpriseMeta: *defaultEntMeta,
Sources: []*structs.SourceIntention{
{
LegacyID: id1,
Name: "web",
EnterpriseMeta: *defaultEntMeta,
Action: structs.IntentionActionAllow,
Precedence: 9,
Type: structs.IntentionSourceConsul,
},
},
}, entry)
// only one
_, entries, err := s.ConfigEntries(nil, nil)
require.NoError(t, err)
require.Len(t, entries, 1)
lastIndex++
}
// Try to create a duplicate intention.
{
testutil.RequireErrorContains(t, s.IntentionMutation(lastIndex, structs.IntentionOpCreate, &structs.IntentionMutation{
Destination: structs.NewServiceName("api", defaultEntMeta),
Value: &structs.SourceIntention{
Name: "web",
EnterpriseMeta: *defaultEntMeta,
Action: structs.IntentionActionDeny,
LegacyID: id2,
LegacyCreateTime: &testTimeB,
LegacyUpdateTime: &testTimeB,
},
}), `more than once`)
}
// Create intention with existing config entry
{
require.NoError(t, s.IntentionMutation(lastIndex, structs.IntentionOpCreate, &structs.IntentionMutation{
Destination: structs.NewServiceName("api", defaultEntMeta),
Value: &structs.SourceIntention{
Name: "debug",
EnterpriseMeta: *defaultEntMeta,
Action: structs.IntentionActionDeny,
LegacyID: id2,
LegacyCreateTime: &testTimeB,
LegacyUpdateTime: &testTimeB,
},
}))
// Ensure it's there now.
idx, entry, ixn, err := s.IntentionGet(nil, id2)
require.NoError(t, err)
require.NotNil(t, entry)
require.NotNil(t, ixn)
require.Equal(t, lastIndex, idx)
eqEntry(t, &structs.ServiceIntentionsConfigEntry{
Kind: structs.ServiceIntentions,
Name: "api",
EnterpriseMeta: *defaultEntMeta,
Sources: []*structs.SourceIntention{
{
Name: "web",
EnterpriseMeta: *defaultEntMeta,
Action: structs.IntentionActionAllow,
LegacyID: id1,
Precedence: 9,
Type: structs.IntentionSourceConsul,
},
{
Name: "debug",
EnterpriseMeta: *defaultEntMeta,
Action: structs.IntentionActionDeny,
LegacyID: id2,
Precedence: 9,
Type: structs.IntentionSourceConsul,
},
},
}, entry)
// only one
_, entries, err := s.ConfigEntries(nil, nil)
require.NoError(t, err)
require.Len(t, entries, 1)
lastIndex++
}
// Try to update an intention without specifying an ID
testutil.RequireErrorContains(t, s.IntentionMutation(lastIndex, structs.IntentionOpUpdate, &structs.IntentionMutation{
ID: "",
Destination: structs.NewServiceName("api", defaultEntMeta),
Value: &structs.SourceIntention{
Name: "web",
EnterpriseMeta: *defaultEntMeta,
Action: structs.IntentionActionAllow,
},
}), `failed config entry lookup: index error: UUID must be 36 characters`)
// Try to update a non-existent intention
testutil.RequireErrorContains(t, s.IntentionMutation(lastIndex, structs.IntentionOpUpdate, &structs.IntentionMutation{
ID: id3,
Destination: structs.NewServiceName("api", defaultEntMeta),
Value: &structs.SourceIntention{
Name: "web",
EnterpriseMeta: *defaultEntMeta,
Action: structs.IntentionActionAllow,
},
}), `Cannot modify non-existent intention`)
// Update an existing intention by ID
{
require.NoError(t, s.IntentionMutation(lastIndex, structs.IntentionOpUpdate, &structs.IntentionMutation{
ID: id2,
Destination: structs.NewServiceName("api", defaultEntMeta),
Value: &structs.SourceIntention{
Name: "debug",
EnterpriseMeta: *defaultEntMeta,
Action: structs.IntentionActionDeny,
LegacyID: id2,
LegacyCreateTime: &testTimeB,
LegacyUpdateTime: &testTimeC,
Description: "op update",
},
}))
// Ensure it's there now.
idx, entry, ixn, err := s.IntentionGet(nil, id2)
require.NoError(t, err)
require.NotNil(t, entry)
require.NotNil(t, ixn)
require.Equal(t, lastIndex, idx)
eqEntry(t, &structs.ServiceIntentionsConfigEntry{
Kind: structs.ServiceIntentions,
Name: "api",
EnterpriseMeta: *defaultEntMeta,
Sources: []*structs.SourceIntention{
{
Name: "web",
EnterpriseMeta: *defaultEntMeta,
Action: structs.IntentionActionAllow,
LegacyID: id1,
Precedence: 9,
Type: structs.IntentionSourceConsul,
},
{
Name: "debug",
EnterpriseMeta: *defaultEntMeta,
Action: structs.IntentionActionDeny,
LegacyID: id2,
Precedence: 9,
Type: structs.IntentionSourceConsul,
Description: "op update",
},
},
}, entry)
// only one
_, entries, err := s.ConfigEntries(nil, nil)
require.NoError(t, err)
require.Len(t, entries, 1)
lastIndex++
}
// Try to delete a non-existent intention
testutil.RequireErrorContains(t, s.IntentionMutation(lastIndex, structs.IntentionOpDelete, &structs.IntentionMutation{
ID: id3,
}), `Cannot delete non-existent intention`)
// delete by id
{
require.NoError(t, s.IntentionMutation(lastIndex, structs.IntentionOpDelete, &structs.IntentionMutation{
ID: id1,
}))
// only one
_, entries, err := s.ConfigEntries(nil, nil)
require.NoError(t, err)
require.Len(t, entries, 1)
eqEntry(t, &structs.ServiceIntentionsConfigEntry{
Kind: structs.ServiceIntentions,
Name: "api",
EnterpriseMeta: *defaultEntMeta,
Sources: []*structs.SourceIntention{
{
Name: "debug",
EnterpriseMeta: *defaultEntMeta,
Action: structs.IntentionActionDeny,
LegacyID: id2,
Precedence: 9,
Type: structs.IntentionSourceConsul,
Description: "op update",
},
},
}, entries[0].(*structs.ServiceIntentionsConfigEntry))
lastIndex++
}
// delete last one by id
{
require.NoError(t, s.IntentionMutation(lastIndex, structs.IntentionOpDelete, &structs.IntentionMutation{
ID: id2,
}))
// none one
_, entries, err := s.ConfigEntries(nil, nil)
require.NoError(t, err)
require.Empty(t, entries)
lastIndex++
}
// upsert intention for first time
{
require.NoError(t, s.IntentionMutation(lastIndex, structs.IntentionOpUpsert, &structs.IntentionMutation{
Destination: structs.NewServiceName("api", defaultEntMeta),
Value: &structs.SourceIntention{
Name: "web",
EnterpriseMeta: *defaultEntMeta,
Action: structs.IntentionActionAllow,
},
}))
// Ensure it's there now.
idx, entry, ixn, err := s.IntentionGetExact(nil, &structs.IntentionQueryExact{
SourceNS: "default",
SourceName: "web",
DestinationNS: "default",
DestinationName: "api",
})
require.NoError(t, err)
require.NotNil(t, entry)
require.NotNil(t, ixn)
require.Equal(t, lastIndex, idx)
eqEntry(t, &structs.ServiceIntentionsConfigEntry{
Kind: structs.ServiceIntentions,
Name: "api",
EnterpriseMeta: *defaultEntMeta,
Sources: []*structs.SourceIntention{
{
Name: "web",
EnterpriseMeta: *defaultEntMeta,
Action: structs.IntentionActionAllow,
Precedence: 9,
Type: structs.IntentionSourceConsul,
},
},
}, entry)
// only one
_, entries, err := s.ConfigEntries(nil, nil)
require.NoError(t, err)
require.Len(t, entries, 1)
lastIndex++
}
// upsert over itself (REPLACE)
{
require.NoError(t, s.IntentionMutation(lastIndex, structs.IntentionOpUpsert, &structs.IntentionMutation{
Destination: structs.NewServiceName("api", defaultEntMeta),
Source: structs.NewServiceName("web", defaultEntMeta),
Value: &structs.SourceIntention{
Name: "web",
EnterpriseMeta: *defaultEntMeta,
Action: structs.IntentionActionAllow,
Description: "upserted over",
},
}))
// Ensure it's there now.
idx, entry, ixn, err := s.IntentionGetExact(nil, &structs.IntentionQueryExact{
SourceNS: "default",
SourceName: "web",
DestinationNS: "default",
DestinationName: "api",
})
require.NoError(t, err)
require.NotNil(t, entry)
require.NotNil(t, ixn)
require.Equal(t, lastIndex, idx)
require.Equal(t, "upserted over", ixn.Description)
eqEntry(t, &structs.ServiceIntentionsConfigEntry{
Kind: structs.ServiceIntentions,
Name: "api",
EnterpriseMeta: *defaultEntMeta,
Sources: []*structs.SourceIntention{
{
Name: "web",
EnterpriseMeta: *defaultEntMeta,
Action: structs.IntentionActionAllow,
Precedence: 9,
Type: structs.IntentionSourceConsul,
Description: "upserted over",
},
},
}, entry)
// only one
_, entries, err := s.ConfigEntries(nil, nil)
require.NoError(t, err)
require.Len(t, entries, 1)
lastIndex++
}
// upsert into existing config entry (APPEND)
{
require.NoError(t, s.IntentionMutation(lastIndex, structs.IntentionOpUpsert, &structs.IntentionMutation{
Destination: structs.NewServiceName("api", defaultEntMeta),
Source: structs.NewServiceName("debug", defaultEntMeta),
Value: &structs.SourceIntention{
Name: "debug",
EnterpriseMeta: *defaultEntMeta,
Action: structs.IntentionActionDeny,
},
}))
// Ensure it's there now.
idx, entry, ixn, err := s.IntentionGetExact(nil, &structs.IntentionQueryExact{
SourceNS: "default",
SourceName: "debug",
DestinationNS: "default",
DestinationName: "api",
})
require.NoError(t, err)
require.NotNil(t, entry)
require.NotNil(t, ixn)
require.Equal(t, lastIndex, idx)
eqEntry(t, &structs.ServiceIntentionsConfigEntry{
Kind: structs.ServiceIntentions,
Name: "api",
EnterpriseMeta: *defaultEntMeta,
Sources: []*structs.SourceIntention{
{
Name: "web",
EnterpriseMeta: *defaultEntMeta,
Action: structs.IntentionActionAllow,
Precedence: 9,
Type: structs.IntentionSourceConsul,
Description: "upserted over",
},
{
Name: "debug",
EnterpriseMeta: *defaultEntMeta,
Action: structs.IntentionActionDeny,
Precedence: 9,
Type: structs.IntentionSourceConsul,
},
},
}, entry)
// only one
_, entries, err := s.ConfigEntries(nil, nil)
require.NoError(t, err)
require.Len(t, entries, 1)
lastIndex++
}
// Try to delete a non-existent intention by name
require.NoError(t, s.IntentionMutation(lastIndex, structs.IntentionOpDelete, &structs.IntentionMutation{
Destination: structs.NewServiceName("api", defaultEntMeta),
Source: structs.NewServiceName("blurb", defaultEntMeta),
}))
// delete by name
{
require.NoError(t, s.IntentionMutation(lastIndex, structs.IntentionOpDelete, &structs.IntentionMutation{
Destination: structs.NewServiceName("api", defaultEntMeta),
Source: structs.NewServiceName("web", defaultEntMeta),
}))
// only one
_, entries, err := s.ConfigEntries(nil, nil)
require.NoError(t, err)
require.Len(t, entries, 1)
eqEntry(t, &structs.ServiceIntentionsConfigEntry{
Kind: structs.ServiceIntentions,
Name: "api",
EnterpriseMeta: *defaultEntMeta,
Sources: []*structs.SourceIntention{
{
Name: "debug",
EnterpriseMeta: *defaultEntMeta,
Action: structs.IntentionActionDeny,
Precedence: 9,
Type: structs.IntentionSourceConsul,
},
},
}, entries[0].(*structs.ServiceIntentionsConfigEntry))
lastIndex++
}
// delete last one by name
{
require.NoError(t, s.IntentionMutation(lastIndex, structs.IntentionOpDelete, &structs.IntentionMutation{
Destination: structs.NewServiceName("api", defaultEntMeta),
Source: structs.NewServiceName("debug", defaultEntMeta),
}))
// none one
_, entries, err := s.ConfigEntries(nil, nil)
require.NoError(t, err)
require.Empty(t, entries)
lastIndex++
}
// Try to update an intention with an ID on a non-legacy config entry.
{
idFake := testUUID()
require.NoError(t, s.EnsureConfigEntry(lastIndex, &structs.ServiceIntentionsConfigEntry{
Kind: structs.ServiceIntentions,
Name: "new",
EnterpriseMeta: *defaultEntMeta,
Sources: []*structs.SourceIntention{
{
Name: "web",
EnterpriseMeta: *defaultEntMeta,
Action: structs.IntentionActionAllow,
Precedence: 9,
Type: structs.IntentionSourceConsul,
},
},
}, nil))
lastIndex++
// ...via create
testutil.RequireErrorContains(t, s.IntentionMutation(lastIndex, structs.IntentionOpCreate, &structs.IntentionMutation{
Destination: structs.NewServiceName("new", defaultEntMeta),
Value: &structs.SourceIntention{
Name: "old",
EnterpriseMeta: *defaultEntMeta,
Action: structs.IntentionActionAllow,
LegacyID: idFake,
},
}), `cannot use legacy intention API to edit intentions with a destination`)
}
}
func TestStore_LegacyIntentionSet_emptyId(t *testing.T) { func TestStore_LegacyIntentionSet_emptyId(t *testing.T) {
// note: irrelevant test for config entries variant // note: irrelevant test for config entries variant
s := testStateStore(t) s := testStateStore(t)
@ -282,24 +824,27 @@ func TestStore_IntentionSet_updateCreatedAt(t *testing.T) {
testBothIntentionFormats(t, func(t *testing.T, s *Store, legacy bool) { testBothIntentionFormats(t, func(t *testing.T, s *Store, legacy bool) {
// Build a valid intention // Build a valid intention
var ( var (
id = testUUID() id = testUUID()
createTime time.Time
) )
if legacy { if legacy {
ixn := structs.Intention{ ixn := structs.Intention{
ID: id, ID: id,
CreatedAt: time.Now().UTC(), SourceNS: "default",
SourceName: "*",
DestinationNS: "default",
DestinationName: "web",
Action: structs.IntentionActionAllow,
CreatedAt: testTimeA,
UpdatedAt: testTimeA,
} }
// Insert // Insert
require.NoError(t, s.LegacyIntentionSet(1, &ixn)) require.NoError(t, s.LegacyIntentionSet(1, &ixn))
createTime = ixn.CreatedAt
// Change a value and test updating // Change a value and test updating
ixnUpdate := ixn ixnUpdate := ixn
ixnUpdate.CreatedAt = createTime.Add(10 * time.Second) ixnUpdate.CreatedAt = testTimeB
require.NoError(t, s.LegacyIntentionSet(2, &ixnUpdate)) require.NoError(t, s.LegacyIntentionSet(2, &ixnUpdate))
id = ixn.ID id = ixn.ID
@ -310,10 +855,12 @@ func TestStore_IntentionSet_updateCreatedAt(t *testing.T) {
Name: "web", Name: "web",
Sources: []*structs.SourceIntention{ Sources: []*structs.SourceIntention{
{ {
LegacyID: id, LegacyID: id,
Name: "*", Name: "*",
Action: structs.IntentionActionAllow, Action: structs.IntentionActionAllow,
LegacyMeta: map[string]string{}, LegacyMeta: map[string]string{},
LegacyCreateTime: &testTimeA,
LegacyUpdateTime: &testTimeA,
}, },
}, },
} }
@ -321,15 +868,13 @@ func TestStore_IntentionSet_updateCreatedAt(t *testing.T) {
require.NoError(t, conf.LegacyNormalize()) require.NoError(t, conf.LegacyNormalize())
require.NoError(t, conf.LegacyValidate()) require.NoError(t, conf.LegacyValidate())
require.NoError(t, s.EnsureConfigEntry(1, conf.Clone(), nil)) require.NoError(t, s.EnsureConfigEntry(1, conf.Clone(), nil))
createTime = *conf.Sources[0].LegacyCreateTime
} }
// Read it back and verify // Read it back and verify
_, _, actual, err := s.IntentionGet(nil, id) _, _, actual, err := s.IntentionGet(nil, id)
require.NoError(t, err) require.NoError(t, err)
require.NotNil(t, actual) require.NotNil(t, actual)
require.Equal(t, createTime, actual.CreatedAt) require.Equal(t, testTimeA, actual.CreatedAt)
}) })
} }
@ -339,7 +884,14 @@ func TestStore_IntentionSet_metaNil(t *testing.T) {
if legacy { if legacy {
// Build a valid intention // Build a valid intention
ixn := &structs.Intention{ ixn := &structs.Intention{
ID: id, ID: id,
SourceNS: "default",
SourceName: "*",
DestinationNS: "default",
DestinationName: "web",
Action: structs.IntentionActionAllow,
CreatedAt: testTimeA,
UpdatedAt: testTimeA,
} }
// Insert // Insert
@ -351,9 +903,11 @@ func TestStore_IntentionSet_metaNil(t *testing.T) {
Name: "web", Name: "web",
Sources: []*structs.SourceIntention{ Sources: []*structs.SourceIntention{
{ {
LegacyID: id, LegacyID: id,
Name: "*", Name: "*",
Action: structs.IntentionActionAllow, Action: structs.IntentionActionAllow,
LegacyCreateTime: &testTimeA,
LegacyUpdateTime: &testTimeA,
}, },
}, },
} }
@ -380,8 +934,15 @@ func TestStore_IntentionSet_metaSet(t *testing.T) {
if legacy { if legacy {
// Build a valid intention // Build a valid intention
ixn := structs.Intention{ ixn := structs.Intention{
ID: id, ID: id,
Meta: expectMeta, SourceNS: "default",
SourceName: "*",
DestinationNS: "default",
DestinationName: "web",
Action: structs.IntentionActionAllow,
CreatedAt: testTimeA,
UpdatedAt: testTimeA,
Meta: expectMeta,
} }
// Insert // Insert
@ -394,10 +955,12 @@ func TestStore_IntentionSet_metaSet(t *testing.T) {
Name: "web", Name: "web",
Sources: []*structs.SourceIntention{ Sources: []*structs.SourceIntention{
{ {
LegacyID: id, LegacyID: id,
Name: "*", Name: "*",
Action: structs.IntentionActionAllow, Action: structs.IntentionActionAllow,
LegacyMeta: expectMeta, LegacyCreateTime: &testTimeA,
LegacyUpdateTime: &testTimeA,
LegacyMeta: expectMeta,
}, },
}, },
} }
@ -428,7 +991,14 @@ func TestStore_IntentionDelete(t *testing.T) {
// Create // Create
if legacy { if legacy {
ixn := &structs.Intention{ ixn := &structs.Intention{
ID: id, ID: id,
SourceNS: "default",
SourceName: "*",
DestinationNS: "default",
DestinationName: "web",
Action: structs.IntentionActionAllow,
CreatedAt: testTimeA,
UpdatedAt: testTimeA,
} }
lastIndex++ lastIndex++
require.NoError(t, s.LegacyIntentionSet(lastIndex, ixn)) require.NoError(t, s.LegacyIntentionSet(lastIndex, ixn))
@ -442,9 +1012,11 @@ func TestStore_IntentionDelete(t *testing.T) {
Name: "web", Name: "web",
Sources: []*structs.SourceIntention{ Sources: []*structs.SourceIntention{
{ {
LegacyID: id, LegacyID: id,
Name: "*", Name: "*",
Action: structs.IntentionActionAllow, Action: structs.IntentionActionAllow,
LegacyCreateTime: &testTimeA,
LegacyUpdateTime: &testTimeA,
}, },
}, },
} }
@ -519,6 +1091,8 @@ func TestStore_IntentionsList(t *testing.T) {
SourceType: structs.IntentionSourceConsul, SourceType: structs.IntentionSourceConsul,
Action: structs.IntentionActionAllow, Action: structs.IntentionActionAllow,
Meta: map[string]string{}, Meta: map[string]string{},
CreatedAt: testTimeA,
UpdatedAt: testTimeA,
} }
} }
@ -530,22 +1104,17 @@ func TestStore_IntentionsList(t *testing.T) {
id := testUUID() id := testUUID()
for _, src := range srcs { for _, src := range srcs {
conf.Sources = append(conf.Sources, &structs.SourceIntention{ conf.Sources = append(conf.Sources, &structs.SourceIntention{
LegacyID: id, LegacyID: id,
Name: src, Name: src,
Action: structs.IntentionActionAllow, Action: structs.IntentionActionAllow,
LegacyCreateTime: &testTimeA,
LegacyUpdateTime: &testTimeA,
}) })
} }
return conf return conf
} }
cmpIntention := func(ixn *structs.Intention, id string) *structs.Intention { clearIrrelevantFields := func(ixns ...*structs.Intention) {
ixn.ID = id
//nolint:staticcheck
ixn.UpdatePrecedence()
return ixn
}
clearIrrelevantFields := func(ixns []*structs.Intention) {
// Clear fields irrelevant for comparison. // Clear fields irrelevant for comparison.
for _, ixn := range ixns { for _, ixn := range ixns {
ixn.Hash = nil ixn.Hash = nil
@ -556,6 +1125,15 @@ func TestStore_IntentionsList(t *testing.T) {
} }
} }
cmpIntention := func(ixn *structs.Intention, id string) *structs.Intention {
ixn2 := ixn.Clone()
ixn2.ID = id
clearIrrelevantFields(ixn2)
//nolint:staticcheck
ixn2.UpdatePrecedence()
return ixn2
}
var ( var (
expectIDs []string expectIDs []string
) )
@ -610,7 +1188,7 @@ func TestStore_IntentionsList(t *testing.T) {
require.Equal(t, !legacy, fromConfig) require.Equal(t, !legacy, fromConfig)
require.Equal(t, lastIndex, idx) require.Equal(t, lastIndex, idx)
clearIrrelevantFields(actual) clearIrrelevantFields(actual...)
require.Equal(t, expected, actual) require.Equal(t, expected, actual)
}) })
} }

View File

@ -69,7 +69,7 @@ func (s *Snapshot) Tombstones() (memdb.ResultIterator, error) {
// KVS is used when restoring from a snapshot. Use KVSSet for general inserts. // KVS is used when restoring from a snapshot. Use KVSSet for general inserts.
func (s *Restore) KVS(entry *structs.DirEntry) error { func (s *Restore) KVS(entry *structs.DirEntry) error {
if err := insertKVTxn(s.tx, entry, true); err != nil { if err := insertKVTxn(s.tx, entry, true, true); err != nil {
return fmt.Errorf("failed inserting kvs entry: %s", err) return fmt.Errorf("failed inserting kvs entry: %s", err)
} }
@ -153,7 +153,7 @@ func kvsSetTxn(tx WriteTxn, idx uint64, entry *structs.DirEntry, updateSession b
entry.ModifyIndex = idx entry.ModifyIndex = idx
// Store the kv pair in the state store and update the index. // Store the kv pair in the state store and update the index.
if err := insertKVTxn(tx, entry, false); err != nil { if err := insertKVTxn(tx, entry, false, false); err != nil {
return fmt.Errorf("failed inserting kvs entry: %s", err) return fmt.Errorf("failed inserting kvs entry: %s", err)
} }

View File

@ -16,7 +16,7 @@ func kvsIndexer() *memdb.StringFieldIndex {
} }
} }
func insertKVTxn(tx WriteTxn, entry *structs.DirEntry, updateMax bool) error { func insertKVTxn(tx WriteTxn, entry *structs.DirEntry, updateMax bool, _ bool) error {
if err := tx.Insert("kvs", entry); err != nil { if err := tx.Insert("kvs", entry); err != nil {
return err return err
} }

View File

@ -146,7 +146,7 @@ func (s *Snapshot) Sessions() (memdb.ResultIterator, error) {
// Session is used when restoring from a snapshot. For general inserts, use // Session is used when restoring from a snapshot. For general inserts, use
// SessionCreate. // SessionCreate.
func (s *Restore) Session(sess *structs.Session) error { func (s *Restore) Session(sess *structs.Session) error {
if err := insertSessionTxn(s.tx, sess, sess.ModifyIndex, true); err != nil { if err := insertSessionTxn(s.tx, sess, sess.ModifyIndex, true, true); err != nil {
return fmt.Errorf("failed inserting session: %s", err) return fmt.Errorf("failed inserting session: %s", err)
} }
@ -213,7 +213,7 @@ func sessionCreateTxn(tx *txn, idx uint64, sess *structs.Session) error {
} }
// Insert the session // Insert the session
if err := insertSessionTxn(tx, sess, idx, false); err != nil { if err := insertSessionTxn(tx, sess, idx, false, false); err != nil {
return fmt.Errorf("failed inserting session: %s", err) return fmt.Errorf("failed inserting session: %s", err)
} }

View File

@ -48,7 +48,7 @@ func sessionDeleteWithSession(tx WriteTxn, session *structs.Session, idx uint64)
return nil return nil
} }
func insertSessionTxn(tx *txn, session *structs.Session, idx uint64, updateMax bool) error { func insertSessionTxn(tx *txn, session *structs.Session, idx uint64, updateMax bool, _ bool) error {
if err := tx.Insert("sessions", session); err != nil { if err := tx.Insert("sessions", session); err != nil {
return err return err
} }

View File

@ -61,7 +61,7 @@ func (c *ClientConnPool) ClientConn(datacenter string) (*grpc.ClientConn, error)
grpc.WithInsecure(), grpc.WithInsecure(),
grpc.WithContextDialer(c.dialer), grpc.WithContextDialer(c.dialer),
grpc.WithDisableRetry(), grpc.WithDisableRetry(),
grpc.WithStatsHandler(newStatsHandler(defaultMetrics)), grpc.WithStatsHandler(newStatsHandler(defaultMetrics())),
// nolint:staticcheck // there is no other supported alternative to WithBalancerName // nolint:staticcheck // there is no other supported alternative to WithBalancerName
grpc.WithBalancerName("pick_first")) grpc.WithBalancerName("pick_first"))
if err != nil { if err != nil {

View File

@ -14,11 +14,12 @@ import (
// The register function will be called with the grpc.Server to register // The register function will be called with the grpc.Server to register
// gRPC services with the server. // gRPC services with the server.
func NewHandler(addr net.Addr, register func(server *grpc.Server)) *Handler { func NewHandler(addr net.Addr, register func(server *grpc.Server)) *Handler {
metrics := defaultMetrics()
// We don't need to pass tls.Config to the server since it's multiplexed // We don't need to pass tls.Config to the server since it's multiplexed
// behind the RPC listener, which already has TLS configured. // behind the RPC listener, which already has TLS configured.
srv := grpc.NewServer( srv := grpc.NewServer(
grpc.StatsHandler(newStatsHandler(defaultMetrics)), grpc.StatsHandler(newStatsHandler(metrics)),
grpc.StreamInterceptor((&activeStreamCounter{metrics: defaultMetrics}).Intercept), grpc.StreamInterceptor((&activeStreamCounter{metrics: metrics}).Intercept),
) )
register(srv) register(srv)

View File

@ -10,7 +10,6 @@ import (
"google.golang.org/grpc/stats" "google.golang.org/grpc/stats"
) )
var defaultMetrics = metrics.Default()
var StatsGauges = []prometheus.GaugeDefinition{ var StatsGauges = []prometheus.GaugeDefinition{
{ {
Name: []string{"grpc", "server", "connections"}, Name: []string{"grpc", "server", "connections"},
@ -48,6 +47,8 @@ var StatsCounters = []prometheus.CounterDefinition{
}, },
} }
var defaultMetrics = metrics.Default
// statsHandler is a grpc/stats.StatsHandler which emits connection and // statsHandler is a grpc/stats.StatsHandler which emits connection and
// request metrics to go-metrics. // request metrics to go-metrics.
type statsHandler struct { type statsHandler struct {

View File

@ -113,11 +113,14 @@ func patchGlobalMetrics(t *testing.T) (*fakeMetricsSink, func()) {
FilterDefault: true, FilterDefault: true,
} }
var err error var err error
defaultMetrics, err = metrics.New(cfg, sink) defaultMetrics = func() *metrics.Metrics {
m, _ := metrics.New(cfg, sink)
return m
}
require.NoError(t, err) require.NoError(t, err)
reset := func() { reset := func() {
t.Helper() t.Helper()
defaultMetrics, err = metrics.New(cfg, &metrics.BlackholeSink{}) defaultMetrics = metrics.Default
require.NoError(t, err, "failed to reset global metrics") require.NoError(t, err, "failed to reset global metrics")
} }
return sink, reset return sink, reset

View File

@ -365,6 +365,7 @@ func (s *HTTPHandlers) wrap(handler endpoint, methods []string) http.HandlerFunc
return func(resp http.ResponseWriter, req *http.Request) { return func(resp http.ResponseWriter, req *http.Request) {
setHeaders(resp, s.agent.config.HTTPResponseHeaders) setHeaders(resp, s.agent.config.HTTPResponseHeaders)
setTranslateAddr(resp, s.agent.config.TranslateWANAddrs) setTranslateAddr(resp, s.agent.config.TranslateWANAddrs)
setACLDefaultPolicy(resp, s.agent.config.ACLDefaultPolicy)
// Obfuscate any tokens from appearing in the logs // Obfuscate any tokens from appearing in the logs
formVals, err := url.ParseQuery(req.URL.RawQuery) formVals, err := url.ParseQuery(req.URL.RawQuery)
@ -705,6 +706,12 @@ func setConsistency(resp http.ResponseWriter, consistency string) {
} }
} }
func setACLDefaultPolicy(resp http.ResponseWriter, aclDefaultPolicy string) {
if aclDefaultPolicy != "" {
resp.Header().Set("X-Consul-Default-ACL-Policy", aclDefaultPolicy)
}
}
// setLastContact is used to set the last contact header // setLastContact is used to set the last contact header
func setLastContact(resp http.ResponseWriter, last time.Duration) { func setLastContact(resp http.ResponseWriter, last time.Duration) {
if last < 0 { if last < 0 {

View File

@ -415,6 +415,54 @@ func TestHTTPAPI_TranslateAddrHeader(t *testing.T) {
} }
} }
func TestHTTPAPI_DefaultACLPolicy(t *testing.T) {
t.Parallel()
type testcase struct {
name string
hcl string
expect string
}
cases := []testcase{
{
name: "default is allow",
hcl: ``,
expect: "allow",
},
{
name: "explicit allow",
hcl: `acl { default_policy = "allow" }`,
expect: "allow",
},
{
name: "explicit deny",
hcl: `acl { default_policy = "deny" }`,
expect: "deny",
},
}
for _, tc := range cases {
tc := tc
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
a := NewTestAgent(t, tc.hcl)
defer a.Shutdown()
resp := httptest.NewRecorder()
handler := func(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
return nil, nil
}
req, _ := http.NewRequest("GET", "/v1/agent/self", nil)
a.srv.wrap(handler, []string{"GET"})(resp, req)
require.Equal(t, tc.expect, resp.Header().Get("X-Consul-Default-ACL-Policy"))
})
}
}
func TestHTTPAPIResponseHeaders(t *testing.T) { func TestHTTPAPIResponseHeaders(t *testing.T) {
t.Parallel() t.Parallel()
a := NewTestAgent(t, ` a := NewTestAgent(t, `

View File

@ -11,6 +11,8 @@ func autopilotToAPIServerEnterprise(_ *autopilot.ServerState, _ *api.AutopilotSe
// noop in oss // noop in oss
} }
func autopilotToAPIStateEnterprise(_ *autopilot.State, _ *api.AutopilotState) { func autopilotToAPIStateEnterprise(state *autopilot.State, apiState *api.AutopilotState) {
// noop in oss // without the enterprise features there is no different between these two and we don't want to
// alarm anyone by leaving this as the zero value.
apiState.OptimisticFailureTolerance = state.FailureTolerance
} }

View File

@ -656,9 +656,10 @@ func TestAutopilotStateToAPIConversion(t *testing.T) {
} }
expected := api.AutopilotState{ expected := api.AutopilotState{
Healthy: true, Healthy: true,
FailureTolerance: 1, FailureTolerance: 1,
Leader: string(leaderID), OptimisticFailureTolerance: 1,
Leader: string(leaderID),
Voters: []string{ Voters: []string{
string(leaderID), string(leaderID),
string(follower1ID), string(follower1ID),

View File

@ -519,7 +519,7 @@ START:
// Try to get a conn first // Try to get a conn first
conn, err := p.acquire(dc, nodeName, addr) conn, err := p.acquire(dc, nodeName, addr)
if err != nil { if err != nil {
return nil, nil, fmt.Errorf("failed to get conn: %v", err) return nil, nil, fmt.Errorf("failed to get conn: %w", err)
} }
// Get a client // Get a client
@ -533,7 +533,7 @@ START:
retries++ retries++
goto START goto START
} }
return nil, nil, fmt.Errorf("failed to start stream: %v", err) return nil, nil, fmt.Errorf("failed to start stream: %w", err)
} }
return conn, client, nil return conn, client, nil
} }
@ -575,14 +575,14 @@ func (p *ConnPool) rpcInsecure(dc string, addr net.Addr, method string, args int
var codec rpc.ClientCodec var codec rpc.ClientCodec
conn, _, err := p.dial(dc, addr, 0, RPCTLSInsecure) conn, _, err := p.dial(dc, addr, 0, RPCTLSInsecure)
if err != nil { if err != nil {
return fmt.Errorf("rpcinsecure error establishing connection: %v", err) return fmt.Errorf("rpcinsecure error establishing connection: %w", err)
} }
codec = msgpackrpc.NewCodecFromHandle(true, true, conn, structs.MsgpackHandle) codec = msgpackrpc.NewCodecFromHandle(true, true, conn, structs.MsgpackHandle)
// Make the RPC call // Make the RPC call
err = msgpackrpc.CallWithCodec(codec, method, args, reply) err = msgpackrpc.CallWithCodec(codec, method, args, reply)
if err != nil { if err != nil {
return fmt.Errorf("rpcinsecure error making call: %v", err) return fmt.Errorf("rpcinsecure error making call: %w", err)
} }
return nil return nil
@ -594,7 +594,7 @@ func (p *ConnPool) rpc(dc string, nodeName string, addr net.Addr, method string,
// Get a usable client // Get a usable client
conn, sc, err := p.getClient(dc, nodeName, addr) conn, sc, err := p.getClient(dc, nodeName, addr)
if err != nil { if err != nil {
return fmt.Errorf("rpc error getting client: %v", err) return fmt.Errorf("rpc error getting client: %w", err)
} }
// Make the RPC call // Make the RPC call
@ -611,7 +611,7 @@ func (p *ConnPool) rpc(dc string, nodeName string, addr net.Addr, method string,
} }
p.releaseConn(conn) p.releaseConn(conn)
return fmt.Errorf("rpc error making call: %v", err) return fmt.Errorf("rpc error making call: %w", err)
} }
// Done with the connection // Done with the connection

View File

@ -59,6 +59,59 @@ func (e *ServiceIntentionsConfigEntry) DestinationServiceName() ServiceName {
return NewServiceName(e.Name, &e.EnterpriseMeta) return NewServiceName(e.Name, &e.EnterpriseMeta)
} }
func (e *ServiceIntentionsConfigEntry) UpdateSourceByLegacyID(legacyID string, update *SourceIntention) bool {
for i, src := range e.Sources {
if src.LegacyID == legacyID {
e.Sources[i] = update
return true
}
}
return false
}
func (e *ServiceIntentionsConfigEntry) UpsertSourceByName(sn ServiceName, upsert *SourceIntention) {
for i, src := range e.Sources {
if src.SourceServiceName() == sn {
e.Sources[i] = upsert
return
}
}
e.Sources = append(e.Sources, upsert)
}
func (e *ServiceIntentionsConfigEntry) DeleteSourceByLegacyID(legacyID string) bool {
for i, src := range e.Sources {
if src.LegacyID == legacyID {
// Delete slice element: https://github.com/golang/go/wiki/SliceTricks#delete
// a = append(a[:i], a[i+1:]...)
e.Sources = append(e.Sources[:i], e.Sources[i+1:]...)
if len(e.Sources) == 0 {
e.Sources = nil
}
return true
}
}
return false
}
func (e *ServiceIntentionsConfigEntry) DeleteSourceByName(sn ServiceName) bool {
for i, src := range e.Sources {
if src.SourceServiceName() == sn {
// Delete slice element: https://github.com/golang/go/wiki/SliceTricks#delete
// a = append(a[:i], a[i+1:]...)
e.Sources = append(e.Sources[:i], e.Sources[i+1:]...)
if len(e.Sources) == 0 {
e.Sources = nil
}
return true
}
}
return false
}
func (e *ServiceIntentionsConfigEntry) ToIntention(src *SourceIntention) *Intention { func (e *ServiceIntentionsConfigEntry) ToIntention(src *SourceIntention) *Intention {
meta := e.Meta meta := e.Meta
if src.LegacyID != "" { if src.LegacyID != "" {
@ -352,6 +405,9 @@ func (e *ServiceIntentionsConfigEntry) normalize(legacyWrite bool) error {
return fmt.Errorf("config entry is nil") return fmt.Errorf("config entry is nil")
} }
// NOTE: this function must be deterministic so that the raft log doesn't
// diverge. This means no ID assignments or time.Now() usage!
e.Kind = ServiceIntentions e.Kind = ServiceIntentions
e.EnterpriseMeta.Normalize() e.EnterpriseMeta.Normalize()
@ -377,11 +433,6 @@ func (e *ServiceIntentionsConfigEntry) normalize(legacyWrite bool) error {
if src.LegacyMeta == nil { if src.LegacyMeta == nil {
src.LegacyMeta = make(map[string]string) src.LegacyMeta = make(map[string]string)
} }
// Set the created/updated times. If this is an update instead of an insert
// the UpdateOver() will fix it up appropriately.
now := time.Now().UTC()
src.LegacyCreateTime = timePointer(now)
src.LegacyUpdateTime = timePointer(now)
} else { } else {
// Legacy fields are cleared, except LegacyMeta which we leave // Legacy fields are cleared, except LegacyMeta which we leave
// populated so that we can later fail the write in Validate() and // populated so that we can later fail the write in Validate() and
@ -541,11 +592,25 @@ func (e *ServiceIntentionsConfigEntry) validate(legacyWrite bool) error {
) )
} }
} }
if src.LegacyCreateTime == nil {
return fmt.Errorf("Sources[%d].LegacyCreateTime must be set", i)
}
if src.LegacyUpdateTime == nil {
return fmt.Errorf("Sources[%d].LegacyUpdateTime must be set", i)
}
} else { } else {
if len(src.LegacyMeta) > 0 { if len(src.LegacyMeta) > 0 {
return fmt.Errorf("Sources[%d].LegacyMeta must be omitted", i) return fmt.Errorf("Sources[%d].LegacyMeta must be omitted", i)
} }
src.LegacyMeta = nil // ensure it's completely unset src.LegacyMeta = nil // ensure it's completely unset
if src.LegacyCreateTime != nil {
return fmt.Errorf("Sources[%d].LegacyCreateTime must be omitted", i)
}
if src.LegacyUpdateTime != nil {
return fmt.Errorf("Sources[%d].LegacyUpdateTime must be omitted", i)
}
} }
if legacyWrite { if legacyWrite {

View File

@ -21,6 +21,14 @@ func generateUUID() (ret string) {
} }
func TestServiceIntentionsConfigEntry(t *testing.T) { func TestServiceIntentionsConfigEntry(t *testing.T) {
var (
testLocation = time.FixedZone("UTC-8", -8*60*60)
testTimeA = time.Date(1955, 11, 5, 6, 15, 0, 0, testLocation)
testTimeB = time.Date(1985, 10, 26, 1, 35, 0, 0, testLocation)
testTimeC = time.Date(2015, 10, 21, 16, 29, 0, 0, testLocation)
)
type testcase struct { type testcase struct {
entry *ServiceIntentionsConfigEntry entry *ServiceIntentionsConfigEntry
legacy bool legacy bool
@ -126,9 +134,11 @@ func TestServiceIntentionsConfigEntry(t *testing.T) {
Name: "test", Name: "test",
Sources: []*SourceIntention{ Sources: []*SourceIntention{
{ {
LegacyID: legacyIDs[0], LegacyID: legacyIDs[0],
Name: "foo", Name: "foo",
Action: IntentionActionAllow, Action: IntentionActionAllow,
LegacyCreateTime: &testTimeA,
LegacyUpdateTime: &testTimeA,
}, },
}, },
Meta: map[string]string{ Meta: map[string]string{
@ -198,10 +208,12 @@ func TestServiceIntentionsConfigEntry(t *testing.T) {
Name: "test", Name: "test",
Sources: []*SourceIntention{ Sources: []*SourceIntention{
{ {
LegacyID: legacyIDs[0], LegacyID: legacyIDs[0],
Name: "foo", Name: "foo",
Action: IntentionActionAllow, Action: IntentionActionAllow,
Description: strings.Repeat("x", 512), Description: strings.Repeat("x", 512),
LegacyCreateTime: &testTimeA,
LegacyUpdateTime: &testTimeA,
LegacyMeta: map[string]string{ // stray Meta will be dropped LegacyMeta: map[string]string{ // stray Meta will be dropped
"old": "data", "old": "data",
}, },
@ -217,10 +229,12 @@ func TestServiceIntentionsConfigEntry(t *testing.T) {
Name: "test", Name: "test",
Sources: []*SourceIntention{ Sources: []*SourceIntention{
{ {
LegacyID: legacyIDs[0], LegacyID: legacyIDs[0],
Name: "foo", Name: "foo",
Action: IntentionActionAllow, Action: IntentionActionAllow,
LegacyMeta: makeStringMap(65, 5, 5), LegacyCreateTime: &testTimeA,
LegacyUpdateTime: &testTimeA,
LegacyMeta: makeStringMap(65, 5, 5),
}, },
}, },
}, },
@ -233,10 +247,12 @@ func TestServiceIntentionsConfigEntry(t *testing.T) {
Name: "test", Name: "test",
Sources: []*SourceIntention{ Sources: []*SourceIntention{
{ {
LegacyID: legacyIDs[0], LegacyID: legacyIDs[0],
Name: "foo", Name: "foo",
Action: IntentionActionAllow, Action: IntentionActionAllow,
LegacyMeta: makeStringMap(64, 129, 5), LegacyCreateTime: &testTimeA,
LegacyUpdateTime: &testTimeA,
LegacyMeta: makeStringMap(64, 129, 5),
}, },
}, },
}, },
@ -249,10 +265,12 @@ func TestServiceIntentionsConfigEntry(t *testing.T) {
Name: "test", Name: "test",
Sources: []*SourceIntention{ Sources: []*SourceIntention{
{ {
LegacyID: legacyIDs[0], LegacyID: legacyIDs[0],
Name: "foo", Name: "foo",
Action: IntentionActionAllow, Action: IntentionActionAllow,
LegacyMeta: makeStringMap(64, 128, 513), LegacyCreateTime: &testTimeA,
LegacyUpdateTime: &testTimeA,
LegacyMeta: makeStringMap(64, 128, 513),
}, },
}, },
}, },
@ -265,10 +283,12 @@ func TestServiceIntentionsConfigEntry(t *testing.T) {
Name: "test", Name: "test",
Sources: []*SourceIntention{ Sources: []*SourceIntention{
{ {
LegacyID: legacyIDs[0], LegacyID: legacyIDs[0],
Name: "foo", Name: "foo",
Action: IntentionActionAllow, Action: IntentionActionAllow,
LegacyMeta: makeStringMap(64, 128, 512), LegacyCreateTime: &testTimeA,
LegacyUpdateTime: &testTimeA,
LegacyMeta: makeStringMap(64, 128, 512),
}, },
}, },
}, },
@ -280,9 +300,11 @@ func TestServiceIntentionsConfigEntry(t *testing.T) {
Name: "test", Name: "test",
Sources: []*SourceIntention{ Sources: []*SourceIntention{
{ {
Name: "foo", Name: "foo",
Action: IntentionActionAllow, Action: IntentionActionAllow,
Description: strings.Repeat("x", 512), Description: strings.Repeat("x", 512),
LegacyCreateTime: &testTimeA,
LegacyUpdateTime: &testTimeA,
}, },
}, },
}, },
@ -1008,8 +1030,10 @@ func TestServiceIntentionsConfigEntry(t *testing.T) {
Action: IntentionActionDeny, Action: IntentionActionDeny,
}, },
{ {
Name: "foo", Name: "foo",
Action: IntentionActionAllow, Action: IntentionActionAllow,
LegacyCreateTime: &testTimeA, // stray times will be dropped
LegacyUpdateTime: &testTimeA,
}, },
{ {
Name: "bar", Name: "bar",
@ -1059,9 +1083,11 @@ func TestServiceIntentionsConfigEntry(t *testing.T) {
Name: "test", Name: "test",
Sources: []*SourceIntention{ Sources: []*SourceIntention{
{ {
Name: WildcardSpecifier, Name: WildcardSpecifier,
Action: IntentionActionDeny, Action: IntentionActionDeny,
LegacyID: legacyIDs[0], LegacyID: legacyIDs[0],
LegacyCreateTime: &testTimeA,
LegacyUpdateTime: &testTimeA,
}, },
{ {
Name: "foo", Name: "foo",
@ -1071,23 +1097,27 @@ func TestServiceIntentionsConfigEntry(t *testing.T) {
"key1": "val1", "key1": "val1",
"key2": "val2", "key2": "val2",
}, },
LegacyCreateTime: &testTimeB,
LegacyUpdateTime: &testTimeB,
}, },
{ {
Name: "bar", Name: "bar",
Action: IntentionActionDeny, Action: IntentionActionDeny,
LegacyID: legacyIDs[2], LegacyID: legacyIDs[2],
LegacyCreateTime: &testTimeC,
LegacyUpdateTime: &testTimeC,
}, },
}, },
}, },
check: func(t *testing.T, entry *ServiceIntentionsConfigEntry) { check: func(t *testing.T, entry *ServiceIntentionsConfigEntry) {
require.Len(t, entry.Sources, 3) require.Len(t, entry.Sources, 3)
assert.False(t, entry.Sources[0].LegacyCreateTime.IsZero()) // assert.False(t, entry.Sources[0].LegacyCreateTime.IsZero())
assert.False(t, entry.Sources[0].LegacyUpdateTime.IsZero()) // assert.False(t, entry.Sources[0].LegacyUpdateTime.IsZero())
assert.False(t, entry.Sources[1].LegacyCreateTime.IsZero()) // assert.False(t, entry.Sources[1].LegacyCreateTime.IsZero())
assert.False(t, entry.Sources[1].LegacyUpdateTime.IsZero()) // assert.False(t, entry.Sources[1].LegacyUpdateTime.IsZero())
assert.False(t, entry.Sources[2].LegacyCreateTime.IsZero()) // assert.False(t, entry.Sources[2].LegacyCreateTime.IsZero())
assert.False(t, entry.Sources[2].LegacyUpdateTime.IsZero()) // assert.False(t, entry.Sources[2].LegacyUpdateTime.IsZero())
assert.Equal(t, []*SourceIntention{ assert.Equal(t, []*SourceIntention{
{ {
@ -1299,6 +1329,8 @@ func TestMigrateIntentions(t *testing.T) {
LegacyMeta: map[string]string{ LegacyMeta: map[string]string{
"key1": "val1", "key1": "val1",
}, },
LegacyCreateTime: &anyTime,
LegacyUpdateTime: &anyTime,
}, },
}, },
}, },
@ -1349,6 +1381,8 @@ func TestMigrateIntentions(t *testing.T) {
LegacyMeta: map[string]string{ LegacyMeta: map[string]string{
"key1": "val1", "key1": "val1",
}, },
LegacyCreateTime: &anyTime,
LegacyUpdateTime: &anyTime,
}, },
{ {
LegacyID: legacyIDs[1], LegacyID: legacyIDs[1],
@ -1359,6 +1393,8 @@ func TestMigrateIntentions(t *testing.T) {
LegacyMeta: map[string]string{ LegacyMeta: map[string]string{
"key2": "val2", "key2": "val2",
}, },
LegacyCreateTime: &anyTime,
LegacyUpdateTime: &anyTime,
}, },
}, },
}, },
@ -1409,6 +1445,8 @@ func TestMigrateIntentions(t *testing.T) {
LegacyMeta: map[string]string{ LegacyMeta: map[string]string{
"key1": "val1", "key1": "val1",
}, },
LegacyCreateTime: &anyTime,
LegacyUpdateTime: &anyTime,
}, },
}, },
}, },
@ -1425,6 +1463,8 @@ func TestMigrateIntentions(t *testing.T) {
LegacyMeta: map[string]string{ LegacyMeta: map[string]string{
"key2": "val2", "key2": "val2",
}, },
LegacyCreateTime: &anyTime,
LegacyUpdateTime: &anyTime,
}, },
}, },
}, },

View File

@ -440,6 +440,9 @@ func (x *Intention) ToConfigEntry(legacy bool) *ServiceIntentionsConfigEntry {
} }
func (x *Intention) ToSourceIntention(legacy bool) *SourceIntention { func (x *Intention) ToSourceIntention(legacy bool) *SourceIntention {
ct := x.CreatedAt // copy
ut := x.UpdatedAt
src := &SourceIntention{ src := &SourceIntention{
Name: x.SourceName, Name: x.SourceName,
EnterpriseMeta: *x.SourceEnterpriseMeta(), EnterpriseMeta: *x.SourceEnterpriseMeta(),
@ -450,8 +453,8 @@ func (x *Intention) ToSourceIntention(legacy bool) *SourceIntention {
Type: x.SourceType, Type: x.SourceType,
Description: x.Description, Description: x.Description,
LegacyMeta: x.Meta, LegacyMeta: x.Meta,
LegacyCreateTime: nil, // Ignore LegacyCreateTime: &ct,
LegacyUpdateTime: nil, // Ignore LegacyUpdateTime: &ut,
} }
if !legacy { if !legacy {
src.Permissions = x.Permissions src.Permissions = x.Permissions
@ -522,13 +525,30 @@ type IntentionRequest struct {
Op IntentionOp Op IntentionOp
// Intention is the intention. // Intention is the intention.
//
// This is mutually exclusive with the Mutation field.
Intention *Intention Intention *Intention
// Mutation is a change to make to an Intention.
//
// This is mutually exclusive with the Intention field.
//
// This field is only set by the leader before writing to the raft log and
// is not settable via the API or an RPC.
Mutation *IntentionMutation
// WriteRequest is a common struct containing ACL tokens and other // WriteRequest is a common struct containing ACL tokens and other
// write-related common elements for requests. // write-related common elements for requests.
WriteRequest WriteRequest
} }
type IntentionMutation struct {
ID string
Destination ServiceName
Source ServiceName
Value *SourceIntention
}
// RequestDatacenter returns the datacenter for a given request. // RequestDatacenter returns the datacenter for a given request.
func (q *IntentionRequest) RequestDatacenter() string { func (q *IntentionRequest) RequestDatacenter() string {
return q.Datacenter return q.Datacenter

File diff suppressed because one or more lines are too long

View File

@ -158,15 +158,6 @@ const (
// configured to use TLS. Any other value indicates that it was not setup in // configured to use TLS. Any other value indicates that it was not setup in
// that manner. // that manner.
MemberTagValueUseTLS = "1" MemberTagValueUseTLS = "1"
// MemberTagKeyReadReplica is the key used to indicate that the member is a read
// replica server (will remain a Raft non-voter).
// Read Replicas are a Consul Enterprise feature.
MemberTagKeyReadReplica = "nonvoter"
// MemberTagValueReadReplica is the value of the MemberTagKeyReadReplica key when
// the member is in fact a read-replica. Any other value indicates that it is not.
// Read Replicas are a Consul Enterprise feature.
MemberTagValueReadReplica = "1"
) )
type MemberACLMode string type MemberACLMode string

View File

@ -254,6 +254,11 @@ type QueryMeta struct {
// CacheAge is set if request was ?cached and indicates how stale the cached // CacheAge is set if request was ?cached and indicates how stale the cached
// response is. // response is.
CacheAge time.Duration CacheAge time.Duration
// DefaultACLPolicy is used to control the ACL interaction when there is no
// defined policy. This can be "allow" which means ACLs are used to
// deny-list, or "deny" which means ACLs are allow-lists.
DefaultACLPolicy string
} }
// WriteMeta is used to return meta data about a write // WriteMeta is used to return meta data about a write
@ -962,6 +967,12 @@ func parseQueryMeta(resp *http.Response, q *QueryMeta) error {
q.AddressTranslationEnabled = false q.AddressTranslationEnabled = false
} }
// Parse X-Consul-Default-ACL-Policy
switch v := header.Get("X-Consul-Default-ACL-Policy"); v {
case "allow", "deny":
q.DefaultACLPolicy = v
}
// Parse Cache info // Parse Cache info
if cacheStr := header.Get("X-Cache"); cacheStr != "" { if cacheStr := header.Get("X-Cache"); cacheStr != "" {
q.CacheHit = strings.EqualFold(cacheStr, "HIT") q.CacheHit = strings.EqualFold(cacheStr, "HIT")

View File

@ -840,6 +840,7 @@ func TestAPI_ParseQueryMeta(t *testing.T) {
resp.Header.Set("X-Consul-LastContact", "80") resp.Header.Set("X-Consul-LastContact", "80")
resp.Header.Set("X-Consul-KnownLeader", "true") resp.Header.Set("X-Consul-KnownLeader", "true")
resp.Header.Set("X-Consul-Translate-Addresses", "true") resp.Header.Set("X-Consul-Translate-Addresses", "true")
resp.Header.Set("X-Consul-Default-ACL-Policy", "deny")
qm := &QueryMeta{} qm := &QueryMeta{}
if err := parseQueryMeta(resp, qm); err != nil { if err := parseQueryMeta(resp, qm); err != nil {
@ -858,6 +859,9 @@ func TestAPI_ParseQueryMeta(t *testing.T) {
if !qm.AddressTranslationEnabled { if !qm.AddressTranslationEnabled {
t.Fatalf("Bad: %v", qm) t.Fatalf("Bad: %v", qm)
} }
if qm.DefaultACLPolicy != "deny" {
t.Fatalf("Bad: %v", qm)
}
} }
func TestAPI_UnixSocket(t *testing.T) { func TestAPI_UnixSocket(t *testing.T) {

View File

@ -114,7 +114,7 @@ type OperatorHealthReply struct {
type AutopilotState struct { type AutopilotState struct {
Healthy bool Healthy bool
FailureTolerance int FailureTolerance int
OptimisitcFailureTolerance int OptimisticFailureTolerance int
Servers map[string]AutopilotServer Servers map[string]AutopilotServer
Leader string Leader string
@ -137,7 +137,7 @@ type AutopilotServer struct {
StableSince time.Time StableSince time.Time
RedundancyZone string `json:",omitempty"` RedundancyZone string `json:",omitempty"`
UpgradeVersion string `json:",omitempty"` UpgradeVersion string `json:",omitempty"`
ReadReplica bool `json:",omitempty"` ReadReplica bool
Status AutopilotServerStatus Status AutopilotServerStatus
Meta map[string]string Meta map[string]string
NodeType AutopilotServerType NodeType AutopilotServerType

View File

@ -1,4 +1,4 @@
ARG GOLANG_VERSION=1.14.11 ARG GOLANG_VERSION=1.15.5
FROM golang:${GOLANG_VERSION} FROM golang:${GOLANG_VERSION}
ARG GOTOOLS="github.com/elazarl/go-bindata-assetfs/... \ ARG GOTOOLS="github.com/elazarl/go-bindata-assetfs/... \

View File

@ -1,6 +1,6 @@
FROM travisci/ci-garnet:packer-1512502276-986baf0 FROM travisci/ci-garnet:packer-1512502276-986baf0
ENV GOLANG_VERSION 1.14.11 ENV GOLANG_VERSION 1.15.5
RUN mkdir -p /home/travis/go && chown -R travis /home/travis/go RUN mkdir -p /home/travis/go && chown -R travis /home/travis/go

View File

@ -85,6 +85,7 @@ import (
operauto "github.com/hashicorp/consul/command/operator/autopilot" operauto "github.com/hashicorp/consul/command/operator/autopilot"
operautoget "github.com/hashicorp/consul/command/operator/autopilot/get" operautoget "github.com/hashicorp/consul/command/operator/autopilot/get"
operautoset "github.com/hashicorp/consul/command/operator/autopilot/set" operautoset "github.com/hashicorp/consul/command/operator/autopilot/set"
operautostate "github.com/hashicorp/consul/command/operator/autopilot/state"
operraft "github.com/hashicorp/consul/command/operator/raft" operraft "github.com/hashicorp/consul/command/operator/raft"
operraftlist "github.com/hashicorp/consul/command/operator/raft/listpeers" operraftlist "github.com/hashicorp/consul/command/operator/raft/listpeers"
operraftremove "github.com/hashicorp/consul/command/operator/raft/removepeer" operraftremove "github.com/hashicorp/consul/command/operator/raft/removepeer"
@ -202,6 +203,7 @@ func init() {
Register("operator autopilot", func(cli.Ui) (cli.Command, error) { return operauto.New(), nil }) Register("operator autopilot", func(cli.Ui) (cli.Command, error) { return operauto.New(), nil })
Register("operator autopilot get-config", func(ui cli.Ui) (cli.Command, error) { return operautoget.New(ui), nil }) Register("operator autopilot get-config", func(ui cli.Ui) (cli.Command, error) { return operautoget.New(ui), nil })
Register("operator autopilot set-config", func(ui cli.Ui) (cli.Command, error) { return operautoset.New(ui), nil }) Register("operator autopilot set-config", func(ui cli.Ui) (cli.Command, error) { return operautoset.New(ui), nil })
Register("operator autopilot state", func(ui cli.Ui) (cli.Command, error) { return operautostate.New(ui), nil })
Register("operator raft", func(cli.Ui) (cli.Command, error) { return operraft.New(), nil }) Register("operator raft", func(cli.Ui) (cli.Command, error) { return operraft.New(), nil })
Register("operator raft list-peers", func(ui cli.Ui) (cli.Command, error) { return operraftlist.New(ui), nil }) Register("operator raft list-peers", func(ui cli.Ui) (cli.Command, error) { return operraftlist.New(ui), nil })
Register("operator raft remove-peer", func(ui cli.Ui) (cli.Command, error) { return operraftremove.New(ui), nil }) Register("operator raft remove-peer", func(ui cli.Ui) (cli.Command, error) { return operraftremove.New(ui), nil })

View File

@ -44,7 +44,7 @@ func TestConfigUtil_Values(t *testing.T) {
{ {
`{ "duration": "nope" }`, `{ "duration": "nope" }`,
"", "",
"invalid duration nope", `invalid duration "nope"`,
}, },
{ {
`{ "string": 123 }`, `{ "string": 123 }`,

View File

@ -0,0 +1,206 @@
package state
import (
"bytes"
"encoding/json"
"fmt"
"sort"
"github.com/hashicorp/consul/api"
)
const (
PrettyFormat string = "pretty"
JSONFormat string = "json"
)
// Formatter defines methods provided by an autopilot state output formatter
type Formatter interface {
FormatState(state *api.AutopilotState) (string, error)
}
// GetSupportedFormats returns supported formats
func GetSupportedFormats() []string {
return []string{PrettyFormat, JSONFormat}
}
// NewFormatter returns Formatter implementation
func NewFormatter(format string) (formatter Formatter, err error) {
switch format {
case PrettyFormat:
formatter = newPrettyFormatter()
case JSONFormat:
formatter = newJSONFormatter()
default:
err = fmt.Errorf("Unknown format: %s", format)
}
return formatter, err
}
func newPrettyFormatter() Formatter {
return &prettyFormatter{}
}
type prettyFormatter struct {
}
func outputStringSlice(buffer *bytes.Buffer, indent string, values []string) {
for _, val := range values {
buffer.WriteString(fmt.Sprintf("%s%s\n", indent, val))
}
}
type mapOutput struct {
key string
value string
}
func formatZone(zoneName string, zone *api.AutopilotZone) string {
var buffer bytes.Buffer
buffer.WriteString(fmt.Sprintf(" %s:\n", zoneName))
buffer.WriteString(fmt.Sprintf(" Failure Tolerance: %d\n", zone.FailureTolerance))
buffer.WriteString(" Voters:\n")
outputStringSlice(&buffer, " ", zone.Voters)
buffer.WriteString(" Servers:\n")
outputStringSlice(&buffer, " ", zone.Servers)
return buffer.String()
}
func formatServer(srv *api.AutopilotServer) string {
var buffer bytes.Buffer
buffer.WriteString(fmt.Sprintf(" %s\n", srv.ID))
buffer.WriteString(fmt.Sprintf(" Name: %s\n", srv.Name))
buffer.WriteString(fmt.Sprintf(" Address: %s\n", srv.Address))
buffer.WriteString(fmt.Sprintf(" Version: %s\n", srv.Version))
buffer.WriteString(fmt.Sprintf(" Status: %s\n", srv.Status))
buffer.WriteString(fmt.Sprintf(" Node Type: %s\n", srv.NodeType))
buffer.WriteString(fmt.Sprintf(" Node Status: %s\n", srv.NodeStatus))
buffer.WriteString(fmt.Sprintf(" Healthy: %t\n", srv.Healthy))
buffer.WriteString(fmt.Sprintf(" Last Contact: %s\n", srv.LastContact.String()))
buffer.WriteString(fmt.Sprintf(" Last Term: %d\n", srv.LastTerm))
buffer.WriteString(fmt.Sprintf(" Last Index: %d\n", srv.LastIndex))
if srv.RedundancyZone != "" {
buffer.WriteString(fmt.Sprintf(" Redundancy Zone: %s\n", srv.RedundancyZone))
}
if srv.UpgradeVersion != "" {
buffer.WriteString(fmt.Sprintf(" Upgrade Version: %s\n", srv.UpgradeVersion))
}
if srv.ReadReplica {
buffer.WriteString(fmt.Sprintf(" Read Replica: %t\n", srv.ReadReplica))
}
if len(srv.Meta) > 0 {
buffer.WriteString(fmt.Sprintf(" Meta\n"))
var outputs []mapOutput
for k, v := range srv.Meta {
outputs = append(outputs, mapOutput{key: k, value: fmt.Sprintf(" %q: %q\n", k, v)})
}
sort.Slice(outputs, func(i, j int) bool {
return outputs[i].key < outputs[j].key
})
for _, output := range outputs {
buffer.WriteString(output.value)
}
}
return buffer.String()
}
func (f *prettyFormatter) FormatState(state *api.AutopilotState) (string, error) {
var buffer bytes.Buffer
buffer.WriteString(fmt.Sprintf("Healthy: %t\n", state.Healthy))
buffer.WriteString(fmt.Sprintf("Failure Tolerance: %d\n", state.FailureTolerance))
buffer.WriteString(fmt.Sprintf("Optimistic Failure Tolerance: %d\n", state.OptimisticFailureTolerance))
buffer.WriteString(fmt.Sprintf("Leader: %s\n", state.Leader))
buffer.WriteString("Voters:\n")
outputStringSlice(&buffer, " ", state.Voters)
if len(state.ReadReplicas) > 0 {
buffer.WriteString("Read Replicas:\n")
outputStringSlice(&buffer, " ", state.ReadReplicas)
}
if len(state.RedundancyZones) > 0 {
var outputs []mapOutput
buffer.WriteString("Redundancy Zones:\n")
for zoneName, zone := range state.RedundancyZones {
outputs = append(outputs, mapOutput{key: zoneName, value: formatZone(zoneName, &zone)})
}
sort.Slice(outputs, func(i, j int) bool {
return outputs[i].key < outputs[j].key
})
for _, output := range outputs {
buffer.WriteString(output.value)
}
}
if state.Upgrade != nil {
u := state.Upgrade
buffer.WriteString("Upgrade:\n")
buffer.WriteString(fmt.Sprintf(" Status: %s\n", u.Status))
buffer.WriteString(fmt.Sprintf(" Target Version: %s\n", u.TargetVersion))
if len(u.TargetVersionVoters) > 0 {
buffer.WriteString(" Target Version Voters:\n")
outputStringSlice(&buffer, " ", u.TargetVersionVoters)
}
if len(u.TargetVersionNonVoters) > 0 {
buffer.WriteString(" Target Version Non-Voters:\n")
outputStringSlice(&buffer, " ", u.TargetVersionNonVoters)
}
if len(u.TargetVersionReadReplicas) > 0 {
buffer.WriteString(" Target Version ReadReplicas:\n")
outputStringSlice(&buffer, " ", u.TargetVersionReadReplicas)
}
if len(u.OtherVersionVoters) > 0 {
buffer.WriteString(" Other Version Voters:\n")
outputStringSlice(&buffer, " ", u.OtherVersionVoters)
}
if len(u.OtherVersionNonVoters) > 0 {
buffer.WriteString(" Other Version Non-Voters:\n")
outputStringSlice(&buffer, " ", u.OtherVersionNonVoters)
}
if len(u.OtherVersionReadReplicas) > 0 {
buffer.WriteString(" Other Version ReadReplicas:\n")
outputStringSlice(&buffer, " ", u.OtherVersionReadReplicas)
}
}
buffer.WriteString("Servers:\n")
var outputs []mapOutput
for id, srv := range state.Servers {
outputs = append(outputs, mapOutput{key: id, value: formatServer(&srv)})
}
sort.Slice(outputs, func(i, j int) bool {
return outputs[i].key < outputs[j].key
})
for _, output := range outputs {
buffer.WriteString(output.value)
}
return buffer.String(), nil
}
func newJSONFormatter() Formatter {
return &jsonFormatter{}
}
type jsonFormatter struct {
}
func (f *jsonFormatter) FormatState(state *api.AutopilotState) (string, error) {
b, err := json.MarshalIndent(state, "", " ")
if err != nil {
return "", fmt.Errorf("Failed to marshal token: %v", err)
}
return string(b), nil
}

View File

@ -0,0 +1,99 @@
package state
import (
"flag"
"fmt"
"strings"
"github.com/hashicorp/consul/api"
"github.com/hashicorp/consul/command/flags"
"github.com/mitchellh/cli"
)
func New(ui cli.Ui) *cmd {
c := &cmd{UI: ui}
c.init()
return c
}
type cmd struct {
UI cli.Ui
flags *flag.FlagSet
http *flags.HTTPFlags
help string
format string
}
func (c *cmd) init() {
c.flags = flag.NewFlagSet("", flag.ContinueOnError)
c.flags.StringVar(
&c.format,
"format",
PrettyFormat,
fmt.Sprintf("Output format {%s}", strings.Join(GetSupportedFormats(), "|")),
)
c.http = &flags.HTTPFlags{}
flags.Merge(c.flags, c.http.ClientFlags())
flags.Merge(c.flags, c.http.ServerFlags())
c.help = flags.Usage(help, c.flags)
}
func (c *cmd) Run(args []string) int {
if err := c.flags.Parse(args); err != nil {
if err == flag.ErrHelp {
return 0
}
c.UI.Error(fmt.Sprintf("Failed to parse args: %v", err))
return 1
}
// Set up a client.
client, err := c.http.APIClient()
if err != nil {
c.UI.Error(fmt.Sprintf("Error initializing client: %s", err))
return 1
}
// Fetch the current configuration.
opts := &api.QueryOptions{
AllowStale: c.http.Stale(),
}
state, err := client.Operator().AutopilotState(opts)
if err != nil {
c.UI.Error(fmt.Sprintf("Error querying Autopilot state: %s", err))
return 1
}
formatter, err := NewFormatter(c.format)
if err != nil {
c.UI.Error(err.Error())
return 1
}
out, err := formatter.FormatState(state)
if err != nil {
c.UI.Error(err.Error())
return 1
}
if out != "" {
c.UI.Info(out)
}
return 0
}
func (c *cmd) Synopsis() string {
return synopsis
}
func (c *cmd) Help() string {
return c.help
}
const synopsis = "Display the current Autopilot configuration"
const help = `
Usage: consul operator autopilot get-config [options]
Displays the current Autopilot configuration.
`

View File

@ -0,0 +1,126 @@
package state
import (
"encoding/json"
"flag"
"io/ioutil"
"path/filepath"
"strings"
"testing"
"github.com/hashicorp/consul/agent"
"github.com/hashicorp/consul/api"
"github.com/hashicorp/consul/testrpc"
"github.com/mitchellh/cli"
"github.com/stretchr/testify/require"
)
// update allows golden files to be updated based on the current output.
var update = flag.Bool("update", false, "update golden files")
// golden reads and optionally writes the expected data to the golden file,
// returning the contents as a string.
func golden(t *testing.T, name, got string) string {
t.Helper()
golden := filepath.Join("testdata", name+".golden")
if *update && got != "" {
err := ioutil.WriteFile(golden, []byte(got), 0644)
require.NoError(t, err)
}
expected, err := ioutil.ReadFile(golden)
require.NoError(t, err)
return string(expected)
}
func TestStateCommand_noTabs(t *testing.T) {
t.Parallel()
if strings.ContainsRune(New(cli.NewMockUi()).Help(), '\t') {
t.Fatal("help has tabs")
}
}
func TestStateCommand_Pretty(t *testing.T) {
t.Parallel()
a := agent.NewTestAgent(t, `
node_id = "f0427127-7531-455a-b651-f1ea1d8451f0"
`)
defer a.Shutdown()
testrpc.WaitForLeader(t, a.RPC, "dc1")
ui := cli.NewMockUi()
cmd := New(ui)
args := []string{
"-http-addr=" + a.HTTPAddr(),
}
code := cmd.Run(args)
require.Empty(t, ui.ErrorWriter.String())
require.Equal(t, code, 0)
output := ui.OutputWriter.String()
// Just a few quick checks to ensure we got output
// the output formatter will be tested in another test.
require.Regexp(t, `^Healthy:`, output)
require.Regexp(t, `(?m)^Leader:`, output)
}
func TestStateCommand_JSON(t *testing.T) {
t.Parallel()
a := agent.NewTestAgent(t, "")
defer a.Shutdown()
testrpc.WaitForLeader(t, a.RPC, "dc1")
ui := cli.NewMockUi()
cmd := New(ui)
args := []string{
"-http-addr=" + a.HTTPAddr(),
"-format=json",
}
code := cmd.Run(args)
require.Empty(t, ui.ErrorWriter.String())
require.Equal(t, code, 0)
output := ui.OutputWriter.String()
var state api.AutopilotState
require.NoError(t, json.Unmarshal([]byte(output), &state))
}
func TestStateCommand_Formatter(t *testing.T) {
cases := []string{
"oss",
"enterprise",
}
for _, name := range cases {
t.Run(name, func(t *testing.T) {
statePath := filepath.Join("testdata", name, "state.json")
input, err := ioutil.ReadFile(statePath)
require.NoError(t, err)
var state api.AutopilotState
require.NoError(t, json.Unmarshal(input, &state))
for _, format := range GetSupportedFormats() {
t.Run(format, func(t *testing.T) {
formatter, err := NewFormatter(format)
require.NoError(t, err)
actual, err := formatter.FormatState(&state)
require.NoError(t, err)
expected := golden(t, filepath.Join(name, format), actual)
require.Equal(t, expected, actual)
})
}
})
}
}

View File

@ -0,0 +1,401 @@
{
"Healthy": true,
"FailureTolerance": 1,
"OptimisticFailureTolerance": 0,
"Servers": {
"1b8044f6-1d25-4a83-9662-acdf404341d2": {
"ID": "1b8044f6-1d25-4a83-9662-acdf404341d2",
"Name": "node6.new",
"Address": "198.18.1.6:8300",
"NodeStatus": "alive",
"Version": "1.9.0",
"LastContact": "1ms",
"LastTerm": 3,
"LastIndex": 41,
"Healthy": true,
"StableSince": "2020-11-06T14:52:00Z",
"RedundancyZone": "zone3",
"UpgradeVersion": "2.0.0",
"ReadReplica": false,
"Status": "non-voter",
"Meta": {
"bar": "baz",
"upgrade": "2.0.0",
"zone": "zone3"
},
"NodeType": "zone-standby"
},
"1baeb453-ad9e-489a-bbfe-53bee097aec8": {
"ID": "1baeb453-ad9e-489a-bbfe-53bee097aec8",
"Name": "node4",
"Address": "198.18.0.4:8300",
"NodeStatus": "alive",
"Version": "1.9.0",
"LastContact": "1ms",
"LastTerm": 3,
"LastIndex": 41,
"Healthy": true,
"StableSince": "2020-11-06T14:52:00Z",
"RedundancyZone": "zone2",
"UpgradeVersion": "1.0.0",
"ReadReplica": false,
"Status": "non-voter",
"Meta": {
"bar": "baz",
"upgrade": "1.0.0",
"zone": "zone2"
},
"NodeType": "zone-standby"
},
"3044d109-f028-4489-b59c-f267afe408f2": {
"ID": "3044d109-f028-4489-b59c-f267afe408f2",
"Name": "node2.new",
"Address": "198.18.1.2:8300",
"NodeStatus": "alive",
"Version": "1.9.0",
"LastContact": "1ms",
"LastTerm": 3,
"LastIndex": 41,
"Healthy": true,
"StableSince": "2020-11-06T14:52:00Z",
"RedundancyZone": "zone1",
"UpgradeVersion": "2.0.0",
"ReadReplica": false,
"Status": "non-voter",
"Meta": {
"bar": "baz",
"upgrade": "2.0.0",
"zone": "zone1"
},
"NodeType": "zone-standby"
},
"4691e516-6989-4a16-8f55-12ff2226a3c9": {
"ID": "4691e516-6989-4a16-8f55-12ff2226a3c9",
"Name": "node4.new",
"Address": "198.18.1.4:8300",
"NodeStatus": "alive",
"Version": "1.9.0",
"LastContact": "1ms",
"LastTerm": 3,
"LastIndex": 41,
"Healthy": true,
"StableSince": "2020-11-06T14:52:00Z",
"RedundancyZone": "zone2",
"UpgradeVersion": "2.0.0",
"ReadReplica": false,
"Status": "non-voter",
"Meta": {
"bar": "baz",
"upgrade": "2.0.0",
"zone": "zone2"
},
"NodeType": "zone-standby"
},
"4c42fe86-321d-4e8f-be0d-c261e6cfc453": {
"ID": "4c42fe86-321d-4e8f-be0d-c261e6cfc453",
"Name": "node5",
"Address": "198.18.0.5:8300",
"NodeStatus": "alive",
"Version": "1.9.0",
"LastContact": "1ms",
"LastTerm": 3,
"LastIndex": 42,
"Healthy": true,
"StableSince": "2020-11-06T14:51:00Z",
"RedundancyZone": "zone3",
"UpgradeVersion": "1.0.0",
"ReadReplica": false,
"Status": "voter",
"Meta": {
"foo": "bar",
"upgrade": "1.0.0",
"zone": "zone3"
},
"NodeType": "zone-voter"
},
"6ca5a41c-6162-41c1-b4eb-09082efe206f": {
"ID": "6ca5a41c-6162-41c1-b4eb-09082efe206f",
"Name": "node2",
"Address": "198.18.0.2:8300",
"NodeStatus": "alive",
"Version": "1.9.0",
"LastContact": "1ms",
"LastTerm": 3,
"LastIndex": 41,
"Healthy": true,
"StableSince": "2020-11-06T14:52:00Z",
"RedundancyZone": "zone1",
"UpgradeVersion": "1.0.0",
"ReadReplica": false,
"Status": "non-voter",
"Meta": {
"bar": "baz",
"upgrade": "1.0.0",
"zone": "zone1"
},
"NodeType": "zone-standby"
},
"6e8a37e5-a1c4-4212-a75e-91487d8cda6d": {
"ID": "6e8a37e5-a1c4-4212-a75e-91487d8cda6d",
"Name": "node3",
"Address": "198.18.0.3:8300",
"NodeStatus": "alive",
"Version": "1.9.0",
"LastContact": "1ms",
"LastTerm": 3,
"LastIndex": 42,
"Healthy": true,
"StableSince": "2020-11-06T14:51:00Z",
"RedundancyZone": "zone2",
"UpgradeVersion": "1.0.0",
"ReadReplica": false,
"Status": "voter",
"Meta": {
"foo": "bar",
"upgrade": "1.0.0",
"zone": "zone2"
},
"NodeType": "zone-voter"
},
"746b8782-ce77-41fd-bce0-cce62cca62b4": {
"ID": "746b8782-ce77-41fd-bce0-cce62cca62b4",
"Name": "node6",
"Address": "198.18.0.6:8300",
"NodeStatus": "alive",
"Version": "1.9.0",
"LastContact": "1ms",
"LastTerm": 3,
"LastIndex": 41,
"Healthy": true,
"StableSince": "2020-11-06T14:52:00Z",
"RedundancyZone": "zone3",
"UpgradeVersion": "1.0.0",
"ReadReplica": false,
"Status": "non-voter",
"Meta": {
"bar": "baz",
"upgrade": "1.0.0",
"zone": "zone3"
},
"NodeType": "zone-standby"
},
"79324811-9588-4311-b208-f272e38aaabf": {
"ID": "79324811-9588-4311-b208-f272e38aaabf",
"Name": "node1",
"Address": "198.18.0.1:8300",
"NodeStatus": "alive",
"Version": "1.9.0",
"LastContact": "0s",
"LastTerm": 3,
"LastIndex": 42,
"Healthy": true,
"StableSince": "2020-11-06T14:51:00Z",
"RedundancyZone": "zone1",
"UpgradeVersion": "1.0.0",
"ReadReplica": false,
"Status": "leader",
"Meta": {
"foo": "bar",
"upgrade": "1.0.0",
"zone": "zone1"
},
"NodeType": "zone-voter"
},
"7b02a615-ccce-4251-bda8-b89e0bd4f7c7": {
"ID": "7b02a615-ccce-4251-bda8-b89e0bd4f7c7",
"Name": "read-replica",
"Address": "198.18.0.7:8300",
"NodeStatus": "alive",
"Version": "1.9.0",
"LastContact": "2ms",
"LastTerm": 3,
"LastIndex": 39,
"Healthy": true,
"StableSince": "2020-11-06T14:53:00Z",
"UpgradeVersion": "1.0.0",
"ReadReplica": true,
"Status": "non-voter",
"Meta": {
"baz": "foo",
"version": "1.0.0"
},
"NodeType": "read-replica"
},
"98dfd0fd-504e-4280-8e73-6983a6af1b8c": {
"ID": "98dfd0fd-504e-4280-8e73-6983a6af1b8c",
"Name": "node5.new",
"Address": "198.18.1.5:8300",
"NodeStatus": "alive",
"Version": "1.9.0",
"LastContact": "1ms",
"LastTerm": 3,
"LastIndex": 42,
"Healthy": true,
"StableSince": "2020-11-06T14:51:00Z",
"RedundancyZone": "zone3",
"UpgradeVersion": "2.0.0",
"ReadReplica": false,
"Status": "non-voter",
"Meta": {
"foo": "bar",
"upgrade": "2.0.0",
"zone": "zone3"
},
"NodeType": "zone-standby"
},
"997b0851-37c5-4d65-a477-a8b3a56eea42": {
"ID": "997b0851-37c5-4d65-a477-a8b3a56eea42",
"Name": "node3.new",
"Address": "198.18.1.3:8300",
"NodeStatus": "alive",
"Version": "1.9.0",
"LastContact": "1ms",
"LastTerm": 3,
"LastIndex": 42,
"Healthy": true,
"StableSince": "2020-11-06T14:51:00Z",
"RedundancyZone": "zone2",
"UpgradeVersion": "2.0.0",
"ReadReplica": false,
"Status": "non-voter",
"Meta": {
"foo": "bar",
"upgrade": "2.0.0",
"zone": "zone2"
},
"NodeType": "zone-standby"
},
"de95799e-15a4-4c86-b508-78840554b7cb": {
"ID": "de95799e-15a4-4c86-b508-78840554b7cb",
"Name": "node1.new",
"Address": "198.18.1.1:8300",
"NodeStatus": "alive",
"Version": "1.9.0",
"LastContact": "0s",
"LastTerm": 3,
"LastIndex": 42,
"Healthy": true,
"StableSince": "2020-11-06T14:51:00Z",
"RedundancyZone": "zone1",
"UpgradeVersion": "2.0.0",
"ReadReplica": false,
"Status": "non-voter",
"Meta": {
"foo": "bar",
"upgrade": "2.0.0",
"zone": "zone1"
},
"NodeType": "zone-standby"
}
},
"Leader": "79324811-9588-4311-b208-f272e38aaabf",
"Voters": [
"79324811-9588-4311-b208-f272e38aaabf",
"6e8a37e5-a1c4-4212-a75e-91487d8cda6d",
"4c42fe86-321d-4e8f-be0d-c261e6cfc453"
],
"ReadReplicas": [
"7b02a615-ccce-4251-bda8-b89e0bd4f7c7"
],
"RedundancyZones": {
"zone1": {
"Servers": [
"79324811-9588-4311-b208-f272e38aaabf",
"6ca5a41c-6162-41c1-b4eb-09082efe206f",
"de95799e-15a4-4c86-b508-78840554b7cb",
"3044d109-f028-4489-b59c-f267afe408f2"
],
"Voters": [
"79324811-9588-4311-b208-f272e38aaabf"
],
"FailureTolerance": 3
},
"zone2": {
"Servers": [
"6e8a37e5-a1c4-4212-a75e-91487d8cda6d",
"1baeb453-ad9e-489a-bbfe-53bee097aec8",
"997b0851-37c5-4d65-a477-a8b3a56eea42",
"4691e516-6989-4a16-8f55-12ff2226a3c9"
],
"Voters": [
"6e8a37e5-a1c4-4212-a75e-91487d8cda6d"
],
"FailureTolerance": 3
},
"zone3": {
"Servers": [
"4c42fe86-321d-4e8f-be0d-c261e6cfc453",
"746b8782-ce77-41fd-bce0-cce62cca62b4",
"98dfd0fd-504e-4280-8e73-6983a6af1b8c",
"1b8044f6-1d25-4a83-9662-acdf404341d2"
],
"Voters": [
"4c42fe86-321d-4e8f-be0d-c261e6cfc453"
],
"FailureTolerance": 3
}
},
"Upgrade": {
"Status": "promoting",
"TargetVersion": "2.0.0",
"TargetVersionNonVoters": [
"de95799e-15a4-4c86-b508-78840554b7cb",
"3044d109-f028-4489-b59c-f267afe408f2",
"997b0851-37c5-4d65-a477-a8b3a56eea42",
"4691e516-6989-4a16-8f55-12ff2226a3c9",
"98dfd0fd-504e-4280-8e73-6983a6af1b8c",
"1b8044f6-1d25-4a83-9662-acdf404341d2"
],
"OtherVersionVoters": [
"79324811-9588-4311-b208-f272e38aaabf",
"6e8a37e5-a1c4-4212-a75e-91487d8cda6d",
"4c42fe86-321d-4e8f-be0d-c261e6cfc453"
],
"OtherVersionNonVoters": [
"6ca5a41c-6162-41c1-b4eb-09082efe206f",
"1baeb453-ad9e-489a-bbfe-53bee097aec8",
"746b8782-ce77-41fd-bce0-cce62cca62b4"
],
"OtherVersionReadReplicas": [
"7b02a615-ccce-4251-bda8-b89e0bd4f7c7"
],
"RedundancyZones": {
"zone1": {
"TargetVersionNonVoters": [
"de95799e-15a4-4c86-b508-78840554b7cb",
"3044d109-f028-4489-b59c-f267afe408f2"
],
"OtherVersionVoters": [
"79324811-9588-4311-b208-f272e38aaabf"
],
"OtherVersionNonVoters": [
"6ca5a41c-6162-41c1-b4eb-09082efe206f"
]
},
"zone2": {
"TargetVersionNonVoters": [
"997b0851-37c5-4d65-a477-a8b3a56eea42",
"4691e516-6989-4a16-8f55-12ff2226a3c9"
],
"OtherVersionVoters": [
"6e8a37e5-a1c4-4212-a75e-91487d8cda6d"
],
"OtherVersionNonVoters": [
"1baeb453-ad9e-489a-bbfe-53bee097aec8"
]
},
"zone3": {
"TargetVersionNonVoters": [
"98dfd0fd-504e-4280-8e73-6983a6af1b8c",
"1b8044f6-1d25-4a83-9662-acdf404341d2"
],
"OtherVersionVoters": [
"4c42fe86-321d-4e8f-be0d-c261e6cfc453"
],
"OtherVersionNonVoters": [
"746b8782-ce77-41fd-bce0-cce62cca62b4"
]
}
}
}
}

View File

@ -0,0 +1,279 @@
Healthy: true
Failure Tolerance: 1
Optimistic Failure Tolerance: 0
Leader: 79324811-9588-4311-b208-f272e38aaabf
Voters:
79324811-9588-4311-b208-f272e38aaabf
6e8a37e5-a1c4-4212-a75e-91487d8cda6d
4c42fe86-321d-4e8f-be0d-c261e6cfc453
Read Replicas:
7b02a615-ccce-4251-bda8-b89e0bd4f7c7
Redundancy Zones:
zone1:
Failure Tolerance: 3
Voters:
79324811-9588-4311-b208-f272e38aaabf
Servers:
79324811-9588-4311-b208-f272e38aaabf
6ca5a41c-6162-41c1-b4eb-09082efe206f
de95799e-15a4-4c86-b508-78840554b7cb
3044d109-f028-4489-b59c-f267afe408f2
zone2:
Failure Tolerance: 3
Voters:
6e8a37e5-a1c4-4212-a75e-91487d8cda6d
Servers:
6e8a37e5-a1c4-4212-a75e-91487d8cda6d
1baeb453-ad9e-489a-bbfe-53bee097aec8
997b0851-37c5-4d65-a477-a8b3a56eea42
4691e516-6989-4a16-8f55-12ff2226a3c9
zone3:
Failure Tolerance: 3
Voters:
4c42fe86-321d-4e8f-be0d-c261e6cfc453
Servers:
4c42fe86-321d-4e8f-be0d-c261e6cfc453
746b8782-ce77-41fd-bce0-cce62cca62b4
98dfd0fd-504e-4280-8e73-6983a6af1b8c
1b8044f6-1d25-4a83-9662-acdf404341d2
Upgrade:
Status: promoting
Target Version: 2.0.0
Target Version Non-Voters:
de95799e-15a4-4c86-b508-78840554b7cb
3044d109-f028-4489-b59c-f267afe408f2
997b0851-37c5-4d65-a477-a8b3a56eea42
4691e516-6989-4a16-8f55-12ff2226a3c9
98dfd0fd-504e-4280-8e73-6983a6af1b8c
1b8044f6-1d25-4a83-9662-acdf404341d2
Other Version Voters:
79324811-9588-4311-b208-f272e38aaabf
6e8a37e5-a1c4-4212-a75e-91487d8cda6d
4c42fe86-321d-4e8f-be0d-c261e6cfc453
Other Version Non-Voters:
6ca5a41c-6162-41c1-b4eb-09082efe206f
1baeb453-ad9e-489a-bbfe-53bee097aec8
746b8782-ce77-41fd-bce0-cce62cca62b4
Other Version ReadReplicas:
7b02a615-ccce-4251-bda8-b89e0bd4f7c7
Servers:
1b8044f6-1d25-4a83-9662-acdf404341d2
Name: node6.new
Address: 198.18.1.6:8300
Version: 1.9.0
Status: non-voter
Node Type: zone-standby
Node Status: alive
Healthy: true
Last Contact: 1ms
Last Term: 3
Last Index: 41
Redundancy Zone: zone3
Upgrade Version: 2.0.0
Meta
"bar": "baz"
"upgrade": "2.0.0"
"zone": "zone3"
1baeb453-ad9e-489a-bbfe-53bee097aec8
Name: node4
Address: 198.18.0.4:8300
Version: 1.9.0
Status: non-voter
Node Type: zone-standby
Node Status: alive
Healthy: true
Last Contact: 1ms
Last Term: 3
Last Index: 41
Redundancy Zone: zone2
Upgrade Version: 1.0.0
Meta
"bar": "baz"
"upgrade": "1.0.0"
"zone": "zone2"
3044d109-f028-4489-b59c-f267afe408f2
Name: node2.new
Address: 198.18.1.2:8300
Version: 1.9.0
Status: non-voter
Node Type: zone-standby
Node Status: alive
Healthy: true
Last Contact: 1ms
Last Term: 3
Last Index: 41
Redundancy Zone: zone1
Upgrade Version: 2.0.0
Meta
"bar": "baz"
"upgrade": "2.0.0"
"zone": "zone1"
4691e516-6989-4a16-8f55-12ff2226a3c9
Name: node4.new
Address: 198.18.1.4:8300
Version: 1.9.0
Status: non-voter
Node Type: zone-standby
Node Status: alive
Healthy: true
Last Contact: 1ms
Last Term: 3
Last Index: 41
Redundancy Zone: zone2
Upgrade Version: 2.0.0
Meta
"bar": "baz"
"upgrade": "2.0.0"
"zone": "zone2"
4c42fe86-321d-4e8f-be0d-c261e6cfc453
Name: node5
Address: 198.18.0.5:8300
Version: 1.9.0
Status: voter
Node Type: zone-voter
Node Status: alive
Healthy: true
Last Contact: 1ms
Last Term: 3
Last Index: 42
Redundancy Zone: zone3
Upgrade Version: 1.0.0
Meta
"foo": "bar"
"upgrade": "1.0.0"
"zone": "zone3"
6ca5a41c-6162-41c1-b4eb-09082efe206f
Name: node2
Address: 198.18.0.2:8300
Version: 1.9.0
Status: non-voter
Node Type: zone-standby
Node Status: alive
Healthy: true
Last Contact: 1ms
Last Term: 3
Last Index: 41
Redundancy Zone: zone1
Upgrade Version: 1.0.0
Meta
"bar": "baz"
"upgrade": "1.0.0"
"zone": "zone1"
6e8a37e5-a1c4-4212-a75e-91487d8cda6d
Name: node3
Address: 198.18.0.3:8300
Version: 1.9.0
Status: voter
Node Type: zone-voter
Node Status: alive
Healthy: true
Last Contact: 1ms
Last Term: 3
Last Index: 42
Redundancy Zone: zone2
Upgrade Version: 1.0.0
Meta
"foo": "bar"
"upgrade": "1.0.0"
"zone": "zone2"
746b8782-ce77-41fd-bce0-cce62cca62b4
Name: node6
Address: 198.18.0.6:8300
Version: 1.9.0
Status: non-voter
Node Type: zone-standby
Node Status: alive
Healthy: true
Last Contact: 1ms
Last Term: 3
Last Index: 41
Redundancy Zone: zone3
Upgrade Version: 1.0.0
Meta
"bar": "baz"
"upgrade": "1.0.0"
"zone": "zone3"
79324811-9588-4311-b208-f272e38aaabf
Name: node1
Address: 198.18.0.1:8300
Version: 1.9.0
Status: leader
Node Type: zone-voter
Node Status: alive
Healthy: true
Last Contact: 0s
Last Term: 3
Last Index: 42
Redundancy Zone: zone1
Upgrade Version: 1.0.0
Meta
"foo": "bar"
"upgrade": "1.0.0"
"zone": "zone1"
7b02a615-ccce-4251-bda8-b89e0bd4f7c7
Name: read-replica
Address: 198.18.0.7:8300
Version: 1.9.0
Status: non-voter
Node Type: read-replica
Node Status: alive
Healthy: true
Last Contact: 2ms
Last Term: 3
Last Index: 39
Upgrade Version: 1.0.0
Read Replica: true
Meta
"baz": "foo"
"version": "1.0.0"
98dfd0fd-504e-4280-8e73-6983a6af1b8c
Name: node5.new
Address: 198.18.1.5:8300
Version: 1.9.0
Status: non-voter
Node Type: zone-standby
Node Status: alive
Healthy: true
Last Contact: 1ms
Last Term: 3
Last Index: 42
Redundancy Zone: zone3
Upgrade Version: 2.0.0
Meta
"foo": "bar"
"upgrade": "2.0.0"
"zone": "zone3"
997b0851-37c5-4d65-a477-a8b3a56eea42
Name: node3.new
Address: 198.18.1.3:8300
Version: 1.9.0
Status: non-voter
Node Type: zone-standby
Node Status: alive
Healthy: true
Last Contact: 1ms
Last Term: 3
Last Index: 42
Redundancy Zone: zone2
Upgrade Version: 2.0.0
Meta
"foo": "bar"
"upgrade": "2.0.0"
"zone": "zone2"
de95799e-15a4-4c86-b508-78840554b7cb
Name: node1.new
Address: 198.18.1.1:8300
Version: 1.9.0
Status: non-voter
Node Type: zone-standby
Node Status: alive
Healthy: true
Last Contact: 0s
Last Term: 3
Last Index: 42
Redundancy Zone: zone1
Upgrade Version: 2.0.0
Meta
"foo": "bar"
"upgrade": "2.0.0"
"zone": "zone1"

View File

@ -0,0 +1,389 @@
{
"Healthy": true,
"FailureTolerance": 1,
"OptimisitcFailureTolerance": 10,
"Servers": {
"1b8044f6-1d25-4a83-9662-acdf404341d2": {
"ID": "1b8044f6-1d25-4a83-9662-acdf404341d2",
"Name": "node6.new",
"Address": "198.18.1.6:8300",
"NodeStatus": "alive",
"Version": "1.9.0",
"LastContact": "1ms",
"LastTerm": 3,
"LastIndex": 41,
"Healthy": true,
"StableSince": "2020-11-06T14:52:00Z",
"RedundancyZone": "zone3",
"UpgradeVersion": "2.0.0",
"Status": "non-voter",
"Meta": {
"bar": "baz",
"upgrade": "2.0.0",
"zone": "zone3"
},
"NodeType": "zone-standby"
},
"1baeb453-ad9e-489a-bbfe-53bee097aec8": {
"ID": "1baeb453-ad9e-489a-bbfe-53bee097aec8",
"Name": "node4",
"Address": "198.18.0.4:8300",
"NodeStatus": "alive",
"Version": "1.9.0",
"LastContact": "1ms",
"LastTerm": 3,
"LastIndex": 41,
"Healthy": true,
"StableSince": "2020-11-06T14:52:00Z",
"RedundancyZone": "zone2",
"UpgradeVersion": "1.0.0",
"Status": "non-voter",
"Meta": {
"bar": "baz",
"upgrade": "1.0.0",
"zone": "zone2"
},
"NodeType": "zone-standby"
},
"3044d109-f028-4489-b59c-f267afe408f2": {
"ID": "3044d109-f028-4489-b59c-f267afe408f2",
"Name": "node2.new",
"Address": "198.18.1.2:8300",
"NodeStatus": "alive",
"Version": "1.9.0",
"LastContact": "1ms",
"LastTerm": 3,
"LastIndex": 41,
"Healthy": true,
"StableSince": "2020-11-06T14:52:00Z",
"RedundancyZone": "zone1",
"UpgradeVersion": "2.0.0",
"Status": "non-voter",
"Meta": {
"bar": "baz",
"upgrade": "2.0.0",
"zone": "zone1"
},
"NodeType": "zone-standby"
},
"4691e516-6989-4a16-8f55-12ff2226a3c9": {
"ID": "4691e516-6989-4a16-8f55-12ff2226a3c9",
"Name": "node4.new",
"Address": "198.18.1.4:8300",
"NodeStatus": "alive",
"Version": "1.9.0",
"LastContact": "1ms",
"LastTerm": 3,
"LastIndex": 41,
"Healthy": true,
"StableSince": "2020-11-06T14:52:00Z",
"RedundancyZone": "zone2",
"UpgradeVersion": "2.0.0",
"Status": "non-voter",
"Meta": {
"bar": "baz",
"upgrade": "2.0.0",
"zone": "zone2"
},
"NodeType": "zone-standby"
},
"4c42fe86-321d-4e8f-be0d-c261e6cfc453": {
"ID": "4c42fe86-321d-4e8f-be0d-c261e6cfc453",
"Name": "node5",
"Address": "198.18.0.5:8300",
"NodeStatus": "alive",
"Version": "1.9.0",
"LastContact": "1ms",
"LastTerm": 3,
"LastIndex": 42,
"Healthy": true,
"StableSince": "2020-11-06T14:51:00Z",
"RedundancyZone": "zone3",
"UpgradeVersion": "1.0.0",
"Status": "voter",
"Meta": {
"foo": "bar",
"upgrade": "1.0.0",
"zone": "zone3"
},
"NodeType": "zone-voter"
},
"6ca5a41c-6162-41c1-b4eb-09082efe206f": {
"ID": "6ca5a41c-6162-41c1-b4eb-09082efe206f",
"Name": "node2",
"Address": "198.18.0.2:8300",
"NodeStatus": "alive",
"Version": "1.9.0",
"LastContact": "1ms",
"LastTerm": 3,
"LastIndex": 41,
"Healthy": true,
"StableSince": "2020-11-06T14:52:00Z",
"RedundancyZone": "zone1",
"UpgradeVersion": "1.0.0",
"Status": "non-voter",
"Meta": {
"bar": "baz",
"upgrade": "1.0.0",
"zone": "zone1"
},
"NodeType": "zone-standby"
},
"6e8a37e5-a1c4-4212-a75e-91487d8cda6d": {
"ID": "6e8a37e5-a1c4-4212-a75e-91487d8cda6d",
"Name": "node3",
"Address": "198.18.0.3:8300",
"NodeStatus": "alive",
"Version": "1.9.0",
"LastContact": "1ms",
"LastTerm": 3,
"LastIndex": 42,
"Healthy": true,
"StableSince": "2020-11-06T14:51:00Z",
"RedundancyZone": "zone2",
"UpgradeVersion": "1.0.0",
"Status": "voter",
"Meta": {
"foo": "bar",
"upgrade": "1.0.0",
"zone": "zone2"
},
"NodeType": "zone-voter"
},
"746b8782-ce77-41fd-bce0-cce62cca62b4": {
"ID": "746b8782-ce77-41fd-bce0-cce62cca62b4",
"Name": "node6",
"Address": "198.18.0.6:8300",
"NodeStatus": "alive",
"Version": "1.9.0",
"LastContact": "1ms",
"LastTerm": 3,
"LastIndex": 41,
"Healthy": true,
"StableSince": "2020-11-06T14:52:00Z",
"RedundancyZone": "zone3",
"UpgradeVersion": "1.0.0",
"Status": "non-voter",
"Meta": {
"bar": "baz",
"upgrade": "1.0.0",
"zone": "zone3"
},
"NodeType": "zone-standby"
},
"79324811-9588-4311-b208-f272e38aaabf": {
"ID": "79324811-9588-4311-b208-f272e38aaabf",
"Name": "node1",
"Address": "198.18.0.1:8300",
"NodeStatus": "alive",
"Version": "1.9.0",
"LastContact": "0s",
"LastTerm": 3,
"LastIndex": 42,
"Healthy": true,
"StableSince": "2020-11-06T14:51:00Z",
"RedundancyZone": "zone1",
"UpgradeVersion": "1.0.0",
"Status": "leader",
"Meta": {
"foo": "bar",
"upgrade": "1.0.0",
"zone": "zone1"
},
"NodeType": "zone-voter"
},
"7b02a615-ccce-4251-bda8-b89e0bd4f7c7": {
"ID": "7b02a615-ccce-4251-bda8-b89e0bd4f7c7",
"Name": "read-replica",
"Address": "198.18.0.7:8300",
"NodeStatus": "alive",
"Version": "1.9.0",
"LastContact": "2ms",
"LastTerm": 3,
"LastIndex": 39,
"Healthy": true,
"StableSince": "2020-11-06T14:53:00Z",
"UpgradeVersion": "1.0.0",
"ReadReplica": true,
"Status": "non-voter",
"Meta": {
"baz": "foo",
"version": "1.0.0"
},
"NodeType": "read-replica"
},
"98dfd0fd-504e-4280-8e73-6983a6af1b8c": {
"ID": "98dfd0fd-504e-4280-8e73-6983a6af1b8c",
"Name": "node5.new",
"Address": "198.18.1.5:8300",
"NodeStatus": "alive",
"Version": "1.9.0",
"LastContact": "1ms",
"LastTerm": 3,
"LastIndex": 42,
"Healthy": true,
"StableSince": "2020-11-06T14:51:00Z",
"RedundancyZone": "zone3",
"UpgradeVersion": "2.0.0",
"Status": "non-voter",
"Meta": {
"foo": "bar",
"upgrade": "2.0.0",
"zone": "zone3"
},
"NodeType": "zone-standby"
},
"997b0851-37c5-4d65-a477-a8b3a56eea42": {
"ID": "997b0851-37c5-4d65-a477-a8b3a56eea42",
"Name": "node3.new",
"Address": "198.18.1.3:8300",
"NodeStatus": "alive",
"Version": "1.9.0",
"LastContact": "1ms",
"LastTerm": 3,
"LastIndex": 42,
"Healthy": true,
"StableSince": "2020-11-06T14:51:00Z",
"RedundancyZone": "zone2",
"UpgradeVersion": "2.0.0",
"Status": "non-voter",
"Meta": {
"foo": "bar",
"upgrade": "2.0.0",
"zone": "zone2"
},
"NodeType": "zone-standby"
},
"de95799e-15a4-4c86-b508-78840554b7cb": {
"ID": "de95799e-15a4-4c86-b508-78840554b7cb",
"Name": "node1.new",
"Address": "198.18.1.1:8300",
"NodeStatus": "alive",
"Version": "1.9.0",
"LastContact": "0s",
"LastTerm": 3,
"LastIndex": 42,
"Healthy": true,
"StableSince": "2020-11-06T14:51:00Z",
"RedundancyZone": "zone1",
"UpgradeVersion": "2.0.0",
"Status": "non-voter",
"Meta": {
"foo": "bar",
"upgrade": "2.0.0",
"zone": "zone1"
},
"NodeType": "zone-standby"
}
},
"Leader": "79324811-9588-4311-b208-f272e38aaabf",
"Voters": [
"79324811-9588-4311-b208-f272e38aaabf",
"6e8a37e5-a1c4-4212-a75e-91487d8cda6d",
"4c42fe86-321d-4e8f-be0d-c261e6cfc453"
],
"ReadReplicas": [
"7b02a615-ccce-4251-bda8-b89e0bd4f7c7"
],
"RedundancyZones": {
"zone1": {
"Servers": [
"79324811-9588-4311-b208-f272e38aaabf",
"6ca5a41c-6162-41c1-b4eb-09082efe206f",
"de95799e-15a4-4c86-b508-78840554b7cb",
"3044d109-f028-4489-b59c-f267afe408f2"
],
"Voters": [
"79324811-9588-4311-b208-f272e38aaabf"
],
"FailureTolerance": 3
},
"zone2": {
"Servers": [
"6e8a37e5-a1c4-4212-a75e-91487d8cda6d",
"1baeb453-ad9e-489a-bbfe-53bee097aec8",
"997b0851-37c5-4d65-a477-a8b3a56eea42",
"4691e516-6989-4a16-8f55-12ff2226a3c9"
],
"Voters": [
"6e8a37e5-a1c4-4212-a75e-91487d8cda6d"
],
"FailureTolerance": 3
},
"zone3": {
"Servers": [
"4c42fe86-321d-4e8f-be0d-c261e6cfc453",
"746b8782-ce77-41fd-bce0-cce62cca62b4",
"98dfd0fd-504e-4280-8e73-6983a6af1b8c",
"1b8044f6-1d25-4a83-9662-acdf404341d2"
],
"Voters": [
"4c42fe86-321d-4e8f-be0d-c261e6cfc453"
],
"FailureTolerance": 3
}
},
"Upgrade": {
"Status": "promoting",
"TargetVersion": "2.0.0",
"TargetVersionNonVoters": [
"de95799e-15a4-4c86-b508-78840554b7cb",
"3044d109-f028-4489-b59c-f267afe408f2",
"997b0851-37c5-4d65-a477-a8b3a56eea42",
"4691e516-6989-4a16-8f55-12ff2226a3c9",
"98dfd0fd-504e-4280-8e73-6983a6af1b8c",
"1b8044f6-1d25-4a83-9662-acdf404341d2"
],
"OtherVersionVoters": [
"79324811-9588-4311-b208-f272e38aaabf",
"6e8a37e5-a1c4-4212-a75e-91487d8cda6d",
"4c42fe86-321d-4e8f-be0d-c261e6cfc453"
],
"OtherVersionNonVoters": [
"6ca5a41c-6162-41c1-b4eb-09082efe206f",
"1baeb453-ad9e-489a-bbfe-53bee097aec8",
"746b8782-ce77-41fd-bce0-cce62cca62b4"
],
"OtherVersionReadReplicas": [
"7b02a615-ccce-4251-bda8-b89e0bd4f7c7"
],
"RedundancyZones": {
"zone1": {
"TargetVersionNonVoters": [
"de95799e-15a4-4c86-b508-78840554b7cb",
"3044d109-f028-4489-b59c-f267afe408f2"
],
"OtherVersionVoters": [
"79324811-9588-4311-b208-f272e38aaabf"
],
"OtherVersionNonVoters": [
"6ca5a41c-6162-41c1-b4eb-09082efe206f"
]
},
"zone2": {
"TargetVersionNonVoters": [
"997b0851-37c5-4d65-a477-a8b3a56eea42",
"4691e516-6989-4a16-8f55-12ff2226a3c9"
],
"OtherVersionVoters": [
"6e8a37e5-a1c4-4212-a75e-91487d8cda6d"
],
"OtherVersionNonVoters": [
"1baeb453-ad9e-489a-bbfe-53bee097aec8"
]
},
"zone3": {
"TargetVersionNonVoters": [
"98dfd0fd-504e-4280-8e73-6983a6af1b8c",
"1b8044f6-1d25-4a83-9662-acdf404341d2"
],
"OtherVersionVoters": [
"4c42fe86-321d-4e8f-be0d-c261e6cfc453"
],
"OtherVersionNonVoters": [
"746b8782-ce77-41fd-bce0-cce62cca62b4"
]
}
}
}
}

View File

@ -0,0 +1,67 @@
{
"Healthy": true,
"FailureTolerance": 1,
"OptimisticFailureTolerance": 0,
"Servers": {
"79324811-9588-4311-b208-f272e38aaabf": {
"ID": "79324811-9588-4311-b208-f272e38aaabf",
"Name": "node1",
"Address": "198.18.0.1:8300",
"NodeStatus": "alive",
"Version": "1.9.0",
"LastContact": "0s",
"LastTerm": 3,
"LastIndex": 42,
"Healthy": true,
"StableSince": "2020-11-06T14:51:00Z",
"ReadReplica": false,
"Status": "leader",
"Meta": {
"foo": "bar"
},
"NodeType": "voter"
},
"ae84aefb-a303-4734-8739-5c102d4ee2d9": {
"ID": "ae84aefb-a303-4734-8739-5c102d4ee2d9",
"Name": "node3",
"Address": "198.18.0.3:8300",
"NodeStatus": "alive",
"Version": "1.9.0",
"LastContact": "2ms",
"LastTerm": 3,
"LastIndex": 39,
"Healthy": true,
"StableSince": "2020-11-06T14:53:00Z",
"ReadReplica": false,
"Status": "voter",
"Meta": {
"baz": "foo"
},
"NodeType": "voter"
},
"ef8aee9a-f9d6-4ec4-b383-aac956bdb80f": {
"ID": "ef8aee9a-f9d6-4ec4-b383-aac956bdb80f",
"Name": "node2",
"Address": "198.18.0.2:8300",
"NodeStatus": "alive",
"Version": "1.9.0",
"LastContact": "1ms",
"LastTerm": 3,
"LastIndex": 41,
"Healthy": true,
"StableSince": "2020-11-06T14:52:00Z",
"ReadReplica": false,
"Status": "voter",
"Meta": {
"bar": "baz"
},
"NodeType": "voter"
}
},
"Leader": "79324811-9588-4311-b208-f272e38aaabf",
"Voters": [
"79324811-9588-4311-b208-f272e38aaabf",
"ef8aee9a-f9d6-4ec4-b383-aac956bdb80f",
"ae84aefb-a303-4734-8739-5c102d4ee2d9"
]
}

View File

@ -0,0 +1,48 @@
Healthy: true
Failure Tolerance: 1
Optimistic Failure Tolerance: 0
Leader: 79324811-9588-4311-b208-f272e38aaabf
Voters:
79324811-9588-4311-b208-f272e38aaabf
ef8aee9a-f9d6-4ec4-b383-aac956bdb80f
ae84aefb-a303-4734-8739-5c102d4ee2d9
Servers:
79324811-9588-4311-b208-f272e38aaabf
Name: node1
Address: 198.18.0.1:8300
Version: 1.9.0
Status: leader
Node Type: voter
Node Status: alive
Healthy: true
Last Contact: 0s
Last Term: 3
Last Index: 42
Meta
"foo": "bar"
ae84aefb-a303-4734-8739-5c102d4ee2d9
Name: node3
Address: 198.18.0.3:8300
Version: 1.9.0
Status: voter
Node Type: voter
Node Status: alive
Healthy: true
Last Contact: 2ms
Last Term: 3
Last Index: 39
Meta
"baz": "foo"
ef8aee9a-f9d6-4ec4-b383-aac956bdb80f
Name: node2
Address: 198.18.0.2:8300
Version: 1.9.0
Status: voter
Node Type: voter
Node Status: alive
Healthy: true
Last Contact: 1ms
Last Term: 3
Last Index: 41
Meta
"bar": "baz"

View File

@ -0,0 +1,64 @@
{
"Healthy": true,
"FailureTolerance": 1,
"OptimisitcFailureTolerance": 0,
"Servers": {
"79324811-9588-4311-b208-f272e38aaabf": {
"ID": "79324811-9588-4311-b208-f272e38aaabf",
"Name": "node1",
"Address": "198.18.0.1:8300",
"NodeStatus": "alive",
"Version": "1.9.0",
"LastContact": "0s",
"LastTerm": 3,
"LastIndex": 42,
"Healthy": true,
"StableSince": "2020-11-06T14:51:00Z",
"Status": "leader",
"Meta": {
"foo": "bar"
},
"NodeType": "voter"
},
"ae84aefb-a303-4734-8739-5c102d4ee2d9": {
"ID": "ae84aefb-a303-4734-8739-5c102d4ee2d9",
"Name": "node3",
"Address": "198.18.0.3:8300",
"NodeStatus": "alive",
"Version": "1.9.0",
"LastContact": "2ms",
"LastTerm": 3,
"LastIndex": 39,
"Healthy": true,
"StableSince": "2020-11-06T14:53:00Z",
"Status": "voter",
"Meta": {
"baz": "foo"
},
"NodeType": "voter"
},
"ef8aee9a-f9d6-4ec4-b383-aac956bdb80f": {
"ID": "ef8aee9a-f9d6-4ec4-b383-aac956bdb80f",
"Name": "node2",
"Address": "198.18.0.2:8300",
"NodeStatus": "alive",
"Version": "1.9.0",
"LastContact": "1ms",
"LastTerm": 3,
"LastIndex": 41,
"Healthy": true,
"StableSince": "2020-11-06T14:52:00Z",
"Status": "voter",
"Meta": {
"bar": "baz"
},
"NodeType": "voter"
}
},
"Leader": "79324811-9588-4311-b208-f272e38aaabf",
"Voters": [
"79324811-9588-4311-b208-f272e38aaabf",
"ef8aee9a-f9d6-4ec4-b383-aac956bdb80f",
"ae84aefb-a303-4734-8739-5c102d4ee2d9"
]
}

View File

@ -48,18 +48,31 @@ func (_ *prettyFormatter) Format(info *OutputFormat) (string, error) {
fmt.Fprintf(tw, "\n Term\t%d", info.Meta.Term) fmt.Fprintf(tw, "\n Term\t%d", info.Meta.Term)
fmt.Fprintf(tw, "\n Version\t%d", info.Meta.Version) fmt.Fprintf(tw, "\n Version\t%d", info.Meta.Version)
fmt.Fprintf(tw, "\n") fmt.Fprintf(tw, "\n")
fmt.Fprintln(tw, "\n Type\tCount\tSize\t") fmt.Fprintln(tw, "\n Type\tCount\tSize")
fmt.Fprintf(tw, " %s\t%s\t%s\t", "----", "----", "----") fmt.Fprintf(tw, " %s\t%s\t%s", "----", "----", "----")
// For each different type generate new output // For each different type generate new output
for _, s := range info.Stats { for _, s := range info.Stats {
fmt.Fprintf(tw, "\n %s\t%d\t%s\t", s.Name, s.Count, ByteSize(uint64(s.Sum))) fmt.Fprintf(tw, "\n %s\t%d\t%s", s.Name, s.Count, ByteSize(uint64(s.Sum)))
}
fmt.Fprintf(tw, "\n %s\t%s\t%s", "----", "----", "----")
fmt.Fprintf(tw, "\n Total\t\t%s", ByteSize(uint64(info.TotalSize)))
if info.StatsKV != nil {
fmt.Fprintf(tw, "\n")
fmt.Fprintln(tw, "\n Key Name\tCount\tSize")
fmt.Fprintf(tw, " %s\t%s\t%s", "----", "----", "----")
// For each different type generate new output
for _, s := range info.StatsKV {
fmt.Fprintf(tw, "\n %s\t%d\t%s", s.Name, s.Count, ByteSize(uint64(s.Sum)))
}
fmt.Fprintf(tw, "\n %s\t%s\t%s", "----", "----", "----")
fmt.Fprintf(tw, "\n Total\t\t%s", ByteSize(uint64(info.TotalSizeKV)))
} }
fmt.Fprintf(tw, "\n %s\t%s\t%s\t", "----", "----", "----")
fmt.Fprintf(tw, "\n Total\t\t%s\t", ByteSize(uint64(info.TotalSize)))
if err := tw.Flush(); err != nil { if err := tw.Flush(); err != nil {
return b.String(), err return b.String(), err
} }
return b.String(), nil return b.String(), nil
} }

View File

@ -13,6 +13,11 @@ func TestFormat(t *testing.T) {
Sum: 1, Sum: 1,
Count: 2, Count: 2,
}} }}
mkv := []typeStats{{
Name: "msgKV",
Sum: 1,
Count: 2,
}}
info := OutputFormat{ info := OutputFormat{
Meta: &MetadataInfo{ Meta: &MetadataInfo{
ID: "one", ID: "one",
@ -21,8 +26,10 @@ func TestFormat(t *testing.T) {
Term: 4, Term: 4,
Version: 1, Version: 1,
}, },
Stats: m, Stats: m,
TotalSize: 1, StatsKV: mkv,
TotalSize: 1,
TotalSizeKV: 1,
} }
formatters := map[string]Formatter{ formatters := map[string]Formatter{

View File

@ -29,10 +29,21 @@ type cmd struct {
flags *flag.FlagSet flags *flag.FlagSet
help string help string
format string format string
// flags
kvDetails bool
kvDepth int
kvFilter string
} }
func (c *cmd) init() { func (c *cmd) init() {
c.flags = flag.NewFlagSet("", flag.ContinueOnError) c.flags = flag.NewFlagSet("", flag.ContinueOnError)
c.flags.BoolVar(&c.kvDetails, "kvdetails", false,
"Provides a detailed KV space usage breakdown for any KV data that's been stored.")
c.flags.IntVar(&c.kvDepth, "kvdepth", 2,
"Can only be used with -kvdetails. The key prefix depth used to breakdown KV store data. Defaults to 2.")
c.flags.StringVar(&c.kvFilter, "kvfilter", "",
"Can only be used with -kvdetails. Limits KV key breakdown using this prefix filter.")
c.flags.StringVar( c.flags.StringVar(
&c.format, &c.format,
"format", "format",
@ -52,12 +63,24 @@ type MetadataInfo struct {
Version raft.SnapshotVersion Version raft.SnapshotVersion
} }
// SnapshotInfo is used for passing snapshot stat
// information between functions
type SnapshotInfo struct {
Meta MetadataInfo
Stats map[structs.MessageType]typeStats
StatsKV map[string]typeStats
TotalSize int
TotalSizeKV int
}
// OutputFormat is used for passing information // OutputFormat is used for passing information
// through the formatter // through the formatter
type OutputFormat struct { type OutputFormat struct {
Meta *MetadataInfo Meta *MetadataInfo
Stats []typeStats Stats []typeStats
TotalSize int StatsKV []typeStats
TotalSize int
TotalSizeKV int
} }
func (c *cmd) Run(args []string) int { func (c *cmd) Run(args []string) int {
@ -101,7 +124,7 @@ func (c *cmd) Run(args []string) int {
} }
}() }()
stats, totalSize, err := enhance(readFile) info, err := c.enhance(readFile)
if err != nil { if err != nil {
c.UI.Error(fmt.Sprintf("Error extracting snapshot data: %s", err)) c.UI.Error(fmt.Sprintf("Error extracting snapshot data: %s", err))
return 1 return 1
@ -122,13 +145,17 @@ func (c *cmd) Run(args []string) int {
} }
//Restructures stats given above to be human readable //Restructures stats given above to be human readable
formattedStats := generatetypeStats(stats) formattedStats := generateStats(info)
formattedStatsKV := generateKVStats(info)
in := &OutputFormat{ in := &OutputFormat{
Meta: metaformat, Meta: metaformat,
Stats: formattedStats, Stats: formattedStats,
TotalSize: totalSize, StatsKV: formattedStatsKV,
TotalSize: info.TotalSize,
TotalSizeKV: info.TotalSizeKV,
} }
out, err := formatter.Format(in) out, err := formatter.Format(in)
if err != nil { if err != nil {
c.UI.Error(err.Error()) c.UI.Error(err.Error())
@ -145,19 +172,55 @@ type typeStats struct {
Count int Count int
} }
func generatetypeStats(info map[structs.MessageType]typeStats) []typeStats { // generateStats formats the stats for the output struct
ss := make([]typeStats, 0, len(info)) // that's used to produce the printed output the user sees.
func generateStats(info SnapshotInfo) []typeStats {
ss := make([]typeStats, 0, len(info.Stats))
for _, s := range info { for _, s := range info.Stats {
ss = append(ss, s) ss = append(ss, s)
} }
// Sort the stat slice ss = sortTypeStats(ss)
sort.Slice(ss, func(i, j int) bool { return ss[i].Sum > ss[j].Sum })
return ss return ss
} }
// generateKVStats reformats the KV stats to work with
// the output struct that's used to produce the printed
// output the user sees.
func generateKVStats(info SnapshotInfo) []typeStats {
kvLen := len(info.StatsKV)
if kvLen > 0 {
ks := make([]typeStats, 0, kvLen)
for _, s := range info.StatsKV {
ks = append(ks, s)
}
ks = sortTypeStats(ks)
return ks
}
return nil
}
// sortTypeStats sorts the stat slice by size and then
// alphabetically in the case the size is identical
func sortTypeStats(stats []typeStats) []typeStats {
sort.Slice(stats, func(i, j int) bool {
// sort alphabetically if size is equal
if stats[i].Sum == stats[j].Sum {
return stats[i].Name < stats[j].Name
}
return stats[i].Sum > stats[j].Sum
})
return stats
}
// countingReader helps keep track of the bytes we have read // countingReader helps keep track of the bytes we have read
// when reading snapshots // when reading snapshots
type countingReader struct { type countingReader struct {
@ -175,36 +238,89 @@ func (r *countingReader) Read(p []byte) (n int, err error) {
// enhance utilizes ReadSnapshot to populate the struct with // enhance utilizes ReadSnapshot to populate the struct with
// all of the snapshot's itemized data // all of the snapshot's itemized data
func enhance(file io.Reader) (map[structs.MessageType]typeStats, int, error) { func (c *cmd) enhance(file io.Reader) (SnapshotInfo, error) {
stats := make(map[structs.MessageType]typeStats) info := SnapshotInfo{
Stats: make(map[structs.MessageType]typeStats),
StatsKV: make(map[string]typeStats),
TotalSize: 0,
TotalSizeKV: 0,
}
cr := &countingReader{wrappedReader: file} cr := &countingReader{wrappedReader: file}
totalSize := 0
handler := func(header *fsm.SnapshotHeader, msg structs.MessageType, dec *codec.Decoder) error { handler := func(header *fsm.SnapshotHeader, msg structs.MessageType, dec *codec.Decoder) error {
name := structs.MessageType.String(msg) name := structs.MessageType.String(msg)
s := stats[msg] s := info.Stats[msg]
if s.Name == "" { if s.Name == "" {
s.Name = name s.Name = name
} }
var val interface{} var val interface{}
err := dec.Decode(&val) err := dec.Decode(&val)
if err != nil { if err != nil {
return fmt.Errorf("failed to decode msg type %v, error %v", name, err) return fmt.Errorf("failed to decode msg type %v, error %v", name, err)
} }
size := cr.read - totalSize size := cr.read - info.TotalSize
s.Sum += size s.Sum += size
s.Count++ s.Count++
totalSize = cr.read info.TotalSize = cr.read
stats[msg] = s info.Stats[msg] = s
c.kvEnhance(s.Name, val, size, &info)
return nil return nil
} }
if err := fsm.ReadSnapshot(cr, handler); err != nil { if err := fsm.ReadSnapshot(cr, handler); err != nil {
return nil, 0, err return info, err
} }
return stats, totalSize, nil return info, nil
} }
// kvEnhance populates the struct with all of the snapshot's
// size information for KV data stored in it
func (c *cmd) kvEnhance(keyType string, val interface{}, size int, info *SnapshotInfo) {
if c.kvDetails {
if keyType != "KVS" {
return
}
// have to coerce this into a usable type here or this won't work
keyVal := val.(map[string]interface{})
for k, v := range keyVal {
// we only care about the entry on the key specifically
// related to the key name, so skip all others
if k != "Key" {
continue
}
// check for whether a filter is specified. if it is, skip
// any keys that don't match.
if len(c.kvFilter) > 0 && !strings.HasPrefix(v.(string), c.kvFilter) {
break
}
split := strings.Split(v.(string), "/")
// handle the situation where the key is shorter than
// the specified depth.
actualDepth := c.kvDepth
if c.kvDepth > len(split) {
actualDepth = len(split)
}
prefix := strings.Join(split[0:actualDepth], "/")
kvs := info.StatsKV[prefix]
if kvs.Name == "" {
kvs.Name = prefix
}
kvs.Sum += size
kvs.Count++
info.TotalSizeKV += size
info.StatsKV[prefix] = kvs
}
}
}
func (c *cmd) Synopsis() string { func (c *cmd) Synopsis() string {
return synopsis return synopsis
} }

View File

@ -95,3 +95,57 @@ func TestSnapshotInspectCommand(t *testing.T) {
want := golden(t, t.Name(), ui.OutputWriter.String()) want := golden(t, t.Name(), ui.OutputWriter.String())
require.Equal(t, want, ui.OutputWriter.String()) require.Equal(t, want, ui.OutputWriter.String())
} }
func TestSnapshotInspectKVDetailsCommand(t *testing.T) {
filepath := "./testdata/backupWithKV.snap"
// Inspect the snapshot
ui := cli.NewMockUi()
c := New(ui)
args := []string{"-kvdetails", filepath}
code := c.Run(args)
if code != 0 {
t.Fatalf("bad: %d. %#v", code, ui.ErrorWriter.String())
}
want := golden(t, t.Name(), ui.OutputWriter.String())
require.Equal(t, want, ui.OutputWriter.String())
}
func TestSnapshotInspectKVDetailsDepthCommand(t *testing.T) {
filepath := "./testdata/backupWithKV.snap"
// Inspect the snapshot
ui := cli.NewMockUi()
c := New(ui)
args := []string{"-kvdetails", "-kvdepth", "3", filepath}
code := c.Run(args)
if code != 0 {
t.Fatalf("bad: %d. %#v", code, ui.ErrorWriter.String())
}
want := golden(t, t.Name(), ui.OutputWriter.String())
require.Equal(t, want, ui.OutputWriter.String())
}
func TestSnapshotInspectKVDetailsDepthFilterCommand(t *testing.T) {
filepath := "./testdata/backupWithKV.snap"
// Inspect the snapshot
ui := cli.NewMockUi()
c := New(ui)
args := []string{"-kvdetails", "-kvdepth", "3", "-kvfilter", "vault/logical", filepath}
code := c.Run(args)
if code != 0 {
t.Fatalf("bad: %d. %#v", code, ui.ErrorWriter.String())
}
want := golden(t, t.Name(), ui.OutputWriter.String())
require.Equal(t, want, ui.OutputWriter.String())
}

View File

@ -4,16 +4,16 @@
Term 2 Term 2
Version 1 Version 1
Type Count Size Type Count Size
---- ---- ---- ---- ---- ----
Register 3 1.7KB Register 3 1.7KB
ConnectCA 1 1.2KB ConnectCA 1 1.2KB
ConnectCAProviderState 1 1.1KB ConnectCAProviderState 1 1.1KB
Index 12 344B Index 12 344B
Autopilot 1 199B Autopilot 1 199B
ConnectCAConfig 1 197B ConnectCAConfig 1 197B
FederationState 1 139B FederationState 1 139B
SystemMetadata 1 68B SystemMetadata 1 68B
ChunkingState 1 12B ChunkingState 1 12B
---- ---- ---- ---- ---- ----
Total 5KB Total 5KB

View File

@ -4,15 +4,15 @@
Term 2 Term 2
Version 1 Version 1
Type Count Size Type Count Size
---- ---- ---- ---- ---- ----
Register 3 1.8KB Register 3 1.8KB
ConnectCA 1 1.2KB ConnectCA 1 1.2KB
ConnectCAProviderState 1 1.1KB ConnectCAProviderState 1 1.1KB
Index 11 313B Index 11 313B
ConnectCAConfig 1 247B ConnectCAConfig 1 247B
Autopilot 1 199B Autopilot 1 199B
SystemMetadata 1 68B SystemMetadata 1 68B
ChunkingState 1 12B ChunkingState 1 12B
---- ---- ---- ---- ---- ----
Total 5KB Total 5KB

View File

@ -0,0 +1,27 @@
ID 2-12426-1604593650375
Size 17228
Index 12426
Term 2
Version 1
Type Count Size
---- ---- ----
KVS 27 12.3KB
Register 5 3.4KB
Index 11 285B
Autopilot 1 199B
Session 1 199B
CoordinateBatchUpdate 1 166B
Tombstone 2 146B
FederationState 1 139B
ChunkingState 1 12B
---- ---- ----
Total 16.8KB
Key Name Count Size
---- ---- ----
vault/core 16 5.9KB
vault/sys 7 4.4KB
vault/logical 4 2KB
---- ---- ----
Total 12.3KB

View File

@ -0,0 +1,44 @@
ID 2-12426-1604593650375
Size 17228
Index 12426
Term 2
Version 1
Type Count Size
---- ---- ----
KVS 27 12.3KB
Register 5 3.4KB
Index 11 285B
Autopilot 1 199B
Session 1 199B
CoordinateBatchUpdate 1 166B
Tombstone 2 146B
FederationState 1 139B
ChunkingState 1 12B
---- ---- ----
Total 16.8KB
Key Name Count Size
---- ---- ----
vault/sys/policy 3 3.3KB
vault/logical/0989e79e-06cd-5374-c8c0-4c6d675bc1c9 3 1.8KB
vault/core/leader 1 1.6KB
vault/sys/token 3 1KB
vault/core/mounts 1 675B
vault/core/wrapping 1 633B
vault/core/local-mounts 1 450B
vault/core/auth 1 423B
vault/core/cluster 2 388B
vault/core/keyring 1 320B
vault/core/master 1 237B
vault/core/seal-config 1 211B
vault/logical/5c018b68-3573-41d3-0c33-04bce60cd6b0 1 210B
vault/core/hsm 1 189B
vault/core/local-audit 1 185B
vault/core/local-auth 1 183B
vault/core/audit 1 179B
vault/core/lock 1 170B
vault/core/shamir-kek 1 159B
vault/sys/counters 1 155B
---- ---- ----
Total 12.3KB

View File

@ -0,0 +1,26 @@
ID 2-12426-1604593650375
Size 17228
Index 12426
Term 2
Version 1
Type Count Size
---- ---- ----
KVS 27 12.3KB
Register 5 3.4KB
Index 11 285B
Autopilot 1 199B
Session 1 199B
CoordinateBatchUpdate 1 166B
Tombstone 2 146B
FederationState 1 139B
ChunkingState 1 12B
---- ---- ----
Total 16.8KB
Key Name Count Size
---- ---- ----
vault/logical/0989e79e-06cd-5374-c8c0-4c6d675bc1c9 3 1.8KB
vault/logical/5c018b68-3573-41d3-0c33-04bce60cd6b0 1 210B
---- ---- ----
Total 2KB

BIN
command/snapshot/inspect/testdata/backupWithKV.snap (Stored with Git LFS) vendored Normal file

Binary file not shown.

View File

@ -13,5 +13,13 @@
"Count": 2 "Count": 2
} }
], ],
"TotalSize": 1 "StatsKV": [
{
"Name": "msgKV",
"Sum": 1,
"Count": 2
}
],
"TotalSize": 1,
"TotalSizeKV": 1
} }

View File

@ -4,8 +4,14 @@
Term 4 Term 4
Version 1 Version 1
Type Count Size Type Count Size
---- ---- ---- ---- ---- ----
msg 2 1B msg 2 1B
---- ---- ---- ---- ---- ----
Total 1B Total 1B
Key Name Count Size
---- ---- ----
msgKV 2 1B
---- ---- ----
Total 1B

View File

@ -1,29 +1,16 @@
-----BEGIN CERTIFICATE----- -----BEGIN CERTIFICATE-----
MIIFADCCAugCCQCPPTSu2adkQzANBgkqhkiG9w0BAQsFADBFMQswCQYDVQQGEwJB MIICnDCCAkOgAwIBAgIRAOnKNzSoGq53Rq/G5tbm85swCgYIKoZIzj0EAwIwgbkx
VTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0 CzAJBgNVBAYTAlVTMQswCQYDVQQIEwJDQTEWMBQGA1UEBxMNU2FuIEZyYW5jaXNj
cyBQdHkgTHRkMB4XDTE3MDQxNDE5MTE0MVoXDTIyMDMxOTE5MTE0MVowPzELMAkG bzEaMBgGA1UECRMRMTAxIFNlY29uZCBTdHJlZXQxDjAMBgNVBBETBTk0MTA1MRcw
A1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxGzAZBgNVBAoMEkNvbnN1bCBU FQYDVQQKEw5IYXNoaUNvcnAgSW5jLjFAMD4GA1UEAxM3Q29uc3VsIEFnZW50IENB
ZXN0IENsaWVudDCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAOyJwuXC IDE4NTU3MTQ5MTMzMTA0NzczNDYwMjQyMDcxODI5NjUzMzQzNTQ0MzAeFw0yMDEw
N+8sOnFmWDx6w5Xs+ADd905EKkfrdLc0qIcrndZwdCx9zOeArLhUS1aoh0ctwz5x MjgyMjI3NTZaFw0yMTEwMjgyMjI3NTZaMBwxGjAYBgNVBAMTEWNsaWVudC5kYzEu
wBsmUgwIQwr/V6Q1+sAEDmPmnuAiR/m3lxE+ZPr1CyNhrrTqs7jXkRRDNtZevw6d Y29uc3VsMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEMtVdDd8tDZBaOaDFFzWD
mTE0FC4Tho016NVXckVZRRJL8Svfk2GvZbZ+1HAIi1a/Du7VQPbd1HOLKPkpb0JU 0hTxO7soxUuz1dWaO8FGhIS07dfSBjYumEOgfNtfOzAILvkBd4gS8DrQZ2Rbks86
AksdAaks/avzKhRUoHQBR/T4S9GK95WrCqJIZG6iOVK2cymfZ9t/qYdm5czdq8ho iKOBxzCBxDAOBgNVHQ8BAf8EBAMCBaAwHQYDVR0lBBYwFAYIKwYBBQUHAwIGCCsG
kWNqOtVb+yBx4/zs8JZI3/TZ/K4nSFcpZNZmPI7xsBL0/GG7nqWyAFWFsn/+4q0a AQUFBwMBMAwGA1UdEwEB/wQCMAAwKQYDVR0OBCIEIEJWUjlDw7H2fbRpGG8fpqCq
FIBVUe+ydONKoXKZI080b9CLkNXIp1P8rJsodjKZO/xr+/JCeo6D7ZAJymFe1QXJ GEX80iDpQqXOU0wg6fEPMCsGA1UdIwQkMCKAIAu+td60D/Er7Xjtyg0B6XflfKYm
aGt7ac08s9scU97n0iuCRn6RXLdii9pbiAlArmAE1zXQKA/hcyVtjfUdeq0kA1RE IdXjPfiFy8SGeKS2MC0GA1UdEQQmMCSCEWNsaWVudC5kYzEuY29uc3Vsgglsb2Nh
308gUab552XstQUHH0/+JcoPhn1UN+D6UOw7CxVQrq6FBDeDt7S/WtJNGM3GYrQW bGhvc3SHBH8AAAEwCgYIKoZIzj0EAwIDRwAwRAIgYAZTf8VcZ4nQl4lbm579BfXy
tbvTQhDW0jBp0Tr+BwIDXYN5QLM6rcnB2GR3+aYVuvre6DrvisA6yKO+0F29n2LM 6YpYz/DdfkEODUBxUyYCIDXhfmxtL/gTSkIh1E+fV7H7ZmqPKgTDH1XBV2zYnj/C
DdcUu8ZGzoEyFOZAgcw943VlU8bSxWkANoO0e3gKub4AvOaRqvVKyeFuFnwzKjSE
jiZTHVWs/1XVTnK51b9H33uK976O0q9qGlERAgMBAAEwDQYJKoZIhvcNAQELBQAD
ggIBAAGb44gmBikP4KJOLDDQWUX+l6kfIEobKzulmUcZ5puNS68fnQrWYUXTk5MK
IKBfuQSrxux/CEx1nBIZS/IcAnxG6GpxIcVdtzrZ/2yhDsmT3ZlgMo0+kNgqPnB2
hVe19HO44KAkjKpbjzqya2YX5CY83WBbB0kLvbRtBraU74LDyPD8Nb2YgCKkKZuC
WHtvMw/3bGQ74FXRIjLBv1moOiWaOvaMpztn4GzChZuU8Ikyps3YiRKES2gSvPIj
tynzyoED/ddNA6Q/NeLYaCqwzIVScuqBsDCNTvFqZztS4J7VJxxnRztXb/og2yE4
xqXezVV/O+6HhRPnxInYNiMRS++C/cyeKt150t/YS718KlebL4Pd+fQ4fDQzMyYl
ixEOVFLZiT07heUBkNA//Y0thBr/VbMvvcnMKmm3wLKsL/VGznI5akZhBa0DRj/0
y+dSNJ2CL4I11SQ5yIaVgggqVEJ6wUWpE5woLQuvg+P+QVgkBtGR7H5dmREMq06L
dPWFoBNBil5MNDLAt2eoOauoVdba1XWWW6HZJP9lR/qgFeE9yWAFUB0BwonkYJYM
0Tm9+Jv87nJAy+a1RDszjAMV/N1RvCOb9+2g4NnHSXNVqH+gP0BsVYP5e2A31GX0
aftVp7tPN1bSFt7nFZNbbsFhRD/fQvOpCOC/7pTnXx8zhl/2
-----END CERTIFICATE----- -----END CERTIFICATE-----

View File

@ -1,27 +0,0 @@
-----BEGIN CERTIFICATE REQUEST-----
MIIEhDCCAmwCAQAwPzELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUx
GzAZBgNVBAoMEkNvbnN1bCBUZXN0IENsaWVudDCCAiIwDQYJKoZIhvcNAQEBBQAD
ggIPADCCAgoCggIBAOyJwuXCN+8sOnFmWDx6w5Xs+ADd905EKkfrdLc0qIcrndZw
dCx9zOeArLhUS1aoh0ctwz5xwBsmUgwIQwr/V6Q1+sAEDmPmnuAiR/m3lxE+ZPr1
CyNhrrTqs7jXkRRDNtZevw6dmTE0FC4Tho016NVXckVZRRJL8Svfk2GvZbZ+1HAI
i1a/Du7VQPbd1HOLKPkpb0JUAksdAaks/avzKhRUoHQBR/T4S9GK95WrCqJIZG6i
OVK2cymfZ9t/qYdm5czdq8hokWNqOtVb+yBx4/zs8JZI3/TZ/K4nSFcpZNZmPI7x
sBL0/GG7nqWyAFWFsn/+4q0aFIBVUe+ydONKoXKZI080b9CLkNXIp1P8rJsodjKZ
O/xr+/JCeo6D7ZAJymFe1QXJaGt7ac08s9scU97n0iuCRn6RXLdii9pbiAlArmAE
1zXQKA/hcyVtjfUdeq0kA1RE308gUab552XstQUHH0/+JcoPhn1UN+D6UOw7CxVQ
rq6FBDeDt7S/WtJNGM3GYrQWtbvTQhDW0jBp0Tr+BwIDXYN5QLM6rcnB2GR3+aYV
uvre6DrvisA6yKO+0F29n2LMDdcUu8ZGzoEyFOZAgcw943VlU8bSxWkANoO0e3gK
ub4AvOaRqvVKyeFuFnwzKjSEjiZTHVWs/1XVTnK51b9H33uK976O0q9qGlERAgMB
AAGgADANBgkqhkiG9w0BAQsFAAOCAgEARWD26zR/JuyER2SmCq0GYc4i5bTmQOZw
BcToQsYQcfTrdZspzKhb4EM560tVqE8V6Ckkl2V97ZbbD7KIRgY6LEsCcr6VCfU5
rSiyHo1M/PDnK6FqYE0qTqqu3jOGmh97RAZi8REy8gfT/PrE8kDaEVfm0nCB8aaW
8wg6ncmStRLKJB4QP1YDpcQH568MMY/0E7xUixp1KjaA4VwE4QEFNoxmpp/Dwpzw
BB6kjlKM2tsjkbm0EHCPU5creAoqPNVQNCIVTDBUgkwhUrU2ljAjlC24tL66L0FP
RKmyk8yWR2PQkkkWlRwarypGmOAE0GMVMICNh5WTaPaa1XTTpZme3ctpZQZO3LRT
Bao4tXM3pA8vApCbkqExR1ow86HvOHv50Y+LQpD1fk7cj18npCR831f1cr/jQw6V
p4Z6bdftF38ZCsCdjUHXbtc3tA0220I+0e8ng1znAfMPQC+KVrHaapt1OFWGHPFk
KDLyxtZN1IOEOkjtI23KOUlEtG4jnm5UjFG43EXA3qmbUrHNCsHhRpHhMBqu4a0Z
ifPQ5+YFyUCmgVUSGeGmpnyxWalmwOT2Ygd/uhn7TnZ0GAv+x1tLHNe78Q8YKi9a
gw4zZcDFJmCPnVkfQ+lVSSY5GjSPI8DZCfr+IJBIPOiknTGP9g2dEbUX8xVyEfK6
18IjYxoATJE=
-----END CERTIFICATE REQUEST-----

View File

@ -1,51 +1,5 @@
-----BEGIN RSA PRIVATE KEY----- -----BEGIN EC PRIVATE KEY-----
MIIJKAIBAAKCAgEA7InC5cI37yw6cWZYPHrDlez4AN33TkQqR+t0tzSohyud1nB0 MHcCAQEEIDxDVYnUL3LCN7kSKF/ShH1c8HacmeUyU/2qJ/fo+5kDoAoGCCqGSM49
LH3M54CsuFRLVqiHRy3DPnHAGyZSDAhDCv9XpDX6wAQOY+ae4CJH+beXET5k+vUL AwEHoUQDQgAEMtVdDd8tDZBaOaDFFzWD0hTxO7soxUuz1dWaO8FGhIS07dfSBjYu
I2GutOqzuNeRFEM21l6/Dp2ZMTQULhOGjTXo1VdyRVlFEkvxK9+TYa9ltn7UcAiL mEOgfNtfOzAILvkBd4gS8DrQZ2Rbks86iA==
Vr8O7tVA9t3Uc4so+SlvQlQCSx0BqSz9q/MqFFSgdAFH9PhL0Yr3lasKokhkbqI5 -----END EC PRIVATE KEY-----
UrZzKZ9n23+ph2blzN2ryGiRY2o61Vv7IHHj/Ozwlkjf9Nn8ridIVylk1mY8jvGw
EvT8YbuepbIAVYWyf/7irRoUgFVR77J040qhcpkjTzRv0IuQ1cinU/ysmyh2Mpk7
/Gv78kJ6joPtkAnKYV7VBcloa3tpzTyz2xxT3ufSK4JGfpFct2KL2luICUCuYATX
NdAoD+FzJW2N9R16rSQDVETfTyBRpvnnZey1BQcfT/4lyg+GfVQ34PpQ7DsLFVCu
roUEN4O3tL9a0k0YzcZitBa1u9NCENbSMGnROv4HAgNdg3lAszqtycHYZHf5phW6
+t7oOu+KwDrIo77QXb2fYswN1xS7xkbOgTIU5kCBzD3jdWVTxtLFaQA2g7R7eAq5
vgC85pGq9UrJ4W4WfDMqNISOJlMdVaz/VdVOcrnVv0ffe4r3vo7Sr2oaURECAwEA
AQKCAgBm8FkSNmCzRJM2kKyrvV1q3NLdRbv/oqin3e9QX6lMEg5BqXTVe/X1dck0
+vJCh1s//clvXn+VESs5s0rB+XfBrgAvGlTM4yuXLTQXl+81gOrfUE8Fmdg3QcDv
G1k28T1nM5qAGNP3VsvFdZfj0mc+mSzQw1XM7aHKTyVLqNJiBnYbP4ysNr+f7syz
4rw3gINXU9HokrjgyYHUhxDqiQtyB5ZAheIz2O7eBVVUHDingUu73fuGZIJfxdCj
9L3pgD1X18yPjfpfwnZSLhJu/0GR6+eT76kPXOKbQ9s2m3wX1ixapRHUXiuLuOQF
Bh8hGOsiyuEJJkVvyDG9V/OIiw0RBQBOlVHhLoAGvFMVNjAo7Ei/zCU5FH1G9Lh8
XiIF0PVbO94jcfgfxtKCaj7xO9aoDV/pkp/2vrmWxfewf/5h7aZN8ny7/hSrvwCo
Q/XQ8bUxBnsfqVxiWqZIktnak2WNzLk614Y+aPkkie5x0FZe7pq71N0bnofv6Rh6
xp8A6cRvYmx7//lJZkmwk/gpZI1u6Kz6nOIfzKsGPRi12VlJpDq+zJAOBiiiFn1m
i/HV3ieqpCSIB2LgZYerG+jb5liBSjXR36puvJJQcqDb9S+4jRkMW9kbvt+k6VAI
aT0Sq6OIAj10eGH70z+z3SdrUs8wZ81/79/F8wBTys1o9HIYAQKCAQEA/4Wkox8B
cfgX5z1i/Jd95NEMnjjJZhm6q6GnzZ13bfdfO50uL1rdVMLZO8SjN/kc33TsKxPl
VkQhjjTjcZKtkYvy4citpWWVevQQtaFaHudz8J3XUlWW9R7O/gtpUhHR+fp5yqLK
wKLrv0upS9Nm4S6eEUPXoKzv+khQTVUm/z8KRD2yMM+4cNMtVf02HIclZNbbxCPf
PXYR+0FbCfXy2KpoZxxE/NX8zrwN5KKbtvuIF7BkNvNxOzP3/dWPaykiryOgRyRX
E4aY5BWT415xg86AAY+dpYlGp9xK3VdO6hNKBJF4F5q37imFh+b3Mk1GACZMY3h9
swIfE5u1bYHBgQKCAQEA7PsHGlrLap6lTTA7HIYJ09qblTw4hxsMVa2+nIx31WTN
HYGTcDGIYUegp/sLT5ucAHKnwHPZgCvOmwrxo5IoZ6PKLrE7kaA4CTKogAo3tTt1
xc8dUH8l/zF8gDaYEobTNG5O0h+ALCEoBCR9RfS8Ub217f0x9s7nzyftiFQ/c9MF
gAJtvRnIU8il98qGz/PJVrIwVklVi1GuZXIYw922shxGnolcVEYjiBjWNblxfOO3
5RzsHGNK6NoHGx16Ux62zoc8Rw9E44GBARo+qw0aSbjLgfB10wsp2rzC65ARWM9Y
gst6pKIJUwOQjWaiffc1qAfc+W0kQNSqYHcFdYk3kQKCAQBUyhEWu+wr2Gp+JiWZ
sd9ptWDdg/R4t+L0nwDivvTpfaORUZgIyLsXLE0PgzGyGizVjaPsq353gMYtvSkX
/9cuq+TdvUy5zJqsoR6GVtNj2+PiHU5dGN+t2RpQvJKnVBh8Pfx6HEjxYV6fLMkx
yyWhZWm4Su3beGdtgt96ud3l5xJOELb3cYY/kiPCG/L/xmzHKHDmhgzHBU30NPyz
snRyJyHbzUqrJ4rrQwXNL5RCRPck/ThT77ZMMfOBvIMJyS2kNksyMEHgzdIgJXTc
hvNeDID3g6OJUaMrgnMpPZaHH/14xJi4JHQSSJ7xuNegTnoDBLJmc44qf3K2e/3Z
J6yBAoIBADHh6S3X/Md1m2/y/g5T/I+WjXdNVMzDmcYTK3NCchr9+9sBImrUUlO/
wwZ45nmcVKsXd04gVKERF401MYXvxweBx5YqglJ1+jWdbzB8dht056Z6oT4HdZUQ
8pb+ZuZHcP+xVHAQZ2dil0y/7YqjKFzAZSIyUKkWBl9plStEKJMV0SuP10+dtLhG
HQFapSPyuefA3EHdb99Ck0YRTTs1WTaGkyrd2Qx4MxR7veNTJJtYR6Y3f0++as82
zZYcj1odtfclKj/+685DvUbhIl3ZBTaNanDwj6ybxfSgFRuGmNAr3QKzGB69aN8L
egr5lqyTM70p4o6yNZZb7X0esIx8FLECggEBAKqeqOipGQopRptxQXQQuFA9CDff
waZEEt7usmFpwWPa/IEERIxfFwA/d9qnUYlhbawMakbZmaMR/3o4Wt/G09YOvmX+
Q5ci57XQ6jg85Ym0kmCOFK0d8heG+Wm5LoSP4BjwSziGbo+uz0ouzQ/e8o84wXK/
OIAgja327BsEVhYAl8OgKU9ZCDwGp9xo7vVn6ZYnQEKZrF1cS9jPfNX+A615AvxB
yGgzkm7PXObcdYoh6vKpDu6E+mFeSns+cYBhG698TGhR4k4A/MtfBIl1XTQMgoft
ichLZuS7o0NsVouhsFEKIaxwmIdgQ7vr9f+QPLBWoCEBcezm7du2VB9hsMY=
-----END RSA PRIVATE KEY-----

View File

@ -0,0 +1,5 @@
-----BEGIN EC PRIVATE KEY-----
MHcCAQEEINtFYGWAzcVyRRQKjadE83olH8xAwZYe5sEn4rfPtI8xoAoGCCqGSM49
AwEHoUQDQgAErHueX3t67iU5Bj7Nh53zhggnF4pLwjuDbmTDSYIe/Tbeixc2M2Nb
7cGr9/Bk9cH8exB/o2KzbQ2nxPZ+ftBTAQ==
-----END EC PRIVATE KEY-----

View File

@ -0,0 +1,18 @@
-----BEGIN CERTIFICATE-----
MIIC7zCCApSgAwIBAgIRAIubxOonau4Z6UJRYv5KBDMwCgYIKoZIzj0EAwIwgbkx
CzAJBgNVBAYTAlVTMQswCQYDVQQIEwJDQTEWMBQGA1UEBxMNU2FuIEZyYW5jaXNj
bzEaMBgGA1UECRMRMTAxIFNlY29uZCBTdHJlZXQxDjAMBgNVBBETBTk0MTA1MRcw
FQYDVQQKEw5IYXNoaUNvcnAgSW5jLjFAMD4GA1UEAxM3Q29uc3VsIEFnZW50IENB
IDE4NTU3MTQ5MTMzMTA0NzczNDYwMjQyMDcxODI5NjUzMzQzNTQ0MzAeFw0yMDEw
MjgyMjI3NTZaFw0yNTEwMjcyMjI3NTZaMIG5MQswCQYDVQQGEwJVUzELMAkGA1UE
CBMCQ0ExFjAUBgNVBAcTDVNhbiBGcmFuY2lzY28xGjAYBgNVBAkTETEwMSBTZWNv
bmQgU3RyZWV0MQ4wDAYDVQQREwU5NDEwNTEXMBUGA1UEChMOSGFzaGlDb3JwIElu
Yy4xQDA+BgNVBAMTN0NvbnN1bCBBZ2VudCBDQSAxODU1NzE0OTEzMzEwNDc3MzQ2
MDI0MjA3MTgyOTY1MzM0MzU0NDMwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAASs
e55fe3ruJTkGPs2HnfOGCCcXikvCO4NuZMNJgh79Nt6LFzYzY1vtwav38GT1wfx7
EH+jYrNtDafE9n5+0FMBo3sweTAOBgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUw
AwEB/zApBgNVHQ4EIgQgC7613rQP8SvteO3KDQHpd+V8piYh1eM9+IXLxIZ4pLYw
KwYDVR0jBCQwIoAgC7613rQP8SvteO3KDQHpd+V8piYh1eM9+IXLxIZ4pLYwCgYI
KoZIzj0EAwIDSQAwRgIhALoE4RO8DHR4AkxmO5ostQxAYMIpiSTC9VZsWva3hHj4
AiEAijGw7bHPearXh9I2ghGE4jGJbGK4R9JHcLOq3+GE2Ng=
-----END CERTIFICATE-----

View File

@ -0,0 +1,5 @@
-----BEGIN EC PRIVATE KEY-----
MHcCAQEEIDxDVYnUL3LCN7kSKF/ShH1c8HacmeUyU/2qJ/fo+5kDoAoGCCqGSM49
AwEHoUQDQgAEMtVdDd8tDZBaOaDFFzWD0hTxO7soxUuz1dWaO8FGhIS07dfSBjYu
mEOgfNtfOzAILvkBd4gS8DrQZ2Rbks86iA==
-----END EC PRIVATE KEY-----

View File

@ -0,0 +1,16 @@
-----BEGIN CERTIFICATE-----
MIICnDCCAkOgAwIBAgIRAOnKNzSoGq53Rq/G5tbm85swCgYIKoZIzj0EAwIwgbkx
CzAJBgNVBAYTAlVTMQswCQYDVQQIEwJDQTEWMBQGA1UEBxMNU2FuIEZyYW5jaXNj
bzEaMBgGA1UECRMRMTAxIFNlY29uZCBTdHJlZXQxDjAMBgNVBBETBTk0MTA1MRcw
FQYDVQQKEw5IYXNoaUNvcnAgSW5jLjFAMD4GA1UEAxM3Q29uc3VsIEFnZW50IENB
IDE4NTU3MTQ5MTMzMTA0NzczNDYwMjQyMDcxODI5NjUzMzQzNTQ0MzAeFw0yMDEw
MjgyMjI3NTZaFw0yMTEwMjgyMjI3NTZaMBwxGjAYBgNVBAMTEWNsaWVudC5kYzEu
Y29uc3VsMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEMtVdDd8tDZBaOaDFFzWD
0hTxO7soxUuz1dWaO8FGhIS07dfSBjYumEOgfNtfOzAILvkBd4gS8DrQZ2Rbks86
iKOBxzCBxDAOBgNVHQ8BAf8EBAMCBaAwHQYDVR0lBBYwFAYIKwYBBQUHAwIGCCsG
AQUFBwMBMAwGA1UdEwEB/wQCMAAwKQYDVR0OBCIEIEJWUjlDw7H2fbRpGG8fpqCq
GEX80iDpQqXOU0wg6fEPMCsGA1UdIwQkMCKAIAu+td60D/Er7Xjtyg0B6XflfKYm
IdXjPfiFy8SGeKS2MC0GA1UdEQQmMCSCEWNsaWVudC5kYzEuY29uc3Vsgglsb2Nh
bGhvc3SHBH8AAAEwCgYIKoZIzj0EAwIDRwAwRAIgYAZTf8VcZ4nQl4lbm579BfXy
6YpYz/DdfkEODUBxUyYCIDXhfmxtL/gTSkIh1E+fV7H7ZmqPKgTDH1XBV2zYnj/C
-----END CERTIFICATE-----

View File

@ -0,0 +1,5 @@
-----BEGIN EC PRIVATE KEY-----
MHcCAQEEICYdaRvHDtbGbReTekgKf9uyKFEnR7kr7VU3kw3uGzAhoAoGCCqGSM49
AwEHoUQDQgAE0etZvg/aUTU+HPwDHtEwZslBuEshwHl7AcERHQeFTuhtfjpwHQw+
uTunFkmQoqNmE+n7P4v7fe771lpxif8VwA==
-----END EC PRIVATE KEY-----

View File

@ -0,0 +1,17 @@
-----BEGIN CERTIFICATE-----
MIICxjCCAmugAwIBAgIRAOKZmO0GuFJUOfJ7Ycf0WOEwCgYIKoZIzj0EAwIwgbkx
CzAJBgNVBAYTAlVTMQswCQYDVQQIEwJDQTEWMBQGA1UEBxMNU2FuIEZyYW5jaXNj
bzEaMBgGA1UECRMRMTAxIFNlY29uZCBTdHJlZXQxDjAMBgNVBBETBTk0MTA1MRcw
FQYDVQQKEw5IYXNoaUNvcnAgSW5jLjFAMD4GA1UEAxM3Q29uc3VsIEFnZW50IENB
IDE4NTU3MTQ5MTMzMTA0NzczNDYwMjQyMDcxODI5NjUzMzQzNTQ0MzAeFw0yMDEw
MjgyMjI3NTZaFw0yMTEwMjgyMjI3NTZaMBwxGjAYBgNVBAMTEXNlcnZlci5kYzEu
Y29uc3VsMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE0etZvg/aUTU+HPwDHtEw
ZslBuEshwHl7AcERHQeFTuhtfjpwHQw+uTunFkmQoqNmE+n7P4v7fe771lpxif8V
wKOB7zCB7DAOBgNVHQ8BAf8EBAMCBaAwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsG
AQUFBwMCMAwGA1UdEwEB/wQCMAAwKQYDVR0OBCIEIEA1xxAYluRqg6wFwGu75o/5
8Ty6FWR9RgIYvZzCM2N9MCsGA1UdIwQkMCKAIAu+td60D/Er7Xjtyg0B6XflfKYm
IdXjPfiFy8SGeKS2MFUGA1UdEQROMEyCC2NvbnN1bC50ZXN0ghlzZXJ2ZXIwLnNl
cnZlci5kYzEuY29uc3VsghFzZXJ2ZXIuZGMxLmNvbnN1bIIJbG9jYWxob3N0hwR/
AAABMAoGCCqGSM49BAMCA0kAMEYCIQDz9YnCvKkgGqw5M0HLDI82rqwQsH2SRQUs
kogKi3oGmQIhAPBA5AgF3y1E94PbeYfvoDBJy1JiY3KsckY2Gz+M8Iyc
-----END CERTIFICATE-----

33
test/client_certs/generate.sh Executable file
View File

@ -0,0 +1,33 @@
#!/bin/bash
set -euo pipefail
cd "$(dirname "$0")"
if [[ ! -f consul-agent-ca-key.pem ]] || [[ ! -f consul-agent-ca.pem ]]; then
echo "Regenerating CA..."
rm -f consul-agent-ca-key.pem consul-agent-ca.pem
consul tls ca create
fi
rm -f rootca.crt rootca.key path/rootca.crt
cp consul-agent-ca.pem rootca.crt
cp consul-agent-ca-key.pem rootca.key
cp rootca.crt path
if [[ ! -f dc1-server-consul-0.pem ]] || [[ ! -f dc1-server-consul-0-key.pem ]]; then
echo "Regenerating server..."
rm -f dc1-server-consul-0.pem dc1-server-consul-0-key.pem
consul tls cert create -server -node=server0 -additional-dnsname=consul.test
fi
rm -f server.crt server.key
cp dc1-server-consul-0.pem server.crt
cp dc1-server-consul-0-key.pem server.key
if [[ ! -f dc1-client-consul-0.pem ]] || [[ ! -f dc1-client-consul-0-key.pem ]]; then
echo "Regenerating client..."
rm -f dc1-client-consul-0.pem dc1-client-consul-0-key.pem
consul tls cert create -client
fi
rm -f client.crt client.key
cp dc1-client-consul-0.pem client.crt
cp dc1-client-consul-0-key.pem client.key

View File

@ -1,31 +1,18 @@
-----BEGIN CERTIFICATE----- -----BEGIN CERTIFICATE-----
MIIFXTCCA0WgAwIBAgIJAKkYXwqUpHWIMA0GCSqGSIb3DQEBCwUAMEUxCzAJBgNV MIIC7zCCApSgAwIBAgIRAIubxOonau4Z6UJRYv5KBDMwCgYIKoZIzj0EAwIwgbkx
BAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBX CzAJBgNVBAYTAlVTMQswCQYDVQQIEwJDQTEWMBQGA1UEBxMNU2FuIEZyYW5jaXNj
aWRnaXRzIFB0eSBMdGQwHhcNMTcwNDE0MTkwNjIwWhcNMjIwNDE0MTkwNjIwWjBF bzEaMBgGA1UECRMRMTAxIFNlY29uZCBTdHJlZXQxDjAMBgNVBBETBTk0MTA1MRcw
MQswCQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50 FQYDVQQKEw5IYXNoaUNvcnAgSW5jLjFAMD4GA1UEAxM3Q29uc3VsIEFnZW50IENB
ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIIC IDE4NTU3MTQ5MTMzMTA0NzczNDYwMjQyMDcxODI5NjUzMzQzNTQ0MzAeFw0yMDEw
CgKCAgEA0pMZRBhBHaYG1FttDzt+rctrM8NtslfbAbHflM4ly7mibCmrnh/sGYSP MjgyMjI3NTZaFw0yNTEwMjcyMjI3NTZaMIG5MQswCQYDVQQGEwJVUzELMAkGA1UE
Y/QcrGnCWrjXspUM0dxtmXGmXKj4yRpKhVYw16saDcn2t55dxMw23uEhgfrigtYS CBMCQ0ExFjAUBgNVBAcTDVNhbiBGcmFuY2lzY28xGjAYBgNVBAkTETEwMSBTZWNv
/MoC78OaQryRGvYdxF5unrybBrXDrVfxA8ycYBOV04piS7NlN1/m3TkiNrqkl1up bmQgU3RyZWV0MQ4wDAYDVQQREwU5NDEwNTEXMBUGA1UEChMOSGFzaGlDb3JwIElu
paPscfFaY59yz3Lgq2vs8U1SLtph8ALwjJcz8O3BbBUQuiZYNuiLLnkRroxT3oSp Yy4xQDA+BgNVBAMTN0NvbnN1bCBBZ2VudCBDQSAxODU1NzE0OTEzMzEwNDc3MzQ2
Zbdna2aXtdD/H9JJUoJLjoZp6x5fNOtc+5vF8QZ/jKeJDsxqwf4VT4Z6t8sUMFtO MDI0MjA3MTgyOTY1MzM0MzU0NDMwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAASs
YAR3QEV7wtOhWWfFVzdlCsKZhD8ryemlYSMJ7wfmXUeoiElSPRfvIHUTcJFx37VR e55fe3ruJTkGPs2HnfOGCCcXikvCO4NuZMNJgh79Nt6LFzYzY1vtwav38GT1wfx7
V6LmOzs1gjMpGZxGLk/GtaCbDlyCaT/hae8bqtRhngBy5bvGxmdSBQd0fjYRr2CP EH+jYrNtDafE9n5+0FMBo3sweTAOBgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUw
Pz+Gzx3yQx8yb9VV7dTI3H4LWaTjuy2WooITzy3xY4bOvp4UwusFgbIdywwCpDFJ AwEB/zApBgNVHQ4EIgQgC7613rQP8SvteO3KDQHpd+V8piYh1eM9+IXLxIZ4pLYw
zzy3FaqoMx06v5D3I9JCanlXk5FErA0GX45pPM4x3FWZfVwrXZtomBFDC6qlRMpH KwYDVR0jBCQwIoAgC7613rQP8SvteO3KDQHpd+V8piYh1eM9+IXLxIZ4pLYwCgYI
FH8A/KrbpolwiDklSWUxHbfz32gpzf3kLW2nOVpnZPyAgSYudjSFGW00jRomCXOy KoZIzj0EAwIDSQAwRgIhALoE4RO8DHR4AkxmO5ostQxAYMIpiSTC9VZsWva3hHj4
EOgQj/w+M3hKosvVqIE8IQgfMPNRCK9hn11gVqUvkdcYvo/2jUMCAwEAAaNQME4w AiEAijGw7bHPearXh9I2ghGE4jGJbGK4R9JHcLOq3+GE2Ng=
HQYDVR0OBBYEFMnRHZ6zGzu/N5MdNv4HKPP5rMqOMB8GA1UdIwQYMBaAFMnRHZ6z
Gzu/N5MdNv4HKPP5rMqOMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQELBQADggIB
AFPWJZiQTupGr5B14PGPkiesyL1VCkO9D7nr6dXgQwAUTIdDuo9+nZg1HzOd1evY
tRWGTAYOsyGNLBIjdhvTZcZz625i1zXFRzkpu0iOUoaH77/XVGkAKyaeYc3oIe9i
bRke37/NQokmM3Eko+M/KoAk31osDxlmdQTOVqqoZN+ag6xsrpN6XUmhu3DNfw1j
xud6RMH6J+SamG/vUg+GyqKZ9WpKNcLliVPJgOfX1XI3wstOIqkhlw/qveIgIfPv
aUVf+rykdgqYMMQvR6qx0k5iHeqj+F1cZRp3P0Uao0hoxi+udgj0X3F7/s/Cbr9c
TPZrIlhicZgli9UOwrjZ4B4mEZ4aD8yDbYO3TZe2DnuhPI7uDFG8lvCuLYu8V/lA
0yRoBaJf5Z1IXI4190Ww6bmxYV/n5EAFi6o46hWRUiYROtKEcP9Rmabodgp+Jxw6
fwzcYqUXTOXR8/gAFPxt5oEfhZ8VJ4nlB9PFTjDi7Gbz+2MnmJokqkxLAR7//FEz
Rdmyfl+CvnPZ4TXLk77tuhf3Os9zHSTLobBdDivrTOpc0LdYw5l9yN9s93bG2TBr
2T0aKInqAduReLE2nhkYCdlY+dbjbELEiLicqSaEiwr9WbaWuLMoy4tDY52pYgTR
lEclafi+O3Y35hEE6VAfZvM1TeR3gvnnQf4ThqIxkRVl
-----END CERTIFICATE----- -----END CERTIFICATE-----

View File

@ -1,31 +1,18 @@
-----BEGIN CERTIFICATE----- -----BEGIN CERTIFICATE-----
MIIFXTCCA0WgAwIBAgIJAKkYXwqUpHWIMA0GCSqGSIb3DQEBCwUAMEUxCzAJBgNV MIIC7zCCApSgAwIBAgIRAIubxOonau4Z6UJRYv5KBDMwCgYIKoZIzj0EAwIwgbkx
BAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBX CzAJBgNVBAYTAlVTMQswCQYDVQQIEwJDQTEWMBQGA1UEBxMNU2FuIEZyYW5jaXNj
aWRnaXRzIFB0eSBMdGQwHhcNMTcwNDE0MTkwNjIwWhcNMjIwNDE0MTkwNjIwWjBF bzEaMBgGA1UECRMRMTAxIFNlY29uZCBTdHJlZXQxDjAMBgNVBBETBTk0MTA1MRcw
MQswCQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50 FQYDVQQKEw5IYXNoaUNvcnAgSW5jLjFAMD4GA1UEAxM3Q29uc3VsIEFnZW50IENB
ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIIC IDE4NTU3MTQ5MTMzMTA0NzczNDYwMjQyMDcxODI5NjUzMzQzNTQ0MzAeFw0yMDEw
CgKCAgEA0pMZRBhBHaYG1FttDzt+rctrM8NtslfbAbHflM4ly7mibCmrnh/sGYSP MjgyMjI3NTZaFw0yNTEwMjcyMjI3NTZaMIG5MQswCQYDVQQGEwJVUzELMAkGA1UE
Y/QcrGnCWrjXspUM0dxtmXGmXKj4yRpKhVYw16saDcn2t55dxMw23uEhgfrigtYS CBMCQ0ExFjAUBgNVBAcTDVNhbiBGcmFuY2lzY28xGjAYBgNVBAkTETEwMSBTZWNv
/MoC78OaQryRGvYdxF5unrybBrXDrVfxA8ycYBOV04piS7NlN1/m3TkiNrqkl1up bmQgU3RyZWV0MQ4wDAYDVQQREwU5NDEwNTEXMBUGA1UEChMOSGFzaGlDb3JwIElu
paPscfFaY59yz3Lgq2vs8U1SLtph8ALwjJcz8O3BbBUQuiZYNuiLLnkRroxT3oSp Yy4xQDA+BgNVBAMTN0NvbnN1bCBBZ2VudCBDQSAxODU1NzE0OTEzMzEwNDc3MzQ2
Zbdna2aXtdD/H9JJUoJLjoZp6x5fNOtc+5vF8QZ/jKeJDsxqwf4VT4Z6t8sUMFtO MDI0MjA3MTgyOTY1MzM0MzU0NDMwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAASs
YAR3QEV7wtOhWWfFVzdlCsKZhD8ryemlYSMJ7wfmXUeoiElSPRfvIHUTcJFx37VR e55fe3ruJTkGPs2HnfOGCCcXikvCO4NuZMNJgh79Nt6LFzYzY1vtwav38GT1wfx7
V6LmOzs1gjMpGZxGLk/GtaCbDlyCaT/hae8bqtRhngBy5bvGxmdSBQd0fjYRr2CP EH+jYrNtDafE9n5+0FMBo3sweTAOBgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUw
Pz+Gzx3yQx8yb9VV7dTI3H4LWaTjuy2WooITzy3xY4bOvp4UwusFgbIdywwCpDFJ AwEB/zApBgNVHQ4EIgQgC7613rQP8SvteO3KDQHpd+V8piYh1eM9+IXLxIZ4pLYw
zzy3FaqoMx06v5D3I9JCanlXk5FErA0GX45pPM4x3FWZfVwrXZtomBFDC6qlRMpH KwYDVR0jBCQwIoAgC7613rQP8SvteO3KDQHpd+V8piYh1eM9+IXLxIZ4pLYwCgYI
FH8A/KrbpolwiDklSWUxHbfz32gpzf3kLW2nOVpnZPyAgSYudjSFGW00jRomCXOy KoZIzj0EAwIDSQAwRgIhALoE4RO8DHR4AkxmO5ostQxAYMIpiSTC9VZsWva3hHj4
EOgQj/w+M3hKosvVqIE8IQgfMPNRCK9hn11gVqUvkdcYvo/2jUMCAwEAAaNQME4w AiEAijGw7bHPearXh9I2ghGE4jGJbGK4R9JHcLOq3+GE2Ng=
HQYDVR0OBBYEFMnRHZ6zGzu/N5MdNv4HKPP5rMqOMB8GA1UdIwQYMBaAFMnRHZ6z
Gzu/N5MdNv4HKPP5rMqOMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQELBQADggIB
AFPWJZiQTupGr5B14PGPkiesyL1VCkO9D7nr6dXgQwAUTIdDuo9+nZg1HzOd1evY
tRWGTAYOsyGNLBIjdhvTZcZz625i1zXFRzkpu0iOUoaH77/XVGkAKyaeYc3oIe9i
bRke37/NQokmM3Eko+M/KoAk31osDxlmdQTOVqqoZN+ag6xsrpN6XUmhu3DNfw1j
xud6RMH6J+SamG/vUg+GyqKZ9WpKNcLliVPJgOfX1XI3wstOIqkhlw/qveIgIfPv
aUVf+rykdgqYMMQvR6qx0k5iHeqj+F1cZRp3P0Uao0hoxi+udgj0X3F7/s/Cbr9c
TPZrIlhicZgli9UOwrjZ4B4mEZ4aD8yDbYO3TZe2DnuhPI7uDFG8lvCuLYu8V/lA
0yRoBaJf5Z1IXI4190Ww6bmxYV/n5EAFi6o46hWRUiYROtKEcP9Rmabodgp+Jxw6
fwzcYqUXTOXR8/gAFPxt5oEfhZ8VJ4nlB9PFTjDi7Gbz+2MnmJokqkxLAR7//FEz
Rdmyfl+CvnPZ4TXLk77tuhf3Os9zHSTLobBdDivrTOpc0LdYw5l9yN9s93bG2TBr
2T0aKInqAduReLE2nhkYCdlY+dbjbELEiLicqSaEiwr9WbaWuLMoy4tDY52pYgTR
lEclafi+O3Y35hEE6VAfZvM1TeR3gvnnQf4ThqIxkRVl
-----END CERTIFICATE----- -----END CERTIFICATE-----

View File

@ -1,51 +1,5 @@
-----BEGIN RSA PRIVATE KEY----- -----BEGIN EC PRIVATE KEY-----
MIIJKAIBAAKCAgEA0pMZRBhBHaYG1FttDzt+rctrM8NtslfbAbHflM4ly7mibCmr MHcCAQEEINtFYGWAzcVyRRQKjadE83olH8xAwZYe5sEn4rfPtI8xoAoGCCqGSM49
nh/sGYSPY/QcrGnCWrjXspUM0dxtmXGmXKj4yRpKhVYw16saDcn2t55dxMw23uEh AwEHoUQDQgAErHueX3t67iU5Bj7Nh53zhggnF4pLwjuDbmTDSYIe/Tbeixc2M2Nb
gfrigtYS/MoC78OaQryRGvYdxF5unrybBrXDrVfxA8ycYBOV04piS7NlN1/m3Tki 7cGr9/Bk9cH8exB/o2KzbQ2nxPZ+ftBTAQ==
Nrqkl1uppaPscfFaY59yz3Lgq2vs8U1SLtph8ALwjJcz8O3BbBUQuiZYNuiLLnkR -----END EC PRIVATE KEY-----
roxT3oSpZbdna2aXtdD/H9JJUoJLjoZp6x5fNOtc+5vF8QZ/jKeJDsxqwf4VT4Z6
t8sUMFtOYAR3QEV7wtOhWWfFVzdlCsKZhD8ryemlYSMJ7wfmXUeoiElSPRfvIHUT
cJFx37VRV6LmOzs1gjMpGZxGLk/GtaCbDlyCaT/hae8bqtRhngBy5bvGxmdSBQd0
fjYRr2CPPz+Gzx3yQx8yb9VV7dTI3H4LWaTjuy2WooITzy3xY4bOvp4UwusFgbId
ywwCpDFJzzy3FaqoMx06v5D3I9JCanlXk5FErA0GX45pPM4x3FWZfVwrXZtomBFD
C6qlRMpHFH8A/KrbpolwiDklSWUxHbfz32gpzf3kLW2nOVpnZPyAgSYudjSFGW00
jRomCXOyEOgQj/w+M3hKosvVqIE8IQgfMPNRCK9hn11gVqUvkdcYvo/2jUMCAwEA
AQKCAgEAw54ncJzfkP11hr1QOUBZ1HYOps28EFuRdqeZPpGrhvBytOyZI5IgMSx2
ULKsGHc/OCxTJPFWMXcG0e9ETvwh8iBcbjW9tfybfYfLjJSwI2xa5P5btHYvCsB4
byHzTG131tt6KYPN72iSdyBbHAarO1Ng8NiZxJ8tJpF98zk6pBCRVZ4H7LPCx2E9
3kTEGK3P/JBZheIAWP8v5JKh8Cirpt30PYcRl5Yng5KmMWgBtzCca5XJGU//cc3n
2Dhi+OEbuqnm99bQirfEHSk9KFDUvUKQ5KS3Y8tXnoDc0ESSQJkbjv8s7aTYonuP
+Z7scWabLEiRsY63QuiRE0foeXR95audkWzVBe+H8LUI1MIRUnzxfcTfggj+PzSz
5coIRTrJrQknI5IXvimS2uY8v0Y6u3tE4u/o9Ua3oWIyyw6NFMslNrJbeMVGpxTa
+0+LShfJiiASuGn5NPC/16rN/VMqBt7LlYK7NpUPPwRNO/qPbTC8G8O/AzZN1Jz2
im9Lv+xlAKOdwtjEpgY0IDnrydrjq8kscjGlxR2/Hn7eegWFZHtBF79Yvu0rUH4G
fox+xKhKx/tb1B6pGz1dPZBZe9f3OPP485PFbb9cv2eGsS4MHvm2EXJs+x4dcsU5
+7AsTPRLT3aGGXLfC5xYAf+zoCLJg38SeI1fuJvZXtNB5QF8WRECggEBAOtxIEPA
q6kZjubiccRglZ00mdg3HRQGBZ6v00G982YXHVTGcBnArFUQ9BFpCPNNdSZbxITZ
muLi8+FQ5ykxFuM0036YC1VxzVTB3lum1KbND/BOBpRvIflkXss1ah7zrHO46dGf
ATjZhfaflPVDnfLbdeggR0dhepggdgkZuHIET5MGpK84/A7kYFKXwqUHM2fOIjFc
nW49Rd8z27+sAsEC9i8VmJsc9kAF8GL0CRtQ/QtSy5SpDDiDrdyrg8GbSjDRVm4d
AFHiDGQ3N29k1/SiPhaoGec6ECZsZJBhZEuZlUDte+AQPpTC5ePMHwkJ/XYDD3gG
Oy3SQTZIO90gwl8CggEBAOT2HBP6w4G53/m299qN8kQOowLDn3yDCWFwZv8Mpj5u
Am32sftN/oE6OU8daTabCCPY/nIA+6Kf7K1Bk3X7km8wzjwLzrd8lf/I1icma22z
55krrEjO6tSEvZFOv74mpRJGhZ/LKIdhgCZqC91dBAlKE5gGYgf8qAJ9AJsjMO9j
QX+7VMnyCBA5Gja1+kmOZ0roJfV3kM2GSPNCon6NjxMfTl+MJEMVPPdu/y2p1Ggc
Ymee+JoJCOXVAlaw1L6NtCKTVW0/sIYGe3xQr35eqL7eT4hxD722l/tZmbKRa7gT
e/j9qxV7Mi/NpXEdeb+D/wk1BQs4Qh59o5iC1lxpR50CggEACLyp9dmwhRXtt6Ov
lRoAc1UAYIWrDpMqojjkHgxue9tfu0Wh41LDEmUOqZa8PkshjcraABQTK1hAtJvL
+DtaHhRXxNrfkMwoUnzfQ4dtXMM/VCuREvEM0bRn0CKrTXq9a43xH1ZHNVTdI8nI
PVHFCr4aIgMQohV79yk9OBk8Pv7p9QrKEbaLpAHVkTsQfg9GWRPNMQe+z9h2P1It
VW+Mqpzxhc3HW/o3KSkPQpzLubfHrCPmah3b1j0MtqOmwAiDOEyMaImq+V7qFs31
wKx9VxauNykFzQ7aipJ7KOB0WFnasA4gCrCPofWZklqAzFUSks6KRGn2yDyFLv5/
OjV9AQKCAQATrtynEw2vn00T6JjSHxXOp/t3h120lIc/6yvPjUTVZRusXGLcmc3h
SiIXHQ4odZdzjXoCTvdS+bCdDGAi6meiS23PV6yDtaAnhxpx7ymZGrg0QL7k23Tw
pCCv1zdAn43dTla6b/qh+M3Nf5xZgV+RdN7OWO4ghaXj4N8mdxYD3mKJGo+ldLsg
uef5ABfuLuHOXLq2qXq3UG4BC59whbbhC/Xu3NtZMQA2vUIOqOTrtlT3V4FDrLcp
GvDChx0i7Iep2USkya7hNrly7HTJxlV3YyEvN5kE1CeoogFGip3aC0LDGvuUMy0T
UviACuqmfjB0mCxA1KtKd76So9zNwPc5AoIBADPzlGeAzIK4NTfJzgpUqMn+3+Kc
PCcFWki3R86dSST6q3buUOu3Hr8rT4025XOlumhMo+OKUiC/gHNYyXgYW8UIa0uT
PJgzJ/18az0Ci6YF0Mf8KZhtBMZp9RKvJOshkcjUsqagnUhpktVW/eb5BFHWb5N+
+Ln1XLUSqUEKsHOL9nKkFMtddXOcfG5VHtqgl2rMPfbkrFthTXf355PF3pfG4j6L
KSrvQXl7FhQhFG/46p7CGugmaLu2glMloHFWjmcWOGcO9TMFTkAMitIq3LLyzCV/
6ShoSNJ76RROeeegU3u3AKVfckYkkai+kyGjw0mY1zOV/5/+bL7qrGjKZSg=
-----END RSA PRIVATE KEY-----

View File

@ -1 +0,0 @@
8F3D34AED9A76443

View File

@ -1,30 +1,17 @@
-----BEGIN CERTIFICATE----- -----BEGIN CERTIFICATE-----
MIIFHDCCAwQCCQCPPTSu2adkQjANBgkqhkiG9w0BAQsFADBFMQswCQYDVQQGEwJB MIICxjCCAmugAwIBAgIRAOKZmO0GuFJUOfJ7Ycf0WOEwCgYIKoZIzj0EAwIwgbkx
VTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0 CzAJBgNVBAYTAlVTMQswCQYDVQQIEwJDQTEWMBQGA1UEBxMNU2FuIEZyYW5jaXNj
cyBQdHkgTHRkMB4XDTE3MDQxNDE5MDkzOFoXDTIyMDMxOTE5MDkzOFowWzELMAkG bzEaMBgGA1UECRMRMTAxIFNlY29uZCBTdHJlZXQxDjAMBgNVBBETBTk0MTA1MRcw
A1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoMGEludGVybmV0 FQYDVQQKEw5IYXNoaUNvcnAgSW5jLjFAMD4GA1UEAxM3Q29uc3VsIEFnZW50IENB
IFdpZGdpdHMgUHR5IEx0ZDEUMBIGA1UEAwwLY29uc3VsLnRlc3QwggIiMA0GCSqG IDE4NTU3MTQ5MTMzMTA0NzczNDYwMjQyMDcxODI5NjUzMzQzNTQ0MzAeFw0yMDEw
SIb3DQEBAQUAA4ICDwAwggIKAoICAQDI57K1LPlNXpK4ud6XbqhQAM9w6mym5d+5 MjgyMjI3NTZaFw0yMTEwMjgyMjI3NTZaMBwxGjAYBgNVBAMTEXNlcnZlci5kYzEu
utGlq+ojNrnKdhaokzDOcEpELLguQlFevDrTGuBJLxInmDvRN8BmYshZAuwW+odw Y29uc3VsMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE0etZvg/aUTU+HPwDHtEw
iGa4IiT9rN0JeQIQr9DvNjRe7Rhzb3v0CIFIwaBWZyRSdEKuZORyDOvSudATI2p9 ZslBuEshwHl7AcERHQeFTuhtfjpwHQw+uTunFkmQoqNmE+n7P4v7fe771lpxif8V
Ux9HpWJKG9clHEiAjWeseLfEttkAZgojPgdz/7Nq2CT877QtdbbKO8h+IIKevhVM wKOB7zCB7DAOBgNVHQ8BAf8EBAMCBaAwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsG
JLIyWhwGMy5orlVP42om7wgAIonm8LFCHe84JUVlOjuJgMzfUvz/UuKJFbAku7Dt AQUFBwMCMAwGA1UdEwEB/wQCMAAwKQYDVR0OBCIEIEA1xxAYluRqg6wFwGu75o/5
UXKL+gZsJg5YHhtMMHQO40UDNTfxNEiIRNyOHaQv4ptRUJq7vIbBng+m1uyYFkJp 8Ty6FWR9RgIYvZzCM2N9MCsGA1UdIwQkMCKAIAu+td60D/Er7Xjtyg0B6XflfKYm
NvTf4+OApXzsfhb8bjt8QRn5fsCsYU94oUVy+DSw10EW+IR1F7pGJZX5dYZgm6H7 IdXjPfiFy8SGeKS2MFUGA1UdEQROMEyCC2NvbnN1bC50ZXN0ghlzZXJ2ZXIwLnNl
E+cj0pYV1dKbdP/5L3mv6KJUTaGDRanLiNPQFOyhs6y6Qf1Y7a270oge9UpHAQ6D cnZlci5kYzEuY29uc3VsghFzZXJ2ZXIuZGMxLmNvbnN1bIIJbG9jYWxob3N0hwR/
KI5jvCuwzW2wDE7KEpo5SGRIjoK9KfouyhlYAaIiJNnVyjVXDXGhzJgH+/2cGW/n AAABMAoGCCqGSM49BAMCA0kAMEYCIQDz9YnCvKkgGqw5M0HLDI82rqwQsH2SRQUs
TQAqKl4OtmzyNpihgv9Mhm86nro0XLnZHruJZ/SvZgpmSs8luo70CFK990j65JiM kogKi3oGmQIhAPBA5AgF3y1E94PbeYfvoDBJy1JiY3KsckY2Gz+M8Iyc
UTvBdKADfjCZB4NxVo3yUC0UVaMsoMg8mbOGJPErY8WaFCqwsBHLPmbvbZvp8BYs
ve3dDhh+OQIDAQABMA0GCSqGSIb3DQEBCwUAA4ICAQABCbceu3dDQ+NSRN45vEiB
wiyq7NLVHoSw7UWN0RPg7SVOPNt7lOqQ5ZtYNJQ0XAuBdHzNAZ/sfUgfURJbYhs5
MYalkkmMkpNWZQoMYxdj1sLX6KzLFFOtcACUSuZyOSGCBmuEDuDUTsQp1drZjNqb
fOtCWAuvSSbw8SnO6eTjUCwjcbe482kR5vnoaoRetmtTpIFdxwOXnegDXgkBnMMr
JSx+xIRVn2JzxJwaBmtn16P0/ksEMSPh0rKRvzE9zC7Jm2DvNOgEkdK8ae6DHFYY
zSonWjX3SlT+gpvuCwXC4ZmO2nV4CH34JDUUNRPub7o+t5XTGUxPGo0c/tdQYWZc
k0XTRzK6pLAq4X3NAF83nZ8pEi5z1u0Y+Dcx3AqBfhJSc2wg5++GZbELZGAmgiH2
WZa2/9zkCUGPke4GoctYNNhTmquk6ysJjDO+xOtaEQNwk04mqBwvb7j85iRNBkYX
+9PDF4sumEkkuNFB9vRSEJVrn3onQuZ3n2k1IujR6S6Vojhvideo5aT1NVq+1SQ5
bDe5E4IyrsFuxjYNIyA1hZmX2/qXYJ9U05QbyMs170lNhoKkdOf1voCxG3+sRTfz
yhGNJ2ks0MuEEiEjOFh9Za9lnk4/OHUG4yjWWdE5S/WH1xyyfokIM7xbo3Le8XgJ
ddL0E6Ag/Ow1ey1H7gsNOA==
-----END CERTIFICATE----- -----END CERTIFICATE-----

View File

@ -1,27 +0,0 @@
-----BEGIN CERTIFICATE REQUEST-----
MIIEoDCCAogCAQAwWzELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUx
ITAfBgNVBAoMGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDEUMBIGA1UEAwwLY29u
c3VsLnRlc3QwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDI57K1LPlN
XpK4ud6XbqhQAM9w6mym5d+5utGlq+ojNrnKdhaokzDOcEpELLguQlFevDrTGuBJ
LxInmDvRN8BmYshZAuwW+odwiGa4IiT9rN0JeQIQr9DvNjRe7Rhzb3v0CIFIwaBW
ZyRSdEKuZORyDOvSudATI2p9Ux9HpWJKG9clHEiAjWeseLfEttkAZgojPgdz/7Nq
2CT877QtdbbKO8h+IIKevhVMJLIyWhwGMy5orlVP42om7wgAIonm8LFCHe84JUVl
OjuJgMzfUvz/UuKJFbAku7DtUXKL+gZsJg5YHhtMMHQO40UDNTfxNEiIRNyOHaQv
4ptRUJq7vIbBng+m1uyYFkJpNvTf4+OApXzsfhb8bjt8QRn5fsCsYU94oUVy+DSw
10EW+IR1F7pGJZX5dYZgm6H7E+cj0pYV1dKbdP/5L3mv6KJUTaGDRanLiNPQFOyh
s6y6Qf1Y7a270oge9UpHAQ6DKI5jvCuwzW2wDE7KEpo5SGRIjoK9KfouyhlYAaIi
JNnVyjVXDXGhzJgH+/2cGW/nTQAqKl4OtmzyNpihgv9Mhm86nro0XLnZHruJZ/Sv
ZgpmSs8luo70CFK990j65JiMUTvBdKADfjCZB4NxVo3yUC0UVaMsoMg8mbOGJPEr
Y8WaFCqwsBHLPmbvbZvp8BYsve3dDhh+OQIDAQABoAAwDQYJKoZIhvcNAQELBQAD
ggIBAIq+JNjO/pixXFRwYUISRib68ya39C0VKj2T8L4HsAtpP2K7gkKGEoQ5AFhi
wxe0424myEBBZ4kS6y5BCETtKFb3C9K4c2l1Wp5GIR9QUrj2qrorGnZNJSEBrtHm
G4neTxPyepZ+n9C9K5RWC/S/l8nge4+oWU2F0nWIAFK+4GGGTyXFjbfGKtET8CKd
NQ4jcrabkRd49qPrXhEMTWzPkKdtZ4Gh7eFVAi5IrgJJlJPhb5/XnUQ+6L7NrIWG
D1nHCq8XNDm8Gg9KiMuB4/A7dcqyaoyYofojrnZ2HryGTeLbezpwQmMfVy1dM0ht
vueKQwOS7xjiglsV48G5mmql+0auYoBLes2yYGBJ4gLC+Gwokav+FtRhyyRUVZ3+
lta2a8XpzdMpmESQrwvz+v37fC/eBsplhk8tTy0IQUzUp0KVCRkR40UHeH9YKsu1
cvPwfzk3DPt2wHWjBw7b8RKSXQFyszP9QWODqxnOtE7BoNdHiyxF/W4RaysIVrAG
Vs2iRNVSPexiT30BgnfQN1i1HU4eXC/QuTWilo86vwDOjpHhr9FdjPghLk2TbV+g
iO3TX0zy0t6Un5rLGMt066pAFCIqynT1sf3CTVXbaeWpkHj1wjVmYOoG75xxWMsv
GKmSoKRex/SDko3s2pmfQ+vmkCTKQqabayUH5mh1Ron9B043
-----END CERTIFICATE REQUEST-----

View File

@ -1,51 +1,5 @@
-----BEGIN RSA PRIVATE KEY----- -----BEGIN EC PRIVATE KEY-----
MIIJKAIBAAKCAgEAyOeytSz5TV6SuLnel26oUADPcOpspuXfubrRpavqIza5ynYW MHcCAQEEICYdaRvHDtbGbReTekgKf9uyKFEnR7kr7VU3kw3uGzAhoAoGCCqGSM49
qJMwznBKRCy4LkJRXrw60xrgSS8SJ5g70TfAZmLIWQLsFvqHcIhmuCIk/azdCXkC AwEHoUQDQgAE0etZvg/aUTU+HPwDHtEwZslBuEshwHl7AcERHQeFTuhtfjpwHQw+
EK/Q7zY0Xu0Yc2979AiBSMGgVmckUnRCrmTkcgzr0rnQEyNqfVMfR6ViShvXJRxI uTunFkmQoqNmE+n7P4v7fe771lpxif8VwA==
gI1nrHi3xLbZAGYKIz4Hc/+zatgk/O+0LXW2yjvIfiCCnr4VTCSyMlocBjMuaK5V -----END EC PRIVATE KEY-----
T+NqJu8IACKJ5vCxQh3vOCVFZTo7iYDM31L8/1LiiRWwJLuw7VFyi/oGbCYOWB4b
TDB0DuNFAzU38TRIiETcjh2kL+KbUVCau7yGwZ4PptbsmBZCaTb03+PjgKV87H4W
/G47fEEZ+X7ArGFPeKFFcvg0sNdBFviEdRe6RiWV+XWGYJuh+xPnI9KWFdXSm3T/
+S95r+iiVE2hg0Wpy4jT0BTsobOsukH9WO2tu9KIHvVKRwEOgyiOY7wrsM1tsAxO
yhKaOUhkSI6CvSn6LsoZWAGiIiTZ1co1Vw1xocyYB/v9nBlv500AKipeDrZs8jaY
oYL/TIZvOp66NFy52R67iWf0r2YKZkrPJbqO9AhSvfdI+uSYjFE7wXSgA34wmQeD
cVaN8lAtFFWjLKDIPJmzhiTxK2PFmhQqsLARyz5m722b6fAWLL3t3Q4YfjkCAwEA
AQKCAgBHT2ZxRHtg6PavNto5af+4FfGLpMnYG7PjmtobMgAza5Nat7unLkeenuDd
ffoKAWQcejdvAxUlJN4Oy8w/oMhcDygJ4C2oolg8q026gfQbTqZOXHNNNPq2Tckd
AI8zOhkHL5WkG4Yr5QRReA7LE+i6SrfR3j5q7KE5xq1NovhWUbd15qodZxOrdlXU
LwqrR4zFoZjHpbUrcXj/hp2vnR66fanWiveSHOo2UrglgzJ7SONqKKcDajcdhq6S
TbAhFsH0M+fbR+9v1NGZJuyRQEWo4uShv977ytssAULlSGPLM17YDCeoTXKEbkrq
rpMivGoaZEbc8sx6araykCe8B1jU8vMZJCr+3dFd6cDFUypfeXtR/tgsQx+8xGj0
EvJoTRjIiDzP2+ZDblMzSUM9m28falxtmqPx3xmgZcIob3pgjeQq9SDwJfQEz+Ll
ZN4j7ROm9FB7/sqHZmWd17BSaghdjFPvkY4ibk/N9tO1sslw3DoA/jMEOmqa+CKx
bKE2KeNmqCiMiENuNVhpAQ9SvJw7MfNi2UNpl58Oeu4H3zc58JlFj5Xxo9TBGRDR
rVkzfqPxcNaJgy7WAgCGQM+pdcYq0ByGW1RSCo7zfYIaIilofySiZf5XWqFkfklT
SiCorumhB8rjVRnE4N/rcL6uq2ZWMMxmBPw5lFgsLWka8jlcmQKCAQEA+NnweOLC
WibA3UbdUagdcM/u2zk9SwewkLoTe9gLi2uwj757UqXSN9R6r7v/bmYRMGNob37i
kxdAidnFiYHAWItp27qphLry8pyWRzxNYnutHu0ZKeR3A+FWSxsw08Y0jC2Ry0Jw
a+CHeR8xKh93IR4RG1Yp2REfEeZFklmb5CZ37i1SFCmYuVnXZrbGQ1d7san3p2YB
it5y4ezk3AIaVKo0T6iZzuwGohpZMf7JeD5UPH7N02+VoJKEbjgarnDhXLph1DX+
J+JRmw/grHYHT7odPTpY+F1CInwaoXj4lqMUOhx+BmTYvb3PTPbbEtAppB4yrrNZ
a6VVs/7WZVE3RwKCAQEAzq0pEf3qgOBMmYYMvZhlNdVimEyqY8ISCCHTFW+aTc+l
dRB2tBVb5eTP6j5hYfbJVXi8N7uvCdiVhmR11x+8HqYGr8nMWDopoEvO+bNcGwvq
KXYx/BsjK8nY3i+s3MuwsLZg/tP14FHVqrStg4IueTzru4IpnknwpkkVBM28MKh1
VveorwdKFpP5nkenCt4KkYlFQff/rqE8I3LaGlvxIFVOPDXa08P7dKGIokuNfoQD
nAwQf5Wd/8RuRY6tIuFL5MNGSFRzzWgXwSMOUWb9bhTq6ZkLECfwE9TVqDxlkTIq
lK3o7lsWHRMUcFO7y6SHAH4lm92Y5Z5JAw1rCG5efwKCAQEA1o1Qv2kCmCeBcUZ/
2r9PYsxj667WIbJnkOBdnBERIwueFtDsEr3VGT2g6ZL1D4IAn++VQ0vqnVcW1cNk
hMHRzIWmp0OwlDd676ICDzj2n0pyYI+benr4AehuNiMjXfMtqw4+/TgzJU9Yfh1e
jirC01LQ/Pi06+nPF+epZBzOQ07HaBq7AZc7jdLf3DcJiVYL14nrc/CGs+xGsHNG
fklx2j1FDMkYk0b8ERcWf/xkR7+1cNMDMqCqKN8qPr0wg+Xe58vqPMSwdEK0iTSP
SSIZ+6tDOl7sBnahZooJi954tae08MVQAsM/+5eC+6B6ESZYQJ+ooucO2biaw62b
u47iUQKCAQBKWIH4peVwfL8xTsZQgXyO8/amoJV+kzZXVIuRH3dbXEHBra11tGU9
eqTMN6piShs8stTKG6qomQ+Yq5S0UQcj40dufuISLsIAlqSasEmGtS+DwK/UZ5Ks
Usy/iFjfiCpENycHJApDqkx5PstYDkFXfXGzHuyHs0NtHccA1l1HB6JGKYq1g6LE
InDd3hqZzyvwFHgkdei00e1HNy574u0HW4hsIldYbByNZPo4n4MDqst9m91nd9PB
SND+FofzjyX04cXriO2rSzGYWVryL2Ek80NZyqLvKd6z05EUFr3WkDw/BZxP+dYn
mVB147kfVUz3AWnX+svgdaMABRimjMVTAoIBAGNVJuNpGUo3ynvUf6hu5x8+e4Zx
CeSmeeNxMsbth8UyNpBWGgJiZPtIIHCKMoNgXAvyNV46aqc/IfOuBMcn4YEewk6g
0/Ww6/vB01gpa0y1isizPYvVL0Ur6VCyMzPI+I6auXAXIQGr9qjqtHC8TbDL7wlv
svH9ddYbpmG5nf0T32n6+A8Yenrlp4aDnZCt7u0Lwfx+GWowXMetY6wYcwCN9KLQ
6WnzO+BtmaIDFyTYuMmLw+wcECulh5DeZ64qzXf+aZQTK9ClJB7wOTRMawY5FOSs
E5SmBs3FquiNVuzcn7WPTqWT34y0XefMFZEvsNJ6QesAA7qm2YWEHrbdVW0=
-----END RSA PRIVATE KEY-----

View File

@ -920,11 +920,6 @@ func (c *Configurator) wrapALPNTLSClient(dc, nodeName, alpnProto string, conn ne
return nil, err return nil, err
} }
if cs := tlsConn.ConnectionState(); !cs.NegotiatedProtocolIsMutual {
tlsConn.Close()
return nil, fmt.Errorf("could not negotiate ALPN protocol %q with %q", alpnProto, config.ServerName)
}
return tlsConn, nil return tlsConn, nil
} }

View File

@ -215,7 +215,6 @@ func TestConfigurator_outgoingWrapperALPN_OK(t *testing.T) {
tlsConn := tlsClient.(*tls.Conn) tlsConn := tlsClient.(*tls.Conn)
cs := tlsConn.ConnectionState() cs := tlsConn.ConnectionState()
require.Equal(t, "foo", cs.NegotiatedProtocol) require.Equal(t, "foo", cs.NegotiatedProtocol)
require.True(t, cs.NegotiatedProtocolIsMutual)
err = <-errc err = <-errc
require.NoError(t, err) require.NoError(t, err)

View File

@ -5,8 +5,8 @@ module.exports = {
ecmaVersion: 2018, ecmaVersion: 2018,
sourceType: 'module', sourceType: 'module',
ecmaFeatures: { ecmaFeatures: {
legacyDecorators: true legacyDecorators: true,
} },
}, },
plugins: ['ember'], plugins: ['ember'],
extends: ['eslint:recommended', 'plugin:ember/recommended'], extends: ['eslint:recommended', 'plugin:ember/recommended'],
@ -17,7 +17,7 @@ module.exports = {
'no-unused-vars': ['error', { args: 'none' }], 'no-unused-vars': ['error', { args: 'none' }],
'ember/no-new-mixins': ['warn'], 'ember/no-new-mixins': ['warn'],
'ember/no-jquery': 'warn', 'ember/no-jquery': 'warn',
'ember/no-global-jquery': 'warn' 'ember/no-global-jquery': 'warn',
}, },
overrides: [ overrides: [
// node files // node files
@ -31,14 +31,14 @@ module.exports = {
'blueprints/*/index.js', 'blueprints/*/index.js',
'config/**/*.js', 'config/**/*.js',
'lib/*/index.js', 'lib/*/index.js',
'server/**/*.js' 'server/**/*.js',
], ],
parserOptions: { parserOptions: {
sourceType: 'script' sourceType: 'script',
}, },
env: { env: {
browser: false, browser: false,
node: true node: true,
}, },
plugins: ['node'], plugins: ['node'],
rules: Object.assign({}, require('eslint-plugin-node').configs.recommended.rules, { rules: Object.assign({}, require('eslint-plugin-node').configs.recommended.rules, {
@ -46,8 +46,8 @@ module.exports = {
// this can be removed once the following is fixed // this can be removed once the following is fixed
// https://github.com/mysticatea/eslint-plugin-node/issues/77 // https://github.com/mysticatea/eslint-plugin-node/issues/77
'node/no-unpublished-require': 'off' 'node/no-unpublished-require': 'off',
}) }),
} },
] ],
}; };

Some files were not shown because too many files have changed in this diff Show More