Field level security

edit

Field level security restricts the fields that users have read access to. In particular, it restricts which fields can be accessed from document-based read APIs.

To enable field level security, specify the fields that each role can access as part of the indices permissions in a role definition. Field level security is thus bound to a well-defined set of data streams or indices (and potentially a set of documents).

The following role definition grants read access only to the category, @timestamp, and message fields in all the events-* data streams and indices.

resp = client.security.put_role(
    name="test_role1",
    indices=[
        {
            "names": [
                "events-*"
            ],
            "privileges": [
                "read"
            ],
            "field_security": {
                "grant": [
                    "category",
                    "@timestamp",
                    "message"
                ]
            }
        }
    ],
)
print(resp)
const response = await client.security.putRole({
  name: "test_role1",
  indices: [
    {
      names: ["events-*"],
      privileges: ["read"],
      field_security: {
        grant: ["category", "@timestamp", "message"],
      },
    },
  ],
});
console.log(response);
POST /_security/role/test_role1
{
  "indices": [
    {
      "names": [ "events-*" ],
      "privileges": [ "read" ],
      "field_security" : {
        "grant" : [ "category", "@timestamp", "message" ]
      }
    }
  ]
}

Access to the following metadata fields is always allowed: _id, _type, _parent, _routing, _timestamp, _ttl, _size and _index. If you specify an empty list of fields, only these metadata fields are accessible.

Omitting the fields entry entirely disables field level security.

You can also specify field expressions. For example, the following example grants read access to all fields that start with an event_ prefix:

resp = client.security.put_role(
    name="test_role2",
    indices=[
        {
            "names": [
                "*"
            ],
            "privileges": [
                "read"
            ],
            "field_security": {
                "grant": [
                    "event_*"
                ]
            }
        }
    ],
)
print(resp)
const response = await client.security.putRole({
  name: "test_role2",
  indices: [
    {
      names: ["*"],
      privileges: ["read"],
      field_security: {
        grant: ["event_*"],
      },
    },
  ],
});
console.log(response);
POST /_security/role/test_role2
{
  "indices" : [
    {
      "names" : [ "*" ],
      "privileges" : [ "read" ],
      "field_security" : {
        "grant" : [ "event_*" ]
      }
    }
  ]
}

Use the dot notations to refer to nested fields in more complex documents. For example, assuming the following document:

{
  "customer": {
    "handle": "Jim",
    "email": "jim@mycompany.com",
    "phone": "555-555-5555"
  }
}

The following role definition enables only read access to the customer handle field:

resp = client.security.put_role(
    name="test_role3",
    indices=[
        {
            "names": [
                "*"
            ],
            "privileges": [
                "read"
            ],
            "field_security": {
                "grant": [
                    "customer.handle"
                ]
            }
        }
    ],
)
print(resp)
const response = await client.security.putRole({
  name: "test_role3",
  indices: [
    {
      names: ["*"],
      privileges: ["read"],
      field_security: {
        grant: ["customer.handle"],
      },
    },
  ],
});
console.log(response);
POST /_security/role/test_role3
{
  "indices" : [
    {
      "names" : [ "*" ],
      "privileges" : [ "read" ],
      "field_security" : {
        "grant" : [ "customer.handle" ]
      }
    }
  ]
}

This is where wildcard support shines. For example, use customer.* to enable only read access to the customer data:

resp = client.security.put_role(
    name="test_role4",
    indices=[
        {
            "names": [
                "*"
            ],
            "privileges": [
                "read"
            ],
            "field_security": {
                "grant": [
                    "customer.*"
                ]
            }
        }
    ],
)
print(resp)
const response = await client.security.putRole({
  name: "test_role4",
  indices: [
    {
      names: ["*"],
      privileges: ["read"],
      field_security: {
        grant: ["customer.*"],
      },
    },
  ],
});
console.log(response);
POST /_security/role/test_role4
{
  "indices" : [
    {
      "names" : [ "*" ],
      "privileges" : [ "read" ],
      "field_security" : {
        "grant" : [ "customer.*" ]
      }
    }
  ]
}

You can deny permission to access fields with the following syntax:

resp = client.security.put_role(
    name="test_role5",
    indices=[
        {
            "names": [
                "*"
            ],
            "privileges": [
                "read"
            ],
            "field_security": {
                "grant": [
                    "*"
                ],
                "except": [
                    "customer.handle"
                ]
            }
        }
    ],
)
print(resp)
const response = await client.security.putRole({
  name: "test_role5",
  indices: [
    {
      names: ["*"],
      privileges: ["read"],
      field_security: {
        grant: ["*"],
        except: ["customer.handle"],
      },
    },
  ],
});
console.log(response);
POST /_security/role/test_role5
{
  "indices" : [
    {
      "names" : [ "*" ],
      "privileges" : [ "read" ],
      "field_security" : {
        "grant" : [ "*"],
        "except": [ "customer.handle" ]
      }
    }
  ]
}

The following rules apply:

  • The absence of field_security in a role is equivalent to * access.
  • If permission has been granted explicitly to some fields, you can specify denied fields. The denied fields must be a subset of the fields to which permissions were granted.
  • Defining denied and granted fields implies access to all granted fields except those which match the pattern in the denied fields.

For example:

resp = client.security.put_role(
    name="test_role6",
    indices=[
        {
            "names": [
                "*"
            ],
            "privileges": [
                "read"
            ],
            "field_security": {
                "except": [
                    "customer.handle"
                ],
                "grant": [
                    "customer.*"
                ]
            }
        }
    ],
)
print(resp)
const response = await client.security.putRole({
  name: "test_role6",
  indices: [
    {
      names: ["*"],
      privileges: ["read"],
      field_security: {
        except: ["customer.handle"],
        grant: ["customer.*"],
      },
    },
  ],
});
console.log(response);
POST /_security/role/test_role6
{
  "indices" : [
    {
      "names" : [ "*" ],
      "privileges" : [ "read" ],
      "field_security" : {
        "except": [ "customer.handle" ],
        "grant" : [ "customer.*" ]
      }
    }
  ]
}

In the above example, users can read all fields with the prefix "customer." except for "customer.handle".

An empty array for grant (for example, "grant" : []) means that access has not been granted to any fields.

When a user has several roles that specify field level permissions, the resulting field level permissions per data stream or index are the union of the individual role permissions. For example, if these two roles are merged:

resp = client.security.put_role(
    name="test_role7",
    indices=[
        {
            "names": [
                "*"
            ],
            "privileges": [
                "read"
            ],
            "field_security": {
                "grant": [
                    "a.*"
                ],
                "except": [
                    "a.b*"
                ]
            }
        }
    ],
)
print(resp)

resp1 = client.security.put_role(
    name="test_role8",
    indices=[
        {
            "names": [
                "*"
            ],
            "privileges": [
                "read"
            ],
            "field_security": {
                "grant": [
                    "a.b*"
                ],
                "except": [
                    "a.b.c*"
                ]
            }
        }
    ],
)
print(resp1)
const response = await client.security.putRole({
  name: "test_role7",
  indices: [
    {
      names: ["*"],
      privileges: ["read"],
      field_security: {
        grant: ["a.*"],
        except: ["a.b*"],
      },
    },
  ],
});
console.log(response);

const response1 = await client.security.putRole({
  name: "test_role8",
  indices: [
    {
      names: ["*"],
      privileges: ["read"],
      field_security: {
        grant: ["a.b*"],
        except: ["a.b.c*"],
      },
    },
  ],
});
console.log(response1);
POST /_security/role/test_role7
{
  "indices" : [
    {
      "names" : [ "*" ],
      "privileges" : [ "read" ],
      "field_security" : {
        "grant": [ "a.*" ],
        "except" : [ "a.b*" ]
      }
    }
  ]
}

POST /_security/role/test_role8
{
  "indices" : [
    {
      "names" : [ "*" ],
      "privileges" : [ "read" ],
      "field_security" : {
        "grant": [ "a.b*" ],
        "except" : [ "a.b.c*" ]
      }
    }
  ]
}

The resulting permission is equal to:

{
  // role 1 + role 2
  ...
  "indices" : [
    {
      "names" : [ "*" ],
      "privileges" : [ "read" ],
      "field_security" : {
        "grant": [ "a.*" ],
        "except" : [ "a.b.c*" ]
      }
    }
  ]
}

Field-level security should not be set on alias fields. To secure a concrete field, its field name must be used directly.

For more information, see Setting up field and document level security.