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
35 changes: 31 additions & 4 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -122,9 +122,9 @@ jobs:
if: ${{ env.DOCKERHUB_PASSWORD && env.DOCKERHUB_USERNAME }}
run: |
echo "Login to Docker Hub";echo "$DOCKERHUB_PASSWORD" | docker login -u "$DOCKERHUB_USERNAME" --password-stdin
env:
DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }}
DOCKERHUB_PASSWORD: ${{ secrets.DOCKERHUB_PASSWORD }}
env:
DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }}
DOCKERHUB_PASSWORD: ${{ secrets.DOCKERHUB_PASSWORD }}
- name: Run Barge
working-directory: ${{ github.workspace }}/barge
run: |
Expand All @@ -145,6 +145,19 @@ jobs:
- name: docker logs
run: docker logs ocean-ocean-contracts-1 && docker logs ocean-typesense-1
if: ${{ failure() }}
- name: Set DOCKER_REGISTRY_AUTHS from Docker Hub secrets
if: env.DOCKERHUB_USERNAME && env.DOCKERHUB_PASSWORD
run: |
DOCKER_REGISTRY_AUTHS=$(jq -n \
--arg username "$DOCKERHUB_USERNAME" \
--arg password "$DOCKERHUB_PASSWORD" \
'{ "https://registry-1.docker.io": { "username": $username, "password": $password } }')
echo "DOCKER_REGISTRY_AUTHS<<EOF" >> $GITHUB_ENV
echo "$DOCKER_REGISTRY_AUTHS" >> $GITHUB_ENV
echo "EOF" >> $GITHUB_ENV
env:
DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }}
DOCKERHUB_PASSWORD: ${{ secrets.DOCKERHUB_PASSWORD }}
- name: integration tests
run: npm run test:integration:cover
env:
Expand All @@ -162,6 +175,7 @@ jobs:
FEE_AMOUNT: '{ "amount": 1, "unit": "MB" }'
ASSET_PURGATORY_URL: 'https://raw.githubusercontent.com/oceanprotocol/list-purgatory/main/list-assets.json'
ACCOUNT_PURGATORY_URL: 'https://raw.githubusercontent.com/oceanprotocol/list-purgatory/main/list-accounts.json'
DOCKER_REGISTRY_AUTHS: ${{ env.DOCKER_REGISTRY_AUTHS }}
- name: docker logs
run: docker logs ocean-ocean-contracts-1 && docker logs ocean-typesense-1
if: ${{ failure() }}
Expand Down Expand Up @@ -237,7 +251,19 @@ jobs:
repository: 'oceanprotocol/ocean-node'
path: 'ocean-node'
ref: ${{ github.event_name == 'pull_request' && github.head_ref || 'main' }}

- name: Set DOCKER_REGISTRY_AUTHS from Docker Hub secrets
if: env.DOCKERHUB_USERNAME && env.DOCKERHUB_PASSWORD
run: |
DOCKER_REGISTRY_AUTHS=$(jq -n \
--arg username "$DOCKERHUB_USERNAME" \
--arg password "$DOCKERHUB_PASSWORD" \
'{ "https://registry-1.docker.io": { "username": $username, "password": $password } }')
echo "DOCKER_REGISTRY_AUTHS<<EOF" >> $GITHUB_ENV
echo "$DOCKER_REGISTRY_AUTHS" >> $GITHUB_ENV
echo "EOF" >> $GITHUB_ENV
env:
DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }}
DOCKERHUB_PASSWORD: ${{ secrets.DOCKERHUB_PASSWORD }}
- name: Start Ocean Node
working-directory: ${{ github.workspace }}/ocean-node
run: |
Expand Down Expand Up @@ -265,6 +291,7 @@ jobs:
MAX_REQ_PER_MINUTE: 320
MAX_CONNECTIONS_PER_MINUTE: 320
DOCKER_COMPUTE_ENVIRONMENTS: '[{"socketPath":"/var/run/docker.sock","resources":[{"id":"disk","total":10}],"storageExpiry":604800,"maxJobDuration":3600,"minJobDuration": 60,"fees":{"8996":[{"prices":[{"id":"cpu","price":1}]}]},"free":{"maxJobDuration":60,"minJobDuration": 10,"maxJobs":3,"resources":[{"id":"cpu","max":1},{"id":"ram","max":1},{"id":"disk","max":1}]}}]'
DOCKER_REGISTRY_AUTHS: ${{ env.DOCKER_REGISTRY_AUTHS }}
- name: Check Ocean Node is running
run: |
for i in $(seq 1 90); do
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/docker.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ jobs:
- name: Login to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
username: ${{ secrets.DOCKERHUB_PUSH_USERNAME }}
password: ${{ secrets.DOCKER_PUSH_TOKEN }}

- name: Set Docker metadata
Expand Down Expand Up @@ -111,7 +111,7 @@ jobs:
- name: Login to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
username: ${{ secrets.DOCKERHUB_PUSH_USERNAME }}
password: ${{ secrets.DOCKER_PUSH_TOKEN }}
- name: Create manifest list and push
working-directory: /tmp/digests
Expand Down
29 changes: 15 additions & 14 deletions docs/API.md
Original file line number Diff line number Diff line change
Expand Up @@ -1402,20 +1402,21 @@ starts a free compute job and returns jobId if succesfull

#### Parameters

| name | type | required | description |
| ----------------- | ------ | -------- | ----------------------------------------------------------------------------- |
| command | string | v | command name |
| node | string | | if not present it means current node |
| consumerAddress | string | v | consumer address |
| signature | string | v | signature (msg=String(nonce) ) |
| nonce | string | v | nonce for the request |
| datasets | object | | list of ComputeAsset to be used as inputs |
| algorithm | object | | ComputeAlgorithm definition |
| environment | string | v | compute environment to use |
| resources | object | | optional list of required resources |
| metadata | object | | optional metadata for the job, data provided by the user |
| additionalViewers | object | | optional array of addresses that are allowed to fetch the result |
| queueMaxWaitTime | number | | optional max time in seconds a job can wait in the queue before being started |
| name | type | required | description |
| --------------------------- | ------ | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| command | string | v | command name |
| node | string | | if not present it means current node |
| consumerAddress | string | v | consumer address |
| signature | string | v | signature (msg=String(nonce) ) |
| nonce | string | v | nonce for the request |
| datasets | object | | list of ComputeAsset to be used as inputs |
| algorithm | object | | ComputeAlgorithm definition |
| environment | string | v | compute environment to use |
| resources | object | | optional list of required resources |
| metadata | object | | optional metadata for the job, data provided by the user |
| additionalViewers | object | | optional array of addresses that are allowed to fetch the result |
| queueMaxWaitTime | number | | optional max time in seconds a job can wait in the queue before being started |
| encryptedDockerRegistryAuth | string | | Ecies encrypted docker auth schema for image (see [Private Docker Registries with Per-Job Authentication](../env.md#private-docker-registries-with-per-job-authentication)) |

#### Request

Expand Down
202 changes: 202 additions & 0 deletions docs/env.md
Original file line number Diff line number Diff line change
Expand Up @@ -226,3 +226,205 @@ The `DOCKER_COMPUTE_ENVIRONMENTS` environment variable should be a JSON array of
- **total**: Total number of the resource available.
- **min**: Minimum number of the resource needed for a job.
- **max**: Maximum number of the resource for a job.

### Docker Registry Authentication

- `DOCKER_REGISTRY_AUTHS`: JSON object mapping Docker registry URLs to authentication credentials. Used for accessing private Docker/OCI registries when validating and pulling Docker images. Each registry entry must provide either `username`+`password` or `auth`. Example:

```json
{
"https://registry-1.docker.io": {
"username": "myuser",
"password": "mypassword"
},
"https://ghcr.io": {
"username": "myuser",
"password": "ghp_..."
},
"https://registry.gitlab.com": {
"auth": "glpat-..."
}
}
```

**Configuration Options:**

- **Registry URL** (key): The full registry URL including protocol (e.g., `https://registry-1.docker.io`, `https://ghcr.io`, `https://registry.gitlab.com`)
- **username** (optional): Username for registry authentication. Required if using password-based auth.
- **password** (optional): Password or personal access token for registry authentication. Required if using username-based auth.
- **auth** (optional): Authentication token (alternative to username+password). Required if not using username+password.

**Notes:**

- For Docker Hub (`registry-1.docker.io`), you can use your Docker Hub username and password, or a personal access token (PAT) as the password.
- For GitHub Container Registry (GHCR), use your GitHub username with a personal access token (PAT) as the password, or use a token directly.
- For GitLab Container Registry, use a personal access token (PAT) or deploy token.
- The registry URL must match exactly (including protocol) with the registry used in the Docker image reference.
- If no credentials are configured for a registry, the node will attempt unauthenticated access (works for public images only).

---

## Private Docker Registries with Per-Job Authentication

In addition to node-level registry authentication via `DOCKER_REGISTRY_AUTHS`, you can provide encrypted Docker registry authentication credentials on a per-job basis. This allows different users to use different private registries or credentials for their compute jobs.

### Overview

The `encryptedDockerRegistryAuth` parameter allows you to securely provide Docker registry credentials that are:

- Encrypted using ECIES (Elliptic Curve Integrated Encryption Scheme) with the node's public key
- Validated to ensure proper format (either `auth` string OR `username`+`password`)
- Used only for the specific compute job, overriding node-level configuration if provided

### Encryption Format

The `encryptedDockerRegistryAuth` must be:

1. A JSON object matching the Docker registry auth schema (see below)
2. Encrypted using ECIES with the node's public key
3. Hex-encoded as a string

**Auth Schema Format:**

The decrypted JSON must follow this structure:

```json
{
"username": "myuser",
"password": "mypassword"
}
```

OR

```json
{
"auth": "base64-encoded-username:password"
}
```

OR (all fields present)

```json
{
"username": "myuser",
"password": "mypassword",
"auth": "base64-encoded-username:password"
}
```

**Validation Rules:**

- Either `auth` string must be provided (non-empty), OR
- Both `username` AND `password` must be provided (both non-empty)
- Empty strings are not accepted

### Usage Examples

#### 1. Paid Compute Start (`POST /api/services/compute`)

```json
{
"command": "startCompute",
"consumerAddress": "0x...",
"signature": "...",
"nonce": "123",
"environment": "0x...",
"algorithm": {
"meta": {
"container": {
"image": "registry.example.com/myorg/myimage:latest"
}
}
},
"datasets": [],
"payment": { ... },
"encryptedDockerRegistryAuth": "0xdeadbeef..." // ECIES encrypted hex string
}
```

#### 2. Free Compute Start (`POST /api/services/freeCompute`)

```json
{
"command": "freeStartCompute",
"consumerAddress": "0x...",
"signature": "...",
"nonce": "123",
"environment": "0x...",
"algorithm": {
"meta": {
"container": {
"image": "ghcr.io/myorg/myimage:latest"
}
}
},
"datasets": [],
"encryptedDockerRegistryAuth": "0xdeadbeef..." // ECIES encrypted hex string
}
```

#### 3. Initialize Compute

The `initialize` command accepts `encryptedDockerRegistryAuth` as part of the command payload, as it validates the image

```json
{
"command": "initialize",
"datasets": [...],
"algorithm": {
"meta": {
"container": {
"image": "registry.gitlab.com/myorg/myimage:latest"
}
}
},
"environment": "0x...",
"payment": { ... },
"consumerAddress": "0x...",
"maxJobDuration": 3600,
"encryptedDockerRegistryAuth": "0xdeadbeef..." // ECIES encrypted hex string
}
```

### Encryption Process

To create `encryptedDockerRegistryAuth`, you need to:

1. **Prepare the auth JSON object:**

```json
{
"username": "myuser",
"password": "mypassword"
}
```

2. **Get the node's public key** (available via the node's API or P2P interface)

3. **Encrypt the JSON string** using ECIES with the node's public key

4. **Hex-encode the encrypted result**

### Behavior

- **Priority**: If `encryptedDockerRegistryAuth` is provided, it takes precedence over node-level `DOCKER_REGISTRY_AUTHS` configuration for that specific job
- **Validation**: The encrypted auth is decrypted and validated before the job starts. Invalid formats will result in an error
- **Scope**: The credentials are used for:
- Validating the Docker image exists (during initialize)
- Pulling the Docker image (during job execution)
- **Security**: Credentials are encrypted and only decrypted by the node using its private key

### Error Handling

If `encryptedDockerRegistryAuth` is invalid, you'll receive an error:

- **Decryption failure**: `Invalid encryptedDockerRegistryAuth: failed to parse JSON - [error message]`
- **Schema validation failure**: `Invalid encryptedDockerRegistryAuth: Either 'auth' must be provided, or both 'username' and 'password' must be provided`

### Notes

- The `encryptedDockerRegistryAuth` parameter is optional. If not provided, the node will use `DOCKER_REGISTRY_AUTHS` configuration or attempt unauthenticated access
- The registry URL in the Docker image reference must match the registry you're authenticating to
- For Docker Hub, use `registry-1.docker.io` as the registry URL
- Credentials are stored encrypted in the job record and decrypted only when needed for image operations
1 change: 1 addition & 0 deletions src/@types/C2D/C2D.ts
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,7 @@ export interface DBComputeJob extends ComputeJob {
metadata?: DBComputeJobMetadata
additionalViewers?: string[] // addresses of additional addresses that can get results
algoDuration: number // duration of the job in seconds
encryptedDockerRegistryAuth?: string
}

// make sure we keep them both in sync
Expand Down
10 changes: 10 additions & 0 deletions src/@types/OceanNode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,8 +93,18 @@ export interface AccessListContract {
[chainId: string]: string[]
}

export interface dockerRegistryAuth {
username?: string
password?: string
auth?: string
}
export interface dockerRegistrysAuth {
[registry: string]: dockerRegistryAuth
}

export interface OceanNodeConfig {
dockerComputeEnvironments: C2DDockerConfig[]
dockerRegistrysAuth: dockerRegistrysAuth
authorizedDecrypters: string[]
authorizedDecryptersList: AccessListContract | null
allowedValidators: string[]
Expand Down
2 changes: 2 additions & 0 deletions src/@types/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,7 @@ export interface ComputeInitializeCommand extends Command {
maxJobDuration: number
policyServer?: any // object to pass to policy server
queueMaxWaitTime?: number // max time in seconds a job can wait in the queue before being started
encryptedDockerRegistryAuth?: string
}

export interface FreeComputeStartCommand extends Command {
Expand All @@ -216,6 +217,7 @@ export interface FreeComputeStartCommand extends Command {
metadata?: DBComputeJobMetadata
additionalViewers?: string[] // addresses of additional addresses that can get results
queueMaxWaitTime?: number // max time in seconds a job can wait in the queue before being started
encryptedDockerRegistryAuth?: string
}
export interface PaidComputeStartCommand extends FreeComputeStartCommand {
payment: ComputePayment
Expand Down
Loading
Loading