Reputation: 91
Figured it out
So there's two ways to solve this:
Option 1:
I was not enabling the project to have access to the KMS Key that was being used to encrypt/decrypt the storage bucket. I was able to test by running the following command in the cli while logged in as myself:
gsutil kms authorize -p PROJECTNAME -k projects/PROJECTNAME/locations/global/keyRings/KEYRINGNAME/cryptoKeys/KEYNAME
Option 2:
I also updated the scope of the SA Credentials for upload to have both cloudkms and devstorage.full_control. I'm not sure if that affected anything, though.
Original Question:
I'm making a workflow that automatically creates service accounts, storage buckets, and KMS Key Rings & Keys automatically for a multi-tenant hosting environment.
I have a Service Account with limited KMS, SA, and Storage permissions that can create other Service Accounts and allow them to be the administrators of their own tenanted items (EG: Create a Service Account for a Tenant, and it's got full control to that Tenant's KMS and Bucket, but not to other Tenant's).
I'm currently running into an issue getting the new Service Account to be able to upload files, however. It's got all the permissions that it needs:
1. KMS Admin and Encrypt/Decrypt for its KeyRing
2. Storage Bucket Admin
But, I get the following error when I try to upload something with that Service Account
[403] Errors [
Message[Permission denied on Cloud KMS key.
Please ensure that your Cloud Storage service
account has been authorized to use this key. ]
Location[ - ]
Reason[forbidden]
Domain[global]
Here's the code I'm using to assign permissions, followed by the code used to access the bucket:
class Program
{
private static string solutionLocation = Path.GetFullPath(Path.Combine(Directory.GetCurrentDirectory(), @".." + Path.DirectorySeparatorChar + ".." + Path.DirectorySeparatorChar + ".." + Path.DirectorySeparatorChar));
static void Main(string[] args)
{
//Deserialize the JSON File for use with other things
JSONCreds jsonCreds = JsonConvert.DeserializeObject<JSONCreds>(
File.ReadAllText(Path.Combine(solutionLocation, "gcp-general-sa.json")));
Environment.SetEnvironmentVariable("GOOGLE_APPLICATION_CREDENTIALS",
Path.Combine(solutionLocation, "gcp-general-sa.json"));
KeyManagementServiceClient client = KeyManagementServiceClient.Create();
StorageClient storageClient = StorageClient.Create();
//Collect Tenant ID for testing purposes
Console.WriteLine("Tenant ID?");
string TID = Console.ReadLine();
if (TID.Length > 23)
{
TID = TID.Substring(0, 23);
}
//Setting some variables that are used throughout
string keyID = "key-" + TID;
string keyRingName = "ring-" + TID;
string keyLocationID = "global";
string saName = "sa-" + TID;
//Create a Service Account for this agency
var newServiceAccount = CreateServiceAccount(jsonCreds.project_id, saName, saName);
//Create an API Key for this Service Account, and then decode it
var credential = GoogleCredential.GetApplicationDefault().CreateScoped(IamService.Scope.CloudPlatform);
var service = new IamService(new IamService.Initializer
{
HttpClientInitializer = credential
});
var newServiceAccountFullKey = service.Projects.ServiceAccounts.Keys.Create( new CreateServiceAccountKeyRequest(), "projects/-/serviceAccounts/" + newServiceAccount.Email).Execute();
var newServiceAccountKey = System.Text.ASCIIEncoding.ASCII.GetString(Convert.FromBase64String(newServiceAccountFullKey.PrivateKeyData));
Console.WriteLine("Created Service Account Key For: " + newServiceAccountFullKey.Name);
//Create KMS Key Ring for this agency
KeyRing newKeyRing = CreateKeyRing(client, jsonCreds.project_id, keyLocationID, keyRingName);
//Create a KMS Key in that new Key Ring
CryptoKey newKey = CreateCryptoKey(client, jsonCreds.project_id, keyLocationID, newKeyRing.KeyRingName.KeyRingId, keyID);
//Create Bucket with specified Parameters
Bucket bucket = new Bucket
{
Location = "us-central1",
Name = TID,
StorageClass = StorageClasses.Standard,
Encryption = new Bucket.EncryptionData()
{
DefaultKmsKeyName = newKey.Name
}
};
var newStorageBucket = storageClient.CreateBucket(jsonCreds.project_id, bucket);
//Set permissions for the new Service Account for the new KeyRing and Bucket
AddMemberToKeyRingPolicy(client, jsonCreds.project_id, keyLocationID, newKeyRing.KeyRingName.KeyRingId, "custom_role_with_multiple_permissions", "serviceAccount:" + newServiceAccount.Email);
AddBucketIamMember(newStorageBucket.Name, "roles/storage.admin", "serviceAccount:" + newServiceAccount.Email);
//Testing uploading to the new bucket with the new account
var newSACredential = GoogleCredential.FromJson(newServiceAccountKey.ToString()).CreateScoped("https://www.googleapis.com/auth/cloudkms");
var storage = StorageClient.Create(newSACredential);
using (var fileStream = new FileStream("sample_image.png", FileMode.Open, FileAccess.Read, FileShare.Read))
{
storage.UploadObject(newStorageBucket.Name, "sample_image_uploaded.png", null, fileStream);
}
}
Any ideas what I might be doing wrong? It looks like it's a permissions issue, but I have pretty much every single one available for both Storage and KMS assigned to this new Service Account that gets created on the fly.
Full Stack Trace:
Google.GoogleApiException: Google.Apis.Requests.RequestError
Insufficient Permission [403]
Errors [
Message[Insufficient Permission] Location[ - ] Reason[insufficientPermissions] Domain[global]
]
at Google.Cloud.Storage.V1.StorageClientImpl.UploadHelper.CheckFinalProgress() in T:\src\github\google-cloud-dotnet\releasebuild\apis\Google.Cloud.Storage.V1\Google.Cloud.Storage.V1\StorageClientImpl.UploadObject.cs:204
at Google.Cloud.Storage.V1.StorageClientImpl.UploadHelper.Execute() in T:\src\github\google-cloud-dotnet\releasebuild\apis\Google.Cloud.Storage.V1\Google.Cloud.Storage.V1\StorageClientImpl.UploadObject.cs:154
at Google.Cloud.Storage.V1.StorageClientImpl.UploadObject(Object destination, Stream source, UploadObjectOptions options, IProgress`1 progress) in T:\src\github\google-cloud-dotnet\releasebuild\apis\Google.Cloud.Storage.V1\Google.Cloud.Storage.V1\StorageClientImpl.UploadObject.cs:97
at Google.Cloud.Storage.V1.StorageClientImpl.UploadObject(String bucket, String objectName, String contentType, Stream source, UploadObjectOptions options, IProgress`1 progress) in T:\src\github\google-cloud-dotnet\releasebuild\apis\Google.Cloud.Storage.V1\Google.Cloud.Storage.V1\StorageClientImpl.UploadObject.cs:70
at ConsoleApp1.Program.Main(String[] args) in /Users/btruman/Desktop/gcp_scripts/VOCA Onboarding/Program.cs:136
Upvotes: 3
Views: 2514
Reputation: 91
So there's two ways to solve this:
Option 1:
I was not enabling the project to have access to the KMS Key that was being used to encrypt/decrypt the storage bucket. I was able to test by running the following command in the cli while logged in as myself:
gsutil kms authorize -p PROJECTNAME -k projects/PROJECTNAME/locations/global/keyRings/KEYRINGNAME/cryptoKeys/KEYNAME
I then logged in as the service account and tried to upload a file. It was successful after doing so.
Option 2:
Upvotes: 0
Reputation: 404
You must create the Cloud KMS key in the same location as the data you intend to encrypt.For further reference please check link [1].
https://cloud.google.com/storage/docs/encryption/using-customer-managed-keys#prereqs
Upvotes: 1