Table of Contents

Gateway discovery

The OpenShell CLI keeps a registration for every gateway it knows: the endpoint, the authentication mode, and the credentials. The OpenShell.Gateway namespace reads the same files with the same rules as the CLI, so an application can connect to whatever gateway the user selected, without its own configuration.

Connect

GatewayClient.Connect loads a registration, builds the authentication and TLS settings from it, and returns an OpenShellClient. Pass no name for the active gateway.

// The gateway the OpenShell CLI made active, with its stored credentials.
using var client = GatewayClient.Connect();

// Or a registered gateway by name.
using var staging = GatewayClient.Connect("staging");

var info = await client.Health.GetGatewayInfoAsync();
Console.WriteLine(info);

GatewayConnectOptions adjusts the result: the default workspace and timeout, your own Auth or Tls in place of the registered ones, whether expired OIDC tokens are renewed (AutoRefresh) and written back (WriteBack), and how a Cloudflare Access gateway is reached (EdgeTunnel).

using var client = GatewayClient.Connect("prod", new GatewayConnectOptions
{
    DefaultWorkspace = "team-a",
    DefaultTimeout = TimeSpan.FromSeconds(30),
    WriteBack = false,   // keep renewed tokens in memory; leave oidc_token.json untouched
});

By authentication mode (auth_mode in metadata.json), the client gets:

auth_mode GatewayAuthMode Authentication
missing or none None No credentials.
plaintext Plaintext No credentials, plaintext HTTP.
mtls Mtls The client certificate in mtls/tls.crt and mtls/tls.key; fails when they are missing.
oidc Oidc The access token in oidc_token.json, renewed with its refresh token and written back.
cloudflare_jwt CloudflareJwt The Access token in edge_token (or the legacy cf_token), through a local edge tunnel.

For an https endpoint, the mtls directory also supplies the trust: the CA in mtls/ca.crt when present, otherwise the system roots, and the client certificate when present.

Where registrations live

Location Default Holds
User configuration directory $XDG_CONFIG_HOME/openshell; otherwise %APPDATA%\openshell on Windows and ~/.config/openshell elsewhere active_gateway, and gateways/<name>/ with metadata.json and the credentials
System configuration directory $OPENSHELL_SYSTEM_GATEWAY_DIR when it is an absolute path; otherwise /etc/openshell on Linux and macOS, and none on Windows active_gateway, and gateways/<name>/metadata.json

The rules, as in the CLI:

  • Active gateway. $OPENSHELL_GATEWAY, then the user active_gateway file, then the system active_gateway file. A missing, empty, or invalid file falls through to the next source.
  • Registrations. A user entry shadows a system registration of the same name only when it has its own metadata.json. Only active_gateway and metadata.json fall back to the system directory.
  • Credentials. The mtls bundle, oidc_token.json, and edge_token are always read from and written to the user gateway directory, GatewayMetadata.StateDirectory, also for a system registration. GatewayMetadata.Directory is where metadata.json was found.
  • Names. ASCII letters, digits, -, _, and ., so host-derived names such as gw.example.com and 192.0.2.1 work. . and .., a leading or trailing dot, and Windows device names such as CON or COM1.example.com are rejected on every platform.

Inspect registrations

GatewayLocator exposes the same lookups:

var locator = new GatewayLocator();
Console.WriteLine($"user config:   {locator.UserConfigDirectory}");
Console.WriteLine($"system config: {locator.SystemConfigDirectory ?? "(none)"}");

foreach (var entry in locator.List())
{
    Console.WriteLine($"{(entry.Active ? "*" : " ")} {entry.Name} ({entry.Source})");
}

var gateway = locator.Load(locator.ResolveActiveName());
Console.WriteLine($"{gateway.Name}: {gateway.Endpoint}, auth {gateway.AuthMode}");
Console.WriteLine($"registration in {gateway.Directory}, credentials in {gateway.StateDirectory}");

List() sorts by name and skips registrations whose metadata cannot be read; Load(name) throws OpenShellException with NotFound or InvalidConfig when it cannot load one.

The token file

oidc_token.json uses the CLI format, so one openshell gateway login serves the CLI and the SDK:

{
  "access_token": "<access token>",
  "refresh_token": "<refresh token>",
  "expires_at": 1793000000,
  "issuer": "https://idp.example.com/realms/openshell",
  "client_id": "openshell-cli"
}

expires_at is in Unix seconds. A token issued for another issuer or client id than the registration names is never sent. To sign in from .NET, see Signing in to a registered gateway.

Build the options yourself

GatewayClient.CreateOptions returns the OpenShellClientOptions that Connect would use, for settings GatewayConnectOptions does not cover. It does not start an edge tunnel.

// Build the options yourself to set what GatewayConnectOptions does not expose.
// CreateOptions does not start an edge tunnel: use Connect for cloudflare_jwt gateways.
var gateway = new GatewayLocator().Load("prod");
var options = GatewayClient.CreateOptions(gateway) with
{
    Retry = RetryPolicy.Default with { MaxAttempts = 6 },
};

using var client = new OpenShellClient(options);

Other directories

GatewayLocatorOptions points the locator at other directories, and replaces the environment lookup, for example in tests:

// Read registrations from other directories, for example in tests.
var locator = new GatewayLocator(new GatewayLocatorOptions
{
    UserConfigDirectory = Path.Combine(root, "user"),
    SystemConfigDirectory = Path.Combine(root, "system"),
    GetEnvironmentVariable = _ => null,   // ignore OPENSHELL_GATEWAY and the other variables
});

using var client = GatewayClient.Connect("local", new GatewayConnectOptions { Locator = locator });