diff --git a/pom.xml b/pom.xml index b7bfd2f..97ae62c 100644 --- a/pom.xml +++ b/pom.xml @@ -1,6 +1,6 @@ - 4.0.0 @@ -16,6 +16,11 @@ + + com.hierynomus + sshj + 0.13.0 + org.hibernate @@ -95,7 +100,7 @@ org.slf4j slf4j-log4j12 - 1.5.6 + 1.6.2 com.google.code.gson diff --git a/src/main/java/com/sergeev/controlpanel/model/Node.java b/src/main/java/com/sergeev/controlpanel/model/Node.java index 1c20036..73279ab 100644 --- a/src/main/java/com/sergeev/controlpanel/model/Node.java +++ b/src/main/java/com/sergeev/controlpanel/model/Node.java @@ -1,16 +1,12 @@ package com.sergeev.controlpanel.model; -import com.google.gson.Gson; -import com.google.gson.JsonArray; -import com.google.gson.JsonObject; import com.sergeev.controlpanel.model.user.User; import com.sun.istack.internal.NotNull; -import org.json.simple.JSONArray; -import org.json.simple.JSONObject; import javax.persistence.*; import java.net.InetAddress; -import java.util.*; +import java.util.HashSet; +import java.util.Set; /** * Created by dmitry-sergeev on 22.09.15. @@ -35,6 +31,9 @@ public class Node extends AbstractModel{ @Column(name = "osVersion") private String osVersion; + @Column(name = "publicKey", unique = true, nullable = false) + private String publicKey; // Looks like "a6:90:99:9c:c5:15:d8:07:b5:fa:c5:79:77:93:9b:b6" + @ElementCollection(targetClass = Component.class) private Set components = null; @@ -44,12 +43,21 @@ public class Node extends AbstractModel{ public Node() { } - public Node(String name, InetAddress inetAddress, String osName, String osVersion) { + public Node(String name, InetAddress inetAddress, String osName, String osVersion, String publicKey) { this.name = name; this.inetAddress = inetAddress; this.osName = osName; this.osVersion = osVersion; users = new HashSet<>(); + this.publicKey = publicKey; + } + + public String getPublicKey() { + return publicKey; + } + + public void setPublicKey(String publicKey) { + this.publicKey = publicKey; } public long getId() { @@ -92,15 +100,15 @@ public void setOsVersion(String osVersion) { this.osVersion = osVersion; } - public void setComponents(Set components) { - this.components = components; - } - @OneToMany(mappedBy = "node", cascade = CascadeType.ALL) - public Set getComponents(){ + public Set getComponents() { return components; } + public void setComponents(Set components) { + this.components = components; + } + public Set addComponent(Component component){ components.add(component); component.setNode(this); diff --git a/src/main/java/com/sergeev/controlpanel/ssh/SshService.java b/src/main/java/com/sergeev/controlpanel/ssh/SshService.java new file mode 100644 index 0000000..77d22e2 --- /dev/null +++ b/src/main/java/com/sergeev/controlpanel/ssh/SshService.java @@ -0,0 +1,144 @@ +package com.sergeev.controlpanel.ssh; + +import com.sergeev.controlpanel.model.Node; +import net.schmizz.sshj.SSHClient; +import net.schmizz.sshj.common.IOUtils; +import net.schmizz.sshj.connection.ConnectionException; +import net.schmizz.sshj.connection.channel.direct.Session; +import net.schmizz.sshj.connection.channel.direct.Session.Command; +import net.schmizz.sshj.transport.TransportException; +import net.schmizz.sshj.userauth.UserAuthException; + +import java.io.File; +import java.io.IOException; +import java.util.concurrent.TimeUnit; + + +public class SshService { + private static final String KEY_PATH; + + static { + KEY_PATH = (new File("src/main/resources/cp.pem")).getAbsolutePath(); + } + + private final SSHClient ssh; + private Node node; + private Session session; + + public SshService(Node node) { + ssh = new SSHClient(); + this.node = node; + } + + public void updatePublicKey() { + ssh.addHostKeyVerifier(node.getPublicKey()); + } + + private void connect() throws IOException { + ssh.connect(node.getInetAddress()); + } + + private void disconnect() throws IOException { + ssh.disconnect(); + } + + private void logIn() throws UserAuthException, TransportException { + ssh.authPublickey("root", KEY_PATH); + } + + private void logIn(String password) throws UserAuthException, TransportException { + ssh.authPassword("root", password); + } + + private void startSession() throws ConnectionException, TransportException { + session = ssh.startSession(); + } + + private void stopSession() throws ConnectionException, TransportException { + session.close(); + } + + /* + String["Result", "Exit status"] + */ + private String[] executeCommand(String stringCommand) throws IOException { + String[] response = new String[2]; + final Command cmd = session.exec(stringCommand); + response[0] = IOUtils.readFully(cmd.getInputStream()).toString(); + cmd.join(5, TimeUnit.SECONDS); + response[1] = cmd.getExitStatus().toString(); + + return response; + } + + public String[] sendCommand(String cmd, String password) throws IOException { + this.connect(); + try { + if (password.isEmpty()) { + this.logIn(); + } else { + this.logIn(password); + } + this.startSession(); + try { + return this.executeCommand(cmd); + } finally { + this.stopSession(); + } + } finally { + this.disconnect(); + } + } + + public void initializeNode(String password) throws IOException { + // TODO: Read from resources/cp.id_rsa.pub + this.sendCommand( + "echo \"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDYCj4uihLqTI2c+A++p0tXwWCnIh3F8m2OD7h4OtJdoF8Q4Lz3/Ziq0X+BuZ2QRmVXeJkBQMT/z8E1iOktSRGYgZrVqGDdOsAiu8g6PTbnE3BSsqa5pUJ4mdZ6xc5l0xRrGb05TN8qw/OtYbcnC0E7ya+sM1JXJBG5Xosz+QrRanTJZrtBXjjWD82yJAuvypX4g3tbl156ZZKN8PIYRVFEMvzxwz36kKLfCagfgFFfDnG+38rLPqDbKTvx5NspSupdmis2I8tg5FfKTxX8RygrTEzjPoxRVLGSoYiYEDw6V9a3COE9sxjXyGT38fqFQUqIPGNmbBFYY7IYg82IFWhf ControlPanel\" >> ~/.ssh/authorized_keys", + password); + } + + /* + public static void main(String[] args) + throws IOException, Exception { + String res = ""; + final SSHClient ssh = new SSHClient(); + ssh.addHostKeyVerifier("a6:90:99:9c:c5:15:d8:07:b5:fa:c5:79:77:93:9b:b6"); + + ssh.connect("192.168.1.169"); + try { + //ssh.authPassword("root", ""); + File file = new File("src/main/resources/cp.pem"); + ssh.authPublickey("root", file.getAbsolutePath()); + final Session session = ssh.startSession(); + try { + final Command cmd = session.exec("uname -a"); + res += IOUtils.readFully(cmd.getInputStream()).toString(); + cmd.join(5, TimeUnit.SECONDS); + res += "\n** exit status: " + cmd.getExitStatus(); + } finally { + session.close(); + } + } finally { + ssh.disconnect(); + } + + + System.out.println(res); + }*/ + + /* + public int connectToNode(Node node) throws Exception { + //ssh.addHostKeyVerifier(node.getSshId()); + ssh.connect(node.getInetAddress()); + ssh.authPublickey("root"); + + Session session = ssh.startSession(); + sessions.add(session); + + // It's possible to just get size of the array, but that might result race conditions + // Giving away Session is a bad idea, as that brakes OOP principles + return sessions.lastIndexOf(session); + } +*/ + +} \ No newline at end of file diff --git a/src/main/java/com/sergeev/controlpanel/utils/Utils.java b/src/main/java/com/sergeev/controlpanel/utils/Utils.java index aafa17f..86c55c2 100644 --- a/src/main/java/com/sergeev/controlpanel/utils/Utils.java +++ b/src/main/java/com/sergeev/controlpanel/utils/Utils.java @@ -1,19 +1,10 @@ package com.sergeev.controlpanel.utils; -import com.google.gson.Gson; import com.google.gson.JsonObject; -import com.google.gson.JsonParser; -import com.sergeev.controlpanel.controller.MainController; -import com.sergeev.controlpanel.model.AbstractModel; -import org.json.simple.JSONArray; -import org.json.simple.JSONObject; -import org.json.simple.parser.JSONParser; import org.slf4j.Logger; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; -import java.util.*; - /** * Created by dmitry-sergeev on 22.09.15. */ @@ -26,5 +17,44 @@ public static ResponseEntity status(int status){ jsonObject.addProperty("status", HttpStatus.valueOf(status).name()); return new ResponseEntity<>(jsonObject.toString(), HttpStatus.valueOf(status)); } +/* + public static KeyPair readKeypair(final InputStream is, final char[] password) throws IOException { + PasswordFinder passwordFinder = password != null ? new StaticPasswordFinder(password) : null; + + KeyPair kp = null; + try { + // read the stream as a PEM encoded + try { + + final PemReader pem = new PemReader(new InputStreamReader(is), passwordFinder); + try { + // Skip over entries in the file which are not KeyPairs + do { + final Object o = pem.readObject(); + if (o == null) + break; // at end of file + else if (o instanceof KeyPair) + kp = (KeyPair) o; + } while (kp == null); + } + finally { + pem.close(); + } + } + catch (EncryptionException e) { + throw new IOException("Error reading PEM stream: " + e.getMessage(), e); + } + } + finally { + is.close(); + } + + // Cast the return to a KeyPair (or, if there is no [valid] return, throw an exception) + if (kp != null) + return kp; + else + throw new IOException("Stream " + is + " did not contain a PKCS8 KeyPair"); + } +*/ } diff --git a/src/main/resources/cp.der b/src/main/resources/cp.der new file mode 100644 index 0000000..5a7f351 Binary files /dev/null and b/src/main/resources/cp.der differ diff --git a/src/main/resources/cp.id_rsa.pub b/src/main/resources/cp.id_rsa.pub new file mode 100644 index 0000000..5288f10 --- /dev/null +++ b/src/main/resources/cp.id_rsa.pub @@ -0,0 +1 @@ +ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDYCj4uihLqTI2c+A++p0tXwWCnIh3F8m2OD7h4OtJdoF8Q4Lz3/Ziq0X+BuZ2QRmVXeJkBQMT/z8E1iOktSRGYgZrVqGDdOsAiu8g6PTbnE3BSsqa5pUJ4mdZ6xc5l0xRrGb05TN8qw/OtYbcnC0E7ya+sM1JXJBG5Xosz+QrRanTJZrtBXjjWD82yJAuvypX4g3tbl156ZZKN8PIYRVFEMvzxwz36kKLfCagfgFFfDnG+38rLPqDbKTvx5NspSupdmis2I8tg5FfKTxX8RygrTEzjPoxRVLGSoYiYEDw6V9a3COE9sxjXyGT38fqFQUqIPGNmbBFYY7IYg82IFWhf ControlPanel diff --git a/src/main/resources/cp.pem b/src/main/resources/cp.pem new file mode 100644 index 0000000..71fc39a --- /dev/null +++ b/src/main/resources/cp.pem @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEowIBAAKCAQEA2Ao+LooS6kyNnPgPvqdLV8FgpyIdxfJtjg+4eDrSXaBfEOC8 +9/2YqtF/gbmdkEZlV3iZAUDE/8/BNYjpLUkRmIGa1ahg3TrAIrvIOj025xNwUrKm +uaVCeJnWesXOZdMUaxm9OUzfKsPzrWG3JwtBO8mvrDNSVyQRuV6LM/kK0Wp0yWa7 +QV441g/NsiQLr8qV+IN7W5deemWSjfDyGEVRRDL88cM9+pCi3wmoH4BRXw5xvt/K +yz6g2yk78eTbKUrqXZorNiPLYORXyk8V/EcoK0xM4z6MUVSxkqGImBA8OlfWtwjh +PbMY18hk9/H6hUFKiDxjZmwRWGOyGIPNiBVoXwIDAQABAoIBACbtiZDXPltLmgTb +yfJ/sJrKdIEJK7Y8XbNIb+PyLW/Dcv3WkRZacsTs5P3aFWMm3CHr0B4irpytsdHU +rreDQBFr4Rt4sKOMb4ySq5ya5Sa0IPw1xscS2SxkA/qxY+SDKV23EJqfmGLbVjA6 +uEbnx0RfrjDoOoELNcpiF9Ewodtj74ootNEU0fROrmgDFi/yEmSJSQqvghrOi5yZ +K4w9Nq43JQM/ejhR8TiTnhmYpWfehlmSOL6uf1NDEhlE+6WOWzUm4cevlCn3EsTO +CIFRBOXJ8m9ZUdf44yQoH/DsvurLYoppBwmbgPLlsu0fPEMvbqTjPJt1dBFb1bD7 +wQbneRkCgYEA8Q4gYuezCgu2x4Ci5+LJf+3uXMBjqcl88btcRY73VgtpT2eoBjyk +66smpiI+MNPK43OIstHXrC+9qK8GqXR6ck/sLACOHgosXnf8L4o7S4tq/F9ciWhu +OIlMfZNHFejd927x6+ngluteIZw9fPNlAGDNileNRboc9sZJaPFztOsCgYEA5W8X +dI72RqJFtwfT8mqv6jdVansxWPD51O8t5MLdqtnMA6MzAq12nnTWs5LubUY4QbPW +2/LqQvSZDHUo9vfHXoFag9NJgKXW77h/kpX/2Ovz8/xEql5yQKZocW0hbW7uMO1E +QaSrwZWOtVNKUrljBkWnvi75g27uzZma+EpmTV0CgYAvgNwrAYQL38EWUahI0C1r +U4UcrCE3zWgc7xJA9uqQ/1CygDfhesP5WVIVfTwKPUKHTjZLHwVEfmf+vPcwH68d +pdhh134qN1EFENoWuEP1IDVmJJjEz1qhM5VqTcK9c5WCdE+icQV8WEfFkdegLwrh +ZzI6KATED+gzTWIcFzD5HwKBgQDk7u7mnWhcnrsVoTf5oj8aZFBUycw5xWpk8KxI +obDyNBUAZC4YM+Iyxr8dvDUw0Gp+FOcF3eOnH84/wgA4PpGvWT9qXr/vIIvR87VI +HWiHmRl5kXUq0scKf4Gj/JLoUVJXe8kp/xhrN8KIaC23Ucjfj02L1e+fGGgsu2MI +8aQW+QKBgDC+1nGJjfaph469DqPvormRFwFLrfy7jpnHAdHLOYhAj0+/sYPwYenE +vINtmtq8OQXOrE/hMO92b+dUDtP17O3n4yFWSKzYIMKDC4Gaq4Q6HgsfP2XimQUb +v0EkqviZ/G5WK4iYz6mWxdJG+o+365z4CO3BIAP2vpPa22t444KT +-----END RSA PRIVATE KEY----- \ No newline at end of file