Skip to content

Commit 08b2075

Browse files
authored
Adds functionality to autocreate signed URLs for large file upload (#1116)
1 parent c2a212f commit 08b2075

File tree

2 files changed

+76
-0
lines changed

2 files changed

+76
-0
lines changed

grr/core/grr_response_core/config/server.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -382,3 +382,27 @@
382382
"`/etc/passwd.cache` file."
383383
),
384384
)
385+
386+
config_lib.DEFINE_string(
387+
"Server.signed_url_service_account_email",
388+
default=None,
389+
help=(
390+
"The email of the Service Account to use for signing the URL (https://cloud.google.com/storage/docs/access-control/signed-urls#signing-resumable)."
391+
),
392+
)
393+
394+
config_lib.DEFINE_string(
395+
"Server.signed_url_gcs_bucket_name",
396+
default=None,
397+
help=(
398+
"The GCS bucket name to include in the signed URL (https://cloud.google.com/storage/docs/access-control/signed-urls#signing-resumable)."
399+
),
400+
)
401+
402+
config_lib.DEFINE_integer(
403+
"Server.signed_url_expire_hours",
404+
default=12,
405+
help=(
406+
"The TTL until the signed URL expires (https://cloud.google.com/storage/docs/access-control/signed-urls#signing-resumable)."
407+
),
408+
)

grr/server/grr_response_server/flows/general/large_file.py

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,15 @@
11
#!/usr/bin/env python
22
"""A module with the implementation of the large file collection flow."""
3+
import logging
34
import os
5+
from datetime import datetime, timedelta
6+
7+
import google.auth
8+
from google.auth import compute_engine
9+
from google.auth.transport import requests
10+
from google.cloud import storage
11+
12+
from grr_response_core import config
413

514
from grr_response_core.lib.rdfvalues import large_file as rdf_large_file
615
from grr_response_core.lib.rdfvalues import paths as rdf_paths
@@ -64,11 +73,32 @@ def Start(self) -> None:
6473
# analyst decrypt the file later.
6574
self.state.encryption_key = os.urandom(16)
6675

76+
sa_email = config.CONFIG["Server.signed_url_service_account_email"]
77+
bucket_name = config.CONFIG["Server.signed_url_gcs_bucket_name"]
78+
expires_hours = config.CONFIG["Server.signed_url_expire_hours"]
79+
6780
args = rdf_large_file.CollectLargeFileArgs()
6881
args.path_spec = self.args.path_spec
6982
args.signed_url = self.args.signed_url
7083
args.encryption_key = self.state.encryption_key
7184

85+
if not args.signed_url:
86+
if not sa_email:
87+
# Log that no Service Account Email config has been provided
88+
self.Log("To autocreate a signed URL you need to provide a Service Account Email: https://cloud.google.com/storage/docs/access-control/signed-urls#signing-resumable")
89+
elif not bucket_name:
90+
# Log that no GCS config has been provided
91+
self.Log("To autocreate a signed URL you need to provide a GCS Bucket: https://cloud.google.com/storage/docs/access-control/signed-urls#signing-resumable")
92+
else:
93+
head_tail = os.path.split(args.path_spec.path)
94+
blob_name = self.rdf_flow.client_id+'-'+self.rdf_flow.flow_id+'-'+head_tail[1]
95+
self.Log("Signed URL Service Account email: %s", sa_email)
96+
self.Log("Signed URL GCS Bucket Name: %s", bucket_name)
97+
self.Log("Signed URL expires in %s hours", expires_hours)
98+
self.Log("GCS blob_name: %s", blob_name)
99+
args.signed_url = self.generate_resumable_upload_url(bucket_name, blob_name, sa_email, expires_hours)
100+
self.Log("Signed URL: %s", args.signed_url)
101+
72102
self.CallClient(
73103
server_stubs.CollectLargeFile,
74104
args,
@@ -102,3 +132,25 @@ def Callback(self, responses: _Responses) -> None:
102132

103133
self.state.session_uri = response.session_uri
104134
self.state.progress.session_uri = response.session_uri
135+
136+
def generate_resumable_upload_url(self, bucket_name, blob_name, sa_email, expires_hours):
137+
"""Generates a v4 signed URL for resumably uploading a blob using HTTP POST.
138+
"""
139+
140+
auth_request = requests.Request()
141+
credentials, project = google.auth.default()
142+
storage_client = storage.Client(project, credentials)
143+
bucket = storage_client.lookup_bucket(bucket_name)
144+
blob = bucket.blob(blob_name)
145+
expires_at = datetime.now() + timedelta(hours=expires_hours)
146+
signing_credentials = compute_engine.IDTokenCredentials(auth_request, "", service_account_email=sa_email)
147+
148+
signed_url = blob.generate_signed_url(
149+
version='v4',
150+
expiration=expires_at,
151+
method='POST',
152+
content_type="application/octet-stream",
153+
headers={"X-Goog-Resumable": "start", "Content-Type": "application/octet-stream"},
154+
credentials=signing_credentials)
155+
156+
return(signed_url)

0 commit comments

Comments
 (0)