Maintained fork of François Onimus's original repository: fonimus/ssh-shell-spring-boot
Spring shell in spring boot application over ssh
For more information please
visit spring shell website.
- Getting started
- Configuration
- Commands
- Post processors
- Parameter providers
- Custom authentication
- Command helper
- Banner
- Listeners
- Session Manager
- Tests
- Samples
- Release notes
- For Next Maintainer
<dependency>
<groupId>io.github.choseongah</groupId>
<artifactId>ssh-shell-spring-boot-starter</artifactId>
</dependency>Warning: interactive shell is enabled by default.
You can set property spring.shell.interactive.enabled=false to disable it.
Note: auto configuration
SshShellAutoConfiguration(active by default) can be deactivated by property ssh.shell.enable=false.
It means that the ssh server won't start and the commands won't be scanned. If you only want to use the application through SSH, you should usually also disable the local interactive shell with the following property:
spring:
shell:
interactive:
enabled: falsePlease check class: SshShellProperties.java for more information.
ssh:
shell:
enable: true
host: 127.0.0.1
port: 2222
user: user
# displayed in log if generated
password:
display-banner: true
# 'simple' or 'security'
authentication: simple
# if authentication is set to 'security', the AuthenticationManager bean name
# if not specified and only one AuthenticationManager bean is present in the context, it will be used
auth-provider-bean-name:
host-key-file: <java.io.tmpdir>/hostKey.ser
# since 1.2.2, optional file containing authorized public keys (standard authorized_keys format, one key per line
# starting with 'ssh-rsa'), takes precedence over authentication (simple or security)
authorized-public-keys-file:
# optional spring resource containing authorized public keys (file:, classpath:, etc)
# note: in case of a non file resource, a temporary file can be created depending on resource type
# this takes precedence over authentication (simple or security)
authorized-public-keys:
history-file: <java.io.tmpdir>/sshShellHistory.log
# set to false have one file per user (<history-directory>/sshShellHistory-<user>.log)
shared-history: true
# only used if shared-history is set to false
history-directory: <java.io.tmpdir>
# for ssh helper 'confirm' method
confirmation-words:
- y
- yes
prompt:
# in enum: io.github.choseongah.ssh.shell.PromptColor (black, red, green, yellow, blue, magenta, cyan, white, bright)
color: white
text: 'shell>'
commands:
# all starter command groups except stacktrace are disabled by default in the Spring Shell 4 line
actuator:
enabled: false
restricted: true
# empty by default
includes:
- ...
# empty by default
excludes:
- ...
authorized-roles:
- ACTUATOR
datasource:
enabled: false
restricted: true
# empty by default
includes:
- ...
# datasource-update is excluded by default
excludes:
- datasource-update
authorized-roles:
- ADMIN
history:
enabled: false
restricted: true
# empty by default
includes:
- ...
# empty by default
excludes:
- ...
authorized-roles:
- ADMIN
jmx:
enabled: false
restricted: true
# empty by default
includes:
- ...
# empty by default
excludes:
- ...
authorized-roles:
- ADMIN
manage-sessions:
enabled: false
restricted: true
# empty by default
includes:
- ...
# empty by default
excludes:
- ...
authorized-roles:
- ADMIN
postprocessors:
enabled: false
restricted: false
# empty by default
includes:
- ...
# empty by default
excludes:
- ...
# not used if restricted is false
authorized-roles:
- ...
script:
enabled: false
restricted: true
# empty by default
includes:
- ...
# empty by default
excludes:
- ...
authorized-roles:
- ADMIN
stacktrace:
enabled: true
restricted: false
# empty by default
includes:
- ...
# empty by default
excludes:
- ...
# not used if restricted is false
authorized-roles:
system:
enabled: false
restricted: true
# empty by default
includes:
- ...
# empty by default
excludes:
- ...
authorized-roles:
- ADMIN
tasks:
enabled: false
restricted: true
# empty by default
includes:
- ...
# empty by default
excludes:
- ...
authorized-roles:
- ADMIN-
Add
spring-boot-starter-actuatordependency to get actuator commands -
Add
spring-boot-starter-securitydependency to configuressh.shell.authentication=securitywithAuthenticationManager -
Role based restrictions are meaningful with
ssh.shell.authentication=securitybecause the defaultsimpleauthentication does not attach authorities
For a typical SSH only application, the following setup is the recommended baseline:
spring:
shell:
interactive:
enabled: false
ssh:
shell:
authentication: security
commands:
actuator:
enabled: true
history:
enabled: true
script:
enabled: true
stacktrace:
enabled: trueRecommended usage rules:
- Set
spring.shell.interactive.enabled=falseif the application is meant to be used through SSH only - Prefer
ssh.shell.authentication=securityif you use restricted commands orauthorized-roles - Enable starter command groups explicitly with
ssh.shell.commands.<group>.enabled=true - Use
authorized-public-keysorauthorized-public-keys-fileif public key authentication is preferred over password authentication - Use
shared-history=falsewithhistory-directoryif you want one history file per SSH user
The following settings are supported, but need some context:
ssh.shell.authentication=simpleis supported, but restricted commands are effectively allowed for authenticated SSH users because no authorities are attached in simple mode.ssh.shell.commands.<group>.restrictedandauthorized-rolesare meaningful for SSH sessions with Spring Security authorities; local prompt execution still bypasses authority checks.management.endpoints.access.*andmanagement.endpoint.<id>.accessare supported and recommended for actuator commands, but current implementation still also checks legacymanagement.endpoint.<id>.enabled.ssh.shell.commands.actuator.restrictedis supported, butinfois intentionally still available even when the rest of the actuator group is forbidden for the current SSH user.authorized-public-keyssupports SpringResourcevalues such asfile:andclasspath:; for non-file resources the content is copied to a temporary file because sshd requires a file based authorized keys source.ssh.shell.enable=falsedisables this starter, but it is not a substitute forspring.shell.interactive.enabled=false.
All starter command groups are disabled by default.
To enable a group, set the enabled property to true:
ssh:
shell:
commands:
manage-sessions:
enabled: trueIf enabled=false, the command group bean is not created, the commands are not
registered, and they do not appear in help.
Sub commands in a group can be filtered by includes and excludes
properties:
ssh:
shell:
commands:
datasource:
includes:
- datasource-list
- datasource-properties
excludes:
- datasource-updateTo include all sub commands, set excludes to an empty array:
ssh:
shell:
commands:
datasource:
enabled: true
excludes:You can write your command exactly the way you would do with spring shell.
For more information please
visit spring shell website.
Instead of using a regular @Component, you can use
io.github.choseongah.ssh.shell.commands.SshShellComponent:
it is just a conditional @Component with @ConditionalOnProperty on
property ssh.shell.enable.
Commands themselves are declared with Spring Shell 4 annotations such as
@Command, @Argument and @Option.
Example:
import io.github.choseongah.ssh.shell.commands.SshShellComponent;
import org.springframework.shell.core.command.annotation.Argument;
import org.springframework.shell.core.command.annotation.Command;
import org.springframework.shell.core.command.annotation.Option;
@SshShellComponent
public class TestCommands {
@Command(name = "test", group = "Test Commands", description = "test command")
public String test(
@Argument(index = 0, defaultValue = "world") String message,
@Option(longName = "uppercase", defaultValue = "false") boolean uppercase
) {
return uppercase ? message.toUpperCase() : message;
}
}All command groups can be activated or deactivated by enabled property:
ssh:
shell:
commands:
<command>:
enabled: trueSub commands in group can also be filtered by includes and excludes
properties:
ssh:
shell:
commands:
<command>:
includes:
- xxx
excludes:
- xxxIf org.springframework.boot:spring-boot-starter-actuator dependency is
present and ssh.shell.commands.actuator.enabled=true,
actuator commands can be available.
Command availability is bound to endpoint access and bean presence.
For Spring Boot 4, actuator access is based on management.endpoints.access
and endpoint specific access properties.
Current implementation also still checks management.endpoint.<id>.enabled
in addition to the Spring Boot 4 access properties.
Example:
management:
endpoints:
access:
default: unrestricted
web:
exposure:
include: "*"
jmx:
exposure:
include: "*"
endpoint:
shutdown:
access: unrestricted
heapdump:
access: unrestrictedSome endpoints also require extra beans or dependencies to exist in the
application context, for example audit, httpexchanges, sessions,
prometheus, flyway, liquibase, or quartz.
Also note that info is intentionally left available even when the rest of the
actuator command group is restricted for the current SSH user.
Available actuator commands in this starter are:
auditbeansconditionsconfigpropsenvhealthhttpexchangesinfologgersmetricsmappingsscheduledtaskssessionsshutdownthreaddump
If you have @EnableScheduling,
these commands allow you to interact with spring boot scheduled tasks:
tasks-list: Display the available scheduled taskstasks-stop: Stop all or specified task(s)tasks-restart: Restart all or specified task(s)tasks-single: Launch one execution of all or specified task(s)
Note: refresh parameter in tasks-list will remove single executions.
Based on spring
documentation org.springframework.scheduling.annotation.ScheduledAnnotationBeanPostProcessor.setScheduler
the task scheduler used for scheduled tasks will be:
If not specified, it will look for unique bean of type TaskScheduler, or
with name
taskScheduler. Otherwise, a local single-threaded one will be created.
The TasksCommand keeps the same mechanism in order to be able to restart
stopped scheduled tasks.
It also provides a setTaskScheduler() in case you want to specify a custom
one.
| Context | Task scheduler used in TasksCommand |
|---|---|
No TaskScheduler bean in context |
Local single-threaded |
One TaskScheduler bean named ts in context |
ts bean |
Multiple TaskScheduler beans named ts1, ts2 in context |
Local single-threaded (could not find name taskScheduler) |
Multiple TaskScheduler beans named taskScheduler, ts2, ts3 in context |
taskScheduler bean |
Task scheduler specified in method SchedulingConfigurer#configureTasks |
Local single-threaded (not set in task) |
Task scheduler specified in method SchedulingConfigurer#configureTasks AND io.github.choseongah.ssh.shell.commands.TasksCommand.setTaskScheduler |
Scheduler manually set |
jmx-info: Displays information about jmx mbean. Use--all-attributes-valuesoption to query attribute values.jmx-invoke: Invoke operation on object name.jmx-list: List jmx mbeans.
system-env: List system environment.system-properties: List system properties.system-threads: List jvm threads.
datasource-list: List available datasourcesdatasource-properties: Datasource properties command. Executes 'show variables'datasource-query: Datasource query command.datasource-update: Datasource update command.
history: Display or save previously run commands
script: Execute commands from a script file
stacktrace: Display the full stacktrace of the last error
postprocessors: Display the available post processors
Note: since 1.0.6
Post processors can be used with | (pipe character) followed by the name of
the post processor and the parameters.
Also, custom ones can be added.
This specific post processor takes the key character >.
Example: echo test > /path/to/file.txt
This post processor, named pretty takes an object and applies jackson pretty
writer.
Example: info | pretty
This post processor, named json allows you to find a specific path within a
JSON object.
Caution: you need to have a JSON string. You can apply pretty post processor
before to do so.
Example: info | pretty | json /build/version
This post processor, named grep allows you to find specific patterns within a
string.
Examples: info | grep boot, info | pretty | grep boot spring
This post processor, named highlight allows you to highlight specific patterns
within a
string.
Examples: info | highlight boot, info | pretty | highlight boot spring
To register a new JSON result post processor, you need to implement
interface PostProcessor.
Then register it within a spring configuration.
Example:
@Configuration
class PostProcessorConfiguration {
@Bean
public PostProcessor<String, String> quotePostProcessor() {
return new PostProcessor<>() {
@Override
public String getName() {
return "quote";
}
@Override
public String getDescription() {
return "Add quotes";
}
@Override
public String process(String input, List<String> parameters) {
return "'" + input + "'";
}
};
}
}Enumeration option parameters have auto-completion by default.
Thanks to ExtendedFileCompletionProvider.java,
auto-completion is available
for java.io.File parameters and behaves correctly on Windows paths.
To enable auto-completion for a command, declare a CompletionProvider bean
and reference it from the command.
Note: the completion provider has to be in the spring context.
@SshShellComponent
class Commands {
@Command(name = "echo", completionProvider = "customValuesProvider")
public String command(@Argument(index = 0) String message) {
return message;
}
}
@Component("customValuesProvider")
class CustomValuesProvider implements CompletionProvider {
private static final String[] VALUES = new String[]{
"message1", "message2", "message3"
};
@Override
public List<CompletionProposal> apply(CompletionContext completionContext) {
return Arrays.stream(VALUES).map(CompletionProposal::new).toList();
}
}Instead of setting user and password (or using generated one), you can implement
your
own SshShellAuthenticationProvider.
Auto configuration will create default implementation only if there is not an existing one in the spring context.
Example:
import io.github.choseongah.ssh.shell.auth.SshShellAuthenticationProvider;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class CustomPasswordConfiguration {
@Bean
public SshShellAuthenticationProvider sshShellAuthenticationProvider() {
return (user, pass, serverSession) -> user.equals(pass);
}
}If you prefer to rely on Spring Security, set
ssh.shell.authentication=security.
In that mode this starter looks for an AuthenticationManager bean.
If more than one is present, set ssh.shell.auth-provider-bean-name.
A io.github.choseongah.ssh.shell.SshShellHelper bean is provided in context to
help for additional functionalities.
You can either autowire it or inject it in a constructor:
import io.github.choseongah.ssh.shell.SshShellHelper;
import io.github.choseongah.ssh.shell.commands.SshShellComponent;
@SshShellComponent
public class DemoCommand {
@Autowired
private SshShellHelper helper;
// or
public DemoCommand(SshShellHelper helper) {
this.helper = helper;
}
}@SshShellComponent
public class DemoCommand {
@Autowired
private SshShellHelper helper;
@Command(name = "print", description = "Print command")
public String print() {
boolean success = true;
helper.print("Some message");
helper.print("Some black message", PromptColor.BLACK);
helper.printSuccess("Some success message");
return success ? helper.getSuccess("Some returned success message")
: helper.getColored("Some returned blue message", PromptColor.BLUE);
}
}@SshShellComponent
public class DemoCommand {
@Autowired
private SshShellHelper helper;
@Command(name = "welcome", description = "Welcome command")
public String welcome() {
String name = helper.read("What's your name ?");
return "Hello, '" + name + "' !";
}
}Util confirm method displays confirmation message and returns true
if response equals ignore case confirmation words.
Default confirmation words are [y, yes]:
You can specify if it is case-sensitive and provide your own confirmation words.
@SshShellComponent
public class DemoCommand {
@Autowired
private SshShellHelper helper;
@Command(name = "conf", description = "Confirmation command")
public String conf() {
return helper.confirm("Are you sure ?") ? "Great ! Let's do it !" : "Such a shame ...";
}
}The SimpleTable.builder() API is available to
quickly set up print table.
Quick example:
class Commands {
public String table() {
return helper.renderTable(SimpleTable.builder()
.column("col1")
.column("col2")
.column("col3")
.column("col4")
.line(Arrays.asList("line1 col1", "line1 col2", "line1 col3", "line1 col4"))
.line(Arrays.asList("line2 col1", "line2 col2", "line2 col3", "line2 col4"))
.line(Arrays.asList("line3 col1", "line3 col2", "line3 col3", "line3 col4"))
.line(Arrays.asList("line4 col1", "line4 col2", "line4 col3", "line4 col4"))
.line(Arrays.asList("line5 col1", "line5 col2", "line5 col3", "line5 col4"))
.line(Arrays.asList("line6 col1", "line6 col2", "line6 col3", "line6 col4"))
.build());
}
}Result:
┌──────────┬──────────┬──────────┬──────────┐
│ col1 │ col2 │ col3 │ col4 │
├──────────┼──────────┼──────────┼──────────┤
│line1 col1│line1 col2│line1 col3│line1 col4│
├──────────┼──────────┼──────────┼──────────┤
│line2 col1│line2 col2│line2 col3│line2 col4│
├──────────┼──────────┼──────────┼──────────┤
│line3 col1│line3 col2│line3 col3│line3 col4│
├──────────┼──────────┼──────────┼──────────┤
│line4 col1│line4 col2│line4 col3│line4 col4│
├──────────┼──────────┼──────────┼──────────┤
│line5 col1│line5 col2│line5 col3│line5 col4│
├──────────┼──────────┼──────────┼──────────┤
│line6 col1│line6 col2│line6 col3│line6 col4│
└──────────┴──────────┴──────────┴──────────┘
Note: since 1.1.3
This method takes an interface to display lines at regular interval.
Every refresh delay (here 2
seconds), io.github.choseongah.ssh.shell.interactive.InteractiveInput.getLines
is
called.
This can be used to display progress, monitoring, etc.
The interactive builder, Interactive.java allows you to build your interactive command.
This builder can also take key bindings to make specific actions, which can be made by the following builder: KeyBinding.java.
@SshShellComponent
public class DemoCommand {
@Autowired
private SshShellHelper helper;
@Command(name = "interactive", description = "Interactive command")
public void interactive() {
KeyBinding binding = KeyBinding.builder()
.description("K binding example")
.key("k").input(() -> LOGGER.info("In specific action triggered by key 'k' !")).build();
Interactive interactive = Interactive.builder().input((size, currentDelay) -> {
LOGGER.info("In interactive command for input...");
List<AttributedString> lines = new ArrayList<>();
AttributedStringBuilder sb = new AttributedStringBuilder(size.getColumns());
sb.append("\nCurrent time", AttributedStyle.BOLD).append(" : ");
sb.append(String.format("%8tT", new Date()));
lines.add(sb.toAttributedString());
SecureRandom sr = new SecureRandom();
lines.add(new AttributedStringBuilder().append(helper.progress(sr.nextInt(100)),
AttributedStyle.DEFAULT.foreground(sr.nextInt(6) + 1)).toAttributedString());
lines.add(AttributedString.fromAnsi(SshShellHelper.INTERACTIVE_LONG_MESSAGE + "\n"));
return lines;
}).binding(binding).fullScreen(true).refreshDelay(5000).build();
helper.interactive(interactive);
}
}Note: existing key bindings are:
q: to quit interactive command and go back to shell+: to increase refresh delay by 1000 milliseconds-: to decrease refresh delay by 1000 milliseconds
If you are using Spring Security thanks to
property ssh.shell.authentication=security, you can check that the
connected user has the right authorities for a command.
With the current Spring Shell 4 integration, availability is provided through a
named AvailabilityProvider bean.
Example:
@SshShellComponent
public class DemoCommand {
@Autowired
private SshShellHelper helper;
@Command(name = "admin", description = "Admin command", availabilityProvider = "adminAvailabilityProvider")
public String admin() {
return "Finally an administrator !!";
}
public Availability adminAvailability() {
if (!helper.checkAuthorities(Collections.singletonList("ADMIN"))) {
return Availability.unavailable("admin command is only for an admin users !");
}
return Availability.available();
}
}
@Configuration
class DemoConfiguration {
@Bean("adminAvailabilityProvider")
public AvailabilityProvider adminAvailabilityProvider(DemoCommand demoCommand) {
return demoCommand::adminAvailability;
}
}@SshShellComponent
public class DemoCommand {
@Autowired
private SshShellHelper helper;
@Command(name = "authentication", description = "Authentication command")
public SshAuthentication authentication() {
return helper.getAuthentication();
}
}If a banner is found in spring context and display-banner is set to true,
it will be used as welcome prompt message.
An interface is provided in order to receive events on ssh
sessions: io.github.choseongah.ssh.shell.listeners.SshShellListener.
Implement it and define a spring bean in order to receive events.
Example
@Configuration
class ShellListenerConfiguration {
@Bean
public SshShellListener sshShellListener() {
return event -> LOGGER.info("[listener] event '{}' [id={}, ip={}]",
event.getType(),
event.getSession().getServerSession().getIoSession().getId(),
event.getSession().getServerSession().getIoSession().getRemoteAddress());
}
}Note: since 1.3.0
A session manager bean is available and allows you to:
- list active sessions
- get information about one session
- stop a session
Note: you need to use @Lazy injection if you are using it in a command
Example
class Commands {
public MyCommand(@Lazy SshShellSessionManager sessionManager) {
this.sessionManager = sessionManager;
}
@Command(name = "my-command", description = "My command")
public String myCommand() {
sessionManager.listSessions();
//...
}
}If activated ssh.shell.commands.manage-sessions.enabled=true, the following
commands are available:
manage-sessions-info: Displays information about single sessionmanage-sessions-list: Displays active sessionsmanage-sessions-stop: Stop single specific session
It can be annoying to load ssh server during spring boot tests.
SshShellProperties class provides constants to easily deactivate
this starter and the main Spring Shell auto configuration:
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE, properties = {"ssh.shell.port=2346",
SshShellProperties.DISABLE_SSH_SHELL,
SshShellProperties.DISABLE_SPRING_SHELL_AUTO_CONFIG
})
@ExtendWith(SpringExtension.class)
public class ApplicationTest {
}- Basic sample, no actuator, no security, no sessions
- Complete sample, with actuator, security dependencies and configurations
Please check GitHub releases page.