on 10-22-2020 2:41 PM
Hi there!
I am working on a project where I have to connect to an on-premise system through SFTP with CPI and Cloud Connector (Cloud Foundry enviroment).
I have followed this official guide but it's not working: https://help.sap.com/viewer/cca91383641e40ffbe03bdc78f00f681/Cloud/en-US/cd1583775afa43f0bb9ec69d9db...
Whenever I try to run it, I get a SOCKS5 command failed with status: NETWORK_UNREACHABLE error, but when I check from Connectivity test in CPI, everything's fine. I can get valid JWT and Location ID, so I can't see where the problem is.
Below I will show you my entire code.
import com.sap.gateway.ip.core.customdev.util.Message;
import java.lang.Object;
import java.util.HashMap;
import groovy.json.*;
import org.cloudfoundry.identity.client.UaaContextFactory;
import org.cloudfoundry.identity.client.UaaContext;
import org.cloudfoundry.identity.client.token.TokenRequest;
import org.cloudfoundry.identity.uaa.oauth.token.CompositeAccessToken;
import org.cloudfoundry.identity.client.token.GrantType;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.SocketAddress;
import java.net.SocketException;
import java.nio.ByteBuffer;
import java.util.Base64; // or any other library for base64 encoding
import org.json.JSONArray; // or any other library for JSON objects
import org.json.JSONObject; // or any other library for JSON objects
import org.json.JSONException; // or any other library for JSON objects
public class ConnectivitySocks5ProxySocket extends Socket {
private static final byte SOCKS5_VERSION = 0x05;
private static final byte SOCKS5_JWT_AUTHENTICATION_METHOD = (byte) 0x80;
private static final byte SOCKS5_JWT_AUTHENTICATION_METHOD_VERSION = 0x01;
private static final byte SOCKS5_COMMAND_CONNECT_BYTE = 0x01;
private static final byte SOCKS5_COMMAND_REQUEST_RESERVED_BYTE = 0x00;
private static final byte SOCKS5_COMMAND_ADDRESS_TYPE_IPv4_BYTE = 0x01;
private static final byte SOCKS5_COMMAND_ADDRESS_TYPE_DOMAIN_BYTE = 0x03;
private static final byte SOCKS5_AUTHENTICATION_METHODS_COUNT = 0x01;
private static final int SOCKS5_JWT_AUTHENTICATION_METHOD_UNSIGNED_VALUE = 0x80 & 0xFF;
private static final byte SOCKS5_AUTHENTICATION_SUCCESS_BYTE = 0x00;
private static final String SOCKS5_PROXY_HOST_PROPERTY = "onpremise_proxy_host";
private static final String SOCKS5_PROXY_PORT_PROPERTY = "onpremise_socks5_proxy_port";
private final String jwtToken;
private final String sccLocationId;
public ConnectivitySocks5ProxySocket(String jwtToken, String sccLocationId) {
this.jwtToken = jwtToken;
this.sccLocationId = sccLocationId != null ? Base64.getEncoder().encodeToString(sccLocationId.getBytes()) : "";
}
protected InetSocketAddress getProxyAddress() {
try {
JSONObject jsonObj = new JSONObject(System.getenv("VCAP_SERVICES"));
def jsonSlurper = new JsonSlurper()
def object = jsonSlurper.parseText(jsonObj.toString())
String connProxyHost = object.connectivity[0].credentials.onpremise_proxy_host
int connProxyPort = Integer.parseInt(object.connectivity[0].credentials.onpremise_socks5_proxy_port);
return new InetSocketAddress(connProxyHost, connProxyPort);
} catch (JSONException ex) {
throw new IllegalStateException("Unable to extract the SOCKS5 proxy host and port", ex);
}
}
private JSONObject extractEnvironmentCredentials() throws JSONException {
JSONObject jsonObj = new JSONObject(System.getenv("VCAP_SERVICES"));
JSONArray jsonArr = jsonObj.getJSONArray("connectivity");
return jsonArr.getJSONObject(0).getJSONObject("credentials");
}
@Override
public void connect(SocketAddress endpoint, int timeout) throws IOException {
super.connect(getProxyAddress(), timeout);
OutputStream outputStream = getOutputStream();
executeSOCKS5InitialRequest(outputStream);
executeSOCKS5AuthenticationRequest(outputStream);
executeSOCKS5ConnectRequest(outputStream, (InetSocketAddress) endpoint);
}
private void executeSOCKS5InitialRequest(OutputStream outputStream) throws IOException {
byte[] initialRequest = createInitialSOCKS5Request();
outputStream.write(initialRequest);
assertServerInitialResponse();
}
private byte[] createInitialSOCKS5Request() throws IOException {
ByteArrayOutputStream byteArraysStream = new ByteArrayOutputStream();
try {
byteArraysStream.write(SOCKS5_VERSION);
byteArraysStream.write(SOCKS5_AUTHENTICATION_METHODS_COUNT);
byteArraysStream.write(SOCKS5_JWT_AUTHENTICATION_METHOD);
return byteArraysStream.toByteArray();
} finally {
byteArraysStream.close();
}
}
private void assertServerInitialResponse() throws IOException {
InputStream inputStream = getInputStream();
int versionByte = inputStream.read();
if (SOCKS5_VERSION != versionByte) {
throw new SocketException(String.format("Unsupported SOCKS version - expected %s, but received %s", SOCKS5_VERSION, versionByte));
}
int authenticationMethodValue = inputStream.read();
if (SOCKS5_JWT_AUTHENTICATION_METHOD_UNSIGNED_VALUE != authenticationMethodValue) {
throw new SocketException(String.format("Unsupported authentication method value - expected %s, but received %s",
SOCKS5_JWT_AUTHENTICATION_METHOD_UNSIGNED_VALUE, authenticationMethodValue));
}
}
private void executeSOCKS5AuthenticationRequest(OutputStream outputStream) throws IOException {
byte[] authenticationRequest = createJWTAuthenticationRequest();
outputStream.write(authenticationRequest);
assertAuthenticationResponse();
}
private byte[] createJWTAuthenticationRequest() throws IOException {
ByteArrayOutputStream byteArraysStream = new ByteArrayOutputStream();
try {
byteArraysStream.write(SOCKS5_JWT_AUTHENTICATION_METHOD_VERSION);
byteArraysStream.write(ByteBuffer.allocate(4).putInt(jwtToken.getBytes().length).array());
byteArraysStream.write(jwtToken.getBytes());
byteArraysStream.write(ByteBuffer.allocate(1).put((byte) sccLocationId.getBytes().length).array());
byteArraysStream.write(sccLocationId.getBytes());
return byteArraysStream.toByteArray();
} finally {
byteArraysStream.close();
}
}
private void assertAuthenticationResponse() throws IOException {
InputStream inputStream = getInputStream();
int authenticationMethodVersion = inputStream.read();
if (SOCKS5_JWT_AUTHENTICATION_METHOD_VERSION != authenticationMethodVersion) {
throw new SocketException(String.format("Unsupported authentication method version - expected %s, but received %s",
SOCKS5_JWT_AUTHENTICATION_METHOD_VERSION, authenticationMethodVersion));
}
int authenticationStatus = inputStream.read();
if (SOCKS5_AUTHENTICATION_SUCCESS_BYTE != authenticationStatus) {
throw new SocketException("Authentication failed!");
}
}
private void executeSOCKS5ConnectRequest(OutputStream outputStream, InetSocketAddress endpoint) throws IOException {
byte[] commandRequest = createConnectCommandRequest(endpoint);
outputStream.write(commandRequest);
assertConnectCommandResponse();
}
private byte[] createConnectCommandRequest(InetSocketAddress endpoint) throws IOException {
String host = endpoint.getHostName();
int port = endpoint.getPort();
ByteArrayOutputStream byteArraysStream = new ByteArrayOutputStream();
try {
byteArraysStream.write(SOCKS5_VERSION);
byteArraysStream.write(SOCKS5_COMMAND_CONNECT_BYTE);
byteArraysStream.write(SOCKS5_COMMAND_REQUEST_RESERVED_BYTE);
byte[] hostToIPv4 = parseHostToIPv4(host);
if (hostToIPv4 != null) {
byteArraysStream.write(SOCKS5_COMMAND_ADDRESS_TYPE_IPv4_BYTE);
byteArraysStream.write(hostToIPv4);
} else {
byteArraysStream.write(SOCKS5_COMMAND_ADDRESS_TYPE_DOMAIN_BYTE);
byteArraysStream.write(ByteBuffer.allocate(1).put((byte) host.getBytes().length).array());
byteArraysStream.write(host.getBytes());
}
byteArraysStream.write(ByteBuffer.allocate(2).putShort((short) port).array());
return byteArraysStream.toByteArray();
} finally {
byteArraysStream.close();
}
}
private void assertConnectCommandResponse() throws IOException {
InputStream inputStream = getInputStream();
int versionByte = inputStream.read();
if (SOCKS5_VERSION != versionByte) {
throw new SocketException(String.format("Unsupported SOCKS version - expected %s, but received %s", SOCKS5_VERSION, versionByte));
}
int connectStatusByte = inputStream.read();
assertConnectStatus(connectStatusByte);
readRemainingCommandResponseBytes(inputStream);
}
private void assertConnectStatus(int commandConnectStatus) throws IOException {
if (commandConnectStatus == 0) {
return;
}
String commandConnectStatusTranslation;
switch (commandConnectStatus) {
case 1:
commandConnectStatusTranslation = "FAILURE";
break;
case 2:
commandConnectStatusTranslation = "FORBIDDEN";
break;
case 3:
commandConnectStatusTranslation = "NETWORK_UNREACHABLE";
break;
case 4:
commandConnectStatusTranslation = "HOST_UNREACHABLE";
break;
case 5:
commandConnectStatusTranslation = "CONNECTION_REFUSED";
break;
case 6:
commandConnectStatusTranslation = "TTL_EXPIRED";
break;
case 7:
commandConnectStatusTranslation = "COMMAND_UNSUPPORTED";
break;
case 8:
commandConnectStatusTranslation = "ADDRESS_UNSUPPORTED";
break;
default:
commandConnectStatusTranslation = "UNKNOWN";
break;
}
throw new SocketException("SOCKS5 command failed with status: " + commandConnectStatusTranslation);
}
private byte[] parseHostToIPv4(String hostName) {
byte[] parsedHostName = null;
String[] virtualHostOctets = hostName.split("\\.", -1);
int octetsCount = virtualHostOctets.length;
if (octetsCount == 4) {
try {
byte[] ipOctets = new byte[octetsCount];
for (int i = 0; i < octetsCount; i++) {
int currentOctet = Integer.parseInt(virtualHostOctets[i]);
if ((currentOctet < 0) || (currentOctet > 255)) {
throw new IllegalArgumentException(String.format("Provided octet %s is not in the range of [0-255]", currentOctet));
}
ipOctets[i] = (byte) currentOctet;
}
parsedHostName = ipOctets;
} catch (IllegalArgumentException ex) {
return null;
}
}
return parsedHostName;
}
private void readRemainingCommandResponseBytes(InputStream inputStream) throws IOException {
inputStream.read(); // skipping over SOCKS5 reserved byte
int addressTypeByte = inputStream.read();
if (SOCKS5_COMMAND_ADDRESS_TYPE_IPv4_BYTE == addressTypeByte) {
for (int i = 0; i < 6; i++) {
inputStream.read();
}
} else if (SOCKS5_COMMAND_ADDRESS_TYPE_DOMAIN_BYTE == addressTypeByte) {
int domainNameLength = inputStream.read();
int portBytes = 2;
inputStream.read(new byte[domainNameLength + portBytes], 0, domainNameLength + portBytes);
}
}
}
// THE CODE WHERE I CALL THIS CLASS:
JSONObject jsonObj = new JSONObject(System.getenv("VCAP_SERVICES"));
def jsonSlurper = new JsonSlurper()
def object = jsonSlurper.parseText(jsonObj.toString())
String connProxyHost = object.connectivity[0].credentials.onpremise_proxy_host
int connProxyPort = Integer.parseInt(object.connectivity[0].credentials.onpremise_proxy_http_port);
// get value of "clientid" and "clientsecret" from the environment variables
String clientid = object.connectivity[0].credentials.clientid;
String clientsecret = object.connectivity[0].credentials.clientsecret;
// get the URL to xsuaa from the environment variables
URI xsuaaUrl = new URI(object.connectivity[0].credentials.token_service_url);
// make request to UAA to retrieve access token
UaaContextFactory factory = UaaContextFactory.factory(xsuaaUrl).authorizePath("/oauth/authorize").tokenPath("/oauth/token");
TokenRequest tokenRequest = factory.tokenRequest();
tokenRequest.setGrantType(GrantType.CLIENT_CREDENTIALS);
tokenRequest.setClientId(clientid);
tokenRequest.setClientSecret(clientsecret);
UaaContext xsuaaContext = factory.authenticate(tokenRequest);
def accessToken = xsuaaContext.getToken();
ConnectivitySocks5ProxySocket ourConnection = new ConnectivitySocks5ProxySocket(accessToken.toString(), "<Location_Id>");
SocketAddress socketAddressTry = new InetSocketAddress("<on_premise_host_url>", <on_premise_port>);
ourConnection.connect(socketAddressTry, 30000);
Did you set a Location ID in both the Cloud Connector and the Coding (when creating the instance for the ConnectivitySocks5ProxySocket)?
I ask because I try to do the same at the moment (CPI-CF -> SOCKS5/CC -> On-Premise System). When I tried to set Location ID (even when I set it such that the value in CC and the Java-Coding was matching) I got the same result. Working in Connectivity Test but not working from Groovy Script (NETWORK UNREACHABLE). When I did not set the Location ID (both in CC and in the Java Coding) I get the response (FORBIDDEN). So that's why I guess somehow with the above Java Coding I am not hitting the "right" Cloud Connector, at least from within the CPI instance.
But I have no solution at the moment to get a connection from within the CPI instance
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
Hi @jozsef_huszko
Do you get a solution with the connection SOCK5 JAVA?. I can help you. I have done this but i have another problem. If you have a solution please apreciate your help. Share me your experience .
Thanks
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
Hi Alfredo,
can you share how you got the SOCKS5 Java-Coding provided by SAP to work from within the CPI/Cloud Foundry? I tried to do the same as Jozsef (Link) and I seem to get one stup further since I have at least reached the Cloud Connector but cannot get through the cloud connector.
Best Regards and thanks in advance
Patrick
User | Count |
---|---|
93 | |
10 | |
10 | |
9 | |
9 | |
7 | |
6 | |
5 | |
5 | |
4 |
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.