func respondError(w http.ResponseWriter, status int, err error) { // Adjust status code when sealed if errwrap.Contains(err, vault.ErrSealed.Error()) { status = http.StatusServiceUnavailable } // Adjust status code on if errwrap.Contains(err, "http: request body too large") { status = http.StatusRequestEntityTooLarge } // Allow HTTPCoded error passthrough to specify a code if t, ok := err.(logical.HTTPCodedError); ok { status = t.Code() } w.Header().Add("Content-Type", "application/json") w.WriteHeader(status) resp := &ErrorResponse{Errors: make([]string, 0, 1)} if err != nil { resp.Errors = append(resp.Errors, err.Error()) } enc := json.NewEncoder(w) enc.Encode(resp) }
func TestCore_LimitedUseToken(t *testing.T) { c, _, root := TestCoreUnsealed(t) // Create a new credential req := logical.TestRequest(t, logical.UpdateOperation, "auth/token/create") req.ClientToken = root req.Data["num_uses"] = "1" resp, err := c.HandleRequest(req) if err != nil { t.Fatalf("err: %v", err) } // Put a secret req = &logical.Request{ Operation: logical.UpdateOperation, Path: "secret/foo", Data: map[string]interface{}{ "foo": "bar", }, ClientToken: resp.Auth.ClientToken, } _, err = c.HandleRequest(req) if err != nil { t.Fatalf("err: %v", err) } // Second operation should fail _, err = c.HandleRequest(req) if err == nil || !errwrap.Contains(err, logical.ErrPermissionDenied.Error()) { t.Fatalf("err: %v", err) } }
func handleSysSeal(core *vault.Core) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { req, statusCode, err := buildLogicalRequest(core, w, r) if err != nil || statusCode != 0 { respondError(w, statusCode, err) return } switch req.Operation { case logical.UpdateOperation: default: respondError(w, http.StatusMethodNotAllowed, nil) return } // Seal with the token above if err := core.SealWithRequest(req); err != nil { if errwrap.Contains(err, logical.ErrPermissionDenied.Error()) { respondError(w, http.StatusForbidden, err) return } else { respondError(w, http.StatusInternalServerError, err) return } } respondOk(w, nil) }) }
func TestAuditBroker_LogRequest(t *testing.T) { l := logformat.NewVaultLogger(log.LevelTrace) b := NewAuditBroker(l) a1 := &NoopAudit{} a2 := &NoopAudit{} b.Register("foo", a1, nil) b.Register("bar", a2, nil) auth := &logical.Auth{ ClientToken: "foo", Policies: []string{"dev", "ops"}, Metadata: map[string]string{ "user": "******", "source": "github", }, } req := &logical.Request{ Operation: logical.ReadOperation, Path: "sys/mounts", } // Create an identifier for the request to verify against var err error req.ID, err = uuid.GenerateUUID() if err != nil { t.Fatalf("failed to generate identifier for the request: path%s err: %v", req.Path, err) } reqErrs := errors.New("errs") err = b.LogRequest(auth, req, reqErrs) if err != nil { t.Fatalf("err: %v", err) } for _, a := range []*NoopAudit{a1, a2} { if !reflect.DeepEqual(a.ReqAuth[0], auth) { t.Fatalf("Bad: %#v", a.ReqAuth[0]) } if !reflect.DeepEqual(a.Req[0], req) { t.Fatalf("Bad: %#v", a.Req[0]) } if !reflect.DeepEqual(a.ReqErrs[0], reqErrs) { t.Fatalf("Bad: %#v", a.ReqErrs[0]) } } // Should still work with one failing backend a1.ReqErr = fmt.Errorf("failed") if err := b.LogRequest(auth, req, nil); err != nil { t.Fatalf("err: %v", err) } // Should FAIL work with both failing backends a2.ReqErr = fmt.Errorf("failed") if err := b.LogRequest(auth, req, nil); !errwrap.Contains(err, "no audit backend succeeded in logging the request") { t.Fatalf("err: %v", err) } }
// request is a helper to perform a request and properly exit in the // case of an error. func request(core *vault.Core, w http.ResponseWriter, rawReq *http.Request, r *logical.Request) (*logical.Response, bool) { resp, err := core.HandleRequest(r) if errwrap.Contains(err, vault.ErrStandby.Error()) { respondStandby(core, w, rawReq.URL) return resp, false } if respondErrorCommon(w, resp, err) { return resp, false } return resp, true }
func respondErrorCommon(w http.ResponseWriter, resp *logical.Response, err error) bool { // If there are no errors return if err == nil && (resp == nil || !resp.IsError()) { return false } // Start out with internal server error since in most of these cases there // won't be a response so this won't be overridden statusCode := http.StatusInternalServerError // If we actually have a response, start out with bad request if resp != nil { statusCode = http.StatusBadRequest } // Now, check the error itself; if it has a specific logical error, set the // appropriate code if err != nil { switch { case errwrap.ContainsType(err, new(vault.StatusBadRequest)): statusCode = http.StatusBadRequest case errwrap.Contains(err, logical.ErrPermissionDenied.Error()): statusCode = http.StatusForbidden case errwrap.Contains(err, logical.ErrUnsupportedOperation.Error()): statusCode = http.StatusMethodNotAllowed case errwrap.Contains(err, logical.ErrUnsupportedPath.Error()): statusCode = http.StatusNotFound case errwrap.Contains(err, logical.ErrInvalidRequest.Error()): statusCode = http.StatusBadRequest } } if resp != nil && resp.IsError() { err = fmt.Errorf("%s", resp.Data["error"].(string)) } respondError(w, statusCode, err) return true }
// Test a root path is denied if non-root func TestCore_HandleRequest_RootPath(t *testing.T) { c, _, root := TestCoreUnsealed(t) testCoreMakeToken(t, c, root, "child", "", []string{"test"}) req := &logical.Request{ Operation: logical.ReadOperation, Path: "sys/policy", // root protected! ClientToken: "child", } resp, err := c.HandleRequest(req) if err == nil || !errwrap.Contains(err, logical.ErrPermissionDenied.Error()) { t.Fatalf("err: %v, resp: %v", err, resp) } }
// Check that standard permissions work func TestCore_HandleRequest_PermissionDenied(t *testing.T) { c, _, root := TestCoreUnsealed(t) testCoreMakeToken(t, c, root, "child", "", []string{"test"}) req := &logical.Request{ Operation: logical.UpdateOperation, Path: "secret/test", Data: map[string]interface{}{ "foo": "bar", "lease": "1h", }, ClientToken: "child", } resp, err := c.HandleRequest(req) if err == nil || !errwrap.Contains(err, logical.ErrPermissionDenied.Error()) { t.Fatalf("err: %v, resp: %v", err, resp) } }
func handleSysLeaderGet(core *vault.Core, w http.ResponseWriter, r *http.Request) { haEnabled := true isLeader, address, err := core.Leader() if errwrap.Contains(err, vault.ErrHANotEnabled.Error()) { haEnabled = false err = nil } if err != nil { respondError(w, http.StatusInternalServerError, err) return } respondOk(w, &LeaderResponse{ HAEnabled: haEnabled, IsSelf: isLeader, LeaderAddress: address, }) }
func TestCore_HandleRequest_MissingToken(t *testing.T) { c, _, _ := TestCoreUnsealed(t) req := &logical.Request{ Operation: logical.UpdateOperation, Path: "secret/test", Data: map[string]interface{}{ "foo": "bar", "lease": "1h", }, } resp, err := c.HandleRequest(req) if err == nil || !errwrap.Contains(err, logical.ErrInvalidRequest.Error()) { t.Fatalf("err: %v", err) } if resp.Data["error"] != "missing client token" { t.Fatalf("bad: %#v", resp) } }
// Test performs an acceptance test on a backend with the given test case. // // Tests are not run unless an environmental variable "VAULT_ACC" is // set to some non-empty value. This is to avoid test cases surprising // a user by creating real resources. // // Tests will fail unless the verbose flag (`go test -v`, or explicitly // the "-test.v" flag) is set. Because some acceptance tests take quite // long, we require the verbose flag so users are able to see progress // output. func Test(tt TestT, c TestCase) { // We only run acceptance tests if an env var is set because they're // slow and generally require some outside configuration. if c.AcceptanceTest && os.Getenv(TestEnvVar) == "" { tt.Skip(fmt.Sprintf( "Acceptance tests skipped unless env '%s' set", TestEnvVar)) return } // We require verbose mode so that the user knows what is going on. if c.AcceptanceTest && !testTesting && !testing.Verbose() { tt.Fatal("Acceptance tests must be run with the -v flag on tests") return } // Run the PreCheck if we have it if c.PreCheck != nil { c.PreCheck() } // Check that something is provided if c.Backend == nil && c.Factory == nil { tt.Fatal("Must provide either Backend or Factory") return } // Create an in-memory Vault core core, err := vault.NewCore(&vault.CoreConfig{ Physical: physical.NewInmem(logger), LogicalBackends: map[string]logical.Factory{ "test": func(conf *logical.BackendConfig) (logical.Backend, error) { if c.Backend != nil { return c.Backend, nil } return c.Factory(conf) }, }, DisableMlock: true, }) if err != nil { tt.Fatal("error initializing core: ", err) return } // Initialize the core init, err := core.Initialize(&vault.SealConfig{ SecretShares: 1, SecretThreshold: 1, }, nil) if err != nil { tt.Fatal("error initializing core: ", err) return } // Unseal the core if unsealed, err := core.Unseal(init.SecretShares[0]); err != nil { tt.Fatal("error unsealing core: ", err) return } else if !unsealed { tt.Fatal("vault shouldn't be sealed") return } // Create an HTTP API server and client ln, addr := http.TestServer(nil, core) defer ln.Close() clientConfig := api.DefaultConfig() clientConfig.Address = addr client, err := api.NewClient(clientConfig) if err != nil { tt.Fatal("error initializing HTTP client: ", err) return } // Set the token so we're authenticated client.SetToken(init.RootToken) // Mount the backend prefix := "mnt" mountInfo := &api.MountInput{ Type: "test", Description: "acceptance test", } if err := client.Sys().Mount(prefix, mountInfo); err != nil { tt.Fatal("error mounting backend: ", err) return } // Make requests var revoke []*logical.Request for i, s := range c.Steps { log.Printf("[WARN] Executing test step %d", i+1) // Create the request req := &logical.Request{ Operation: s.Operation, Path: s.Path, Data: s.Data, } if !s.Unauthenticated { req.ClientToken = client.Token() } if s.RemoteAddr != "" { req.Connection = &logical.Connection{RemoteAddr: s.RemoteAddr} } if s.ConnState != nil { req.Connection = &logical.Connection{ConnState: s.ConnState} } if s.PreFlight != nil { ct := req.ClientToken req.ClientToken = "" if err := s.PreFlight(req); err != nil { tt.Error(fmt.Sprintf("Failed preflight for step %d: %s", i+1, err)) break } req.ClientToken = ct } // Make sure to prefix the path with where we mounted the thing req.Path = fmt.Sprintf("%s/%s", prefix, req.Path) // Make the request resp, err := core.HandleRequest(req) if resp != nil && resp.Secret != nil { // Revoke this secret later revoke = append(revoke, &logical.Request{ Operation: logical.UpdateOperation, Path: "sys/revoke/" + resp.Secret.LeaseID, }) } // Test step returned an error. if err != nil { // But if an error is expected, do not fail the test step, // regardless of whether the error is a 'logical.ErrorResponse' // or not. Set the err to nil. If the error is a logical.ErrorResponse, // it will be handled later. if s.ErrorOk { err = nil } else { // If the error is not expected, fail right away. tt.Error(fmt.Sprintf("Failed step %d: %s", i+1, err)) break } } // If the error is a 'logical.ErrorResponse' and if error was not expected, // set the error so that this can be caught below. if resp.IsError() && !s.ErrorOk { err = fmt.Errorf("Erroneous response:\n\n%#v", resp) } // Either the 'err' was nil or if an error was expected, it was set to nil. // Call the 'Check' function if there is one. // // TODO: This works perfectly for now, but it would be better if 'Check' // function takes in both the response object and the error, and decide on // the action on its own. if err == nil && s.Check != nil { // Call the test method err = s.Check(resp) } if err != nil { tt.Error(fmt.Sprintf("Failed step %d: %s", i+1, err)) break } } // Revoke any secrets we might have. var failedRevokes []*logical.Secret for _, req := range revoke { log.Printf("[WARN] Revoking secret: %#v", req) req.ClientToken = client.Token() resp, err := core.HandleRequest(req) if err == nil && resp.IsError() { err = fmt.Errorf("Erroneous response:\n\n%#v", resp) } if err != nil { failedRevokes = append(failedRevokes, req.Secret) tt.Error(fmt.Sprintf("[ERR] Revoke error: %s", err)) } } // Perform any rollbacks. This should no-op if there aren't any. // We set the "immediate" flag here that any backend can pick up on // to do all rollbacks immediately even if the WAL entries are new. log.Printf("[WARN] Requesting RollbackOperation") req := logical.RollbackRequest(prefix + "/") req.Data["immediate"] = true req.ClientToken = client.Token() resp, err := core.HandleRequest(req) if err == nil && resp.IsError() { err = fmt.Errorf("Erroneous response:\n\n%#v", resp) } if err != nil { if !errwrap.Contains(err, logical.ErrUnsupportedOperation.Error()) { tt.Error(fmt.Sprintf("[ERR] Rollback error: %s", err)) } } // If we have any failed revokes, log it. if len(failedRevokes) > 0 { for _, s := range failedRevokes { tt.Error(fmt.Sprintf( "WARNING: Revoking the following secret failed. It may\n"+ "still exist. Please verify:\n\n%#v", s)) } } // Cleanup if c.Teardown != nil { c.Teardown() } }
func handleSysUnseal(core *vault.Core) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { switch r.Method { case "PUT": case "POST": default: respondError(w, http.StatusMethodNotAllowed, nil) return } // Parse the request var req UnsealRequest if err := parseRequest(r, w, &req); err != nil { respondError(w, http.StatusBadRequest, err) return } if !req.Reset && req.Key == "" { respondError( w, http.StatusBadRequest, errors.New("'key' must specified in request body as JSON, or 'reset' set to true")) return } if req.Reset { sealed, err := core.Sealed() if err != nil { respondError(w, http.StatusInternalServerError, err) return } if !sealed { respondError(w, http.StatusBadRequest, errors.New("vault is unsealed")) return } core.ResetUnsealProcess() } else { // Decode the key, which is base64 or hex encoded min, max := core.BarrierKeyLength() key, err := hex.DecodeString(req.Key) // We check min and max here to ensure that a string that is base64 // encoded but also valid hex will not be valid and we instead base64 // decode it if err != nil || len(key) < min || len(key) > max { key, err = base64.StdEncoding.DecodeString(req.Key) if err != nil { respondError( w, http.StatusBadRequest, errors.New("'key' must be a valid hex or base64 string")) return } } // Attempt the unseal if _, err := core.Unseal(key); err != nil { switch { case errwrap.ContainsType(err, new(vault.ErrInvalidKey)): case errwrap.Contains(err, vault.ErrBarrierInvalidKey.Error()): case errwrap.Contains(err, vault.ErrBarrierNotInit.Error()): case errwrap.Contains(err, vault.ErrBarrierSealed.Error()): case errwrap.Contains(err, vault.ErrStandby.Error()): default: respondError(w, http.StatusInternalServerError, err) return } respondError(w, http.StatusBadRequest, err) return } } // Return the seal status handleSysSealStatusRaw(core, w, r) }) }