Having issues trying to allow a client to upload a file via a presigned url.
Error received
<?xml version='1.0' encoding='UTF-8'?>
<Error>
<Code>AccessDenied</Code>
<Message>Access denied.</Message>
<Details>
urlsigner@<project>.iam.gserviceaccount.com does not have storage.objects.create access to <bucket-name>/<filename>.pdf.
</Details>
</Error>
Code executes as a gcp cloud function and is used for generating. Cloud function is default service account function was updated to urlSigner
async createSignedUrl({
contentType,
fileName,
userId,
}: SignedUrlRequest): Promise<string> {
const bucket = await this.cloudStorage.bucket(BUCKET_NAME);
console.log(
moment.utc().add('minutes', 15).toDate(),
Date.now() + 15 * 60 * 1000
);
const options: any = {
version: 'v4',
action: 'write',
expires: Date.now() + 15 * 60 * 1000,
contentType,
};
const [signedUrl] = await bucket
.file(`${userId}-${fileName}`)
.getSignedUrl(options);
return signedUrl;
}
CORS temp json file
[
{
"maxAgeSeconds": 3600,
"method": ["GET", "POST", "PUT", "DELETE", "OPTIONS"],
"origin": ["*"],
"responseHeader": [
"Content-Type",
"Authorization",
"Content-Length",
"User-Agent",
"x-goog-resumable"
]
}
]
Steps used to create gcp resources
gcloud iam service-accounts create urlsigner --display-name="GCS URL Signer" --project=<project-id>
gcloud iam service-accounts keys create service_account.json --iam-account=urlsigner@<project-id>.iam.gserviceaccount.com
gsutil mb gs://<bucket-name>
gsutil iam ch serviceAccount:urlsigner@<project-id>.iam.gserviceaccount.com:roles/storage.admin gs://<bucket-name>
gsutil cors set cors-json-file.json gs://<bucket-name>
The upload code
export async function uploadDoc(preSignedUrl: string, file: File) {
return await new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.open('PUT', preSignedUrl, true);
xhr.onload = () => {
const status = xhr.status;
if (status === 200) {
console.log('uploaded');
resolve('good to go');
} else {
console.log('failed to upload');
reject('not good to go');
}
};
xhr.onabort = () => {
reject('aborted for idk');
};
xhr.onerror = () => {
reject('Failed for idk');
};
xhr.setRequestHeader('Content-Type', file.type);
const formData = new FormData();
formData.append('verify-doc', file);
xhr.send(formData);
});
}
Lastly, verified that the cloud function has the Service-Token-Creator role necessary for signing the blob that gets sent back to the front-end and that under the bucket permissions the service account is listed with the storage admin role too. So at this point just scratching my head as to why I'm getting the error of access denied for lack of permissions by the service account.
Thanks in advance for whoever can assist.
PS. I referenced this article as well https://medium.com/google-cloud/upload-download-files-from-a-browser-with-gcs-signed-urls-and-signed-policy-documents-f66fff8e425