From e520b10319fc88e4c30bd5180b85d4b2180a1954 Mon Sep 17 00:00:00 2001 From: hengyunabc Date: Thu, 4 Mar 2021 22:47:58 +0800 Subject: [PATCH] Add auth command, support authentication (#1724) --- bin/as.sh | 30 +++ .../com/taobao/arthas/boot/Bootstrap.java | 32 +++ .../taobao/arthas/common/ArthasConstants.java | 9 + core/src/main/java/arthas.properties | 4 + .../java/com/taobao/arthas/core/Arthas.java | 10 +- .../core/command/BuiltinCommandPack.java | 2 + .../core/command/basic1000/AuthCommand.java | 109 ++++++++ .../taobao/arthas/core/config/Configure.java | 19 ++ .../arthas/core/security/BasicPrincipal.java | 69 +++++ .../core/security/SecurityAuthenticator.java | 71 +++++ .../security/SecurityAuthenticatorImpl.java | 90 +++++++ .../arthas/core/server/ArthasBootstrap.java | 24 +- .../arthas/core/shell/impl/ShellImpl.java | 17 ++ .../shell/system/impl/JobControllerImpl.java | 23 +- .../core/shell/term/impl/HttpTermServer.java | 8 +- .../arthas/core/shell/term/impl/TermImpl.java | 4 + .../http/BasicHttpAuthenticatorHandler.java | 138 ++++++++++ .../term/impl/http/ExtHttpTtyConnection.java | 76 ++++++ .../term/impl/http/HttpRequestHandler.java | 2 +- .../impl/http/NettyWebsocketTtyBootstrap.java | 7 +- .../term/impl/http/TtyServerInitializer.java | 14 +- .../impl/http/TtyWebSocketFrameHandler.java | 35 +-- .../term/impl/http/api/HttpApiHandler.java | 22 +- .../term/impl/http/session/HttpSession.java | 156 +++++++++++ .../impl/http/session/HttpSessionManager.java | 95 +++++++ .../term/impl/http/session/LRUCache.java | 100 +++++++ .../impl/http/session/SimpleHttpSession.java | 79 ++++++ .../impl/httptelnet/HttpTelnetTermServer.java | 7 +- .../httptelnet/NettyHttpTelnetBootstrap.java | 8 +- .../NettyHttpTelnetTtyBootstrap.java | 6 +- .../httptelnet/ProtocolDetectHandler.java | 10 +- .../taobao/arthas/core/util/StringUtils.java | 37 +++ .../taobao/arthas/core/http/web-console.js | 2 + .../SecurityAuthenticatorImplTest.java | 40 +++ pom.xml | 248 +++++++++--------- site/src/site/sphinx/advanced-use.md | 3 + site/src/site/sphinx/auth.md | 46 ++++ site/src/site/sphinx/commands.md | 1 + site/src/site/sphinx/en/advanced-use.md | 4 + site/src/site/sphinx/en/auth.md | 45 ++++ site/src/site/sphinx/en/commands.md | 1 + .../server/TunnelSocketServerInitializer.java | 2 +- 42 files changed, 1515 insertions(+), 190 deletions(-) create mode 100644 core/src/main/java/com/taobao/arthas/core/command/basic1000/AuthCommand.java create mode 100644 core/src/main/java/com/taobao/arthas/core/security/BasicPrincipal.java create mode 100644 core/src/main/java/com/taobao/arthas/core/security/SecurityAuthenticator.java create mode 100644 core/src/main/java/com/taobao/arthas/core/security/SecurityAuthenticatorImpl.java create mode 100644 core/src/main/java/com/taobao/arthas/core/shell/term/impl/http/BasicHttpAuthenticatorHandler.java create mode 100644 core/src/main/java/com/taobao/arthas/core/shell/term/impl/http/ExtHttpTtyConnection.java create mode 100644 core/src/main/java/com/taobao/arthas/core/shell/term/impl/http/session/HttpSession.java create mode 100644 core/src/main/java/com/taobao/arthas/core/shell/term/impl/http/session/HttpSessionManager.java create mode 100644 core/src/main/java/com/taobao/arthas/core/shell/term/impl/http/session/LRUCache.java create mode 100644 core/src/main/java/com/taobao/arthas/core/shell/term/impl/http/session/SimpleHttpSession.java create mode 100644 core/src/test/java/com/taobao/arthas/core/security/SecurityAuthenticatorImplTest.java create mode 100644 site/src/site/sphinx/auth.md create mode 100644 site/src/site/sphinx/en/auth.md diff --git a/bin/as.sh b/bin/as.sh index 99f9cdc76..e5d8182bd 100755 --- a/bin/as.sh +++ b/bin/as.sh @@ -145,6 +145,12 @@ STAT_URL= # app name APP_NAME= +# username +USERNAME= + +# password +PASSWORD= + ############ Command Arguments ############ # if arguments contains -c/--command or -f/--batch-file, BATCH_MODE will be true @@ -400,6 +406,7 @@ Usage: [--http-port ] [--session-timeout ] [--arthas-home ] [--tunnel-server ] [--agent-id ] [--stat-url ] [--app-name ] + [--username ] [--password ] [--use-version ] [--repo-mirror ] [--versions] [--use-http] [--attach-only] [-c ] [-f ] [-v] [pid] @@ -420,6 +427,8 @@ Options and Arguments: --tunnel-server Remote tunnel server url --agent-id Special agent id --app-name Special app name + --username Special username + --password Special password --select select target process by classname or JARfilename -c,--command Command to execute, multiple commands separated by ; @@ -433,6 +442,7 @@ EXAMPLES: ./as.sh ./as.sh --target-ip 0.0.0.0 ./as.sh --telnet-port 9999 --http-port -1 + ./as.sh --username admin --password ./as.sh --tunnel-server 'ws://192.168.10.11:7777/ws' --app-name demoapp ./as.sh --tunnel-server 'ws://192.168.10.11:7777/ws' --agent-id bvDOe8XbTM2pQWjF4cfw ./as.sh --stat-url 'http://192.168.10.11:8080/api/stat' @@ -607,6 +617,16 @@ parse_arguments() shift # past argument shift # past value ;; + --username) + USERNAME="$2" + shift # past argument + shift # past value + ;; + --password) + PASSWORD="$2" + shift # past argument + shift # past value + ;; --use-http) USE_HTTP=true shift # past argument @@ -806,6 +826,16 @@ attach_jvm() tempArgs+=("${APP_NAME}") fi + if [ "${USERNAME}" ]; then + tempArgs+=("-username") + tempArgs+=("${USERNAME}") + fi + + if [ "${PASSWORD}" ]; then + tempArgs+=("-password") + tempArgs+=("${PASSWORD}") + fi + if [ "${TARGET_IP}" ]; then tempArgs+=("-target-ip") tempArgs+=("${TARGET_IP}") diff --git a/boot/src/main/java/com/taobao/arthas/boot/Bootstrap.java b/boot/src/main/java/com/taobao/arthas/boot/Bootstrap.java index 69192c3c0..8cdd1d639 100644 --- a/boot/src/main/java/com/taobao/arthas/boot/Bootstrap.java +++ b/boot/src/main/java/com/taobao/arthas/boot/Bootstrap.java @@ -47,6 +47,7 @@ import static com.taobao.arthas.boot.ProcessUtils.STATUS_EXEC_TIMEOUT; @Summary("Bootstrap Arthas") @Description("EXAMPLES:\n" + " java -jar arthas-boot.jar \n" + " java -jar arthas-boot.jar --target-ip 0.0.0.0\n" + " java -jar arthas-boot.jar --telnet-port 9999 --http-port -1\n" + + " java -jar arthas-boot.jar --username admin --password \n" + " java -jar arthas-boot.jar --tunnel-server 'ws://192.168.10.11:7777/ws' --app-name demoapp\n" + " java -jar arthas-boot.jar --tunnel-server 'ws://192.168.10.11:7777/ws' --agent-id bvDOe8XbTM2pQWjF4cfw\n" + " java -jar arthas-boot.jar --stat-url 'http://192.168.10.11:8080/api/stat'\n" @@ -120,6 +121,9 @@ public class Bootstrap { private String appName; + private String username; + private String password; + private String statUrl; private String select; @@ -266,6 +270,17 @@ public class Bootstrap { this.appName = appName; } + @Option(longName = "username") + @Description("The username") + public void setUsername(String username) { + this.username = username; + } + @Option(longName = "password") + @Description("The password") + public void setPassword(String password) { + this.password = password; + } + @Option(longName = "stat-url") @Description("The report stat url") public void setStatUrl(String statUrl) { @@ -505,6 +520,15 @@ public class Bootstrap { attachArgs.add("" + bootstrap.getSessionTimeout()); } + if (bootstrap.getUsername() != null) { + attachArgs.add("-username"); + attachArgs.add(bootstrap.getUsername()); + } + if (bootstrap.getPassword() != null) { + attachArgs.add("-password"); + attachArgs.add(bootstrap.getPassword()); + } + if (bootstrap.getTunnelServer() != null) { attachArgs.add("-tunnel-server"); attachArgs.add(bootstrap.getTunnelServer()); @@ -812,4 +836,12 @@ public class Bootstrap { public String getSelect() { return select; } + + public String getUsername() { + return username; + } + + public String getPassword() { + return password; + } } diff --git a/common/src/main/java/com/taobao/arthas/common/ArthasConstants.java b/common/src/main/java/com/taobao/arthas/common/ArthasConstants.java index cfee4b65f..db9a20b74 100644 --- a/common/src/main/java/com/taobao/arthas/common/ArthasConstants.java +++ b/common/src/main/java/com/taobao/arthas/common/ArthasConstants.java @@ -26,4 +26,13 @@ public class ArthasConstants { public static final int TELNET_PORT = 3658; public static final int WEBSOCKET_IDLE_SECONDS = 60; + + /** + * HTTP cookie id + */ + public static final String ASESSION_KEY = "asession"; + + public static final String DEFAULT_USERNAME = "arthas"; + public static final String SUBJECT_KEY = "subject"; + public static final String AUTH = "auth"; } diff --git a/core/src/main/java/arthas.properties b/core/src/main/java/arthas.properties index 64cb21762..045c9a6fa 100644 --- a/core/src/main/java/arthas.properties +++ b/core/src/main/java/arthas.properties @@ -8,6 +8,10 @@ arthas.sessionTimeout=1800 arthas.enhanceLoaders=java.lang.ClassLoader +# https://arthas.aliyun.com/doc/en/auth +# arthas.username=arthas +# arthas.password=arthas + #arthas.appName=demoapp #arthas.tunnelServer=ws://127.0.0.1:7777/ws #arthas.agentId=mmmmmmyiddddd diff --git a/core/src/main/java/com/taobao/arthas/core/Arthas.java b/core/src/main/java/com/taobao/arthas/core/Arthas.java index 1d282870e..60c7b4839 100644 --- a/core/src/main/java/com/taobao/arthas/core/Arthas.java +++ b/core/src/main/java/com/taobao/arthas/core/Arthas.java @@ -38,6 +38,9 @@ public class Arthas { Option sessionTimeout = new TypedOption().setType(Integer.class) .setShortName("session-timeout"); + Option username = new TypedOption().setType(String.class).setShortName("username"); + Option password = new TypedOption().setType(String.class).setShortName("password"); + Option tunnelServer = new TypedOption().setType(String.class).setShortName("tunnel-server"); Option agentId = new TypedOption().setType(String.class).setShortName("agent-id"); Option appName = new TypedOption().setType(String.class).setShortName(ArthasConstants.APP_NAME); @@ -45,7 +48,9 @@ public class Arthas { Option statUrl = new TypedOption().setType(String.class).setShortName("stat-url"); CLI cli = CLIs.create("arthas").addOption(pid).addOption(core).addOption(agent).addOption(target) - .addOption(telnetPort).addOption(httpPort).addOption(sessionTimeout).addOption(tunnelServer).addOption(agentId).addOption(appName).addOption(statUrl); + .addOption(telnetPort).addOption(httpPort).addOption(sessionTimeout) + .addOption(username).addOption(password) + .addOption(tunnelServer).addOption(agentId).addOption(appName).addOption(statUrl); CommandLine commandLine = cli.parse(Arrays.asList(args)); Configure configure = new Configure(); @@ -67,6 +72,9 @@ public class Arthas { configure.setHttpPort((Integer) commandLine.getOptionValue("http-port")); } + configure.setUsername((String) commandLine.getOptionValue("username")); + configure.setPassword((String) commandLine.getOptionValue("password")); + configure.setTunnelServer((String) commandLine.getOptionValue("tunnel-server")); configure.setAgentId((String) commandLine.getOptionValue("agent-id")); configure.setStatUrl((String) commandLine.getOptionValue("stat-url")); diff --git a/core/src/main/java/com/taobao/arthas/core/command/BuiltinCommandPack.java b/core/src/main/java/com/taobao/arthas/core/command/BuiltinCommandPack.java index f240e5ac2..1e9f91953 100644 --- a/core/src/main/java/com/taobao/arthas/core/command/BuiltinCommandPack.java +++ b/core/src/main/java/com/taobao/arthas/core/command/BuiltinCommandPack.java @@ -9,6 +9,7 @@ import com.taobao.arthas.core.command.basic1000.HelpCommand; import com.taobao.arthas.core.command.basic1000.HistoryCommand; import com.taobao.arthas.core.command.basic1000.KeymapCommand; import com.taobao.arthas.core.command.basic1000.OptionsCommand; +import com.taobao.arthas.core.command.basic1000.AuthCommand; import com.taobao.arthas.core.command.basic1000.PwdCommand; import com.taobao.arthas.core.command.basic1000.ResetCommand; import com.taobao.arthas.core.command.basic1000.SessionCommand; @@ -69,6 +70,7 @@ public class BuiltinCommandPack implements CommandResolver { private static void initCommands() { commands.add(Command.create(HelpCommand.class)); + commands.add(Command.create(AuthCommand.class)); commands.add(Command.create(KeymapCommand.class)); commands.add(Command.create(SearchClassCommand.class)); commands.add(Command.create(SearchMethodCommand.class)); diff --git a/core/src/main/java/com/taobao/arthas/core/command/basic1000/AuthCommand.java b/core/src/main/java/com/taobao/arthas/core/command/basic1000/AuthCommand.java new file mode 100644 index 000000000..0ca808d0b --- /dev/null +++ b/core/src/main/java/com/taobao/arthas/core/command/basic1000/AuthCommand.java @@ -0,0 +1,109 @@ +package com.taobao.arthas.core.command.basic1000; + +import javax.security.auth.Subject; +import javax.security.auth.login.LoginException; + +import com.alibaba.arthas.deps.org.slf4j.Logger; +import com.alibaba.arthas.deps.org.slf4j.LoggerFactory; +import com.taobao.arthas.common.ArthasConstants; +import com.taobao.arthas.core.command.Constants; +import com.taobao.arthas.core.security.BasicPrincipal; +import com.taobao.arthas.core.security.SecurityAuthenticator; +import com.taobao.arthas.core.server.ArthasBootstrap; +import com.taobao.arthas.core.shell.cli.Completion; +import com.taobao.arthas.core.shell.cli.CompletionUtils; +import com.taobao.arthas.core.shell.command.AnnotatedCommand; +import com.taobao.arthas.core.shell.command.CommandProcess; +import com.taobao.arthas.core.shell.session.Session; +import com.taobao.middleware.cli.annotations.Argument; +import com.taobao.middleware.cli.annotations.DefaultValue; +import com.taobao.middleware.cli.annotations.Description; +import com.taobao.middleware.cli.annotations.Name; +import com.taobao.middleware.cli.annotations.Option; +import com.taobao.middleware.cli.annotations.Summary; + +/** + * TODO 支持更多的鉴权方式。目前只支持 username/password的方式 + * + * @author hengyunabc 2021-03-03 + * + */ +// @formatter:off +@Name(ArthasConstants.AUTH) +@Summary("Authenticates the current session") +@Description(Constants.EXAMPLE + + " auth" + + " auth \n" + + " auth --username \n" + + Constants.WIKI + Constants.WIKI_HOME + ArthasConstants.AUTH) +//@formatter:on +public class AuthCommand extends AnnotatedCommand { + private static final Logger logger = LoggerFactory.getLogger(AuthCommand.class); + + private String username; + private String password; + private SecurityAuthenticator authenticator = ArthasBootstrap.getInstance().getSecurityAuthenticator(); + + @Argument(argName = "password", index = 0, required = false) + @Description("password") + public void setPassword(String password) { + this.password = password; + } + + @Option(shortName = "n", longName = "username") + @Description("username, default value 'arthas'") + @DefaultValue(ArthasConstants.DEFAULT_USERNAME) + public void setUsername(String username) { + this.username = username; + } + + @Override + public void process(CommandProcess process) { + int status = 0; + String message = ""; + try { + Session session = process.session(); + if (username == null) { + status = 1; + message = "username can not be empty!"; + return; + } + if (password == null) { // 没有传入passowrd参数时,打印当前结果 + boolean authenticated = session.get(ArthasConstants.SUBJECT_KEY) != null; + boolean needLogin = this.authenticator.needLogin(); + + message = "Authentication result: " + authenticated + ", Need authentication: " + needLogin; + if (needLogin && !authenticated) { + status = 1; + } + return; + } else { + // 尝试进行鉴权 + BasicPrincipal principal = new BasicPrincipal(username, password); + try { + Subject subject = authenticator.login(principal); + if (subject != null) { + // 把subject 保存到 session里,后续其它命令则可以正常执行 + session.put(ArthasConstants.SUBJECT_KEY, subject); + message = "Authentication result: " + true + ", username: " + username; + } else { + status = 1; + message = "Authentication result: " + false + ", username: " + username; + } + } catch (LoginException e) { + logger.error("Authentication error, username: {}", username, e); + } + } + } finally { + process.end(status, message); + } + } + + @Override + public void complete(Completion completion) { + if (!CompletionUtils.completeFilePath(completion)) { + super.complete(completion); + } + } + +} diff --git a/core/src/main/java/com/taobao/arthas/core/config/Configure.java b/core/src/main/java/com/taobao/arthas/core/config/Configure.java index cec4e16cb..a3b7ba34e 100644 --- a/core/src/main/java/com/taobao/arthas/core/config/Configure.java +++ b/core/src/main/java/com/taobao/arthas/core/config/Configure.java @@ -28,6 +28,9 @@ public class Configure { private String tunnelServer; private String agentId; + private String username; + private String password; + /** * @see com.taobao.arthas.common.ArthasConstants#ARTHAS_OUTPUT */ @@ -161,6 +164,22 @@ public class Configure { this.outputPath = outputPath; } + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + /** * 序列化成字符串 * diff --git a/core/src/main/java/com/taobao/arthas/core/security/BasicPrincipal.java b/core/src/main/java/com/taobao/arthas/core/security/BasicPrincipal.java new file mode 100644 index 000000000..327047513 --- /dev/null +++ b/core/src/main/java/com/taobao/arthas/core/security/BasicPrincipal.java @@ -0,0 +1,69 @@ +package com.taobao.arthas.core.security; + +import java.security.Principal; + +/** + * Basic {@link Principal}. + * + * @author hengyunabc 2021-03-04 + */ +public final class BasicPrincipal implements Principal { + + private final String username; + private final String password; + + public BasicPrincipal(String username, String password) { + this.username = username; + this.password = password; + } + + @Override + public String getName() { + return username; + } + + public String getUsername() { + return username; + } + + public String getPassword() { + return password; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((password == null) ? 0 : password.hashCode()); + result = prime * result + ((username == null) ? 0 : username.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + BasicPrincipal other = (BasicPrincipal) obj; + if (password == null) { + if (other.password != null) + return false; + } else if (!password.equals(other.password)) + return false; + if (username == null) { + if (other.username != null) + return false; + } else if (!username.equals(other.username)) + return false; + return true; + } + + @Override + public String toString() { + // do not display the password + return "BasicPrincipal[" + username + "]"; + } +} \ No newline at end of file diff --git a/core/src/main/java/com/taobao/arthas/core/security/SecurityAuthenticator.java b/core/src/main/java/com/taobao/arthas/core/security/SecurityAuthenticator.java new file mode 100644 index 000000000..598c31980 --- /dev/null +++ b/core/src/main/java/com/taobao/arthas/core/security/SecurityAuthenticator.java @@ -0,0 +1,71 @@ +package com.taobao.arthas.core.security; + +import java.security.Principal; + +import javax.security.auth.Subject; +import javax.security.auth.login.LoginException; + +/** + * A {@link SecurityAuthenticator} allows to plugin custom authenticators, such + * as JAAS based or custom implementations. + */ +public interface SecurityAuthenticator { + + + boolean needLogin(); + + /** + * Sets the name of the realm to use. + */ + void setName(String name); + + /** + * Gets the name of the realm. + */ + String getName(); + + /** + * Sets the role class names (separated by comma) + *

+ * By default if no explicit role class names has been configured, then this + * implementation will assume the {@link Subject} + * {@link java.security.Principal}s is a role if the classname contains the word + * role (lower cased). + * + * @param names a list of FQN class names for role + * {@link java.security.Principal} implementations. + */ + void setRoleClassNames(String names); + + /** + * Attempts to login the {@link java.security.Principal} on this realm. + *

+ * The login is a success if no Exception is thrown, and a {@link Subject} is + * returned. + * + * @param principal the principal + * @return the subject for the logged in principal, must not be + * null + * @throws LoginException is thrown if error logging in the + * {@link java.security.Principal} + */ + Subject login(Principal principal) throws LoginException; + + /** + * Attempt to logout the subject. + * + * @param subject subject to logout + * @throws LoginException is thrown if error logging out subject + */ + void logout(Subject subject) throws LoginException; + + /** + * Gets the user roles from the given {@link Subject} + * + * @param subject the subject + * @return null if no roles, otherwise a String with roles separated by + * comma. + */ + String getUserRoles(Subject subject); + +} \ No newline at end of file diff --git a/core/src/main/java/com/taobao/arthas/core/security/SecurityAuthenticatorImpl.java b/core/src/main/java/com/taobao/arthas/core/security/SecurityAuthenticatorImpl.java new file mode 100644 index 000000000..e43bd19d8 --- /dev/null +++ b/core/src/main/java/com/taobao/arthas/core/security/SecurityAuthenticatorImpl.java @@ -0,0 +1,90 @@ +package com.taobao.arthas.core.security; + +import java.security.Principal; + +import javax.security.auth.Subject; +import javax.security.auth.login.LoginException; + +import com.alibaba.arthas.deps.org.slf4j.Logger; +import com.alibaba.arthas.deps.org.slf4j.LoggerFactory; +import com.taobao.arthas.common.ArthasConstants; +import com.taobao.arthas.core.util.StringUtils; + +/** + * TODO 支持不同角色不同权限,command按角色分类? + * + * @author hengyunabc 2021-03-03 + * + */ +public class SecurityAuthenticatorImpl implements SecurityAuthenticator { + private static final Logger logger = LoggerFactory.getLogger(SecurityAuthenticatorImpl.class); + private String username; + private String password; + private Subject subject; + + public SecurityAuthenticatorImpl(String username, String password) { + if (username != null && password == null) { + password = StringUtils.randomString(32); + logger.info("\nUsing generated security password: {}\n", password); + } + if (username == null && password != null) { + username = ArthasConstants.DEFAULT_USERNAME; + } + + this.username = username; + this.password = password; + + subject = new Subject(); + } + + @Override + public void setName(String name) { + // TODO Auto-generated method stub + + } + + @Override + public String getName() { + // TODO Auto-generated method stub + return null; + } + + @Override + public void setRoleClassNames(String names) { + // TODO Auto-generated method stub + + } + + @Override + public Subject login(Principal principal) throws LoginException { + if (principal == null) { + return null; + } + if (principal instanceof BasicPrincipal) { + BasicPrincipal basicPrincipal = (BasicPrincipal) principal; + if (basicPrincipal.getName().equals(username) && basicPrincipal.getPassword().equals(this.password)) { + return subject; + } + } + + return null; + } + + @Override + public void logout(Subject subject) throws LoginException { + // TODO Auto-generated method stub + + } + + @Override + public String getUserRoles(Subject subject) { + // TODO Auto-generated method stub + return null; + } + + @Override + public boolean needLogin() { + return username != null && password != null; + } + +} diff --git a/core/src/main/java/com/taobao/arthas/core/server/ArthasBootstrap.java b/core/src/main/java/com/taobao/arthas/core/server/ArthasBootstrap.java index a627da9a0..609bdeecd 100644 --- a/core/src/main/java/com/taobao/arthas/core/server/ArthasBootstrap.java +++ b/core/src/main/java/com/taobao/arthas/core/server/ArthasBootstrap.java @@ -50,6 +50,8 @@ import com.taobao.arthas.core.env.ArthasEnvironment; import com.taobao.arthas.core.env.MapPropertySource; import com.taobao.arthas.core.env.PropertiesPropertySource; import com.taobao.arthas.core.env.PropertySource; +import com.taobao.arthas.core.security.SecurityAuthenticator; +import com.taobao.arthas.core.security.SecurityAuthenticatorImpl; import com.taobao.arthas.core.server.instrument.ClassLoader_Instrument; import com.taobao.arthas.core.shell.ShellServer; import com.taobao.arthas.core.shell.ShellServerOptions; @@ -62,6 +64,7 @@ import com.taobao.arthas.core.shell.session.SessionManager; import com.taobao.arthas.core.shell.session.impl.SessionManagerImpl; import com.taobao.arthas.core.shell.term.impl.HttpTermServer; import com.taobao.arthas.core.shell.term.impl.http.api.HttpApiHandler; +import com.taobao.arthas.core.shell.term.impl.http.session.HttpSessionManager; import com.taobao.arthas.core.shell.term.impl.httptelnet.HttpTelnetTermServer; import com.taobao.arthas.core.util.ArthasBanner; import com.taobao.arthas.core.util.FileUtils; @@ -119,6 +122,9 @@ public class ArthasBootstrap { private HttpApiHandler httpApiHandler; + private HttpSessionManager httpSessionManager; + private SecurityAuthenticator securityAuthenticator; + private ArthasBootstrap(Instrumentation instrumentation, Map args) throws Throwable { this.instrumentation = instrumentation; @@ -320,7 +326,7 @@ public class ArthasBootstrap { overrideAll = Boolean.parseBoolean(properties.getProperty(CONFIG_OVERRIDE_ALL, "false")); } - PropertySource propertySource = new PropertiesPropertySource(location, properties); + PropertySource propertySource = new PropertiesPropertySource(location, properties); if (overrideAll) { arthasEnvironment.addFirst(propertySource); } else { @@ -383,6 +389,9 @@ public class ArthasBootstrap { options.setSessionTimeout(configure.getSessionTimeout() * 1000); } + this.httpSessionManager = new HttpSessionManager(); + this.securityAuthenticator = new SecurityAuthenticatorImpl(configure.getUsername(), configure.getPassword()); + shellServer = new ShellServerImpl(options); BuiltinCommandPack builtinCommands = new BuiltinCommandPack(); List resolvers = new ArrayList(); @@ -394,18 +403,18 @@ public class ArthasBootstrap { // TODO: discover user provided command resolver if (configure.getTelnetPort() != null && configure.getTelnetPort() > 0) { shellServer.registerTermServer(new HttpTelnetTermServer(configure.getIp(), configure.getTelnetPort(), - options.getConnectionTimeout(), workerGroup)); + options.getConnectionTimeout(), workerGroup, httpSessionManager)); } else { logger().info("telnet port is {}, skip bind telnet server.", configure.getTelnetPort()); } if (configure.getHttpPort() != null && configure.getHttpPort() > 0) { shellServer.registerTermServer(new HttpTermServer(configure.getIp(), configure.getHttpPort(), - options.getConnectionTimeout(), workerGroup)); + options.getConnectionTimeout(), workerGroup, httpSessionManager)); } else { // listen local address in VM communication if (configure.getTunnelServer() != null) { shellServer.registerTermServer(new HttpTermServer(configure.getIp(), configure.getHttpPort(), - options.getConnectionTimeout(), workerGroup)); + options.getConnectionTimeout(), workerGroup, httpSessionManager)); } logger().info("http port is {}, skip bind http server.", configure.getHttpPort()); } @@ -480,6 +489,9 @@ public class ArthasBootstrap { sessionManager.close(); sessionManager = null; } + if (this.httpSessionManager != null) { + httpSessionManager.stop(); + } if (timer != null) { timer.cancel(); } @@ -633,4 +645,8 @@ public class ArthasBootstrap { return outputPath; } + public SecurityAuthenticator getSecurityAuthenticator() { + return securityAuthenticator; + } + } diff --git a/core/src/main/java/com/taobao/arthas/core/shell/impl/ShellImpl.java b/core/src/main/java/com/taobao/arthas/core/shell/impl/ShellImpl.java index a9e3ac54b..f69002adf 100644 --- a/core/src/main/java/com/taobao/arthas/core/shell/impl/ShellImpl.java +++ b/core/src/main/java/com/taobao/arthas/core/shell/impl/ShellImpl.java @@ -18,13 +18,18 @@ import com.taobao.arthas.core.shell.system.impl.InternalCommandManager; import com.taobao.arthas.core.shell.system.impl.JobControllerImpl; import com.taobao.arthas.core.shell.term.Term; import com.taobao.arthas.core.shell.term.impl.TermImpl; +import com.taobao.arthas.core.shell.term.impl.http.ExtHttpTtyConnection; import com.taobao.arthas.core.util.Constants; import com.taobao.arthas.core.util.FileUtils; +import io.termd.core.tty.TtyConnection; + import java.io.File; import java.lang.instrument.Instrumentation; import java.util.Date; import java.util.List; +import java.util.Map; +import java.util.Map.Entry; import java.util.Set; import java.util.UUID; @@ -49,6 +54,18 @@ public class ShellImpl implements Shell { public ShellImpl(ShellServer server, Term term, InternalCommandManager commandManager, Instrumentation instrumentation, long pid, JobControllerImpl jobController) { + if (term instanceof TermImpl) { + TermImpl termImpl = (TermImpl) term; + TtyConnection conn = termImpl.getConn(); + if (conn instanceof ExtHttpTtyConnection) { + // 传递http cookie 里的鉴权信息到新建立的session中 + ExtHttpTtyConnection extConn = (ExtHttpTtyConnection) conn; + Map extSessions = extConn.extSessions(); + for (Entry entry : extSessions.entrySet()) { + session.put(entry.getKey(), entry.getValue()); + } + } + } session.put(Session.COMMAND_MANAGER, commandManager); session.put(Session.INSTRUMENTATION, instrumentation); session.put(Session.PID, pid); diff --git a/core/src/main/java/com/taobao/arthas/core/shell/system/impl/JobControllerImpl.java b/core/src/main/java/com/taobao/arthas/core/shell/system/impl/JobControllerImpl.java index e21b3551d..d8880f8e5 100644 --- a/core/src/main/java/com/taobao/arthas/core/shell/system/impl/JobControllerImpl.java +++ b/core/src/main/java/com/taobao/arthas/core/shell/system/impl/JobControllerImpl.java @@ -1,7 +1,9 @@ package com.taobao.arthas.core.shell.system.impl; +import com.taobao.arthas.common.ArthasConstants; import com.taobao.arthas.core.GlobalOptions; import com.taobao.arthas.core.distribution.ResultDistributor; +import com.taobao.arthas.core.server.ArthasBootstrap; import com.taobao.arthas.core.shell.cli.CliToken; import com.taobao.arthas.core.shell.command.Command; import com.taobao.arthas.core.shell.command.internal.RedirectHandler; @@ -60,15 +62,30 @@ public class JobControllerImpl implements JobController { return jobs.remove(id) != null; } + private void checkPermission(Session session, CliToken token) { + if (ArthasBootstrap.getInstance().getSecurityAuthenticator().needLogin()) { + // 检查session是否有 Subject + Object subject = session.get(ArthasConstants.SUBJECT_KEY); + if (subject == null) { + if (token != null && token.isText() && token.value().trim().equals(ArthasConstants.AUTH)) { + // 执行的是auth 命令 + return; + } + throw new IllegalArgumentException("Error! command not permitted, try to use 'auth' command to authenticates."); + } + } + } + @Override public Job createJob(InternalCommandManager commandManager, List tokens, Session session, JobListener jobHandler, Term term, ResultDistributor resultDistributor) { + checkPermission(session, tokens.get(0)); int jobId = idGenerator.incrementAndGet(); StringBuilder line = new StringBuilder(); for (CliToken arg : tokens) { line.append(arg.raw()); } boolean runInBackground = runInBackground(tokens); - Process process = createProcess(tokens, commandManager, jobId, term, resultDistributor); + Process process = createProcess(session, tokens, commandManager, jobId, term, resultDistributor); process.setJobId(jobId); JobImpl job = new JobImpl(jobId, this, process, line.toString(), runInBackground, session, jobHandler); jobs.put(jobId, job); @@ -126,12 +143,14 @@ public class JobControllerImpl implements JobController { * @param resultDistributor * @return the created process */ - private Process createProcess(List line, InternalCommandManager commandManager, int jobId, Term term, ResultDistributor resultDistributor) { + private Process createProcess(Session session, List line, InternalCommandManager commandManager, int jobId, Term term, ResultDistributor resultDistributor) { try { ListIterator tokens = line.listIterator(); while (tokens.hasNext()) { CliToken token = tokens.next(); if (token.isText()) { + // check before create process + checkPermission(session, token); Command command = commandManager.getCommand(token.value()); if (command != null) { return createCommandProcess(command, tokens, jobId, term, resultDistributor); diff --git a/core/src/main/java/com/taobao/arthas/core/shell/term/impl/HttpTermServer.java b/core/src/main/java/com/taobao/arthas/core/shell/term/impl/HttpTermServer.java index c8dafe942..73ee3cc89 100644 --- a/core/src/main/java/com/taobao/arthas/core/shell/term/impl/HttpTermServer.java +++ b/core/src/main/java/com/taobao/arthas/core/shell/term/impl/HttpTermServer.java @@ -7,6 +7,8 @@ import com.taobao.arthas.core.shell.handlers.Handler; import com.taobao.arthas.core.shell.term.Term; import com.taobao.arthas.core.shell.term.TermServer; import com.taobao.arthas.core.shell.term.impl.http.NettyWebsocketTtyBootstrap; +import com.taobao.arthas.core.shell.term.impl.http.session.HttpSessionManager; + import io.netty.util.concurrent.EventExecutorGroup; import io.termd.core.function.Consumer; import io.termd.core.tty.TtyConnection; @@ -26,12 +28,14 @@ public class HttpTermServer extends TermServer { private int port; private long connectionTimeout; private EventExecutorGroup workerGroup; + private HttpSessionManager httpSessionManager; - public HttpTermServer(String hostIp, int port, long connectionTimeout, EventExecutorGroup workerGroup) { + public HttpTermServer(String hostIp, int port, long connectionTimeout, EventExecutorGroup workerGroup, HttpSessionManager httpSessionManager) { this.hostIp = hostIp; this.port = port; this.connectionTimeout = connectionTimeout; this.workerGroup = workerGroup; + this.httpSessionManager = httpSessionManager; } @Override @@ -43,7 +47,7 @@ public class HttpTermServer extends TermServer { @Override public TermServer listen(Handler> listenHandler) { // TODO: charset and inputrc from options - bootstrap = new NettyWebsocketTtyBootstrap(workerGroup).setHost(hostIp).setPort(port); + bootstrap = new NettyWebsocketTtyBootstrap(workerGroup, httpSessionManager).setHost(hostIp).setPort(port); try { bootstrap.start(new Consumer() { @Override diff --git a/core/src/main/java/com/taobao/arthas/core/shell/term/impl/TermImpl.java b/core/src/main/java/com/taobao/arthas/core/shell/term/impl/TermImpl.java index 34731be58..1a47e8c6f 100644 --- a/core/src/main/java/com/taobao/arthas/core/shell/term/impl/TermImpl.java +++ b/core/src/main/java/com/taobao/arthas/core/shell/term/impl/TermImpl.java @@ -221,6 +221,10 @@ public class TermImpl implements Term { } } + public TtyConnection getConn() { + return conn; + } + public void echo(int... codePoints) { Consumer out = conn.stdoutHandler(); for (int codePoint : codePoints) { diff --git a/core/src/main/java/com/taobao/arthas/core/shell/term/impl/http/BasicHttpAuthenticatorHandler.java b/core/src/main/java/com/taobao/arthas/core/shell/term/impl/http/BasicHttpAuthenticatorHandler.java new file mode 100644 index 000000000..b9a6e8b37 --- /dev/null +++ b/core/src/main/java/com/taobao/arthas/core/shell/term/impl/http/BasicHttpAuthenticatorHandler.java @@ -0,0 +1,138 @@ +package com.taobao.arthas.core.shell.term.impl.http; + +import java.nio.charset.Charset; + +import javax.security.auth.Subject; + +import com.alibaba.arthas.deps.org.slf4j.Logger; +import com.alibaba.arthas.deps.org.slf4j.LoggerFactory; +import com.taobao.arthas.common.ArthasConstants; +import com.taobao.arthas.core.security.BasicPrincipal; +import com.taobao.arthas.core.security.SecurityAuthenticator; +import com.taobao.arthas.core.server.ArthasBootstrap; +import com.taobao.arthas.core.shell.term.impl.http.session.HttpSession; +import com.taobao.arthas.core.shell.term.impl.http.session.HttpSessionManager; +import com.taobao.arthas.core.util.StringUtils; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import io.netty.channel.ChannelDuplexHandler; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelPromise; +import io.netty.handler.codec.base64.Base64; +import io.netty.handler.codec.http.DefaultHttpResponse; +import io.netty.handler.codec.http.HttpHeaderNames; +import io.netty.handler.codec.http.HttpRequest; +import io.netty.handler.codec.http.HttpResponse; +import io.netty.handler.codec.http.HttpResponseStatus; +import io.netty.handler.codec.http.HttpVersion; +import io.netty.util.Attribute; + +/** + * + * @author hengyunabc 2021-03-03 + * + */ +public final class BasicHttpAuthenticatorHandler extends ChannelDuplexHandler { + private static final Logger logger = LoggerFactory.getLogger(BasicHttpAuthenticatorHandler.class); + + private HttpSessionManager httpSessionManager; + + private SecurityAuthenticator securityAuthenticator = ArthasBootstrap.getInstance().getSecurityAuthenticator(); + + public BasicHttpAuthenticatorHandler(HttpSessionManager httpSessionManager) { + this.httpSessionManager = httpSessionManager; + } + + @Override + public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { + if (!securityAuthenticator.needLogin()) { + ctx.fireChannelRead(msg); + return; + } + + boolean authed = false; + if (msg instanceof HttpRequest) { + HttpRequest httpRequest = (HttpRequest) msg; + + // 判断session里是否有已登陆信息 + HttpSession session = httpSessionManager.getOrCreateHttpSession(ctx, httpRequest); + if (session != null && session.getAttribute(ArthasConstants.SUBJECT_KEY) != null) { + authed = true; + } + + // 判断请求header里是否带有 username/password + if (!authed) { + BasicPrincipal principal = extractBasicAuthSubject(httpRequest); + Subject subject = securityAuthenticator.login(principal); + if (subject != null) { + authed = true; + session.setAttribute(ArthasConstants.SUBJECT_KEY, subject); + } + } + + if (!authed) { + // restricted resource, so send back 401 to require valid username/password + HttpResponse response = new DefaultHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.UNAUTHORIZED); + response.headers().set(HttpHeaderNames.WWW_AUTHENTICATE, "Basic realm=\"arthas webconsole\""); + response.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/plain"); + response.headers().set(HttpHeaderNames.CONTENT_LENGTH, 0); + + ctx.writeAndFlush(response); + // close the channel + ctx.channel().close(); + return; + } + + } + + ctx.fireChannelRead(msg); + } + + @Override + public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception { + if (msg instanceof HttpResponse) { + // write cookie + HttpResponse response = (HttpResponse) msg; + Attribute attribute = ctx.channel().attr(HttpSessionManager.SESSION_KEY); + HttpSession session = attribute.get(); + if (session != null) { + HttpSessionManager.setSessionCookie(response, session); + } + } + super.write(ctx, msg, promise); + } + + /** + * Extracts the username and password details from the HTTP basic header + * Authorization. + *

+ * This requires that the Authorization HTTP header is provided, and + * its using Basic. Currently Digest is not supported. + * + * @return {@link HttpPrincipal} with username and password details, or + * null if not possible to extract + */ + protected static BasicPrincipal extractBasicAuthSubject(HttpRequest request) { + String auth = request.headers().get(HttpHeaderNames.AUTHORIZATION); + if (auth != null) { + String constraint = StringUtils.before(auth, " "); + if (constraint != null) { + if ("Basic".equalsIgnoreCase(constraint.trim())) { + String decoded = StringUtils.after(auth, " "); + // the decoded part is base64 encoded, so we need to decode that + ByteBuf buf = Unpooled.wrappedBuffer(decoded.getBytes()); + ByteBuf out = Base64.decode(buf); + String userAndPw = out.toString(Charset.defaultCharset()); + String username = StringUtils.before(userAndPw, ":"); + String password = StringUtils.after(userAndPw, ":"); + BasicPrincipal principal = new BasicPrincipal(username, password); + logger.debug("Extracted Basic Auth principal from HTTP header: {}", principal); + return principal; + } + } + } + return null; + } + +} diff --git a/core/src/main/java/com/taobao/arthas/core/shell/term/impl/http/ExtHttpTtyConnection.java b/core/src/main/java/com/taobao/arthas/core/shell/term/impl/http/ExtHttpTtyConnection.java new file mode 100644 index 000000000..06f957fa3 --- /dev/null +++ b/core/src/main/java/com/taobao/arthas/core/shell/term/impl/http/ExtHttpTtyConnection.java @@ -0,0 +1,76 @@ +package com.taobao.arthas.core.shell.term.impl.http; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.TimeUnit; + +import com.taobao.arthas.common.ArthasConstants; +import com.taobao.arthas.core.shell.term.impl.http.session.HttpSession; +import com.taobao.arthas.core.shell.term.impl.http.session.HttpSessionManager; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.http.websocketx.TextWebSocketFrame; +import io.termd.core.http.HttpTtyConnection; + +/** + * 从http请求传递过来的 session 信息。解析websocket创建的 term 还需要登陆验证问题 + * + * @author hengyunabc 2021-03-04 + * + */ +public class ExtHttpTtyConnection extends HttpTtyConnection { + private ChannelHandlerContext context; + + public ExtHttpTtyConnection(ChannelHandlerContext context) { + this.context = context; + } + + @Override + protected void write(byte[] buffer) { + ByteBuf byteBuf = Unpooled.buffer(); + byteBuf.writeBytes(buffer); + if (context != null) { + context.writeAndFlush(new TextWebSocketFrame(byteBuf)); + } + } + + @Override + public void schedule(Runnable task, long delay, TimeUnit unit) { + if (context != null) { + context.executor().schedule(task, delay, unit); + } + } + + @Override + public void execute(Runnable task) { + if (context != null) { + context.executor().execute(task); + } + } + + @Override + public void close() { + if (context != null) { + context.close(); + } + } + + public Map extSessions() { + if (context != null) { + HttpSession httpSession = HttpSessionManager.getHttpSessionFromContext(context); + if (httpSession != null) { + Object subject = httpSession.getAttribute(ArthasConstants.SUBJECT_KEY); + if (subject != null) { + Map result = new HashMap(); + result.put(ArthasConstants.SUBJECT_KEY, subject); + return result; + } + } + } + return Collections.emptyMap(); + } + +} diff --git a/core/src/main/java/com/taobao/arthas/core/shell/term/impl/http/HttpRequestHandler.java b/core/src/main/java/com/taobao/arthas/core/shell/term/impl/http/HttpRequestHandler.java index ba3777855..c2589e90b 100644 --- a/core/src/main/java/com/taobao/arthas/core/shell/term/impl/http/HttpRequestHandler.java +++ b/core/src/main/java/com/taobao/arthas/core/shell/term/impl/http/HttpRequestHandler.java @@ -77,7 +77,7 @@ public class HttpRequestHandler extends SimpleChannelInboundHandler 0) { ServerBootstrap b = new ServerBootstrap(); b.group(group).channel(NioServerSocketChannel.class).handler(new LoggingHandler(LogLevel.INFO)) - .childHandler(new TtyServerInitializer(channelGroup, handler, workerGroup)); + .childHandler(new TtyServerInitializer(channelGroup, handler, workerGroup, httpSessionManager)); final ChannelFuture f = b.bind(host, port); f.addListener(new GenericFutureListener>() { diff --git a/core/src/main/java/com/taobao/arthas/core/shell/term/impl/http/TtyServerInitializer.java b/core/src/main/java/com/taobao/arthas/core/shell/term/impl/http/TtyServerInitializer.java index ff4cba921..d6582de68 100644 --- a/core/src/main/java/com/taobao/arthas/core/shell/term/impl/http/TtyServerInitializer.java +++ b/core/src/main/java/com/taobao/arthas/core/shell/term/impl/http/TtyServerInitializer.java @@ -1,5 +1,8 @@ package com.taobao.arthas.core.shell.term.impl.http; +import com.taobao.arthas.common.ArthasConstants; +import com.taobao.arthas.core.shell.term.impl.http.session.HttpSessionManager; + import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelPipeline; import io.netty.channel.group.ChannelGroup; @@ -13,10 +16,6 @@ import io.netty.util.concurrent.EventExecutorGroup; import io.termd.core.function.Consumer; import io.termd.core.tty.TtyConnection; -import java.io.File; - -import com.taobao.arthas.common.ArthasConstants; - /** * @author Julien Viet @@ -26,11 +25,13 @@ public class TtyServerInitializer extends ChannelInitializer { private final ChannelGroup group; private final Consumer handler; private EventExecutorGroup workerGroup; + private HttpSessionManager httpSessionManager; - public TtyServerInitializer(ChannelGroup group, Consumer handler, EventExecutorGroup workerGroup) { + public TtyServerInitializer(ChannelGroup group, Consumer handler, EventExecutorGroup workerGroup, HttpSessionManager httpSessionManager) { this.group = group; this.handler = handler; - this.workerGroup = workerGroup; + this.workerGroup = workerGroup; + this.httpSessionManager = httpSessionManager; } @Override @@ -40,6 +41,7 @@ public class TtyServerInitializer extends ChannelInitializer { pipeline.addLast(new HttpServerCodec()); pipeline.addLast(new ChunkedWriteHandler()); pipeline.addLast(new HttpObjectAggregator(ArthasConstants.MAX_HTTP_CONTENT_LENGTH)); + pipeline.addLast(new BasicHttpAuthenticatorHandler(httpSessionManager)); pipeline.addLast(workerGroup, "HttpRequestHandler", new HttpRequestHandler("/ws")); pipeline.addLast(new WebSocketServerProtocolHandler("/ws")); pipeline.addLast(new IdleStateHandler(0, 0, ArthasConstants.WEBSOCKET_IDLE_SECONDS)); diff --git a/core/src/main/java/com/taobao/arthas/core/shell/term/impl/http/TtyWebSocketFrameHandler.java b/core/src/main/java/com/taobao/arthas/core/shell/term/impl/http/TtyWebSocketFrameHandler.java index e3702ea31..527166592 100644 --- a/core/src/main/java/com/taobao/arthas/core/shell/term/impl/http/TtyWebSocketFrameHandler.java +++ b/core/src/main/java/com/taobao/arthas/core/shell/term/impl/http/TtyWebSocketFrameHandler.java @@ -16,8 +16,6 @@ package com.taobao.arthas.core.shell.term.impl.http; -import io.netty.buffer.ByteBuf; -import io.netty.buffer.Unpooled; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.SimpleChannelInboundHandler; import io.netty.channel.group.ChannelGroup; @@ -29,7 +27,6 @@ import io.termd.core.function.Consumer; import io.termd.core.http.HttpTtyConnection; import io.termd.core.tty.TtyConnection; -import java.util.concurrent.TimeUnit; /** * @author Julien Viet @@ -57,37 +54,7 @@ public class TtyWebSocketFrameHandler extends SimpleChannelInboundHandlerlong specifying when this session was created, + * expressed in milliseconds since 1/1/1970 GMT + * @exception IllegalStateException if this method is called on an invalidated + * session + */ + public long getCreationTime(); + + /** + * Returns a string containing the unique identifier assigned to this session. + * The identifier is assigned by the servlet container and is implementation + * dependent. + * + * @return a string specifying the identifier assigned to this session + * @exception IllegalStateException if this method is called on an invalidated + * session + */ + public String getId(); + + /** + * Returns the last time the client sent a request associated with this session, + * as the number of milliseconds since midnight January 1, 1970 GMT, and marked + * by the time the container received the request. + *

+ * Actions that your application takes, such as getting or setting a value + * associated with the session, do not affect the access time. + * + * @return a long representing the last time the client sent a + * request associated with this session, expressed in milliseconds since + * 1/1/1970 GMT + * @exception IllegalStateException if this method is called on an invalidated + * session + */ + public long getLastAccessedTime(); + + /** + * Specifies the time, in seconds, between client requests before the servlet + * container will invalidate this session. A zero or negative time indicates + * that the session should never timeout. + * + * @param interval An integer specifying the number of seconds + */ + public void setMaxInactiveInterval(int interval); + + /** + * Returns the maximum time interval, in seconds, that the servlet container + * will keep this session open between client accesses. After this interval, the + * servlet container will invalidate the session. The maximum time interval can + * be set with the setMaxInactiveInterval method. A zero or + * negative time indicates that the session should never timeout. + * + * @return an integer specifying the number of seconds this session remains open + * between client requests + * @see #setMaxInactiveInterval + */ + public int getMaxInactiveInterval(); + + /** + * Returns the object bound with the specified name in this session, or + * null if no object is bound under the name. + * + * @param name a string specifying the name of the object + * @return the object with the specified name + * @exception IllegalStateException if this method is called on an invalidated + * session + */ + public Object getAttribute(String name); + + /** + * Returns an Enumeration of String objects containing + * the names of all the objects bound to this session. + * + * @return an Enumeration of String objects specifying + * the names of all the objects bound to this session + * @exception IllegalStateException if this method is called on an invalidated + * session + */ + public Enumeration getAttributeNames(); + + /** + * Binds an object to this session, using the name specified. If an object of + * the same name is already bound to the session, the object is replaced. + *

+ * After this method executes, and if the new object implements + * HttpSessionBindingListener, the container calls + * HttpSessionBindingListener.valueBound. The container then + * notifies any HttpSessionAttributeListeners in the web + * application. + *

+ * If an object was already bound to this session of this name that implements + * HttpSessionBindingListener, its + * HttpSessionBindingListener.valueUnbound method is called. + *

+ * If the value passed in is null, this has the same effect as calling + * removeAttribute(). + * + * @param name the name to which the object is bound; cannot be null + * @param value the object to be bound + * @exception IllegalStateException if this method is called on an invalidated + * session + */ + public void setAttribute(String name, Object value); + + /** + * Removes the object bound with the specified name from this session. If the + * session does not have an object bound with the specified name, this method + * does nothing. + *

+ * After this method executes, and if the object implements + * HttpSessionBindingListener, the container calls + * HttpSessionBindingListener.valueUnbound. The container then + * notifies any HttpSessionAttributeListeners in the web + * application. + * + * @param name the name of the object to remove from this session + * @exception IllegalStateException if this method is called on an invalidated + * session + */ + public void removeAttribute(String name); + + /** + * Invalidates this session then unbinds any objects bound to it. + * + * @exception IllegalStateException if this method is called on an already + * invalidated session + */ + public void invalidate(); + + /** + * Returns true if the client does not yet know about the session + * or if the client chooses not to join the session. For example, if the server + * used only cookie-based sessions, and the client had disabled the use of + * cookies, then a session would be new on each request. + * + * @return true if the server has created a session, but the client + * has not yet joined + * @exception IllegalStateException if this method is called on an already + * invalidated session + */ + public boolean isNew(); +} diff --git a/core/src/main/java/com/taobao/arthas/core/shell/term/impl/http/session/HttpSessionManager.java b/core/src/main/java/com/taobao/arthas/core/shell/term/impl/http/session/HttpSessionManager.java new file mode 100644 index 000000000..607fbfb05 --- /dev/null +++ b/core/src/main/java/com/taobao/arthas/core/shell/term/impl/http/session/HttpSessionManager.java @@ -0,0 +1,95 @@ +package com.taobao.arthas.core.shell.term.impl.http.session; + +import java.util.Collections; +import java.util.Set; + +import com.taobao.arthas.common.ArthasConstants; + +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.http.HttpHeaderNames; +import io.netty.handler.codec.http.HttpRequest; +import io.netty.handler.codec.http.HttpResponse; +import io.netty.handler.codec.http.cookie.Cookie; +import io.netty.handler.codec.http.cookie.ServerCookieDecoder; +import io.netty.handler.codec.http.cookie.ServerCookieEncoder; +import io.netty.util.Attribute; +import io.netty.util.AttributeKey; + +/** + *

+ * netty里的http session管理。因为同一域名的不同端口共享cookie,所以需要共用。
+ * 
+ * + * @author hengyunabc 2021-03-03 + * + */ +public class HttpSessionManager { + public static AttributeKey SESSION_KEY = AttributeKey.valueOf("session"); + + private LRUCache sessions = new LRUCache(1024); + + public HttpSessionManager() { + + } + + private HttpSession getSession(HttpRequest httpRequest) { + // TODO 增加从 url中获取 session id 功能? + + Set cookies; + String value = httpRequest.headers().get(HttpHeaderNames.COOKIE); + if (value == null) { + cookies = Collections.emptySet(); + } else { + cookies = ServerCookieDecoder.STRICT.decode(value); + } + for (Cookie cookie : cookies) { + if (ArthasConstants.ASESSION_KEY.equals(cookie.name())) { + String sessionId = cookie.value(); + return sessions.get(sessionId); + } + } + return null; + } + + public static HttpSession getHttpSessionFromContext(ChannelHandlerContext ctx) { + return ctx.channel().attr(SESSION_KEY).get(); + } + + public HttpSession getOrCreateHttpSession(ChannelHandlerContext ctx, HttpRequest httpRequest) { + // 尝试用 ctx 和从 cookie里读取出 session + Attribute attribute = ctx.channel().attr(SESSION_KEY); + HttpSession httpSession = attribute.get(); + if (httpSession != null) { + return httpSession; + } + httpSession = getSession(httpRequest); + if (httpSession != null) { + attribute.set(httpSession); + return httpSession; + } + // 创建session,并设置到ctx里 + httpSession = newHttpSession(); + attribute.set(httpSession); + return httpSession; + } + + private HttpSession newHttpSession() { + SimpleHttpSession session = new SimpleHttpSession(); + this.sessions.put(session.getId(), session); + return session; + } + + public static void setSessionCookie(HttpResponse response, HttpSession session) { + response.headers().add(HttpHeaderNames.SET_COOKIE, + ServerCookieEncoder.STRICT.encode(ArthasConstants.ASESSION_KEY, session.getId())); + } + + public void start() { + + } + + public void stop() { + sessions.clear(); + } + +} diff --git a/core/src/main/java/com/taobao/arthas/core/shell/term/impl/http/session/LRUCache.java b/core/src/main/java/com/taobao/arthas/core/shell/term/impl/http/session/LRUCache.java new file mode 100644 index 000000000..f5b659925 --- /dev/null +++ b/core/src/main/java/com/taobao/arthas/core/shell/term/impl/http/session/LRUCache.java @@ -0,0 +1,100 @@ +package com.taobao.arthas.core.shell.term.impl.http.session; + +import java.util.LinkedHashMap; +import java.util.Collection; +import java.util.Map; +import java.util.ArrayList; + +/** + * An LRU cache, based on LinkedHashMap. + * + *

+ * This cache has a fixed maximum number of elements (cacheSize). + * If the cache is full and another entry is added, the LRU (least recently + * used) entry is dropped. + * + *

+ * This class is thread-safe. All methods of this class are synchronized. + * + *

+ * Author: Christian d'Heureuse, Inventec Informatik AG, Zurich, Switzerland
+ * Multi-licensed: EPL / LGPL / GPL / AL / BSD. + */ +public class LRUCache { + + private static final float hashTableLoadFactor = 0.75f; + + private LinkedHashMap map; + private int cacheSize; + + /** + * Creates a new LRU cache. + * + * @param cacheSize the maximum number of entries that will be kept in this + * cache. + */ + public LRUCache(int cacheSize) { + this.cacheSize = cacheSize; + int hashTableCapacity = (int) Math.ceil(cacheSize / hashTableLoadFactor) + 1; + map = new LinkedHashMap(hashTableCapacity, hashTableLoadFactor, true) { + // (an anonymous inner class) + private static final long serialVersionUID = 1; + + @Override + protected boolean removeEldestEntry(Map.Entry eldest) { + return size() > LRUCache.this.cacheSize; + } + }; + } + + /** + * Retrieves an entry from the cache.
+ * The retrieved entry becomes the MRU (most recently used) entry. + * + * @param key the key whose associated value is to be returned. + * @return the value associated to this key, or null if no value with this key + * exists in the cache. + */ + public synchronized V get(K key) { + return map.get(key); + } + + /** + * Adds an entry to this cache. The new entry becomes the MRU (most recently + * used) entry. If an entry with the specified key already exists in the cache, + * it is replaced by the new entry. If the cache is full, the LRU (least + * recently used) entry is removed from the cache. + * + * @param key the key with which the specified value is to be associated. + * @param value a value to be associated with the specified key. + */ + public synchronized void put(K key, V value) { + map.put(key, value); + } + + /** + * Clears the cache. + */ + public synchronized void clear() { + map.clear(); + } + + /** + * Returns the number of used entries in the cache. + * + * @return the number of entries currently in the cache. + */ + public synchronized int usedEntries() { + return map.size(); + } + + /** + * Returns a Collection that contains a copy of all cache entries. + * + * @return a Collection with a copy of the cache content. + */ + public synchronized Collection> getAll() { + return new ArrayList>(map.entrySet()); + } + +} // end class LRUCache diff --git a/core/src/main/java/com/taobao/arthas/core/shell/term/impl/http/session/SimpleHttpSession.java b/core/src/main/java/com/taobao/arthas/core/shell/term/impl/http/session/SimpleHttpSession.java new file mode 100644 index 000000000..230f82dcc --- /dev/null +++ b/core/src/main/java/com/taobao/arthas/core/shell/term/impl/http/session/SimpleHttpSession.java @@ -0,0 +1,79 @@ +package com.taobao.arthas.core.shell.term.impl.http.session; + +import java.util.Collections; +import java.util.Enumeration; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import com.taobao.arthas.core.util.StringUtils; + +/** + * + * @author hengyunabc 2021-03-03 + * + */ +public class SimpleHttpSession implements HttpSession { + private Map attributes = new ConcurrentHashMap(); + + private String id; + + public SimpleHttpSession() { + id = StringUtils.randomString(32); + } + + @Override + public long getCreationTime() { + return 0; + } + + @Override + public String getId() { + return id; + } + + @Override + public long getLastAccessedTime() { + return 0; + } + + @Override + public void setMaxInactiveInterval(int interval) { + + } + + @Override + public int getMaxInactiveInterval() { + return 0; + } + + @Override + public Object getAttribute(String name) { + return attributes.get(name); + } + + @Override + public Enumeration getAttributeNames() { + return Collections.enumeration(this.attributes.keySet()); + } + + @Override + public void setAttribute(String name, Object value) { + attributes.put(name, value); + } + + @Override + public void removeAttribute(String name) { + attributes.remove(name); + } + + @Override + public void invalidate() { + + } + + @Override + public boolean isNew() { + return false; + } + +} diff --git a/core/src/main/java/com/taobao/arthas/core/shell/term/impl/httptelnet/HttpTelnetTermServer.java b/core/src/main/java/com/taobao/arthas/core/shell/term/impl/httptelnet/HttpTelnetTermServer.java index f7de93789..97f52e434 100644 --- a/core/src/main/java/com/taobao/arthas/core/shell/term/impl/httptelnet/HttpTelnetTermServer.java +++ b/core/src/main/java/com/taobao/arthas/core/shell/term/impl/httptelnet/HttpTelnetTermServer.java @@ -10,6 +10,7 @@ import com.taobao.arthas.core.shell.term.Term; import com.taobao.arthas.core.shell.term.TermServer; import com.taobao.arthas.core.shell.term.impl.Helper; import com.taobao.arthas.core.shell.term.impl.TermImpl; +import com.taobao.arthas.core.shell.term.impl.http.session.HttpSessionManager; import io.netty.util.concurrent.EventExecutorGroup; import io.termd.core.function.Consumer; @@ -31,12 +32,14 @@ public class HttpTelnetTermServer extends TermServer { private int port; private long connectionTimeout; private EventExecutorGroup workerGroup; + private HttpSessionManager httpSessionManager; - public HttpTelnetTermServer(String hostIp, int port, long connectionTimeout, EventExecutorGroup workerGroup) { + public HttpTelnetTermServer(String hostIp, int port, long connectionTimeout, EventExecutorGroup workerGroup, HttpSessionManager httpSessionManager) { this.hostIp = hostIp; this.port = port; this.connectionTimeout = connectionTimeout; this.workerGroup = workerGroup; + this.httpSessionManager = httpSessionManager; } @Override @@ -48,7 +51,7 @@ public class HttpTelnetTermServer extends TermServer { @Override public TermServer listen(Handler> listenHandler) { // TODO: charset and inputrc from options - bootstrap = new NettyHttpTelnetTtyBootstrap(workerGroup).setHost(hostIp).setPort(port); + bootstrap = new NettyHttpTelnetTtyBootstrap(workerGroup, httpSessionManager).setHost(hostIp).setPort(port); try { bootstrap.start(new Consumer() { @Override diff --git a/core/src/main/java/com/taobao/arthas/core/shell/term/impl/httptelnet/NettyHttpTelnetBootstrap.java b/core/src/main/java/com/taobao/arthas/core/shell/term/impl/httptelnet/NettyHttpTelnetBootstrap.java index 07644d9ee..95aa1b47b 100644 --- a/core/src/main/java/com/taobao/arthas/core/shell/term/impl/httptelnet/NettyHttpTelnetBootstrap.java +++ b/core/src/main/java/com/taobao/arthas/core/shell/term/impl/httptelnet/NettyHttpTelnetBootstrap.java @@ -1,5 +1,7 @@ package com.taobao.arthas.core.shell.term.impl.httptelnet; +import com.taobao.arthas.core.shell.term.impl.http.session.HttpSessionManager; + import io.netty.bootstrap.ServerBootstrap; import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelOption; @@ -31,11 +33,13 @@ public class NettyHttpTelnetBootstrap extends TelnetBootstrap { private EventLoopGroup group; private ChannelGroup channelGroup; private EventExecutorGroup workerGroup; + private HttpSessionManager httpSessionManager; - public NettyHttpTelnetBootstrap(EventExecutorGroup workerGroup) { + public NettyHttpTelnetBootstrap(EventExecutorGroup workerGroup, HttpSessionManager httpSessionManager) { this.workerGroup = workerGroup; this.group = new NioEventLoopGroup(new DefaultThreadFactory("arthas-NettyHttpTelnetBootstrap", true)); this.channelGroup = new DefaultChannelGroup(ImmediateEventExecutor.INSTANCE); + this.httpSessionManager = httpSessionManager; } public NettyHttpTelnetBootstrap setHost(String host) { @@ -60,7 +64,7 @@ public class NettyHttpTelnetBootstrap extends TelnetBootstrap { .childHandler(new ChannelInitializer() { @Override public void initChannel(SocketChannel ch) throws Exception { - ch.pipeline().addLast(new ProtocolDetectHandler(channelGroup, handlerFactory, factory, workerGroup)); + ch.pipeline().addLast(new ProtocolDetectHandler(channelGroup, handlerFactory, factory, workerGroup, httpSessionManager)); } }); diff --git a/core/src/main/java/com/taobao/arthas/core/shell/term/impl/httptelnet/NettyHttpTelnetTtyBootstrap.java b/core/src/main/java/com/taobao/arthas/core/shell/term/impl/httptelnet/NettyHttpTelnetTtyBootstrap.java index 05a7ec08a..f5b1b7bfd 100644 --- a/core/src/main/java/com/taobao/arthas/core/shell/term/impl/httptelnet/NettyHttpTelnetTtyBootstrap.java +++ b/core/src/main/java/com/taobao/arthas/core/shell/term/impl/httptelnet/NettyHttpTelnetTtyBootstrap.java @@ -2,6 +2,8 @@ package com.taobao.arthas.core.shell.term.impl.httptelnet; import java.nio.charset.Charset; +import com.taobao.arthas.core.shell.term.impl.http.session.HttpSessionManager; + import io.netty.util.concurrent.EventExecutorGroup; import io.termd.core.function.Consumer; import io.termd.core.function.Supplier; @@ -22,8 +24,8 @@ public class NettyHttpTelnetTtyBootstrap { private boolean inBinary; private Charset charset = Charset.forName("UTF-8"); - public NettyHttpTelnetTtyBootstrap(EventExecutorGroup workerGroup) { - this.httpTelnetTtyBootstrap = new NettyHttpTelnetBootstrap(workerGroup); + public NettyHttpTelnetTtyBootstrap(EventExecutorGroup workerGroup, HttpSessionManager httpSessionManager) { + this.httpTelnetTtyBootstrap = new NettyHttpTelnetBootstrap(workerGroup, httpSessionManager); } public String getHost() { diff --git a/core/src/main/java/com/taobao/arthas/core/shell/term/impl/httptelnet/ProtocolDetectHandler.java b/core/src/main/java/com/taobao/arthas/core/shell/term/impl/httptelnet/ProtocolDetectHandler.java index 77cb80672..2b217320b 100644 --- a/core/src/main/java/com/taobao/arthas/core/shell/term/impl/httptelnet/ProtocolDetectHandler.java +++ b/core/src/main/java/com/taobao/arthas/core/shell/term/impl/httptelnet/ProtocolDetectHandler.java @@ -1,12 +1,14 @@ package com.taobao.arthas.core.shell.term.impl.httptelnet; -import java.io.File; import java.util.concurrent.TimeUnit; import com.taobao.arthas.common.ArthasConstants; +import com.taobao.arthas.core.shell.term.impl.http.BasicHttpAuthenticatorHandler; import com.taobao.arthas.core.shell.term.impl.http.HttpRequestHandler; import com.taobao.arthas.core.shell.term.impl.http.TtyWebSocketFrameHandler; +import com.taobao.arthas.core.shell.term.impl.http.session.HttpSessionManager; + import io.netty.buffer.ByteBuf; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelInboundHandlerAdapter; @@ -35,13 +37,16 @@ public class ProtocolDetectHandler extends ChannelInboundHandlerAdapter { private Supplier handlerFactory; private Consumer ttyConnectionFactory; private EventExecutorGroup workerGroup; + private HttpSessionManager httpSessionManager; public ProtocolDetectHandler(ChannelGroup channelGroup, final Supplier handlerFactory, - Consumer ttyConnectionFactory, EventExecutorGroup workerGroup) { + Consumer ttyConnectionFactory, EventExecutorGroup workerGroup, + HttpSessionManager httpSessionManager) { this.channelGroup = channelGroup; this.handlerFactory = handlerFactory; this.ttyConnectionFactory = ttyConnectionFactory; this.workerGroup = workerGroup; + this.httpSessionManager = httpSessionManager; } private ScheduledFuture detectTelnetFuture; @@ -87,6 +92,7 @@ public class ProtocolDetectHandler extends ChannelInboundHandlerAdapter { pipeline.addLast(new HttpServerCodec()); pipeline.addLast(new ChunkedWriteHandler()); pipeline.addLast(new HttpObjectAggregator(ArthasConstants.MAX_HTTP_CONTENT_LENGTH)); + pipeline.addLast(new BasicHttpAuthenticatorHandler(httpSessionManager)); pipeline.addLast(workerGroup, "HttpRequestHandler", new HttpRequestHandler("/ws")); pipeline.addLast(new WebSocketServerProtocolHandler("/ws")); pipeline.addLast(new IdleStateHandler(0, 0, ArthasConstants.WEBSOCKET_IDLE_SECONDS)); diff --git a/core/src/main/java/com/taobao/arthas/core/util/StringUtils.java b/core/src/main/java/com/taobao/arthas/core/util/StringUtils.java index 165b5ed64..225a88c0f 100644 --- a/core/src/main/java/com/taobao/arthas/core/util/StringUtils.java +++ b/core/src/main/java/com/taobao/arthas/core/util/StringUtils.java @@ -13,6 +13,7 @@ import java.util.StringTokenizer; import java.util.TreeSet; public abstract class StringUtils { + private static final String AB = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; /** * 获取异常的原因描述 @@ -912,4 +913,40 @@ public abstract class StringUtils { } return result; } + + public static String randomString(int length) { + StringBuilder sb = new StringBuilder(length); + for (int i = 0; i < length; i++) + sb.append(AB.charAt(ThreadLocalRandom.current().nextInt(AB.length()))); + return sb.toString(); + } + + /** + * Returns the string before the given token + * + * @param text the text + * @param before the token + * @return the text before the token, or null if text does not contain + * the token + */ + public static String before(String text, String before) { + int pos = text.indexOf(before); + return pos == -1 ? null : text.substring(0, pos); + } + + /** + * Returns the string after the given token + * + * @param text the text + * @param after the token + * @return the text after the token, or null if text does not contain + * the token + */ + public static String after(String text, String after) { + int pos = text.indexOf(after); + if (pos == -1) { + return null; + } + return text.substring(pos + after.length()); + } } diff --git a/core/src/main/resources/com/taobao/arthas/core/http/web-console.js b/core/src/main/resources/com/taobao/arthas/core/http/web-console.js index 7f9486643..a63efdf42 100644 --- a/core/src/main/resources/com/taobao/arthas/core/http/web-console.js +++ b/core/src/main/resources/com/taobao/arthas/core/http/web-console.js @@ -8,6 +8,8 @@ $(function () { if (ip != '' && ip != null) { $('#ip').val(ip); + } else { + $('#ip').val(window.location.hostname); } if (port != '' && port != null) { $('#port').val(port); diff --git a/core/src/test/java/com/taobao/arthas/core/security/SecurityAuthenticatorImplTest.java b/core/src/test/java/com/taobao/arthas/core/security/SecurityAuthenticatorImplTest.java new file mode 100644 index 000000000..07bd91fe0 --- /dev/null +++ b/core/src/test/java/com/taobao/arthas/core/security/SecurityAuthenticatorImplTest.java @@ -0,0 +1,40 @@ +package com.taobao.arthas.core.security; + +import java.security.Principal; + +import javax.security.auth.Subject; +import javax.security.auth.login.LoginException; + +import org.assertj.core.api.Assertions; +import org.junit.Test; + +/** + * + * @author hengyunabc 2021-03-04 + * + */ +public class SecurityAuthenticatorImplTest { + + @Test + public void test1() throws LoginException { + String username = "test"; + String password = "ppp"; + SecurityAuthenticatorImpl auth = new SecurityAuthenticatorImpl(username, password); + + Assertions.assertThat(auth.needLogin()).isTrue(); + + Principal principal = new BasicPrincipal(username, password); + Subject subject = auth.login(principal); + + Assertions.assertThat(subject).isNotNull(); + } + + @Test + public void test2() { + String username = "test"; + String password = null; + SecurityAuthenticatorImpl auth = new SecurityAuthenticatorImpl(username, password); + Assertions.assertThat(auth.needLogin()).isTrue(); + } + +} diff --git a/pom.xml b/pom.xml index 7cde30360..6c4becfc0 100644 --- a/pom.xml +++ b/pom.xml @@ -73,131 +73,8 @@ packaging - - - jdk8 - - [1.8,) - - - tunnel-server - - - - - - pl.project13.maven - git-commit-id-plugin - 4.0.1 - - - - revision - - - - - false - yyyy-MM-dd'T'HH:mm:ssZ - true - ${project.build.outputDirectory}/arthas-git.properties - - git.branch - git.build.host - git.build.time - git.build.user.email - git.build.user.name - git.remote.origin.url - git.total.commit.count - git.commit.time - git.local.branch.ahead - git.local.branch.behind - git.tags - - true - - - - - - - - - jdk12 - - [12,) - - JAVA8_HOME - - - - - com.sun - tools - 1.6.0 - system - ${JAVA8_HOME}/lib/tools.jar - - - - - - full - - - - org.apache.maven.plugins - maven-javadoc-plugin - 3.2.0 - - none - 1.8 - false - - - - release - package - - jar - - - - - - - - - - release - - - performRelease - true - - - - - - org.apache.maven.plugins - maven-gpg-plugin - - - sign-artifacts - verify - - sign - - - - - - - - - - 3.4.8 + 3.4.9-SNAPSHOT UTF-8 1.6 1.6 @@ -342,6 +219,129 @@ + + + jdk8 + + [1.8,) + + + tunnel-server + + + + + + pl.project13.maven + git-commit-id-plugin + 4.0.1 + + + + revision + + + + + false + yyyy-MM-dd'T'HH:mm:ssZ + true + ${project.build.outputDirectory}/arthas-git.properties + + git.branch + git.build.host + git.build.time + git.build.user.email + git.build.user.name + git.remote.origin.url + git.total.commit.count + git.commit.time + git.local.branch.ahead + git.local.branch.behind + git.tags + + true + + + + + + + + + jdk12 + + [12,) + + JAVA8_HOME + + + + + com.sun + tools + 1.6.0 + system + ${JAVA8_HOME}/lib/tools.jar + + + + + + full + + + + org.apache.maven.plugins + maven-javadoc-plugin + 3.2.0 + + none + 1.8 + false + + + + release + package + + jar + + + + + + + + + + release + + + performRelease + true + + + + + + org.apache.maven.plugins + maven-gpg-plugin + + + sign-artifacts + verify + + sign + + + + + + + + + diff --git a/site/src/site/sphinx/advanced-use.md b/site/src/site/sphinx/advanced-use.md index 27138c8bc..b5f72bfa6 100644 --- a/site/src/site/sphinx/advanced-use.md +++ b/site/src/site/sphinx/advanced-use.md @@ -64,6 +64,9 @@ * [profiler](profiler.md)--使用[async-profiler](https://github.com/jvm-profiling-tools/async-profiler)对应用采样,生成火焰图 +## 鉴权 + +* [auth](auth.md)--鉴权 ## options * [options](options.md)——查看或设置Arthas全局开关 diff --git a/site/src/site/sphinx/auth.md b/site/src/site/sphinx/auth.md new file mode 100644 index 000000000..b8aeb6206 --- /dev/null +++ b/site/src/site/sphinx/auth.md @@ -0,0 +1,46 @@ +auth +=== + +> 验证当前会话 + +### 配置用户名和密码 + +在attach时,可以在命令行指定密码。比如: + +``` +java -jar arthas-boot.jar --password ppp +``` + +* 可以通过 `--username` 选项来指定用户,默认值是`arthas`。 +* 也可以在 arthas.properties 里中配置 username/password。命令行的优先级大于配置文件。 + + +### 在telnet console里鉴权 + +连接到arthas后,直接执行命令会提示需要鉴权: + +```bash +[arthas@37430]$ help +Error! command not permitted, try to use 'auth' command to authenticates. +``` + +使用`auth`命令来鉴权,成功之后可以执行其它命令。 + +``` +[arthas@37430]$ auth ppp +Authentication result: true +``` + +* 可以通过 `--username` 选项来指定用户,默认值是`arthas`。 + +### web console密码验证 + +打开浏览器,会有弹窗提示需要输入 用户名 和 密码。 + +成功之后,则可以直接连接上 web console。 + +### http api验证 + +Arthas 采用的是 HTTP 标准的 Basic Authorization,客户端请求时增加对应的header即可。 + +* 参考:[https://developer.mozilla.org/en-US/docs/Web/HTTP/Authentication](https://developer.mozilla.org/en-US/docs/Web/HTTP/Authentication) \ No newline at end of file diff --git a/site/src/site/sphinx/commands.md b/site/src/site/sphinx/commands.md index 7077aacfc..9a6e06b59 100644 --- a/site/src/site/sphinx/commands.md +++ b/site/src/site/sphinx/commands.md @@ -39,6 +39,7 @@ * [base64](base64.md) * [tee](tee.md) * [pwd](pwd.md) +* [auth](auth.md) * [options](options.md) ### Arthas 基础命令 diff --git a/site/src/site/sphinx/en/advanced-use.md b/site/src/site/sphinx/en/advanced-use.md index 6464c02c0..608e181c1 100644 --- a/site/src/site/sphinx/en/advanced-use.md +++ b/site/src/site/sphinx/en/advanced-use.md @@ -55,6 +55,10 @@ Advanced Usage * [stack](stack.md) - display the stack trace for the specified class and method * [tt](tt.md) - time tunnel, record the arguments and returned value for the methods and replay +## authentication + +* [auth](auth.md) - authentication + ## options * [options](options.md) - check/set Arthas global options diff --git a/site/src/site/sphinx/en/auth.md b/site/src/site/sphinx/en/auth.md new file mode 100644 index 000000000..b50cdcb72 --- /dev/null +++ b/site/src/site/sphinx/en/auth.md @@ -0,0 +1,45 @@ +auth +=== + +> Authenticates the current session + +### Configure username and password + +When attaching, you can specify a password on the command line. such as: + +``` +java -jar arthas-boot.jar --password ppp +``` + +* The user can be specified by the `--username` option, the default value is `arthas`. +* You can also configure username/password in arthas.properties. The priority of the command line is higher than that of the configuration file. + +### Authenticate in the telnet console + +After connecting to arthas, directly executing the command will prompt for authentication: + +```bash +[arthas@37430]$ help +Error! command not permitted, try to use 'auth' command to authenticates. +``` + +Use the `auth` command to authenticate, and you can execute other commands after success. + +``` +[arthas@37430]$ auth ppp +Authentication result: true +``` + +* The user can be specified by the `--username` option, the default value is `arthas`. + +### Web console Authentication + +Open the browser, there will be a pop-up window prompting you to enter your username and password. + +After success, you can directly connect to the web console. + +### HTTP API Authentication + +Arthas uses the HTTP standard Basic Authorization. + +* Reference: [https://developer.mozilla.org/en-US/docs/Web/HTTP/Authentication](https://developer.mozilla.org/en-US/docs/Web/HTTP/Authentication) \ No newline at end of file diff --git a/site/src/site/sphinx/en/commands.md b/site/src/site/sphinx/en/commands.md index 13a35189c..e997ca785 100644 --- a/site/src/site/sphinx/en/commands.md +++ b/site/src/site/sphinx/en/commands.md @@ -39,6 +39,7 @@ All Commands * [base64](base64.md) * [tee](tee.md) * [pwd](pwd.md) +* [auth](auth.md) * [options](options.md) diff --git a/tunnel-server/src/main/java/com/alibaba/arthas/tunnel/server/TunnelSocketServerInitializer.java b/tunnel-server/src/main/java/com/alibaba/arthas/tunnel/server/TunnelSocketServerInitializer.java index 8ea047cf2..f69626bed 100644 --- a/tunnel-server/src/main/java/com/alibaba/arthas/tunnel/server/TunnelSocketServerInitializer.java +++ b/tunnel-server/src/main/java/com/alibaba/arthas/tunnel/server/TunnelSocketServerInitializer.java @@ -34,7 +34,7 @@ public class TunnelSocketServerInitializer extends ChannelInitializer