diff --git a/src/main/java/net/lamgc/oracle/sentry/oci/compute/ssh/InstanceSsh.java b/src/main/java/net/lamgc/oracle/sentry/oci/compute/ssh/InstanceSsh.java index 1523d7a..1a9c71c 100644 --- a/src/main/java/net/lamgc/oracle/sentry/oci/compute/ssh/InstanceSsh.java +++ b/src/main/java/net/lamgc/oracle/sentry/oci/compute/ssh/InstanceSsh.java @@ -65,7 +65,8 @@ public class InstanceSsh implements AutoCloseable { if (instancePublicIps.stream().findFirst().isEmpty()) { throw new IllegalStateException("Instance has no public IP available."); } - String connectUri = "ssh://" + authInfo.getUsername() + "@" + instancePublicIps.stream().findFirst().get() + ":22"; + String connectUri = "ssh://" + authInfo.getUsername() + "@" + + instancePublicIps.stream().findFirst().get() + ":" + authInfo.getPort(); log.info("SSH 正在连接: {}", connectUri); ConnectFuture connect = sshClient.connect(connectUri); connect.verify(); diff --git a/src/main/java/net/lamgc/oracle/sentry/oci/compute/ssh/SshAuthInfo.java b/src/main/java/net/lamgc/oracle/sentry/oci/compute/ssh/SshAuthInfo.java index bc97516..78193bf 100644 --- a/src/main/java/net/lamgc/oracle/sentry/oci/compute/ssh/SshAuthInfo.java +++ b/src/main/java/net/lamgc/oracle/sentry/oci/compute/ssh/SshAuthInfo.java @@ -15,11 +15,8 @@ public abstract class SshAuthInfo { private final static Logger log = LoggerFactory.getLogger(SshAuthInfo.class); private String username; - /** - * 使用 Sha256 计算的密钥指纹. - */ private PublicKey serverKey; - + private int port; private SshAuthIdentityProvider provider; /** @@ -65,6 +62,22 @@ public abstract class SshAuthInfo { this.username = username; } + /** + * 设置 SSH 连接端口. + * @param port SSH 端口号. + */ + public void setPort(int port) { + this.port = port; + } + + /** + * 获取 SSH 端口号. + * @return 返回 SSH 端口号. + */ + public int getPort() { + return port; + } + /** * 设置 SSH 认证配置提供器. *

设置后, 可在首次连接认证通过后, 保存服务器公钥到文件中. diff --git a/src/main/java/net/lamgc/oracle/sentry/oci/compute/ssh/SshAuthInfoSerializer.java b/src/main/java/net/lamgc/oracle/sentry/oci/compute/ssh/SshAuthInfoSerializer.java index 022635c..56defd3 100644 --- a/src/main/java/net/lamgc/oracle/sentry/oci/compute/ssh/SshAuthInfoSerializer.java +++ b/src/main/java/net/lamgc/oracle/sentry/oci/compute/ssh/SshAuthInfoSerializer.java @@ -56,6 +56,24 @@ public final class SshAuthInfoSerializer implements JsonSerializer, throw new JsonParseException("Unsupported authentication type: " + authType); } info.setUsername(getFieldToStringOrFail(infoObject, "username")); + String portStr = getFieldToString(infoObject, "port"); + if (portStr != null) { + try { + int port = Integer.parseInt(portStr); + if (checkPortNumber(port)) { + info.setPort(port); + } else { + log.warn("端口号非法, 将使用默认端口号.(Input: {})", port); + info.setPort(22); + } + } catch (NumberFormatException e) { + log.warn("端口号无法转换成数字, 端口号将使用默认端口号.(Input: {})", portStr); + info.setPort(22); + } + } else { + info.setPort(22); + } + String serverKeyStr = getFieldToString(infoObject, "serverKey"); if (!Strings.isNullOrEmpty(serverKeyStr)) { try { @@ -88,6 +106,7 @@ public final class SshAuthInfoSerializer implements JsonSerializer, json.addProperty("authType", src.getType().toString()); json.addProperty("username", src.getUsername()); + json.addProperty("port", src.getPort()); if (src.getServerKey() != null) { json.addProperty("serverKey", encodeSshPublicKey(src.getServerKey())); } else { @@ -96,6 +115,10 @@ public final class SshAuthInfoSerializer implements JsonSerializer, return json; } + private boolean checkPortNumber(int port) { + return port >= 0 && port <= 65535; + } + private String getFieldToStringOrFail(JsonObject object, String field) { if (!object.has(field) || !object.get(field).isJsonPrimitive()) { throw new JsonParseException("Missing field: " + field); diff --git a/src/test/java/net/lamgc/oracle/sentry/oci/compute/ssh/SshAuthInfoSerializerTest.java b/src/test/java/net/lamgc/oracle/sentry/oci/compute/ssh/SshAuthInfoSerializerTest.java index 062b182..8ca340e 100644 --- a/src/test/java/net/lamgc/oracle/sentry/oci/compute/ssh/SshAuthInfoSerializerTest.java +++ b/src/test/java/net/lamgc/oracle/sentry/oci/compute/ssh/SshAuthInfoSerializerTest.java @@ -44,10 +44,6 @@ class SshAuthInfoSerializerTest { return gson.fromJson(new InputStreamReader(resource, StandardCharsets.UTF_8), JsonObject.class); } - private boolean matchTestsInfo(String name, JsonObject object) { - return getTestsInfo(name).equals(object); - } - @Test public void deserializePasswordTest() { SshAuthInfo info = gson.fromJson(getTestsInfo("StandardPassword"), SshAuthInfo.class); @@ -71,6 +67,39 @@ class SshAuthInfoSerializerTest { } } + @Test + public void deserializeBadPortNumberTest() { + SshAuthInfo info = gson.fromJson(getTestsInfo("BadPortValue-NonNumber"), SshAuthInfo.class); + + assertTrue(info instanceof PasswordAuthInfo); + assertEquals("opc", info.getUsername()); + assertEquals("123456", ((PasswordAuthInfo) info).getPassword()); + assertEquals("SHA256:qBu2jRXM6Wog/jWUJJ0WLTMb3UdDGAmYEVZQNZdFZNM", KeyUtils.getFingerPrint(info.getServerKey())); + assertEquals(22, info.getPort()); + } + + @Test + public void deserializePortNumberOutOfBoundTest() { + SshAuthInfo info = gson.fromJson(getTestsInfo("BadPortValue-OutOfBound"), SshAuthInfo.class); + + assertTrue(info instanceof PasswordAuthInfo); + assertEquals("opc", info.getUsername()); + assertEquals("123456", ((PasswordAuthInfo) info).getPassword()); + assertEquals("SHA256:qBu2jRXM6Wog/jWUJJ0WLTMb3UdDGAmYEVZQNZdFZNM", KeyUtils.getFingerPrint(info.getServerKey())); + assertEquals(22, info.getPort()); + } + + @Test + public void deserializePortNumberOutOfBoundMinusTest() { + SshAuthInfo info = gson.fromJson(getTestsInfo("BadPortValue-OutOfBound-minus"), SshAuthInfo.class); + + assertTrue(info instanceof PasswordAuthInfo); + assertEquals("opc", info.getUsername()); + assertEquals("123456", ((PasswordAuthInfo) info).getPassword()); + assertEquals("SHA256:qBu2jRXM6Wog/jWUJJ0WLTMb3UdDGAmYEVZQNZdFZNM", KeyUtils.getFingerPrint(info.getServerKey())); + assertEquals(22, info.getPort()); + } + @Test public void deserializeUnsupportedTest() { assertThrows(JsonParseException.class, () -> @@ -107,10 +136,17 @@ class SshAuthInfoSerializerTest { gson.fromJson(getTestsInfo("UnsupportedJsonType"), SshAuthInfo.class)); } + @Test + public void deserializeBadRequiredFieldJsonTypeTest() { + assertThrows(JsonParseException.class, () -> + gson.fromJson(getTestsInfo("BadRequiredFieldType"), SshAuthInfo.class)); + } + private void initialSshAuthInfo(SshAuthInfo info) { try { KeyPair pair = KeyUtils.generateKeyPair("ssh-rsa", 3072); info.setServerKey(pair.getPublic()); + info.setPort(new Random().nextInt(65536)); info.setUsername("linux"); if (info instanceof PasswordAuthInfo psw) { psw.setPassword(String.valueOf(new Random().nextLong())); @@ -149,6 +185,7 @@ class SshAuthInfoSerializerTest { assertEquals(SshAuthInfo.AuthType.PASSWORD.name(), getOrFailField(json, "authType")); assertEquals(KeyUtils.getFingerPrint(info.getServerKey()), KeyUtils.getFingerPrint(decodeSshPublicKey(getOrFailField(json, "serverKey")))); + assertEquals(info.getPort(), Integer.parseInt(getOrFailField(json, "port"))); assertEquals(info.getUsername(), getOrFailField(json, "username")); assertEquals(info.getPassword(), getOrFailField(json, "password")); @@ -164,6 +201,7 @@ class SshAuthInfoSerializerTest { assertEquals(KeyUtils.getFingerPrint(info.getServerKey()), KeyUtils.getFingerPrint(decodeSshPublicKey(getOrFailField(json, "serverKey")))); assertEquals(info.getUsername(), getOrFailField(json, "username")); + assertEquals(info.getPort(), Integer.parseInt(getOrFailField(json, "port"))); assertEquals(info.getPrivateKeyPath().getCanonicalFile(), new File(getOrFailField(json, "privateKeyPath"))); assertEquals(info.getKeyPassword(), getOrFailField(json, "keyPassword")); @@ -180,6 +218,7 @@ class SshAuthInfoSerializerTest { assertEquals(SshAuthInfo.AuthType.PASSWORD.name(), getOrFailField(json, "authType")); assertTrue(json.get("serverKey").isJsonNull()); assertEquals(info.getUsername(), getOrFailField(json, "username")); + assertEquals(info.getPort(), Integer.parseInt(getOrFailField(json, "port"))); assertEquals(info.getPassword(), getOrFailField(json, "password")); } @@ -212,6 +251,7 @@ class SshAuthInfoSerializerTest { assertEquals(SshAuthInfo.AuthType.PASSWORD.name(), getOrFailField(json, "authType")); assertTrue(json.get("serverKey").isJsonNull()); assertEquals(info.getUsername(), getOrFailField(json, "username")); + assertEquals(info.getPort(), Integer.parseInt(getOrFailField(json, "port"))); assertEquals(info.getPassword(), getOrFailField(json, "password")); } diff --git a/src/test/resources/ssh-auth/BadPortValue-NonNumber.json b/src/test/resources/ssh-auth/BadPortValue-NonNumber.json new file mode 100644 index 0000000..ee6cb22 --- /dev/null +++ b/src/test/resources/ssh-auth/BadPortValue-NonNumber.json @@ -0,0 +1,7 @@ +{ + "username": "opc", + "authType": "password", + "port": "test", + "serverKey": "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQC/NGFFKkchNdE8HDE9WHGIcw97ZVOP5edY7drtRQn0xSSG6uLu08T36B8IWT+XJdg45/YMmcuVSzsG1QZs/R3s0URVUhsWjwdezWDeWeBHt8/6TGl2AsgA0iXSAOeRNldhZlITFvWoBEv2wElNjCTsEGo5bBp3rVPqqZNJFUs+FR9s/uVgmFqe7HGhuKhhk7BrRThJ/NcgDRicMQ4yXU3Hl++pG54TVLH+0HmgWg312XNAWtzw2iRmKBAuu2I4pP1TRp93K/lbD7QU8k8W7QcyGSAc73nZrhyzYVMko5wQGt4/vGpchOw7ehkotSejTB1GSyhzBTZobA23For76YLzuVFOjF3lEvSh1QV30ysu0PREKLtY83ad0WHVFqVgJrFHkkXQrglN335BhGwhFzwyMpRxbD8HCDtz6VjpqwoKtd/ExQkcfaj/g10o28vRzHGyzUbCTe433V61fjSsC4Bikw15vTnQ3ZuyOzfyoCYUNpFcf1Wv+mkoWqn9xU8lGvk= Test-Server", + "password": "123456" +} \ No newline at end of file diff --git a/src/test/resources/ssh-auth/BadPortValue-OutOfBound-minus.json b/src/test/resources/ssh-auth/BadPortValue-OutOfBound-minus.json new file mode 100644 index 0000000..473b74c --- /dev/null +++ b/src/test/resources/ssh-auth/BadPortValue-OutOfBound-minus.json @@ -0,0 +1,7 @@ +{ + "username": "opc", + "authType": "password", + "port": "-22", + "serverKey": "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQC/NGFFKkchNdE8HDE9WHGIcw97ZVOP5edY7drtRQn0xSSG6uLu08T36B8IWT+XJdg45/YMmcuVSzsG1QZs/R3s0URVUhsWjwdezWDeWeBHt8/6TGl2AsgA0iXSAOeRNldhZlITFvWoBEv2wElNjCTsEGo5bBp3rVPqqZNJFUs+FR9s/uVgmFqe7HGhuKhhk7BrRThJ/NcgDRicMQ4yXU3Hl++pG54TVLH+0HmgWg312XNAWtzw2iRmKBAuu2I4pP1TRp93K/lbD7QU8k8W7QcyGSAc73nZrhyzYVMko5wQGt4/vGpchOw7ehkotSejTB1GSyhzBTZobA23For76YLzuVFOjF3lEvSh1QV30ysu0PREKLtY83ad0WHVFqVgJrFHkkXQrglN335BhGwhFzwyMpRxbD8HCDtz6VjpqwoKtd/ExQkcfaj/g10o28vRzHGyzUbCTe433V61fjSsC4Bikw15vTnQ3ZuyOzfyoCYUNpFcf1Wv+mkoWqn9xU8lGvk= Test-Server", + "password": "123456" +} \ No newline at end of file diff --git a/src/test/resources/ssh-auth/BadPortValue-OutOfBound.json b/src/test/resources/ssh-auth/BadPortValue-OutOfBound.json new file mode 100644 index 0000000..7d597c0 --- /dev/null +++ b/src/test/resources/ssh-auth/BadPortValue-OutOfBound.json @@ -0,0 +1,7 @@ +{ + "username": "opc", + "authType": "password", + "port": "1000000", + "serverKey": "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQC/NGFFKkchNdE8HDE9WHGIcw97ZVOP5edY7drtRQn0xSSG6uLu08T36B8IWT+XJdg45/YMmcuVSzsG1QZs/R3s0URVUhsWjwdezWDeWeBHt8/6TGl2AsgA0iXSAOeRNldhZlITFvWoBEv2wElNjCTsEGo5bBp3rVPqqZNJFUs+FR9s/uVgmFqe7HGhuKhhk7BrRThJ/NcgDRicMQ4yXU3Hl++pG54TVLH+0HmgWg312XNAWtzw2iRmKBAuu2I4pP1TRp93K/lbD7QU8k8W7QcyGSAc73nZrhyzYVMko5wQGt4/vGpchOw7ehkotSejTB1GSyhzBTZobA23For76YLzuVFOjF3lEvSh1QV30ysu0PREKLtY83ad0WHVFqVgJrFHkkXQrglN335BhGwhFzwyMpRxbD8HCDtz6VjpqwoKtd/ExQkcfaj/g10o28vRzHGyzUbCTe433V61fjSsC4Bikw15vTnQ3ZuyOzfyoCYUNpFcf1Wv+mkoWqn9xU8lGvk= Test-Server", + "password": "123456" +} \ No newline at end of file diff --git a/src/test/resources/ssh-auth/BadRequiredFieldType.json b/src/test/resources/ssh-auth/BadRequiredFieldType.json new file mode 100644 index 0000000..acb9ba2 --- /dev/null +++ b/src/test/resources/ssh-auth/BadRequiredFieldType.json @@ -0,0 +1,9 @@ +{ + "username": { + + }, + "authType": "password", + "port": 22, + "serverKey": "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQC/NGFFKkchNdE8HDE9WHGIcw97ZVOP5edY7drtRQn0xSSG6uLu08T36B8IWT+XJdg45/YMmcuVSzsG1QZs/R3s0URVUhsWjwdezWDeWeBHt8/6TGl2AsgA0iXSAOeRNldhZlITFvWoBEv2wElNjCTsEGo5bBp3rVPqqZNJFUs+FR9s/uVgmFqe7HGhuKhhk7BrRThJ/NcgDRicMQ4yXU3Hl++pG54TVLH+0HmgWg312XNAWtzw2iRmKBAuu2I4pP1TRp93K/lbD7QU8k8W7QcyGSAc73nZrhyzYVMko5wQGt4/vGpchOw7ehkotSejTB1GSyhzBTZobA23For76YLzuVFOjF3lEvSh1QV30ysu0PREKLtY83ad0WHVFqVgJrFHkkXQrglN335BhGwhFzwyMpRxbD8HCDtz6VjpqwoKtd/ExQkcfaj/g10o28vRzHGyzUbCTe433V61fjSsC4Bikw15vTnQ3ZuyOzfyoCYUNpFcf1Wv+mkoWqn9xU8lGvk= Test-Server", + "password": "123456" +} \ No newline at end of file diff --git a/src/test/resources/ssh-auth/StandardPassword.json b/src/test/resources/ssh-auth/StandardPassword.json index cf79842..ecb0219 100644 --- a/src/test/resources/ssh-auth/StandardPassword.json +++ b/src/test/resources/ssh-auth/StandardPassword.json @@ -1,6 +1,7 @@ { "username": "opc", "authType": "password", + "port": 22, "serverKey": "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQC/NGFFKkchNdE8HDE9WHGIcw97ZVOP5edY7drtRQn0xSSG6uLu08T36B8IWT+XJdg45/YMmcuVSzsG1QZs/R3s0URVUhsWjwdezWDeWeBHt8/6TGl2AsgA0iXSAOeRNldhZlITFvWoBEv2wElNjCTsEGo5bBp3rVPqqZNJFUs+FR9s/uVgmFqe7HGhuKhhk7BrRThJ/NcgDRicMQ4yXU3Hl++pG54TVLH+0HmgWg312XNAWtzw2iRmKBAuu2I4pP1TRp93K/lbD7QU8k8W7QcyGSAc73nZrhyzYVMko5wQGt4/vGpchOw7ehkotSejTB1GSyhzBTZobA23For76YLzuVFOjF3lEvSh1QV30ysu0PREKLtY83ad0WHVFqVgJrFHkkXQrglN335BhGwhFzwyMpRxbD8HCDtz6VjpqwoKtd/ExQkcfaj/g10o28vRzHGyzUbCTe433V61fjSsC4Bikw15vTnQ3ZuyOzfyoCYUNpFcf1Wv+mkoWqn9xU8lGvk= Test-Server", "password": "123456" } \ No newline at end of file