2015-11-07 06:18:11 +00:00
|
|
|
package consul
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"strings"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/armon/go-metrics"
|
2020-11-13 02:12:12 +00:00
|
|
|
"github.com/armon/go-metrics/prometheus"
|
2021-04-08 22:58:15 +00:00
|
|
|
"github.com/hashicorp/go-hclog"
|
|
|
|
"github.com/hashicorp/go-memdb"
|
|
|
|
"github.com/hashicorp/go-uuid"
|
|
|
|
|
2017-08-23 14:52:48 +00:00
|
|
|
"github.com/hashicorp/consul/acl"
|
pkg refactor
command/agent/* -> agent/*
command/consul/* -> agent/consul/*
command/agent/command{,_test}.go -> command/agent{,_test}.go
command/base/command.go -> command/base.go
command/base/* -> command/*
commands.go -> command/commands.go
The script which did the refactor is:
(
cd $GOPATH/src/github.com/hashicorp/consul
git mv command/agent/command.go command/agent.go
git mv command/agent/command_test.go command/agent_test.go
git mv command/agent/flag_slice_value{,_test}.go command/
git mv command/agent .
git mv command/base/command.go command/base.go
git mv command/base/config_util{,_test}.go command/
git mv commands.go command/
git mv consul agent
rmdir command/base/
gsed -i -e 's|package agent|package command|' command/agent{,_test}.go
gsed -i -e 's|package agent|package command|' command/flag_slice_value{,_test}.go
gsed -i -e 's|package base|package command|' command/base.go command/config_util{,_test}.go
gsed -i -e 's|package main|package command|' command/commands.go
gsed -i -e 's|base.Command|BaseCommand|' command/commands.go
gsed -i -e 's|agent.Command|AgentCommand|' command/commands.go
gsed -i -e 's|\tCommand:|\tBaseCommand:|' command/commands.go
gsed -i -e 's|base\.||' command/commands.go
gsed -i -e 's|command\.||' command/commands.go
gsed -i -e 's|command|c|' main.go
gsed -i -e 's|range Commands|range command.Commands|' main.go
gsed -i -e 's|Commands: Commands|Commands: command.Commands|' main.go
gsed -i -e 's|base\.BoolValue|BoolValue|' command/operator_autopilot_set.go
gsed -i -e 's|base\.DurationValue|DurationValue|' command/operator_autopilot_set.go
gsed -i -e 's|base\.StringValue|StringValue|' command/operator_autopilot_set.go
gsed -i -e 's|base\.UintValue|UintValue|' command/operator_autopilot_set.go
gsed -i -e 's|\bCommand\b|BaseCommand|' command/base.go
gsed -i -e 's|BaseCommand Options|Command Options|' command/base.go
gsed -i -e 's|base.Command|BaseCommand|' command/*.go
gsed -i -e 's|c\.Command|c.BaseCommand|g' command/*.go
gsed -i -e 's|\tCommand:|\tBaseCommand:|' command/*_test.go
gsed -i -e 's|base\.||' command/*_test.go
gsed -i -e 's|\bCommand\b|AgentCommand|' command/agent{,_test}.go
gsed -i -e 's|cmd.AgentCommand|cmd.BaseCommand|' command/agent.go
gsed -i -e 's|cli.AgentCommand = new(Command)|cli.Command = new(AgentCommand)|' command/agent_test.go
gsed -i -e 's|exec.AgentCommand|exec.Command|' command/agent_test.go
gsed -i -e 's|exec.BaseCommand|exec.Command|' command/agent_test.go
gsed -i -e 's|NewTestAgent|agent.NewTestAgent|' command/agent_test.go
gsed -i -e 's|= TestConfig|= agent.TestConfig|' command/agent_test.go
gsed -i -e 's|: RetryJoin|: agent.RetryJoin|' command/agent_test.go
gsed -i -e 's|\.\./\.\./|../|' command/config_util_test.go
gsed -i -e 's|\bverifyUniqueListeners|VerifyUniqueListeners|' agent/config{,_test}.go command/agent.go
gsed -i -e 's|\bserfLANKeyring\b|SerfLANKeyring|g' agent/{agent,keyring,testagent}.go command/agent.go
gsed -i -e 's|\bserfWANKeyring\b|SerfWANKeyring|g' agent/{agent,keyring,testagent}.go command/agent.go
gsed -i -e 's|\bNewAgent\b|agent.New|g' command/agent{,_test}.go
gsed -i -e 's|\bNewAgent|New|' agent/{acl_test,agent,testagent}.go
gsed -i -e 's|\bAgent\b|agent.&|g' command/agent{,_test}.go
gsed -i -e 's|\bBool\b|agent.&|g' command/agent{,_test}.go
gsed -i -e 's|\bConfig\b|agent.&|g' command/agent{,_test}.go
gsed -i -e 's|\bDefaultConfig\b|agent.&|g' command/agent{,_test}.go
gsed -i -e 's|\bDevConfig\b|agent.&|g' command/agent{,_test}.go
gsed -i -e 's|\bMergeConfig\b|agent.&|g' command/agent{,_test}.go
gsed -i -e 's|\bReadConfigPaths\b|agent.&|g' command/agent{,_test}.go
gsed -i -e 's|\bParseMetaPair\b|agent.&|g' command/agent{,_test}.go
gsed -i -e 's|\bSerfLANKeyring\b|agent.&|g' command/agent{,_test}.go
gsed -i -e 's|\bSerfWANKeyring\b|agent.&|g' command/agent{,_test}.go
gsed -i -e 's|circonus\.agent|circonus|g' command/agent{,_test}.go
gsed -i -e 's|logger\.agent|logger|g' command/agent{,_test}.go
gsed -i -e 's|metrics\.agent|metrics|g' command/agent{,_test}.go
gsed -i -e 's|// agent.Agent|// agent|' command/agent{,_test}.go
gsed -i -e 's|a\.agent\.Config|a.Config|' command/agent{,_test}.go
gsed -i -e 's|agent\.AppendSliceValue|AppendSliceValue|' command/{configtest,validate}.go
gsed -i -e 's|consul/consul|agent/consul|' GNUmakefile
gsed -i -e 's|\.\./test|../../test|' agent/consul/server_test.go
# fix imports
f=$(grep -rl 'github.com/hashicorp/consul/command/agent' * | grep '\.go')
gsed -i -e 's|github.com/hashicorp/consul/command/agent|github.com/hashicorp/consul/agent|' $f
goimports -w $f
f=$(grep -rl 'github.com/hashicorp/consul/consul' * | grep '\.go')
gsed -i -e 's|github.com/hashicorp/consul/consul|github.com/hashicorp/consul/agent/consul|' $f
goimports -w $f
goimports -w command/*.go main.go
)
2017-06-09 22:28:28 +00:00
|
|
|
"github.com/hashicorp/consul/agent/consul/state"
|
2017-07-06 10:34:00 +00:00
|
|
|
"github.com/hashicorp/consul/agent/structs"
|
2020-01-28 23:50:41 +00:00
|
|
|
"github.com/hashicorp/consul/logging"
|
2015-11-07 06:18:11 +00:00
|
|
|
)
|
|
|
|
|
2020-11-13 02:12:12 +00:00
|
|
|
var PreparedQuerySummaries = []prometheus.SummaryDefinition{
|
|
|
|
{
|
2020-11-13 21:18:04 +00:00
|
|
|
Name: []string{"prepared-query", "apply"},
|
2020-11-16 19:02:11 +00:00
|
|
|
Help: "Measures the time it takes to apply a prepared query update.",
|
2020-11-13 02:12:12 +00:00
|
|
|
},
|
|
|
|
{
|
2020-11-13 21:18:04 +00:00
|
|
|
Name: []string{"prepared-query", "explain"},
|
2020-11-16 19:02:11 +00:00
|
|
|
Help: "Measures the time it takes to process a prepared query explain request.",
|
2020-11-13 02:12:12 +00:00
|
|
|
},
|
|
|
|
{
|
2020-11-13 21:18:04 +00:00
|
|
|
Name: []string{"prepared-query", "execute"},
|
2020-11-16 19:02:11 +00:00
|
|
|
Help: "Measures the time it takes to process a prepared query execute request.",
|
2020-11-13 02:12:12 +00:00
|
|
|
},
|
|
|
|
{
|
2020-11-13 21:18:04 +00:00
|
|
|
Name: []string{"prepared-query", "execute_remote"},
|
2020-11-16 19:02:11 +00:00
|
|
|
Help: "Measures the time it takes to process a prepared query execute request that was forwarded to another datacenter.",
|
2020-11-13 02:12:12 +00:00
|
|
|
},
|
|
|
|
}
|
|
|
|
|
2015-11-10 04:37:41 +00:00
|
|
|
// PreparedQuery manages the prepared query endpoint.
|
|
|
|
type PreparedQuery struct {
|
2020-01-28 23:50:41 +00:00
|
|
|
srv *Server
|
|
|
|
logger hclog.Logger
|
2015-11-07 06:18:11 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Apply is used to apply a modifying request to the data store. This should
|
|
|
|
// only be used for operations that modify the data. The ID of the session is
|
|
|
|
// returned in the reply.
|
2015-11-10 04:37:41 +00:00
|
|
|
func (p *PreparedQuery) Apply(args *structs.PreparedQueryRequest, reply *string) (err error) {
|
2020-07-07 19:45:08 +00:00
|
|
|
if done, err := p.srv.ForwardRPC("PreparedQuery.Apply", args, args, reply); done {
|
2015-11-07 06:18:11 +00:00
|
|
|
return err
|
|
|
|
}
|
2017-10-04 23:43:27 +00:00
|
|
|
defer metrics.MeasureSince([]string{"prepared-query", "apply"}, time.Now())
|
2015-11-07 06:18:11 +00:00
|
|
|
|
|
|
|
// Validate the ID. We must create new IDs before applying to the Raft
|
|
|
|
// log since it's not deterministic.
|
2015-11-10 04:37:41 +00:00
|
|
|
if args.Op == structs.PreparedQueryCreate {
|
2015-11-07 06:18:11 +00:00
|
|
|
if args.Query.ID != "" {
|
2015-11-10 04:37:41 +00:00
|
|
|
return fmt.Errorf("ID must be empty when creating a new prepared query")
|
2015-11-07 06:18:11 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// We are relying on the fact that UUIDs are random and unlikely
|
|
|
|
// to collide since this isn't inside a write transaction.
|
2015-11-10 04:37:41 +00:00
|
|
|
state := p.srv.fsm.State()
|
2015-11-07 06:18:11 +00:00
|
|
|
for {
|
2016-01-29 19:42:34 +00:00
|
|
|
if args.Query.ID, err = uuid.GenerateUUID(); err != nil {
|
|
|
|
return fmt.Errorf("UUID generation for prepared query failed: %v", err)
|
|
|
|
}
|
2017-01-24 17:22:07 +00:00
|
|
|
_, query, err := state.PreparedQueryGet(nil, args.Query.ID)
|
2015-11-07 06:18:11 +00:00
|
|
|
if err != nil {
|
2015-11-10 04:37:41 +00:00
|
|
|
return fmt.Errorf("Prepared query lookup failed: %v", err)
|
2015-11-07 06:18:11 +00:00
|
|
|
}
|
|
|
|
if query == nil {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
*reply = args.Query.ID
|
|
|
|
|
2016-02-24 09:26:16 +00:00
|
|
|
// Get the ACL token for the request for the checks below.
|
2018-10-19 16:04:07 +00:00
|
|
|
rule, err := p.srv.ResolveToken(args.Token)
|
2015-11-07 06:18:11 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2016-02-24 09:26:16 +00:00
|
|
|
|
|
|
|
// If prefix ACLs apply to the incoming query, then do an ACL check. We
|
|
|
|
// need to make sure they have write access for whatever they are
|
|
|
|
// proposing.
|
2016-02-25 00:26:43 +00:00
|
|
|
if prefix, ok := args.Query.GetACLPrefix(); ok {
|
2019-10-15 20:58:50 +00:00
|
|
|
if rule != nil && rule.PreparedQueryWrite(prefix, nil) != acl.Allow {
|
2020-01-28 23:50:41 +00:00
|
|
|
p.logger.Warn("Operation on prepared query denied due to ACLs", "query", args.Query.ID)
|
2017-08-23 14:52:48 +00:00
|
|
|
return acl.ErrPermissionDenied
|
2016-02-24 09:26:16 +00:00
|
|
|
}
|
Creates new "prepared-query" ACL type and new token capture behavior.
Prior to this change, prepared queries had the following behavior for
ACLs, which will need to change to support templates:
1. A management token, or a token with read access to the service being
queried needed to be provided in order to create a prepared query.
2. The token used to create the prepared query was stored with the query
in the state store and used to execute the query.
3. A management token, or the token used to create the query needed to be
supplied to perform and CRUD operations on an existing prepared query.
This was pretty subtle and complicated behavior, and won't work for
templates since the service name is computed at execution time. To solve
this, we introduce a new "prepared-query" ACL type, where the prefix
applies to the query name for static prepared query types and to the
prefix for template prepared query types.
With this change, the new behavior is:
1. A management token, or a token with "prepared-query" write access to
the query name or (soon) the given template prefix is required to do
any CRUD operations on a prepared query, or to list prepared queries
(the list is filtered by this ACL).
2. You will no longer need a management token to list prepared queries,
but you will only be able to see prepared queries that you have access
to (you get an empty list instead of permission denied).
3. When listing or getting a query, because it was easy to capture
management tokens given the past behavior, this will always blank out
the "Token" field (replacing the contents as <hidden>) for all tokens
unless a management token is supplied. Going forward, we should
discourage people from binding tokens for execution unless strictly
necessary.
4. No token will be captured by default when a prepared query is created.
If the user wishes to supply an execution token then can pass it in via
the "Token" field in the prepared query definition. Otherwise, this
field will default to empty.
5. At execution time, we will use the captured token if it exists with the
prepared query definition, otherwise we will use the token that's passed
in with the request, just like we do for other RPCs (or you can use the
agent's configured token for DNS).
6. Prepared queries with no name (accessible only by ID) will not require
ACLs to create or modify (execution time will depend on the service ACL
configuration). Our argument here is that these are designed to be
ephemeral and the IDs are as good as an ACL. Management tokens will be
able to list all of these.
These changes enable templates, but also enable delegation of authority to
manage the prepared query namespace.
2016-02-23 08:12:58 +00:00
|
|
|
}
|
2015-11-07 06:18:11 +00:00
|
|
|
|
Creates new "prepared-query" ACL type and new token capture behavior.
Prior to this change, prepared queries had the following behavior for
ACLs, which will need to change to support templates:
1. A management token, or a token with read access to the service being
queried needed to be provided in order to create a prepared query.
2. The token used to create the prepared query was stored with the query
in the state store and used to execute the query.
3. A management token, or the token used to create the query needed to be
supplied to perform and CRUD operations on an existing prepared query.
This was pretty subtle and complicated behavior, and won't work for
templates since the service name is computed at execution time. To solve
this, we introduce a new "prepared-query" ACL type, where the prefix
applies to the query name for static prepared query types and to the
prefix for template prepared query types.
With this change, the new behavior is:
1. A management token, or a token with "prepared-query" write access to
the query name or (soon) the given template prefix is required to do
any CRUD operations on a prepared query, or to list prepared queries
(the list is filtered by this ACL).
2. You will no longer need a management token to list prepared queries,
but you will only be able to see prepared queries that you have access
to (you get an empty list instead of permission denied).
3. When listing or getting a query, because it was easy to capture
management tokens given the past behavior, this will always blank out
the "Token" field (replacing the contents as <hidden>) for all tokens
unless a management token is supplied. Going forward, we should
discourage people from binding tokens for execution unless strictly
necessary.
4. No token will be captured by default when a prepared query is created.
If the user wishes to supply an execution token then can pass it in via
the "Token" field in the prepared query definition. Otherwise, this
field will default to empty.
5. At execution time, we will use the captured token if it exists with the
prepared query definition, otherwise we will use the token that's passed
in with the request, just like we do for other RPCs (or you can use the
agent's configured token for DNS).
6. Prepared queries with no name (accessible only by ID) will not require
ACLs to create or modify (execution time will depend on the service ACL
configuration). Our argument here is that these are designed to be
ephemeral and the IDs are as good as an ACL. Management tokens will be
able to list all of these.
These changes enable templates, but also enable delegation of authority to
manage the prepared query namespace.
2016-02-23 08:12:58 +00:00
|
|
|
// This is the second part of the check above. If they are referencing
|
2016-02-24 09:26:16 +00:00
|
|
|
// an existing query then make sure it exists and that they have write
|
|
|
|
// access to whatever they are changing, if prefix ACLs apply to it.
|
2015-11-10 04:37:41 +00:00
|
|
|
if args.Op != structs.PreparedQueryCreate {
|
|
|
|
state := p.srv.fsm.State()
|
2017-01-24 17:22:07 +00:00
|
|
|
_, query, err := state.PreparedQueryGet(nil, args.Query.ID)
|
2015-11-07 06:18:11 +00:00
|
|
|
if err != nil {
|
2015-11-10 04:37:41 +00:00
|
|
|
return fmt.Errorf("Prepared Query lookup failed: %v", err)
|
2015-11-07 06:18:11 +00:00
|
|
|
}
|
|
|
|
if query == nil {
|
2015-11-10 04:37:41 +00:00
|
|
|
return fmt.Errorf("Cannot modify non-existent prepared query: '%s'", args.Query.ID)
|
2015-11-07 06:18:11 +00:00
|
|
|
}
|
2016-02-24 09:26:16 +00:00
|
|
|
|
2016-02-25 00:26:43 +00:00
|
|
|
if prefix, ok := query.GetACLPrefix(); ok {
|
2019-10-15 20:58:50 +00:00
|
|
|
if rule != nil && rule.PreparedQueryWrite(prefix, nil) != acl.Allow {
|
2020-01-28 23:50:41 +00:00
|
|
|
p.logger.Warn("Operation on prepared query denied due to ACLs", "query", args.Query.ID)
|
2017-08-23 14:52:48 +00:00
|
|
|
return acl.ErrPermissionDenied
|
2016-02-24 09:26:16 +00:00
|
|
|
}
|
2015-11-07 06:18:11 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Parse the query and prep it for the state store.
|
|
|
|
switch args.Op {
|
2015-11-10 04:37:41 +00:00
|
|
|
case structs.PreparedQueryCreate, structs.PreparedQueryUpdate:
|
2020-05-29 21:16:03 +00:00
|
|
|
if err := parseQuery(args.Query); err != nil {
|
2015-11-10 04:37:41 +00:00
|
|
|
return fmt.Errorf("Invalid prepared query: %v", err)
|
2015-11-07 06:18:11 +00:00
|
|
|
}
|
|
|
|
|
2015-11-10 04:37:41 +00:00
|
|
|
case structs.PreparedQueryDelete:
|
2015-11-07 06:18:11 +00:00
|
|
|
// Nothing else to verify here, just do the delete (we only look
|
|
|
|
// at the ID field for this op).
|
|
|
|
|
|
|
|
default:
|
2015-11-10 04:37:41 +00:00
|
|
|
return fmt.Errorf("Unknown prepared query operation: %s", args.Op)
|
2015-11-07 06:18:11 +00:00
|
|
|
}
|
|
|
|
|
Creates new "prepared-query" ACL type and new token capture behavior.
Prior to this change, prepared queries had the following behavior for
ACLs, which will need to change to support templates:
1. A management token, or a token with read access to the service being
queried needed to be provided in order to create a prepared query.
2. The token used to create the prepared query was stored with the query
in the state store and used to execute the query.
3. A management token, or the token used to create the query needed to be
supplied to perform and CRUD operations on an existing prepared query.
This was pretty subtle and complicated behavior, and won't work for
templates since the service name is computed at execution time. To solve
this, we introduce a new "prepared-query" ACL type, where the prefix
applies to the query name for static prepared query types and to the
prefix for template prepared query types.
With this change, the new behavior is:
1. A management token, or a token with "prepared-query" write access to
the query name or (soon) the given template prefix is required to do
any CRUD operations on a prepared query, or to list prepared queries
(the list is filtered by this ACL).
2. You will no longer need a management token to list prepared queries,
but you will only be able to see prepared queries that you have access
to (you get an empty list instead of permission denied).
3. When listing or getting a query, because it was easy to capture
management tokens given the past behavior, this will always blank out
the "Token" field (replacing the contents as <hidden>) for all tokens
unless a management token is supplied. Going forward, we should
discourage people from binding tokens for execution unless strictly
necessary.
4. No token will be captured by default when a prepared query is created.
If the user wishes to supply an execution token then can pass it in via
the "Token" field in the prepared query definition. Otherwise, this
field will default to empty.
5. At execution time, we will use the captured token if it exists with the
prepared query definition, otherwise we will use the token that's passed
in with the request, just like we do for other RPCs (or you can use the
agent's configured token for DNS).
6. Prepared queries with no name (accessible only by ID) will not require
ACLs to create or modify (execution time will depend on the service ACL
configuration). Our argument here is that these are designed to be
ephemeral and the IDs are as good as an ACL. Management tokens will be
able to list all of these.
These changes enable templates, but also enable delegation of authority to
manage the prepared query namespace.
2016-02-23 08:12:58 +00:00
|
|
|
// Commit the query to the state store.
|
2021-04-08 22:58:15 +00:00
|
|
|
_, err = p.srv.raftApply(structs.PreparedQueryRequestType, args)
|
2015-11-07 06:18:11 +00:00
|
|
|
if err != nil {
|
2021-04-08 22:58:15 +00:00
|
|
|
return fmt.Errorf("raft apply failed: %w", err)
|
2015-11-07 06:18:11 +00:00
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// parseQuery makes sure the entries of a query are valid for a create or
|
|
|
|
// update operation. Some of the fields are not checked or are partially
|
|
|
|
// checked, as noted in the comments below. This also updates all the parsed
|
|
|
|
// fields of the query.
|
2020-05-29 21:16:03 +00:00
|
|
|
func parseQuery(query *structs.PreparedQuery) error {
|
2015-11-07 06:18:11 +00:00
|
|
|
// We skip a few fields:
|
|
|
|
// - ID is checked outside this fn.
|
|
|
|
// - Name is optional with no restrictions, except for uniqueness which
|
|
|
|
// is checked for integrity during the transaction. We also make sure
|
|
|
|
// names do not overlap with IDs, which is also checked during the
|
|
|
|
// transaction. Otherwise, people could "steal" queries that they don't
|
|
|
|
// have proper ACL rights to change.
|
2016-02-26 08:25:44 +00:00
|
|
|
// - Template is checked during the transaction since that's where we
|
|
|
|
// compile it.
|
2016-02-25 01:23:09 +00:00
|
|
|
|
2016-12-10 19:19:08 +00:00
|
|
|
// Anonymous queries require a session or need to be part of a template.
|
2020-05-29 21:16:03 +00:00
|
|
|
if query.Name == "" && query.Template.Type == "" && query.Session == "" {
|
|
|
|
return fmt.Errorf("Must be bound to a session")
|
2016-12-10 19:19:08 +00:00
|
|
|
}
|
|
|
|
|
2016-02-25 01:23:09 +00:00
|
|
|
// Token is checked when the query is executed, but we do make sure the
|
|
|
|
// user hasn't accidentally pasted-in the special redacted token name,
|
|
|
|
// which if we allowed in would be super hard to debug and understand.
|
|
|
|
if query.Token == redactedToken {
|
|
|
|
return fmt.Errorf("Bad Token '%s', it looks like a query definition with a redacted token was submitted", query.Token)
|
|
|
|
}
|
2015-11-07 06:18:11 +00:00
|
|
|
|
|
|
|
// Parse the service query sub-structure.
|
|
|
|
if err := parseService(&query.Service); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Parse the DNS options sub-structure.
|
|
|
|
if err := parseDNS(&query.DNS); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// parseService makes sure the entries of a query are valid for a create or
|
|
|
|
// update operation. Some of the fields are not checked or are partially
|
|
|
|
// checked, as noted in the comments below. This also updates all the parsed
|
|
|
|
// fields of the query.
|
|
|
|
func parseService(svc *structs.ServiceQuery) error {
|
Creates new "prepared-query" ACL type and new token capture behavior.
Prior to this change, prepared queries had the following behavior for
ACLs, which will need to change to support templates:
1. A management token, or a token with read access to the service being
queried needed to be provided in order to create a prepared query.
2. The token used to create the prepared query was stored with the query
in the state store and used to execute the query.
3. A management token, or the token used to create the query needed to be
supplied to perform and CRUD operations on an existing prepared query.
This was pretty subtle and complicated behavior, and won't work for
templates since the service name is computed at execution time. To solve
this, we introduce a new "prepared-query" ACL type, where the prefix
applies to the query name for static prepared query types and to the
prefix for template prepared query types.
With this change, the new behavior is:
1. A management token, or a token with "prepared-query" write access to
the query name or (soon) the given template prefix is required to do
any CRUD operations on a prepared query, or to list prepared queries
(the list is filtered by this ACL).
2. You will no longer need a management token to list prepared queries,
but you will only be able to see prepared queries that you have access
to (you get an empty list instead of permission denied).
3. When listing or getting a query, because it was easy to capture
management tokens given the past behavior, this will always blank out
the "Token" field (replacing the contents as <hidden>) for all tokens
unless a management token is supplied. Going forward, we should
discourage people from binding tokens for execution unless strictly
necessary.
4. No token will be captured by default when a prepared query is created.
If the user wishes to supply an execution token then can pass it in via
the "Token" field in the prepared query definition. Otherwise, this
field will default to empty.
5. At execution time, we will use the captured token if it exists with the
prepared query definition, otherwise we will use the token that's passed
in with the request, just like we do for other RPCs (or you can use the
agent's configured token for DNS).
6. Prepared queries with no name (accessible only by ID) will not require
ACLs to create or modify (execution time will depend on the service ACL
configuration). Our argument here is that these are designed to be
ephemeral and the IDs are as good as an ACL. Management tokens will be
able to list all of these.
These changes enable templates, but also enable delegation of authority to
manage the prepared query namespace.
2016-02-23 08:12:58 +00:00
|
|
|
// Service is required.
|
2015-11-07 06:18:11 +00:00
|
|
|
if svc.Service == "" {
|
2016-02-26 08:25:44 +00:00
|
|
|
return fmt.Errorf("Must provide a Service name to query")
|
2015-11-07 06:18:11 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// NearestN can be 0 which means "don't fail over by RTT".
|
|
|
|
if svc.Failover.NearestN < 0 {
|
|
|
|
return fmt.Errorf("Bad NearestN '%d', must be >= 0", svc.Failover.NearestN)
|
|
|
|
}
|
|
|
|
|
2017-01-23 23:53:45 +00:00
|
|
|
// Make sure the metadata filters are valid
|
2020-03-09 20:59:02 +00:00
|
|
|
if err := structs.ValidateNodeMetadata(svc.NodeMeta, true); err != nil {
|
2017-01-23 23:53:45 +00:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2015-11-07 06:18:11 +00:00
|
|
|
// We skip a few fields:
|
|
|
|
// - There's no validation for Datacenters; we skip any unknown entries
|
|
|
|
// at execution time.
|
|
|
|
// - OnlyPassing is just a boolean so doesn't need further validation.
|
|
|
|
// - Tags is a free-form list of tags and doesn't need further validation.
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// parseDNS makes sure the entries of a query are valid for a create or
|
|
|
|
// update operation. This also updates all the parsed fields of the query.
|
|
|
|
func parseDNS(dns *structs.QueryDNSOptions) error {
|
|
|
|
if dns.TTL != "" {
|
|
|
|
ttl, err := time.ParseDuration(dns.TTL)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("Bad DNS TTL '%s': %v", dns.TTL, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if ttl < 0 {
|
|
|
|
return fmt.Errorf("DNS TTL '%d', must be >=0", ttl)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2015-11-11 20:20:40 +00:00
|
|
|
// Get returns a single prepared query by ID.
|
2015-11-12 01:27:51 +00:00
|
|
|
func (p *PreparedQuery) Get(args *structs.PreparedQuerySpecificRequest,
|
|
|
|
reply *structs.IndexedPreparedQueries) error {
|
2020-07-07 19:45:08 +00:00
|
|
|
if done, err := p.srv.ForwardRPC("PreparedQuery.Get", args, args, reply); done {
|
2015-11-10 07:03:20 +00:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2017-01-24 17:22:07 +00:00
|
|
|
return p.srv.blockingQuery(
|
2015-11-10 07:03:20 +00:00
|
|
|
&args.QueryOptions,
|
|
|
|
&reply.QueryMeta,
|
2017-04-21 00:46:29 +00:00
|
|
|
func(ws memdb.WatchSet, state *state.Store) error {
|
2017-01-24 17:22:07 +00:00
|
|
|
index, query, err := state.PreparedQueryGet(ws, args.QueryID)
|
2015-11-10 07:03:20 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2015-11-13 20:57:06 +00:00
|
|
|
if query == nil {
|
2020-07-01 00:49:13 +00:00
|
|
|
return structs.ErrQueryNotFound
|
2015-11-13 20:57:06 +00:00
|
|
|
}
|
2015-11-10 07:03:20 +00:00
|
|
|
|
2016-02-24 09:26:16 +00:00
|
|
|
// If no prefix ACL applies to this query, then they are
|
2016-02-26 23:59:00 +00:00
|
|
|
// always allowed to see it if they have the ID. We still
|
|
|
|
// have to filter the remaining object for tokens.
|
Creates new "prepared-query" ACL type and new token capture behavior.
Prior to this change, prepared queries had the following behavior for
ACLs, which will need to change to support templates:
1. A management token, or a token with read access to the service being
queried needed to be provided in order to create a prepared query.
2. The token used to create the prepared query was stored with the query
in the state store and used to execute the query.
3. A management token, or the token used to create the query needed to be
supplied to perform and CRUD operations on an existing prepared query.
This was pretty subtle and complicated behavior, and won't work for
templates since the service name is computed at execution time. To solve
this, we introduce a new "prepared-query" ACL type, where the prefix
applies to the query name for static prepared query types and to the
prefix for template prepared query types.
With this change, the new behavior is:
1. A management token, or a token with "prepared-query" write access to
the query name or (soon) the given template prefix is required to do
any CRUD operations on a prepared query, or to list prepared queries
(the list is filtered by this ACL).
2. You will no longer need a management token to list prepared queries,
but you will only be able to see prepared queries that you have access
to (you get an empty list instead of permission denied).
3. When listing or getting a query, because it was easy to capture
management tokens given the past behavior, this will always blank out
the "Token" field (replacing the contents as <hidden>) for all tokens
unless a management token is supplied. Going forward, we should
discourage people from binding tokens for execution unless strictly
necessary.
4. No token will be captured by default when a prepared query is created.
If the user wishes to supply an execution token then can pass it in via
the "Token" field in the prepared query definition. Otherwise, this
field will default to empty.
5. At execution time, we will use the captured token if it exists with the
prepared query definition, otherwise we will use the token that's passed
in with the request, just like we do for other RPCs (or you can use the
agent's configured token for DNS).
6. Prepared queries with no name (accessible only by ID) will not require
ACLs to create or modify (execution time will depend on the service ACL
configuration). Our argument here is that these are designed to be
ephemeral and the IDs are as good as an ACL. Management tokens will be
able to list all of these.
These changes enable templates, but also enable delegation of authority to
manage the prepared query namespace.
2016-02-23 08:12:58 +00:00
|
|
|
reply.Index = index
|
|
|
|
reply.Queries = structs.PreparedQueries{query}
|
2016-02-25 00:26:43 +00:00
|
|
|
if _, ok := query.GetACLPrefix(); !ok {
|
2016-02-26 23:59:00 +00:00
|
|
|
return p.srv.filterACL(args.Token, &reply.Queries[0])
|
2016-02-24 09:26:16 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Otherwise, attempt to filter it the usual way.
|
Creates new "prepared-query" ACL type and new token capture behavior.
Prior to this change, prepared queries had the following behavior for
ACLs, which will need to change to support templates:
1. A management token, or a token with read access to the service being
queried needed to be provided in order to create a prepared query.
2. The token used to create the prepared query was stored with the query
in the state store and used to execute the query.
3. A management token, or the token used to create the query needed to be
supplied to perform and CRUD operations on an existing prepared query.
This was pretty subtle and complicated behavior, and won't work for
templates since the service name is computed at execution time. To solve
this, we introduce a new "prepared-query" ACL type, where the prefix
applies to the query name for static prepared query types and to the
prefix for template prepared query types.
With this change, the new behavior is:
1. A management token, or a token with "prepared-query" write access to
the query name or (soon) the given template prefix is required to do
any CRUD operations on a prepared query, or to list prepared queries
(the list is filtered by this ACL).
2. You will no longer need a management token to list prepared queries,
but you will only be able to see prepared queries that you have access
to (you get an empty list instead of permission denied).
3. When listing or getting a query, because it was easy to capture
management tokens given the past behavior, this will always blank out
the "Token" field (replacing the contents as <hidden>) for all tokens
unless a management token is supplied. Going forward, we should
discourage people from binding tokens for execution unless strictly
necessary.
4. No token will be captured by default when a prepared query is created.
If the user wishes to supply an execution token then can pass it in via
the "Token" field in the prepared query definition. Otherwise, this
field will default to empty.
5. At execution time, we will use the captured token if it exists with the
prepared query definition, otherwise we will use the token that's passed
in with the request, just like we do for other RPCs (or you can use the
agent's configured token for DNS).
6. Prepared queries with no name (accessible only by ID) will not require
ACLs to create or modify (execution time will depend on the service ACL
configuration). Our argument here is that these are designed to be
ephemeral and the IDs are as good as an ACL. Management tokens will be
able to list all of these.
These changes enable templates, but also enable delegation of authority to
manage the prepared query namespace.
2016-02-23 08:12:58 +00:00
|
|
|
if err := p.srv.filterACL(args.Token, reply); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2016-02-24 09:26:16 +00:00
|
|
|
// Since this is a GET of a specific query, if ACLs have
|
|
|
|
// prevented us from returning something that exists,
|
|
|
|
// then alert the user with a permission denied error.
|
Creates new "prepared-query" ACL type and new token capture behavior.
Prior to this change, prepared queries had the following behavior for
ACLs, which will need to change to support templates:
1. A management token, or a token with read access to the service being
queried needed to be provided in order to create a prepared query.
2. The token used to create the prepared query was stored with the query
in the state store and used to execute the query.
3. A management token, or the token used to create the query needed to be
supplied to perform and CRUD operations on an existing prepared query.
This was pretty subtle and complicated behavior, and won't work for
templates since the service name is computed at execution time. To solve
this, we introduce a new "prepared-query" ACL type, where the prefix
applies to the query name for static prepared query types and to the
prefix for template prepared query types.
With this change, the new behavior is:
1. A management token, or a token with "prepared-query" write access to
the query name or (soon) the given template prefix is required to do
any CRUD operations on a prepared query, or to list prepared queries
(the list is filtered by this ACL).
2. You will no longer need a management token to list prepared queries,
but you will only be able to see prepared queries that you have access
to (you get an empty list instead of permission denied).
3. When listing or getting a query, because it was easy to capture
management tokens given the past behavior, this will always blank out
the "Token" field (replacing the contents as <hidden>) for all tokens
unless a management token is supplied. Going forward, we should
discourage people from binding tokens for execution unless strictly
necessary.
4. No token will be captured by default when a prepared query is created.
If the user wishes to supply an execution token then can pass it in via
the "Token" field in the prepared query definition. Otherwise, this
field will default to empty.
5. At execution time, we will use the captured token if it exists with the
prepared query definition, otherwise we will use the token that's passed
in with the request, just like we do for other RPCs (or you can use the
agent's configured token for DNS).
6. Prepared queries with no name (accessible only by ID) will not require
ACLs to create or modify (execution time will depend on the service ACL
configuration). Our argument here is that these are designed to be
ephemeral and the IDs are as good as an ACL. Management tokens will be
able to list all of these.
These changes enable templates, but also enable delegation of authority to
manage the prepared query namespace.
2016-02-23 08:12:58 +00:00
|
|
|
if len(reply.Queries) == 0 {
|
2020-01-28 23:50:41 +00:00
|
|
|
p.logger.Warn("Request to get prepared query denied due to ACLs", "query", args.QueryID)
|
2017-08-23 14:52:48 +00:00
|
|
|
return acl.ErrPermissionDenied
|
2015-11-10 07:03:20 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
// List returns all the prepared queries.
|
|
|
|
func (p *PreparedQuery) List(args *structs.DCSpecificRequest, reply *structs.IndexedPreparedQueries) error {
|
2020-07-07 19:45:08 +00:00
|
|
|
if done, err := p.srv.ForwardRPC("PreparedQuery.List", args, args, reply); done {
|
2015-11-10 07:03:20 +00:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2017-01-24 17:22:07 +00:00
|
|
|
return p.srv.blockingQuery(
|
2015-11-10 07:03:20 +00:00
|
|
|
&args.QueryOptions,
|
|
|
|
&reply.QueryMeta,
|
2017-04-21 00:46:29 +00:00
|
|
|
func(ws memdb.WatchSet, state *state.Store) error {
|
2017-01-24 17:22:07 +00:00
|
|
|
index, queries, err := state.PreparedQueryList(ws)
|
2015-11-10 07:03:20 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
reply.Index, reply.Queries = index, queries
|
Creates new "prepared-query" ACL type and new token capture behavior.
Prior to this change, prepared queries had the following behavior for
ACLs, which will need to change to support templates:
1. A management token, or a token with read access to the service being
queried needed to be provided in order to create a prepared query.
2. The token used to create the prepared query was stored with the query
in the state store and used to execute the query.
3. A management token, or the token used to create the query needed to be
supplied to perform and CRUD operations on an existing prepared query.
This was pretty subtle and complicated behavior, and won't work for
templates since the service name is computed at execution time. To solve
this, we introduce a new "prepared-query" ACL type, where the prefix
applies to the query name for static prepared query types and to the
prefix for template prepared query types.
With this change, the new behavior is:
1. A management token, or a token with "prepared-query" write access to
the query name or (soon) the given template prefix is required to do
any CRUD operations on a prepared query, or to list prepared queries
(the list is filtered by this ACL).
2. You will no longer need a management token to list prepared queries,
but you will only be able to see prepared queries that you have access
to (you get an empty list instead of permission denied).
3. When listing or getting a query, because it was easy to capture
management tokens given the past behavior, this will always blank out
the "Token" field (replacing the contents as <hidden>) for all tokens
unless a management token is supplied. Going forward, we should
discourage people from binding tokens for execution unless strictly
necessary.
4. No token will be captured by default when a prepared query is created.
If the user wishes to supply an execution token then can pass it in via
the "Token" field in the prepared query definition. Otherwise, this
field will default to empty.
5. At execution time, we will use the captured token if it exists with the
prepared query definition, otherwise we will use the token that's passed
in with the request, just like we do for other RPCs (or you can use the
agent's configured token for DNS).
6. Prepared queries with no name (accessible only by ID) will not require
ACLs to create or modify (execution time will depend on the service ACL
configuration). Our argument here is that these are designed to be
ephemeral and the IDs are as good as an ACL. Management tokens will be
able to list all of these.
These changes enable templates, but also enable delegation of authority to
manage the prepared query namespace.
2016-02-23 08:12:58 +00:00
|
|
|
return p.srv.filterACL(args.Token, reply)
|
2015-11-10 07:03:20 +00:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2016-03-04 01:30:36 +00:00
|
|
|
// Explain resolves a prepared query and returns the (possibly rendered template)
|
2016-03-03 09:04:12 +00:00
|
|
|
// to the caller. This is useful for letting operators figure out which query is
|
2016-03-04 01:30:36 +00:00
|
|
|
// picking up a given name. We can also add additional info about how the query
|
|
|
|
// will be executed here.
|
|
|
|
func (p *PreparedQuery) Explain(args *structs.PreparedQueryExecuteRequest,
|
|
|
|
reply *structs.PreparedQueryExplainResponse) error {
|
2020-07-07 19:45:08 +00:00
|
|
|
if done, err := p.srv.ForwardRPC("PreparedQuery.Explain", args, args, reply); done {
|
2016-03-03 09:04:12 +00:00
|
|
|
return err
|
|
|
|
}
|
2017-10-04 23:43:27 +00:00
|
|
|
defer metrics.MeasureSince([]string{"prepared-query", "explain"}, time.Now())
|
2016-03-03 09:04:12 +00:00
|
|
|
|
|
|
|
// We have to do this ourselves since we are not doing a blocking RPC.
|
|
|
|
p.srv.setQueryMeta(&reply.QueryMeta)
|
|
|
|
if args.RequireConsistent {
|
|
|
|
if err := p.srv.consistentRead(); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Try to locate the query.
|
|
|
|
state := p.srv.fsm.State()
|
2017-08-30 00:02:50 +00:00
|
|
|
_, query, err := state.PreparedQueryResolve(args.QueryIDOrName, args.Agent)
|
2016-03-03 09:04:12 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if query == nil {
|
2020-07-01 00:49:13 +00:00
|
|
|
return structs.ErrQueryNotFound
|
2016-03-03 09:04:12 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Place the query into a list so we can run the standard ACL filter on
|
|
|
|
// it.
|
|
|
|
queries := &structs.IndexedPreparedQueries{
|
|
|
|
Queries: structs.PreparedQueries{query},
|
|
|
|
}
|
|
|
|
if err := p.srv.filterACL(args.Token, queries); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// If the query was filtered out, return an error.
|
|
|
|
if len(queries.Queries) == 0 {
|
2020-01-28 23:50:41 +00:00
|
|
|
p.logger.Warn("Explain on prepared query denied due to ACLs", "query", query.ID)
|
2017-08-23 14:52:48 +00:00
|
|
|
return acl.ErrPermissionDenied
|
2016-03-03 09:04:12 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
reply.Query = *(queries.Queries[0])
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2015-11-07 06:18:11 +00:00
|
|
|
// Execute runs a prepared query and returns the results. This will perform the
|
|
|
|
// failover logic if no local results are available. This is typically called as
|
|
|
|
// part of a DNS lookup, or when executing prepared queries from the HTTP API.
|
2015-11-10 04:37:41 +00:00
|
|
|
func (p *PreparedQuery) Execute(args *structs.PreparedQueryExecuteRequest,
|
|
|
|
reply *structs.PreparedQueryExecuteResponse) error {
|
2020-07-07 19:45:08 +00:00
|
|
|
if done, err := p.srv.ForwardRPC("PreparedQuery.Execute", args, args, reply); done {
|
2015-11-07 06:18:11 +00:00
|
|
|
return err
|
|
|
|
}
|
2017-10-04 23:43:27 +00:00
|
|
|
defer metrics.MeasureSince([]string{"prepared-query", "execute"}, time.Now())
|
2015-11-07 06:18:11 +00:00
|
|
|
|
|
|
|
// We have to do this ourselves since we are not doing a blocking RPC.
|
2015-11-12 06:34:46 +00:00
|
|
|
p.srv.setQueryMeta(&reply.QueryMeta)
|
2015-11-07 06:18:11 +00:00
|
|
|
if args.RequireConsistent {
|
2015-11-10 04:37:41 +00:00
|
|
|
if err := p.srv.consistentRead(); err != nil {
|
2015-11-07 06:18:11 +00:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Try to locate the query.
|
2015-11-10 04:37:41 +00:00
|
|
|
state := p.srv.fsm.State()
|
2017-08-30 00:02:50 +00:00
|
|
|
_, query, err := state.PreparedQueryResolve(args.QueryIDOrName, args.Agent)
|
2015-11-07 06:18:11 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if query == nil {
|
2020-07-01 00:49:13 +00:00
|
|
|
return structs.ErrQueryNotFound
|
2015-11-07 06:18:11 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Execute the query for the local DC.
|
2018-06-05 21:42:01 +00:00
|
|
|
if err := p.execute(query, reply, args.Connect); err != nil {
|
2015-11-07 06:18:11 +00:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
Creates new "prepared-query" ACL type and new token capture behavior.
Prior to this change, prepared queries had the following behavior for
ACLs, which will need to change to support templates:
1. A management token, or a token with read access to the service being
queried needed to be provided in order to create a prepared query.
2. The token used to create the prepared query was stored with the query
in the state store and used to execute the query.
3. A management token, or the token used to create the query needed to be
supplied to perform and CRUD operations on an existing prepared query.
This was pretty subtle and complicated behavior, and won't work for
templates since the service name is computed at execution time. To solve
this, we introduce a new "prepared-query" ACL type, where the prefix
applies to the query name for static prepared query types and to the
prefix for template prepared query types.
With this change, the new behavior is:
1. A management token, or a token with "prepared-query" write access to
the query name or (soon) the given template prefix is required to do
any CRUD operations on a prepared query, or to list prepared queries
(the list is filtered by this ACL).
2. You will no longer need a management token to list prepared queries,
but you will only be able to see prepared queries that you have access
to (you get an empty list instead of permission denied).
3. When listing or getting a query, because it was easy to capture
management tokens given the past behavior, this will always blank out
the "Token" field (replacing the contents as <hidden>) for all tokens
unless a management token is supplied. Going forward, we should
discourage people from binding tokens for execution unless strictly
necessary.
4. No token will be captured by default when a prepared query is created.
If the user wishes to supply an execution token then can pass it in via
the "Token" field in the prepared query definition. Otherwise, this
field will default to empty.
5. At execution time, we will use the captured token if it exists with the
prepared query definition, otherwise we will use the token that's passed
in with the request, just like we do for other RPCs (or you can use the
agent's configured token for DNS).
6. Prepared queries with no name (accessible only by ID) will not require
ACLs to create or modify (execution time will depend on the service ACL
configuration). Our argument here is that these are designed to be
ephemeral and the IDs are as good as an ACL. Management tokens will be
able to list all of these.
These changes enable templates, but also enable delegation of authority to
manage the prepared query namespace.
2016-02-23 08:12:58 +00:00
|
|
|
// If they supplied a token with the query, use that, otherwise use the
|
|
|
|
// token passed in with the request.
|
|
|
|
token := args.QueryOptions.Token
|
|
|
|
if query.Token != "" {
|
|
|
|
token = query.Token
|
|
|
|
}
|
|
|
|
if err := p.srv.filterACL(token, &reply.Nodes); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO (slackpad) We could add a special case here that will avoid the
|
|
|
|
// fail over if we filtered everything due to ACLs. This seems like it
|
|
|
|
// might not be worth the code complexity and behavior differences,
|
|
|
|
// though, since this is essentially a misconfiguration.
|
|
|
|
|
2015-11-07 06:18:11 +00:00
|
|
|
// Shuffle the results in case coordinates are not available if they
|
|
|
|
// requested an RTT sort.
|
|
|
|
reply.Nodes.Shuffle()
|
2016-06-21 19:39:40 +00:00
|
|
|
|
2016-06-30 23:51:18 +00:00
|
|
|
// Build the query source. This can be provided by the client, or by
|
|
|
|
// the prepared query. Client-specified takes priority.
|
|
|
|
qs := args.Source
|
|
|
|
if qs.Datacenter == "" {
|
|
|
|
qs.Datacenter = args.Agent.Datacenter
|
|
|
|
}
|
|
|
|
if query.Service.Near != "" && qs.Node == "" {
|
|
|
|
qs.Node = query.Service.Near
|
|
|
|
}
|
2015-11-10 04:59:16 +00:00
|
|
|
|
2016-06-30 23:51:18 +00:00
|
|
|
// Respect the magic "_agent" flag.
|
|
|
|
if qs.Node == "_agent" {
|
|
|
|
qs.Node = args.Agent.Node
|
2018-04-10 18:50:50 +00:00
|
|
|
} else if qs.Node == "_ip" {
|
|
|
|
if args.Source.Ip != "" {
|
|
|
|
_, nodes, err := state.Nodes(nil)
|
2018-04-12 00:32:35 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, node := range nodes {
|
|
|
|
if args.Source.Ip == node.Address {
|
|
|
|
qs.Node = node.Node
|
|
|
|
break
|
2018-04-10 18:50:50 +00:00
|
|
|
}
|
|
|
|
}
|
2018-04-12 14:10:37 +00:00
|
|
|
} else {
|
2020-01-28 23:50:41 +00:00
|
|
|
p.logger.Warn("Prepared Query using near=_ip requires " +
|
2018-04-13 16:57:25 +00:00
|
|
|
"the source IP to be set but none was provided. No distance " +
|
2018-04-12 14:10:37 +00:00
|
|
|
"sorting will be done.")
|
2018-04-13 16:57:25 +00:00
|
|
|
|
2018-04-12 00:32:35 +00:00
|
|
|
}
|
2018-04-13 16:57:25 +00:00
|
|
|
|
2018-04-10 18:50:50 +00:00
|
|
|
// Either a source IP was given but we couldnt find the associated node
|
|
|
|
// or no source ip was given. In both cases we should wipe the Node value
|
|
|
|
if qs.Node == "_ip" {
|
|
|
|
qs.Node = ""
|
|
|
|
}
|
2016-06-30 23:51:18 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Perform the distance sort
|
|
|
|
err = p.srv.sortNodesByDistanceFrom(qs, reply.Nodes)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
2016-06-20 21:07:08 +00:00
|
|
|
}
|
|
|
|
|
2016-07-01 21:28:58 +00:00
|
|
|
// If we applied a distance sort, make sure that the node queried for is in
|
|
|
|
// position 0, provided the results are from the same datacenter.
|
|
|
|
if qs.Node != "" && reply.Datacenter == qs.Datacenter {
|
|
|
|
for i, node := range reply.Nodes {
|
|
|
|
if node.Node.Node == qs.Node {
|
|
|
|
reply.Nodes[0], reply.Nodes[i] = reply.Nodes[i], reply.Nodes[0]
|
|
|
|
break
|
|
|
|
}
|
|
|
|
|
|
|
|
// Put a cap on the depth of the search. The local agent should
|
|
|
|
// never be further in than this if distance sorting was applied.
|
|
|
|
if i == 9 {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-11-10 04:59:16 +00:00
|
|
|
// Apply the limit if given.
|
|
|
|
if args.Limit > 0 && len(reply.Nodes) > args.Limit {
|
|
|
|
reply.Nodes = reply.Nodes[:args.Limit]
|
2015-11-07 06:18:11 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// In the happy path where we found some healthy nodes we go with that
|
|
|
|
// and bail out. Otherwise, we fail over and try remote DCs, as allowed
|
|
|
|
// by the query setup.
|
|
|
|
if len(reply.Nodes) == 0 {
|
2015-11-10 04:37:41 +00:00
|
|
|
wrapper := &queryServerWrapper{p.srv}
|
2018-06-05 21:42:01 +00:00
|
|
|
if err := queryFailover(wrapper, query, args, reply); err != nil {
|
2015-11-07 06:18:11 +00:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// ExecuteRemote is used when a local node doesn't have any instances of a
|
|
|
|
// service available and needs to probe remote DCs. This sends the full query
|
|
|
|
// over since the remote side won't have it in its state store, and this doesn't
|
|
|
|
// do the failover logic since that's already being run on the originating DC.
|
|
|
|
// We don't want things to fan out further than one level.
|
2015-11-10 04:37:41 +00:00
|
|
|
func (p *PreparedQuery) ExecuteRemote(args *structs.PreparedQueryExecuteRemoteRequest,
|
|
|
|
reply *structs.PreparedQueryExecuteResponse) error {
|
2020-07-07 19:45:08 +00:00
|
|
|
if done, err := p.srv.ForwardRPC("PreparedQuery.ExecuteRemote", args, args, reply); done {
|
2015-11-07 06:18:11 +00:00
|
|
|
return err
|
|
|
|
}
|
2017-10-04 23:43:27 +00:00
|
|
|
defer metrics.MeasureSince([]string{"prepared-query", "execute_remote"}, time.Now())
|
2015-11-07 06:18:11 +00:00
|
|
|
|
|
|
|
// We have to do this ourselves since we are not doing a blocking RPC.
|
2015-11-12 06:34:46 +00:00
|
|
|
p.srv.setQueryMeta(&reply.QueryMeta)
|
2015-11-07 06:18:11 +00:00
|
|
|
if args.RequireConsistent {
|
2015-11-10 04:37:41 +00:00
|
|
|
if err := p.srv.consistentRead(); err != nil {
|
2015-11-07 06:18:11 +00:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Run the query locally to see what we can find.
|
2018-06-05 21:42:01 +00:00
|
|
|
if err := p.execute(&args.Query, reply, args.Connect); err != nil {
|
2015-11-07 06:18:11 +00:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
Creates new "prepared-query" ACL type and new token capture behavior.
Prior to this change, prepared queries had the following behavior for
ACLs, which will need to change to support templates:
1. A management token, or a token with read access to the service being
queried needed to be provided in order to create a prepared query.
2. The token used to create the prepared query was stored with the query
in the state store and used to execute the query.
3. A management token, or the token used to create the query needed to be
supplied to perform and CRUD operations on an existing prepared query.
This was pretty subtle and complicated behavior, and won't work for
templates since the service name is computed at execution time. To solve
this, we introduce a new "prepared-query" ACL type, where the prefix
applies to the query name for static prepared query types and to the
prefix for template prepared query types.
With this change, the new behavior is:
1. A management token, or a token with "prepared-query" write access to
the query name or (soon) the given template prefix is required to do
any CRUD operations on a prepared query, or to list prepared queries
(the list is filtered by this ACL).
2. You will no longer need a management token to list prepared queries,
but you will only be able to see prepared queries that you have access
to (you get an empty list instead of permission denied).
3. When listing or getting a query, because it was easy to capture
management tokens given the past behavior, this will always blank out
the "Token" field (replacing the contents as <hidden>) for all tokens
unless a management token is supplied. Going forward, we should
discourage people from binding tokens for execution unless strictly
necessary.
4. No token will be captured by default when a prepared query is created.
If the user wishes to supply an execution token then can pass it in via
the "Token" field in the prepared query definition. Otherwise, this
field will default to empty.
5. At execution time, we will use the captured token if it exists with the
prepared query definition, otherwise we will use the token that's passed
in with the request, just like we do for other RPCs (or you can use the
agent's configured token for DNS).
6. Prepared queries with no name (accessible only by ID) will not require
ACLs to create or modify (execution time will depend on the service ACL
configuration). Our argument here is that these are designed to be
ephemeral and the IDs are as good as an ACL. Management tokens will be
able to list all of these.
These changes enable templates, but also enable delegation of authority to
manage the prepared query namespace.
2016-02-23 08:12:58 +00:00
|
|
|
// If they supplied a token with the query, use that, otherwise use the
|
|
|
|
// token passed in with the request.
|
|
|
|
token := args.QueryOptions.Token
|
|
|
|
if args.Query.Token != "" {
|
|
|
|
token = args.Query.Token
|
|
|
|
}
|
|
|
|
if err := p.srv.filterACL(token, &reply.Nodes); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2015-11-07 06:18:11 +00:00
|
|
|
// We don't bother trying to do an RTT sort here since we are by
|
|
|
|
// definition in another DC. We just shuffle to make sure that we
|
|
|
|
// balance the load across the results.
|
|
|
|
reply.Nodes.Shuffle()
|
|
|
|
|
2015-11-10 04:59:16 +00:00
|
|
|
// Apply the limit if given.
|
|
|
|
if args.Limit > 0 && len(reply.Nodes) > args.Limit {
|
|
|
|
reply.Nodes = reply.Nodes[:args.Limit]
|
|
|
|
}
|
|
|
|
|
2015-11-07 06:18:11 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// execute runs a prepared query in the local DC without any failover. We don't
|
Creates new "prepared-query" ACL type and new token capture behavior.
Prior to this change, prepared queries had the following behavior for
ACLs, which will need to change to support templates:
1. A management token, or a token with read access to the service being
queried needed to be provided in order to create a prepared query.
2. The token used to create the prepared query was stored with the query
in the state store and used to execute the query.
3. A management token, or the token used to create the query needed to be
supplied to perform and CRUD operations on an existing prepared query.
This was pretty subtle and complicated behavior, and won't work for
templates since the service name is computed at execution time. To solve
this, we introduce a new "prepared-query" ACL type, where the prefix
applies to the query name for static prepared query types and to the
prefix for template prepared query types.
With this change, the new behavior is:
1. A management token, or a token with "prepared-query" write access to
the query name or (soon) the given template prefix is required to do
any CRUD operations on a prepared query, or to list prepared queries
(the list is filtered by this ACL).
2. You will no longer need a management token to list prepared queries,
but you will only be able to see prepared queries that you have access
to (you get an empty list instead of permission denied).
3. When listing or getting a query, because it was easy to capture
management tokens given the past behavior, this will always blank out
the "Token" field (replacing the contents as <hidden>) for all tokens
unless a management token is supplied. Going forward, we should
discourage people from binding tokens for execution unless strictly
necessary.
4. No token will be captured by default when a prepared query is created.
If the user wishes to supply an execution token then can pass it in via
the "Token" field in the prepared query definition. Otherwise, this
field will default to empty.
5. At execution time, we will use the captured token if it exists with the
prepared query definition, otherwise we will use the token that's passed
in with the request, just like we do for other RPCs (or you can use the
agent's configured token for DNS).
6. Prepared queries with no name (accessible only by ID) will not require
ACLs to create or modify (execution time will depend on the service ACL
configuration). Our argument here is that these are designed to be
ephemeral and the IDs are as good as an ACL. Management tokens will be
able to list all of these.
These changes enable templates, but also enable delegation of authority to
manage the prepared query namespace.
2016-02-23 08:12:58 +00:00
|
|
|
// apply any sorting options or ACL checks at this level - it should be done up above.
|
2015-11-10 04:37:41 +00:00
|
|
|
func (p *PreparedQuery) execute(query *structs.PreparedQuery,
|
2018-06-05 21:42:01 +00:00
|
|
|
reply *structs.PreparedQueryExecuteResponse,
|
|
|
|
forceConnect bool) error {
|
2015-11-10 04:37:41 +00:00
|
|
|
state := p.srv.fsm.State()
|
2018-06-05 21:17:19 +00:00
|
|
|
|
|
|
|
// If we're requesting Connect-capable services, then switch the
|
|
|
|
// lookup to be the Connect function.
|
|
|
|
f := state.CheckServiceNodes
|
2018-06-05 21:42:01 +00:00
|
|
|
if query.Service.Connect || forceConnect {
|
2018-06-05 21:17:19 +00:00
|
|
|
f = state.CheckConnectServiceNodes
|
|
|
|
}
|
|
|
|
|
2019-12-10 02:26:41 +00:00
|
|
|
_, nodes, err := f(nil, query.Service.Service, &query.Service.EnterpriseMeta)
|
2015-11-07 06:18:11 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Filter out any unhealthy nodes.
|
2018-04-10 12:28:27 +00:00
|
|
|
nodes = nodes.FilterIgnore(query.Service.OnlyPassing,
|
|
|
|
query.Service.IgnoreCheckIDs)
|
2015-11-07 06:18:11 +00:00
|
|
|
|
2017-01-18 21:23:33 +00:00
|
|
|
// Apply the node metadata filters, if any.
|
|
|
|
if len(query.Service.NodeMeta) > 0 {
|
|
|
|
nodes = nodeMetaFilter(query.Service.NodeMeta, nodes)
|
|
|
|
}
|
|
|
|
|
Improve Connect with Prepared Queries (#5291)
Given a query like:
```
{
"Name": "tagged-connect-query",
"Service": {
"Service": "foo",
"Tags": ["tag"],
"Connect": true
}
}
```
And a Consul configuration like:
```
{
"services": [
"name": "foo",
"port": 8080,
"connect": { "sidecar_service": {} },
"tags": ["tag"]
]
}
```
If you executed the query it would always turn up with 0 results. This was because the sidecar service was being created without any tags. You could instead make your config look like:
```
{
"services": [
"name": "foo",
"port": 8080,
"connect": { "sidecar_service": {
"tags": ["tag"]
} },
"tags": ["tag"]
]
}
```
However that is a bit redundant for most cases. This PR ensures that the tags and service meta of the parent service get copied to the sidecar service. If there are any tags or service meta set in the sidecar service definition then this copying does not take place. After the changes, the query will now return the expected results.
A second change was made to prepared queries in this PR which is to allow filtering on ServiceMeta just like we allow for filtering on NodeMeta.
2019-02-04 14:36:51 +00:00
|
|
|
// Apply the service metadata filters, if any.
|
|
|
|
if len(query.Service.ServiceMeta) > 0 {
|
|
|
|
nodes = serviceMetaFilter(query.Service.ServiceMeta, nodes)
|
|
|
|
}
|
|
|
|
|
2015-11-07 06:18:11 +00:00
|
|
|
// Apply the tag filters, if any.
|
|
|
|
if len(query.Service.Tags) > 0 {
|
|
|
|
nodes = tagFilter(query.Service.Tags, nodes)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Capture the nodes and pass the DNS information through to the reply.
|
2015-11-13 18:38:44 +00:00
|
|
|
reply.Service = query.Service.Service
|
2020-02-10 15:40:44 +00:00
|
|
|
reply.EnterpriseMeta = query.Service.EnterpriseMeta
|
2015-11-07 06:18:11 +00:00
|
|
|
reply.Nodes = nodes
|
|
|
|
reply.DNS = query.DNS
|
|
|
|
|
2015-11-11 01:42:41 +00:00
|
|
|
// Stamp the result for this datacenter.
|
|
|
|
reply.Datacenter = p.srv.config.Datacenter
|
|
|
|
|
2015-11-07 06:18:11 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// tagFilter returns a list of nodes who satisfy the given tags. Nodes must have
|
2015-11-11 02:23:37 +00:00
|
|
|
// ALL the given tags, and NONE of the forbidden tags (prefixed with !). Note
|
|
|
|
// for performance this modifies the original slice.
|
2015-11-07 06:18:11 +00:00
|
|
|
func tagFilter(tags []string, nodes structs.CheckServiceNodes) structs.CheckServiceNodes {
|
|
|
|
// Build up lists of required and disallowed tags.
|
|
|
|
must, not := make([]string, 0), make([]string, 0)
|
|
|
|
for _, tag := range tags {
|
|
|
|
tag = strings.ToLower(tag)
|
2015-11-10 04:39:15 +00:00
|
|
|
if strings.HasPrefix(tag, "!") {
|
2015-11-07 06:18:11 +00:00
|
|
|
tag = tag[1:]
|
|
|
|
not = append(not, tag)
|
|
|
|
} else {
|
|
|
|
must = append(must, tag)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
n := len(nodes)
|
|
|
|
for i := 0; i < n; i++ {
|
|
|
|
node := nodes[i]
|
|
|
|
|
|
|
|
// Index the tags so lookups this way are cheaper.
|
|
|
|
index := make(map[string]struct{})
|
2015-11-11 02:23:37 +00:00
|
|
|
if node.Service != nil {
|
|
|
|
for _, tag := range node.Service.Tags {
|
|
|
|
tag = strings.ToLower(tag)
|
|
|
|
index[tag] = struct{}{}
|
|
|
|
}
|
2015-11-07 06:18:11 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Bail if any of the required tags are missing.
|
|
|
|
for _, tag := range must {
|
|
|
|
if _, ok := index[tag]; !ok {
|
|
|
|
goto DELETE
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Bail if any of the disallowed tags are present.
|
|
|
|
for _, tag := range not {
|
|
|
|
if _, ok := index[tag]; ok {
|
|
|
|
goto DELETE
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// At this point, the service is ok to leave in the list.
|
|
|
|
continue
|
|
|
|
|
|
|
|
DELETE:
|
|
|
|
nodes[i], nodes[n-1] = nodes[n-1], structs.CheckServiceNode{}
|
|
|
|
n--
|
|
|
|
i--
|
|
|
|
}
|
|
|
|
return nodes[:n]
|
|
|
|
}
|
|
|
|
|
2017-01-18 21:23:33 +00:00
|
|
|
// nodeMetaFilter returns a list of the nodes who satisfy the given metadata filters. Nodes
|
|
|
|
// must have ALL the given tags.
|
|
|
|
func nodeMetaFilter(filters map[string]string, nodes structs.CheckServiceNodes) structs.CheckServiceNodes {
|
|
|
|
var filtered structs.CheckServiceNodes
|
|
|
|
for _, node := range nodes {
|
|
|
|
if structs.SatisfiesMetaFilters(node.Node.Meta, filters) {
|
|
|
|
filtered = append(filtered, node)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return filtered
|
|
|
|
}
|
|
|
|
|
Improve Connect with Prepared Queries (#5291)
Given a query like:
```
{
"Name": "tagged-connect-query",
"Service": {
"Service": "foo",
"Tags": ["tag"],
"Connect": true
}
}
```
And a Consul configuration like:
```
{
"services": [
"name": "foo",
"port": 8080,
"connect": { "sidecar_service": {} },
"tags": ["tag"]
]
}
```
If you executed the query it would always turn up with 0 results. This was because the sidecar service was being created without any tags. You could instead make your config look like:
```
{
"services": [
"name": "foo",
"port": 8080,
"connect": { "sidecar_service": {
"tags": ["tag"]
} },
"tags": ["tag"]
]
}
```
However that is a bit redundant for most cases. This PR ensures that the tags and service meta of the parent service get copied to the sidecar service. If there are any tags or service meta set in the sidecar service definition then this copying does not take place. After the changes, the query will now return the expected results.
A second change was made to prepared queries in this PR which is to allow filtering on ServiceMeta just like we allow for filtering on NodeMeta.
2019-02-04 14:36:51 +00:00
|
|
|
func serviceMetaFilter(filters map[string]string, nodes structs.CheckServiceNodes) structs.CheckServiceNodes {
|
|
|
|
var filtered structs.CheckServiceNodes
|
|
|
|
for _, node := range nodes {
|
|
|
|
if structs.SatisfiesMetaFilters(node.Service.Meta, filters) {
|
|
|
|
filtered = append(filtered, node)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return filtered
|
|
|
|
}
|
|
|
|
|
2015-11-07 06:18:11 +00:00
|
|
|
// queryServer is a wrapper that makes it easier to test the failover logic.
|
|
|
|
type queryServer interface {
|
2020-01-28 23:50:41 +00:00
|
|
|
GetLogger() hclog.Logger
|
2015-11-07 06:18:11 +00:00
|
|
|
GetOtherDatacentersByDistance() ([]string, error)
|
|
|
|
ForwardDC(method, dc string, args interface{}, reply interface{}) error
|
|
|
|
}
|
|
|
|
|
|
|
|
// queryServerWrapper applies the queryServer interface to a Server.
|
|
|
|
type queryServerWrapper struct {
|
|
|
|
srv *Server
|
|
|
|
}
|
|
|
|
|
2015-11-10 05:13:53 +00:00
|
|
|
// GetLogger returns the server's logger.
|
2020-01-28 23:50:41 +00:00
|
|
|
func (q *queryServerWrapper) GetLogger() hclog.Logger {
|
|
|
|
return q.srv.loggers.Named(logging.PreparedQuery)
|
2015-11-07 06:18:11 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// GetOtherDatacentersByDistance calls into the server's fn and filters out the
|
|
|
|
// server's own DC.
|
|
|
|
func (q *queryServerWrapper) GetOtherDatacentersByDistance() ([]string, error) {
|
2017-03-14 05:56:24 +00:00
|
|
|
// TODO (slackpad) - We should cache this result since it's expensive to
|
|
|
|
// compute.
|
|
|
|
dcs, err := q.srv.router.GetDatacentersByDistance()
|
2015-11-07 06:18:11 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
var result []string
|
|
|
|
for _, dc := range dcs {
|
|
|
|
if dc != q.srv.config.Datacenter {
|
|
|
|
result = append(result, dc)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return result, nil
|
|
|
|
}
|
|
|
|
|
2015-11-10 05:13:53 +00:00
|
|
|
// ForwardDC calls into the server's RPC forwarder.
|
|
|
|
func (q *queryServerWrapper) ForwardDC(method, dc string, args interface{}, reply interface{}) error {
|
|
|
|
return q.srv.forwardDC(method, dc, args, reply)
|
|
|
|
}
|
|
|
|
|
2015-11-07 06:18:11 +00:00
|
|
|
// queryFailover runs an algorithm to determine which DCs to try and then calls
|
|
|
|
// them to try to locate alternative services.
|
|
|
|
func queryFailover(q queryServer, query *structs.PreparedQuery,
|
2018-06-05 21:42:01 +00:00
|
|
|
args *structs.PreparedQueryExecuteRequest,
|
2015-11-10 04:37:41 +00:00
|
|
|
reply *structs.PreparedQueryExecuteResponse) error {
|
2015-11-07 06:18:11 +00:00
|
|
|
|
2015-11-10 05:13:53 +00:00
|
|
|
// Pull the list of other DCs. This is sorted by RTT in case the user
|
|
|
|
// has selected that.
|
|
|
|
nearest, err := q.GetOtherDatacentersByDistance()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// This will help us filter unknown DCs supplied by the user.
|
|
|
|
known := make(map[string]struct{})
|
|
|
|
for _, dc := range nearest {
|
|
|
|
known[dc] = struct{}{}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Build a candidate list of DCs to try, starting with the nearest N
|
|
|
|
// from RTTs.
|
2015-11-07 06:18:11 +00:00
|
|
|
var dcs []string
|
|
|
|
index := make(map[string]struct{})
|
|
|
|
if query.Service.Failover.NearestN > 0 {
|
|
|
|
for i, dc := range nearest {
|
|
|
|
if !(i < query.Service.Failover.NearestN) {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
|
|
|
|
dcs = append(dcs, dc)
|
|
|
|
index[dc] = struct{}{}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Then add any DCs explicitly listed that weren't selected above.
|
|
|
|
for _, dc := range query.Service.Failover.Datacenters {
|
2015-11-10 05:13:53 +00:00
|
|
|
// This will prevent a log of other log spammage if we do not
|
|
|
|
// attempt to talk to datacenters we don't know about.
|
|
|
|
if _, ok := known[dc]; !ok {
|
2020-01-28 23:50:41 +00:00
|
|
|
q.GetLogger().Debug("Skipping unknown datacenter in prepared query", "datacenter", dc)
|
2015-11-10 05:13:53 +00:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
// This will make sure we don't re-try something that fails
|
|
|
|
// from the NearestN list.
|
|
|
|
if _, ok := index[dc]; !ok {
|
2015-11-07 06:18:11 +00:00
|
|
|
dcs = append(dcs, dc)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-11-11 16:48:03 +00:00
|
|
|
// Now try the selected DCs in priority order.
|
|
|
|
failovers := 0
|
|
|
|
for _, dc := range dcs {
|
|
|
|
// This keeps track of how many iterations we actually run.
|
|
|
|
failovers++
|
|
|
|
|
2015-11-15 05:59:08 +00:00
|
|
|
// Be super paranoid and set the nodes slice to nil since it's
|
|
|
|
// the same slice we used before. We know there's nothing in
|
|
|
|
// there, but the underlying msgpack library has a policy of
|
|
|
|
// updating the slice when it's non-nil, and that feels dirty.
|
|
|
|
// Let's just set it to nil so there's no way to communicate
|
|
|
|
// through this slice across successive RPC calls.
|
|
|
|
reply.Nodes = nil
|
|
|
|
|
2015-11-11 16:48:03 +00:00
|
|
|
// Note that we pass along the limit since it can be applied
|
|
|
|
// remotely to save bandwidth. We also pass along the consistency
|
Creates new "prepared-query" ACL type and new token capture behavior.
Prior to this change, prepared queries had the following behavior for
ACLs, which will need to change to support templates:
1. A management token, or a token with read access to the service being
queried needed to be provided in order to create a prepared query.
2. The token used to create the prepared query was stored with the query
in the state store and used to execute the query.
3. A management token, or the token used to create the query needed to be
supplied to perform and CRUD operations on an existing prepared query.
This was pretty subtle and complicated behavior, and won't work for
templates since the service name is computed at execution time. To solve
this, we introduce a new "prepared-query" ACL type, where the prefix
applies to the query name for static prepared query types and to the
prefix for template prepared query types.
With this change, the new behavior is:
1. A management token, or a token with "prepared-query" write access to
the query name or (soon) the given template prefix is required to do
any CRUD operations on a prepared query, or to list prepared queries
(the list is filtered by this ACL).
2. You will no longer need a management token to list prepared queries,
but you will only be able to see prepared queries that you have access
to (you get an empty list instead of permission denied).
3. When listing or getting a query, because it was easy to capture
management tokens given the past behavior, this will always blank out
the "Token" field (replacing the contents as <hidden>) for all tokens
unless a management token is supplied. Going forward, we should
discourage people from binding tokens for execution unless strictly
necessary.
4. No token will be captured by default when a prepared query is created.
If the user wishes to supply an execution token then can pass it in via
the "Token" field in the prepared query definition. Otherwise, this
field will default to empty.
5. At execution time, we will use the captured token if it exists with the
prepared query definition, otherwise we will use the token that's passed
in with the request, just like we do for other RPCs (or you can use the
agent's configured token for DNS).
6. Prepared queries with no name (accessible only by ID) will not require
ACLs to create or modify (execution time will depend on the service ACL
configuration). Our argument here is that these are designed to be
ephemeral and the IDs are as good as an ACL. Management tokens will be
able to list all of these.
These changes enable templates, but also enable delegation of authority to
manage the prepared query namespace.
2016-02-23 08:12:58 +00:00
|
|
|
// mode information and token we were given, so that applies to
|
|
|
|
// the remote query as well.
|
2015-11-10 04:37:41 +00:00
|
|
|
remote := &structs.PreparedQueryExecuteRemoteRequest{
|
2015-11-07 06:18:11 +00:00
|
|
|
Datacenter: dc,
|
|
|
|
Query: *query,
|
2018-06-05 21:42:01 +00:00
|
|
|
Limit: args.Limit,
|
|
|
|
QueryOptions: args.QueryOptions,
|
|
|
|
Connect: args.Connect,
|
2015-11-07 06:18:11 +00:00
|
|
|
}
|
2015-11-11 05:16:04 +00:00
|
|
|
if err := q.ForwardDC("PreparedQuery.ExecuteRemote", dc, remote, reply); err != nil {
|
2020-01-28 23:50:41 +00:00
|
|
|
q.GetLogger().Warn("Failed querying for service in datacenter",
|
|
|
|
"service", query.Service.Service,
|
|
|
|
"datacenter", dc,
|
|
|
|
"error", err,
|
|
|
|
)
|
2015-11-10 05:13:53 +00:00
|
|
|
continue
|
2015-11-07 06:18:11 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// We can stop if we found some nodes.
|
|
|
|
if len(reply.Nodes) > 0 {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-11-11 16:48:03 +00:00
|
|
|
// Set this at the end because the response from the remote doesn't have
|
|
|
|
// this information.
|
|
|
|
reply.Failovers = failovers
|
|
|
|
|
2015-11-07 06:18:11 +00:00
|
|
|
return nil
|
|
|
|
}
|