# Secretless Access

> Cordium documentation. Canonical page: <https://octelium.com/docs/cordium/latest/workspaces/secretless>.

Every *Workspace* is an Octelium identity. When a *Workspace* starts, Cordium creates a dedicated Octelium *Session* for its run, and the *Workspace* automatically connects to the *Cluster* via an `octelium connect` process that uses that *Session*. As a result, the processes running inside the *Workspace* can access every Octelium *Service* that the *Workspace*'s owner is authorized to access, by simply using its name, with no API key, password, private key, kubeconfig or certificate inside the *Workspace*. The upstream credentials are stored as Octelium *Secrets* and injected by Octelium's identity-aware proxies on a per-request basis (read more about secretless access [here](https://octelium.com/docs/octelium/latest/management/core/service/secretless.md)).

*Diagram: OcteliumSecretless.* [View the diagram on the canonical HTML page](https://octelium.com/docs/cordium/latest/workspaces/secretless).

## Accessing Services

Inside a *Workspace*, you access an Octelium *Service* exactly as you would from a connected laptop (read more [here](https://octelium.com/docs/octelium/latest/user/cli/access.md)): via its name if it belongs to the `default` *Namespace* (e.g. `anthropic`), via `<SERVICE>.<NAMESPACE>` otherwise (e.g. `pg-staging.payments`), or via its full private FQDN (e.g. `pg-staging.payments.local.<DOMAIN>`). Here are some examples with standard tools:

```bash
# List the Services that you can access
octelium get service

# HTTP and gRPC APIs, without any API key
curl http://ledger-api.payments/v1/health

# PostgreSQL and MySQL databases, without any password
psql -h pg-staging.payments -c "SELECT now();"
mysql -h mysql-analytics

# SSH servers, without any private key or password
ssh bastion-eu

# Kubernetes clusters, without any kubeconfig credentials
octelium cfg k8s-staging
export KUBECONFIG=~/.kube/k8s-staging.<DOMAIN>
kubectl get pods -n payments

# LLM providers, with a placeholder API key that is never forwarded
ANTHROPIC_BASE_URL=http://anthropic ANTHROPIC_API_KEY=unused claude -p "Summarize the CHANGELOG"
```

Every request is authorized by Octelium *Policies* against the full request context (e.g. the HTTP method and path, the SQL query, the Kubernetes verb and namespace, or the LLM model) and logged via OpenTelemetry-native access logs that identify the *User*, the *Workspace*, its *Space* and its *Template*.

> **Note:**
>
> `octelium connect` starts as the first `POST_START` task of every run. Tasks that access Octelium *Services* must therefore be `POST_START` tasks, and they should wait until the *Service* becomes reachable (read more [here](https://octelium.com/docs/cordium/latest/workspaces/runtime.md#task-order)). Terminals, `cordium exec` and SSH sessions do not need to wait since they are typically used once the *Workspace* is running.

## Serving Services

A *Workspace* can also act as the upstream of Octelium *Services* (read more about serving *Services* from connected *Users* [here](https://octelium.com/docs/octelium/latest/management/core/service/overview.md#remotely-via-a-connected-user)). This turns any application running inside a *Workspace* into a full-fledged Octelium *Service* with its own *Policies*, secretless access, visibility, and optionally public or anonymous access, which is useful for preview environments, webhooks receivers, demos and internal tools. First, define a *Service* whose upstream is served by the *Workspace*'s owner (e.g. `alice`):

```yaml
kind: Service
metadata:
  name: storefront-preview
spec:
  mode: WEB
  isPublic: true
  config:
    upstream:
      url: http://localhost:5173
      user: alice
```

And then ask the *Workspace* to serve it via the `spec.runtime.octelium` field:

```yaml
spec:
  runtime:
    octelium:
      serveServices:
        - storefront-preview
```

Or serve all the *Services* assigned to its owner:

```yaml
spec:
  runtime:
    octelium:
      serveAll: true
```

You can also use the `--serve` and `--serve-all` flags of `cordium create workspace`:

```bash
cordium create ws --template storefront.payments.cordium --serve storefront-preview --start
```

The *Service* is now available to the authorized *Users* at `https://storefront-preview.<DOMAIN>` while it is served from the *Workspace*. If several *Workspaces* (or a *Workspace* and your laptop) serve the same *Service* at the same time, Octelium load-balances the requests among them. Read a complete example [here](https://octelium.com/docs/cordium/latest/examples/security/serving.md).

## Policies for Workspaces

The *Session* of every *Workspace* run carries the *Workspace*'s identity in its `status.ext.cordium` field, which is available to Octelium *Policies* and dynamic configuration rules via `ctx.session.status.ext.cordium`:

| Field               | Description                                                                         |
| ------------------- | ----------------------------------------------------------------------------------- |
| `workspaceRef.name` | The *Workspace*'s name (e.g. `x7k2`).                                               |
| `spaceRef.name`     | The full name of the *Workspace*'s *Space* (e.g. `payments.cordium`).               |
| `templateRef.name`  | The full name of the *Workspace*'s *Template* (e.g. `migrations.payments.cordium`). |
| `spaceType`         | `USER` or `ORGANIZATION`.                                                           |

Sessions of the same *User* that do not belong to a *Workspace* (e.g. the *User*'s laptop or browser) do not have the `cordium` entry, which lets you grant or deny access to *Workspaces* specifically. Since the entry is absent from those *Sessions*, conditions should check for its presence first. Here is an example of a *Service* that can only be accessed by the `dba` *Group* from *Workspaces* created from the `migrations` *Template* of the `payments` *Space*:

```yaml
kind: Service
metadata:
  name: pg-production.payments
spec:
  mode: POSTGRES
  port: 5432
  config:
    upstream:
      url: postgres://pg-production.internal.example.com
    postgres:
      user: migrator
      database: payments
      auth:
        password:
          fromSecret: pg-production-migrator-password
  authorization:
    inlinePolicies:
      - spec:
          rules:
            - effect: ALLOW
              condition:
                match: >-
                  "dba" in ctx.user.spec.groups &&
                  "ext" in ctx.session.status &&
                  "cordium" in ctx.session.status.ext &&
                  ctx.session.status.ext.cordium.templateRef.name == "migrations.payments.cordium"
```

And here is an example of a *Policy* that denies all *Workspace* *Sessions* access to the `production` *Namespace*, while keeping the access of the same *Users* from their own machines untouched. It can be attached to *Users*, *Groups* or the `production` *Namespace* itself (read more about *Policies* [here](https://octelium.com/docs/octelium/latest/management/core/policy.md)):

```yaml
kind: Policy
metadata:
  name: deny-workspaces-in-production
spec:
  rules:
    - effect: DENY
      condition:
        match: >-
          "ext" in ctx.session.status &&
          "cordium" in ctx.session.status.ext &&
          ctx.namespace.metadata.name == "production"
```

## Dynamic Credential Mapping

Since the *Workspace*'s identity is part of the request context, a single Octelium *Service* can map different *Workspaces* to different upstream credentials via its dynamic configuration (read more [here](https://octelium.com/docs/octelium/latest/management/core/service/dynamic-config.md)). Here is an example where *Workspaces* of the `agents` *Template* use a read-only database user, while everyone else uses the default read-write user:

```yaml
kind: Service
metadata:
  name: pg-staging.payments
spec:
  mode: POSTGRES
  port: 5432
  config:
    upstream:
      url: postgres://pg-staging.internal.example.com
    postgres:
      user: app_readwrite
      database: payments
      auth:
        password:
          fromSecret: pg-staging-readwrite-password
  dynamicConfig:
    configs:
      - name: agents
        upstream:
          url: postgres://pg-staging.internal.example.com
        postgres:
          user: app_readonly
          database: payments
          auth:
            password:
              fromSecret: pg-staging-readonly-password
    rules:
      - condition:
          match: >-
            "ext" in ctx.session.status &&
            "cordium" in ctx.session.status.ext &&
            ctx.session.status.ext.cordium.templateRef.name == "claude-agent.payments.cordium"
        configName: agents
```

From the *Workspace*'s perspective, nothing changes: `psql -h pg-staging.payments` works in both cases, while Octelium transparently chooses the database user based on the *Workspace*'s identity on a per-request basis.
