Cloud Run - gRPC authentication through service account - Java - authentication

I've deployed to Google Cloud Run (fully managed) a gRPC server with the option "Required Authentication" set to true.
I'm trying to authenticate the calls from my gRPC client through a Google Service Account, however I'm always getting below exception.
Exception in thread "main" io.grpc.StatusRuntimeException: UNAUTHENTICATED: HTTP status code 401
Below is how I'm creating the gRPC channel and attaching the service account.
public GrpcClient(Channel channel) throws IOException {
Credentials credentials = GoogleCredentials.getApplicationDefault();
blockingStub = CalculatorServiceGrpc
.newBlockingStub(channel)
.withCallCredentials(MoreCallCredentials.from(credentials));
}
Obs.: env var GOOGLE_APPLICATION_CREDENTIALS is set with the path of the SA, and the SA has Cloud Run Invoker privilege
Is there anything that I'm missing?

When calling a Cloud Run server from a generic HTTP client, setting GOOGLE_APPLICATION_CREDENTIALS doesn't have an effect. (That only works when you call Google’s APIs with a Google client library.)
Even when deployed to Cloud Run, gRPC is just HTTP/2, so authenticating to a Cloud Run service is documented at the Service-to-Service Authentication page. In a nutshell this involves:
getting a JWT (identity token) from metadata service endpoint inside the container
setting it as a header on the request to the Cloud Run app, as Authorization: Bearer [[ID_TOKEN]].
In gRPC, headers are called "metadata", so you should find the equivalent gRPC Java method to set that. (It probably is a per-RPC option.)
Read about a Go example here, it basically explains you that gRPC servers running on Cloud Run still authenticate the same way. In this case, also make sure to tell Java:
you need to connect to domain:443 (not :80)
gRPC Java needs to use machine root CA certificates to verify validity of TLS certificate presented by Cloud Run (as opposed to skipping TLS verification)

After some more research I was able to authenticate the requests using IdTokenCredentials. See below the result.
public GrpcClient(Channel channel) throws IOException {
ServiceAccountCredentials saCreds = ServiceAccountCredentials
.fromStream(new FileInputStream("path\to\sa"));
IdTokenCredentials tokenCredential = IdTokenCredentials.newBuilder().setIdTokenProvider(saCreds)
.setTargetAudience("https://{projectId}-{hash}-uc.a.run.app").build();
blockingStub = CalculatorServiceGrpc
.newBlockingStub(channel)
.withCallCredentials(MoreCallCredentials.from(tokenCredential));
}

I came across this post looking for a Python-related answer.
So for those who want to solve this problem using a Python client:
import google.oauth2
import google.oauth2.id_token
import google.auth
import google.auth.transport
import google.auth.transport.requests
TARGET_CHANNEL = "your-app-name.run.app:443"
token = google.oauth2.id_token.fetch_id_token(google.auth.transport.requests.Request(), TARGET_CHANNEL)
call_cred = grpc.access_token_call_credentials(auth.get_identification_token(
"https://" + TARGET_CHANNEL.strip(':443')))
channel_cred = grpc.composite_channel_credentials(grpc.ssl_channel_credentials(), call_cred)
channel = grpc.secure_channel(TARGET_CHANNEL, credentials=channel_cred)`

I was also running into UNAUTHENTICATED: HTTP status code 401. I had a GCP Load Balancer all setup with HTTPS and a backend routing to a Cloud Run Service. The backend service has to be HTTP2 for grpc like the above answer. But there was one more spot that needed HTTP2. The cloud run service needs to be setup to accept HTTP2, by default it is HTTP1. You can use
gcloud run services update <SERVICE> --use-http2
to set the cloud run to use http2. This will allow the load balancers backend HTTP2 to communicate with the Cloud Run Service over HTTP which grpc is required to have.
https://cloud.google.com/run/docs/configuring/http2

Related

does flutter dart's http request have tls support?

I do not know much about computer networks but I've been dabbling with flutter and aws lambda.
I have a flutter (dart) code that uses http package to make an http request like the following:
import 'package:http/http.dart' as http
final response = await http.get('https://<address to my lambda function via api gateway>');
final body = response.body;
Looking at the http package in pub.dev, it says that the package is a A composable, Future-based library for making HTTP requests. and does not say anything about TLS(SSL). However, the url I provided in the above code is https generated by aws API gateway. So my question is, in the above code, is it using https or http? If it is using http, it is not secure hence, i need to add another layer of security to prevent hackers such as Man in the middle attack. If it is https, does that mean the data that gets sent is encrypted via TLS, hence I do not need any sort of asymmetric encryption between the client and the server?

Azure app can't connect to external API - Https to Http

My Azure application needs to connect to an API that is deployed on a remote server. The Azure app uses https, while the API uses http. To avoid a mixed content issue, I changed my API address to https. But now I receive the following error:
Failed to load resource: net::ERR_SSL_PROTOCOL_ERROR
So in summary, using http for my API gives me a Mixed Content error, and using https for my API gives me an SSL-related error.
Does anyone have experience providing an SSL certificate to an API thats already been deployed? Any advice would be greatly appreciated. Thank you.

API GET Authentication

I will start by saying I'm not a developer and don't know too much about API's.
Using BambooHR (Cloud based HR service) I have an API that I can call through a browser and it returns a CSV report. I am looking to automate this but not too sure how to call this through PowerShell to return my file.
The API url looks like this:
HTTP://<API TOKEN >:x#api.bamboohr.com/api/gateway.php/<COMPANY Name>/reports/<report>/?format=csv
When I try the following in Powershell:
Invoke-WebRequest -URI HTTP://<API TOKEN>:x#api.bamboohr.com/api/gateway.php/<COMPANY Name>/reports/<report>/?format=csv
I get the following error:
The underlying connection was closed: An unexpected error occurred on
a send.
Any ideas how I can just create a simple PowerShell script to download this file?
According to bamboohr api documentation you should only use https with signed certificate:
All requests made to our APIs must be sent over HTTPS. The SSL
certificate used for the HTTPS connection is signed and all
implementations should configure their SSL layer to verify it.

Websockets on localhost over https:// problems

I'm trying to write a service, my service is a WebsocketServer is written in nodejs, with express and the ws module. This service should be installed with node-webkit locally on a computer(on localhost) to communicate with a web site in https:// to exchange information. My problem is, that i need a trusted certificate for localhost(its not possible), because all browsers give a warning if i use a self signed certificate. I know i can ignore that warning, but in the future this software should be distribuited, and i dont want to give that warning for future clients. Is there a good way to resolve this problem?
Use one of the following options to work around this:
Package your client-side interface as a browser extension
Setup a Message Queue which transforms messages to your node server as Websocket requests
Use a PAAS provider with free certificates to proxy to your domain
References
How do I use a self signed certificate for a HTTPS Node.js server?
Simulating GitHub OAuth2 Login for Tests with JUnit, Webdriver & Hoverfly
Apache configuration using Self Signed Certificate - YouTube
Amazon SES: Sendmail
SMTP Gateway Plugin for RabbitMQ
Sending RabbitMQ messages via websockets

How do I prevent a user from accessing a server's API directly and instead force them to use the UI?

More of a theoretical question, but I'm really curious!
I have a two part application:
Apache server hosting my UI
Back-end that services all http requests from the UI
The apache service proxies all http requests from the UI to the server. So, if the user is reasonably adept, they can reverse engineer our API by inspecting the calls in the browser's developer tools.
Thus, how do I prevent a user from using the server API directly and instead force them to use the UI?
The server can't determine whether a call came from the UI or not because a user can make a call to myapp.com/apache-proxy/blah/blah/blah from outside of the UI, apache will get the request and forward it to the server, which will have no idea it's not coming from a UI.
The option I see is to inject a header into the request from the UI, that indicates the origin of the request as the UI. This seems ripe for exploitation though.
To me, this is more of a networking question since its something I'd resolve at the network level. If you run your backend application in a private network (or on a public network with firewall rules) you can configure the backend host to only accept communication from your Apache server.
That way the end-user can't connect directly to the API, since its not accessible to the public. Only the allowed Apache server will be able to communicate with the backend API. That way the Apache server acts as an intermediary between the end-user (client side) and the backend API server.
An example diagram from AWS.
You could make the backend server require connections to be authenticated before accepting any requests from them. Then make it so only the Apache server can successfully authenticate in a way that end users cannot replicate. For example, by using SSL/TLS between Apache and the backend, where the backend requires client certificates to be used, and then issue Apache a private certificate that the backend will accept. Then end users will not be able to authenticate with the backend directly.