Skip to content

Commit 2060ee7

Browse files
ikreymertw4l
andauthored
Support Presigning for use with custom domain (#2249)
If access_endpoint_url is provided: - Use virtual host addressing style, so presigned URLs are of the form `https://bucket.s3-host.example.com/path/` instead of `https://s3-host.example.com/bucket/path/` - Allow for replacing `https://bucket.s3-host.example.com/path/` -> `https://my-custom-domain.example.com/path/`, where `https://my-custom-domain.example.com/path/` is the access_endpoint_url - Remove old `use_access_for_presign` which is no longer used - Fixes #2248 - docs: update deployment docs storages section to mention custom storages, access_endpoint_url --------- Co-authored-by: Tessa Walsh <tessa@bitarchivist.net>
1 parent 8e37533 commit 2060ee7

4 files changed

Lines changed: 89 additions & 39 deletions

File tree

backend/btrixcloud/models.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1180,7 +1180,6 @@ class S3Storage(BaseModel):
11801180
secret_key: str
11811181
access_endpoint_url: str
11821182
region: str = ""
1183-
use_access_for_presign: bool = True
11841183

11851184

11861185
# ============================================================================

backend/btrixcloud/storages.py

Lines changed: 25 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
from fastapi import Depends, HTTPException
3030
from stream_zip import stream_zip, NO_COMPRESSION_64, Method
3131
from remotezip import RemoteZip
32+
from aiobotocore.config import AioConfig
3233

3334
import aiobotocore.session
3435
import requests
@@ -50,7 +51,7 @@
5051
AddedResponseName,
5152
)
5253

53-
from .utils import is_bool, slug_from_name
54+
from .utils import slug_from_name
5455
from .version import __version__
5556

5657

@@ -77,15 +78,12 @@ class StorageOps:
7778
org_ops: OrgOps
7879
crawl_manager: CrawlManager
7980

80-
is_local_minio: bool
8181
frontend_origin: str
8282

8383
def __init__(self, org_ops, crawl_manager) -> None:
8484
self.org_ops = org_ops
8585
self.crawl_manager = crawl_manager
8686

87-
self.is_local_minio = is_bool(os.environ.get("IS_LOCAL_MINIO"))
88-
8987
frontend_origin = os.environ.get(
9088
"FRONTEND_ORIGIN", "http://browsertrix-cloud-frontend"
9189
)
@@ -138,12 +136,7 @@ def _create_s3_storage(self, storage: dict[str, str]) -> S3Storage:
138136
if bucket_name:
139137
endpoint_url += bucket_name + "/"
140138

141-
if self.is_local_minio:
142-
access_endpoint_url = "/data/"
143-
use_access_for_presign = False
144-
else:
145-
access_endpoint_url = storage.get("access_endpoint_url") or endpoint_url
146-
use_access_for_presign = is_bool(storage.get("use_access_for_presign"))
139+
access_endpoint_url = storage.get("access_endpoint_url") or endpoint_url
147140

148141
return S3Storage(
149142
access_key=storage["access_key"],
@@ -152,7 +145,6 @@ def _create_s3_storage(self, storage: dict[str, str]) -> S3Storage:
152145
endpoint_url=endpoint_url,
153146
endpoint_no_bucket_url=endpoint_no_bucket_url,
154147
access_endpoint_url=access_endpoint_url,
155-
use_access_for_presign=use_access_for_presign,
156148
)
157149

158150
async def add_custom_storage(
@@ -177,7 +169,6 @@ async def add_custom_storage(
177169
endpoint_url=endpoint_url,
178170
endpoint_no_bucket_url=endpoint_no_bucket_url,
179171
access_endpoint_url=storagein.access_endpoint_url or storagein.endpoint_url,
180-
use_access_for_presign=True,
181172
)
182173

183174
try:
@@ -264,12 +255,12 @@ def get_available_storages(self, org: Organization) -> List[StorageRef]:
264255

265256
@asynccontextmanager
266257
async def get_s3_client(
267-
self, storage: S3Storage, use_access=False
258+
self, storage: S3Storage, for_presign=False
268259
) -> AsyncIterator[tuple[AIOS3Client, str, str]]:
269260
"""context manager for s3 client"""
270-
endpoint_url = (
271-
storage.endpoint_url if not use_access else storage.access_endpoint_url
272-
)
261+
# parse bucket and key from standard endpoint_url
262+
endpoint_url = storage.endpoint_url
263+
273264
if not endpoint_url.endswith("/"):
274265
endpoint_url += "/"
275266

@@ -280,12 +271,17 @@ async def get_s3_client(
280271

281272
session = aiobotocore.session.get_session()
282273

274+
config = None
275+
if for_presign and storage.access_endpoint_url != storage.endpoint_url:
276+
config = AioConfig(s3={"addressing_style": "virtual"})
277+
283278
async with session.create_client(
284279
"s3",
285-
region_name=storage.region,
280+
region_name=storage.region or "us-east-1",
286281
endpoint_url=endpoint_url,
287282
aws_access_key_id=storage.access_key,
288283
aws_secret_access_key=storage.secret_key,
284+
config=config,
289285
) as client:
290286
yield client, bucket, key
291287

@@ -454,24 +450,27 @@ async def get_presigned_url(
454450

455451
s3storage = self.get_org_storage_by_ref(org, crawlfile.storage)
456452

457-
async with self.get_s3_client(s3storage, s3storage.use_access_for_presign) as (
458-
client,
459-
bucket,
460-
key,
461-
):
453+
async with self.get_s3_client(
454+
s3storage,
455+
for_presign=True,
456+
) as (client, bucket, key):
457+
orig_key = key
462458
key += crawlfile.filename
463459

464460
presigned_url = await client.generate_presigned_url(
465461
"get_object", Params={"Bucket": bucket, "Key": key}, ExpiresIn=duration
466462
)
467463

468464
if (
469-
not s3storage.use_access_for_presign
470-
and s3storage.access_endpoint_url
465+
s3storage.access_endpoint_url
471466
and s3storage.access_endpoint_url != s3storage.endpoint_url
472467
):
468+
parts = urlsplit(s3storage.endpoint_url)
469+
host_endpoint_url = (
470+
f"{parts.scheme}://{bucket}.{parts.netloc}/{orig_key}"
471+
)
473472
presigned_url = presigned_url.replace(
474-
s3storage.endpoint_url, s3storage.access_endpoint_url
473+
host_endpoint_url, s3storage.access_endpoint_url
475474
)
476475

477476
return presigned_url
@@ -490,11 +489,7 @@ async def _delete_file(
490489

491490
s3storage = self.get_org_storage_by_ref(org, storage)
492491

493-
async with self.get_s3_client(s3storage) as (
494-
client,
495-
bucket,
496-
key,
497-
):
492+
async with self.get_s3_client(s3storage) as (client, bucket, key):
498493
key += filename
499494
response = await client.delete_object(Bucket=bucket, Key=key)
500495
status_code = response["ResponseMetadata"]["HTTPStatusCode"]

chart/values.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -405,6 +405,7 @@ storages:
405405
bucket_name: *local_bucket_name
406406

407407
endpoint_url: "http://local-minio.default:9000/"
408+
access_endpoint_url: "/data/"
408409

409410

410411
# optional: duration in minutes for WACZ download links to be valid

frontend/docs/docs/deploy/customization.md

Lines changed: 63 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -32,33 +32,87 @@ crawler_channels:
3232

3333
## Storage
3434

35-
The `storage` setting is used to specify primary and replica storage for a Browsertrix deployment. All configured storage options must be S3-compatible buckets. At minimum, there must be one configured storage option, as can be seen in the default configuration:
35+
The `storage` setting is used to specify primary and replica storage for a Browsertrix deployment. All configured storage options must be S3-compatible buckets. At minimum, there must be one configured storage option, which includes a `is_default_primary: true`.
36+
37+
### Using Local Minio Storage
38+
39+
Browsertrix includes a built-in Minio storage service, which is enabled by default (`minio_local: true` is set).
40+
41+
The configuration for this is as follows:
42+
3643

3744
```yaml
3845
storages:
3946
- name: "default"
4047
type: "s3"
4148
access_key: "ADMIN"
4249
secret_key: "PASSW0RD"
43-
bucket_name: *local_bucket_name
50+
bucket_name: btrix-data
4451
4552
endpoint_url: "http://local-minio.default:9000/"
53+
access_endpoint_url: /data/
4654
```
4755

48-
It is possible to add one or more replica storage locations. If replica locations are enabled, all stored content in the application will be automatically replicated to each configured replica storage location in background jobs after being stored in the default primary storage. If replica locations are enabled, at least one must be set as the default replica location for primary backups. This is indicated with `is_default_replica: True`. If more than one storage location is configured, the primary storage must also be indicated with `is_default_primary: True`.
56+
The `access_key` and `secret_key` should be changed, otherwise no additional changes are needed, and all local data will be stored in this Minio instance by default.
57+
58+
The S3 bucket is accessible via `/data/` path on the same host Browsertrix is running on, eg. `http://localhost:30870/data/`.
59+
60+
61+
### Using External S3 Storage Providers
62+
63+
Browsertrix can also be used with external S3 storage providers, which can be configured as follows:
64+
65+
```yaml
66+
storages:
67+
- name: default
68+
type: "s3"
69+
access_key: "accesskey"
70+
secret_key: "secret"
71+
72+
endpoint_url: "https://s3provider.example.com/bucket/path/"
73+
access_endpoint_url: "https://my-custom-domain.example.com/path/" #optional
74+
is_default_primary: true
75+
```
76+
77+
78+
When using an external S3 provider, a custom `access_endpoint_url` can be provided, and the `bucket_name` need to be specified separately.
79+
This URL is used for direct access to WACZ files, and can be used to specify a custom domain to access the bucket.
80+
81+
The `endpoint_url` should be provided in 'path prefix' form (with the bucket after the path), eg:
82+
`https://s3provider.example.com/bucket/path/`.
83+
84+
Browsertrix will handle presigning S3 URLs so that WACZ files (and other data) can be accessed directly, using URLs of the form: `https://s3provider.example.com/bucket/path/to/files/crawl.wacz?signature...`
85+
86+
Since the local Minio service is not used, `minio_local: false` can be set to save resource in not deploying Minio.
87+
88+
89+
### Custom Access Endpoint URL
90+
91+
It may be useful to provide a custom access endpoint for accessing WACZ files and other data. if the `access_endpoint_url` is provided,
92+
it should be in 'virtual host' form (the bucket is not added to the path, but is assumed to be the in the host).
93+
94+
The host portion of the URL is then replaced with the `access_endpoint_url`. For example, given `endpoint_url: https://s3provider.example.com/bucket/path/` and `access_endpoint_url: https://my-custom-domain.example.com/path/`, a URL to a WACZ files in 'virtual host' form may be `https://bucket.s3provider.example.com/path/to/files/crawl.wacz?signature...`.
95+
96+
The `https://bucket.s3provider.example.com/path/` is then replaced with the `https://my-custom-domain.example.com/path/`, and the final URL becomes `https://my-custom-domain.example.com/path/to/files/crawl.wacz?signature...`.
97+
98+
99+
### Storage Replicas
100+
101+
It is possible to add one or more replica storage locations. If replica locations are enabled, all stored content in the application will be automatically replicated to each configured replica storage location in background jobs after being stored in the default primary storage. If replica locations are enabled, at least one must be set as the default replica location for primary backups. This is indicated with `is_default_replica: true`. If more than one storage location is configured, the primary storage must also be indicated with `is_default_primary: true`.
49102

50-
For example, here is what a storage configuration with two replica locations, one in another bucket on the same Minio S3 service as primary storage as well as another in an external S3 provider:
103+
For example, here is what a storage configuration with two replica locations, one in another bucket on the same local Minio S3 service as primary storage as well as another in an external S3 provider:
51104

52105
```yaml
53106
storages:
54107
- name: "default"
55108
type: "s3"
56109
access_key: "ADMIN"
57110
secret_key: "PASSW0RD"
58-
bucket_name: *local_bucket_name
111+
bucket_name: btrix-data
112+
access_endpoint_url: /data/
59113
60114
endpoint_url: "http://local-minio.default:9000/"
61-
is_default_primary: True
115+
is_default_primary: true
62116
63117
- name: "replica-0"
64118
type: "s3"
@@ -67,15 +121,16 @@ storages:
67121
bucket_name: "replica-0"
68122
69123
endpoint_url: "http://local-minio.default:9000/"
70-
is_default_replica: True
124+
is_default_replica: true
71125
72126
- name: "replica-1"
73127
type: "s3"
74128
access_key: "accesskey"
75129
secret_key: "secret"
76130
bucket_name: "replica-1"
77131
78-
endpoint_url: "http://s3provider.example.com"
132+
endpoint_url: "https://s3provider.example.com/bucket/path/"
133+
access_endpoint_url: "https://my-custom-domain.example.com/path/"
79134
```
80135

81136
## Horizontal Autoscaling

0 commit comments

Comments
 (0)