Installation guide¶
Gafaelfawr was written to run inside a Kubernetes environment. While there is nothing intrinsic in Gafaelfawr that would prevent it from working in some other environment, only installation on Kubernetes has been documented or tested.
Prerequisites¶
The NGINX ingress controller must already be configured and working. Gafaelfawr expects TLS termination to be done by the ingress controller.
The instructions below assume that you will use Vault to store secrets and Vault Secrets Operator to materialize those secrets as Kubernetes secrets. The Gafaelfawr Helm chart requires this.
Client configuration¶
GitHub¶
If you will be using GitHub as the authentication provider, you will need to create a GitHub OAuth app for Gafaelfawr and obtain a client ID and secret.
To get these values, go to Settings > Developer Settings for either a GitHub user or an organization, go into OAuth Apps, and create a new application.
The callback URL should be the /login route under the hostname you will use for your Gafaelfawr deployment.
CILogon¶
If you will use CILogon as the authentication provider, you will need to register with CILogon to get a client ID and secret.
- Go to the registration page.
- Enter the client name. For Rubin Observatory deployments, include “Rubin Observatory LSP” in the name.
- Enter the contact email. You will be notified at this email when the client is registered.
- Enter the top page of the LSP deployment as the home URL.
For example:
https://lsst-lsp-instance.example.com - Enter the
/loginroute as the callback URL. For example:https://lsst-lsp-instance.example.com/login - Leave the public client box unchecked.
- Select the following scopes: - email - org.cilogin.userinfo - profile You will need some additional custom configuration, but you will have to request that via email since it isn’t available on the registration page.
- Enter one day (86400 seconds) as the refresh token lifetime.
Submit that information. You will get a client ID and secret. This will not work immediately; you will need to wait for the CILogon team to register the client.
After you have gotten email confirming that your client has been registered, reply to that email and request that the client configuration be copied from the client cilogon:/client_id/6ca7b54ac075b65bccb9c885f9ba4a75.
This will add the scope that releases group and UID information from LDAP.
Vault secrets¶
The standard Helm chart for Gafaelfawr (described below) assumes that you will use Vault to store secrets and Vault Secrets Operator to materialize those secrets in Kubernetes. If you are using it, create a Vault secret with the following keys:
cilogon-client-secret- The CILogon secret, obtained during client registration as described above. This is not required if you are using GitHub authentication.
github-client-secret- The GitHub secret, obtained when creating the OAuth App as described above. This is not required if you are using CILogin authentication.
session-secret- Encryption key for the Gafaelfawr session cookie.
Generate with
cryptography.fernet.Fernet.generate_key(). signing-key- The PEM-encoded RSA private key used to sign internally-issued JWTs.
Generate with
gafaelfawr generate-key.
You will reference the path to this secret in Vault when configuring the Helm chart later.
If you are not using the standard Helm chart, you can use Kubernetes secrets directly or use Vault secrets with a different naming or organization. You will specify the paths to the secrets in the Gafaelfawr configuration, as documented at Settings.
Helm deployment¶
There is a Helm chart for Gafaelfawr named gafaelfawr available from the Rubin Observatory charts repository.
The Helm chart only supports GitHub or CILogon as identity providers.
To use that chart, you will need to provide a values.yaml file with the following keys under a gafaelfawr key:
host(required)- The FQDN of the host under which Gafaelfawr is running.
The
/auth,/login,/logout,/oauth2/callback, and/.well-known/jwks.jsonroutes will be claimed under this host by the Gafaelfawr ingress configuration. This setting will be used to derive multiple other URLs, such as the issuer. ingress.host(optional)- The host-based virtual host under which to create the ingress routes.
Normally this should be set to the same thing as
host. However, you may wish to leave it unset if you want all routes to be configured with the*virtual host. image(optional)The Docker image to use for the Gafaelfawr application. Takes the following subkeys:
repository(optional)- The name of the Docker repository from which to pull an image. Defaults to the official release repository.
tag(optional)- The version of image to use.
If not set, defaults to the image corresponding to the
appVersionmetadata property of the chart, which is normally the latest stable release. pullPolicy(optional)- Kubernetes pull policy for the image.
Defaults to
Always.
redis_claim(optional)- The name of a persistent volume claim to use for Redis storage.
If not given, Redis will use
emptyDir, which is ephemeral storage that will be cleared on every pod restart (thus invalidating all user authentication sessions and user-issued tokens). vault_secrets_path(required)- The path in Vault for the Vault secret containing the secret keys described in Vault secrets.
proxies(optional)- A list of network blocks that should be treated as internal to the cluster and therefore ignored when analyzing
X-Forwarded-Forto find the true client IP. If not set, defaults to the RFC 1918 private address spaces. See Client IP addresses and theproxiesdocumentation in Settings for more information. user_scope(required)- The token scope to require before allowing access to the
/auth/tokensroute, which allows the user to issue and revoke their own tokens. loglevel(optional)- The Python logging level.
Set to one of the (all-caps) string log level values from the Python
loggingmodule. issuer.exp_minutes(optional)- The lifetime (in minutes) of the issued JWTs and thus the user’s authentication session. The default is 1440 (one day).
github.client_id- The client ID for the GitHub OAuth App if using GitHub as the identity provider.
Only set either this or
cilogon.client_id. cilogon.client_id- The client ID for CILogon if using CILogon as the identity provider.
Only set either this or
github.client_id. cilogon.redirect_url- The full redirect URL for CILogon if using CILogon as the identity provider.
Set this if you need to change the redirect URL to the
/oauth2/callbackroute instead of the/loginroute. cilogon.login_params- A mapping of additional parameters to send to the CILogon authorize route.
Can be used to set parameters like
skinorselected_idp. See the CILogon OIDC documentation for more information. known_scopes- Mapping of scope names to descriptions.
This is used to populate the new token creation page.
It is copied directly to the
known_scopesconfiguration setting documented in Settings. group_mapping- Mapping of scope names to lists of groups that provide that scope.
When GitHub is used as the provider, group membership will be synthesized from GitHub team membership.
See Groups from GitHub for more information.
When an OpenID Connect provider such as CILogon is used as the provider, group membership will be taken from the
isMemberOfclaim of the token returned by the provider.
For an example, see the configuration for the LSST Science Platform deployments.
The Helm chart will generate a Gafaelfawr configuration file via a ConfigMap resource.
See Settings if you need to understand that configuration file or fine-tune its settings.
Application configuration¶
Protecting a service¶
Gafaelfawr’s routes must be exposed under the same hostname as the service that it is protecting.
IF you need to protect services running under multiple hostnames, you will need to configure Gafaelfawr’s ingress to add its routes (specifically /auth and /login) to each of those hostnames.
Authentication and authorization for a service are configured via annotations on the ingress for that service. The typical annotations for a web application used via a web browser are:
annotations:
kubernetes.io/ingress.class: nginx
nginx.ingress.kubernetes.io/auth-method: GET
nginx.ingress.kubernetes.io/auth-response-headers: X-Auth-Request-Token
nginx.ingress.kubernetes.io/auth-signin: "https://<hostname>/login"
nginx.ingress.kubernetes.io/auth-url: "https://<hostname>/auth?scope=<scope>"
Replace <hostname> with the hostname of the ingress on which the Gafaelfawr routes are configured, and <scope> with the name of the scope that should be required in order to visit this site.
This will send a request to the Gafaelfawr /auth route for each request.
It will find the user’s authentication token, check that it is valid, and check that the user has the required scope.
If the user is not authenticated, they will be redirected to the sign-in URL configured here, which in turn will either send the user to CILogon or to GitHub to authenticate.
If the user is already authenticated but does not have the desired scope, they will receive a 403 error.
The typical annotations for a API that expects direct requests from programs are:
annotations:
kubernetes.io/ingress.class: nginx
nginx.ingress.kubernetes.io/auth-response-headers: X-Auth-Request-Token
nginx.ingress.kubernetes.io/auth-url: "https://<hostname>/auth?scope=<scope>"
The difference in this case is that the 401 error when authentication is not provided will be returned to the client, rather than returning a redirect to the login page.
If the user authenticates and authorizes successfully, the request will be sent to the application.
Included in the request will be an X-Auth-Request-Token header containing the user’s JWT.
This will be a reissued token signed by Gafaelfawr.
Disabling error caching¶
Web browsers cache 403 (HTTP Forbidden) error replies by default.
Unfortunately, NGINX does not pass a Cache-Control response header from an auth_request handler back to the client.
It also does not set Cache-Control on a 403 response itself, and the Kubernetes ingress-nginx does not provide a configuration knob to change that.
This can cause user confusion; if they reauthenticate after a 403 error and obtain additional group memberships, they may still get a 403 error when they return to the page they were trying to access even if they now have access.
This can be avoided by setting a custom error page that sets a Cache-Control header to tell the browser not to cache the error.
Gafaelfawr provides /auth/forbidden as a custom error handler for this purpose.
To use this, add the following annotation to the ingress for the application:
annotations:
nginx.ingress.kubernetes.io/configuration-snippet: |
error_page 403 = "/auth/forbidden?scope=<scope>";
The parameters to the /auth/forbidden URL must be the same as the parameters given in the auth-url annotation.
The scheme and host of the URL defined for the 403 error must be omitted so that NGINX will generate an internal redirect, which in turn requires (as with the rest of Gafaelfawr) that the Gafaelfawr /auth route be defined on the same virtual host as the protected application.
Be aware that this will intercept all 403 errors from the protected application, not just ones from Gafaelfawr. If the protected application returns its own 403 errors, the resulting error will probably be nonsensical, and this facility may not be usable.
Configuring authentication¶
The URL in the nginx.ingress.kubernetes.io/auth-url annotation accepts several parameters to customize the authentication request.
scope(required)- The scope claim that the client JWT must have.
May be given multiple times.
If given multiple times, the meaning is govered by the
satisfyparameter. Scopes are determined by mapping the group membership provided by the authentication provider, using thegroup_mappingconfiguration directive. See Settings for more information. satisfy(optional)- How to interpret multiple
scopeparameters. If set toall(or unset), the user’s token must have all of the given scopes. If set toany, the user’s token must have one of the given scopes. auth_type(optional)- Controls the authentication type in the challenge returned in
WWW-Authenticateif the user is not authenticated. By default, this isbearer. Applications that want to prompt for HTTP Basic Authentication should set this tobasicinstead. audience(optional)- May be set to the value of the
issuer.aud.internalconfiguration parameter, in which case a new token will be issued from the user’s token with all the same claims but with that audience. This newly-issued token will be returned in theX-Auth-Request-Tokenheader instead of the user’s regular token. The intent of this feature is to send an audience-restricted version of a token to an internal service, which may use it to make subrequests to other internal services but should not be able to make requests to public-facing services.
These parameters must be URL-encoded as GET parameters to the /auth route.
Additional authentication headers¶
The following headers may be requested by the application by adding them to the nginx.ingress.kubernetes.io/auth-response-headers annotation for the ingress rule.
The value of that annotation is a comma-separated list of desired headers.
X-Auth-Request-Client-Ip- The IP address of the client, as determined after parsing
X-Forwarded-Forheaders. See Client IP addresses for more information. X-Auth-Request-Email- If enabled and the claim is available, this will be set based on the
emailclaim in the token. X-Auth-Request-User- If enabled and the claim is available, this will be set from token based on the
username_claimsetting (by default, theuidclaim). X-Auth-Request-Uid- If enabled and the claim is available, this will be set from token based on the
uid_claimsetting (by default, theuidNumberclaim). X-Auth-Request-Groups- If the token lists groups in an
isMemberOfclaim, the names of the groups will be returned, comma-separated, in this header. X-Auth-Request-Token- If enabled, the encoded token will be sent.
X-Auth-Request-Token-Scopes- If the token has scopes in the
scopeclaim or derived from groups listed inisMemberOf, they will be returned in this header. X-Auth-Request-Token-Scopes-Accepted- A space-separated list of token scopes the reliant resource accepts.
This is configured in the
nginx.ingress.kubernetes.io/auth-urlannotation via thescopeparameter. X-Auth-Request-Token-Scopes-Satisfy- The strategy the reliant resource uses to determine whether a token satisfies the scope requirements.
It will be either
anyorall. This is configured in thenginx.ingress.kubernetes.io/auth-urlannotation via thesatisfyparameter.
Verifying tokens¶
A JWKS for the Gafaelfawr token issuer is available via the /.well-known/jwks.json route.
An application may use that URL to retrieve the public key of Gafaelfawr and use it to verify the token signature.