Get ServiceAccountCredentials from ComputeEngineCredentials to do user impersonation - java

I'm deploying a Java API that does user impersonation via Domain-widge Delegation to access the calendar of a user. For this I have created a service account, done the delegation and given it the right permissions and access to the users calendar.
While developing locally I've been using a downloaded key for the service account in the JSON format, and pointed to it with the GOOGLE_APPLICATION_CREDENTIALS environment variable. In my code, I create the credentials like this using the Java client library:
#Bean
#Qualifier("userCredentials")
public GoogleCredentials impersonateCalendarOwner() throws IOException {
final List<String> scopes = Collections.singletonList(CalendarScopes.CALENDAR);
return ((ServiceAccountCredentials) GoogleCredentials.getApplicationDefault())
.toBuilder()
.setServiceAccountUser(GOOGLE_CALENDARS_OWNER)
.build()
.createScoped(scopes);
}
This works fine locally, but when running in Cloud Run I get:
nested exception is java.lang.ClassCastException:
class com.google.auth.oauth2.ComputeEngineCredentials cannot be cast to
class com.google.auth.oauth2.ServiceAccountCredentials
After many hours of debugging I think I finally understand how getApplicationDefault works. I think what happens locally is this:
getApplicationDefault looks first at the environment variable GOOGLE_APPLICATION_CREDENTIALS, and because it is set it reads the credentials from that file
Because the file is a service account key file it creates a ServiceAccountCredentials instance so the "cast" succeeds.
And in Cloud Run it happens like this:
getApplicationDefault looks first at the environment variable GOOGLE_APPLICATION_CREDENTIALS, but on Cloud Run it isn't set
So it falls back to the service account identity the revision is running as by fetching the credentials from the Metadata server and returns them as an instance of ComputeEngineCredentials (since the Metadata server doesn't hand out the keys of the service account).
It then tries to cast ComputeEngineCredentials to ServiceAccountCredentials which obviously doesn't work.
So now my questions remain:
How can I convert some ComputeEngineCredentials to ServiceAccountCredentials?
Is there some other way to get the credentials as an instance of ServiceAccountCredentials instead?
Is there some other way to do user impersonation that doesn't require an instance of ServiceAccountCredentials?
My current idea is to use the ComputeEngineCredentials I get, to fetch the service account JSON key file from Secret Manager at service start up and then pass that file to ServiceAccountCredentials.fromStream but it feels like an extra step since the ComputeEngineCredentials I would be using are already the credentials of the same service account I would be fetching the key for.

The credentials provided by the instance metadata (Metadata server) does not include the service account private key which is required to sign requests.
Therefore the service account impersonation code that you are trying to use will not work. You will need to use an actual service account JSON that contains the private key.
My recommendation is to setup the default service account with a role to access Secret Manager. Store a service account JSON key file as data in Secret Manager. On your program startup fetch the service account JSON content and proceed with your impersonation code.

You need to tell the Cloud Run service to run as your service account:
gcloud run deploy --image IMAGE_URL --service-account SERVICE_ACCOUNT

Related

Scope Issue in Azure Graph Rest API Java

I am new using Azure Graph Rest API Java using this repo.
My aim is to list all of the users in the AAD tenant
So far I was only able to get to this:
List<String> scopes= Arrays.asList("https://graph.microsoft.com/User.Read.All");
AzureProfile profile = new AzureProfile(tenantId, subscriptionId, AzureEnvironment.AZURE);
final ClientSecretCredential credential = new ClientSecretCredentialBuilder()
.clientId(clientId)
.clientSecret(clientSecret)
.tenantId(tenantId)
//.httpClient(client)
.authorityHost(profile.getEnvironment().getActiveDirectoryEndpoint())
.build();
TokenCredentialAuthProvider tokenCredentialAuthProvider = new TokenCredentialAuthProvider(scopes, credential);
GraphServiceClient<Request> graphClient =
GraphServiceClient
.builder()
.authenticationProvider(tokenCredentialAuthProvider)
.buildClient();
UserCollectionPage users = graphClient.users()
.buildRequest()
.get();
for(User user: users.getCurrentPage()){
System.out.println(user.displayName);
System.out.println(user.id);
System.out.println(user.userPrincipalName);
}
However, I run into this error instead:
Caused by: java.io.IOException:
java.util.concurrent.ExecutionException:
com.microsoft.aad.msal4j.MsalServiceException:
AADSTS1002012: The
provided value for scope https://graph.microsoft.com/User.Read.All
openid profile offline_access is not valid. Client credential flows
must have a scope value with /.default suffixed to the resource
identifier (application ID URI).
It seems the Scope that I have used is wrong/insufficient, but I am not too sure what should I use the scope with. Any idea?
It is written in the documentation that:
Client credentials requests in your client service must include
scope={resource}/.default. Here, {resource} is the web API that your
app intends to call, and wishes to obtain an access token for. Issuing
a client credentials request by using individual application
permissions (roles) is not supported. All the app roles (application
permissions) that have been granted for that web API are included in
the returned access token.
The Client Credential flow is best suited for situations where you have a Deamon App that will have to authenticate and get access to some kind of a resource through a Non-Interactive way, which in sequence means that the permissions for this Deamon App have been configured and consented from a step done prior to the auth request.
The /.default scope can be translated as the request of the Background App that runs unattended, to get the bulk of the permissions that it has been configured with and access the resource that it asks.
In plain english, the use of the above scope in the Client Credentials flow is a convention that has to be implemented always when this flow is chosen :P.
I tried to reproduce the same in my environment via Postman and got below results:
I registered one Azure AD application and added API permissions like below:
When I tried to generate access token with same scope as you via Postman using client credentials flow, I got same error as below:
POST https://login.microsoftonline.com/<tenantID>/oauth2/v2.0/token
grant_type:client_credentials
client_id: <appID>
client_secret: <secret_value>
scope: https://graph.microsoft.com/User.Read.All openid profile offline_access
Response:
To resolve the above error, you must change your scope to https://graph.microsoft.com/.default if you are using client credentials flow.
After changing the scope, I'm able to generate access token successfully like below:
POST https://login.microsoftonline.com/<tenantID>/oauth2/v2.0/token
grant_type:client_credentials
client_id: <appID>
client_secret: <secret_value>
scope: https://graph.microsoft.com/.default
Response:
When I used the above token to call below Graph query, I got the list of users with display name, id and user principal name successfully like below:
GET https://graph.microsoft.com/v1.0/users?$select=displayName,id,userPrincipalName
Response:
In your case, change scope value in your code like below:
List<String> scopes= Arrays.asList("https://graph.microsoft.com/.default");

Retrieve Firebase User using token, from Google Cloud application running locally

I'm working on a Java API that functions as an endpoint API, and on production
it runs on the Google Cloud Platform. API methods are called by passing a Firebase token as part of the URL, and the token is used to create a User that's available inside the API method:
#ApiMethod(path = "myPath/{tokenId}/doSomething", httpMethod = "get")
public ResponseMessage ReturnSomething(#Named("tokenId") String tokenId, User user) throws UnauthorizedException, BadRequestException, InternalServerErrorException, FirebaseAuthException
{
if (user == null)
...
In production, when the URL is called from an Angular application on Firebase that passes the token in the URL, user is correctly created. I don't fully understand how the User is created from the token, I only know that it somehow happens "automatically" as part of Firebase integration with Google Cloud.
I want to debug the API locally by using Debug As > App Engine from inside Eclipse. When I do this however, and call the API from my local Angular application running using Firebase serve, the token is correctly passed to my locally running API, however user is always null.
#ApiMethod(path = "myPath/{tokenId}/doSomething", httpMethod = "get")
public ResponseMessage ReturnSomething(#Named("tokenId") String tokenId, User user) throws UnauthorizedException, BadRequestException, InternalServerErrorException, FirebaseAuthException
{
if (user == null)
// this is always null
I suspect this is a problem with my locally running Java API correctly authenticating to Firebase. I've looked at this guide, which suggests that the GOOGLE_APPLICATION_CREDENTIALS property on Windows should be set to the path of the JSON key of the App Engine default service account, which is the normal way to ensure that local access is granted to Google Cloud (and presumably Firebase) resources.
I've added this explicitly (I'd already run gcloud auth application-default login anyway, using the command line) however it's still not working. I just get null for the user and there's no indication of what's going on. I don't want to programatically authenticate as that means altering the API code to authenticate differently during debugging. How do I retrieve a User when debugging locally as App Engine?
UPDATE
I've realised that although the tokenId in the URL is present, I'm getting the following error when the API is called:
WARNING: Authentication failed: com.google.api.auth.UnauthenticatedException: No auth token is contained in the HTTP request
The tokenId value in the code below is a valid value, so I'm not sure why I'm getting this message:
#ApiMethod(path = "myPath/{tokenId}/doSomething", httpMethod = "get")
public ResponseMessage ReturnSomething(#Named("tokenId") String tokenId, User user)
I discovered that this was actually a problem with the Auth0 library that's being used in Angular to support authenticated HTTP requests to the Java API. The Auth0 library is used to inject the auth token into the Bearer of the request header whenever an Angular http.get is called from the Angular application. Creation of the User depends on this property being present in the HTTP header, with its value set to the value of the auth token.
I fixed this by altering the config for this library. I needed to temporarily whitelist localhost for the port (8080) that the API runs on, to allow Auth0 to inject the token into the HTTP header whenever there is a request to localhost:8080
const jwtConf: JwtModuleOptions = {
config: {
tokenGetter: getToken,
whitelistedDomains: ['localhost:8080']
}
};

"Parameter client_assertion_type is missing" in keycloak

I am trying out get the access token from the super user so that I can the same to create new users in key cloak, I have deployed keycloak in wildfly and when I try to do the get call, I am getting Invalid user credentials as response,
How to know the actual credentials?
And when I try to update the password from the console, I getting the error message like below.
Since I am new to this and din't find enough information from internet also, any kind of help will be appreciated .
Updated:
Now i am getting new error description as Parameter client_assertion_type is missing like below. What should be client_assertion_type here ?
This keycloak help page describes the most likely reason for the second error:
Q: When logging in, I get an error: *Parameter client_assertion_type is missing [invalid_client].
A: This error means your client is configured with Signed JWT token credentials, which means you have to use the --keystore parameter when logging in.
Alternatively you can disable using JWT tokens for the client in Keycloak.
For your information, the client_assertion_type would probably be urn:ietf:params:oauth:client-assertion-type:jwt-bearer. But then you'd get another error because the client_assertion is missing.
If ccp-portal is a confidential client using client authentication with signed JWT then the Keycloak doc states that
During authentication, the client generates a JWT token and signs it
with its private key and sends it to Keycloak in the particular
backchannel request (for example, code-to-token request) in the
client_assertion parameter.
I guess it's not possible to generate a JWT with PostMan.
This is meant for backchannel client-keycloak communication, not for
user authentication.
Solutions
You can use the admin-cli as client_id instead of your ccp-portal client. The admin-cli should be in the list of clients configured for your ccp realm. You can see that from the Keycloak interface.
Another option is allow direct access grants in ccp-portal client config.
Finally you could use ccp-portal client in your application configured with one of the Keycloak client adapters, instead of POSTMan.
As subrob sugrobych mentionned, parameters should be passed as form-data.
first of all, when you are posting data to keycloak over a rest client, you need to input parameters as form paramaters, and not as query parameters. This is why you are getting this strange error of not providing parameter grant_type, when you obviously are providing it. Same is valid for accessing keycloak api via code.
Next thing you need to think about are roles for your superuser. You can assign realm roles and client roles. There is a client named 'realm-management' which contains roles which would normally count as "system roles". You will need to use them. When you are getting HTTP code 403, it means, that probably your user is missing a role from this client.

How to modify and serve file content with User credential

I want to modify the content of a file stored in the Google Cloud Storage bucket and serve it to the user. The delivery content will change based on the User profile.
By the following ways I am trying to achieve it.
User will request for a particular hash key, this key is mapped to a cloud storage url. The contents are read, modified and served to the User when the hash key is used in the url. Say /serve/hash-key.
User will not know the cloud storage url, nor will have a direct access to the content of the file.
Java servlet is mapped in the server-side to process the User request. To process all /serve/* queries.
User can do the request either through the current tab or through a new tab.
The problem what I am facing is when the request is processed in the Java Servlet, App Engine User details could not be fetched using OAuthServiceFactory class. OAuthRequestException is thrown when getCurrentUser Api is called.
Is there a possible way to achieve this or address the error?

Android OAuth with valid "CONSUMER_KEY" and "CONSUMER_SECRET"

I have a valid consumer_key and consumer_secret for my app to access resources on a certain website.
I create the consumer like this:
private void createConsumer(){
mConsumer = new CommonsHttpOAuthConsumer(CONSUMER_KEY, CONSUMER_SECRET);
}
I create the provider like this:
private void createProvider(){
mProvider = new CommonsHttpOAuthProvider(REQUEST_TOKEN_URL, ACCESS_TOKEN_URL,
AUTHORIZE_URL);
}
I then use an AsyncTask to retrieve what I assume is a valid AccessToken from the server.
mAccessTokenUrl = mProvider.retrieveRequestToeken(mConsumer, CALL_BACK_URL);
with this call I get something similar to this:
``http://www.mygarden.org/oauth/authorize?/oauth_token=XXXXXXXXXXXXXXhs343sjd&oauth_callback=http%3A%2F%2Flocalhost
I'm assuming the token is correct because if I test this with the incorrect credentials I don't get anything back.
Question 1:
Is this callback url corrent, because on the site it does not specify a callback url, just the app's website; and how should I use it on the android app?
Question 2:
How do I now use the Access Token to consume certain services on the website, for example to get all the plants I should use this link: http://api.mygarden.org/activities/all which should return certain data in xml/json format
Extra information is that all calls to the API should use the http GET method unless specified otherwise. I do not want the user to first register on the site, I just want to consume the services they provide using my app's access credentials. If user credentials are necessary, could I embed them within the URL and grant the user access automatically?
The website is: http://www.mygarden.org
Thanks for your help.
To get a list of all the plants you don't need to authenticate a user. This is only necessary for user related actions, like posting status updates or adding plants to your garden.
So you only have to send a call to oauth/request_token and then oauth/access_token, after that you're ready to get some data from the API
Answer for Question 1:
If your developing an android application you don't need the callback url, this is needed when you want to authenticate a user trough a website. Make sure you select Client in the app settings on mygarden.org
Answer for Question 2:
To get all the plants you need plants/all. If you're using a standard oauth library, all the required oauth parameters are automatically added to your query
I'm a developer at mygarden.org, you can always contact our support ;)

Categories