fix: adds handling for all kcadm prompts as env variables (#29430)

closes: #21961

Signed-off-by: Steve Hawkins <shawkins@redhat.com>
This commit is contained in:
Steven Hawkins 2024-06-06 09:08:23 -04:00 committed by GitHub
parent f34baf3c24
commit c7e9ee2bff
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
26 changed files with 95 additions and 306 deletions

View file

@ -87,6 +87,10 @@ $ kcadm.sh config truststore --trustpass $PASSWORD ~/.keycloak/truststore.jks
c:\> kcadm config truststore --trustpass %PASSWORD% %HOMEPATH%\.keycloak\truststore.jks
----
=== Sensitive Options
Sensitive values, such as passwords, may be specified as command options. That is generally not recommended. There are also mechanisms by which you can be prompted for the sensitive value - by either omitting the option or providing a value or -. Finally all will have a corresponding env variable that can be used instead - check the help of the command you are running to see all possible options.
=== Authenticating
When you log in with the Admin CLI, you specify:
@ -105,7 +109,7 @@ Two primary mechanisms are available for authentication. One mechanism uses `kca
[options="nowrap",subs="attributes+"]
----
$ kcadm.sh config credentials --server http://localhost:8080{kc_base_path} --realm master --user admin --password admin
$ kcadm.sh config credentials --server http://localhost:8080{kc_base_path} --realm master --user admin
----
This mechanism maintains an authenticated session between the `kcadm` command invocations by saving the obtained access token and its associated refresh token. It can maintain other secrets in a private configuration file. See the <<_working_with_alternative_configurations, next chapter>> for more information.
@ -115,13 +119,14 @@ The second mechanism authenticates each command invocation for the duration of t
For example, when performing an operation, specify all the information required for authentication.
[options="nowrap",subs="attributes+"]
----
$ kcadm.sh get realms --no-config --server http://localhost:8080{kc_base_path} --realm master --user admin --password admin
$ kcadm.sh get realms --no-config --server http://localhost:8080{kc_base_path} --realm master --user admin
----
Run the `kcadm.sh help` command for more information on using the Admin CLI.
Run the `kcadm.sh config credentials --help` command for more information about starting an authenticated session.
If you do not specify the --password option (it is generally recommended to not provide passwords as part of the command), you will be prompted for a password unless one is specified as the environment variable KC_CLI_PASSWORD.
[[_working_with_alternative_configurations]]
=== Working with alternative configurations
@ -184,7 +189,7 @@ SERVER_URI/admin/realms/TARGET_REALM/ENDPOINT
For example:
[options="nowrap",subs="attributes+"]
----
$ kcadm.sh config credentials --server http://localhost:8080{kc_base_path} --realm master --user admin --password admin
$ kcadm.sh config credentials --server http://localhost:8080{kc_base_path} --realm master --user admin
$ kcadm.sh create users -s username=testuser -s enabled=true -r demorealm
----

View file

@ -44,6 +44,11 @@ public class KcAdmMain {
return DEFAULT_CONFIG_FILE_PATH;
}
@Override
public boolean isTokenGlobal() {
return true;
};
};
public static void main(String [] args) {

View file

@ -22,7 +22,6 @@ import org.keycloak.client.cli.config.ConfigData;
import picocli.CommandLine.Option;
/**
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
*/

View file

@ -21,7 +21,6 @@ import com.fasterxml.jackson.databind.node.ObjectNode;
import picocli.CommandLine.Command;
import picocli.CommandLine.Option;
import org.keycloak.client.admin.cli.KcAdmMain;
import org.keycloak.client.admin.cli.operations.ClientOperations;
import org.keycloak.client.admin.cli.operations.GroupOperations;
import org.keycloak.client.admin.cli.operations.RoleOperations;
@ -264,10 +263,6 @@ public class AddRolesCmd extends AbstractAuthOptionsCmd {
@Override
protected String help() {
return usage();
}
public static String usage() {
StringWriter sb = new StringWriter();
PrintWriter out = new PrintWriter(sb);
out.println("Usage: " + CMD + " add-roles (--uusername USERNAME | --uid ID) [--cclientid CLIENT_ID | --cid ID] (--rolename NAME | --roleid ID)+ [ARGUMENTS]");
@ -284,21 +279,7 @@ public class AddRolesCmd extends AbstractAuthOptionsCmd {
out.println("to a specific user. If group is specified using --gname, --gpath or --gid then roles are added to a specific group.");
out.println("If composite role is specified using --rname or --rid then roles are added to a specific composite role.");
out.println("One or more roles have to be specified using --rolename or --roleid so that they are added to a group, a user or a composite role.");
out.println();
out.println("Arguments:");
out.println();
out.println(" Global options:");
out.println(" -x Print full stack trace when exiting with error");
out.println(" --config Path to the config file (" + KcAdmMain.DEFAULT_CONFIG_FILE_STRING + " by default)");
out.println(" --no-config Don't use config file - no authentication info is loaded or saved");
out.println(" --token Token to use to invoke on Keycloak. Other credential may be ignored if this flag is set.");
out.println(" --truststore PATH Path to a truststore containing trusted certificates");
out.println(" --trustpass PASSWORD Truststore password (prompted for if not specified and --truststore is used)");
out.println(" CREDENTIALS OPTIONS Same set of options as accepted by '" + CMD + " config credentials' in order to establish");
out.println(" an authenticated sessions. In combination with --no-config option this allows transient");
out.println(" (on-the-fly) authentication to be performed which leaves no tokens in config file.");
out.println();
out.println(" Command specific options:");
globalOptions(out);
out.println(" --uusername User's 'username'. If more than one user exists with the same username");
out.println(" you'll have to use --uid to specify the target user");
out.println(" --uid User's 'id' attribute");
@ -333,4 +314,5 @@ public class AddRolesCmd extends AbstractAuthOptionsCmd {
out.println("Use '" + CMD + " help' for general information and a list of commands");
return sb.toString();
}
}

View file

@ -16,8 +16,6 @@
*/
package org.keycloak.client.admin.cli.commands;
import org.keycloak.client.admin.cli.KcAdmMain;
import java.io.PrintWriter;
import java.io.StringWriter;
@ -75,10 +73,6 @@ public class CreateCmd extends AbstractRequestCmd {
@Override
protected String help() {
return usage();
}
public static String usage() {
StringWriter sb = new StringWriter();
PrintWriter out = new PrintWriter(sb);
out.println("Usage: " + CMD + " create ENDPOINT_URI [ARGUMENTS]");
@ -87,21 +81,7 @@ public class CreateCmd extends AbstractRequestCmd {
out.println();
out.println("Use '" + CMD + " config credentials' to establish an authenticated sessions, or use --no-config with ");
out.println("CREDENTIALS OPTIONS to perform one time authentication.");
out.println();
out.println("Arguments:");
out.println();
out.println(" Global options:");
out.println(" -x Print full stack trace when exiting with error");
out.println(" --config Path to the config file (" + KcAdmMain.DEFAULT_CONFIG_FILE_STRING + " by default)");
out.println(" --no-config Don't use config file - no authentication info is loaded or saved");
out.println(" --token Token to use to invoke on Keycloak. Other credential may be ignored if this flag is set.");
out.println(" --truststore PATH Path to a truststore containing trusted certificates");
out.println(" --trustpass PASSWORD Truststore password (prompted for if not specified and --truststore is used)");
out.println(" CREDENTIALS OPTIONS Same set of options as accepted by '" + CMD + " config credentials' in order to establish");
out.println(" an authenticated sessions. In combination with --no-config option this allows transient");
out.println(" (on-the-fly) authentication to be performed which leaves no tokens in config file.");
out.println();
out.println(" Command specific options:");
globalOptions(out);
out.println(" ENDPOINT_URI URI used to compose a target resource url. Commonly used values are:");
out.println(" realms, users, roles, groups, clients, keys, serverinfo, components ...");
out.println(" If it starts with 'http://' then it will be used as target resource url");

View file

@ -16,8 +16,6 @@
*/
package org.keycloak.client.admin.cli.commands;
import org.keycloak.client.admin.cli.KcAdmMain;
import java.io.PrintWriter;
import java.io.StringWriter;
@ -39,10 +37,6 @@ public class DeleteCmd extends CreateCmd {
@Override
protected String help() {
return usage();
}
public static String usage() {
StringWriter sb = new StringWriter();
PrintWriter out = new PrintWriter(sb);
out.println("Usage: " + CMD + " delete ENDPOINT_URI [ARGUMENTS]");
@ -51,21 +45,7 @@ public class DeleteCmd extends CreateCmd {
out.println();
out.println("Use '" + CMD + " config credentials' to establish an authenticated sessions, or use CREDENTIALS OPTIONS");
out.println("to perform one time authentication.");
out.println();
out.println("Arguments:");
out.println();
out.println(" Global options:");
out.println(" -x Print full stack trace when exiting with error");
out.println(" --config Path to the config file (" + KcAdmMain.DEFAULT_CONFIG_FILE_STRING + " by default)");
out.println(" --no-config Don't use config file - no authentication info is loaded or saved");
out.println(" --token Token to use to invoke on Keycloak. Other credential may be ignored if this flag is set.");
out.println(" --truststore PATH Path to a truststore containing trusted certificates");
out.println(" --trustpass PASSWORD Truststore password (prompted for if not specified and --truststore is used)");
out.println(" CREDENTIALS OPTIONS Same set of options as accepted by '" + CMD + " config credentials' in order to establish");
out.println(" an authenticated sessions. In combination with --no-config option this allows transient");
out.println(" (on-the-fly) authentication to be performed which leaves no tokens in config file.");
out.println();
out.println(" Command specific options:");
globalOptions(out);
out.println(" ENDPOINT_URI URI used to compose a target resource url. Commonly used values start with:");
out.println(" realms/, users/, roles/, groups/, clients/, keys/, components/ ...");
out.println(" If it starts with 'http://' then it will be used as target resource url");

View file

@ -16,8 +16,6 @@
*/
package org.keycloak.client.admin.cli.commands;
import org.keycloak.client.admin.cli.KcAdmMain;
import java.io.PrintWriter;
import java.io.StringWriter;
@ -75,10 +73,6 @@ public class GetCmd extends AbstractRequestCmd {
@Override
protected String help() {
return usage();
}
public static String usage() {
StringWriter sb = new StringWriter();
PrintWriter out = new PrintWriter(sb);
out.println("Usage: " + CMD + " get ENDPOINT_URI [ARGUMENTS]");
@ -87,21 +81,7 @@ public class GetCmd extends AbstractRequestCmd {
out.println();
out.println("Use '" + CMD + " config credentials' to establish an authenticated session, or use CREDENTIALS OPTIONS");
out.println("to perform one time authentication.");
out.println();
out.println("Arguments:");
out.println();
out.println(" Global options:");
out.println(" -x Print full stack trace when exiting with error");
out.println(" --config Path to the config file (" + KcAdmMain.DEFAULT_CONFIG_FILE_STRING + " by default)");
out.println(" --no-config Don't use config file - no authentication info is loaded or saved");
out.println(" --token Token to use to invoke on Keycloak. Other credential may be ignored if this flag is set.");
out.println(" --truststore PATH Path to a truststore containing trusted certificates");
out.println(" --trustpass PASSWORD Truststore password (prompted for if not specified and --truststore is used)");
out.println(" CREDENTIALS OPTIONS Same set of options as accepted by '" + CMD + " config credentials' in order to establish");
out.println(" an authenticated sessions. In combination with --no-config option this allows transient");
out.println(" (on-the-fly) authentication to be performed which leaves no tokens in config file.");
out.println();
out.println(" Command specific options:");
globalOptions(out);
out.println(" ENDPOINT_URI URI used to compose a target resource url. Commonly used values are:");
out.println(" realms, users, roles, groups, clients, keys, serverinfo, components ...");
out.println(" If it starts with 'http://' then it will be used as target resource url");

View file

@ -16,7 +16,6 @@
*/
package org.keycloak.client.admin.cli.commands;
import org.keycloak.client.admin.cli.KcAdmMain;
import org.keycloak.client.admin.cli.operations.ClientOperations;
import org.keycloak.client.admin.cli.operations.GroupOperations;
import org.keycloak.client.admin.cli.operations.RoleOperations;
@ -293,10 +292,6 @@ public class GetRolesCmd extends GetCmd {
@Override
protected String help() {
return usage();
}
public static String usage() {
StringWriter sb = new StringWriter();
PrintWriter out = new PrintWriter(sb);
out.println("Usage: " + CMD + " get-roles [--cclientid CLIENT_ID | --cid ID] [ARGUMENTS]");
@ -319,21 +314,7 @@ public class GetRolesCmd extends GetCmd {
out.println("If --effective is specified, then roles added to the target user or group are transitively resolved and a full");
out.println("set of roles in effect for that user, group or composite role is returned.");
out.println("If --all is specified, then client roles for all clients are returned in addition to realm roles.");
out.println();
out.println("Arguments:");
out.println();
out.println(" Global options:");
out.println(" -x Print full stack trace when exiting with error");
out.println(" --config Path to the config file (" + KcAdmMain.DEFAULT_CONFIG_FILE_STRING + " by default)");
out.println(" --no-config Don't use config file - no authentication info is loaded or saved");
out.println(" --token Token to use to invoke on Keycloak. Other credential may be ignored if this flag is set.");
out.println(" --truststore PATH Path to a truststore containing trusted certificates");
out.println(" --trustpass PASSWORD Truststore password (prompted for if not specified and --truststore is used)");
out.println(" CREDENTIALS OPTIONS Same set of options as accepted by '" + CMD + " config credentials' in order to establish");
out.println(" an authenticated sessions. In combination with --no-config option this allows transient");
out.println(" (on-the-fly) authentication to be performed which leaves no tokens in config file.");
out.println();
out.println(" Command specific options:");
globalOptions(out);
out.println(" --uusername User's 'username'. If more than one user exists with the same username");
out.println(" you'll have to use --uid to specify the target user");
out.println(" --uid User's 'id' attribute");

View file

@ -52,35 +52,35 @@ public class HelpCmd implements Runnable {
break;
}
case "create": {
printOut(CreateCmd.usage());
printOut(new CreateCmd().help());
break;
}
case "get": {
printOut(GetCmd.usage());
printOut(new GetCmd().help());
break;
}
case "update": {
printOut(UpdateCmd.usage());
printOut(new UpdateCmd().help());
break;
}
case "delete": {
printOut(DeleteCmd.usage());
printOut(new DeleteCmd().help());
break;
}
case "get-roles": {
printOut(GetRolesCmd.usage());
printOut(new GetRolesCmd().help());
break;
}
case "add-roles": {
printOut(AddRolesCmd.usage());
printOut(new AddRolesCmd().help());
break;
}
case "remove-roles": {
printOut(RemoveRolesCmd.usage());
printOut(new RemoveRolesCmd().help());
break;
}
case "set-password": {
printOut(SetPasswordCmd.usage());
printOut(new SetPasswordCmd().help());
break;
}
case "new-object": {

View file

@ -16,7 +16,6 @@
*/
package org.keycloak.client.admin.cli.commands;
import org.keycloak.client.admin.cli.KcAdmMain;
import org.keycloak.client.admin.cli.operations.ClientOperations;
import org.keycloak.client.admin.cli.operations.GroupOperations;
import org.keycloak.client.admin.cli.operations.LocalSearch;
@ -271,10 +270,6 @@ public class RemoveRolesCmd extends AbstractAuthOptionsCmd {
@Override
protected String help() {
return usage();
}
public static String usage() {
StringWriter sb = new StringWriter();
PrintWriter out = new PrintWriter(sb);
out.println("Usage: " + CMD + " remove-roles (--uusername USERNAME | --uid ID) [--cclientid CLIENT_ID | --cid ID] (--rolename NAME | --roleid ID)+ [ARGUMENTS]");
@ -291,21 +286,7 @@ public class RemoveRolesCmd extends AbstractAuthOptionsCmd {
out.println("from a specific user. If group is specified using --gname, --gpath or --gid then roles are removed from a specific group.");
out.println("If composite role is specified using --rname or --rid then roles are removed from a specific composite role.");
out.println("One or more roles have to be specified using --rolename or --roleid to be removed from a group, a user or a composite role.");
out.println();
out.println("Arguments:");
out.println();
out.println(" Global options:");
out.println(" -x Print full stack trace when exiting with error");
out.println(" --config Path to the config file (" + KcAdmMain.DEFAULT_CONFIG_FILE_STRING + " by default)");
out.println(" --no-config Don't use config file - no authentication info is loaded or saved");
out.println(" --token Token to use to invoke on Keycloak. Other credential may be ignored if this flag is set.");
out.println(" --truststore PATH Path to a truststore containing trusted certificates");
out.println(" --trustpass PASSWORD Truststore password (prompted for if not specified and --truststore is used)");
out.println(" CREDENTIALS OPTIONS Same set of options as accepted by '" + CMD + " config credentials' in order to establish");
out.println(" an authenticated sessions. In combination with --no-config option this allows transient");
out.println(" (on-the-fly) authentication to be performed which leaves no tokens in config file.");
out.println();
out.println(" Command specific options:");
globalOptions(out);
out.println(" --uusername User's 'username'. If more than one user exists with the same username");
out.println(" you'll have to use --uid to specify the target user");
out.println(" --uid User's 'id' attribute");

View file

@ -16,7 +16,6 @@
*/
package org.keycloak.client.admin.cli.commands;
import org.keycloak.client.admin.cli.KcAdmMain;
import org.keycloak.client.cli.config.ConfigData;
import java.io.PrintWriter;
@ -45,7 +44,7 @@ public class SetPasswordCmd extends AbstractAuthOptionsCmd {
@Option(names = "--userid", description = "User ID")
String userid;
@Option(names = {"-p", "--new-password"}, description = "New password")
@Option(names = {"-p", "--new-password"}, description = "New password", defaultValue = "${env:KC_CLI_PASSWORD}")
String pass;
@Option(names = {"-t", "--temporary"}, description = "is password temporary")
@ -99,10 +98,6 @@ public class SetPasswordCmd extends AbstractAuthOptionsCmd {
@Override
protected String help() {
return usage();
}
public static String usage() {
StringWriter sb = new StringWriter();
PrintWriter out = new PrintWriter(sb);
out.println("Usage: " + CMD + " set-password (--username USERNAME | --userid ID) [--new-password PASSWORD] [ARGUMENTS]");
@ -111,24 +106,10 @@ public class SetPasswordCmd extends AbstractAuthOptionsCmd {
out.println();
out.println("Use `" + CMD + " config credentials` to establish an authenticated session, or use CREDENTIALS OPTIONS");
out.println("to perform one time authentication.");
out.println();
out.println("Arguments:");
out.println();
out.println(" Global options:");
out.println(" -x Print full stack trace when exiting with error");
out.println(" --config Path to the config file (" + KcAdmMain.DEFAULT_CONFIG_FILE_STRING + " by default)");
out.println(" --no-config Don't use config file - no authentication info is loaded or saved");
out.println(" --token Token to use to invoke on Keycloak. Other credential may be ignored if this flag is set.");
out.println(" --truststore PATH Path to a truststore containing trusted certificates");
out.println(" --trustpass PASSWORD Truststore password (prompted for if not specified and --truststore is used)");
out.println(" CREDENTIALS OPTIONS Same set of options as accepted by '" + CMD + " config credentials' in order to establish");
out.println(" an authenticated sessions. In combination with --no-config option this allows transient");
out.println(" (on-the-fly) authentication to be performed which leaves no tokens in config file.");
out.println();
out.println(" Command specific options:");
globalOptions(out);
out.println(" --username USERNAME Identify target user by 'username'");
out.println(" --userid ID Identify target user by 'id'");
out.println(" -p, --new-password New password to set. If not specified you will be prompted for it.");
out.println(" -p, --new-password New password to set. If not specified and the env variable KC_CLI_PASSWORD is not defined, you will be prompted for it.");
out.println(" -t, --temporary Make the new password temporary - user has to change it on next logon");
out.println(" -a, --admin-root URL URL of Admin REST endpoint root if not default - e.g. http://localhost:8080/admin");
out.println(" -r, --target-realm REALM Target realm to issue requests against if not the one authenticated against");

View file

@ -17,8 +17,6 @@
package org.keycloak.client.admin.cli.commands;
import org.keycloak.client.admin.cli.KcAdmMain;
import java.io.PrintWriter;
import java.io.StringWriter;
@ -81,10 +79,6 @@ public class UpdateCmd extends AbstractRequestCmd {
@Override
protected String help() {
return usage();
}
public static String usage() {
StringWriter sb = new StringWriter();
PrintWriter out = new PrintWriter(sb);
out.println("Usage: " + CMD + " update ENDPOINT_URI [ARGUMENTS]");
@ -93,21 +87,7 @@ public class UpdateCmd extends AbstractRequestCmd {
out.println();
out.println("Use '" + CMD + " config credentials' to establish an authenticated sessions, or use CREDENTIALS OPTIONS");
out.println("to perform one time authentication.");
out.println();
out.println("Arguments:");
out.println();
out.println(" Global options:");
out.println(" -x Print full stack trace when exiting with error");
out.println(" --config Path to the config file (" + KcAdmMain.DEFAULT_CONFIG_FILE_STRING + " by default)");
out.println(" --no-config Don't use config file - no authentication info is loaded or saved");
out.println(" --token Token to use to invoke on Keycloak. Other credential may be ignored if this flag is set.");
out.println(" --truststore PATH Path to a truststore containing trusted certificates");
out.println(" --trustpass PASSWORD Truststore password (prompted for if not specified and --truststore is used)");
out.println(" CREDENTIALS OPTIONS Same set of options as accepted by '" + CMD + " config credentials' in order to establish");
out.println(" an authenticated sessions. In combination with --no-config option this allows transient");
out.println(" (on-the-fly) authentication to be performed which leaves no tokens in config file.");
out.println();
out.println(" Command specific options:");
globalOptions(out);
out.println(" ENDPOINT_URI URI used to compose a target resource url. Commonly used values start with:");
out.println(" realms/, users/, roles/, groups/, clients/, keys/, components/ ...");
out.println(" If it starts with 'http://' then it will be used as target resource url");

View file

@ -28,6 +28,7 @@ import org.keycloak.client.cli.util.HttpUtil;
import org.keycloak.client.cli.util.IoUtil;
import java.io.File;
import java.io.PrintWriter;
import picocli.CommandLine.Option;
@ -62,19 +63,19 @@ public abstract class BaseAuthOptionsCmd extends BaseGlobalOptionsCmd {
@Option(names = "--user", description = "Username to login with")
protected String user;
@Option(names = "--password", description = "Password to login with (prompted for if not specified and --user is used)")
@Option(names = "--password", description = "Password to login with (prompted for if not specified, --user is used, and the env variable KC_CLI_PASSWORD is not defined)", defaultValue = "${env:KC_CLI_PASSWORD}")
protected String password;
@Option(names = "--secret", description = "Secret to authenticate the client (prompted for if no --user or --keystore is specified)")
@Option(names = "--secret", description = "Secret to authenticate the client (prompted for if no --user nor --keystore is specified, and the env variable KC_CLI_CLIENT_SECRET is not defined)", defaultValue = "${env:KC_CLI_CLIENT_SECRET}")
protected String secret;
@Option(names = "--keystore", description = "Path to a keystore containing private key")
protected String keystore;
@Option(names = "--storepass", description = "Keystore password (prompted for if not specified and --keystore is used)")
@Option(names = "--storepass", description = "Keystore password (prompted for if not specified, --keystore is used, and the env variable KC_CLI_STORE_PASSWORD is undefined)", defaultValue = "${env:KC_CLI_STORE_PASSWORD}")
protected String storePass;
@Option(names = "--keypass", description = "Key password (prompted for if not specified and --keystore is used without --storepass, \n otherwise defaults to keystore password)")
@Option(names = "--keypass", description = "Key password (prompted for if not specified and --keystore is used without --storepass, \n otherwise defaults to keystore password)", defaultValue = "${env:KC_CLI_KEY_PASSWORD}")
protected String keyPass;
@Option(names = "--alias", description = "Alias of the key inside a keystore (defaults to the value of ClientId)")
@ -83,7 +84,7 @@ public abstract class BaseAuthOptionsCmd extends BaseGlobalOptionsCmd {
@Option(names = "--truststore", description = "Path to a truststore")
protected String trustStore;
@Option(names = "--trustpass", description = "Truststore password (prompted for if not specified and --truststore is used)")
@Option(names = "--trustpass", description = "Truststore password (prompted for if not specified, --user is used, and the env variable KC_CLI_TRUSTSTORE_PASSWORD is not defined)", defaultValue = "${env:KC_CLI_TRUSTSTORE_PASSWORD}")
protected String trustPass;
@Option(names = "--insecure", description = "Turns off TLS validation")
@ -273,4 +274,23 @@ public abstract class BaseAuthOptionsCmd extends BaseGlobalOptionsCmd {
return AuthUtil.ensureToken(config, getCommand());
}
protected void globalOptions(PrintWriter out) {
out.println();
out.println("Arguments:");
out.println();
out.println(" Global options:");
out.println(" -x Print full stack trace when exiting with error");
out.println(" --config Path to the config file (" + commandState.getDefaultConfigFilePath() + " by default)");
out.println(" --no-config Don't use config file - no authentication info is loaded or saved");
if (commandState.isTokenGlobal()) {
out.println(" --token Token to use to invoke on Keycloak. Other credential may be ignored if this flag is set.");
}
out.println(" --truststore PATH Path to a truststore containing trusted certificates");
out.println(" --trustpass PASSWORD Truststore password (prompted for if not specified, --truststore is used, and the KC_CLI_TRUSTSTORE_PASSWORD env property is not defined)");
out.println(" CREDENTIALS OPTIONS Same set of options as accepted by '" + commandState.getCommand() + " config credentials' in order to establish");
out.println(" an authenticated sessions. In combination with --no-config option this allows transient");
out.println(" (on-the-fly) authentication to be performed which leaves no tokens in config file.");
out.println();
}
}

View file

@ -113,10 +113,8 @@ public class BaseConfigCredentialsCmd extends BaseAuthOptionsCmd {
} else if (keystore != null || secret != null || clientSet) {
grantTypeForAuthentication = OAuth2Constants.CLIENT_CREDENTIALS;
printErr("Logging into " + server + " as " + "service-account-" + clientId + " of realm " + realm);
if (keystore == null) {
if (secret == null) {
secret = readSecret("Enter client secret: ");
}
if (keystore == null && secret == null) {
secret = readSecret("Enter client secret: ");
}
}
@ -196,18 +194,18 @@ public class BaseConfigCredentialsCmd extends BaseAuthOptionsCmd {
out.println(" -x Print full stack trace when exiting with error");
out.println(" --config Path to a config file (" + getDefaultConfigFilePath() + " by default)");
out.println(" --truststore PATH Path to a truststore containing trusted certificates");
out.println(" --trustpass PASSWORD Truststore password (prompted for if not specified and --truststore is used)");
out.println(" --trustpass PASSWORD Truststore password (prompted for if not specified, --truststore is used, and the KC_CLI_TRUSTSTORE_PASSWORD env property is not defined)");
out.println();
out.println(" Command specific options:");
out.println(" --server SERVER_URL Server endpoint url (e.g. 'http://localhost:8080')");
out.println(" --realm REALM Realm name to use");
out.println(" --user USER Username to login with");
out.println(" --password PASSWORD Password to login with (prompted for if not specified and --user is used)");
out.println(" --password PASSWORD Password to login with (prompted for if not specified, --user is used, and the env variable KC_CLI_PASSWORD is not defined)");
out.println(" --client CLIENT_ID ClientId used by this client tool ('admin-cli' by default)");
out.println(" --secret SECRET Secret to authenticate the client (prompted for if --client is specified, and no --keystore is specified)");
out.println(" --secret SECRET Secret to authenticate the client (prompted for if no --user nor --keystore is specified, and the env variable KC_CLI_CLIENT_SECRET is not defined)");
out.println(" --keystore PATH Path to a keystore containing private key");
out.println(" --storepass PASSWORD Keystore password (prompted for if not specified and --keystore is used)");
out.println(" --keypass PASSWORD Key password (prompted for if not specified and --keystore is used without --storepass,");
out.println(" --storepass PASSWORD Keystore password (prompted for if not specified, --keystore is used, and the env variable KC_CLI_STORE_PASSWORD)");
out.println(" --keypass PASSWORD Key password (prompted for if not specified, --keystore is used without --storepass, and KC_CLI_KEY_PASSWORD");
out.println(" otherwise defaults to keystore password)");
out.println(" --alias ALIAS Alias of the key inside a keystore (defaults to the value of ClientId)");
out.println();

View file

@ -115,7 +115,7 @@ public class BaseConfigTruststoreCmd extends BaseAuthOptionsCmd {
out.println();
out.println(" Command specific options:");
out.println(" TRUSTSTORE Path to truststore file");
out.println(" --trustpass PASSWORD Truststore password to unlock truststore (prompted for if set to '-')");
out.println(" --trustpass PASSWORD Truststore password to unlock truststore (prompted for if set to '-'), defaults to the env variable KC_CLI_TRUSTSTORE_PASSWORD");
out.println(" -d, --delete Remove truststore configuration");
out.println();
out.println();

View file

@ -23,4 +23,6 @@ public interface CommandState {
String getDefaultConfigFilePath();
boolean isTokenGlobal();
}

View file

@ -65,14 +65,6 @@ public class IoUtil {
return content;
}
public static void waitFor(long millis) {
try {
Thread.sleep(millis);
} catch (InterruptedException e) {
throw new RuntimeException("Interrupted");
}
}
public static String readSecret(String prompt) {
Console cons = System.console();
if (cons == null) {

View file

@ -28,6 +28,11 @@ public class KcRegMain {
return DEFAULT_CONFIG_FILE_PATH;
}
@Override
public boolean isTokenGlobal() {
return false;
};
};
public static void main(String [] args) {

View file

@ -20,7 +20,6 @@ package org.keycloak.client.registration.cli.commands;
import org.keycloak.client.registration.cli.CmdStdinContext;
import org.keycloak.client.registration.cli.EndpointType;
import org.keycloak.client.registration.cli.EndpointTypeConverter;
import org.keycloak.client.registration.cli.KcRegMain;
import org.keycloak.client.cli.common.AttributeOperation;
import org.keycloak.client.cli.config.ConfigData;
import org.keycloak.client.cli.util.HttpUtil;
@ -200,10 +199,6 @@ public class CreateCmd extends AbstractAuthOptionsCmd {
@Override
protected String help() {
return usage();
}
public static String usage() {
StringWriter sb = new StringWriter();
PrintWriter out = new PrintWriter(sb);
out.println("Usage: " + CMD + " create [ARGUMENTS]");
@ -211,20 +206,7 @@ public class CreateCmd extends AbstractAuthOptionsCmd {
out.println("Command to create new client configurations on the server. If Initial Access Token is specified (-t TOKEN)");
out.println("or has previously been set for the server, and realm in the configuration ('" + CMD + " config initial-token'),");
out.println("then that will be used, otherwise session access / refresh tokens will be used.");
out.println();
out.println("Arguments:");
out.println();
out.println(" Global options:");
out.println(" -x Print full stack trace when exiting with error");
out.println(" --config Path to the config file (" + KcRegMain.DEFAULT_CONFIG_FILE_STRING + " by default)");
out.println(" --no-config Don't use config file - no authentication info is loaded or saved");
out.println(" --truststore PATH Path to a truststore containing trusted certificates");
out.println(" --trustpass PASSWORD Truststore password (prompted for if not specified and --truststore is used)");
out.println(" CREDENTIALS OPTIONS Same set of options as accepted by '" + CMD + " config credentials' in order to establish");
out.println(" an authenticated sessions. In combination with --no-config option this allows transient");
out.println(" (on-the-fly) authentication to be performed which leaves no tokens in config file.");
out.println();
out.println(" Command specific options:");
globalOptions(out);
out.println(" -t, --token TOKEN Use the specified Initial Access Token for authorization or read it from standard input ");
out.println(" if '-' is specified. This overrides any token set by '" + CMD + " config initial-token'.");
out.println(" If not specified, session credentials are used - see: CREDENTIALS OPTIONS.");

View file

@ -17,7 +17,6 @@
package org.keycloak.client.registration.cli.commands;
import org.keycloak.client.registration.cli.KcRegMain;
import org.keycloak.client.cli.config.ConfigData;
import org.keycloak.client.registration.cli.CmdStdinContext;
@ -98,30 +97,13 @@ public class DeleteCmd extends AbstractAuthOptionsCmd {
@Override
protected String help() {
return usage();
}
public static String usage() {
StringWriter sb = new StringWriter();
PrintWriter out = new PrintWriter(sb);
out.println("Usage: " + CMD + " delete CLIENT [ARGUMENTS]");
out.println();
out.println("Command to delete a specific client configuration. If registration access token is specified or is available in ");
out.println("configuration file, then it is used. Otherwise, current active session is used.");
out.println();
out.println("Arguments:");
out.println();
out.println(" Global options:");
out.println(" -x Print full stack trace when exiting with error");
out.println(" --config Path to the config file (" + KcRegMain.DEFAULT_CONFIG_FILE_STRING + " by default)");
out.println(" --no-config Don't use config file - no authentication info is loaded or saved");
out.println(" --truststore PATH Path to a truststore containing trusted certificates");
out.println(" --trustpass PASSWORD Truststore password (prompted for if not specified and --truststore is used)");
out.println(" CREDENTIALS OPTIONS Same set of options as accepted by '" + CMD + " config credentials' in order to establish");
out.println(" an authenticated sessions. In combination with --no-config option this allows transient");
out.println(" (on-the-fly) authentication to be performed which leaves no tokens in config file.");
out.println();
out.println(" Command specific options:");
globalOptions(out);
out.println(" CLIENT ClientId of the client to delete");
out.println(" -t, --token TOKEN Use the specified Registration Access Token for authorization");
out.println();

View file

@ -17,7 +17,6 @@
package org.keycloak.client.registration.cli.commands;
import org.keycloak.client.registration.cli.KcRegMain;
import org.keycloak.client.cli.config.ConfigData;
import org.keycloak.client.registration.cli.CmdStdinContext;
import org.keycloak.client.registration.cli.EndpointType;
@ -160,30 +159,13 @@ public class GetCmd extends AbstractAuthOptionsCmd {
@Override
protected String help() {
return usage();
}
public static String usage() {
StringWriter sb = new StringWriter();
PrintWriter out = new PrintWriter(sb);
out.println("Usage: " + CMD + " get CLIENT [ARGUMENTS]");
out.println();
out.println("Command to retrieve a client configuration description for a specified client. If registration access token");
out.println("is specified or is available in configuration file, then it is used. Otherwise, current active session is used.");
out.println();
out.println("Arguments:");
out.println();
out.println(" Global options:");
out.println(" -x Print full stack trace when exiting with error");
out.println(" --config Path to the config file (" + KcRegMain.DEFAULT_CONFIG_FILE_STRING + " by default)");
out.println(" --no-config Don't use config file - no authentication info is loaded or saved");
out.println(" --truststore PATH Path to a truststore containing trusted certificates");
out.println(" --trustpass PASSWORD Truststore password (prompted for if not specified and --truststore is used)");
out.println(" CREDENTIALS OPTIONS Same set of options as accepted by '" + CMD + " config credentials' in order to establish");
out.println(" an authenticated sessions. In combination with --no-config option this allows transient");
out.println(" (on-the-fly) authentication to be performed which leaves no tokens in config file.");
out.println();
out.println(" Command specific options:");
globalOptions(out);
out.println(" CLIENT ClientId of the client to display");
out.println(" -t, --token TOKEN Use the specified Registration Access Token for authorization");
out.println(" -c, --compressed Don't pretty print the output");

View file

@ -48,19 +48,19 @@ public class HelpCmd implements Runnable {
break;
}
case "create": {
printOut(CreateCmd.usage());
printOut(new CreateCmd().help());
break;
}
case "get": {
printOut(GetCmd.usage());
printOut(new GetCmd().help());
break;
}
case "update": {
printOut(UpdateCmd.usage());
printOut(new UpdateCmd().help());
break;
}
case "delete": {
printOut(DeleteCmd.usage());
printOut(new DeleteCmd().help());
break;
}
case "attrs": {
@ -68,7 +68,7 @@ public class HelpCmd implements Runnable {
break;
}
case "update-token": {
printOut(UpdateTokenCmd.usage());
printOut(new UpdateTokenCmd().help());
break;
}
default: {

View file

@ -28,7 +28,6 @@ import org.keycloak.client.registration.cli.CmdStdinContext;
import org.keycloak.client.registration.cli.EndpointType;
import org.keycloak.client.registration.cli.EndpointTypeConverter;
import org.keycloak.client.registration.cli.ReflectionUtil;
import org.keycloak.client.registration.cli.KcRegMain;
import org.keycloak.client.cli.common.AttributeOperation;
import org.keycloak.client.cli.config.ConfigData;
import org.keycloak.representations.idm.ClientRepresentation;
@ -311,10 +310,6 @@ public class UpdateCmd extends AbstractAuthOptionsCmd {
@Override
protected String help() {
return usage();
}
public static String usage() {
StringWriter sb = new StringWriter();
PrintWriter out = new PrintWriter(sb);
out.println("Usage: " + CMD + " update CLIENT [ARGUMENTS]");
@ -323,19 +318,7 @@ public class UpdateCmd extends AbstractAuthOptionsCmd {
out.println("Otherwise, if 'registrationAccessToken' attribute is set, that is used. Otherwise, if registration access");
out.println("token is available in configuration file, we use that. Finally, if it's not available anywhere, the current ");
out.println("active session is used.");
out.println();
out.println("Arguments:");
out.println();
out.println(" Global options:");
out.println(" -x Print full stack trace when exiting with error");
out.println(" --config Path to the config file (" + KcRegMain.DEFAULT_CONFIG_FILE_STRING + " by default)");
out.println(" --no-config Don't use config file - no authentication info is loaded or saved");
out.println(" --truststore PATH Path to a truststore containing trusted certificates");
out.println(" --trustpass PASSWORD Truststore password (prompted for if not specified and --truststore is used)");
out.println(" CREDENTIALS OPTIONS Same set of options as accepted by '" + CMD + " config credentials' in order to establish");
out.println(" an authenticated sessions. In combination with --no-config option this allows transient");
out.println(" (on-the-fly) authentication to be performed which leaves no tokens in config file.");
out.println();
globalOptions(out);
out.println(" Command specific options:");
out.println(" CLIENT ClientId of the client to update");
out.println(" -t, --token TOKEN Use the specified Registration Access Token for authorization");

View file

@ -22,7 +22,6 @@ import com.fasterxml.jackson.core.type.TypeReference;
import picocli.CommandLine.Command;
import picocli.CommandLine.Parameters;
import org.keycloak.client.registration.cli.KcRegMain;
import org.keycloak.client.cli.config.ConfigData;
import org.keycloak.client.registration.cli.CmdStdinContext;
import org.keycloak.representations.idm.ClientRepresentation;
@ -120,30 +119,13 @@ public class UpdateTokenCmd extends AbstractAuthOptionsCmd {
@Override
protected String help() {
return usage();
}
public static String usage() {
StringWriter sb = new StringWriter();
PrintWriter out = new PrintWriter(sb);
out.println("Usage: " + CMD + " update-token CLIENT [ARGUMENTS]");
out.println();
out.println("Command to reissue, and set a new registration access token if an old one is lost or becomes invalid.");
out.println("It requires an authenticated session using an account with administrator privileges.");
out.println();
out.println("Arguments:");
out.println();
out.println(" Global options:");
out.println(" -x Print full stack trace when exiting with error");
out.println(" --config Path to the config file (" + KcRegMain.DEFAULT_CONFIG_FILE_STRING + " by default)");
out.println(" --no-config Don't use config file - no authentication info is loaded or saved");
out.println(" --truststore PATH Path to a truststore containing trusted certificates");
out.println(" --trustpass PASSWORD Truststore password (prompted for if not specified and --truststore is used)");
out.println(" CREDENTIALS OPTIONS Same set of options as accepted by '" + CMD + " config credentials' in order to establish");
out.println(" an authenticated sessions. In combination with --no-config option this allows transient");
out.println(" (on-the-fly) authentication to be performed which leaves no tokens in config file.");
out.println();
out.println(" Command specific options:");
globalOptions(out);
out.println(" CLIENT ClientId of the client to reissue a new Registration Access Token for");
out.println(" The new token is saved to a config file or printed to stdout if --no-config");
out.println(" (on-the-fly) authentication is used");

View file

@ -345,12 +345,18 @@ public abstract class AbstractAdmCliTest extends AbstractCliTest {
realm.getUsers().add(account);
}
void loginAsUser(File configFile, String server, String realm, String user, String password) {
KcAdmExec exe = KcAdmExec.execute("config credentials --server " + server + " --realm " + realm +
" --user " + user + " --password " + password + " --config " + configFile.getAbsolutePath());
void loginAsUser(File configFile, String server, String realm, String user, String password, boolean envPassword) {
KcAdmExec exe = KcAdmExec.newBuilder()
.argsLine("config credentials --server " + server + " --realm " + realm +
" --user " + user + (envPassword ? "" : (" --password " + password)) + " --config " + configFile.getAbsolutePath())
.env(envPassword ? "KC_CLI_PASSWORD=" + password : null)
.execute();
assertExitCodeAndStreamSizes(exe, 0, 0, 1);
}
void loginAsUser(File configFile, String server, String realm, String user, String password) {
loginAsUser(configFile, server, realm, user, password, false);
}
}

View file

@ -36,8 +36,9 @@ public class KcAdmSessionTest extends AbstractAdmCliTest {
try (TempFileResource configFile = new TempFileResource(FileConfigHandler.getConfigFile())) {
// login as admin
loginAsUser(configFile.getFile(), serverUrl, "master", "admin", "admin");
// login as admin using command option and env password
loginAsUser(configFile.getFile(), serverUrl, "master", "admin", "admin", false);
loginAsUser(configFile.getFile(), serverUrl, "master", "admin", "admin", true);
// create realm
KcAdmExec exe = execute("create realms --config '" + configFile.getName() + "' -s realm=demorealm -s enabled=true");