diff --git a/collector/src/main/java/org/dromara/hertzbeat/collector/collect/ssh/SshCollectImpl.java b/collector/src/main/java/org/dromara/hertzbeat/collector/collect/ssh/SshCollectImpl.java index e34fbf4bab9..3bc3027a432 100644 --- a/collector/src/main/java/org/dromara/hertzbeat/collector/collect/ssh/SshCollectImpl.java +++ b/collector/src/main/java/org/dromara/hertzbeat/collector/collect/ssh/SshCollectImpl.java @@ -17,6 +17,7 @@ package org.dromara.hertzbeat.collector.collect.ssh; +import org.apache.sshd.common.util.security.SecurityUtils; import org.dromara.hertzbeat.collector.collect.AbstractCollect; import org.dromara.hertzbeat.collector.collect.common.cache.CacheIdentifier; import org.dromara.hertzbeat.collector.collect.common.cache.CommonCache; @@ -24,8 +25,8 @@ import org.dromara.hertzbeat.collector.collect.common.ssh.CommonSshClient; import org.dromara.hertzbeat.collector.dispatch.DispatchConstants; import org.dromara.hertzbeat.collector.util.CollectUtil; +import org.dromara.hertzbeat.collector.util.PrivateKeyUtils; import org.dromara.hertzbeat.common.constants.CollectorConstants; -import org.dromara.hertzbeat.collector.util.KeyPairUtil; import org.dromara.hertzbeat.common.entity.job.Metrics; import org.dromara.hertzbeat.common.entity.job.protocol.SshProtocol; import org.dromara.hertzbeat.common.entity.message.CollectRep; @@ -39,8 +40,10 @@ import org.springframework.util.StringUtils; import java.io.ByteArrayOutputStream; +import java.io.FileInputStream; import java.io.IOException; import java.net.ConnectException; +import java.security.GeneralSecurityException; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; @@ -55,7 +58,6 @@ * ssh协议采集实现 * * @author tom - * */ @Slf4j public class SshCollectImpl extends AbstractCollect { @@ -63,7 +65,7 @@ public class SshCollectImpl extends AbstractCollect { private static final String PARSE_TYPE_ONE_ROW = "oneRow"; private static final String PARSE_TYPE_MULTI_ROW = "multiRow"; private static final String PARSE_TYPE_NETCAT = "netcat"; - + private static final int DEFAULT_TIMEOUT = 10_000; public SshCollectImpl() { @@ -236,7 +238,7 @@ private void parseResponseDataByMulti(String result, List aliasFields, builder.addValues(valueRowBuilder.build()); } } - + private void removeConnectSessionCache(SshProtocol sshProtocol) { CacheIdentifier identifier = CacheIdentifier.builder() .ip(sshProtocol.getHost()).port(sshProtocol.getPort()) @@ -245,7 +247,7 @@ private void removeConnectSessionCache(SshProtocol sshProtocol) { CommonCache.getInstance().removeCache(identifier); } - private ClientSession getConnectSession(SshProtocol sshProtocol, int timeout) throws IOException { + private ClientSession getConnectSession(SshProtocol sshProtocol, int timeout) throws IOException, GeneralSecurityException { CacheIdentifier identifier = CacheIdentifier.builder() .ip(sshProtocol.getHost()).port(sshProtocol.getPort()) .username(sshProtocol.getUsername()).password(sshProtocol.getPassword()) @@ -274,14 +276,11 @@ private ClientSession getConnectSession(SshProtocol sshProtocol, int timeout) th if (StringUtils.hasText(sshProtocol.getPassword())) { clientSession.addPasswordIdentity(sshProtocol.getPassword()); } else if (StringUtils.hasText(sshProtocol.getPrivateKey())) { - var keyPair = KeyPairUtil.getKeyPairFromPrivateKey(sshProtocol.getPrivateKey()); - if (keyPair != null) { - clientSession.addPublicKeyIdentity(keyPair); - } - } else { - clientSession.close(); - throw new IllegalArgumentException("please input password or secret."); - } + var resourceKey = PrivateKeyUtils.writePrivateKey(sshProtocol.getHost(), sshProtocol.getPrivateKey()); + SecurityUtils.loadKeyPairIdentities(null, () -> resourceKey, new FileInputStream(resourceKey), null) + .forEach(clientSession::addPublicKeyIdentity); + } // else auth with localhost private public key certificates + // auth if (!clientSession.auth().verify(timeout, TimeUnit.MILLISECONDS).isSuccess()) { clientSession.close(); diff --git a/collector/src/main/java/org/dromara/hertzbeat/collector/util/KeyPairUtil.java b/collector/src/main/java/org/dromara/hertzbeat/collector/util/KeyPairUtil.java deleted file mode 100644 index cbe2d742c00..00000000000 --- a/collector/src/main/java/org/dromara/hertzbeat/collector/util/KeyPairUtil.java +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.dromara.hertzbeat.collector.util; - -import lombok.experimental.UtilityClass; -import lombok.extern.slf4j.Slf4j; -import org.springframework.util.StringUtils; - -import java.io.ByteArrayOutputStream; -import java.io.ObjectOutputStream; -import java.security.KeyPair; -import java.security.KeyPairGenerator; - -/** - * 密钥工具类 - * @author tom - */ -@Slf4j -@UtilityClass -public class KeyPairUtil { - - private static KeyPairGenerator keyPairGenerator; - - static { - try { - keyPairGenerator = KeyPairGenerator.getInstance("RSA"); - } catch (Exception e) { - log.error(e.getMessage(), e); - } - } - - /** - * 获取密钥对 - */ - public static KeyPair getKeyPairFromPrivateKey(String privateKeyStr) { - if (!StringUtils.hasText(privateKeyStr)) { - return null; - } - try { - var keyPair = keyPairGenerator.generateKeyPair(); - var stream = new ByteArrayOutputStream(); - stream.write(privateKeyStr.getBytes()); - var oos = new ObjectOutputStream(stream); - oos.writeObject(keyPair); - return keyPair; - } catch (Exception e) { - log.info("[keyPair] parse failed, {}." + e.getMessage()); - return null; - } - } - -} diff --git a/collector/src/main/java/org/dromara/hertzbeat/collector/util/PrivateKeyUtils.java b/collector/src/main/java/org/dromara/hertzbeat/collector/util/PrivateKeyUtils.java new file mode 100644 index 00000000000..a4f6797dcd4 --- /dev/null +++ b/collector/src/main/java/org/dromara/hertzbeat/collector/util/PrivateKeyUtils.java @@ -0,0 +1,48 @@ +package org.dromara.hertzbeat.collector.util; + +import lombok.experimental.UtilityClass; +import lombok.extern.slf4j.Slf4j; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.Objects; + +/** + * 将私钥写入~/.ssh + * + * @author gcdd1993 + * Created by gcdd1993 on 2023/7/9 + */ +@Slf4j +@UtilityClass +public class PrivateKeyUtils { + + private static final String KEY_PATH = System.getProperty("user.home") + "/.ssh"; + + /** + * write private key to ~/.ssh, filename is ~/.ssh/id_rsa_${host} + * + * @param host host + * @param keyStr key string + * @return key file path + * @throws IOException if ~/.ssh is not exist and create dir error + */ + public static String writePrivateKey(String host, String keyStr) throws IOException { + var sshPath = Paths.get(KEY_PATH); + if (!Files.exists(sshPath)) { + Files.createDirectories(sshPath); + } + var keyPath = Paths.get(KEY_PATH, "id_rsa_" + host); + if (!Files.exists(keyPath)) { + Files.writeString(keyPath, keyStr); + } else { + var oldKey = Files.readString(keyPath); + if (!Objects.equals(oldKey, keyStr)) { + Files.writeString(keyPath, keyStr); + } + } + return keyPath.toString(); + } + +} diff --git a/collector/src/test/java/org/dromara/hertzbeat/collector/util/KeyPairUtilTest.java b/collector/src/test/java/org/dromara/hertzbeat/collector/util/KeyPairUtilTest.java deleted file mode 100644 index 8929bb6b185..00000000000 --- a/collector/src/test/java/org/dromara/hertzbeat/collector/util/KeyPairUtilTest.java +++ /dev/null @@ -1,71 +0,0 @@ -package org.dromara.hertzbeat.collector.util; - -import org.apache.commons.codec.binary.Base64; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import java.security.KeyPair; -import java.security.KeyPairGenerator; -import java.security.PublicKey; -import java.security.SecureRandom; - -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertNull; - -/** - * Test case for {@link KeyPairUtil} - */ -class KeyPairUtilTest { - - @BeforeEach - void setUp() { - } - - @Test - void getKeyPairFromPublicKey() { - // test null key - KeyPair nullKey = KeyPairUtil.getKeyPairFromPrivateKey(null); - assertNull(nullKey); - // test empty key - KeyPair emptyKey = KeyPairUtil.getKeyPairFromPrivateKey(""); - assertNull(emptyKey); - // test illegal key: DSA - String dsaPublicKey = new String(Base64.encodeBase64(genPublicKey("DSA").getEncoded())); - KeyPair illegalKey = KeyPairUtil.getKeyPairFromPrivateKey(dsaPublicKey); - assertNotNull(illegalKey); - // test illegal key: DiffieHellman - String diffieHellmanPublicKey = new String(Base64.encodeBase64(genPublicKey("DiffieHellman").getEncoded())); - illegalKey = KeyPairUtil.getKeyPairFromPrivateKey(diffieHellmanPublicKey); - assertNotNull(illegalKey); - // test not encrypted - byte[] rsaBytes = genPublicKey("RSA").getEncoded(); - String rawRsaPublicKey = new String(rsaBytes); - KeyPair raw = KeyPairUtil.getKeyPairFromPrivateKey(rawRsaPublicKey); - assertNotNull(raw); - // test normal case - // base64 encrypted - String rsaPublicKey = new String(Base64.encodeBase64(rsaBytes)); - KeyPair normal = KeyPairUtil.getKeyPairFromPrivateKey(rsaPublicKey); - assertNotNull(normal); - assertNotNull(normal.getPublic()); - } - - - private PublicKey genPublicKey(String key) { - KeyPairGenerator keyPairGen = null; - try { - keyPairGen = KeyPairGenerator.getInstance(key); - } catch (Exception e) { - // nothing to do - // this case shouldn't happen - throw new RuntimeException(e); - } - // init - keyPairGen.initialize(1024, new SecureRandom()); - // generate key - KeyPair keyPair = keyPairGen.generateKeyPair(); - // get public key - return keyPair.getPublic(); - } - -} \ No newline at end of file diff --git a/collector/src/test/java/org/dromara/hertzbeat/collector/util/PrivateKeyUtilsTest.java b/collector/src/test/java/org/dromara/hertzbeat/collector/util/PrivateKeyUtilsTest.java new file mode 100644 index 00000000000..04007797a6a --- /dev/null +++ b/collector/src/test/java/org/dromara/hertzbeat/collector/util/PrivateKeyUtilsTest.java @@ -0,0 +1,61 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.dromara.hertzbeat.collector.util; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.io.IOException; + +/** + * @author gcdd1993 + * Created by gcdd1993 on 2023/7/9 + */ +class PrivateKeyUtilsTest { + + @DisplayName("write key to ~/.ssh") + @Test + void writePrivateKey() throws IOException { + var key = "-----BEGIN RSA PRIVATE KEY-----\n" + + "MIIEogIBAAKCAQEA4ctFYk/xy89L6/6YFeeMrwCW9lCP/ThXMn+9G63s5bGn4oIN\n" + + "8cEf/JYkmGw8vMP41IAP9dyH8ji2wIZSLeTPWucEK6P6jA01iIBQ95ng6RTsnQgL\n" + + "h4pYHxlEaNHcXkjy5GlMdzaWadjdRevpThGR1VOtWFtK3yoC0c/te2Junu04f+11\n" + + "cpk8QvmVfzrBUooVnG0/7oekwUy1c5sSl0qVoLzXOv4XG9w34cyvacFC30zv1Nl8\n" + + "ASi2pmOBVx9njPvqQ7qZrDk0nwn+RZUmGh/PbmHxrBV7ZA5NjZcEnf2VGIfjGUVu\n" + + "qE4VnkbvS4j03afV2rsp1yo74K+k/ZC6GCHB5QIBIwKCAQBG9r4I9I3SVxfcdJYy\n" + + "xR2WFiDREgFeNkdKYqkl9NVsws5dIY9am8g5cQQv54DNnK1KGZ6dulaclXtD0nGZ\n" + + "ZSs505OYr+EHcd2f7dBN0Uavp32QcD4jSLycD0FixZ0HsIbaEnceJxlUd1t8YBYf\n" + + "2aLcpUUbxOulORbUOgjPAa286uDeQYN5IbdruDfvbuFFm7hBoGZoKLJ7FPcJ0U3A\n" + + "14KRK+Z1oCYJIS0ubaHbhaPIVPPQEmTNHpsvxIJXfZtVy9+XIuBGmD3+Aq6SSFPC\n" + + "A8mU1iKzzdRCXZwvPeUiivIIZc6DRXjhtJ2Lya/XndKidOT/QUj8Z+f9pWAonlzM\n" + + "3PMXAoGBAPvzctkkDjUJjLyEuYQq8soYokS4n4ykFTP5oFgnodK/cYocbxTT6Tn9\n" + + "vH7b6lK6ZAf+tZk8rcEeIO650pOvmaa1/OuZSxfcFUGBvOvYXiHF7zmkePh/pQgB\n" + + "7Cl0RYrI52Cjbd9aCUIYK3A82qsUq30INGeOhMNrfaHn2pgx8xlDAoGBAOVsNctw\n" + + "CHnLaIQX8eS+eUcQEm+NZppnDBJavdpP48ZZM/t5v/2fQ5ytbYqk0KEzIGu0dP8g\n" + + "jfB76JbMvStvTfB+TrXsfhGyA3oJrEcG+3IUshsRU2sohT1ScY27z2VMLgilnWvF\n" + + "7t49sQm9uB/yn669n8LIciHxDItOpvqgKdG3AoGBAO2NxA6PtZ+4jAIz/19bsbc7\n" + + "zDIqaovrKe8tMMglXg/ZE0e0aLvdvqRkRAKU1Z51Ob5lLuDwEYoyWZCgk1gL90Vp\n" + + "wpT+P3zlcyCBo39IWMDB8C8IydRbF/GbaaNtoKds92m+qWwwUd87XCf+3M0wvvI6\n" + + "75TW1PLEbyOgFz8Khh8hAoGBAJbDc87Ul9sCAtp2Ip2hvWk2coPR8vfADz9C8cn5\n" + + "/BShBOcVfipSt2b1n8GCP/TnFU4XgBVeiSkA9+4Rg6AzMzejdY1+JvWvfqCnRVM/\n" + + "GkOnMzZb17tyZi+ck8OKC/IcHkAyUYFWL0GWQSOojvBsPQxt+0V8aEIwsHjNSSha\n" + + "nyNpAoGAd0XqdByRxbWgg5ZsvM0tvrpMITpEZsGMG9VeQPGl0wsQvC2zw5QGLvz/\n" + + "57YhofOOr0M3yElcFA9Imvek5CYZsyL8eIWGZyadfRiYvGOUyvDDO3BYRG4DmhyF\n" + + "KVk3URjEuOCC29ORvZ/7HaCO9iuEbvAA/mrAtd7KdCA+3PzfEOw=\n" + + "-----END RSA PRIVATE KEY-----"; + PrivateKeyUtils.writePrivateKey("127.0.0.1", key); + } +} \ No newline at end of file diff --git a/manager/src/main/resources/define/app-almalinux.yml b/manager/src/main/resources/define/app-almalinux.yml index ae38e8de2a7..2e7e6469310 100644 --- a/manager/src/main/resources/define/app-almalinux.yml +++ b/manager/src/main/resources/define/app-almalinux.yml @@ -123,6 +123,7 @@ params: # type-param field type(most mapping the html input type) # type-字段类型,样式(大部分映射input标签type属性) type: textarea + placeholder: -----BEGIN RSA PRIVATE KEY----- # required-true or false # required-是否是必输项 true-必填 false-可选 required: false diff --git a/manager/src/main/resources/define/app-centos.yml b/manager/src/main/resources/define/app-centos.yml index f82d5698abd..a0b1fc6657e 100644 --- a/manager/src/main/resources/define/app-centos.yml +++ b/manager/src/main/resources/define/app-centos.yml @@ -123,6 +123,7 @@ params: # type-param field type(most mapping the html input type) # type-字段类型,样式(大部分映射input标签type属性) type: textarea + placeholder: -----BEGIN RSA PRIVATE KEY----- # required-true or false # required-是否是必输项 true-必填 false-可选 required: false diff --git a/manager/src/main/resources/define/app-coreos.yml b/manager/src/main/resources/define/app-coreos.yml index a08d938155f..75002545542 100644 --- a/manager/src/main/resources/define/app-coreos.yml +++ b/manager/src/main/resources/define/app-coreos.yml @@ -123,6 +123,7 @@ params: # type-param field type(most mapping the html input type) # type-字段类型,样式(大部分映射input标签type属性) type: textarea + placeholder: -----BEGIN RSA PRIVATE KEY----- # required-true or false # required-是否是必输项 true-必填 false-可选 required: false diff --git a/manager/src/main/resources/define/app-debian.yml b/manager/src/main/resources/define/app-debian.yml index ec1e1bc4b96..d823c4354c8 100644 --- a/manager/src/main/resources/define/app-debian.yml +++ b/manager/src/main/resources/define/app-debian.yml @@ -123,6 +123,7 @@ params: # type-param field type(most mapping the html input type) # type-字段类型,样式(大部分映射input标签type属性) type: textarea + placeholder: -----BEGIN RSA PRIVATE KEY----- # required-true or false # required-是否是必输项 true-必填 false-可选 required: false diff --git a/manager/src/main/resources/define/app-euleros.yml b/manager/src/main/resources/define/app-euleros.yml index e5aabe062e5..61d9a9c00d0 100644 --- a/manager/src/main/resources/define/app-euleros.yml +++ b/manager/src/main/resources/define/app-euleros.yml @@ -123,6 +123,7 @@ params: # type-param field type(most mapping the html input type) # type-字段类型,样式(大部分映射input标签type属性) type: textarea + placeholder: -----BEGIN RSA PRIVATE KEY----- # required-true or false # required-是否是必输项 true-必填 false-可选 required: false diff --git a/manager/src/main/resources/define/app-freebsd.yml b/manager/src/main/resources/define/app-freebsd.yml index ce94562b581..10d288c6699 100644 --- a/manager/src/main/resources/define/app-freebsd.yml +++ b/manager/src/main/resources/define/app-freebsd.yml @@ -123,6 +123,7 @@ params: # type-param field type(most mapping the html input type) # type-字段类型,样式(大部分映射input标签type属性) type: textarea + placeholder: -----BEGIN RSA PRIVATE KEY----- # required-true or false # required-是否是必输项 true-必填 false-可选 required: false diff --git a/manager/src/main/resources/define/app-linux.yml b/manager/src/main/resources/define/app-linux.yml index b7e99f91e4c..94c41d41a7f 100644 --- a/manager/src/main/resources/define/app-linux.yml +++ b/manager/src/main/resources/define/app-linux.yml @@ -123,6 +123,7 @@ params: # type-param field type(most mapping the html input type) # type-字段类型,样式(大部分映射input标签type属性) type: textarea + placeholder: -----BEGIN RSA PRIVATE KEY----- # required-true or false # required-是否是必输项 true-必填 false-可选 required: false diff --git a/manager/src/main/resources/define/app-opensuse.yml b/manager/src/main/resources/define/app-opensuse.yml index 5299c9cbd2e..aceeeae4b95 100644 --- a/manager/src/main/resources/define/app-opensuse.yml +++ b/manager/src/main/resources/define/app-opensuse.yml @@ -123,6 +123,7 @@ params: # type-param field type(most mapping the html input type) # type-字段类型,样式(大部分映射input标签type属性) type: textarea + placeholder: -----BEGIN RSA PRIVATE KEY----- # required-true or false # required-是否是必输项 true-必填 false-可选 required: false diff --git a/manager/src/main/resources/define/app-redhat.yml b/manager/src/main/resources/define/app-redhat.yml index 62275271cda..1b9b87b5373 100644 --- a/manager/src/main/resources/define/app-redhat.yml +++ b/manager/src/main/resources/define/app-redhat.yml @@ -123,6 +123,7 @@ params: # type-param field type(most mapping the html input type) # type-字段类型,样式(大部分映射input标签type属性) type: textarea + placeholder: -----BEGIN RSA PRIVATE KEY----- # required-true or false # required-是否是必输项 true-必填 false-可选 required: false diff --git a/manager/src/main/resources/define/app-rockylinux.yml b/manager/src/main/resources/define/app-rockylinux.yml index 303d8fcc05d..38ac8792516 100644 --- a/manager/src/main/resources/define/app-rockylinux.yml +++ b/manager/src/main/resources/define/app-rockylinux.yml @@ -123,6 +123,7 @@ params: # type-param field type(most mapping the html input type) # type-字段类型,样式(大部分映射input标签type属性) type: textarea + placeholder: -----BEGIN RSA PRIVATE KEY----- # required-true or false # required-是否是必输项 true-必填 false-可选 required: false diff --git a/manager/src/main/resources/define/app-ubuntu.yml b/manager/src/main/resources/define/app-ubuntu.yml index c02ff240f22..735d664343d 100644 --- a/manager/src/main/resources/define/app-ubuntu.yml +++ b/manager/src/main/resources/define/app-ubuntu.yml @@ -123,6 +123,7 @@ params: # type-param field type(most mapping the html input type) # type-字段类型,样式(大部分映射input标签type属性) type: textarea + placeholder: -----BEGIN RSA PRIVATE KEY----- # required-true or false # required-是否是必输项 true-必填 false-可选 required: false