I am trying to authenticate with Google Storage by passing a service account key manually as a Json object. I am trying to pass a specific service account to be used and not the default service account created by the project. However even when I pass the key of my specific service account, the error I get is still using the default service account. How do I tell the google storage api to use the key being passed to it instead?
var accessKey = {
"type": "service_account",
"project_id": "...",
"private_key_id": "...",
"private_key": "...",
"client_email": "myserviceaccount#gcp-serviceaccounts-keys.iam.gserviceaccount.com",
"client_id": "...",
"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": "..."
};
const projectId = " ...";
const { Storage } = require('#google-cloud/storage');
const storage = new Storage({projectId, accessKey});
const bucket = storage.bucket('test-bucket-aa');
The error I get:
Error: gcp-myprojectid#appspot.gserviceaccount.com does not have storage.objects.get access to the Google Cloud Storage object.
It's strongly preferable to keep the key out of the source.
The Storage client wants a reference to the key's filename.
See: Storage examples
Put the string in your source into a file e.g. key.json and then:
const keyFilename = "/path/to/key.json";
const storage = new Storage({
projectId: projectId,
keyFilename: keyFilename
});
Using ADCs with Cloud Functions and Cloud Storage
PROJECT=... # Your Project ID
ACCOUNT=... # Your Service Account
FUNCTION=... # Your Cloud Function
# Create Service Account
gcloud iam service-accounts create ${ACCOUNT} \
--description="Used by Cloud Functions to Access Cloud Storage" \
--display-name="Cloud Functions Storage Accessor" \
--project=${PROJECT}
EMAIL="${ACCOUNT}#${PROJECT}.iam.gserviceaccount.com"
ROLE="roles/storage.objectAdmin"
# Permit the Service Account `storageAdmin`
gcloud projects add-iam-policy-binding ${PROJECT} \
--member=serviceAccount:${EMAIL} \
--role=roles/${ROLE}
# Deploy the Cloud Functions to run as the Service Account
# Cloud Functions uses ADCs to auth as the Service Account
gcloud functions deploy ${FUNCTION} \
... \
--service-account=${EMAIL} \
--project=${PROJECT}
NOTE Using the above approach, your code is simpler using just const storage = new Storage();, the platform auth's using the Service Accounts credentials.
NOTE It's preferable to set the IAM policy on a specific bucket rather than the project (all its buckets), see: https://cloud.google.com/storage/docs/access-control/iam#project-level_roles_vs_bucket-level_roles
Perhaps, instead of gcloud projects add-iam-policy-binding, you could:
BUCKET=gs://[[YOUR-BUCKET]]
gsutil iam ch serviceAccount:${EMAIL}:roles/${ROLE} ${BUCKET}
https://cloud.google.com/storage/docs/gsutil/commands/iam#ch
Related
GCP allows the Kubernetes service account to impersonate the IAM service account by adding an IAM policy binding between the two service accounts. This binding allows the Kubernetes service account to act as the IAM service account.
gcloud iam service-accounts add-iam-policy-binding GSA_NAME#GSA_PROJECT.iam.gserviceaccount.com \
--role roles/iam.workloadIdentityUser \
--member "serviceAccount:PROJECT_ID.svc.id.goog[NAMESPACE/KSA_NAME]"
We would like to create the same via Terraform resource and we tried this way, refer: article
resource "google_service_account_iam_binding" "service-account-iam" {
service_account_id = "GSA_NAME#GSA_PROJECT.iam.gserviceaccount.com"
role = "roles/iam.workloadIdentityUser"
members = [
"serviceAccount:PROJECT_ID.svc.id.goog[NAMESPACE/KSA_NAME]",
]
}
But we received the below error:
Error: "service_account_id" ("XXX#XXX.iam.gserviceaccount.com") doesn't match regexp "projects/(?:(?:[-a-z0-9]{1,63}\.)(?:a-z?):)?(?:[0-9]{1,19}|(?:a-z0-9?)|-)/serviceAccounts/((?:(?:[-a-z0-9]{1,63}\.)(?:a-z?):)?(?:[0-9]{1,19}|(?:a-z0-9?))#[a-z]+.gserviceaccount.com$|[0-9]{1,20}-compute#developer.gserviceaccount.com|a-z#[-a-z0-9\.]{1,63}\.iam\.gserviceaccount\.com$)"
What's wrong here?
service_account_id is the fully-qualified name of the service account to apply the policy to.
projects/PROJECT_ID/serviceAccounts/SERVICE_ACCOUNT_EMAIL
I want to connect service principal with certificate to authorize using pyspark. I could see the code in scala in this link - https://github.com/Azure/azure-event-hubs-spark/blob/master/docs/use-aad-authentication-to-connect-eventhubs.md
I have client _id and tenant_id and certificate details. Could some please share me the code in pyspark for same?
You add the Azure AD service principal to the Azure Databricks workspace using the SCIM API 2.0. Authentication using Pyspark isn't available.
To authenticate using service principal, you need to follow below steps:
As you already have clientID and tenantID, so the service principal ID already created.
Create the Azure Databricks personal access token
You’ll use an Azure Databricks personal access token (PAT) to authenticate against the Databricks REST API. To create a PAT that can be used to make API requests:
Go to your Azure Databricks workspace.
Click the user icon in the top-right corner of the screen and click User Settings.
Click Access Tokens > Generate New Token.
Copy and save the token value.
Add the service principal to the Azure Databricks workspace
You add the Azure AD service principal to a workspace using the SCIM API 2.0. You must also give the service principal permission to launch automated job clusters. You can grant this through the allow-cluster-create permission. Open a terminal and run the following command to add the service principal and grant the required permissions:
curl -X POST 'https://<per-workspace-url>/api/2.0/preview/scim/v2/ServicePrincipals' \
--header 'Content-Type: application/scim+json' \
--header 'Authorization: Bearer <personal-access-token>' \
--data-raw '{
"schemas":[
"urn:ietf:params:scim:schemas:core:2.0:ServicePrincipal"
],
"applicationId":"<application-id>",
"displayName": "test-sp",
"entitlements":[
{
"value":"allow-cluster-create"
}
]
}'
Replace with the unique per-workspace URL for your Azure Databricks workspace.
Replace with the Azure Databricks personal access token.
Replace with the Application (client) ID for the Azure AD application registration.
Create an Azure Key Vault-backed secret scope in Azure Databricks
Secret scopes provide secure storage and management of secrets. You’ll store the secret associated with the service principal in a secret scope. You can store secrets in a Azure Databricks secret scope or an Azure Key Vault-backed secret scope. These instructions describe the Azure Key Vault-backed option:
Create an Azure Key Vault instance in the Azure portal.
Create the Azure Databricks secret scope backed by the Azure Key Vault instance.
Step 1: Create an Azure Key Vault instance
In the Azure portal, select Key Vaults > + Add and give the key vault a name.
Click Review + create.
After validation completes, click Create .
After creating the key vault, go to the Properties page for the new key vault.
Copy and save the Vault URI and Resource ID.
Step 2: Create An Azure Key Vault-backed secret scope
Azure Databricks resources can reference secrets stored in an Azure key vault by creating a Key Vault-backed secret scope. To create the Azure Databricks secret scope:
Go to the Azure Databricks Create Secret Scope page at https:///#secrets/createScope. Replace per-workspace-url with the unique per-workspace URL for your Azure Databricks workspace.
Enter a Scope Name.
Enter the Vault URI and Resource ID values for the Azure key vault you created in Step 1: Create an Azure Key Vault instance.
Click Create.
Save the client secret in Azure Key Vault
In the Azure portal, go to the Key vaults service.
Select the key vault created in Step 1: Create an Azure Key Vault instance.
Under Settings > Secrets, click Generate/Import.
Select the Manual upload option and enter the client secret in the Value field.
Click Create.
Grant the service principal read access to the secret scope
You’ve created a secret scope and stored the service principal’s client secret in that scope. Now you’ll give the service principal access to read the secret from the secret scope.
Open a terminal and run the following command:
curl -X POST 'https://<per-workspace-url/api/2.0/secrets/acls/put' \
--header 'Authorization: Bearer <personal-access-token>' \
--header 'Content-Type: application/json' \
--data-raw '{
"scope": "<scope-name>",
"principal": "<application-id>",
"permission": "READ"
}'
Replace with the unique per-workspace URL for your Azure Databricks workspace.
Replace with the Azure Databricks personal access token.
Replace with the name of the Azure Databricks secret scope that contains the client secret.
Replace with the Application (client) ID for the Azure AD application registration.
Create a job in Azure Databricks and configure the cluster to read secrets from the secret scope
You’re now ready to create a job that can run as the new service principal. You’ll use a notebook created in the Azure Databricks UI and add the configuration to allow the job cluster to retrieve the service principal’s secret.
Go to your Azure Databricks landing page and select Create Blank Notebook. Give your notebook a name and select SQL as the default language.
Enter SELECT 1 in the first cell of the notebook. This is a simple command that just displays 1 if it succeeds. If you have granted your service principal access to particular files or paths in Azure Data Lake Storage Gen 2, you can read from those paths instead.
Go to Workflows and click the + Create Job button. Give the job a name, click Select Notebook, and select the notebook you just created.
Click Edit next to the Cluster information.
On the Configure Cluster page, click Advanced Options.
On the Spark tab, enter the following Spark Config:
fs.azure.account.auth.type.acmeadls.dfs.core.windows.net OAuth
fs.azure.account.oauth.provider.type.acmeadls.dfs.core.windows.net org.apache.hadoop.fs.azurebfs.oauth2.ClientCredsTokenProvider
fs.azure.account.oauth2.client.id.acmeadls.dfs.core.windows.net <application-id>
fs.azure.account.oauth2.client.secret.acmeadls.dfs.core.windows.net {{secrets/<secret-scope-name>/<secret-name>}}
fs.azure.account.oauth2.client.endpoint.acmeadls.dfs.core.windows.net https://login.microsoftonline.com/<directory-id>/oauth2/token
Replace with the name of the Azure Databricks secret scope that contains the client secret.
Replace with the Application (client) ID for the Azure AD application registration.
Replace with the name associated with the client secret value in the secret scope.
Replace with the Directory (tenant) ID for the Azure AD application registration.
Transfer ownership of the job to the service principal
A job can have exactly one owner, so you’ll need to transfer ownership of the job from yourself to the service principal. To ensure that other users can manage the job, you can also grant Can Manage permissions to a group. In this example, we use the Permissions API to set these permissions.
Open a terminal and run the following command:
curl -X PUT 'https://<per-workspace-url>/api/2.0/permissions/jobs/<job-id>' \
--header 'Authorization: Bearer <personal-access-token>' \
--header 'Content-Type: application/json' \
--data-raw '{
"access_control_list": [
{
"service_principal_name": "<application-id>",
"permission_level": "IS_OWNER"
},
{
"group_name": "admins",
"permission_level": "CAN_MANAGE"
}
]
}'
Replace with the unique per-workspace URL for your Azure Databricks workspace.
Replace with the Azure Databricks personal access token.
Replace with the Application (client) ID for the Azure AD application registration.
The job will also need read permissions to the notebook. Run the following command to grant the required permissions:
curl -X PUT 'https://<per-workspace-url>/api/2.0/permissions/notebooks/<notebook-id>' \
--header 'Authorization: Bearer <personal-access-token>' \
--header 'Content-Type: application/json' \
--data-raw '{
"access_control_list": [
{
"service_principal_name": "<application-id>",
"permission_level": "CAN_READ"
}
]
}'
Replace with the unique per-workspace URL for your Azure Databricks workspace.
Replace with the ID of the notebook associated with the job. To find the ID, go to the notebook in the Azure Databricks workspace and look for the numeric ID that follows notebook/ in the notebook’s URL.
Replace with the Azure Databricks personal access token.
Replace with the Application (client) ID for the Azure AD application registration.
You can now test the job. You run jobs with a service principal the same way you run jobs as a user, either through the UI, API, or CLI.
Hashicorp Vault is the native product of our organization and is a widely used and recommended approach for storing all the key-value pairs or any secrets. Any applications that are deployed on Azure too must store/retrieve the token from Hashicorp Vault and not from the Azure Key Vault. I provided this information just to add a bit of background to the requirement.
Now coming to the actual problem, I deployed the dotnet application on Azure App Service, enable the system-managed identity, and was able to successfully retrieve the JWT token.
As per the flow which I understood by reading the documentation, it says, first retrieve the application token deployed on Azure having System Managed Identity enabled. Once this is done, pass this token for validation to Vault which gets it validated using OIDC from AAD. On successful validation, I will be given back the Vault token which can be used to fetch the secrets from Vault.
To perform these steps configuration is required at the Vault side, for which, I performed all the below steps on the vault server installed on my windows local machine:-
Command line operation
Start the Vault server
Open the other command prompt and set the environment variables set
VAULT_ADDR=http://127.0.0.1:8200 set
VAULT_TOKEN=s.iDdVbLKPCzmqF2z0RiXPMxLk
vault auth enable jwt
vault write auth/jwt/config
oidc_discovery_url=https://sts.windows.net/4a95f16f-35ba-4a52-9cb3-7f300cdc0c60/
bound_issuer=https://sts.windows.net/4a95f16f-35ba-4a52-9cb3-7f300cdc0c60/
vault read auth/jwt/config
Policy associated with the sqlconnection:-
create a role (webapp-role) by using the command
curl --header “X-Vault-Token: %VAULT_TOKEN%” --insecure --request POST
--data #C:\Users\48013\source\repos\HashVaultAzure\Vault-files\payload.json
%VAULT_ADDR%/v1/auth/jwt/role/webapp-role
–payload.json { “bound_audiences”: “https://management.azure.com/”,
“bound_claims”: { “idp”:
“https://sts.windows.net/4a95f16f-35ba-4a52-9cb3-7f300cdc0c60/”,
“oid”: “8d2b99fb-f4f4-4afb-9ee3-276891f40a65”, “tid”:
“4a95f16f-35ba-4a52-9cb3-7f300cdc0c60/” }, “bound_subject”:
“8d2b99fb-f4f4-4afb-9ee3-276891f40a65”, “claim_mappings”: { “appid”:
“application_id”, “xms_mirid”: “resource_id” }, “policies”:
[“sqlconnection”], “role_type”: “jwt”, “token_bound_cidrs”:
[“10.0.0.0/16”], “token_max_ttl”: “24h”, “user_claim”: “sub” }
Vault read auth/jwt/role/webapp-role
Run the command below with the JWT token retrieved from the application (having the managed identity enabled) deployed on Azure
AAD and pass it as “your_jwt”. This command should return the vault
token as shown in the link https://www.vaultproject.io/docs/auth/jwt
curl --request POST --data '{"jwt": "your_jwt", "role":
"webapp-role"}' http://127.0.0.1:8200/v1/auth/jwt/login
At this point I receive an error – “Missing Role”,
I am stuck here and not able to find any solution.
Expected response should be a vault token/client_token as shown:-
JWT Token decoded information
{
"aud": "https://management.azure.com",
"iss": "https://sts.windows.net/4a95f16f-35ba-4a52-9cb3-7f300cdc0c60/",
"iat": 1631172032,
"nbf": 1631172032,
"exp": 1631258732,
"aio": "E2ZgYNBN4JVfle92Tsl1b8m8pc9jAA==",
"appid": "cf5c734c-a4fd-4d85-8049-53de46db4ec0",
"appidacr": "2",
"idp": "https://sts.windows.net/4a95f16f-35ba-4a52-9cb3-7f300cdc0c60/",
"oid": "8d2b99fb-f4f4-4afb-9ee3-276891f40a65",
"rh": "0.AVMAb_GVSro1Ukqcs38wDNwMYExzXM_9pIVNgElT3kbbTsBTAAA.",
"sub": "8d2b99fb-f4f4-4afb-9ee3-276891f40a65",
"tid": "4a95f16f-35ba-4a52-9cb3-7f300cdc0c60",
"uti": "LDjkUZdlKUS4paEleUUFAA",
"ver": "1.0",
"xms_mirid": "/subscriptions/0edeaa4a-d371-4fa8-acbd-3675861b0ac8/resourcegroups/AzureAADResource/providers/Microsoft.Web/sites/hashvault-test",
"xms_tcdt": "1600006540"
}
The issue was with the missing configuration both at the Azure Cloud and Vault side.
These were the addition steps done further to make it work.
Create an Azure SPN (which is equal to creating an app registration with client secret)
az ad sp create-for-rbac --name "Hashicorp Vault Prod AzureSPN"
--skip-assignment Assign as Reader on subscription
Create Vault config
vault auth enable azure vault write auth/jwt/config
tenant_id=lg240e12-76g1-748b-cd9c-je6f29562476
resource=https://management.azure.com/ client_id=34906a49-
9a8f-462b-9d68-33ae40hgf8ug client_secret=123456ABCDEF
I am trying to publish the message on topic but I am not able to publish the message. I am using laravelframework. My subscription is push type.
I have used $ composer require google/cloud-pubsub from https://github.com/googleapis/google-cloud-php-pubsub link
I have followed this link: (https://cloud.google.com/pubsub/docs/publisher#php)
**use Google\Cloud\PubSub\PubSubClient;**
function publish_message($projectId, $topicName, $message)
{
$pubsub = new PubSubClient([\[][1]
'projectId' => $projectId,
]);
$topic = $pubsub->topic($topicName);
$topic->publish(['data' => $message]);
print('Message published' . PHP_EOL);
}
I am getting this error (open this link : https://i.stack.imgur.com/XXHZ5.png.
[1]: https://i.stack.imgur.com/XXHZ5.png
Your question would benefit from a more detailed explanation.
As it is, the code you show is the same code as published by Google.
Assuming (!?) Google's code works (probable but not certain), your code should work.
Since we know your code doesn't work, it's probably something else.
I suspect you've missed one or more of the following possibly the last steps:
created a Google Cloud Platform project ($projectId)?
enabled the Pub/Sub API?
created a Pub/Sub topic [and >=1 subscriber] ($topicName)?
created (service account) credentials permitted to publish to this topic?
set GOOGLE_APPLICATION_CREDENTIALS to point to the account's key?
How are you running the code?
If possible please also print the ClientException that you show in the image.
Update
I tested Google's code and it works for me:
BILLING_ID=[[YOUR-BILLING]]
PROJECT_ID=[[YOUR-PROJECT]]
TOPIC_NAME=[[YOUR-TOPIC-NAME]]
gcloud projects create ${PROJECT}
gcloud beta billing projects link ${PROJECT} \
--billing-account=${BILLING}
# Enabled Pub/Sub and create topic=subscription=${TOPIC_NAME}
gcloud services enable pubsub.googleapis.com \
--project=${PROJECT}
gcloud pubsub topics create ${TOPIC_NAME} \
--project=${PROJECT}
gcloud pubsub subscriptions create ${TOPIC_NAME} \
--topic=${TOPIC_NAME} \
--project=${PROJECT}
# Create service account ${ROBOT} and key `./${ROBOT}.json`
# Grant the account `publisher` permissions
ROBOT=[[YOUR-ACCOUNT-NAME]]
gcloud iam service-accounts create ${ROBOT} \
--project=${PROJECT}
gcloud iam service-accounts keys create ./${ROBOT}.json \
--iam-account=${ROBOT}#${PROJECT}.iam.gserviceaccount.com \
--project=${PROJECT}
gcloud projects add-iam-policy-binding ${PROJECT} \
--member=serviceAccount:${ROBOT}#${PROJECT}.iam.gserviceaccount.com \
--role=roles/pubsub.publisher
Then -- and apologies, I'm no PHP developer -- here's what I did:
composer.json:
{ "require": { "google/cloud-pubsub": "1.24.1" } }
pubsub.php:
<?php
require_once 'vendor/autoload.php';
use Google\Cloud\PubSub\PubSubClient;
// Expects Env PROJECT_ID, TOPIC_NAME **and** GOOGLE_APPLICATION_CREDENTIALS
$projectId = getenv("PROJECT_ID");
$topicName = getenv("TOPIC_NAME");
$pubsub = new PubSubClient([
"projectId" => $projectId
]);
$topic = $pubsub->topic($topicName);
$topic->publish(["data"=>"Hello Freddie!"]);
print("Message published" . PHP_EOL);
?>
Then:
export GOOGLE_APPLICATION_CREDENTIALS=./${ROBOT}.json
export PROJECT_ID
export TOPIC_NAME
php pubsub.php
NOTE the code implicitly assumes GOOGLE_APPLICATION_CREDENTIALS to authenticate against the service, see Application Default Credentials
yields:
Message published
And:
gcloud pubsub subscriptions pull ${TOPIC_NAME} \
--project=${PROJECT} \
--format="value(message.data)"
Hello Freddie!
I am guessing the issue you are faced is because of you must have missed the authentication step. Have you created a SA and downloaded the json file to authenticate? If so, double check that you have this line in your filesystem:
export GOOGLE_APPLICATION_CREDENTIALS=/path/to/service-account-credentials.json
Google API clients typically recognise the GOOGLE_APPLICATION_CREDENTIALS environment variable. If found, it's expected to point to a JSON file with credentials for either a service account or a user.
Service account credentials can be downloaded from the GCP web console and look like this:
{
"type": "service_account",
"project_id": "...",
"private_key_id": "...",
"private_key": "...",
"client_email": "...",
"client_id": "...",
"auth_uri": "...",
"token_uri": "...",
"auth_provider_x509_cert_url": "...",
"client_x509_cert_url": "..."
}
User credentials are often available in ~/.config/gcloud/application_default_credentials.json and look something like:
{
"client_id": "...",
"client_secret": "...",
"refresh_token": "...",
"type": "authorized_user"
}
Here's an example of the official google rubygem detecting the type of credentials provided via the environment var.
I'd like to authenticate an unconfigured gcloud install with both types of credential. In our case we happen to be passing the GOOGLE_APPLICATION_CREDENTIALS variable and path into a docker container, but I think this is a valid question for clean installs outside docker too.
If the credentials file is a service account type, I can do this:
gcloud auth activate-service-account --key-file=${GOOGLE_APPLICATION_CREDENTIALS}
However I can't see any way to handle the case where the credentials belong to a real user.
Questions:
Why doesn't the official gcloud tool follow the conventions that other google API clients use and use GOOGLE_APPLICATION_CREDENTIALS when available?
Is there a hidden method that will activate the user credentials case?
As you point out gcloud command line tool (CLI) does not use application default credentials. It has separate system for managing its own credentials.
GOOGLE_APPLICATION_CREDENTIALS are designed for client libraries to simplify wiring in credentials, and gcloud CLI is not a library. Even in the client code best practice is not to depend on this environment variable but instead explicitly provide credentials.
To answer your second question, user credentials can be obtained via
gcloud auth login
command. (NOTE this is different from gcloud auth application-default login) This besides saving actual credentials will also set account property in current configuration:
gcloud config list
gcloud can have many configurations, each with different credentials. See
gcloud config configurations list
You can create multiple configurations, one with user account another with service account and use it simultaneously by providing --configuration parameter, for example
gcloud compute instances list --configuration MY_USER_ACCOUNT_CONFIG
Similarly you can also switch which credentials are used by using --account flag, in which case it will use same configuration and will only swap out the account.
I've found a way to authenticate a fresh gcloud when GOOGLE_APPLICATION_CREDENTIALS points to a file with user credentials rather than service account credentials.
cat ${GOOGLE_APPLICATION_CREDENTIALS}
{
"client_id": "aaa",
"client_secret": "bbb",
"refresh_token": "ccc",
"type": "authorized_user"
}
gcloud config set auth/client_id aaa
gcloud config set auth/client_secret bbb
gcloud auth activate-refresh-token user ccc
This uses the undocumented auth activate-refresh-token subcommand - which isn't ideal - but it does work.
Paired with gcloud auth activate-service-account --key-file=credentials.json, this makes it possible to initialize gcloud regardless of the credential type available at $GOOGLE_APPLICATION_CREDENTIALS