jobserv/src/main/java/JobServ/JobServClient.java

358 lines
12 KiB
Java
Raw Normal View History

/*
* JobServClient
*
* v1.0
*
* May 18, 2019
*/
2019-05-17 01:28:26 -07:00
package JobServ;
import io.grpc.ManagedChannel;
import io.grpc.ManagedChannelBuilder;
import io.grpc.StatusRuntimeException;
2019-05-19 12:21:00 -07:00
import io.grpc.netty.GrpcSslContexts;
import io.grpc.netty.NettyChannelBuilder;
import io.netty.handler.ssl.SslContext;
import io.netty.handler.ssl.SslContextBuilder;
import javax.net.ssl.SSLException;
import java.io.File;
2019-05-18 12:20:12 -07:00
import java.util.InputMismatchException;
2019-05-17 01:28:26 -07:00
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.Scanner;
/*
* The JobServClient class extends the gRPC stub code
* Additionally, it plugs a command line interface into the API code.
*/
2019-05-17 01:28:26 -07:00
public class JobServClient {
2019-05-19 12:21:00 -07:00
/*
* The client should not use the same logging module as the server.
2019-05-17 01:28:26 -07:00
* In a more robust product the server logging module will take advantage of system level
* log aggregators such as journalctl, which the client should not be writing to on the users system
*/
private static final Logger logger = Logger.getLogger(JobServClient.class.getName());
private final ManagedChannel channel;
2019-05-19 12:21:00 -07:00
/*
* blockingStub is used when the client needs to block until the server responds
2019-05-17 01:28:26 -07:00
* the client doesnt nessesarily need to support asynchronously firing off commands
2019-05-19 12:21:00 -07:00
* in this shell-like interface it would be disconcerting to get multiple returns out of order
2019-05-17 01:28:26 -07:00
*/
private final ShellServerGrpc.ShellServerBlockingStub blockingStub;
2019-05-19 12:21:00 -07:00
/*
* Constructor
* Creates an SslContext from cert, key, and trust store
* Creates a ManagedChannel object from SSL Parameters
* Spawns a new blockingStub for network operations with the server
*/
public JobServClient(String host,
int port,
String trustStore,
String clientCert,
String clientPrivateKey) throws SSLException {
SslContextBuilder builder = GrpcSslContexts.forClient();
builder.trustManager(new File(trustStore));
builder.keyManager(new File(clientCert), new File(clientPrivateKey));
this.channel = NettyChannelBuilder.forAddress(host, port)
.sslContext(builder.build())
.build();
2019-05-17 01:28:26 -07:00
2019-05-19 12:21:00 -07:00
blockingStub = ShellServerGrpc.newBlockingStub(this.channel);
2019-05-17 01:28:26 -07:00
}
2019-05-19 12:21:00 -07:00
/*
* shutdown()
* Gets called when you press cntrl+c
* takes at most 5 seconds to close its connection
*/
2019-05-17 01:28:26 -07:00
public void shutdown() throws InterruptedException {
channel.shutdown().awaitTermination(5, TimeUnit.SECONDS);
}
2019-05-19 12:21:00 -07:00
/*
* getProcessInfo()
* sends the server a request for output from the process identified by 'pid'
* returns process output as string
*/
2019-05-17 01:28:26 -07:00
public String getProcessOutput(int pid) {
logger.info("[+] requesting output");
2019-05-18 12:20:12 -07:00
PIDMessage request = PIDMessage.newBuilder()
.setPid(pid)
2019-05-17 01:28:26 -07:00
.build();
OutputMessage response;
try {
// blocking network operation
response = blockingStub.getOutput(request);
2019-05-17 01:28:26 -07:00
} catch (StatusRuntimeException e) {
2019-05-19 14:16:09 -07:00
logger.log(Level.WARNING, "(API Failure) Request for output failed: " + e.getStatus());
2019-05-18 12:20:12 -07:00
return "<Error connecting to API>";
2019-05-17 01:28:26 -07:00
}
2019-05-18 12:20:12 -07:00
return response.getOutput();
2019-05-17 01:28:26 -07:00
}
2019-05-19 12:21:00 -07:00
/*
* sendNewJobMessage()
* sends a shell command to the api server
* returns new pid of job
*/
2019-05-17 01:28:26 -07:00
public int sendNewJobMessage(String command) {
// thought of escaping this, but the vulnerability is only client side, from client user input.
logger.info("[+] Sending command to server");
NewJobMessage request = NewJobMessage.newBuilder()
.setCommand(command)
.build();
PIDMessage response;
2019-05-17 01:28:26 -07:00
try {
// blocking network operation
response = blockingStub.newJob(request);
2019-05-17 01:28:26 -07:00
} catch (StatusRuntimeException e) {
2019-05-19 14:16:09 -07:00
logger.log(Level.WARNING, "(API Failure) Request for new job failed: " + e.getStatus());
2019-05-17 01:28:26 -07:00
return -1;
}
2019-05-18 12:20:12 -07:00
if(response.getPid() == -1) {
2019-05-17 01:28:26 -07:00
logger.log(Level.WARNING, "New job creation failed server side!");
}
return response.getPid();
}
2019-05-19 12:21:00 -07:00
/*
* getProcessStatus()
* requests running status of process pid
* returns true if process still running else false
*/
2019-05-18 12:20:12 -07:00
public Boolean getProcessStatus(int pid) {
logger.info("[+] Requesting status of a job");
PIDMessage request = PIDMessage.newBuilder()
.setPid(pid)
.build();
StatusMessage response;
try {
// blocking network operation
response = blockingStub.getStatus(request);
} catch (StatusRuntimeException e) {
2019-05-19 14:16:09 -07:00
logger.log(Level.WARNING, "(API Failure) Request for status failed: " + e.getStatus());
2019-05-18 12:20:12 -07:00
return false;
}
return response.getIsRunning();
}
2019-05-19 12:21:00 -07:00
/*
* sends PID to server
* returns process exit code
* returns a 0-255 return code or 277 if still running
* or 278 if error in API
*/
public int getProcessReturn(int pid) {
logger.info("[+] Requesting return code of a job");
PIDMessage request = PIDMessage.newBuilder()
.setPid(pid)
.build();
ReturnMessage response;
try {
// blocking network operation
response = blockingStub.getReturn(request);
} catch (StatusRuntimeException e) {
2019-05-19 14:16:09 -07:00
logger.log(Level.WARNING, "(API Failure) Failed to get return code: " + e.getStatus());
return 278;
}
return response.getProcessReturnCode();
}
2019-05-19 12:21:00 -07:00
/*
* killProcess()
* send a PID to be killed, function returns nothing
* logs warning if job status comes back still running
*/
public void killProcess(int pid) {
logger.info("[+] Killing a job");
PIDMessage request = PIDMessage.newBuilder()
.setPid(pid)
.build();
StatusMessage response;
try {
// blocking network operation
response = blockingStub.killJob(request);
} catch (StatusRuntimeException e) {
2019-05-19 14:16:09 -07:00
logger.log(Level.WARNING, "(API Failure) Failed to send request: " + e.getStatus());
return;
}
if (response.getIsRunning()) {
logger.log(Level.WARNING, "[-] Server failed to kill job!");
}
2019-05-17 01:28:26 -07:00
}
2019-05-19 12:21:00 -07:00
/*
* main()
* Client entrypoint
* Parses arguments and calls the correct function
*/
2019-05-17 01:28:26 -07:00
public static void main(String[] args) throws Exception {
2019-05-18 13:05:02 -07:00
if (args.length == 1 && args[0] == "help"){
outputHelp();
}
2019-05-17 01:28:26 -07:00
// check args
2019-05-19 12:21:00 -07:00
if (args.length < 7) {
System.out.println("Usage: $ ./jobserv-client privatekey, cert, truststore, host, port, command, args");
System.out.println("Or try $ ./jobserv-client help");
2019-05-17 01:28:26 -07:00
return;
}
2019-05-19 12:21:00 -07:00
// start client
// fails if port is improperly formatted or if an ssl exception occurs
2019-05-17 01:28:26 -07:00
JobServClient client;
try {
2019-05-19 13:03:53 -07:00
client = new JobServClient(args[3], Integer.parseInt(args[4]), args[2], args[1], args[0]);
2019-05-17 01:28:26 -07:00
} catch (NumberFormatException e) {
System.out.println("Invalid Port");
return;
2019-05-19 12:21:00 -07:00
} catch (SSLException e) {
System.out.println(e.getMessage());
return;
2019-05-17 01:28:26 -07:00
}
2019-05-19 12:21:00 -07:00
// declare pid up here so that multiple switch cases can use it
int candidatePid;
2019-05-19 12:21:00 -07:00
// parse remaining args
2019-05-19 13:03:53 -07:00
switch (args[5]) {
case "new":
2019-05-19 12:21:00 -07:00
if (args.length < 7) {
System.out.println("Improper formatting, try client --help");
break;
}
2019-05-17 01:28:26 -07:00
String command = "";
2019-05-19 12:21:00 -07:00
for (int token = 6; token < args.length; token++) {
command += " " + args[token];
}
2019-05-17 01:28:26 -07:00
int newProcess = client.sendNewJobMessage(command);
2019-05-18 12:20:12 -07:00
System.out.printf("Process started, assigned pid is %d\n", newProcess);
2019-05-17 01:28:26 -07:00
break;
case "output":
2019-05-19 12:21:00 -07:00
if (args.length != 7) {
System.out.println("Improper formatting, try client --help");
break;
}
try {
2019-05-19 12:21:00 -07:00
candidatePid = Integer.parseInt(args[6]);
} catch (InputMismatchException e) {
2019-05-19 12:21:00 -07:00
System.out.println(args[6] + " is not a valid int, much less a valid pid");
break;
}
String processOutput = client.getProcessOutput(candidatePid);
System.out.println(processOutput);
2019-05-17 01:28:26 -07:00
break;
case "status":
2019-05-19 12:21:00 -07:00
if (args.length != 7) {
System.out.println("Improper formatting, try client --help");
break;
}
2019-05-17 01:28:26 -07:00
try {
2019-05-19 12:21:00 -07:00
candidatePid = Integer.parseInt(args[6]);
2019-05-17 01:28:26 -07:00
} catch (InputMismatchException e) {
2019-05-19 12:21:00 -07:00
System.out.println(args[6] + " is not a valid int, much less a valid pid");
break;
}
Boolean processStatus = client.getProcessStatus(candidatePid);
2019-05-18 12:20:12 -07:00
System.out.printf("Process is currently running? %b\n", processStatus);
2019-05-17 01:28:26 -07:00
break;
case "kill":
2019-05-19 12:21:00 -07:00
if (args.length != 7) {
System.out.println("Improper formatting, try client --help");
break;
}
try {
2019-05-19 12:21:00 -07:00
candidatePid = Integer.parseInt(args[6]);
} catch (InputMismatchException e) {
2019-05-19 12:21:00 -07:00
System.out.println(args[6] + " is not a valid int, much less a valid pid");
break;
}
2019-05-18 12:20:12 -07:00
client.killProcess(candidatePid);
System.out.println("End process request recieved!");
2019-05-17 01:28:26 -07:00
break;
case "return":
2019-05-19 12:21:00 -07:00
if (args.length != 7) {
System.out.println("Improper formatting, try client --help");
break;
}
try {
2019-05-19 12:21:00 -07:00
candidatePid = Integer.parseInt(args[6]);
} catch (InputMismatchException e) {
2019-05-19 12:21:00 -07:00
System.out.println(args[6] + " is not a valid int, much less a valid pid");
break;
}
2019-05-18 12:20:12 -07:00
int returnCode = client.getProcessReturn(candidatePid);
if (returnCode == 277) {
System.out.println("Process is still running");
break;
} else if (returnCode == 278) {
System.out.println("RPC Call error!");
break;
} else {
2019-05-18 12:20:12 -07:00
System.out.printf("Process Exit Code: %d\n", returnCode);
}
default:
System.out.println("Improper command, try 'help'");
break;
}
}
public static void outputHelp() {
System.out.println("... new (command)");
2019-05-18 12:20:12 -07:00
System.out.println("Starts a new process on the server");
System.out.println("... output (pid)");
System.out.println("Garners output from process on server");
System.out.println("... status (pid)");
System.out.println("Returns whether process on server is running");
System.out.println("... return (pid)");
System.out.println("Collects return code from remote process");
System.out.println("... kill (pid)");
System.out.println("Immediately destroys remote process");
2019-05-17 01:28:26 -07:00
}
}