Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Deprecate the kibana reserved user; introduce kibana_system user #54967

Merged
merged 16 commits into from
Apr 27, 2020
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -200,11 +200,11 @@ public void testGetUsers() throws Exception {
List<User> users = new ArrayList<>(3);
users.addAll(response.getUsers());
assertNotNull(response);
// 9 users are expected to be returned
// 10 users are expected to be returned
// test_users (3): user1, user2, user3
// system_users (6): elastic, beats_system, apm_system, logstash_system, kibana, remote_monitoring_user
// system_users (6): elastic, beats_system, apm_system, logstash_system, kibana, kibana_system, remote_monitoring_user
logger.info(users);
assertThat(users.size(), equalTo(9));
assertThat(users.size(), equalTo(10));
}

{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ public static boolean isReserved(String username, Settings settings) {
assert username != null;
switch (username) {
case UsernamesField.ELASTIC_NAME:
case UsernamesField.DEPRECATED_KIBANA_NAME:
case UsernamesField.KIBANA_NAME:
case UsernamesField.LOGSTASH_NAME:
case UsernamesField.BEATS_NAME:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.xpack.core.security.user;

import org.elasticsearch.xpack.core.security.support.MetadataUtils;

/**
* Built in user for the kibana server
*/
public class KibanaSystemUser extends User {

public static final String NAME = UsernamesField.KIBANA_NAME;
public static final String ROLE_NAME = UsernamesField.KIBANA_ROLE;

public KibanaSystemUser(boolean enabled) {
super(NAME, new String[]{ ROLE_NAME }, null, null, MetadataUtils.DEFAULT_RESERVED_METADATA, enabled);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,16 @@

/**
* Built in user for the kibana server
* @deprecated use KibanaSystemUser
*/
@Deprecated
public class KibanaUser extends User {

public static final String NAME = UsernamesField.KIBANA_NAME;
public static final String NAME = UsernamesField.DEPRECATED_KIBANA_NAME;
public static final String ROLE_NAME = UsernamesField.KIBANA_ROLE;

public KibanaUser(boolean enabled) {
super(NAME, new String[]{ ROLE_NAME }, null, null, MetadataUtils.DEFAULT_RESERVED_METADATA, enabled);
super(NAME, new String[]{ ROLE_NAME }, null, null,
MetadataUtils.getDeprecatedReservedMetadata("Please use the [kibana_system] user instead."), enabled);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@
public final class UsernamesField {
public static final String ELASTIC_NAME = "elastic";
public static final String ELASTIC_ROLE = "superuser";
public static final String KIBANA_NAME = "kibana";
public static final String DEPRECATED_KIBANA_NAME = "kibana";
public static final String KIBANA_NAME = "kibana_system";
public static final String KIBANA_ROLE = "kibana_system";
public static final String SYSTEM_NAME = "_system";
public static final String SYSTEM_ROLE = "_system";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ public class IdentityProviderAuthenticationIT extends IdpRestTestCase {

@Before
public void setupSecurityData() throws IOException {
setUserPassword("kibana", new SecureString("kibana".toCharArray()));
setUserPassword("kibana_system", new SecureString("kibana_system".toCharArray()));
createApplicationPrivileges("elastic-cloud", Map.ofEntries(
Map.entry("deployment_admin", Set.of("sso:admin")),
Map.entry("deployment_viewer", Set.of("sso:viewer"))
Expand Down Expand Up @@ -113,7 +113,7 @@ private Map<String, Object> validateAuthnRequest(String entityId, String authnRe
private SamlPrepareAuthenticationResponse generateSamlAuthnRequest(String realmName) throws Exception {
final Request request = new Request("POST", "/_security/saml/prepare");
request.setJsonEntity("{\"realm\":\"" + realmName + "\"}");
try (RestClient kibanaClient = restClientAsKibana()) {
try (RestClient kibanaClient = restClientAsKibanaSystem()) {
final Response response = kibanaClient.performRequest(request);
final Map<String, Object> map = entityAsMap(response);
assertThat(ObjectPath.eval("realm", map), equalTo(realmName));
Expand Down Expand Up @@ -152,7 +152,7 @@ private void authenticateWithSamlResponse(String samlResponse, @Nullable String
request.setJsonEntity("{\"content\":\"" + encodedResponse + "\", \"realm\":\"" + REALM_NAME + "\"}");
}
final String accessToken;
try (RestClient kibanaClient = restClientAsKibana()) {
try (RestClient kibanaClient = restClientAsKibanaSystem()) {
final Response response = kibanaClient.performRequest(request);
final Map<String, Object> map = entityAsMap(response);
assertThat(ObjectPath.eval("username", map), instanceOf(String.class));
Expand Down Expand Up @@ -184,10 +184,10 @@ private RestClient restClientWithToken(String accessToken) throws IOException {
getClusterHosts().toArray(new HttpHost[getClusterHosts().size()]));
}

private RestClient restClientAsKibana() throws IOException {
private RestClient restClientAsKibanaSystem() throws IOException {
return buildClient(
Settings.builder().put(ThreadContext.PREFIX + ".Authorization", basicAuthHeaderValue("kibana",
new SecureString("kibana".toCharArray()))).build(),
Settings.builder().put(ThreadContext.PREFIX + ".Authorization", basicAuthHeaderValue("kibana_system",
new SecureString("kibana_system".toCharArray()))).build(),
getClusterHosts().toArray(new HttpHost[getClusterHosts().size()]));
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import org.apache.logging.log4j.message.ParameterizedMessage;
import org.apache.logging.log4j.util.Supplier;
import org.elasticsearch.action.ActionListener;
import org.elasticsearch.common.logging.DeprecationLogger;
import org.elasticsearch.common.settings.KeyStoreWrapper;
import org.elasticsearch.common.settings.SecureSetting;
import org.elasticsearch.common.settings.SecureString;
Expand All @@ -23,10 +24,12 @@
import org.elasticsearch.xpack.core.security.authc.support.Hasher;
import org.elasticsearch.xpack.core.security.authc.support.UsernamePasswordToken;
import org.elasticsearch.xpack.core.security.support.Exceptions;
import org.elasticsearch.xpack.core.security.support.MetadataUtils;
import org.elasticsearch.xpack.core.security.user.APMSystemUser;
import org.elasticsearch.xpack.core.security.user.AnonymousUser;
import org.elasticsearch.xpack.core.security.user.BeatsSystemUser;
import org.elasticsearch.xpack.core.security.user.ElasticUser;
import org.elasticsearch.xpack.core.security.user.KibanaSystemUser;
import org.elasticsearch.xpack.core.security.user.KibanaUser;
import org.elasticsearch.xpack.core.security.user.LogstashSystemUser;
import org.elasticsearch.xpack.core.security.user.RemoteMonitoringUser;
Expand All @@ -40,6 +43,7 @@
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;

/**
* A realm for predefined users. These users can only be modified in terms of changing their passwords; no other modifications are allowed.
Expand All @@ -62,6 +66,8 @@ public class ReservedRealm extends CachingUsernamePasswordRealm {
private final ReservedUserInfo disabledDefaultUserInfo;
private final ReservedUserInfo enabledDefaultUserInfo;

private final DeprecationLogger deprecationLogger = new DeprecationLogger(logger);

public ReservedRealm(Environment env, Settings settings, NativeUsersStore nativeUsersStore, AnonymousUser anonymousUser,
SecurityIndexManager securityIndex, ThreadPool threadPool) {
super(new RealmConfig(new RealmConfig.RealmIdentifier(TYPE, TYPE),
Expand Down Expand Up @@ -98,6 +104,7 @@ protected void doAuthenticate(UsernamePasswordToken token, ActionListener<Authen
result = AuthenticationResult.terminate("failed to authenticate user [" + token.principal() + "]", null);
} else if (userInfo.verifyPassword(token.credentials())) {
final User user = getUser(token.principal(), userInfo);
logDeprecatedUser(user);
result = AuthenticationResult.success(user);
} else {
result = AuthenticationResult.terminate("failed to authenticate user [" + token.principal() + "]", null);
Expand Down Expand Up @@ -147,6 +154,8 @@ private User getUser(String username, ReservedUserInfo userInfo) {
return new ElasticUser(userInfo.enabled);
case KibanaUser.NAME:
return new KibanaUser(userInfo.enabled);
case KibanaSystemUser.NAME:
return new KibanaSystemUser(userInfo.enabled);
case LogstashSystemUser.NAME:
return new LogstashSystemUser(userInfo.enabled);
case BeatsSystemUser.NAME:
Expand Down Expand Up @@ -177,6 +186,9 @@ public void users(ActionListener<Collection<User>> listener) {
userInfo = reservedUserInfos.get(KibanaUser.NAME);
users.add(new KibanaUser(userInfo == null || userInfo.enabled));

userInfo = reservedUserInfos.get(KibanaSystemUser.NAME);
users.add(new KibanaSystemUser(userInfo == null || userInfo.enabled));

userInfo = reservedUserInfos.get(LogstashSystemUser.NAME);
users.add(new LogstashSystemUser(userInfo == null || userInfo.enabled));

Expand Down Expand Up @@ -220,6 +232,15 @@ private void getUserInfo(final String username, ActionListener<ReservedUserInfo>
}
}

private void logDeprecatedUser(final User user){
Map<String, Object> metadata = user.metadata();
if (Boolean.TRUE.equals(metadata.get(MetadataUtils.DEPRECATED_METADATA_KEY))) {
deprecationLogger.deprecatedAndMaybeLog("deprecated_user-" + user.principal(), "The user [" + user.principal() +
"] is deprecated and will be removed in a future version of Elasticsearch. " +
metadata.get(MetadataUtils.DEPRECATED_REASON_METADATA_KEY));
}
}

private ReservedUserInfo getDefaultUserInfo(String username) {
if (ElasticUser.NAME.equals(username)) {
return bootstrapUserInfo.deepClone();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
import org.elasticsearch.xpack.core.security.user.APMSystemUser;
import org.elasticsearch.xpack.core.security.user.BeatsSystemUser;
import org.elasticsearch.xpack.core.security.user.ElasticUser;
import org.elasticsearch.xpack.core.security.user.KibanaSystemUser;
import org.elasticsearch.xpack.core.security.user.KibanaUser;
import org.elasticsearch.xpack.core.security.user.LogstashSystemUser;
import org.elasticsearch.xpack.core.security.user.RemoteMonitoringUser;
Expand Down Expand Up @@ -65,8 +66,8 @@
public class SetupPasswordTool extends LoggingAwareMultiCommand {

private static final char[] CHARS = ("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789").toCharArray();
public static final List<String> USERS = asList(ElasticUser.NAME, APMSystemUser.NAME, KibanaUser.NAME, LogstashSystemUser.NAME,
BeatsSystemUser.NAME, RemoteMonitoringUser.NAME);
public static final List<String> USERS = asList(ElasticUser.NAME, APMSystemUser.NAME, KibanaUser.NAME, KibanaSystemUser.NAME,
LogstashSystemUser.NAME, BeatsSystemUser.NAME, RemoteMonitoringUser.NAME);

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's cumbersome on the user to have password prompts for both kibana and kibana_system.

I suggest we display the prompt for the new kibana_system user and also set the same password for the kibana user.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point, I agree this is cumbersome. I'll take a stab at updating the SetupPasswordTool to support this

private final Function<Environment, CommandLineHttpClient> clientFunction;
private final CheckedFunction<Environment, KeyStoreWrapper, Exception> keyStoreFunction;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,13 @@ public class KibanaSystemRoleIntegTests extends SecurityIntegTestCase {
public String configUsers() {
final String usersPasswdHashed = new String(getFastStoredHashAlgoForTests().hash(USERS_PASSWD));
return super.configUsers() +
"kibana_system:" + usersPasswdHashed;
"my_kibana_system:" + usersPasswdHashed;
}

@Override
public String configUsersRoles() {
return super.configUsersRoles() +
"kibana_system:kibana_system";
"kibana_system:my_kibana_system";
}


Expand All @@ -42,13 +42,14 @@ public void testCreateIndexDeleteInKibanaIndex() throws Exception {

if (randomBoolean()) {
CreateIndexResponse createIndexResponse = client().filterWithHeader(singletonMap("Authorization",
UsernamePasswordToken.basicAuthHeaderValue("kibana_system", USERS_PASSWD)))
UsernamePasswordToken.basicAuthHeaderValue("my_kibana_system", USERS_PASSWD)))
.admin().indices().prepareCreate(index).get();
assertThat(createIndexResponse.isAcknowledged(), is(true));
}

IndexResponse response = client()
.filterWithHeader(singletonMap("Authorization", UsernamePasswordToken.basicAuthHeaderValue("kibana_system", USERS_PASSWD)))
.filterWithHeader(singletonMap("Authorization",
UsernamePasswordToken.basicAuthHeaderValue("my_kibana_system", USERS_PASSWD)))
.prepareIndex()
.setIndex(index)
.setSource("foo", "bar")
Expand All @@ -57,7 +58,8 @@ public void testCreateIndexDeleteInKibanaIndex() throws Exception {
assertEquals(DocWriteResponse.Result.CREATED, response.getResult());

DeleteResponse deleteResponse = client()
.filterWithHeader(singletonMap("Authorization", UsernamePasswordToken.basicAuthHeaderValue("kibana_system", USERS_PASSWD)))
.filterWithHeader(singletonMap("Authorization",
UsernamePasswordToken.basicAuthHeaderValue("my_kibana_system", USERS_PASSWD)))
.prepareDelete(index, response.getId())
.get();
assertEquals(DocWriteResponse.Result.DELETED, deleteResponse.getResult());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import org.elasticsearch.xpack.core.security.user.APMSystemUser;
import org.elasticsearch.xpack.core.security.user.BeatsSystemUser;
import org.elasticsearch.xpack.core.security.user.ElasticUser;
import org.elasticsearch.xpack.core.security.user.KibanaSystemUser;
import org.elasticsearch.xpack.core.security.user.KibanaUser;
import org.elasticsearch.xpack.core.security.user.LogstashSystemUser;
import org.elasticsearch.xpack.core.security.user.RemoteMonitoringUser;
Expand Down Expand Up @@ -108,8 +109,8 @@ public void setupReservedPasswords(RestClient restClient) throws IOException {
RequestOptions.Builder optionsBuilder = RequestOptions.DEFAULT.toBuilder();
optionsBuilder.addHeader("Authorization", UsernamePasswordToken.basicAuthHeaderValue(ElasticUser.NAME, reservedPassword));
RequestOptions options = optionsBuilder.build();
final List<String> usernames = Arrays.asList(KibanaUser.NAME, LogstashSystemUser.NAME, BeatsSystemUser.NAME, APMSystemUser.NAME,
RemoteMonitoringUser.NAME);
final List<String> usernames = Arrays.asList(KibanaUser.NAME, KibanaSystemUser.NAME, LogstashSystemUser.NAME, BeatsSystemUser.NAME,
APMSystemUser.NAME, RemoteMonitoringUser.NAME);
for (String username : usernames) {
Request request = new Request("PUT", "/_security/user/" + username + "/_password");
request.setJsonEntity("{\"password\": \"" + new String(reservedPassword.getChars()) + "\"}");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
import org.elasticsearch.xpack.core.security.user.APMSystemUser;
import org.elasticsearch.xpack.core.security.user.BeatsSystemUser;
import org.elasticsearch.xpack.core.security.user.ElasticUser;
import org.elasticsearch.xpack.core.security.user.KibanaSystemUser;
import org.elasticsearch.xpack.core.security.user.KibanaUser;
import org.elasticsearch.xpack.core.security.user.LogstashSystemUser;
import org.elasticsearch.xpack.core.security.user.RemoteMonitoringUser;
Expand Down Expand Up @@ -85,8 +86,8 @@ void doExecute(ActionType<Response> action, Request request, ActionListener<Resp
public void testPasswordUpsertWhenSetEnabledOnReservedUser() throws Exception {
final NativeUsersStore nativeUsersStore = startNativeUsersStore();

final String user = randomFrom(ElasticUser.NAME, KibanaUser.NAME, LogstashSystemUser.NAME,
BeatsSystemUser.NAME, APMSystemUser.NAME, RemoteMonitoringUser.NAME);
final String user = randomFrom(ElasticUser.NAME, KibanaUser.NAME, KibanaSystemUser.NAME,
LogstashSystemUser.NAME, BeatsSystemUser.NAME, APMSystemUser.NAME, RemoteMonitoringUser.NAME);

final PlainActionFuture<Void> future = new PlainActionFuture<>();
nativeUsersStore.setEnabled(user, true, WriteRequest.RefreshPolicy.IMMEDIATE, future);
Expand All @@ -104,8 +105,8 @@ public void testPasswordUpsertWhenSetEnabledOnReservedUser() throws Exception {
public void testBlankPasswordInIndexImpliesDefaultPassword() throws Exception {
final NativeUsersStore nativeUsersStore = startNativeUsersStore();

final String user = randomFrom(ElasticUser.NAME, KibanaUser.NAME, LogstashSystemUser.NAME,
BeatsSystemUser.NAME, APMSystemUser.NAME, RemoteMonitoringUser.NAME);
final String user = randomFrom(ElasticUser.NAME, KibanaUser.NAME, KibanaSystemUser.NAME,
LogstashSystemUser.NAME, BeatsSystemUser.NAME, APMSystemUser.NAME, RemoteMonitoringUser.NAME);
final Map<String, Object> values = new HashMap<>();
values.put(ENABLED_FIELD, Boolean.TRUE);
values.put(PASSWORD_FIELD, BLANK_PASSWORD);
Expand Down
Loading