Disable the sys/raw endpoint by default (#3329)

* disable raw endpoint by default

* adding docs

* config option raw -> raw_storage_endpoint

* docs updates

* adding listing on raw endpoint

* reworking tests for enabled raw endpoints

* root protecting base raw endpoint
This commit is contained in:
Chris Hoffman
2017-09-15 00:21:35 -04:00
committed by GitHub
parent 2c6e64226c
commit 4a8c33cca3
14 changed files with 182 additions and 52 deletions

View File

@@ -260,6 +260,7 @@ func (c *ServerCommand) Run(args []string) int {
ClusterName: config.ClusterName, ClusterName: config.ClusterName,
CacheSize: config.CacheSize, CacheSize: config.CacheSize,
PluginDirectory: config.PluginDirectory, PluginDirectory: config.PluginDirectory,
EnableRaw: config.EnableRawEndpoint,
} }
if dev { if dev {
coreConfig.DevToken = devRootTokenID coreConfig.DevToken = devRootTokenID

View File

@@ -46,13 +46,17 @@ type Config struct {
ClusterCipherSuites string `hcl:"cluster_cipher_suites"` ClusterCipherSuites string `hcl:"cluster_cipher_suites"`
PluginDirectory string `hcl:"plugin_directory"` PluginDirectory string `hcl:"plugin_directory"`
EnableRawEndpoint bool `hcl:"-"`
EnableRawEndpointRaw interface{} `hcl:"raw_storage_endpoint"`
} }
// DevConfig is a Config that is used for dev mode of Vault. // DevConfig is a Config that is used for dev mode of Vault.
func DevConfig(ha, transactional bool) *Config { func DevConfig(ha, transactional bool) *Config {
ret := &Config{ ret := &Config{
DisableCache: false, DisableCache: false,
DisableMlock: true, DisableMlock: true,
EnableRawEndpoint: true,
Storage: &Storage{ Storage: &Storage{
Type: "inmem", Type: "inmem",
@@ -288,6 +292,11 @@ func (c *Config) Merge(c2 *Config) *Config {
result.EnableUI = c2.EnableUI result.EnableUI = c2.EnableUI
} }
result.EnableRawEndpoint = c.EnableRawEndpoint
if c2.EnableRawEndpoint {
result.EnableRawEndpoint = c2.EnableRawEndpoint
}
result.PluginDirectory = c.PluginDirectory result.PluginDirectory = c.PluginDirectory
if c2.PluginDirectory != "" { if c2.PluginDirectory != "" {
result.PluginDirectory = c2.PluginDirectory result.PluginDirectory = c2.PluginDirectory
@@ -306,9 +315,8 @@ func LoadConfig(path string, logger log.Logger) (*Config, error) {
if fi.IsDir() { if fi.IsDir() {
return LoadConfigDir(path, logger) return LoadConfigDir(path, logger)
} else {
return LoadConfigFile(path, logger)
} }
return LoadConfigFile(path, logger)
} }
// LoadConfigFile loads the configuration from the given file. // LoadConfigFile loads the configuration from the given file.
@@ -363,6 +371,12 @@ func ParseConfig(d string, logger log.Logger) (*Config, error) {
} }
} }
if result.EnableRawEndpointRaw != nil {
if result.EnableRawEndpoint, err = parseutil.ParseBool(result.EnableRawEndpointRaw); err != nil {
return nil, err
}
}
list, ok := obj.Node.(*ast.ObjectList) list, ok := obj.Node.(*ast.ObjectList)
if !ok { if !ok {
return nil, fmt.Errorf("error parsing: file doesn't contain a root object") return nil, fmt.Errorf("error parsing: file doesn't contain a root object")
@@ -385,6 +399,7 @@ func ParseConfig(d string, logger log.Logger) (*Config, error) {
"cluster_name", "cluster_name",
"cluster_cipher_suites", "cluster_cipher_suites",
"plugin_directory", "plugin_directory",
"raw_storage_endpoint",
} }
if err := checkHCLKeys(list, valid); err != nil { if err := checkHCLKeys(list, valid); err != nil {
return nil, err return nil, err

View File

@@ -62,6 +62,9 @@ func TestLoadConfigFile(t *testing.T) {
EnableUI: true, EnableUI: true,
EnableUIRaw: true, EnableUIRaw: true,
EnableRawEndpoint: true,
EnableRawEndpointRaw: true,
MaxLeaseTTL: 10 * time.Hour, MaxLeaseTTL: 10 * time.Hour,
MaxLeaseTTLRaw: "10h", MaxLeaseTTLRaw: "10h",
DefaultLeaseTTL: 10 * time.Hour, DefaultLeaseTTL: 10 * time.Hour,
@@ -129,6 +132,9 @@ func TestLoadConfigFile_json(t *testing.T) {
DisableMlockRaw: interface{}(nil), DisableMlockRaw: interface{}(nil),
EnableUI: true, EnableUI: true,
EnableUIRaw: true, EnableUIRaw: true,
EnableRawEndpoint: true,
EnableRawEndpointRaw: true,
} }
if !reflect.DeepEqual(config, expected) { if !reflect.DeepEqual(config, expected) {
t.Fatalf("expected \n\n%#v\n\n to be \n\n%#v\n\n", config, expected) t.Fatalf("expected \n\n%#v\n\n to be \n\n%#v\n\n", config, expected)
@@ -178,6 +184,8 @@ func TestLoadConfigFile_json2(t *testing.T) {
EnableUI: true, EnableUI: true,
EnableRawEndpoint: true,
Telemetry: &Telemetry{ Telemetry: &Telemetry{
StatsiteAddr: "foo", StatsiteAddr: "foo",
StatsdAddr: "bar", StatsdAddr: "bar",
@@ -232,6 +240,8 @@ func TestLoadConfigDir(t *testing.T) {
EnableUI: true, EnableUI: true,
EnableRawEndpoint: true,
Telemetry: &Telemetry{ Telemetry: &Telemetry{
StatsiteAddr: "qux", StatsiteAddr: "qux",
StatsdAddr: "baz", StatsdAddr: "baz",

View File

@@ -4,5 +4,6 @@ telemetry {
disable_hostname = true disable_hostname = true
} }
ui=true ui=true
raw_storage_endpoint=true
default_lease_ttl = "10h" default_lease_ttl = "10h"
cluster_name = "testcluster" cluster_name = "testcluster"

View File

@@ -28,3 +28,4 @@ telemetry {
max_lease_ttl = "10h" max_lease_ttl = "10h"
default_lease_ttl = "10h" default_lease_ttl = "10h"
cluster_name = "testcluster" cluster_name = "testcluster"
raw_storage_endpoint = true

View File

@@ -17,5 +17,6 @@
"max_lease_ttl": "10h", "max_lease_ttl": "10h",
"default_lease_ttl": "10h", "default_lease_ttl": "10h",
"cluster_name":"testcluster", "cluster_name":"testcluster",
"ui":true "ui":true,
"raw_storage_endpoint":true
} }

View File

@@ -1,5 +1,6 @@
{ {
"ui":true, "ui":true,
"raw_storage_endpoint":true,
"listener":[ "listener":[
{ {
"tcp":{ "tcp":{

View File

@@ -344,6 +344,9 @@ type Core struct {
// uiEnabled indicates whether Vault Web UI is enabled or not // uiEnabled indicates whether Vault Web UI is enabled or not
uiEnabled bool uiEnabled bool
// rawEnabled indicates whether the Raw endpoint is enabled
rawEnabled bool
// pluginDirectory is the location vault will look for plugin binaries // pluginDirectory is the location vault will look for plugin binaries
pluginDirectory string pluginDirectory string
@@ -402,6 +405,9 @@ type CoreConfig struct {
EnableUI bool `json:"ui" structs:"ui" mapstructure:"ui"` EnableUI bool `json:"ui" structs:"ui" mapstructure:"ui"`
// Enable the raw endpoint
EnableRaw bool `json:"enable_raw" structs:"enable_raw" mapstructure:"enable_raw"`
PluginDirectory string `json:"plugin_directory" structs:"plugin_directory" mapstructure:"plugin_directory"` PluginDirectory string `json:"plugin_directory" structs:"plugin_directory" mapstructure:"plugin_directory"`
ReloadFuncs *map[string][]reload.ReloadFunc ReloadFuncs *map[string][]reload.ReloadFunc
@@ -462,6 +468,7 @@ func NewCore(conf *CoreConfig) (*Core, error) {
clusterListenerShutdownSuccessCh: make(chan struct{}), clusterListenerShutdownSuccessCh: make(chan struct{}),
clusterPeerClusterAddrsCache: cache.New(3*heartbeatInterval, time.Second), clusterPeerClusterAddrsCache: cache.New(3*heartbeatInterval, time.Second),
enableMlock: !conf.DisableMlock, enableMlock: !conf.DisableMlock,
rawEnabled: conf.EnableRaw,
} }
if conf.ClusterCipherSuites != "" { if conf.ClusterCipherSuites != "" {

View File

@@ -22,7 +22,7 @@ var (
// protectedPaths cannot be accessed via the raw APIs. // protectedPaths cannot be accessed via the raw APIs.
// This is both for security and to prevent disrupting Vault. // This is both for security and to prevent disrupting Vault.
protectedPaths = []string{ protectedPaths = []string{
"core", keyringPath,
} }
replicationPaths = func(b *SystemBackend) []*framework.Path { replicationPaths = func(b *SystemBackend) []*framework.Path {
@@ -59,6 +59,7 @@ func NewSystemBackend(core *Core) *SystemBackend {
"remount", "remount",
"audit", "audit",
"audit/*", "audit/*",
"raw",
"raw/*", "raw/*",
"replication/primary/secondary-token", "replication/primary/secondary-token",
"replication/reindex", "replication/reindex",
@@ -652,25 +653,6 @@ func NewSystemBackend(core *Core) *SystemBackend {
HelpDescription: strings.TrimSpace(sysHelp["audit"][1]), HelpDescription: strings.TrimSpace(sysHelp["audit"][1]),
}, },
&framework.Path{
Pattern: "raw/(?P<path>.+)",
Fields: map[string]*framework.FieldSchema{
"path": &framework.FieldSchema{
Type: framework.TypeString,
},
"value": &framework.FieldSchema{
Type: framework.TypeString,
},
},
Callbacks: map[logical.Operation]framework.OperationFunc{
logical.ReadOperation: b.handleRawRead,
logical.UpdateOperation: b.handleRawWrite,
logical.DeleteOperation: b.handleRawDelete,
},
},
&framework.Path{ &framework.Path{
Pattern: "key-status$", Pattern: "key-status$",
@@ -871,6 +853,28 @@ func NewSystemBackend(core *Core) *SystemBackend {
b.Backend.Paths = append(b.Backend.Paths, replicationPaths(b)...) b.Backend.Paths = append(b.Backend.Paths, replicationPaths(b)...)
if core.rawEnabled {
b.Backend.Paths = append(b.Backend.Paths, &framework.Path{
Pattern: "(raw/?$|raw/(?P<path>.+))",
Fields: map[string]*framework.FieldSchema{
"path": &framework.FieldSchema{
Type: framework.TypeString,
},
"value": &framework.FieldSchema{
Type: framework.TypeString,
},
},
Callbacks: map[logical.Operation]framework.OperationFunc{
logical.ReadOperation: b.handleRawRead,
logical.UpdateOperation: b.handleRawWrite,
logical.DeleteOperation: b.handleRawDelete,
logical.ListOperation: b.handleRawList,
},
})
}
b.Backend.Invalidate = b.invalidate b.Backend.Invalidate = b.invalidate
return b return b
@@ -2143,6 +2147,29 @@ func (b *SystemBackend) handleRawDelete(
return nil, nil return nil, nil
} }
// handleRawList is used to list directly from the barrier
func (b *SystemBackend) handleRawList(
req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
path := data.Get("path").(string)
if path != "" && !strings.HasSuffix(path, "/") {
path = path + "/"
}
// Prevent access of protected paths
for _, p := range protectedPaths {
if strings.HasPrefix(path, p) {
err := fmt.Sprintf("cannot list '%s'", path)
return logical.ErrorResponse(err), logical.ErrInvalidRequest
}
}
keys, err := b.Core.barrier.List(path)
if err != nil {
return handleError(err)
}
return logical.ListResponse(keys), nil
}
// handleKeyStatus returns status information about the backend key // handleKeyStatus returns status information about the backend key
func (b *SystemBackend) handleKeyStatus( func (b *SystemBackend) handleKeyStatus(
req *logical.Request, data *framework.FieldData) (*logical.Response, error) { req *logical.Request, data *framework.FieldData) (*logical.Response, error) {

View File

@@ -27,6 +27,7 @@ func TestSystemBackend_RootPaths(t *testing.T) {
"remount", "remount",
"audit", "audit",
"audit/*", "audit/*",
"raw",
"raw/*", "raw/*",
"replication/primary/secondary-token", "replication/primary/secondary-token",
"replication/reindex", "replication/reindex",
@@ -1447,7 +1448,7 @@ func TestSystemBackend_disableAudit(t *testing.T) {
} }
func TestSystemBackend_rawRead_Protected(t *testing.T) { func TestSystemBackend_rawRead_Protected(t *testing.T) {
b := testSystemBackend(t) b := testSystemBackendRaw(t)
req := logical.TestRequest(t, logical.ReadOperation, "raw/"+keyringPath) req := logical.TestRequest(t, logical.ReadOperation, "raw/"+keyringPath)
_, err := b.HandleRequest(req) _, err := b.HandleRequest(req)
@@ -1457,7 +1458,7 @@ func TestSystemBackend_rawRead_Protected(t *testing.T) {
} }
func TestSystemBackend_rawWrite_Protected(t *testing.T) { func TestSystemBackend_rawWrite_Protected(t *testing.T) {
b := testSystemBackend(t) b := testSystemBackendRaw(t)
req := logical.TestRequest(t, logical.UpdateOperation, "raw/"+keyringPath) req := logical.TestRequest(t, logical.UpdateOperation, "raw/"+keyringPath)
_, err := b.HandleRequest(req) _, err := b.HandleRequest(req)
@@ -1467,7 +1468,7 @@ func TestSystemBackend_rawWrite_Protected(t *testing.T) {
} }
func TestSystemBackend_rawReadWrite(t *testing.T) { func TestSystemBackend_rawReadWrite(t *testing.T) {
c, b, _ := testCoreSystemBackend(t) c, b, _ := testCoreSystemBackendRaw(t)
req := logical.TestRequest(t, logical.UpdateOperation, "raw/sys/policy/test") req := logical.TestRequest(t, logical.UpdateOperation, "raw/sys/policy/test")
req.Data["value"] = `path "secret/" { policy = "read" }` req.Data["value"] = `path "secret/" { policy = "read" }`
@@ -1503,7 +1504,7 @@ func TestSystemBackend_rawReadWrite(t *testing.T) {
} }
func TestSystemBackend_rawDelete_Protected(t *testing.T) { func TestSystemBackend_rawDelete_Protected(t *testing.T) {
b := testSystemBackend(t) b := testSystemBackendRaw(t)
req := logical.TestRequest(t, logical.DeleteOperation, "raw/"+keyringPath) req := logical.TestRequest(t, logical.DeleteOperation, "raw/"+keyringPath)
_, err := b.HandleRequest(req) _, err := b.HandleRequest(req)
@@ -1513,7 +1514,7 @@ func TestSystemBackend_rawDelete_Protected(t *testing.T) {
} }
func TestSystemBackend_rawDelete(t *testing.T) { func TestSystemBackend_rawDelete(t *testing.T) {
c, b, _ := testCoreSystemBackend(t) c, b, _ := testCoreSystemBackendRaw(t)
// set the policy! // set the policy!
p := &Policy{Name: "test"} p := &Policy{Name: "test"}
@@ -1589,6 +1590,25 @@ func TestSystemBackend_rotate(t *testing.T) {
func testSystemBackend(t *testing.T) logical.Backend { func testSystemBackend(t *testing.T) logical.Backend {
c, _, _ := TestCoreUnsealed(t) c, _, _ := TestCoreUnsealed(t)
return testSystemBackendInternal(t, c)
}
func testSystemBackendRaw(t *testing.T) logical.Backend {
c, _, _ := TestCoreUnsealedRaw(t)
return testSystemBackendInternal(t, c)
}
func testCoreSystemBackend(t *testing.T) (*Core, logical.Backend, string) {
c, _, root := TestCoreUnsealed(t)
return c, testSystemBackendInternal(t, c), root
}
func testCoreSystemBackendRaw(t *testing.T) (*Core, logical.Backend, string) {
c, _, root := TestCoreUnsealedRaw(t)
return c, testSystemBackendInternal(t, c), root
}
func testSystemBackendInternal(t *testing.T, c *Core) logical.Backend {
bc := &logical.BackendConfig{ bc := &logical.BackendConfig{
Logger: c.logger, Logger: c.logger,
System: logical.StaticSystemView{ System: logical.StaticSystemView{
@@ -1606,24 +1626,6 @@ func testSystemBackend(t *testing.T) logical.Backend {
return b return b
} }
func testCoreSystemBackend(t *testing.T) (*Core, logical.Backend, string) {
c, _, root := TestCoreUnsealed(t)
bc := &logical.BackendConfig{
Logger: c.logger,
System: logical.StaticSystemView{
DefaultLeaseTTLVal: time.Hour * 24,
MaxLeaseTTLVal: time.Hour * 24 * 32,
},
}
b := NewSystemBackend(c)
err := b.Backend.Setup(bc)
if err != nil {
t.Fatal(err)
}
return c, b, root
}
func TestSystemBackend_PluginCatalog_CRUD(t *testing.T) { func TestSystemBackend_PluginCatalog_CRUD(t *testing.T) {
c, b, _ := testCoreSystemBackend(t) c, b, _ := testCoreSystemBackend(t)
// Bootstrap the pluginCatalog // Bootstrap the pluginCatalog

View File

@@ -107,7 +107,7 @@ func (d *TestSeal) SetRecoveryKey(key []byte) error {
func testCoreUnsealedWithConfigs(t *testing.T, barrierConf, recoveryConf *SealConfig) (*Core, [][]byte, [][]byte, string) { func testCoreUnsealedWithConfigs(t *testing.T, barrierConf, recoveryConf *SealConfig) (*Core, [][]byte, [][]byte, string) {
seal := &TestSeal{} seal := &TestSeal{}
core := TestCoreWithSeal(t, seal) core := TestCoreWithSeal(t, seal, false)
result, err := core.Initialize(&InitParams{ result, err := core.Initialize(&InitParams{
BarrierConfig: barrierConf, BarrierConfig: barrierConf,
RecoveryConfig: recoveryConf, RecoveryConfig: recoveryConf,

View File

@@ -85,18 +85,24 @@ oOyBJU/HMVvBfv4g+OVFLVgSwwm6owwsouZ0+D/LasbuHqYyqYqdyPJQYzWA2Y+F
// TestCore returns a pure in-memory, uninitialized core for testing. // TestCore returns a pure in-memory, uninitialized core for testing.
func TestCore(t testing.T) *Core { func TestCore(t testing.T) *Core {
return TestCoreWithSeal(t, nil) return TestCoreWithSeal(t, nil, false)
}
// TestCoreRaw returns a pure in-memory, uninitialized core for testing. The raw
// storage endpoints are enabled with this core.
func TestCoreRaw(t testing.T) *Core {
return TestCoreWithSeal(t, nil, true)
} }
// TestCoreNewSeal returns a pure in-memory, uninitialized core with // TestCoreNewSeal returns a pure in-memory, uninitialized core with
// the new seal configuration. // the new seal configuration.
func TestCoreNewSeal(t testing.T) *Core { func TestCoreNewSeal(t testing.T) *Core {
return TestCoreWithSeal(t, &TestSeal{}) return TestCoreWithSeal(t, &TestSeal{}, false)
} }
// TestCoreWithSeal returns a pure in-memory, uninitialized core with the // TestCoreWithSeal returns a pure in-memory, uninitialized core with the
// specified seal for testing. // specified seal for testing.
func TestCoreWithSeal(t testing.T, testSeal Seal) *Core { func TestCoreWithSeal(t testing.T, testSeal Seal, enableRaw bool) *Core {
logger := logformat.NewVaultLogger(log.LevelTrace) logger := logformat.NewVaultLogger(log.LevelTrace)
physicalBackend, err := physInmem.NewInmem(nil, logger) physicalBackend, err := physInmem.NewInmem(nil, logger)
if err != nil { if err != nil {
@@ -105,6 +111,10 @@ func TestCoreWithSeal(t testing.T, testSeal Seal) *Core {
conf := testCoreConfig(t, physicalBackend, logger) conf := testCoreConfig(t, physicalBackend, logger)
if enableRaw {
conf.EnableRaw = true
}
if testSeal != nil { if testSeal != nil {
conf.Seal = testSeal conf.Seal = testSeal
} }
@@ -198,6 +208,17 @@ func TestCoreUnseal(core *Core, key []byte) (bool, error) {
// initialized and unsealed. // initialized and unsealed.
func TestCoreUnsealed(t testing.T) (*Core, [][]byte, string) { func TestCoreUnsealed(t testing.T) (*Core, [][]byte, string) {
core := TestCore(t) core := TestCore(t)
return testCoreUnsealed(t, core)
}
// TestCoreUnsealedRaw returns a pure in-memory core that is already
// initialized, unsealed, and with raw endpoints enabled.
func TestCoreUnsealedRaw(t testing.T) (*Core, [][]byte, string) {
core := TestCoreRaw(t)
return testCoreUnsealed(t, core)
}
func testCoreUnsealed(t testing.T, core *Core) (*Core, [][]byte, string) {
keys, token := TestCoreInit(t, core) keys, token := TestCoreInit(t, core)
for _, key := range keys { for _, key := range keys {
if _, err := TestCoreUnseal(core, TestKeyCopy(key)); err != nil { if _, err := TestCoreUnseal(core, TestKeyCopy(key)); err != nil {

View File

@@ -10,6 +10,10 @@ description: |-
The `/sys/raw` endpoint is access the raw underlying store in Vault. The `/sys/raw` endpoint is access the raw underlying store in Vault.
This endpont is off by default. See the
[Vault configuration documentation](/docs/configuration/index.html) to
enable.
## Read Raw ## Read Raw
This endpoint reads the value of the key at the given path. This is the raw path This endpoint reads the value of the key at the given path. This is the raw path
@@ -76,6 +80,41 @@ $ curl \
https://vault.rocks/v1/sys/raw/secret/foo https://vault.rocks/v1/sys/raw/secret/foo
``` ```
## List Raw
This endpoint returns a list keys for a given path prefix.
**This endpoint requires 'sudo' capability.**
| Method | Path | Produces |
| :------- | :--------------------------- | :--------------------- |
| `LIST` | `/sys/raw/:prefix` | `200 application/json` |
| `GET` | `/sys/raw/:prefix?list=true` | `200 application/json` |
### Sample Request
```
$ curl \
--header "X-Vault-Token: ..." \
--request LIST \
https://vault.rocks/v1/sys/raw/logical
```
### Sample Response
```json
{
"data":{
"keys":[
"abcd-1234...",
"efgh-1234...",
"ijkl-1234..."
]
}
}
```
## Delete Raw ## Delete Raw
This endpoint deletes the key with given path. This is the raw path in the This endpoint deletes the key with given path. This is the raw path in the

View File

@@ -100,6 +100,10 @@ to specify where the configuration is.
duration for tokens and secrets. This is specified using a label duration for tokens and secrets. This is specified using a label
suffix like `"30s"` or `"1h"`. suffix like `"30s"` or `"1h"`.
- `raw_storage_endpoint` `(bool: false)` Enables the `sys/raw` endpoint which
allows the decryption/encryption of raw data into and out of the security
barrier. This is a highly priveleged endpoint.
- `ui` `(bool: false, Enterprise-only)` Enables the built-in web UI, which is - `ui` `(bool: false, Enterprise-only)` Enables the built-in web UI, which is
available on all listeners (address + port) at the `/ui` path. Browsers accessing available on all listeners (address + port) at the `/ui` path. Browsers accessing
the standard Vault API address will automatically redirect there. This can also the standard Vault API address will automatically redirect there. This can also