I am trying to get some data from a REST web service. So far I can get the data correctly if I don't use HTTPS with this code working as expected -
val client = Http.client.newService(s"$host:80")
val r = http.Request(http.Method.Post, "/api/search/")
r.host(host)
r.content = queryBuf
r.headerMap.add(Fields.ContentLength, queryBuf.length.toString)
r.headerMap.add("Content-Type", "application/json;charset=UTF-8")
val response: Future[http.Response] = client(r)
But when I am trying to get the same data from https request (Following this link)
val client = Http.client.withTls(host).newService(s"$host:443")
val r = http.Request(http.Method.Post, "/api/search/")
r.headerMap.add("Cookie", s"_elfowl=${authToken.elfowlToken}; dc=$dc")
r.host(host)
r.content = queryBuf
r.headerMap.add(Fields.ContentLength, queryBuf.length.toString)
r.headerMap.add("Content-Type", "application/json;charset=UTF-8")
r.headerMap.add("User-Agent", authToken.userAgent)
val response: Future[http.Response] = client(r)
I get the error
Remote Info: Not Available at remote address: searchservice.com/10.59.201.29:443. Remote Info: Not Available, flags=0x08
I can curl the same endpoint with 443 port and it returns the right result. Can anyone please help me troubleshoot the issue ?
Few things to check:
withTls(host)
needs to be the host name that is in the certificate of server (as opposed to the the ip for instance)
you can try:
Http.client.withTlsWithoutValidation
to verify the above.
Also you might want to verify if the server checks that the host header is set, and if so, you might want to include it:
val withHeader = new SimpleFilter[http.Request, http.Response] {
override def apply(request: http.Request, service: HttpService): Future[http.Response] = {
request.host_=(host)
service(request)
}
}
withHeader.andThen(client)
more info on host header:
What is http host header?
Related
Is it possible to reload at runtime certificates in a web server using API of Akka-HTTP? I would that the HttpsConnectionContext is reloaded without shutdown of server. Is there a way to do that? I've already implemented a mechanism that read the renew of certificate but my problem is to reload the context at runtime.
Below I show how my server is started:
log.info("HTTPS ENABLED!")
val https: HttpsConnectionContext = newHttpsConnectionContext()
val (host, port, connectionContext) = ("0.0.0.0", 8080, https)
log.debug(s" Binding RESTful Web Services ... https://$host:$port/")
val bindingFuture =
Http().bindAndHandle(
endpoints,
host,
port,
connectionContext
)
bindingFuture.onComplete {
case Success(bind) =>
log.info(s"HTTPS server binding $bind")
binding = Some(bind)
log.warn(
s" Service online at https://$host:$port/"
)
case Failure(ex) =>
log.error(
s" Failed to bind to https://$host:$port/ - Error : ${ex.getMessage}"
)
close()
}
}
def newHttpsConnectionContext(): HttpsConnectionContext = {
import myService.TlsSettings
log.debug(
s"Creating a new HTTPS Connection Context between my Service (Server) and Clients..."
)
val sslParameters: Option[SSLParameters] = None
val sslConnectionContext: HttpsConnectionContext =
ConnectionContext.https(
TlsSettings(
ApplicationProperties.clientCert,
ApplicationProperties.clientPrivKey
).sslContext,
None,
Some(SSL_CIPHER_SUITES),
Some(SSL_PROTOCOLS),
Some(TLSClientAuth.Need),
sslParameters
)
log.info(
s"HTTPS Connection Context my Service <--> Clients created! $sslConnectionContext"
)
sslConnectionContext
}
I was trying to follow the ktor documentation for Raw Sockets and in specific the part related to secured sockets (https://ktor.io/servers/raw-sockets.html):
runBlocking {
val socket = aSocket(ActorSelectorManager(ioCoroutineDispatcher)).tcp().connect(InetSocketAddress("google.com", 443)).tls()
val w = socket.openWriteChannel(autoFlush = false)
w.write("GET / HTTP/1.1\r\n")
w.write("Host: google.com\r\n")
w.write("\r\n")
w.flush()
val r = socket.openReadChannel()
println(r.readUTF8Line())
}
You can adjust a few optional parameters for the TLS connection:
suspend fun Socket.tls(
trustManager: X509TrustManager? = null,
randomAlgorithm: String = "NativePRNGNonBlocking",
serverName: String? = null,
coroutineContext: CoroutineContext = ioCoroutineDispatcher
): Socket
But the NativePRNGNonBlocking SecureRandom algorithm is not available on Windows, so my only option was to use SHA1PRNG (https://docs.oracle.com/javase/8/docs/technotes/guides/security/SunProviders.html#SecureRandomImp)
This is the code I'm running to connect to a listening socket :
socket = aSocket(ActorSelectorManager(Dispatchers.IO)).tcp().connect(InetSocketAddress(host, port))
.tls(Dispatchers.IO, randomAlgorithm = "SHA1PRNG")
Unfortunately, I always receive the same error: "Channel was closed"
If I remove tls, keeping only the raw socket:
socket = aSocket(ActorSelectorManager(Dispatchers.IO)).tcp().connect(InetSocketAddress(host, port))
Everything works as expected.
Does anyone has used Ktor Secure Sockets in Windows ? (Unfortunately, Ktor's documentation still has a long way to go).
Thanks,
J
I'm using Akka (version 2.5.18) to send JSON strings to a specific server via https. I have used a poolRouter (balancing-pool with 10 instances) in order to create a pool of actors that are going to send JSONs (generated from different customers) to a single server:
val router: ActorRef = system.actorOf(
FromConfig.props(Props(new SenderActor(configuration.getString("https://server.com"), this.self))),
"poolRouter"
)
The project specification says that the requests can also be sent using curl:
curl -X PUT --cert certificate.pem --key private.key -H 'Content-Type: application / json' -H 'cache-control: no-cache' -d '[{"id" : "test"}] 'https://server.com'
Where "certificate.pem" is the tls certificate of the customer and "private.key" is the private key used to generate the CSR of the customer.
I'm using a balancing-pool because I will have a very big set of certificates (one for each customer) and I need to send the requests concurrently.
My approach is to have a "SenderActor" class that will be created by the balancing pool. Each actor, upon the reception of a message with a "customerId" and the JSON data generated by this customer, will send a https request:
override def receive: Receive = {
case Data(customerId, jsonData) =>
send(customerId(cid, jsonData))
Each SenderActor will read the certificate (and the private key) based on a path using the customerId. For instance, the customerId: "cust1" will have their certificate and key stored in "/home/test/cust1". This way, the same actor class can be used for all the customers.
According to the documentation, I need to create a HttpsConnectionContext in order to send the different requests:
def send(customerId: String, dataToSend): Future[HttpResponse] = {
// Create the request
val req = HttpRequest(
PUT,
uri = "https://server.com",
entity = HttpEntity(`application/x-www-form-urlencoded` withCharset `UTF-8`, dataToSend),
protocol = `HTTP/1.0`)
val ctx: SSLContext = SSLContext.getInstance("TLS")
val permissiveTrustManager: TrustManager = new X509TrustManager() {
override def checkClientTrusted(chain: Array[X509Certificate], authType: String): Unit = {}
override def checkServerTrusted(chain: Array[X509Certificate], authType: String): Unit = {}
override def getAcceptedIssuers(): Array[X509Certificate] = Array.empty
}
ctx.init(Array.empty, Array(permissiveTrustManager), new SecureRandom())
val httpsConnContext: HttpsConnectionContext = ConnectionContext.https(ctx)
// Send the request
Http(system).singleRequest(req, httpsConnContext)
}
The problem I have is that I don't have any clue about how to "set the certificate and the key" in the request, so that the server accepts them.
For instance, I can read the certificate using the following code:
import java.util.Base64
val certificate: String => String = (customer: String) => IO {
Source.fromInputStream(getClass.getClassLoader
.getResourceAsStream("/home/test/".concat(customer).concat("_cert.pem")))
.getLines().mkString
}.unsafeRunSync()
val decodedCertificate = Base64.getDecoder.decode(certificate(customerId)
.replaceAll(X509Factory.BEGIN_CERT, "").replaceAll(X509Factory.END_CERT, ""))
val cert: Certificate = CertificateFactory.getInstance("X.509")
.generateCertificate(new ByteArrayInputStream(decodedCertificate))
But I don't know how to "set" this certificate and the private key in the request (which is protected by a passphrase), so that the server accepts it.
Any hint or help would be greatly appreciated.
The following allows making a https request and identifying yourself with a private key from a x.509 certificate.
The following libraries are used to manage ssl configuration and to make https calls:
ssl-config
akka-http
Convert your pem certificate to pks12 format as defined here
openssl pkcs12 -export -out certificate.pfx -inkey privateKey.key -in certificate.crt
Define key-store in your application.conf. It supports only pkcs12 and because of
this step 1 is required.
ssl-config {
keyManager {
stores = [
{
type = "pkcs12"
path = "/path/to/pkcs12/cetificate"
password = changeme //the password is set when using openssl
}
]
}
}
Load ssl config using special akka trait DefaultSSLContextCreation
import akka.actor.ActorSystem
import akka.actor.ExtendedActorSystem
import akka.http.scaladsl.DefaultSSLContextCreation
import com.typesafe.sslconfig.akka.AkkaSSLConfig
import com.typesafe.sslconfig.ssl.SSLConfigFactory
class TlsProvider(val actorSystem: ActorSystem) extends DefaultSSLContextCreation {
override protected def sslConfig: AkkaSSLConfig =
throw new RuntimeException("Unsupported behaviour when creating new sslConfig")
def httpsConnectionContext() = {
val akkaSslConfig =
new AkkaSSLConfig(system.asInstanceOf[ExtendedActorSystem], SSLConfigFactory.parse(system.settings.config))
createClientHttpsContext(akkaSslConfig)
}
}
Create a https context and use in http connection pool.
Http(actorSystem).cachedHostConnectionPoolHttps[RequestContext](
host = host,
port = portValue,
connectionContext = new TlsProvider(actorSystem).httpsConnectionContext()
)
Or set connection context to Http(actorSystem).singleRequest method.
In summary, I used ssl-config library to manage certificates instead of doing it programmatically yourself. By defining a keyManager in a ssl-config, any http request done with help of custom httpsConnectionContext will use the certificate to identify the caller/client.
I focused on describing how to establish a https connection using client certificate. Any dynamic behavior for managing multiple certificates is omitted. But I hope this code should be able give you understanding how to proceed.
I have a Scala app that runs an akka-http webserver on a custom port, let's say 8000.
Until a while ago, it would only handle http:// requests, but recently I switched to https://.
Some of the clients have the link bookmarked and keep getting the no connection error because they try the address with http:// instead of https:// and they keep forgetting why it happens.
I tried binding two services to the same port but failed because only the first one gets binded.
Http().bind(interface = "0.0.0.0", port = Global.settings.restPort, connectionContext = httpsContext)
Http().bind(interface = "0.0.0.0", port = Global.settings.restPort)
All I need from the http:// server is to return a 301 code and redirect to the same address, but with https protocol.
How can I achieve that?
As others have commented, you can't bind the HTTP and HTTPS servers to the same port. You can have both servers running on separate ports and redirect all HTTP traffic to the HTTPS server using Akka-http's scheme() and redirect():
val hostName = "www.example.com"
val portHttp = 8080
val portHttps = 8443
val route =
scheme("http") {
extract(_.request.uri) { uri =>
redirect( uri.withScheme("https").withAuthority(hostName, portHttps),
StatusCodes.MovedPermanently
)
}
} ~
pathSingleSlash {
get {
complete( HttpEntity( ContentTypes.`text/html(UTF-8)`,
"Welcome to Akka-HTTP!"
) )
}
}
Http().bindAndHandle(route, hostName, portHttp)
Http().bindAndHandle(route, hostName, portHttps, connectionContext = httpsContext)
Note that there is no need for applying withAuthority() if you're using standard HTTP and HTTPS ports (i.e. 80 and 443).
I have a client that I generate using the WSDL that I have. When I tried to connect to this WebService, I get the following error:
javax.xml.ws.soap.SOAPFaultException: An error occurred when verifying security for the message.
at org.apache.cxf.jaxws.JaxWsClientProxy.invoke(JaxWsClientProxy.java:161)
The actual server that hosts the WebService expects a SOAP message that contains the security header like:
<soapenv:Header>
<o:Security s:mustUnderstand="1" xmlns:o="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
<o:UsernameToken u:Id="XXXX">
<o:Username>XXXX</o:Username>
<o:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText">XXXX</o:Password>
</o:UsernameToken>
</o:Security>
</soapenv:Header>
How to get this along with my SOAP message that gets generated when the request is made? This is what I do in my code to call the WebService:
val factory = new org.apache.cxf.jaxws.JaxWsProxyFactoryBean()
factory.setServiceClass(classOf[MyWebService])
// if there is an address, then let's set it!
factory.setAddress("https://my/server/address/MyWebService.svc")
// log incoming and outgoing
factory.getInInterceptors.add(new org.apache.cxf.interceptor.LoggingInInterceptor())
factory.getOutInterceptors.add(new org.apache.cxf.interceptor.LoggingOutInterceptor())
val myServiceClient = factory.create().asInstanceOf[MyWebService]
myServiceClient.myMethod("param")
Any suggestions?
Here is what you have to do to get this working:
1. Add the following dependencies (if you have not added it already)
"org.apache.cxf" % "cxf-rt-ws-security" % "3.1.4",
"org.apache.ws.security" % "wss4j" % "1.6.13",
2. In the place where you set information in the client proxy, add the following:
val myWebService = factory.create().asInstanceOf[MyWebService]
val proxy = ClientProxy.getClient(myWebService)
val endpoint = proxy.getEndpoint
import collection.JavaConversions._
val callbackHandler = new CallbackHandler {
override def handle(callbacks: Array[Callback]): Unit = {
val pc = callbacks(0).asInstanceOf[WSPasswordCallback]
pc.setPassword("your password")
}
}
val outProps = Map(
WSHandlerConstants.ACTION -> WSHandlerConstants.USERNAME_TOKEN,
WSHandlerConstants.USER -> "Your user name",
WSHandlerConstants.PASSWORD_TYPE -> WSConstants.PW_TEXT
)
outProps.updated(WSHandlerConstants.PW_CALLBACK_REF, callbackHandler)
val wssOut = new WSS4JOutInterceptor(outProps)
endpoint.getOutInterceptors.add(wssOut)
// set the credentials, timeouts etc
proxy.getRequestContext().put("password", "your password")