C3 AI Documentation Home

Configure OpenID Connect in an Airgapped Cluster

In a standard deployment, OidcIdpConfig#importFromDiscoveryUrl reaches out to the identity provider (IdP) to read the OIDC discovery document and download the token signing certificates from the keys URL. In an airgapped cluster, network egress to the IdP is blocked, so both the discovery URL and the keys URL are unreachable and importFromDiscoveryUrl fails.

In an airgapped cluster you must configure OidcIdpConfig manually: supply every endpoint the discovery document would have provided, set autoImportCertificates to false, and set the IdP signing certificates directly. Because the certificates are pinned in the config rather than fetched at authentication time, you must repeat the certificate step whenever the IdP rotates its signing keys.

Obtain the OIDC discovery document and the JSON Web Key Set (JWKS) from the IdP through an out-of-band channel, for example from a machine that has access to the IdP. The discovery document is served at the well-known configuration endpoint (https://login.microsoftonline.com/<tenant_id>/v2.0/.well-known/openid-configuration for Microsoft Entra ID) and the JWKS is served at the jwks_uri listed in that document.

Configuring a brand new IdP integration

Run the following from the C3 AI Console of the IdP configured application. For a cluster-wide integration the IdP configured application is c3/c3 unless configured otherwise; for an app-scoped integration it is the app you are configuring. Replace the placeholder values with the endpoints from the IdP discovery document and the keys from the JWKS.

The JWKS typically contains multiple signing keys — often more than five — so add one OidcIdpServerCertificate per key in the JWKS rather than only the first.

JavaScript
var hostname = "<your host name>";
var uri = "https://" + hostname + "/c3/c3/oidc/login";

var client = OidcIdpClient.make({clientId: "<client_id>", redirectUri: uri});

var certificates = Array.of(
  OidcIdpServerCertificate.make({
    kid: "<key id from JWKS>",
    certificate: "<x5c certificate from JWKS>",
    formatKind: OidcCertKind.PEM,
    keyType: "RSA"
  })
);

var config = OidcIdpConfig.make({
  id: hostname,
  client: client,
  issuer: "<issuer from discovery document>",
  authorizationEndPoint: "<authorization_endpoint from discovery document>",
  tokenEndPoint: "<token_endpoint from discovery document>",
  userInfoEndPoint: "<userinfo_endpoint from discovery document>",
  keysUrl: "<jwks_uri from discovery document>",
  autoImportCertificates: false,
  certificates: certificates,
  scopes: Array.of("openid", "email", "profile", "Group.Read.All"),
  flowKind: OidcAuthFlowKind.IMPLICIT,
  defaultResponseMode: "form_post",
  defaultResponseType: "id_token",
  jitUserCreation: true,
  audiences: Array.of("<client_id>")
});
config.setConfig(ConfigOverride.CLUSTER);

OidcIdpConfig.forId(hostname).setConfigValue("trustedApplicationHosts", Array.ofStr(hostname), ConfigOverride.CLUSTER);

The keysUrl is recorded so certificates can be refreshed later if egress is opened, but with autoImportCertificates set to false the platform never contacts it during authentication and instead validates tokens against the pinned certificates.

In an airgapped cluster use the Implicit flow, with flowKind left at its default IMPLICIT, defaultResponseType set to id_token, and defaultResponseMode set to form_post (as shown above). The Authorization Code flow requires the platform to reach the IdP's token endpoint to exchange the code, and — if fetchUserInfo is enabled — the userinfo endpoint as well. Both calls go out to the IdP at authentication time, so in an airgapped cluster they fail and the login errors out. The Implicit flow returns the id_token directly to the browser, which the platform validates locally against the pinned certificates, requiring no outbound call to the IdP. For the same reason, leave fetchUserInfo disabled and rely on the claims present in the id_token.

Refresh certificates after keys have been rotated

When the IdP rotates its token signing keys, the certificates pinned in the config become stale and authentication fails with an invalid token or a null certificate error. Because the airgapped cluster cannot download the new keys automatically, update the certificates config value with the new keys from the JWKS, then clear the config cache on every app associated with the config so all nodes pick up the new certificates.

  1. Set the new certificates from the rotated JWKS. Use the same ConfigOverride level you used when creating the config (CLUSTER in this guide); a lower-override write wins the merge but only applies to the app it is set on, so other apps keep the stale certificates:

    JavaScript
    var certificates = Array.of(
      OidcIdpServerCertificate.make({
        kid: "<new key id from JWKS>",
        certificate: "<new x5c certificate from JWKS>",
        formatKind: OidcCertKind.PEM,
        keyType: "RSA"
      })
    );
    OidcIdpConfig.forId("<your host name>").setConfigValue("certificates", certificates, ConfigOverride.CLUSTER);
  2. Clear the cached config on all nodes so every node reloads the new certificates:

    JavaScript
    C3.app().nodes().each(n => n.callJson("OidcIdpConfig", "clearCacheLocalOnlyAllApps"));

    If you cannot reach an app directly, run the clear from a higher-level app's C3 AI Console. For example, to clear the config for cluster-env-app, run the following from /c3/c3:

    JavaScript
    App.forId("cluster-env-app").callJson("OidcIdpConfig", "clearCacheLocalOnlyAllApps", null, null);
  3. Validate the redirect flow in a private browsing session. If authentication still fails, confirm the kid of the pinned certificate matches the kid in the token header, and confirm the certificate value matches the current JWKS.

Alternative: open the Kubernetes cluster to the IdP for the Authorization Code flow

If the Kubernetes cluster administrators can open a controlled network path from the cluster to the IdP — for example by allowlisting the IdP's static IP addresses or opening the IdP endpoints through the firewall — the cluster can reach the IdP's token and userinfo endpoints at authentication time. Once that path is open, you can use the Authorization Code flow instead of the Implicit flow. Set the flow kind to AUTHORIZATION_CODE, set defaultResponseType to code (the Implicit flow uses id_token), and provide the client secret. Leave defaultResponseMode as form_post, which the IdP uses to POST the authorization code back for both flows:

JavaScript
OidcIdpConfig.forId(hostname).setConfigValue("flowKind", OidcAuthFlowKind.AUTHORIZATION_CODE, ConfigOverride.CLUSTER);
OidcIdpConfig.forId(hostname).setConfigValue("defaultResponseType", "code", ConfigOverride.CLUSTER);
OidcIdpConfig.forId(hostname).setSecretValue("clientSecret", "<client secret>", ConfigOverride.CLUSTER);
OidcIdpConfig.forId(hostname).setConfigValue("fetchUserInfo", true, ConfigOverride.CLUSTER);

With the network path open you may also enable autoImportCertificates so the platform refreshes signing certificates from the keysUrl automatically, removing the need for the manual certificate rotation steps above.

See also

Was this page helpful?