How to check Google Service Account access for a domain? - google-cloud-platform

I have a service account that I have created in a project in GCP. This service account will then be provided access to different domains using Domain-wide Delegation.
When the Domain-wide Delegation is set up, I need to control scenarios where the correct scopes are not allocated to the service account. Is there a way that a service account can see what scopes it has access to?
The only way I can think of currently is just to try certain API calls to see if they return 403 or not, but that seems like a waste of an API call. I know when using OAuth2, you can call https://www.googleapis.com/oauth2/v1/tokeninfo?access_token= to get the scopes, but I would need to know the customerID of the domain I am querying against. All of the API calls I plan on using will either be for a specific resource, or take a customerID parameter, so access could be different per domain.
If it's worth knowing, I am currently using the google-api-dotnet-client as it handles a lot of the authentication for me, but if this is easier to do in HTTPS calls then I'm happy to do that.
Update: Thought it might help to show an example of the code I am using to access the Google APIs, to show where I might run into issues. The code itself works fine, I am just trying to think about all possible scenarios where things could go wrong
string serviceAccountEmail = "";
string serviceAccountPrivateKey = "";
ServiceAccountCredential serviceCredential = new(new ServiceAccountCredential.Initializer(serviceAccountEmail)
{
Scopes = new[] {
DirectoryService.Scope.AdminDirectoryDeviceChromeosReadonly
},
User = "" // This would be different per Google account I am accessing
}.FromPrivateKey(serviceAccountPrivateKey));
DirectoryService service = new(new BaseClientService.Initializer()
{
HttpClientInitializer = serviceCredential
});
string customerId = ""; // This would be different per Google account I am accessing
// This could error if the DirectoryService.Scope.AdminDirectoryDeviceChromeosReadonly was not granted to the service account
var result = await services.Chromeosdevices.List(customerId).ExecuteAsync();

Related

Accessing users account with service account

We need to use an API to verify if a certain user exists as managed account (it means, that belongs to our Google Domain organization).
GSuite adminSDK performs that operation, however, it requires OAuth2 authentication, authorized by an authenticated user - https://developers.google.com/admin-sdk/reports/v1/guides/authorizing .
My question is, if there any way to use that API with service account, or any other methos to retrieve this information with service account, since it would be used in a Server-2-Server scenario.
Thanks,
Vasco
As you may know, Service Accounts don't belong to an individual end user, but to an application. An administrator of a G Suite domain, though, can authorize the Service Account to access user data, that is, to impersonate users in the domain. This is called domain-wide delegation.
To achieve this, go to the Admin console and follow the steps specified here.
Reference:
Delegating domain-wide authority to the service account
Reports API > Perform G Suite Domain-Wide Delegation of Authority
Since this is completely not obvious - the docs "hint" at this, but don't spell it out and I had trouble finding any specific examples that worked. They all kept returning this error:
Google.GoogleApiException: 'Google.Apis.Requests.RequestError
Not Authorized to access this resource/api [403]
Errors [
Message[Not Authorized to access this resource/api] Location[ - ] Reason[forbidden] Domain[global]
]
The basic issue that the service account MUST impersonate another user. It's mentioned in this link at the bottom, highlighted in blue:
https://developers.google.com/admin-sdk/directory/v1/guides/delegation
Only users with access to the Admin APIs can access the Admin SDK Directory API, therefore your service account needs to impersonate one of those users to access the Admin SDK Directory API. Additionally, the user must have logged in at least once and accepted the Google Workspace Terms of Service.
But it just wasn't clicking as to how I was supposed to do that - was this some setting hiding in one of the admin consoles? No - you pass this as part of your initial connection.
So just to put the instructions in one place and hopefully save someone else the same headache, these were the steps:
Created a project via https://console.developers.google.com
Searched for then enabled the Admin SDK API
Created a Service Account
Show domain-wide delegation / Enable G Suite Domain-wide Delegation
At this point I had a service account name, unique ID, Email, and Client ID
It generated a key file (json) that I downloaded.
Go to: https://admin.google.com
Security > API Controls.
Manage Domain Wide Delegation
Added entry using client ID from above, applied these two scopes - you can apply other scopes as needed.
https://www.googleapis.com/auth/admin.directory.user.readonly
https://www.googleapis.com/auth/admin.directory.group.member.readonly
Then I created a .NET Core console app and installed these NuGet packages:
Google.Apis
Google.Apis.Auth
Google.Apis.Admin.Directory.directory_v1
Here's an ugly proof of concept with everything working:
using System;
using System.IO;
using System.Net;
using Google.Apis.Admin.Directory.directory_v1;
using Google.Apis.Auth.OAuth2;
using Google.Apis.Services;
namespace GoogleDirectoryTest
{
class Program
{
static void Main(string[] args)
{
var dirService = GetDirectoryService(#"C:\tmp\google-cred-sample-12345abdef.json", "user-with-admin-permission-here#mydomainhere.com", "My App Name");
var list = dirService.Users.List();
list.Domain = "mydomainhere.com";
var users = list.Execute();
foreach (var user in users.UsersValue)
{
Console.WriteLine($"{user.Name.FullName}");
}
Console.ReadKey();
}
static DirectoryService GetDirectoryService(string keyfilepath, string impersonateAccount, string appName)
{
using (var stream = new FileStream(keyfilepath, FileMode.Open, FileAccess.Read))
{
var credentials = GoogleCredential.FromStream(stream).CreateWithUser(impersonateAccount);
if (credentials.IsCreateScopedRequired)
credentials = credentials.CreateScoped(new[] { DirectoryService.Scope.AdminDirectoryUserReadonly });
var service = new DirectoryService(new BaseClientService.Initializer()
{
HttpClientInitializer = credentials,
ApplicationName = appName,
});
return service;
}
}
Hopefully this saves someone else some headaches.
A service account is a special kind of account used by an application, rather than a person.
You can use a service account to access data or perform actions by the robot account itself, or to access data on behalf of Google Workspace or Cloud Identity users.
Prerequisites:
A Google Cloud Platform project
With the Admin SDK API enabled service account with domain-wide delegation.
A Google Workspace domain.
With account in that domain with administrator privileges.
Visual Studio 2013 or later
Step 1: Set up the Google Cloud Platform project
Create Google Cloud project
A Google Cloud project is required to use Google Workspace APIs and build Google Workspace add-ons or apps.
If you don't already have a Google Cloud project, refer to: How to create a Google Cloud project
Enable Google Workspace APIs
Before using Google APIs, you need to enable them in a Google Cloud project.
To Enable Google Workspace APIs refer to: How to Enable Google Workspace APIs
For this example you are enabling the the Admin SDK Directory API
with the data scope /auth/admin.directory.user.readonly.
Create Service Account with domain-wide delegation
To create service account refer to: How to create service account?
In the Domain wide delegation pane, select Manage Domain Wide Delegation.
Download Service Account private key (p12 format)
Download p12 file contains the private key for your Service Account.
Step 2: Set up the Google Workspace
Enable API access in the Google Workspace domain with
To enable API access in Google Workspace domain, refer to: how to enable API access
Delegating domain-wide authority to the service account
To call APIs on behalf of users in a Google Workspace organization, your service account needs to be granted domain-wide delegation of authority in the Google Workspace Admin console by a super administrator account
To delegating domain-wide authority in Google Workspace domain, refer to: How to Delegating domain-wide authority to the service account
Step 3: Prepare Visual Stodio project -
Create a new Visual C# Console Application (.NET Framework) project in Visual Studio.
Open the NuGet Package Manager Console, select the package source nuget.org, and run the following commands:
Install-Package Google.Apis.Auth
Install-Package Google.Apis.Admin.Directory.directory_v1
Step 4: Add code
Full example at GitHub
List top 10 users alias from Google Workspace Domain
/// <summary>
/// Example how to list all users from google workspace domain, using a service account (user impersonation).
/// </summary>
internal class Program {
static void Main(string[] args) {
// Scope for only retrieving users or user aliases.
string[] _scopes = {
"https://www.googleapis.com/auth/admin.directory.user.readonly"
};
var _paramters = new SACInitializeParameters(
// The service account ID (typically an e-mail address like: *#*iam.gserviceaccount.com)
serviceAccountId: "[Service Account ID]",
// The full path; name of a certificate file
x509CertificateFilePath: "[X509 Certificate File]",
// The email address of the user you trying to impersonate
impersonateEmail: "[User Email]",
// The scopes which indicate API access your application is requesting
scopes: _scopes);
using (var directoryService = DirectoryServiceFactory.CreateDirectoryService(_paramters)) {
// Retrieves a paginated list of either deleted users or all users in a domain.
var request = directoryService.Users.List();
// The unique ID for the customer's Google Workspace account
// the `my_customer` alias represent current identety account's
request.Customer = "my_customer";
request.MaxResults = 10;
var response = request.Execute();
foreach (var user in response.UsersValue) {
System.Console.WriteLine($"{user.Name.FullName}, {user.PrimaryEmail}, {user.Id}");
}
}
}
}

AWS Cognito authorizer not working with AWS SDK for Android

In my Android project, I use the AWS SDK to register users thanks to Cognito and call APIs in API Gateway. In my Cognito user pool, I created a user pool group. The purpose of that group is to only allow the users in that group to call a specific API.
In order to make it work, I try to follow this tutorial (especially the video). So I created a Cognito authorizer, added it to my method request in API Gateway, and try to call the API from my app using the AWS SDK:
#Service(endpoint = "https://abcdefghig.execute-api.eu-central-1.amazonaws.com/staging")
public interface AwsdemoapiClient
{
#Operation(path = "/test-api", method = "GET")
Empty testApiGet();
}
The problem is: whether the user is authenticated or not, and in the group or not, I always get the following error, when I call testApiGet() in my app: 401 Unauthorized, even if I have the right authorization in my IAM roles. After some research, it looks like the id token is missing, which could be the reason why I get that error.
But isn't it supposed to be automatically managed by the AWS SDK for Android? How can I fix that?
Thanks for your help.
Sending the id token in the header actually solved the problem:
#Service(endpoint = "https://abcdefghig.execute-api.eu-central-1.amazonaws.com/staging")
public interface AwsdemoapiClient
{
#Operation(path = "/test-api", method = "GET")
Empty testApiGet(#Parameter(name = "Authorization", location = "header") String idToken);;
}
You can get the id token by calling the following function:
CognitoUserPool pool = new CognitoUserPool(context, userPoolId, clientId, clientSecret, region);
pool.getCurrentUser().getSessionInBackground(...);

AWS Cognito User Pools and OpenId

I am playing around with Amazon Cognito and after reading some of the docs and creating a user pool I am running into some issues. I believe that a cognito user pool can be used with OpenId to redirect the user to a hosted UI for user authentication (without federating out to another provider). I have tried to use the authentication options in DotNetCore 2 to do this as this is something I have done previous with other providers.
I have the following:
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
})
.AddCookie()
.AddOpenIdConnect(options =>
{
options.ResponseType = "code";
options.MetadataAddress = $"https://cognito-idp.{authOptions.AwsRegion}.amazonaws.com/{authOptions.PoolId}/.well-known/openid-configuration";
options.ClientId = authOptions.ClientId;
options.ClientSecret = authOptions.ClientSecret;
});
but everytime I try it always returns
{"code":"BadRequest","message":"The server did not understand the operation that was requested.","type":"client"}
Just wondering if anyone had any experience with this please? I have tried to create the user pool in different regions just to make sure that it is not only supported in certain regions but always get the same.
I used to have the same problem. Configured my pool and code according to this tutorial. The crucial part was
Another configuration that may be important is the App integration > Domain name. It allows us to configure what will be the domain of the sign-in and sign-up pages.
After I configured domian name everything worked fine.

initialize amazon aws cognito client via BasicAWSCredentials vs CognitoAWSCredentials

What is the difference between these two approaches of initializing a new AmazonCognitoIdentityClient?
AmazonCognitoIdentity identityClient = new AmazonCognitoIdentityClient(
new BasicAWSCredentials("access_key_id", "secret_access_key")
);
identityClient.GetOpenIdTokenForDeveloperIdentity()
-
AmazonCognitoIdentity identityClient = new AmazonCognitoIdentityClient(
new CognitoAWSCredentials ("IDENTITY_POOL_ID", "REGION_NAME");
);
identityClient.GetOpenIdTokenForDeveloperIdentity()
Although most examples on the internet show BasicAWSCredentials being used to instantiate a CognitoIdentityClient, but the method signature in the doc says it accepts AWSCredentials class - both BasicAWSCredentials as well as CognitoAWSCredentials are subclasses of the AWSCredentials class. Hence, i am assuming both should be working normally i guess???
I am trying to understand how will this difference, impact the following:
privileges,
timeouts,
etc?
API reference for cognitoIdentityClient constructors is here: http://docs.aws.amazon.com/sdkfornet/v3/apidocs/index.html?page=CognitoIdentity/TCognitoIdentityCognitoIdentityClient.html&tocid=Amazon_CognitoIdentity_AmazonCognitoIdentityClient
In client device you should instantiate AmazonCognitoIdentityClient client using CognitoAWSCredentials. Using STS service, AWS client will obtain temporary credentials that will let the client assume the role you previously defined in your identity pool. Typically this role would have very limited access to your AWS resources. (S3 upload to a specific bucket etc.) This is like giving out to people a special type of your car key which can only turn on the music system, not the engine.
On the other hand GetOpenIdTokenForDeveloperIdentity is a special API call that needs developer credentials. You should never deploy developer credentials to any client device and you should keep it only on your server. Once you instantiate a AmazonCognitoIdentityClient using developer credentials on your server, you can expose a REST endpoint for clients to obtain OpenId token (for a given identity id or creating a new one). Let's assume that your users are logging in to your API using their username and password and you return them a custom access token which is stored in your database. After that your endpoint may implement this logic:
Fetch unique identifier for your user using custom token (user id, username, e-mail etc.)
Use LookupDeveloperIdentity and find out the identityId for the given username.
Use GetOpenIdTokenForDeveloperIdentity with found identityId and send back to the client. So they can "login" to that identityId.
If this user has not any identityId assigned to its username, create a new one and send back.
As you see developer AWS tokens are enabling a couple of sensitive API calls. Now let us assume that client had developer access token and thus has access to GetOpenIdTokenForDeveloperIdentity call. Then they would be able to generate OpenId tokens, switch to other people's identities easily and access their private data.
If you don't use developer authenticated identities (https://docs.aws.amazon.com/cognito/latest/developerguide/developer-authenticated-identities.html) you do not need GetOpenIdTokenForDeveloperIdentity mechanism though. If you are using only public authentication methods (Twitter, Facebook etc.) you should ignore the second paragraph and use CognitoAWSCredentials.
As described in Çağatay's answer, the BasicAwsCredentials are instantiated with your developer credentials. What he described about when to use each constructor is absolutely correct in every case other than developer authenticated identities.
Since this code will be deployed on a back end service, it's exposure is less of a concern. Mobile clients will communicate with this server and get the token from it, with which they will get credentials. The credentials are never exposed to the user in this pattern.
As you can see in the Cognito documentation, the sample has BasicAwsCredentials. An end to end sample is visible in this blog post.

SOAP Exception when trying to Access GP WebServices

I am trying to write a program that simply connects up to a GP WebService and invokes it's GetCustomerList() method to get customers from Great Plains. The code I am outlining below is an duplication of what I was able to find in the documentation, however, when I run it I am getting a SoapException. I am not sure if I am missing credentials (eg. username and password) or if I am even invoking this properly. I believe that I have the Security settings set correctly in the Dynamics Security Console, but I am not 100% sure or if there is anything else I need to configure. Any help would be greatly appreciated.
public IList<string> GetCustomerNames()
{
//Added a ServiceReference to the actual WebService which appears to be working
var service = new DynamicsGPClient();
var context = new Context();
var customerKey = new CustomerKey();
var companyKey = new CompanyKey();
//Trying to load the test data
companyKey.Id = (-1);
context.OrganizationKey = (OrganizationKey)companyKey;
//Trying to load the test data
customerKey.Id = "AARONFIT0001";
var customers = service.GetCustomerList(new CustomerCriteria(), context);
return customers.Select(x => x.Name).ToList();
}
Sounds like a security issue ... are you getting a specific error message ... that might be helpful ...
Also found this ...
It sounds to me like you need a Windows App Pool Identity on your
webservice. Right now you have the IIS Security set to "anonymous", so the
clients aren't required to pass the credentials when they call your methods.
You'll need to turn that off and run your app pool as a windows account. Once
you've got that working, you can choose if you want to just add that one App
Pool identity into the security for webservices, and have all the operations
done as that account (poor security), or, in your wrapper, you could use the
HTTP User's context identity, and set it to the "WorkOnBehalfOf" property for
the GP WebService call you're actually using.
You'll have to give the App
Pool identity "WorkOnBehalfOf" permission in the web service security
console, and then whichever users you want to call the webservice. This keeps
the security model intact, and you're authorizing that the users calling the
wrapped webservice actually do have permission to call the original GP
Webservice methods. (This is how Business Portal forwards requests to the
webservices.)
https://groups.google.com/forum/?fromgroups=#!topic/microsoft.public.greatplains/W7gAo_zXit8