mirror of
https://github.com/spring-projects/spring-boot.git
synced 2024-08-29 03:06:45 +08:00
Add remote shell implementation based on crsh
This commit adds a new starter named spring-boot-starter-shell-crsh and auto configuration support to embed a system shell within Spring Boot applications. The embedded shell allows clients to connect via ssh or telnet to the Boot app and execute commands. Commands can be implemented and embedded with app. For sample usage see spring-boot-samples-actuator.
This commit is contained in:
parent
90a2bf38da
commit
6b599b8483
@ -72,6 +72,11 @@
|
||||
<artifactId>tomcat-embed-core</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.crashub</groupId>
|
||||
<artifactId>crash.embed.spring</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<!-- Test -->
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
|
@ -0,0 +1,462 @@
|
||||
/*
|
||||
* Copyright 2013 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.actuate.autoconfigure;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import javax.annotation.PreDestroy;
|
||||
|
||||
import org.crsh.auth.AuthenticationPlugin;
|
||||
import org.crsh.plugin.CRaSHPlugin;
|
||||
import org.crsh.plugin.PluginContext;
|
||||
import org.crsh.plugin.PluginDiscovery;
|
||||
import org.crsh.plugin.PluginLifeCycle;
|
||||
import org.crsh.plugin.PropertyDescriptor;
|
||||
import org.crsh.plugin.ServiceLoaderDiscovery;
|
||||
import org.crsh.vfs.FS;
|
||||
import org.crsh.vfs.spi.AbstractFSDriver;
|
||||
import org.springframework.beans.factory.ListableBeanFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.actuate.properties.CrshProperties;
|
||||
import org.springframework.boot.actuate.properties.CrshProperties.AuthenticationProperties;
|
||||
import org.springframework.boot.actuate.properties.CrshProperties.JaasAuthenticationProperties;
|
||||
import org.springframework.boot.actuate.properties.CrshProperties.KeyAuthenticationProperties;
|
||||
import org.springframework.boot.actuate.properties.CrshProperties.SimpleAuthenticationProperties;
|
||||
import org.springframework.boot.actuate.properties.CrshProperties.SpringAuthenticationProperties;
|
||||
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.SpringVersion;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.core.io.support.ResourcePatternResolver;
|
||||
import org.springframework.security.access.AccessDecisionManager;
|
||||
import org.springframework.security.access.AccessDeniedException;
|
||||
import org.springframework.security.access.SecurityConfig;
|
||||
import org.springframework.security.authentication.AuthenticationManager;
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.AuthenticationException;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* {@link EnableAutoConfiguration Auto-configuration} for embedding an extensible shell into a Spring
|
||||
* Boot enabled application. By default a SSH daemon is started on port 2000 with a default username
|
||||
* <code>user</code> and password (default password is logged during application startup).
|
||||
*
|
||||
* <p>
|
||||
* This configuration will auto detect the existence of a Spring Security {@link AuthenticationManager}
|
||||
* and will delegate authentication requests for shell access to this detected instance.
|
||||
*
|
||||
* <p>
|
||||
* To add customizations to the shell simply define beans of type {@link CRaSHPlugin} in the
|
||||
* application context. Those beans will get auto detected during startup and registered with the
|
||||
* underlying shell infrastructure.
|
||||
*
|
||||
* <p>
|
||||
* Additional shell commands can be implemented using the guide and documentation at
|
||||
* <a href="http://www.crashub.org">crashub.org</a>. By default Boot will search for commands using
|
||||
* the following classpath scanning pattern <code>classpath*:/commands/**</code>. To add different
|
||||
* locations or override the default use <code>shell.command_path_patterns</code> in your application
|
||||
* configuration.
|
||||
*
|
||||
* @author Christian Dupuis
|
||||
*/
|
||||
@Configuration
|
||||
@ConditionalOnClass({ PluginLifeCycle.class })
|
||||
@EnableConfigurationProperties({ CrshProperties.class })
|
||||
@AutoConfigureAfter(SecurityAutoConfiguration.class)
|
||||
public class CrshAutoConfiguration {
|
||||
|
||||
@Autowired
|
||||
private CrshProperties properties;
|
||||
|
||||
|
||||
@Bean
|
||||
@ConditionalOnExpression("#{environment['shell.auth'] == 'jaas'}")
|
||||
@ConditionalOnMissingBean({ AuthenticationProperties.class })
|
||||
public AuthenticationProperties jaasAuthenticationProperties() {
|
||||
return new JaasAuthenticationProperties();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnExpression("#{environment['shell.auth'] == 'key'}")
|
||||
@ConditionalOnMissingBean({ AuthenticationProperties.class })
|
||||
public AuthenticationProperties keyAuthenticationProperties() {
|
||||
return new KeyAuthenticationProperties();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnExpression("#{environment['shell.auth'] == 'simple'}")
|
||||
@ConditionalOnMissingBean({ AuthenticationProperties.class })
|
||||
public AuthenticationProperties simpleAuthenticationProperties() {
|
||||
return new SimpleAuthenticationProperties();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnExpression("#{environment['shell.auth'] == 'spring'}")
|
||||
@ConditionalOnMissingBean({ AuthenticationProperties.class })
|
||||
public AuthenticationProperties SpringAuthenticationProperties() {
|
||||
return new SpringAuthenticationProperties();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnBean({ AuthenticationManager.class })
|
||||
public CRaSHPlugin<?> shellAuthenticationManager() {
|
||||
return new AuthenticationManagerAdapter();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean({ PluginLifeCycle.class })
|
||||
public PluginLifeCycle shellBootstrap() {
|
||||
CrshBootstrap bs = new CrshBootstrap();
|
||||
bs.setConfig(properties.mergeProperties(new Properties()));
|
||||
return bs;
|
||||
}
|
||||
|
||||
|
||||
public static class CrshBootstrap extends PluginLifeCycle {
|
||||
|
||||
@Autowired
|
||||
private ListableBeanFactory beanFactory;
|
||||
|
||||
@Autowired
|
||||
private CrshProperties properties;
|
||||
|
||||
@Autowired
|
||||
private ResourcePatternResolver resourceLoader;
|
||||
|
||||
|
||||
@PreDestroy
|
||||
public void destroy() {
|
||||
stop();
|
||||
}
|
||||
|
||||
@PostConstruct
|
||||
public void init() throws Exception {
|
||||
FS commandFileSystem = createFileSystem(properties.getCommandPathPatterns());
|
||||
FS confFileSystem = createFileSystem(properties.getConfigPathPatterns());
|
||||
|
||||
PluginDiscovery discovery = new BeanFactoryFilteringPluginDiscovery(resourceLoader.getClassLoader(),
|
||||
beanFactory, properties.getDisabledPlugins());
|
||||
|
||||
PluginContext context = new PluginContext(discovery, createPluginContextAttributes(),
|
||||
commandFileSystem, confFileSystem, resourceLoader.getClassLoader());
|
||||
|
||||
context.refresh();
|
||||
start(context);
|
||||
}
|
||||
|
||||
|
||||
protected FS createFileSystem(String[] pathPatterns) throws IOException, URISyntaxException {
|
||||
Assert.notNull(pathPatterns);
|
||||
FS cmdFS = new FS();
|
||||
for (String pathPattern : pathPatterns) {
|
||||
cmdFS.mount(new SimpleFileSystemDriver(new DirectoryHandle(pathPattern, resourceLoader)));
|
||||
}
|
||||
return cmdFS;
|
||||
}
|
||||
|
||||
protected Map<String, Object> createPluginContextAttributes() {
|
||||
Map<String, Object> attributes = new HashMap<String, Object>();
|
||||
String bootVersion = CrshAutoConfiguration.class.getPackage().getImplementationVersion();
|
||||
if (bootVersion != null) {
|
||||
attributes.put("spring.boot.version", bootVersion);
|
||||
}
|
||||
attributes.put("spring.version", SpringVersion.getVersion());
|
||||
if (beanFactory != null) {
|
||||
attributes.put("spring.beanfactory", beanFactory);
|
||||
}
|
||||
return attributes;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
private static class AuthenticationManagerAdapter extends CRaSHPlugin<AuthenticationPlugin> implements
|
||||
AuthenticationPlugin<String> {
|
||||
|
||||
private static final PropertyDescriptor<String> ROLES = PropertyDescriptor.create(
|
||||
"auth.spring.roles", "ADMIN", "Comma separated list of roles required to access the shell");
|
||||
|
||||
|
||||
@Autowired(required=false)
|
||||
private AccessDecisionManager accessDecisionManager;
|
||||
|
||||
@Autowired
|
||||
private AuthenticationManager authenticationManager;
|
||||
|
||||
private String[] roles = new String[] { "ROLE_ADMIN" };
|
||||
|
||||
|
||||
@Override
|
||||
public boolean authenticate(String username, String password) throws Exception {
|
||||
// Authenticate first to make credentials are valid
|
||||
Authentication token = new UsernamePasswordAuthenticationToken(username, password);
|
||||
try {
|
||||
token = authenticationManager.authenticate(token);
|
||||
}
|
||||
catch (AuthenticationException ae) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Test access rights if a Spring Security AccessDecisionManager is installed
|
||||
if (accessDecisionManager != null && token.isAuthenticated() && roles != null) {
|
||||
try {
|
||||
accessDecisionManager.decide(token, this, SecurityConfig.createList(roles));
|
||||
}
|
||||
catch (AccessDeniedException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return token.isAuthenticated();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<String> getCredentialType() {
|
||||
return String.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AuthenticationPlugin<String> getImplementation() {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "spring";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init() {
|
||||
String rolesPropertyValue = getContext().getProperty(ROLES);
|
||||
if (rolesPropertyValue != null) {
|
||||
this.roles = StringUtils.commaDelimitedListToStringArray(rolesPropertyValue);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected Iterable<PropertyDescriptor<?>> createConfigurationCapabilities() {
|
||||
return Arrays.<PropertyDescriptor<?>>asList(ROLES);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
private static class BeanFactoryFilteringPluginDiscovery extends ServiceLoaderDiscovery {
|
||||
|
||||
private ListableBeanFactory beanFactory;
|
||||
|
||||
private String[] disabledPlugins;
|
||||
|
||||
|
||||
public BeanFactoryFilteringPluginDiscovery(ClassLoader classLoader, ListableBeanFactory beanFactory,
|
||||
String[] disabledPlugins)
|
||||
throws NullPointerException {
|
||||
super(classLoader);
|
||||
this.beanFactory = beanFactory;
|
||||
this.disabledPlugins = disabledPlugins;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("rawtypes")
|
||||
public Iterable<CRaSHPlugin<?>> getPlugins() {
|
||||
List<CRaSHPlugin<?>> plugins = new ArrayList<CRaSHPlugin<?>>();
|
||||
|
||||
for (CRaSHPlugin<?> p : super.getPlugins()) {
|
||||
if (!shouldFilter(p)) {
|
||||
plugins.add(p);
|
||||
}
|
||||
}
|
||||
|
||||
Collection<CRaSHPlugin> springPlugins = beanFactory.getBeansOfType(CRaSHPlugin.class).values();
|
||||
for (CRaSHPlugin<?> p : springPlugins) {
|
||||
if (!shouldFilter(p)) {
|
||||
plugins.add(p);
|
||||
}
|
||||
}
|
||||
|
||||
return plugins;
|
||||
}
|
||||
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
protected boolean shouldFilter(CRaSHPlugin<?> plugin) {
|
||||
Assert.notNull(plugin);
|
||||
|
||||
Set<Class> classes = ClassUtils.getAllInterfacesAsSet(plugin);
|
||||
classes.add(plugin.getClass());
|
||||
|
||||
for (Class<?> clazz : classes) {
|
||||
if (disabledPlugins != null && disabledPlugins.length > 0) {
|
||||
for (String disabledPlugin : disabledPlugins) {
|
||||
if (ClassUtils.getShortName(clazz).equalsIgnoreCase(disabledPlugin)
|
||||
|| ClassUtils.getQualifiedName(clazz).equalsIgnoreCase(disabledPlugin)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
private static class SimpleFileSystemDriver extends AbstractFSDriver<ResourceHandle> {
|
||||
|
||||
private ResourceHandle root;
|
||||
|
||||
|
||||
public SimpleFileSystemDriver(ResourceHandle handle) {
|
||||
this.root = handle;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Iterable<ResourceHandle> children(ResourceHandle handle) throws IOException {
|
||||
if (handle instanceof DirectoryHandle) {
|
||||
return ((DirectoryHandle) handle).members();
|
||||
}
|
||||
return Collections.emptySet();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getLastModified(ResourceHandle handle) throws IOException {
|
||||
if (handle instanceof FileHandle) {
|
||||
return ((FileHandle) handle).getLastModified();
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDir(ResourceHandle handle) throws IOException {
|
||||
return handle instanceof DirectoryHandle;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String name(ResourceHandle handle) throws IOException {
|
||||
return handle.getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<InputStream> open(ResourceHandle handle) throws IOException {
|
||||
if (handle instanceof FileHandle) {
|
||||
return Collections.singletonList(((FileHandle) handle).openStream()).iterator();
|
||||
}
|
||||
return Collections.<InputStream>emptyList().iterator();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResourceHandle root() throws IOException {
|
||||
return root;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
private static class DirectoryHandle extends ResourceHandle {
|
||||
|
||||
private ResourcePatternResolver resourceLoader;
|
||||
|
||||
|
||||
public DirectoryHandle(String name, ResourcePatternResolver resourceLoader) {
|
||||
super(name);
|
||||
this.resourceLoader = resourceLoader;
|
||||
}
|
||||
|
||||
|
||||
public List<ResourceHandle> members() throws IOException {
|
||||
Resource[] resources = resourceLoader.getResources(getName());
|
||||
List<ResourceHandle> files = new ArrayList<ResourceHandle>();
|
||||
for (Resource resource : resources) {
|
||||
if (!resource.getURL().getPath().endsWith("/")) {
|
||||
files.add(new FileHandle(resource.getFilename(), resource));
|
||||
}
|
||||
}
|
||||
return files;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
private static class FileHandle extends ResourceHandle {
|
||||
|
||||
private Resource resource;
|
||||
|
||||
|
||||
public FileHandle(String name, Resource resource) {
|
||||
super(name);
|
||||
this.resource = resource;
|
||||
}
|
||||
|
||||
|
||||
public InputStream openStream() throws IOException {
|
||||
return this.resource.getInputStream();
|
||||
}
|
||||
|
||||
public long getLastModified() {
|
||||
try {
|
||||
return this.resource.lastModified();
|
||||
}
|
||||
catch (IOException e) {}
|
||||
return -1;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
private abstract static class ResourceHandle {
|
||||
|
||||
private String name;
|
||||
|
||||
|
||||
public ResourceHandle(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,355 @@
|
||||
/*
|
||||
* Copyright 2013 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.actuate.properties;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Properties;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* Configuration properties for the shell subsystem.
|
||||
*
|
||||
* @author Christian Dupuis
|
||||
*/
|
||||
@ConfigurationProperties(name = "shell", ignoreUnknownFields = true)
|
||||
public class CrshProperties {
|
||||
|
||||
protected static final String CRASH_AUTH = "crash.auth";
|
||||
protected static final String CRASH_AUTH_JAAS_DOMAIN = "crash.auth.jaas.domain";
|
||||
protected static final String CRASH_AUTH_KEY_PATH = "crash.auth.key.path";
|
||||
protected static final String CRASH_AUTH_SIMPLE_PASSWORD = "crash.auth.simple.password";
|
||||
protected static final String CRASH_AUTH_SIMPLE_USERNAME = "crash.auth.simple.username";
|
||||
protected static final String CRASH_AUTH_SPRING_ROLES = "crash.auth.spring.roles";
|
||||
protected static final String CRASH_SSH_KEYPATH = "crash.ssh.keypath";
|
||||
protected static final String CRASH_SSH_PORT = "crash.ssh.port";
|
||||
protected static final String CRASH_TELNET_PORT = "crash.telnet.port";
|
||||
protected static final String CRASH_VFS_REFRESH_PERIOD = "crash.vfs.refresh_period";
|
||||
|
||||
private String auth = "simple";
|
||||
|
||||
@Autowired(required = false)
|
||||
private AuthenticationProperties authenticationProperties;
|
||||
|
||||
private int commandRefreshInterval = -1;
|
||||
|
||||
private String[] commandPathPatterns = new String[] { "classpath*:/commands/**",
|
||||
"classpath*:/crash/commands/**" };
|
||||
|
||||
private String[] configPathPatterns = new String[] { "classpath*:/crash/*" };
|
||||
|
||||
private String[] disabledPlugins = new String[0];
|
||||
|
||||
private Ssh ssh = new Ssh();
|
||||
|
||||
private Telnet telnet = new Telnet();
|
||||
|
||||
|
||||
public String getAuth() {
|
||||
return this.auth;
|
||||
}
|
||||
|
||||
public AuthenticationProperties getAuthenticationProperties() {
|
||||
return this.authenticationProperties;
|
||||
}
|
||||
|
||||
public int getCommandRefreshInterval() {
|
||||
return this.commandRefreshInterval;
|
||||
}
|
||||
|
||||
public String[] getCommandPathPatterns() {
|
||||
return this.commandPathPatterns;
|
||||
}
|
||||
|
||||
public String[] getConfigPathPatterns() {
|
||||
return this.configPathPatterns;
|
||||
}
|
||||
|
||||
public String[] getDisabledPlugins() {
|
||||
return this.disabledPlugins;
|
||||
}
|
||||
|
||||
public Ssh getSsh() {
|
||||
return this.ssh;
|
||||
}
|
||||
|
||||
public Telnet getTelnet() {
|
||||
return this.telnet;
|
||||
}
|
||||
|
||||
public Properties mergeProperties(Properties properties) {
|
||||
properties = ssh.mergeProperties(properties);
|
||||
properties = telnet.mergeProperties(properties);
|
||||
|
||||
properties.put(CRASH_AUTH, auth);
|
||||
if (authenticationProperties != null) {
|
||||
properties = authenticationProperties.mergeProperties(properties);
|
||||
}
|
||||
|
||||
if (this.commandRefreshInterval > 0) {
|
||||
properties.put(CRASH_VFS_REFRESH_PERIOD, String.valueOf(this.commandRefreshInterval));
|
||||
}
|
||||
|
||||
// special handling for disabling Ssh and Telnet support
|
||||
List<String> dp = new ArrayList<String>(Arrays.asList(this.disabledPlugins));
|
||||
if (!ssh.isEnabled()) {
|
||||
dp.add("org.crsh.ssh.SSHPlugin");
|
||||
}
|
||||
if (!telnet.isEnabled()) {
|
||||
dp.add("org.crsh.telnet.TelnetPlugin");
|
||||
}
|
||||
this.disabledPlugins = dp.toArray(new String[dp.size()]);
|
||||
|
||||
return properties;
|
||||
}
|
||||
|
||||
public void setAuth(String auth) {
|
||||
Assert.hasLength(auth);
|
||||
this.auth = auth;
|
||||
}
|
||||
|
||||
public void setAuthenticationProperties(AuthenticationProperties authenticationProperties) {
|
||||
Assert.notNull(authenticationProperties);
|
||||
this.authenticationProperties = authenticationProperties;
|
||||
}
|
||||
|
||||
public void setCommandRefreshInterval(int commandRefreshInterval) {
|
||||
this.commandRefreshInterval = commandRefreshInterval;
|
||||
}
|
||||
|
||||
public void setCommandPathPatterns(String[] commandPathPatterns) {
|
||||
Assert.notEmpty(commandPathPatterns);
|
||||
this.commandPathPatterns = commandPathPatterns;
|
||||
}
|
||||
|
||||
public void setConfigPathPatterns(String[] configPathPatterns) {
|
||||
Assert.notEmpty(configPathPatterns);
|
||||
this.configPathPatterns = configPathPatterns;
|
||||
}
|
||||
|
||||
public void setDisabledPlugins(String[] disabledPlugins) {
|
||||
Assert.notEmpty(disabledPlugins);
|
||||
this.disabledPlugins = disabledPlugins;
|
||||
}
|
||||
|
||||
public void setSsh(Ssh ssh) {
|
||||
Assert.notNull(ssh);
|
||||
this.ssh = ssh;
|
||||
}
|
||||
|
||||
public void setTelnet(Telnet telnet) {
|
||||
Assert.notNull(telnet);
|
||||
this.telnet = telnet;
|
||||
}
|
||||
|
||||
|
||||
public interface AuthenticationProperties extends PropertiesProvider {
|
||||
}
|
||||
|
||||
|
||||
@ConfigurationProperties(name = "shell.auth.jaas", ignoreUnknownFields = false)
|
||||
public static class JaasAuthenticationProperties implements AuthenticationProperties {
|
||||
|
||||
private String domain = "my-domain";
|
||||
|
||||
|
||||
@Override
|
||||
public Properties mergeProperties(Properties properties) {
|
||||
properties.put(CRASH_AUTH_JAAS_DOMAIN, this.domain);
|
||||
return properties;
|
||||
}
|
||||
|
||||
public void setDomain(String domain) {
|
||||
Assert.hasText(domain);
|
||||
this.domain = domain;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@ConfigurationProperties(name = "shell.auth.key", ignoreUnknownFields = false)
|
||||
public static class KeyAuthenticationProperties implements AuthenticationProperties {
|
||||
|
||||
private String path;
|
||||
|
||||
|
||||
@Override
|
||||
public Properties mergeProperties(Properties properties) {
|
||||
if (this.path != null) {
|
||||
properties.put(CRASH_AUTH_KEY_PATH, this.path);
|
||||
}
|
||||
return properties;
|
||||
}
|
||||
|
||||
public void setPath(String path) {
|
||||
Assert.hasText(path);
|
||||
this.path = path;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
public interface PropertiesProvider {
|
||||
Properties mergeProperties(Properties properties);
|
||||
}
|
||||
|
||||
|
||||
@ConfigurationProperties(name = "shell.auth.simple", ignoreUnknownFields = false)
|
||||
public static class SimpleAuthenticationProperties implements AuthenticationProperties {
|
||||
|
||||
private static Log logger = LogFactory.getLog(SimpleAuthenticationProperties.class);
|
||||
|
||||
|
||||
private String username = "user";
|
||||
|
||||
private String password = UUID.randomUUID().toString();
|
||||
|
||||
private boolean defaultPassword = true;
|
||||
|
||||
|
||||
public boolean isDefaultPassword() {
|
||||
return this.defaultPassword;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Properties mergeProperties(Properties properties) {
|
||||
properties.put(CRASH_AUTH_SIMPLE_USERNAME, this.username);
|
||||
properties.put(CRASH_AUTH_SIMPLE_PASSWORD, this.password);
|
||||
if (this.defaultPassword) {
|
||||
logger.info("Using default password for shell access: " + this.password);
|
||||
}
|
||||
return properties;
|
||||
}
|
||||
|
||||
public void setPassword(String password) {
|
||||
if (password.startsWith("${") && password.endsWith("}") || !StringUtils.hasLength(password)) {
|
||||
return;
|
||||
}
|
||||
this.password = password;
|
||||
this.defaultPassword = false;
|
||||
}
|
||||
|
||||
public void setUsername(String username) {
|
||||
Assert.hasLength(username);
|
||||
this.username = username;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@ConfigurationProperties(name = "shell.auth.spring", ignoreUnknownFields = false)
|
||||
public static class SpringAuthenticationProperties implements AuthenticationProperties {
|
||||
|
||||
private String[] roles = new String[] { "ROLE_ADMIN" };
|
||||
|
||||
|
||||
@Override
|
||||
public Properties mergeProperties(Properties properties) {
|
||||
if (this.roles != null) {
|
||||
properties.put(CRASH_AUTH_SPRING_ROLES, StringUtils.arrayToCommaDelimitedString(this.roles));
|
||||
}
|
||||
return properties;
|
||||
}
|
||||
|
||||
public void setRoles(String[] roles) {
|
||||
Assert.notNull(roles);
|
||||
this.roles = roles;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
public static class Ssh implements PropertiesProvider {
|
||||
|
||||
private boolean enabled = true;
|
||||
|
||||
private String keyPath = null;
|
||||
|
||||
private String port = "2000";
|
||||
|
||||
|
||||
public boolean isEnabled() {
|
||||
return this.enabled;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Properties mergeProperties(Properties properties) {
|
||||
if (this.enabled) {
|
||||
properties.put(CRASH_SSH_PORT, this.port);
|
||||
if (this.keyPath != null) {
|
||||
properties.put(CRASH_SSH_KEYPATH, this.keyPath);
|
||||
}
|
||||
}
|
||||
return properties;
|
||||
}
|
||||
|
||||
public void setEnabled(boolean enabled) {
|
||||
this.enabled = enabled;
|
||||
}
|
||||
|
||||
public void setKeyPath(String keyPath) {
|
||||
Assert.hasText(keyPath);
|
||||
this.keyPath = keyPath;
|
||||
}
|
||||
|
||||
public void setPort(Integer port) {
|
||||
Assert.notNull(port);
|
||||
this.port = port.toString();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
public static class Telnet implements PropertiesProvider {
|
||||
|
||||
private boolean enabled = false;
|
||||
|
||||
private String port = "5000";
|
||||
|
||||
|
||||
public boolean isEnabled() {
|
||||
return this.enabled;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Properties mergeProperties(Properties properties) {
|
||||
if (this.enabled) {
|
||||
properties.put(CRASH_TELNET_PORT, this.port);
|
||||
}
|
||||
return properties;
|
||||
}
|
||||
|
||||
public void setEnabled(boolean enabled) {
|
||||
this.enabled = enabled;
|
||||
}
|
||||
|
||||
public void setPort(Integer port) {
|
||||
Assert.notNull(port);
|
||||
this.port = port.toString();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -1,5 +1,6 @@
|
||||
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
|
||||
org.springframework.boot.actuate.autoconfigure.AuditAutoConfiguration,\
|
||||
org.springframework.boot.actuate.autoconfigure.CrshAutoConfiguration,\
|
||||
org.springframework.boot.actuate.autoconfigure.EndpointAutoConfiguration,\
|
||||
org.springframework.boot.actuate.autoconfigure.EndpointWebMvcAutoConfiguration,\
|
||||
org.springframework.boot.actuate.autoconfigure.ErrorMvcAutoConfiguration,\
|
||||
|
@ -0,0 +1,333 @@
|
||||
/*
|
||||
* Copyright 2013 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.actuate.autoconfigure;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.crsh.auth.AuthenticationPlugin;
|
||||
import org.crsh.auth.JaasAuthenticationPlugin;
|
||||
import org.crsh.lang.groovy.GroovyREPL;
|
||||
import org.crsh.plugin.PluginContext;
|
||||
import org.crsh.plugin.PluginLifeCycle;
|
||||
import org.crsh.plugin.ResourceKind;
|
||||
import org.crsh.processor.term.ProcessorIOHandler;
|
||||
import org.crsh.vfs.Resource;
|
||||
import org.junit.After;
|
||||
import org.junit.Test;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.mock.env.MockEnvironment;
|
||||
import org.springframework.mock.web.MockServletContext;
|
||||
import org.springframework.security.access.AccessDecisionManager;
|
||||
import org.springframework.security.access.AccessDecisionVoter;
|
||||
import org.springframework.security.access.vote.RoleVoter;
|
||||
import org.springframework.security.access.vote.UnanimousBased;
|
||||
import org.springframework.security.authentication.AuthenticationManager;
|
||||
import org.springframework.security.authentication.BadCredentialsException;
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.AuthenticationException;
|
||||
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
|
||||
|
||||
/**
|
||||
* Tests for {@link CrshAutoConfiguration}.
|
||||
*
|
||||
* @author Christian Dupuis
|
||||
*/
|
||||
@SuppressWarnings({ "rawtypes", "unchecked" })
|
||||
public class CrshAutoConfigurationTests {
|
||||
|
||||
private AnnotationConfigWebApplicationContext context;
|
||||
|
||||
@After
|
||||
public void tearDown() {
|
||||
if (this.context != null) {
|
||||
this.context.close();
|
||||
this.context = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDisabledPlugins() throws Exception {
|
||||
MockEnvironment env = new MockEnvironment();
|
||||
env.setProperty("shell.disabled_plugins", "GroovyREPL, termIOHandler, org.crsh.auth.AuthenticationPlugin");
|
||||
this.context = new AnnotationConfigWebApplicationContext();
|
||||
this.context.setEnvironment(env);
|
||||
this.context.register(CrshAutoConfiguration.class);
|
||||
this.context.refresh();
|
||||
|
||||
PluginLifeCycle lifeCycle = this.context.getBean(PluginLifeCycle.class);
|
||||
assertNotNull(lifeCycle);
|
||||
|
||||
assertNull(lifeCycle.getContext().getPlugin(GroovyREPL.class));
|
||||
assertNull(lifeCycle.getContext().getPlugin(ProcessorIOHandler.class));
|
||||
assertNull(lifeCycle.getContext().getPlugin(JaasAuthenticationPlugin.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAttributes() throws Exception {
|
||||
this.context = new AnnotationConfigWebApplicationContext();
|
||||
this.context.register(CrshAutoConfiguration.class);
|
||||
this.context.refresh();
|
||||
|
||||
PluginLifeCycle lifeCycle = this.context.getBean(PluginLifeCycle.class);
|
||||
|
||||
Map<String, Object> attributes = lifeCycle.getContext().getAttributes();
|
||||
assertTrue(attributes.containsKey("spring.version"));
|
||||
assertTrue(attributes.containsKey("spring.beanfactory"));
|
||||
assertEquals(this.context.getBeanFactory(), attributes.get("spring.beanfactory"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSshConfiguration() {
|
||||
MockEnvironment env = new MockEnvironment();
|
||||
env.setProperty("shell.ssh.enabled", "true");
|
||||
env.setProperty("shell.ssh.port", "3333");
|
||||
this.context = new AnnotationConfigWebApplicationContext();
|
||||
this.context.setEnvironment(env);
|
||||
this.context.register(CrshAutoConfiguration.class);
|
||||
this.context.refresh();
|
||||
|
||||
PluginLifeCycle lifeCycle = this.context.getBean(PluginLifeCycle.class);
|
||||
|
||||
assertEquals(lifeCycle.getConfig().getProperty("crash.ssh.port"), "3333");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSshConfigurationWithKeyPath() {
|
||||
MockEnvironment env = new MockEnvironment();
|
||||
env.setProperty("shell.ssh.enabled", "true");
|
||||
env.setProperty("shell.ssh.key_path", "~/.ssh/id.pem");
|
||||
this.context = new AnnotationConfigWebApplicationContext();
|
||||
this.context.setEnvironment(env);
|
||||
this.context.register(CrshAutoConfiguration.class);
|
||||
this.context.refresh();
|
||||
|
||||
PluginLifeCycle lifeCycle = this.context.getBean(PluginLifeCycle.class);
|
||||
|
||||
assertEquals(lifeCycle.getConfig().getProperty("crash.ssh.keypath"), "~/.ssh/id.pem");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCommandResolution() {
|
||||
this.context = new AnnotationConfigWebApplicationContext();
|
||||
this.context.register(CrshAutoConfiguration.class);
|
||||
this.context.refresh();
|
||||
|
||||
PluginLifeCycle lifeCycle = this.context.getBean(PluginLifeCycle.class);
|
||||
|
||||
int count = 0;
|
||||
Iterator<Resource> resources = lifeCycle.getContext().loadResources("login", ResourceKind.LIFECYCLE).iterator();
|
||||
while (resources.hasNext()) {
|
||||
count++;
|
||||
resources.next();
|
||||
}
|
||||
assertEquals(1, count);
|
||||
|
||||
count = 0;
|
||||
resources = lifeCycle.getContext().loadResources("help.java", ResourceKind.COMMAND).iterator();
|
||||
while (resources.hasNext()) {
|
||||
count++;
|
||||
resources.next();
|
||||
}
|
||||
assertEquals(1, count);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAuthenticationProvidersAreInstalled() {
|
||||
this.context = new AnnotationConfigWebApplicationContext();
|
||||
this.context.setServletContext(new MockServletContext());
|
||||
this.context.register(SecurityConfiguration.class);
|
||||
this.context.register(CrshAutoConfiguration.class);
|
||||
this.context.refresh();
|
||||
|
||||
PluginLifeCycle lifeCycle = this.context.getBean(PluginLifeCycle.class);
|
||||
PluginContext pluginContext = lifeCycle.getContext();
|
||||
|
||||
int count = 0;
|
||||
Iterator<AuthenticationPlugin> plugins = pluginContext.getPlugins(AuthenticationPlugin.class).iterator();
|
||||
while (plugins.hasNext()) {
|
||||
count++;
|
||||
plugins.next();
|
||||
}
|
||||
assertEquals(3, count);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testJaasAuthenticationProvider() {
|
||||
MockEnvironment env = new MockEnvironment();
|
||||
env.setProperty("shell.auth", "jaas");
|
||||
env.setProperty("shell.auth.jaas.domain", "my-test-domain");
|
||||
this.context = new AnnotationConfigWebApplicationContext();
|
||||
this.context.setEnvironment(env);
|
||||
this.context.setServletContext(new MockServletContext());
|
||||
this.context.register(SecurityConfiguration.class);
|
||||
this.context.register(CrshAutoConfiguration.class);
|
||||
this.context.refresh();
|
||||
|
||||
PluginLifeCycle lifeCycle = this.context.getBean(PluginLifeCycle.class);
|
||||
assertEquals(lifeCycle.getConfig().get("crash.auth"), "jaas");
|
||||
assertEquals(lifeCycle.getConfig().get("crash.auth.jaas.domain"), "my-test-domain");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testKeyAuthenticationProvider() {
|
||||
MockEnvironment env = new MockEnvironment();
|
||||
env.setProperty("shell.auth", "key");
|
||||
env.setProperty("shell.auth.key.path", "~/test.pem");
|
||||
this.context = new AnnotationConfigWebApplicationContext();
|
||||
this.context.setEnvironment(env);
|
||||
this.context.setServletContext(new MockServletContext());
|
||||
this.context.register(SecurityConfiguration.class);
|
||||
this.context.register(CrshAutoConfiguration.class);
|
||||
this.context.refresh();
|
||||
|
||||
PluginLifeCycle lifeCycle = this.context.getBean(PluginLifeCycle.class);
|
||||
assertEquals(lifeCycle.getConfig().get("crash.auth"), "key");
|
||||
assertEquals(lifeCycle.getConfig().get("crash.auth.key.path"), "~/test.pem");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSimpleAuthenticationProvider() {
|
||||
MockEnvironment env = new MockEnvironment();
|
||||
env.setProperty("shell.auth", "simple");
|
||||
env.setProperty("shell.auth.simple.username", "user");
|
||||
env.setProperty("shell.auth.simple.password", "password");
|
||||
this.context = new AnnotationConfigWebApplicationContext();
|
||||
this.context.setEnvironment(env);
|
||||
this.context.setServletContext(new MockServletContext());
|
||||
this.context.register(SecurityConfiguration.class);
|
||||
this.context.register(CrshAutoConfiguration.class);
|
||||
this.context.refresh();
|
||||
|
||||
PluginLifeCycle lifeCycle = this.context.getBean(PluginLifeCycle.class);
|
||||
assertEquals(lifeCycle.getConfig().get("crash.auth"), "simple");
|
||||
|
||||
AuthenticationPlugin<String> authenticationPlugin = null;
|
||||
String authentication = lifeCycle.getConfig().getProperty("crash.auth");
|
||||
assertNotNull(authentication);
|
||||
for (AuthenticationPlugin plugin : lifeCycle.getContext().getPlugins(AuthenticationPlugin.class)) {
|
||||
if (authentication.equals(plugin.getName())) {
|
||||
authenticationPlugin = plugin;
|
||||
break;
|
||||
}
|
||||
}
|
||||
assertNotNull(authenticationPlugin);
|
||||
try {
|
||||
assertTrue(authenticationPlugin.authenticate("user", "password"));
|
||||
}
|
||||
catch (Exception e) {
|
||||
fail();
|
||||
}
|
||||
|
||||
try {
|
||||
assertFalse(authenticationPlugin.authenticate(UUID.randomUUID().toString(),
|
||||
"password"));
|
||||
}
|
||||
catch (Exception e) {
|
||||
fail();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSpringAuthenticationProvider() {
|
||||
MockEnvironment env = new MockEnvironment();
|
||||
env.setProperty("shell.auth", "spring");
|
||||
this.context = new AnnotationConfigWebApplicationContext();
|
||||
this.context.setEnvironment(env);
|
||||
this.context.setServletContext(new MockServletContext());
|
||||
this.context.register(SecurityConfiguration.class);
|
||||
this.context.register(CrshAutoConfiguration.class);
|
||||
this.context.refresh();
|
||||
|
||||
PluginLifeCycle lifeCycle = this.context.getBean(PluginLifeCycle.class);
|
||||
|
||||
AuthenticationPlugin<String> authenticationPlugin = null;
|
||||
String authentication = lifeCycle.getConfig().getProperty("crash.auth");
|
||||
assertNotNull(authentication);
|
||||
for (AuthenticationPlugin plugin : lifeCycle.getContext().getPlugins(AuthenticationPlugin.class)) {
|
||||
if (authentication.equals(plugin.getName())) {
|
||||
authenticationPlugin = plugin;
|
||||
break;
|
||||
}
|
||||
}
|
||||
assertNotNull(authenticationPlugin);
|
||||
try {
|
||||
assertTrue(authenticationPlugin.authenticate(SecurityConfiguration.USERNAME,
|
||||
SecurityConfiguration.PASSWORD));
|
||||
}
|
||||
catch (Exception e) {
|
||||
fail();
|
||||
}
|
||||
|
||||
try {
|
||||
assertFalse(authenticationPlugin.authenticate(UUID.randomUUID().toString(),
|
||||
SecurityConfiguration.PASSWORD));
|
||||
}
|
||||
catch (Exception e) {
|
||||
fail();
|
||||
}
|
||||
}
|
||||
|
||||
@Configuration
|
||||
public static class SecurityConfiguration {
|
||||
|
||||
public static final String USERNAME = UUID.randomUUID().toString();
|
||||
|
||||
public static final String PASSWORD = UUID.randomUUID().toString();
|
||||
|
||||
@Bean
|
||||
public AuthenticationManager authenticationManager() {
|
||||
return new AuthenticationManager() {
|
||||
|
||||
@Override
|
||||
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
|
||||
if (authentication.getName().equals(USERNAME) && authentication.getCredentials().equals(PASSWORD)) {
|
||||
authentication = new UsernamePasswordAuthenticationToken(authentication.getPrincipal(),
|
||||
authentication.getCredentials(), Collections.singleton(new SimpleGrantedAuthority("ROLE_ADMIN")));
|
||||
}
|
||||
else {
|
||||
throw new BadCredentialsException("Invalid username and password");
|
||||
}
|
||||
return authentication;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Bean
|
||||
public AccessDecisionManager accessDecisionManager() {
|
||||
List<AccessDecisionVoter> voters = new ArrayList<AccessDecisionVoter>();
|
||||
voters.add(new RoleVoter());
|
||||
AccessDecisionManager result = new UnanimousBased(voters);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,282 @@
|
||||
/*
|
||||
* Copyright 2013 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.actuate.properties;
|
||||
|
||||
import static org.junit.Assert.assertArrayEquals;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
import org.springframework.beans.MutablePropertyValues;
|
||||
import org.springframework.boot.actuate.properties.CrshProperties.JaasAuthenticationProperties;
|
||||
import org.springframework.boot.actuate.properties.CrshProperties.KeyAuthenticationProperties;
|
||||
import org.springframework.boot.actuate.properties.CrshProperties.SimpleAuthenticationProperties;
|
||||
import org.springframework.boot.actuate.properties.CrshProperties.SpringAuthenticationProperties;
|
||||
import org.springframework.boot.bind.RelaxedDataBinder;
|
||||
import org.springframework.core.convert.support.DefaultConversionService;
|
||||
|
||||
/**
|
||||
* Tests for {@link CrshProperties}.
|
||||
*
|
||||
* @author Christian Dupuis
|
||||
*/
|
||||
public class CrshPropertiesTests {
|
||||
|
||||
@Test
|
||||
public void testBindingAuth() {
|
||||
CrshProperties props = new CrshProperties();
|
||||
RelaxedDataBinder binder = new RelaxedDataBinder(props, "shell");
|
||||
binder.bind(new MutablePropertyValues(Collections.singletonMap("shell.auth", "spring")));
|
||||
assertFalse(binder.getBindingResult().hasErrors());
|
||||
assertEquals("spring", props.getAuth());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBindingAuthIfEmpty() {
|
||||
CrshProperties props = new CrshProperties();
|
||||
RelaxedDataBinder binder = new RelaxedDataBinder(props, "shell");
|
||||
binder.bind(new MutablePropertyValues(Collections.singletonMap("shell.auth", "")));
|
||||
assertTrue(binder.getBindingResult().hasErrors());
|
||||
assertEquals("simple", props.getAuth());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBindingCommandRefreshInterval() {
|
||||
CrshProperties props = new CrshProperties();
|
||||
RelaxedDataBinder binder = new RelaxedDataBinder(props, "shell");
|
||||
binder.setConversionService(new DefaultConversionService());
|
||||
binder.bind(new MutablePropertyValues(Collections.singletonMap("shell.command_refresh_interval", "1")));
|
||||
assertFalse(binder.getBindingResult().hasErrors());
|
||||
assertEquals(1, props.getCommandRefreshInterval());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBindingCommandPathPatterns() {
|
||||
CrshProperties props = new CrshProperties();
|
||||
RelaxedDataBinder binder = new RelaxedDataBinder(props, "shell");
|
||||
binder.setConversionService(new DefaultConversionService());
|
||||
binder.bind(new MutablePropertyValues(Collections.singletonMap("shell.command_path_patterns",
|
||||
"pattern1, pattern2")));
|
||||
assertFalse(binder.getBindingResult().hasErrors());
|
||||
assertEquals(2, props.getCommandPathPatterns().length);
|
||||
Assert.assertArrayEquals(new String[] { "pattern1", "pattern2" }, props.getCommandPathPatterns());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBindingConfigPathPatterns() {
|
||||
CrshProperties props = new CrshProperties();
|
||||
RelaxedDataBinder binder = new RelaxedDataBinder(props, "shell");
|
||||
binder.setConversionService(new DefaultConversionService());
|
||||
binder.bind(new MutablePropertyValues(Collections.singletonMap("shell.config_path_patterns",
|
||||
"pattern1, pattern2")));
|
||||
assertFalse(binder.getBindingResult().hasErrors());
|
||||
assertEquals(2, props.getConfigPathPatterns().length, 2);
|
||||
Assert.assertArrayEquals(new String[] { "pattern1", "pattern2" }, props.getConfigPathPatterns());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBindingDisabledPlugins() {
|
||||
CrshProperties props = new CrshProperties();
|
||||
RelaxedDataBinder binder = new RelaxedDataBinder(props, "shell");
|
||||
binder.setConversionService(new DefaultConversionService());
|
||||
binder.bind(new MutablePropertyValues(Collections.singletonMap("shell.disabled_plugins",
|
||||
"pattern1, pattern2")));
|
||||
assertFalse(binder.getBindingResult().hasErrors());
|
||||
assertEquals(2, props.getDisabledPlugins().length, 2);
|
||||
assertArrayEquals(new String[] { "pattern1", "pattern2" }, props.getDisabledPlugins());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBindingSsh() {
|
||||
CrshProperties props = new CrshProperties();
|
||||
RelaxedDataBinder binder = new RelaxedDataBinder(props, "shell");
|
||||
binder.setConversionService(new DefaultConversionService());
|
||||
Map<String, String> map = new HashMap<String, String>();
|
||||
map.put("shell.ssh.enabled", "true");
|
||||
map.put("shell.ssh.port", "2222");
|
||||
map.put("shell.ssh.key_path", "~/.ssh/test.pem");
|
||||
binder.bind(new MutablePropertyValues(map));
|
||||
assertFalse(binder.getBindingResult().hasErrors());
|
||||
|
||||
Properties p = new Properties();
|
||||
p = props.mergeProperties(p);
|
||||
|
||||
assertEquals("2222", p.get(CrshProperties.CRASH_SSH_PORT));
|
||||
assertEquals("~/.ssh/test.pem", p.get(CrshProperties.CRASH_SSH_KEYPATH));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBindingSshIgnored() {
|
||||
CrshProperties props = new CrshProperties();
|
||||
RelaxedDataBinder binder = new RelaxedDataBinder(props, "shell");
|
||||
binder.setConversionService(new DefaultConversionService());
|
||||
Map<String, String> map = new HashMap<String, String>();
|
||||
map.put("shell.ssh.enabled", "false");
|
||||
map.put("shell.ssh.port", "2222");
|
||||
map.put("shell.ssh.key_path", "~/.ssh/test.pem");
|
||||
binder.bind(new MutablePropertyValues(map));
|
||||
assertFalse(binder.getBindingResult().hasErrors());
|
||||
|
||||
Properties p = new Properties();
|
||||
p = props.mergeProperties(p);
|
||||
|
||||
assertNull(p.get(CrshProperties.CRASH_SSH_PORT));
|
||||
assertNull(p.get(CrshProperties.CRASH_SSH_KEYPATH));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBindingTelnet() {
|
||||
CrshProperties props = new CrshProperties();
|
||||
RelaxedDataBinder binder = new RelaxedDataBinder(props, "shell");
|
||||
binder.setConversionService(new DefaultConversionService());
|
||||
Map<String, String> map = new HashMap<String, String>();
|
||||
map.put("shell.telnet.enabled", "true");
|
||||
map.put("shell.telnet.port", "2222");
|
||||
binder.bind(new MutablePropertyValues(map));
|
||||
assertFalse(binder.getBindingResult().hasErrors());
|
||||
|
||||
Properties p = new Properties();
|
||||
p = props.mergeProperties(p);
|
||||
|
||||
assertEquals("2222", p.get(CrshProperties.CRASH_TELNET_PORT));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBindingTelnetIgnored() {
|
||||
CrshProperties props = new CrshProperties();
|
||||
RelaxedDataBinder binder = new RelaxedDataBinder(props, "shell");
|
||||
binder.setConversionService(new DefaultConversionService());
|
||||
Map<String, String> map = new HashMap<String, String>();
|
||||
map.put("shell.telnet.enabled", "false");
|
||||
map.put("shell.telnet.port", "2222");
|
||||
binder.bind(new MutablePropertyValues(map));
|
||||
assertFalse(binder.getBindingResult().hasErrors());
|
||||
|
||||
Properties p = new Properties();
|
||||
p = props.mergeProperties(p);
|
||||
|
||||
assertNull(p.get(CrshProperties.CRASH_TELNET_PORT));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBindingJaas() {
|
||||
JaasAuthenticationProperties props = new JaasAuthenticationProperties();
|
||||
RelaxedDataBinder binder = new RelaxedDataBinder(props, "shell.auth.jaas");
|
||||
binder.setConversionService(new DefaultConversionService());
|
||||
Map<String, String> map = new HashMap<String, String>();
|
||||
map.put("shell.auth.jaas.domain", "my-test-domain");
|
||||
binder.bind(new MutablePropertyValues(map));
|
||||
assertFalse(binder.getBindingResult().hasErrors());
|
||||
|
||||
Properties p = new Properties();
|
||||
p = props.mergeProperties(p);
|
||||
|
||||
assertEquals("my-test-domain", p.get(CrshProperties.CRASH_AUTH_JAAS_DOMAIN));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBindingKey() {
|
||||
KeyAuthenticationProperties props = new KeyAuthenticationProperties();
|
||||
RelaxedDataBinder binder = new RelaxedDataBinder(props, "shell.auth.key");
|
||||
binder.setConversionService(new DefaultConversionService());
|
||||
Map<String, String> map = new HashMap<String, String>();
|
||||
map.put("shell.auth.key.path", "~/.ssh/test.pem");
|
||||
binder.bind(new MutablePropertyValues(map));
|
||||
assertFalse(binder.getBindingResult().hasErrors());
|
||||
|
||||
Properties p = new Properties();
|
||||
p = props.mergeProperties(p);
|
||||
|
||||
assertEquals("~/.ssh/test.pem", p.get(CrshProperties.CRASH_AUTH_KEY_PATH));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBindingKeyIgnored() {
|
||||
KeyAuthenticationProperties props = new KeyAuthenticationProperties();
|
||||
RelaxedDataBinder binder = new RelaxedDataBinder(props, "shell.auth.key");
|
||||
binder.setConversionService(new DefaultConversionService());
|
||||
Map<String, String> map = new HashMap<String, String>();
|
||||
binder.bind(new MutablePropertyValues(map));
|
||||
assertFalse(binder.getBindingResult().hasErrors());
|
||||
|
||||
Properties p = new Properties();
|
||||
p = props.mergeProperties(p);
|
||||
|
||||
assertNull(p.get(CrshProperties.CRASH_AUTH_KEY_PATH));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBindingSimple() {
|
||||
SimpleAuthenticationProperties props = new SimpleAuthenticationProperties();
|
||||
RelaxedDataBinder binder = new RelaxedDataBinder(props, "shell.auth.simple");
|
||||
binder.setConversionService(new DefaultConversionService());
|
||||
Map<String, String> map = new HashMap<String, String>();
|
||||
map.put("shell.auth.simple.username", "username123");
|
||||
map.put("shell.auth.simple.password", "password123");
|
||||
binder.bind(new MutablePropertyValues(map));
|
||||
assertFalse(binder.getBindingResult().hasErrors());
|
||||
|
||||
Properties p = new Properties();
|
||||
p = props.mergeProperties(p);
|
||||
|
||||
assertEquals("username123", p.get(CrshProperties.CRASH_AUTH_SIMPLE_USERNAME));
|
||||
assertEquals("password123", p.get(CrshProperties.CRASH_AUTH_SIMPLE_PASSWORD));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDefaultPasswordAutogeneratedIfUnresolovedPlaceholder() {
|
||||
SimpleAuthenticationProperties security = new SimpleAuthenticationProperties();
|
||||
RelaxedDataBinder binder = new RelaxedDataBinder(security, "security");
|
||||
binder.bind(new MutablePropertyValues(Collections.singletonMap(
|
||||
"shell.auth.simple.password", "${ADMIN_PASSWORD}")));
|
||||
assertFalse(binder.getBindingResult().hasErrors());
|
||||
assertTrue(security.isDefaultPassword());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDefaultPasswordAutogeneratedIfEmpty() {
|
||||
SimpleAuthenticationProperties security = new SimpleAuthenticationProperties();
|
||||
RelaxedDataBinder binder = new RelaxedDataBinder(security, "security");
|
||||
binder.bind(new MutablePropertyValues(Collections.singletonMap(
|
||||
"shell.auth.simple.password", "")));
|
||||
assertFalse(binder.getBindingResult().hasErrors());
|
||||
assertTrue(security.isDefaultPassword());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBindingSpring() {
|
||||
SpringAuthenticationProperties props = new SpringAuthenticationProperties();
|
||||
RelaxedDataBinder binder = new RelaxedDataBinder(props, "shell.auth.spring");
|
||||
binder.bind(new MutablePropertyValues(Collections.singletonMap("shell.auth.spring.roles", "role1, role2")));
|
||||
assertFalse(binder.getBindingResult().hasErrors());
|
||||
|
||||
Properties p = new Properties();
|
||||
p = props.mergeProperties(p);
|
||||
|
||||
assertEquals("role1, role2", p.get(CrshProperties.CRASH_AUTH_SPRING_ROLES));
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -47,6 +47,7 @@
|
||||
<thymeleaf-extras-springsecurity3.version>2.0.1</thymeleaf-extras-springsecurity3.version>
|
||||
<thymeleaf-layout-dialect.version>1.1.3</thymeleaf-layout-dialect.version>
|
||||
<tomcat.version>7.0.42</tomcat.version>
|
||||
<crashub.version>1.3.0-beta8</crashub.version>
|
||||
</properties>
|
||||
<dependencyManagement>
|
||||
<dependencies>
|
||||
@ -481,6 +482,31 @@
|
||||
<artifactId>geronimo-jms_1.1_spec</artifactId>
|
||||
<version>1.1</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.crashub</groupId>
|
||||
<artifactId>crash.cli</artifactId>
|
||||
<version>${crashub.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.crashub</groupId>
|
||||
<artifactId>crash.connectors.ssh</artifactId>
|
||||
<version>${crashub.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.crashub</groupId>
|
||||
<artifactId>crash.connectors.telnet</artifactId>
|
||||
<version>${crashub.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.crashub</groupId>
|
||||
<artifactId>crash.embed.spring</artifactId>
|
||||
<version>${crashub.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.crashub</groupId>
|
||||
<artifactId>crash.shell</artifactId>
|
||||
<version>${crashub.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
<build>
|
||||
|
@ -32,6 +32,10 @@
|
||||
<groupId>${project.groupId}</groupId>
|
||||
<artifactId>spring-boot-starter-security</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>${project.groupId}</groupId>
|
||||
<artifactId>spring-boot-starter-shell-crsh</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<build>
|
||||
<plugins>
|
||||
|
@ -6,4 +6,12 @@ server.port: 8080
|
||||
server.tomcat.basedir: target/tomcat
|
||||
server.tomcat.access_log_pattern: %h %t "%r" %s %b
|
||||
security.require_ssl: false
|
||||
service.name: Phil
|
||||
service.name: Phil
|
||||
shell.ssh.enabled: true
|
||||
shell.ssh.port: 2222
|
||||
shell.telnet.enabled: false
|
||||
#shell.telnet.port: 1111
|
||||
shell.auth: spring
|
||||
#shell.auth: key
|
||||
#shell.auth.key.path: ${user.home}/test/id_rsa.pub.pem
|
||||
#shell.auth: simple
|
@ -26,6 +26,7 @@
|
||||
<module>spring-boot-starter-actuator</module>
|
||||
<module>spring-boot-starter-parent</module>
|
||||
<module>spring-boot-starter-security</module>
|
||||
<module>spring-boot-starter-shell-crsh</module>
|
||||
<module>spring-boot-starter-test</module>
|
||||
<module>spring-boot-starter-tomcat</module>
|
||||
<module>spring-boot-starter-web</module>
|
||||
|
@ -98,6 +98,11 @@
|
||||
<artifactId>spring-boot-starter-security</artifactId>
|
||||
<version>0.5.0.BUILD-SNAPSHOT</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-shell-crsh</artifactId>
|
||||
<version>0.5.0.BUILD-SNAPSHOT</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-websocket</artifactId>
|
||||
|
67
spring-boot-starters/spring-boot-starter-shell-crsh/pom.xml
Normal file
67
spring-boot-starters/spring-boot-starter-shell-crsh/pom.xml
Normal file
@ -0,0 +1,67 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starters</artifactId>
|
||||
<version>0.5.0.BUILD-SNAPSHOT</version>
|
||||
</parent>
|
||||
<artifactId>spring-boot-starter-shell-crsh</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
<properties>
|
||||
<main.basedir>${basedir}/../..</main.basedir>
|
||||
</properties>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>${project.groupId}</groupId>
|
||||
<artifactId>spring-boot-starter</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.crashub</groupId>
|
||||
<artifactId>crash.cli</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.crashub</groupId>
|
||||
<artifactId>crash.connectors.ssh</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.crashub</groupId>
|
||||
<artifactId>crash.connectors.telnet</artifactId>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>javax.servlet</groupId>
|
||||
<artifactId>servlet-api</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<groupId>log4j</groupId>
|
||||
<artifactId>log4j</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<groupId>commons-logging</groupId>
|
||||
<artifactId>commons-logging</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.crashub</groupId>
|
||||
<artifactId>crash.embed.spring</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.crashub</groupId>
|
||||
<artifactId>crash.shell</artifactId>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>org.codehaus.groovy</groupId>
|
||||
<artifactId>groovy-all</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.codehaus.groovy</groupId>
|
||||
<artifactId>groovy</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
@ -0,0 +1,58 @@
|
||||
package crash.commands.base;
|
||||
|
||||
import org.crsh.cli.Command;
|
||||
import org.crsh.cli.Usage;
|
||||
import org.crsh.command.BaseCommand;
|
||||
import org.crsh.command.DescriptionFormat;
|
||||
import org.crsh.command.InvocationContext;
|
||||
import org.crsh.command.ShellCommand;
|
||||
import org.crsh.shell.impl.command.CRaSH;
|
||||
import org.crsh.text.Color;
|
||||
import org.crsh.text.Decoration;
|
||||
import org.crsh.text.Style;
|
||||
import org.crsh.text.ui.LabelElement;
|
||||
import org.crsh.text.ui.RowElement;
|
||||
import org.crsh.text.ui.TableElement;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/** @author Julien Viet */
|
||||
public class help extends BaseCommand {
|
||||
|
||||
@Usage("provides basic help")
|
||||
@Command
|
||||
public void main(InvocationContext<Object> context) throws IOException {
|
||||
|
||||
//
|
||||
TableElement table = new TableElement().rightCellPadding(1);
|
||||
table.add(
|
||||
new RowElement().
|
||||
add(new LabelElement("NAME").style(Style.style(Decoration.bold))).
|
||||
add(new LabelElement("DESCRIPTION")));
|
||||
|
||||
//
|
||||
CRaSH crash = (CRaSH)context.getSession().get("crash");
|
||||
Iterable<String> names = crash.getCommandNames();
|
||||
for (String name : names) {
|
||||
try {
|
||||
ShellCommand cmd = crash.getCommand(name);
|
||||
if (cmd != null) {
|
||||
String desc = cmd.describe(name, DescriptionFormat.DESCRIBE);
|
||||
if (desc == null) {
|
||||
desc = "";
|
||||
}
|
||||
table.add(
|
||||
new RowElement().
|
||||
add(new LabelElement(name).style(Style.style(Color.red))).
|
||||
add(new LabelElement(desc)));
|
||||
}
|
||||
} catch (Exception ignore) {
|
||||
//
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
context.provide(new LabelElement("Try one of these commands with the -h or --help switch:\n"));
|
||||
context.provide(table);
|
||||
}
|
||||
}
|
@ -0,0 +1,123 @@
|
||||
package crash.commands.base;
|
||||
|
||||
import org.crsh.cli.Argument;
|
||||
import org.crsh.cli.Command;
|
||||
import org.crsh.cli.Option;
|
||||
import org.crsh.cli.Usage;
|
||||
import org.crsh.command.BaseCommand;
|
||||
import org.crsh.command.InvocationContext;
|
||||
import org.crsh.command.PipeCommand;
|
||||
import org.crsh.command.ScriptException;
|
||||
|
||||
import javax.management.JMException;
|
||||
import javax.management.MBeanAttributeInfo;
|
||||
import javax.management.MBeanInfo;
|
||||
import javax.management.MBeanServer;
|
||||
import javax.management.ObjectInstance;
|
||||
import javax.management.ObjectName;
|
||||
import java.io.IOException;
|
||||
import java.lang.management.ManagementFactory;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/** @author Julien Viet */
|
||||
@Usage("Java Management Extensions")
|
||||
public class jmx extends BaseCommand {
|
||||
|
||||
@Usage("find mbeans")
|
||||
@Command
|
||||
public void find(
|
||||
InvocationContext<ObjectName> context,
|
||||
@Usage("The object name pattern")
|
||||
@Option(names = {"p", "pattern"})
|
||||
String pattern) throws Exception {
|
||||
|
||||
//
|
||||
ObjectName patternName = pattern != null ? ObjectName.getInstance(pattern) : null;
|
||||
MBeanServer server = ManagementFactory.getPlatformMBeanServer();
|
||||
Set<ObjectInstance> instances = server.queryMBeans(patternName, null);
|
||||
for (ObjectInstance instance : instances) {
|
||||
context.provide(instance.getObjectName());
|
||||
}
|
||||
/*
|
||||
if (context.piped) {
|
||||
} else {
|
||||
UIBuilder ui = new UIBuilder()
|
||||
ui.table(columns: [1,3]) {
|
||||
row(bold: true, fg: black, bg: white) {
|
||||
label("CLASS NAME"); label("OBJECT NAME")
|
||||
}
|
||||
instances.each { instance ->
|
||||
row() {
|
||||
label(foreground: red, instance.getClassName()); label(instance.objectName)
|
||||
}
|
||||
}
|
||||
}
|
||||
out << ui;
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
@Command
|
||||
@Usage("return the attributes info of an MBean")
|
||||
public void attributes(InvocationContext<Map> context, @Argument ObjectName name) throws IOException {
|
||||
MBeanServer server = ManagementFactory.getPlatformMBeanServer();
|
||||
try {
|
||||
MBeanInfo info = server.getMBeanInfo(name);
|
||||
for (MBeanAttributeInfo attributeInfo : info.getAttributes()) {
|
||||
HashMap<String, Object> tuple = new HashMap<String, Object>();
|
||||
tuple.put("name", attributeInfo.getName());
|
||||
tuple.put("type", attributeInfo.getType());
|
||||
tuple.put("description", attributeInfo.getDescription());
|
||||
context.provide(tuple);
|
||||
}
|
||||
}
|
||||
catch (JMException e) {
|
||||
throw new ScriptException("Could not access MBean meta data", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Usage("get attributes of an MBean")
|
||||
@Command
|
||||
public PipeCommand<ObjectName, Map> get(@Argument final List<String> attributes) {
|
||||
|
||||
// Determine common attributes from all names
|
||||
if (attributes == null || attributes.isEmpty()) {
|
||||
throw new ScriptException("Must provide JMX attributes");
|
||||
}
|
||||
|
||||
//
|
||||
return new PipeCommand<ObjectName, Map>() {
|
||||
|
||||
/** . */
|
||||
private MBeanServer server;
|
||||
|
||||
@Override
|
||||
public void open() throws ScriptException {
|
||||
server = ManagementFactory.getPlatformMBeanServer();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void provide(ObjectName name) throws IOException {
|
||||
try {
|
||||
HashMap<String, Object> tuple = new HashMap<String, Object>();
|
||||
for (String attribute : attributes) {
|
||||
String prop = name.getKeyProperty(attribute);
|
||||
if (prop != null) {
|
||||
tuple.put(attribute, prop);
|
||||
}
|
||||
else {
|
||||
tuple.put(attribute, server.getAttribute(name, attribute));
|
||||
}
|
||||
}
|
||||
context.provide(tuple);
|
||||
}
|
||||
catch (JMException ignore) {
|
||||
//
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
@ -0,0 +1,22 @@
|
||||
welcome = { ->
|
||||
def hostName;
|
||||
try {
|
||||
hostName = java.net.InetAddress.getLocalHost().getHostName();
|
||||
} catch (java.net.UnknownHostException ignore) {
|
||||
hostName = "localhost";
|
||||
}
|
||||
def version = crash.context.attributes.get("spring.boot.version")
|
||||
return """\
|
||||
. ____ _ __ _ _
|
||||
/\\\\ / ___'_ __ _ _(_)_ __ __ _ \\ \\ \\ \\
|
||||
( ( )\\___ | '_ | '_| | '_ \\/ _` | \\ \\ \\ \\
|
||||
\\\\/ ___)| |_)| | | | | || (_| | ) ) ) )
|
||||
' |____| .__|_| |_|_| |_\\__, | / / / /
|
||||
=========|_|==============|___/=/_/_/_/
|
||||
:: Spring Boot :: (v$version) on $hostName
|
||||
""";
|
||||
}
|
||||
|
||||
prompt = { ->
|
||||
return "> ";
|
||||
}
|
@ -0,0 +1,49 @@
|
||||
package commands
|
||||
|
||||
import org.crsh.text.ui.UIBuilder
|
||||
import org.springframework.boot.actuate.endpoint.MetricsEndpoint
|
||||
|
||||
class metrics {
|
||||
|
||||
@Usage("Display metrics provided by Spring Boot")
|
||||
@Command
|
||||
public void main(InvocationContext context) {
|
||||
|
||||
context.takeAlternateBuffer();
|
||||
try {
|
||||
while (!Thread.interrupted()) {
|
||||
out.cls()
|
||||
out.show(new UIBuilder().table(columns:[1]) {
|
||||
header {
|
||||
table(columns:[1], separator: dashed) {
|
||||
header(bold: true, fg: black, bg: white) { label("metrics"); }
|
||||
}
|
||||
}
|
||||
row {
|
||||
table(columns:[1, 1]) {
|
||||
header(bold: true, fg: black, bg: white) {
|
||||
label("NAME")
|
||||
label("VALUE")
|
||||
}
|
||||
|
||||
context.attributes['spring.beanfactory'].getBeansOfType(MetricsEndpoint.class).each { name, metrics ->
|
||||
metrics.invoke().each { k, v ->
|
||||
row {
|
||||
label(k)
|
||||
label(v)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
out.flush();
|
||||
Thread.sleep(1000);
|
||||
}
|
||||
}
|
||||
finally {
|
||||
context.releaseAlternateBuffer();
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user