Authentication

JuliaHub uses Dex to authenticate users. Dex sits between JuliaHub and your identity provider, and is configured with one or more connectors — one per identity provider you want users to log in with.

There are two places a connector can be configured, and which one you want depends on whether you can already log in:

  • The admin console, after the platform is running. Convenient for adding or adjusting a provider on a working installation.
  • At install time, through Replicated or Helm values. Necessary when the identity provider is the only intended way in, since there is no administrator session to configure it from yet.

Configuring a connector in the admin console

Sign in as an administrator and go to Administration ➤ Server Configuration ➤ Authentication Configuration. The connector configuration is entered as YAML, in Dex's own schema.

JuliaHub identifies users by email address, so whichever provider you configure must supply one.

The sections below give a working example per provider. See the Dex connector reference for the full set of options each one supports.

LDAP and Active Directory

User entries are expected to have an email attribute (emailAttr) and a display name attribute (nameAttr). Dex first binds using bindDN and bindPW, searches for the given username, then binds as that user to verify their password. TLS is strongly recommended.

connectors:
  - type: ldap
    id: corp
    name: Corporate LDAP
    config:
      host: ldap.example.com:636
      bindDN: cn=service-account,ou=people,dc=example,dc=com
      bindPW: $LDAP_BIND_PW
      userSearch:
        baseDN: ou=people,dc=example,dc=com
        filter: "(objectClass=person)"
        username: uid
        idAttr: uid
        emailAttr: mail
        nameAttr: cn
      groupSearch:
        baseDN: ou=groups,dc=example,dc=com
        userMatchers:
          - userAttr: uid
            groupAttr: member
        nameAttr: cn

See Supplying credentials as secrets for the $LDAP_BIND_PW reference.

OpenID Connect (OIDC)

connectors:
  - type: oidc
    id: okta
    name: Okta
    config:
      issuer: https://example.okta.com
      clientID: <client-id>
      clientSecret: $OIDC_CLIENT_SECRET
      redirectURI: https://<hostname>/dex/callback

To map identity provider groups onto JuliaHub group membership, set insecureEnableGroups: true. As the Dex documentation explains, this is flagged as insecure because group membership can only be refreshed when the ID token is refreshed, so revoking a group does not take effect immediately.

SAML

connectors:
  - type: saml
    id: oktasaml
    name: Okta SAML
    config:
      ssoURL: <sso-url>
      ssoIssuer: <sso-issuer>
      redirectURI: https://<hostname>/dex/callback
      caData: <base64-encoded PEM certificate>
      usernameAttr: email
      emailAttr: email
      groupsAttr: groups

caData takes the identity provider's signing certificate inline, base64 encoded. Dex also accepts ca with a path to a certificate file, which requires that file to be present in the Dex container.

On the identity provider side, the endpoint you configure must match redirectURI, and the attributes it puts in the SAML claim must match usernameAttr, emailAttr, and groupsAttr.

GitHub and GitLab

connectors:
  - type: github
    id: github
    name: GitHub
    config:
      clientID: <client-id>
      clientSecret: $GITHUB_CLIENT_SECRET
      redirectURI: https://<hostname>/dex/callback
      orgs:
        - name: my-organization

GitLab is configured the same way with type: gitlab, plus a baseURL for self-hosted instances.

Authenticating proxy (header-based SSO)

Use this when a reverse proxy — typically a corporate web single sign-on (WSSO) gateway — sits in front of JuliaHub, authenticates the user itself, and forwards the resulting identity in request headers. Dex trusts those headers instead of running a login flow of its own.

The authproxy connector trusts its headers unconditionally

Only use this connector when the proxy is the only network path to JuliaHub. Any client that can reach JuliaHub directly can set these headers and impersonate any user.

JuliaHub does not strip these headers from incoming requests, and it does not restrict who can reach it: the default network policy permits all sources to reach the web server, and permits any pod in the same namespace to reach any JuliaHub service. Enforcing the "only network path" property is therefore your responsibility. Before enabling this connector:

  • Restrict inbound access to JuliaHub to the proxy, at the load balancer or security group.
  • Set networkPolicy.allowedCIDRs in your Helm values to the proxy's addresses, so the platform's own network policy admits only those sources. Under Replicated, use Allowed Inbound CIDRs in the Network Policy section. Note that this only takes effect if your cluster's network plugin enforces NetworkPolicy — see below.
  • Configure the proxy to strip these headers from incoming client requests before setting its own, so a client cannot supply them itself.

NetworkPolicy is only enforced if your cluster's network plugin implements it. Embedded Cluster installations use Calico, which does. Older kURL-based installations use Flannel, which ignores NetworkPolicy — the resources are created but nothing acts on them, so restrict access at the load balancer or security group instead. On an existing cluster, check with your cluster administrator which plugin is in use.

None of this constrains traffic originating inside the cluster. On an installation with compute enabled, user-supplied jobs run without network restrictions and can reach the web server directly, so anyone able to submit a job can set these headers and authenticate as any user. Do not enable this connector on a compute-enabled installation without first discussing it with JuliaHub support.

connectors:
  - type: authproxy
    id: authproxy
    name: Corporate SSO
    config:
      userIDHeader: X-Remote-User-Id
      userHeader: X-Remote-User
      emailHeader: X-Remote-User-Email
      groupHeader: X-Remote-Group
HeaderPurpose
userIDHeaderUnique, stable identifier for the user. This keys the JuliaHub account, so it must not change for a given person.
userHeaderThe user's login name.
emailHeaderThe user's email address. JuliaHub identifies users by email, so the proxy should always set this.
groupHeaderComma-separated group names. Omit if the proxy does not provide groups.

Each header falls back to the X-Remote-* name shown above when left unset.

Configuring a connector at install time

A connector configured in the admin console requires an administrator to log in first. When the identity provider you are configuring is the intended way in, configure it during installation instead.

Replicated

The Authentication section of the configuration screen offers an Identity Provider Connector setting:

  • Configure later in the admin console — the default. Nothing is applied, and connectors remain configurable from the admin console.
  • Authenticating proxy (header-based SSO) — builds the authproxy connector from the header names entered alongside it.
  • Custom YAML — applies Dex connector configuration you paste in, in the same format as the examples above.

Helm

Set dexsrvr.additionalConfig to the Dex configuration you want merged in:

dexsrvr:
  additionalConfig:
    connectors:
      - type: authproxy
        id: authproxy
        name: Corporate SSO
        config:
          userIDHeader: X-Remote-User-Id
          emailHeader: X-Remote-User-Email

If the connector configuration is managed outside the chart — by an external secret operator, for instance — set dexsrvr.additionalConfigSecret to the name of an existing Kubernetes Secret instead. Each of its keys is read as a JSON or YAML document, according to the key's file extension.

additionalConfig must be a mapping of top-level Dex configuration keys, not a bare list of connectors. Each key given replaces that whole key in the generated configuration, so a value containing storage: or issuer: overrides the platform's own settings rather than adding to them.

Configuration declared at install time replaces the corresponding setting from the admin console and is reapplied on every deploy. Leave these values unset to manage connectors from the admin console instead.

Supplying credentials as secrets

Most connectors need a credential — an LDAP bind password, an OIDC client secret. Writing one directly into the connector configuration puts it in plain text in your Helm values, in the Replicated configuration screen, and in helm get values output.

Instead, put the credential in a Kubernetes Secret and reference it from the connector configuration as an environment variable. Dex expands $VAR and ${VAR} references in connector configuration, so any key of an injected Secret can be used directly.

This is also the only way to use a credential containing a $. Because Dex expands variables throughout each connector's config: block, a literal $ written inline is read as a variable reference and silently replaced with nothing — and $$ does not escape it. A password such as S3cret$word cannot be given inline at all, and must come from a Secret.

Create the Secret in the JuliaHub namespace:

kubectl create secret generic dex-connector-credentials \
  --namespace <namespace> \
  --from-literal=LDAP_BIND_PW='<password>'

Under Helm, name the Secret in dexsrvr.additionalConfigEnvSecrets and reference its keys from the connector configuration:

dexsrvr:
  additionalConfigEnvSecrets:
    - dex-connector-credentials
  additionalConfig:
    connectors:
      - type: ldap
        id: corp
        name: Corporate LDAP
        config:
          host: ldap.example.com:636
          bindDN: cn=service-account,ou=people,dc=example,dc=com
          bindPW: $LDAP_BIND_PW

Under Replicated, set Connector Credentials Secret in the Authentication section to the Secret name, and reference its keys the same way.

Every key in each named Secret becomes an environment variable in the Dex container. The Secrets are treated as optional, so one that does not exist yet will not block startup, and the Dex pod restarts automatically when any of them change.

Administrator access

At least one user matching an entry in the Administrators field must be able to log in through a configured connector, otherwise the configuration is rejected when saved from the admin console.

For LDAP, JuliaHub verifies this by searching the directory. For other connector types it cannot enumerate users ahead of time, so the configuration is accepted with a warning and you should confirm you can still sign in before ending your current session.