Connect to BigQuery with Service Account Credentials? - google-bigquery

I create a Google service account to access BigQuery, it provides a .json credentials file, which looks like:
{
"type": "service_account",
"project_id": "<redacted>",
"private_key_id": "<redacted>",
"private_key": "-----BEGIN PRIVATE KEY-----<redacted>\n-----END PRIVATE KEY-----\n",
"client_email": "<redacted>",
"client_id": "<redacted>",
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
"token_uri": "https://oauth2.googleapis.com/token",
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
"client_x509_cert_url": "<redacted>"
}
Using a secrets manager, I load the above into a Python dict bigquery_credentials, then try to connect:
client = bigquery.Client(credentials=bigquery_credentials)
I get the error:
DefaultCredentialsError: Could not automatically determine credentials. Please set GOOGLE_APPLICATION_CREDENTIALS or explicitly create credentials and re-run the application. For more information, please see https://cloud.google.com/docs/authentication/getting-started
Clearly, I'm not passing the credentials in a valid way and bigquery.Client isn't recognizing them.
The docs say "Set the environment variable GOOGLE_APPLICATION_CREDENTIALS to the path of the JSON file that contains your service account key."
I'd prefer to avoid using the file system. This code is running in an environment where I may not have access to the local file system. Also, I'm using a secret manager: storing secrets on the file system is considered a security risk.
Is there any way I can specify the service account .json credentials to bigquery.Client?

I just had this one, you can use the following:
from google.cloud import bigquery
client = bigquery.Client.from_service_account_json("/path/to/your/file.json")

Related

GCP text-to-Speech API auth issue

I was trying the above api in postman. Here is the request json:
{
"input":{
"text":"Flutter is awesome!"
},
"voice":{
"languageCode":"en-gb",
"name":"en-GB-Standard-A",
"ssmlGender":"FEMALE"
},
"audioConfig":{
"audioEncoding":"MP3"
}
}
for auth, i chose Bearer in postman auth and first executed the following command in my terminal to get the token:
gcloud auth application-default print-access-token
i pasted this token in auth header, and i received the following response :
{
"error": {
"code": 403,
"message": "Your application has authenticated using end user credentials from the Google Cloud SDK or Google Cloud Shell which are not supported by the texttospeech.googleapis.com. We recommend configuring the billing/quota_project setting in gcloud or using a service account through the auth/impersonate_service_account setting. For more information about service accounts and how to use them in your application, see https://cloud.google.com/docs/authentication/. If you are getting this error with curl or similar tools, you may need to specify 'X-Goog-User-Project' HTTP header for quota and billing purposes. For more information regarding 'X-Goog-User-Project' header, please check https://cloud.google.com/apis/docs/system-parameters.",
"status": "PERMISSION_DENIED",
"details": [
{
"#type": "type.googleapis.com/google.rpc.ErrorInfo",
"reason": "SERVICE_DISABLED",
"domain": "googleapis.com",
"metadata": {
"consumer": "projects/12345678910",
"service": "texttospeech.googleapis.com"
}
}
]
}
}
I am very new to GCP in general and don't know how to navigate this issue. For additional context, i am trying to make a REST API call where i send the text and get a base64encoded string containig audio back. Any help is appreciated.
This is confusing/complex but the error is helpful:
Your application has authenticated using end user credentials from the Google Cloud SDK or Google Cloud Shell which are not supported by the texttospeech.googleapis.com.
NOTE You can try this method using Google's APIs Explorer at this link
text.synthesize.
The issue is that gcloud is an OAuth2 application and tokens issued by gcloud either using gcloud auth print-[access|identity]-token and gcloud auth application-default print-access-token are issued against a Google-managed project (that Google provides for gcloud) and -- importantly -- not one of your own projects.
Google wants to provide gcloud for its users but does not want to provide arbitrary API access (for free) to its users. Hence the "not supported" part of the error.
The solution (as described) is that you should:
Use (or create) your own Google Project
Enable the Text-to-Speech service (API) in this project
Create a Service Account and key
gcloud auth activate-service-account providing the Service Account key
gcloud auth print-access-token to get an access token to invoke the API
See the following link for the steps:
https://cloud.google.com/text-to-speech/docs/libraries

Can I access Google Secrets Manager secrets externally

I am writing an app that I will be hosting in Google Cloud Functions with some config stored in Secrets Manager. I would like to share this information with another node app that is running on my local machine. Is this possible? I have tried using the npm package but I can’t figure out how I can authenticate to get access to the manager.
I am using a service key to access firestore:
import { initializeApp } from "firebase/app";
import { getFirestore } from "firebase/firestore";
const service_key = {
apiKey: myKey,
authDomain: "my-poroject.firebaseapp.com",
projectId: "my-poroject",
storageBucket: "my-poroject.appspot.com",
messagingSenderId: "0123456789",
appId: "0:00000000:web:00000000000000"
}
const app = initializeApp(service_Key);
export const db = getFirestore(app);
This all works perfectly, but I can't see how I would apply the key or 'app' when using secret manager:
const {SecretManagerServiceClient} = require('#google-cloud/secret-manager');
const client = new SecretManagerServiceClient();
As public cloud provider, most of the Google Cloud services are publicly accessible. So YES, you can access the secret from outside.
However, you must have the required credentials and permissions to access the secrets.
You can use a service account key file, which is also a secret (and I never recommend that option, but in some cases, it's useful), to generate an access token and to query safely secret manager. The problem is the service account key file, it's a secret to protect secret... The security level depends on your external platform.
You can also have a look to Identity Federation Pool that can help you to use your already known identity and to be transparently authenticated on Google Cloud. It's very powerful and you no longer need secret on your side and you increase your security posture.

How to authenticate Python client app for access to restricted Google Cloud function?

I've created a Google Cloud function and I would like to access it from a Python application I'm developing. I am able to access the function when there is no authentication required, but can't access the functions when I enable authentication.
Here is the service account key I'm using with stripped out info. The only role it is configured for is invoking cloud functions.
{
"type": "service_account",
"project_id": "XYZ",
"private_key_id": "XYZ",
"private_key": "XYZ",
"client_email": "XYZ",
"client_id": "XYZ",
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
"token_uri": "https://oauth2.googleapis.com/token",
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
"client_x509_cert_url": "XYZ"
}
It seems that authenticated requests require a token that's included in the requests Authorization header, but I don't understand where to acquire this token.
I've tried using the approach outlined here with environment variables and the default auth method, but this doesn't work. I assume this is because the service account key is different from an OAuth token. (I have created a new service account the Cloud Functions invoker permission and am using that service account key). I receive the following error:
google.auth.exceptions.RefreshError: ('invalid_scope: Invalid OAuth scope or ID token audience provided.', '{"error":"invalid_scope","error_description":"Invalid OAuth scope or ID token audience provided."}')
How do I generate this token to authenticate the request from my Python script? Or is the approach with the service account recommended, but something else is going wrong?
I was able to get this working, though at time of writing there is a bug in the documentation that threw me off.
Access protected Cloud Functions is possible by using the IDTokenCredentials class within the google-auth library:
credentials = service_account.IDTokenCredentials.from_service_account_file(
SERVICE_ACCOUNT_JSON_FILE,
target_audience="https://function/url/here",
)
authed_session = AuthorizedSession(credentials)
response = authed_session.post(url)
I have my service account configured with the "Cloud Functions Invoker" role.
Your problem is almost certainly in the roles you gave that service account. Service accounts are finicky and the roles / permissions do not always act the way you think they will in my experience. Start by making a Service Account that has full permissions (project owner). Use that service account in your script then begin limiting the permissions from there. It sounds like you will need at a minimum cloud function "admin". If that works try another level down. Cloud function "developer" etc.
If you are using, for instance, App Engine or even other Cloud Functions to connect to your Cloud Function, you can use this: Function-to-function, the steps, basically are:
Grant the Cloud Functions Invoker.
In the calling function, you'll need to:
Create a Google-signed OAuth ID token with the audience (aud) set to the URL of the receiving function
Include the ID token in an Authorization: Bearer ID_TOKEN header in the request to the function.
import requests
//# TODO<developer>: set these values
REGION = 'us-central1'
PROJECT_ID = 'my-project'
RECEIVING_FUNCTION = 'my-function'
//# Constants for setting up metadata server request
//# See https://cloud.google.com/compute/docs/instances/verifying-instance-identity#request_signature
function_url = f'https://{REGION}-{PROJECT_ID}.cloudfunctions.net/{RECEIVING_FUNCTION}'
metadata_server_url = \
'http://metadata/computeMetadata/v1/instance/service-accounts/default/identity?audience='
token_full_url = metadata_server_url + function_url
token_headers = {'Metadata-Flavor': 'Google'}
def calling_function(request):
//# Fetch the token
token_response = requests.get(token_full_url, headers=token_headers)
jwt = token_response.text
//# Provide the token in the request to the receiving function
function_headers = {'Authorization': f'bearer {jwt}'}
function_response = requests.get(function_url, headers=function_headers)
return function_response.text
I have tested this solution and works as expected.
If you're invoking a function from a compute instance that doesn't have access to compute metadata (e.g. your own server), you'll have to manually generate the proper token:
Self-sign a service account JWT with the target_audience claim set to the URL of the receiving function.
Exchange the self-signed JWT for a Google-signed ID token, which should have the aud claim set to the above URL.
Include the ID token in an Authorization: Bearer ID_TOKEN header in the request to the function.
The Cloud IAP docs have sample code to demonstrate this functionality. The part you could be interested in should be this Authenticating from a service account

Call azure function secured by AAD using client id and client secret

I have an Azure Function secured by the Azure Active Directory.
I can successfully call the application from the browser provided that I'm logged.
Now, what I want to do is to call that function from an application that doesn't use the username and password in order to authenticate but rather a trusted application client id and client secret.
I tried to use for that purpose the same application (say ApplicationA) that has been created for securing the function application.
I went to the App Registrations, found the application created by configuring the Azure Active Directory authentication provider in the Function App Authentication/Authorization settings (ApplicationA) and I set a Client Secret for it.
Now I'm able to obtain a token using said application Client Id and the generated secret.
But when I'm trying to call the Function using that token I'm getting 401 error.
I tried the same operation with a newly created application in App Registration (say ApplicationB) but with the same result, I'm still getting 401.
I even tried to add a Reader Role for that application in Subscriptions but then again - 401 when calling Function with the token.
I know, there's something like API permissions in the application settings.
So I tried to add the permission for ApplicationB to ApplicationA user_impersonation but it also doesn't work so that's not the way.
So how do I grant the permission for a registered Application for which I'm able to acquire a token so I can use this token to call the Function?
EDIT:
I'm using the following code for token acquisition:
using Microsoft.Identity.Client;
string ClientId = "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX";
string ClientSecret = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX";
string TenantId = "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX";
string Authority = $"https://login.microsoftonline.com/{TenantId}";
string[] scopes = new string[] { "https://graph.microsoft.com/.default" };
IConfidentialClientApplication app = ConfidentialClientApplicationBuilder.Create(ClientId)
.WithClientSecret(ClientSecret)
.WithAuthority(new Uri(Authority))
.Build();
AuthenticationResult result = await app.AcquireTokenForClient(scopes).ExecuteAsync();
string token = result.AccessToken;
You need to register at least one application permission (appRole with allowed member type Application) via the manifest into the functions app registration.
Then you can assign the application permission to the client app.
You can see my article for more info on defining permissions: https://joonasw.net/view/defining-permissions-and-roles-in-aad
Example app permission in manifest:
"appRoles": [
{
"allowedMemberTypes": [
"Application"
],
"description": "Allow the application to read all things as itself.",
"displayName": "Read all things",
"id": "32028ccd-3212-4f39-3212-beabd6787d81",
"isEnabled": true,
"lang": null,
"origin": "Application",
"value": "Things.Read.All"
}
],
You need to define the id yourself, it just needs to be a GUID.
The value is what is sent in the tokens in the roles claim.
Description and display name are just for how the permissions shows up in the UI.

ERROR: (gcloud.auth.activate-service-account) Failed to activate the given service account. Please ensure provided key file is valid

I'm trying to follow this guide https://cloud.google.com/speech/docs/getting-started to call GAE speech to text api through curl. But it doesn't seem to work.
I've setup a project and enabled speech to text api. But then when I try to active the service account it fails. I've run diagnostics, tried different accounts, verified the json file (has email), tried gcloud beta init :-(
bash-3.2$ gcloud auth activate-service-account account#project.iam.gserviceaccount.com --key-file=project.json
ERROR: (gcloud.auth.activate-service-account) Failed to activate the given service account. Please ensure provided key file is valid.
The next step though 'gcloud auth print-access-token' returns a token.
But the final step (curl) returns this -
{
"error": {
"code": 403,
"message": "Google Cloud Speech API has not been used in project google.com:cloudsdktool before or it is disabled. Enable it by visiting https://console.developers.google.com/apis/api/speech.googleapis.com/overview?project=google.com:cloudsdktool then retry. If you enabled this API recently, wait a few minutes for the action to propagate to our systems and retry.",
"status": "PERMISSION_DENIED",
"details": [
{
"#type": "type.googleapis.com/google.rpc.Help",
"links": [
{
"description": "Google developers console API activation",
"url": "https://console.developers.google.com/apis/api/speech.googleapis.com/overview?project=google.com:cloudsdktool"
}
]
}
]
}
}
The problem seems to lie in the project(google.com:cloudsdktool instead of mine) used to authenticate the incoming request.
I'm guessing the call to activate-service-account is causing this?
You are getting the error message for google.com:cloudsdktool project because the command you ran with curl gcloud auth print-access-token was using your user account credentials (created by gcloud auth login) and not service account (as you point out that step failed for you).
The command to activate service account is correct (btw you do not need to provide the account, as one from the file will be used)
$ gcloud auth activate-service-account --key-file=project.json
Make sure your project.json is correct file in the right format format. You can create this json key file either in
in https://console.cloud.google.com/iam-admin/serviceaccounts/project?project=YOUR_PROJECT_NAME
gcloud iam service-accounts keys create, see reference guide.
The file will look like
{
"private_key_id": "....",
"private_key": "-----BEGIN PRIVATE KEY-----...-----END PRIVATE KEY-----\n",
"client_email": "...",
"client_id": "...",
"type": "service_account"
}
Note that client_email will be used for ACCOUNT in activate-service-account command.