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

Autogenerate and print elastic pwd on startup #77291

Merged
merged 22 commits into from
Sep 8, 2021
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
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 @@ -32,6 +32,7 @@
import org.elasticsearch.common.network.NetworkService;
import org.elasticsearch.common.settings.ClusterSettings;
import org.elasticsearch.common.settings.IndexScopedSettings;
import org.elasticsearch.common.settings.SecureString;
import org.elasticsearch.common.settings.Setting;
import org.elasticsearch.common.settings.Setting.Property;
import org.elasticsearch.common.settings.Settings;
Expand All @@ -49,6 +50,7 @@
import org.elasticsearch.env.NodeEnvironment;
import org.elasticsearch.http.HttpServerTransport;
import org.elasticsearch.index.IndexModule;
import org.elasticsearch.index.engine.VersionConflictEngineException;
import org.elasticsearch.indices.ExecutorNames;
import org.elasticsearch.indices.SystemIndexDescriptor;
import org.elasticsearch.indices.breaker.CircuitBreakerService;
Expand Down Expand Up @@ -159,6 +161,7 @@
import org.elasticsearch.xpack.core.security.index.RestrictedIndicesNames;
import org.elasticsearch.xpack.core.security.support.Automatons;
import org.elasticsearch.xpack.core.security.user.AnonymousUser;
import org.elasticsearch.xpack.core.security.user.ElasticUser;
import org.elasticsearch.xpack.core.ssl.SSLConfigurationSettings;
import org.elasticsearch.xpack.core.ssl.SSLService;
import org.elasticsearch.xpack.core.ssl.TLSLicenseBootstrapCheck;
Expand Down Expand Up @@ -339,10 +342,12 @@
import static org.elasticsearch.xpack.core.XPackSettings.HTTP_SSL_ENABLED;
import static org.elasticsearch.xpack.core.security.index.RestrictedIndicesNames.SECURITY_MAIN_ALIAS;
import static org.elasticsearch.xpack.core.security.index.RestrictedIndicesNames.SECURITY_TOKENS_ALIAS;
import static org.elasticsearch.xpack.security.authc.esnative.ReservedRealm.BOOTSTRAP_ELASTIC_PASSWORD;
import static org.elasticsearch.xpack.security.operator.OperatorPrivileges.OPERATOR_PRIVILEGES_ENABLED;
import static org.elasticsearch.xpack.security.support.SecurityIndexManager.INTERNAL_MAIN_INDEX_FORMAT;
import static org.elasticsearch.xpack.security.support.SecurityIndexManager.INTERNAL_TOKENS_INDEX_FORMAT;
import static org.elasticsearch.xpack.security.support.SecurityIndexManager.SECURITY_VERSION_STRING;
import static org.elasticsearch.xpack.security.tool.CommandUtils.generatePassword;

public class Security extends Plugin implements SystemIndexPlugin, IngestPlugin, NetworkPlugin, ClusterPlugin,
DiscoveryPlugin, MapperPlugin, ExtensiblePlugin, SearchPlugin {
Expand Down Expand Up @@ -390,6 +395,7 @@ public class Security extends Plugin implements SystemIndexPlugin, IngestPlugin,
private final List<SecurityExtension> securityExtensions = new ArrayList<>();
private final SetOnce<Transport> transportReference = new SetOnce<>();
private final SetOnce<ScriptService> scriptServiceReference = new SetOnce<>();
private final SetOnce<NativeUsersStore> nativeUsersStoreReference = new SetOnce<>();

public Security(Settings settings, final Path configPath) {
this(settings, configPath, Collections.emptyList());
Expand Down Expand Up @@ -453,7 +459,6 @@ Collection<Object> createComponents(Client client, ThreadPool threadPool, Cluste
}

scriptServiceReference.set(scriptService);

// We need to construct the checks here while the secure settings are still available.
// If we wait until #getBoostrapChecks the secure settings will have been cleared/closed.
final List<BootstrapCheck> checks = new ArrayList<>();
Expand Down Expand Up @@ -492,9 +497,12 @@ Collection<Object> createComponents(Client client, ThreadPool threadPool, Cluste
);
this.tokenService.set(tokenService);
components.add(tokenService);

if (BOOTSTRAP_ELASTIC_PASSWORD.exists(settings) == false) {
securityIndex.get().addStateListener(this::generateElasticPassword);
}
// realms construction
final NativeUsersStore nativeUsersStore = new NativeUsersStore(settings, client, securityIndex.get());
nativeUsersStoreReference.set(nativeUsersStore);
final NativeRoleMappingStore nativeRoleMappingStore = new NativeRoleMappingStore(settings, client, securityIndex.get(),
scriptService);
final AnonymousUser anonymousUser = new AnonymousUser(settings);
Expand Down Expand Up @@ -628,6 +636,61 @@ auditTrailService, failureHandler, threadPool, anonymousUser, getAuthorizationEn
return components;
}

private void generateElasticPassword(SecurityIndexManager.State previousState, SecurityIndexManager.State currentState) {
if (previousState.equals(SecurityIndexManager.State.UNRECOVERED_STATE)
&& currentState.equals(SecurityIndexManager.State.UNRECOVERED_STATE) == false
&& securityIndex.get().indexExists() == false) {

final SecureString elasticPassword = new SecureString(generatePassword(20));
nativeUsersStoreReference.get()
.createReservedUser(
ElasticUser.NAME,
elasticPassword.getChars(),
ActionListener.wrap(
r -> {
Copy link
Member Author

@jkakavas jkakavas Sep 5, 2021

Choose a reason for hiding this comment

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

We could pass a reference to the original stdout PrintStream from Bootstrap to Node and then to the SecurityPlugin's createComponents so that we can temporarily call System.setOut to that , print to stdout and revert to log4j afterwards. This needs setIO RuntimePermission for the security plugin though and I'm not sure how comfortable I am with this change.

Copy link
Contributor

Choose a reason for hiding this comment

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

I don't understand why you'd need to call System.setOut - you just need a reference to the PrintStream so you can use it.

What we need is for Bootstrap (or something close to it) to hold on to the original stdout in a place that we can get to it (but is on the forbidden APIs list).

Bootstrap already has code to close original stream if we're not in the foreground, so I think a change to add a SystemConsole class in org.elasticsearch.bootstrap is entirely feasible.

Do you want me to pull something together?

Copy link
Member Author

Choose a reason for hiding this comment

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

I don't understand why you'd need to call System.setOut - you just need a reference to the PrintStream so you can use it.

Yes, you are right. I misunderstood how we do it ( and how handling stdout works most probably ) in LogConfigurator, and I had presumed the PrintStream would be unusable.

Do you want me to pull something together?

That would be great

logger.info("");
logger.info("-----------------------------------------------------------------");
logger.info("");
logger.info("");
logger.info("");
logger.info("Password for the elastic user is: ");
logger.info(elasticPassword);
logger.info("");
logger.info("");
logger.info("Please note this down as it will not be shown again.");
logger.info("");
logger.info("You can use 'bin/elasticsearch-reset-elastic-password' at any time");
logger.info("in order to reset the password for the elastic user.");
logger.info("");
logger.info("");
logger.info("");
logger.info("-----------------------------------------------------------------");
logger.info("");
},
e -> {
if (e instanceof VersionConflictEngineException == false) {
logger.info("");
logger.info("-----------------------------------------------------------------");
logger.info("");
logger.info("");
logger.info("");
logger.info("Failed to set the password for the elastic user automatically");
logger.info("");
logger.info("You can use 'bin/elasticsearch-reset-elastic-password'");
logger.info("in order to set the password for the elastic user.");
logger.info("");
logger.info("");
logger.info("");
logger.info("-----------------------------------------------------------------");
logger.info("");
}
logger.warn(e);
}
)
);
}
}

private AuthorizationEngine getAuthorizationEngine() {
AuthorizationEngine authorizationEngine = null;
String extensionName = null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import org.elasticsearch.ElasticsearchException;
import org.elasticsearch.ExceptionsHelper;
import org.elasticsearch.action.ActionListener;
import org.elasticsearch.action.DocWriteRequest;
import org.elasticsearch.action.DocWriteResponse;
import org.elasticsearch.action.delete.DeleteRequest;
import org.elasticsearch.action.delete.DeleteResponse;
Expand Down Expand Up @@ -252,7 +253,7 @@ public void onResponse(UpdateResponse updateResponse) {
public void onFailure(Exception e) {
if (isIndexNotFoundOrDocumentMissing(e)) {
if (docType.equals(RESERVED_USER_TYPE)) {
createReservedUser(username, request.passwordHash(), request.getRefreshPolicy(), listener);
updateReservedUser(username, request.passwordHash(), request.getRefreshPolicy(), listener);
} else {
logger.debug((org.apache.logging.log4j.util.Supplier<?>) () ->
new ParameterizedMessage("failed to change password for user [{}]", request.username()), e);
Expand All @@ -269,10 +270,10 @@ public void onFailure(Exception e) {
}

/**
* Asynchronous method to create a reserved user with the given password hash. The cache for the user will be cleared after the document
* Asynchronous method to update a reserved user with the given password hash. The cache for the user will be cleared after the document
* has been indexed
*/
private void createReservedUser(String username, char[] passwordHash, RefreshPolicy refresh, ActionListener<Void> listener) {
private void updateReservedUser(String username, char[] passwordHash, RefreshPolicy refresh, ActionListener<Void> listener) {
securityIndex.prepareIndexIfNeededThenExecute(listener::onFailure, () -> {
executeAsyncWithOrigin(client.threadPool().getThreadContext(), SECURITY_ORIGIN,
client.prepareIndex(SECURITY_MAIN_ALIAS).setId(getIdForUser(RESERVED_USER_TYPE, username))
Expand All @@ -283,6 +284,34 @@ private void createReservedUser(String username, char[] passwordHash, RefreshPol
});
}

/**
* Asynchronous method to create a reserved user with the given password hash. This should fail when the document for the
* specified user already exists in the security index
*/
public void createReservedUser(String username, char[] passwordHash, ActionListener<Void> listener) {
securityIndex.prepareIndexIfNeededThenExecute(listener::onFailure, () -> {
executeAsyncWithOrigin(
client.threadPool().getThreadContext(),
SECURITY_ORIGIN,
client.prepareIndex(SECURITY_MAIN_ALIAS)
.setOpType(DocWriteRequest.OpType.CREATE)
.setId(getIdForUser(RESERVED_USER_TYPE, username))
.setSource(
Fields.PASSWORD.getPreferredName(),
String.valueOf(passwordHash),
Fields.ENABLED.getPreferredName(),
true,
Fields.TYPE.getPreferredName(),
RESERVED_USER_TYPE
)
.setRefreshPolicy(RefreshPolicy.IMMEDIATE)
.request(),
listener.<IndexResponse>delegateFailure((l, indexResponse) -> l.onResponse(null)),
client::index
);
});
}

/**
* Asynchronous method to put a user. A put user request without a password hash is treated as an update and will fail with a
* {@link ValidationException} if the user does not exist. If a password hash is provided, then we issue a update request with an
Expand Down