aspcms三合一网站源码,城市建设模拟游戏登陆网站,设计公司怎么接业务,wordpress可以商用网络篇12 | SSH2的应用 解决的业务问题协议选定SSH2#xff08;Secure Shell 2#xff0c;目前基本用这个#xff09;SSH1#xff08;Secure Shell 1#xff09;Telnet 代码实现落地方案1#xff1a;ganymed-ssh2maven坐标关键源代码技术效果验证连接高版本OpenSSH报错分… 网络篇12 | SSH2的应用 解决的业务问题协议选定SSH2Secure Shell 2目前基本用这个SSH1Secure Shell 1Telnet 代码实现落地方案1ganymed-ssh2maven坐标关键源代码技术效果验证连接高版本OpenSSH报错分析 落地方案2jschmaven坐标jsch客户端功能不墨迹直接上源码Controller层接口文件内容service 层将文件落地连接管道发送sh命令执行curl回调JSchUtils管道连接的工具类是灵魂就免费赠送大家了画重点 进阶应用方案1rsync调试实现文件的传输1. 使用此组件前简单验证安装sshpass工具并尝试两台机器之间的文件交互。2. 上传文件结合expect实现免密效果3. 下载文件结合expect实现免密效果 方案2sftp调试实现文件的传输方案3scp模式 解决的业务问题
在禁使用STP子模式的情况下研发一套“平台”级的程序实现与10万多台的Linux机器交互包含但不限于通道连接、shell命令执行、文件传输等。
具体的业务需求为获取Linux主机上的shadow文件、以及上传shell的基线检测脚本并将检查结果下载到系统中。
协议选定
SSH2、SSH1和Telnet都是网络协议它们在远程通信和数据传输方面发挥着重要作用但各自具有不同的特点和用途。以下是对这三个协议的详细解释
SSH2Secure Shell 2目前基本用这个
定义与特点 SSH2Secure Shell 2是一种用于计算机网络的加密协议旨在在不安全的网络中安全地传输数据。它是SSH协议的升级版本提供了更强大的加密和认证机制。 SSH2协议通过加密通道来传输数据防止数据在传输过程中被窃听、篡改或伪造。它采用了公钥加密、对称加密和消息认证码等多种加密技术保障了数据的机密性、完整性和可靠性。 SSH2还提供了强大的身份认证机制包括密码认证、公钥认证和基于密钥的认证等确保了通信双方的身份合法性和安全性。 SSH2协议支持多种加密算法和密钥长度可以根据实际需求选择合适的加密方式提高了系统的灵活性和安全性。
应用场景 SSH2协议被广泛用于远程登录和管理服务器、网络设备。 它也用于安全文件传输等场景支持端口转发和X11转发等功能可以实现安全的远程访问和数据传输。
SSH1Secure Shell 1
定义与特点 SSH1是SSH协议的第一个版本由芬兰赫尔辛基工业大学的研究员Tatu Ylönen设计。 它最初提出的目的是替代非安全的Telnet、rsh、rexec等远程Shell协议。 SSH1虽然在一定程度上提高了远程通信的安全性但随着时间的推移其安全性漏洞逐渐暴露因此被SSH2所取代。
应用现状 由于SSH1存在安全漏洞目前已被SSH2广泛替代。然而在一些旧系统或特定场景下SSH1可能仍然被使用但建议尽快升级到SSH2以提高安全性。
Telnet
定义与特点 Telnet是一种网络协议用于远程登录到另一台计算机并执行命令。 它可以在本地与远程计算机之间建立一个虚拟终端会话使用户可以像在本地计算机上一样操作远程计算机。 使用Telnet连接远程计算机需要知道目标计算机的IP地址或域名并开启远程登录服务。
安全隐患 Telnet协议本身并不提供加密功能因此数据传输过程中存在被窃听的风险。 这使得Telnet在安全性要求较高的场景下不再适用而是被SSH等更安全的协议所取代。
代码实现
落地方案1ganymed-ssh2
maven坐标 中央仓库最新版本2622014年的版本有点旧了。
dependencygroupIdch.ethz.ganymed/groupIdartifactIdganymed-ssh2/artifactIdversion262/version
/dependency为什么选型这个第三方的实现 此处是基于mqcloud搜狐开源的中间件管理框架他集成这种技术用来管理rocketmq的broker与ns的发布与运维管理。
关键源代码
技术效果验证
以下效果是本机环境通过传入ip与账户获取服务器上jdk的版本号功能远程登录服务器并执行获取jdk版本的shell命令source /etc/profile;javap -version
以下是通过类似xshell的客户端工具root登录后获取jdk的版本号
到此为止你认为成功了吗我们发到正式环境试一下吧。 由于正式环境OpenSSH版本比较高所以直接报错了。原因是算法不对等
连接高版本OpenSSH报错分析
正式环境服务器的ssh版本OpenSSH_9.6p1支持的算法
[myhomepaas-core-hu5-a-2 ~]$ ssh -V
OpenSSH_9.6p1, OpenSSL 1.1.1k FIPS 25 Mar 2021
[myhomepaas-core-hu5-a-2 ~]$ ssh -Q kex
diffie-hellman-group1-sha1
diffie-hellman-group14-sha1
diffie-hellman-group14-sha256
diffie-hellman-group16-sha512
diffie-hellman-group18-sha512
diffie-hellman-group-exchange-sha1
diffie-hellman-group-exchange-sha256
ecdh-sha2-nistp256
ecdh-sha2-nistp384
ecdh-sha2-nistp521
curve25519-sha256
curve25519-sha256libssh.org
sntrup761x25519-sha512openssh.com[myhomepaas-core-hu5-a-2 ~]$ ssh -Q mac
hmac-sha1
hmac-sha1-96
hmac-sha2-256
hmac-sha2-512
hmac-md5
hmac-md5-96
umac-64openssh.com
umac-128openssh.com
hmac-sha1-etmopenssh.com
hmac-sha1-96-etmopenssh.com
hmac-sha2-256-etmopenssh.com
hmac-sha2-512-etmopenssh.com
hmac-md5-etmopenssh.com
hmac-md5-96-etmopenssh.com
umac-64-etmopenssh.com
umac-128-etmopenssh.com[myhomepaas-core-hu5-a-2 ~]$ ssh -Q cipher
3des-cbc
aes128-cbc
aes192-cbc
aes256-cbc
aes128-ctr
aes192-ctr
aes256-ctr
aes128-gcmopenssh.com
aes256-gcmopenssh.com
chacha20-poly1305openssh.com开发环境服务器的ssh版本OpenSSH_7.4p1支持的算法
[rootAC-SEC-01 ~]# ssh -V
OpenSSH_7.4p1, OpenSSL 1.0.2k-fips 26 Jan 2017
[rootAC-SEC-01 ~]# ssh -Q kex
diffie-hellman-group1-sha1
diffie-hellman-group14-sha1
diffie-hellman-group14-sha256
diffie-hellman-group16-sha512
diffie-hellman-group18-sha512
diffie-hellman-group-exchange-sha1
diffie-hellman-group-exchange-sha256
ecdh-sha2-nistp256
ecdh-sha2-nistp384
ecdh-sha2-nistp521
curve25519-sha256
curve25519-sha256libssh.org
gss-gex-sha1-
gss-group1-sha1-
gss-group14-sha1-[rootAC-SEC-01 ~]# ssh -Q mac
hmac-sha1
hmac-sha1-96
hmac-sha2-256
hmac-sha2-512
hmac-md5
hmac-md5-96
hmac-ripemd160
hmac-ripemd160openssh.com
umac-64openssh.com
umac-128openssh.com
hmac-sha1-etmopenssh.com
hmac-sha1-96-etmopenssh.com
hmac-sha2-256-etmopenssh.com
hmac-sha2-512-etmopenssh.com
hmac-md5-etmopenssh.com
hmac-md5-96-etmopenssh.com
hmac-ripemd160-etmopenssh.com
umac-64-etmopenssh.com
umac-128-etmopenssh.com[rootAC-SEC-01 ~]# ssh -Q cipher
3des-cbc
blowfish-cbc
cast128-cbc
arcfour
arcfour128
arcfour256
aes128-cbc
aes192-cbc
aes256-cbc
rijndael-cbclysator.liu.se
aes128-ctr
aes192-ctr
aes256-ctr
aes128-gcmopenssh.com
aes256-gcmopenssh.com
chacha20-poly1305openssh.com服务器的生效配置 sudo cat /etc/ssh/sshd_config
MACs hmac-sha1
KexAlgorithms curve25519-sha256,curve25519-sha256libssh.org,ecdh-sha2-nistp256,ecdh-sha2-nistp384,ecdh-sha2-nistp521
Ciphers aes128-ctr,aes192-ctr,aes256-ctr客户端算法 ganymed-ssh2 262版本从源码中抓取出来
MACs getMacList() hmac-sha1-96, hmac-sha1, hmac-md5-96, hmac-md5
KexAlgorithms getDefaultClientKexAlgorithmList() diffie-hellman-group-exchange-sha1, diffie-hellman-group14-sha1, diffie-hellman-group1-sha1 ecdh-sha2-nistp256
Ciphers aes128-ctr,aes192-ctr,aes256-ctr,blowfish-ctr,3des-ctr,3des-cbc结论就是KexAlgorithms算法不匹配 因为客户端diffie系列算法不安全所有高版本的OpenSSH服务端已经默认去除了这些算法如果想用也是可以的就是容易被攻击比如直接在服务端的sshd_config的KexAlgorithms 最后一个算法加上客户端你想用的算法比如, “diffie-hellman-group1-sha1”。 为了安全我只能使用加密等级比较高的非对称椭圆算法一个选择是在262版本上继续二开加入高版本的算法支持或者找找别人遇到类似的问题他升级了此包。当然还有一条路换第三方实现我最终换成了jsch的实现。
落地方案2jsch
maven坐标
dependencygroupIdcom.github.mwiede/groupIdartifactIdjsch/artifactIdversion0.2.19/version
/dependencyjsch客户端功能
session: 一个基础的会话通道通常用来建立一个基础的交互式命令行会话。
shell: 提供了一个登录shell的交互环境允许用户执行命令。
exec: 用于执行远程命令不提供交互式shell。
x11: 允许X11图形应用通过SSH隧道进行转发。
auth-agentopenssh.com: 用于SSH认证代理转发这样远程服务器可以使用本地机器的SSH密钥来访问其他系统。
direct-tcpip: 用于创建一个TCP/IP端口转发通道允许通过SSH隧道转发TCP流量。
forwarded-tcpip: 类似于direct-tcpip但是它是由远程服务器发起的连接请求到指定的地址和端口。
sftp: 用于安全文件传输协议(SFTP)允许文件传输。useWriteFlushWorkaround是一个配置选项可能与某些SFTP服务器的兼容性有关。
subsystem: 允许运行注册了的子系统如sftp子系统。
direct-streamlocalopenssh.com: 用于本地流套接字的直接连接通常用于非TCP协议或特定于平台的服务。不墨迹直接上源码
Controller层接口文件内容
Data
public class ScriptUploadRequest {private String fileContent;private String filePath;private String fileName;private String resourceHost;private String bastionHost;
}Slf4j
Api(value 代理层通用接口, tags {代理层通用接口})
RestController
RequestMapping(/api/xshell)
public class BasicLineController {Autowiredprivate basicLineService basicLineService;ApiOperation(value 脚本上传)PostMapping(/script/upload)public AjaxResult uploadScript(RequestBody ScriptUploadRequest suRequest) {if(ChannelCache.isNotEmpty(testResult))return testResult;return basicLineService.uploadScript(suRequest);}
} service 层将文件落地连接管道发送sh命令执行curl回调
/*** 上传脚本接受应用系统推送过来的文件内容将文本内容写入本地并连上Linux通道通过curl方式回调getfile接口抓取并写入到主机的本机上* param suRequest fileContent 脚本文件内容* param suRequest filePath 脚本文件在前置服务器上保存的目录* param suRequest fileName 脚本文件的文件名很重要* param suRequest resourceHost 主机的IP* param suRequest bastionHost 堡垒机的IP没有的堡垒机的兄弟们直接忽略此IP* **/public AjaxResult uploadScript(ScriptUploadRequest suRequest) {try {// 获取请求参数
// MultipartFile file suRequest.getFile(); //也可以传文件嘛String fileContent AESUtil.decryptStr(suRequest.getFileContent());String filePath suRequest.getFilePath();String fileName suRequest.getFileName();String resourceHost suRequest.getResourceHost();String bastionHost suRequest.getBastionHost();ResHosts resHosts ProxyUtils.getResHosts(proxyProperties, bastionHost, resourceHost);String cacheKey ProxyUtils.getCacheKey(resHosts);// 1. 保存文件到本地Path path Paths.get(filePath, fileName);Files.createDirectories(path.getParent());// 创建父目录如果它们不存在Files.write(path, fileContent.getBytes());// 写入内容到文件如果文件不存在则创建文件ChannelShell channelShell JSchUtils.remoteConnectShell(cacheKey, resHosts, proxyProperties, proxyProperties.getSocketOverwrite());log.info(通道连接...cacheKey{},channel{} , cacheKey, channelShell.isConnected());Thread.sleep(2000);if(!channelShell.isConnected()){return AjaxResult.error(连接失败);}// 获取当前接口的URL与端口String callUrl JSchUtils.getCallUrl(proxyProperties, Constants.callGetFile);String command1 mkdir -p Constants.dirScript;String command2 curl --location callUrl?filePathConstants.dirproxyscriptfilenamefileName --header access_token: Constants.access_token -o Constants.dirScript File.separator fileName;String command3 chmod x Constants.dirScript File.separator fileName;String command command1command2command3;AjaxResult ajaxResult JSchUtils.remoteExecuteShell(ProxyUtils.getCacheKey(resHosts), resHosts, command, proxyProperties);log.info(指令执行(2)...cacheKey{},channel{},msg{} , cacheKey, channelShell.isConnected(), ajaxResult.get(AjaxResult.MSG_TAG));return ajaxResult;} catch (IOException | InterruptedException e) {log.error(连接失败, e);return AjaxResult.error(连接失败e.getMessage());}}JSchUtils管道连接的工具类是灵魂就免费赠送大家了画重点
为了这里面的实现花了3天没日没夜的各种姿势尝试相信遇到一个技术卡点的技术爱好者又缺少对方系统的各种信息这个滋味应该很酸爽放出来的目的也是为了让有相同近况的少走一些弯路。
package com.why.proxy.app.util;import com.why.app.entity.ResHosts;
import com.why.app.service.ChannelCache;
import com.why.ganymed.util.Result;
import com.why.ganymed.vo.AjaxResult;
import com.why.proxy.app.ProxyProperties;
import com.jcraft.jsch.*;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.multipart.MultipartFile;import javax.swing.*;
import java.awt.*;
import java.io.*;
import java.net.InetSocketAddress;
import java.net.MalformedURLException;
import java.net.Socket;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;Slf4j
public class JSchUtils {public static ChannelShell remoteConnectShell(String cacheKey, ResHosts resHosts, ProxyProperties proxyProperties, String overwrite) {if(1.equals(overwrite)){ChannelCache.disconnectChannelShell(cacheKey);ChannelCache.disconnectSessionShell(cacheKey);}//1. socket连接Session session connectSessionShell(cacheKey, resHosts, proxyProperties);//2. channel连接ChannelShell channelShell connectChannelShell(cacheKey, proxyProperties, session);return channelShell;}public static AjaxResult remoteExecuteShell(String cacheKey, ResHosts resHosts, String command, ProxyProperties proxyProperties) {return remoteExecute4aShell(cacheKey, resHosts, command, proxyProperties);}public static AjaxResult remoteExecute4aShell(String cacheKey, ResHosts resHosts, String command, ProxyProperties proxyProperties) {//1. command命令发送ChannelShell channel getChannelShell(cacheKey, resHosts, proxyProperties, 3);if(ChannelCache.isEmpty(channel)){return AjaxResult.error(channel无法连接...);}//2. command命令发送InputStream inputStream null;OutputStream outputStream null;try {inputStream channel.getInputStream();outputStream channel.getOutputStream();// 向远程服务器发送命令byte[] cmdBytes (command \n).getBytes(StandardCharsets.UTF_8);outputStream.write(cmdBytes);outputStream.flush();log.info(---------{}发送命令 {}, cacheKey, command);Thread.sleep(200);} catch (IOException e) {ChannelCache.disconnectChannelShell(cacheKey);ChannelCache.disconnectSessionShell(cacheKey);log.info(shell命令command 发送失败..., e.getMessage(), e);return AjaxResult.error(e.getMessage());} catch (InterruptedException e) {throw new RuntimeException(e);}//3. command命令回执String inputLine ;try {inputLine JSchUtils.readLineWithTimeout(inputStream, channel, proxyProperties.getCommandReadTimeOut());} catch (Exception e) {ChannelCache.disconnectChannelShell(cacheKey);ChannelCache.disconnectSessionShell(cacheKey);return AjaxResult.error(e.getMessage());}if(ChannelCache.isEmpty(inputLine) || !channel.isConnected()){log.info(执行命令失败...command);return AjaxResult.error(4a命令执行未获取到结果...);}return AjaxResult.success(inputLine);}private static ChannelShell getChannelShell(String cacheKey, ResHosts resHosts, ProxyProperties proxyProperties, int retry) {if(retry 0){log.info(获取服务器连接失败...cacheKeycacheKey);return null;}retry --;ChannelShell channel ChannelCache.cacheChannelShellMap.get(cacheKey);if(ChannelCache.isEmpty(channel) || !channel.isConnected()){log.info(当前channel{}cacheKey{}重连中..., ChannelCache.isEmpty(channel)?null :channel.isConnected(), cacheKey);remoteConnectShell(resHosts.getHost() resHosts.getUser(), resHosts, proxyProperties,1);channel ChannelCache.cacheChannelShellMap.get(cacheKey);if(ChannelCache.isEmpty(channel) || !channel.isConnected()){getChannelShell(cacheKey, resHosts, proxyProperties, retry);}}return channel;}private static ChannelShell connectChannelShell(String cacheKey, ProxyProperties proxyProperties, Session session) {ChannelShell channel ChannelCache.cacheChannelShellMap.get(cacheKey);try {if(ChannelCache.isEmpty(channel) || !channel.isConnected()){channel (ChannelShell) session.openChannel(shell);channel.setPty(true); // 伪终端开启发送请求 PTY 来确保输出立即可用channel.connect(proxyProperties.getChannelConnectTimeout());log.info(channel连接成功...cacheKey{}, cacheKey);Thread.sleep(200);// JSchUtils.readConnectWithTimeout(channel, proxyProperties.getConnectReadTimeOut(), result);boolean sessionConnected session.isConnected();boolean channelConnected channel.isConnected();log.info(channel连接消息读取...cacheKey{}session.isConnected{}channel.isConnected{}, cacheKey, sessionConnected, channelConnected);ChannelCache.cacheChannelShellMap.put(cacheKey, channel);}else {boolean sessionConnected session.isConnected();boolean channelConnected channel.isConnected();log.info(channel连接成功(cache)...cacheKey{}session.isConnected{}channel.isConnected{}, cacheKey, sessionConnected, channelConnected);}} catch (Exception e) {log.info(channel连接失败...cacheKey{}, cacheKey);ChannelCache.disconnectChannelShell(cacheKey);throw new RuntimeException(e);}return channel;}private static Session connectSessionShell(String cacheKey, ResHosts resHosts, ProxyProperties proxyProperties) {Session session ChannelCache.cacheSessionMap.get(cacheKey);try {if(ChannelCache.isEmpty(session) || !session.isConnected()){if(ChannelCache.isNotEmpty(session) !session.isConnected()){ChannelCache.disconnectChannelShell(cacheKey);}JSch jsch new JSch();session jsch.getSession(resHosts.getUser(), resHosts.getHost(), resHosts.getPort());session.setPassword(resHosts.getPassword());session.setConfig(StrictHostKeyChecking, no);session.setConfig(PreferredAuthentications, password);session.connect(proxyProperties.getSessionConnectTimeout());ChannelCache.cacheSessionMap.put(cacheKey, session);log.info(Socket连接成功...cacheKey{}, cacheKey);}else {
// log.info(获取缓存的Session对象成功...ip{}, cacheKey);}} catch (JSchException e) {log.info(Socket连接失败...ip{}, cacheKey, e);ChannelCache.disconnectChannelShell(cacheKey);ChannelCache.disconnectSessionShell(cacheKey);throw new RuntimeException(e);}return session;}public static void readConnectWithTimeout(InputStream inputStream, ChannelShell channel, int timeout, StringBuffer result) throws InterruptedException, IOException {if(!channel.isConnected()){result.append(连接失败...);return;}String readLineStr readLineWithTimeout(inputStream, channel, timeout);if(ChannelCache.isNotEmpty(readLineStr)){result.append(readLineStr);}//连接返回值的结束字符串
// if(readLineStr.indexOf(-bash: PROMPT_COMMAND: readonly variable) 0){
// readConnectWithTimeout(channel, timeout, result);
// }}public static String readLineWithTimeout(InputStream inputStream, ChannelShell channel, int timeout) throws InterruptedException, IOException {BufferedReader inputReader new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8));StringBuffer line new StringBuffer();long start System.currentTimeMillis();Thread thread new Thread(() - {try {while (channel.isConnected()) {String str inputReader.readLine();if(ChannelCache.isEmpty(str)){return;}
// log.info(-------------------inputReader.readLine--- str);line.append(str \n);}} catch (IOException e) {if(e instanceof java.io.InterruptedIOException){}else {log.error(Error reading from BufferedReader:, e);}}});thread.start();//3秒的超时触发强制退出监听io事件while (System.currentTimeMillis() - start timeout) {TimeUnit.MILLISECONDS.sleep(10);} thread.interrupt();// if(inputReader ! null){
// inputReader.close();
// }
// if(inputStream ! null){
// inputStream.close();
// }if(ChannelCache.isNotEmpty(line)){log.info(---------接收数据 channel.isConnected{} {}, channel.isConnected(), line);}return line.toString();}public static String getIpFromUrl(String urlString) {// 解析URL字符串String host ;try {// 使用URL类解析URLURL url new URL(urlString);host url.getHost(); // 获取主机名部分} catch (MalformedURLException e) {System.err.println(Invalid URL: urlString);e.printStackTrace();}// 如果主机名包含[和]则可能是IPv6地址去除方括号if (host.startsWith([) host.endsWith(])) {host host.substring(1, host.length() - 1);}return host;}public static String getCallUrl(ProxyProperties proxyProperties, String uri) {String callUrl proxyProperties.getProxyCallUrl() uri;log.info(回调地址...callUrl{}, callUrl);return callUrl;}/*** excute remote command** param resHosts resHosts* param command command* return /* throws JSchException /*/public static Result? remoteExecuteExec(ResHosts resHosts, String command, ProxyProperties proxyProperties) {Integer readTimeOut proxyProperties.getConnectReadTimeOut();Integer sessionConnectTimeout proxyProperties.getSessionConnectTimeout();Integer channelConnectTimeout proxyProperties.getChannelConnectTimeout();log.info( {}, command);StringBuffer result new StringBuffer();Session session null;ChannelExec channel null;try {JSch jsch new JSch();// 如果需要使用私钥认证则加载私钥if (Files.exists(Paths.get(resHosts.getIdentity()))) {jsch.addIdentity(resHosts.getIdentity(), resHosts.getPassphrase());}session jsch.getSession(resHosts.getUser(), resHosts.getHost(), resHosts.getPort());session.setConfig(StrictHostKeyChecking, no);session.setConfig(PreferredAuthentications, password);MyUserInfo myUserInfo new MyUserInfo();myUserInfo.setPassword(resHosts.getPassword());session.setUserInfo(myUserInfo);session.connect(sessionConnectTimeout);// 设置连接超时时间log.info(ip{} Session connected., resHosts.getHost());channel (ChannelExec)session.openChannel(exec);channel.setCommand(command);channel.setPty(true);channel.setInputStream(null);// 设置标准错误输出流channel.setErrStream(System.err);InputStream input channel.getInputStream();// 设置通道连接超时时间log.info(ip{} Channel connecting..., resHosts.getHost());channel.connect(channelConnectTimeout);log.info(ip{} Channel connected., resHosts.getHost());BufferedReader inputReader new BufferedReader(new InputStreamReader(input, StandardCharsets.UTF_8));String inputLine;while ((inputLine inputReader.readLine()) ! null) {log.info( {}, inputLine);result.append(inputLine \n);}// 获取命令执行的退出状态log.info(ip{} ExitStatus{} result{}, resHosts.getHost(), channel.getExitStatus(), result);} catch (IOException | JSchException e) {log.info(Error executing command: {}, e.getMessage(), e);return Result.getWebErrorResult(e);} finally {if (channel ! null channel.isConnected()) {channel.disconnect();
// log.info(ip{} Channel disconnected., resHosts.getHost());}if (session ! null session.isConnected()) {disconnect(session);
// log.info(ip{} Session disconnected., resHosts.getHost());}}return Result.success(result);}public static Result? remoteExecute_1(ResHosts resHosts, String command) {log.info( {}, command);StringBuffer result new StringBuffer();Session session null;ChannelExec channel null;try {JSch jsch new JSch();// 如果需要使用私钥认证则加载私钥if (Files.exists(Paths.get(resHosts.getIdentity()))) {jsch.addIdentity(resHosts.getIdentity(), resHosts.getPassphrase());}session jsch.getSession(resHosts.getUser(), resHosts.getHost(), resHosts.getPort());session.setPassword(resHosts.getPassword());session.setConfig(StrictHostKeyChecking, no);session.setConfig(PreferredAuthentications, password);// 设置连接超时时间int SESSION_TIMEOUT 5000; // 示例超时时间为5秒session.connect(SESSION_TIMEOUT);log.info(ip{} Session connected., resHosts.getHost());channel (ChannelExec) session.openChannel(exec);channel.setCommand(command);channel.setPty(true);// 设置标准错误输出流ByteArrayOutputStream baos new ByteArrayOutputStream();PrintStream ps new PrintStream(baos);channel.setErrStream(ps);InputStream input channel.getInputStream();// 设置通道连接超时时间int CONNECT_TIMEOUT 5000; // 示例超时时间为5秒log.info(ip{} Channel connecting..., resHosts.getHost());channel.connect(CONNECT_TIMEOUT);log.info(ip{} Channel connected., resHosts.getHost());String errorOutput baos.toString();log.info(ErrStream: errorOutput);if (ChannelCache.isNotEmpty(errorOutput)) {return Result.success(errorOutput);}BufferedReader inputReader new BufferedReader(new InputStreamReader(input, StandardCharsets.UTF_8));String inputLine;while ((inputLine inputReader.readLine()) ! null) {log.info( {}, inputLine);result.append(inputLine \n);}// 获取命令执行的退出状态log.info(ip{} ExitStatus{} result{}, resHosts.getHost(), channel.getExitStatus(), result);} catch (IOException | JSchException e) {log.error(Error executing command: {}, e.getMessage(), e);return Result.getWebErrorResult(e);} finally {if (channel ! null channel.isConnected()) {channel.disconnect();log.info(ip{} Channel disconnected., resHosts.getHost());}if (session ! null session.isConnected()) {disconnect(session);log.info(ip{} Session disconnected., resHosts.getHost());}}return Result.success(result);}/*** get session** param remote ssh server info* return session* throws JSchException /*/public static Session connectSessionShell(ResHosts remote, ProxyProperties proxyProperties) {Integer sessionConnectTimeout proxyProperties.getSessionConnectTimeout();Session session null;try {JSch jSch new JSch();if (Files.exists(Paths.get(remote.getIdentity()))) {jSch.addIdentity(remote.getIdentity(), remote.getPassphrase());}session jSch.getSession(remote.getUser(), remote.getHost(), remote.getPort());session.setPassword(remote.getPassword());session.setConfig(StrictHostKeyChecking, no);session.setConfig(PreferredAuthentications, password);session.connect(sessionConnectTimeout);} catch (JSchException e) {throw new RuntimeException(e);}return session;}public static Result? proxyExecute(ResHosts jumpHost, ResHosts targetHost, String command, ProxyProperties proxyProperties) {Integer readTimeOut proxyProperties.getConnectReadTimeOut();Integer sessionConnectTimeout proxyProperties.getSessionConnectTimeout();Integer channelConnectTimeout proxyProperties.getChannelConnectTimeout();log.info( {}, command);StringBuilder result new StringBuilder();Session jumpSession null;ChannelDirectTCPIP jumpChannel null;Session targetSession null;ChannelExec targetChannel null;try {// 创建跳板机的会话JSch jumpJsch new JSch();jumpSession jumpJsch.getSession(jumpHost.getUser(), jumpHost.getHost(), jumpHost.getPort());jumpSession.setPassword(jumpHost.getPassword());jumpSession.setConfig(StrictHostKeyChecking, no);jumpSession.setConfig(PreferredAuthentications, password);jumpSession.connect(sessionConnectTimeout);log.info(Connected to the jump host: {}{}:{}, jumpHost.getUser(), jumpHost.getHost(), jumpHost.getPort());// 创建跳板机上的ChannelDirectTCPIPjumpChannel (ChannelDirectTCPIP) jumpSession.openChannel(direct-tcpip);jumpChannel.setOrgPort(0); // 自动选择本地端口jumpChannel.setHost(targetHost.getHost()); // 设置目标主机的IP地址jumpChannel.setPort(targetHost.getPort()); // 设置目标主机端口// 连接通道jumpChannel.connect(channelConnectTimeout);log.info(TCP/IP forwarding established.);Socket socket new Socket();socket.connect(new InetSocketAddress(jumpHost.getHost(), jumpHost.getPort()));int localPort ((InetSocketAddress) socket.getLocalSocketAddress()).getPort();socket.close();log.info(Local port used for forwarding: {}, localPort);// 创建目标主机的会话JSch targetJsch new JSch();targetSession targetJsch.getSession(targetHost.getUser(), localhost, localPort);targetSession.setPassword(targetHost.getPassword());targetSession.setConfig(StrictHostKeyChecking, no);targetSession.setConfig(PreferredAuthentications, password);targetSession.connect(sessionConnectTimeout);log.info(Connected to the target host via direct-tcpip.);// 创建目标主机上的ChannelExectargetChannel (ChannelExec) targetSession.openChannel(exec);targetChannel.setCommand(command);// 请求 PTY 来确保输出立即可用targetChannel.setPty(true);InputStream input targetChannel.getInputStream();targetChannel.connect(channelConnectTimeout);log.info(Channel connected.);BufferedReader inputReader new BufferedReader(new InputStreamReader(input, StandardCharsets.UTF_8));String inputLine;while ((inputLine inputReader.readLine()) ! null) {log.info( {}, inputLine);result.append(inputLine).append(\n);}// 获取命令执行的退出状态int exitStatus targetChannel.getExitStatus();log.info(Command exit status: {}, exitStatus);} catch (JSchException | IOException e) {log.error(Error executing command:, e);return Result.getWebErrorResult(e);} finally {if (targetChannel ! null targetChannel.isConnected()) {targetChannel.disconnect();log.info(Target channel disconnected.);}if (targetSession ! null targetSession.isConnected()) {targetSession.disconnect();log.info(Target session disconnected.);}if (jumpSession ! null jumpSession.isConnected()) {jumpSession.disconnect();log.info(Jump session disconnected.);}}return Result.success(result);}/*** scp file to remote server** param session session* param source local file* param destination remote target file* return file size*/public static long scpTo(Session session, String source, String destination, ProxyProperties proxyProperties) {Integer channelConnectTimeout proxyProperties.getChannelConnectTimeout();FileInputStream fileInputStream null;ChannelExec channel null;try {channel openExecChannel(session, proxyProperties);OutputStream out channel.getOutputStream();InputStream in channel.getInputStream();boolean ptimestamp false;String command scp;if (ptimestamp) {command -p;}command -t destination;channel.setCommand(command);channel.connect(channelConnectTimeout);if (checkAck(in) ! 0) {return -1;}File _lfile new File(source);if (ptimestamp) {command T (_lfile.lastModified() / 1000) 0;// The access time should be sent here,// but it is not accessible with JavaAPI ;-command ( (_lfile.lastModified() / 1000) 0\n);out.write(command.getBytes());out.flush();if (checkAck(in) ! 0) {return -1;}}//send C0644 filesize filename, where filename should not include /long fileSize _lfile.length();command C0644 fileSize ;if (source.lastIndexOf(/) 0) {command source.substring(source.lastIndexOf(/) 1);} else {command source;}command \n;out.write(command.getBytes());out.flush();if (checkAck(in) ! 0) {return -1;}//send content of filefileInputStream new FileInputStream(source);byte[] buf new byte[1024];long sum 0;while (true) {int len fileInputStream.read(buf, 0, buf.length);if (len 0) {break;}out.write(buf, 0, len);sum len;}//send \0buf[0] 0;out.write(buf, 0, 1);out.flush();if (checkAck(in) ! 0) {return -1;}return sum;} catch (JSchException e) {log.error(scp to caught jsch exception, , e);} catch (IOException e) {log.error(scp to caught io exception, , e);} catch (Exception e) {log.error(scp to error, , e);} finally {closeInputStream(fileInputStream);disconnect(channel);}return -1;}/*** scp remote file to local** param session session* param source remote file* param destination local file* return file size*/public static long scpFrom(Session session, String source, String destination, ProxyProperties proxyProperties) {FileOutputStream fileOutputStream null;ChannelExec channel null;try {channel openExecChannel(session, proxyProperties);channel.setCommand(scp -f source);OutputStream out channel.getOutputStream();InputStream in channel.getInputStream();channel.connect();byte[] buf new byte[1024];//send \0buf[0] 0;out.write(buf, 0, 1);out.flush();while (true) {if (checkAck(in) ! C) {break;}}//read 644 in.read(buf, 0, 4);long fileSize 0;while (true) {if (in.read(buf, 0, 1) 0) {break;}if (buf[0] ) {break;}fileSize fileSize * 10L (long) (buf[0] - 0);}String file null;for (int i 0; ; i) {in.read(buf, i, 1);if (buf[i] (byte) 0x0a) {file new String(buf, 0, i);break;}}// send \0buf[0] 0;out.write(buf, 0, 1);out.flush();// read a content of lfileif (Files.isDirectory(Paths.get(destination))) {fileOutputStream new FileOutputStream(destination File.separator file);} else {fileOutputStream new FileOutputStream(destination);}long sum 0;while (true) {int len in.read(buf, 0, buf.length);if (len 0) {break;}sum len;if (len fileSize) {fileOutputStream.write(buf, 0, (int) fileSize);break;}fileOutputStream.write(buf, 0, len);fileSize - len;}return sum;} catch (JSchException e) {log.error(scp to caught jsch exception, , e);} catch (IOException e) {log.error(scp to caught io exception, , e);} catch (Exception e) {log.error(scp to error, , e);} finally {closeOutputStream(fileOutputStream);disconnect(channel);}return -1;}/*** remote edit** param session session* param source target file* param process edit command collect* return isSuccess*/private static boolean remoteEdit(Session session, String source, FunctionListString, ListString process, ProxyProperties proxyProperties) {InputStream in null;OutputStream out null;try {String fileName source;int index source.lastIndexOf(/);if (index 0) {fileName source.substring(index 1);}//backup source
// remoteExecute(session, String.format(cp %s %s, source, source .bak. System.currentTimeMillis()));//scp from remoteString tmpSource System.getProperty(java.io.tmpdir) session.getHost() - fileName;scpFrom(session, source, tmpSource, proxyProperties);in new FileInputStream(tmpSource);//edit file according function processString tmpDestination tmpSource .des;out new FileOutputStream(tmpDestination);ListString inputLines new ArrayList();BufferedReader reader new BufferedReader(new InputStreamReader(in));String inputLine null;while ((inputLine reader.readLine()) ! null) {inputLines.add(inputLine);}ListString outputLines process.apply(inputLines);for (String outputLine : outputLines) {out.write((outputLine \n).getBytes());out.flush();}//scp to remotescpTo(session, tmpDestination, source, proxyProperties);return true;} catch (Exception e) {log.error(remote edit error, , e);return false;} finally {closeInputStream(in);closeOutputStream(out);}}/*** update file** param session session* param in file stream* param directory local dir* param fileName FTP server file namexxx.txt ||xxx.txt.zip*/public static boolean uploadFile(Session session, InputStream in, String directory, String fileName, ProxyProperties proxyProperties) {log.info(uploadFile--ftp start);ChannelSftp channel null;try {channel openSftpChannel(session, proxyProperties);channel.connect(proxyProperties.getChannelConnectTimeout());String[] folders directory.split(/);try {for (int i 0; i folders.length; i) {if (i 0 folders[i].length() 0) {channel.cd(/);} else if (folders[i].length() 0) {try {channel.cd(folders[i]);} catch (SftpException e) {channel.mkdir(folders[i]);channel.cd(folders[i]);}}}} catch (SftpException e) {log.error(ftp create file fail directory, e);return false;}try {channel.put(in, fileName);} catch (SftpException e) {log.error(sftp error-- e.getMessage(), e);return false;}log.info(uploadFile--ftp upload end);log.info(ftp upload dir{}filename{}, directory, fileName);return true;} catch (JSchException e) {log.error(JSch error-- e.getMessage(), e);return false;} finally {closeInputStream(in);disconnect(channel);}}/***** param channel sftp connect* param directory* param fileName* return*/public static InputStream stream(ChannelSftp channel, String directory, String fileName, ProxyProperties proxyProperties) {try {channel.connect(proxyProperties.getChannelConnectTimeout());InputStream inputStream channel.get(directory / fileName);log.info(ftp file directory{}filename{}, directory, fileName);return inputStream;} catch (SftpException e) {log.error(sftp error-- e.getMessage());return null;} catch (JSchException e) {log.error(JSch error-- e.getMessage());return null;}}/*** ftp delete remote file** param session session* param directory directory* param fileName filename* return is Success*/public static boolean deleteFile(Session session, String directory, String fileName, ProxyProperties proxyProperties) {log.info(deleteFile--ftp delete file end);ChannelSftp channel null;try {channel openSftpChannel(session, proxyProperties);channel.connect(proxyProperties.getChannelConnectTimeout());channel.rm(directory / fileName);log.info(deleteFile--deletefile end);log.info(ftp delete file directory{}filename{}, directory, fileName);} catch (SftpException e) {log.error(ftp create directory fail directory);return false;} catch (JSchException e) {log.error(JSch error-- e.getMessage());return false;} finally {disconnect(channel);}return true;}public static Channel openChannel(Session session, String type, ProxyProperties proxyProperties) throws JSchException {if (!session.isConnected()) {session.connect(proxyProperties.getSessionConnectTimeout());}return session.openChannel(type);}public static ChannelSftp openSftpChannel(Session session, ProxyProperties proxyProperties) throws JSchException {return (ChannelSftp) openChannel(session, sftp, proxyProperties);}public static ChannelExec openExecChannel(Session session, ProxyProperties proxyProperties) throws JSchException {return (ChannelExec) openChannel(session, exec, proxyProperties);}/*** disconnect** param session*/public static void disconnect(Session session) {if (session ! null) {if (session.isConnected()) {try {session.disconnect();log.info(session disconnect successfully);} catch (Exception e) {log.error(JSch session disconnect error:, e);}}}}/*** close connection** param channel channel connection*/public static void disconnect(Channel channel) {if (channel ! null) {if (channel.isConnected()) {try {channel.disconnect();log.info(channel is closed already);} catch (Exception e) {log.error(JSch channel disconnect error:, e);}}}}public static int checkAck(InputStream in) throws IOException {int b in.read();// b may be 0 for success,// 1 for error,// 2 for fatal error,// -1if (b 0) {return b;}if (b -1) {return b;}if (b 1 || b 2) {StringBuilder sb new StringBuilder();int c;do {c in.read();sb.append((char) c);}while (c ! \n);if (b 1) { // errorlog.debug(sb.toString());}if (b 2) { // fatal errorlog.debug(sb.toString());}}return b;}public static void closeInputStream(InputStream in) {if (in ! null) {try {in.close();} catch (IOException e) {log.error(Close input stream error. e.getMessage());}}}public static void closeOutputStream(OutputStream out) {if (out ! null) {try {out.close();} catch (IOException e) {log.error(Close output stream error. e.getMessage());}}}public static void transferToFile(MultipartFile file, String filePath, String filename) {try {// 构建目标文件File targetFile new File(filePath, filename);// 如果目标目录不存在则创建它if (!targetFile.getParentFile().exists()) {targetFile.getParentFile().mkdirs();}// 将上传的文件保存到指定位置file.transferTo(targetFile);log.info(本地文件生成成功...filePath{},filename{}, filePath, filename);} catch (IOException e) {log.error(文件上传失败, e);}}public static class MyUserInfo implements UserInfo, UIKeyboardInteractive {Overridepublic String getPassword() {return passwd;}public void setPassword(String passwd) {this.passwd passwd;}Overridepublic boolean promptYesNo(String str) {Object[] options {yes, no};int foo JOptionPane.showOptionDialog(null, str, Warning, JOptionPane.DEFAULT_OPTION,JOptionPane.WARNING_MESSAGE, null, options, options[0]);return foo 0;}String passwd;JTextField passwordField new JPasswordField(20);Overridepublic String getPassphrase() {return null;}Overridepublic boolean promptPassphrase(String message) {return true;}Overridepublic boolean promptPassword(String message) {
// Object[] ob {passwordField};
// int result JOptionPane.showConfirmDialog(null, ob, message, JOptionPane.OK_CANCEL_OPTION);
// if (result JOptionPane.OK_OPTION) {
// passwd passwordField.getText();
// return true;
// } else {
// return false;
// }return true;}Overridepublic void showMessage(String message) {JOptionPane.showMessageDialog(null, message);}final GridBagConstraints gbc new GridBagConstraints(0, 0, 1, 1, 1, 1,GridBagConstraints.NORTHWEST, GridBagConstraints.NONE, new Insets(0, 0, 0, 0), 0, 0);private Container panel;Overridepublic String[] promptKeyboardInteractive(String destination, String name, String instruction,String[] prompt, boolean[] echo) {panel new JPanel();panel.setLayout(new GridBagLayout());gbc.weightx 1.0;gbc.gridwidth GridBagConstraints.REMAINDER;gbc.gridx 0;panel.add(new JLabel(instruction), gbc);gbc.gridy;gbc.gridwidth GridBagConstraints.RELATIVE;JTextField[] texts new JTextField[prompt.length];for (int i 0; i prompt.length; i) {gbc.fill GridBagConstraints.NONE;gbc.gridx 0;gbc.weightx 1;panel.add(new JLabel(prompt[i]), gbc);gbc.gridx 1;gbc.fill GridBagConstraints.HORIZONTAL;gbc.weighty 1;if (echo[i]) {texts[i] new JTextField(20);} else {texts[i] new JPasswordField(20);}panel.add(texts[i], gbc);gbc.gridy;}if (JOptionPane.showConfirmDialog(null, panel, destination : name,JOptionPane.OK_CANCEL_OPTION, JOptionPane.QUESTION_MESSAGE) JOptionPane.OK_OPTION) {String[] response new String[prompt.length];for (int i 0; i prompt.length; i) {response[i] texts[i].getText();}return response;} else {return null; // cancel}}}
}
进阶应用
方案1rsync调试实现文件的传输
我们是部署了一套代码由代码获取到主机的ssh连接shell命令执行对象故执行一些特殊命令会弹出密码验证那么直接发送两条命令或者使用连接命令是否可行答案是不行但Linux提供了一些工具比如sshpass支持所谓的“免密”的登陆效果。
1. 使用此组件前简单验证安装sshpass工具并尝试两台机器之间的文件交互。
sshpass --version
sudo yum install sshpass
--往前置机发送文件
sshpass -p 密码 rsync -avz --progress -e ssh -p 22050 /apps/system_lae.sh root42.10.10.101:/apps/system_lae.sh
--从前置机抓取文件
sshpass -p 密码 rsync -avz --progress -e ssh -p 22050 root42.10.10.101:/apps/system_lae.sh /apps/system_lae.sh2. 上传文件结合expect实现免密效果
mkdir /apps/script
touch /apps/script/system_lae_push.exp
chmod x /apps/script/system_lae_push.expcat /apps/script/system_lae_push.exp EOF
#!/usr/bin/expect
spawn rsync -avz --progress -e ssh -p 22 myhome10.10.10.101:/apps/proxy/script/system_lae.sh /apps/script/system_lae.sh
expect myhome10.10.10.101s password:
send -- Wzw123!\r
expect {myhome10.10.10.101s password: { send -- Wzw123!\r; exp_continue }Permission denied { puts Error: Permission denied.; exit }eof { puts Error: Unexpected end of file.; exit }
}
interact
EOF
cat /apps/script/system_lae_push.exp | expect
chmod x /apps/script/system_lae.sh3. 下载文件结合expect实现免密效果
------生产下载文件
cat /apps/script/system_lae_pull.exp EOF
#!/usr/bin/expect
spawn rsync -avz --progress -e ssh -p 22 /apps/system_lae.sh myhome10.10.10.101:/apps/system_lae.sh
expect myhome10.10.10.101s password:
send -- Wzw123!\r
expect {myhome10.10.10.101s password: { send -- Wzw123!\r; exp_continue }Permission denied { puts Error: Permission denied.; exit }eof { puts Error: Unexpected end of file.; exit }
}
interact
EOF
cat /apps/script/system_lae_pull.exp | expect方案2sftp调试实现文件的传输
这种方式同样存在要输入密码的问题可结合sshpass解决。 以下是简单列出sftp的使用方式这种方式很常见唯一的区别是将ftp搭建在前置服务器上将命令发送到上千台主机服务器反向连接到此ftp上并执行主机上的文件上传与下载操作。
#sftp
1、查看sftp服务端grep -i sftp-server /etc/ssh/sshd_config2、创建sftp专用帐号2.1 生成账户sudo useradd -m -s /sbin/nologin sftpusersudo passwd sftpuser sftpuser9988992.2 配置sftp专用帐号访问权限编辑 /etc/ssh/sshd_config 文件添加或修改以下配置行 Match User sftpuserChrootDirectory /appsForceCommand internal-sftp2.3 设置了 /apps 目录的所有者为 root组为 sftpuser并赋予了正确的读写权限。sudo mkdir -p /appssudo chown root:sftpuser /appssudo chmod 775 /apps2.4 重启 SSH 服务sudo systemctl restart sshd#3、测试机验证sftp
sftp sftpuser60.204.111.222
sftpuser998899
put /apps/data/linux/system_lae.sh /apps/system_lae.sh
exit方案3scp模式
此模式与rsync类似如果是正向上传直接调用客户端的接口接口如果反向让资源主机访问前置服务那么就类似于方案1了。