intersystems-community / intersystems-community/iris-agentic-dev

Trust the OS cert store for Atelier TLS, not just bundled roots

Open
#127 1 comment 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
Rust
Stars
40
Forks
14
Avg merge
1d 23h
Merged PRs (30d)
7

Description

Summary

Connecting to an IRIS instance whose web gateway serves HTTPS with a
locally-issued certificate (e.g. mkcert, which automatically adds to
the system trust) fails at the TLS handshake, with no config path in
.iris-agentic-dev.toml to accept it. The only workaround is to publish
a second, plain-HTTP port on the gateway and connect over that
instead - undesirable for a stack that's otherwise HTTPS-only by
design.

What I found

reqwest is pinned to the rustls-tls feature (bundled webpki roots,
never reads the OS trust store):

# Cargo.toml
reqwest = { version = "0.12", features = ["json", "rustls-tls", "cookies"], default-features = false }

tokio-tungstenite, a few lines away in the same crate, already asks
for native roots instead:

tokio-tungstenite = { version = "0.26", features = ["rustls-tls-native-roots", "connect", "handshake"], default-features = false }

So the websocket client already trusts a locally-issued cert; the
Atelier REST client (reqwest) doesn't. IRIS_INSECURE / IRIS_TLS_VERIFY
env vars exist as a workaround, disabling cert validation entirely
(connection.rs, danger_accept_invalid_certs):

pub fn http_client() -> anyhow::Result<reqwest::Client> {
    let insecure = std::env::var("IRIS_INSECURE")
        .ok()
        .map(|v| v == "true" || v == "1")
        .unwrap_or_else(|| {
            std::env::var("IRIS_TLS_VERIFY")
                .map(|v| v == "false" || v == "0")
                .unwrap_or(false)
        });
    ...
}

But neither variable appears in docs/connecting.md's "Environment
variables" table, and WorkspaceConfig (workspace_config.rs) has no
corresponding field, so there's no way to set this per-project in
.iris-agentic-dev.toml either - only via process environment, which
doesn't compose with a project-local config file the way every other
connection setting does.

Suggested fix

  • Give reqwest the same rustls-tls-native-roots feature
    tokio-tungstenite already uses, so it trusts the OS certificate
    store the way curl and browsers do. mkcert's CA (and any other
    locally-trusted CA) is already in that store - no user action
    needed, and no need to disable validation at all.
  • IRIS_INSECURE/IRIS_TLS_VERIFY are a blunter existing workaround -
    document them in docs/connecting.md's env var table regardless, but
    the native-roots fix above should make them unnecessary for this
    case.

Repro

Verified this against a real Web Gateway container - no IRIS instance
behind it is even needed, since the failure happens before any HTTP
request completes. A bare container = "..." config wouldn't have
discovered this setup either: the IRIS container itself publishes no
web port of its own, only the gateway does, so docker-based
auto-discovery has nothing to find.

  1. Issue a cert with mkcert, which installs its root CA into the OS
    trust store automatically:

    mkcert -cert-file cert/webcert.pem -key-file cert/webkey.pem localhost 127.0.0.1
    
  2. Run a Web Gateway container terminating TLS with it:

    docker run -d -p 9443:443 \
      -v ./cert/webcert.pem:/irissys/tls/webgateway/tls.crt \
      -v ./cert/webkey.pem:/irissys/tls/webgateway/tls.key \
      containers.intersystems.com/intersystems/webgateway:2025.2 --ssl
    
  3. .iris-agentic-dev.toml:

    host = "localhost"
    web_port = 9443
    scheme = "https"
    
  4. curl -v https://localhost:9443/api/atelier/ completes the TLS
    handshake cleanly (cert verified, TLS 1.3) and only times out
    afterward, waiting on a response nothing is configured to send.
    iris-agentic-dev tool iris_info --args '{"what":"namespace"}'
    fails immediately instead (~35ms):

    error: HTTP error: error sending request for url (https://localhost:9443/api/atelier/v1/USER)
    

    Setting IRIS_INSECURE=true changes that failure into the same
    hang curl gets - confirming the difference is exactly the TLS
    trust step, not anything HTTP-level. (check_config won't show
    this either way - it makes no network calls, so it reports
    whatever config resolved without ever testing reachability.)

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with the reqwest feature declaration in Cargo.toml and the http_client implementation in connection.rs, then inspect WorkspaceConfig in workspace_config.rs and the environment-variable table in docs/connecting.md. Confirm that the REST client trusts OS roots like the websocket client, while the existing IRIS_INSECURE and IRIS_TLS_VERIFY workarounds are documented; verify the change against the mkcert HTTPS reproduction described here.

Written by the indexing model from the issue text.

Assessment

Tech stack
rust
Domain
backend, security
Issue type
Feature
Difficulty
3/5
Estimated time
1-2 days
Activity status
Active
Clarity
Clearly specified
Newbie friendliness
76/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.