From 47d52be3aee0d65fb586bf67ee6e98f6511c2a3e Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Wed, 1 Apr 2015 18:36:13 -0700 Subject: [PATCH] http: audit endpoints --- http/handler.go | 2 + http/sys_audit.go | 113 +++++++++++++++++++++++++++++++++++++++++ http/sys_audit_test.go | 68 +++++++++++++++++++++++++ vault/testing.go | 17 +++++++ 4 files changed, 200 insertions(+) create mode 100644 http/sys_audit.go create mode 100644 http/sys_audit_test.go diff --git a/http/handler.go b/http/handler.go index d9f3a7c898..746ed6584b 100644 --- a/http/handler.go +++ b/http/handler.go @@ -26,6 +26,8 @@ func Handler(core *vault.Core) http.Handler { mux.Handle("/v1/sys/revoke/", handleSysRevoke(core)) mux.Handle("/v1/sys/revoke-prefix/", handleSysRevokePrefix(core)) mux.Handle("/v1/sys/auth/", handleSysAuth(core)) + mux.Handle("/v1/sys/audit", handleSysListAudit(core)) + mux.Handle("/v1/sys/audit/", handleSysAudit(core)) mux.Handle("/v1/", handleLogical(core)) return mux } diff --git a/http/sys_audit.go b/http/sys_audit.go new file mode 100644 index 0000000000..c11a5f8cbb --- /dev/null +++ b/http/sys_audit.go @@ -0,0 +1,113 @@ +package http + +import ( + "net/http" + "strings" + + "github.com/hashicorp/vault/logical" + "github.com/hashicorp/vault/vault" +) + +func handleSysListAudit(core *vault.Core) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.Method != "GET" { + respondError(w, http.StatusMethodNotAllowed, nil) + return + } + + resp, err := core.HandleRequest(requestAuth(r, &logical.Request{ + Operation: logical.ReadOperation, + Path: "sys/audit", + })) + if err != nil { + respondError(w, http.StatusInternalServerError, err) + return + } + + respondOk(w, resp.Data) + }) +} + +func handleSysAudit(core *vault.Core) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + switch r.Method { + case "POST": + fallthrough + case "PUT": + handleSysEnableAudit(core, w, r) + case "DELETE": + handleSysDisableAudit(core, w, r) + default: + respondError(w, http.StatusMethodNotAllowed, nil) + return + } + }) +} + +func handleSysDisableAudit(core *vault.Core, w http.ResponseWriter, r *http.Request) { + // Determine the path... + prefix := "/v1/sys/audit/" + if !strings.HasPrefix(r.URL.Path, prefix) { + respondError(w, http.StatusNotFound, nil) + return + } + path := r.URL.Path[len(prefix):] + if path == "" { + respondError(w, http.StatusNotFound, nil) + return + } + + _, err := core.HandleRequest(requestAuth(r, &logical.Request{ + Operation: logical.DeleteOperation, + Path: "sys/audit/" + path, + })) + if err != nil { + respondError(w, http.StatusInternalServerError, err) + return + } + + respondOk(w, nil) +} + +func handleSysEnableAudit(core *vault.Core, w http.ResponseWriter, r *http.Request) { + // Determine the path... + prefix := "/v1/sys/audit/" + if !strings.HasPrefix(r.URL.Path, prefix) { + respondError(w, http.StatusNotFound, nil) + return + } + path := r.URL.Path[len(prefix):] + if path == "" { + respondError(w, http.StatusNotFound, nil) + return + } + + // Parse the request if we can + var req enableAuditRequest + if err := parseRequest(r, &req); err != nil { + respondError(w, http.StatusBadRequest, err) + return + } + + _, err := core.HandleRequest(requestAuth(r, &logical.Request{ + Operation: logical.WriteOperation, + Path: "sys/audit/" + path, + Data: map[string]interface{}{ + "type": req.Type, + "description": req.Description, + "options": req.Options, + }, + })) + if err != nil { + respondError(w, http.StatusInternalServerError, err) + return + } + + respondOk(w, nil) +} + +type enableAuditRequest struct { + Type string `json:"type"` + Description string `json:"description"` + Options map[string]string `json:"options"` +} diff --git a/http/sys_audit_test.go b/http/sys_audit_test.go new file mode 100644 index 0000000000..f3053925bd --- /dev/null +++ b/http/sys_audit_test.go @@ -0,0 +1,68 @@ +package http + +import ( + "net/http" + "reflect" + "testing" + + "github.com/hashicorp/vault/vault" +) + +func TestSysAudit(t *testing.T) { + core, _, token := vault.TestCoreUnsealed(t) + ln, addr := TestServer(t, core) + defer ln.Close() + TestServerAuth(t, addr, token) + + resp := testHttpPost(t, addr+"/v1/sys/audit/noop", map[string]interface{}{ + "type": "noop", + }) + testResponseStatus(t, resp, 204) + + resp, err := http.Get(addr + "/v1/sys/audit") + if err != nil { + t.Fatalf("err: %s", err) + } + + var actual map[string]interface{} + expected := map[string]interface{}{ + "noop": map[string]interface{}{ + "type": "noop", + "description": "", + "options": map[string]interface{}{}, + }, + } + testResponseStatus(t, resp, 200) + testResponseBody(t, resp, &actual) + if !reflect.DeepEqual(actual, expected) { + t.Fatalf("bad: %#v", actual) + } +} + +func TestSysDisableAudit(t *testing.T) { + core, _, token := vault.TestCoreUnsealed(t) + ln, addr := TestServer(t, core) + defer ln.Close() + TestServerAuth(t, addr, token) + + resp := testHttpPost(t, addr+"/v1/sys/audit/foo", map[string]interface{}{ + "type": "noop", + }) + testResponseStatus(t, resp, 204) + + resp = testHttpDelete(t, addr+"/v1/sys/audit/foo") + testResponseStatus(t, resp, 204) + + resp, err := http.Get(addr + "/v1/sys/audit") + if err != nil { + t.Fatalf("err: %s", err) + } + + var actual map[string]interface{} + expected := map[string]interface{}{} + testResponseStatus(t, resp, 200) + testResponseBody(t, resp, &actual) + if !reflect.DeepEqual(actual, expected) { + t.Fatalf("bad: %#v", actual) + } +} diff --git a/vault/testing.go b/vault/testing.go index 7a82a3b560..a4cbf72c1c 100644 --- a/vault/testing.go +++ b/vault/testing.go @@ -3,6 +3,7 @@ package vault import ( "testing" + "github.com/hashicorp/vault/audit" "github.com/hashicorp/vault/logical" "github.com/hashicorp/vault/logical/framework" "github.com/hashicorp/vault/physical" @@ -13,6 +14,11 @@ import ( // TestCore returns a pure in-memory, uninitialized core for testing. func TestCore(t *testing.T) *Core { + noopAudits := map[string]audit.Factory{ + "noop": func(map[string]string) (audit.Backend, error) { + return new(noopAudit), nil + }, + } noopBackends := make(map[string]logical.Factory) noopBackends["noop"] = func(map[string]string) (logical.Backend, error) { return new(framework.Backend), nil @@ -21,6 +27,7 @@ func TestCore(t *testing.T) *Core { physicalBackend := physical.NewInmem() c, err := NewCore(&CoreConfig{ Physical: physicalBackend, + AuditBackends: noopAudits, LogicalBackends: noopBackends, CredentialBackends: noopBackends, }) @@ -71,3 +78,13 @@ func TestKeyCopy(key []byte) []byte { copy(result, key) return result } + +type noopAudit struct{} + +func (n *noopAudit) LogRequest(a *logical.Auth, r *logical.Request) error { + return nil +} + +func (n *noopAudit) LogResponse(a *logical.Auth, r *logical.Request, re *logical.Response, err error) error { + return nil +}