diff --git a/changelog/15586.txt b/changelog/15586.txt new file mode 100644 index 000000000..063f193ae --- /dev/null +++ b/changelog/15586.txt @@ -0,0 +1,3 @@ +```release-note:improvement +core: Add an export API for historical activity log data +``` diff --git a/http/logical.go b/http/logical.go index 224f4b647..2de6c3295 100644 --- a/http/logical.go +++ b/http/logical.go @@ -88,6 +88,8 @@ func buildLogicalRequestNoAuth(perfStandby bool, w http.ResponseWriter, r *http. responseWriter = w case path == "sys/storage/raft/snapshot": responseWriter = w + case path == "sys/internal/counters/activity/export": + responseWriter = w case path == "sys/monitor": passHTTPReq = true responseWriter = w diff --git a/vault/activity/test_fixtures/aug.csv b/vault/activity/test_fixtures/aug.csv new file mode 100644 index 000000000..575a8953e --- /dev/null +++ b/vault/activity/test_fixtures/aug.csv @@ -0,0 +1,21 @@ +client_id,namespace_id,timestamp,non_entity,mount_accessor +111122222-3333-4444-5555-000000000000,root,1,false,auth_1 +111122222-3333-4444-5555-000000000001,root,1,false,auth_1 +111122222-3333-4444-5555-000000000002,root,1,false,auth_1 +111122222-3333-4444-5555-000000000003,root,1,false,auth_1 +111122222-3333-4444-5555-000000000004,root,1,false,auth_1 +111122222-3333-4444-5555-000000000005,aaaaa,1,false,auth_2 +111122222-3333-4444-5555-000000000006,aaaaa,1,false,auth_2 +111122222-3333-4444-5555-000000000007,aaaaa,1,false,auth_2 +111122222-3333-4444-5555-000000000008,aaaaa,1,false,auth_2 +111122222-3333-4444-5555-000000000009,aaaaa,1,false,auth_2 +111122222-3333-4444-5555-000000000010,bbbbb,1,false,auth_3 +111122222-3333-4444-5555-000000000011,bbbbb,1,false,auth_3 +111122222-3333-4444-5555-000000000012,bbbbb,1,false,auth_3 +111122222-3333-4444-5555-000000000013,bbbbb,2,false,auth_3 +111122222-3333-4444-5555-000000000014,bbbbb,2,false,auth_3 +111122222-3333-4444-5555-000000000015,root,2,false,auth_4 +111122222-3333-4444-5555-000000000016,root,2,false,auth_4 +111122222-3333-4444-5555-000000000017,root,2,false,auth_4 +111122222-3333-4444-5555-000000000018,root,2,false,auth_4 +111122222-3333-4444-5555-000000000019,root,2,false,auth_4 diff --git a/vault/activity/test_fixtures/aug.json b/vault/activity/test_fixtures/aug.json new file mode 100644 index 000000000..17f117ab2 --- /dev/null +++ b/vault/activity/test_fixtures/aug.json @@ -0,0 +1,20 @@ +{"client_id":"111122222-3333-4444-5555-000000000000","namespace_id":"root","timestamp":1,"mount_accessor":"auth_1"} +{"client_id":"111122222-3333-4444-5555-000000000001","namespace_id":"root","timestamp":1,"mount_accessor":"auth_1"} +{"client_id":"111122222-3333-4444-5555-000000000002","namespace_id":"root","timestamp":1,"mount_accessor":"auth_1"} +{"client_id":"111122222-3333-4444-5555-000000000003","namespace_id":"root","timestamp":1,"mount_accessor":"auth_1"} +{"client_id":"111122222-3333-4444-5555-000000000004","namespace_id":"root","timestamp":1,"mount_accessor":"auth_1"} +{"client_id":"111122222-3333-4444-5555-000000000005","namespace_id":"aaaaa","timestamp":1,"mount_accessor":"auth_2"} +{"client_id":"111122222-3333-4444-5555-000000000006","namespace_id":"aaaaa","timestamp":1,"mount_accessor":"auth_2"} +{"client_id":"111122222-3333-4444-5555-000000000007","namespace_id":"aaaaa","timestamp":1,"mount_accessor":"auth_2"} +{"client_id":"111122222-3333-4444-5555-000000000008","namespace_id":"aaaaa","timestamp":1,"mount_accessor":"auth_2"} +{"client_id":"111122222-3333-4444-5555-000000000009","namespace_id":"aaaaa","timestamp":1,"mount_accessor":"auth_2"} +{"client_id":"111122222-3333-4444-5555-000000000010","namespace_id":"bbbbb","timestamp":1,"mount_accessor":"auth_3"} +{"client_id":"111122222-3333-4444-5555-000000000011","namespace_id":"bbbbb","timestamp":1,"mount_accessor":"auth_3"} +{"client_id":"111122222-3333-4444-5555-000000000012","namespace_id":"bbbbb","timestamp":1,"mount_accessor":"auth_3"} +{"client_id":"111122222-3333-4444-5555-000000000013","namespace_id":"bbbbb","timestamp":2,"mount_accessor":"auth_3"} +{"client_id":"111122222-3333-4444-5555-000000000014","namespace_id":"bbbbb","timestamp":2,"mount_accessor":"auth_3"} +{"client_id":"111122222-3333-4444-5555-000000000015","namespace_id":"root","timestamp":2,"mount_accessor":"auth_4"} +{"client_id":"111122222-3333-4444-5555-000000000016","namespace_id":"root","timestamp":2,"mount_accessor":"auth_4"} +{"client_id":"111122222-3333-4444-5555-000000000017","namespace_id":"root","timestamp":2,"mount_accessor":"auth_4"} +{"client_id":"111122222-3333-4444-5555-000000000018","namespace_id":"root","timestamp":2,"mount_accessor":"auth_4"} +{"client_id":"111122222-3333-4444-5555-000000000019","namespace_id":"root","timestamp":2,"mount_accessor":"auth_4"} diff --git a/vault/activity/test_fixtures/aug_oct.csv b/vault/activity/test_fixtures/aug_oct.csv new file mode 100644 index 000000000..d7a3848b1 --- /dev/null +++ b/vault/activity/test_fixtures/aug_oct.csv @@ -0,0 +1,41 @@ +client_id,namespace_id,timestamp,non_entity,mount_accessor +111122222-3333-4444-5555-000000000000,root,1,false,auth_1 +111122222-3333-4444-5555-000000000001,root,1,false,auth_1 +111122222-3333-4444-5555-000000000002,root,1,false,auth_1 +111122222-3333-4444-5555-000000000003,root,1,false,auth_1 +111122222-3333-4444-5555-000000000004,root,1,false,auth_1 +111122222-3333-4444-5555-000000000005,aaaaa,1,false,auth_2 +111122222-3333-4444-5555-000000000006,aaaaa,1,false,auth_2 +111122222-3333-4444-5555-000000000007,aaaaa,1,false,auth_2 +111122222-3333-4444-5555-000000000008,aaaaa,1,false,auth_2 +111122222-3333-4444-5555-000000000009,aaaaa,1,false,auth_2 +111122222-3333-4444-5555-000000000010,bbbbb,1,false,auth_3 +111122222-3333-4444-5555-000000000011,bbbbb,1,false,auth_3 +111122222-3333-4444-5555-000000000012,bbbbb,1,false,auth_3 +111122222-3333-4444-5555-000000000013,bbbbb,2,false,auth_3 +111122222-3333-4444-5555-000000000014,bbbbb,2,false,auth_3 +111122222-3333-4444-5555-000000000015,root,2,false,auth_4 +111122222-3333-4444-5555-000000000016,root,2,false,auth_4 +111122222-3333-4444-5555-000000000017,root,2,false,auth_4 +111122222-3333-4444-5555-000000000018,root,2,false,auth_4 +111122222-3333-4444-5555-000000000019,root,2,false,auth_4 +111122222-3333-4444-5555-000000000020,root,3,false,auth_5 +111122222-3333-4444-5555-000000000021,root,3,false,auth_5 +111122222-3333-4444-5555-000000000022,root,3,false,auth_5 +111122222-3333-4444-5555-000000000023,root,3,false,auth_5 +111122222-3333-4444-5555-000000000024,root,3,false,auth_5 +111122222-3333-4444-5555-000000000025,ccccc,3,false,auth_6 +111122222-3333-4444-5555-000000000026,ccccc,3,false,auth_6 +111122222-3333-4444-5555-000000000027,ccccc,3,false,auth_6 +111122222-3333-4444-5555-000000000028,ccccc,3,false,auth_6 +111122222-3333-4444-5555-000000000029,ccccc,3,false,auth_6 +111122222-3333-4444-5555-000000000030,root,4,false,auth_7 +111122222-3333-4444-5555-000000000031,root,4,false,auth_7 +111122222-3333-4444-5555-000000000032,root,4,false,auth_7 +111122222-3333-4444-5555-000000000033,root,4,false,auth_7 +111122222-3333-4444-5555-000000000034,root,4,false,auth_7 +111122222-3333-4444-5555-000000000035,bbbbb,4,false,auth_8 +111122222-3333-4444-5555-000000000036,bbbbb,4,false,auth_8 +111122222-3333-4444-5555-000000000037,bbbbb,4,false,auth_8 +111122222-3333-4444-5555-000000000038,bbbbb,4,false,auth_8 +111122222-3333-4444-5555-000000000039,bbbbb,4,false,auth_8 diff --git a/vault/activity/test_fixtures/aug_oct.json b/vault/activity/test_fixtures/aug_oct.json new file mode 100644 index 000000000..011b9f6d8 --- /dev/null +++ b/vault/activity/test_fixtures/aug_oct.json @@ -0,0 +1,40 @@ +{"client_id":"111122222-3333-4444-5555-000000000000","namespace_id":"root","timestamp":1,"mount_accessor":"auth_1"} +{"client_id":"111122222-3333-4444-5555-000000000001","namespace_id":"root","timestamp":1,"mount_accessor":"auth_1"} +{"client_id":"111122222-3333-4444-5555-000000000002","namespace_id":"root","timestamp":1,"mount_accessor":"auth_1"} +{"client_id":"111122222-3333-4444-5555-000000000003","namespace_id":"root","timestamp":1,"mount_accessor":"auth_1"} +{"client_id":"111122222-3333-4444-5555-000000000004","namespace_id":"root","timestamp":1,"mount_accessor":"auth_1"} +{"client_id":"111122222-3333-4444-5555-000000000005","namespace_id":"aaaaa","timestamp":1,"mount_accessor":"auth_2"} +{"client_id":"111122222-3333-4444-5555-000000000006","namespace_id":"aaaaa","timestamp":1,"mount_accessor":"auth_2"} +{"client_id":"111122222-3333-4444-5555-000000000007","namespace_id":"aaaaa","timestamp":1,"mount_accessor":"auth_2"} +{"client_id":"111122222-3333-4444-5555-000000000008","namespace_id":"aaaaa","timestamp":1,"mount_accessor":"auth_2"} +{"client_id":"111122222-3333-4444-5555-000000000009","namespace_id":"aaaaa","timestamp":1,"mount_accessor":"auth_2"} +{"client_id":"111122222-3333-4444-5555-000000000010","namespace_id":"bbbbb","timestamp":1,"mount_accessor":"auth_3"} +{"client_id":"111122222-3333-4444-5555-000000000011","namespace_id":"bbbbb","timestamp":1,"mount_accessor":"auth_3"} +{"client_id":"111122222-3333-4444-5555-000000000012","namespace_id":"bbbbb","timestamp":1,"mount_accessor":"auth_3"} +{"client_id":"111122222-3333-4444-5555-000000000013","namespace_id":"bbbbb","timestamp":2,"mount_accessor":"auth_3"} +{"client_id":"111122222-3333-4444-5555-000000000014","namespace_id":"bbbbb","timestamp":2,"mount_accessor":"auth_3"} +{"client_id":"111122222-3333-4444-5555-000000000015","namespace_id":"root","timestamp":2,"mount_accessor":"auth_4"} +{"client_id":"111122222-3333-4444-5555-000000000016","namespace_id":"root","timestamp":2,"mount_accessor":"auth_4"} +{"client_id":"111122222-3333-4444-5555-000000000017","namespace_id":"root","timestamp":2,"mount_accessor":"auth_4"} +{"client_id":"111122222-3333-4444-5555-000000000018","namespace_id":"root","timestamp":2,"mount_accessor":"auth_4"} +{"client_id":"111122222-3333-4444-5555-000000000019","namespace_id":"root","timestamp":2,"mount_accessor":"auth_4"} +{"client_id":"111122222-3333-4444-5555-000000000020","namespace_id":"root","timestamp":3,"mount_accessor":"auth_5"} +{"client_id":"111122222-3333-4444-5555-000000000021","namespace_id":"root","timestamp":3,"mount_accessor":"auth_5"} +{"client_id":"111122222-3333-4444-5555-000000000022","namespace_id":"root","timestamp":3,"mount_accessor":"auth_5"} +{"client_id":"111122222-3333-4444-5555-000000000023","namespace_id":"root","timestamp":3,"mount_accessor":"auth_5"} +{"client_id":"111122222-3333-4444-5555-000000000024","namespace_id":"root","timestamp":3,"mount_accessor":"auth_5"} +{"client_id":"111122222-3333-4444-5555-000000000025","namespace_id":"ccccc","timestamp":3,"mount_accessor":"auth_6"} +{"client_id":"111122222-3333-4444-5555-000000000026","namespace_id":"ccccc","timestamp":3,"mount_accessor":"auth_6"} +{"client_id":"111122222-3333-4444-5555-000000000027","namespace_id":"ccccc","timestamp":3,"mount_accessor":"auth_6"} +{"client_id":"111122222-3333-4444-5555-000000000028","namespace_id":"ccccc","timestamp":3,"mount_accessor":"auth_6"} +{"client_id":"111122222-3333-4444-5555-000000000029","namespace_id":"ccccc","timestamp":3,"mount_accessor":"auth_6"} +{"client_id":"111122222-3333-4444-5555-000000000030","namespace_id":"root","timestamp":4,"mount_accessor":"auth_7"} +{"client_id":"111122222-3333-4444-5555-000000000031","namespace_id":"root","timestamp":4,"mount_accessor":"auth_7"} +{"client_id":"111122222-3333-4444-5555-000000000032","namespace_id":"root","timestamp":4,"mount_accessor":"auth_7"} +{"client_id":"111122222-3333-4444-5555-000000000033","namespace_id":"root","timestamp":4,"mount_accessor":"auth_7"} +{"client_id":"111122222-3333-4444-5555-000000000034","namespace_id":"root","timestamp":4,"mount_accessor":"auth_7"} +{"client_id":"111122222-3333-4444-5555-000000000035","namespace_id":"bbbbb","timestamp":4,"mount_accessor":"auth_8"} +{"client_id":"111122222-3333-4444-5555-000000000036","namespace_id":"bbbbb","timestamp":4,"mount_accessor":"auth_8"} +{"client_id":"111122222-3333-4444-5555-000000000037","namespace_id":"bbbbb","timestamp":4,"mount_accessor":"auth_8"} +{"client_id":"111122222-3333-4444-5555-000000000038","namespace_id":"bbbbb","timestamp":4,"mount_accessor":"auth_8"} +{"client_id":"111122222-3333-4444-5555-000000000039","namespace_id":"bbbbb","timestamp":4,"mount_accessor":"auth_8"} diff --git a/vault/activity/test_fixtures/aug_sep.csv b/vault/activity/test_fixtures/aug_sep.csv new file mode 100644 index 000000000..34549f8ed --- /dev/null +++ b/vault/activity/test_fixtures/aug_sep.csv @@ -0,0 +1,31 @@ +client_id,namespace_id,timestamp,non_entity,mount_accessor +111122222-3333-4444-5555-000000000000,root,1,false,auth_1 +111122222-3333-4444-5555-000000000001,root,1,false,auth_1 +111122222-3333-4444-5555-000000000002,root,1,false,auth_1 +111122222-3333-4444-5555-000000000003,root,1,false,auth_1 +111122222-3333-4444-5555-000000000004,root,1,false,auth_1 +111122222-3333-4444-5555-000000000005,aaaaa,1,false,auth_2 +111122222-3333-4444-5555-000000000006,aaaaa,1,false,auth_2 +111122222-3333-4444-5555-000000000007,aaaaa,1,false,auth_2 +111122222-3333-4444-5555-000000000008,aaaaa,1,false,auth_2 +111122222-3333-4444-5555-000000000009,aaaaa,1,false,auth_2 +111122222-3333-4444-5555-000000000010,bbbbb,1,false,auth_3 +111122222-3333-4444-5555-000000000011,bbbbb,1,false,auth_3 +111122222-3333-4444-5555-000000000012,bbbbb,1,false,auth_3 +111122222-3333-4444-5555-000000000013,bbbbb,2,false,auth_3 +111122222-3333-4444-5555-000000000014,bbbbb,2,false,auth_3 +111122222-3333-4444-5555-000000000015,root,2,false,auth_4 +111122222-3333-4444-5555-000000000016,root,2,false,auth_4 +111122222-3333-4444-5555-000000000017,root,2,false,auth_4 +111122222-3333-4444-5555-000000000018,root,2,false,auth_4 +111122222-3333-4444-5555-000000000019,root,2,false,auth_4 +111122222-3333-4444-5555-000000000020,root,3,false,auth_5 +111122222-3333-4444-5555-000000000021,root,3,false,auth_5 +111122222-3333-4444-5555-000000000022,root,3,false,auth_5 +111122222-3333-4444-5555-000000000023,root,3,false,auth_5 +111122222-3333-4444-5555-000000000024,root,3,false,auth_5 +111122222-3333-4444-5555-000000000025,ccccc,3,false,auth_6 +111122222-3333-4444-5555-000000000026,ccccc,3,false,auth_6 +111122222-3333-4444-5555-000000000027,ccccc,3,false,auth_6 +111122222-3333-4444-5555-000000000028,ccccc,3,false,auth_6 +111122222-3333-4444-5555-000000000029,ccccc,3,false,auth_6 diff --git a/vault/activity/test_fixtures/aug_sep.json b/vault/activity/test_fixtures/aug_sep.json new file mode 100644 index 000000000..fb3a52193 --- /dev/null +++ b/vault/activity/test_fixtures/aug_sep.json @@ -0,0 +1,30 @@ +{"client_id":"111122222-3333-4444-5555-000000000000","namespace_id":"root","timestamp":1,"mount_accessor":"auth_1"} +{"client_id":"111122222-3333-4444-5555-000000000001","namespace_id":"root","timestamp":1,"mount_accessor":"auth_1"} +{"client_id":"111122222-3333-4444-5555-000000000002","namespace_id":"root","timestamp":1,"mount_accessor":"auth_1"} +{"client_id":"111122222-3333-4444-5555-000000000003","namespace_id":"root","timestamp":1,"mount_accessor":"auth_1"} +{"client_id":"111122222-3333-4444-5555-000000000004","namespace_id":"root","timestamp":1,"mount_accessor":"auth_1"} +{"client_id":"111122222-3333-4444-5555-000000000005","namespace_id":"aaaaa","timestamp":1,"mount_accessor":"auth_2"} +{"client_id":"111122222-3333-4444-5555-000000000006","namespace_id":"aaaaa","timestamp":1,"mount_accessor":"auth_2"} +{"client_id":"111122222-3333-4444-5555-000000000007","namespace_id":"aaaaa","timestamp":1,"mount_accessor":"auth_2"} +{"client_id":"111122222-3333-4444-5555-000000000008","namespace_id":"aaaaa","timestamp":1,"mount_accessor":"auth_2"} +{"client_id":"111122222-3333-4444-5555-000000000009","namespace_id":"aaaaa","timestamp":1,"mount_accessor":"auth_2"} +{"client_id":"111122222-3333-4444-5555-000000000010","namespace_id":"bbbbb","timestamp":1,"mount_accessor":"auth_3"} +{"client_id":"111122222-3333-4444-5555-000000000011","namespace_id":"bbbbb","timestamp":1,"mount_accessor":"auth_3"} +{"client_id":"111122222-3333-4444-5555-000000000012","namespace_id":"bbbbb","timestamp":1,"mount_accessor":"auth_3"} +{"client_id":"111122222-3333-4444-5555-000000000013","namespace_id":"bbbbb","timestamp":2,"mount_accessor":"auth_3"} +{"client_id":"111122222-3333-4444-5555-000000000014","namespace_id":"bbbbb","timestamp":2,"mount_accessor":"auth_3"} +{"client_id":"111122222-3333-4444-5555-000000000015","namespace_id":"root","timestamp":2,"mount_accessor":"auth_4"} +{"client_id":"111122222-3333-4444-5555-000000000016","namespace_id":"root","timestamp":2,"mount_accessor":"auth_4"} +{"client_id":"111122222-3333-4444-5555-000000000017","namespace_id":"root","timestamp":2,"mount_accessor":"auth_4"} +{"client_id":"111122222-3333-4444-5555-000000000018","namespace_id":"root","timestamp":2,"mount_accessor":"auth_4"} +{"client_id":"111122222-3333-4444-5555-000000000019","namespace_id":"root","timestamp":2,"mount_accessor":"auth_4"} +{"client_id":"111122222-3333-4444-5555-000000000020","namespace_id":"root","timestamp":3,"mount_accessor":"auth_5"} +{"client_id":"111122222-3333-4444-5555-000000000021","namespace_id":"root","timestamp":3,"mount_accessor":"auth_5"} +{"client_id":"111122222-3333-4444-5555-000000000022","namespace_id":"root","timestamp":3,"mount_accessor":"auth_5"} +{"client_id":"111122222-3333-4444-5555-000000000023","namespace_id":"root","timestamp":3,"mount_accessor":"auth_5"} +{"client_id":"111122222-3333-4444-5555-000000000024","namespace_id":"root","timestamp":3,"mount_accessor":"auth_5"} +{"client_id":"111122222-3333-4444-5555-000000000025","namespace_id":"ccccc","timestamp":3,"mount_accessor":"auth_6"} +{"client_id":"111122222-3333-4444-5555-000000000026","namespace_id":"ccccc","timestamp":3,"mount_accessor":"auth_6"} +{"client_id":"111122222-3333-4444-5555-000000000027","namespace_id":"ccccc","timestamp":3,"mount_accessor":"auth_6"} +{"client_id":"111122222-3333-4444-5555-000000000028","namespace_id":"ccccc","timestamp":3,"mount_accessor":"auth_6"} +{"client_id":"111122222-3333-4444-5555-000000000029","namespace_id":"ccccc","timestamp":3,"mount_accessor":"auth_6"} diff --git a/vault/activity/test_fixtures/full_history.csv b/vault/activity/test_fixtures/full_history.csv new file mode 100644 index 000000000..7a37ed236 --- /dev/null +++ b/vault/activity/test_fixtures/full_history.csv @@ -0,0 +1,46 @@ +client_id,namespace_id,timestamp,non_entity,mount_accessor +111122222-3333-4444-5555-000000000040,rrrrr,0,false,auth_9 +111122222-3333-4444-5555-000000000041,rrrrr,0,false,auth_9 +111122222-3333-4444-5555-000000000042,rrrrr,0,false,auth_9 +111122222-3333-4444-5555-000000000043,rrrrr,0,false,auth_9 +111122222-3333-4444-5555-000000000044,rrrrr,0,false,auth_9 +111122222-3333-4444-5555-000000000000,root,1,false,auth_1 +111122222-3333-4444-5555-000000000001,root,1,false,auth_1 +111122222-3333-4444-5555-000000000002,root,1,false,auth_1 +111122222-3333-4444-5555-000000000003,root,1,false,auth_1 +111122222-3333-4444-5555-000000000004,root,1,false,auth_1 +111122222-3333-4444-5555-000000000005,aaaaa,1,false,auth_2 +111122222-3333-4444-5555-000000000006,aaaaa,1,false,auth_2 +111122222-3333-4444-5555-000000000007,aaaaa,1,false,auth_2 +111122222-3333-4444-5555-000000000008,aaaaa,1,false,auth_2 +111122222-3333-4444-5555-000000000009,aaaaa,1,false,auth_2 +111122222-3333-4444-5555-000000000010,bbbbb,1,false,auth_3 +111122222-3333-4444-5555-000000000011,bbbbb,1,false,auth_3 +111122222-3333-4444-5555-000000000012,bbbbb,1,false,auth_3 +111122222-3333-4444-5555-000000000013,bbbbb,2,false,auth_3 +111122222-3333-4444-5555-000000000014,bbbbb,2,false,auth_3 +111122222-3333-4444-5555-000000000015,root,2,false,auth_4 +111122222-3333-4444-5555-000000000016,root,2,false,auth_4 +111122222-3333-4444-5555-000000000017,root,2,false,auth_4 +111122222-3333-4444-5555-000000000018,root,2,false,auth_4 +111122222-3333-4444-5555-000000000019,root,2,false,auth_4 +111122222-3333-4444-5555-000000000020,root,3,false,auth_5 +111122222-3333-4444-5555-000000000021,root,3,false,auth_5 +111122222-3333-4444-5555-000000000022,root,3,false,auth_5 +111122222-3333-4444-5555-000000000023,root,3,false,auth_5 +111122222-3333-4444-5555-000000000024,root,3,false,auth_5 +111122222-3333-4444-5555-000000000025,ccccc,3,false,auth_6 +111122222-3333-4444-5555-000000000026,ccccc,3,false,auth_6 +111122222-3333-4444-5555-000000000027,ccccc,3,false,auth_6 +111122222-3333-4444-5555-000000000028,ccccc,3,false,auth_6 +111122222-3333-4444-5555-000000000029,ccccc,3,false,auth_6 +111122222-3333-4444-5555-000000000030,root,4,false,auth_7 +111122222-3333-4444-5555-000000000031,root,4,false,auth_7 +111122222-3333-4444-5555-000000000032,root,4,false,auth_7 +111122222-3333-4444-5555-000000000033,root,4,false,auth_7 +111122222-3333-4444-5555-000000000034,root,4,false,auth_7 +111122222-3333-4444-5555-000000000035,bbbbb,4,false,auth_8 +111122222-3333-4444-5555-000000000036,bbbbb,4,false,auth_8 +111122222-3333-4444-5555-000000000037,bbbbb,4,false,auth_8 +111122222-3333-4444-5555-000000000038,bbbbb,4,false,auth_8 +111122222-3333-4444-5555-000000000039,bbbbb,4,false,auth_8 diff --git a/vault/activity/test_fixtures/full_history.json b/vault/activity/test_fixtures/full_history.json new file mode 100644 index 000000000..7516adfad --- /dev/null +++ b/vault/activity/test_fixtures/full_history.json @@ -0,0 +1,45 @@ +{"client_id":"111122222-3333-4444-5555-000000000040","namespace_id":"rrrrr","mount_accessor":"auth_9"} +{"client_id":"111122222-3333-4444-5555-000000000041","namespace_id":"rrrrr","mount_accessor":"auth_9"} +{"client_id":"111122222-3333-4444-5555-000000000042","namespace_id":"rrrrr","mount_accessor":"auth_9"} +{"client_id":"111122222-3333-4444-5555-000000000043","namespace_id":"rrrrr","mount_accessor":"auth_9"} +{"client_id":"111122222-3333-4444-5555-000000000044","namespace_id":"rrrrr","mount_accessor":"auth_9"} +{"client_id":"111122222-3333-4444-5555-000000000000","namespace_id":"root","timestamp":1,"mount_accessor":"auth_1"} +{"client_id":"111122222-3333-4444-5555-000000000001","namespace_id":"root","timestamp":1,"mount_accessor":"auth_1"} +{"client_id":"111122222-3333-4444-5555-000000000002","namespace_id":"root","timestamp":1,"mount_accessor":"auth_1"} +{"client_id":"111122222-3333-4444-5555-000000000003","namespace_id":"root","timestamp":1,"mount_accessor":"auth_1"} +{"client_id":"111122222-3333-4444-5555-000000000004","namespace_id":"root","timestamp":1,"mount_accessor":"auth_1"} +{"client_id":"111122222-3333-4444-5555-000000000005","namespace_id":"aaaaa","timestamp":1,"mount_accessor":"auth_2"} +{"client_id":"111122222-3333-4444-5555-000000000006","namespace_id":"aaaaa","timestamp":1,"mount_accessor":"auth_2"} +{"client_id":"111122222-3333-4444-5555-000000000007","namespace_id":"aaaaa","timestamp":1,"mount_accessor":"auth_2"} +{"client_id":"111122222-3333-4444-5555-000000000008","namespace_id":"aaaaa","timestamp":1,"mount_accessor":"auth_2"} +{"client_id":"111122222-3333-4444-5555-000000000009","namespace_id":"aaaaa","timestamp":1,"mount_accessor":"auth_2"} +{"client_id":"111122222-3333-4444-5555-000000000010","namespace_id":"bbbbb","timestamp":1,"mount_accessor":"auth_3"} +{"client_id":"111122222-3333-4444-5555-000000000011","namespace_id":"bbbbb","timestamp":1,"mount_accessor":"auth_3"} +{"client_id":"111122222-3333-4444-5555-000000000012","namespace_id":"bbbbb","timestamp":1,"mount_accessor":"auth_3"} +{"client_id":"111122222-3333-4444-5555-000000000013","namespace_id":"bbbbb","timestamp":2,"mount_accessor":"auth_3"} +{"client_id":"111122222-3333-4444-5555-000000000014","namespace_id":"bbbbb","timestamp":2,"mount_accessor":"auth_3"} +{"client_id":"111122222-3333-4444-5555-000000000015","namespace_id":"root","timestamp":2,"mount_accessor":"auth_4"} +{"client_id":"111122222-3333-4444-5555-000000000016","namespace_id":"root","timestamp":2,"mount_accessor":"auth_4"} +{"client_id":"111122222-3333-4444-5555-000000000017","namespace_id":"root","timestamp":2,"mount_accessor":"auth_4"} +{"client_id":"111122222-3333-4444-5555-000000000018","namespace_id":"root","timestamp":2,"mount_accessor":"auth_4"} +{"client_id":"111122222-3333-4444-5555-000000000019","namespace_id":"root","timestamp":2,"mount_accessor":"auth_4"} +{"client_id":"111122222-3333-4444-5555-000000000020","namespace_id":"root","timestamp":3,"mount_accessor":"auth_5"} +{"client_id":"111122222-3333-4444-5555-000000000021","namespace_id":"root","timestamp":3,"mount_accessor":"auth_5"} +{"client_id":"111122222-3333-4444-5555-000000000022","namespace_id":"root","timestamp":3,"mount_accessor":"auth_5"} +{"client_id":"111122222-3333-4444-5555-000000000023","namespace_id":"root","timestamp":3,"mount_accessor":"auth_5"} +{"client_id":"111122222-3333-4444-5555-000000000024","namespace_id":"root","timestamp":3,"mount_accessor":"auth_5"} +{"client_id":"111122222-3333-4444-5555-000000000025","namespace_id":"ccccc","timestamp":3,"mount_accessor":"auth_6"} +{"client_id":"111122222-3333-4444-5555-000000000026","namespace_id":"ccccc","timestamp":3,"mount_accessor":"auth_6"} +{"client_id":"111122222-3333-4444-5555-000000000027","namespace_id":"ccccc","timestamp":3,"mount_accessor":"auth_6"} +{"client_id":"111122222-3333-4444-5555-000000000028","namespace_id":"ccccc","timestamp":3,"mount_accessor":"auth_6"} +{"client_id":"111122222-3333-4444-5555-000000000029","namespace_id":"ccccc","timestamp":3,"mount_accessor":"auth_6"} +{"client_id":"111122222-3333-4444-5555-000000000030","namespace_id":"root","timestamp":4,"mount_accessor":"auth_7"} +{"client_id":"111122222-3333-4444-5555-000000000031","namespace_id":"root","timestamp":4,"mount_accessor":"auth_7"} +{"client_id":"111122222-3333-4444-5555-000000000032","namespace_id":"root","timestamp":4,"mount_accessor":"auth_7"} +{"client_id":"111122222-3333-4444-5555-000000000033","namespace_id":"root","timestamp":4,"mount_accessor":"auth_7"} +{"client_id":"111122222-3333-4444-5555-000000000034","namespace_id":"root","timestamp":4,"mount_accessor":"auth_7"} +{"client_id":"111122222-3333-4444-5555-000000000035","namespace_id":"bbbbb","timestamp":4,"mount_accessor":"auth_8"} +{"client_id":"111122222-3333-4444-5555-000000000036","namespace_id":"bbbbb","timestamp":4,"mount_accessor":"auth_8"} +{"client_id":"111122222-3333-4444-5555-000000000037","namespace_id":"bbbbb","timestamp":4,"mount_accessor":"auth_8"} +{"client_id":"111122222-3333-4444-5555-000000000038","namespace_id":"bbbbb","timestamp":4,"mount_accessor":"auth_8"} +{"client_id":"111122222-3333-4444-5555-000000000039","namespace_id":"bbbbb","timestamp":4,"mount_accessor":"auth_8"} diff --git a/vault/activity_log.go b/vault/activity_log.go index 68fb88958..7e334a2db 100644 --- a/vault/activity_log.go +++ b/vault/activity_log.go @@ -2,9 +2,12 @@ package vault import ( "context" + "encoding/csv" "encoding/json" "errors" "fmt" + "io" + "net/http" "os" "sort" "strconv" @@ -20,6 +23,7 @@ import ( "github.com/hashicorp/vault/helper/timeutil" "github.com/hashicorp/vault/sdk/logical" "github.com/hashicorp/vault/vault/activity" + "go.uber.org/atomic" ) const ( @@ -160,6 +164,8 @@ type ActivityLog struct { // partialMonthClientTracker tracks active clients this month. Protected by fragmentLock. partialMonthClientTracker map[string]*activity.EntityRecord + + inprocessExport *atomic.Bool } // These non-persistent configuration options allow us to disable @@ -207,6 +213,7 @@ func NewActivityLog(core *Core, logger log.Logger, view *BarrierView, metrics me clientSequenceNumber: 0, }, standbyFragmentsReceived: make([]*activity.LogFragment, 0), + inprocessExport: atomic.NewBool(false), } config, err := a.loadConfigOrDefault(core.activeContext) @@ -525,10 +532,7 @@ func (a *ActivityLog) getLastEntitySegmentNumber(ctx context.Context, startTime } // WalkEntitySegments loads each of the entity segments for a particular start time -func (a *ActivityLog) WalkEntitySegments(ctx context.Context, - startTime time.Time, - walkFn func(*activity.EntityActivityLog, time.Time), -) error { +func (a *ActivityLog) WalkEntitySegments(ctx context.Context, startTime time.Time, walkFn func(*activity.EntityActivityLog, time.Time) error) error { basePath := activityEntityBasePath + fmt.Sprint(startTime.Unix()) + "/" pathList, err := a.view.List(ctx, basePath) if err != nil { @@ -550,7 +554,10 @@ func (a *ActivityLog) WalkEntitySegments(ctx context.Context, if err != nil { return fmt.Errorf("unable to parse segment %v%v: %w", basePath, path, err) } - walkFn(out, startTime) + err = walkFn(out, startTime) + if err != nil { + return fmt.Errorf("unable to walk entities: %w", err) + } } return nil } @@ -2054,7 +2061,7 @@ func (a *ActivityLog) precomputedQueryWorker(ctx context.Context) error { byNamespace := make(map[string]*processByNamespace) byMonth := make(map[int64]*processMonth) - walkEntities := func(l *activity.EntityActivityLog, startTime time.Time) { + walkEntities := func(l *activity.EntityActivityLog, startTime time.Time) error { for _, e := range l.Clients { processClientRecord(e, byNamespace, byMonth, startTime) @@ -2102,6 +2109,8 @@ func (a *ActivityLog) precomputedQueryWorker(ctx context.Context) error { } } } + + return nil } walkTokens := func(l *activity.TokenCount) { @@ -2569,3 +2578,163 @@ func (a *ActivityLog) partialMonthClientCount(ctx context.Context) (map[string]i return responseData, nil } + +func (a *ActivityLog) writeExport(ctx context.Context, rw http.ResponseWriter, format string, startTime, endTime time.Time) error { + // For capacity reasons only allow a single in-process export at a time. + // TODO do we really need to do this? + if !a.inprocessExport.CAS(false, true) { + return fmt.Errorf("existing export in progress") + } + defer a.inprocessExport.Store(false) + + // Find the months with activity log data that are between the start and end + // months. We want to walk this in cronological order so the oldest instance of a + // client usage is recorded, not the most recent. + times, err := a.getMostRecentNonContiguousActivityLogSegments(ctx) + if err != nil { + a.logger.Warn("failed to list recent segments", "error", err) + return fmt.Errorf("failed to list recent segments: %w", err) + } + sort.Slice(times, func(i, j int) bool { + // sort in chronological order to produce the output we want showing what + // month an entity first had activity. + return times[i].Before(times[j]) + }) + + // Filter over just the months we care about + filteredList := make([]time.Time, 0, len(times)) + for _, t := range times { + if timeutil.InRange(t, startTime, endTime) { + filteredList = append(filteredList, t) + } + } + if len(filteredList) == 0 { + a.logger.Info("no data to export", "start_time", startTime, "end_time", endTime) + return fmt.Errorf("no data to export in provided time range") + } + + actualStartTime := filteredList[len(filteredList)-1] + a.logger.Trace("choose start time for export", "actualStartTime", actualStartTime, "months_included", filteredList) + + // Add headers here because we start to immediately write in the csv encoder + // constructor. + rw.Header().Add("Content-Disposition", fmt.Sprintf("attachment; filename=\"activity_export_%d_to_%d.%s\"", actualStartTime.Unix(), endTime.Unix(), format)) + rw.Header().Add("Content-Type", fmt.Sprintf("application/%s", format)) + + var encoder encoder + switch format { + case "json": + encoder = newJSONEncoder(rw) + case "csv": + var err error + encoder, err = newCSVEncoder(rw) + if err != nil { + return fmt.Errorf("failed to create csv encoder: %w", err) + } + default: + return fmt.Errorf("invalid format: %s", format) + } + + a.logger.Info("starting activity log export", "start_time", startTime, "end_time", endTime, "format", format) + + dedupedIds := make(map[string]struct{}) + walkEntities := func(l *activity.EntityActivityLog, startTime time.Time) error { + for _, e := range l.Clients { + if _, ok := dedupedIds[e.ClientID]; ok { + continue + } + + dedupedIds[e.ClientID] = struct{}{} + err := encoder.Encode(e) + if err != nil { + return err + } + } + + return nil + } + + // For each month in the filtered list walk all the log segments + for _, startTime := range filteredList { + err := a.WalkEntitySegments(ctx, startTime, walkEntities) + if err != nil { + a.logger.Error("failed to load segments for export", "error", err) + return fmt.Errorf("failed to load segments for export: %w", err) + } + } + + // Flush and error check the encoder. This is neccessary for buffered + // encoders like csv. + encoder.Flush() + if err := encoder.Error(); err != nil { + a.logger.Error("failed to flush export encoding", "error", err) + return fmt.Errorf("failed to flush export encoding: %w", err) + } + + return nil +} + +type encoder interface { + Encode(*activity.EntityRecord) error + Flush() + Error() error +} + +var _ encoder = (*jsonEncoder)(nil) + +type jsonEncoder struct { + e *json.Encoder +} + +func newJSONEncoder(w io.Writer) *jsonEncoder { + return &jsonEncoder{ + e: json.NewEncoder(w), + } +} + +func (j *jsonEncoder) Encode(er *activity.EntityRecord) error { + return j.e.Encode(er) +} + +// Flush is a no-op because json.Encoder doesn't buffer data +func (j *jsonEncoder) Flush() {} + +// Error is a no-op because flushing is a no-op. +func (j *jsonEncoder) Error() error { return nil } + +var _ encoder = (*csvEncoder)(nil) + +type csvEncoder struct { + *csv.Writer +} + +func newCSVEncoder(w io.Writer) (*csvEncoder, error) { + writer := csv.NewWriter(w) + + err := writer.Write([]string{ + "client_id", + "namespace_id", + "timestamp", + "non_entity", + "mount_accessor", + }) + if err != nil { + return nil, err + } + + return &csvEncoder{ + Writer: writer, + }, nil +} + +// Encode converts an export bundle into a set of strings and writes them to the +// csv writer. +func (c *csvEncoder) Encode(e *activity.EntityRecord) error { + return c.Writer.Write([]string{ + e.ClientID, + e.NamespaceID, + fmt.Sprintf("%d", e.Timestamp), + fmt.Sprintf("%t", e.NonEntity), + e.MountAccessor, + }) +} diff --git a/vault/activity_log_test.go b/vault/activity_log_test.go index 044433cd5..53d69c9d2 100644 --- a/vault/activity_log_test.go +++ b/vault/activity_log_test.go @@ -1,10 +1,14 @@ package vault import ( + "bytes" "context" "encoding/json" "errors" "fmt" + "net/http" + "os" + "path/filepath" "reflect" "sort" "strconv" @@ -1715,6 +1719,192 @@ func TestActivityLog_refreshFromStoredLogPreviousMonth(t *testing.T) { } } +func TestActivityLog_Export(t *testing.T) { + timeutil.SkipAtEndOfMonth(t) + + january := time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC) + august := time.Date(2020, 8, 15, 12, 0, 0, 0, time.UTC) + september := timeutil.StartOfMonth(time.Date(2020, 9, 1, 0, 0, 0, 0, time.UTC)) + october := timeutil.StartOfMonth(time.Date(2020, 10, 1, 0, 0, 0, 0, time.UTC)) + november := timeutil.StartOfMonth(time.Date(2020, 11, 1, 0, 0, 0, 0, time.UTC)) + + core, _, _, _ := TestCoreUnsealedWithMetrics(t) + a := core.activityLog + ctx := namespace.RootContext(nil) + + // Generate overlapping sets of entity IDs from this list. + // january: 40-44 RRRRR + // first month: 0-19 RRRRRAAAAABBBBBRRRRR + // second month: 10-29 BBBBBRRRRRRRRRRCCCCC + // third month: 15-39 RRRRRRRRRRCCCCCRRRRRBBBBB + + entityRecords := make([]*activity.EntityRecord, 45) + entityNamespaces := []string{"root", "aaaaa", "bbbbb", "root", "root", "ccccc", "root", "bbbbb", "rrrrr"} + authMethods := []string{"auth_1", "auth_2", "auth_3", "auth_4", "auth_5", "auth_6", "auth_7", "auth_8", "auth_9"} + + for i := range entityRecords { + entityRecords[i] = &activity.EntityRecord{ + ClientID: fmt.Sprintf("111122222-3333-4444-5555-%012v", i), + NamespaceID: entityNamespaces[i/5], + MountAccessor: authMethods[i/5], + } + } + + toInsert := []struct { + StartTime int64 + Segment uint64 + Clients []*activity.EntityRecord + }{ + // January, should not be included + { + january.Unix(), + 0, + entityRecords[40:45], + }, + // Artifically split August and October + { // 1 + august.Unix(), + 0, + entityRecords[:13], + }, + { // 2 + august.Unix(), + 1, + entityRecords[13:20], + }, + { // 3 + september.Unix(), + 0, + entityRecords[10:30], + }, + { // 4 + october.Unix(), + 0, + entityRecords[15:40], + }, + { + october.Unix(), + 1, + entityRecords[15:40], + }, + { + october.Unix(), + 2, + entityRecords[17:23], + }, + } + + for i, segment := range toInsert { + eal := &activity.EntityActivityLog{ + Clients: segment.Clients, + } + + // Mimic a lower time stamp for earlier clients + for _, c := range eal.Clients { + c.Timestamp = int64(i) + } + + data, err := proto.Marshal(eal) + if err != nil { + t.Fatal(err) + } + path := fmt.Sprintf("%ventity/%v/%v", ActivityLogPrefix, segment.StartTime, segment.Segment) + WriteToStorage(t, core, path, data) + } + + tCases := []struct { + format string + startTime time.Time + endTime time.Time + expected string + }{ + { + format: "json", + startTime: august, + endTime: timeutil.EndOfMonth(september), + expected: "aug_sep.json", + }, + { + format: "csv", + startTime: august, + endTime: timeutil.EndOfMonth(september), + expected: "aug_sep.csv", + }, + { + format: "json", + startTime: january, + endTime: timeutil.EndOfMonth(november), + expected: "full_history.json", + }, + { + format: "csv", + startTime: january, + endTime: timeutil.EndOfMonth(november), + expected: "full_history.csv", + }, + { + format: "json", + startTime: august, + endTime: timeutil.EndOfMonth(october), + expected: "aug_oct.json", + }, + { + format: "csv", + startTime: august, + endTime: timeutil.EndOfMonth(october), + expected: "aug_oct.csv", + }, + { + format: "json", + startTime: august, + endTime: timeutil.EndOfMonth(august), + expected: "aug.json", + }, + { + format: "csv", + startTime: august, + endTime: timeutil.EndOfMonth(august), + expected: "aug.csv", + }, + } + + for _, tCase := range tCases { + rw := &fakeResponseWriter{ + buffer: &bytes.Buffer{}, + headers: http.Header{}, + } + if err := a.writeExport(ctx, rw, tCase.format, tCase.startTime, tCase.endTime); err != nil { + t.Fatal(err) + } + + expected, err := os.ReadFile(filepath.Join("activity", "test_fixtures", tCase.expected)) + if err != nil { + t.Fatal(err) + } + + if !bytes.Equal(rw.buffer.Bytes(), expected) { + t.Fatal(rw.buffer.String()) + } + } +} + +type fakeResponseWriter struct { + buffer *bytes.Buffer + headers http.Header +} + +func (f *fakeResponseWriter) Write(b []byte) (int, error) { + return f.buffer.Write(b) +} + +func (f *fakeResponseWriter) Header() http.Header { + return f.headers +} + +func (f *fakeResponseWriter) WriteHeader(statusCode int) { + panic("unimplmeneted") +} + func TestActivityLog_IncludeNamespace(t *testing.T) { root := namespace.RootNamespace a := &ActivityLog{} diff --git a/vault/logical_system.go b/vault/logical_system.go index ec59e3858..4641a4227 100644 --- a/vault/logical_system.go +++ b/vault/logical_system.go @@ -5198,6 +5198,10 @@ This path responds to the following HTTP methods. "Query the historical count of clients.", "Query the historical count of clients.", }, + "activity-export": { + "Export the historical activity of clients.", + "Export the historical activity of clients.", + }, "activity-monthly": { "Count of active clients so far this month.", "Count of active clients so far this month.", diff --git a/vault/logical_system_activity.go b/vault/logical_system_activity.go index 2557a252e..77123f767 100644 --- a/vault/logical_system_activity.go +++ b/vault/logical_system_activity.go @@ -2,7 +2,9 @@ package vault import ( "context" + "fmt" "net/http" + "os" "path" "strings" "time" @@ -97,15 +99,37 @@ func (b *SystemBackend) rootActivityPaths() []*framework.Path { }, }, }, + { + Pattern: "internal/counters/activity/export$", + Fields: map[string]*framework.FieldSchema{ + "start_time": { + Type: framework.TypeTime, + Description: "Start of query interval", + }, + "end_time": { + Type: framework.TypeTime, + Description: "End of query interval", + }, + "format": { + Type: framework.TypeString, + Description: "Format of the file. Either a CSV or a JSON file with an object per line.", + Default: "json", + }, + }, + HelpSynopsis: strings.TrimSpace(sysHelp["activity-export"][0]), + HelpDescription: strings.TrimSpace(sysHelp["activity-export"][1]), + + Operations: map[logical.Operation]framework.OperationHandler{ + logical.ReadOperation: &framework.PathOperation{ + Callback: b.handleClientExport, + Summary: "Report the client count metrics, for this namespace and all child namespaces.", + }, + }, + }, } } -func (b *SystemBackend) handleClientMetricQuery(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) { - a := b.Core.activityLog - if a == nil { - return logical.ErrorResponse("no activity log present"), nil - } - +func parseStartEndTimes(a *ActivityLog, d *framework.FieldData) (time.Time, time.Time, error) { startTime := d.Get("start_time").(time.Time) endTime := d.Get("end_time").(time.Time) @@ -126,7 +150,52 @@ func (b *SystemBackend) handleClientMetricQuery(ctx context.Context, req *logica startTime = startTime.UTC() } if startTime.After(endTime) { - return logical.ErrorResponse("start_time is later than end_time"), nil + return time.Time{}, time.Time{}, fmt.Errorf("start_time is later than end_time") + } + + return startTime, endTime, nil +} + +func (b *SystemBackend) handleClientExport(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) { + a := b.Core.activityLog + if a == nil { + return logical.ErrorResponse("no activity log present"), nil + } + + startTime, endTime, err := parseStartEndTimes(a, d) + if err != nil { + return logical.ErrorResponse(err.Error()), nil + } + + // This is to avoid the default 90s context timeout. + timeout := 10 * time.Minute + if durationRaw := os.Getenv("VAULT_ACTIVITY_EXPORT_DURATION"); durationRaw != "" { + d, err := time.ParseDuration(durationRaw) + if err == nil { + timeout = d + } + } + + runCtx, cancelFunc := context.WithTimeout(b.Core.activeContext, timeout) + defer cancelFunc() + + err = a.writeExport(runCtx, req.ResponseWriter, d.Get("format").(string), startTime, endTime) + if err != nil { + return nil, err + } + + return nil, nil +} + +func (b *SystemBackend) handleClientMetricQuery(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) { + a := b.Core.activityLog + if a == nil { + return logical.ErrorResponse("no activity log present"), nil + } + + startTime, endTime, err := parseStartEndTimes(a, d) + if err != nil { + return logical.ErrorResponse(err.Error()), nil } results, err := a.handleQuery(ctx, startTime, endTime) diff --git a/website/content/api-docs/system/internal-counters.mdx b/website/content/api-docs/system/internal-counters.mdx index 451cde9c7..5624d504a 100644 --- a/website/content/api-docs/system/internal-counters.mdx +++ b/website/content/api-docs/system/internal-counters.mdx @@ -848,3 +848,58 @@ $ curl \ "warnings": null } ``` + +## Activity Export + +This endpoint returns an export of the clients that had activity within the +provided start and end times. The returned set of client information will be +deduplicated over the time window and will show the earliest activity logged for +each client. The output will be ordered chronologically by month of activity. + +~> **NOTE**: This endpoint is currently in tech preview status. + +There are a few things to keep in mind while using this API. + +- The response includes the actual time period covered, which may not exactly +match the query parameters due to the month granularity of data or missing +months in the requested time range. + +- If the `end_date` supplied to the API is for the current month, the activity +information returned by this API will include activity for this month, however +it may be up to 20 minutes delayed. + +This endpoint was added in Vault 1.11. + +| Method | Path | +| :----- | :---------------------------------------- | +| `GET` | `/sys/internal/counters/activity/export` | + +### Parameters + +- `start_time` `(string, optional)` - An RFC3339 timestamp or Unix epoch time. Specifies the start of the + period for which client counts will be reported. If no start time is specified, the `default_report_months` + prior to the `end_time` will be used. +- `end_time` `(string, optional)` - An RFC3339 timestamp or Unix epoch time. Specifies the end of the period + for which client counts will be reported. If no end time is specified, the end of the previous calendar + month will be used. +- `format` `(string, optional)` - The desired format of the output file. Allowed + values are `csv` and `json`. If no format is provided a default of `json` + will be used. + +### Sample Request + +```shell-session +$ curl \ + --header "X-Vault-Token: ..." \ + --request GET \ + http://127.0.0.1:8200/v1/sys/internal/counters/activity/export +``` + +### Sample Response + +```json +{"client_id":"3f210722-7210-98e8-1f0d-e6a39ffb29c6","namespace_id":"root","timestamp":1653350457,"mount_accessor":"auth_userpass_bb52979d"} +{"client_id":"X/Yed4Oj4cqODj9tSHjKwnRy5QVSBRlX3COxjjWSXyI=","namespace_id":"root","timestamp":1653350491,"non_entity":true,"mount_accessor":"auth_token_f6f2c11c"} +{"client_id":"d93405dc-b592-b1c3-a520-14e618d359c1","namespace_id":"root","timestamp":1653350501,"mount_accessor":"auth_userpass_bb52979d"} +``` +