8000 Make bootstrap admins temporary conditionally by slaskawi · Pull Request #40309 · keycloak/keycloak · GitHub
[go: up one dir, main page]

Skip to content
Closed
Show file tree
Hide file tree
Changes from all 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
7 changes: 7 additions & 0 deletions docs/guides/server/bootstrap-admin-recovery.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,13 @@ The `--no-prompt` parameter can be useful if the username or client ID should be

This creates a temporary admin user with the default username without prompting for confirmation. For more information, see the <<Default values>> section above.

== Treating the temporary admin account as a permanent one

Both `bootstrap-admin user` and `bootstrap-admin service` commands support the `--is-temporary` parameter. This parameter allows you to treat the created admin account as a permanent one, meaning it will not be marked as temporary and will not be subject to the temporary account restrictions.

A temporary admin account can also be created with the Welcome Screen that is displayed at the first start of {project_name}. In this case, the temporary account behavior is controlled by the `--bootstrap-admin-temporary` parameter. If the variable or setting is not defined, the account will be created as a temporary one.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The --bootstrap-admin-temporary also work with the --bootstrap-admin-* options used for creating admin user/svc account at startup, no? Not just with the Welcome Screen.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@vmuzikar that wouldn't be consistent with the bootstrap admin commands existing handling. The commands do not consult the bootstrap-admin=* options. Someone could be setting bootstrap-admin-* options in keycloak.conf for example, and we don't want that influencing the bootstrap admin command unintentionally.

So --bootstrap-admin-temporary should just affect the initial admin creation / Welcome Screen.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

So --bootstrap-admin-temporary should just affect the initial admin creation / Welcome Screen.

Yes, that's what I meant. Sorry, I was probably not clear before.

I meant that --bootstrap-admin-temporary should also affect when you create an admin user/service account at startup, not just the Welcome Screen. And that this behaviour should be documented.


Comment thread
slaskawi marked this conversation as resolved.
Resolved
At the moment, a temporary account is reflected only in a form of visual warnings when using it, it does not actually expire the account. This might change with a future release.
== Environment variables

For the `bootstrap-admin user` command, both username and password can be optionally set as environment variables:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,25 @@ public class BootstrapAdminOptions {
public static final String DEFAULT_TEMP_ADMIN_USERNAME = "temp-admin";
public static final String DEFAULT_TEMP_ADMIN_SERVICE = DEFAULT_TEMP_ADMIN_USERNAME;
public static final int DEFAULT_TEMP_ADMIN_EXPIRATION = 120;
public static final boolean DEFAULT_TEMP_ADMIN_IS_TEMPORARY = true;
private static final String USED_ONLY_WHEN = " Used only when the master realm is created.";
private static final String NON_CLI = " Use a non-CLI configuration option for this option if possible.";

// Since the Configuration object is not available in the WelcomeResource, we'll just pass it through a static
// handler. It's definitely a suboptimal approach but the alternatives are to either put it in the Profile
// (which doesn't feel right) or RealmModel (which seems like too much).
// There have been some other attempts to handle this better (https://github.com/keycloak/keycloak/pull/39123)
// but they didn't get too much traction.
private static boolean isTemporaryRuntimeAdmin = true;
Comment thread
slaskawi marked this conversation as resolved.
Resolved

public static final Option<String> PASSWORD = new OptionBuilder<>("bootstrap-admin-password", String.class)
.category(OptionCategory.BOOTSTRAP_ADMIN)
.description("Temporary bootstrap admin password." + USED_ONLY_WHEN + NON_CLI)
.description("Bootstrap admin password." + USED_ONLY_WHEN + NON_CLI)
.build();

public static final Option<String> USERNAME = new OptionBuilder<>("bootstrap-admin-username", String.class)
.category(OptionCategory.BOOTSTRAP_ADMIN)
.description("Temporary bootstrap admin username." + USED_ONLY_WHEN)
.description("Bootstrap admin username." + USED_ONLY_WHEN)
.defaultValue(DEFAULT_TEMP_ADMIN_USERNAME)
.build();

Expand All @@ -27,13 +35,28 @@ public class BootstrapAdminOptions {

public static final Option<String> CLIENT_ID = new OptionBuilder<>("bootstrap-admin-client-id", String.class)
.category(OptionCategory.BOOTSTRAP_ADMIN)
.description("Client id for the temporary bootstrap admin service account." + USED_ONLY_WHEN)
.description("Client id for the bootstrap admin service account." + USED_ONLY_WHEN)
.defaultValue(DEFAULT_TEMP_ADMIN_SERVICE)
.build();

public static final Option<String> CLIENT_SECRET = new OptionBuilder<>("bootstrap-admin-client-secret", String.class)
.category(OptionCategory.BOOTSTRAP_ADMIN)
.description("Client secret for the temporary bootstrap admin service account." + USED_ONLY_WHEN + NON_CLI)
.description("Client secret for the bootstrap admin service account." + USED_ONLY_WHEN + NON_CLI)
.build();

public static final Option<Boolean> IS_TEMPORARY = new OptionBuilder<>("bootstrap-admin-temporary", Boolean.class)
.category(OptionCategory.BOOTSTRAP_ADMIN)
.description("Indicates whether the admin user or service account is temporary." + USED_ONLY_WHEN)
.defaultValue(DEFAULT_TEMP_ADMIN_IS_TEMPORARY)
.build();

public static synchronized void setTemporaryRuntimeAdmin(boolean temporary) {
isTemporaryRuntimeAdmin = temporary;
}

public static synchronized boolean isTemporaryRuntimeAdmin() {
return isTemporaryRuntimeAdmin;
}


}
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,9 @@ static class ClientIdOptions {
@Option(paramLabel = "SECRET", names = { "--client-secret:env" }, description = "Environment variable name for the client secret")
String clientSecretEnv;

@Option(names = { "--is-temporary" }, description = "Indicates whether this admin account is temporary, defaults to true", defaultValue = "true")
boolean isTemporary;

String clientSecret;
String clientId;

Expand Down Expand Up @@ -105,7 +108,7 @@ public void onStart(QuarkusKeycloakApplication application) {
//BootstrapAdmin bootstrap = spec.commandLine().getParent().getCommand();
KeycloakSessionFactory sessionFactory = KeycloakApplication.getSessionFactory();
KeycloakModelUtils.runJobInTransaction(sessionFactory, session -> application
.createTemporaryMasterRealmAdminService(clientId, clientSecret, /* bootstrap.expiration, */ session));
.createTemporaryMasterRealmAdminService(clientId, clientSecret, isTemporary, /* bootstrap.expiration, */ session));
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,9 @@ static class UsernameOptions {
@Option(paramLabel = "PASSWORD", names = { "--password:env" }, description = "Environment variable name for the admin user password")
String passwordEnv;

@Option(names = { "--is-temporary" }, description = "Indicates whether this admin account is temporary, defaults to true", defaultValue = "true")
boolean isTemporary;

String password;
String username;

Expand Down Expand Up @@ -105,7 +108,7 @@ public void onStart(QuarkusKeycloakApplication application) {
//BootstrapAdmin bootstrap = spec.commandLine().getParent().getCommand();
KeycloakSessionFactory sessionFactory = KeycloakApplication.getSessionFactory();
KeycloakModelUtils.runJobInTransaction(sessionFactory, session -> application
.createTemporaryMasterRealmAdminUser(username, password, /* bootstrap.expiration, */ session));
.createTemporaryMasterRealmAdminUser(username, password, isTemporary, /* bootstrap.expiration, */ session));
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ public static PropertyMapper<?>[] getMappers() {
.paramLabel("client secret")
.isMasked(true)
.build(),
fromOption(BootstrapAdminOptions.IS_TEMPORARY)
.build()
};
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,8 @@ public KeycloakSessionFactory createSessionFactory() {

@Override
protected void loadConfig() {
// no need to load config provider because we force quarkus impl
var isTemporary = Boolean.parseBoolean(Configuration.getOptionalKcValue(BootstrapAdminOptions.IS_TEMPORARY.getKey()).orElse("true"));
BootstrapAdminOptions.setTemporaryRuntimeAdmin(isTemporary);
}

@Override
Expand All @@ -76,10 +77,10 @@ protected void createTemporaryAdmin(KeycloakSession session) {

try {
//Integer expiration = Configuration.getOptionalKcValue(BootstrapAdminOptions.EXPIRATION.getKey()).map(Integer::valueOf).orElse(null);
if (StringUtil.isNotBlank(adminPassword) && !createTemporaryMasterRealmAdminUser(adminUsername, adminPassword, /*expiration,*/ session)) {
if (StringUtil.isNotBlank(adminPassword) && !createTemporaryMasterRealmAdminUser(adminUsername, adminPassword, BootstrapAdminOptions.isTemporaryRuntimeAdmin(), /*expiration,*/ session)) {
throw new RuntimeException("Aborting startup and the creation of the master realm, because the temporary admin user account could not be created.");
}
if (StringUtil.isNotBlank(clientSecret) && !createTemporaryMasterRealmAdminService(clientId, clientSecret, /*expiration,*/ session)) {
if (StringUtil.isNotBlank(clientSecret) && !createTemporaryMasterRealmAdminService(clientId, clientSecret, BootstrapAdminOptions.isTemporaryRuntimeAdmin(), /*expiration,*/ session)) {
throw new RuntimeException("Aborting startup and the creation of the master realm, because the temporary admin service account could not be created.");
}
} catch (NumberFormatException e) {
Expand All @@ -102,12 +103,12 @@ private String getOption(String option, String envVar) {
}
}

public boolean createTemporaryMasterRealmAdminUser(String adminUserName, String adminPassword, /*Integer adminExpiration,*/ KeycloakSession session) {
return new ApplianceBootstrap(session).createTemporaryMasterRealmAdminUser(adminUserName, adminPassword /*, adminExpiration*/, false);
public boolean createTemporaryMasterRealmAdminUser(String adminUserName, String adminPassword, boolean isTemporary, /*Integer adminExpiration,*/ KeycloakSession session) {
return new ApplianceBootstrap(session).createMasterRealmAdminUser(adminUserName, adminPassword, isTemporary /*, adminExpiration*/, false);
}

public boolean createTemporaryMasterRealmAdminService(String clientId, String clientSecret, /*Integer adminExpiration,*/ KeycloakSession session) {
return new ApplianceBootstrap(session).createTemporaryMasterRealmAdminService(clientId, clientSecret /*, adminExpiration*/);
public boolean createTemporaryMasterRealmAdminService(String clientId, String clientSecret, boolean isTemporary, /*Integer adminExpiration,*/ KeycloakSession session) {
return new ApplianceBootstrap(session).createMasterRealmAdminService(clientId, clientSecret, isTemporary /*, adminExpiration*/);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ void failBadExpiration(LaunchResult result) {
() -> "The Output:\n" + result.getErrorOutput() + "doesn't contains the expected string.");
}*/


@Test
@Launch({ "bootstrap-admin", "user", "--db=dev-file", "--username=admin", "--password:env=MY_PASSWORD" })
void failEnvNotSet(LaunchResult result) {
Expand All @@ -60,15 +61,16 @@ void failEnvNotSet(LaunchResult result) {
@Test
@WithEnvVars({"MY_PASSWORD", "admin123"})
@Launch({ "bootstrap-admin", "user", "--db=dev-file", "--username=admin", "--password:env=MY_PASSWORD" })
void createAdmin(LaunchResult result) {
void createTemporaryAdmin(LaunchResult result) {
assertTrue(result.getErrorOutput().isEmpty(), result.getErrorOutput());
assertTrue(result.getOutput().contains("Created temporary admin user with username admin"));
}

@Test
@Launch({ "start-dev", "--bootstrap-admin-password=MY_PASSWORD" })
void createAdminWithCliOptions(LaunchResult result) {
assertTrue(result.getErrorOutput().isEmpty(), result.getErrorOutput());
result.getOutput().contains("Created temporary admin user with username temp-admin");
assertTrue(result.getOutput().contains("Created temporary admin user with username temp-admin"));
}

@Test
Expand All @@ -78,6 +80,22 @@ void failServiceAccountNoSecret(LaunchResult result) {
() -> "The Output:\n" + result.getErrorOutput() + "doesn't contains the expected string.");
}

@Test
@WithEnvVars({"MY_SECRET", "admin123"})
@Launch({ "bootstrap-admin", "service", "--client-secret:env=MY_SECRET", "--is-temporary=false"})
void createAdminService(LaunchResult result) {
assertTrue(result.getErrorOutput().isEmpty(), result.getErrorOutput());
assertTrue(result.getOutput().contains("Created admin service account with client id temp-admin"));
}

@Test
@WithEnvVars({"MY_PASSWORD", "admin123"})
@Launch({ "bootstrap-admin", "user", "--db=dev-file", "--username=admin", "--password:env=MY_PASSWORD", "--is-temporary=false" })
void createAdmin(LaunchResult result) {
assertTrue(result.getErrorOutput().isEmpty(), result.getErrorOutput());
assertTrue(result.getOutput().contains("Created admin user with username admin"));
}

@Test
@Launch({ "bootstrap-admin", "service", "--db=dev-file", "--client-id=admin", "--client-secret:env=MY_SECRET" })
void failServiceAccountEnvNotSet(LaunchResult result) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ Options:
Environment variable name for the client secret
-h, --help This help message.
--help-all This same help message but with additional options.
--is-temporary <isTemporary>
Indicates whether this admin account is temporary, defaults to true
--no-prompt Run non-interactive without prompting
--optimized Use this option to achieve an optimal startup time if you have previously
built a server image using the 'build' command.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ Options:

-h, --help This help message.
--help-all This same help message but with additional options.
--is-temporary <isTemporary>
Indicates whether this admin account is temporary, defaults to true
--no-prompt Run non-interactive without prompting
--optimized Use this option to achieve an optimal startup time if you have previously
built a server image using the 'build' command.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -270,17 +270,20 @@ Export:
Bootstrap Admin:

--bootstrap-admin-client-id <client id>
Client id for the temporary bootstrap admin service account. Used only when
the master realm is created. Available only when bootstrap admin client
secret is set. Default: temp-admin.
Client id for the bootstrap admin service account. Used only when the master
realm is created. Available only when bootstrap admin client secret is set.
Default: temp-admin.
--bootstrap-admin-client-secret <client secret>
Client secret for the temporary bootstrap admin service account. Used only
when the master realm is created. Use a non-CLI configuration option for
this option if possible.
Client secret for the bootstrap admin service account. Used only when the
master realm is created. Use a non-CLI configuration option for this option
if possible.
--bootstrap-admin-password <password>
Temporary bootstrap admin password. Used only when the master realm is
created. Use a non-CLI configuration option for this option if possible.
Bootstrap admin password. Used only when the master realm is created. Use a
non-CLI configuration option for this option if possible.
--bootstrap-admin-temporary <true|false>
Indicates whether the admin user or service account is temporary. Used only
when the master realm is created. Use a non-CLI configuration option for
this option if possible. Default: true.
--bootstrap-admin-username <username>
Temporary bootstrap admin username. Used only when the master realm is
created. Available only when bootstrap admin password is set. Default:
temp-admin.
Bootstrap admin username. Used only when the master realm is created.
Available only when bootstrap admin password is set. Default: temp-admin.
Original file line number Diff line number Diff line change
Expand Up @@ -438,17 +438,20 @@ Export:
Bootstrap Admin:

--bootstrap-admin-client-id <client id>
Client id for the temporary bootstrap admin service account. Used only when
the master realm is created. Available only when bootstrap admin client
secret is set. Default: temp-admin.
Client id for the bootstrap admin service account. Used only when the master
realm is created. Available only when bootstrap admin client secret is set.
Default: temp-admin.
--bootstrap-admin-client-secret <client secret>
Client secret for the temporary bootstrap admin service account. Used only
when the master realm is created. Use a non-CLI configuration option for
this option if possible.
Client secret for the bootstrap admin service account. Used only when the
master realm is created. Use a non-CLI configuration option for this option
if possible.
--bootstrap-admin-password <password>
Temporary bootstrap admin password. Used only when the master realm is
created. Use a non-CLI configuration option for this option if possible.
Bootstrap admin password. Used only when the master realm is created. Use a
non-CLI configuration option for this option if possible.
--bootstrap-admin-temporary <true|false>
Indicates whether the admin user or service account is temporary. Used only
when the master realm is created. Use a non-CLI configuration option for
this option if possible. Default: true.
--bootstrap-admin-username <username>
Temporary bootstrap admin username. Used only when the master realm is
created. Available only when bootstrap admin password is set. Default:
temp-admin.
Bootstrap admin username. Used only when the master realm is created.
Available only when bootstrap admin password is set. Default: temp-admin.
Loading
0