Identity Providers

An IdentityProvider represents an identity provider (IdP) that is used by the Cluster to authenticate and re-authenticate the Users in order for them to obtain and keep a valid Session and consequently be able to interact with the Cluster and access its Services (read more about Sessions here). As will be discussed in detail below, an IdentityProvider has one of the following types: github, oidc, saml and oidcIdentityToken.

Listing IdentityProviders

You can list the Cluster's IdentityProviders (read more about listing resources here) as follows:

octeliumctl get identityprovider # Or simply octeliumctl get idp # Show a certain IdentityProvider octeliumctl get idp <NAME>

You can also delete a specific IdentityProvider by its name via octelium delete idp command as follows:

octeliumctl delete idp <NAME> # Or simply octeliumctl delete identityprovider <NAME>

Web Identity Providers

Whenever you create an OAuth2/OIDC/SAML 2.0 application from within your identity provider's dashboard, you need to set the redirection URL to https://<DOMAIN>/callback in your application settings in order for the identity provider to know where to redirect the authentication flow back to Portal.

note

Read the detailed guides on how to self-host FOSS identity providers (IdPs) on the same Kubernetes cluster that is running the Octelium Cluster for Keycloak, Authentik and Dex and use them to create an OpenID Connect IdentityProvider that can be used to login into the Cluster.

Login Page Example

GitHub OAuth2

The GitHub IdentityProvider can be defined as in the following example:

kind: IdentityProvider metadata: name: github displayName: GitHub spec: github: clientID: abcdef... clientSecret: fromSecret: github-client-secret
note

As you can see, the client secret is referenced by a Secret resource via the field fromSecret in very similar way to how secrets are referenced in Kubernetes for example. That means you have to first create a Secret via the command octeliumctl create secret and then reference it by its name in your IdentityProvider. You can read more about Secrets here.

To get the IdentityProvider client ID and secret, you will have to create an OAuth application as follows:

  1. Go to Developer Settings here.

  2. Add a new OAuth app and fill the following required fields as follows:

  • Set the Homepage URL field to https://<DOMAIN>

  • Authorization callback URL field to https://<DOMAIN>/callback

Now you obtain the OAuth client credentials and you set the client secret as an Octelium Secret as follows:

octeliumctl create secret github-client-secret

And now you can actually create your IdentityProvider as follows:

kind: IdentityProvider metadata: name: github spec: # Optional: this is the text shown on the Login Button displayName: Login with GitHub github: # this is your GitHub OAuth app client ID clientID: abcdef.... clientSecret: # This is the Secret name of the OAuth app client secret fromSecret: github-client-secret

And apply the creation with an octeliumctl apply command (read more here).

note

Note that while Octelium requests the email and user OAuth scopes from GitHub, GitHub might not always reveal the user email if the user keeps their primary email as private in their GitHub user settings. Therefore, using email as an identifier for GitHub depends on whether the user keeps their primary email private regardless of the granted OAuth scopes. You can use, however, the GitHub usernames as an identifier of a certain IdentityProvider for such users in the User's list of identities (read more here). Here is an example:

kind: User metadata: name: linus spec: type: HUMAN authentication: identities: # "github" is the name of the IdentityProvider - identityProvider: github # "linus" here is the GitHub account username identifier: linus

OpenID Connect

OpenID Connect is an identity layer built on top of the OAuth 2.0 protocol and is currently usually preferred over SAML in most cases. Octelium can use an OIDC client to authenticate the User from an OIDC IdentityProvider. Here is an example:

kind: IdentityProvider metadata: name: okta-oidc-01 displayName: Okta spec: oidc: # REQUIRED. Your issuer URL is where the OIDC configuration can be # obtained automatically by adding the suffix "/.well-known/openid-configuration" # to the value of "issuerURL" issuerURL: https://dev-<ID>.okta.com # REQUIRED clientID: <CLIENT_ID> # REQUIRED clientSecret: fromSecret: <SECRET_NAME> # Optional, by default the claim `email` is used as the identifier. identifierClaim: custom-identifier # Optional but RECOMMENDED, this checks whether the claim `email_verified` claim value is set to true. checkEmailVerified: true # Optional, this obtains the claims via the UserInfo endpoint instead of the ID token. useUserInfoEndpoint: true # Optional, by default the scopes `profile` and `email` are added in addition to the mandatory scope `openid`. scopes: ["scope1", "scope2"]
note

As mentioned in the example above, the value of the issuerURL is when appended by /.well-known/openid-configuration shows you the OIDC discovery endpoint, or simply the OIDC provider configuration document. In some OIDC providers such as Auth0, such value needs to be appended with a final trailing slash since the issuer field in the endpoint document must match the issuerURL.

Gitlab Example

You can use Gitlab cloud as an OpenID Connect IdentityProvider as follows:

  1. Go to Applications in Settings here to add a new application.

  2. Set the Redirect URI field to https://<DOMAIN>/callback.

  3. Enable openid, email and profile in the scopes.

  4. Now you create an Octelium Secret from the obtained Gitlab app secret as follows:

octeliumctl create secret gitlab-oidc

And then set the value when asked.

  1. Now you can actually create the Gitlab IdentityProvider as follows:

kind: IdentityProvider metadata: name: gitlab spec: displayName: Login with Gitlab oidc: issuerURL: https://gitlab.com # Your clientID is your Gitlab's Application ID clientID: d46f... clientSecret: fromSecret: gitlab-oidc

And apply the creation with an octeliumctl apply command (read more here).

note

For more information regarding using Gitlab as an OpenID Connect identity provider, you can read Gitlab's own docs here

SAML 2.0

You can define a SAML 2.0 IdentityProvider as in the following example:

kind: IdentityProvider metadata: name: okta-saml-01 displayName: Okta SAML spec: saml: # Required metadataURL: https://dev-12345678.okta.com/app/123456789abcde/sso/saml/metadata # Optional, the entity ID by default is set to https://<DOMAIN> # i.e. if the Cluster domain is example.com then the entity ID is https://example.com entityID: urn:<DOMAIN> # Optional. By default, the identifier attribute is assumed to be # `http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress`. identifierAttribute: custom-email-attr # Optional, this forces a re-authentication even if the user has a SSO session at the IdP forceAuthn: true

You can also use the metadata content directly instead of using a metadata URL as follow:

kind: IdentityProvider metadata: name: okta-saml-01 displayName: Okta SAML spec: saml: # Required metadata: | <?xml version="1.0" encoding="UTF-8"?><md:EntityDescriptor xmlns:md="urn:oasis:names:tc:SAML:2.0:metadata" entityID="https://accounts.google.com/o/saml2?idpid=ABCDEFG" validUntil="2030-01-01T13:48:23.000Z"> ### The rest of the metadata XML content identifierAttribute: email forceAuthn: true

By default, the SAML 2.0 provider always checks for the user's identifier, typical the email, using the identifierAttribute which is set by default to http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress. However, you can always override that value by setting identifierAttribute.

User Email as an Identifier

By default, an authentication by a web IdP IdentityProvider (ie. GitHub OAuth2, OpenID Connect or SAML 2.0 IdentityProvider) at first tries to find the User via an explicitly set identity (read more here) with an identityProvider field that matches the IdP IdentityProvider's name and identifier that matches the identifier in the assertion returned by the provider.

If no explicit identity matches or if the list of identities is simply empty, then the email field of the User (read more here) is then inspected against the identifier in the assertion returned to the Cluster by the identity provider. In other words, the email field acts as a default identifier that can be used automatically for any IdP IdentityProvider to find a User. However, you can disable this default behavior in a certain IdentityProvider, for example, when you do not trust that the email has been verified by that identity provider, as follows:

kind: IdentityProvider metadata: name: github displayName: GitHub spec: disableEmailAsIdentity: true github: clientID: <CLIENT_ID> clientSecret: fromSecret: <SECRET_NAME>

Public Display Name

You can set the public display name that shows up on the button of the corresponding IdentityProvider when Users visit the login page. If not set, the resource metadata's display name will be used as a fallback. Here is an example:

```yaml kind: IdentityProvider metadata: name: okta-oidc displayName: Okta OIDC spec: displayName: Login with Okta oidc: # The rest of your configuration

Workload Identity Providers

OIDC Assertion

Octelium currently also supports authentication for WORKLOAD Users using OpenID Connect (OIDC) assertions where workloads can authenticate themselves to the Cluster using OIDC ID tokens issued by the identity provider hosting the workload instead of typically having to use authentication token Credentials. The main disadvantage of using authentication tokens is that they need to be issued by the Cluster and have to be stored securely at the User's side. This can be cumbersome at scale and may lead to a breach if a Credential leaks. This "secretless" assertion-based authentication method is increasingly getting more popular recently for workloads running on major cloud providers such as Azure, CI/CD providers such as GitHub Actions and CircleCI and it can also work for Kubernetes clusters. There are 3 modes to define the OIDC assertion IdentityProvider:

Public OIDC Issuer

This mode is preferable when the OIDC issuer publicly publishes its endpoint. Many cloud providers, CI/CD providers and managed Kubernetes clusters already offer their OIDC endpoint publicly.

kind: IdentityProvider metadata: name: github-actions-01 spec: oidcIdentityToken: # this is the URL that will be appended with the path "/.well-known/openid-configuration" # to get the discovery-document URL of the issuer issuerURL: https://token.actions.githubusercontent.com audience: https://example.com

JWKS URL

In some cases, you might want to set the JWKS url directly if you are not dealing with a fully compliant OIDC provider. In such case, you also have to explicitly set the issuer in order to be verified against the issuer claim in the OIDC ID token assertion.

kind: IdentityProvider metadata: name: github-actions-01 spec: oidcIdentityToken: jwksURL: https://token.actions.githubusercontent.com/.well-known/jwks issuer: https://token.actions.githubusercontent.com audience: https://example.com

JWKS Content

This mode can be useful for cases where the OIDC issuer is not publishing its endpoint publicly (e.g. Kubernetes clusters that do not publish their endpoints publicly). In this case you can just manually set the JWKS content manually. In such case, you also have to explicitly set the issuer in order to be verified against the issuer claim in the OIDC ID token assertion.

kind: IdentityProvider metadata: name: k8s-01 spec: oidcIdentityToken: jwksContent: | { "keys": [ { "use": "sig", "kty": "RSA", "kid": "I4twhfbX-awEXmXezQS_i_e7L-a6EWRp8-fYdAhga6A", "alg": "RS256", "n": "oloso_qOKvQ8Fu9rkUVUHqYJsyQ_2p_G_pAp5....", "e": "AQAB" } ] } issuer: https://kubernetes.default.svc.cluster.local audience: https://kubernetes.default.svc.cluster.local

Authenticator Assurance Level

One of the important goals of the zero trust model is to control access based on the User's identity whose degree of confidence and assurance depends on the strength of authentication methods used by SSO identity providers. For example, an IdP might only require a password, another might require MFA second-factor authentication method such as TOTP, and a third forces the User to authenticate with a hardware-based phishing resistant authenticators such as FIDO-2 security keys (e.g. Yubikey) that is capable of enforcing user consent and verification methods during authentication such as biometric (e.g. fingerprint) or PIN in order for the authenticator to proceed with the authentication process. NIST defines a technical guide of 3 authenticator assurance levels (AAL) of increasing strength AAL1, AAL2 and AAL3 here where level-3 corresponds to the highest level of assurance. Octelium enables you to control such AAL level during authentication depending on the content of the assertion (e.g. ID token in the case of OpenID Connect IdentityProviders) returned by the IdP to the Cluster, and use the resulting AAL in your access control decisions (read more about access control and Policies here). For example, you might want to be sure that all or certain Users can access all Services or certain sensitive Services only if they authenticated via a phishing resistant hardware-based authenticator that satisfies AAL3 requirements. Here is an example:

kind: Policy metadata: name: production-policy spec: rules: - name: production effect: ALLOW condition: all: of: - match: ctx.namespace.metadata.name == "production" - match: "ops" in ctx.user.spec.groups - match: ctx.user.spec.type == "HUMAN" - match: ctx.session.status.authentication.info.aal == "AAL3"

To actually set the AAL value based on the assertion content upon authentication, Octelium enables you to define your own policy-as-code AAL rules, represented by the aalRules list as shown below, where the value chosen corresponds to the first rule whose condition matches. This is similar to dynamic configuration rules (read more here). Octelium converts the assertion into a map in the case of oidc, oidcIdentityToken and github IdentityProviders where you can define your conditions in the assertionMap object. Here is an example:

kind: IdentityProvider metadata: name: oidc-01 displayName: My OIDC IdP spec: oidc: issuerURL: https://oidc.example.com # The rest of your configs aalRules: - aal: AAL3 condition: match: ctx.assertionMap.hasMFA
note

For the case of OpenID Connect, some IdPs use standard claims such as amr and acr to provide AAL information, others do use custom claims.

For the case of saml IdentityProviders, the assertion is available as a string value in the assertion field. Here is an example:

kind: IdentityProvider metadata: name: oidc-01 displayName: My OIDC IdP spec: saml: # The rest of your configs aalRules: - aal: AAL2 condition: match: ctx.assertion.contains("mfa")

Disabling an IdentityProvider

You can disable/deactivate a IdentityProvider and immediately disable the Users ability to see that IdentityProvider in the log in page or log in with it. Here is an example:

kind: IdentityProvider metadata: name: oidc spec: isDisabled: true oidc: # The rest of your configurations

Post-Authentication Rules

By default, a successful authentication/re-authentication by an IdentityProvider automatically creates/extends the validity of the Session. Post-authentication rules, however, provide you a way to control that behavior, enabling you to reject even a successful authentication via policy-as-code rules. This can be useful, for example, to prevent certain Users, or certain groups of Users, from logging in during certain times or under certain contexts, or logging in via certain IdentityProviders, or logging in without using strong, hardware-based MFAs such as security keys. Here is an example that only allows Users whose email domain is example.com who log in with an AAL of AAL3 to be able to use that particular IdentityProvider, otherwise do not accept the log-in:

kind: IdentityProvider metadata: name: oidc-01 displayName: My OIDC IdP spec: oidc: issuerURL: https://oidc.example.com # The rest of your configs postAuthenticationRules: - condition: match: ctx.user.spec.email.endsWith("@example.com") && ctx.authenticationInfo.aal == "AAL3" effect: ALLOW - condition: matchAny: true effect: DENY

Authenticators and MFA

In addition to using the MFA provided by the IdentityProvider and setting the Authenticator Assurance Level and using it in your access control decisions, Octelium also provides its own Authenticators, including FIDO and TOTP, that can be used for MFA and passwordless login via Passkeys. You can read in detail about Authenticators here.

Passkey Login

You can enable Passkey passwordless login as shown in detail here.

Octelium Passkey