This page documents experiments with field-level access control in ES8.x.
tl;dr: Assumptions and Requirements
For field-level access control in Elasticsearch, we will need the following:
-
This feature is enterprise or platinum license only. Not available in free tier.
-
Access control is at the level of the field, not within a field. In other words, if you have a long text string as a field, the entire blob gets an Access/Deny flag, we cannot do it for a substring within the field.
-
Elasticsearch-level user management. ES requires logging in as a specific user with the specific authorizations as applicable.
-
Elasticsearch-level role management. ES requires specifying a role with the relevant grant or deny permissions as needed.
-
Explicit mappings in the ES index for fields we want to control access. Analyzed text fields are tricky, numeric fields are easy.
For the rest of the document, we show how to explore this in a test Elasticsearch environment.
Setup
Download quick-start ES, start Kibana. As of this writing, it comes with platinum license features for 30 days.
Go to Elasticsearch > Content > Indices. Expand “Console”.
Creating Test Index
Let’s ingest a document into ES in a test index. This document will have two fields, of which we will later grant access to only one field.
Let’s specify and create the index.
PUT test_index
{
"mappings" : {
"properties" : {
"age": { "type" : "integer" },
"handle": { "type": "keyword" }
}
}
}
Let’s post a document with two fields there.
POST test_index/_doc
{
"customer": {
"age": 35,
"handle": "jim"
}
}
We can verify that the document is indexed as follows:
GET test_index/_search
{
"query": {
"match_all": {}
}
}
# you will see 1 match here
Specifying Field-Level Access
Now, we will create a security-role that grants access to the “handle” field only, i.e. we don’t want to grant access to the age field.
POST /_security/role/test_role3
{
"indices" : [
{
"names" : [ "*" ],
"privileges" : [ "read" ],
"field_security" : {
"grant" : [ "customer.handle" ]
}
}
]
}
Next, we will create a user and bind this role to the user.
POST /_security/user/deepakn
{
"password" : "deepakn",
"roles" : [ "test_role3" ],
"full_name" : "Deepak N",
"email" : "n.deepak@gmail.com"
}
Verifying Field-Level Access
Now let’s search for the age 35. It works as a regular user:
GET test_index/_search
{
"query": {
"term": {
"customer.age": {
"value": 35
}
}
}
}
# will show
# {
# ...
# "hits": {
# ...
# "hits": [
# {
# "_index": "test_index",
# # ...
# "_source": {
# "customer": {
# "age": 35,
# "handle": "jim"
# }
# }
# }
# ]
# }
# }
It will fail if we run the search as the user we created above, with no access to the age field. Note the es-security-runas-user
HTTP header:
curl -X GET -u elastic \
-H "es-security-runas-user: deepakn" \
-H "Content-Type: application/json" \
-d '{"query":{"term":{"customer.age":{"value":35}}}}' \
http://localhost:9200/test_index/_search \
| jq .
Enter host password for user 'elastic': # enter passwd
{
# ...
"hits": {
"total": {
"value": 0,
# ...
}
# ...
}
}
However, it can search for a different field, but note that the “age” field is still not exposed to the user:
curl -X GET -u elastic \
-H "es-security-runas-user: deepakn" \
-H "Content-Type: application/json" \
-d '{"query":{"term":{"customer.handle":{"value":"jim"}}}}' \
http://localhost:9200/test_index/_search \
| jq .
Enter host password for user 'elastic': # enter passwd
{
# ...
"hits": {
"total": {
"value": 1,
# ...
},
# ...
"hits": [
{
"_index": "test_index",
# ...
"_source": {
"customer": {
"handle": "jim"
}
}
}
]
}
}
Cleanup
To clean up, we can run the following commands in Elasticsearch Console.
DELETE /_security/user/deepakn
DELETE /_security/role/test_role3
DELETE test_index
Reference
Official documentation: Elasticsearch field level security.
Elastic Blog: Document-level attribute-based access control. There is not much beyond this blog post on ABAC.