|
1 | 1 | #!/usr/bin/env python |
2 | 2 | """A module with the implementation of the large file collection flow.""" |
| 3 | +import logging |
3 | 4 | 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 |
4 | 13 |
|
5 | 14 | from grr_response_core.lib.rdfvalues import large_file as rdf_large_file |
6 | 15 | from grr_response_core.lib.rdfvalues import paths as rdf_paths |
@@ -64,11 +73,32 @@ def Start(self) -> None: |
64 | 73 | # analyst decrypt the file later. |
65 | 74 | self.state.encryption_key = os.urandom(16) |
66 | 75 |
|
| 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 | + |
67 | 80 | args = rdf_large_file.CollectLargeFileArgs() |
68 | 81 | args.path_spec = self.args.path_spec |
69 | 82 | args.signed_url = self.args.signed_url |
70 | 83 | args.encryption_key = self.state.encryption_key |
71 | 84 |
|
| 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 | + |
72 | 102 | self.CallClient( |
73 | 103 | server_stubs.CollectLargeFile, |
74 | 104 | args, |
@@ -102,3 +132,25 @@ def Callback(self, responses: _Responses) -> None: |
102 | 132 |
|
103 | 133 | self.state.session_uri = response.session_uri |
104 | 134 | 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