Cloud Run: Service-to-Service 401 while curl works - google-cloud-run

This is an issue I've been trying to figure out for a few days now.
Service-to-service communication always results in 401 for my Cloud Run applications.
For token retrieval in service I've been using this code snippet:
tokenURL := fmt.Sprintf("/instance/service-accounts/default/identity?audience=%s", c.serviceURL)
var err error
token, err = metadata.Get(tokenURL)
if err != nil {
return nil, fmt.Errorf("metadata.Get: failed to query id_token: %+v", err)
}
I have also tried to supply a Service account key to my Cloud Run service and using the token that is returned from google.JWTAccessTokenSourceFromJSON to no avail.
The problem is that the JWT that is returned does work when used from cURL, but does not work when used in a service that runs on Cloud Run.
Both the Cloud Run service account and the external service account (for the key) has the roles/run.invoker IAM binding. And the audience is the Cloud Run issued service URL.
I have tried the following:
Using the metadata service for token retrieval (401 inside Cloud Run, but works when the token is used in cURL and Postman)
Using the JWTAccessTokenSourceFromJSON for token retrieval (401 inside Cloud Run, but works when the token is used in cURL and Postman)
Tried adding a delay before using issued token (in case the token is not propagated)
Tried manually pinging the Cloud Run service a few times with given token after the delay (using http.Client)
Nothing seems to work. Every request when run inside Cloud Run is returning 401 and the logs show The request was not authorized to invoke this service. Read more at https://cloud.google.com/run/docs/securing/authenticating. While using cURL or Postman I am able to access the service.
I've tried these methods to invoke the service:
tokenURL := fmt.Sprintf("/instance/service-accounts/default/identity?audience=%s", c.serviceURL)
var err error
token, err := metadata.Get(tokenURL)
if err != nil {
return nil, fmt.Errorf("metadata.Get: failed to query id_token: %+v", err)
}
// First one (ping)
req, _ := http.NewRequest("GET", "{{serviceURL}}", nil)
req.Header.Set("Authorization", "Bearer: "+token)
l, _ := cr.Do(req)
m, _ := ioutil.ReadAll(l.Body)
logrus.Println(l.Header)
logrus.Println(string(m))
// RoundTripper
r.Header.Set("Authorization", "Bearer: "+token)
return c.r.RoundTrip(r)
I appreciate any answer.
Thanks!

While posting code examples I just realized I added an extra colon(:) after Bearer which makes this request fail. Can't believe I spent days to solve this!

Related

Keycloak and Go with gocloak - Cannot decode tokens

I am trying to do some testing using keycloak and go in a local docker setup. Currently, I have a keycloak container (with a postgres backend) running on 8080. I set this up with a client that has service account access, and have my ClientID, client secret, realm etc all in a config file.
I then have a go API running in docker as well. In this go API, at startup I create a new gocloak client and hold reference to it:
client := gocloak.NewClient(host)
log.Infof("clientid: %s pw: %s // realm: %s", clientId, clientSecret, realm)
ctx := context.Background()
tkn, err := client.LoginClient(ctx, clientId, clientSecret, realm)
if err != nil {
log.Errorf("error occurred connecting to keycloack: %s", err)
}
The value of host above is:
http://keycloak:8080
as the docker container running keycloak is just called keycloak. Obviosuly if i Try and use localhost:8080 here, it fails because we are inside a container.
And then as a middleware on any calls coming in to this service's routes, I use that client and check:
authHeader := c.GetHeader("Authorization")
parts := strings.Split(authHeader, " ")
if len(parts) != 2 {
c.AbortWithStatus(401)
return
}
ctx := context.Background()
kc.Logger.Infof("Incoming auth header: %s", parts[1])
token, err := kc.Auth.RetrospectToken(ctx, parts[1], kc.ClientId, kc.ClientSecret, kc.Realm)
if err != nil {
kc.Logger.Errorf("error occurred parsing token: %s", err)
}
kc.Logger.Infof("token: %v", token)
In order to test this, what I have been doing is starting up all the services, and then navigating to:
http://localhost:8080/auth/realms/dev/account/#/
and then I sign in with google (an account I have already set up on keycloak earlier).
Then, using the developer tools in the browser, im finding a network call that contains
KEYCLOAK_IDENTITY=eyJhbGciOiJIUzI1NiIsInR5cCIgOiAiaSldUIiwia2lkIiA6ICIwMTg3MzU0MS03YmE3LTQ0MmQtYjMxOC0zMjBhYmIzYjNmMmIi<AND_SO_ON>; Version=1; Path=/auth/realms/dev/; SameSite=None; Secure; HttpOnly
From this i extract just the token, and then use that as a Bearer token in a postman call to my go back end.
The problem I am having is that, in step 1 when I log in my service account, if i retrospect the token returned there, I see the struct I'd expect with the oauth2 token values.
This middleware, when it retrospects the incoming tokens I give it, i just get
{
active: false
}
every time.
I am not entirely sure where I have gone wrong here. I have read a number of posts that say the host has to be the EXACT SAME between the acquire token, and the endpoint used to inspect it, which leads me to beleive that since I get a token from:
http://localhost:8080/auth/realms/dev/account/#/
but am decoding it on a host inside the container thats called:
http://keycloack:8080
That this may be my problem? Is this accurate? or have I gone wrong somewhere else?
If this IS in fact the problem, how can I go about solving this? I can't get a token from the internal docker name from the outside in order to test calls going in to my API, and I cannot use "localhost" inside the go container to match where i get it from as this wont resolve.
How can I either get a hostname i can access from both inside and outside the container?
Or, as mentioned, have I gone wrong somewhere else entirely?
Any help will be appreciated!

Get my IOS installations and other app store data

I'm trying to get the number of installs of my IOS application but failed.
I tried using api.appstoreconnect.apple.com/, with the query of
res = await axios.get
('https://api.appstoreconnect.apple.com/v1/salesReports?filter\[frequency\]=MONTHLY&filter[reportDate]=2020-12&filter\[reportSubType\]=SUMMARY&filter\[reportType\]=SALES&filter\[vendorNumber\]=******'
,{
headers: {
Accept:'application/a-gzip, application/json',
Authorization: 'Bearer ' + token //the token is a variable which holds the token
}
})
console.log(res.data)
and finally managed to get a response but the response is of type application/a-gzip
so I don't know how to read it.
Looks like -
ՓQo�0ǟo��O{���(�*Z��A�g
��*��}0���&ɲ�l�ʢU����|���iՋ5d�Ū��w�Y#�=6���BP��Dm��.��*bŮE��(I���ZHA��l\�Xõ�,) �^�{#�
����i������E��{����霨m�W�iQ�۪}~V�t�=6�M�EKC
Using itunesconnectanalytics, using this npm library I finally manage to get the info, but I don't know what to do with the 2-factor authentication once I upload my code to the could (AWS Fargate)
If anyone has a solution for any of the cases or other tips it would be amazing,
Thanks

Cant get simple Oauth wth vertx to work with google

Trying to create a web client that uses oauth to connect to multiple sso endopints, google mainly. This is on top of a spring boot project, I just keep getting the same error that no code is provided, but I'm not sure how i'm supposed to get a code without the access token first. Here is a simple version of what im trying to run I want localhost/8080 to redir to google to login and comeback to the same page or a different one doesn't matter
#RequestMapping("/google")
fun google(#RequestParam(value = "code") code: String?, model: Model): String {
val clientId = "asdf.apps.googleusercontent.com"
val secret = "1234"
var goog = GoogleAuth.create(Vertx.factory.vertx(), clientId, secret)
goog.authenticate(JsonObject().put("code", code), {
System.out.println(it)
})
return "test"
}
the error is always
"error": "invalid_request",
"error_description": "Missing required parameter: code"
}}
e```
but how can I provide a code first I need some sort of response from the server. I'm pretty familiar with restful oauth and must be missing something
You can't use the GoogleAuth like that. GoogleAuth provides the basic primitives to handle the OAuth2 protocol. As you're not using the vertx-web part you will need to setup a callback endpoint in your application (I guess it's the /google endpoint you listed) but now you miss the whole Oauth2 handshake. Your client (browser) should call Google, which calls your server to validate the code.
So what you're asking here is to re-implement the vert.x web Oauth2Handler using Spring Boot APIs.

jhipster with keycloak : error 401 not forwarded to login

I'm using Jhipster 4.13.3 with the Oauth2/OIDC option to generate a gateway connected keycloak.
When a service send a 401 status, redirection to /login doesn't executed.
An interceptor (auth-expired.interceptor.ts) is defined to handle the error, but it doesn't work correctly.
First question
In the if condition, error.json() doesn't contain path, so the condition is false and the redirection is never executed.
if (error.status === 401 && error.text() !== ''
&& error.json().path && !error.json().path.includes('/api/account')) {
const destination = this.stateStorageService.getDestinationState();
if (destination !== null) {
const to = destination.destination;
const toParams = destination.params;
if (to.name === 'accessdenied') {
this.stateStorageService.storePreviousState(to.name, toParams);
}
} else {
this.stateStorageService.storeUrl('/');
}
const loginService: LoginService = this.injector.get(LoginService);
loginService.login();
}
Somebody knows the correct condition ?
Second question
I disabled the check on "error.json().path...", the redirection is called but fails "often", it works sometimes, I didn't found a explanation. Even if I kill all sessions in the keycloak admin console, the browsers redirects to the home page, not the keycloak login form.
Does somebody have an explanation about this ?
Thanks,
Philippe
We kept getting HTTP 401 status code responses when the hostname stamped on the iss field of the bearer's/user's access token had a different case i.e. lowercase vs. uppercase than the hostname in the URL used to post an HTTP request to keycloak's token endpoint.
Any kind of hostname mismatch can cause 401 errors:
Short hostname alias vs. fully qualified hostname e.g. https://myhost:8080 vs. https://myhost.domain.com:8080
https://MYHOST:8080 vs. https://myhost:8080
https://Myhost:8080 vs. https://myhost:8080
etc.
This happened in a Microsoft Windows 10 version 1809 environment.

Google Oauth - TokenVerifier How to USE?

I'm trying to use Google OAuth with Sign in & Sign Up for my Web Server Application.
This is the page : https://developers.google.com/identity/sign-in/web/backend-auth that I have referenced, but I am stuck in using the Google Client API, the TokenVerifier that is mentioned below in the document. I tried to find some examples, but I couldn't find one, as I am not sure how to handle the parameters in the methods that the sample shows.
import com.google.api.client.googleapis.auth.oauth2.GoogleIdToken;
import com.google.api.client.googleapis.auth.oauth2.GoogleIdToken.Payload;
import com.google.api.client.googleapis.auth.oauth2.GoogleIdTokenVerifier;
...
GoogleIdTokenVerifier verifier = new GoogleIdTokenVerifier.Builder(transport, jsonFactory)
.setAudience(Arrays.asList(CLIENT_ID))
.build();
// (Receive idTokenString by HTTPS POST)
GoogleIdToken idToken = verifier.verify(idTokenString);
if (idToken != null) {
Payload payload = idToken.getPayload();
if (payload.getHostedDomain().equals(APPS_DOMAIN_NAME)
// If multiple clients access the backend server:
&& Arrays.asList(ANDROID_CLIENT_ID, IOS_CLIENT_ID).contains(payload.getAuthorizedParty())) {
System.out.println("User ID: " + payload.getSubject());
} else {
System.out.println("Invalid ID token.");
}
} else {
System.out.println("Invalid ID token.");
}
For example, I know what these CLIENT_ID, ANDROID_CLIENT_ID, IOS_CLIENT_ID parameters mean in the sample code(in the reference page), but the server only receives id_token, which is basically a String Text. (That was made by the google api in the client-side, the javascript)
So, I do not have these parameter values passed to the server from the client. I know that google shows another way: the tokeninfo endpoint, but they mentioned that it is for only 100user/month only. (If I translated it correctly) However, for the tokeninfo endpoint way, they return the JSON file of containing client ids, which I think that would be the values for the parameters that I mentioned before, but I do not want to use the token info endpoint method.
So, my question is, how do I get the right parameter values for the sample code that is showed in the google document page? I only receive id_token value from the client.
ANDROID_CLIENT_ID or IOS_CLIENT_ID should be hard coded (in a config file) in your server's code.
Essentially your server is getting an id_token in a request and you need to make sure if it is meant for your app or server by checking the audience in there and making sure it matches one of the values you are expecting.

Resources