Configuration

Overview

The server configuration is mainly done in a file named application.yml. ShinyProxy includes the following fallback configuration file, in case no configuration file is specified:

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: simple
  admin-groups: scientists
  # Example: 'simple' authentication configuration
  users:
    - name: jack
      password: password
      groups: scientists
    - name: jeff
      password: password
      groups: mathematicians
  docker:
    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

The above configuration can be used using the Docker backend and allows to login using the following credentials:

  • username: jack and password: password
  • username: jeff and password: password

Note: versions of ShinyProxy before version 3.0.2 might include fallback configuration that uses the LDAP authentication backend. In this case you can login using username: tesla and password: password.

The configuration can be changed by providing a custom application.yml. The correct location depends on the deployment method:

  • JAR: create an application.yml file in the same directory as the JAR file (and start the JAR file from that same directory)
  • Docker: when using the Official Docker image copy or mount an application.yml file into /opt/shinyproxy/application.yml
  • RPM: create a file /etc/shinyproxy/application.yml
  • DEB: create a file /etc/shinyproxy/application.yml
  • Kubernetes: use the ShinyProxy Operator

As soon as you provide a custom configuration file, the fallback configuration file is no longer loaded or used. Therefore, it can be convenient to use the above configuration as a starting point. ShinyProxy requires that the configuration file has the extension .yml and not .yaml. The latter wil not work. Advanced configuration examples are available on GitHub. Finally, see the demos page for a list of examples on deploying specific apps.

The configuration of ShinyProxy uses the yaml format. Some Tips & Tricks for working with the ShinyProxy configuration file:

  • yaml uses whitespaces to structure the config file, double check the indentation when something is not working

  • this documentation refers to properties using a dot notation, every dot represents a new object in hierarchy. E.g. when the documentation mentions the proxy.docker.url property, you have to write the following block in your config:

    proxy:
      docker:
        url: example
    
  • most of the ShinyProxy specific configuration should be placed under the proxy property. However, some configuration options must be placed outside the proxy property, such as the server.servlet.context-path property:

    proxy:
      docker:
        url: example
    server:
      servlet:
        context-path: /my_path
    
  • this documentation tries to always specify the full path of a property, e.g. starting with proxy., so that it is always clear where this property should be placed

  • when adding new configuration properties, make sure to merge existing objects. E.g. if you already have the following config:

    proxy:
      container-backend: docker
      docker:
        url: example
    

    and you want to add the property proxy.title, make sure to put title under the existing proxy property:

    proxy:
      container-backend: docker
      docker:
        url: example
      title: my title
    

    To conclude, this will not work:

    # This configuration does not work!
    proxy:
      container-backend: docker
      docker:
        url: example
    
    proxy:
      title: my title
    
  • properties can be specified using both kebab-case and CamelCase. For example the property to change the container backend can be written as:

    proxy:
        # kebab-case
        container-backend: docker
    

    or

    proxy:
        # camel case
        containerBackend: docker
    
  • many ShinyProxy config properties require you to specify multiple values, i.e. a list of values. All properties support four formats:

    • Single option: when specifying only a single option, you can place this after the name of the property, e.g.:

      proxy:
        admin-groups: myAdminGroup
      
    • JSON Array notation: place the values after the name of the property, enclosed in square brackets ([]) and separated by commas (,):

      proxy:
        admin-groups: [myAdminGroup, mySecondAdminGroup]
      
    • YAML Array notation: place the values on a separate line, prefixed with a dash -:

      proxy:
        admin-groups:
          - myAdminGroup
          - mySecondAdminGroup
      
    • Comma separated: place the values after the name of the property, separated by commas (,). Try to avoid this notation, since it’s more error-prone:

      proxy:
        admin-groups: myAdminGroup, mySecondAdminGroup
      
  • every configuration option can be specified using environment variables. Transform the key of the property, by changing dots (.) and dashes (-) into underscores (_) and making it upper-case. E.g. proxy.container-backend becomes PROXY_CONTAINER_BACKEND. Multiple values can be specified by appending _ and an (increasing) number to the environment variable. E.g. to specify multiple proxy.admin-groups, use:

    PROXY_ADMIN_GROUPS_0="myAdminGroup"
    PROXY_ADMIN_GROUPS_1="mySecondAdminGroup"
    
  • it’s possible to directly use environment variables in the value of a property, e.g. to use the variable MY_SHINYPROXY_TITLE:

    proxy:
      title: ${MY_SHINYPROXY_TITLE}
    
  • it’s possible to configure ShinyProxy using multiple config files, by listing additional files to load in the spring.config.import property. For example:

    # filename: application.yml
    spring:
      config:
        import:
         - users.yml
    proxy:
      authentication: simple
    
    # filename: users.yml
    proxy:
      users:
        - name: jack
          password: password
          groups: scientists
        - name: jeff
          password: password
          groups: mathematicians
    

    Note: it’s not possible to specify the same option in multiple files, for example you cannot specify multiple specs (using proxy.specs) in multiple files.

  • the location of the config file can be overriden by configuring the spring.config.location property in of the following ways:

    • as Java system property: java -Dspring.config.location=myfile.yml -jar shinyproxy.jar
    • as environment variable: SPRING_CONFIG_LOCATION=myfile.yml java -jar shinyproxy.jar
    • as an argument: java -jar shinyproxy.jar --spring.config.location=myfile.yml

    Note: Although Spring may support other ways of specifying the config location, these are the only methods supported by ShinyProxy. Using another way might cause the fallback config to be loaded.

The next sections explain all available configuration options.

General

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

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

  • proxy.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://)

  • proxy.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. Can be overriden per app. Do not add file:// before the path.

  • proxy.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. Using FirstApp redirects the user to the first app accessible to the user. Using SingleApp redirects the user to the only app the user has access to, or to the main page when the user has access to multiple apps. Other typical values are /app/<app-name> or /app_direct/<app-name> which allows to immediately land on a (single) Shiny app. Since ShinyProxy 3.1.0 the context-path is automatically added to the URL.

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

  • proxy.heartbeat-timeout: the time in milliseconds that the server must receive no heartbeat in order to stop the app. The default value is 60000 (60 seconds). Apps automatically send heartbeats while the app is opened in a browser. Therefore, by default ShinyProxy stops the app if the browser tab has been closed for 60 seconds.

    Note:

    • apps can override this using the heartbeat-timeout property.
    • it is possible to set this value to -1 in order to disable this feature (previous version of ShinyProxy used the heartbeat-enabled option, this option has been removed).
    • if the proxy.heartbeat-timeout is longer than the session time of a user, the app will be closed when the session of the user expires (so before the longer heartbeat-timeout value). You can prevent this by setting the default-stop-proxy-on-logout option.
    • see the examples on GitHub for running apps in the background.
  • proxy.bind-address: a hostname or IP address to be used as bind address for ShinyProxy; default value is 0.0.0.0 (all interfaces);

  • proxy.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.

  • proxy.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;

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

    proxy:
      template-groups:
        - id: tools
          properties:
            display-name: Tools
            maintainer: Tesla
        - id: visualizations
          properties:
            display-name: Visualizations
            maintainer: Einstein
        - id: documentation
          properties:
            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.

  • proxy.authentication: authentication method; one of ldap, openid, saml, simple or none; see the relevant section below for configuration details regarding each of these authentication methods;

  • admin-groups: list of 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. Example:

    admin-groups: [group1, group2]
    # or:
    admin-groups:
      - group1
      - group2
    
  • admin-users: list of users that have access to the administrative interface of ShinyProxy.

    admin-users: [user1, user2]
    # or:
    admin-users:
      - user1
      - user
    

    Note:

    • this option uses the usernames as shown in the navigation bar (or in the SHINYPROXY_USERNAME environment variable).
    • this option works in addition to the admin-groups option, that means a user must be listed in admin-users or be member of one of the groups listed in admin-groups to have admin access. So they do not need to be part of both the group and the users list.

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

  • proxy.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.

  • proxy.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. See the examples on GitHub for for running apps in the background.

  • proxy.default-max-instances: see the User Interface page

  • proxy.default-webSocket-reconnection-mode see the User Interface page

  • proxy.my-apps-mode: see the User Interface page

  • proxy.default-cache-headers-mode: configures the default method of handling cache headers in the HTTP responses sent by apps. See the app specific property cache-headers-mode for more information.

  • proxy.max-total-instances: limits the maximum number of app instances that can run at the same time. Applies to all apps of all users, including when users run multiple instances of the same app. When the limit is reached and an app is started, the message The server does not have enough capacity to start this app, please try again later. is shown. This can be combined with the per app limit, both limits will be checked when an app starts.

  • proxy.notification-message: (optional), displays the provided message to every user on the Main Page, supports HTML

  • 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

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.

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

  • proxy.ldap.url: the LDAP connection string, composed of the URL and base DN of the LDAP directory;
  • proxy.ldap.user-dn-pattern: pattern of the distinguished name for a user. Use this if all your users are in a single LDAP location;
  • proxy.ldap.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;
  • proxy.ldap.user-search-base: search base to search for users. Only used if user-search-filter is set;
  • proxy.ldap.group-search-filter: LDAP filter used to search for group memberships;
  • proxy.ldap.group-search-base: search base to search for groups. Only used if group-search-filter is set;
  • proxy.ldap.manager-dn: the distinguished name of the user used to bind to the LDAP directory; leave empty if the initial bind is anonymous;
  • proxy.ldap.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).

Note:

  • 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:

proxy:
  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

Note:

  • 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

Note:

  • 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.

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.

When using OIDC, ShinyProxy will act as the Client or Relying Party (RP). The idea is to integrate ShinyProxy with an OpenID Provider or Identity Provider (IDP) (sometimes also called Authorization server). The IDP contains the list of users, including a password and additional information. In case of ShinyProxy, the log-in process is as follows:

  1. a user browses to your ShinyProxy server (e.g. at https://shinyproxy-demo.local)
  2. ShinyProxy redirects the user to the IDP
  3. the user has to log-in on the website of the IDP
  4. the IDP redirects back to ShinyProxy
  5. the ShinyProxy server sends a HTTP request to the IDP, requesting for tokens
  6. the user is logged-in into ShinyProxy

In OIDC this flow is called the Authorization Code Grant. The request sent in step 5, is sent from the ShinyProxy server, this is invisible to the user. However, this means that ShinyProxy must be able to communicate with the IDP.

The IDP will send three tokens to ShinyProxy:

  • ID Token: this is a short-lived token and contains information, called claims or attributes about the user. This token cannot be refreshed. The OIDC spec requires that this token is a JSON Web Token (JWT).
  • Access Token: this is a short-lived token that provides access to protected resources. In the case of ShinyProxy it’s used to authorize (i.e. check) that a user has access to ShinyProxy itself. This token can be refreshed by using the refresh token. The format of this token is not fixed and varies between IDPs, therefore ShinyProxy does not parse user-information from this token.
  • Refresh Token: this a token that allows refreshing the access token. Since the access token is short-lived, ShinyProxy must request new access tokens in order to know that the user is still authorized to access ShinyProxy.

Once a user has logged in, ShinyProxy keeps refreshing the access token using the refresh token. This ensures that the user still has access to ShinyProxy. For example, if the user is logged into multiple applications and performs a logout at the IDP, the user will automatically get logged out of ShinyProxy (once the current access token has expired). This also guarantees that the maximum session times defined in the IDP are followed by ShinyProxy.

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

  1. Register your ShinyProxy installation in your IDP
  2. Obtain the configuration parameters for your IDP and configure ShinyProxy
  3. (Optional) For group-based authorization, add a custom claim in the OIDC ID Token

Registering ShinyProxy in the IDP

Since there are many IDPs systems and the configuration depends on the IDP used, this documentation cannot explain the exact steps for your situation. However, we provide a few examples for some common IDPs.

When configuring the IDP (i.e. creating a new OIDC Client), you’ll need to provide a callback URL or a redirect URL. For ShinyProxy this is:

https://shinyproxy-demo.local/login/oauth2/code/shinyproxy

Make sure to replace https://shinyproxy-demo.local with the URL of your ShinyProxy server.

Defining an incorrect callback URL will result in authentication errors in ShinyProxy. If your ShinyProxy is running at a non-default port (i.e. another port than 80 for HTTP and port 443 for HTTPS), you need to include the port number in the URL:

https://shinyproxy-demo.local:8080/login/oauth2/code/shinyproxy

It is important to make the client “private” or “confidential”, this means a “client secret” is needed to be able to use the client. This is sometimes also called “enable client authentication”.

Configuring ShinyProxy

Once the IDP is configured, you can configure ShinyProxy. This section explains the minimal set of configuration options required for the authentication to work. The full list of available options are explained below.

In order to configure ShinyProxy, you’ll need to retrieve the following information from your IDP:

  • 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.

The Client ID is just an identifier and may be a simple string (e.g. just shinyproxy), it is not a secret value (it’s visible in the URL while authenticating). As the name implies, Client Secret is a secret value, make sure to not expose this anywhere.

Some IDPs might simply provide you with an OpenID configuration in JSON format, sometimes available at an URL ending with .well-known/openid-configuration. You can manually retrieve the URLs from this file:

  • Auth Endpoint URL: authorization_endpoint
  • Token Endpoint URL: token_endpoint
  • JSON Web Key Set (jwks) URL: jwks_uri

Once you have received this information, you can configure ShinyProxy by adding the above information into the configuration:

proxy:
  authentication: openid
  openid:
    auth-url:  ...
    token-url: ...
    jwks-url: ...
    client-id:  ...
    client-secret:  ...

In order to protect the client secret, you can specify it as an environment variable.

Group-based authorization

While OIDC specifies an authentication flow, it has no notion of “user groups”, “roles” or “permission levels”. To achieve this in ShinyProxy, your IDP needs to send this information as a custom claim in the ID token. Many IDPs automatically send some default claims such as sub, email, given_name, family_name.

Adding a custom claim to an ID Token is specific to each OIDC provider. See the provider specific examples. The value of the group claim should ideally be a JSON array, although ShinyProxy is more flexible on the format. Once your IDP sends a custom claim, you can configure ShinyProxy to use it:

proxy:
    authentication: openid
    openid:
        roles-claim: name_of_the_claim

Configuration reference

This section lists all configuration options for OpenID:

  • proxy.openid.auth-url: required, provided by the IDP (see above)

  • proxy.openid.token-url: required, provided by the IDP (see above)

  • proxy.openid.jwks-url: required, provided by the IDP (see above)

  • proxy.openid.client-id: required, the ID of the OpenID client, provided by the IDP (see above)

  • proxy.openid.client-secret: required, the secret of the OpenID client, provided by the IDP (see above)

  • proxy.openid.logout-url: URL to be used for logging out of the OpenId session on the OpenId provider back-end.

    Note:

    • some IDPs send the user back to ShinyProxy after logging out. Depending on the configuration of the IDP, the user may be automatically logged-in again. ShinyProxy contains an endpoint /logout-success, showing a message that the user was successfully logged out. You can configure the IDP to redirect to this endpoint, so that the user is not automatically logged-in again. For example, some IDPs support specifying the parameter returnTo or post_logout_redirect_uri in the logout-url:

      proxy:
        openid:
          logout-url: https://idp.local/logout?returnTo=http%3A%2F%2Fshinyproxy-demo.local/logout-success
      
    • 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://idp.local/logout?id_token_hint=#{oidcUser.idToken.tokenValue}
      
    • some OpenID providers require you to include the client_id in the URL:

      proxy:
          openid:
             logout-url: https://idp.local/logout?returnTo=http%3A%2F%2Fshinyproxy-demo.local&client_id=YOUR_CLIENT_ID
      
    • some OpenID providers require you to add the redirect URL to an allow-list, refer to the documentation of the provider.

  • proxy.openid.username-attribute: name of the attribute to be used as the user’s name. Common attributes are email (default), name, sub, nickname, preferred_username; a complete list is provided in the OpenId Connect specification.

  • proxy.openid.with-pkce: (boolean) whether to use PKCE

  • proxy.openid.userinfo-url: (optional) the URL of the OpenID userinfo service. Can be used to provide claims using the userinfo endpoint instead of the ID token.

  • proxy.openid.scopes: (optional) list of additional scopes to request. Some providers (such as Azure B2C) require you to add the offline_access scope in order to get a refresh token (e.g. in order to pass the refresh token to a container). ShinyProxy always add the scopes openid and email.

  • proxy.openid.ignore-session-expire: (boolean) whether to ignore OIDC session expiration. By default (i.e. when this option is false or not specified) ShinyProxy expires the session in ShinyProxy when the OIDC session expires (or is revoked/logged out).

  • proxy.openid.jwks-signature-algorithm: (optional) the signature algorithm used (specified as string), default is RS256, see Spring docs for a list of supported values.

  • proxy.openid.client-authentication-method: (optional) the method to use to authenticate the client when fetching the token. Supported values:

    • client_secret_basic (used by default when a client-secret is provided)
    • client_secret_post
    • none (used by default when no client-secret is provided)

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

Troubleshooting

See the OpenID section on the troubleshooting page.

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:

  • proxy.saml.idp-metadata-url: the URL where the IDP’s metadata can be retrieved
  • proxy.saml.app-entity-id: the entity ID of this application, must match the AudienceRestriction in the SAML assertion received from the IDP
  • proxy.saml.app-base-url: the application’s base URL. Some IDPs require that you specify valid callback URLs. This must match a valid callback URL.
  • proxy.saml.name-attribute: the name of the attribute in the SAML assertion containing the username
  • proxy.saml.roles-attribute: the name of the attribute in the SAML assertion containing the user roles
  • proxy.saml.logout-url: optional URL to which the user will be redirected when they logout
  • proxy.saml.logout-method: can be local (default) or saml. See below for more information.
  • proxy.saml.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).
  • proxy.saml.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.
  • proxy.saml.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:

  • proxy.saml.keystore: path to the JKS keystore
  • proxy.saml.keystore-password: password to access the keystore. If omitted, proxy.saml.encryption-cert-password will be used instead
  • proxy.saml.encryption-cert-name: name of the certificate whose public key the SAML Assertion is encrypted with
  • proxy.saml.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 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.

  • 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.

    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

Web Service Based Authentication

Amongst the different authentication methods, ShinyProxy also offers the possibility to use a custom web service that indicates whether the authentication is successful.

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.

Note:

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

Authorization

Since ShinyProxy 3.1.0, authorization using groups is supported. This requires that the webservice returns a JSON response, containing a list of groups. Next, a SpEL expression must be provided using the proxy.webservice.groups-expression property. This expression can use the object json, which contains the (parsed) JSON response. The result of this expression should be the list of groups.

For example, when the webservice returns the following response:

{
  "data": {
    "uuid": "30cc6338-cbc0-4b12-bd31-4cc4ddd2d5e5",
    "groups": [
      "scientists",
      "mathematicians"
    ]
  }
}

you can use the following ShinyProxy config:

proxy:
  webservice:
    groups-expression: "#{json.get('data').get('groups')}"

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.

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

Before ShinyPRoxy 3.0.0, the groups should be seperated by commas (which is still supported in ShinyProxy 3.0.1):

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
  • AWS Elastic Container Service (ECS)

The backend can be configured using

proxy:
  container-backend: docker

The proxy.container-backend property can be one of docker (default), docker-swarm, kubernetes or ecs. The specific configuration of 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:

  • proxy.docker.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;
  • proxy.docker.url: URL and port on which to connect to the docker daemon, if not specified ShinyProxy tries to connect using the Unix socket of the Docker daemon.
  • proxy.docker.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);
  • proxy.docker.privileged: run all containers with extended privileges (true) or not (false; default value);
  • proxy.docker.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.).
  • proxy.docker.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:
  • proxy.docker.image-pull-policy: the pull policy for images; the default value is IfNotPresent; valid options are Never, IfNotPresent and Always. See the app settings for authenticating with the registry.
  • proxy.docker.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 Docker Swarm backend supports all configuration properties of the Docker backend, in addition the following properties are supported:

  • proxy.docker.service-wait-time: the time ShinyProxy waits for a Docker Swarm Service to become ready (in milliseconds). By default this is 60 seconds. This option should be changed when your Service takes more than 60 seconds to startup. For example if you use very large images.

Note:

  • when proxy.docker.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; valid options are Never, IfNotPresent and Always
  • 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 a list of 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. For example:
    node-selector: "kubernetes.io/os=linux,kubernetes.io/arch=amd64"
    
  • proxy.kubernetes.debug-patches: boolean (true/false) controlling whether the kubernetes-pod-patches feature should produce debug output. If enabled the specification of pods is printed to the log before a patch is applied and after a patch is applied. In addition, it logs the manifests generated by the additional manifests feature.
  • 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 takes 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 proxy.kubernetes.internal-networking is true, no ports will be allocated per proxy and the proxy target URLs will use the Pod IP.

ECS

In order to use AWS ECS as back-end, set proxy.container-backend to ecs. Note that ShinyProxy only supports using ECS with Fargate. The configuration of the back-end can be done using the following properties:

  • proxy.ecs.name: (required) the name of the ECS cluster to use
  • proxy.ecs.region: (required) the region of the ECS cluster, e.g. eu-west-1
  • proxy.ecs.subnets: (required) list of subnets ids to run the apps in (subnets must be created upfront, in the region of the cluster). Specify the subnet id, e.g. subnet-0123456789abcdefghij
  • proxy.ecs.security-groups: (required) list of security group ids to assign to the app, the security group must allow ShinyProxy to access the app using the specific port of the app (e.g. 3838 for Shiny apps). Specify the security group id, e.g. sg-0123456789abcdefghj
  • proxy.ecs.service-wait-time: (optional) the time ShinyProxy waits for an ECS service to become ready (in milliseconds). By default this is 3 minutes. This option should be changed when your service takes more than 3 minutes to startup.
  • proxy.ecs.enable-cloudwatch: (optional) configures ECS to send application logs to CloudWatch. In most cases the other CloudWatch properties are not needed. Requires additional setup of IAM policies, see AWS documentation.
  • proxy.ecs.enable-cloudwatch-group-prefix: (optional) a prefix for the log group created by ShinyProxy. By default the prefix is /ecs/. Every app gets its own log group, starting with the prefix, followed by sp- and the id of the app.
  • proxy.ecs.enable-cloudwatch-region: (optional) the region used for storing the logs, defaults to the region specified in proxy.ecs.region
  • proxy.ecs.enable-cloudwatch-stream-prefix: prefix for the name of the CloudWatch stream, defaults to ecs.

See this example for configuring the AWS infrastructure in order to run ShinyProxy on ECS.

Apps

Every single (Shiny) app served by ShinyProxy has its own configuration block under proxy.specs:

proxy:
  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]

Most type of applications can be configured using only a few lines, since all properties have proper default values. Nevertheless, the configuration can be extended to support more advances use cases.

See the demos page for examples on configuring common applications.

Common properties

The configuration properties listed in this section, are supported by most container back-ends. See the next sections for properties or values that can only be used by specific container backends.

  • id: required, the identifier of the application, this is used in the URL of the app
  • container-image: required, name of the docker image to use, this image can be stored on a registry
  • display-name: the name that is displayed for the app on the ShinyProxy landing page as well as in the browser tab (once the application is opened). The id is used when no display-name is provided.
  • container-cmd: the command (as a list of arguments) that will be run when the Docker container is launched. If no command is provided, the container runtime automatically uses the command included in the container (i.e. using CMD instruction in a Dockerfile). In the case of Shiny, this is typically the R command, followed by the R code that launches the Shiny app:
    proxy:
      specs:
        - id: 01_hello
          container-cmd: ["R", "-e", "shinyproxy::run_01_hello()"]
          container-image: openanalytics/shinyproxy-demo
    
  • container-memory-request:
    • Docker: equivalent with --memory-reservation
    • Docker Swarm: equivalent with --reserve-memory
    • Kubernetes: equivalent with spec.containers[].resources.requests.memory, see docs
  • container-memory-limit:
    • Docker: equivalent with --memory
    • Docker Swarm: equivalent with --limit-memory
    • Kubernetes: equivalent with spec.containers[].resources.limits.memory, see docs
  • container-cpu-request:
    • Docker: does nothing
    • Docker Swarm: equivalent with --reserve-cpu
    • Kubernetes: equivalent with spec.containers[].resources.requests.cpu, see docs
  • container-cpu-limit:
    • Docker: maps to a combination of cpu-period and cpu-quota and is equivalent to the --cpus setting (available since Docker 1.13)
    • Docker Swarm: equivalent with --limit-cpu
    • Kubernetes: equivalent with spec.containers[].resources.limits.cpu, see the docs
  • port: the port on which the app is listening in the container; this setting will override the default port (3838)
  • access-groups: list of groups (e.g. LDAP groups) a user needs to belong to in order to gain access to the app, this field allows to authorize access per app
    proxy:
      specs:
        - id: 01_hello
          container-image: openanalytics/shinyproxy-demo
          access-groups: [scientists, mathematicians]
    
  • access-users: list of users which may access the app
    proxy:
      specs:
        - id: 01_hello
          container-image: openanalytics/shinyproxy-demo
          access-users: [jack, jeff]
    
  • 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.
  • 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:

  • container-privileged: run the container for this app with extended privileges (true) or not (false; default value), only supported for the Docker and Kubernetes backend.
  • container-dns: list of IP addresses that are added as DNS servers, by adding them as server lines in the /etc/resolv.conf file of the container
  • container-env: one or more environment variables specified as map, which are passed to the container.
    container-env:
      VAR1: VALUE1
      VAR2: VALUE2
    
  • container-env-file: a path to a file in which environment variables are specified to be passed to the container
    container-env-file: /path/to/env-file
    
  • container-volumes: list of docker volumes to (bind) mount into the container.
    container-volumes: [ "/host/path1:/container/path1", "/host/path2:/container/path2" ]
    
  • logo-url: the URL to an image that is used as the logo for an application on the main page; this can also be a local file using the file scheme (file://)
  • logo-height: the height of the logo (specified by logo-url), e.g. 50px
  • logo-width: the width of the logo (specified by logo-url), e.g. 50px
  • logo-classes: one or more CSS classes (seperated by a space) to add to the logo (specified by logo-url), e.g. class1 class2
  • logo-style: CSS styling to add to the logo (specified by logo-url), e.g. background-color: red;
  • favicon-path: path to the favicon file to be used for this app, both relative (to the working directory of ShinyProxy) and absolute paths can be used. Overrides the global option for this app. Do not add file:// before the path.
  • 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 stop the app. 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 an 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).
  • 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:
    • see runtime values for a list of labels that are automatically added by ShinyProxy.

    • in order to use / in the key, wrap the key in [], for example:

      - id: myapp
        labels:
          "[openanalytics.eu/mylabel]": myvalue
      
  • 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.
  • always-show-switch-instance: boolean, when enabled, the switch instance modal is shown when clicking on app on the main page. Therefore, before starting a new instance, users first see a list of instances they already have running and users are forced to give the new instance a name. This should be used together with the max-instances property.
  • max-instances: see the User Interface page
  • websocket-reconnection-mode: see the User Interface page
  • shiny-force-full-reload: see the User Interface page
  • parameters: see the dedicated page.
  • track-app-url: boolean, whether the app page should track the URL of the app, i.e. if the URL of the app changes (e.g. because the user clicks a link), the URL visible in the browser will change as well. When the user reloads the page, the app automatically opens the exact same URL as before the reload. Similarly, it is possible to deep link to sub-pages inside the app. This feature works natively with Shiny bookmarking.
  • additional-port-mappings: list of additional mappings to other ports besides the main port. For example:
    - id: myapp
      port: 8080
      additional-port-mappings:
        - name: mypath
          port: 8701
        - name: special_subpath
          port: 8888
          target-path: "#{proxy.getRuntimeValue('SHINYPROXY_PUBLIC_PATH')}special_subpath"
    
    In the above example the following mappings are used:
    • /app/myapp/ maps to container:8080/
    • /app/myapp/mypath/ maps to container:8701/
    • /app/myapp/special_subpath/ maps to container:8888/app_proxy/<proxy_id>/special_subpath/
  • add-default-http-headers: boolean (default is true), whether to add the headers X-SP-UserId and X-SP-UserGroups to every request sent to an app. If enabled, ShinyProxy (by using Undertow feature) guarantees these headers are not spoofed by the client. Can by used by apps to extract the userid and user groups from a request, e.g. when using Container pre-initialization and sharing. See the demo on how to extract the headers using Shiny.
  • http-headers: a map of additional headers to add to every request. These headers are added to the request sent between ShinyProxy and the app. The value can contain a SpEL expression, however, this expression is only resolved during startup of the app.
    - id: myapp
      http-headers:
        my-header: my-static-value
        proxy-id: "#{proxy.id}"
    
    Note: The HTTP standard specifies that only printable ASCII characters are allowed in HTTP headers. Therefore, if a username contains any other character, the app may fail to start. This can be circumvented by encoding the values using base64, see this example.
  • cache-headers-mode: (optional) configures how ShinyProxy must handle cache headers in the HTTP responses sent by apps. This overrides any value of the proxy.default-cache-headers-mode property. This option supports three values:
    • EnforceNoCache: (default) ensures responses from apps are never cached, by adding the Cache-Control: no-cache, no-store, max-age=0, must-revalidate, Expires: 0 and Pragma: no-cache headers. This is the most secure option, since the responses from the app are not cached on the system of the user. In general web applications should not cache sensitive responses (see OWASP), however, ShinyProxy does not know when a page (sent by an app) contains sensitive information and therefore defaults to disable caching for all responses. This ensures the app is secure, even if the developer did not think about adding these headers.
    • Passthrough: cache headers are controlled by the app, ShinyProxy does not add or remove cache headers. This can be used if you know the app uses the correct cache headers for every response. Make sure to validate that your app sends the correct headers, before using this option.
    • EnforceCacheAssets: ensures assets are cached for one day (by first removing the Cache-Control, Expires and Pragma headers and then adding the Cache-Control: max-age=86400 header) and that all other responses from apps are never cached (in the same way as the EnforceNoCache option). ShinyProxy adds the cache header for the following media types:
      • application/javascript
      • text/javascript
      • text/css
      • font/*
      • application/font-woff
      • application/font-woff2
      • application/font-sfnt
      • application/font-tdpfr
  • max-total-instances: limits the maximum number of app instances that can run at the same time. Applies to only this app, but for of all users, including when users run multiple instances of the same app. When the limit is reached and an app is started, the message The server does not have enough capacity to start this app, please try again later. is shown. This can be combined with the global limit, both limits will be checked when an app starts. Note that the max-instances controls the maximum number of instances a single user is allowed to run.
  • external-url: on the main page, this opens the specified (external) URL instead of starting an app. When directly browsing to the app, the user is redirected to the external URL. Can not be used together with container-image or other container-* properties. Supports access control and properties that provide styling on the main page ( e.g. display-name, description, logo-url etc). For example:
    - id: basic-external-url
      display-name: Basic External URL
      description: Example description
      external-url: https://shinyproxy.io
      access-users:
       - jack
    
  • support-mail-to-address: overrides the address to which support e-mails are sent
  • support-mail-subject: overrides the subject of support e-mails

Container pre-initialization and sharing

By default ShinyProxy starts a new container every time a user starts an application. This container is dedicated to this user and is never used by other users. In other words, if 10 users are running an app, ShinyProxy will have started 10 containers. This approach has many advantages, but also has the consequence that a user has to wait for both the container and application to start up and that (depending on the app) more and more resources are used to host apps. ShinyProxy 3.1.0 introduces the concept of container pre-initialization and container sharing. For every app, you can specify a number of seats ShinyProxy has to pre-initialize (e.g. X seats). When ShinyProxy starts up, it will start X containers. As soon as a user wants to use an application, ShinyProxy assigns a container (seat) to the user. Therefore, the user has almost no waiting time for the container to start up. As soon as a seat gets claimed, ShinyProxy will scale up the number of containers, such that there are again X containers ready to be used by new users. Similarly, when a user stops using an app, the seat is released and ShinyProxy will scale-down the pool of available seats (and thus the number of containers). In addition, you can specify how many seats can be run on a single container, therefore allowing to share a single container among multiple users.

Before using this feature, make sure to understand the following:

  • ShinyProxy does not pass the username and groups using environment variables, however, they are passed to the app using HTTP headers.
  • the container cannot be customized for a specific user. This means that you cannot use SpEL expression in the configuration of an app. However, it is possible to add custom headers (containing SpEL expression) to every HTTP request send to the app.
  • usage of App Parameters is limited to the the http-headers property
  • sharing or re-using containers by multiple users, has implications w.r.t security
  • app recovery cannot be used, however, Redis is fully supported
  • ShinyProxy does not automatically restart the containers. The same containers can be used indefinitely. Since version 3.1.1, The API contains an endpoint /admin/delegate-proxy that can be used to force a restart of all containers. This endpoint can only be called by admin users. In a future update, the admin panel will have a button to replace the containers (in addition to an overview of the containers). When using this endpoint, it’s advices to set the image-pull-policy (docker, kubernetes), to Always, to ensure the latest version of your image is used.

The following configuration options are available for this feature:

  • minimum-seats-available: the number of seats ShinyProxy should keep available for new users. This option is required to enable the container pre-initialization and sharing feature. The number should be higher than one.
  • seats-per-container: the number of seats that can be run on a single container (by default 1).
  • allow-container-re-use: boolean, whether a (pre-initialized) container can be re-used by a different user (has no effect when minimum-seats-available isn’t specified). ShinyProxy removes the container after first use. Can only be used when seats-per-container equals 1. When disabled, you benefit from pre-initialized containers, while still having the guarantee that a container is only used by a single user.
  • scale-down-delay: the time (in minutes) ShinyProxy waits to scale-down after a scale-up (defaults to two minutes). This means that if a user stops using an app, the container is only released if there was no scale-up in the specified period. By increasing this value, you can prevent too many scale-up and scale-downs in a short period.

The global property proxy.seat-wait-time controls the maximum time a user can wait for a seat. During the specified time, ShinyProxy (continuously) tries to claim a seat for the user. If no seat is available after the specified timeout, the app fails to start. The time must be specified in milliseconds, and be at least 3 seconds. The default timeout is 5 minutes.

Examples:

  • Configure ShinyProxy to keep 5 seats ready for new users. Every container can only by used by a single user at the same time, but when a user stops an app, the same container can be re-used by a new user. In this configuration, ShinyProxy creates 5 containers at startup. As soon as a user starts an app, a new container is created.

    - id: 01_hello
      container-image: openanalytics/shinyproxy-demo
      minimum-seats-available: 5
    
  • Configure ShinyProxy to keep 5 seats ready for new users. Every container can only by used by a single user at the same time, and containers cannot be re-used by new users. I.e. wen a user stops an app, the container is immediately removed. In this configuration, ShinyProxy creates 5 containers at startup. As soon as a user starts an app, a new container is created.

    - id: 01_hello
      container-image: openanalytics/shinyproxy-demo
      minimum-seats-available: 5
      allow-container-re-use: false
    
  • Configure ShinyProxy to keep 5 seats ready for new users. Every container can be used by 5 users at the same time (and thus containers can be re-used by new users). In this configuration, ShinyProxy creates 1 container at startup (but with 5 seats ready to use). As soon as a user starts an app, there are only 4 seats left and therefore a new container is created, after which the available number of seats will be 9.

    - id: 01_hello
      container-image: openanalytics/shinyproxy-demo
      minimum-seats-available: 5
      seats-per-container: 5
    
  • Configure ShinyProxy to keep 3 seats ready for new users. Every container can be used by 5 users at the same time (and thus containers can be re-used by new users). In this configuration, ShinyProxy creates 1 container at startup (but with 5 seats ready to use). When a single user starts an app, 4 seats are still available and ShinyProxy will not create a new container. Only when two other users join (so 3 users in total), ShinyProxy will create a new container, to guarantee that 3 seats are available.

    - id: 01_hello
      container-image: openanalytics/shinyproxy-demo
      minimum-seats-available: 3
      seats-per-container: 5
    

Docker and Docker Swarm

This section lists properties that are only supported by the Docker and Docker swarm backends.

  • 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-network-connections: 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" ]
    
  • docker-registry-username: (optional), username used for authenticating with a Docker registry
  • docker-registry-password: (optional), password used for authenticating with a Docker registry
  • docker-registry-domain: (optional), domain of the registry when authentication is required Example when using Docker Hub:
    proxy:
      docker:
        image-pull-policy: Always
      specs:
      - id: myapp
        container-image: docker.io/your_docker_username/docker-test:latest
        docker-registry-username: your_docker_username
        docker-registry-password: your_docker_token
        docker-registry-domain: docker.io
    
    Example when using GitHub Container Registry:
    proxy:
      docker:
        image-pull-policy: Always
      specs:
      - id: myapp
        container-image: ghcr.io/your_github_username/ghcr-test:latest
        docker-registry-username: your_github_username
        docker-registry-password: your_github_pat
        docker-registry-domain: ghcr.io
    
  • docker-user: the user name or id user to run the container

Docker

This section lists properties that are only supported by the Docker backend (while not being supported by Docker Swarm).

  • resource-name: a SpEL expression used to generate the name of the Docker container. By default this is equivalent to sp-container-#{proxy.id}-0. Note that Docker restricts which characters can be used in the name, therefore it is usual not a good idea to use usernames or app names for the docker container.
  • docker-ipc: the IPC mode for the container, see Docker docs
  • docker-runtime: the runtime for the container, see Docker docs
  • docker-device-requests: allows configuring Docker to use GPU(s), see Docker docs Examples:
    • - id: gpu-example
        docker-device-requests:
          - driver: "nvidia"
            count: 1
            capabilities:
              - ["utility"]
      
    • - id: gpu-example
        docker-device-requests:
          - deviceIds:
              - "0"
            capabilities:
              - ["utility"]
      
    • - id: gpu-example
        docker-device-requests:
          - driver: "nvidia"
            count: 1
            capabilities:
              - ["utility"]
            options:
                my: option
      
    • - id: gpu-example
        docker-device-requests:
          - driver: nvidia
            count: 1
      

Docker Swarm

This section lists properties that are only supported by the Docker swarm backend.

  • resource-name: a SpEL expression used to generate the name of the Docker Swarm service. By default this is equivalent to sp-service-#{proxy.id}-0. Note that the name must be a valid DNS label, i.e. it should be at most 63 characters, and can only contain alphanumeric characters and -. Therefore, it is usual not a good idea to use usernames or app names for the docker container.
  • docker-swarm-secrets: a list of secrets to mount in a Docker swarm container. Each entry should at least have a name property and can optionally have target, gid, uid and mode properties. For example:
    - id: myapp
      docker-swarm-secrets:
       - name: my_secret
       - name: my_secret_2
         target: /var/pass
         gid: 1000
         uid: 1000
         mode: 600
    

Kubernetes

This section lists properties that are only supported by the Kubernetes backend.

  • resource-name: a SpEL expression used to generate the name of the Kubernetes pod. By default this is equivalent to sp-pod-#{proxy.id}-0. Note that the name must be a valid DNS label, i.e. it should be at most 63 characters, and can only contain alphanumeric characters and -. Therefore, it is usual not a good idea to use usernames or app names for the docker container.
  • kubernetes-pod-patches: allows to apply JSON Patches to the Kubernetes Pod created for this app. The parameter is a string which should contain a YAML array. The following patch:
    • adds a ServiceAccount
    • changes the namespace
    • adds a Volume and VolumeMount
    • adds an environment variable
    • adds 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  
    
    Using the pod patches it is possible to add additional containers (sidecars) to the pod created by ShinyProxy, for example to add a fluentbit sidecar:
    kubernetes-pod-patches: |
      - op: add
        path: /spec/containers/-
        value:
          name: fluentbit
          image: cr.fluentbit.io/fluent/fluent-bit:1.9.10
          envFrom:
            - secretRef:
                name: fluent-bit
          volumeMounts:
            - name: logs
              mountPath: /mnt/logs
            - name: fluent-bit-config
              mountPath: /fluent-bit/etc/  
    
  • kubernetes-authorized-pod-patches: variant of kubernetes-pod-patches containing authorization, therefore the patch is only applied if the current user passes the access-control settings. Supports users, groups and access-expression.
      kubernetes-authorized-pod-patches:
        - access-control:
            users:
              - jack
              - jeff
            # groups: my-group
            # expression: '#{...}'
          patches: |
            - op: add
              path: /spec/volumes/-
              value:
                  name: my-protected-volume
                  persistentVolumeClaim:
                      claimName: "my-protected-volume"
            - op: add
              path: /spec/containers/0/volumeMounts/-
              value:
                  mountPath: "/mnt"
                  name: my-protected-volume        
    
  • kubernetes-additional-manifests: a list of Kubernetes manifests, which are created when the app starts and destroyed when the app stops. 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-authorized-additional-manifests: variant of kubernetes-additional-manifests containing authorization, therefore the manifest is only created if the current user passes the access-control settings. Supports users, groups and access-expression.
    kubernetes-authorized-additional-persistent-manifests:
      - access-control:
          users:
            - jack
            - jeff
          # groups: my-group
          # expression: '#{...}'
        manifests:
          - |
            apiVersion: v1
            kind: Secret
            metadata:
              name: manifests-secret
            type: Opaque
            data:
              password: cGFzc3dvcmQ=        
    
  • kubernetes-additional-persistent-manifests: a list of Kubernetes manifests, which are created when the app starts, however, these manifests are not destroyed when the app stops. 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=      
    
  • kubernetes-authorized-additional-persistent-manifests: variant of kubernetes-additional-persistent-manifests containing authorization, therefore the manifest is only created if the current user passes the access-control settings. Supports users, groups and access-expression.
    kubernetes-authorized-additional-persistent-manifests:
      - access-control:
          users:
            - jack
            - jeff
          # groups: my-group
          # expression: '#{...}'
        manifests:
          - |
            apiVersion: v1
            kind: Secret
            metadata:
              name: manifests-secret
            type: Opaque
            data:
              password: cGFzc3dvcmQ=        
    

Note:

  • the proxy.kubernetes.debug-patches option can help with debugging when using the above features.
  • the manifests created by the kubernetes-additional-manifests and kubernetes-additional-persistent-manifests options will be created in the namespace specified in the manifest. If no namespace is provided, ShinyProxy will use the namespace of the app (if the namespace is changed using kubernetes-pod-patches, it will use the patched namespace).
  • the manifests created by the kubernetes-additional-manifests and kubernetes-additional-persistent-manifests options automatically get some labels:
    • openanalytics.eu/sp-additional-manifest: always set to true
    • openanalytics.eu/sp-persistent-manifest: true or false depending on which variant is used
    • openanalytics.eu/sp-manifest-id: an identifier representing the specId and userId which caused this resource to get created These labels are used in order to delete the manifests when the app stops (in case the non-persistent variant is used). Because these labels are used (instead of the resource name), the manifests can have a name which uses SpEL.
  • both the kubernetes-additional-manifests and kubernetes-additional-persistent-manifests option support a special annotation called openanalytics.eu/sp-additional-manifest-policy. This policy controls the exact action ShinyProxy performs when handling the manifest. The annotation supports the following values:
    • CreateOnce (default when annotation is not present): Creates the manifest if it is not already present
    • Patch: Creates the manifest if it’s not already present, otherwise applies the manifest as a patch to the already existing resource (i.e. equivalent to kubectl apply -f ...)
    • Replace: Creates the manifest if it’s not already present, otherwise replaces it (i.e. delete and create)
    • Delete: Deletes the manifest if it’s present The most common used options are CreateOnce and Patch. Note that in most cases these options are used together with the persistent variant. The following example creates a configmap, containing the current time in milliseconds. The configmap is updated every time a user starts the application.
    kubernetes-additional-persistent-manifests:
      - |
        apiVersion: v1
        kind: ConfigMap
        metadata:
          name: manifests-configmap
          annotations:
            openanalytics.eu/sp-additional-manifest-policy: Patch
        data:
          mydata.txt: |
            last_modified: #{T(System).currentTimeMillis()}
            test: abc    
    
  • the options kubernetes-pod-patches, kubernetes-authorized-pod-patches, kubernetes-additional-manifests, kubernetes-additional-manifests, kubernetes-additional-persistent-manifests and kubernetes-additional-persistent-manifests: support the use of Spring Expression Language. 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      
    
  • the access-control property inside the authorized options is optional. If the option isn’t provided, the patch or manifest is applied for all users. This can simplify the config when combining patches that do and do not require access control.
  • the access-control.expression property inside the authorized options can be used for more than just access control, e.g. to conditionally apply patches. The following example, uses app parameters allowing the user to choose whether a volume should be mounted:
    - id: 01_hello
      container-image: openanalytics/shinyproxy-demo
      parameters:
        definitions:
          - id: mount-volume
            display-name: Mount volume?
            default-value: "No"
        value-sets:
          - values:
              mount-volume:
                - "Yes"
                - "No"
      kubernetes-authorized-pod-patches:
        - access-control:
            expression: "#{proxy.getRuntimeObject('SHINYPROXY_PARAMETERS').getValue('mount-volume') == 'Yes'}"
          patches: |
            - op: add
              path: /spec/volumes
              value:
                - name: my-conditional-volume
                  persistentVolumeClaim:
                      claimName: "my-conditional-volume"
            - op: add
              path: /spec/containers/0/volumeMounts
              value:
               -  mountPath: "/mnt"
                  name: my-conditional-volume        
    

ECS

ShinyProxy can run containers on ECS using a pre-defined task-definition (by specifying the task ARN as the container-image), or by dynamically creating a task definition for every container. In the first case, it is not possible to use the many configuration options of ShinyProxy, instead all configuration should be performed in the task definition. Therefore, we advice to let ShinyProxy create the task definition (the remainder of this section assumes that ShinyProxy creates the task definition).

  • ecs-task-role (optional) the task role to use for this app

  • ecs-execution-role (optional) the task execution role to use for this app

  • ecs-cpu-architecture (optional) the CPU architecture to use, can be X86_64 or ARM64

  • ecs-operation-system-family (optional), the Operation System Family to use (ShinyProxy generally uses Linux based containers)

  • ecs-ephemeral-storage (optional) the amount of storage to allocate for the container (default is 21G)

  • ecs-efs-volumes (optional) list of EFS volumes to mount into the container (see AWS documentation), every entry is a map:

    • name: (required) the name of the mount. The name should be used in the container-volumes property in order to actually mount the volume.
    • file-system-id: (required) the id of the EFS file system
    • access-point-id: (optional) the id of the EFS Access Point to use
    • enable-iam: (optional) whether to use IAM when mounting the volume, default is false
    • root-directory: (optional) the directory within the EFS file system to mount, default is / (this is not the location where the volume will be mounted, the mount location is specified using the container-volumes property)
    • transit-encryption: (optional) boolean, whether transit encryption is used, default is false
    • transit-encryption-port: (optional) number, the port to use for transit encryption

    Example:

    id: myapp
    container-image: openanalytics/shinyproxy-demo
    ecs-efs-volumes:
     - name: my-volume
       file-system-id: fs01234567
    container-volumes: ["my-volume:/mnt/my-volume"]
    
  • ecs-enable-execute-command (optional), boolean, whether to allow exec’ing into the container, disabled by default, see AWS documentation

Note:

  • Fargate does not support running privileged containers, therefore you cannot use the container-privileged property
  • Fargate does not support using custom DNS servers, therefore you cannot use the container-dns property
  • AWS ECS does not support giving the ECS Task a name, therefore you cannot use the resource-name property
  • it is required to specify both the container-cpu-request and container-memory-request options, otherwise Fargate would be unable to allocate a proper node, see AWS documentation for supported value.
  • you cannot use the container-cpu-limit or container-memory-limit properties, the task will always get the exact amount of resources specified in the container-memory-request and container-cpu-request properties
  • ShinyProxy adds the Runtime Values and custom labels as tags to the ECS Task. However, AWS restricts both the length and the set of characters, therefore, not all values can be added as tags. This does not prevent the correct working of ShinyProxy.
  • the ECS backend does not support App Recovery, use App Persistence if you want to persist app sessions
  • see this example for configuring the AWS infrastructure in order to run ShinyProxy on ECS.

High availability and scaling

Starting with ShinyProxy 3.0.0, it is possible to run multiple replicas of ShinyProxy. This provides both high availability and scaling. The most optimal way to run ShinyProxy with multiple replicas is to use Kubernetes and the ShinyProxy Operator, since the Kubernetes platform provides many of the building blocks required to run and manage scalable and high available applications. However, for less demanding setups, it is possible to use Docker Swarm to achieve high availability and scaling. A complete example using Docker swarm will be made available. In both cases ShinyProxy uses Redis for storing active apps, communication and leader election.

Note that it is possible to use the high availability mode with a single replica. This allows you to restart ShinyProxy without having the apps of users stopped, although there is still some downtime when ShinyProxy restarts.

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.

JSON (Logstash) logging

In order to simplify the ingestion of ShinyProxy logs by other systems, ShinyProxy can be configured to output the logs as JSON:

proxy:
  log-as-json: true

Stack traces logged as part of the JSON output, are simplified: duplicate or useless parts are stripped. When something is logged with respect to an (active) app, the log contains some additional metadata. This allows for filtering on specific app events.

{"@timestamp":"2023-02-27T11:24:19.734+01:00","@version":"1","message":"[user=jack proxyId=aeb68e41-ed90-48f2-aa67-5cbf7e4b13da specId=01_hello] Proxy being stopped by user jack","logger_name":"e.o.containerproxy.service.ProxyService","thread_name":"XNIO-1 task-4","level":"INFO","level_value":20000,"user":"jack","proxyId":"aeb68e41-ed90-48f2-aa67-5cbf7e4b13da","specId":"01_hello"}
{"@timestamp":"2023-02-27T11:24:20.087+01:00","@version":"1","message":"[user=jack proxyId=aeb68e41-ed90-48f2-aa67-5cbf7e4b13da specId=01_hello] Proxy released","logger_name":"e.o.containerproxy.service.ProxyService","thread_name":"pool-1-thread-1","level":"INFO","level_value":20000,"user":"jack","proxyId":"aeb68e41-ed90-48f2-aa67-5cbf7e4b13da","specId":"01_hello"}

Although the format is based on the Logstash format, any system should be able to ingest the logs. For example, in the ShinyProxy monitoring stack Loki is used to process the JSON logs.

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 main process running in the container:

Note: if the app crashes before it startups, ShinyProxy will not store the logs of that 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

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

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

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

The log files will be attached to support mails automatically.

You can optionally specify a subject for the email using the support-mail-subject property. Apps can override the support-mail-to-address and support-mail-subject

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.

API

See the API page for details on using the API. By default the API is secured using the same authentication backend used for authentication users. For example, if using the simple backend you can authenticate with the API using Basic Auth, e.g.:

curl -u "jack:password" http://localhost:8080/api/proxyspec

Besides this default, it is possible to protect the API using OAuth2. This can be combined with any authentication backend and is independent of using OpenID Connect (which allows authentication users in the browser). However, in most cases it makes sense to combine OAuth2 with using OpenID connect.

  • proxy.oauth2.resource-id: the client-id (or audience)
  • proxy.oauth2.jwks-url: the URL to the JWKS endpoints of the IDP
  • proxy.oauth2.username-attribute: (optional) name of the claim which contains the username
  • proxy.oauth2.roles-claim: (optional) name of the claim which contains a list of groups (or roles) of the user

See the example on GitHub for example configuration and requests.

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.

  • proxy.api-security.hide-spec-details: (boolean) whether to hide (security sensitive) values from the spec returned by the HTTP API. By default, this is true, which is the most secure option.

  • proxy.api-security.cors-allowed-origins: list of domains that are allowed as CORS origin.

  • proxy.api-security.disable-no-sniff-header: when set to true ShinyProxy no longer adds the X-Content-Type-Options=nosniff header to HTTP responses. Only disable this if you override this header in a loadbalancer or proxy, do not disable this to prevent errors in a browser. See MDN for more information on this header.

  • proxy.api-security.disable-xss-protection-header when set to true ShinyProxy no longer adds the X-XSS-Protection: 1 to HTTP responses. Only disable this if you override this header in a loadbalancer or proxy, do not disable this to prevent errors in a browser. See MDN for more information on this header.

  • proxy.api-security.disable-hsts-header when set to true ShinyProxy no longer adds the HSTS (HTTP Strict-Transport-Security) header to HTTP responses. See MDN for more information on this header.

  • proxy.api-security.custom-headers: list of custom headers to add to every HTTP response. Overrides any header that is added by default. Each entry should have the properties name and value. For example:

    proxy:
      api-security:
        custom-headers:
        - name: Content-Language
          value: 'nl-BE'
        - name: Pragma
          value: 'Custom pragma'
    

    Note: only adding the Content-Language header is not enough, the lang attribute of every HTML template used by ShinyProxy must be changed as well. See the template-path property.

Session and App 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). App Persistence is used to store the active apps in ShinyProxy, such that these are still available when restarting ShinyProxy.

Enabling Session Persistence

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
  data:
    redis:
      host: redis
      password: redis_password

In order to use SSL/TLS add the property spring.data.redis.ssl.enabled:

spring:
  session:
    store-type: redis
  data:
    redis:
      host: redis
      password: redis_password
      ssl:
        enabled: true

You can use spring.data.redis.database to connect to a different Redis database:

spring:
  session:
    store-type: redis
  data:
    redis:
      host: redis
      password: redis_password
      database: 10

To connect to Redis Sentinel, use (replace nodes by the correct hostnames):

spring:
  session:
    store-type: redis
  data:
    redis:
      password: redis_password
      sentinel:
        master: shinyproxy
        password: sentinel_password
        nodes: redis-node-0.redis-headless:26379, redis-node-1.redis-headless:26379, redis-node-2.redis-headless:26379

Note:

  • ShinyProxy will hang when the server is using TLS, but ShinyProxy is not configured to connect using TLS
  • ShinyProxy does not support Redis Cluster, use Redis Sentinel instead
  • the configuration of Redis has changed in ShinyProxy 3.0.1
  • see the Spring documentation for a complete list of supported properties
  • in order to protect the Redis password, you can specify it as an environment variable.

Enabling App Persistence

First make sure Session Persistence is enabled, next enable App Persistence using:

proxy:
  store-mode: Redis
  stop-proxies-on-shutdown: false

Note: you cannot use app persistence together with app recovery. Therefore, make sure you do not include the proxy.recover-running-proxies or proxy.recover-running-proxies-from-different-config options.

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.

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 server.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 -1, but beware of session leaks in that case.

Note: do not use 0 for the server.servlet.session.timeout parameter, this does not work when using Redis. Use a negative value to deactivate the session timeout.