I'm trying to create a Kubernetes Cluster via the NodeJS client on google App engine. The Kubernetes cluster is on a separate project to where the app engine project is hosted, say "my-node-project" & "my-k8-project".
"my-node-project" has the relevant service account(Owner level access) for the kubernetes project.
I make the cluster create call as follows:
var client = new container.v1.ClusterManagerClient({
projectId: projectId,
key: serviceAccount
});
var zone = 'us-central1-b';
var password = "<some password>";
var clusterConfig = {
"name": clusterName,
"description": "api created cluster",
"initialNodeCount": 3,
"nodeConfig": {
"oauthScopes": [
"https://www.googleapis.com/auth/compute",
"https://www.googleapis.com/auth/devstorage.read_only"
]
}
,
"masterAuth": {
"username": "admin",
"password": password
},
"zone": zone
};
var request = {
projectId: projectId,
zone: zone,
cluster: clusterConfig,
};
return client.createCluster(request)
.then(responses => {
var response = responses[0];
console.log("response: ", response);
return response;
})
.catch(err => {
console.error(err);
return err;
});
In the above code the serviceAccount variable is a json object containing the service account, with all the private key, project id fields etc.
The strange thing is that when I run the code locally, i.e. call the endpoint that runs the above function, the request goes through just fine, i.e. the clusters are created and I can even add workloads via the api.
However, after I deploy the nodejs project to app engine standard and call the same endpoint running on app engine, I get the error:
Error: 7 PERMISSION_DENIED: Required "container.clusters.create" permission(s) for "projects/my-k8-project". See https://cloud.google.com/kubernetes-engine/docs/troubleshooting#gke_service_account_deleted for more info.
at Object.exports.createStatusError (/srv/node_modules/grpc/src/common.js:91:15)
at Object.onReceiveStatus (/srv/node_modules/grpc/src/client_interceptors.js:1204:28)
at InterceptingListener._callNext (/srv/node_modules/grpc/src/client_interceptors.js:568:42)
at InterceptingListener.onReceiveStatus (/srv/node_modules/grpc/src/client_interceptors.js:618:8)
at callback (/srv/node_modules/grpc/src/client_interceptors.js:845:24)
code: 7,
metadata: Metadata { _internal_repr: {} },
details:
'Required "container.clusters.create" permission(s) for "projects/my-k8-project". See https://cloud.google.com/kubernetes-engine/docs/troubleshooting#gke_service_account_deleted for more info.' }
Since I got that troubleshooting link, I tried to create a new service account and use that. In addition I tried disabling and enabling the both the kubernetes and compute APIs. I also tried to place the service account in the root directory of the project and refer to the service account that way.
Unfortunately everything I tried resulted in exactly the same error. But still worked when running from localhost.
Is there a whitelist somewhere I'm missing? Perhaps localhost is whitelisted by default and "my-node-project" app engine project isn't on the list?
Any tips, hints or pointing in the right direction would be very much appreciated.
You need to add service account also to your "my-k8-project" as a member and give relevant role to it.
In the Cloud Console, navigate to project "my-k8-project". Find the "IAM & admin" > "IAM" page. Click the "Add" button. In the "New members" field paste the name of the service account and give it the appropriate role.
Related
I have a web api in C# 6, deployed as a web service in azure.
Trying to access the swagger i.e. https://myWebService.azurewebsites.net/swagger , I am getting this error in the logs:
2023-01-28T16:47:54.529264514Z System.ArgumentNullException: IDW10106: The 'ClientId' option must be provided.
The ClientId is specified in the secret.json but it looks as if after deploy the program reads from appsettings.json, where it's blank.
I am using the system-managed-identity method described here: https://github.com/Azure-Samples/serviceconnector-webapp-appconfig-dotnet
more precisely, here:
https://github.com/Azure-Samples/serviceconnector-webapp-appconfig-dotnet/blob/main/system-managed-identity/ServiceConnectorSample/Program.cs
I have created an "App Configuration" where, in "Configuration Explorer", the structure of the appsettings is mimic'ed like so: root:ConnectionStrings:AbcDatabase or root:AzureAD:ClientId are given, with their respective value.
Also, from App COnfiguration > Access Control > Role assignments > I have added the web app Id (which requires previously enabling identity on the web app)
I am not sure what else to try, but I can tell that from my local, I am able to connect successfully to the Azure Db by doing this trick (which is obviously not what I want to do in production):
try
{
builder.Configuration.AddAzureAppConfiguration(options =>
{
if (!string.IsNullOrEmpty(appConfigEndpoint))
{
options.Connect(new Uri(appConfigEndpoint), new DefaultAzureCredential());
}
});
}
catch (Exception ex)
{
builder.Configuration.AddAzureAppConfiguration("Endpoint=https://xxx.azconfig.io;Id=xxx:xxx5;Secret=xxx=");
}
In the "catch" the endpoint found in the App COnfiguration > Access keys > Connection String is given. This only works on local. Doesn't work upon deploy, and it's anyway not a good solution, because I don't want to pass the secret in code, I want it to be read from the Service Configuration, when I do "new DefaultAzureCredential()" - not sure how the Default shoudl know where my ClientId is stored in the App Config, however.
For that reason, I also tried something like:
var builder = WebApplication.CreateBuilder(args);
var appConfigEndpoint = builder.Configuration["root:ConnectionStrings:AppConfigEndpoint"]?.ToString();
var userAssignedClientId = builder.Configuration["root:AzureAD:ClientId"]?.ToString();
builder.Configuration.AddAzureAppConfiguration(options =>
{
var credential = new DefaultAzureCredential(new DefaultAzureCredentialOptions { ManagedIdentityClientId = userAssignedClientId });
if (!string.IsNullOrEmpty(appConfigEndpoint))
{
options.Connect(new Uri(appConfigEndpoint), credential);
}
});
But this also gives the same error about the ClientId.
Also confusing to me whether the App COnfiguration should also have Identity set to ON and do I give it any permissions from here or what to do with the Id here?
In the App Service where I am actually deploying my solution I have enabled Identity and use that from the App Config to give the App Service permission to use the App Config.
Hope someone can advise and please let me know if I am missing any important details.
Overview
I am getting an error insufficient permission scopes when running customers.create GCP Channel API, but I can't tell what extra permissions/scopes I need to grant my service account.
I have a node.js provisioning some GCP Resources, where one of the main goals is to automatically create a new Billing Subaccount in GCP.
The latest documenation for this suggests creating a customer, them creating entitlements from there. (A customer is passed into the "create entitlements" method).
Expected Results
Execution of the create customer method should have a successful out of a customer object.
Actual Results
When I implement the create customer I get an error:
UnhandledPromiseRejectionWarning: Error: 7 PERMISSION_DENIED: Request had insufficient authentication scopes.
at Object.callErrorFromStatus ...
My implementation
Authentication : service account credential file
I have a service account with an exported key/credential file I use to successfully authenticate other GCP API calls in my application such as projects.create() and projects.serviceAccounts.create() - so I am confident that my service account authentication is set up correctly as far as the node application goes.
Service account authentication in code:
const auth = new google.auth.GoogleAuth({
keyFile: 'MyServiceAccountKey.json',
scopes: [
'https://www.googleapis.com/auth/cloud-platform',
'https://www.googleapis.com/auth/apps.order',
'https://www.googleapis.com/auth/cloud-billing'
],
});
Service Account Permissions (Granted at folder level)
Permissions contained within the "Customer Billing Admin" Role:
Channel API Method Execution :
const {CloudChannelServiceClient} = require('#google-cloud/channel').v1;
async function CreateCustomer(options){
const channelClient = new CloudChannelServiceClient();
const authClient = await auth.getClient();
const request = {
parent : "accounts/XXXXX-XXXXX-MYACCOUNT",
customer : {
"name": "Customer Test 1",
},
}
const response = await channelClient.createCustomer(request);
console.log(response);
}
Resolution thoughts
Using the GCP APIs I've found that sometimes I just need to add additional permissions to my service account in the IAM page on the GPC console, and other time I needed to include more scopes in the authentication object in my code. This refercence for the accounts.customers.create says the required scope for this api is https://www.googleapis.com/auth/apps.order, which I have included in my code - so I am at a loss as to what permissions/scopes I am missing. From what I can tell, service account authorization should work with this API.
Goal
get and set IAM Policies for auto-provisioned GCP Projects and Service Accounts within said projects using the Node.js Client Library for Google APIs. As well as give a the service account in the project the Dialogflow API Admin role (roles/dialogflow.admin)
Issue
I get the following error when I try to get the IAM policy for a project I just automatically created.
Error: 7 PERMISSION_DENIED: Permission 'resourcemanager.projects.getIamPolicy' denied on resource '//cloudresourcemanager.googleapis.com/projects/va-31b899e6' (or it may not exist).
at Object.callErrorFromStatus (/home/aeglad22/va-project-provisioning/node_modules/#grpc/grpc-js/build/src/call.js:31:26)
at Object.onReceiveStatus (/home/aeglad22/va-project-provisioning/node_modules/#grpc/grpc-js/build/src/client.js:180:52)
at Object.onReceiveStatus (/home/aeglad22/va-project-provisioning/node_modules/#grpc/grpc-js/build/src/client-interceptors.js:365:141)
at Object.onReceiveStatus (/home/aeglad22/va-project-provisioning/node_modules/#grpc/grpc-js/build/src/client-interceptors.js:328:181)
at /home/aeglad22/va-project-provisioning/node_modules/#grpc/grpc-js/build/src/call-stream.js:182:78
at processTicksAndRejections (node:internal/process/task_queues:78:11) {
code: 7,
details: "Permission 'resourcemanager.projects.getIamPolicy' denied on resource '//cloudresourcemanager.googleapis.com/projects/va-31b899e6' (or it may not exist).",
metadata: Metadata {
internalRepr: Map(3) {
'grpc-server-stats-bin' => [Array],
'google.rpc.errorinfo-bin' => [Array],
'grpc-status-details-bin' => [Array]
},
options: {}
},
note: 'Exception occurred in retry method that was not classified as transient'
}
Here is the function I am trying to do this in.
async function setServiceAccountRolesV2(projectID, serviceAccountID){
const authClient = await auth.getClient();
const resourcemanagerClient = new ProjectsClient();
var request = {
resource: "projects/"+projectID,
auth: authClient
}
await resourcemanagerClient.getIamPolicy(request, function(err, response) {
if (err) {
console.error(err);
return;
}
console.log(JSON.stringify(response, null, 2));
});
}
Authentication Info
I am using a service account key to authenticate all of my functions in this node app with. This service account has the following permissions granted at the organization level
This service account I am using to authenticate my app with succeeds at getIamPolicy when I try to get the policy of the project it was created in itself. But I get the error when I try to get the policy of new projects I have created using this "admin project" service account.
Summary
Why is permissions denied when trying to get the IAM Policy of projects I have created programmatically, but successful when getting the policy of the "admin" project that I have this service account and the node.js app running. I thought that if I granted my service account proper permissions at the organization level, and the projects I am creating programmatically were in that same organization, my authenticating service account should inherit all of the right permissions to grant service account roles and change IAM policy in these newly generated accounts.
A potential thought/gut feeling I have that could be completely wrong - is it possible these new projects I'm making don't have IAM Policies at all? so when I try to get and set them there's nothing to change?
Update for clarifications
I have a project that acts as an "administration project" which contain hosts the VM my Node.js app for provisioning GCP resources runs on.
This project is also where I created my service account that the Node.js app authenticates with.
I am creating new projects and service accounts within those projects with this Node.js app.
I have given the aforementioned service account the Owner permission at the organization level.
In my setServiceAccountRolesV2() method, I have tried making the resource my provisioned project manually, as opposed to passed as a parameter to make sure the the project is located correctly. I manually copy and pasted the project ID from one of the auto-provisioned projects into the resource field like this for example
resource: "projects/va-31b899e6",
and I get the same permission denied error (full error message shown above).
However when I try to use this getIamPolicy method with the "admin" project that my node.js app and service account were created in, I get a successful policy return.
resource: "projects/provisioning-admin-339515"
I don't understand why one works, and one doesn't while the service account I'm using to make the call has Owner role at the organization level. The va-31b899e6 project shown above is in fact under the same organization my admin project is.
When I run the gcloud command gcloud projects get-iam-policy va-31b899e6 --format=json > ~/policy.json to check the policy of the api-generated project (not the admin project), I get the following policy back:
{
"bindings": [
{
"members": [
"serviceAccount:tf-admin-sa#provisioner-admin-339515.iam.gserviceaccount.com" ],
"role": "roles/owner"
}
],
"etag": "ByXXh29efSc=",
"version": 1
}
This service account listed in the members is the service account I authenticate my Node app with. Again, Owner granted at the Org level. This to me looks like it should be able to use the get and setIamPolicy methods on this project, as well as any other project in my organization.
New edits to follow trouble shooting tips from answer.
1
Confirmed I am using the correct project in the api call:
async function setServiceAccountRolesV2(projectID, serviceAccountID){
const authClient = await auth.getClient();
const resourcemanagerClient = new ProjectsClient();
var request = {
resource: "projects/va-31b899e6",
auth: authClient
};
await resourcemanagerClient.getIamPolicy(request, function(err, response) {
if (err) {
console.error(err);
return;
}
console.log(JSON.stringify(response, null, 2));
});
}
(project ID copied from GCP Console) : resource: "projects/va-31b899e6",
2
I have verified my credentials are used correctly, I am using a json key file of the service account I created to create more projects and service accounts programmatically. This is how I am authenticating :
const auth = new google.auth.GoogleAuth({
keyFile: 'provisioner-admin-339515-411d1e284a77.json',
scopes: ['https://www.googleapis.com/auth/cloud-platform'],
});
Then in my function, I create a new instance of auth like this:
const authClient = await auth.getClient();
which is then sent in the request of the api call: auth: authClient
3
Verified permissions for my authenticating service account:
When I run
gcloud projects get-iam-policy va-31b899e6 \
--flatten="bindings[].members" \
--format="table(bindings.role)" \
--filter="bindings.members:tf-admin-sa#provisioner-admin-339515.iam.gserviceaccount.com"
I get the output ROLE: roles/owner
Your service account has too many roles. Most of the roles are redundant and included within other roles that you assigned. For example, Billing Account Administrator contains the permissions of Billing Account User. The role Owner possesses almost all of the roles in your screenshot.
Next, you need to understand the Principle of Least Privilege. Seth Vargo put together a good intro video. In summary, only grant the required privileges and no more. Your service account IAM roles are vast and a serious security weakness.
To solve the problem in your question, follow these steps:
STEP 1:
Confirm that the Project ID is correct in the API call. Make sure you are using the Project ID and not the Project Name. List the projects:
gcloud projects list
STEP 2:
Verify that your code is using the correct credentials (the ones you think you configured). Your question does not show how you are authorizing your code. You are using ADC (Application Default Credentials) which means the credentials could be found from several sources (CLI remembered credentials, the environment variable, metadata server).
If you are using the environment variable GOOGLE_APPLICATION_CREDENTIALS, open the file using the variable and make sure that it is a service account JSON key:
vi $GOOGLE_APPLICATION_CREDENTIALS
If you are using the CLI credentials, verify which identity is being used:
gcloud auth list
As a debugging test, clear the environment variable and use a user identity that has the role Owner and then login. Then retest your application.
unset GOOGLE_APPLICATION_CREDENTIALS
gcloud auth application-default login
STEP 3:
Once you have determined the correct Project ID and which service account your code is using, double-check the roles assigned to the service account at the project level. List the IAM roles with this command. Replace with your Project ID and Service Account Email address:
gcloud projects get-iam-policy <PROJECT_ID> \
--flatten="bindings[].members" \
--format="table(bindings.role)" \
--filter="bindings.members:<SERVICE_ACCOUNT_EMAIL>"
The service account needs one of these roles or similar to view IAM bindings:
roles/browser aka Browser
roles/iam.roleViewer aka Viewer
The service account needs this role or similar to modify IAM bindings:
roles/resourcemanager.projectIamAdmin aka Project IAM Admin
Manage access to projects, folders, and organizations
How do you programmatically provision a google cloud project, enable API's and create a service account using node js?
thanks
Chris
The answer lies within the REST API.
Assuming you’re using the Cloud Shell, first install the Node.JS Client Library by running:
npm install googleapis --save
To create a project you can use the Resource Manager API method ‘projects.create‘ as shown in the Node.JS code example there. Replace my-project-id with the desired Project ID, and my-project-name with the desired Project name:
const {google} = require('googleapis');
var cloudResourceManager = google.cloudresourcemanager('v1');
authorize(function(authClient) {
var request = {
resource: {
"projectId": "my-project-id", // TODO
"name": "my-project-name" // TODO
},
auth: authClient,
};
cloudResourceManager.projects.create(request, function(err, response) {
if (err) {
console.error(err);
return;
}
console.log(JSON.stringify(response, null, 2));
});
});
Similarly you can use the Cloud IAM API ‘projects.serviceAccounts.create’ method to create Service Accounts. Replace my-project-id with the project ID the Service Account will be associated with, and my-service-account with the desired Service Account ID:
const {google} = require('googleapis');
var iam = google.iam('v1');
authorize(function(authClient) {
var request = {
name: 'projects/my-project-id', // TODO
resource: {
"accountId": "my-service-account" // TODO
},
auth: authClient,
};
iam.projects.serviceAccounts.create(request, function(err, response) {
if (err) {
console.error(err);
return;
}
console.log(JSON.stringify(response, null, 2));
});
});
And then, to enable an API or Service use the Service Usage API ‘services.enable’ method. In this case, I will enable the Cloud Pub/Sub API. Replace 123 with your project number:
const {google} = require('googleapis');
var serviceUsage = google.serviceusage('v1');
authorize(function(authClient) {
var request = {
name: "projects/123/services/pubsub.googleapis.com", // TODO
auth: authClient,
};
serviceUsage.services.enable(request, function(err, response) {
if (err) {
console.error(err);
return;
}
console.log(JSON.stringify(response, null, 2));
});
});
Alternatively you may use the ‘services.batchEnable‘ method to enable multiple APIs in a single call. You can find a full list of the APIs you can enable here.
You can define each call with:
function authorize(callback) {
google.auth.getClient({
scopes: ['https://www.googleapis.com/auth/cloud-platform']
}).then(client => {
callback(client);
}).catch(err => {
console.error('authentication failed: ', err);
});
}
Note that you should adapt the code to your needs, simplifying it, and modifying or adding any additional parameters you require for your API calls.
The Deployment Manager allows you to provision all these resources and can be triggered through the API.
There is even an official example on GitHub that does the following:
Creates a new project.
Sets the billing account on the new project.
Sets IAM permissions on the new project.
Turns on a set of apis in the new project.
Creates service accounts in the new project.
Remember to replace the values in the config.yaml file. To get the billing account ID, you can use the billingAccounts.list method, and to get the organization ID, you can use the gcloud organizations list command.
Keep in mind that you will need to set up the requirements specified in the README file of the example repository, but this only needs to be done once. The permissions to the DM Service Account can be set in the Manage Resources section of the Cloud Console.
Once you have changed the required configurations, you can test the deployment with the gcloud deployment-manager deployments create command and get the request body sent to the Deployments: insert API by adding the --log-http flag. Notice that what interests you is the first request, the others are made to check the progress of the operation.
Finally, with the request body contents, you can change the values you need and make this call to the API using nodejs. This post provides examples on how to use the google-api-nodejs-client to create deployments.
The advantage of using the Deployment Manager is that all resources can be created in a single request.
Administering projects using Client Libraries to my knowledge is not yet supported.
You'll need to rely on gcloud cli to manage projects and enabling apis achieve this.
If you are looking to automate this you can still do this using a service account while using gcloud cli.
And if you are looking to build this for internal users you can use a CI/CD tool like Jenkins.
For external users you can use something like cloud run to achieve this. Although I can't think of a scenario where you want to do this
when I try to run firebase functions with cloud vision API and test the functions. I get this error:
ERROR: { Error: 7 PERMISSION_DENIED: Cloud Vision API has not been
used in project 563584335869 before or it is disabled. Enable it by
visiting
https://console.developers.google.com/apis/api/vision.googleapis.com/overview?project=563584335869
then retry. If you enabled this API recently, wait a few minutes for
the action to propagate to our systems and retry.
I do not recognize this project number and I have already enabled the API with the project that I am using. I set the GOOGLE_APPLICATION_CREDENTIALS using the project with the enabled API. What is it that I'm doing wrong?
For those of you who are still having this issue here is what worked for me:
const client = new vision.ImageAnnotatorClient({
keyFilename: 'serviceAccountKey.json'
})
This error message is usually thrown when the application is not being authenticated correctly due to several reasons such as missing files, invalid credential paths, incorrect environment variables assignations, among other causes.
Based on this, I recommend you to validate that the credential file and file path are being correctly assigned, as well as follow the Obtaining and providing service account credentials manually guide in order to explicitly specify your service account file directly into your code; In this way, you will be able to set it permanently and verify if you are passing the service credentials correctly. Additionally, you can take a look on this link that contains a useful step-by-step guide to use Firebase functions with Vision API which includes the Vision object authentication code for Node.js.
Passing the path to the service account key in code example:
// Imports the Google Cloud client library.
const Storage = require('#google-cloud/storage');
// Instantiates a client. Explicitly use service account credentials by
// specifying the private key file. All clients in google-cloud-node have this
// helper, see https://github.com/GoogleCloudPlatform/google-cloud-node/blob/master/docs/authentication.md
const storage = new Storage({
keyFilename: '/path/to/keyfile.json'
});
// Makes an authenticated API request.
storage
.getBuckets()
.then((results) => {
const buckets = results[0];
console.log('Buckets:');
buckets.forEach((bucket) => {
console.log(bucket.name);
});
})
.catch((err) => {
console.error('ERROR:', err);
});
API has not been used in project 563584335869
If you clicked that link has been printed in a console, that guide you to this url.
https://console.developers.google.com/apis/api/vision.googleapis.com/overview?project=firebase-cli
So that project id means you used the project credential of 'firebase-cli' not yours.
If you tried to set the value of the environment, you can find the variables in your directory
~/.config/firebase/...credentials.json
And sometime it would be not replaced after you tried to override.
But, you can set your credential in code.
You can find the way of getting a credential in here.
https://cloud.google.com/iam/docs/creating-managing-service-account-keys
And the credential format is like this one.
{
"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":
}
I've faced exactly the same error what you have when I used another google API. and resolved that way including the credential inside code.
const textToSpeech = require("#google-cloud/text-to-speech")
const keyfile = require(".././my-project.json")
const config = {
projectId: keyfile.project_id,
keyFilename: require.resolve(".././my-project.json")
};
const TTS_Client = new textToSpeech.TextToSpeechClient(config)