My native image makes a HTTPS call to a remote resource and throws a Caused by: java.security.InvalidAlgorithmParameterException: the trustAnchors parameter must be non-empty
The code is pretty straightforward:
@Controller("/")
public class ExampleController {
private static final Log LOG = LogFactory.getLog(ExampleController.class);
private HttpClient httpClient;
public ExampleController() {
this.httpClient = httpClientFactory.create(HttpClientSettings.adapt(new ClientConfiguration()));
}
@Get("/http")
public String httpClient() throws IOException {
String url = "https://www.google.com/search?q=httpClient";
HttpGet request = new HttpGet(url);
// add request header
HttpResponse response = httpClient.execute(request);
System.out.println("Response Code : "
+ response.getStatusLine().getStatusCode());
return "ok";
}
}
I've followed the documentation at https://github.com/oracle/graal/blob/master/substratevm/URL-PROTOCOLS.md and https://github.com/oracle/graal/blob/master/substratevm/JCA-SECURITY-SERVICES.md to enable security services, adding --enable-https
The only thing I haven't done is adding libsunec.so. I've looked for it in various JDK installations (1.0.0-rc-12-grl, 8.0.202-zulu, and Oracle's jdk1.8.0_201.jdk), however none of the contained the .so file.
Is that the reason why I'm getting that error? If so, where can I get the needed .so file? An updated of the related doc would come in handy in case it is not shipped by default with any jdk.
@codependent does this fix work for you https://github.com/oracle/graal/issues/768#issuecomment-451776587? Otherwise, can you point to the repo where I can replicate this?
By the way libsunec.so is in GraalVM under graalvm-ce-1.0.0-rc12/jre/lib/amd64/libsunec.so. Various JDK distributions might have it in different locations. However, I don't think missing it is related to that issue.
Regarding libsunec.so, I am using the mac graal distribuition which doesn't include it. I downloaded the linux version and copied it to my project (https://github.com/codependent/graal-app).
I didn't set the java.library.path as the file is copied to /home/application which is the workdir of the image (Dockerfile -> WORKDIR /home/application). However if I start the container manually it still shows:
WARNING: The sunec native library, required by the SunEC provider, could not be loaded. This library is usually shipped as part of the JDK and can be found under
/jre/lib/ /libsunec.so. It is loaded at run time via System.loadLibrary("sunec"), the first time services from SunEC are accessed. To use this provider's services the java.library.path system property needs to be set accordingly to point to a location that contains libsunec.so. Note that if java.library.path is not set it defaults to the current working directory.
On another hand, I followed the comment linked above and set the trustore location (/home/application/cacerts) and password pointing at a copy of the cacerts included in the graal distribution. Previously I put it in the root of the project so that the Dockerfile COPY statement put it into the container at /home/application/cacerts.
The trustore is setup in the ExampleController constructor:
@Controller("/")
public class ExampleController {
private static final Log LOG = LogFactory.getLog(ExampleController.class);
private ObjectMapper mapper = new ObjectMapper()
.disable(MapperFeature.CAN_OVERRIDE_ACCESS_MODIFIERS)
.disable(MapperFeature.ALLOW_FINAL_FIELDS_AS_MUTATORS)
.enable(JsonParser.Feature.ALLOW_COMMENTS)
.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
private static final HttpClientFactory<ConnectionManagerAwareHttpClient> httpClientFactory = new
ApacheHttpClientFactory();
private HttpClient httpClient;
public ExampleController() {
this.httpClient = httpClientFactory.create(HttpClientSettings.adapt(new ClientConfiguration()));
LOG.info("init");
LOG.info("Registering truststore in ExampleController <init>");
System.setProperty("javax.net.ssl.trustStore","/home/application/cacerts");
System.setProperty("javax.net.ssl.trustStorePassword", "changeit");
final ConnectionManagerFactory<HttpClientConnectionManager> cmFactory = new ApacheConnectionManagerFactory();
final HttpClientConnectionManager cm = cmFactory.create(HttpClientSettings.adapt(new ClientConfiguration()));
ClientConnectionManagerFactory.wrap(cm);
}
...
@Get("/http")
public String httpClient() throws IOException {
String url = "https://www.google.com/search?q=httpClient";
HttpGet request = new HttpGet(url);
HttpResponse response = httpClient.execute(request);
LOG.info("Response Code : {}" + response.getStatusLine().getStatusCode());
return "ok";
}
However I'm still getting the exception:
Caused by: java.security.InvalidAlgorithmParameterException: the trustAnchors parameter must be non-empty
at java.security.cert.PKIXParameters.setTrustAnchors(PKIXParameters.java:200)
at java.security.cert.PKIXParameters.<init>(PKIXParameters.java:120)
at java.security.cert.PKIXBuilderParameters.<init>(PKIXBuilderParameters.java:104)
at sun.security.validator.PKIXValidator.<init>(PKIXValidator.java:89)
... 65 more
To reproduce:
export AWS_DEFAULT_REGION=eu-central-1docker build . -t graal-app./sam-local.shhttp://localhost:3000/httplibsunec.so is a run time dependency, so you need to pack it in the function.zip: RUN zip -j function.zip bootstrap libsunec.so server.
I cannot replicate the InvalidAlgorithmParameterException. Here is what I get:
$ ./sam-local.sh
2019-03-06 16:50:12 Mounting MyServiceFunction at http://127.0.0.1:3000/{proxy+} [GET, DELETE, PUT, POST, HEAD, OPTIONS, PATCH]
2019-03-06 16:50:12 You can now browse to the above endpoints to invoke your functions. You do not need to restart/reload SAM CLI while working on your functions, changes will be reflected instantly/automatically. You only need to restart SAM CLI if you update your AWS SAM template
2019-03-06 16:50:12 * Running on http://127.0.0.1:3000/ (Press CTRL+C to quit)
2019-03-06 16:50:21 Invoking not.used.in.provided.runtime (provided)
2019-03-06 16:50:22 Decompressing /home/cdrtz/projects/github/gh-1034/graal-app/build/function.zip
Fetching lambci/lambda:provided Docker container image......
2019-03-06 16:50:24 Mounting /tmp/tmpH_JzU2 as /var/task:ro inside runtime container
00:50:25.187 [main] INFO i.m.f.a.p.AbstractLambdaContainerHandler - Starting Lambda Container Handler
00:50:25.187 [main] INFO i.m.context.env.DefaultEnvironment - Established active environments: [function]
START RequestId: 52fdfc07-2182-154f-163f-5f0f9a621d72 Version: $LATEST
Mar 07, 2019 12:50:25 AM graal.app.ExampleController <init>
INFO: init
Mar 07, 2019 12:50:25 AM graal.app.ExampleController <init>
INFO: Registering truststore in ExampleController <init>
2019-03-06 16:50:40 Function 'MyServiceFunction' timed out after 15 seconds
2019-03-06 16:50:40 Function returned an invalid response (must include one of: body, headers or statusCode in the response object). Response received:
2019-03-06 16:50:40 127.0.0.1 - - [06/Mar/2019 16:50:40] "GET /http HTTP/1.1" 502 -
----------------------------------------
Exception happened during processing of request from ('127.0.0.1', 51306)
Traceback (most recent call last):
File "/usr/lib/python2.7/SocketServer.py", line 596, in process_request_thread
self.finish_request(request, client_address)
File "/usr/lib/python2.7/SocketServer.py", line 331, in finish_request
self.RequestHandlerClass(request, client_address, self)
File "/usr/lib/python2.7/SocketServer.py", line 654, in __init__
self.finish()
File "/usr/lib/python2.7/SocketServer.py", line 713, in finish
self.wfile.close()
File "/usr/lib/python2.7/socket.py", line 283, in close
self.flush()
File "/usr/lib/python2.7/socket.py", line 307, in flush
self._sock.sendall(view[write_offset:write_offset+buffer_size])
error: [Errno 32] Broken pipe
----------------------------------------
It looks like I needed to specify --docker-network host to the sam command.
The cacerts file is also a run time dependency so you need to package it for run time: RUN zip -j function.zip bootstrap libsunec.so cacerts server. Setting
javax.net.ssl.trustStore and javax.net.ssl.trustStorePassword
in ExampleController() doesn't work, I think it is too late, before the classes that need it are already initialized. You need to set them in bootstrap via ./server -Djavax.net.ssl.trustStore=cacerts -Djavax.net.ssl.trustStorePassword=changeit.
With these changes and using the libsunec.so from graalvm-ce-1.0.0-rc13/jre/lib/amd64/libsunec.so the image builds and runs succesfully and http://127.0.0.1:3000/http returns ok.
Most helpful comment
The
cacertsfile is also a run time dependency so you need to package it for run time:RUN zip -j function.zip bootstrap libsunec.so cacerts server. Settingjavax.net.ssl.trustStoreandjavax.net.ssl.trustStorePasswordin
ExampleController()doesn't work, I think it is too late, before the classes that need it are already initialized. You need to set them inbootstrapvia./server -Djavax.net.ssl.trustStore=cacerts -Djavax.net.ssl.trustStorePassword=changeit.With these changes and using the
libsunec.sofromgraalvm-ce-1.0.0-rc13/jre/lib/amd64/libsunec.sothe image builds and runs succesfully andhttp://127.0.0.1:3000/httpreturnsok.