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