Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Calling https URL throws Caused by: java.lang.RuntimeException: Unexpected error: java.security.InvalidAlgorithmParameterException: the trustAnchors parameter must be non-empty #1034

Closed
codependent opened this issue Mar 4, 2019 · 7 comments
Assignees

Comments

@codependent
Copy link

codependent commented Mar 4, 2019

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.

@cstancu cstancu self-assigned this Mar 5, 2019
@cstancu
Copy link
Member

cstancu commented Mar 5, 2019

@codependent does this fix work for you #768 (comment)? Otherwise, can you point to the repo where I can replicate this?

@cstancu
Copy link
Member

cstancu commented Mar 5, 2019

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.

@codependent
Copy link
Author

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 <JAVA_HOME>/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:

  1. export AWS_DEFAULT_REGION=eu-central-1
  2. docker build . -t graal-app
  3. ./sam-local.sh
  4. Access http://localhost:3000/http

@cstancu
Copy link
Member

cstancu commented Mar 7, 2019

libsunec.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.

@cstancu
Copy link
Member

cstancu commented Mar 7, 2019

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
----------------------------------------

@cstancu
Copy link
Member

cstancu commented Mar 7, 2019

It looks like I needed to specify --docker-network host to the sam command.

@cstancu
Copy link
Member

cstancu commented Mar 7, 2019

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants