Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changeset/wicked-ghosts-tie.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@e2b/python-sdk': minor
'e2b': minor
---

URL signing is resolved automatically
5 changes: 3 additions & 2 deletions apps/web/src/app/(docs)/docs/filesystem/download/page.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ with open('/local/path', 'w') as file:
Sometimes, you may want to let users from unauthorized environments, like a browser, download files from the sandbox.
For this use case, you can use pre-signed URLs to let users download files securely.

All you need to do is create a sandbox with the `secure: true` option. A download URL will then be generated with a signature that allows only authorized users to access files.
You can optionally set an expiration time for the URL so that it will be valid only for a limited time.
<CodeGroup>
```js
import fs from 'fs'
Expand All @@ -44,7 +46,6 @@ const sandbox = await Sandbox.create(template, { secure: true })
// Create a pre-signed URL for file download with a 10 second expiration
const publicUrl = await sandbox.downloadUrl(
'demo.txt', {
useSignature: true,
useSignatureExpiration: 10_000, // optional
},
)
Expand All @@ -61,6 +62,6 @@ sandbox = Sandbox(timeout=12_000, secure=True)

# Create a pre-signed URL for file download with a 10 second expiration
# The user only has to visit the URL to download the file, this also works in a browser.
signed_url = sbx.download_url(path="demo.txt", user="user", use_signature=True, use_signature_expiration=10_000)
signed_url = sbx.download_url(path="demo.txt", user="user", use_signature_expiration=10_000)
```
</CodeGroup>
5 changes: 3 additions & 2 deletions apps/web/src/app/(docs)/docs/filesystem/upload/page.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ with open("path/to/local/file", "rb") as file:
Sometimes, you may want to let users from unauthorized environments, like a browser, upload files to the sandbox.
For this use case, you can use pre-signed URLs to let users upload files securely.

All you need to do is create a sandbox with the `secure: true` option. An upload URL will then be generated with a signature that allows only authorized users to upload files.
You can optionally set an expiration time for the URL so that it will be valid only for a limited time.
<CodeGroup>
```js
import fs from 'fs'
Expand All @@ -44,7 +46,6 @@ const sandbox = await Sandbox.create(template, { secure: true })
// Create a pre-signed URL for file upload with a 10 second expiration
const publicUploadUrl = await sandbox.uploadUrl(
'demo.txt', {
useSignature: true,
useSignatureExpiration: 10_000, // optional
},
)
Expand All @@ -66,7 +67,7 @@ import requests
sandbox = Sandbox(timeout=12_000, secure=True)

# Create a pre-signed URL for file upload with a 10 second expiration
signed_url = sbx.upload_url(path="demo.txt", user="user", use_signature=True, use_signature_expiration=10_000)
signed_url = sbx.upload_url(path="demo.txt", user="user", use_signature_expiration=10_000)

form_data = {"file":"file content"}
requests.post(signed_url, data=form_data)
Expand Down
40 changes: 9 additions & 31 deletions packages/js-sdk/src/sandbox/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,17 +63,9 @@ export interface SandboxOpts extends ConnectionOpts {
* Options for sandbox upload/download URL generation.
*/
export interface SandboxUrlOpts {
/**
* Use signature for the URL.
* This needs to be used in case of using secured envd in sandbox.
*
* @default false
*/
useSignature?: true

/**
* Use signature expiration for the URL.
* Optional parameter to set the expiration time for the signature.
* Optional parameter to set the expiration time for the signature in seconds.
*/
useSignatureExpiration?: number

Expand Down Expand Up @@ -428,26 +420,19 @@ export class Sandbox extends SandboxApi {
async uploadUrl(path?: string, opts?: SandboxUrlOpts) {
opts = opts ?? {}

if (
!this.envdAccessToken &&
(opts.useSignature || opts.useSignatureExpiration != undefined)
) {
throw new Error(
'Signature can be used only when sandbox is spawned with secure option.'
)
}
const useSignature = !!this.envdAccessToken

if (!opts.useSignature && opts.useSignatureExpiration != undefined) {
if (!useSignature && opts.useSignatureExpiration != undefined) {
throw new Error(
'Signature expiration can be used only when signature is set to true.'
'Signature expiration can be used only when sandbox is created as secured.'
)
}

const username = opts.user ?? defaultUsername
const filePath = path ?? ''
const fileUrl = this.fileUrl(filePath, username)

if (opts.useSignature) {
if (useSignature) {
const url = new URL(fileUrl)
const sig = await getSignature({
path: filePath,
Expand Down Expand Up @@ -480,25 +465,18 @@ export class Sandbox extends SandboxApi {
async downloadUrl(path: string, opts?: SandboxUrlOpts) {
opts = opts ?? {}

if (
!this.envdAccessToken &&
(opts.useSignature || opts.useSignatureExpiration != undefined)
) {
throw new Error(
'Signature can be used only when sandbox is spawned with secure option.'
)
}
const useSignature = !!this.envdAccessToken

if (!opts.useSignature && opts.useSignatureExpiration != undefined) {
if (!useSignature && opts.useSignatureExpiration != undefined) {
throw new Error(
'Signature expiration can be used only when signature is set to true.'
'Signature expiration can be used only when sandbox is created as secured.'
)
}

const username = opts.user ?? defaultUsername
const fileUrl = this.fileUrl(path, username)

if (opts.useSignature) {
if (useSignature) {
const url = new URL(fileUrl)
const sig = await getSignature({
path,
Expand Down
33 changes: 2 additions & 31 deletions packages/js-sdk/tests/sandbox/files/signing.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,7 @@ test.skipIf(isDebug)('test access file with expired signing', async () => {
await sbx.files.write('hello.txt', 'hello world')

const fileUrlWithSigning = await sbx.downloadUrl('hello.txt', {
useSignature: true,
useSignatureExpiration: -10_000,
useSignatureExpiration: -10_000
})

const res = await fetch(fileUrlWithSigning)
Expand All @@ -38,8 +37,7 @@ test.skipIf(isDebug)('test access file with valid signing', async () => {
await sbx.files.write('hello.txt', 'hello world')

const fileUrlWithSigning = await sbx.downloadUrl('hello.txt', {
useSignature: true,
useSignatureExpiration: 10_000,
useSignatureExpiration: 10_000
})

const res = await fetch(fileUrlWithSigning)
Expand All @@ -63,7 +61,6 @@ test.skipIf(isDebug)(

const fileUrlWithSigning = await sbx.downloadUrl('hello.txt', {
user: 'root',
useSignature: true,
useSignatureExpiration: 10_000,
})

Expand All @@ -84,7 +81,6 @@ test.skipIf(isDebug)('test upload file with valid signing', async () => {
secure: true,
})
const fileUrlWithSigning = await sbx.uploadUrl('hello.txt', {
useSignature: true,
useSignatureExpiration: 10_000,
})

Expand Down Expand Up @@ -113,7 +109,6 @@ test.skipIf(isDebug)(

const fileUrlWithSigning = await sbx.uploadUrl('hello.txt', {
user: 'root',
useSignature: true,
useSignatureExpiration: 10_000,
})

Expand All @@ -139,7 +134,6 @@ test.skipIf(isDebug)('test upload file with invalid signing', async () => {
secure: true,
})
const fileUrlWithSigning = await sbx.uploadUrl('hello.txt', {
useSignature: true,
useSignatureExpiration: -100_000,
})

Expand All @@ -159,29 +153,6 @@ test.skipIf(isDebug)('test upload file with invalid signing', async () => {
await sbx.kill()
})

test.skipIf(isDebug)('test upload file with missing signing', async () => {
const sbx = await Sandbox.create(template, {
timeoutMs: timeout,
secure: true,
})
const fileUrlWithSigning = await sbx.uploadUrl('hello.txt')

const form = new FormData()
form.append('file', 'file content')

const res = await fetch(fileUrlWithSigning, { method: 'POST', body: form })
const resBody = await res.text()
const resStatus = res.status

assert.equal(resStatus, 401)
assert.deepEqual(JSON.parse(resBody), {
code: 401,
message: 'missing signature query parameter',
})

await sbx.kill()
})

test.skipIf(isDebug)('test command run with secured sbx', async () => {
const sbx = await Sandbox.create(template, {
timeoutMs: timeout,
Expand Down
26 changes: 1 addition & 25 deletions packages/js-sdk/tests/sandbox/secure.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,38 +5,14 @@ import { randomUUID, createHash } from 'node:crypto'

const timeout = 20 * 1000

test.skipIf(isDebug)('test access file without signing', async () => {
const sbx = await Sandbox.create(template, {
timeoutMs: timeout,
secure: true,
})
await sbx.files.write('hello.txt', 'hello world')

const fileUrlWithoutSigning = await sbx.downloadUrl('hello.txt')

const res = await fetch(fileUrlWithoutSigning)
const resBody = await res.text()
const resStatus = res.status

assert.equal(resStatus, 401)
assert.deepEqual(JSON.parse(resBody), {
code: 401,
message: 'missing signature query parameter',
})

await sbx.kill()
})

test.skipIf(isDebug)('test access file with signing', async () => {
const sbx = await Sandbox.create(template, {
timeoutMs: timeout,
secure: true,
})
await sbx.files.write('hello.txt', 'hello world')

const fileUrlWithSigning = await sbx.downloadUrl('hello.txt', {
useSignature: true,
})
const fileUrlWithSigning = await sbx.downloadUrl('hello.txt')

const res = await fetch(fileUrlWithSigning)
const resBody = await res.text()
Expand Down
6 changes: 2 additions & 4 deletions packages/python-sdk/e2b/sandbox/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,20 +77,19 @@ def download_url(
self,
path: str,
user: str = "user",
use_signature: bool = False,
use_signature_expiration: Optional[int] = None,
) -> str:
"""
Get the URL to download a file from the sandbox.

:param path: Path to the file to download
:param user: User to upload the file as
:param use_signature: Whether to use a signed URL for downloading the file
:param use_signature_expiration: Expiration time for the signed URL in seconds

:return: URL for downloading file
"""

use_signature = self._envd_access_token is not None
if use_signature:
signature = get_signature(
path, "read", user, self._envd_access_token, use_signature_expiration
Expand All @@ -105,7 +104,6 @@ def upload_url(
self,
path: Optional[str] = None,
user: str = "user",
use_signature: bool = False,
use_signature_expiration: Optional[int] = None,
) -> str:
"""
Expand All @@ -115,12 +113,12 @@ def upload_url(

:param path: Path to the file to upload
:param user: User to upload the file as
:param use_signature: Whether to use a signed URL for downloading the file
:param use_signature_expiration: Expiration time for the signed URL in seconds

:return: URL for uploading file
"""

use_signature = self._envd_access_token is not None
if use_signature:
signature = get_signature(
path, "write", user, self._envd_access_token, use_signature_expiration
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ async def test_download_url_with_signing(template):

try:
await sbx.files.write(file_path, file_content)
signed_url = sbx.download_url(file_path, "user", True)
signed_url = sbx.download_url(file_path, "user")

with urllib.request.urlopen(signed_url) as resp:
assert resp.status == 200
Expand All @@ -35,7 +35,7 @@ async def test_download_url_with_signing_and_expiration(template):

try:
await sbx.files.write(file_path, file_content)
signed_url = sbx.download_url(file_path, "user", True, 120)
signed_url = sbx.download_url(file_path, "user", 120)

with urllib.request.urlopen(signed_url) as resp:
assert resp.status == 200
Expand All @@ -55,9 +55,7 @@ async def test_download_url_with_expired_signing(template):
try:
await sbx.files.write(file_path, file_content)

signed_url = sbx.download_url(
file_path, "user", use_signature=True, use_signature_expiration=-120
)
signed_url = sbx.download_url(file_path, "user", use_signature_expiration=-120)

with pytest.raises(urllib.error.HTTPError) as exc_info:
urllib.request.urlopen(signed_url)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ async def test_download_url_with_signing(template):

try:
sbx.files.write(file_path, file_content)
signed_url = sbx.download_url(file_path, "user", True)
signed_url = sbx.download_url(file_path, "user")

with urllib.request.urlopen(signed_url) as resp:
assert resp.status == 200
Expand All @@ -33,7 +33,7 @@ async def test_download_url_with_signing_and_expiration(template):

try:
sbx.files.write(file_path, file_content)
signed_url = sbx.download_url(file_path, "user", True, 120)
signed_url = sbx.download_url(file_path, "user", 120)

with urllib.request.urlopen(signed_url) as resp:
assert resp.status == 200
Expand All @@ -53,9 +53,7 @@ async def test_download_url_with_expired_signing(template):
try:
sbx.files.write(file_path, file_content)

signed_url = sbx.download_url(
file_path, "user", use_signature=True, use_signature_expiration=-120
)
signed_url = sbx.download_url(file_path, "user", use_signature_expiration=-120)

with pytest.raises(urllib.error.HTTPError) as exc_info:
urllib.request.urlopen(signed_url)
Expand Down