Skip to content

Commit

Permalink
Chroot Listener (#22304)
Browse files Browse the repository at this point in the history
* Initial oss-patch apply

* Added changelog

* Renamed changelog txt

* Added the imports to the handler file

* Added a check that no two ports are the same, and modified changelog

* Edited go sum entry

* Tidy up using go mod

* Use strutil instead

* Revert go sum and go mod

* Revert sdk go sum

* Edited go.sum to before

* Edited go.sum again to initial

* Revert changes
  • Loading branch information
divyaac authored Aug 14, 2023
1 parent 951f1fe commit d5b29f6
Show file tree
Hide file tree
Showing 12 changed files with 146 additions and 20 deletions.
3 changes: 3 additions & 0 deletions changelog/22304.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:improvement
core: add a listener configuration "chroot_namespace" that forces requests to use a namespace hierarchy
```
5 changes: 4 additions & 1 deletion command/server/config_test_helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -789,7 +789,8 @@ func testConfig_Sanitized(t *testing.T) {
"listeners": []interface{}{
map[string]interface{}{
"config": map[string]interface{}{
"address": "127.0.0.1:443",
"address": "127.0.0.1:443",
"chroot_namespace": "admin/",
},
"type": "tcp",
},
Expand Down Expand Up @@ -882,6 +883,7 @@ listener "tcp" {
proxy_api {
enable_quit = true
}
chroot_namespace = "admin"
}`))

config := Config{
Expand Down Expand Up @@ -926,6 +928,7 @@ listener "tcp" {
EnableQuit: true,
},
CustomResponseHeaders: DefaultCustomHeaders,
ChrootNamespace: "admin/",
},
},
},
Expand Down
1 change: 1 addition & 0 deletions command/server/test-fixtures/config3.hcl
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ cluster_addr = "top_level_cluster_addr"

listener "tcp" {
address = "127.0.0.1:443"
chroot_namespace="admin/"
}

backend "consul" {
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -1815,6 +1815,7 @@ github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 h1:kes8mmyCpxJsI7FTwtzRqEy9
github.com/hashicorp/go-secure-stdlib/strutil v0.1.2/go.mod h1:Gou2R9+il93BqX25LAKCLuM+y9U2T4hlwvT1yprcna4=
github.com/hashicorp/go-secure-stdlib/tlsutil v0.1.2 h1:phcbL8urUzF/kxA/Oj6awENaRwfWsjP59GW7u2qlDyY=
github.com/hashicorp/go-secure-stdlib/tlsutil v0.1.2/go.mod h1:l8slYwnJA26yBz+ErHpp2IRCLr0vuOMGBORIz4rRiAs=
github.com/hashicorp/go-set v0.1.13/go.mod h1:0/D+R4MFUzJ6XmvjU7liXtznF1eQDxh84GJlhXw+lvo=
github.com/hashicorp/go-slug v0.11.1 h1:c6lLdQnlhUWbS5I7hw8SvfymoFuy6EmiFDedy6ir994=
github.com/hashicorp/go-slug v0.11.1/go.mod h1:Ib+IWBYfEfJGI1ZyXMGNbu2BU+aa3Dzu41RKLH301v4=
github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
Expand Down Expand Up @@ -2620,6 +2621,7 @@ github.com/sethvargo/go-limiter v0.7.1/go.mod h1:C0kbSFbiriE5k2FFOe18M1YZbAR2Fiw
github.com/shirou/gopsutil/v3 v3.22.6 h1:FnHOFOh+cYAM0C30P+zysPISzlknLC5Z1G4EAElznfQ=
github.com/shirou/gopsutil/v3 v3.22.6/go.mod h1:EdIubSnZhbAvBS1yJ7Xi+AShB/hxwLHOMz4MCYz7yMs=
github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU=
github.com/shoenig/test v0.6.4/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k=
github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4=
github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8=
Expand Down
9 changes: 6 additions & 3 deletions http/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -358,6 +358,7 @@ func wrapGenericHandler(core *vault.Core, h http.Handler, props *vault.HandlerPr
} else {
ctx, cancelFunc = context.WithTimeout(ctx, maxRequestDuration)
}

// if maxRequestSize < 0, no need to set context value
// Add a size limiter if desired
if maxRequestSize > 0 {
Expand All @@ -379,11 +380,14 @@ func wrapGenericHandler(core *vault.Core, h http.Handler, props *vault.HandlerPr
nw.Header().Set("X-Vault-Hostname", hostname)
}

// Extract the namespace from the header before we modify it
ns := r.Header.Get(consts.NamespaceHeaderName)
switch {
case strings.HasPrefix(r.URL.Path, "/v1/"):
newR, status := adjustRequest(core, r)
// Setting the namespace in the header to be included in the error message
newR, status, err := adjustRequest(core, props.ListenerConfig, r)
if status != 0 {
respondError(nw, status, nil)
respondError(nw, status, err)
cancelFunc()
return
}
Expand Down Expand Up @@ -434,7 +438,6 @@ func wrapGenericHandler(core *vault.Core, h http.Handler, props *vault.HandlerPr
}()

// Setting the namespace in the header to be included in the error message
ns := r.Header.Get(consts.NamespaceHeaderName)
if ns != "" {
nw.Header().Set(consts.NamespaceHeaderName, ns)
}
Expand Down
16 changes: 16 additions & 0 deletions http/handler_stubs_oss.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
//go:build !enterprise

package http

import (
"net/http"

"github.com/hashicorp/vault/internalshared/configutil"
"github.com/hashicorp/vault/vault"
)

//go:generate go run github.com/hashicorp/vault/tools/stubmaker

func adjustRequest(c *vault.Core, listener *configutil.Listener, r *http.Request) (*http.Request, int, error) {
return r, 0, nil
}
4 changes: 0 additions & 4 deletions http/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,6 @@ import (
)

var (
adjustRequest = func(c *vault.Core, r *http.Request) (*http.Request, int) {
return r, 0
}

genericWrapping = func(core *vault.Core, in http.Handler, props *vault.HandlerProperties) http.Handler {
// Wrap the help wrapped handler with another layer with a generic
// handler
Expand Down
20 changes: 19 additions & 1 deletion internalshared/configutil/listener.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import (
"github.com/hashicorp/go-sockaddr/template"
"github.com/hashicorp/hcl"
"github.com/hashicorp/hcl/hcl/ast"
"github.com/hashicorp/vault/helper/namespace"
)

type ListenerTelemetry struct {
Expand Down Expand Up @@ -118,6 +119,10 @@ type Listener struct {
// Custom Http response headers
CustomResponseHeaders map[string]map[string]string `hcl:"-"`
CustomResponseHeadersRaw interface{} `hcl:"custom_response_headers"`

// ChrootNamespace will prepend the specified namespace to requests
ChrootNamespaceRaw interface{} `hcl:"chroot_namespace"`
ChrootNamespace string `hcl:"-"`
}

// AgentAPI allows users to select which parts of the Agent API they want enabled.
Expand Down Expand Up @@ -201,7 +206,6 @@ func ParseListeners(result *SharedConfig, list *ast.ObjectList) error {
return multierror.Prefix(fmt.Errorf("unsupported listener role %q", l.Role), fmt.Sprintf("listeners.%d:", i))
}
}

// Request Parameters
{
if l.MaxRequestSizeRaw != nil {
Expand Down Expand Up @@ -423,6 +427,20 @@ func ParseListeners(result *SharedConfig, list *ast.ObjectList) error {
}

result.Listeners = append(result.Listeners, &l)

// Chroot Namespace
{
// If a valid ChrootNamespace value exists, then canonicalize the namespace value
if l.ChrootNamespaceRaw != nil {
if l.ChrootNamespace, err = parseutil.ParseString(l.ChrootNamespaceRaw); err != nil {
return multierror.Prefix(fmt.Errorf("invalid value for chroot_namespace: %w", err), fmt.Sprintf("listeners.%d", i))
} else {
l.ChrootNamespace = namespace.Canonicalize(l.ChrootNamespace)
}

l.ChrootNamespaceRaw = nil
}
}
}

return nil
Expand Down
1 change: 1 addition & 0 deletions sdk/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ require (
github.com/hashicorp/go-secure-stdlib/password v0.1.1
github.com/hashicorp/go-secure-stdlib/strutil v0.1.2
github.com/hashicorp/go-secure-stdlib/tlsutil v0.1.2
github.com/hashicorp/go-set v0.1.13
github.com/hashicorp/go-sockaddr v1.0.2
github.com/hashicorp/go-uuid v1.0.3
github.com/hashicorp/go-version v1.6.0
Expand Down
3 changes: 3 additions & 0 deletions sdk/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,8 @@ github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 h1:kes8mmyCpxJsI7FTwtzRqEy9
github.com/hashicorp/go-secure-stdlib/strutil v0.1.2/go.mod h1:Gou2R9+il93BqX25LAKCLuM+y9U2T4hlwvT1yprcna4=
github.com/hashicorp/go-secure-stdlib/tlsutil v0.1.2 h1:phcbL8urUzF/kxA/Oj6awENaRwfWsjP59GW7u2qlDyY=
github.com/hashicorp/go-secure-stdlib/tlsutil v0.1.2/go.mod h1:l8slYwnJA26yBz+ErHpp2IRCLr0vuOMGBORIz4rRiAs=
github.com/hashicorp/go-set v0.1.13 h1:k1B5goY3c7OKEzpK+gwAhJexxzAJwDN8kId8YvWrihA=
github.com/hashicorp/go-set v0.1.13/go.mod h1:0/D+R4MFUzJ6XmvjU7liXtznF1eQDxh84GJlhXw+lvo=
github.com/hashicorp/go-sockaddr v1.0.2 h1:ztczhD1jLxIRjVejw8gFomI1BQZOe2WoVOu0SyteCQc=
github.com/hashicorp/go-sockaddr v1.0.2/go.mod h1:rB4wwRAUzs07qva3c5SdrY/NEtAUjGlgmH/UkBUC97A=
github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
Expand Down Expand Up @@ -231,6 +233,7 @@ github.com/rogpeppe/go-internal v1.8.1/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4
github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
github.com/ryanuber/go-glob v1.0.0 h1:iQh3xXAumdQ+4Ufa5b25cRpC5TYKlno6hsv6Cb3pkBk=
github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc=
github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0=
Expand Down
94 changes: 84 additions & 10 deletions sdk/helper/testcluster/docker/environment.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import (
"github.com/hashicorp/vault/api"
dockhelper "github.com/hashicorp/vault/sdk/helper/docker"
"github.com/hashicorp/vault/sdk/helper/logging"
"github.com/hashicorp/vault/sdk/helper/strutil"
"github.com/hashicorp/vault/sdk/helper/testcluster"
uberAtomic "go.uber.org/atomic"
"golang.org/x/net/http2"
Expand Down Expand Up @@ -479,6 +480,7 @@ type DockerClusterNode struct {
ImageTag string
DataVolumeName string
cleanupVolume func()
AllClients []*api.Client
}

func (n *DockerClusterNode) TLSConfig() *tls.Config {
Expand Down Expand Up @@ -506,6 +508,30 @@ func (n *DockerClusterNode) APIClient() *api.Client {
return client
}

func (n *DockerClusterNode) APIClientN(listenerNumber int) (*api.Client, error) {
// We clone to ensure that whenever this method is called, the caller gets
// back a pristine client, without e.g. any namespace or token changes that
// might pollute a shared client. We clone the config instead of the
// client because (1) Client.clone propagates the replicationStateStore and
// the httpClient pointers, (2) it doesn't copy the tlsConfig at all, and
// (3) if clone returns an error, it doesn't feel as appropriate to panic
// below. Who knows why clone might return an error?
if listenerNumber >= len(n.AllClients) {
return nil, fmt.Errorf("invalid listener number %d", listenerNumber)
}
cfg := n.AllClients[listenerNumber].CloneConfig()
client, err := api.NewClient(cfg)
if err != nil {
// It seems fine to panic here, since this should be the same input
// we provided to NewClient when we were setup, and we didn't panic then.
// Better not to completely ignore the error though, suppose there's a
// bug in CloneConfig?
panic(fmt.Sprintf("NewClient error on cloned config: %v", err))
}
client.SetToken(n.Cluster.rootToken)
return client, nil
}

// NewAPIClient creates and configures a Vault API client to communicate with
// the running Vault Cluster for this DockerClusterNode
func (n *DockerClusterNode) apiConfig() (*api.Config, error) {
Expand Down Expand Up @@ -544,6 +570,20 @@ func (n *DockerClusterNode) newAPIClient() (*api.Client, error) {
return client, nil
}

func (n *DockerClusterNode) newAPIClientForAddress(address string) (*api.Client, error) {
config, err := n.apiConfig()
if err != nil {
return nil, err
}
config.Address = fmt.Sprintf("https://%s", address)
client, err := api.NewClient(config)
if err != nil {
return nil, err
}
client.SetToken(n.Cluster.GetRootToken())
return client, nil
}

// Cleanup kills the container of the node and deletes its data volume
func (n *DockerClusterNode) Cleanup() {
n.cleanup()
Expand All @@ -563,6 +603,17 @@ func (n *DockerClusterNode) cleanup() error {
return nil
}

func (n *DockerClusterNode) createDefaultListenerConfig() map[string]interface{} {
return map[string]interface{}{"tcp": map[string]interface{}{
"address": fmt.Sprintf("%s:%d", "0.0.0.0", 8200),
"tls_cert_file": "/vault/config/cert.pem",
"tls_key_file": "/vault/config/key.pem",
"telemetry": map[string]interface{}{
"unauthenticated_metrics_access": true,
},
}}
}

func (n *DockerClusterNode) Start(ctx context.Context, opts *DockerClusterOptions) error {
if n.DataVolumeName == "" {
vol, err := n.DockerAPI.VolumeCreate(ctx, volume.CreateOptions{})
Expand All @@ -575,16 +626,25 @@ func (n *DockerClusterNode) Start(ctx context.Context, opts *DockerClusterOption
}
}
vaultCfg := map[string]interface{}{}
vaultCfg["listener"] = map[string]interface{}{
"tcp": map[string]interface{}{
"address": fmt.Sprintf("%s:%d", "0.0.0.0", 8200),
"tls_cert_file": "/vault/config/cert.pem",
"tls_key_file": "/vault/config/key.pem",
"telemetry": map[string]interface{}{
"unauthenticated_metrics_access": true,
},
},
var listenerConfig []map[string]interface{}
listenerConfig = append(listenerConfig, n.createDefaultListenerConfig())
ports := []string{"8200/tcp", "8201/tcp"}

if opts.VaultNodeConfig != nil && opts.VaultNodeConfig.AdditionalListeners != nil {
for _, config := range opts.VaultNodeConfig.AdditionalListeners {
cfg := n.createDefaultListenerConfig()
listener := cfg["tcp"].(map[string]interface{})
listener["address"] = fmt.Sprintf("%s:%d", "0.0.0.0", config.Port)
listener["chroot_namespace"] = config.ChrootNamespace
listenerConfig = append(listenerConfig, cfg)
portStr := fmt.Sprintf("%d/tcp", config.Port)
if strutil.StrListContains(ports, portStr) {
return fmt.Errorf("duplicate port %d specified", config.Port)
}
ports = append(ports, portStr)
}
}
vaultCfg["listener"] = listenerConfig
vaultCfg["telemetry"] = map[string]interface{}{
"disable_hostname": true,
}
Expand Down Expand Up @@ -675,6 +735,7 @@ func (n *DockerClusterNode) Start(ctx context.Context, opts *DockerClusterOption
}
testcluster.JSONLogNoTimestamp(n.Logger, s)
}}

r, err := dockhelper.NewServiceRunner(dockhelper.RunOptions{
ImageRepo: n.ImageRepo,
ImageTag: n.ImageTag,
Expand All @@ -689,7 +750,7 @@ func (n *DockerClusterNode) Start(ctx context.Context, opts *DockerClusterOption
"VAULT_LOG_FORMAT=json",
"VAULT_LICENSE=" + opts.VaultLicense,
},
Ports: []string{"8200/tcp", "8201/tcp"},
Ports: ports,
ContainerName: n.Name(),
NetworkName: opts.NetworkName,
CopyFromTo: copyFromTo,
Expand Down Expand Up @@ -772,6 +833,19 @@ func (n *DockerClusterNode) Start(ctx context.Context, opts *DockerClusterOption
}
client.SetToken(n.Cluster.rootToken)
n.client = client

n.AllClients = append(n.AllClients, client)

for _, addr := range svc.StartResult.Addrs[2:] {
// The second element of this list of addresses is the cluster address
// We do not want to create a client for the cluster address mapping
client, err := n.newAPIClientForAddress(addr)
if err != nil {
return err
}
client.SetToken(n.Cluster.rootToken)
n.AllClients = append(n.AllClients, client)
}
return nil
}

Expand Down
8 changes: 7 additions & 1 deletion sdk/helper/testcluster/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,8 @@ type VaultNodeConfig struct {
// ServiceRegistrationType string
// ServiceRegistrationOptions map[string]string

StorageOptions map[string]string
StorageOptions map[string]string
AdditionalListeners []VaultNodeListenerConfig

DefaultMaxRequestDuration time.Duration `json:"default_max_request_duration"`
LogFormat string `json:"log_format"`
Expand Down Expand Up @@ -102,6 +103,11 @@ type ClusterOptions struct {
AdministrativeNamespacePath string
}

type VaultNodeListenerConfig struct {
Port int
ChrootNamespace string
}

type CA struct {
CACert *x509.Certificate
CACertBytes []byte
Expand Down

0 comments on commit d5b29f6

Please sign in to comment.