Invoke a Google Cloud Run from java

牧云@^-^@ 提交于 2021-01-29 18:36:29

问题


I want to invoke a Cloud Run from an external application. The external application is written in Kotlin (java) and run on the JDK 11 JVM. It authenticates using a service account

ServiceAccountCredentials.fromStream(serviceAccount).createScoped("https://www.googleapis.com/auth/cloud-platform")

where the service account is is a valid JSON file. I have tested the permissions to invoke a cloud run for the service account inside the google cloud console.

I have not found an official API so I used google's GoogleNetHttpTransport to run an HTTP post request. To invoke the cloud run I have tried to use the HttpCredentialsAdapter

transport.createRequestFactory(HttpCredentialsAdapter(googleCredentials))
   .buildPostRequest(GenericUrl(url), JsonHttpContent(JacksonFactory(), data))

I have tried to use the access token directy

val headers = HttpHeaders()
googleCredentials.refreshIfExpired()
headers.authorization = "Bearer " + googleCredentials.accessToken.tokenValue
postRequest.headers = headers

And I have tried to use a jet service account and use the jwt request metadata

val headers = HttpHeaders()
jwtCredentials = ServiceAccountJwtAccessCredentials.fromStream(serviceAccount)
jwtCredentials.getRequestMetadata(URI(url)).forEach {
    headers.set(it.key, it.value)
}
postRequest.headers = headers

In all these cases it returns this error

[20:35:00 WARN]: Exception in thread "TestThread" com.google.api.client.http.HttpResponseException: 401 Unauthorized
[20:35:00 WARN]:    at com.google.api.client.http.HttpRequest.execute(HttpRequest.java:1113)
[20:35:00 WARN]:    at ***.CloudRun$invoke$3.invokeSuspend(CloudRun.kt:34)
[20:35:00 WARN]:    at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
[20:35:00 WARN]:    at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
[20:35:00 WARN]:    at java.base/java.lang.Thread.run(Thread.java:834)

Thanks for your support in advance


回答1:


I spent time on the OAuth2 Java library and the problem is the following. The class ServiceAccountCredentials add an AccessToken as authorization, and the class ServiceAccountJwtAccessCredentials add a self-signed identity token.

I will go deeper into it, but for now you can set the header manually

        String myUri = "https://.....";

        Credentials credentials =  ServiceAccountCredentials
                .fromStream(resourceLoader.getResource("classpath:./key.json")
                        .getInputStream()).createScoped("https://www.googleapis.com/auth/cloud-platform");

        String token = ((IdTokenProvider)credentials).idTokenWithAudience(myUri, Collections.EMPTY_LIST).getTokenValue();

        HttpRequestFactory factory = new NetHttpTransport().createRequestFactory();
        HttpRequest request = factory.buildGetRequest(new GenericUrl(myUri));
        HttpHeaders headers = new HttpHeaders();
        headers.set("Authorization","Bearer " + token);
        request.setHeaders(headers);
        HttpResponse httpResponse = request.execute();
        System.out.println(CharStreams.toString(new InputStreamReader(httpResponse.getContent(), Charsets.UTF_8)));

The pieces are here, but not work together.

EDIT

After some exchange on GitHub, a Google provide me the solution. Now the header is automatically provisioned with a signed id Token

        Credentials credentials =  ServiceAccountCredentials
                .fromStream(resourceLoader.getResource("classpath:./key.json")
                        .getInputStream()).createScoped("https://www.googleapis.com/auth/cloud-platform");


        IdTokenCredentials idTokenCredentials = IdTokenCredentials.newBuilder()
                .setIdTokenProvider((ServiceAccountCredentials)credentials)
                .setTargetAudience(myUri).build();

        HttpRequestFactory factory = new NetHttpTransport().createRequestFactory(new HttpCredentialsAdapter(idTokenCredentials));
        HttpRequest request = factory.buildGetRequest(new GenericUrl(myUri));
        HttpResponse httpResponse = request.execute();
        System.out.println(CharStreams.toString(new InputStreamReader(httpResponse.getContent(), Charsets.UTF_8)));



回答2:


I don't know Java SDKs well enough, but I think I spotted the error:

headers.authorization = "Bearer " + googleCredentials.accessToken.tokenValue
                                                      ^^^^^^^^^^^

You should not be sending a GCP "access token" to a Cloud Run application (e.g. https://*.run.app). That token is for calling Google’s APIs; and Cloud Run apps are not that.

Instead, you should be sending an "identity token", which basically shows that you are in possession of the service account, without giving out any permission to the called service (to use that token to call GCP APIs).

You cannot sign an "identity token" yourself locally, you need to call an endpoint.

This is explained in this page: https://cloud.google.com/iam/docs/creating-short-lived-service-account-credentials#sa-credentials-jwt



来源:https://stackoverflow.com/questions/60999141/invoke-a-google-cloud-run-from-java

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!