Merge branch 'main' into fix-broken-dockerfile

pull/12097/head
Michele Degges 3 years ago committed by GitHub
commit d032fb52a2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -0,0 +1,3 @@
```release-note:improvement
ci: Enable security scanning for CRT
```

@ -0,0 +1,3 @@
```release-note:deprecation
acl: The `consul.acl.ResolveTokenToIdentity` metric is no longer reported. The values that were previous reported as part of this metric will now be part of the `consul.acl.ResolveToken` metric.
```

@ -0,0 +1,3 @@
```release-note:enhancement
ui: Use @hashicorp/flight icons for all our icons.
```

@ -0,0 +1,3 @@
```release-note:bug
ca: adjust validation of PrivateKeyType/Bits with the Vault provider, to remove the error when the cert is created manually in Vault.
```

@ -359,7 +359,7 @@ jobs:
path: /tmp/jsonfile
- run: *notify-slack-failure
# build all distros
# build is a templated job for build-x
build-distros: &build-distros
docker:
- image: *GOLANG_IMAGE
@ -367,7 +367,13 @@ jobs:
<<: *ENVIRONMENT
steps:
- checkout
- run: ./build-support/scripts/build-local.sh
- run:
name: Build
command: |
for os in $XC_OS; do
target="./pkg/bin/${GOOS}_${GOARCH}/"
GOOS="$os" CGO_ENABLED=0 go build -o "$target" -ldflags "$(GOLDFLAGS)" -tags "$(GOTAGS)"
done
# save dev build to CircleCI
- store_artifacts:
@ -380,7 +386,7 @@ jobs:
environment:
<<: *build-env
XC_OS: "freebsd linux windows"
XC_ARCH: "386"
GOARCH: "386"
# build all amd64 architecture supported OS binaries
build-amd64:
@ -388,7 +394,7 @@ jobs:
environment:
<<: *build-env
XC_OS: "darwin freebsd linux solaris windows"
XC_ARCH: "amd64"
GOARCH: "amd64"
# build all arm/arm64 architecture supported OS binaries
build-arm:
@ -433,7 +439,11 @@ jobs:
- attach_workspace: # this normally runs as the first job and has nothing to attach; only used in main branch after rebuilding UI
at: .
- run:
command: make dev
name: Build
command: |
make dev
mkdir -p /home/circleci/go/bin
cp ./bin/consul /home/circleci/go/bin/consul
# save dev build to pass to downstream jobs
- persist_to_workspace:

@ -1,4 +1,5 @@
# Contributing to Consul
>**Note:** We take Consul's security and our users' trust very seriously.
>If you believe you have found a security issue in Consul, please responsibly
>disclose by contacting us at security@hashicorp.com.
@ -14,7 +15,9 @@ talk to us! A great way to do this is in issues themselves. When you want to
work on an issue, comment on it first and tell us the approach you want to take.
## Getting Started
### Some Ways to Contribute
* Report potential bugs.
* Suggest product enhancements.
* Increase our test coverage.
@ -24,7 +27,8 @@ work on an issue, comment on it first and tell us the approach you want to take.
are deployed from this repo.
* Respond to questions about usage on the issue tracker or the Consul section of the [HashiCorp forum]: (https://discuss.hashicorp.com/c/consul)
### Reporting an Issue:
### Reporting an Issue
>Note: Issues on GitHub for Consul are intended to be related to bugs or feature requests.
>Questions should be directed to other community resources such as the: [Discuss Forum](https://discuss.hashicorp.com/c/consul/29), [FAQ](https://www.consul.io/docs/faq.html), or [Guides](https://www.consul.io/docs/guides/index.html).
@ -53,68 +57,95 @@ issue. Stale issues will be closed.
4. The issue is addressed in a pull request or commit. The issue will be
referenced in the commit message so that the code that fixes it is clearly
linked.
linked. Any change a Consul user might need to know about will include a
changelog entry in the PR.
5. The issue is closed.
## Building Consul
If you wish to work on Consul itself, you'll first need [Go](https://golang.org)
installed (The version of Go should match the one of our [CI config's](https://github.com/hashicorp/consul/blob/main/.circleci/config.yml) Go image).
Next, clone this repository and then run `make dev`. In a few moments, you'll have a working
`consul` executable in `consul/bin` and `$GOPATH/bin`:
>Note: `make dev` will build for your local machine's os/architecture. If you wish to build for all os/architecture combinations use `make`.
## Making Changes to Consul
The first step to making changes is to fork Consul. Afterwards, the easiest way
to work on the fork is to set it as a remote of the Consul project:
### Prerequisites
1. Navigate to `$GOPATH/src/github.com/hashicorp/consul`
2. Rename the existing remote's name: `git remote rename origin upstream`.
3. Add your fork as a remote by running
`git remote add origin <github url of fork>`. For example:
`git remote add origin https://github.com/myusername/consul`.
4. Checkout a feature branch: `git checkout -t -b new-feature`
5. Make changes
6. Push changes to the fork when ready to submit PR:
`git push -u origin new-feature`
If you wish to work on Consul itself, you'll first need to:
- install [Go](https://golang.org) (the version should match that of our
[CI config's](https://github.com/hashicorp/consul/blob/main/.circleci/config.yml) Go image).
- [fork the Consul repo](../docs/contributing/fork-the-project.md)
By following these steps you can push to your fork to create a PR, but the code on disk still
lives in the spot where the go cli tools are expecting to find it.
### Building Consul
>Note: If you make any changes to the code, run `gofmt -s -w` to automatically format the code according to Go standards.
To build Consul, run `make dev`. In a few moments, you'll have a working
`consul` executable in `consul/bin` and `$GOPATH/bin`:
## Testing
>Note: `make dev` will build for your local machine's os/architecture. If you wish to build for all os/architecture combinations, use `make`.
During development, it may be more convenient to check your work-in-progress by running only the tests which you expect to be affected by your changes, as the full test suite can take several minutes to execute. [Go's built-in test tool](https://golang.org/pkg/cmd/go/internal/test/) allows specifying a list of packages to test and the `-run` option to only include test names matching a regular expression.
The `go test -short` flag can also be used to skip slower tests.
### Modifying the Code
Examples (run from the repository root):
- `go test -v ./connect` will run all tests in the connect package (see `./connect` folder)
- `go test -v -run TestRetryJoin ./command/agent` will run all tests in the agent package (see `./command/agent` folder) with name substring `TestRetryJoin`
#### Code Formatting
When a pull request is opened CI will run all tests and lint to verify the change.
Go provides [tooling to apply consistent code formatting](https://golang.org/doc/effective_go#formatting).
If you make any changes to the code, run `gofmt -s -w` to automatically format the code according to Go standards.
## Go Module Dependencies
#### Updating Go Module Dependencies
If a dependency is added or change, run `go mod tidy` to update `go.mod` and `go.sum`.
## Developer Documentation
#### Developer Documentation
Documentation about the Consul code base is under [./docs],
Developer-focused documentation about the Consul code base is under [./docs],
and godoc package document can be read at [pkg.go.dev/github.com/hashicorp/consul].
[./docs]: ../docs/README.md
[pkg.go.dev/github.com/hashicorp/consul]: https://pkg.go.dev/github.com/hashicorp/consul
### Checklists
### Testing
During development, it may be more convenient to check your work-in-progress by running only the tests which you expect to be affected by your changes, as the full test suite can take several minutes to execute. [Go's built-in test tool](https://golang.org/pkg/cmd/go/internal/test/) allows specifying a list of packages to test and the `-run` option to only include test names matching a regular expression.
The `go test -short` flag can also be used to skip slower tests.
Examples (run from the repository root):
- `go test -v ./connect` will run all tests in the connect package (see `./connect` folder)
- `go test -v -run TestRetryJoin ./command/agent` will run all tests in the agent package (see `./command/agent` folder) with name substring `TestRetryJoin`
Some common changes that many PRs require such as adding config fields, are
documented through checklists.
When a pull request is opened CI will run all tests and lint to verify the change.
Please check in [docs/](../docs/) for any `checklist-*.md` files that might help
with your change.
### Submitting a Pull Request
Before writing any code, we recommend:
- Create a Github issue if none already exists for the code change you'd like to make.
- Write a comment on the Github issue indicating you're interested in contributing so
maintainers can provide their perspective if needed.
Keep your pull requests (PRs) small and open them early so you can get feedback on
approach from maintainers before investing your time in larger changes. For example,
see how [applying URL-decoding of resource names across the whole HTTP API](https://github.com/hashicorp/consul/issues/11258)
started with [iterating on the right approach for a few endpoints](https://github.com/hashicorp/consul/pull/11335)
before applying more broadly.
When you're ready to submit a pull request:
1. Review the [list of checklists](#checklists) for common changes and follow any
that apply to your work.
2. Include evidence that your changes work as intended (e.g., add/modify unit tests;
describe manual tests you ran, in what environment,
and the results including screenshots or terminal output).
3. Open the PR from your fork against base repository `hashicorp/consul` and branch `main`.
- [Link the PR to its associated issue](https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue).
4. Include any specific questions that you have for the reviewer in the PR description
or as a PR comment in Github.
- If there's anything you find the need to explain or clarify in the PR, consider
whether that explanation should be added in the source code as comments.
- You can submit a [draft PR](https://github.blog/2019-02-14-introducing-draft-pull-requests/)
if your changes aren't finalized but would benefit from in-process feedback.
5. If there's any reason Consul users might need to know about this change,
[add a changelog entry](../docs/contributing/add-a-changelog-entry.md).
6. After you submit, the Consul maintainers team needs time to carefully review your
contribution and ensure it is production-ready, considering factors such as: security,
backwards-compatibility, potential regressions, etc.
7. After you address Consul maintainer feedback and the PR is approved, a Consul maintainer
will merge it. Your contribution will be available from the next major release (e.g., 1.x)
unless explicitly backported to an existing or previous major release by the maintainer.
#### Checklists
Some common changes that many PRs require are documented through checklists as
`checklist-*.md` files in [docs/](../docs/), including:
- [Adding config fields](../docs/config/checklist-adding-config-fields.md)

@ -3,9 +3,9 @@ name: build
on:
push:
# Sequence of patterns matched against refs/heads
branches: [
"main"
]
branches:
# Push events on the main branch
- main
env:
PKG_NAME: consul
@ -145,6 +145,7 @@ jobs:
config_dir: ".release/linux/package"
preinstall: ".release/linux/preinstall"
postinstall: ".release/linux/postinstall"
preremove: ".release/linux/preremove"
postremove: ".release/linux/postremove"
- name: Set Package Names

@ -42,8 +42,36 @@ event "upload-dev" {
}
}
event "notarize-darwin-amd64" {
event "security-scan-binaries" {
depends = ["upload-dev"]
action "security-scan-binaries" {
organization = "hashicorp"
repository = "crt-workflows-common"
workflow = "security-scan-binaries"
config = "security-scan.hcl"
}
notification {
on = "fail"
}
}
event "security-scan-containers" {
depends = ["security-scan-binaries"]
action "security-scan-containers" {
organization = "hashicorp"
repository = "crt-workflows-common"
workflow = "security-scan-containers"
config = "security-scan.hcl"
}
notification {
on = "fail"
}
}
event "notarize-darwin-amd64" {
depends = ["security-scan-containers"]
action "notarize-darwin-amd64" {
organization = "hashicorp"
repository = "crt-workflows-common"

@ -1,14 +1,19 @@
#!/bin/bash
if [ "$1" = "purge" ]
then
userdel consul
if [ -d "/run/systemd/system" ]; then
systemctl --system daemon-reload >/dev/null || :
fi
if [ "$1" == "upgrade" ] && [ -d /run/systemd/system ]; then
systemctl --system daemon-reload >/dev/null || true
systemctl restart consul >/dev/null || true
fi
case "$1" in
purge | 0)
userdel consul
;;
exit 0
upgrade | [1-9]*)
if [ -d "/run/systemd/system" ]; then
systemctl try-restart consul.service >/dev/null || :
fi
;;
esac
exit 0

@ -0,0 +1,11 @@
#!/bin/bash
case "$1" in
remove | 0)
if [ -d "/run/systemd/system" ]; then
systemctl --no-reload disable consul.service > /dev/null || :
systemctl stop consul.service > /dev/null || :
fi
;;
esac
exit 0

@ -0,0 +1,13 @@
container {
dependencies = true
alpine_secdb = true
secrets = true
}
binary {
secrets = true
go_modules = false
osv = true
oss_index = true
nvd = true
}

@ -1,6 +1,6 @@
# This Dockerfile contains multiple targets.
# Use 'docker build --target=<name> .' to build one.
# e.g. `docker build --target=dev .`
# e.g. `docker build --target=official .`
#
# All non-dev targets have a VERSION argument that must be provided
# via --build-arg=VERSION=<version> when building.
@ -191,4 +191,4 @@ ENTRYPOINT ["docker-entrypoint.sh"]
# By default you'll get an insecure single-node development server that stores
# everything in RAM, exposes a web UI and HTTP endpoints, and bootstraps itself.
# Don't use this configuration for production.
CMD ["agent", "-dev", "-client", "0.0.0.0"]
CMD ["agent", "-dev", "-client", "0.0.0.0"]

@ -1,3 +1,6 @@
# For documentation on building consul from source, refer to:
# https://www.consul.io/docs/install#compiling-from-source
SHELL = bash
GOGOVERSION?=$(shell grep github.com/gogo/protobuf go.mod | awk '{print $$2}')
GOTOOLS = \
@ -12,8 +15,6 @@ GOTOOLS = \
github.com/hashicorp/lint-consul-retry@master
GOTAGS ?=
GOOS?=$(shell go env GOOS)
GOARCH?=$(shell go env GOARCH)
GOPATH=$(shell go env GOPATH)
MAIN_GOPATH=$(shell go env GOPATH | cut -d: -f1)
@ -134,20 +135,17 @@ ifdef SKIP_DOCKER_BUILD
ENVOY_INTEG_DEPS=noop
endif
# all builds binaries for all targets
all: bin
all: dev-build
# used to make integration dependencies conditional
noop: ;
bin: tools
@$(SHELL) $(CURDIR)/build-support/scripts/build-local.sh
# dev creates binaries for testing locally - these are put into ./bin and $GOPATH
# dev creates binaries for testing locally - these are put into ./bin
dev: dev-build
dev-build:
@$(SHELL) $(CURDIR)/build-support/scripts/build-local.sh -o $(GOOS) -a $(GOARCH)
mkdir -p bin
CGO_ENABLED=0 go build -o ./bin -ldflags "$(GOLDFLAGS)" -tags "$(GOTAGS)"
dev-docker: linux
@echo "Pulling consul container image - $(CONSUL_IMAGE_VERSION)"
@ -175,9 +173,10 @@ ifeq ($(CIRCLE_BRANCH), main)
@docker push $(CI_DEV_DOCKER_NAMESPACE)/$(CI_DEV_DOCKER_IMAGE_NAME):latest
endif
# linux builds a linux package independent of the source platform
# linux builds a linux binary independent of the source platform
linux:
@$(SHELL) $(CURDIR)/build-support/scripts/build-local.sh -o linux -a amd64
mkdir -p bin
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o ./bin -ldflags "$(GOLDFLAGS)" -tags "$(GOTAGS)"
# dist builds binaries for all platforms and packages them for distribution
dist:

@ -1,11 +1,20 @@
# Consul [![CircleCI](https://circleci.com/gh/hashicorp/consul/tree/main.svg?style=svg)](https://circleci.com/gh/hashicorp/consul/tree/main) [![Discuss](https://img.shields.io/badge/discuss-consul-ca2171.svg?style=flat)](https://discuss.hashicorp.com/c/consul)
# Consul
<p>
<a href="https://consul.io" title="Consul website">
<img src="./website/public/img/logo-hashicorp.svg" alt="HashiCorp Consul logo" width="200px">
</a>
</p>
[![Docker Pulls](https://img.shields.io/docker/pulls/_/consul.svg)](https://hub.docker.com/_/consul)
[![Go Report Card](https://goreportcard.com/badge/github.com/hashicorp/consul)](https://goreportcard.com/report/github.com/hashicorp/consul)
Consul is a distributed, highly available, and data center aware solution to connect and configure applications across dynamic, distributed infrastructure.
* Website: https://www.consul.io
* Tutorials: [HashiCorp Learn](https://learn.hashicorp.com/consul)
* Forum: [Discuss](https://discuss.hashicorp.com/c/consul)
Consul is a distributed, highly available, and data center aware solution to connect and configure applications across dynamic, distributed infrastructure.
Consul provides several key features:
* **Multi-Datacenter** - Consul is built to be datacenter aware, and can

@ -15,7 +15,7 @@ import (
// critical purposes, such as logging. Therefore we interpret all errors as empty-string
// so we can safely log it without handling non-critical errors at the usage site.
func (a *Agent) aclAccessorID(secretID string) string {
ident, err := a.delegate.ResolveTokenToIdentity(secretID)
ident, err := a.delegate.ResolveTokenAndDefaultMeta(secretID, nil, nil)
if acl.IsErrNotFound(err) {
return ""
}
@ -23,10 +23,7 @@ func (a *Agent) aclAccessorID(secretID string) string {
a.logger.Debug("non-critical error resolving acl token accessor for logging", "error", err)
return ""
}
if ident == nil {
return ""
}
return ident.ID()
return ident.AccessorID()
}
// vetServiceRegister makes sure the service registration action is allowed by
@ -174,7 +171,7 @@ func (a *Agent) filterMembers(token string, members *[]serf.Member) error {
if authz.NodeRead(node, &authzContext) == acl.Allow {
continue
}
accessorID := a.aclAccessorID(token)
accessorID := authz.AccessorID()
a.logger.Debug("dropping node from result due to ACLs", "node", node, "accessorID", accessorID)
m = append(m[:i], m[i+1:]...)
i--

@ -39,6 +39,12 @@ type TestACLAgent struct {
func NewTestACLAgent(t *testing.T, name string, hcl string, resolveAuthz authzResolver, resolveIdent identResolver) *TestACLAgent {
t.Helper()
if resolveIdent == nil {
resolveIdent = func(s string) (structs.ACLIdentity, error) {
return nil, nil
}
}
a := &TestACLAgent{resolveAuthzFn: resolveAuthz, resolveIdentFn: resolveIdent}
dataDir := testutil.TempDir(t, "acl-agent")
@ -86,26 +92,15 @@ func (a *TestACLAgent) ResolveToken(secretID string) (acl.Authorizer, error) {
return authz, err
}
func (a *TestACLAgent) ResolveTokenToIdentityAndAuthorizer(secretID string) (structs.ACLIdentity, acl.Authorizer, error) {
if a.resolveAuthzFn == nil {
return nil, nil, fmt.Errorf("ResolveTokenToIdentityAndAuthorizer call is unexpected - no authz resolver callback set")
}
return a.resolveAuthzFn(secretID)
}
func (a *TestACLAgent) ResolveTokenToIdentity(secretID string) (structs.ACLIdentity, error) {
if a.resolveIdentFn == nil {
return nil, fmt.Errorf("ResolveTokenToIdentity call is unexpected - no ident resolver callback set")
func (a *TestACLAgent) ResolveTokenAndDefaultMeta(secretID string, entMeta *structs.EnterpriseMeta, authzContext *acl.AuthorizerContext) (consul.ACLResolveResult, error) {
authz, err := a.ResolveToken(secretID)
if err != nil {
return consul.ACLResolveResult{}, err
}
return a.resolveIdentFn(secretID)
}
func (a *TestACLAgent) ResolveTokenAndDefaultMeta(secretID string, entMeta *structs.EnterpriseMeta, authzContext *acl.AuthorizerContext) (acl.Authorizer, error) {
identity, authz, err := a.ResolveTokenToIdentityAndAuthorizer(secretID)
identity, err := a.resolveIdentFn(secretID)
if err != nil {
return nil, err
return consul.ACLResolveResult{}, err
}
// Default the EnterpriseMeta based on the Tokens meta or actual defaults
@ -119,7 +114,7 @@ func (a *TestACLAgent) ResolveTokenAndDefaultMeta(secretID string, entMeta *stru
// Use the meta to fill in the ACL authorization context
entMeta.FillAuthzContext(authzContext)
return authz, err
return consul.ACLResolveResult{Authorizer: authz, ACLIdentity: identity}, err
}
// All of these are stubs to satisfy the interface
@ -523,22 +518,3 @@ func TestACL_filterChecksWithAuthorizer(t *testing.T) {
_, ok = checks["my-other"]
require.False(t, ok)
}
// TODO: remove?
func TestACL_ResolveIdentity(t *testing.T) {
t.Parallel()
a := NewTestACLAgent(t, t.Name(), TestACLConfig(), nil, catalogIdent)
// this test is meant to ensure we are calling the correct function
// which is ResolveTokenToIdentity on the Agent delegate. Our
// nil authz resolver will cause it to emit an error if used
ident, err := a.delegate.ResolveTokenToIdentity(nodeROSecret)
require.NoError(t, err)
require.NotNil(t, ident)
// just double checkingto ensure if we had used the wrong function
// that an error would be produced
_, err = a.delegate.ResolveTokenAndDefaultMeta(nodeROSecret, nil, nil)
require.Error(t, err)
}

@ -167,14 +167,11 @@ type delegate interface {
// RemoveFailedNode is used to remove a failed node from the cluster.
RemoveFailedNode(node string, prune bool, entMeta *structs.EnterpriseMeta) error
// TODO: replace this method with consul.ACLResolver
ResolveTokenToIdentity(token string) (structs.ACLIdentity, error)
// ResolveTokenAndDefaultMeta returns an acl.Authorizer which authorizes
// actions based on the permissions granted to the token.
// If either entMeta or authzContext are non-nil they will be populated with the
// default partition and namespace from the token.
ResolveTokenAndDefaultMeta(token string, entMeta *structs.EnterpriseMeta, authzContext *acl.AuthorizerContext) (acl.Authorizer, error)
ResolveTokenAndDefaultMeta(token string, entMeta *structs.EnterpriseMeta, authzContext *acl.AuthorizerContext) (consul.ACLResolveResult, error)
RPC(method string, args interface{}, reply interface{}) error
SnapshotRPC(args *structs.SnapshotRequest, in io.Reader, out io.Writer, replyFn structs.SnapshotReplyFn) error

@ -1640,8 +1640,8 @@ type fakeResolveTokenDelegate struct {
authorizer acl.Authorizer
}
func (f fakeResolveTokenDelegate) ResolveTokenAndDefaultMeta(_ string, _ *structs.EnterpriseMeta, _ *acl.AuthorizerContext) (acl.Authorizer, error) {
return f.authorizer, nil
func (f fakeResolveTokenDelegate) ResolveTokenAndDefaultMeta(_ string, _ *structs.EnterpriseMeta, _ *acl.AuthorizerContext) (consul.ACLResolveResult, error) {
return consul.ACLResolveResult{Authorizer: f.authorizer}, nil
}
func TestAgent_Reload(t *testing.T) {

@ -136,9 +136,7 @@ func (s *HTTPHandlers) CatalogRegister(resp http.ResponseWriter, req *http.Reque
}
if err := s.rewordUnknownEnterpriseFieldError(decodeBody(req.Body, &args)); err != nil {
resp.WriteHeader(http.StatusBadRequest)
fmt.Fprintf(resp, "Request decode failed: %v", err)
return nil, nil
return nil, BadRequestError{Reason: fmt.Sprintf("Request decode failed: %v", err)}
}
// Setup the default DC if not provided
@ -168,9 +166,7 @@ func (s *HTTPHandlers) CatalogDeregister(resp http.ResponseWriter, req *http.Req
return nil, err
}
if err := s.rewordUnknownEnterpriseFieldError(decodeBody(req.Body, &args)); err != nil {
resp.WriteHeader(http.StatusBadRequest)
fmt.Fprintf(resp, "Request decode failed: %v", err)
return nil, nil
return nil, BadRequestError{Reason: fmt.Sprintf("Request decode failed: %v", err)}
}
// Setup the default DC if not provided
@ -367,9 +363,7 @@ func (s *HTTPHandlers) catalogServiceNodes(resp http.ResponseWriter, req *http.R
return nil, err
}
if args.ServiceName == "" {
resp.WriteHeader(http.StatusBadRequest)
fmt.Fprint(resp, "Missing service name")
return nil, nil
return nil, BadRequestError{Reason: "Missing service name"}
}
// Make the RPC request
@ -444,9 +438,7 @@ func (s *HTTPHandlers) CatalogNodeServices(resp http.ResponseWriter, req *http.R
return nil, err
}
if args.Node == "" {
resp.WriteHeader(http.StatusBadRequest)
fmt.Fprint(resp, "Missing node name")
return nil, nil
return nil, BadRequestError{Reason: "Missing node name"}
}
// Make the RPC request
@ -511,9 +503,7 @@ func (s *HTTPHandlers) CatalogNodeServiceList(resp http.ResponseWriter, req *htt
return nil, err
}
if args.Node == "" {
resp.WriteHeader(http.StatusBadRequest)
fmt.Fprint(resp, "Missing node name")
return nil, nil
return nil, BadRequestError{Reason: "Missing node name"}
}
// Make the RPC request
@ -564,9 +554,7 @@ func (s *HTTPHandlers) CatalogGatewayServices(resp http.ResponseWriter, req *htt
return nil, err
}
if args.ServiceName == "" {
resp.WriteHeader(http.StatusBadRequest)
fmt.Fprint(resp, "Missing gateway name")
return nil, nil
return nil, BadRequestError{Reason: "Missing gateway name"}
}
// Make the RPC request

@ -90,16 +90,12 @@ func (s *HTTPHandlers) configDelete(resp http.ResponseWriter, req *http.Request)
pathArgs := strings.SplitN(kindAndName, "/", 2)
if len(pathArgs) != 2 {
resp.WriteHeader(http.StatusNotFound)
fmt.Fprintf(resp, "Must provide both a kind and name to delete")
return nil, nil
return nil, NotFoundError{Reason: "Must provide both a kind and name to delete"}
}
entry, err := structs.MakeConfigEntry(pathArgs[0], pathArgs[1])
if err != nil {
resp.WriteHeader(http.StatusBadRequest)
fmt.Fprintf(resp, "%v", err)
return nil, nil
return nil, BadRequestError{Reason: err.Error()}
}
args.Entry = entry
// Parse enterprise meta.

@ -16,10 +16,9 @@ import (
vaultapi "github.com/hashicorp/vault/api"
"github.com/mitchellh/mapstructure"
"github.com/hashicorp/consul/lib/decode"
"github.com/hashicorp/consul/agent/connect"
"github.com/hashicorp/consul/agent/structs"
"github.com/hashicorp/consul/lib/decode"
)
const (
@ -161,6 +160,34 @@ func (v *VaultProvider) Configure(cfg ProviderConfig) error {
return nil
}
func (v *VaultProvider) ValidateConfigUpdate(prevRaw, nextRaw map[string]interface{}) error {
prev, err := ParseVaultCAConfig(prevRaw)
if err != nil {
return fmt.Errorf("failed to parse existing CA config: %w", err)
}
next, err := ParseVaultCAConfig(nextRaw)
if err != nil {
return fmt.Errorf("failed to parse new CA config: %w", err)
}
if prev.RootPKIPath != next.RootPKIPath {
return nil
}
if prev.PrivateKeyType != "" && prev.PrivateKeyType != connect.DefaultPrivateKeyType {
if prev.PrivateKeyType != next.PrivateKeyType {
return fmt.Errorf("cannot update the PrivateKeyType field without changing RootPKIPath")
}
}
if prev.PrivateKeyBits != 0 && prev.PrivateKeyBits != connect.DefaultPrivateKeyBits {
if prev.PrivateKeyBits != next.PrivateKeyBits {
return fmt.Errorf("cannot update the PrivateKeyBits field without changing RootPKIPath")
}
}
return nil
}
// renewToken uses a vaultapi.LifetimeWatcher to repeatedly renew our token's lease.
// If the token can no longer be renewed and auth method is set,
// it will re-authenticate to Vault using the auth method and restart the renewer with the new token.
@ -273,31 +300,6 @@ func (v *VaultProvider) GenerateRoot() (RootResult, error) {
if err != nil {
return RootResult{}, err
}
if rootPEM != "" {
rootCert, err := connect.ParseCert(rootPEM)
if err != nil {
return RootResult{}, err
}
// Vault PKI doesn't allow in-place cert/key regeneration. That
// means if you need to change either the key type or key bits then
// you also need to provide new mount points.
// https://www.vaultproject.io/api-docs/secret/pki#generate-root
//
// A separate bug in vault likely also requires that you use the
// ForceWithoutCrossSigning option when changing key types.
foundKeyType, foundKeyBits, err := connect.KeyInfoFromCert(rootCert)
if err != nil {
return RootResult{}, err
}
if v.config.PrivateKeyType != foundKeyType {
return RootResult{}, fmt.Errorf("cannot update the PrivateKeyType field without choosing a new PKI mount for the root CA")
}
if v.config.PrivateKeyBits != foundKeyBits {
return RootResult{}, fmt.Errorf("cannot update the PrivateKeyBits field without choosing a new PKI mount for the root CA")
}
}
}
return RootResult{PEM: rootPEM}, nil
@ -400,17 +402,14 @@ func (v *VaultProvider) SetIntermediate(intermediatePEM, rootPEM string) error {
return fmt.Errorf("cannot set an intermediate using another root in the primary datacenter")
}
err := validateSetIntermediate(
intermediatePEM, rootPEM,
"", // we don't have access to the private key directly
v.spiffeID,
)
// the private key is in vault, so we can't use it in this validation
err := validateSetIntermediate(intermediatePEM, rootPEM, "", v.spiffeID)
if err != nil {
return err
}
_, err = v.client.Logical().Write(v.config.IntermediatePKIPath+"intermediate/set-signed", map[string]interface{}{
"certificate": fmt.Sprintf("%s\n%s", intermediatePEM, rootPEM),
"certificate": intermediatePEM,
})
if err != nil {
return err

@ -34,10 +34,6 @@ var ACLSummaries = []prometheus.SummaryDefinition{
Name: []string{"acl", "ResolveToken"},
Help: "This measures the time it takes to resolve an ACL token.",
},
{
Name: []string{"acl", "ResolveTokenToIdentity"},
Help: "This measures the time it takes to resolve an ACL token to an Identity.",
},
}
// These must be kept in sync with the constants in command/agent/acl.go.
@ -1057,13 +1053,16 @@ func (r *ACLResolver) resolveLocallyManagedToken(token string) (structs.ACLIdent
return r.resolveLocallyManagedEnterpriseToken(token)
}
func (r *ACLResolver) ResolveTokenToIdentityAndAuthorizer(token string) (structs.ACLIdentity, acl.Authorizer, error) {
// ResolveToken to an acl.Authorizer and structs.ACLIdentity. The acl.Authorizer
// can be used to check permissions granted to the token, and the ACLIdentity
// describes the token and any defaults applied to it.
func (r *ACLResolver) ResolveToken(token string) (ACLResolveResult, error) {
if !r.ACLsEnabled() {
return nil, acl.ManageAll(), nil
return ACLResolveResult{Authorizer: acl.ManageAll()}, nil
}
if acl.RootAuthorizer(token) != nil {
return nil, nil, acl.ErrRootDenied
return ACLResolveResult{}, acl.ErrRootDenied
}
// handle the anonymous token
@ -1072,7 +1071,7 @@ func (r *ACLResolver) ResolveTokenToIdentityAndAuthorizer(token string) (structs
}
if ident, authz, ok := r.resolveLocallyManagedToken(token); ok {
return ident, authz, nil
return ACLResolveResult{Authorizer: authz, ACLIdentity: ident}, nil
}
defer metrics.MeasureSince([]string{"acl", "ResolveToken"}, time.Now())
@ -1082,10 +1081,11 @@ func (r *ACLResolver) ResolveTokenToIdentityAndAuthorizer(token string) (structs
r.handleACLDisabledError(err)
if IsACLRemoteError(err) {
r.logger.Error("Error resolving token", "error", err)
return &missingIdentity{reason: "primary-dc-down", token: token}, r.down, nil
ident := &missingIdentity{reason: "primary-dc-down", token: token}
return ACLResolveResult{Authorizer: r.down, ACLIdentity: ident}, nil
}
return nil, nil, err
return ACLResolveResult{}, err
}
// Build the Authorizer
@ -1098,7 +1098,7 @@ func (r *ACLResolver) ResolveTokenToIdentityAndAuthorizer(token string) (structs
authz, err := policies.Compile(r.cache, &conf)
if err != nil {
return nil, nil, err
return ACLResolveResult{}, err
}
chain = append(chain, authz)
@ -1106,42 +1106,32 @@ func (r *ACLResolver) ResolveTokenToIdentityAndAuthorizer(token string) (structs
if err != nil {
if IsACLRemoteError(err) {
r.logger.Error("Error resolving identity defaults", "error", err)
return identity, r.down, nil
return ACLResolveResult{Authorizer: r.down, ACLIdentity: identity}, nil
}
return nil, nil, err
return ACLResolveResult{}, err
} else if authz != nil {
chain = append(chain, authz)
}
chain = append(chain, acl.RootAuthorizer(r.config.ACLDefaultPolicy))
return identity, acl.NewChainedAuthorizer(chain), nil
return ACLResolveResult{Authorizer: acl.NewChainedAuthorizer(chain), ACLIdentity: identity}, nil
}
// TODO: rename to AccessorIDFromToken. This method is only used to retrieve the
// ACLIdentity.ID, so we don't need to return a full ACLIdentity. We could
// return a much smaller type (instad of just a string) to allow for changes
// in the future.
func (r *ACLResolver) ResolveTokenToIdentity(token string) (structs.ACLIdentity, error) {
if !r.ACLsEnabled() {
return nil, nil
}
if acl.RootAuthorizer(token) != nil {
return nil, acl.ErrRootDenied
}
// handle the anonymous token
if token == "" {
token = anonymousToken
}
type ACLResolveResult struct {
acl.Authorizer
// TODO: likely we can reduce this interface
ACLIdentity structs.ACLIdentity
}
if ident, _, ok := r.resolveLocallyManagedToken(token); ok {
return ident, nil
func (a ACLResolveResult) AccessorID() string {
if a.ACLIdentity == nil {
return ""
}
return a.ACLIdentity.ID()
}
defer metrics.MeasureSince([]string{"acl", "ResolveTokenToIdentity"}, time.Now())
return r.resolveIdentityFromToken(token)
func (a ACLResolveResult) Identity() structs.ACLIdentity {
return a.ACLIdentity
}
func (r *ACLResolver) ACLsEnabled() bool {
@ -1160,6 +1150,30 @@ func (r *ACLResolver) ACLsEnabled() bool {
return true
}
func (r *ACLResolver) ResolveTokenAndDefaultMeta(token string, entMeta *structs.EnterpriseMeta, authzContext *acl.AuthorizerContext) (ACLResolveResult, error) {
result, err := r.ResolveToken(token)
if err != nil {
return ACLResolveResult{}, err
}
if entMeta == nil {
entMeta = &structs.EnterpriseMeta{}
}
// Default the EnterpriseMeta based on the Tokens meta or actual defaults
// in the case of unknown identity
if result.ACLIdentity != nil {
entMeta.Merge(result.ACLIdentity.EnterpriseMetadata())
} else {
entMeta.Merge(structs.DefaultEnterpriseMetaInDefaultPartition())
}
// Use the meta to fill in the ACL authorization context
entMeta.FillAuthzContext(authzContext)
return result, err
}
// aclFilter is used to filter results from our state store based on ACL rules
// configured for the provided token.
type aclFilter struct {
@ -1967,7 +1981,7 @@ func filterACLWithAuthorizer(logger hclog.Logger, authorizer acl.Authorizer, sub
// not authorized for read access will be removed from subj.
func filterACL(r *ACLResolver, token string, subj interface{}) error {
// Get the ACL from the token
_, authorizer, err := r.ResolveTokenToIdentityAndAuthorizer(token)
authorizer, err := r.ResolveToken(token)
if err != nil {
return err
}

@ -1,7 +1,6 @@
package consul
import (
"github.com/hashicorp/consul/acl"
"github.com/hashicorp/consul/agent/structs"
)
@ -48,35 +47,3 @@ func (c *clientACLResolverBackend) ResolveRoleFromID(roleID string) (bool, *stru
// clients do no local role resolution at the moment
return false, nil, nil
}
func (c *Client) ResolveTokenToIdentity(token string) (structs.ACLIdentity, error) {
// not using ResolveTokenToIdentityAndAuthorizer because in this case we don't
// need to resolve the roles, policies and namespace but just want the identity
// information such as accessor id.
return c.acls.ResolveTokenToIdentity(token)
}
// TODO: Server has an identical implementation, remove duplication
func (c *Client) ResolveTokenAndDefaultMeta(token string, entMeta *structs.EnterpriseMeta, authzContext *acl.AuthorizerContext) (acl.Authorizer, error) {
identity, authz, err := c.acls.ResolveTokenToIdentityAndAuthorizer(token)
if err != nil {
return nil, err
}
if entMeta == nil {
entMeta = &structs.EnterpriseMeta{}
}
// Default the EnterpriseMeta based on the Tokens meta or actual defaults
// in the case of unknown identity
if identity != nil {
entMeta.Merge(identity.EnterpriseMetadata())
} else {
entMeta.Merge(structs.DefaultEnterpriseMetaInDefaultPartition())
}
// Use the meta to fill in the ACL authorization context
entMeta.FillAuthzContext(authzContext)
return authz, err
}

@ -724,7 +724,7 @@ func (a *ACL) tokenSetInternal(args *structs.ACLTokenSetRequest, reply *structs.
}
// Purge the identity from the cache to prevent using the previous definition of the identity
a.srv.acls.cache.RemoveIdentity(tokenSecretCacheID(token.SecretID))
a.srv.ACLResolver.cache.RemoveIdentity(tokenSecretCacheID(token.SecretID))
// Don't check expiration times here as it doesn't really matter.
if _, updatedToken, err := a.srv.fsm.State().ACLTokenGetByAccessor(nil, token.AccessorID, nil); err == nil && updatedToken != nil {
@ -876,7 +876,7 @@ func (a *ACL) TokenDelete(args *structs.ACLTokenDeleteRequest, reply *string) er
}
// Purge the identity from the cache to prevent using the previous definition of the identity
a.srv.acls.cache.RemoveIdentity(tokenSecretCacheID(token.SecretID))
a.srv.ACLResolver.cache.RemoveIdentity(tokenSecretCacheID(token.SecretID))
if reply != nil {
*reply = token.AccessorID
@ -1198,7 +1198,7 @@ func (a *ACL) PolicySet(args *structs.ACLPolicySetRequest, reply *structs.ACLPol
}
// Remove from the cache to prevent stale cache usage
a.srv.acls.cache.RemovePolicy(policy.ID)
a.srv.ACLResolver.cache.RemovePolicy(policy.ID)
if _, policy, err := a.srv.fsm.State().ACLPolicyGetByID(nil, policy.ID, &policy.EnterpriseMeta); err == nil && policy != nil {
*reply = *policy
@ -1257,7 +1257,7 @@ func (a *ACL) PolicyDelete(args *structs.ACLPolicyDeleteRequest, reply *string)
return fmt.Errorf("Failed to apply policy delete request: %v", err)
}
a.srv.acls.cache.RemovePolicy(policy.ID)
a.srv.ACLResolver.cache.RemovePolicy(policy.ID)
*reply = policy.Name
@ -1318,12 +1318,12 @@ func (a *ACL) PolicyResolve(args *structs.ACLPolicyBatchGetRequest, reply *struc
}
// get full list of policies for this token
identity, policies, err := a.srv.acls.resolveTokenToIdentityAndPolicies(args.Token)
identity, policies, err := a.srv.ACLResolver.resolveTokenToIdentityAndPolicies(args.Token)
if err != nil {
return err
}
entIdentity, entPolicies, err := a.srv.acls.resolveEnterpriseIdentityAndPolicies(identity)
entIdentity, entPolicies, err := a.srv.ACLResolver.resolveEnterpriseIdentityAndPolicies(identity)
if err != nil {
return err
}
@ -1609,7 +1609,7 @@ func (a *ACL) RoleSet(args *structs.ACLRoleSetRequest, reply *structs.ACLRole) e
}
// Remove from the cache to prevent stale cache usage
a.srv.acls.cache.RemoveRole(role.ID)
a.srv.ACLResolver.cache.RemoveRole(role.ID)
if _, role, err := a.srv.fsm.State().ACLRoleGetByID(nil, role.ID, &role.EnterpriseMeta); err == nil && role != nil {
*reply = *role
@ -1664,7 +1664,7 @@ func (a *ACL) RoleDelete(args *structs.ACLRoleDeleteRequest, reply *string) erro
return fmt.Errorf("Failed to apply role delete request: %v", err)
}
a.srv.acls.cache.RemoveRole(role.ID)
a.srv.ACLResolver.cache.RemoveRole(role.ID)
*reply = role.Name
@ -1719,12 +1719,12 @@ func (a *ACL) RoleResolve(args *structs.ACLRoleBatchGetRequest, reply *structs.A
}
// get full list of roles for this token
identity, roles, err := a.srv.acls.resolveTokenToIdentityAndRoles(args.Token)
identity, roles, err := a.srv.ACLResolver.resolveTokenToIdentityAndRoles(args.Token)
if err != nil {
return err
}
entIdentity, entRoles, err := a.srv.acls.resolveEnterpriseIdentityAndRoles(identity)
entIdentity, entRoles, err := a.srv.ACLResolver.resolveEnterpriseIdentityAndRoles(identity)
if err != nil {
return err
}
@ -2481,7 +2481,7 @@ func (a *ACL) Logout(args *structs.ACLLogoutRequest, reply *bool) error {
}
// Purge the identity from the cache to prevent using the previous definition of the identity
a.srv.acls.cache.RemoveIdentity(tokenSecretCacheID(token.SecretID))
a.srv.ACLResolver.cache.RemoveIdentity(tokenSecretCacheID(token.SecretID))
*reply = true

@ -165,47 +165,10 @@ func (s *serverACLResolverBackend) ResolveRoleFromID(roleID string) (bool, *stru
return s.InPrimaryDatacenter() || index > 0, role, acl.ErrNotFound
}
func (s *Server) ResolveToken(token string) (acl.Authorizer, error) {
_, authz, err := s.acls.ResolveTokenToIdentityAndAuthorizer(token)
return authz, err
}
func (s *Server) ResolveTokenToIdentity(token string) (structs.ACLIdentity, error) {
// not using ResolveTokenToIdentityAndAuthorizer because in this case we don't
// need to resolve the roles, policies and namespace but just want the identity
// information such as accessor id.
return s.acls.ResolveTokenToIdentity(token)
}
// TODO: Client has an identical implementation, remove duplication
func (s *Server) ResolveTokenAndDefaultMeta(token string, entMeta *structs.EnterpriseMeta, authzContext *acl.AuthorizerContext) (acl.Authorizer, error) {
identity, authz, err := s.acls.ResolveTokenToIdentityAndAuthorizer(token)
if err != nil {
return nil, err
}
if entMeta == nil {
entMeta = &structs.EnterpriseMeta{}
}
// Default the EnterpriseMeta based on the Tokens meta or actual defaults
// in the case of unknown identity
if identity != nil {
entMeta.Merge(identity.EnterpriseMetadata())
} else {
entMeta.Merge(structs.DefaultEnterpriseMetaInDefaultPartition())
}
// Use the meta to fill in the ACL authorization context
entMeta.FillAuthzContext(authzContext)
return authz, err
}
func (s *Server) filterACL(token string, subj interface{}) error {
return filterACL(s.acls, token, subj)
return filterACL(s.ACLResolver, token, subj)
}
func (s *Server) filterACLWithAuthorizer(authorizer acl.Authorizer, subj interface{}) {
filterACLWithAuthorizer(s.acls.logger, authorizer, subj)
filterACLWithAuthorizer(s.ACLResolver.logger, authorizer, subj)
}

@ -46,10 +46,11 @@ type asyncResolutionResult struct {
err error
}
func verifyAuthorizerChain(t *testing.T, expected acl.Authorizer, actual acl.Authorizer) {
expectedChainAuthz, ok := expected.(*acl.ChainedAuthorizer)
func verifyAuthorizerChain(t *testing.T, expected ACLResolveResult, actual ACLResolveResult) {
t.Helper()
expectedChainAuthz, ok := expected.Authorizer.(*acl.ChainedAuthorizer)
require.True(t, ok, "expected Authorizer is not a ChainedAuthorizer")
actualChainAuthz, ok := actual.(*acl.ChainedAuthorizer)
actualChainAuthz, ok := actual.Authorizer.(*acl.ChainedAuthorizer)
require.True(t, ok, "actual Authorizer is not a ChainedAuthorizer")
expectedChain := expectedChainAuthz.AuthorizerChain()
@ -65,19 +66,13 @@ func verifyAuthorizerChain(t *testing.T, expected acl.Authorizer, actual acl.Aut
}
func resolveTokenAsync(r *ACLResolver, token string, ch chan *asyncResolutionResult) {
_, authz, err := r.ResolveTokenToIdentityAndAuthorizer(token)
authz, err := r.ResolveToken(token)
ch <- &asyncResolutionResult{authz: authz, err: err}
}
// Deprecated: use resolveToken or ACLResolver.ResolveTokenToIdentityAndAuthorizer instead
func (r *ACLResolver) ResolveToken(token string) (acl.Authorizer, error) {
_, authz, err := r.ResolveTokenToIdentityAndAuthorizer(token)
return authz, err
}
func resolveToken(t *testing.T, r *ACLResolver, token string) acl.Authorizer {
t.Helper()
_, authz, err := r.ResolveTokenToIdentityAndAuthorizer(token)
authz, err := r.ResolveToken(token)
require.NoError(t, err)
return authz
}
@ -739,7 +734,7 @@ func TestACLResolver_Disabled(t *testing.T) {
r := newTestACLResolver(t, delegate, nil)
authz, err := r.ResolveToken("does not exist")
require.Equal(t, acl.ManageAll(), authz)
require.Equal(t, ACLResolveResult{Authorizer: acl.ManageAll()}, authz)
require.Nil(t, err)
}
@ -753,22 +748,19 @@ func TestACLResolver_ResolveRootACL(t *testing.T) {
r := newTestACLResolver(t, delegate, nil)
t.Run("Allow", func(t *testing.T) {
authz, err := r.ResolveToken("allow")
require.Nil(t, authz)
_, err := r.ResolveToken("allow")
require.Error(t, err)
require.True(t, acl.IsErrRootDenied(err))
})
t.Run("Deny", func(t *testing.T) {
authz, err := r.ResolveToken("deny")
require.Nil(t, authz)
_, err := r.ResolveToken("deny")
require.Error(t, err)
require.True(t, acl.IsErrRootDenied(err))
})
t.Run("Manage", func(t *testing.T) {
authz, err := r.ResolveToken("manage")
require.Nil(t, authz)
_, err := r.ResolveToken("manage")
require.Error(t, err)
require.True(t, acl.IsErrRootDenied(err))
})
@ -817,7 +809,11 @@ func TestACLResolver_DownPolicy(t *testing.T) {
authz, err := r.ResolveToken("foo")
require.NoError(t, err)
require.NotNil(t, authz)
require.Equal(t, authz, acl.DenyAll())
expected := ACLResolveResult{
Authorizer: acl.DenyAll(),
ACLIdentity: &missingIdentity{reason: "primary-dc-down", token: "foo"},
}
require.Equal(t, expected, authz)
requireIdentityCached(t, r, tokenSecretCacheID("foo"), false, "not present")
})
@ -841,7 +837,11 @@ func TestACLResolver_DownPolicy(t *testing.T) {
authz, err := r.ResolveToken("foo")
require.NoError(t, err)
require.NotNil(t, authz)
require.Equal(t, authz, acl.AllowAll())
expected := ACLResolveResult{
Authorizer: acl.AllowAll(),
ACLIdentity: &missingIdentity{reason: "primary-dc-down", token: "foo"},
}
require.Equal(t, expected, authz)
requireIdentityCached(t, r, tokenSecretCacheID("foo"), false, "not present")
})
@ -958,7 +958,7 @@ func TestACLResolver_DownPolicy(t *testing.T) {
config.Config.ACLDownPolicy = "extend-cache"
})
_, authz, err := r.ResolveTokenToIdentityAndAuthorizer("not-found")
authz, err := r.ResolveToken("not-found")
require.NoError(t, err)
require.NotNil(t, authz)
require.Equal(t, acl.Deny, authz.NodeWrite("foo", nil))
@ -1255,10 +1255,9 @@ func TestACLResolver_DownPolicy(t *testing.T) {
// the go routine spawned will eventually return and this will be a not found error
retry.Run(t, func(t *retry.R) {
authz3, err := r.ResolveToken("found")
_, err := r.ResolveToken("found")
assert.Error(t, err)
assert.True(t, acl.IsErrNotFound(err))
assert.Nil(t, authz3)
})
requireIdentityCached(t, r, tokenSecretCacheID("found"), false, "no longer cached")
@ -1526,7 +1525,6 @@ func TestACLResolver_Client(t *testing.T) {
// policies within the cache)
authz, err = r.ResolveToken("a1a54629-5050-4d17-8a4e-560d2423f835")
require.EqualError(t, err, acl.ErrNotFound.Error())
require.Nil(t, authz)
require.True(t, modified)
require.True(t, deleted)
@ -1534,36 +1532,6 @@ func TestACLResolver_Client(t *testing.T) {
require.Equal(t, policyResolves, int32(3))
})
t.Run("Resolve-Identity", func(t *testing.T) {
t.Parallel()
delegate := &ACLResolverTestDelegate{
enabled: true,
datacenter: "dc1",
legacy: false,
localTokens: false,
localPolicies: false,
}
delegate.tokenReadFn = delegate.plainTokenReadFn
delegate.policyResolveFn = delegate.plainPolicyResolveFn
delegate.roleResolveFn = delegate.plainRoleResolveFn
r := newTestACLResolver(t, delegate, nil)
ident, err := r.ResolveTokenToIdentity("found-policy-and-role")
require.NoError(t, err)
require.NotNil(t, ident)
require.Equal(t, "5f57c1f6-6a89-4186-9445-531b316e01df", ident.ID())
require.EqualValues(t, 0, delegate.localTokenResolutions)
require.EqualValues(t, 1, delegate.remoteTokenResolutions)
require.EqualValues(t, 0, delegate.localPolicyResolutions)
require.EqualValues(t, 0, delegate.remotePolicyResolutions)
require.EqualValues(t, 0, delegate.localRoleResolutions)
require.EqualValues(t, 0, delegate.remoteRoleResolutions)
require.EqualValues(t, 0, delegate.remoteLegacyResolutions)
})
t.Run("Concurrent-Token-Resolve", func(t *testing.T) {
t.Parallel()
@ -1705,8 +1673,7 @@ func testACLResolver_variousTokens(t *testing.T, delegate *ACLResolverTestDelega
runTwiceAndReset("Missing Identity", func(t *testing.T) {
delegate.UseTestLocalData(nil)
authz, err := r.ResolveToken("doesn't exist")
require.Nil(t, authz)
_, err := r.ResolveToken("doesn't exist")
require.Error(t, err)
require.True(t, acl.IsErrNotFound(err))
})
@ -3959,12 +3926,12 @@ func TestACLResolver_AgentRecovery(t *testing.T) {
tokens.UpdateAgentRecoveryToken("9a184a11-5599-459e-b71a-550e5f9a5a23", token.TokenSourceConfig)
ident, authz, err := r.ResolveTokenToIdentityAndAuthorizer("9a184a11-5599-459e-b71a-550e5f9a5a23")
authz, err := r.ResolveToken("9a184a11-5599-459e-b71a-550e5f9a5a23")
require.NoError(t, err)
require.NotNil(t, ident)
require.Equal(t, "agent-recovery:foo", ident.ID())
require.NotNil(t, authz)
require.Equal(t, r.agentRecoveryAuthz, authz)
require.NotNil(t, authz.ACLIdentity)
require.Equal(t, "agent-recovery:foo", authz.ACLIdentity.ID())
require.NotNil(t, authz.Authorizer)
require.Equal(t, r.agentRecoveryAuthz, authz.Authorizer)
require.Equal(t, acl.Allow, authz.AgentWrite("foo", nil))
require.Equal(t, acl.Allow, authz.NodeRead("bar", nil))
require.Equal(t, acl.Deny, authz.NodeWrite("bar", nil))
@ -4028,7 +3995,7 @@ func TestACLResolver_ACLsEnabled(t *testing.T) {
}
func TestACLResolver_ResolveTokenToIdentityAndAuthorizer_UpdatesPurgeTheCache(t *testing.T) {
func TestACLResolver_ResolveToken_UpdatesPurgeTheCache(t *testing.T) {
if testing.Short() {
t.Skip("too slow for testing.Short")
}
@ -4065,7 +4032,7 @@ func TestACLResolver_ResolveTokenToIdentityAndAuthorizer_UpdatesPurgeTheCache(t
require.NoError(t, err)
runStep(t, "first resolve", func(t *testing.T) {
_, authz, err := srv.acls.ResolveTokenToIdentityAndAuthorizer(token)
authz, err := srv.ACLResolver.ResolveToken(token)
require.NoError(t, err)
require.NotNil(t, authz)
require.Equal(t, acl.Allow, authz.KeyRead("foo", nil))
@ -4084,7 +4051,7 @@ func TestACLResolver_ResolveTokenToIdentityAndAuthorizer_UpdatesPurgeTheCache(t
err := msgpackrpc.CallWithCodec(codec, "ACL.PolicySet", &reqPolicy, &structs.ACLPolicy{})
require.NoError(t, err)
_, authz, err := srv.acls.ResolveTokenToIdentityAndAuthorizer(token)
authz, err := srv.ACLResolver.ResolveToken(token)
require.NoError(t, err)
require.NotNil(t, authz)
require.Equal(t, acl.Deny, authz.KeyRead("foo", nil))
@ -4100,7 +4067,7 @@ func TestACLResolver_ResolveTokenToIdentityAndAuthorizer_UpdatesPurgeTheCache(t
err := msgpackrpc.CallWithCodec(codec, "ACL.TokenDelete", &req, &resp)
require.NoError(t, err)
_, _, err = srv.acls.ResolveTokenToIdentityAndAuthorizer(token)
_, err = srv.ACLResolver.ResolveToken(token)
require.True(t, acl.IsErrNotFound(err), "Error %v is not acl.ErrNotFound", err)
})
}

@ -107,7 +107,7 @@ func (s *Server) reapExpiredACLTokens(local, global bool) (int, error) {
// Purge the identities from the cache
for _, secretID := range secretIDs {
s.acls.cache.RemoveIdentity(tokenSecretCacheID(secretID))
s.ACLResolver.cache.RemoveIdentity(tokenSecretCacheID(secretID))
}
return len(req.TokenIDs), nil

@ -56,7 +56,7 @@ type Client struct {
config *Config
// acls is used to resolve tokens to effective policies
acls *ACLResolver
*ACLResolver
// Connection pool to consul servers
connPool *pool.ConnPool
@ -127,7 +127,7 @@ func NewClient(config *Config, deps Deps) (*Client, error) {
Tokens: deps.Tokens,
}
var err error
if c.acls, err = NewACLResolver(&aclConfig); err != nil {
if c.ACLResolver, err = NewACLResolver(&aclConfig); err != nil {
c.Shutdown()
return nil, fmt.Errorf("Failed to create ACL resolver: %v", err)
}
@ -172,7 +172,7 @@ func (c *Client) Shutdown() error {
// Close the connection pool
c.connPool.Shutdown()
c.acls.Close()
c.ACLResolver.Close()
return nil
}

@ -558,92 +558,88 @@ func TestConnectCAConfig_Vault_TriggerRotation_Fails(t *testing.T) {
t.Parallel()
testVault := ca.NewTestVaultServer(t)
defer testVault.Stop()
newConfig := func(keyType string, keyBits int) map[string]interface{} {
return map[string]interface{}{
"Address": testVault.Addr,
"Token": testVault.RootToken,
"RootPKIPath": "pki-root/",
"IntermediatePKIPath": "pki-intermediate/",
"PrivateKeyType": keyType,
"PrivateKeyBits": keyBits,
}
}
_, s1 := testServerWithConfig(t, func(c *Config) {
c.Build = "1.6.0"
c.PrimaryDatacenter = "dc1"
c.CAConfig = &structs.CAConfiguration{
Provider: "vault",
Config: map[string]interface{}{
"Address": testVault.Addr,
"Token": testVault.RootToken,
"RootPKIPath": "pki-root/",
"IntermediatePKIPath": "pki-intermediate/",
},
Config: newConfig(connect.DefaultPrivateKeyType, connect.DefaultPrivateKeyBits),
}
})
defer s1.Shutdown()
codec := rpcClient(t, s1)
defer codec.Close()
testrpc.WaitForTestAgent(t, s1.RPC, "dc1")
// Capture the current root.
{
rootList, _, err := getTestRoots(s1, "dc1")
require.NoError(t, err)
require.Len(t, rootList.Roots, 1)
}
cases := []struct {
// note: unlike many table tests, the ordering of these cases does matter
// because any non-errored case will modify the CA config, and any subsequent
// tests will use the same agent with that new CA config.
testSteps := []struct {
name string
configFn func() (*structs.CAConfiguration, error)
configFn func() *structs.CAConfiguration
expectErr string
}{
{
name: "cannot edit key bits",
configFn: func() (*structs.CAConfiguration, error) {
name: "allow modifying key type and bits from default",
configFn: func() *structs.CAConfiguration {
return &structs.CAConfiguration{
Provider: "vault",
Config: map[string]interface{}{
"Address": testVault.Addr,
"Token": testVault.RootToken,
"RootPKIPath": "pki-root/",
"IntermediatePKIPath": "pki-intermediate/",
//
"PrivateKeyType": "ec",
"PrivateKeyBits": 384,
},
Provider: "vault",
Config: newConfig("rsa", 4096),
ForceWithoutCrossSigning: true,
}, nil
}
},
expectErr: `error generating CA root certificate: cannot update the PrivateKeyBits field without choosing a new PKI mount for the root CA`,
},
{
name: "cannot edit key type",
configFn: func() (*structs.CAConfiguration, error) {
name: "error when trying to modify key bits",
configFn: func() *structs.CAConfiguration {
return &structs.CAConfiguration{
Provider: "vault",
Config: map[string]interface{}{
"Address": testVault.Addr,
"Token": testVault.RootToken,
"RootPKIPath": "pki-root/",
"IntermediatePKIPath": "pki-intermediate/",
//
"PrivateKeyType": "rsa",
"PrivateKeyBits": 4096,
},
Provider: "vault",
Config: newConfig("rsa", 2048),
ForceWithoutCrossSigning: true,
}, nil
}
},
expectErr: `cannot update the PrivateKeyBits field without changing RootPKIPath`,
},
{
name: "error when trying to modify key type",
configFn: func() *structs.CAConfiguration {
return &structs.CAConfiguration{
Provider: "vault",
Config: newConfig("ec", 256),
ForceWithoutCrossSigning: true,
}
},
expectErr: `cannot update the PrivateKeyType field without changing RootPKIPath`,
},
{
name: "allow update that does not change key type or bits",
configFn: func() *structs.CAConfiguration {
return &structs.CAConfiguration{
Provider: "vault",
Config: newConfig("rsa", 4096),
ForceWithoutCrossSigning: true,
}
},
expectErr: `error generating CA root certificate: cannot update the PrivateKeyType field without choosing a new PKI mount for the root CA`,
},
}
for _, tc := range cases {
for _, tc := range testSteps {
t.Run(tc.name, func(t *testing.T) {
newConfig, err := tc.configFn()
require.NoError(t, err)
args := &structs.CARequest{
Datacenter: "dc1",
Config: newConfig,
Config: tc.configFn(),
}
var reply interface{}
err = msgpackrpc.CallWithCodec(codec, "ConnectCA.ConfigurationSet", args, &reply)
codec := rpcClient(t, s1)
err := msgpackrpc.CallWithCodec(codec, "ConnectCA.ConfigurationSet", args, &reply)
if tc.expectErr == "" {
require.NoError(t, err)
} else {

@ -100,20 +100,13 @@ func (s *Intention) Apply(args *structs.IntentionRequest, reply *string) error {
}
// Get the ACL token for the request for the checks below.
identity, authz, err := s.srv.acls.ResolveTokenToIdentityAndAuthorizer(args.Token)
var entMeta structs.EnterpriseMeta
authz, err := s.srv.ACLResolver.ResolveTokenAndDefaultMeta(args.Token, &entMeta, nil)
if err != nil {
return err
}
var accessorID string
var entMeta structs.EnterpriseMeta
if identity != nil {
entMeta.Merge(identity.EnterpriseMetadata())
accessorID = identity.ID()
} else {
entMeta.Merge(structs.DefaultEnterpriseMetaInDefaultPartition())
}
accessorID := authz.AccessorID()
var (
mut *structs.IntentionMutation
legacyWrite bool
@ -432,7 +425,8 @@ func (s *Intention) Get(args *structs.IntentionQueryRequest, reply *structs.Inde
// Get the ACL token for the request for the checks below.
var entMeta structs.EnterpriseMeta
if _, err := s.srv.ResolveTokenAndDefaultMeta(args.Token, &entMeta, nil); err != nil {
authz, err := s.srv.ResolveTokenAndDefaultMeta(args.Token, &entMeta, nil)
if err != nil {
return err
}
@ -479,13 +473,11 @@ func (s *Intention) Get(args *structs.IntentionQueryRequest, reply *structs.Inde
reply.Intentions = structs.Intentions{ixn}
// Filter
if err := s.srv.filterACL(args.Token, reply); err != nil {
return err
}
s.srv.filterACLWithAuthorizer(authz, reply)
// If ACLs prevented any responses, error
if len(reply.Intentions) == 0 {
accessorID := s.aclAccessorID(args.Token)
accessorID := authz.AccessorID()
// todo(kit) Migrate intention access denial logging over to audit logging when we implement it
s.logger.Warn("Request to get intention denied due to ACLs", "intention", args.IntentionID, "accessorID", accessorID)
return acl.ErrPermissionDenied
@ -618,7 +610,7 @@ func (s *Intention) Match(args *structs.IntentionQueryRequest, reply *structs.In
for _, entry := range args.Match.Entries {
entry.FillAuthzContext(&authzContext)
if prefix := entry.Name; prefix != "" && authz.IntentionRead(prefix, &authzContext) != acl.Allow {
accessorID := s.aclAccessorID(args.Token)
accessorID := authz.AccessorID()
// todo(kit) Migrate intention access denial logging over to audit logging when we implement it
s.logger.Warn("Operation on intention prefix denied due to ACLs", "prefix", prefix, "accessorID", accessorID)
return acl.ErrPermissionDenied
@ -708,7 +700,7 @@ func (s *Intention) Check(args *structs.IntentionQueryRequest, reply *structs.In
var authzContext acl.AuthorizerContext
query.FillAuthzContext(&authzContext)
if authz.ServiceRead(prefix, &authzContext) != acl.Allow {
accessorID := s.aclAccessorID(args.Token)
accessorID := authz.AccessorID()
// todo(kit) Migrate intention access denial logging over to audit logging when we implement it
s.logger.Warn("test on intention denied due to ACLs", "prefix", prefix, "accessorID", accessorID)
return acl.ErrPermissionDenied
@ -760,24 +752,6 @@ func (s *Intention) Check(args *structs.IntentionQueryRequest, reply *structs.In
return nil
}
// aclAccessorID is used to convert an ACLToken's secretID to its accessorID for non-
// critical purposes, such as logging. Therefore we interpret all errors as empty-string
// so we can safely log it without handling non-critical errors at the usage site.
func (s *Intention) aclAccessorID(secretID string) string {
_, ident, err := s.srv.ResolveIdentityFromToken(secretID)
if acl.IsErrNotFound(err) {
return ""
}
if err != nil {
s.logger.Debug("non-critical error resolving acl token accessor for logging", "error", err)
return ""
}
if ident == nil {
return ""
}
return ident.ID()
}
func (s *Intention) validateEnterpriseIntention(ixn *structs.Intention) error {
if err := s.srv.validateEnterpriseIntentionPartition(ixn.SourcePartition); err != nil {
return fmt.Errorf("Invalid source partition %q: %v", ixn.SourcePartition, err)

@ -401,13 +401,13 @@ func (m *Internal) EventFire(args *structs.EventFireRequest,
}
// Check ACLs
authz, err := m.srv.ResolveToken(args.Token)
authz, err := m.srv.ResolveTokenAndDefaultMeta(args.Token, nil, nil)
if err != nil {
return err
}
if authz.EventWrite(args.Name, nil) != acl.Allow {
accessorID := m.aclAccessorID(args.Token)
accessorID := authz.AccessorID()
m.logger.Warn("user event blocked by ACLs", "event", args.Name, "accessorID", accessorID)
return acl.ErrPermissionDenied
}
@ -433,11 +433,11 @@ func (m *Internal) KeyringOperation(
}
// Check ACLs
identity, authz, err := m.srv.acls.ResolveTokenToIdentityAndAuthorizer(args.Token)
authz, err := m.srv.ACLResolver.ResolveToken(args.Token)
if err != nil {
return err
}
if err := m.srv.validateEnterpriseToken(identity); err != nil {
if err := m.srv.validateEnterpriseToken(authz.Identity()); err != nil {
return err
}
switch args.Operation {
@ -545,21 +545,3 @@ func (m *Internal) executeKeyringOpMgr(
return serfResp, err
}
// aclAccessorID is used to convert an ACLToken's secretID to its accessorID for non-
// critical purposes, such as logging. Therefore we interpret all errors as empty-string
// so we can safely log it without handling non-critical errors at the usage site.
func (m *Internal) aclAccessorID(secretID string) string {
_, ident, err := m.srv.ResolveIdentityFromToken(secretID)
if acl.IsErrNotFound(err) {
return ""
}
if err != nil {
m.logger.Debug("non-critical error resolving acl token accessor for logging", "error", err)
return ""
}
if ident == nil {
return ""
}
return ident.ID()
}

@ -363,7 +363,7 @@ func (s *Server) initializeACLs(ctx context.Context) error {
// Purge the cache, since it could've changed while we were not the
// leader.
s.acls.cache.Purge()
s.ACLResolver.cache.Purge()
// Purge the auth method validators since they could've changed while we
// were not leader.

@ -654,7 +654,7 @@ func (c *CAManager) secondaryInitializeIntermediateCA(provider ca.Provider, conf
}
if needsNewIntermediate {
if err := c.secondaryRenewIntermediate(provider, newActiveRoot); err != nil {
if err := c.secondaryRequestNewSigningCert(provider, newActiveRoot); err != nil {
return err
}
} else {
@ -781,7 +781,7 @@ func (c *CAManager) UpdateConfiguration(args *structs.CARequest) (reterr error)
}()
// Attempt to initialize the config if we failed to do so in Initialize for some reason
_, err = c.initializeCAConfig()
prevConfig, err := c.initializeCAConfig()
if err != nil {
return err
}
@ -832,6 +832,15 @@ func (c *CAManager) UpdateConfiguration(args *structs.CARequest) (reterr error)
RawConfig: args.Config.Config,
State: args.Config.State,
}
if args.Config.Provider == config.Provider {
if validator, ok := newProvider.(ValidateConfigUpdater); ok {
if err := validator.ValidateConfigUpdate(prevConfig.Config, args.Config.Config); err != nil {
return fmt.Errorf("new configuration is incompatible with previous configuration: %w", err)
}
}
}
if err := newProvider.Configure(pCfg); err != nil {
return fmt.Errorf("error configuring provider: %v", err)
}
@ -858,6 +867,19 @@ func (c *CAManager) UpdateConfiguration(args *structs.CARequest) (reterr error)
return nil
}
// ValidateConfigUpdater is an optional interface that may be implemented
// by a ca.Provider. If the provider implements this interface, the
// ValidateConfigurationUpdate will be called when a user attempts to change the
// CA configuration, and the provider type has not changed from the previous
// configuration.
type ValidateConfigUpdater interface {
// ValidateConfigUpdate should return an error if the next configuration is
// incompatible with the previous configuration.
//
// TODO: use better types after https://github.com/hashicorp/consul/issues/12238
ValidateConfigUpdate(previous, next map[string]interface{}) error
}
func (c *CAManager) primaryUpdateRootCA(newProvider ca.Provider, args *structs.CARequest, config *structs.CAConfiguration) error {
providerRoot, err := newProvider.GenerateRoot()
if err != nil {
@ -1028,9 +1050,11 @@ func (c *CAManager) primaryRenewIntermediate(provider ca.Provider, newActiveRoot
return nil
}
// secondaryRenewIntermediate should only be called while the state lock is held by
// setting the state to non-ready.
func (c *CAManager) secondaryRenewIntermediate(provider ca.Provider, newActiveRoot *structs.CARoot) error {
// secondaryRequestNewSigningCert creates a Certificate Signing Request, sends
// the request to the primary, and stores the received certificate in the
// provider.
// Should only be called while the state lock is held by setting the state to non-ready.
func (c *CAManager) secondaryRequestNewSigningCert(provider ca.Provider, newActiveRoot *structs.CARoot) error {
csr, err := provider.GenerateIntermediateCSR()
if err != nil {
return err
@ -1145,7 +1169,7 @@ func (c *CAManager) RenewIntermediate(ctx context.Context, isPrimary bool) error
// Enough time has passed, go ahead with getting a new intermediate.
renewalFunc := c.primaryRenewIntermediate
if !isPrimary {
renewalFunc = c.secondaryRenewIntermediate
renewalFunc = c.secondaryRequestNewSigningCert
}
errCh := make(chan error, 1)
go func() {

@ -17,6 +17,7 @@ import (
"time"
msgpackrpc "github.com/hashicorp/net-rpc-msgpackrpc"
vaultapi "github.com/hashicorp/vault/api"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
@ -606,6 +607,88 @@ func TestCAManager_UpdateConfiguration_Vault_Primary(t *testing.T) {
require.Equal(t, connect.HexString(cert.SubjectKeyId), newRoot.SigningKeyID)
}
func TestCAManager_Initialize_Vault_WithIntermediateAsPrimaryCA(t *testing.T) {
if testing.Short() {
t.Skip("too slow for testing.Short")
}
ca.SkipIfVaultNotPresent(t)
vault := ca.NewTestVaultServer(t)
vclient := vault.Client()
generateExternalRootCA(t, vclient)
meshRootPath := "pki-root"
primaryCert := setupPrimaryCA(t, vclient, meshRootPath)
_, s1 := testServerWithConfig(t, func(c *Config) {
c.CAConfig = &structs.CAConfiguration{
Provider: "vault",
Config: map[string]interface{}{
"Address": vault.Addr,
"Token": vault.RootToken,
"RootPKIPath": meshRootPath,
"IntermediatePKIPath": "pki-intermediate/",
// TODO: there are failures to init the CA system if these are not set
// to the values of the already initialized CA.
"PrivateKeyType": "ec",
"PrivateKeyBits": 256,
},
}
})
defer s1.Shutdown()
runStep(t, "check primary DC", func(t *testing.T) {
testrpc.WaitForTestAgent(t, s1.RPC, "dc1")
codec := rpcClient(t, s1)
roots := structs.IndexedCARoots{}
err := msgpackrpc.CallWithCodec(codec, "ConnectCA.Roots", &structs.DCSpecificRequest{}, &roots)
require.NoError(t, err)
require.Len(t, roots.Roots, 1)
require.Equal(t, primaryCert, roots.Roots[0].RootCert)
leafCertPEM := getLeafCert(t, codec, roots.TrustDomain, "dc1")
verifyLeafCert(t, roots.Roots[0], leafCertPEM)
})
// TODO: renew primary leaf signing cert
// TODO: rotate root
runStep(t, "run secondary DC", func(t *testing.T) {
_, sDC2 := testServerWithConfig(t, func(c *Config) {
c.Datacenter = "dc2"
c.PrimaryDatacenter = "dc1"
c.CAConfig = &structs.CAConfiguration{
Provider: "vault",
Config: map[string]interface{}{
"Address": vault.Addr,
"Token": vault.RootToken,
"RootPKIPath": meshRootPath,
"IntermediatePKIPath": "pki-secondary/",
// TODO: there are failures to init the CA system if these are not set
// to the values of the already initialized CA.
"PrivateKeyType": "ec",
"PrivateKeyBits": 256,
},
}
})
defer sDC2.Shutdown()
joinWAN(t, sDC2, s1)
testrpc.WaitForActiveCARoot(t, sDC2.RPC, "dc2", nil)
codec := rpcClient(t, sDC2)
roots := structs.IndexedCARoots{}
err := msgpackrpc.CallWithCodec(codec, "ConnectCA.Roots", &structs.DCSpecificRequest{}, &roots)
require.NoError(t, err)
require.Len(t, roots.Roots, 1)
leafCertPEM := getLeafCert(t, codec, roots.TrustDomain, "dc2")
verifyLeafCert(t, roots.Roots[0], leafCertPEM)
// TODO: renew secondary leaf signing cert
})
}
func getLeafCert(t *testing.T, codec rpc.ClientCodec, trustDomain string, dc string) string {
pk, _, err := connect.GeneratePrivateKey()
require.NoError(t, err)
@ -624,3 +707,58 @@ func getLeafCert(t *testing.T, codec rpc.ClientCodec, trustDomain string, dc str
return cert.CertPEM
}
func generateExternalRootCA(t *testing.T, client *vaultapi.Client) string {
t.Helper()
err := client.Sys().Mount("corp", &vaultapi.MountInput{
Type: "pki",
Description: "External root, probably corporate CA",
Config: vaultapi.MountConfigInput{
MaxLeaseTTL: "2400h",
DefaultLeaseTTL: "1h",
},
})
require.NoError(t, err, "failed to mount")
resp, err := client.Logical().Write("corp/root/generate/internal", map[string]interface{}{
"common_name": "corporate CA",
"ttl": "2400h",
})
require.NoError(t, err, "failed to generate root")
return resp.Data["certificate"].(string)
}
func setupPrimaryCA(t *testing.T, client *vaultapi.Client, path string) string {
t.Helper()
err := client.Sys().Mount(path, &vaultapi.MountInput{
Type: "pki",
Description: "primary CA for Consul CA",
Config: vaultapi.MountConfigInput{
MaxLeaseTTL: "2200h",
DefaultLeaseTTL: "1h",
},
})
require.NoError(t, err, "failed to mount")
out, err := client.Logical().Write(path+"/intermediate/generate/internal", map[string]interface{}{
"common_name": "primary CA",
"ttl": "2200h",
"key_type": "ec",
"key_bits": 256,
})
require.NoError(t, err, "failed to generate root")
intermediate, err := client.Logical().Write("corp/root/sign-intermediate", map[string]interface{}{
"csr": out.Data["csr"],
"use_csr_values": true,
"format": "pem_bundle",
"ttl": "2200h",
})
require.NoError(t, err, "failed to sign intermediate")
_, err = client.Logical().Write(path+"/intermediate/set-signed", map[string]interface{}{
"certificate": intermediate.Data["certificate"],
})
require.NoError(t, err, "failed to set signed intermediate")
return ca.EnsureTrailingNewline(intermediate.Data["certificate"].(string))
}

@ -17,11 +17,11 @@ func (op *Operator) AutopilotGetConfiguration(args *structs.DCSpecificRequest, r
}
// This action requires operator read access.
identity, authz, err := op.srv.acls.ResolveTokenToIdentityAndAuthorizer(args.Token)
authz, err := op.srv.ACLResolver.ResolveToken(args.Token)
if err != nil {
return err
}
if err := op.srv.validateEnterpriseToken(identity); err != nil {
if err := op.srv.validateEnterpriseToken(authz.Identity()); err != nil {
return err
}
if authz.OperatorRead(nil) != acl.Allow {
@ -49,11 +49,11 @@ func (op *Operator) AutopilotSetConfiguration(args *structs.AutopilotSetConfigRe
}
// This action requires operator write access.
identity, authz, err := op.srv.acls.ResolveTokenToIdentityAndAuthorizer(args.Token)
authz, err := op.srv.ACLResolver.ResolveToken(args.Token)
if err != nil {
return err
}
if err := op.srv.validateEnterpriseToken(identity); err != nil {
if err := op.srv.validateEnterpriseToken(authz.Identity()); err != nil {
return err
}
if authz.OperatorWrite(nil) != acl.Allow {
@ -84,11 +84,11 @@ func (op *Operator) ServerHealth(args *structs.DCSpecificRequest, reply *structs
}
// This action requires operator read access.
identity, authz, err := op.srv.acls.ResolveTokenToIdentityAndAuthorizer(args.Token)
authz, err := op.srv.ACLResolver.ResolveToken(args.Token)
if err != nil {
return err
}
if err := op.srv.validateEnterpriseToken(identity); err != nil {
if err := op.srv.validateEnterpriseToken(authz.Identity()); err != nil {
return err
}
if authz.OperatorRead(nil) != acl.Allow {
@ -151,11 +151,11 @@ func (op *Operator) AutopilotState(args *structs.DCSpecificRequest, reply *autop
}
// This action requires operator read access.
identity, authz, err := op.srv.acls.ResolveTokenToIdentityAndAuthorizer(args.Token)
authz, err := op.srv.ACLResolver.ResolveToken(args.Token)
if err != nil {
return err
}
if err := op.srv.validateEnterpriseToken(identity); err != nil {
if err := op.srv.validateEnterpriseToken(authz.Identity()); err != nil {
return err
}
if authz.OperatorRead(nil) != acl.Allow {

@ -81,11 +81,11 @@ func (op *Operator) RaftRemovePeerByAddress(args *structs.RaftRemovePeerRequest,
// This is a super dangerous operation that requires operator write
// access.
identity, authz, err := op.srv.acls.ResolveTokenToIdentityAndAuthorizer(args.Token)
authz, err := op.srv.ACLResolver.ResolveToken(args.Token)
if err != nil {
return err
}
if err := op.srv.validateEnterpriseToken(identity); err != nil {
if err := op.srv.validateEnterpriseToken(authz.Identity()); err != nil {
return err
}
if authz.OperatorWrite(nil) != acl.Allow {
@ -134,11 +134,11 @@ func (op *Operator) RaftRemovePeerByID(args *structs.RaftRemovePeerRequest, repl
// This is a super dangerous operation that requires operator write
// access.
identity, authz, err := op.srv.acls.ResolveTokenToIdentityAndAuthorizer(args.Token)
authz, err := op.srv.ACLResolver.ResolveToken(args.Token)
if err != nil {
return err
}
if err := op.srv.validateEnterpriseToken(identity); err != nil {
if err := op.srv.validateEnterpriseToken(authz.Identity()); err != nil {
return err
}
if authz.OperatorWrite(nil) != acl.Allow {

@ -141,7 +141,7 @@ type Server struct {
aclConfig *acl.Config
// acls is used to resolve tokens to effective policies
acls *ACLResolver
*ACLResolver
aclAuthMethodValidators authmethod.Cache
@ -457,7 +457,7 @@ func NewServer(config *Config, flat Deps) (*Server, error) {
Tokens: flat.Tokens,
}
// Initialize the ACL resolver.
if s.acls, err = NewACLResolver(&aclConfig); err != nil {
if s.ACLResolver, err = NewACLResolver(&aclConfig); err != nil {
s.Shutdown()
return nil, fmt.Errorf("Failed to create ACL resolver: %v", err)
}
@ -994,8 +994,8 @@ func (s *Server) Shutdown() error {
s.connPool.Shutdown()
}
if s.acls != nil {
s.acls.Close()
if s.ACLResolver != nil {
s.ACLResolver.Close()
}
if s.fsm != nil {

@ -121,7 +121,7 @@ func (s *Server) setupSerfConfig(opts setupSerfOptions) (*serf.Config, error) {
// TODO(ACL-Legacy-Compat): remove in phase 2. These are kept for now to
// allow for upgrades.
if s.acls.ACLsEnabled() {
if s.ACLResolver.ACLsEnabled() {
conf.Tags[metadata.TagACLs] = string(structs.ACLModeEnabled)
} else {
conf.Tags[metadata.TagACLs] = string(structs.ACLModeDisabled)

@ -8,16 +8,13 @@ import (
"github.com/hashicorp/consul/agent/structs"
)
// checkCoordinateDisabled will return a standard response if coordinates are
// disabled. This returns true if they are disabled and we should not continue.
func (s *HTTPHandlers) checkCoordinateDisabled(resp http.ResponseWriter, req *http.Request) bool {
// checkCoordinateDisabled will return an unauthorized error if coordinates are
// disabled. Otherwise, a nil error will be returned.
func (s *HTTPHandlers) checkCoordinateDisabled() error {
if !s.agent.config.DisableCoordinates {
return false
return nil
}
resp.WriteHeader(http.StatusUnauthorized)
fmt.Fprint(resp, "Coordinate support disabled")
return true
return UnauthorizedError{Reason: "Coordinate support disabled"}
}
// sorter wraps a coordinate list and implements the sort.Interface to sort by
@ -44,8 +41,8 @@ func (s *sorter) Less(i, j int) bool {
// CoordinateDatacenters returns the WAN nodes in each datacenter, along with
// raw network coordinates.
func (s *HTTPHandlers) CoordinateDatacenters(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
if s.checkCoordinateDisabled(resp, req) {
return nil, nil
if err := s.checkCoordinateDisabled(); err != nil {
return nil, err
}
var out []structs.DatacenterMap
@ -73,8 +70,8 @@ func (s *HTTPHandlers) CoordinateDatacenters(resp http.ResponseWriter, req *http
// CoordinateNodes returns the LAN nodes in the given datacenter, along with
// raw network coordinates.
func (s *HTTPHandlers) CoordinateNodes(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
if s.checkCoordinateDisabled(resp, req) {
return nil, nil
if err := s.checkCoordinateDisabled(); err != nil {
return nil, err
}
args := structs.DCSpecificRequest{}
@ -98,8 +95,8 @@ func (s *HTTPHandlers) CoordinateNodes(resp http.ResponseWriter, req *http.Reque
// CoordinateNode returns the LAN node in the given datacenter, along with
// raw network coordinates.
func (s *HTTPHandlers) CoordinateNode(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
if s.checkCoordinateDisabled(resp, req) {
return nil, nil
if err := s.checkCoordinateDisabled(); err != nil {
return nil, err
}
node, err := getPathSuffixUnescaped(req.URL.Path, "/v1/coordinate/node/")
@ -153,15 +150,13 @@ func filterCoordinates(req *http.Request, in structs.Coordinates) structs.Coordi
// CoordinateUpdate inserts or updates the LAN coordinate of a node.
func (s *HTTPHandlers) CoordinateUpdate(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
if s.checkCoordinateDisabled(resp, req) {
return nil, nil
if err := s.checkCoordinateDisabled(); err != nil {
return nil, err
}
args := structs.CoordinateUpdateRequest{}
if err := decodeBody(req.Body, &args); err != nil {
resp.WriteHeader(http.StatusBadRequest)
fmt.Fprintf(resp, "Request decode failed: %v", err)
return nil, nil
return nil, BadRequestError{Reason: fmt.Sprintf("Request decode failed: %v", err)}
}
s.parseDC(req, &args.Datacenter)
s.parseToken(req, &args.Token)

@ -39,16 +39,14 @@ func TestCoordinate_Disabled_Response(t *testing.T) {
req, _ := http.NewRequest("PUT", "/should/not/care", nil)
resp := httptest.NewRecorder()
obj, err := tt(resp, req)
if err != nil {
t.Fatalf("err: %v", err)
err, ok := err.(UnauthorizedError)
if !ok {
t.Fatalf("expected unauthorized error but got %v", err)
}
if obj != nil {
t.Fatalf("bad: %#v", obj)
}
if got, want := resp.Code, http.StatusUnauthorized; got != want {
t.Fatalf("got %d want %d", got, want)
}
if !strings.Contains(resp.Body.String(), "Coordinate support disabled") {
if !strings.Contains(err.Error(), "Coordinate support disabled") {
t.Fatalf("bad: %#v", resp)
}
})

@ -47,11 +47,6 @@ func (m *delegateMock) RemoveFailedNode(node string, prune bool, entMeta *struct
return m.Called(node, prune, entMeta).Error(0)
}
func (m *delegateMock) ResolveTokenToIdentity(token string) (structs.ACLIdentity, error) {
ret := m.Called(token)
return ret.Get(0).(structs.ACLIdentity), ret.Error(1)
}
func (m *delegateMock) ResolveTokenAndDefaultMeta(token string, entMeta *structs.EnterpriseMeta, authzContext *acl.AuthorizerContext) (acl.Authorizer, error) {
ret := m.Called(token, entMeta, authzContext)
return ret.Get(0).(acl.Authorizer), ret.Error(1)

@ -51,9 +51,7 @@ func (s *HTTPHandlers) DiscoveryChainRead(resp http.ResponseWriter, req *http.Re
if apiReq.OverrideMeshGateway.Mode != "" {
_, err := structs.ValidateMeshGatewayMode(string(apiReq.OverrideMeshGateway.Mode))
if err != nil {
resp.WriteHeader(http.StatusBadRequest)
fmt.Fprint(resp, "Invalid OverrideMeshGateway.Mode parameter")
return nil, nil
return nil, BadRequestError{Reason: "Invalid OverrideMeshGateway.Mode parameter"}
}
args.OverrideMeshGateway = apiReq.OverrideMeshGateway
}

@ -2,7 +2,6 @@ package agent
import (
"bytes"
"fmt"
"io"
"net/http"
"strconv"
@ -26,9 +25,7 @@ func (s *HTTPHandlers) EventFire(resp http.ResponseWriter, req *http.Request) (i
return nil, err
}
if event.Name == "" {
resp.WriteHeader(http.StatusBadRequest)
fmt.Fprint(resp, "Missing name")
return nil, nil
return nil, BadRequestError{Reason: "Missing name"}
}
// Get the ACL token
@ -58,9 +55,7 @@ func (s *HTTPHandlers) EventFire(resp http.ResponseWriter, req *http.Request) (i
// Try to fire the event
if err := s.agent.UserEvent(dc, token, event); err != nil {
if acl.IsErrPermissionDenied(err) {
resp.WriteHeader(http.StatusForbidden)
fmt.Fprint(resp, acl.ErrPermissionDenied.Error())
return nil, nil
return nil, ForbiddenError{Reason: acl.ErrPermissionDenied.Error()}
}
resp.WriteHeader(http.StatusInternalServerError)
return nil, err

@ -88,13 +88,11 @@ func TestEventFire_token(t *testing.T) {
url := fmt.Sprintf("/v1/event/fire/%s?token=%s", c.event, token)
req, _ := http.NewRequest("PUT", url, nil)
resp := httptest.NewRecorder()
if _, err := a.srv.EventFire(resp, req); err != nil {
t.Fatalf("err: %s", err)
}
_, err := a.srv.EventFire(resp, req)
// Check the result
body := resp.Body.String()
if c.allowed {
body := resp.Body.String()
if acl.IsErrPermissionDenied(errors.New(body)) {
t.Fatalf("bad: %s", body)
}
@ -102,11 +100,11 @@ func TestEventFire_token(t *testing.T) {
t.Fatalf("bad: %d", resp.Code)
}
} else {
if !acl.IsErrPermissionDenied(errors.New(body)) {
t.Fatalf("bad: %s", body)
if !acl.IsErrPermissionDenied(err) {
t.Fatalf("bad: %s", err.Error())
}
if resp.Code != 403 {
t.Fatalf("bad: %d", resp.Code)
if err, ok := err.(ForbiddenError); !ok {
t.Fatalf("Expected forbidden but got %v", err)
}
}
}

@ -1,7 +1,6 @@
package agent
import (
"fmt"
"net/http"
"net/url"
"strconv"
@ -36,9 +35,7 @@ func (s *HTTPHandlers) HealthChecksInState(resp http.ResponseWriter, req *http.R
return nil, err
}
if args.State == "" {
resp.WriteHeader(http.StatusBadRequest)
fmt.Fprint(resp, "Missing check state")
return nil, nil
return nil, BadRequestError{Reason: "Missing check state"}
}
// Make the RPC request
@ -82,9 +79,7 @@ func (s *HTTPHandlers) HealthNodeChecks(resp http.ResponseWriter, req *http.Requ
// Pull out the service name
args.Node = strings.TrimPrefix(req.URL.Path, "/v1/health/node/")
if args.Node == "" {
resp.WriteHeader(http.StatusBadRequest)
fmt.Fprint(resp, "Missing node name")
return nil, nil
return nil, BadRequestError{Reason: "Missing node name"}
}
// Make the RPC request
@ -130,9 +125,7 @@ func (s *HTTPHandlers) HealthServiceChecks(resp http.ResponseWriter, req *http.R
// Pull out the service name
args.ServiceName = strings.TrimPrefix(req.URL.Path, "/v1/health/checks/")
if args.ServiceName == "" {
resp.WriteHeader(http.StatusBadRequest)
fmt.Fprint(resp, "Missing service name")
return nil, nil
return nil, BadRequestError{Reason: "Missing service name"}
}
// Make the RPC request
@ -218,9 +211,7 @@ func (s *HTTPHandlers) healthServiceNodes(resp http.ResponseWriter, req *http.Re
// Pull out the service name
args.ServiceName = strings.TrimPrefix(req.URL.Path, prefix)
if args.ServiceName == "" {
resp.WriteHeader(http.StatusBadRequest)
fmt.Fprint(resp, "Missing service name")
return nil, nil
return nil, BadRequestError{Reason: "Missing service name"}
}
out, md, err := s.agent.rpcClientHealth.ServiceNodes(req.Context(), args)
@ -238,9 +229,7 @@ func (s *HTTPHandlers) healthServiceNodes(resp http.ResponseWriter, req *http.Re
// Filter to only passing if specified
filter, err := getBoolQueryParam(params, api.HealthPassing)
if err != nil {
resp.WriteHeader(http.StatusBadRequest)
fmt.Fprint(resp, "Invalid value for ?passing")
return nil, nil
return nil, BadRequestError{Reason: "Invalid value for ?passing"}
}
// FIXME: remove filterNonPassing, replace with nodes.Filter, which is used by DNSServer

@ -1,14 +1,13 @@
package agent
import (
"bytes"
"fmt"
"io/ioutil"
"net/http"
"net/http/httptest"
"net/url"
"reflect"
"strconv"
"strings"
"testing"
"time"
@ -1241,18 +1240,12 @@ func TestHealthServiceNodes_PassingFilter(t *testing.T) {
t.Run("passing_bad", func(t *testing.T) {
req, _ := http.NewRequest("GET", "/v1/health/service/consul?passing=nope-nope-nope", nil)
resp := httptest.NewRecorder()
a.srv.HealthServiceNodes(resp, req)
if code := resp.Code; code != 400 {
t.Errorf("bad response code %d, expected %d", code, 400)
_, err := a.srv.HealthServiceNodes(resp, req)
if _, ok := err.(BadRequestError); !ok {
t.Fatalf("Expected bad request error but got %v", err)
}
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
t.Fatal(err)
}
if !bytes.Contains(body, []byte("Invalid value for ?passing")) {
t.Errorf("bad %s", body)
if !strings.Contains(err.Error(), "Invalid value for ?passing") {
t.Errorf("bad %s", err.Error())
}
})
}
@ -1654,12 +1647,12 @@ func TestHealthConnectServiceNodes_PassingFilter(t *testing.T) {
req, _ := http.NewRequest("GET", fmt.Sprintf(
"/v1/health/connect/%s?passing=nope-nope", args.Service.Proxy.DestinationServiceName), nil)
resp := httptest.NewRecorder()
a.srv.HealthConnectServiceNodes(resp, req)
assert.Equal(t, 400, resp.Code)
_, err := a.srv.HealthConnectServiceNodes(resp, req)
assert.NotNil(t, err)
_, ok := err.(BadRequestError)
assert.True(t, ok)
body, err := ioutil.ReadAll(resp.Body)
assert.Nil(t, err)
assert.True(t, bytes.Contains(body, []byte("Invalid value for ?passing")))
assert.True(t, strings.Contains(err.Error(), "Invalid value for ?passing"))
})
}

@ -78,6 +78,14 @@ func (e UnauthorizedError) Error() string {
return e.Reason
}
type EntityTooLargeError struct {
Reason string
}
func (e EntityTooLargeError) Error() string {
return e.Reason
}
// CodeWithPayloadError allow returning non HTTP 200
// Error codes while not returning PlainText payload
type CodeWithPayloadError struct {
@ -91,10 +99,11 @@ func (e CodeWithPayloadError) Error() string {
}
type ForbiddenError struct {
Reason string
}
func (e ForbiddenError) Error() string {
return "Access is restricted"
return e.Reason
}
// HTTPHandlers provides an HTTP api for an agent.
@ -443,6 +452,11 @@ func (s *HTTPHandlers) wrap(handler endpoint, methods []string) http.HandlerFunc
return err.Error() == consul.ErrRateLimited.Error()
}
isEntityToLarge := func(err error) bool {
_, ok := err.(EntityTooLargeError)
return ok
}
addAllowHeader := func(methods []string) {
resp.Header().Add("Allow", strings.Join(methods, ","))
}
@ -488,6 +502,9 @@ func (s *HTTPHandlers) wrap(handler endpoint, methods []string) http.HandlerFunc
case isTooManyRequests(err):
resp.WriteHeader(http.StatusTooManyRequests)
fmt.Fprint(resp, err.Error())
case isEntityToLarge(err):
resp.WriteHeader(http.StatusRequestEntityTooLarge)
fmt.Fprint(resp, err.Error())
default:
resp.WriteHeader(http.StatusInternalServerError)
fmt.Fprint(resp, err.Error())
@ -1136,7 +1153,7 @@ func (s *HTTPHandlers) checkWriteAccess(req *http.Request) error {
}
}
return ForbiddenError{}
return ForbiddenError{Reason: "Access is restricted"}
}
func (s *HTTPHandlers) parseFilter(req *http.Request, filter *string) {

@ -323,9 +323,7 @@ func (s *HTTPHandlers) IntentionGetExact(resp http.ResponseWriter, req *http.Req
if err := s.agent.RPC("Intention.Get", &args, &reply); err != nil {
// We have to check the string since the RPC sheds the error type
if err.Error() == consul.ErrIntentionNotFound.Error() {
resp.WriteHeader(http.StatusNotFound)
fmt.Fprint(resp, err.Error())
return nil, nil
return nil, NotFoundError{Reason: err.Error()}
}
// Not ideal, but there are a number of error scenarios that are not
@ -521,9 +519,7 @@ func (s *HTTPHandlers) IntentionSpecificGet(id string, resp http.ResponseWriter,
if err := s.agent.RPC("Intention.Get", &args, &reply); err != nil {
// We have to check the string since the RPC sheds the error type
if err.Error() == consul.ErrIntentionNotFound.Error() {
resp.WriteHeader(http.StatusNotFound)
fmt.Fprint(resp, err.Error())
return nil, nil
return nil, NotFoundError{Reason: err.Error()}
}
// Not ideal, but there are a number of error scenarios that are not

@ -55,8 +55,8 @@ func (s *HTTPHandlers) KVSGet(resp http.ResponseWriter, req *http.Request, args
params := req.URL.Query()
if _, ok := params["recurse"]; ok {
method = "KVS.List"
} else if missingKey(resp, args) {
return nil, nil
} else if args.Key == "" {
return nil, BadRequestError{Reason: "Missing key name"}
}
// Do not allow wildcard NS on GET reqs
@ -156,8 +156,8 @@ func (s *HTTPHandlers) KVSPut(resp http.ResponseWriter, req *http.Request, args
if err := s.parseEntMetaNoWildcard(req, &args.EnterpriseMeta); err != nil {
return nil, err
}
if missingKey(resp, args) {
return nil, nil
if args.Key == "" {
return nil, BadRequestError{Reason: "Missing key name"}
}
if conflictingFlags(resp, req, "cas", "acquire", "release") {
return nil, nil
@ -208,13 +208,10 @@ func (s *HTTPHandlers) KVSPut(resp http.ResponseWriter, req *http.Request, args
// Check the content-length
if req.ContentLength > int64(s.agent.config.KVMaxValueSize) {
resp.WriteHeader(http.StatusRequestEntityTooLarge)
fmt.Fprintf(resp,
"Request body(%d bytes) too large, max size: %d bytes. See %s.",
req.ContentLength, s.agent.config.KVMaxValueSize,
"https://www.consul.io/docs/agent/options.html#kv_max_value_size",
)
return nil, nil
return nil, EntityTooLargeError{
Reason: fmt.Sprintf("Request body(%d bytes) too large, max size: %d bytes. See %s.",
req.ContentLength, s.agent.config.KVMaxValueSize, "https://www.consul.io/docs/agent/options.html#kv_max_value_size"),
}
}
// Copy the value
@ -259,8 +256,8 @@ func (s *HTTPHandlers) KVSDelete(resp http.ResponseWriter, req *http.Request, ar
params := req.URL.Query()
if _, ok := params["recurse"]; ok {
applyReq.Op = api.KVDeleteTree
} else if missingKey(resp, args) {
return nil, nil
} else if args.Key == "" {
return nil, BadRequestError{Reason: "Missing key name"}
}
// Check for cas value
@ -286,16 +283,6 @@ func (s *HTTPHandlers) KVSDelete(resp http.ResponseWriter, req *http.Request, ar
return true, nil
}
// missingKey checks if the key is missing
func missingKey(resp http.ResponseWriter, args *structs.KeyRequest) bool {
if args.Key == "" {
resp.WriteHeader(http.StatusBadRequest)
fmt.Fprint(resp, "Missing key name")
return true
}
return false
}
// conflictingFlags determines if non-composable flags were passed in a request.
func conflictingFlags(resp http.ResponseWriter, req *http.Request, flags ...string) bool {
params := req.URL.Query()

@ -14,6 +14,7 @@ import (
"github.com/hashicorp/go-hclog"
"github.com/hashicorp/consul/acl"
"github.com/hashicorp/consul/agent/consul"
"github.com/hashicorp/consul/agent/structs"
"github.com/hashicorp/consul/agent/token"
"github.com/hashicorp/consul/api"
@ -150,7 +151,7 @@ func (c *CheckState) CriticalFor() time.Duration {
type rpc interface {
RPC(method string, args interface{}, reply interface{}) error
ResolveTokenToIdentity(secretID string) (structs.ACLIdentity, error)
ResolveTokenAndDefaultMeta(token string, entMeta *structs.EnterpriseMeta, authzContext *acl.AuthorizerContext) (consul.ACLResolveResult, error)
}
// State is used to represent the node's services,
@ -1538,7 +1539,7 @@ func (l *State) notifyIfAliased(serviceID structs.ServiceID) {
// critical purposes, such as logging. Therefore we interpret all errors as empty-string
// so we can safely log it without handling non-critical errors at the usage site.
func (l *State) aclAccessorID(secretID string) string {
ident, err := l.Delegate.ResolveTokenToIdentity(secretID)
ident, err := l.Delegate.ResolveTokenAndDefaultMeta(secretID, nil, nil)
if acl.IsErrNotFound(err) {
return ""
}
@ -1546,8 +1547,5 @@ func (l *State) aclAccessorID(secretID string) string {
l.logger.Debug("non-critical error resolving acl token accessor for logging", "error", err)
return ""
}
if ident == nil {
return ""
}
return ident.ID()
return ident.AccessorID()
}

@ -12,8 +12,10 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/hashicorp/consul/acl"
"github.com/hashicorp/consul/agent"
"github.com/hashicorp/consul/agent/config"
"github.com/hashicorp/consul/agent/consul"
"github.com/hashicorp/consul/agent/local"
"github.com/hashicorp/consul/agent/structs"
"github.com/hashicorp/consul/agent/token"
@ -2372,6 +2374,6 @@ func (f *fakeRPC) RPC(method string, args interface{}, reply interface{}) error
return nil
}
func (f *fakeRPC) ResolveTokenToIdentity(_ string) (structs.ACLIdentity, error) {
return nil, nil
func (f *fakeRPC) ResolveTokenAndDefaultMeta(string, *structs.EnterpriseMeta, *acl.AuthorizerContext) (consul.ACLResolveResult, error) {
return consul.ACLResolveResult{}, nil
}

@ -23,9 +23,7 @@ func (s *HTTPHandlers) preparedQueryCreate(resp http.ResponseWriter, req *http.R
s.parseDC(req, &args.Datacenter)
s.parseToken(req, &args.Token)
if err := decodeBody(req.Body, &args.Query); err != nil {
resp.WriteHeader(http.StatusBadRequest)
fmt.Fprintf(resp, "Request decode failed: %v", err)
return nil, nil
return nil, BadRequestError{Reason: fmt.Sprintf("Request decode failed: %v", err)}
}
var reply string
@ -145,9 +143,7 @@ func (s *HTTPHandlers) preparedQueryExecute(id string, resp http.ResponseWriter,
// We have to check the string since the RPC sheds
// the specific error type.
if structs.IsErrQueryNotFound(err) {
resp.WriteHeader(http.StatusNotFound)
fmt.Fprint(resp, err.Error())
return nil, nil
return nil, NotFoundError{Reason: err.Error()}
}
return nil, err
}
@ -200,9 +196,7 @@ RETRY_ONCE:
// We have to check the string since the RPC sheds
// the specific error type.
if structs.IsErrQueryNotFound(err) {
resp.WriteHeader(http.StatusNotFound)
fmt.Fprint(resp, err.Error())
return nil, nil
return nil, NotFoundError{Reason: err.Error()}
}
return nil, err
}
@ -231,9 +225,7 @@ RETRY_ONCE:
// We have to check the string since the RPC sheds
// the specific error type.
if structs.IsErrQueryNotFound(err) {
resp.WriteHeader(http.StatusNotFound)
fmt.Fprint(resp, err.Error())
return nil, nil
return nil, NotFoundError{Reason: err.Error()}
}
return nil, err
}
@ -255,9 +247,7 @@ func (s *HTTPHandlers) preparedQueryUpdate(id string, resp http.ResponseWriter,
s.parseToken(req, &args.Token)
if req.ContentLength > 0 {
if err := decodeBody(req.Body, &args.Query); err != nil {
resp.WriteHeader(http.StatusBadRequest)
fmt.Fprintf(resp, "Request decode failed: %v", err)
return nil, nil
return nil, BadRequestError{Reason: fmt.Sprintf("Request decode failed: %v", err)}
}
}

@ -620,11 +620,9 @@ func TestPreparedQuery_Execute(t *testing.T) {
body := bytes.NewBuffer(nil)
req, _ := http.NewRequest("GET", "/v1/query/not-there/execute", body)
resp := httptest.NewRecorder()
if _, err := a.srv.PreparedQuerySpecific(resp, req); err != nil {
t.Fatalf("err: %v", err)
}
if resp.Code != 404 {
t.Fatalf("bad code: %d", resp.Code)
_, err := a.srv.PreparedQuerySpecific(resp, req)
if err, ok := err.(NotFoundError); !ok {
t.Fatalf("Expected not found error but got %v", err)
}
})
}
@ -757,11 +755,9 @@ func TestPreparedQuery_Explain(t *testing.T) {
body := bytes.NewBuffer(nil)
req, _ := http.NewRequest("GET", "/v1/query/not-there/explain", body)
resp := httptest.NewRecorder()
if _, err := a.srv.PreparedQuerySpecific(resp, req); err != nil {
t.Fatalf("err: %v", err)
}
if resp.Code != 404 {
t.Fatalf("bad code: %d", resp.Code)
_, err := a.srv.PreparedQuerySpecific(resp, req)
if err, ok := err.(NotFoundError); !ok {
t.Fatalf("Expected not found error but got %v", err)
}
})
@ -848,11 +844,9 @@ func TestPreparedQuery_Get(t *testing.T) {
body := bytes.NewBuffer(nil)
req, _ := http.NewRequest("GET", "/v1/query/f004177f-2c28-83b7-4229-eacc25fe55d1", body)
resp := httptest.NewRecorder()
if _, err := a.srv.PreparedQuerySpecific(resp, req); err != nil {
t.Fatalf("err: %v", err)
}
if resp.Code != 404 {
t.Fatalf("bad code: %d", resp.Code)
_, err := a.srv.PreparedQuerySpecific(resp, req)
if err, ok := err.(NotFoundError); !ok {
t.Fatalf("Expected not found error but got %v", err)
}
})
}

@ -40,9 +40,7 @@ func (s *HTTPHandlers) SessionCreate(resp http.ResponseWriter, req *http.Request
// Handle optional request body
if req.ContentLength > 0 {
if err := s.rewordUnknownEnterpriseFieldError(lib.DecodeJSON(req.Body, &args.Session)); err != nil {
resp.WriteHeader(http.StatusBadRequest)
fmt.Fprintf(resp, "Request decode failed: %v", err)
return nil, nil
return nil, BadRequestError{Reason: fmt.Sprintf("Request decode failed: %v", err)}
}
}
@ -77,9 +75,7 @@ func (s *HTTPHandlers) SessionDestroy(resp http.ResponseWriter, req *http.Reques
return nil, err
}
if args.Session.ID == "" {
resp.WriteHeader(http.StatusBadRequest)
fmt.Fprint(resp, "Missing session")
return nil, nil
return nil, BadRequestError{Reason: "Missing session"}
}
var out string
@ -107,18 +103,14 @@ func (s *HTTPHandlers) SessionRenew(resp http.ResponseWriter, req *http.Request)
}
args.Session = args.SessionID
if args.SessionID == "" {
resp.WriteHeader(http.StatusBadRequest)
fmt.Fprint(resp, "Missing session")
return nil, nil
return nil, BadRequestError{Reason: "Missing session"}
}
var out structs.IndexedSessions
if err := s.agent.RPC("Session.Renew", &args, &out); err != nil {
return nil, err
} else if out.Sessions == nil {
resp.WriteHeader(http.StatusNotFound)
fmt.Fprintf(resp, "Session id '%s' not found", args.SessionID)
return nil, nil
return nil, NotFoundError{Reason: fmt.Sprintf("Session id '%s' not found", args.SessionID)}
}
return out.Sessions, nil
@ -142,9 +134,7 @@ func (s *HTTPHandlers) SessionGet(resp http.ResponseWriter, req *http.Request) (
}
args.Session = args.SessionID
if args.SessionID == "" {
resp.WriteHeader(http.StatusBadRequest)
fmt.Fprint(resp, "Missing session")
return nil, nil
return nil, BadRequestError{Reason: "Missing session"}
}
var out structs.IndexedSessions
@ -200,9 +190,7 @@ func (s *HTTPHandlers) SessionsForNode(resp http.ResponseWriter, req *http.Reque
return nil, err
}
if args.Node == "" {
resp.WriteHeader(http.StatusBadRequest)
fmt.Fprint(resp, "Missing node name")
return nil, nil
return nil, BadRequestError{Reason: "Missing node name"}
}
var out structs.IndexedSessions

@ -89,8 +89,8 @@ var ACLBootstrapNotAllowedErr = errors.New("ACL bootstrap no longer allowed")
var ACLBootstrapInvalidResetIndexErr = errors.New("Invalid ACL bootstrap reset index")
type ACLIdentity interface {
// ID returns a string that can be used for logging and telemetry. This should not
// contain any secret data used for authentication
// ID returns the accessor ID, a string that can be used for logging and
// telemetry. It is not the secret ID used for authentication.
ID() string
SecretToken() string
PolicyIDs() []string

@ -63,7 +63,7 @@ func isWrite(op api.KVOp) bool {
// internal RPC format. This returns a count of the number of write ops, and
// a boolean, that if false means an error response has been generated and
// processing should stop.
func (s *HTTPHandlers) convertOps(resp http.ResponseWriter, req *http.Request) (structs.TxnOps, int, bool) {
func (s *HTTPHandlers) convertOps(resp http.ResponseWriter, req *http.Request) (structs.TxnOps, int, error) {
// The TxnMaxReqLen limit and KVMaxValueSize limit both default to the
// suggested raft data size and can be configured independently. The
// TxnMaxReqLen is enforced on the cumulative size of the transaction,
@ -87,13 +87,10 @@ func (s *HTTPHandlers) convertOps(resp http.ResponseWriter, req *http.Request) (
// Check Content-Length first before decoding to return early
if req.ContentLength > maxTxnLen {
resp.WriteHeader(http.StatusRequestEntityTooLarge)
fmt.Fprintf(resp,
"Request body(%d bytes) too large, max size: %d bytes. See %s.",
req.ContentLength, maxTxnLen,
"https://www.consul.io/docs/agent/options.html#txn_max_req_len",
)
return nil, 0, false
return nil, 0, EntityTooLargeError{
Reason: fmt.Sprintf("Request body(%d bytes) too large, max size: %d bytes. See %s.",
req.ContentLength, maxTxnLen, "https://www.consul.io/docs/agent/options.html#txn_max_req_len"),
}
}
var ops api.TxnOps
@ -102,30 +99,24 @@ func (s *HTTPHandlers) convertOps(resp http.ResponseWriter, req *http.Request) (
if err.Error() == "http: request body too large" {
// The request size is also verified during decoding to double check
// if the Content-Length header was not set by the client.
resp.WriteHeader(http.StatusRequestEntityTooLarge)
fmt.Fprintf(resp,
"Request body too large, max size: %d bytes. See %s.",
maxTxnLen,
"https://www.consul.io/docs/agent/options.html#txn_max_req_len",
)
return nil, 0, EntityTooLargeError{
Reason: fmt.Sprintf("Request body too large, max size: %d bytes. See %s.",
maxTxnLen, "https://www.consul.io/docs/agent/options.html#txn_max_req_len"),
}
} else {
// Note the body is in API format, and not the RPC format. If we can't
// decode it, we will return a 400 since we don't have enough context to
// associate the error with a given operation.
resp.WriteHeader(http.StatusBadRequest)
fmt.Fprintf(resp, "Failed to parse body: %v", err)
return nil, 0, BadRequestError{Reason: fmt.Sprintf("Failed to parse body: %v", err)}
}
return nil, 0, false
}
// Enforce a reasonable upper limit on the number of operations in a
// transaction in order to curb abuse.
if size := len(ops); size > maxTxnOps {
resp.WriteHeader(http.StatusRequestEntityTooLarge)
fmt.Fprintf(resp, "Transaction contains too many operations (%d > %d)",
size, maxTxnOps)
return nil, 0, false
return nil, 0, EntityTooLargeError{
Reason: fmt.Sprintf("Transaction contains too many operations (%d > %d)", size, maxTxnOps),
}
}
// Convert the KV API format into the RPC format. Note that fixupKVOps
@ -138,9 +129,9 @@ func (s *HTTPHandlers) convertOps(resp http.ResponseWriter, req *http.Request) (
case in.KV != nil:
size := len(in.KV.Value)
if int64(size) > kvMaxValueSize {
resp.WriteHeader(http.StatusRequestEntityTooLarge)
fmt.Fprintf(resp, "Value for key %q is too large (%d > %d bytes)", in.KV.Key, size, s.agent.config.KVMaxValueSize)
return nil, 0, false
return nil, 0, EntityTooLargeError{
Reason: fmt.Sprintf("Value for key %q is too large (%d > %d bytes)", in.KV.Key, size, s.agent.config.KVMaxValueSize),
}
}
verb := in.KV.Verb
@ -297,7 +288,7 @@ func (s *HTTPHandlers) convertOps(resp http.ResponseWriter, req *http.Request) (
}
}
return opsRPC, writes, true
return opsRPC, writes, nil
}
// Txn handles requests to apply multiple operations in a single, atomic
@ -306,9 +297,9 @@ func (s *HTTPHandlers) convertOps(resp http.ResponseWriter, req *http.Request) (
// and everything else will be routed through Raft like a normal write.
func (s *HTTPHandlers) Txn(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
// Convert the ops from the API format to the internal format.
ops, writes, ok := s.convertOps(resp, req)
if !ok {
return nil, nil
ops, writes, err := s.convertOps(resp, req)
if err != nil {
return nil, err
}
// Fast-path a transaction with only writes to the read-only endpoint,

@ -30,13 +30,12 @@ func TestTxnEndpoint_Bad_JSON(t *testing.T) {
buf := bytes.NewBuffer([]byte("{"))
req, _ := http.NewRequest("PUT", "/v1/txn", buf)
resp := httptest.NewRecorder()
if _, err := a.srv.Txn(resp, req); err != nil {
t.Fatalf("err: %v", err)
}
if resp.Code != 400 {
t.Fatalf("expected 400, got %d", resp.Code)
_, err := a.srv.Txn(resp, req)
err, ok := err.(BadRequestError)
if !ok {
t.Fatalf("expected bad request error but got %v", err)
}
if !bytes.Contains(resp.Body.Bytes(), []byte("Failed to parse")) {
if !strings.Contains(err.Error(), "Failed to parse") {
t.Fatalf("expected conflicting args error")
}
}
@ -63,14 +62,12 @@ func TestTxnEndpoint_Bad_Size_Item(t *testing.T) {
`, value)))
req, _ := http.NewRequest("PUT", "/v1/txn", buf)
resp := httptest.NewRecorder()
if _, err := agent.srv.Txn(resp, req); err != nil {
t.Fatalf("err: %v", err)
}
if resp.Code != 413 && !wantPass {
t.Fatalf("expected 413, got %d", resp.Code)
_, err := agent.srv.Txn(resp, req)
if err, ok := err.(EntityTooLargeError); !ok && !wantPass {
t.Fatalf("expected too large error but got %v", err)
}
if resp.Code != 200 && wantPass {
t.Fatalf("expected 200, got %d", resp.Code)
if err != nil && wantPass {
t.Fatalf("err: %v", err)
}
}
@ -140,14 +137,12 @@ func TestTxnEndpoint_Bad_Size_Net(t *testing.T) {
`, value, value, value)))
req, _ := http.NewRequest("PUT", "/v1/txn", buf)
resp := httptest.NewRecorder()
if _, err := agent.srv.Txn(resp, req); err != nil {
t.Fatalf("err: %v", err)
}
if resp.Code != 413 && !wantPass {
t.Fatalf("expected 413, got %d", resp.Code)
_, err := agent.srv.Txn(resp, req)
if err, ok := err.(EntityTooLargeError); !ok && !wantPass {
t.Fatalf("expected too large error but got %v", err)
}
if resp.Code != 200 && wantPass {
t.Fatalf("expected 200, got %d", resp.Code)
if err != nil && wantPass {
t.Fatalf("err: %v", err)
}
}
@ -209,11 +204,9 @@ func TestTxnEndpoint_Bad_Size_Ops(t *testing.T) {
`, strings.Repeat(`{ "KV": { "Verb": "get", "Key": "key" } },`, 2*maxTxnOps))))
req, _ := http.NewRequest("PUT", "/v1/txn", buf)
resp := httptest.NewRecorder()
if _, err := a.srv.Txn(resp, req); err != nil {
t.Fatalf("err: %v", err)
}
if resp.Code != 413 {
t.Fatalf("expected 413, got %d", resp.Code)
_, err := a.srv.Txn(resp, req)
if err, ok := err.(EntityTooLargeError); !ok {
t.Fatalf("expected too large error but got %v", err)
}
}

@ -139,9 +139,7 @@ func (s *HTTPHandlers) UINodeInfo(resp http.ResponseWriter, req *http.Request) (
return nil, err
}
if args.Node == "" {
resp.WriteHeader(http.StatusBadRequest)
fmt.Fprint(resp, "Missing node name")
return nil, nil
return nil, BadRequestError{Reason: "Missing node name"}
}
// Make the RPC request
@ -255,9 +253,7 @@ func (s *HTTPHandlers) UIGatewayServicesNodes(resp http.ResponseWriter, req *htt
return nil, err
}
if args.ServiceName == "" {
resp.WriteHeader(http.StatusBadRequest)
fmt.Fprint(resp, "Missing gateway name")
return nil, nil
return nil, BadRequestError{Reason: "Missing gateway name"}
}
// Make the RPC request
@ -301,16 +297,12 @@ func (s *HTTPHandlers) UIServiceTopology(resp http.ResponseWriter, req *http.Req
return nil, err
}
if args.ServiceName == "" {
resp.WriteHeader(http.StatusBadRequest)
fmt.Fprint(resp, "Missing service name")
return nil, nil
return nil, BadRequestError{Reason: "Missing service name"}
}
kind, ok := req.URL.Query()["kind"]
if !ok {
resp.WriteHeader(http.StatusBadRequest)
fmt.Fprint(resp, "Missing service kind")
return nil, nil
return nil, BadRequestError{Reason: "Missing service kind"}
}
args.ServiceKind = structs.ServiceKind(kind[0])
@ -318,9 +310,7 @@ func (s *HTTPHandlers) UIServiceTopology(resp http.ResponseWriter, req *http.Req
case structs.ServiceKindTypical, structs.ServiceKindIngressGateway:
// allowed
default:
resp.WriteHeader(http.StatusBadRequest)
fmt.Fprintf(resp, "Unsupported service kind %q", args.ServiceKind)
return nil, nil
return nil, BadRequestError{Reason: fmt.Sprintf("Unsupported service kind %q", args.ServiceKind)}
}
// Make the RPC request
@ -584,9 +574,7 @@ func (s *HTTPHandlers) UIGatewayIntentions(resp http.ResponseWriter, req *http.R
return nil, err
}
if name == "" {
resp.WriteHeader(http.StatusBadRequest)
fmt.Fprint(resp, "Missing gateway name")
return nil, nil
return nil, BadRequestError{Reason: "Missing gateway name"}
}
args.Match = &structs.IntentionQueryMatch{
Type: structs.IntentionMatchDestination,

@ -1409,14 +1409,15 @@ func TestUIServiceTopology(t *testing.T) {
retry.Run(t, func(r *retry.R) {
resp := httptest.NewRecorder()
obj, err := a.srv.UIServiceTopology(resp, tc.httpReq)
assert.Nil(r, err)
if tc.wantErr != "" {
assert.NotNil(r, err)
assert.Nil(r, tc.want) // should not define a non-nil want
require.Equal(r, tc.wantErr, resp.Body.String())
require.Contains(r, err.Error(), tc.wantErr)
require.Nil(r, obj)
return
}
assert.Nil(r, err)
require.NoError(r, checkIndex(resp))
require.NotNil(r, obj)

@ -326,8 +326,7 @@ function build_consul {
-e CGO_ENABLED=0 \
-e GOLDFLAGS="${GOLDFLAGS}" \
-e GOTAGS="${GOTAGS}" \
${image_name} \
./build-support/scripts/build-local.sh -o "${XC_OS}" -a "${XC_ARCH}")
${image_name} make linux
ret=$?
if test $ret -eq 0
@ -354,110 +353,3 @@ function build_consul {
popd > /dev/null
return $ret
}
function build_consul_local {
# Arguments:
# $1 - Path to the top level Consul source
# $2 - Space separated string of OSes to build. If empty will use env vars for determination.
# $3 - Space separated string of architectures to build. If empty will use env vars for determination.
# $4 - Subdirectory to put binaries in under pkg/bin (optional)
#
# Returns:
# 0 - success
# * - error
#
# Note:
# The GOLDFLAGS and GOTAGS environment variables will be used if set
# If the CONSUL_DEV environment var is truthy only the local platform/architecture is built.
# If the XC_OS or the XC_ARCH environment vars are present then only those platforms/architectures
# will be built. Otherwise all supported platform/architectures are built
# The GOXPARALLEL environment variable is used if set
if ! test -d "$1"
then
err "ERROR: '$1' is not a directory. build_consul must be called with the path to the top level source as the first argument'"
return 1
fi
local sdir="$1"
local build_os="$2"
local build_arch="$3"
local extra_dir_name="$4"
local extra_dir=""
if test -n "${extra_dir_name}"
then
extra_dir="${extra_dir_name}/"
fi
pushd ${sdir} > /dev/null
if is_set "${CONSUL_DEV}"
then
if test -z "${XC_OS}"
then
XC_OS=$(go env GOOS)
fi
if test -z "${XC_ARCH}"
then
XC_ARCH=$(go env GOARCH)
fi
fi
XC_OS=${XC_OS:-"solaris darwin freebsd linux windows"}
XC_ARCH=${XC_ARCH:-"386 amd64 arm arm64"}
if test -z "${build_os}"
then
build_os="${XC_OS}"
fi
if test -z "${build_arch}"
then
build_arch="${XC_ARCH}"
fi
status_stage "==> Building Consul - OSes: ${build_os}, Architectures: ${build_arch}"
mkdir pkg.bin.new 2> /dev/null
status "Building sequentially with go install"
for os in ${build_os}
do
for arch in ${build_arch}
do
outdir="pkg.bin.new/${extra_dir}${os}_${arch}"
osarch="${os}/${arch}"
if ! supported_osarch "${osarch}"
then
continue
fi
echo "---> ${osarch}"
mkdir -p "${outdir}"
GOBIN_EXTRA=""
if test "${os}" != "$(go env GOHOSTOS)" -o "${arch}" != "$(go env GOHOSTARCH)"
then
GOBIN_EXTRA="${os}_${arch}/"
fi
binname="consul"
if [ $os == "windows" ];then
binname="consul.exe"
fi
debug_run env CGO_ENABLED=0 GOOS=${os} GOARCH=${arch} go install -ldflags "${GOLDFLAGS}" -tags "${GOTAGS}" && cp "${MAIN_GOPATH}/bin/${GOBIN_EXTRA}${binname}" "${outdir}/${binname}"
if test $? -ne 0
then
err "ERROR: Failed to build Consul for ${osarch}"
rm -r pkg.bin.new
return 1
fi
done
done
build_consul_post "${sdir}" "${extra_dir_name}"
if test $? -ne 0
then
err "ERROR: Failed postprocessing Consul binaries"
return 1
fi
return 0
}

@ -1,107 +0,0 @@
#!/bin/bash
SCRIPT_NAME="$(basename ${BASH_SOURCE[0]})"
pushd $(dirname ${BASH_SOURCE[0]}) > /dev/null
SCRIPT_DIR=$(pwd)
pushd ../.. > /dev/null
SOURCE_DIR=$(pwd)
popd > /dev/null
pushd ../functions > /dev/null
FN_DIR=$(pwd)
popd > /dev/null
popd > /dev/null
source "${SCRIPT_DIR}/functions.sh"
function usage {
cat <<-EOF
Usage: ${SCRIPT_NAME} [<options ...>]
Description:
This script will build the Consul binary on the local system.
All the requisite tooling must be installed for this to be
successful.
Options:
-s | --source DIR Path to source to build.
Defaults to "${SOURCE_DIR}"
-o | --os OSES Space separated string of OS
platforms to build.
-a | --arch ARCH Space separated string of
architectures to build.
-h | --help Print this help text.
EOF
}
function err_usage {
err "$1"
err ""
err "$(usage)"
}
function main {
declare sdir="${SOURCE_DIR}"
declare build_os=""
declare build_arch=""
while test $# -gt 0
do
case "$1" in
-h | --help )
usage
return 0
;;
-s | --source )
if test -z "$2"
then
err_usage "ERROR: option -s/--source requires an argument"
return 1
fi
if ! test -d "$2"
then
err_usage "ERROR: '$2' is not a directory and not suitable for the value of -s/--source"
return 1
fi
sdir="$2"
shift 2
;;
-o | --os )
if test -z "$2"
then
err_usage "ERROR: option -o/--os requires an argument"
return 1
fi
build_os="$2"
shift 2
;;
-a | --arch )
if test -z "$2"
then
err_usage "ERROR: option -a/--arch requires an argument"
return 1
fi
build_arch="$2"
shift 2
;;
* )
err_usage "ERROR: Unknown argument: '$1'"
return 1
;;
esac
done
build_consul_local "${sdir}" "${build_os}" "${build_arch}" || return 1
return 0
}
main "$@"
exit $?

@ -0,0 +1,42 @@
# Adding a Changelog Entry
Any change that a Consul user might need to know about should have a changelog entry.
What doesn't need a changelog entry?
- Docs changes
- Typos fixes, unless they are in a public-facing API
- Code changes we are certain no Consul users will need to know about
To include a [changelog entry](../.changelog) in a PR, commit a text file
named `.changelog/<PR#>.txt`, where `<PR#>` is the number associated with the open
PR in Github. The text file should describe the changes in the following format:
````
```release-note:<change type>
<code area>: <brief description of the improvement you made here>
```
````
Valid values for `<change type>` include:
- `feature`: for the addition of a new feature
- `improvement`: for an improvement (not a bug fix) to an existing feature
- `bug`: for a bug fix
- `security`: for any Common Vulnerabilities and Exposures (CVE) resolutions
- `breaking-change`: for any change that is not fully backwards-compatible
- `deprecation`: for functionality which is now marked for removal in a future release
`<code area>` is meant to categorize the functionality affected by the change.
Some common values are:
- `checks`: related to node or service health checks
- `cli`: related to the command-line interface and its commands
- `config`: related to configuration changes (e.g., adding a new config option)
- `connect`: catch-all for the Connect subsystem that provides service mesh functionality
if no more specific `<code area>` applies
- `http`: related to the HTTP API interface and its endpoints
- `dns`: related to DNS functionality
- `ui`: any change related to the built-in Consul UI (`website/` folder)
Look in the [`.changelog/`](../.changelog) folder for examples of existing changelog entries.
If a PR deserves multiple changelog entries, just add multiple entries separated by a newline
in the format described above to the `.changelog/<PR#>.txt` file.

@ -0,0 +1,19 @@
# Forking the Consul Repo
Community members wishing to contribute code to Consul must fork the Consul project
(`your-github-username/consul`). Branches pushed to that fork can then be submitted
as pull requests to the upstream project (`hashicorp/consul`).
To locally clone the repo so that you can pull the latest from the upstream project
(`hashicorp/consul`) and push changes to your own fork (`your-github-username/consul`):
1. [Create the forked repository](https://docs.github.com/en/get-started/quickstart/fork-a-repo#forking-a-repository) (`your-github-username/consul`)
2. Clone the `hashicorp/consul` repository and `cd` into the folder
3. Make `hashicorp/consul` the `upstream` remote rather than `origin`:
`git remote rename origin upstream`.
4. Add your fork as the `origin` remote. For example:
`git remote add origin https://github.com/myusername/consul`
5. Checkout a feature branch: `git checkout -t -b new-feature`
6. [Make changes](../../.github/CONTRIBUTING.md#modifying-the-code)
7. Push changes to the fork when ready to [submit a PR](../../.github/CONTRIBUTING.md#submitting-a-pull-request):
`git push -u origin new-feature`

@ -5,8 +5,8 @@ all: build
deps: ../../node_modules clean
# incase we ever need to clean anything again
clean:
rm -rf ./tmp
build-staging: deps
yarn run build:staging
@ -17,6 +17,9 @@ build-ci: deps
build: deps
yarn run build
build-debug:
BROCCOLI_DEBUG=* $(MAKE) build
start: deps
yarn run start

@ -100,7 +100,6 @@
%sort-button::before {
@extend %with-sort-mask, %as-pseudo;
position: relative;
top: 4px;
width: 16px;
height: 16px;
}

@ -1,3 +1,31 @@
.consul-external-source {
@extend %pill-200, %frame-gray-600, %p1;
}
.consul-external-source.kubernetes::before {
@extend %with-logo-kubernetes-color-icon, %as-pseudo;
}
.consul-external-source.terraform::before {
@extend %with-logo-terraform-color-icon, %as-pseudo;
}
.consul-external-source.nomad::before {
@extend %with-logo-nomad-color-icon, %as-pseudo;
}
.consul-external-source.consul::before,
.consul-external-source.consul-api-gateway::before {
@extend %with-logo-consul-color-icon, %as-pseudo;
}
.consul-external-source.vault::before {
@extend %with-vault-100;
}
.consul-external-source.aws::before {
@extend %with-aws-100;
}
.consul-external-source.leader::before {
@extend %with-star-outline-mask, %as-pseudo;
}
.consul-external-source.jwt::before {
@extend %with-logo-jwt-color-icon, %as-pseudo;
}
.consul-external-source.oidc::before {
@extend %with-logo-oidc-color-icon, %as-pseudo;
}

@ -1,9 +1,7 @@
%pill-allow::before,
%pill-deny::before,
%pill-l7::before {
@extend %as-pseudo;
margin-right: 5px;
font-size: 0.9em;
}
%pill-allow,
%pill-deny,
@ -24,11 +22,11 @@
@extend %frame-gray-900;
}
%pill-allow::before {
@extend %with-arrow-right-mask;
@extend %with-allow-300;
}
%pill-deny::before {
@extend %with-deny-color-mask;
@extend %with-deny-300;
}
%pill-l7::before {
@extend %with-layers-mask;
@extend %with-l7-300;
}

@ -1,12 +1,11 @@
.consul-intention-fieldsets {
.value-allow > :last-child::before {
@extend %with-arrow-right-mask, %as-pseudo;
color: rgb(var(--tone-green-500));
@extend %with-allow-500;
}
.value-deny > :last-child::before {
@extend %with-deny-color-icon, %as-pseudo;
@extend %with-deny-500;
}
.value- > :last-child::before {
@extend %with-layers-mask, %as-pseudo;
@extend %with-l7-500;
}
}

@ -20,31 +20,3 @@ span.policy-node-identity::before {
span.policy-service-identity::before {
content: 'Service Identity: ';
}
%pill.kubernetes::before {
@extend %with-logo-kubernetes-color-icon, %as-pseudo;
}
%pill.terraform::before {
@extend %with-logo-terraform-color-icon, %as-pseudo;
}
%pill.nomad::before {
@extend %with-logo-nomad-color-icon, %as-pseudo;
}
%pill.consul::before,
%pill.consul-api-gateway::before {
@extend %with-logo-consul-color-icon, %as-pseudo;
}
%pill.vault::before {
@extend %with-vault-300;
}
%pill.aws::before {
@extend %with-logo-aws-color-icon, %as-pseudo;
}
%pill.leader::before {
@extend %with-star-outline-mask, %as-pseudo;
}
%pill.jwt::before {
@extend %with-logo-jwt-color-icon, %as-pseudo;
}
%pill.oidc::before {
@extend %with-logo-oidc-color-icon, %as-pseudo;
}

@ -6,7 +6,8 @@
}
%pill::before {
margin-right: 4px;
font-size: 0.8em;
width: 0.75rem !important; /* 12px */
height: 0.75rem !important; /* 12px */
}
%pill-200 {
@extend %pill;

@ -50,7 +50,7 @@
color: rgb(var(--tone-gray-500));
}
%popover-select .aws button::before {
@extend %with-logo-aws-color-icon, %as-pseudo;
@extend %with-aws-300;
}
%popover-select .kubernetes button::before {
@extend %with-logo-kubernetes-color-icon, %as-pseudo;

@ -0,0 +1,18 @@
import Helper from '@ember/component/helper';
import { assert } from '@ember/debug';
import { adoptStyles } from '@lit/reactive-element';
export default class AdoptStylesHelper extends Helper {
/**
* Adopt/apply given styles to a `ShadowRoot` using constructable styleSheets if supported
*
* @param {[ShadowRoot, CSSResultGroup]} params
*/
compute([$shadow, styles], hash) {
assert(
'adopt-styles can only be used to apply styles to ShadowDOM elements',
$shadow instanceof ShadowRoot
);
adoptStyles($shadow, [styles]);
}
}

@ -0,0 +1,28 @@
# adopt-styles
Adopt/apply given styles to a `ShadowRoot` using constructable styleSheets if supported
```hbs preview-template
<div
{{attach-shadow (set this 'shadow')}}
>
{{#if this.shadow}}
{{#in-element this.shadow}}
{{adopt-styles this.shadow (css '
:host {
background-color: red;
width: 100px;
height: 100px;
}
')}}
{{/in-element}}
{{/if}}
</div>
```
## Positional Arguments
| Argument | Type | Default | Description |
| --- | --- | --- | --- |
| `params` | `[ShadowRoot, CSSResultGroup]` | | |

@ -0,0 +1,8 @@
import Helper from '@ember/component/helper';
import { css } from '@lit/reactive-element';
export default class ConsoleLogHelper extends Helper {
compute([str], hash) {
return css([str]);
}
}

@ -0,0 +1,22 @@
import { helper } from '@ember/component/helper';
/**
* Conditionally maps styles to a string ready for typical DOM
* usage (i.e. semi-colon delimited)
*
* @typedef {([string, (string | undefined), string] | [string, (string | undefined)])} styleInfo
* @param {styleInfo[]} entries - An array of `styleInfo`s to map
* @param {boolean} transform=true - whether to perform the build-time 'helper
* to modifier' transpilation. Note a transpiler needs installing separately.
*/
const styleMap = (entries, transform = true) => {
const str = entries.reduce((prev, [prop, value, unit = '']) => {
if (value == null) {
return prev;
}
return `${prev}${prop}:${value.toString()}${unit};`;
}, '');
return str.length > 0 ? str : undefined;
};
export default helper(styleMap);

@ -0,0 +1,58 @@
# style-map
`{{style-map}}` is used to easily add a list of styles, conditionally, and
have them all formatted nicely to be printed in a DOM `style` attribute.
As well as an entry-like array you can also pass an additional `unit` property
as the 3rd item in the array. This is to make it easier to do mathamatical
calculations for units without having to use `(concat)`.
If any property has a value of `null` or `undefined`, that style property will
not be included in the resulting string.
```hbs preview-template
<figure>
<figcaption>
This div has the correct style added/omitted.
</figcaption>
<div
style={{style-map
(array 'outline' '1px solid red')
(array 'width' '600px')
(array 'height' 100 'px')
(array 'padding' 1 'rem')
(array 'background' null)
}}
>
<code>
style={{style-map
(array 'outline' '1px solid red')
(array 'width' '600px')
(array 'height' 100 'px')
(array 'padding' 1 'rem')
(array 'background' null)
}}
</code>
</div>
</figure>
```
## Positional Arguments
| Argument | Type | Default | Description |
| --- | --- | --- | --- |
| `entries` | `styleInfo[]` | | An array of `styleInfo`s to map |
## Named Arguments
| Argument | Type | Default | Description |
| --- | --- | --- | --- |
| `transform` | `boolean` | true | whether to perform the build-time 'helper to modifier' transpilation |
## Types
| Type | Default | Description |
| --- | --- | --- |
| `styleInfo` | `([string, (string \| undefined), string] \| [string, (string \| undefined)])` | |

@ -0,0 +1,23 @@
import { setModifierManager, capabilities } from '@ember/modifier';
export default setModifierManager(
() => ({
capabilities: capabilities('3.13', { disableAutoTracking: true }),
createModifier() {},
installModifier(_state, element, { positional: [fn, ...args], named }) {
let shadow;
try {
shadow = element.attachShadow({ mode: 'open' });
} catch (e) {
// shadow = false;
console.error(e);
}
fn(shadow);
},
updateModifier() {},
destroyModifier() {},
}),
class CustomElementModifier {}
);

@ -0,0 +1,28 @@
# attach-shadow
`{{attach-shadow (set this 'shadow')}}` attaches a `ShadowRoot` to the modified DOM element
and pass a reference to that `ShadowRoot` to the setter function.
Please note: This should be used as a utility modifier for when having access
to the shadow DOM is handy, not really for building full blown shadow DOM
based Web Components.
```hbs preview-template
<div
{{attach-shadow (set this 'shadow')}}
>
{{#if this.shadow}}
{{#in-element this.shadow}}
<slot name="name"></slot>
{{/in-element}}
{{/if}}
<p slot="name">Hello from the shadows!</p>
</div>
```
## Positional Arguments
| Argument | Type | Default | Description |
| --- | --- | --- | --- |
| `setter` | `function` | | Usually `set` or `mut` or similar |

@ -0,0 +1,45 @@
import Modifier from 'ember-modifier';
import { action } from '@ember/object';
import { inject as service } from '@ember/service';
export default class OnOutsideModifier extends Modifier {
@service('dom') dom;
constructor() {
super(...arguments);
this.doc = this.dom.document();
}
async connect(params, options) {
await new Promise(resolve => setTimeout(resolve, 0));
try {
this.doc.addEventListener(params[0], this.listen);
} catch (e) {
// continue
}
}
@action
listen(e) {
if (this.dom.isOutside(this.element, e.target)) {
const dispatch = typeof this.params[1] === 'function' ? this.params[1] : _ => {};
dispatch.apply(this.element, [e]);
}
}
disconnect() {
this.doc.removeEventListener('click', this.listen);
}
didReceiveArguments() {
this.params = this.args.positional;
this.options = this.args.named;
}
didInstall() {
this.connect(this.args.positional, this.args.named);
}
willRemove() {
this.disconnect();
}
}

@ -0,0 +1,22 @@
# on-outside
`{{on-outside 'click' (fn @callback}}` works similarly to `{{on }}` but allows
you to attach handlers that is specifically not the currently modified
element.
```hbs preview-template
<button
{{on-outside 'click' (set this 'clicked' 'outside clicked')}}
{{on 'click' (set this 'clicked' 'inside clicked')}}
style="background: red;width: 100px;height: 100px;"
>
{{or this.clicked "click me or outside of me"}}
</button>
```
## Positional Arguments
| Argument | Type | Default | Description |
| --- | --- | --- | --- |
| `event` | `string` | | Name of the event to listen for |
| `handler` | `function` | | Function to handle the event |

@ -15,3 +15,6 @@
@import 'icons';
/* global control of themeable components */
@import 'themes';
/* debug only, this is empty during a production build */
/* but uses the contents of ./debug.scss during dev */
@import '_debug';

@ -1,3 +1,11 @@
%theme-light {
--theme-dark-none: ;
--theme-light-none: initial;
}
%theme-dark {
--theme-dark-none: initial;
--theme-light-none: ;
}
%with-icon {
background-repeat: no-repeat;
background-position: center;
@ -20,8 +28,8 @@
content: '';
visibility: visible;
background-size: contain;
width: 1.2em;
height: 1.2em;
width: 1rem; /* 16px */
height: 1rem; /* 16px */
vertical-align: text-top;
}
%led-icon {

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

@ -1,4 +1,3 @@
@import './app';
@import './base/icons/debug';
@import 'prism-coldark-dark';

@ -1,4 +1,71 @@
%with-vault-100 {
@extend %with-vault-16-mask, %as-pseudo;
color: rgb(var(--tone-vault-500));
}
%with-vault-300 {
@extend %with-vault-16-mask, %as-pseudo;
color: rgb(var(--tone-vault-500));
}
%with-aws-100,
%with-aws-300 {
@extend %aws-color-16-svg-prop;
@extend %with-icon, %as-pseudo;
background-image: var(--theme-dark-none) var(--aws-color-16-svg);
-webkit-mask-image: var(--theme-light-none) var(--aws-color-16-svg);
-webkit-mask-repeat: var(--theme-light-none) no-repeat;
-webkit-mask-position: var(--theme-light-none) center;
mask-image: var(--theme-light-none) var(--aws-color-16-svg);
mask-repeat: var(--theme-light-none) no-repeat;
mask-position: var(--theme-light-none) center;
background-color: var(--theme-light-none) rgb(var(--white));
}
%with-allow-100,
%with-aws-100,
%with-deny-100,
%with-l7-100,
%with-vault-100 {
width: 0.75rem; /* 12px */
height: 0.75rem; /* 12px */
}
%with-allow-500,
%with-deny-500,
%with-l7-500 {
width: 1.25rem; /* 20px */
height: 1.25rem; /* 20px */
}
%with-allow-300,
%with-allow-500 {
color: rgb(var(--tone-green-500));
}
%with-deny-300,
%with-deny-500 {
color: rgb(var(--tone-red-500));
}
%with-allow-300,
%with-allow-500,
%with-deny-300,
%with-deny-500,
%with-l7-300,
%with-l7-500 {
@extend %as-pseudo;
}
%with-allow-300 {
@extend %with-arrow-right-16-mask;
}
%with-allow-500 {
@extend %with-arrow-right-24-mask;
}
%with-deny-300 {
@extend %with-skip-16-mask;
}
%with-deny-500 {
@extend %with-skip-24-mask;
}
%with-l7-300 {
@extend %with-layers-16-mask;
}
%with-l7-500 {
@extend %with-layers-24-mask;
}

@ -97,14 +97,8 @@ module.exports = function(defaults, $ = process.env) {
// exclude docfy
'@docfy/ember'
];
} else {
// add debug css is we are not in test or production environments
outputPaths.app = {
css: {
'debug': '/assets/debug.css'
}
}
}
if(['production'].includes(env)) {
// everything apart from production is 'debug', including test
// which means this and everything it affects is never tested

@ -3,6 +3,8 @@
const Funnel = require('broccoli-funnel');
const mergeTrees = require('broccoli-merge-trees');
const writeFile = require('broccoli-file-creator');
const read = require('fs').readFileSync;
module.exports = {
name: require('./package').name,
@ -12,15 +14,18 @@ module.exports = {
* @import 'app-name/components/component-name/index.scss'
*/
treeForStyles: function(tree) {
let debug = read(`${this.project.root}/app/styles/debug.scss`);
if (['production', 'test'].includes(process.env.EMBER_ENV)) {
debug = '';
}
return this._super.treeForStyles.apply(this, [
mergeTrees(
(tree || []).concat([
new Funnel(`${this.project.root}/app/components`, {
destDir: `app/components`,
include: ['**/*.scss'],
}),
])
),
mergeTrees([
writeFile(`_debug.scss`, debug),
new Funnel(`${this.project.root}/app/components`, {
destDir: `app/components`,
include: ['**/*.scss'],
}),
]),
]);
},
};

@ -8,9 +8,7 @@ module.exports = ({ appName, environment, rootURL, config }) => `
<link rel="icon" href="${rootURL}assets/favicon.svg" type="image/svg+xml">
<link rel="apple-touch-icon" href="${rootURL}assets/apple-touch-icon.png">
<link integrity="" rel="stylesheet" href="${rootURL}assets/vendor.css">
<link integrity="" rel="stylesheet" href="${rootURL}assets/${
!['test', 'production'].includes(environment) ? 'debug' : appName
}.css">
<link integrity="" rel="stylesheet" href="${rootURL}assets/${appName}.css">
${
environment === 'test' ? `<link rel="stylesheet" href="${rootURL}assets/test-support.css">` : ``
}

@ -63,6 +63,7 @@
"@glimmer/component": "^1.0.0",
"@glimmer/tracking": "^1.0.0",
"@hashicorp/ember-cli-api-double": "^3.1.0",
"@lit/reactive-element": "^1.2.1",
"@mapbox/rehype-prism": "^0.6.0",
"@xstate/fsm": "^1.4.0",
"a11y-dialog": "^6.0.1",
@ -79,8 +80,8 @@
"chalk": "^4.1.0",
"clipboard": "^2.0.4",
"consul-acls": "*",
"consul-partitions": "*",
"consul-nspaces": "*",
"consul-partitions": "*",
"css.escape": "^1.5.1",
"d3-array": "^2.8.0",
"d3-scale": "^3.2.3",

@ -1560,6 +1560,11 @@
resolved "https://registry.yarnpkg.com/@istanbuljs/schema/-/schema-0.1.3.tgz#e45e384e4b8ec16bce2fd903af78450f6bf7ec98"
integrity sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==
"@lit/reactive-element@^1.2.1":
version "1.2.1"
resolved "https://registry.yarnpkg.com/@lit/reactive-element/-/reactive-element-1.2.1.tgz#8620d7f0baf63e12821fa93c34d21e23477736f7"
integrity sha512-03FYfMguIWo9E1y1qcTpXzoO8Ukpn0j5o4GjNFq/iHqJEPY6pYopsU44e7NSFIgCTorr8wdUU5PfVy8VeD6Rwg==
"@mapbox/rehype-prism@^0.6.0":
version "0.6.0"
resolved "https://registry.yarnpkg.com/@mapbox/rehype-prism/-/rehype-prism-0.6.0.tgz#3d8a860870951d4354257d0ba908d11545bd5ed5"

@ -21,8 +21,8 @@ export default function Footer({ openConsentManager }) {
<Link href="/security">
<a>Security</a>
</Link>
<Link href="/files/press-kit.zip">
<a>Press Kit</a>
<Link href="https://www.hashicorp.com/brand">
<a>Brand</a>
</Link>
<a onClick={openConsentManager}>Consent Manager</a>
</div>

@ -36,6 +36,8 @@ Usage: `consul connect redirect-traffic [options]`
#### Options for Traffic Redirection Rules
- `-consul-dns-ip` - The IP address of the Consul DNS resolver. If provided, DNS queries will be redirected to the provided IP address for name resolution.
- `-proxy-id` - The [proxy service](/docs/connect/registration/service-registration) ID.
This service ID must already be registered with the local agent.
@ -47,13 +49,13 @@ Usage: `consul connect redirect-traffic [options]`
- `-exclude-inbound-port` - Inbound port to exclude from traffic redirection. May be provided multiple times.
- `exclude-outbound-cidr` - Outbound CIDR to exclude from traffic redirection. May be provided multiple times.
- `-exclude-outbound-cidr` - Outbound CIDR to exclude from traffic redirection. May be provided multiple times.
- `exclude-outbound-port` - Outbound port to exclude from traffic redirection. May be provided multiple times.
- `-exclude-outbound-port` - Outbound port to exclude from traffic redirection. May be provided multiple times.
- `exclude-uid` - Additional user ID to exclude from traffic redirection. May be provided multiple times.
- `-exclude-uid` - Additional user ID to exclude from traffic redirection. May be provided multiple times.
- `netns` - The Linux network namespace where traffic redirection rules should apply.
- `-netns` - The Linux network namespace where traffic redirection rules should apply.
This must be a path to the network namespace, e.g. /var/run/netns/foo.
#### Enterprise Options

@ -388,7 +388,7 @@ These metrics are used to monitor the health of the Consul servers.
| Metric | Description | Unit | Type |
| --------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------- | ------- |
| `consul.acl.ResolveToken` | Measures the time it takes to resolve an ACL token. | ms | timer |
| `consul.acl.ResolveTokenToIdentity` | Measures the time it takes to resolve an ACL token to an Identity. | ms | timer |
| `consul.acl.ResolveTokenToIdentity` | Measures the time it takes to resolve an ACL token to an Identity. This metric was removed in Consul 1.12. The time will now be reflected in `consul.acl.ResolveToken`. | ms | timer |
| `consul.acl.token.cache_hit` | Increments if Consul is able to resolve a token's identity, or a legacy token, from the cache. | cache read op | counter |
| `consul.acl.token.cache_miss` | Increments if Consul cannot resolve a token's identity, or a legacy token, from the cache. | cache read op | counter |
| `consul.cache.bypass` | Counts how many times a request bypassed the cache because no cache-key was provided. | counter | counter |

@ -21,10 +21,10 @@ Consul API Gateway implements the Kubernetes [Gateway API Specification](https:/
Your datacenter must meet the following requirements prior to configuring the Consul API Gateway:
- A Kubernetes cluster must be running
- Kubernetes 1.21+
- `kubectl` 1.21+
- Consul 1.11.2+
- HashiCorp Helm chart 0.40.0+
- HashiCorp Consul Helm chart 0.40.0+
## Installation
@ -38,7 +38,7 @@ Your datacenter must meet the following requirements prior to configuring the Co
</CodeBlockConfig>
1. Create a values file for your Consul server agents that contains the following parameters:
1. Create a `values.yaml` file for your Consul API Gateway deployment. Copy the content below into the `values.yaml` file. The `values.yaml` will be used by the Consul Helm chart. See [Helm Chart Configuration - apigateway](https://www.consul.io/docs/k8s/helm#apigateway) for more available options on how to configure your Consul API Gateway deployment via the Helm chart.
<CodeBlockConfig hideClipboard filename="values.yaml">

@ -600,7 +600,12 @@ spec:
protocol: http
services:
- name: api
# HTTP Header manipulation is not supported in Kubernetes CRD
requestHeaders:
add:
x-gateway: us-east-ingress
responseHeaders:
remove:
- x-debug
```
```json
@ -676,7 +681,12 @@ spec:
services:
- name: api
namespace: frontend
# HTTP Header manipulation is not supported in Kubernetes CRD
requestHeaders:
add:
x-gateway: us-east-ingress
responseHeaders:
remove:
- x-debug
```
```json
@ -981,21 +991,25 @@ You can specify the following parameters to configure ingress gateway configurat
},
{
name: 'TLSMinVersion',
yaml: false,
type: 'string: ""',
description: "Set the default minimum TLS version supported for the gateway's listeners. One of `TLS_AUTO`, `TLSv1_0`, `TLSv1_1`, `TLSv1_2`, or `TLSv1_3`. If unspecified, Envoy v1.22.0 and newer [will default to TLS 1.2 as a min version](https://github.com/envoyproxy/envoy/pull/19330), while older releases of Envoy default to TLS 1.0.",
description:
"Set the default minimum TLS version supported for the gateway's listeners. One of `TLS_AUTO`, `TLSv1_0`, `TLSv1_1`, `TLSv1_2`, or `TLSv1_3`. If unspecified, Envoy v1.22.0 and newer [will default to TLS 1.2 as a min version](https://github.com/envoyproxy/envoy/pull/19330), while older releases of Envoy default to TLS 1.0.",
},
{
name: 'TLSMaxVersion',
yaml: false,
type: 'string: ""',
description: {
hcl:
"Set the default maximum TLS version supported for the gateway's listeners. Must be greater than or equal to `TLSMinVersion`. One of `TLS_AUTO`, `TLSv1_0`, `TLSv1_1`, `TLSv1_2`, or `TLSv1_3`." ,
"Set the default maximum TLS version supported for the gateway's listeners. Must be greater than or equal to `TLSMinVersion`. One of `TLS_AUTO`, `TLSv1_0`, `TLSv1_1`, `TLSv1_2`, or `TLSv1_3`.",
yaml:
"Set the default maximum TLS version supported for the gateway's listeners. Must be greater than or equal to `tls_min_version`. One of `TLS_AUTO`, `TLSv1_0`, `TLSv1_1`, `TLSv1_2`, or `TLSv1_3`." ,
"Set the default maximum TLS version supported for the gateway's listeners. Must be greater than or equal to `tls_min_version`. One of `TLS_AUTO`, `TLSv1_0`, `TLSv1_1`, `TLSv1_2`, or `TLSv1_3`.",
},
},
{
name: 'CipherSuites',
yaml: false,
type: 'array<string>: <optional>',
description: `Set the default list of TLS cipher suites for the gateway's
listeners to support when negotiating connections using
@ -1007,11 +1021,10 @@ You can specify the following parameters to configure ingress gateway configurat
releases of Envoy may remove currently-supported but
insecure cipher suites, and future releases of Consul
may add new supported cipher suites if any are added to
Envoy.`
Envoy.`,
},
{
name: 'SDS',
yaml: false,
type: 'SDSConfig: <optional>',
description:
'Defines a set of parameters that configures the gateway to load TLS certificates from an external SDS service. See [SDS](/docs/connect/gateways/ingress-gateway#sds) for more details on usage.<br><br>SDS properties defined in this field are used as defaults for all listeners on the gateway.',
@ -1105,7 +1118,6 @@ You can specify the following parameters to configure ingress gateway configurat
\`*-suffix.example.com\` are not.`,
},
{
yaml: false,
name: 'RequestHeaders',
type: 'HTTPHeaderModifiers: <optional>',
description: `A set of [HTTP-specific header modification rules](/docs/connect/config-entries/service-router#httpheadermodifiers)
@ -1113,7 +1125,6 @@ You can specify the following parameters to configure ingress gateway configurat
This cannot be used with a \`tcp\` listener.`,
},
{
yaml: false,
name: 'ResponseHeaders',
type: 'HTTPHeaderModifiers: <optional>',
description: `A set of [HTTP-specific header modification rules](/docs/connect/config-entries/service-router#httpheadermodifiers)
@ -1122,7 +1133,6 @@ You can specify the following parameters to configure ingress gateway configurat
},
{
name: 'TLS',
yaml: false,
type: 'ServiceTLSConfig: <optional>',
description: 'TLS configuration for this service.',
children: [
@ -1154,7 +1164,6 @@ You can specify the following parameters to configure ingress gateway configurat
},
{
name: 'TLS',
yaml: false,
type: 'TLSConfig: <optional>',
description: 'TLS configuration for this listener.',
children: [
@ -1165,26 +1174,26 @@ You can specify the following parameters to configure ingress gateway configurat
hcl:
"Set this configuration to `true` to enable built-in TLS for this listener.<br><br>If TLS is enabled, then each host defined in each service's `Hosts` field will be added as a DNSSAN to the gateway's x509 certificate. Note that even hosts from other listeners with TLS disabled will be added. TLS can not be disabled for individual listeners if it is enabled on the gateway.",
yaml:
"Set this configuration to `true` to enable built-in TLS for this listener.<br><br>If TLS is enabled, then each host defined in the `hosts` field will be added as a DNSSAN to the gateway's x509 certificate. Note that even hosts from other listeners with TLS disabled will be added. TLS can not be disabled for individual listeners if it is enabled on the gateway.",
"Set this configuration to `true` to enable built-in TLS for this listener.<br><br>If TLS is enabled, then each host defined in each service's `hosts` field will be added as a DNSSAN to the gateway's x509 certificate. Note that even hosts from other listeners with TLS disabled will be added. TLS can not be disabled for individual listeners if it is enabled on the gateway.",
},
},
{
name: 'TLSMinVersion',
yaml: false,
type: 'string: ""',
description: "Set the minimum TLS version supported for this listener. One of `TLS_AUTO`, `TLSv1_0`, `TLSv1_1`, `TLSv1_2`, or `TLSv1_3`. If unspecified, Envoy v1.22.0 and newer [will default to TLS 1.2 as a min version](https://github.com/envoyproxy/envoy/pull/19330), while older releases of Envoy default to TLS 1.0.",
description:
'Set the minimum TLS version supported for this listener. One of `TLS_AUTO`, `TLSv1_0`, `TLSv1_1`, `TLSv1_2`, or `TLSv1_3`. If unspecified, Envoy v1.22.0 and newer [will default to TLS 1.2 as a min version](https://github.com/envoyproxy/envoy/pull/19330), while older releases of Envoy default to TLS 1.0.',
},
{
name: 'TLSMaxVersion',
yaml: false,
type: 'string: ""',
description: {
hcl:
"Set the maximum TLS version supported for this listener. Must be greater than or equal to `TLSMinVersion`. One of `TLS_AUTO`, `TLSv1_0`, `TLSv1_1`, `TLSv1_2`, or `TLSv1_3`." ,
yaml:
"Set the maximum TLS version supported for this listener. Must be greater than or equal to `tls_min_version`. One of `TLS_AUTO`, `TLSv1_0`, `TLSv1_1`, `TLSv1_2`, or `TLSv1_3`." ,
},
description:
'Set the maximum TLS version supported for this listener. Must be greater than or equal to `TLSMinVersion`. One of `TLS_AUTO`, `TLSv1_0`, `TLSv1_1`, `TLSv1_2`, or `TLSv1_3`.',
},
{
name: 'CipherSuites',
yaml: false,
type: 'array<string>: <optional>',
description: `Set the list of TLS cipher suites to support when negotiating
connections using TLS 1.2 or earlier. If unspecified,
@ -1195,7 +1204,7 @@ You can specify the following parameters to configure ingress gateway configurat
and is dependent on underlying support in Envoy. Future
releases of Envoy may remove currently-supported but
insecure cipher suites, and future releases of Consul
may add new supported cipher suites if any are added to Envoy.`
may add new supported cipher suites if any are added to Envoy.`,
},
{
name: 'SDS',

@ -36,10 +36,9 @@ service of the same name.
to any configured
[`service-resolver`](/docs/connect/config-entries/service-resolver).
## UI
## UI
Once a `service-router` is successfully entered, you can view it in the UI. Service routers, service splitters, and service resolvers can all be viewed by clicking on your service then switching to the *routing* tab.
Once a `service-router` is successfully entered, you can view it in the UI. Service routers, service splitters, and service resolvers can all be viewed by clicking on your service then switching to the _routing_ tab.
![screenshot of service router in the UI](/img/l7-routing/Router.png)
@ -309,14 +308,16 @@ spec:
name: 'Namespace',
type: `string: "default"`,
enterprise: true,
description: 'Specifies the namespace to which the configuration entry will apply.',
description:
'Specifies the namespace to which the configuration entry will apply.',
yaml: false,
},
{
name: 'Partition',
type: `string: "default"`,
enterprise: true,
description: 'Specifies the admin partition to which the configuration will apply.',
description:
'Specifies the admin partition to which the configuration will apply.',
yaml: false,
},
{
@ -596,7 +597,6 @@ spec:
'A list of HTTP response status codes that are eligible for retry.',
},
{
yaml: false,
name: 'RequestHeaders',
type: 'HTTPHeaderModifiers: <optional>',
description: `A set of [HTTP-specific header modification rules](/docs/connect/config-entries/service-router#httpheadermodifiers)
@ -604,7 +604,6 @@ spec:
This cannot be used with a \`tcp\` listener.`,
},
{
yaml: false,
name: 'ResponseHeaders',
type: 'HTTPHeaderModifiers: <optional>',
description: `A set of [HTTP-specific header modification rules](/docs/connect/config-entries/service-router#httpheadermodifiers)
@ -614,21 +613,12 @@ spec:
]}
/>
### `HTTPHeaderModifiers`
<ConfigEntryReference
topLevel={false}
yaml={false}
keys={[
{
hcl: false,
name: 'Unsupported',
type: '',
description: `HTTP Header modification is not yet supported in our Kubernetes CRDs.`,
},
{
yaml: false,
name: 'Add',
type: 'map<string|string>: optional',
description: `The set of key/value pairs that specify header values to add.
@ -641,7 +631,6 @@ spec:
metadata into the value added.`,
},
{
yaml: false,
name: 'Set',
type: 'map<string|string>: optional',
description: `The set of key/value pairs that specify header values to add.
@ -654,7 +643,6 @@ spec:
metadata into the value added.`,
},
{
yaml: false,
name: 'Remove',
type: 'array<string>: optional',
description: `The set of header names to remove. Only headers

@ -39,9 +39,9 @@ resolution stage.
to any configured
[`service-resolver`](/docs/connect/config-entries/service-resolver).
## UI
## UI
Once a `service-splitter` is successfully entered, you can view it in the UI. Service routers, service splitters, and service resolvers can all be viewed by clicking on your service then switching to the *routing* tab.
Once a `service-splitter` is successfully entered, you can view it in the UI. Service routers, service splitters, and service resolvers can all be viewed by clicking on your service then switching to the _routing_ tab.
![screenshot of service splitter in the UI](/img/l7-routing/Splitter.png)
@ -152,13 +152,12 @@ spec:
</CodeTabs>
### Set HTTP Headers
Split traffic between two subsets with extra headers added so clients can tell
which version (not yet supported in Kubernetes CRD):
which version:
<CodeTabs tabs={[ "HCL", "JSON" ]}>
<CodeTabs tabs={[ "HCL", "Kubernetes YAML", "JSON" ]}>
```hcl
Kind = "service-splitter"
@ -185,6 +184,25 @@ Splits = [
]
```
```yaml
apiVersion: consul.hashicorp.com/v1alpha1
kind: ServiceSplitter
metadata:
name: web
spec:
splits:
- weight: 90
serviceSubset: v1
responseHeaders:
set:
x-web-version: v1
- weight: 10
serviceSubset: v2
responseHeaders:
set:
x-web-version: v2
```
```json
{
"Kind": "service-splitter",
@ -240,14 +258,16 @@ Splits = [
name: 'Namespace',
type: `string: "default"`,
enterprise: true,
description: 'Specifies the namespace to which the configuration entry will apply.',
description:
'Specifies the namespace to which the configuration entry will apply.',
yaml: false,
},
{
name: 'Partition',
type: `string: "default"`,
enterprise: true,
description: 'Specifies the admin partition to which the configuration entry will apply.',
description:
'Specifies the admin partition to which the configuration entry will apply.',
yaml: false,
},
{
@ -314,7 +334,6 @@ Splits = [
'The admin partition to resolve the service from instead of the current partition. If empty, the current partition is used.',
},
{
yaml: false,
name: 'RequestHeaders',
type: 'HTTPHeaderModifiers: <optional>',
description: `A set of [HTTP-specific header modification rules](/docs/connect/config-entries/service-router#httpheadermodifiers)
@ -322,7 +341,6 @@ Splits = [
This cannot be used with a \`tcp\` listener.`,
},
{
yaml: false,
name: 'ResponseHeaders',
type: 'HTTPHeaderModifiers: <optional>',
description: `A set of [HTTP-specific header modification rules](/docs/connect/config-entries/service-router#httpheadermodifiers)

@ -13,7 +13,6 @@ The [Envoy proxy](/docs/connect/proxies/envoy) should be used for production dep
Consul comes with a built-in L4 proxy for testing and development with Consul
Connect service mesh.
## Getting Started
To get started with the built-in proxy and see a working example you can follow the [Getting Started](https://learn.hashicorp.com/tutorials/consul/get-started-service-networking) tutorial.
@ -57,10 +56,9 @@ All fields are optional with a reasonable default.
_public_ mTLS listener to. It defaults to the same address the agent binds to.
- `bind_port` - The port the proxy will bind its _public_
mTLS listener to. If not provided, the agent will attempt to assign one from its
[configured proxy port range](/docs/agent/options#sidecar_min_port) if available.
By default the range is [20000, 20255] and the port is selected at random from
that range.
mTLS listener to. If not provided, the agent will assign a random port from its
configured proxy port range specified by [`sidecar_min_port`](/docs/agent/options#sidecar_min_port)
and [`sidecar_max_port`](/docs/agent/options#sidecar_max_port).
- `local_service_address`- The `[address]:port`
that the proxy should use to connect to the local application instance. By default

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

Loading…
Cancel
Save