I am creating a cloud function resource with Terraform and wanted to overwrite the default service account '#appspot.gserviceaccount.com' to use a custom service account with least privileges.
I've done the following but once my Terraform resources are created and I check the cloud function permissions tab, it's still defaulting to the original one '#appspot.gserviceaccount.com'
resource "google_service_account" "service_account" {
account_id = "mysa"
display_name = "Service Account"
}
data "google_iam_policy" "cfunction_iam" {
binding {
role = google_project_iam_custom_role.cfunction_role.id
members = [
"serviceAccount:${google_service_account.service_account.email}",
]
}
binding {
role = "roles/cloudfunctions.developer"
members = [
"serviceAccount:${google_service_account.service_account.email}",
]
}
}
resource "google_cloudfunctions_function_iam_policy" "policy" {
project = google_cloudfunctions_function.function.project
region = google_cloudfunctions_function.function.region
cloud_function = google_cloudfunctions_function.function.name
policy_data = data.google_iam_policy.cfunction_iam.policy_data
}
resource "google_project_iam_custom_role" "cfunction_role" {
role_id = "customCFunctionRole"
title = "Custom Cloud Function Role"
description = "More granular permissions other than default #appspot SA"
permissions = [
"storage.objects.create",
"storage.multipartUploads.create",
"storage.objects.get",
"bigquery.tables.create",
"bigquery.tables.list",
"bigquery.tables.updateData",
"logging.logEntries.create",
]
}
#Update, I've set the service account parameter within the Cloud Function resource as well:
service_account_email = "${google_service_account.service_account.email}"
What am I missing here?
Thanks!
Adding my own answer here. After deleting the previous state and let Terraform re-create all the resources, it picked up the correct service account as defined in the description.
Related
I'm coming from AWS and still learning how IAM/Policies work in GCP. In AWS, if I wanted to grant a role access to multiple buckets I would do something like this in terraform:
data "aws_iam_policy_document" "policy" {
statement {
actions = [
"s3:Get*"
]
resources = [
"${var.bucket1_arn}/*",
"${var.bucket2_arn}/*",
"${var.bucket3_arn}/*",
]
}
}
resource "aws_iam_policy" "policy" {
name = "my-policy"
policy = data.aws_iam_policy_document.policy.json
}
resource "aws_iam_role_policy_attachment" "policy_attachment" {
policy_arn = aws_iam_policy.policy.arn
role = ${var.role_name}
}
I've been trying to figure out how to do it in GCP, but all I've found so far is that I need to attach a policy to each bucket individually, like so:
data "google_iam_policy" "policy" {
binding {
role = "roles/storage.objectViewer"
members = [
"serviceAccount:${service_account}",
]
}
}
resource "google_storage_bucket_iam_policy" "bucket_1" {
bucket = google_storage_bucket.bucket_1.name
policy_data = data.google_iam_policy.policy.policy_data
}
resource "google_storage_bucket_iam_policy" "bucket_2" {
bucket = google_storage_bucket.bucket_2.name
policy_data = data.google_iam_policy.policy.policy_data
}
resource "google_storage_bucket_iam_policy" "bucket_3" {
bucket = google_storage_bucket.bucket_3.name
policy_data = data.google_iam_policy.policy.policy_data
}
Is this the correct way (or best practice?) to grant a service account access to multiple buckets?
Yes, Google IAM is resource-centric (my understanding that AWS flips this and is identity-centric), you apply policies to resources.
Because the container (i.e. a Project) may contain many Buckets, you're only alternative is to apply the binding to the Project itself but then, every Bucket in the Project will have the binding.
The approach you're taking yields precision (only those buckets granted the role have it) albeit slightly onerous for the role binding phase (something done infrequently).
DazWikin answer is right, but on GCP you can cheat. In fact, you can use IAM conditions and build something like that:
Grant the account (service or user) at the folder or organisation level, to grant it the access to all the resources. For example, grant the role storage Admin
Use condition to enforce this role on only a subset of bucket
Like that
resource "google_organization_iam_binding" "Binding" {
members = ["<ACCOUNT_EMAIL>"]
org_id = "<YOUR_ORG_ID>"
role = "roldes/storage.admin"
condition {
expression = 'resource.name.startsWith("projects/_/buckets/<BUCKET1>") || resource.name.startsWith("projects/_/buckets/<BUCKET2>")'
title = "bucket filter"
}
}
It's not so clean, especially to update when you have new buckets that you want to add in the list, but it's a workaround at your question.
Trying so assign a created role to a GCP service account which then is used as a workload identity for a k8s deployment.
Terraform:
resource google_project_iam_custom_role sign_blob_role {
permissions = ["iam.serviceAccounts.signBlob"]
role_id = "signBlob"
title = "Sign Blob"
}
resource google_service_account_iam_member document_signer_workload {
service_account_id = module.document_signer_service_accounts.service_accounts_map.doc-sign.name
role = "roles/iam.workloadIdentityUser"
member = local.document_sign_sa
}
module document_signer_service_accounts {
source = "terraform-google-modules/service-accounts/google"
version = "~> 3.0"
project_id = var.gcp_project_name
prefix = "doc-sign-sa"
names = ["doc-sign"]
project_roles = [
"${var.gcp_project_name}=>roles/viewer",
"${var.gcp_project_name}=>roles/storage.objectViewer",
"${var.gcp_project_name}=>roles/iam.workloadIdentityUser",
"${var.gcp_project_name}=>${google_project_iam_custom_role.sign_blob_role.name}"
]
display_name = substr("GCP SA bound to K8S SA ${local.document_sign_sa}. Used to sign document.", 0, 100)
}
Error:
Error: Request "Create IAM Members roles/signBlob serviceAccount:staging-doc-sign#********************.iam.gserviceaccount.com for \"project \\\"********************\\\"\"" returned error: Error applying IAM policy for project "********************": Error setting IAM policy for project "********************": googleapi: Error 400: Role roles/signBlob is not supported for this resource., badRequest
on .terraform/modules/document_signer_service_accounts/main.tf line 46, in resource "google_project_iam_member" "project-roles":
46: resource "google_project_iam_member" "project-roles" {
When I do the same action on the UI though, it allows me to assign the role.
What am I doing wrong here?
It seems that it could be a problem in the way you are calling the custom role.
"${var.gcp_project_name}=>${google_project_iam_custom_role.sign_blob_role.name}"
The custom role already belongs to the project, so it is not necessary to specify ${var.gcp_project_name}
So, the code should be something like:
project_roles = [
"${var.gcp_project_name}=>roles/viewer",
"${var.gcp_project_name}=>roles/storage.objectViewer",
"${var.gcp_project_name}=>roles/iam.workloadIdentityUser",
"${google_project_iam_custom_role.sign_blob_role.name}"
]
Edit 1
According to this documentation
This is the basic usage of the module service-accounts
module "service_accounts" {
source = "terraform-google-modules/service-accounts/google"
version = "~> 2.0"
project_id = "<PROJECT ID>"
prefix = "test-sa"
names = ["first", "second"]
project_roles = [
"project-foo=>roles/viewer",
"project-spam=>roles/storage.objectViewer",
]
}
I think there should be something wrong with the reference to the attribute from your resource.
Nevertheless I have found a github repository that contains some good examples on how to add a custom role to a Service Account:
# https://www.terraform.io/docs/providers/google/r/google_project_iam.html#google_project_iam_binding
resource "google_project_iam_binding" "new-roles" {
role = "projects/${var.project_id}/roles/${google_project_iam_custom_role.new-custom-role.role_id}"
members = ["serviceAccount:${google_service_account.new.email}"]
}
I think you might find it useful to complete this task.
I will start by saying I am very new to both GCP and Terraform, so I hope there is a simple answer that I have just overlooked.
I am trying to create a GCP cloud function and then make it public using Terraform. I am able to create the function but not make it public, despite closely following the documentation's example: https://www.terraform.io/docs/providers/google/r/cloudfunctions_function.html
I receive the error "googleapi: Error 403: Permission 'cloudfunctions.functions.setIamPolicy' denied on resource ... (or resource may not exist)" when the google_cloudfunctions_function_iam_member resource is reached.
How can I make this function public? Does it have something to do with the account/api key I am using for credentials to create all these resources?
Thanks in advance.
my main.tf file:
provider "google" {
project = "my-project"
credentials = "key.json" #compute engine default service account api key
region = "us-central1"
}
terraform {
backend "gcs" {
bucket = "manually-created-bucket"
prefix = "terraform/state"
credentials = "key.json"
}
}
# create the storage bucket for our scripts
resource "google_storage_bucket" "source_code" {
name = "test-bucket-lh05111992"
location = "us-central1"
force_destroy = true
}
# zip up function source code
data "archive_file" "my_function_script_zip" {
type = "zip"
source_dir = "../source/scripts/my-function-script"
output_path = "../source/scripts/my-function-script.zip"
}
# add function source code to storage
resource "google_storage_bucket_object" "my_function_script_zip" {
name = "index.zip"
bucket = google_storage_bucket.source_code.name
source = "../source/scripts/my-function-script.zip"
}
#create the cloudfunction
resource "google_cloudfunctions_function" "function" {
name = "send_my_function_script"
description = "This function is called in GTM. It sends a users' google analytics id to BigQuery."
runtime = "nodejs10"
available_memory_mb = 128
source_archive_bucket = google_storage_bucket.source_code.name
source_archive_object = google_storage_bucket_object.my_function_script_zip.name
trigger_http = true
entry_point = "handleRequest"
}
# IAM entry for all users to invoke the function
resource "google_cloudfunctions_function_iam_member" "invoker" {
project = google_cloudfunctions_function.function.project
region = "us-central1"
cloud_function = google_cloudfunctions_function.function.name
role = "roles/cloudfunctions.invoker"
member = "allUsers"
}
It seems the only problem with that example from the terraform site are the " Cloud Functions IAM resources" which have been modified since Nov 2019. Now you have to specify these resources as explained here. Now for your user case (public cloud function) I'd recommend you to follow this configuration and just change the "members" attribute to "allUsers" so it'd be something like this
resource "google_cloudfunctions_function_iam_binding" "binding" {
project = google_cloudfunctions_function.function.project
region = google_cloudfunctions_function.function.region
cloud_function = google_cloudfunctions_function.function.name
role = "roles/cloudfunctions.invoker"
members = [
"allUsers",
]
}
Finally, you can give it a test and modify the functions you've already created here at the #Try this API" right panel and enter the proper resource and request body like this (make sure to enter the "resource" parameter correcly):
{
"policy": {
"bindings": [
{
"members": [
"allUsers"
],
"role": "roles/cloudfunctions.invoker"
}
]
}
}
In addition to adjusting the IAM roles how #chinoche suggested, I also discovered that I needed to modify the service account I was using to give it poject owner permissions. (I guess the default one I was using didn't have this). I updated my key.json and it finally worked.
My goal is to create a Terraform Module which creates a Child AWS account and creates a set of resources inside the account (for example, AWS Config rules).
The account is created with the following aws_organizations_account definition:
resource "aws_organizations_account" "account" {
name = "my_new_account"
email = "john#doe.org"
}
And an example aws_config_config_rule would be something like:
resource "aws_config_config_rule" "s3_versioning" {
name = "my-config-rule"
description = "Verify versioning is enabled on S3 Buckets."
source {
owner = "AWS"
source_identifier = "S3_BUCKET_VERSIONING_ENABLED"
}
scope {
compliance_resource_types = ["AWS::S3::Bucket"]
}
}
However, doing this creates the AWS Config rule in the master account, not the newly created child account.
How can I define the config rule to apply to the child account?
So, I was actually able to achieve this by defining a new provider in the module which assumes the OrganizationAccountAccessRole inside the newly created account.
Here's an example:
// Define new account
resource "aws_organizations_account" "my_new_account" {
name = "my_new_account"
email = "john#doe.org"
}
provider "aws" {
/* other provider config */
assume_role {
// Assume the organization access role
role_arn = "arn:aws:iam::${aws_organizations_account.my_new_account.id}:role/OrganizationAccountAccessRole"
}
alias = "my_new_account"
}
resource "aws_config_config_rule" "s3_versioning" {
// Tell resource to use the new provider
provider = aws.my_new_account
name = "my-config-rule"
description = "Verify versioning is enabled on S3 Buckets."
source {
owner = "AWS"
source_identifier = "S3_BUCKET_VERSIONING_ENABLED"
}
scope {
compliance_resource_types = ["AWS::S3::Bucket"]
}
}
However, it should be noted that defining the provider inside the module leads to a few quirks, notably once you source this module you cannot delete this module. If you do it will throw a Error: Provider configuration not present since you will have also removed the provider definition.
But, if you don't plan on removing these accounts (or are okay with doing it manually when needed) then this should be good!
I have created a service account and a custom role in GCP using Terraform. How do I attach this custom role to the service account? I could do this using GCP Console but that is not the need here as I have to do it using Terraform. Please find below the code snippets that I have used to create the service account and the custom rule.
resource "google_service_account" "mservice_infra_service_account" {
account_id = "mserviceinfra-service-account"
display_name = "Infrastructure Service Account"
}
resource "google_project_iam_custom_role" "mservice_infra_admin" {
role_id = "mservice_infra_admin"
title = "mservice_infra_admin"
description = "Infrastructure Administrator Custom Role"
permissions = ["compute.disks.create", "compute.firewalls.create", "compute.firewalls.delete", "compute.firewalls.get", "compute.instanceGroupManagers.get", "compute.instances.create", "compute.instances.delete", "compute.instances.get", "compute.instances.setMetadata", "compute.instances.setServiceAccount", "compute.instances.setTags", "compute.machineTypes.get", "compute.networks.create", "compute.networks.delete", "compute.networks.get", "compute.networks.updatePolicy", "compute.subnetworks.create", "compute.subnetworks.delete", "compute.subnetworks.get", "compute.subnetworks.setPrivateIpGoogleAccess", "compute.subnetworks.update", "compute.subnetworks.use", "compute.subnetworks.useExternalIp", "compute.zones.get", "container.clusters.create", "container.clusters.delete", "container.clusters.get", "container.clusters.update", "container.operations.get"]
}
If someone can find a Terraform based solution to solve this problem, it is highly appreciated. Thanks
Using resource google_project_iam_binding
So the full code as below:
data "google_project" "project" {}
resource "google_service_account" "mservice_infra_service_account" {
account_id = "mserviceinfra-service-account"
display_name = "Infrastructure Service Account"
}
resource "google_project_iam_custom_role" "mservice_infra_admin" {
role_id = "mservice_infra_admin"
title = "mservice_infra_admin"
description = "Infrastructure Administrator Custom Role"
permissions = ["compute.disks.create", "compute.firewalls.create", "compute.firewalls.delete", "compute.firewalls.get", "compute.instanceGroupManagers.get", "compute.instances.create", "compute.instances.delete", "compute.instances.get", "compute.instances.setMetadata", "compute.instances.setServiceAccount", "compute.instances.setTags", "compute.machineTypes.get", "compute.networks.create", "compute.networks.delete", "compute.networks.get", "compute.networks.updatePolicy", "compute.subnetworks.create", "compute.subnetworks.delete", "compute.subnetworks.get", "compute.subnetworks.setPrivateIpGoogleAccess", "compute.subnetworks.update", "compute.subnetworks.use", "compute.subnetworks.useExternalIp", "compute.zones.get", "container.clusters.create", "container.clusters.delete", "container.clusters.get", "container.clusters.update", "container.operations.get"]
}
resource "google_project_iam_binding" "mservice_infra_binding" {
role = "projects/${data.google_project.project.project_id}/roles/${google_project_iam_custom_role.mservice_infra_admin.role_id}"
members = [
"serviceAccount:${google_service_account.mservice_infra_service_account.email}",
]
}