Skip to content

Commit

Permalink
Add kibana-system service account (#76449)
Browse files Browse the repository at this point in the history
This change introduces a Service Account for Kibana to use when
authenticating to Elasticsearch. The Service Account with
kibana service name under the elastic namespace,
uses the same RoleDescriptor as the existing kibana_system
built-in user and is its functional equivalent when it comes to
AuthZ.
  • Loading branch information
jkakavas authored Aug 13, 2021
1 parent 2ddbd62 commit 59d75f2
Show file tree
Hide file tree
Showing 10 changed files with 360 additions and 106 deletions.
1 change: 0 additions & 1 deletion rest-api-spec/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,6 @@ def v7compatibilityNotSupportedTests = {
'search.aggregation/200_top_hits_metric/top_hits aggregation with sequence numbers', // #42809 the use nested path and filter sort throws an exception
'search/310_match_bool_prefix/multi_match multiple fields with cutoff_frequency throws exception', //#42654 cutoff_frequency, common terms are not supported. Throwing an exception


]
}
tasks.named("yamlRestCompatTest").configure {
Expand Down
14 changes: 9 additions & 5 deletions x-pack/docs/en/security/authentication/service-accounts.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,12 @@ prevents credential sharing between multiple instances of the same
external service. Each instance can assume the same identity while using
their own distinct service token for authentication.

Service accounts provide flexibility over <<built-in-users,built-in users>>
Service accounts provide flexibility over <<built-in-users,built-in users>>
because they:

* Do not rely on the <<native-realm,internal `native` realm>>, and aren't
always required to rely on the `.security` index
* Use a role descriptor named after the service account principal instead of
* Use a role descriptor named after the service account principal instead of
traditional roles
* Support multiple credentials through service account tokens

Expand All @@ -40,11 +40,15 @@ the format of `<namespace>/<service>`, where the `namespace` is a top-level
grouping of service accounts, and `service` is the name of the service and
must be unique within its namespace.

Service accounts are predefined in code. Currently, only one service account is available:
Service accounts are predefined in code. The following service accounts are
available:

`elastic/fleet-server`:: The service account used by the {fleet} server to
communicate with {es}.

`elastic/kibana`:: The service account used by {kib} to communicate with
{es}.

// tag::service-accounts-usage[]
IMPORTANT: Do not attempt to use service accounts for authenticating individual
users. Service accounts can only be authenticated with service tokens, which are
Expand Down Expand Up @@ -83,11 +87,11 @@ the bearer token in the HTTP response

Both of these methods create a service token with a guaranteed secret string
length of `22`. The minimal, acceptable length of a secret string for a service
token is `10`. If the secret string doesn't meet this minimal length,
token is `10`. If the secret string doesn't meet this minimal length,
authentication with {es} will fail without even checking the value of the
service token.

Service tokens never expire. You must actively
Service tokens never expire. You must actively
<<security-api-delete-service-token,delete>> them if they are no longer needed.

[discrete]
Expand Down
3 changes: 2 additions & 1 deletion x-pack/plugin/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,8 @@ def v7compatibilityNotSupportedTests = {

// a type field was added to cat.ml_trained_models #73660, this is a backwards compatible change.
// still this is a cat api, and we don't support them with rest api compatibility. (the test would be very hard to transform too)
'ml/trained_model_cat_apis/Test cat trained models'
'ml/trained_model_cat_apis/Test cat trained models',
'service_accounts/10_basic/Test get service accounts', //#76449, will remove upon backport
]
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
import org.elasticsearch.xpack.core.security.authz.privilege.ConfigurableClusterPrivilege;
import org.elasticsearch.xpack.core.security.authz.privilege.ConfigurableClusterPrivileges.ManageApplicationPrivileges;
import org.elasticsearch.xpack.core.security.support.MetadataUtils;
import org.elasticsearch.xpack.core.security.user.KibanaUser;
import org.elasticsearch.xpack.core.security.user.KibanaSystemUser;
import org.elasticsearch.xpack.core.security.user.UsernamesField;
import org.elasticsearch.xpack.core.transform.transforms.persistence.TransformInternalIndexConstants;
import org.elasticsearch.xpack.core.watcher.execution.TriggeredWatchStoreField;
Expand Down Expand Up @@ -125,78 +125,7 @@ private static Map<String, RoleDescriptor> initializeReservedRoles() {
null, null,
MetadataUtils.getDeprecatedReservedMetadata("Please use Kibana feature privileges instead"),
null))
.put(KibanaUser.ROLE_NAME, new RoleDescriptor(KibanaUser.ROLE_NAME,
new String[] {
"monitor", "manage_index_templates", MonitoringBulkAction.NAME, "manage_saml", "manage_token", "manage_oidc",
InvalidateApiKeyAction.NAME, "grant_api_key",
GetBuiltinPrivilegesAction.NAME, "delegate_pki", GetLifecycleAction.NAME, PutLifecycleAction.NAME,
// To facilitate ML UI functionality being controlled using Kibana security privileges
"manage_ml",
// The symbolic constant for this one is in SecurityActionMapper, so not accessible from X-Pack core
"cluster:admin/analyze",
// To facilitate using the file uploader functionality
"monitor_text_structure",
// To cancel tasks and delete async searches
"cancel_task"
},
new RoleDescriptor.IndicesPrivileges[] {
RoleDescriptor.IndicesPrivileges.builder()
.indices(".kibana*", ".reporting-*").privileges("all").build(),
RoleDescriptor.IndicesPrivileges.builder()
.indices(".monitoring-*").privileges("read", "read_cross_cluster").build(),
RoleDescriptor.IndicesPrivileges.builder()
.indices(".management-beats").privileges("create_index", "read", "write").build(),
// To facilitate ML UI functionality being controlled using Kibana security privileges
RoleDescriptor.IndicesPrivileges.builder()
.indices(".ml-anomalies*", ".ml-stats-*")
.privileges("read").build(),
RoleDescriptor.IndicesPrivileges.builder().indices(".ml-annotations*", ".ml-notifications*")
.privileges("read", "write").build(),
// APM agent configuration
RoleDescriptor.IndicesPrivileges.builder()
.indices(".apm-agent-configuration").privileges("all").build(),
// APM custom link index creation
RoleDescriptor.IndicesPrivileges.builder()
.indices(".apm-custom-link").privileges("all").build(),
// APM telemetry queries APM indices in kibana task runner
RoleDescriptor.IndicesPrivileges.builder()
.indices("apm-*")
.privileges("read", "read_cross_cluster").build(),
// Data telemetry reads mappings, metadata and stats of indices
RoleDescriptor.IndicesPrivileges.builder()
.indices("*")
.privileges("view_index_metadata", "monitor").build(),
// Endpoint diagnostic information. Kibana reads from these indices to send telemetry
RoleDescriptor.IndicesPrivileges.builder()
.indices(".logs-endpoint.diagnostic.collection-*")
.privileges("read").build(),
// Fleet Server indices. Kibana create this indice before Fleet Server use them.
// Fleet Server indices. Kibana read and write to this indice to manage Elastic Agents
RoleDescriptor.IndicesPrivileges.builder()
.indices(".fleet*")
.privileges("all").build(),
// Legacy "Alerts as data" index. Kibana user will create this index.
// Kibana user will read / write to these indices
RoleDescriptor.IndicesPrivileges.builder()
.indices(ReservedRolesStore.LEGACY_ALERTS_INDEX)
.privileges("all").build(),
// "Alerts as data" index. Kibana user will create this index.
// Kibana user will read / write to these indices
RoleDescriptor.IndicesPrivileges.builder()
.indices(ReservedRolesStore.ALERTS_INDEX)
.privileges("all").build(),
// Endpoint / Fleet policy responses. Kibana requires read access to send telemetry
RoleDescriptor.IndicesPrivileges.builder()
.indices("metrics-endpoint.policy-*")
.privileges("read").build(),
// Endpoint metrics. Kibana requires read access to send telemetry
RoleDescriptor.IndicesPrivileges.builder()
.indices("metrics-endpoint.metrics-*")
.privileges("read").build()
},
null,
new ConfigurableClusterPrivilege[] { new ManageApplicationPrivileges(Collections.singleton("kibana-*")) },
null, MetadataUtils.DEFAULT_RESERVED_METADATA, null))
.put(KibanaSystemUser.ROLE_NAME, kibanaSystemRoleDescriptor(KibanaSystemUser.ROLE_NAME))
.put("logstash_system", new RoleDescriptor("logstash_system", new String[] { "monitor", MonitoringBulkAction.NAME},
null, null, MetadataUtils.DEFAULT_RESERVED_METADATA))
.put("beats_admin", new RoleDescriptor("beats_admin",
Expand Down Expand Up @@ -434,6 +363,81 @@ private static RoleDescriptor kibanaAdminUser(String name, Map<String, Object> m
null, null, metadata, null);
}

public static RoleDescriptor kibanaSystemRoleDescriptor(String name) {
return new RoleDescriptor(name,
new String[] {
"monitor", "manage_index_templates", MonitoringBulkAction.NAME, "manage_saml", "manage_token", "manage_oidc",
InvalidateApiKeyAction.NAME, "grant_api_key",
GetBuiltinPrivilegesAction.NAME, "delegate_pki", GetLifecycleAction.NAME, PutLifecycleAction.NAME,
// To facilitate ML UI functionality being controlled using Kibana security privileges
"manage_ml",
// The symbolic constant for this one is in SecurityActionMapper, so not accessible from X-Pack core
"cluster:admin/analyze",
// To facilitate using the file uploader functionality
"monitor_text_structure",
// To cancel tasks and delete async searches
"cancel_task"
},
new RoleDescriptor.IndicesPrivileges[] {
RoleDescriptor.IndicesPrivileges.builder()
.indices(".kibana*", ".reporting-*").privileges("all").build(),
RoleDescriptor.IndicesPrivileges.builder()
.indices(".monitoring-*").privileges("read", "read_cross_cluster").build(),
RoleDescriptor.IndicesPrivileges.builder()
.indices(".management-beats").privileges("create_index", "read", "write").build(),
// To facilitate ML UI functionality being controlled using Kibana security privileges
RoleDescriptor.IndicesPrivileges.builder()
.indices(".ml-anomalies*", ".ml-stats-*")
.privileges("read").build(),
RoleDescriptor.IndicesPrivileges.builder().indices(".ml-annotations*", ".ml-notifications*")
.privileges("read", "write").build(),
// APM agent configuration
RoleDescriptor.IndicesPrivileges.builder()
.indices(".apm-agent-configuration").privileges("all").build(),
// APM custom link index creation
RoleDescriptor.IndicesPrivileges.builder()
.indices(".apm-custom-link").privileges("all").build(),
// APM telemetry queries APM indices in kibana task runner
RoleDescriptor.IndicesPrivileges.builder()
.indices("apm-*")
.privileges("read", "read_cross_cluster").build(),
// Data telemetry reads mappings, metadata and stats of indices
RoleDescriptor.IndicesPrivileges.builder()
.indices("*")
.privileges("view_index_metadata", "monitor").build(),
// Endpoint diagnostic information. Kibana reads from these indices to send telemetry
RoleDescriptor.IndicesPrivileges.builder()
.indices(".logs-endpoint.diagnostic.collection-*")
.privileges("read").build(),
// Fleet Server indices. Kibana create this indice before Fleet Server use them.
// Fleet Server indices. Kibana read and write to this indice to manage Elastic Agents
RoleDescriptor.IndicesPrivileges.builder()
.indices(".fleet*")
.privileges("all").build(),
// Legacy "Alerts as data" index. Kibana user will create this index.
// Kibana user will read / write to these indices
RoleDescriptor.IndicesPrivileges.builder()
.indices(ReservedRolesStore.LEGACY_ALERTS_INDEX)
.privileges("all").build(),
// "Alerts as data" index. Kibana user will create this index.
// Kibana user will read / write to these indices
RoleDescriptor.IndicesPrivileges.builder()
.indices(ReservedRolesStore.ALERTS_INDEX)
.privileges("all").build(),
// Endpoint / Fleet policy responses. Kibana requires read access to send telemetry
RoleDescriptor.IndicesPrivileges.builder()
.indices("metrics-endpoint.policy-*")
.privileges("read").build(),
// Endpoint metrics. Kibana requires read access to send telemetry
RoleDescriptor.IndicesPrivileges.builder()
.indices("metrics-endpoint.metrics-*")
.privileges("read").build()
},
null,
new ConfigurableClusterPrivilege[] { new ManageApplicationPrivileges(Collections.singleton("kibana-*")) },
null, MetadataUtils.DEFAULT_RESERVED_METADATA, null);
}

public static boolean isReserved(String role) {
return RESERVED_ROLES.containsKey(role) || UsernamesField.SYSTEM_ROLE.equals(role) ||
UsernamesField.XPACK_ROLE.equals(role) || UsernamesField.ASYNC_SEARCH_ROLE.equals(role);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,20 @@
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.Response;
import org.elasticsearch.client.ResponseException;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.bytes.BytesArray;
import org.elasticsearch.common.xcontent.ToXContent;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.json.JsonXContent;
import org.elasticsearch.core.PathUtils;
import org.elasticsearch.common.settings.SecureString;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.util.concurrent.ThreadContext;
import org.elasticsearch.common.xcontent.XContentHelper;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.test.rest.ESRestTestCase;
import org.elasticsearch.xpack.core.security.authz.store.ReservedRolesStore;
import org.elasticsearch.xpack.core.security.user.KibanaSystemUser;
import org.junit.BeforeClass;

import java.io.FileNotFoundException;
Expand Down Expand Up @@ -165,6 +171,18 @@ public void testGetServiceAccount() throws IOException {
assertServiceAccountRoleDescriptor(getServiceAccountResponse3,
"elastic/fleet-server", ELASTIC_FLEET_SERVER_ROLE_DESCRIPTOR);

final Request getServiceAccountRequestKibana = new Request("GET", "_security/service/elastic/kibana");
final Response getServiceAccountResponseKibana = client().performRequest(getServiceAccountRequestKibana);
assertOK(getServiceAccountResponseKibana);
assertServiceAccountRoleDescriptor(
getServiceAccountResponseKibana,
"elastic/kibana",
Strings.toString(
ReservedRolesStore.kibanaSystemRoleDescriptor(KibanaSystemUser.ROLE_NAME)
.toXContent(JsonXContent.contentBuilder(), ToXContent.EMPTY_PARAMS)
)
);

final String requestPath = "_security/service/" + randomFrom("foo", "elastic/foo", "foo/bar");
final Request getServiceAccountRequest4 = new Request("GET", requestPath);
final Response getServiceAccountResponse4 = client().performRequest(getServiceAccountRequest4);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

import org.elasticsearch.common.Strings;
import org.elasticsearch.xpack.core.security.authz.RoleDescriptor;
import org.elasticsearch.xpack.core.security.authz.store.ReservedRolesStore;
import org.elasticsearch.xpack.core.security.user.User;

import java.util.List;
Expand Down Expand Up @@ -43,8 +44,10 @@ final class ElasticServiceAccounts {
null,
null
));
private static final ServiceAccount KIBANA_SYSTEM_ACCOUNT =
new ElasticServiceAccount("kibana", ReservedRolesStore.kibanaSystemRoleDescriptor(NAMESPACE + "/kibana"));

static final Map<String, ServiceAccount> ACCOUNTS = List.of(FLEET_ACCOUNT).stream()
static final Map<String, ServiceAccount> ACCOUNTS = List.of(FLEET_ACCOUNT, KIBANA_SYSTEM_ACCOUNT).stream()
.collect(Collectors.toMap(a -> a.id().asPrincipal(), Function.identity()));;

private ElasticServiceAccounts() {}
Expand Down
Loading

0 comments on commit 59d75f2

Please sign in to comment.