Remove injection into top routes (#5101)

This commit is contained in:
Jeff Mitchell 2018-08-14 15:29:22 -04:00 committed by Vishal Nayak
parent c3e063f2a6
commit f1d72abb39
14 changed files with 232 additions and 216 deletions

View File

@ -5,6 +5,12 @@ DEPRECATIONS/CHANGES:
* Request Timeouts: A default request timeout of 90s is now enforced. This
setting can be overwritten in the config file. If you anticipate requests
taking longer than 90s this setting should be updated before upgrading.
* `sys/` Top Level Injection: For the last two years for backwards
compatibility data for various `sys/` routes has been injected into both the
Secret's Data map and into the top level of the JSON response object.
However, this has some subtle issues that pop up from time to time and is
becoming increasingly complicated to maintain, so it's finally being
removed.
FEATURES:

View File

@ -2,6 +2,7 @@ package api
import (
"context"
"errors"
"fmt"
"github.com/mitchellh/mapstructure"
@ -25,17 +26,24 @@ func (c *Sys) AuditHash(path string, input string) (string, error) {
}
defer resp.Body.Close()
type d struct {
Hash string `json:"hash"`
}
var result d
err = resp.DecodeJSON(&result)
secret, err := ParseSecret(resp.Body)
if err != nil {
return "", err
}
if secret == nil || secret.Data == nil {
return "", errors.New("data from server response is empty")
}
return result.Hash, err
hash, ok := secret.Data["hash"]
if !ok {
return "", errors.New("hash not found in response data")
}
hashStr, ok := hash.(string)
if !ok {
return "", errors.New("could not parse hash in response data")
}
return hashStr, nil
}
func (c *Sys) ListAudit() (map[string]*Audit, error) {
@ -50,30 +58,19 @@ func (c *Sys) ListAudit() (map[string]*Audit, error) {
}
defer resp.Body.Close()
var result map[string]interface{}
err = resp.DecodeJSON(&result)
secret, err := ParseSecret(resp.Body)
if err != nil {
return nil, err
}
if secret == nil || secret.Data == nil {
return nil, errors.New("data from server response is empty")
}
mounts := map[string]*Audit{}
for k, v := range result {
switch v.(type) {
case map[string]interface{}:
default:
continue
}
var res Audit
err = mapstructure.Decode(v, &res)
err = mapstructure.Decode(secret.Data, &mounts)
if err != nil {
return nil, err
}
// Not a mount, some other api.Secret data
if res.Type == "" {
continue
}
mounts[k] = &res
}
return mounts, nil
}
@ -124,16 +121,16 @@ func (c *Sys) DisableAudit(path string) error {
// documentation. Please refer to that documentation for more details.
type EnableAuditOptions struct {
Type string `json:"type"`
Description string `json:"description"`
Options map[string]string `json:"options"`
Local bool `json:"local"`
Type string `json:"type" mapstructure:"type"`
Description string `json:"description" mapstructure:"description"`
Options map[string]string `json:"options" mapstructure:"options"`
Local bool `json:"local" mapstructure:"local"`
}
type Audit struct {
Path string
Type string
Description string
Options map[string]string
Local bool
Type string `json:"type" mapstructure:"type"`
Description string `json:"description" mapstructure:"description"`
Options map[string]string `json:"options" mapstructure:"options"`
Local bool `json:"local" mapstructure:"local"`
Path string `json:"path" mapstructure:"path"`
}

View File

@ -2,6 +2,7 @@ package api
import (
"context"
"errors"
"fmt"
"github.com/mitchellh/mapstructure"
@ -18,30 +19,19 @@ func (c *Sys) ListAuth() (map[string]*AuthMount, error) {
}
defer resp.Body.Close()
var result map[string]interface{}
err = resp.DecodeJSON(&result)
secret, err := ParseSecret(resp.Body)
if err != nil {
return nil, err
}
if secret == nil || secret.Data == nil {
return nil, errors.New("data from server response is empty")
}
mounts := map[string]*AuthMount{}
for k, v := range result {
switch v.(type) {
case map[string]interface{}:
default:
continue
}
var res AuthMount
err = mapstructure.Decode(v, &res)
err = mapstructure.Decode(secret.Data, &mounts)
if err != nil {
return nil, err
}
// Not a mount, some other api.Secret data
if res.Type == "" {
continue
}
mounts[k] = &res
}
return mounts, nil
}

View File

@ -2,7 +2,10 @@ package api
import (
"context"
"errors"
"fmt"
"github.com/mitchellh/mapstructure"
)
func (c *Sys) CapabilitiesSelf(path string) ([]string, error) {
@ -33,22 +36,19 @@ func (c *Sys) Capabilities(token, path string) ([]string, error) {
}
defer resp.Body.Close()
var result map[string]interface{}
err = resp.DecodeJSON(&result)
secret, err := ParseSecret(resp.Body)
if err != nil {
return nil, err
}
if secret == nil || secret.Data == nil {
return nil, errors.New("data from server response is empty")
}
var res []string
err = mapstructure.Decode(secret.Data[path], &res)
if err != nil {
return nil, err
}
if result["capabilities"] == nil {
return nil, nil
}
var capabilities []string
capabilitiesRaw, ok := result["capabilities"].([]interface{})
if !ok {
return nil, fmt.Errorf("error interpreting returned capabilities")
}
for _, capability := range capabilitiesRaw {
capabilities = append(capabilities, capability.(string))
}
return capabilities, nil
return res, nil
}

View File

@ -1,6 +1,11 @@
package api
import "context"
import (
"context"
"errors"
"github.com/mitchellh/mapstructure"
)
func (c *Sys) CORSStatus() (*CORSResponse, error) {
r := c.c.NewRequest("GET", "/v1/sys/config/cors")
@ -13,8 +18,20 @@ func (c *Sys) CORSStatus() (*CORSResponse, error) {
}
defer resp.Body.Close()
secret, err := ParseSecret(resp.Body)
if err != nil {
return nil, err
}
if secret == nil || secret.Data == nil {
return nil, errors.New("data from server response is empty")
}
var result CORSResponse
err = resp.DecodeJSON(&result)
err = mapstructure.Decode(secret.Data, &result)
if err != nil {
return nil, err
}
return &result, err
}
@ -32,8 +49,20 @@ func (c *Sys) ConfigureCORS(req *CORSRequest) (*CORSResponse, error) {
}
defer resp.Body.Close()
secret, err := ParseSecret(resp.Body)
if err != nil {
return nil, err
}
if secret == nil || secret.Data == nil {
return nil, errors.New("data from server response is empty")
}
var result CORSResponse
err = resp.DecodeJSON(&result)
err = mapstructure.Decode(secret.Data, &result)
if err != nil {
return nil, err
}
return &result, err
}
@ -48,18 +77,29 @@ func (c *Sys) DisableCORS() (*CORSResponse, error) {
}
defer resp.Body.Close()
var result CORSResponse
err = resp.DecodeJSON(&result)
return &result, err
secret, err := ParseSecret(resp.Body)
if err != nil {
return nil, err
}
if secret == nil || secret.Data == nil {
return nil, errors.New("data from server response is empty")
}
var result CORSResponse
err = mapstructure.Decode(secret.Data, &result)
if err != nil {
return nil, err
}
return &result, err
}
type CORSRequest struct {
AllowedOrigins string `json:"allowed_origins"`
Enabled bool `json:"enabled"`
AllowedOrigins string `json:"allowed_origins" mapstructure:"allowed_origins"`
Enabled bool `json:"enabled" mapstructure:"enabled"`
}
type CORSResponse struct {
AllowedOrigins string `json:"allowed_origins"`
Enabled bool `json:"enabled"`
AllowedOrigins string `json:"allowed_origins" mapstructure:"allowed_origins"`
Enabled bool `json:"enabled" mapstructure:"enabled"`
}

View File

@ -2,6 +2,7 @@ package api
import (
"context"
"errors"
"fmt"
"github.com/mitchellh/mapstructure"
@ -18,30 +19,19 @@ func (c *Sys) ListMounts() (map[string]*MountOutput, error) {
}
defer resp.Body.Close()
var result map[string]interface{}
err = resp.DecodeJSON(&result)
secret, err := ParseSecret(resp.Body)
if err != nil {
return nil, err
}
if secret == nil || secret.Data == nil {
return nil, errors.New("data from server response is empty")
}
mounts := map[string]*MountOutput{}
for k, v := range result {
switch v.(type) {
case map[string]interface{}:
default:
continue
}
var res MountOutput
err = mapstructure.Decode(v, &res)
err = mapstructure.Decode(secret.Data, &mounts)
if err != nil {
return nil, err
}
// Not a mount, some other api.Secret data
if res.Type == "" {
continue
}
mounts[k] = &res
}
return mounts, nil
}
@ -121,8 +111,16 @@ func (c *Sys) MountConfig(path string) (*MountConfigOutput, error) {
}
defer resp.Body.Close()
secret, err := ParseSecret(resp.Body)
if err != nil {
return nil, err
}
if secret == nil || secret.Data == nil {
return nil, errors.New("data from server response is empty")
}
var result MountConfigOutput
err = resp.DecodeJSON(&result)
err = mapstructure.Decode(secret.Data, &result)
if err != nil {
return nil, err
}

View File

@ -2,7 +2,10 @@ package api
import (
"context"
"errors"
"fmt"
"github.com/mitchellh/mapstructure"
)
func (c *Sys) ListPolicies() ([]string, error) {
@ -16,29 +19,25 @@ func (c *Sys) ListPolicies() ([]string, error) {
}
defer resp.Body.Close()
var result map[string]interface{}
err = resp.DecodeJSON(&result)
secret, err := ParseSecret(resp.Body)
if err != nil {
return nil, err
}
if secret == nil || secret.Data == nil {
return nil, errors.New("data from server response is empty")
}
var result []string
err = mapstructure.Decode(secret.Data["policies"], &result)
if err != nil {
return nil, err
}
var ok bool
if _, ok = result["policies"]; !ok {
return nil, fmt.Errorf("policies not found in response")
}
listRaw := result["policies"].([]interface{})
var policies []string
for _, val := range listRaw {
policies = append(policies, val.(string))
}
return policies, err
return result, err
}
func (c *Sys) GetPolicy(name string) (string, error) {
r := c.c.NewRequest("GET", fmt.Sprintf("/v1/sys/policy/%s", name))
r := c.c.NewRequest("GET", fmt.Sprintf("/v1/sys/policies/acl/%s", name))
ctx, cancelFunc := context.WithCancel(context.Background())
defer cancelFunc()
@ -53,16 +52,15 @@ func (c *Sys) GetPolicy(name string) (string, error) {
return "", err
}
var result map[string]interface{}
err = resp.DecodeJSON(&result)
secret, err := ParseSecret(resp.Body)
if err != nil {
return "", err
}
if rulesRaw, ok := result["rules"]; ok {
return rulesRaw.(string), nil
if secret == nil || secret.Data == nil {
return "", errors.New("data from server response is empty")
}
if policyRaw, ok := result["policy"]; ok {
if policyRaw, ok := secret.Data["policy"]; ok {
return policyRaw.(string), nil
}

View File

@ -1,6 +1,11 @@
package api
import "context"
import (
"context"
"errors"
"github.com/mitchellh/mapstructure"
)
func (c *Sys) RekeyStatus() (*RekeyStatusResponse, error) {
r := c.c.NewRequest("GET", "/v1/sys/rekey/init")
@ -211,8 +216,20 @@ func (c *Sys) RekeyRetrieveBackup() (*RekeyRetrieveResponse, error) {
}
defer resp.Body.Close()
secret, err := ParseSecret(resp.Body)
if err != nil {
return nil, err
}
if secret == nil || secret.Data == nil {
return nil, errors.New("data from server response is empty")
}
var result RekeyRetrieveResponse
err = resp.DecodeJSON(&result)
err = mapstructure.Decode(secret.Data, &result)
if err != nil {
return nil, err
}
return &result, err
}
@ -227,8 +244,20 @@ func (c *Sys) RekeyRetrieveRecoveryBackup() (*RekeyRetrieveResponse, error) {
}
defer resp.Body.Close()
secret, err := ParseSecret(resp.Body)
if err != nil {
return nil, err
}
if secret == nil || secret.Data == nil {
return nil, errors.New("data from server response is empty")
}
var result RekeyRetrieveResponse
err = resp.DecodeJSON(&result)
err = mapstructure.Decode(secret.Data, &result)
if err != nil {
return nil, err
}
return &result, err
}
@ -340,9 +369,9 @@ type RekeyUpdateResponse struct {
}
type RekeyRetrieveResponse struct {
Nonce string `json:"nonce"`
Keys map[string][]string `json:"keys"`
KeysB64 map[string][]string `json:"keys_base64"`
Nonce string `json:"nonce" mapstructure:"nonce"`
Keys map[string][]string `json:"keys" mapstructure:"keys"`
KeysB64 map[string][]string `json:"keys_base64" mapstructure:"keys_base64"`
}
type RekeyVerificationStatusResponse struct {

View File

@ -2,6 +2,8 @@ package api
import (
"context"
"encoding/json"
"errors"
"time"
)
@ -28,9 +30,45 @@ func (c *Sys) KeyStatus() (*KeyStatus, error) {
}
defer resp.Body.Close()
result := new(KeyStatus)
err = resp.DecodeJSON(result)
return result, err
secret, err := ParseSecret(resp.Body)
if err != nil {
return nil, err
}
if secret == nil || secret.Data == nil {
return nil, errors.New("data from server response is empty")
}
var result KeyStatus
termRaw, ok := secret.Data["term"]
if !ok {
return nil, errors.New("term not found in response")
}
term, ok := termRaw.(json.Number)
if !ok {
return nil, errors.New("could not convert term to a number")
}
term64, err := term.Int64()
if err != nil {
return nil, err
}
result.Term = int(term64)
installTimeRaw, ok := secret.Data["install_time"]
if !ok {
return nil, errors.New("install_time not found in response")
}
installTimeStr, ok := installTimeRaw.(string)
if !ok {
return nil, errors.New("could not convert install_time to a string")
}
installTime, err := time.Parse(time.RFC3339Nano, installTimeStr)
if err != nil {
return nil, err
}
result.InstallTime = installTime
return &result, err
}
type KeyStatus struct {

View File

@ -154,7 +154,7 @@ func (c *AuditListCommand) detailedAudits(audits map[string]*api.Audit) []string
}
columns = append(columns, fmt.Sprintf("%s | %s | %s | %s | %s",
audit.Path,
path,
audit.Type,
audit.Description,
replication,

View File

@ -90,14 +90,11 @@ func Handler(props *vault.HandlerProperties) http.Handler {
mux.Handle("/v1/sys/rekey-recovery-key/init", handleRequestForwarding(core, handleSysRekeyInit(core, true)))
mux.Handle("/v1/sys/rekey-recovery-key/update", handleRequestForwarding(core, handleSysRekeyUpdate(core, true)))
mux.Handle("/v1/sys/rekey-recovery-key/verify", handleRequestForwarding(core, handleSysRekeyVerify(core, true)))
mux.Handle("/v1/sys/wrapping/lookup", handleRequestForwarding(core, handleLogical(core, false, wrappingVerificationFunc)))
mux.Handle("/v1/sys/wrapping/rewrap", handleRequestForwarding(core, handleLogical(core, false, wrappingVerificationFunc)))
mux.Handle("/v1/sys/wrapping/unwrap", handleRequestForwarding(core, handleLogical(core, false, wrappingVerificationFunc)))
for _, path := range injectDataIntoTopRoutes {
mux.Handle(path, handleRequestForwarding(core, handleLogical(core, true, nil)))
}
mux.Handle("/v1/sys/", handleRequestForwarding(core, handleLogical(core, false, nil)))
mux.Handle("/v1/", handleRequestForwarding(core, handleLogical(core, false, nil)))
mux.Handle("/v1/sys/wrapping/lookup", handleRequestForwarding(core, handleLogical(core, wrappingVerificationFunc)))
mux.Handle("/v1/sys/wrapping/rewrap", handleRequestForwarding(core, handleLogical(core, wrappingVerificationFunc)))
mux.Handle("/v1/sys/wrapping/unwrap", handleRequestForwarding(core, handleLogical(core, wrappingVerificationFunc)))
mux.Handle("/v1/sys/", handleRequestForwarding(core, handleLogical(core, nil)))
mux.Handle("/v1/", handleRequestForwarding(core, handleLogical(core, nil)))
if core.UIEnabled() == true {
if uiBuiltIn {
mux.Handle("/ui/", http.StripPrefix("/ui/", gziphandler.GzipHandler(handleUIHeaders(core, handleUI(http.FileServer(&UIAssetWrapper{FileSystem: assetFS()}))))))
@ -592,27 +589,3 @@ func respondOk(w http.ResponseWriter, body interface{}) {
type ErrorResponse struct {
Errors []string `json:"errors"`
}
var injectDataIntoTopRoutes = []string{
"/v1/sys/audit",
"/v1/sys/audit/",
"/v1/sys/audit-hash/",
"/v1/sys/auth",
"/v1/sys/auth/",
"/v1/sys/config/cors",
"/v1/sys/config/auditing/request-headers/",
"/v1/sys/config/auditing/request-headers",
"/v1/sys/capabilities",
"/v1/sys/capabilities-accessor",
"/v1/sys/capabilities-self",
"/v1/sys/key-status",
"/v1/sys/mounts",
"/v1/sys/mounts/",
"/v1/sys/policy",
"/v1/sys/policy/",
"/v1/sys/rekey/backup",
"/v1/sys/rekey/recovery-key-backup",
"/v1/sys/remount",
"/v1/sys/rotate",
"/v1/sys/wrapping/wrap",
}

View File

@ -126,7 +126,7 @@ func buildLogicalRequest(core *vault.Core, w http.ResponseWriter, r *http.Reques
return req, 0, nil
}
func handleLogical(core *vault.Core, injectDataIntoTopLevel bool, prepareRequestCallback PrepareRequestFunc) http.Handler {
func handleLogical(core *vault.Core, prepareRequestCallback PrepareRequestFunc) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
req, statusCode, err := buildLogicalRequest(core, w, r)
if err != nil || statusCode != 0 {
@ -155,11 +155,11 @@ func handleLogical(core *vault.Core, injectDataIntoTopLevel bool, prepareRequest
}
// Build the proper response
respondLogical(w, r, req, injectDataIntoTopLevel, resp)
respondLogical(w, r, req, resp)
})
}
func respondLogical(w http.ResponseWriter, r *http.Request, req *logical.Request, injectDataIntoTopLevel bool, resp *logical.Response) {
func respondLogical(w http.ResponseWriter, r *http.Request, req *logical.Request, resp *logical.Response) {
var httpResp *logical.HTTPResponse
var ret interface{}
@ -194,13 +194,6 @@ func respondLogical(w http.ResponseWriter, r *http.Request, req *logical.Request
}
ret = httpResp
if injectDataIntoTopLevel {
injector := logical.HTTPSysInjector{
Response: httpResp,
}
ret = injector
}
}
// Respond

View File

@ -318,7 +318,7 @@ func TestLogical_RespondWithStatusCode(t *testing.T) {
}
w := httptest.NewRecorder()
respondLogical(w, nil, nil, false, resp404)
respondLogical(w, nil, nil, resp404)
if w.Code != 404 {
t.Fatalf("Bad Status code: %d", w.Code)

View File

@ -1,9 +1,6 @@
package logical
import (
"bytes"
"encoding/json"
"fmt"
"time"
)
@ -104,46 +101,3 @@ type HTTPWrapInfo struct {
CreationPath string `json:"creation_path"`
WrappedAccessor string `json:"wrapped_accessor,omitempty"`
}
type HTTPSysInjector struct {
Response *HTTPResponse
}
func (h HTTPSysInjector) MarshalJSON() ([]byte, error) {
j, err := json.Marshal(h.Response)
if err != nil {
return nil, err
}
// Fast path no data or empty data
if h.Response.Data == nil || len(h.Response.Data) == 0 {
return j, nil
}
// Marshaling a response will always be a JSON object, meaning it will
// always start with '{', so we hijack this to prepend necessary values
// Make a guess at the capacity, and write the object opener
buf := bytes.NewBuffer(make([]byte, 0, len(j)*2))
buf.WriteRune('{')
for k, v := range h.Response.Data {
// Marshal each key/value individually
mk, err := json.Marshal(k)
if err != nil {
return nil, err
}
mv, err := json.Marshal(v)
if err != nil {
return nil, err
}
// Write into the final buffer. We'll never have a valid response
// without any fields so we can unconditionally add a comma after each.
buf.WriteString(fmt.Sprintf("%s: %s, ", mk, mv))
}
// Add the rest, without the first '{'
buf.Write(j[1:])
return buf.Bytes(), nil
}