Configuration

Overview

The server configuration is mainly done in a file named application.yml. If the default values must be overridden, this can be done by adding a file application.yml in the same folder where you launch the shinyproxy-*.jar file and specify properties in the YAML format. The standard configuration has the following values:

proxy:
  title: Open Analytics Shiny Proxy
  logo-url: https://www.openanalytics.eu/shinyproxy/logo.png
  landing-page: /
  heartbeat-rate: 10000
  heartbeat-timeout: 60000
  port: 8080
  authentication: ldap
  admin-groups: scientists
  # Example: 'simple' authentication configuration
  users:
  - name: jack
    password: password
    groups: scientists
  - name: jeff
    password: password
    groups: mathematicians
  # Example: 'ldap' authentication configuration
  ldap:
    url: ldap://ldap.forumsys.com:389/dc=example,dc=com
    user-dn-pattern: uid={0}
    group-search-base:
    group-search-filter: (uniqueMember={0})
    manager-dn: cn=read-only-admin,dc=example,dc=com
    manager-password: password
  # Docker configuration
  docker:
    cert-path: /home/none
    url: http://localhost:2375
    port-range-start: 20000
  specs:
  - id: 01_hello
    display-name: Hello Application
    description: Application which demonstrates the basics of a Shiny app
    container-cmd: ["R", "-e", "shinyproxy::run_01_hello()"]
    container-image: openanalytics/shinyproxy-demo
    access-groups: [scientists, mathematicians]
  - id: 06_tabsets
    container-cmd: ["R", "-e", "shinyproxy::run_06_tabsets()"]
    container-image: openanalytics/shinyproxy-demo
    access-groups: scientists

logging:
  file:
    name: shinyproxy.log

General

The first block of configuration in the application.yml file concerns general configuration values for the ShinyProxy application:

  • title: title that is displayed in the ShinyProxy navigation bar;

  • logo-url: url of the logo that is displayed in the ShinyProxy navigation bar; this can also be a local file using the file scheme (file://)

  • favicon-path: path to the favicon file to be used by ShinyProxy; both relative (to the working directory of ShinyProxy) and absolute paths can be used;

  • landing-page: the URL to send a user to after login; default value is / which will redirect the user to a list of the Shiny apps. Other typical values are /app/<app-name> or /app_direct/<app-name> which allows to immediately land on a (single) Shiny app;

  • heartbeat-rate: the user’s browser will sent a heartbeat call every heartbeat-rate milliseconds; default value is 10000 (10 seconds);

  • heartbeat-timeout: if the server does not receive a heartbeat for heartbeat-timeout milliseconds, the relevant proxy will be released (and the container stopped); default value is 60000 (60 seconds). Apps can override this using the heartbeat-timeout property.

    Note: it is possible to set this value to -1 in order to disable this feature.

  • heartbeat-enabled: whether inactive proxies are removed; default is true

  • bind-address: a hostname or IP address to be used as bind address for ShinyProxy; default value is 0.0.0.0 (all interfaces);

  • port: port to be used by ShinyProxy; the default port is 8080;

    Note: root permissions are required when using a port lower than 1024. Therefore, always use a port higher than 1024.

  • heartbeat-enabled: whether inactive proxies are removed; default is true

  • template-path: optional path that can be set to customize the landing page (listing of the apps) of ShinyProxy; it refers to the folder that contains the index.html page and all assets needed to serve the custom landing page; for more detail see the example configuration on Github;

  • template-groups: optionally specifies a list of groups that is used to group apps in the template. For example:

    proxy:
      template-groups:
        - id: tools
          display-name: Tools
          maintainer: Tesla
        - id: visualisations
          display-name: Visualisations
          maintainer: Einstein
        - id: documentation
          display-name: Documentation
    

    Only the id and display-name properties are mandatory. Other properties can be freely added and are made available in the template. See the template-group property of an app and the full example.

  • authentication: authentication method; one of ldap (default), kerberos, keycloak, openid, saml, social, simple or none; see the relevant section below for configuration details regarding each of these authentication methods;

  • admin-groups: one or more groups (as defined in the authentication back-end) that have access to the administrative interface of ShinyProxy e.g. to view and manage active sessions; multiple groups are enclosed in square brackets and separated by commas.

Besides the basic configuration options described above, there are also some more advanced configuration options that are not part of the default configuration:

  • container-wait-time: timeout for the container to be available to ShinyProxy; defaults to 20s (20000):

    proxy:
      [...]
      container-wait-time: 20000
    

    Note that a container is considered to be ‘available’ if its HTTP listener responds with status 200.

  • hide-navbar: boolean; if set to true the navigation bar at the top will be hidden; this may be useful when ShinyProxy is deployed as part of larger applications. You can also hide the navigation bar for individual apps by adding the sp_hide_navbar=true query parameter to the url. For example: http://localhost:8080/app/rstudio?sp_hide_navbar=true.

  • server.servlet.context-path: by default ShinyProxy runs on the / context path; the context path can be configured using this property. Note that it should always start with a slash (as in /abcd):

    server:
      servlet:
        context-path: /abcd
    
  • proxy.stop-proxies-on-shutdown: boolean controlling whether proxies (i.e. apps) must be stopped when ShinyProxy is shutdown. See the dedicated page. The default is true

  • proxy.recover-running-proxies: boolean controlling whether proxies (i.e. apps) must be recovered on startup of ShinyProxy. See the dedicated page. The default is false

  • proxy.recover-running-proxies-from-different-config: boolean controlling whether proxies (i.e. apps) must be recovered on startup of ShinyProxy even if an app was started from a different configuration file. See the dedicated page. The default is false.

  • proxy.default-stop-proxy-on-logout: boolean controlling whether proxies (i.e. apps) of a user must be stopped when a user logs out. The default is true. Each app can override this value using the stop-on-logout property.

  • proxy.default-proxy-max-lifetime: configures the default maximum lifetime of a proxy (i.e. app) in minutes. When an app is running for a longer time than the specified value, it will be automatically stopped. The default value -1 disables this feature and will not kill any app. Each app can override this value using the max-lifetime property.

  • spring.application.name: the name of the application as shown to users (e.g., on web pages, error messages etc). Example:

    spring:
      application: 
        name: MyCustomName
    

Authentication

Note:

  • To establish a TLS connection between ShinyProxy and the authentication server (such as Keycloak), you should configure the ShinyProxy with a Trust Store containing the certificates of the target authentication server. Please refer to https://docs.oracle.com/cd/E19906-01/820-4916/geygn/index.html for details.

  • However, if the ShinyProxy is connecting with the authentication server over plain HTTP it’s not necessary to add certificates in the Trust Store e.g. if both ShinyProxy and Keycloak are in a Kubernetes cluster, there is no need for TLS between them.

LDAP

When using LDAP authentication, ShinyProxy will use the provided LDAP url to:

  • Authenticate users by attempting to bind with their login name and password.
  • Authorize users to access apps by searching for any LDAP groups they are a member of, and matching those group names to the list of group names configured for the app.

With the default values (authentication: ldap), authentication will be done against the LDAP server at ldap.forumsys.com; to log in one can use the user name “tesla” and password “password”.

In order to use it with your own LDAP directory, you can use the following fields:

  • url: the LDAP connection string, composed of the URL and base DN of the LDAP directory;
  • user-dn-pattern: pattern of the distinguished name for a user. Use this if all your users are in a single LDAP location;
  • user-search-filter: LDAP filter to search for users. Use this if your users are in different LDAP locations, and you cannot use user-dn-pattern;
  • user-search-base: search base to search for users. Only used if user-search-filter is set;
  • group-search-filter: LDAP filter used to search for group memberships;
  • group-search-base: search base to search for groups. Only used if group-search-filter is set;
  • manager-dn: the distinguished name of the user used to bind to the LDAP directory; leave empty if the initial bind is anonymous;
  • manager-password: the password of the user used to bind to the LDAP directory; can be omitted if manager-dn is empty (i.e. when the initial bind is anonymous).

Notes:

  • the base DN given in the url (e.g. dc=example,dc=com in ldap://ldap.forumsys.com:389/dc=example,dc=com) does not have to be repeated in user-search-base or group-search-base. However, it must be repeated in the manager-dn.
  • user-dn-pattern and user-search-filter support the placeholder {0} which will be replaced with the user’s login name.
  • group-search-filter supports three placeholders: {0} maps to the user’s DN, {1} maps to the user’s login name, and {2} maps to the user’s CN.

Environment Variables

When a user is authenticated, the following environment variables will be available in any Shiny application launched by the user:

  • SHINYPROXY_USERNAME: the name of the user, as used when logging in
  • SHINYPROXY_USERGROUPS: the groups the authenticated user is a member of, as a comma-separated value

Multiple LDAP providers

When multiple LDAP providers need to be configured (e.g. to support different domains or forests), this can be done using

proxy:
  ldap:
  - url: ldap://ldap.forumsys.com:389/dc=example,dc=com
    ...
  - url: ldap://another.ldap.server:389/...
    ...

instead of the single LDAP configuration

proxy:
  ldap:
    url: ldap://ldap.forumsys.com:389/dc=example,dc=com
    ...

StartTLS

Using LDAP with StartTLS can be achieved by adding a setting

proxy:
  ldap:
    starttls: simple

This setting may have the following values:

  • simple: StartTLS is enabled, using simple client authentication;
  • true: same as simple;
  • external: StartTLS is enabled, using external (certificate) client authentication;
  • (None): if the property is absent (default), StartTLS is disabled.

More information on the StartTLS extension can be found here.

Note:

  • LDAPS and LDAP with StartTLS are mutually exclusive; it is not possible to combine the two mechanisms.

Example: FreeIPA

When using a FreeIPA server for managing identities (e.g. the standard on RHEL), the configuration with the default directory tree will be:

ldap:
      url: ldaps://example.com:636/dc=example,dc=com
      manager-dn: uid=shinyproxy,cn=sysaccounts,cn=etc,dc=example,dc=com
      manager-password: xxxxxxxxxxxx
      user-dn-pattern: uid={0},cn=users,cn=accounts
      group-search-filter: (member={0})
      group-search-base: cn=groups,cn=accounts

Notes:

  • in this example uid=shinyproxy,cn=sysaccounts,cn=etc,dc=example,dc=com is a specific system account created to bind against the FreeIPA LDAP directory on behalf of ShinyProxy;

Example: Active Directory

ldap:
      url: ldaps://example.com:3269/dc=example,dc=com
      manager-dn: cn=shinyproxy,ou=Service Accounts,dc=example,dc=com
      manager-password: xxxxxxxxxxxx
      user-search-filter: (sAMAccountName={0})
      group-search-filter: (member={0})
      group-search-base: ou=Groups

Notes:

  • sAMAccountName is often used as the unique login name in Active Directory environments. That’s why it is used here in the user-search-filter.
  • 3269 is the SSL-enabled port for the Global Catalog.

Kerberos

A second type of authentication is Kerberos based authentication which offers single-sign on top of the authentication and authorization.

Kerberos-based authentication can be configured using

proxy:

  [...]

  authentication: kerberos

in the application.yml file.

The configuration details for Kerberos should be set in a separate kerberos block:

proxy:

[...]

  kerberos:
    auth-service-principal: HTTP/shinyproxy.yourdomain.com
    auth-service-keytab: /etc/security/keytabs/shinyproxy-http.keytab
    deleg-service-principal: shinyproxy-service
    deleg-service-keytab: /etc/security/keytabs/shinyproxy-service.keytab
    client-ccache-path: /path/to/folder

In order to use it with your own Kerberos server, you can use the following fields:

  • auth-service-principal: principal name of the Kerberos entity that processes authentication requests;
  • auth-service-keytab: path to the keytab file containing the secret of the auth-service-principal;
  • deleg-service-principal: principal name of the Kerberos entity that obtains delegated service tickets for the user; if not specified the value of the auth-service-principal will be used also for the deleg-service-principal;
  • deleg-service-keytab: path to the keytab file containing the secret of the deleg-service-principal; if not specified the value of the auth-service-keytab is used as the deleg-service-keytab;
  • backend-principals: array of service principals for which delegated service tickets should be obtained e.g. ["HTTP/your-api-server.com", "postgres/your-postgres-server.com"]
  • client-ccache-path: absolute or relative path to a folder where user-specific credential caches are maintained; these are mounted automatically as volumes in the container, and contain service tickets for the configured backend-principals;
  • ticket-renew-interval: interval in milliseconds for automatically renewing the backend service tickets; the default is 28800000 which corresponds to 8 hours.

Notes:

SPNEGO

The browser must support the SPNEGO protocol for single-sign on authentication to work.

krb5.conf

  • For Active Directory you must explicitly enable forwardable tickets with the following setting in the /etc/krb5/krb5.conf file:
  [libdefaults]
    forwardable = true

The logic used by ShinyProxy to locate and load the krb5.conf file is described here

Environment Variables

When a user is authenticated, the following environment variables will be available in any Shiny application launched by the user:

  • SHINYPROXY_USERNAME: the name of the user, as used when logging in;
  • SHINYPROXY_USERGROUPS: the groups the authenticated user is a member of, as a comma-separated value.

Single-Sign On / Keycloak

A third type of authentication is Keycloak authentication, a very powerful option that delegates authentication and authorization to the open source identity and access management system Keycloak supported by Red Hat. Many advanced features are available to ShinyProxy such as User Federation, Identity Brokering and Social Login.

Keycloak authentication can be configured using

proxy:

  [...]

  authentication: keycloak

in the application.yml file. The details related to the application identifiers and secrets for each of the social platforms can be configured in a separate keycloak block:

proxy:

[...]

  keycloak:
    realm: yoursso
    auth-server-url: http://yoururl.com:8180/auth
    resource: yourresource
    credentials-secret: your-credentials-secret

There are also the following optional settings:

  • ssl-required: one can set the SSL/HTTPS mode to one of none, all or external (default). These options are documented here;
  • proxy.keycloak.name-attribute: name of the attribute to use as the user’s name; one of name (default, current behaviour), preferred_username, nickname or email
  • use-resource-role-mappings: boolean value to use either client roles (true) or realm roles (false; default); see also the relevant Keycloak documentation.

Note:

  • the user name of the authenticated user is made available to the Shiny application via the environment variable SHINYPROXY_USERNAME;
  • the roles the authenticated user has been assigned to are made available to the Shiny application via the environment variable SHINYPROXY_USERGROUPS.

Further documentation on setting up Keycloak can be found here.

OpenID Connect (OIDC)

OpenID Connect is a modern authentication protocol based on the OAuth2 standard. It uses tokens, removing the need to store passwords and offering a single-sign-on experience for desktop, web and mobile apps.

More information about OIDC can be found on the OpenID website.

To configure OIDC in ShinyProxy, several steps must be performed:

  1. Register your ShinyProxy installation with an OIDC provider.
  2. Obtain the configuration parameters for your OIDC provider that ShinyProxy requires.
  3. (Optional) For group-based authorization, add a custom claim in the OIDC ID Token.

Enable OIDC authentication

OIDC authentication can be enabled by setting

proxy:

  [...]

  authentication: openid

in the application.yml file.

Register with an OIDC Provider

Some examples of well-known OIDC providers are: Auth0, Okta, Google, Microsoft, and many more social platforms. Of course, you can also deploy your own OpenID identity provider.

When registering the application, make sure to select the type ‘web application’. This allows you to specify a callback URL. The URL should look like this:

http(s)://(your-shinyproxy-url)/login/oauth2/code/shinyproxy

Defining an incorrect callback URL will result in authentication errors in ShinyProxy.

Set OIDC configuration parameters

Your OIDC provider should offer you the following parameters for your newly registered app:

  • Auth Endpoint URL: The URL where OIDC initiates authentication flows. ShinyProxy will redirect here when an unauthenticated user accesses a page.
  • Token Endpoint URL: The URL where tokens can be retrieved or exchanged. This is used during the authentication process.
  • JSON Web Key Set (jwks) URL: The URL where the provider’s public certificates can be found. This is used during the authentication process.
  • Client ID: A unique ID generated by the provider for your application.
  • Client Secret: A secret generated by the provider for your application.

Enter these parameters into ShinyProxy’s application.yml file. An example for Google is given below:

proxy:
  openid:
    auth-url: https://accounts.google.com/o/oauth2/v2/auth
    token-url: https://www.googleapis.com/oauth2/v4/token
    jwks-url: https://www.googleapis.com/oauth2/v3/certs
    client-id: ***
    client-secret: ***

Similarly, an example for Microsoft Azure B2C:

proxy:
  openid:
    auth-url: https://login.microsoftonline.com/<your-tenant-id>/oauth2/authorize
    token-url: https://login.microsoftonline.com/<your-tenant-id>/oauth2/token
    jwks-url: https://login.microsoftonline.com/common/discovery/keys
    client-id: ***
    client-secret: ***

Note: replace <your-tenant-id> with the ID of your Azure tenant.

Group-based authorization

While OIDC specifies an authentication flow, it has no notion of ‘user groups’ or ‘permission levels’. To achieve this in ShinyProxy, you must use custom claims. Claims are pieces of information about the user that are included in the ID Token returned by the OIDC provider. Common claims include first name, last name, email address, etc.

Adding a custom claim to an ID Token is specific to each OIDC provider. Several examples are given below.

Note: the value of the custom roles claim must be an array!

Auth0
  • Go to the user management page, select a user, and scroll down to the app_metadata block. Enter the following JSON:
{
  "shinyproxy_roles": [ "scientists", "mathematicians" ]
}
  • Go to the rules page and create a new rule that attaches this information to the ID Token upon authentication:
function (user, context, callback) {
  context.idToken['https://shinyproxy.io/shinyproxy_roles'] = user.app_metadata.shinyproxy_roles;
  callback(null, user, context);
}

Note that custom claims must have a URI-like namespace. In this example, we use the name https://shinyproxy.io/shinyproxy_roles.

  • In ShinyProxy’s application.yml file, enter the full name of the custom claim containing the user’s roles:
proxy:
  openid:
    roles-claim: https://shinyproxy.io/shinyproxy_roles
Amazon AWS Cognito

Cognito already provides this information in the claim cognito:groups. For more information, see AWS Cognito: User Pools.

Miscellaneous settings

  • logout-url: URL to be used for logging out of the OpenId session on the OpenId provider back-end; for an Auth0 back-end one can specify e.g.

    proxy:
      openid:
        logout-url: https://YOUR_AUTH0_DOMAIN/v2/logout?returnTo=http%3A%2F%2Fwww.yourshinyproxyserver.com
    

    Note:

    • instead of redirecting the user back to a login form, ShinyProxy can show a page indicating that the user has successfully logged out. For example, to use this using Auth0 use:

      proxy:
        openid:
          logout-url: https://YOUR_AUTH0_DOMAIN/v2/logout?returnTo=http%3A%2F%2Fwww.yourshinyproxyserver.com/logout-scucess
      
    • omitting the logout-url option will simply clear the session of the user and does not terminate the session of the user at the IDP

    • setting the logout-url to /logout-success will also simply clear the session of the user, after which the user is redirected to a page indicating that the user has successfully logged out.

    • you can use SpEL in this configuration option. This can be used to provide the id token as a URL parameter, which is required by some OpenID providers:

      proxy:
        openid:
          logout-url: https://YOUR_IDPD/oauth2/logout?id_token_hint=#{oidcUser.idToken.tokenValue}
      
  • username-attribute: name of the attribute to be used as the user’s name; one of email (default), name, nickname, preferred_username, etc.; a complete list is provided in the OpenId Connect specification.

Environment Variables

When a user is authenticated, the following environment variables will be available in any Shiny application launched by the user:

  • SHINYPROXY_USERNAME: the name of the user, as used when logging in
  • SHINYPROXY_USERGROUPS: the groups the authenticated user is a member of, as a comma-separated value
  • SHINYPROXY_OIDC_ACCESS_TOKEN: the OpenId connect access token that can be reused to invoke another API from within the Shiny app

SAML 2.0

ShinyProxy supports authentication and authorization using an IDP (Identity Provider) conforming to the SAML 2.0 specification.

SAML authentication can be enabled by setting

proxy:

  [...]

  authentication: saml

in the application.yml file.

The basic settings (under proxy.saml) are:

  • idp-metadata-url: the URL where the IDP’s metadata can be retrieved
  • app-entity-id: the entity ID of this application, must match the AudienceRestriction in the SAML assertion received from the IDP
  • app-base-url: the application’s base URL. Some IDPs require that you specify valid callback URLs. This must match a valid callback URL.
  • name-attribute: the name of the attribute in the SAML assertion containing the username
  • roles-attribute: the name of the attribute in the SAML assertion containing the user roles
  • logout-url: optional URL to which the user will be redirected when they logout
  • logout-method: can be local (default) or saml. See below for more information.
  • log-attributes: when set to true, ShinyProxy logs all SAML attributes provided by the IDP when a user logs in. This makes it easier to find the correct attribute to use for the name-attribute and roles-attribute configuration. Note that this may log sensitive information (e.g., some SAML claims may contain passwords or tokens), therefore this option should only be used when setting up SAML (or you have to ensure no sensitive values are contained within the SAML assertion).
  • max-authentication-age: configures the maximum age of SAML credentials in seconds. By default, this is 7200 seconds (i.e. 2 hours). See this FAQ entry for more information and an alternative solution.
  • force-authn: if set to true the IDP is required to re-authenticate the user and not rely on previous authentication events. This is useful when the IDP provides ShinyProxy credentials which are older than the time configured in the max-authentication-age. See this FAQ entry for more information and an alternative solution.

If the IDP sends back encrypted SAML assertions, ShinyProxy must be configured with additional settings to be able to decrypt the assertion:

  • keystore: path to the JKS keystore
  • keystore-password: password to access the keystore. If omitted, encryption-cert-password will be used instead
  • encryption-cert-name: name of the certificate whose public key the SAML Assertion is encrypted with
  • encryption-cert-password: password of the certificate whose public key the SAML Assertion is encrypted with

Note:

  • there are two ways of doing authorization: using group memberships and using application roles
  • when using Azure AD in combination with group membership based authorization, ShinyProxy will receive group GUIDs instead of names; in that case, the application can query the MS Graph API to resolve the group GUID (if needed)
  • the SAML metadata of ShinyProxy is available at the /saml/metadata path

SAML LoadBalancer configuration

The SAML protocol requires the validation of (redirect) URLs, therefore ShinyProxy must be able to know the URL at which it is used by the user. However, when using a loadbalancer or reverse proxy in front of ShinyProxy this becomes harder. The typical solution is to configure the loadbalancer to provide X-Forwarded-... headers as described on our Security page. However, in some rare cases it may not be possible to provide these X-Forwarded-... headers. An example deployment where this can happen is when TLS termination is performed by a networking loadbalancer and not an application loadbalancer. This network loadbalancer is unable to provide the X-Forwarded headers.

Note: this configuration is seldom needed! First try to fix your issues by properly configuring the X-Forwarded errors as described on the Security page and in the FAQ.

  • proxy.saml.lb-server-name: the server name as visible to the user, e.g. `example.com
  • proxy.saml.lb-context-path: the context path as visible to the user. Depending on the configuration of the load balancer this can be different from the server.servlet.context-path property.
  • proxy.saml.lb-port-in-url: whether to include the port into the URL, the default is false)
  • proxy.saml.lb-scheme: the scheme as visible by the user, the default is https.
  • proxy.saml.lb-server-port: the port on which the user accesses ShinyProxy, by default this is 443.

Note: in most cases you only have to configure the proxy.saml.lb-server-name and proxy.saml.lb-context-path properties.

SAML Logout

The SAML protocol can, in addition to authenticating users, also log users out. This can be enabled by setting the logout-method to saml. The following steps are taken when the user clicks the logout button:

  • the user goes to /saml/logout
  • this page sends a request to the logout endpoint of the IDP
  • the IDP handles the request and sends a response back to ShinyProxy
  • ShinyProxy redirects you to the URL specified in the proxy.saml.logout-url property or if this is not setup to the login page of ShinyProxy

Note: in order for this to work, ShinyProxy must be able to sign requests and therefore the keystore properties must be correctly setup.

Note: it is possible to set the proxy.saml.logout-url property to /logout-success. This will prevent the user from being redirected to the login page again.

In addition to the official SAML logout protocol, ShinyProxy can also perform a “local” logout. This does not inform the IDP about the logout of the user. This can be enabled by setting the logout-method to local. In this case the following steps are taken when the user clicks the logout button:

  • the user goes to /logout
  • ShinyProxy removes the session of the user
  • ShinyProxy redirects the user to the URL specified in the proxy.saml.logout-url property or if this is not setup to the login page of ShinyProxy.

Note: when using this option it is a good idea to set the proxy.saml.logout-url property to /logout-success. This will prevent the user from being redirect to the login page again. Otherwise, the user would automatically get logged in again, because the session at the IDP still exists.

SameSiteCookies and SAML

It is possible that the SAML integration is not working when using POST bindings, because the browser is blocking access to the session cookies. In such cases you can get the following error:

Caused by: org.opensaml.common.SAMLException: InResponseToField of the Response doesn't correspond to sent message ...

This error can be fixed by setting server.secure-cookies to true and the proxy.same-site-cookie policy to None. Note that browsers will enforce you to use HTTPS when using this configuration.

server:
  secure-cookies: true
proxy:
  same-site-cookie: None

Example: Auth0

An example configuration using Auth0 as an IDP is given below.

  • Application type: Regular Web Application
  • Domain: my-shinyproxy-setup.eu.auth0.com
  • Allowed Callback URLs: http://localhost:8080/saml/SSO
  • Addons: enable SAML2 Web App
  • Addon settings: Application Callback URL: http://localhost:8080/saml/SSO (same as Allowed Callback URLs)
  • Addon settings: Settings JSON: { "audience": "urn:my-shinyproxy-setup.eu.auth0.com" } (same as Domain)

In ShinyProxy’s application.yml:

proxy:
  saml:
    idp-metadata-url: https://my-shinyproxy-setup.eu.auth0.com/samlp/metadata/<client-id>
    app-entity-id: urn:my-shinyproxy-setup.eu.auth0.com
    app-base-url: http://localhost:8080
    name-attribute: http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress
    roles-attribute: http://schemas.auth0.com/shinyproxy_roles
    logout-url: https://my-shinyproxy-setup.eu.auth0.com/v2/logout?client_id=<client_id>&returnTo=http://localhost:8080/

Example: Azure AD

An example configuration using Azure AD as an IDP is given below.

First, create a directory in Azure AD and define several users (not in scope of this document). Then, under App registrations, register a new app of the type Web app / API. Provide the following configuration:

  • Settings > Properties > Homepage URL: http://localhost:8080
  • Manifest: to enable role-based authorization, fill out the appRoles block:
{
  "appRoles": [
    {
      "allowedMemberTypes": [
        "User"
      ],
      "displayName": "Mathematicians",
      "id": "2e7e882c-6645-4a58-b92d-2e2dbe02bd87",
      "isEnabled": true,
      "description": "These are mathematicians",
      "value": "mathematicians"
    },
    {
      "allowedMemberTypes": [
        "User"
      ],
      "displayName": "Scientists",
      "id": "e858ca1b-66a3-43f0-aa4c-deed69750550",
      "isEnabled": true,
      "description": "These are scientists",
      "value": "scientists"
    }
  ],
}

In ShinyProxy’s application.yml:

proxy:
  saml:
    idp-metadata-url: https://login.microsoftonline.com/<tenant-ID>/FederationMetadata/2007-06/FederationMetadata.xml
    app-entity-id: <see App ID URI in your Azure application properties>
    app-base-url: http://localhost:8080
    roles-attribute: http://schemas.microsoft.com/ws/2008/06/identity/claims/role

Social Authentication

A sixth type of authentication offered by ShinyProxy besides LDAP, Kerberos, Keycloak, OpenID Connect and SAML 2.0 is so-called social authentication. This type of authentication allows users to log in with

  • Facebook,
  • Twitter,
  • Google,
  • Github or
  • Linkedin

accounts into ShinyProxy.

Social authentication can be configured using

proxy:

  [...]

  authentication: social

in the application.yml file. The details related to the application identifiers and secrets for each of the social platforms can be configured in a separate social block:

proxy:

[...]

  social:
    facebook:
      app-id: yourfacebookappid
      app-secret: yourfacebookappsecret
    twitter:
      app-id: yourtwitterappid
      app-secret: yourtwitterappsecret
    google:
      app-id: yourgoogleappid
      app-secret: yourgoogleappsecret
    github:
      app-id: yourgithubappid
      app-secret: yourgithubappsecret
    linkedin:
      app-id: yourlinkedinappid
      app-secret: yourlinkedinappsecret

Since no authorization is offered by the social platforms, authorization logic is not implemented and authenticated users will be able to access all public applications.

Note:

  • the username of the authenticated user is made available to the Shiny application via the environment variable SHINYPROXY_USERNAME.

Web Service Based Authentication

Amongst the different authentication methods, ShinyProxy also offers the possibility to use a custom web service that handles authentication with a HTTP POST call returning a session id and user information.

In order to select this authentication method, one needs to choose the webservice authentication method:

proxy:

  [...]

  authentication: webservice

in the application.yml file.

The details related to the web service can be configured as:

proxy:
  webservice:
    authentication-url: https://your-auth-server.com/login
    authentication-request-body: '{"username": "%s", "password": "%s"}'

The authentication-request-body is a customizable JSON body that gets POSTed to the authentication-url.

If the HTTP response code is 200, the authentication is considered successful while all other response codes result in an authentication error. Since no authorization is offered by this type of web services, authorization logic is not implemented and authenticated users will be able to access all public applications.

Note:

  • the user name of the authenticated user is made available to the Shiny application via the environment variable SHINYPROXY_USERNAME.

Simple Authentication

Besides LDAP and social authentication, ShinyProxy also offers the possibility to define users and groups inside the application.yml file. In order to select this authentication method, one needs to choose the simple authentication method:

proxy:

  [...]

  authentication: simple

The example configuration demonstrates how users and groups can be specified:

proxy:
  users:
  - name: jack
    password: password
    groups: scientists
  - name: jeff
    password: password
    groups: mathematicians

Multiple groups can be specified for a user, by separating the groups with commas (do not use square brackets) as in this example:

proxy:
  users:
  - name: jack
    password: password
    groups: scientists
  - name: jeff
    password: password
    groups: mathematicians, physicists

Since passwords are contained in clear text in the application.yml file, this is not a secure way to set up authentication, but can be useful for demonstration purposes (e.g. in the absence of a network connection) or for very specific use cases.

Note:

  • the user name of the authenticated user is made available to the Shiny application via the environment variable SHINYPROXY_USERNAME;
  • the groups the authenticated user is member of are made available to the Shiny application via the environment variable SHINYPROXY_USERGROUPS. The group names are converted into capital letters (e.g. MATHEMATICIANS)

No authentication

In some scenarios, one wants to disable the ShinyProxy authentication. This can be done using

proxy:

  [...]

  authentication: none

Note:

  • with authentication: none the environment variable SHINYPROXY_USERNAME inside the Docker container will contain a random hash instead of the user name; if a user name needs to be passed from an external application it may be more useful to pass this information to the Shiny application via the URL (see this article).

Container Back-ends

ShinyProxy supports multiple container back-ends to run the Shiny apps, namely

  • a plain Docker host (default)
  • a Docker Swarm cluster and
  • a Kubernetes cluster

The backend can be configured using

proxy:
  container-backend: docker

The container-backend can be one of docker (default), docker-swarm or kubernetes. The specific configuration these back-ends is documented below.

Docker

The Docker back-end is the default back-end for ShinyProxy. In order to specify it explicitly, one can set proxy.container-backend to docker.

The configuration of the back-end can be done using the following properties:

  • cert-path: path to the folder that contains the certificate files (ca.pem, cert.pem and key.pem) used for encrypted traffic to the docker daemon; if the files have other names or are located in different folders, symbolic links can be used (for ca.pem, cert.pem and key.pem) that point to the actual certificate files. If a non-existing path is used as cert-path, traffic will not be encrypted; the default value for cert-path is set to /home/none; this property can be omitted when not applicable;

  • url: URL and port on which to connect to the docker daemon; the default value of http://localhost:2375 does not connect over TLS; this is not recommended for production environments;

  • container-protocol: optional setting to indicate the protocol to be used to communicate with the containers; can be one of http or https; if not set, the protocol is derived from the url specified (cf. above);

  • privileged: run all containers with extended privileges (true) or not (false; default value);

  • port-range-start: every docker container will be assigned a port on the docker host to which the ShinyProxy will proxy the traffic of a particular user; the value of port-range-start will be the port assigned to the first container that is started; by default the first port will be 20000 (second 20001, third 20002 etc.).

  • port-range-max: maximum port number to be handled by ShinyProxy (e.g. 20099, which allows to run a maximum of 100 containers if port-range-start is set to the default value 20000); this allows to limit the number of concurrent apps that can be managed by a single ShinyProxy instance or, in case multiple ShinyProxy instances launch docker containers on a shared Docker Swarm, can prevent the same port number being used by multiple such instances; the default value of port-range-max is -1 (no maximum). If the port pool is exhausted, the following error message will appear:

    Cannot start container: all allocated ports are currently in use. Please try again later or contact an administrator.
    
  • internal-networking: set this to true if ShinyProxy will run as a container on the same Docker host; default value is false.

    Note: when internal-networking is true, no ports will be allocated per proxy and the port range settings are ignored (port-range-start and port-range-max); also, the proxy target URLs will use the container host name.

Docker Swarm

In order to use a Docker Swarm back-end, set proxy.container-backend to docker-swarm. The configuration of the back-end is not different from the configuration of a plain Docker back-end (cf. supra).

Note:

  • when internal-networking is true, no ports will be allocated per proxy and the port range settings are ignored (port-range-start and port-range-max); also, the proxy target URLs will use the container name.

Kubernetes

In order to use a Kubernetes back-end, set proxy.container-backend to kubernetes. The configuration of the back-end can be done using the following properties:

  • proxy.kubernetes.url: the URL of the apiserver
  • proxy.kubernetes.cert-path: the path to a dir containing ca.pem, cert.pem and key.pem to be used if url is https
  • proxy.kubernetes.namespace: the namespace to create pods in; the default value is default
  • proxy.kubernetes.api-version: the API version to use; the default value is v1
  • proxy.kubernetes.image-pull-policy: the pull policy for images; the default value is IfNotPresent
  • proxy.kubernetes.image-pull-secret: the name of a secret to use for pulling images from a registry
  • proxy.kubernetes.image-pull-secrets: see above, but for multiple secrets
  • proxy.kubernetes.privileged: run all containers with extended privileges (true) or not (false; default value);
  • proxy.kubernetes.internal-networking: set this to true if ShinyProxy will run inside the cluster itself; default value is false
  • proxy.kubernetes.container-protocol: the protocol to use when accessing a container; can be one of http (default) or https
  • proxy.kubernetes.port: the TCP port to access on the container; the default port is 3838
  • proxy.kubernetes.node-selector: comma-separated list of key=value pairs that are matched with node labels to select specific nodes in the cluster; see the relevant Kubernetes documentation.
  • proxy.kubernetes.debug-patches: boolean (true/false) controlling whether the kubernetes-pod-patches feature should output debug output. If enabled the specification of pods is printed to the log before a patch is applied and after a patch is applied.
  • proxy.kubernetes.pod-wait-time: the time ShinyProxy waits for a Kubernetes Pod to become ready (in milliseconds). By default this is 60 seconds. This option should be changed when your Pods take more than 60 seconds to startup. For example if you use very large images.

An example configuration is:

proxy:
  container-backend: kubernetes
  kubernetes:
    cert-path: /etc/certs
    url: https://1.2.3.4

Note:

  • when internal-networking is true, no ports will be allocated per proxy and the proxy target URLs will use the Pod IP.

Apps

Every single Shiny app served by ShinyProxy has its own configuration block under specs:

  specs:
  - id: 01_hello
    container-cmd: ["R", "-e", "shinyproxy::run_01_hello()"]
    container-image: openanalytics/shinyproxy-demo
    access-groups: [scientists, mathematicians]
  - id: 06_tabsets
    container-cmd: ["R", "-e", "shinyproxy::run_06_tabsets()"]
    container-image: openanalytics/shinyproxy-demo
    access-groups: [scientists]

For each app, the following basic configuration fields can be specified:

  • id: the identifier of the application

  • display-name: the name that will be displayed for the app on the ShinyProxy landing page as well as in the browser tab (once the application is opened);

  • container-cmd: the command that will be run when the Docker container is launched; typically this command will be the R command ("R") as well as the command that will launch the Shiny app ("-e", "shinyproxy::run_01_hello()");

  • container-image: name of the docker image to be started for every new user of this app; by default a demo image (openanalytics/shinyproxy-demo) will be used;

  • container-privileged: run the container for this app with extended privileges (true) or not (false; default value);

  • container-memory-request: maps to --memory-reservation on Docker and to spec.containers[].resources.requests.memory, on Kubernetes see the documentation

  • container-memory-limit: maps to --memory on Docker spec.containers[].resources.limits.memory on Kubernetes, see the documentation

  • container-cpu-request: does nothing in Docker; maps to spec.containers[].resources.requests.cpu on Kubernetes, see the documentation

  • container-cpu-limit: maps to a combination of cpu-period and cpu-quota and is equivalent to the --cpus setting (available since Docker 1.13), maps to spec.containers[].resources.limits.cpu on Kubernetes, see the documentation

  • access-groups: one or more groups (e.g. LDAP groups) a user needs to belong to in order to gain access to the app; multiple groups are enclosed in square brackets and separated by commas; this field allows to authorize access per app; to test the authorization with LDAP authentication, one can use gauss with password password as an example mathematician; user tesla with password password is one of the example scientists. Other users are described here.

  • access-users: one or more users which may access the app. This field must be specified as a YAML array (i.e. not a comma-separated list.)

  • access-expression: a SpEL expression that determines whether a user has access to an app. This is described in detail on the SpEL page.

    Note: apps for which no access-groups, access-users or access-expression are specified will be handled as “public” applications in the sense that all authenticated users will be able to access these applications.

    Note: it is possible to combine the three access control options. In this case OR logic is applied between the different options. In other words, this means that at least one of the access control options must give a positive result before the user is granted access to the app.

Besides the basic configuration options described above, there are also some more advanced configuration options that are not part of the default configuration:

  • container-dns: a comma-separated list of IP addresses that are added as server lines in the /etc/resolv.conf file of the container; this is the equivalent of launching the container with a --dns option.

  • container-env: one or more environment variables specified as

    container-env:
      VAR1: VALUE1
      VAR2: VALUE2
    

    and that will be passed to the container; this is equivalent to docker run --env.

  • container-env-file: a path to a file in which environment variables are specified to be passed to the container; this can be configured using

        container-env-file: /path/to/env-file
    

    and is equivalent to docker run --env-file.

  • container-network: facilities to set the networking of the container. This is the equivalent of launching the container with a --network option (and defaults to bridge which is also the Docker default).

  • container-volumes: list of docker volumes to mount into the container; can be specified along

    container-volumes: [ "/host/path1:/container/path1", "/host/path2:/container/path2" ]
    

    and implements the functionality of docker run --volume.

  • logo-url: the URL to an image that can be used as the logo for an application; this can also be a local file using the file scheme (file://); if none of the applications in the application.yml specifies a logo-url field, the landing page will present the applications as a bullet list (cf. the default presentation in the openanalytics/shinyproxy-demo image).

  • template-group: when specified the app becomes part of the respective group. See proxy.template-groups for defining the list of possible groups. See the full example.

  • template-properties: a map of additional properties for this application which can be used in the templates. These properties do not have any other effect. See the full example.

  • heartbeat-timeout: the time in milliseconds that the server must receive no heartbeat in order to terminate the proxy. This overrides any value of the proxy.heartbeat-timeout property.

    Note: it is possible to set this value to -1 in order to disable this feature for the current app (even if the default has a different value).

  • stop-on-logout: configures whether this proxy should be stopped when the user logs out. This overrides any value of the proxy.default-stop-proxy-on-logout property.

  • max-lifetime: configures the maximum lifetime of a proxy (i.e. app) in minutes. This overrides any value of the proxy.default-max-lifetime property.

    Note: it is possible to set this value to -1 in order to disable this feature for the current app (even if the default has a different value).

  • port: the port on which the app is listening in the container; this setting will override the default port (3838)

  • target-path: the (context) path on which the app is available. By default this is the root path (/) which suffices for most apps (especially Shiny apps). See the Jupyter demo and Flask demo for scenarios where this option can be useful.

  • labels: a map of labels which are added to every instance of the pod. For example:

    labels:
        role: shinyproxy-app
        component: shinyproxy
    

    Note: when using Kubernetes ShinyProxy adds the app label (containing the ID of the container) to every pod it launches. Therefore, it’s impossible to specify a label named app.

    Note: ShinyProxy automatically adds extra labels (such as the proxy-id) to apps it launches. These labels are prefixed by openanalytics.eu/sp to prevent collisions.

  • network-connections: this is a Docker specific option: the list of Docker networks to link the container to. Can be used if you want your app to reach a container running in a different Docker network. For example:

    - id: database
      container-image: my-database-app
      container-network-connections: [ "mydb-net" ]
    
  • hide-navbar-on-main-page-link: boolean, when enabled, the sp_hide_navbar query parameter will be added to the link on the main page to this app. This way you can control that an individual app is opened with the navbar hidden, while it is still possible to show the navbar for that app (by changing the URL). More information.

  • kubernetes-pod-patches: this option is only used when using the Kubernetes container backend. It allows to apply JSON Patches to the Kubernetes Pod created for this app. The parameter is a string which should contain a YAML array. For example the following patch performs:

    • add a ServiceAccount
    • change the namespace
    • add Volume + VolumeMount
    • add an Environment variable
    • add a secret as an Environment Variable
    kubernetes-pod-patches: |
      - op: add
        path: /spec/serviceAccountName
        value: my-serviceaccount
      - op: replace
        path: /metadata/namespace
        value: my-namespace
      # in case the app has no volumes yet:
      - op: add
        path: /spec/volumes
        value:
          - name: cache-volume
            emptyDir: {}
      - op: add
        path: /spec/containers/0/volumeMounts
        value:
          - mountPath: /cache
            name: cache-volume
      # in case the app has at least one volume:
      # - op: add
      #   path: /spec/volumes/-
      #   value:
      #     name: cache-volume
      #     emptyDir: {}
      # - op: add
      #   path: /spec/containers/0/volumeMounts/-
      #   value:
      #     mountPath: /cache
      #     name: cache-volume
      - op: add
        path: /spec/containers/0/env/-
        value:
          name: ADDED_VAR
          value: VALUE
      - op: add
        path: /spec/containers/0/env/-
        value:
        - name: SOME_PASSWORD
          valueFrom:
            secretKeyRef:
            name: some-password
            key: password
    
  • kubernetes-additional-manifests: this option can only be used when using the Kubernetes container backend. This option contains a list of Kubernetes manifests, which are created when the app starts and destroyed when the app stops. For example the following configuration creates a PersistentVolumeClaim and a Secret.

    kubernetes-additional-manifests:
      - |
        apiVersion: v1
        kind: PersistentVolumeClaim
        metadata:
          name: manifests-pvc
        spec:
          storageClassName: standard
          accessModes:
            - ReadWriteOnce
          resources:
            requests:
              storage: 5Gi
      - |
          apiVersion: v1
          kind: Secret
          metadata:
            name: manifests-secret
          type: Opaque
          data:
            password: cGFzc3dvcmQ=
    
  • kubernetes-additional-persistent-manifests: this option is only used when using the Kubernetes container backend. This option contains a list of Kubernetes manifests, which are created when the app starts, however these manifests are never removed by ShinyProxy. For example the following configuration creates a persistent PersistentVolumeClaim and Secret.

    kubernetes-additional-persistent-manifests:
      - |
        apiVersion: v1
        kind: PersistentVolumeClaim
        metadata:
          name: manifests-pvc
        spec:
          storageClassName: standard
          accessModes:
            - ReadWriteOnce
          resources:
            requests:
              storage: 5Gi
      - |
          apiVersion: v1
          kind: Secret
          metadata:
            name: manifests-secret
          type: Opaque
          data:
            password: cGFzc3dvcmQ=
    

    The resource will be created in the namespace specified in the manifest. If no namespace is provided, ShinyProxy will use the namespace of the app (even if this namespace is changed using kubernetes-pod-patches).

  • Note: the three last options (i.e. kubernetes-pod-patches, kubernetes-additional-manifests and kubernetes-additional-persistent-manifests) support the use of Spring Expression Language. Therefore, you can reference values from the ContainerSpec, Proxy and ProxySpec objects. The following example demonstrates the use of this feature to create a PVC (Persistent Volume Claim) and mount it as home volume into the Shiny app. In addition it adds the id of the proxy as environment variable.

    - id: 01_hello_manifests_expression
      container-image: : openanalytics/shinyproxy-demo
      kubernetes-pod-patches: |
        - op: add
          path: /spec/containers/0/env/-
          value:
              name: PROXY_ID
              value: "#{proxy.id}"
        - op: add
          path: /spec/volumes/-
          value:
            name: "home-dir-pvc-#{proxy.userId}"
            persistentVolumeClaim:
              claimName: "home-dir-pvc-#{proxy.userId}"
        - op: add
          path: /spec/containers/0/volumeMounts/-
          value:
            mountPath: "/home/#{proxy.userId}"
            name: "home-dir-pvc-#{proxy.userId}"
      kubernetes-additional-manifests:
        - |
          apiVersion: v1
          kind: PersistentVolumeClaim
          metadata:
            name: "home-dir-pvc-#{proxy.userId}"
          spec:
            accessModes:
              - ReadWriteOnce
            resources:
              requests:
                storage: 5Gi
    

Environment variables

Besides the environment variables listed in the various authentication backends, ShinyProxy provides the following variables to applications:

  • SHINYPROXY_PUBLIC_PATH: the public path used to access the app. This path conforms to \$CONTEXT_PATH/app_direct/$APP_ID. However, by exposing this path as environment variable to every app, it can be used by the app without depending on a specific name for the path.

Note: some apps require knowledge of the public path, but use a different environment variable. You can easily specify a different environment variable containing the public path. The following example shows the configuration needed for RStudio:

 - id: rstudio
    container-image: openanalytics/shinyproxy-rstudio-ide-demo
    container-env:
      DISABLE_AUTH: true
      WWW_ROOT_PATH: "#{proxy.getRuntimeValue('SHINYPROXY_PUBLIC_PATH')}"

App container labels and annotations

ShinyProxy adds labels and annotations to the containers running the Shiny apps. Since ShinyProxy 2.5.0 these labels and annotations were streamlined to have consistent formatting and to include more information. From this version on we assume these labels and annotations are part of the API provided by ShinyProxy and therefore any changes made to these labels and annotations will be documented in the release notes. This ensures that other applications integrating with ShinyProxy (such as the ShinyProxy Operator) can rely on these labels and annotations. Another prominent use-case is to process this metadata using monitoring and log aggregation tools such as Prometheus and Grafana Loki.

  • openanalytics.eu/sp-instance: the instance-id (see later) of the ShinyProxy that has started this app.
  • openanalytics.eu/sp-proxied-app: indicates that this is a proxied app started by ShinyProxy. This is used to differentiate between ShinyProxy servers and apps.
  • openanalytics.eu/sp-proxy-id: the UUID (Universally unique identifier) of this proxied app.
  • openanalytics.eu/sp-proxy-created-timestamp: the timestamp (in unix epoch format) when the proxied application was started. Note: this is different from the startup timestamp of an application, which indicates the timestamp at which an application is ready to be used.
  • openanalytics.eu/sp-spec-id: the id of the spec of this proxied app.
  • openanalytics.eu/sp-user-groups: the groups the user running this app is a member of, as a comma-separated value. Is always equal to the SHINYPROXY_USERGROUPS environment variable.
  • openanalytics.eu/sp-user-id: the name of the user running this proxied app. Is always equal to the SHINYPROXY_USERNAME environment variable.
  • openanalytics.eu/sp-realm-id: the id of the realm this ShinyProxy server is running in. The realm of an ShinyProxy server can be specified using the proxy.realm-id property in the YAML configuration file or by specifying the PROXY_REALM_ID environment variable. This value is automatically populated by the ShinyProxy Operator.

Note: the ShinyProxy instance id is calculated by ShinyProxy on startup. It is a (SHA-1) hash of a canonicalized version of the ShinyProxy configuration (i.e. the application.yml file). Using a canonical version of the configuration file ensures that the instance id (i.e. the hash) only changes whenever the configuration of ShinyProxy effectively changes. For example, it does not take whitespace, comments, quotes and ordering of properties into account. This value is logged at startup of ShinyProxy.

Example of the information attached to a Docker container:

$ docker inspect 30390e88419f | jq ".[0].Config.Labels"
{
  "openanalytics.eu/sp-instance": "d94112aa0085efea18736d9939ef9ee42ede54a0",
  "openanalytics.eu/sp-proxied-app": "true",
  "openanalytics.eu/sp-proxy-created-timestamp": "1614072237348",
  "openanalytics.eu/sp-proxy-id": "776c684d-035d-4d41-8fb6-6038725dd395",
  "openanalytics.eu/sp-spec-id": "rstudio",
  "openanalytics.eu/sp-user-groups": "ADMIN,GROUP1",
  "openanalytics.eu/sp-user-id": "jack",
  "...": "..."
}

When Kubernetes is used as container backend, not all properties of the above list are set as labels. To be precise, only the openanalytics.eu/sp-instance and openanalytics.eu/sp-proxied-app properties are specified as labels. All other properties are set as annotations. There are two reasons for this: 1) attaching many labels to a Kubernetes Pod causes more load on the Kubernetes API server and 2) labels are only allowed to contain a very small character set, therefore it would be impossible to store usernames, e-mail addresses etc. Nevertheless the two values set as labels should be enough to query the Kubernetes API for ShinyProxy applications started by a specific ShinyProxy server.

Example of the information attached to a pod:

$ kubectl get pod/sp-pod-df1cfe3b-cab0-4c82-b07d-813e7fc75f2d -o jsonpath='{.metadata.labels}' | jq
{
  "app": "df1cfe3b-cab0-4c82-b07d-813e7fc75f2d",
  "openanalytics.eu/sp-instance": "b13acc06aef677c8180a0fa515404aabe37a17c4",
  "openanalytics.eu/sp-proxied-app": "true"
}
$ kubectl get pod/sp-pod-df1cfe3b-cab0-4c82-b07d-813e7fc75f2d -o jsonpath='{.metadata.annotations}' | jq
{
  "openanalytics.eu/sp-proxy-created-timestamp": "1614070894835",
  "openanalytics.eu/sp-proxy-id": "25347649-7178-4d80-8073-04701d961ef5",
  "openanalytics.eu/sp-spec-id": "rstudio",
  "openanalytics.eu/sp-user-groups": "ADMIN,GROUP1",
  "openanalytics.eu/sp-user-id": "jack"
}

Logging

Basic settings

By default, information about various application events will be logged into the standard output stream of the ShinyProxy process. If you are running ShinyProxy in a console or shell, you will see the logging appear there.

In addition, you can also send the logging to a file, using the logging.file.name setting:

logging:
  file:
    name: shinyproxy.log

This file will be ‘rolled over’ on two events:

  • When the file reaches a size of 10MB
  • When a new day begins

Rollover means that the existing log file will be archived (e.g. shinyproxy.log.yyyy-MM-dd.0.gz) and a new file will be created to log into. You can change the maximum file size using the logging.file.max-size setting:

logging:
  file:
    max-size: 50MB

The amount of information being logged is also configurable. ShinyProxy and its underlying components emit logging information of various ‘levels’, the most important ones being ERROR, WARN, INFO and DEBUG. By default, the level being logged is INFO which includes all levels above it.

You can change the log level using the logging.level setting:

logging:
  level:
    org.springframework.security.ldap: DEBUG

The setting can be made for specific components (in the example above, org.springframework.security.ldap) or for the whole application by using the name root:

logging:
  level:
    root: DEBUG

Some of the important components of ShinyProxy include:

  • eu.openanalytics: the code relating to ShinyProxy itself
  • io.undertow: the HTTP server component
  • com.spotify.docker: the component for controlling docker containers
  • io.fabric8.kubernetes: the component for controlling Kubernetes pods
  • org.springframework: the framework dealing with many different aspects, such as security

Advanced settings

Instead of using the settings described above, you can also provide a completely custom configuration. ShinyProxy uses the Logback logging framework, which can be customized in many ways. To do this, create an XML file and specify it with the logging.config setting:

logging:
  config: logback.xml

An example logback config is given below. For more information, see Logback appenders.

<configuration>
  <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
    <file>shinyproxy.log</file>
    <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
      <fileNamePattern>logFile.%d{yyyy-MM-dd}.log</fileNamePattern>
      <maxHistory>30</maxHistory>
      <totalSizeCap>3GB</totalSizeCap>
    </rollingPolicy>

    <encoder>
      <pattern>%-4relative [%thread] %-5level %logger{35} - %msg%n</pattern>
    </encoder>
  </appender>

  <root level="INFO">
    <appender-ref ref="FILE" />
  </root>
</configuration>

Request dumping

In some cases, you may want to obtain detailed information about all incoming requests (and outgoing responses). For this scenario, the following setting can be enabled:

logging:
  requestdump: true

By toggling this setting on, the Undertow RequestDumpingHandler will be enabled, and verbose request information will be printed to the application log, including headers, cookies and body.

Application Container Logs

ShinyProxy can be configured to store log files of running containers. These log files contain the stdout and stderr output of the R process running the Shiny app.

local file back-end

To enable the storing of container log files, use the following configuration:

proxy:
  container-log-path: ./container-logs

If enabled, ShinyProxy will create log files in the configured directory, using the following naming pattern:

<specId>_<proxyId>_<startupTime>_stdout.log
<specId>_<proxyId>_<startupTime>_stderr.log

s3 back-end

It is also possible to store the container log files in an S3 back-end using the following settings:

proxy:
  container-log-path: s3://bucketName/path/to/store/at
  container-log-s3-access-key: ...
  container-log-s3-access-secret: ...

Optionally one can also set

  • container-log-s3-sse: default false
  • container-log-s3-endpoint: default https://s3-eu-west-1.amazonaws.com

See the Amazon docs for more information on server-side encryption.

Note: on a Kubernetes container back-end, the output for the logs is buffered at 1Mb; if the output stays below the 1 Mb buffer until the container stops, it will be force-flushed when the container stops.

Reporting Issues

ShinyProxy can be configured to allow end users to send feedback or bug reports of the Shiny applications that are deployed on ShinyProxy.

In order to do so, a proxy.support block needs to be added to the application.yml file:

proxy:
  support:
    mail-to-address: some.user@somedomain.com

When ShinyProxy is started with this configuration, an extra ‘Report Issue’ link will be displayed in the navigation bar. Users can click the link to send custom messages and these messages will be sent to the configured e-mail address (some.user@somedomain.com in the example configuration). The information that will be sent to the support address will include:

  • user name: name of the authenticated user that sends the issue
  • app name: only if user was running an app when he clicked the ‘Report Issue’ button
  • location: the URL in the user’s browser
  • custom message: the message that the user entered in the Report Issue dialog

These log files will be attached to support mails automatically.

The configuration of the mail server settings can be done in a separate spring.mail block, e.g.

spring:
  mail:
    host: smtp.gmail.com
    # TLS: 587 SSL: 465 Plain: 25
    port: 465
    username: my_username
    password: my_password
    properties:
      # For StartTLS
      mail.smtp.starttls.enable: true
      # For SSL
      #mail.smtp.socketFactory.class: javax.net.ssl.SSLSocketFactory

More information on these properties can be found here.

Note:

Optionally, one can also define the ‘from’ address to be used for the e-mail messages in the proxy.support block:

proxy:
  support:
    mail-from-address: issues@shinyproxy.io

Usage Statistics

ShinyProxy supports the tracking and saving of usage statistics to see which apps are popular and by whom these are used.

Currently, the following backends are supported:

More details on setting up databases for usage statistics tracking are given in a dedicated Usage Statistics section.

Proxy API

ShinyProxy exposes an API, a set of endpoints that can be called programmatically to launch, retrieve, and stop proxies. This is useful, for example, if you want to embed ShinyProxy into a larger web application, and you want to control the lifecycle of proxies in the context of this larger application.

To get an overview and detailed information about the API and its components, have a look at this page.

Endpoint URLs

In common use ShinyProxy offers the Shiny app under two URLs:

  • /app/<app_name>: used in the standard ShinyProxy interface which contains a toolbar and makes the Shiny app available via an iframe
  • /app_direct/<app_name>: can be used as an alternative to directly access the Shiny app without the iframe and navigation bar offered via /app/<app_name>

Note that within the iframe that is displayed under /app/<app_name>, the /app_direct/<app_name> link is used to display the Shiny app.

For embedding apps via the API, a different approach should be taken, namely via /api/route as documented in the example on Github.

Authorization

By default, the ShinyProxy API uses the same authentication framework as the web interface, as configured in the authentication: ... setting. This may work well in simple cases (e.g. when using authentication: none), but has limitations. For a better integration experience, you can instead use OAuth2. This can be enabled by making the following configuration:

proxy:
  oauth2:
    resource-id: your-resource-id
    jwks-url: your-jwks-url

For this to work, you must register your ShinyProxy installation as a resource with an OAuth2 provider, such as Auth0. The resource-id is the unique identifier you give it during registration, and the jwks-url is a provider-specific URL. E.g. for Auth0, the URL is https://your-tenant-name.auth0.com/.well-known/jwks.json.

You can then access the API by including a valid OAuth2 access token in your requests. For an example, see example 08-api-oauth2.

Security

  • server.secure-cookies: when set to true, ShinyProxy sets the secure flag on all cookies (i.e. the session cookie). Defaults to false. If you are using HTTPS we highly recommend enabling this option.

  • server.frame-options: allows to specify the value for the X-Frame-Options header. Following values are allowed:

    • disable: (default) no header is added, ShinyProxy is allowed to be included in (i)frames on any webpage.

    • deny: (not recommended), prohibit any kind of framing, however, this breaks normal operating of ShinyProxy (because it uses an iframe itself).

    • sameorigin: (recommended) allows framing only if the parent and child have the same origin. This should work in all cases where you do not embed ShinyProxy in another page.

    • allow-from (recommended if you embed ShinyProxy in another page): limits embedding of ShinyProxy to specific websites. For example to allow embedding ShinyProxy in example.com:

      server:
        frame-options: allow-from https://example.com
      
  • proxy.same-site-cookie: controls the SameSite policy for cookies. Following values are supported:

    • None: provides no protection against CSRF attacks, therefore this is not recommended. When this value is used, you must set server.secure-cookies to true, otherwise browsers could block access to the cookies.
    • Lax: (recommended) provides protection against CSRF attacks.
    • Strict: provides protection against CSRF attacks. However, note that this will block cookies when a user clicks on a link to ShinyProxy. For example, assume the user is logged-in on ShinyProxy on https://shinyproxy.local and there is a link to https://shinyproxy.local on your intranet at https://myintranet.local. When the user clicks on this link, they will not be logged-in. Therefore we do not recommended this option, without being aware of the consequences.

    Note: before ShinyProxy 2.6.0, the session cookie would be created without a SameSite policy. When using Redis for session persistence, the default policy was Lax. Starting with ShinyProxy 2.6.0, the default policy is always Lax. This should not be a breaking change as browsers will or are already interpreting the missing policy as Lax anyway.

    Note: when using SAML this option may need to be changed to None, see the section about SAML.

Miscellaneous Settings

Long sessions

By default a session of the HTTP engine embedded in ShinyProxy will time out after 30 minutes. To change this e.g. to 60 minutes, one can use the servlet.session.timeout and set it to 3600 in the application.yml file (measured in seconds):

server:
  servlet.session.timeout: 3600

If there should be no timeout, use 0, but beware of session leaks in that case.

Session Persistence

Session persistence in ShinyProxy is supported by using Redis. This means that the user is still logged-in into ShinyProxy when restarting ShinyProxy (i.e. after changing the configuration).

The exact configuration depends on the type of the Redis deployment. The most basic configuration (e.g. using a single Redis instance) is:

spring:
  session:
    store-type: redis
  redis:
    host: redis
    password: redis_password

When running a Redis cluster the configuration should look like:

spring:
  session:
    store-type: redis
  redis:
    cluster:
      # add all hosts of the cluster here (including the port)
      nodes: host1:port1, host2:port2, host3:port3

Finally, when running Redis using SSL/TLS make sure this is reflected in the ShinyProxy configuration:

spring:
  session:
    store-type: redis
  redis:
    ssl: true

Note that ShinyProxy will hang when this option is not specified but you are using a SSL/TLS enabled Redis server.

Common Issues

Session Persistence and AWS ElastiCache

AWS provides a hosted Redis server called ElastiCache. It is possible to use this service for enabling session persistence in ShinyProxy. However, you have to make two changes:

  1. change your ShinyProxy configuration to include:

    spring:
      session:
        redis:
          configure-action: none
    
  2. enable Redis keyspace notifications in ElastiCache. Please follow the instructions in the official documentation of AWS.

When not configuring part one correctly, you will experience the following error on startup of ShinyProxy:

org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'enableRedisKeyspaceNotificationsInitializer' defined in class path resource [org/springframework/boot/autoconfigure/session/RedisSessionConfiguration$SpringBootRedisHttpSessionConfiguration.class]: Invocation of init method failed; nested exception is org.springframework.data.redis.RedisSystemException: Error in execution; nested exception is io.lettuce.core.RedisCommandExecutionException: ERR unknown command 'CONFIG'
...
Caused by: org.springframework.data.redis.RedisSystemException: Error in execution; nested exception is io.lettuce.core.RedisCommandExecutionException: ERR unknown command 'CONFIG'
...
Caused by: io.lettuce.core.RedisCommandExecutionException: ERR unknown command 'CONFIG'
...

The second part ensures that AWS sends keyspace events to ShinyProxy. Without this enabled, ShinyProxy is unable to expire sessions of users. This effectively means that users would stay logged in indefinitely. Hence it is important to correctly configure this feature.