Brian Truman
Brian Truman

Reputation: 91

403 Error on KMS Permissions for Uploading Content to GCP Storage Bucket

Figured it out

So there's two ways to solve this:

Option 1:

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

Answers (2)

Brian Truman
Brian Truman

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:

  • After digging around with the cloud console, I found that there was a Storage Service Account that needed access to Encrypt Decrypt. This account is listed under Storage > Settings > Cloud Storage Service Account.
  • It appears that GCP delegates the actual work to this account to perform the upload task. So while it has bucket access (obviously, since it's the Storage Service Account), it did not have KMS Access. After adding KMS Encrypt/Decrypt to this SA, it now worked for me automatically without any gsutil intervention.

Upvotes: 0

Shafiq I
Shafiq I

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

Related Questions