Search code examples
javagoogle-cloud-functionsretrofitgoogle-kubernetes-enginekubernetes-jobs

Google Cloud Function https certificate to connect to GKE


Due to the difficulty in using the official java kubernetes client inside a cloud function (basically we didn't know how to put the .kube/config file in the environment for the classes of the client to authenticate against) we have decided to try another route: we are trying to use the Kubernetes API to perform operations against the cluster. We have successfully used this approach to have a Cloud Function create a Cloud Run job using the API

@Override
public void service(HttpRequest httpRequest, HttpResponse httpResponse) throws Exception {

        GoogleCredentials googleCredentials;
        try {
            googleCredentials = GoogleCredentials.getApplicationDefault();
        } catch (IOException e) {
            throw new RuntimeException("Could not load Google Creadentials", e);
        }

        GKEScopedCredentials gkeScopedCredentials = new GKEScopedCredentials(googleCredentials);

        HttpLoggingInterceptor logging = new HttpLoggingInterceptor();

        logging.setLevel(HttpLoggingInterceptor.Level.BODY);

        OkHttpClient.Builder httpClient = new OkHttpClient.Builder();

        httpClient.addInterceptor(logging);

        Retrofit retrofit = new Retrofit.Builder()
            .baseUrl("https://34.95.xxx.xx/")
            .addConverterFactory(GsonConverterFactory.create())
            .client(httpClient.build())
            .build();

        String token = gkeScopedCredentials.getAuthorizationHeader();

        GKERestClient gkeRestClient = retrofit.create(GKERestClient.class);

        Call < ResponseBody > responseBodyCall = gkeRestClient.createJob(token, RequestBody.create(MediaType.parse("application/json"), "{\n" +
            "  \"apiVersion\": \"batch/v1\",\n" +
            "  \"kind\": \"Job\",\n" +
            "  \"metadata\": {\n" +
            "    \"name\": \"pi\"\n" +
            "  },\n" +
            "  \"spec\": {\n" +
            "    \"template\": {\n" +
            "      \"spec\": {\n" +
            "        \"containers\": [\n" +
            "          {\n" +
            "            \"name\": \"pi\",\n" +
            "            \"image\": \"perl:5.34.0\",\n" +
            "            \"command\": [\n" +
            "              \"perl\",\n" +
            "              \"-Mbignum=bpi\",\n" +
            "              \"-wle\",\n" +
            "              \"print bpi(2000)\"\n" +
            "            ]\n" +
            "          }\n" +
            "        ],\n" +
            "        \"restartPolicy\": \"Never\"\n" +
            "      }\n" +
            "    },\n" +
            "    \"backoffLimit\": 4\n" +
            "  }\n" +
            "}"));

        Response < ResponseBody > response = responseBodyCall.execute();

Essentially we are using retrofit to perform the following call:

@POST("/apis/batch/v1/namespaces/cloud-run-example-xxx/jobs")
  Call<ResponseBody> createJob(
      @Header("Authorization") String authorization,
      @Body RequestBody job);

Now this approach worked well for the cloud run API but it isn't working for Kubernetes API, we are getting the following error:

javax.net.ssl.SSLHandshakeException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target

at okhttp3.internal.connection.RealConnection.connectTls ( okhttp3/internal.connection/RealConnection.java:336 )
at okhttp3.internal.connection.RealConnection.establishProtocol ( okhttp3/internal.connection/RealConnection.java:300 )
at okhttp3.internal.connection.RealConnection.connect ( okhttp3/internal.connection/RealConnection.java:185 )
at okhttp3.internal.connection.ExchangeFinder.findConnection ( okhttp3/internal.connection/ExchangeFinder.java:224 )
at okhttp3.internal.connection.ExchangeFinder.findHealthyConnection ( okhttp3/internal.connection/ExchangeFinder.java:108 )
at okhttp3.internal.connection.ExchangeFinder.find ( okhttp3/internal.connection/ExchangeFinder.java:88 )
at okhttp3.internal.connection.Transmitter.newExchange ( okhttp3/internal.connection/Transmitter.java:169 )
at okhttp3.internal.connection.ConnectInterceptor.intercept ( okhttp3/internal.connection/ConnectInterceptor.java:41 )
at okhttp3.internal.http.RealInterceptorChain.proceed ( okhttp3/internal.http/RealInterceptorChain.java:142 )
at okhttp3.internal.http.RealInterceptorChain.proceed ( okhttp3/internal.http/RealInterceptorChain.java:117 )
at okhttp3.internal.cache.CacheInterceptor.intercept ( okhttp3/internal.cache/CacheInterceptor.java:94 )
at okhttp3.internal.http.RealInterceptorChain.proceed ( okhttp3/internal.http/RealInterceptorChain.java:142 )
at okhttp3.internal.http.RealInterceptorChain.proceed ( okhttp3/internal.http/RealInterceptorChain.java:117 )
at okhttp3.internal.http.BridgeInterceptor.intercept ( okhttp3/internal.http/BridgeInterceptor.java:93 )
at okhttp3.internal.http.RealInterceptorChain.proceed ( okhttp3/internal.http/RealInterceptorChain.java:142 )
at okhttp3.internal.http.RetryAndFollowUpInterceptor.intercept ( okhttp3/internal.http/RetryAndFollowUpInterceptor.java:88 )
at okhttp3.internal.http.RealInterceptorChain.proceed ( okhttp3/internal.http/RealInterceptorChain.java:142 )
at okhttp3.internal.http.RealInterceptorChain.proceed ( okhttp3/internal.http/RealInterceptorChain.java:117 )
at okhttp3.logging.HttpLoggingInterceptor.intercept ( okhttp3/logging/HttpLoggingInterceptor.java:223 )
at okhttp3.internal.http.RealInterceptorChain.proceed ( okhttp3/internal.http/RealInterceptorChain.java:142 )
at okhttp3.internal.http.RealInterceptorChain.proceed ( okhttp3/internal.http/RealInterceptorChain.java:117 )
at okhttp3.RealCall.getResponseWithInterceptorChain ( okhttp3/RealCall.java:229 )
at okhttp3.RealCall.execute ( okhttp3/RealCall.java:81 )
at retrofit2.OkHttpCall.execute ( retrofit2/OkHttpCall.java:204 )
at functions.Main.service ( functions/Main.java:80 )
Caused by: sun.security.validator.ValidatorException

Via postman the call works well because we can disable the https certificates. Is there any way to add those certificates to the environment of a Cloud Function? or forego them completely?


Solution

  • According to the documentation, you can write files to the Cloud Functions in-memory filesystem (excluding the directories where your source files are located).

    As shown in this other thread, one way to add files for use in the Cloud Function (like the kubeconfig file) would be to import them from Cloud Storage.

    For example, if you'd like to save files to the temporary directory, you could use this snippet:

        //Creates GCS Client
        Storage storage = StorageOptions.newBuilder().setProjectId(projectId).build().getService();
    
        //Fetches GCS object
        Blob blob = storage.get(BlobId.of(bucketName, objectName));
        //Stores object at temp directory
        blob.downloadTo(Paths.get(System.getProperty("java.io.tmpdir") + "/config"));
        ...
    

    I would, however, recommend not using Cloud Functions for the purpose of accessing the Kubernetes API, due to its inflexibility in configuring the environment. You should instead use Cloud Run, so you can configure the application and its container to not run into additional problems.