Event accounting service
Contents
Requires EVA ICS Enterprise.
Functionality
The service provides node-local or cloud-wide event accounting. Accounting measures the resources used (modified) by a user during access, such as unit actions, lmacro executions, file management operations etc.
The audit trail contains also failed authentication attempts, unauthorized API usage, critical system messages sent by the core or services themselves.
Cloud-wide accounting
For cloud-wide accounting the local accounting lvar must be replicated between nodes.
Supported storages
SQLite (default, no database server required)
A system can have multiple event storages using multiple service instances. In such configuration only one service instance should have accounting_lvar_oid set in its configuration. Other services MUST NOT handle the lvar and work as data processors only.
Note
As ELK is commonly used to perform analysis using internal and 3rd party-compatible tools, no querying is supported for Elasticsearch storage kind.
Sending events from custom services
See Accounting.
Querying events from command-line
“accounting query” command of eva-shell provides command-line interface to view event audit trail. Use command arguments to apply required filters.
eva accounting query
Querying events using web UI
Event audit trail can be viewed in Node system dashboard, section “Events”:
Setup
Use the template EVA_DIR/share/svc-tpl/svc-tpl-aaa-accounting.yml:
# Accounting service
command: svc/eva-aaa-accounting
bus:
path: var/bus.ipc
config:
# lvar OID to store accounting info, super-group name MUST match node name,
# ${system_name} is automatically parsed
#
# There should be only once accounting service instance deployed with the same
# accounting_lvar_oid
#
# If lvar OID is not specified, the service does not accept bus accounting
# frames but can be used to store them e.g. in a different storage
#
# The lvar is created by the service automatically
#
# Important: make sure no regular users have access to accounting lvar
accounting_lvar_oid: "lvar:${system_name}/accounting"
# Subscription mask, must match chosen lvar OID
# If not specified, the service does not write any events into the database
subscribe_accounting_lvar_oids: lvar:+/accounting
# Accounting database, default: runtime/svc_data/<SVC_ID>/accounting.db
#db: sqlite:///tmp/accounting.db
#db: postgres://USER:PASSWORD@HOST/DB
#db: elasticsearch
#
# Elasticsearch requires configuration section:
#elasticsearch:
#index: eva-accounting
# for cloud set url to cloud id (cluster_name:...)
#url: http://localhost:9200
#
# authentication (optional, required for cloud), use a single section only
#auth:
#basic:
#login: username
#password: secret
#auth:
#bearer
#token: secret
#auth:
#api_key:
#id: key_id
#key: secret
# Event buffer size, events are DROPPED if overflown
event_buf_size: 16384
# keep events (seconds), if not set: keep forever, ignored for Elasticsearch
keep: 604800
# for PostgreSQL and Elasticsearch the service can work under "nobody"
user: eva
Create the service using eva-shell:
eva svc create eva.aaa.accounting /opt/eva4/share/svc-tpl/svc-tpl-aaa-accounting.yml
or using the bus CLI client:
cd /opt/eva4
cat DEPLOY.yml | ./bin/yml2mp | \
./sbin/bus ./var/bus.ipc rpc call eva.core svc.deploy -
(see eva.core::svc.deploy for more info)
EAPI methods
See EAPI commons for the common information about the bus, types, errors and RPC calls.
query
Description |
Query accounting events |
Parameters |
required |
Returns |
Events matching the filter |
Name |
Type |
Description |
Required |
t_start |
Time |
start time (default: last 24 hours) |
no |
t_end |
Time |
end time (default: now) |
no |
node |
String/Vec<String> |
event node |
no |
u |
String |
user account name |
no |
src |
String |
source |
no |
svc |
String |
service id (substring) |
no |
subj |
String |
event subject |
no |
oid |
String |
OID affected |
no |
note |
String |
a note or its part (substring) |
no |
data |
String |
substring of data payload casted as text |
no |
code |
i16 |
event error code (0 = success) |
no |
err |
String |
event error message |
no |
limit |
i64 |
maximum number of events to return |
no |
offset |
i64 |
number of events to skip |
no |
Return payload example:
[
{
"code": 0,
"data": null,
"err": null,
"id": [
207,
214,
106,
1,
143,
123,
76,
205,
159,
165,
103,
26,
81,
30,
20,
201
],
"node": "mws1",
"note": null,
"oid": null,
"src": "127.0.0.1",
"subj": "login",
"svc": "eva.hmi.default",
"t": 1706309851.0001848,
"u": "admin"
},
{
"code": 0,
"data": null,
"err": null,
"id": [
170,
82,
141,
89,
160,
254,
66,
37,
150,
19,
130,
241,
140,
246,
140,
214
],
"node": "mws1",
"note": null,
"oid": null,
"src": "127.0.0.1",
"subj": "login",
"svc": "eva.hmi.default",
"t": 1706309888.0001206,
"u": "opx"
}
]
query.count
Description |
Count accounting events |
Parameters |
required |
Returns |
Number of events matching the filter, limit and offset are ignored |
Name |
Type |
Description |
Required |
t_start |
Time |
start time (default: last 24 hours) |
no |
t_end |
Time |
end time (default: now) |
no |
node |
String/Vec<String> |
event node |
no |
u |
String |
user account name |
no |
src |
String |
source |
no |
svc |
String |
service id (substring) |
no |
subj |
String |
event subject |
no |
oid |
String |
OID affected |
no |
note |
String |
a note or its part (substring) |
no |
data |
String |
substring of data payload casted as text |
no |
code |
i16 |
event error code (0 = success) |
no |
err |
String |
event error message |
no |
limit |
i64 |
maximum number of events to return |
no |
offset |
i64 |
number of events to skip |
no |
Return payload example:
{
"count": 2
}
query.field_aggregated
Description |
Query aggregated field values |
Parameters |
required |
Returns |
Aggregated field values |
Name |
Type |
Description |
Required |
t_start |
Time |
start time (default: last 24 hours) |
no |
t_end |
Time |
end time (default: now) |
no |
field |
String |
Database field (node/u/src/svc/subj/oid/note/code) |
yes |
Return payload example:
[
"started",
"terminating"
]
report
Description |
Send accounting event via RPC call |
Parameters |
required |
Returns |
nothing |
Name |
Type |
Description |
Required |
u |
String |
user account name |
no |
src |
String |
source (e.g. user’s IP address) |
no |
svc |
String |
service id (default: caller) |
no |
subj |
String |
event subject |
no |
oid |
String |
OID affected |
no |
data |
Object |
any additional information |
no |
note |
String |
a custom note |
no |
code |
i16 |
event error code (0 = success) |
no |
err |
String |
event error message |
no |
HTTP API
The service provides certain methods via extra calls (the methods must be called e.g. as x::eva.aaa.accounting::query)
To use HTTP API methods, a user must be either a node administrator or have “log” operation permission.
query
Description |
Query accunting events |
Parameters |
required |
Returns |
Events matching the filter |
Requires log Access control list ops permission or an admin user
Name |
Type |
Description |
Required |
k |
String |
valid API key/token |
yes |
filter |
Filter structure (see query) |
Record filter |
no |
POST /jrpc HTTP/1.1
accept: application/json
content-type: application/json
host: localhost:7727
{
"id": 1,
"jsonrpc": "2.0",
"method": "query",
"params": {
"filter": {
"node": "mws1",
"subj": "login"
},
"k": "secretkey"
}
}
curl -i -X POST http://localhost:7727/jrpc -H "Accept: application/json" -H "Content-Type: application/json" --data-raw '{"id": 1, "jsonrpc": "2.0", "method": "query", "params": {"filter": {"node": "mws1", "subj": "login"}, "k": "secretkey"}}'
wget -S -O- http://localhost:7727/jrpc --header="Accept: application/json" --header="Content-Type: application/json" --post-data='{"id": 1, "jsonrpc": "2.0", "method": "query", "params": {"filter": {"node": "mws1", "subj": "login"}, "k": "secretkey"}}'
echo '{
"id": 1,
"jsonrpc": "2.0",
"method": "query",
"params": {
"filter": {
"node": "mws1",
"subj": "login"
},
"k": "secretkey"
}
}' | http POST http://localhost:7727/jrpc Accept:application/json Content-Type:application/json
requests.post('http://localhost:7727/jrpc', headers={'Accept': 'application/json', 'Content-Type': 'application/json'}, json={'id': 1, 'jsonrpc': '2.0', 'method': 'query', 'params': {'filter': {'node': 'mws1', 'subj': 'login'}, 'k': 'secretkey'}})
HTTP/1.1 200 OK
cache-control: no-cache, no-store
content-length: 1216
content-type: application/json
date: Thu, 13 Aug 2021 00:00:00 GMT
expires: 0
pragma: no-cache
{
"id": 1,
"jsonrpc": "2.0",
"result": [
{
"code": 0,
"data": null,
"err": null,
"id": [
207,
214,
106,
1,
143,
123,
76,
205,
159,
165,
103,
26,
81,
30,
20,
201
],
"node": "mws1",
"note": null,
"oid": null,
"src": "127.0.0.1",
"subj": "login",
"svc": "eva.hmi.default",
"t": 1706309851.0001848,
"u": "admin"
},
{
"code": 0,
"data": null,
"err": null,
"id": [
170,
82,
141,
89,
160,
254,
66,
37,
150,
19,
130,
241,
140,
246,
140,
214
],
"node": "mws1",
"note": null,
"oid": null,
"src": "127.0.0.1",
"subj": "login",
"svc": "eva.hmi.default",
"t": 1706309888.0001206,
"u": "opx"
}
]
}
query.count
Description |
Count accunting events |
Parameters |
required |
Returns |
Number of events matching the filter |
Requires log Access control list ops permission or an admin user
Name |
Type |
Description |
Required |
k |
String |
valid API key/token |
yes |
filter |
Filter structure (see query) |
Record filter |
no |
POST /jrpc HTTP/1.1
accept: application/json
content-type: application/json
host: localhost:7727
{
"id": 1,
"jsonrpc": "2.0",
"method": "query.count",
"params": {
"filter": {
"node": "mws1",
"subj": "login"
},
"k": "secretkey"
}
}
curl -i -X POST http://localhost:7727/jrpc -H "Accept: application/json" -H "Content-Type: application/json" --data-raw '{"id": 1, "jsonrpc": "2.0", "method": "query.count", "params": {"filter": {"node": "mws1", "subj": "login"}, "k": "secretkey"}}'
wget -S -O- http://localhost:7727/jrpc --header="Accept: application/json" --header="Content-Type: application/json" --post-data='{"id": 1, "jsonrpc": "2.0", "method": "query.count", "params": {"filter": {"node": "mws1", "subj": "login"}, "k": "secretkey"}}'
echo '{
"id": 1,
"jsonrpc": "2.0",
"method": "query.count",
"params": {
"filter": {
"node": "mws1",
"subj": "login"
},
"k": "secretkey"
}
}' | http POST http://localhost:7727/jrpc Accept:application/json Content-Type:application/json
requests.post('http://localhost:7727/jrpc', headers={'Accept': 'application/json', 'Content-Type': 'application/json'}, json={'id': 1, 'jsonrpc': '2.0', 'method': 'query.count', 'params': {'filter': {'node': 'mws1', 'subj': 'login'}, 'k': 'secretkey'}})
HTTP/1.1 200 OK
cache-control: no-cache, no-store
content-length: 78
content-type: application/json
date: Thu, 13 Aug 2021 00:00:00 GMT
expires: 0
pragma: no-cache
{
"id": 1,
"jsonrpc": "2.0",
"result": {
"count": 2
}
}
query.field_aggregated
Description |
Query aggregated field values |
Parameters |
required |
Returns |
Aggregated field values matching the filter |
Requires log Access control list ops permission or an admin user
Name |
Type |
Description |
Required |
k |
String |
valid API key/token |
yes |
filter |
Filter structure (see query.field_aggregated) |
Record filter |
yes |
POST /jrpc HTTP/1.1
accept: application/json
content-type: application/json
host: localhost:7727
{
"id": 1,
"jsonrpc": "2.0",
"method": "query.field_aggregated",
"params": {
"filter": {
"field": "subj"
},
"k": "secretkey"
}
}
curl -i -X POST http://localhost:7727/jrpc -H "Accept: application/json" -H "Content-Type: application/json" --data-raw '{"id": 1, "jsonrpc": "2.0", "method": "query.field_aggregated", "params": {"filter": {"field": "subj"}, "k": "secretkey"}}'
wget -S -O- http://localhost:7727/jrpc --header="Accept: application/json" --header="Content-Type: application/json" --post-data='{"id": 1, "jsonrpc": "2.0", "method": "query.field_aggregated", "params": {"filter": {"field": "subj"}, "k": "secretkey"}}'
echo '{
"id": 1,
"jsonrpc": "2.0",
"method": "query.field_aggregated",
"params": {
"filter": {
"field": "subj"
},
"k": "secretkey"
}
}' | http POST http://localhost:7727/jrpc Accept:application/json Content-Type:application/json
requests.post('http://localhost:7727/jrpc', headers={'Accept': 'application/json', 'Content-Type': 'application/json'}, json={'id': 1, 'jsonrpc': '2.0', 'method': 'query.field_aggregated', 'params': {'filter': {'field': 'subj'}, 'k': 'secretkey'}})
HTTP/1.1 200 OK
cache-control: no-cache, no-store
content-length: 98
content-type: application/json
date: Thu, 13 Aug 2021 00:00:00 GMT
expires: 0
pragma: no-cache
{
"id": 1,
"jsonrpc": "2.0",
"result": [
"started",
"terminating"
]
}