├── .gitignore ├── 01-standalone-docker-engine ├── README.md └── application.yml ├── 02-containerized-docker-engine ├── Dockerfile ├── README.md └── application.yml ├── 03-containerized-kubernetes ├── README.md ├── shinyproxy-example │ ├── Dockerfile │ └── application.yml ├── sp-authorization.yaml ├── sp-deployment.yaml └── sp-service.yaml ├── 04-custom-html-template ├── README.md ├── application.yml └── templates │ ├── 1col │ ├── assets │ │ ├── css │ │ │ └── 1-col-portfolio.css │ │ └── img │ │ │ ├── 01_hello.png │ │ │ └── 06_tabsets.png │ └── index.html │ ├── 2col │ ├── assets │ │ ├── css │ │ │ └── 2-col-portfolio.css │ │ └── img │ │ │ ├── 01_hello.png │ │ │ └── 06_tabsets.png │ └── index.html │ └── modified_navbar │ └── fragments │ └── navbar.html ├── 05-template-groups ├── README.md ├── application.yml └── screenshot.png ├── 06-standalone-docker-swarm ├── README.md └── application.yml ├── 07-containerized-docker-swarm ├── Dockerfile ├── README.md └── application.yml ├── 08-embedded-app ├── README.md ├── application.yml └── index.html ├── 09-api-oauth2 └── README.md ├── 10-background-apps ├── README.md └── application.yml ├── 11-openid-azure-b2c ├── README.md └── img │ ├── 01_register.png │ ├── 02_create_secret.png │ ├── 03_secret.png │ ├── 04_info.png │ ├── 05_email.png │ ├── 06_email.png │ └── 07_groups.png ├── 12-openid-keycloak ├── README.md └── img │ ├── 01_create_client.png │ ├── 02_create_client.png │ ├── 03_create_client.png │ ├── 04_client_secret.png │ ├── 05_endpoints.png │ ├── 06_roles.png │ ├── 07_roles.png │ ├── 08_groups.png │ ├── 09_logout.png │ └── 10_logout.png ├── 13-openid-auth0 ├── README.md └── img │ ├── 01_create_application.png │ ├── 02_application_urls.png │ ├── 03_action_create.png │ ├── 04_action_code.png │ ├── 05_flow.png │ ├── 06_app_metadata.png │ ├── 07_logout.png │ └── 08_logout_confirmation.png ├── 20-ecs-minimal ├── .gitignore ├── README.md ├── docker │ ├── Dockerfile │ ├── application.yml │ └── application.yml.bak └── terraform │ ├── .gitignore │ ├── 10_shinyproxy_service.tf │ ├── 1_shinyproxy_image.tf │ ├── 2_vpc.tf │ ├── 3_ecs.tf │ ├── 4_shinyproxy_task_role.tf │ ├── 5_shinyproxy_execution_role.tf │ ├── 6_shinyproxy_lb.tf │ ├── 7_shinyproxy_sg.tf │ ├── 8_app_sg.tf │ ├── 9_shinyproxy_task_defintion.tf │ ├── aws.tf │ ├── locals.tf │ ├── main.tfvars │ ├── outputs.tf │ └── variables.tf ├── 21-ecs-execution-role ├── README.md └── terraform │ ├── 11_app_execution_role.tf │ └── 12_shinyproxy_task_role_policy.tf └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | /.project 2 | node_modules 3 | -------------------------------------------------------------------------------- /01-standalone-docker-engine/README.md: -------------------------------------------------------------------------------- 1 | # Example: standalone ShinyProxy with a docker engine 2 | 3 | This example represents the most straightforward setup: you run ShinyProxy as a standalone Java process. ShinyProxy accesses a docker engine to spawn containers running the user's Shiny apps. 4 | 5 | There is no clustering involved here: everything runs on a single host, or maybe two hosts if you have your Java runtime and docker engine on separate machines. 6 | 7 | ## Requirement 8 | 9 | ShinyProxy expects relevant Docker images to be already available on the host. Before running this example, pull the Docker image used in this example with: 10 | 11 | ``` 12 | sudo docker pull openanalytics/shinyproxy-demo 13 | ``` 14 | 15 | ## How to run 16 | 17 | 1. Download [ShinyProxy](https://www.shinyproxy.io/downloads "ShinyProxy website") 18 | 2. Download the `application.yml` configuration file from the folder where this README is located. 19 | 3. Place the jar and yml files in the same directory, e.g. `/home/user/sp` 20 | 4. Open a terminal, go to the directory `/home/user/sp`, and run the following command: 21 | 22 | `java -jar shinyproxy.jar` 23 | 24 | ## Notes on the configuration 25 | 26 | * ShinyProxy will listen for HTTP traffic on port `8080`. 27 | * ShinyProxy connects to the Docker daemon using a Unix socket, make sure the 28 | user running ShinyProxy has permission 29 | to [use this socket](https://shinyproxy.io/documentation/getting-started/#access-to-docker-daemon). 30 | -------------------------------------------------------------------------------- /01-standalone-docker-engine/application.yml: -------------------------------------------------------------------------------- 1 | proxy: 2 | port: 8080 3 | authentication: simple 4 | admin-groups: admins 5 | users: 6 | - name: jack 7 | password: password 8 | groups: admins 9 | - name: jeff 10 | password: password 11 | specs: 12 | - id: 01_hello 13 | display-name: Hello Application 14 | description: Application which demonstrates the basics of a Shiny app 15 | container-cmd: ["R", "-e", "shinyproxy::run_01_hello()"] 16 | container-image: openanalytics/shinyproxy-demo 17 | - id: 06_tabsets 18 | container-cmd: ["R", "-e", "shinyproxy::run_06_tabsets()"] 19 | container-image: openanalytics/shinyproxy-demo 20 | 21 | logging: 22 | file: 23 | name: shinyproxy.log 24 | -------------------------------------------------------------------------------- /02-containerized-docker-engine/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM openanalytics/shinyproxy:3.1.1 2 | 3 | COPY application.yml /opt/shinyproxy/application.yml 4 | -------------------------------------------------------------------------------- /02-containerized-docker-engine/README.md: -------------------------------------------------------------------------------- 1 | # Example: containerized ShinyProxy with a docker engine 2 | 3 | This example is similar to the example 'standalone ShinyProxy with a docker engine', with one exception: 4 | ShinyProxy runs in a container itself, in the same container manager (i.e. docker engine) that also hosts 5 | the containers for the users' Shiny apps. 6 | 7 | ## Requirement 8 | 9 | ShinyProxy expects relevant Docker images to be already available on the host. Before running this example, pull the Docker image used in this example with: 10 | 11 | ```bash 12 | sudo docker pull openanalytics/shinyproxy-demo 13 | ``` 14 | 15 | ## How to run 16 | 17 | 1. Download the `Dockerfile` from the folder where this README is located. 18 | 2. Download the `application.yml` configuration file from the folder where this README is located. 19 | 3. Place the files in the same directory, e.g. `/home/user/sp` 20 | 4. Create a docker network that ShinyProxy will use to communicate with the Shiny containers. 21 | 22 | ```bash 23 | sudo docker network create sp-example-net 24 | ``` 25 | 26 | 5. Open a terminal, go to the directory `/home/user/sp`, and run the following command to build the ShinyProxy image: 27 | 28 | ```bash 29 | sudo docker build . -t shinyproxy-example 30 | ``` 31 | 32 | 6. Run the following command to launch the ShinyProxy container: 33 | 34 | ```bash 35 | sudo docker run -v /var/run/docker.sock:/var/run/docker.sock:ro --group-add $(getent group docker | cut -d: -f3) --net sp-example-net -p 8080:8080 shinyproxy-example 36 | ``` 37 | 38 | **Note**: inside the Docker container, ShinyProxy runs as a non-root user, therefore it does not have access to the Docker socket by default. 39 | By adding the `--group-add $(getent group docker | cut -d: -f3)` option we ensure that the user is part of the `docker` group and thus has access to the Docker daemon. 40 | 41 | **Note**: by adding the `-d` option to the command (just after `docker run`), the Docker container will run in the background. 42 | 43 | ## Notes on the configuration 44 | 45 | * ShinyProxy will listen for HTTP traffic on port `8080`. 46 | 47 | * The custom bridge network `sp-example-net` is needed to allow the containers to access each other using 48 | the container ID as hostname. 49 | -------------------------------------------------------------------------------- /02-containerized-docker-engine/application.yml: -------------------------------------------------------------------------------- 1 | proxy: 2 | title: Open Analytics Shiny Proxy 3 | logo-url: https://www.openanalytics.eu/shinyproxy/logo.png 4 | port: 8080 5 | authentication: simple 6 | admin-groups: admins 7 | users: 8 | - name: jack 9 | password: password 10 | groups: admins 11 | - name: jeff 12 | password: password 13 | docker: 14 | internal-networking: true 15 | specs: 16 | - id: 01_hello 17 | display-name: Hello Application 18 | description: Application which demonstrates the basics of a Shiny app 19 | container-cmd: ["R", "-e", "shinyproxy::run_01_hello()"] 20 | container-image: openanalytics/shinyproxy-demo 21 | container-network: sp-example-net 22 | - id: 06_tabsets 23 | container-cmd: ["R", "-e", "shinyproxy::run_06_tabsets()"] 24 | container-image: openanalytics/shinyproxy-demo 25 | container-network: sp-example-net 26 | 27 | logging: 28 | file: 29 | name: shinyproxy.log 30 | -------------------------------------------------------------------------------- /03-containerized-kubernetes/README.md: -------------------------------------------------------------------------------- 1 | # Example: containerized ShinyProxy with a Kubernetes cluster 2 | 3 | In this example, ShinyProxy will run inside a Kubernetes cluster. Shiny containers will also be spawned 4 | in the same cluster. To make the application accessible outside the cluster, a NodePort service is created. 5 | 6 | **Note: this example is the most basic way to deploy ShinyProxy on Kubernetes 7 | and should not be used in production. Use 8 | the [ShinyProxy Operator](https://github.com/openanalytics/shinyproxy-operator) 9 | for deploying ShinyProxy on Kubernetes.** 10 | 11 | ## How to run 12 | 13 | 1. Download the `Dockerfile` and `application.yml` files from the folder `shinyproxy-example`. 14 | 2. Open a terminal, go to the directory containing the Dockerfile, and run the following command to build it: 15 | 16 | ```bash 17 | sudo docker build . -t shinyproxy-example 18 | ``` 19 | 20 | 3. Ensure the `shinyproxy-example` image is available on all your kube nodes. 21 | 4. Open a terminal on a master node (where the `kubectl` command is available). 22 | 5. Download the 3 `yaml` files from the folder where this README is located. 23 | 6. Run the following command to deploy a pod containing `shinyproxy-example`: 24 | 25 | ```bash 26 | kubectl create -f sp-deployment.yaml 27 | ``` 28 | 29 | 7. Run the following command to grant full privileges to the `default` service account which runs the above pod: 30 | 31 | ```bash 32 | kubectl create -f sp-authorization.yaml 33 | ``` 34 | 35 | 8. Run the following command to deploy a service exposing ShinyProxy outside the cluster: 36 | 37 | ```bash 38 | kubectl create -f sp-service.yaml 39 | ``` 40 | 41 | ## Notes on the configuration 42 | 43 | * The service will expose ShinyProxy on all nodes, listening on port `32094`. 44 | 45 | * If you do not deploy the service, you can still access ShinyProxy from within the cluster on port `8080`. 46 | 47 | * To keep the example concise, the `cluster-admin` role is granted to the `default` service account. 48 | Best-practice would be to add a dedicated service account and reference it via `serviceAccountName` in the deployment spec. 49 | The following role is the minimal set of permissions: 50 | 51 | ```yaml 52 | kind: Role 53 | apiVersion: rbac.authorization.k8s.io/v1 54 | metadata: 55 | namespace: example 56 | name: example 57 | rules: 58 | - apiGroups: [""] 59 | resources: ["pods", "pods/log"] 60 | verbs: ["get", "list", "watch", "create", "update", "patch", "delete"] 61 | ``` 62 | -------------------------------------------------------------------------------- /03-containerized-kubernetes/shinyproxy-example/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM openanalytics/shinyproxy:3.1.1 2 | 3 | COPY application.yml /opt/shinyproxy/application.yml 4 | -------------------------------------------------------------------------------- /03-containerized-kubernetes/shinyproxy-example/application.yml: -------------------------------------------------------------------------------- 1 | proxy: 2 | title: Open Analytics Shiny Proxy 3 | logo-url: https://www.openanalytics.eu/shinyproxy/logo.png 4 | port: 8080 5 | authentication: simple 6 | admin-groups: admins 7 | users: 8 | - name: jack 9 | password: password 10 | groups: admins 11 | - name: jeff 12 | password: password 13 | container-backend: kubernetes 14 | kubernetes: 15 | internal-networking: true 16 | specs: 17 | - id: 01_hello 18 | display-name: Hello Application 19 | description: Application which demonstrates the basics of a Shiny app 20 | container-cmd: ["R", "-e", "shinyproxy::run_01_hello()"] 21 | container-image: openanalytics/shinyproxy-demo 22 | - id: 06_tabsets 23 | container-cmd: ["R", "-e", "shinyproxy::run_06_tabsets()"] 24 | container-image: openanalytics/shinyproxy-demo 25 | 26 | logging: 27 | file: 28 | name: shinyproxy.log 29 | -------------------------------------------------------------------------------- /03-containerized-kubernetes/sp-authorization.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRoleBinding 3 | metadata: 4 | name: shinyproxy-auth 5 | subjects: 6 | - kind: ServiceAccount 7 | name: default 8 | namespace: default 9 | roleRef: 10 | kind: ClusterRole 11 | name: cluster-admin 12 | apiGroup: rbac.authorization.k8s.io 13 | -------------------------------------------------------------------------------- /03-containerized-kubernetes/sp-deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: shinyproxy 5 | spec: 6 | selector: 7 | matchLabels: 8 | run: shinyproxy 9 | replicas: 1 10 | template: 11 | metadata: 12 | labels: 13 | run: shinyproxy 14 | spec: 15 | containers: 16 | - name: shinyproxy 17 | image: shinyproxy-example 18 | imagePullPolicy: Never 19 | ports: 20 | - containerPort: 8080 -------------------------------------------------------------------------------- /03-containerized-kubernetes/sp-service.yaml: -------------------------------------------------------------------------------- 1 | kind: Service 2 | apiVersion: v1 3 | metadata: 4 | name: shinyproxy 5 | spec: 6 | type: NodePort 7 | selector: 8 | run: shinyproxy 9 | ports: 10 | - protocol: TCP 11 | port: 8080 12 | targetPort: 8080 13 | nodePort: 32094 -------------------------------------------------------------------------------- /04-custom-html-template/README.md: -------------------------------------------------------------------------------- 1 | # Example: custom HTML template 2 | 3 | By default, ShinyProxy presents available apps in a simple list format. Apps have a name, and optionally a description and a logo. 4 | This presentation can be overridden, however. In the example below, a custom **template** is activated, which changes the appearance 5 | of ShinyProxy in the browser. 6 | 7 | ## How to run 8 | 9 | 1. Download [ShinyProxy](https://www.shinyproxy.io/downloads "ShinyProxy website") 10 | 2. Download the `application.yml` configuration file from the folder where this README is located. 11 | 3. Place the jar and yml files in the same directory, e.g. `/home/user/sp` 12 | 4. Download all the files from the `templates` folder and place them in the folder containing your jar and yml files, e.g. `/home/user/sp/templates` 13 | 5. Open a terminal, go to the directory `/home/user/sp`, and run the following command: 14 | 15 | `java -jar shinyproxy.jar` 16 | 17 | **Note**: change the `proxy.template-path` property in `application.yml` to the example you want to use (`1col`, `2col`, `modified_navbar`) . 18 | 19 | ## How it works 20 | 21 | * The `application.yml` file contains the setting `template-path: ./templates/2col` which refers to the '2col' template. 22 | You can point it to any folder containing your custom HTML files. 23 | 24 | * ShinyProxy uses [Thymeleaf](https://www.thymeleaf.org/) as its HTML templating engine. 25 | 26 | * Assets (css, images, etc) can be referred to using the `@{/assets/...}` Thymeleaf syntax. Such references will be resolved against 27 | the `assets` subfolder of the template. 28 | 29 | * If a particular HTML file is missing from the template, the 30 | default HTML file will be used: 31 | 32 | ## Template properties for an app 33 | 34 | Since ShinyProxy 2.6.0, it is possible to specify additional properties for an 35 | app which can be used in templates, but has no other effect. 36 | 37 | For example: 38 | 39 | ```yaml 40 | - id: 01_hello 41 | display-name: Hello Application 42 | description: Application which demonstrates the basics of a Shiny app 43 | container-cmd: ["R", "-e", "shinyproxy::run_01_hello()"] 44 | container-image: openanalytics/shinyproxy-demo 45 | template-properties: 46 | category: production 47 | maintainer: Tesla 48 | ``` 49 | 50 | These properties can be used in the template as follows: 51 | 52 | ```html 53 | 54 | 55 | 56 | 57 | 63 | 64 | 65 |
66 | 67 |
68 |
69 | 70 | 71 | 72 | 73 | 74 |
75 |
76 |
77 | 78 | ``` 79 | 80 | ## Request and response variables 81 | 82 | Every template can access the request and response variables: 83 | 84 | ```html 85 | Request URI: 86 | Status code:
87 | ``` 88 | 89 | See the Java docs for all available methods: 90 | - [`request`](https://jakarta.ee/specifications/servlet/4.0/apidocs/javax/servlet/http/httpservletrequest) 91 | - [`response`](https://jakarta.ee/specifications/servlet/4.0/apidocs/javax/servlet/http/httpservletresponse) 92 | 93 | ## Where to find the original template files 94 | 95 | In order to directly download a file, right-click on the `Download` link and 96 | click `Save as`. 97 | 98 | * `login.html`: [View Online](https://github.com/openanalytics/containerproxy/blob/master/src/main/resources/templates/login.html) or [Download](https://raw.githubusercontent.com/openanalytics/containerproxy/master/src/main/resources/templates/login.html) 99 | * `error.html`: [View Online](https://github.com/openanalytics/containerproxy/blob/master/src/main/resources/templates/error.html) or [Download](https://raw.githubusercontent.com/openanalytics/containerproxy/master/src/main/resources/templates/error.html) 100 | * `logout-success.html`: [View Online](https://github.com/openanalytics/containerproxy/blob/master/src/main/resources/templates/logout-success.html) or [Download](https://raw.githubusercontent.com/openanalytics/containerproxy/master/src/main/resources/templates/logout-success.html) 101 | * `app-access-denied.html`: [View Online](https://github.com/openanalytics/containerproxy/blob/master/src/main/resources/templates/app-access-denied.html) or [Download](https://raw.githubusercontent.com/openanalytics/containerproxy/master/src/main/resources/templates/app-access-denied.html) 102 | * `auth-error.html`: [View Online](https://github.com/openanalytics/containerproxy/blob/master/src/main/resources/templates/auth-error.html) or [Download](https://raw.githubusercontent.com/openanalytics/containerproxy/master/src/main/resources/templates/auth-error.html) 103 | * `admin.html`: [View Online](https://github.com/openanalytics/shinyproxy/blob/master/src/main/resources/templates/admin.html) or [Download](https://raw.githubusercontent.com/openanalytics/shinyproxy/master/src/main/resources/templates/admin.html) 104 | * `index.html`: [View Online](https://github.com/openanalytics/shinyproxy/blob/master/src/main/resources/templates/index.html) or [Download](https://raw.githubusercontent.com/openanalytics/shinyproxy/master/src/main/resources/templates/index.html) 105 | * `app.html`: [View Online](https://github.com/openanalytics/shinyproxy/blob/master/src/main/resources/templates/app.html) or [Download](https://raw.githubusercontent.com/openanalytics/shinyproxy/master/src/main/resources/templates/app.html) 106 | * `navbar.html`: [View Online](https://github.com/openanalytics/shinyproxy/blob/master/src/main/resources/templates/fragments/navbar.html) or [Download](https://raw.githubusercontent.com/openanalytics/shinyproxy/master/src/main/resources/templates/fragments/navbar.html) 107 | * `modal.html`: [View Online](https://github.com/openanalytics/shinyproxy/blob/master/src/main/resources/templates/fragments/modal.html) or [Download](https://raw.githubusercontent.com/openanalytics/shinyproxy/master/src/main/resources/templates/fragments/modal.html) 108 | -------------------------------------------------------------------------------- /04-custom-html-template/application.yml: -------------------------------------------------------------------------------- 1 | proxy: 2 | title: Open Analytics Shiny Proxy 3 | logo-url: https://www.openanalytics.eu/shinyproxy/logo.png 4 | port: 8080 5 | template-path: ./templates/modified_navbar 6 | authentication: simple 7 | admin-groups: admins 8 | users: 9 | - name: jack 10 | password: password 11 | groups: admins 12 | - name: jeff 13 | password: password 14 | specs: 15 | - id: 01_hello 16 | display-name: Hello Application 17 | description: Application which demonstrates the basics of a Shiny app 18 | container-cmd: ["R", "-e", "shinyproxy::run_01_hello()"] 19 | container-image: openanalytics/shinyproxy-demo 20 | logo-url: /assets/img/01_hello.png 21 | - id: 06_tabsets 22 | container-cmd: ["R", "-e", "shinyproxy::run_06_tabsets()"] 23 | container-image: openanalytics/shinyproxy-demo 24 | logo-url: /assets/img/06_tabsets.png 25 | 26 | logging: 27 | file: 28 | name: shinyproxy.log 29 | -------------------------------------------------------------------------------- /04-custom-html-template/templates/1col/assets/css/1-col-portfolio.css: -------------------------------------------------------------------------------- 1 | /** 2 | * ShinyProxy 3 | * 4 | * Copyright (C) 2016-2018 Open Analytics 5 | * 6 | * =========================================================================== 7 | * 8 | * This program is free software: you can redistribute it and/or modify 9 | * it under the terms of the Apache License as published by 10 | * The Apache Software Foundation, either version 2 of the License, or 11 | * (at your option) any later version. 12 | * 13 | * This program is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * Apache License for more details. 17 | * 18 | * You should have received a copy of the Apache License 19 | * along with this program. If not, see 20 | */ 21 | 22 | body { 23 | padding-top: 70px; /* Required padding for .navbar-fixed-top. Remove if using .navbar-static-top. Change if height of navigation changes. */ 24 | } 25 | 26 | footer { 27 | margin: 50px 0; 28 | } 29 | .img-responsive { 30 | max-height: 300px; 31 | } -------------------------------------------------------------------------------- /04-custom-html-template/templates/1col/assets/img/01_hello.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openanalytics/shinyproxy-config-examples/900c1a7681bed9879d328d7de0c8306223b62474/04-custom-html-template/templates/1col/assets/img/01_hello.png -------------------------------------------------------------------------------- /04-custom-html-template/templates/1col/assets/img/06_tabsets.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openanalytics/shinyproxy-config-examples/900c1a7681bed9879d328d7de0c8306223b62474/04-custom-html-template/templates/1col/assets/img/06_tabsets.png -------------------------------------------------------------------------------- /04-custom-html-template/templates/1col/index.html: -------------------------------------------------------------------------------- 1 | 23 | 24 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 |
46 | 47 |
48 |
49 |
50 |
51 | 54 |
55 |
56 |

57 |

58 | Start 60 |
61 |
62 |
63 |
64 |
65 | 66 |
67 |
68 |

My apps

69 |
70 |
71 |
72 | 80 |
81 | 82 |
83 | 84 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /04-custom-html-template/templates/2col/assets/css/2-col-portfolio.css: -------------------------------------------------------------------------------- 1 | /** 2 | * ShinyProxy 3 | * 4 | * Copyright (C) 2016-2018 Open Analytics 5 | * 6 | * =========================================================================== 7 | * 8 | * This program is free software: you can redistribute it and/or modify 9 | * it under the terms of the Apache License as published by 10 | * The Apache Software Foundation, either version 2 of the License, or 11 | * (at your option) any later version. 12 | * 13 | * This program is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * Apache License for more details. 17 | * 18 | * You should have received a copy of the Apache License 19 | * along with this program. If not, see 20 | */ 21 | body { 22 | padding-top: 70px; /* Required padding for .navbar-fixed-top. Remove if using .navbar-static-top. Change if height of navigation changes. */ 23 | } 24 | 25 | .portfolio-item { 26 | margin-bottom: 25px; 27 | height: 450px; 28 | } 29 | 30 | footer { 31 | margin: 50px 0; 32 | } 33 | 34 | .img-responsive { 35 | max-height: 300px; 36 | } 37 | .image-box { 38 | min-height: 300px; 39 | } 40 | .card-box { 41 | background-clip: border-box; 42 | border: 1px solid rgba(0,0,0,.125); 43 | border-radius: .25rem; 44 | padding: 15px; 45 | min-height: 460px; 46 | } -------------------------------------------------------------------------------- /04-custom-html-template/templates/2col/assets/img/01_hello.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openanalytics/shinyproxy-config-examples/900c1a7681bed9879d328d7de0c8306223b62474/04-custom-html-template/templates/2col/assets/img/01_hello.png -------------------------------------------------------------------------------- /04-custom-html-template/templates/2col/assets/img/06_tabsets.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openanalytics/shinyproxy-config-examples/900c1a7681bed9879d328d7de0c8306223b62474/04-custom-html-template/templates/2col/assets/img/06_tabsets.png -------------------------------------------------------------------------------- /04-custom-html-template/templates/2col/index.html: -------------------------------------------------------------------------------- 1 | 23 | 24 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 |
46 | 47 |
48 |
49 |
50 |
51 |
52 | 53 |
54 | 57 |
58 |
59 |

60 | 62 |

63 |

64 |
65 |
66 |
67 |
68 |
69 | 70 |
71 |
72 |

My apps

73 |
74 |
75 |
76 | 84 |
85 | 86 |
87 | 88 | 94 | 95 | 96 | -------------------------------------------------------------------------------- /04-custom-html-template/templates/modified_navbar/fragments/navbar.html: -------------------------------------------------------------------------------- 1 | 23 | 24 | 27 | 28 | 29 | 131 | 132 | 133 | -------------------------------------------------------------------------------- /05-template-groups/README.md: -------------------------------------------------------------------------------- 1 | # Example: template groups 2 | 3 | Since ShinyProxy 2.6.0 it is possible to group apps on the overview page. It is 4 | possible to specify custom groups and assign apps to these groups. 5 | 6 | ## How to run 7 | 8 | 1. Download [ShinyProxy](https://www.shinyproxy.io/downloads "ShinyProxy website") 9 | 2. Download the `application.yml` configuration file from the folder where this README is located. 10 | 3. Place the jar and yml files in the same directory, e.g. `/home/user/sp` 11 | 4. Open a terminal, go to the directory `/home/user/sp`, and run the following command: 12 | 13 | `java -jar shinyproxy.jar` 14 | 15 | ## How it works 16 | 17 | See the documentation for the [`template-groups`]() and [`template-group`](). 18 | These are the only properties required to make this work. 19 | 20 | Screenshot: 21 | 22 | ![Screenshot of the template group feature](screenshot.png) 23 | 24 | **Note**: as is visible on the screenshot, apps that are not part of a group (or 25 | of a non-existing group) are grouped into the `Other` group. 26 | 27 | ## Using additional metadata for a group 28 | 29 | It is possible to specify extra metadata for a group. The default template of 30 | ShinyProxy only uses the `id` and `display-name` properties. Let's assume you 31 | want to specify `maintainer` for each group: 32 | 33 | ```yaml 34 | proxy: 35 | template-groups: 36 | - id: tools 37 | properties: 38 | display-name: Tools 39 | maintainer: Tesla 40 | - id: visualizations 41 | properties: 42 | display-name: visualizations 43 | maintainer: Einstein 44 | - id: documentation 45 | properties: 46 | display-name: Documentation 47 | ``` 48 | 49 | You can now modify the templates of ShinyProxy to use this property: 50 | 51 | ```html 52 | 53 |
54 |
55 |
56 | 59 | 60 |

61 | Maintainer: 62 |
    63 | 64 |
65 |
66 |
67 | 68 | 69 |

Other

70 |
    71 | 72 |
73 |
74 |
75 |
76 | ``` 77 | -------------------------------------------------------------------------------- /05-template-groups/application.yml: -------------------------------------------------------------------------------- 1 | proxy: 2 | title: Open Analytics Shiny Proxy 3 | logo-url: https://www.openanalytics.eu/shinyproxy/logo.png 4 | port: 8080 5 | authentication: simple 6 | admin-groups: admins 7 | users: 8 | - name: jack 9 | password: password 10 | groups: admins 11 | - name: jeff 12 | password: password 13 | template-groups: 14 | - id: tools 15 | properties: 16 | display-name: Tools 17 | maintainer: Tesla 18 | - id: visualizations 19 | properties: 20 | display-name: Visualizations 21 | maintainer: Einstein 22 | specs: 23 | - id: 01_hello 24 | display-name: Hello Application 25 | description: Application which demonstrates the basics of a Shiny app 26 | container-cmd: ["R", "-e", "shinyproxy::run_01_hello()"] 27 | container-image: openanalytics/shinyproxy-demo 28 | template-group: visualizations 29 | - id: 06_tabsets 30 | container-cmd: ["R", "-e", "shinyproxy::run_06_tabsets()"] 31 | container-image: openanalytics/shinyproxy-demo 32 | 33 | logging: 34 | file: 35 | name: shinyproxy.log 36 | -------------------------------------------------------------------------------- /05-template-groups/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openanalytics/shinyproxy-config-examples/900c1a7681bed9879d328d7de0c8306223b62474/05-template-groups/screenshot.png -------------------------------------------------------------------------------- /06-standalone-docker-swarm/README.md: -------------------------------------------------------------------------------- 1 | # Example: standalone ShinyProxy with a docker swarm 2 | 3 | This example is very similar to example '01-standalone-docker-engine', except that it connects to a docker swarm instead of a single-machine docker engine. As a result, when you launch a Shiny app, its container may run on any node in the docker swarm, at the swarm's discretion. 4 | 5 | ## How to run 6 | 7 | 1. Download [ShinyProxy](https://www.shinyproxy.io/downloads "ShinyProxy website") 8 | 2. Download the `application.yml` configuration file from the folder where this README is located. 9 | 3. Place the jar and yml files in the same directory, e.g. `/home/user/sp` 10 | 4. Open a terminal, go to the directory `/home/user/sp`, and run the following command: 11 | 12 | `java -jar shinyproxy.jar` 13 | 14 | ## Notes on the configuration 15 | 16 | * ShinyProxy will listen for HTTP traffic on port `8080`. 17 | * ShinyProxy connects to the Docker daemon using a Unix socket, make sure the 18 | user running ShinyProxy has permission 19 | to [use this socket](https://shinyproxy.io/documentation/getting-started/#access-to-docker-daemon). 20 | -------------------------------------------------------------------------------- /06-standalone-docker-swarm/application.yml: -------------------------------------------------------------------------------- 1 | proxy: 2 | port: 8080 3 | authentication: simple 4 | admin-groups: admins 5 | users: 6 | - name: jack 7 | password: password 8 | groups: admins 9 | - name: jeff 10 | password: password 11 | container-backend: docker-swarm 12 | specs: 13 | - id: 01_hello 14 | display-name: Hello Application 15 | description: Application which demonstrates the basics of a Shiny app 16 | container-cmd: ["R", "-e", "shinyproxy::run_01_hello()"] 17 | container-image: openanalytics/shinyproxy-demo 18 | - id: 06_tabsets 19 | container-cmd: ["R", "-e", "shinyproxy::run_06_tabsets()"] 20 | container-image: openanalytics/shinyproxy-demo 21 | 22 | logging: 23 | file: 24 | name: shinyproxy.log 25 | -------------------------------------------------------------------------------- /07-containerized-docker-swarm/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM openanalytics/shinyproxy:3.1.1 2 | 3 | COPY application.yml /opt/shinyproxy/application.yml 4 | -------------------------------------------------------------------------------- /07-containerized-docker-swarm/README.md: -------------------------------------------------------------------------------- 1 | # Example: containerized ShinyProxy with a docker swarm 2 | 3 | In this example, ShinyProxy will run inside a Docker Swarm cluster. Shiny containers will also be spawned 4 | in the same cluster. To make the application accessible outside the cluster, a NodePort service is created. 5 | 6 | ## How to run 7 | 8 | 1. Download the `Dockerfile` from the folder where this README is located. 9 | 2. Download the `application.yml` configuration file from the folder where this README is located. 10 | 3. Place the files in the same directory, e.g. `/home/user/sp` 11 | 4. Open a terminal, go to the directory `/home/user/sp`, and run the following command to build the ShinyProxy image: 12 | 13 | `sudo docker build . -t shinyproxy-example` 14 | 15 | 5. Create a docker network that ShinyProxy will use to communicate with the Shiny containers. 16 | 17 | `sudo docker network create -d overlay --attachable sp-example-net` 18 | 19 | 6. Run the following command to launch the ShinyProxy container: 20 | 21 | `sudo docker service create --name sp-test-service --mount type=bind,src=/var/run/docker.sock,dst=/var/run/docker.sock --publish 8080:8080 --network sp-example-net --group $(getent group docker | cut -d: -f3) shinyproxy-example` 22 | 23 | ## Notes on the configuration 24 | 25 | * ShinyProxy will listen for HTTP traffic on port `8080` of each swarm node. 26 | 27 | * The overlay network `sp-example-net` is needed to allow the containers to access each other using 28 | the container ID as hostname. 29 | -------------------------------------------------------------------------------- /07-containerized-docker-swarm/application.yml: -------------------------------------------------------------------------------- 1 | proxy: 2 | port: 8080 3 | authentication: simple 4 | admin-groups: admins 5 | users: 6 | - name: jack 7 | password: password 8 | groups: admins 9 | - name: jeff 10 | password: password 11 | container-backend: docker-swarm 12 | docker: 13 | internal-networking: true 14 | specs: 15 | - id: 01_hello 16 | display-name: Hello Application 17 | description: Application which demonstrates the basics of a Shiny app 18 | container-cmd: ["R", "-e", "shinyproxy::run_01_hello()"] 19 | container-image: openanalytics/shinyproxy-demo 20 | container-network: sp-example-net 21 | - id: 06_tabsets 22 | container-cmd: ["R", "-e", "shinyproxy::run_06_tabsets()"] 23 | container-image: openanalytics/shinyproxy-demo 24 | container-network: sp-example-net 25 | 26 | logging: 27 | file: 28 | name: shinyproxy.log 29 | -------------------------------------------------------------------------------- /08-embedded-app/README.md: -------------------------------------------------------------------------------- 1 | # Example: embed an app in a website 2 | 3 | Shiny apps are often embedded in other, larger contexts. For example, consider a 4 | portal-style webpage that shows a dashboard with components from different 5 | sources, including one or more Shiny apps. 6 | 7 | Apps running in ShinyProxy can easily be embedded into other webpages. 8 | 9 | 1. launch ShinyProxy using the `application.yml` from this directory (e.g. in a similar way as [`01-standalone-docker-engine`](../01-standalone-docker-engine)) 10 | 2. launch a simple python web server to serve the [`index.html`](index.html) file: 11 | 12 | ```bash 13 | python3 -m http.server 14 | ``` 15 | 16 | 3. open the web page on 17 | 18 | **Notes:** 19 | * The example configuration hides the navigation bar, however, this is not 20 | required. 21 | * By 22 | using [`frame-options`](https://shinyproxy.io/documentation/configuration/#frame-options) 23 | property you can restrict which websites can embed your applications. 24 | -------------------------------------------------------------------------------- /08-embedded-app/application.yml: -------------------------------------------------------------------------------- 1 | proxy: 2 | port: 8080 3 | authentication: none 4 | hide-navbar: true 5 | specs: 6 | - id: 01_hello 7 | display-name: Hello Application 8 | description: Application which demonstrates the basics of a Shiny app 9 | container-cmd: ["R", "-e", "shinyproxy::run_01_hello()"] 10 | container-image: openanalytics/shinyproxy-demo 11 | - id: 06_tabsets 12 | container-cmd: ["R", "-e", "shinyproxy::run_06_tabsets()"] 13 | container-image: openanalytics/shinyproxy-demo 14 | 15 | logging: 16 | file: 17 | name: shinyproxy.log 18 | -------------------------------------------------------------------------------- /08-embedded-app/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Example: embedding a Shiny app 5 | 6 | 20 | 21 | 22 |

This page embeds a Shiny App running in ShinyProxy!

23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /09-api-oauth2/README.md: -------------------------------------------------------------------------------- 1 | # Example: use the ShinyProxy API using OAuth2 2 | 3 | ShinyProxy has an API that can be protected using the OAuth2 authorization 4 | framework. OAuth2 can be combined with any authentication backend and is 5 | independent of using OpenID Connect (which allows authentication users in the 6 | browser). However, in most cases it makes sense to combine OAuth2 with using 7 | OpenID connect. 8 | 9 | [Complete API documentation](https://shinyproxy.io/documentation/api/) 10 | 11 | ## Without OpenID Connect 12 | 13 | 1. set up an OAuth2 server, in case you don't already have 14 | one, [Keycloak](https://keycloak.org) is a good option 15 | 2. launch ShinyProxy using the following configuration (adapt the OAuth2 16 | configuration): 17 | 18 | ```yaml 19 | proxy: 20 | port: 8080 21 | authentication: simple 22 | admin-groups: admins 23 | users: 24 | - name: jack 25 | password: password 26 | groups: admins 27 | - name: jeff 28 | password: password 29 | specs: 30 | - id: 01_hello 31 | display-name: Hello Application 32 | description: Application which demonstrates the basics of a Shiny app 33 | container-cmd: [ "R", "-e", "shinyproxy::run_01_hello()" ] 34 | container-image: openanalytics/shinyproxy-demo 35 | container-network: sp-example-net 36 | - id: 06_tabsets 37 | container-cmd: [ "R", "-e", "shinyproxy::run_06_tabsets()" ] 38 | container-image: openanalytics/shinyproxy-demo 39 | container-network: sp-example-net 40 | oauth2: 41 | resource-id: shinyproxy 42 | jwks-url: https://keycloak.example.com/auth/realms/master/protocol/openid-connect/certs 43 | roles-claim: realm_roles 44 | username-attribute: preferred_username 45 | ``` 46 | 47 | 3. create an OAuth2 Access token for your user, for example, using Keycloak and 48 | the direct authentication flow: 49 | 50 | ```bash 51 | KC_REALM=master 52 | KC_USERNAME=jack 53 | KC_PASSWORD="" 54 | KC_CLIENT=shinyproxy 55 | KC_CLIENT_SECRET="" 56 | KC_SERVER=https://keycloak.example.com 57 | KC_CONTEXT=auth 58 | 59 | KC_RESPONSE=$( 60 | curl $CURL_OPTS -X POST \ 61 | -H "Content-Type: application/x-www-form-urlencoded" \ 62 | -d "username=$KC_USERNAME" \ 63 | -d "password=$KC_PASSWORD" \ 64 | -d 'grant_type=password' \ 65 | -d "client_id=$KC_CLIENT" \ 66 | -d "client_secret=$KC_CLIENT_SECRET" \ 67 | "$KC_SERVER/$KC_CONTEXT/realms/$KC_REALM/protocol/openid-connect/token" | jq . 68 | 69 | 70 | echo $KC_RESPONSE | jq -r .access_token 71 | ``` 72 | 73 | 4. use the ShinyProxy API: 74 | 75 | ```bash 76 | curl -v http://localhost:8080/api/proxyspec \ 77 | -H "Authorization: Bearer " | jq . 78 | ``` 79 | 80 | This will output all the specs that are available to the user. 81 | 82 | ## Together with OpenID Connect 83 | 84 | 1. set up an OAuth2 server, in case you don't already have 85 | one, [Keycloak](https://keycloak.org) is a good option 86 | 2. launch ShinyProxy using the following configuration (adapt the OAuth2 87 | configuration, but make sure to use the same client): 88 | 89 | ```yaml 90 | proxy: 91 | port: 8080 92 | authentication: openid 93 | admin-groups: admins 94 | specs: 95 | - id: 01_hello 96 | display-name: Hello Application 97 | description: Application which demonstrates the basics of a Shiny app 98 | container-cmd: [ "R", "-e", "shinyproxy::run_01_hello()" ] 99 | container-image: openanalytics/shinyproxy-demo 100 | container-network: sp-example-net 101 | - id: 06_tabsets 102 | container-cmd: [ "R", "-e", "shinyproxy::run_06_tabsets()" ] 103 | container-image: openanalytics/shinyproxy-demo 104 | container-network: sp-example-net 105 | oauth2: 106 | resource-id: shinyproxy 107 | jwks-url: https://keycloak.example.com/auth/realms/master/protocol/openid-connect/certs 108 | roles-claim: realm_roles 109 | username-attribute: preferred_username 110 | openid: 111 | auth-url: https://keycloak.example.com/auth/realms/master/protocol/openid-connect/auth 112 | token-url: https://keycloak.example.com/auth/realms/master/protocol/openid-connect/token 113 | jwks-url: https://keycloak.example.com/auth/realms/master/protocol/openid-connect/certs 114 | client-id: shinyproxy 115 | client-secret: 116 | roles-claim: realm_roles 117 | username-attribute: preferred_username 118 | ``` 119 | 120 | 3. start an application. Every application will automatically get 121 | the `SHINYPROXY_OIDC_ACCESS_TOKEN` environment variable, which will contain a 122 | valid access token for the authenticated user. 123 | 4. use the ShinyProxy API: 124 | 125 | ```bash 126 | curl -v http://localhost:8080/api/proxyspec \ 127 | -H "Authorization: Bearer $SHINYPROXY_OIDC_ACCESS_TOKEN" | jq . 128 | ``` 129 | 130 | This will output all the specs that are available to the user. 131 | 132 | This set up allows your app to seamlessly authenticate with the ShinyProxy API 133 | because it uses the same IDP and client for both authentication the user and for 134 | securing the ShinyProxy API. 135 | -------------------------------------------------------------------------------- /10-background-apps/README.md: -------------------------------------------------------------------------------- 1 | # Example: background apps in ShinyProxy 2 | 3 | This example demonstrate how to configure ShinyProxy to allow apps running in 4 | the background. See the [`application.yml`](application.yml) file for the 5 | details of the examples. This example requires at least ShinyProxy 2.6.0. 6 | 7 | ## How to run 8 | 9 | 1. Download [ShinyProxy](https://www.shinyproxy.io/downloads "ShinyProxy website") 10 | 2. Download the `application.yml` configuration file from the folder where this README is located. 11 | 3. Place the jar and yml files in the same directory, e.g. `/home/user/sp` 12 | 4. Open a terminal, go to the directory `/home/user/sp`, and run the following command: 13 | 14 | `java -jar shinyproxy.jar` -------------------------------------------------------------------------------- /10-background-apps/application.yml: -------------------------------------------------------------------------------- 1 | proxy: 2 | port: 8080 3 | authentication: simple 4 | admin-groups: admins 5 | users: 6 | - name: jack 7 | password: password 8 | groups: admins 9 | - name: jeff 10 | password: password 11 | specs: 12 | - id: 01_hello 13 | display-name: Hello Application 14 | description: Default behaviour, app is stopped if not opened for 60 seconds. App is also stopped if the user logs out. If opened in a browser (or closed for less than 60 seconds and re-opened), the app can run indefinitely. 15 | container-image: openanalytics/shinyproxy-demo 16 | 17 | - id: 01_hello2 18 | display-name: Hello Application 19 | description: App is stopped if not opened for 5 minutes. App is also stopped if the user logs out. If opened in a browser (or closed for less than 5 minutes and re-opened), the app can run indefinitely. 20 | container-image: openanalytics/shinyproxy-demo 21 | heartbeat-timeout: 3600000 22 | 23 | - id: rstudio1 24 | displayName: RStudio 25 | description: A RStudio server that is never terminated by ShinyProxy 26 | containerImage: openanalytics/shinyproxy-rstudio-ide-demo:1.4.1106__4.0.4 27 | port: 8787 28 | container-env: 29 | DISABLE_AUTH: true 30 | WWW_ROOT_PATH: "#{proxy.getRuntimeValue('SHINYPROXY_PUBLIC_PATH')}" 31 | heartbeat-timeout: -1 32 | stop-on-logout: false 33 | max-lifetime: -1 34 | 35 | - id: rstudio2 36 | displayName: RStudio 37 | description: A RStudio server that is only terminated after 24 hours, even if not opened in a browser or if the user logs out. 38 | containerImage: openanalytics/shinyproxy-rstudio-ide-demo:1.4.1106__4.0.4 39 | port: 8787 40 | container-env: 41 | DISABLE_AUTH: true 42 | WWW_ROOT_PATH: "#{proxy.getRuntimeValue('SHINYPROXY_PUBLIC_PATH')}" 43 | heartbeat-timeout: -1 44 | stop-on-logout: false 45 | max-lifetime: 1440 46 | 47 | - id: rstudio3 48 | displayName: RStudio 49 | containerImage: openanalytics/shinyproxy-rstudio-ide-demo:1.4.1106__4.0.4 50 | description: A RStudio server that is only terminated when the user logs out 51 | port: 8787 52 | container-env: 53 | DISABLE_AUTH: true 54 | WWW_ROOT_PATH: "#{proxy.getRuntimeValue('SHINYPROXY_PUBLIC_PATH')}" 55 | heartbeat-timeout: -1 56 | stop-on-logout: true 57 | max-lifetime: -1 58 | 59 | stop-proxies-on-shutdown: false 60 | recover-running-proxies: true 61 | 62 | logging: 63 | file: 64 | name: shinyproxy.log 65 | -------------------------------------------------------------------------------- /11-openid-azure-b2c/README.md: -------------------------------------------------------------------------------- 1 | # Example: integrating ShinyProxy with Azure B2C 2 | 3 | ShinyProxy can integrate with any OIDC provider, this example specifically 4 | demonstrates how to integrate ShinyProxy with Azure B2C. 5 | 6 | > [!NOTE] 7 | > We do our best to document the steps in Azure B2C, however, the Azure Portal 8 | > may change and this documentation may get outdated. Please open an issue or PR 9 | > in this case. 10 | 11 | It's a good idea to first read the 12 | general [ShinyProxy OpenID documentation](https://shinyproxy.io/documentation/configuration/#openid-connect-oidc). 13 | 14 | ## Configuring Azure B2C 15 | 16 | 1. Log into the Azure Portal 17 | 2. Go to the `Azure AD B2C` service 18 | 3. Click on `App registrations` 19 | 4. Click on `New registration` 20 | 5. Fill in a name for the registration 21 | 6. Choose `Accounts in this organizational directory only`. Do not use the other 22 | options (not even for testing), unless you are aware of the implications. 23 | 7. In the `Redirect URI` section, choose `Web` and use the following value 24 | (replacing `shinyproxy-demo.local` with your domain name): 25 | 26 | ``` 27 | https://shinyproxy-demo.local/login/oauth2/code/shinyproxy 28 | ``` 29 | 30 | 8. The filled in form should look like: 31 | 32 | [![](img/01_register.png)](img/01_register.png) 33 | 34 | 9. Click `Register` 35 | 10. Go to `Certificates & secrets` 36 | 11. Click `Client secrets` 37 | 12. Click `New client secret` 38 | 13. Give a description and choose an expire time. You'll have to create a new 39 | secret and update the ShinyProxy config before the timeout. 40 | 41 | [![](img/02_create_secret.png)](img/02_create_secret.png) 42 | 43 | 14. On the next page, copy the secret in the `Value` column. Make sure to copy 44 | it now, since you'll not be able to retrieve it later. Make sure to not use 45 | the `Secret ID`, you'll not need this value for the configuration. 46 | 47 | [![](img/03_secret.png)](img/03_secret.png) 48 | 49 | 15. Go back to the `Overview` page copy the `Application (client) ID`. You'll 50 | need this in the ShinyProxy configuration. 51 | 52 | [![](img/04_info.png)](img/04_info.png) 53 | 54 | 16. Click on `Endpoints` 55 | 17. Copy the `OAuth 2.0 Authorization endpoint (v2)` 56 | and `OAuth 2.0 token endpoint (v2)` URLs. You'll need this in the ShinyProxy 57 | configuration. 58 | 18. Click on `OpenID Connect metadata document` and copy the `jwks_uri` value. 59 | You'll need this in the ShinyProxy. 60 | 61 | ## Configuring ShinyProxy 62 | 63 | Now that you configured Azure B2C and you retrieved all parameters, you can 64 | configure ShinyProxy. 65 | 66 | 1. Add the following configuration to your ShinyProxy config (replace the 67 | examples with the values you retrieved from the Azure portal): 68 | 69 | ```yaml 70 | proxy: 71 | authentication: openid 72 | openid: 73 | # see step 17 (of previous section): OAuth 2.0 Authorization endpoint (v2) 74 | auth-url: https://login.microsoftonline.com/.../oauth2/v2.0/authorize 75 | # see step 17 (of previous section): OAuth 2.0 token endpoint (v2) 76 | token-url: https://login.microsoftonline.com/.../oauth2/v2.0/token 77 | # see step 18 (of previous section) 78 | jwks-url: https://login.microsoftonline.com/.../discovery/v2.0/keys 79 | # see step 15 (of previous section) 80 | client-id: 9edf3fd9-.... 81 | # see step 14 (of previous section) 82 | client-secret: eB... 83 | username-attribute: sub 84 | scopes: 85 | - offline_access 86 | ``` 87 | 88 | 2. Restart ShinyProxy 89 | 90 | You should now be able to log in on ShinyProxy using an Azure user. You can 91 | create additional users by going to the `Users` page in Azure. 92 | 93 | ## Configuring the username 94 | 95 | The current setup will use the `sub` (subject) of the user to identify it (this 96 | is e.g. shown in the navigation bar of ShinyProxy). This is a generated value 97 | and is not user-friendly. We can configure Azure B2C to send the e-mail address 98 | of the user. The same steps can be used to use a different property. 99 | 100 | 1. In Azure B2C, go to `Token configuration` 101 | 2. Click on `Add optional claim` 102 | 3. Choose `ID` as `Token type` 103 | 4. Select `email` from the list 104 | 105 | [![](img/05_email.png)](img/05_email.png) 106 | 107 | 5. Click on `Add` 108 | 6. In the pop-up that is opened, 109 | select `Turn on the Microsfot Graph email permissions` and click `Add`: 110 | 111 | [![](img/06_email.png)](img/06_email.png) 112 | 113 | 7. Change the `proxy.openid.username-attribute` in ShinyProxy to `email`: 114 | 115 | ```yaml 116 | proxy: 117 | openid: 118 | username-attribute: email 119 | ``` 120 | 121 | 8. Restart ShinyProxy 122 | 123 | When a user now logs in on ShinyProxy, the user is identified by their email 124 | address. 125 | 126 | It's possible to see all claims which are being sent to ShinyProxy, 127 | see [the documentation](https://shinyproxy.io/documentation/troubleshooting/#listing-all-claims-sent-by-the-openid-provider). 128 | 129 | > [!NOTE] 130 | > Make sure that every user has an e-mail address configured. Otherwise, the 131 | > user will get an error when logging in. 132 | 133 | ## Configuring groups 134 | 135 | ShinyProxy can use the groups configured in Azure for authorization: 136 | 137 | > [!NOTE] 138 | > Azure B2C will send the id of the group, instead of a human-friendly name. 139 | > As far as we know, there is currently no proper way to send the name of the 140 | > groups. 141 | 142 | 1. In Azure B2C, go to `Token configuration` 143 | 2. Click on `Add groups claim` 144 | 3. Select `All groups` (the other options will work as well, this depends on 145 | your use-case) 146 | 4. Click on `Group ID` inside the `ID` section 147 | 148 | [![](img/07_groups.png)](img/07_groups.png) 149 | 150 | 5. Click `Add` 151 | 6. Add the `proxy.openid.roles-claim` property to the ShinyProxy config: 152 | 153 | ```yaml 154 | proxy: 155 | openid: 156 | roles-claim: groups 157 | ``` 158 | 159 | 7. Restart ShinyProxy 160 | 161 | When a user now logs in on ShinyProxy, Azure B2C sends the groups of that user 162 | to ShinyProxy. You can check whether this works by starting an app and 163 | retrieving the `SHINYPROXY_USERGROUPS` environment variable. 164 | 165 | ## Logout 166 | 167 | When clicking the logout button in the current setup, the user will just be 168 | logged out from ShinyProxy. You can configure ShinyProxy to logout the user in 169 | Azure B2C: 170 | 171 | 1. Go to the `OpenID Connect metadata document` in Azure B2C (see the first 172 | section) and copy the `end_session_endpoint` value. 173 | 2. Add the `proxy.openid.logout-url` property to the ShinyProxy config: 174 | 175 | ```yaml 176 | proxy: 177 | openid: 178 | logout-url: https://login.microsoftonline.com/.../oauth2/v2.0/logout 179 | ``` 180 | 181 | 3. Restart ShinyProxy 182 | 183 | You can optionally add the `post_logout_redirect_uri` parameter if you want to 184 | redirect the user back to ShinyProxy e.g.: 185 | 186 | ```yaml 187 | proxy: 188 | openid: 189 | logout-url: https://login.microsoftonline.com/.../oauth2/v2.0/logout?post_logout_redirect_uri=http%3A%2F%2Fshinyproxy-demo.local/logout-success 190 | ``` 191 | 192 | ## References 193 | 194 | - [Azure AD B2C documentation](https://learn.microsoft.com/en-us/azure/active-directory-b2c/) 195 | - [ShinyProxy OpenID documentation](https://shinyproxy.io/documentation/configuration/#openid-connect-oidc) 196 | - [ShinyProxy SpEL documentation](https://shinyproxy.io/documentation/spel/) 197 | - [ShinyProxy Troubleshooting OpenID documentation](https://shinyproxy.io/documentation/troubleshooting/#openid-connect-oidc) 198 | - [ShinyProxy Docs on using environment variables](https://shinyproxy.io/documentation/configuration/#config-env-var) 199 | -------------------------------------------------------------------------------- /11-openid-azure-b2c/img/01_register.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openanalytics/shinyproxy-config-examples/900c1a7681bed9879d328d7de0c8306223b62474/11-openid-azure-b2c/img/01_register.png -------------------------------------------------------------------------------- /11-openid-azure-b2c/img/02_create_secret.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openanalytics/shinyproxy-config-examples/900c1a7681bed9879d328d7de0c8306223b62474/11-openid-azure-b2c/img/02_create_secret.png -------------------------------------------------------------------------------- /11-openid-azure-b2c/img/03_secret.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openanalytics/shinyproxy-config-examples/900c1a7681bed9879d328d7de0c8306223b62474/11-openid-azure-b2c/img/03_secret.png -------------------------------------------------------------------------------- /11-openid-azure-b2c/img/04_info.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openanalytics/shinyproxy-config-examples/900c1a7681bed9879d328d7de0c8306223b62474/11-openid-azure-b2c/img/04_info.png -------------------------------------------------------------------------------- /11-openid-azure-b2c/img/05_email.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openanalytics/shinyproxy-config-examples/900c1a7681bed9879d328d7de0c8306223b62474/11-openid-azure-b2c/img/05_email.png -------------------------------------------------------------------------------- /11-openid-azure-b2c/img/06_email.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openanalytics/shinyproxy-config-examples/900c1a7681bed9879d328d7de0c8306223b62474/11-openid-azure-b2c/img/06_email.png -------------------------------------------------------------------------------- /11-openid-azure-b2c/img/07_groups.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openanalytics/shinyproxy-config-examples/900c1a7681bed9879d328d7de0c8306223b62474/11-openid-azure-b2c/img/07_groups.png -------------------------------------------------------------------------------- /12-openid-keycloak/README.md: -------------------------------------------------------------------------------- 1 | # Example: integrating ShinyProxy with Keycloak 2 | 3 | ShinyProxy can integrate with any OIDC provider, this example specifically 4 | demonstrates how to integrate ShinyProxy with Keycloak. 5 | 6 | > [!NOTE] 7 | > We do our best to document the steps in Keycloak, however, Keycloak 8 | > may change and this documentation may get outdated. Please open an issue or PR 9 | > in this case. 10 | 11 | It's a good idea to first read the 12 | general [ShinyProxy OpenID documentation](https://shinyproxy.io/documentation/configuration/#openid-connect-oidc). 13 | 14 | ## Setting up Keycloak 15 | 16 | The complete setup and configuration of Keycloak is out of the scope of this 17 | example. Check 18 | the [Keycloak documentation](https://www.keycloak.org/documentation) 19 | and 20 | the [Docker example](https://www.keycloak.org/getting-started/getting-started-docker) 21 | for more information. 22 | 23 | ## Configuring Keycloak 24 | 25 | 1. Login into Keycloak 26 | 2. Create or select a realm 27 | 3. Click on `Clients`, Click on `Create client` 28 | 4. Provide a `Client ID` and remember this value, click `Next` 29 | 30 | [![](img/01_create_client.png)](img/01_create_client.png) 31 | 32 | 5. Enable `Client authentication`, disable `Direct access grants`, make 33 | sure `Standard flow` is enabled, click `Next` 34 | 35 | [![](img/02_create_client.png)](img/02_create_client.png) 36 | 37 | 6. Fill in the location of ShinyProxy (without any sub-path) in `Root URL` 38 | and `Home URL`. 39 | 7. Add the following as `Valid redirect URI` (replacing `shinyproxy-demo.local` 40 | with your domain name): 41 | 42 | ``` 43 | https://shinyproxy-demo.local/login/oauth2/code/shinyproxy 44 | ``` 45 | 46 | 8. The filled in form should look like: 47 | 48 | [![](img/03_create_client.png)](img/03_create_client.png) 49 | 50 | 9. Go to the `Credentials` tab and copy the `Client secret` value. You'll need 51 | this in the ShinyProxy configuration. 52 | 53 | [![](img/04_client_secret.png)](img/04_client_secret.png) 54 | 55 | 10. Go to `Realm settings` (in the sidebar) and click 56 | on `OpenID Endpoint Configuration` 57 | 58 | [![](img/05_endpoints.png)](img/05_endpoints.png) 59 | 60 | 11. Copy the following fields from the JSON: 61 | 62 | - `authorization_endpoint` 63 | - `token_endpoint` 64 | - `jwks_uri` 65 | 66 | ## Configuring ShinyProxy 67 | 68 | Now that you configured Keycloak and you retrieved all parameters, you can 69 | configure ShinyProxy. 70 | 71 | 1. Add the following configuration to your ShinyProxy config (replace the 72 | examples with the values you retrieved from Keycloak): 73 | 74 | ```yaml 75 | proxy: 76 | authentication: openid 77 | openid: 78 | # see step 11 (of previous section): authorization_endpoint 79 | auth-url: https://keycloak-demo.local/auth/realms/master/protocol/openid-connect/auth 80 | # see step 11 (of previous section): token_endpoint 81 | token-url: https://keycloak-demo.local/auth/realms/master/protocol/openid-connect/token 82 | # see step 11 (of previous section): jwks_uri 83 | jwks-url: https://keycloak-demo.local/auth/realms/master/protocol/openid-connect/certs 84 | # see step 4 (of previous section) 85 | client-id: shinyproxy-config-example 86 | # see step 9 (of previous section) 87 | client-secret: 1aB... 88 | username-attribute: preferred_username 89 | ``` 90 | 91 | 2. Restart ShinyProxy 92 | 93 | You should now be able to log in on ShinyProxy using a Keycloak user. You can 94 | create additional users by going to the `Users` page in Keycloak. 95 | 96 | ## Configuring the username 97 | 98 | The current setup will use the `preferred_username` of the user to identify it 99 | (this is e.g. shown in the navigation bar of ShinyProxy). This corresponds to 100 | the username field in the Keycloak UI. By default keycloak sends most user 101 | information as part of the ID token. Therefore, you can immediately use these 102 | values to identify the user. For example, to use the e-mail address, change the 103 | change the `proxy.openid.username-attribute` in ShinyProxy to `email`: 104 | 105 | ```yaml 106 | proxy: 107 | openid: 108 | username-attribute: email 109 | ``` 110 | 111 | > [!NOTE] 112 | > Make sure that every user has an e-mail address configured. Otherwise, the 113 | > user will get an error when logging in. 114 | 115 | It's possible to see all claims which are being sent to ShinyProxy, 116 | see [the documentation](https://shinyproxy.io/documentation/troubleshooting/#listing-all-claims-sent-by-the-openid-provider). 117 | 118 | ## Configuring roles 119 | 120 | In Keycloak, it is possible to assign (multiple) roles to a user. These roles 121 | can be used in ShinyProxy as groups, e.g. for adding authorization to apps. 122 | 123 | 1. Login into Keycloak 124 | 2. Select a realm 125 | 3. Go to `Clients` and select the client you created for ShinyProxy 126 | 4. Go the `Client Scopes` tab 127 | 5. Click on the first scope, e.g. `shinyproxy-config-example-dedicated` 128 | 129 | [![](img/06_roles.png)](img/06_roles.png) 130 | 131 | 6. Click on `Add mapper`, click on `By configuration` (or immediately click 132 | on `Configure a new mapper`) 133 | 7. Click on `User Realm Role` 134 | 8. Use `realm_roles` as `Name` 135 | 9. Keep `Realm Role prefix` empty 136 | 10. Keep the `Multivalued` option enabled, 137 | 11. Use `realm_roles` as `Token Claim Name` 138 | 12. Use `String` as `Claim JSON type` 139 | 13. Ensure the `Add to ID token` option is selected 140 | 14. The `Add to access token` or `Add to userinfo` options may optionally be 141 | enabled, but are not required for ShinyProxy 142 | 143 | [![](img/07_roles.png)](img/07_roles.png) 144 | 145 | 15. Click `Save` 146 | 16. Add the `proxy.openid.roles-claim` property to the ShinyProxy config: 147 | 148 | ```yaml 149 | proxy: 150 | openid: 151 | roles-claim: realm_roles 152 | ``` 153 | 154 | 17. Restart ShinyProxy 155 | 156 | When a user now logs in on ShinyProxy, Keycloak sends the roles of that user to 157 | ShinyProxy. You can check whether this works by starting an app and retrieving 158 | the `SHINYPROXY_USERGROUPS` environment variable. 159 | 160 | ## Configuring groups 161 | 162 | In addition to roles, Keycloak also supports the concept of groups, 163 | see [Keycloak docs](https://www.keycloak.org/docs/latest/server_admin/#con-comparing-groups-roles_server_administration_guide) 164 | for the difference. Both Keycloak groups and Keycloak roles can be used in 165 | ShinyProxy as groups, e.g. for adding authorization to apps. It is, however, not 166 | possible to use both roles and groups at the same time in ShinyProxy. 167 | 168 | 1. Login into Keycloak 169 | 2. Select a realm 170 | 3. Go to `Clients` and select the client you created for ShinyProxy 171 | 4. Go the `Client Scopes` tab 172 | 5. Click on the first scope, e.g. `shinyproxy-config-example-dedicated` 173 | 174 | [![](img/06_roles.png)](img/06_roles.png) 175 | 176 | 6. Click on `Add mapper`, click on `By configuration` (or immediately click 177 | on `Configure a new mapper`) 178 | 7. Click on `Group Membership` 179 | 8. Use `groups` as `Name` 180 | 9. Use `groups` as `Token Claim Name` 181 | 10. Switch off the `Full group path` option 182 | 11. Ensure the `Add to ID token` option is selected 183 | 12. The `Add to access token` or `Add to userinfo` options may optionally be 184 | enabled, but are not required for ShinyProxy 185 | 186 | [![](img/08_groups.png)](img/08_groups.png) 187 | 188 | 13. Click `Save` 189 | 14. Add the `proxy.openid.roles-claim` property to the ShinyProxy config: 190 | 191 | ```yaml 192 | proxy: 193 | openid: 194 | roles-claim: groups 195 | ``` 196 | 197 | 15. Restart ShinyProxy 198 | 199 | When a user now logs in on ShinyProxy, Keycloak sends the groups of that user to 200 | ShinyProxy. You can check whether this works by starting an app and retrieving 201 | the `SHINYPROXY_USERGROUPS` environment variable. 202 | 203 | **Note**: do not use the built-in groups mapper of Keycloak as this mapper 204 | provides the realm-roles of the user and not the groups. 205 | 206 | ## Logout 207 | 208 | When clicking the logout button in the current setup, the user will just be 209 | logged out from ShinyProxy. You can configure ShinyProxy to logout the user in 210 | Keycloak: 211 | 212 | 1. Go to the `OpenID Endpoint Configuration` in Keycloak (see the first section) 213 | and copy the `end_session_endpoint` value. 214 | 2. Add the `proxy.openid.logout-url` property to the ShinyProxy config: 215 | 216 | ```yaml 217 | proxy: 218 | openid: 219 | logout-url: https://keycloak-demo.local/auth/realms/master/protocol/openid-connect/logout 220 | ``` 221 | 222 | 3. Restart ShinyProxy 223 | 224 | **Note**: 225 | 226 | - When a user clicks the logout button, Keycloak will ask the user whether they 227 | really want to logout: 228 | 229 | [![](img/09_logout.png)](img/09_logout.png) 230 | 231 | If you want to automatically sign out the user you'll have to 232 | add `?id_token_hint=#{oidcUser.idToken.tokenValue}` to the end of 233 | the `logout-url`: 234 | 235 | ```yaml 236 | proxy: 237 | openid: 238 | logout-url: https://keycloak-demo.local/auth/realms/master/protocol/openid-connect/logout?id_token_hint=#{oidcUser.idToken.tokenValue} 239 | ``` 240 | 241 | - When the user is logged out, Keycloak shows a confirmation screen: 242 | 243 | [![](img/10_logout.png)](img/10_logout.png) 244 | 245 | You can redirect the user to a different URL by adding 246 | the `post_logout_redirect_uri` parameter to the URL (in this case 247 | the `id_token_hint` is mandatory): 248 | 249 | ```yaml 250 | proxy: 251 | openid: 252 | logout-url: https://keycloak-demo.local/auth/realms/master/protocol/openid-connect/logout?id_token_hint=#{oidcUser.idToken.tokenValue}&post_logout_redirect_uri=http%3A%2F%2Fshinyproxy-demo.local/logout-success 253 | ``` 254 | 255 | In this setup Keycloak is (virtually) invisible to the user. 256 | 257 | ## Migration from Keycloak authentication 258 | 259 | Up to version 3.0.x, ShinyProxy included a dedicated `keycloak` authentication 260 | backend. This backend was using the official Keycloak Java library, however, 261 | this library was deprecated by the Keycloak developers. In addition, 262 | the `openid` backend is fully compatible with Keycloak and has more 263 | (advances) features. Therefore, we deprecated the `keycloak` backend in 264 | ShinyProxy 3.0.0 (by adding a warning in the release notes and documentation) 265 | and starting with version 3.1.0 of ShinyProxy, the keycloak backend has been 266 | removed. This section explains how you can easily migrate from the old backend 267 | to the new one. 268 | 269 | 1. Find you current Keycloak configuration, for example: 270 | 271 | ```yaml 272 | proxy: 273 | authentication: keycloak 274 | keycloak: 275 | realm: master 276 | auth-server-url: https://keycloak-demo.local/auth 277 | resource: shinyproxy-config-example 278 | credentials-secret: example_secret 279 | ``` 280 | 281 | 2. Follow the steps in [Configuring roles](#configuring-roles), such that 282 | Keycloak sends the roles of a user to ShinyPoxy 283 | 3. Change the ShinyProxy config to use OpenID (remove the keycloak config): 284 | 285 | ```yaml 286 | proxy: 287 | authentication: openid 288 | openid: 289 | # corresponds to {auth-server-url}/realms/{realm}/protocol/openid-connect/auth 290 | # where "{auth-server-url} and "{realm}" are the old Keycloak configuration properties 291 | auth-url: https://keycloak-demo.local/auth/realms/master/protocol/openid-connect/auth 292 | # corresponds to {auth-server-url}/realms/{realm}/protocol/openid-connect/token 293 | # where "{auth-server-url} and "{realm}" are the old Keycloak configuration properties 294 | token-url: https://keycloak-demo.local/auth/realms/master/protocol/openid-connect/token 295 | # corresponds to {auth-server-url}/realms/{realm}/protocol/openid-connect/certs 296 | # where "{auth-server-url} and "{realm}" are the old Keycloak configuration properties 297 | jwks-url: https://keycloak-demo.local/auth/realms/master/protocol/openid-connect/certs 298 | # corresponds to "resource" in the old Keycloak configuration properties 299 | client-id: shinyproxy-config-example 300 | # corresponds to "credentials-secret" in the old Keycloak configuration properties 301 | client-secret: example_secret 302 | # equivalent of proxy.keycloak.name-attribute 303 | # the Keycloak default is "name" 304 | username-attribute: name 305 | ``` 306 | 307 | 4. [Configure the logout url](#logout) 308 | 5. Optionally: [Configure the username](#configuring-the-username) 309 | 6. Restart ShinyProxy 310 | 311 | ## References 312 | 313 | - [Keycloak getting started docs](https://www.keycloak.org/guides#getting-started) 314 | - [Keycloak Server Administration docs](https://www.keycloak.org/docs/latest/server_admin/index.html) 315 | - [ShinyProxy OpenID documentation](https://shinyproxy.io/documentation/configuration/#openid-connect-oidc) 316 | - [ShinyProxy SpEL documentation](https://shinyproxy.io/documentation/spel/) 317 | - [ShinyProxy Troubleshooting OpenID documentation](https://shinyproxy.io/documentation/troubleshooting/#openid-connect-oidc) 318 | - [ShinyProxy Docs on using environment variables](https://shinyproxy.io/documentation/configuration/#config-env-var) 319 | -------------------------------------------------------------------------------- /12-openid-keycloak/img/01_create_client.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openanalytics/shinyproxy-config-examples/900c1a7681bed9879d328d7de0c8306223b62474/12-openid-keycloak/img/01_create_client.png -------------------------------------------------------------------------------- /12-openid-keycloak/img/02_create_client.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openanalytics/shinyproxy-config-examples/900c1a7681bed9879d328d7de0c8306223b62474/12-openid-keycloak/img/02_create_client.png -------------------------------------------------------------------------------- /12-openid-keycloak/img/03_create_client.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openanalytics/shinyproxy-config-examples/900c1a7681bed9879d328d7de0c8306223b62474/12-openid-keycloak/img/03_create_client.png -------------------------------------------------------------------------------- /12-openid-keycloak/img/04_client_secret.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openanalytics/shinyproxy-config-examples/900c1a7681bed9879d328d7de0c8306223b62474/12-openid-keycloak/img/04_client_secret.png -------------------------------------------------------------------------------- /12-openid-keycloak/img/05_endpoints.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openanalytics/shinyproxy-config-examples/900c1a7681bed9879d328d7de0c8306223b62474/12-openid-keycloak/img/05_endpoints.png -------------------------------------------------------------------------------- /12-openid-keycloak/img/06_roles.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openanalytics/shinyproxy-config-examples/900c1a7681bed9879d328d7de0c8306223b62474/12-openid-keycloak/img/06_roles.png -------------------------------------------------------------------------------- /12-openid-keycloak/img/07_roles.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openanalytics/shinyproxy-config-examples/900c1a7681bed9879d328d7de0c8306223b62474/12-openid-keycloak/img/07_roles.png -------------------------------------------------------------------------------- /12-openid-keycloak/img/08_groups.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openanalytics/shinyproxy-config-examples/900c1a7681bed9879d328d7de0c8306223b62474/12-openid-keycloak/img/08_groups.png -------------------------------------------------------------------------------- /12-openid-keycloak/img/09_logout.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openanalytics/shinyproxy-config-examples/900c1a7681bed9879d328d7de0c8306223b62474/12-openid-keycloak/img/09_logout.png -------------------------------------------------------------------------------- /12-openid-keycloak/img/10_logout.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openanalytics/shinyproxy-config-examples/900c1a7681bed9879d328d7de0c8306223b62474/12-openid-keycloak/img/10_logout.png -------------------------------------------------------------------------------- /13-openid-auth0/README.md: -------------------------------------------------------------------------------- 1 | # Example: integrating ShinyProxy with Auth0 2 | 3 | ShinyProxy can integrate with any OIDC provider, this example specifically 4 | demonstrates how to integrate ShinyProxy with Auth0. 5 | 6 | > [!NOTE] 7 | > We do our best to document the steps in Auth0, however, Auth0 8 | > may change and this documentation may get outdated. Please open an issue or PR 9 | > in this case. 10 | 11 | It's a good idea to first read the 12 | general [ShinyProxy OpenID documentation](https://shinyproxy.io/documentation/configuration/#openid-connect-oidc). 13 | 14 | ## Configuring Auth0 15 | 16 | 1. Login into Auth0 17 | 2. Click on `Applications` and click again on `Applications` 18 | 3. Click on `Create Application` 19 | 4. Provide a name for the application and select `Native` 20 | 21 | [![](img/01_create_application.png)](img/01_create_application.png) 22 | 23 | 5. On the new page, copy the `Domain`, `Client ID` and `Client Secret`. You'll 24 | need this in the ShinyProxy configuration. 25 | 6. Scroll down to `Application URIs` 26 | 7. Fill in the location of ShinyProxy (without any sub-path) 27 | in `Application Login URI` 28 | 8. Add the following as `Allowed callback URLs` 29 | (replacing `shinyproxy-demo.local` 30 | with your domain name): 31 | 32 | ``` 33 | https://shinyproxy-demo.local/login/oauth2/code/shinyproxy 34 | ``` 35 | 36 | 9. The filled in form should look like: 37 | 38 | [![](img/02_application_urls.png)](img/02_application_urls.png) 39 | 40 | ## Configuring ShinyProxy 41 | 42 | Now that you configured Auth0 and you retrieved all parameters, you can 43 | configure ShinyProxy. 44 | 45 | 1. Add the following configuration to your ShinyProxy config (replace the 46 | examples with the values you retrieved from Auth0). In this 47 | example `https://dev-abc123xyz.eu.auth0.com/` is used as the Auth0 domain: 48 | 49 | ```yaml 50 | proxy: 51 | authentication: openid 52 | openid: 53 | # see step 5 (of previous section): domain + "/authorize" 54 | auth-url: https://dev-abc123xyz.eu.auth0.com/authorize 55 | # see step 5 (of previous section): domain + "/oauth/token" 56 | token-url: https://dev-abc123xyz.eu.auth0.com/oauth/token 57 | # see step 5 (of previous section) domain + "/.well-known/jwks.json" 58 | jwks-url: https://dev-abc123xyz.eu.auth0.com/ 59 | # see step 5: Client ID 60 | client-id: shinyproxy-config-example 61 | # see step 6: Client Secret 62 | client-secret: 1aB... 63 | ``` 64 | 65 | 2. Restart ShinyProxy 66 | 67 | You should now be able to login into ShinyProxy using an Auth0 user. You can 68 | create additional users by going to the `User Management` -> `Users` page in 69 | Auth0. 70 | 71 | ## Configuring the username 72 | 73 | The current setup will use the `email` of the user to identify it 74 | (this is e.g. shown in the navigation bar of ShinyProxy). This corresponds to 75 | the email field in the Auth0 UI. The following example sends the `nickname` of 76 | the user as an additional claim to ShinyProxy. 77 | 78 | 1. Login into Auth0 79 | 2. Click on `Actions`, click on `Library` 80 | 3. Click on `Create Action`, click on `Build from scratch` 81 | 4. Fill in a name 82 | 5. Use `Login / Post Login` as `Trigger` 83 | 6. Click on `Create` 84 | 85 | [![](img/03_action_create.png)](img/03_action_create.png) 86 | 87 | 7. Modify the code to be (the name of the claim should be a URI-lik namespace): 88 | 89 | ```js 90 | exports.onExecutePostLogin = async (event, api) => { 91 | api.idToken.setCustomClaim("https://shinyproxy.io/nickname", event.user.nickname); 92 | }; 93 | ``` 94 | 95 | [![](img/04_action_code.png)](img/04_action_code.png) 96 | 97 | 8. Click on `Deploy` 98 | 9. Go to `Flows` (in the left sidebar) 99 | 10. Click on `Login` 100 | 11. In the right section, click on `Custom` 101 | 12. Drag and drop your custom action into the flow 102 | 103 | [![](img/05_flow.png)](img/05_flow.png) 104 | 105 | 13. Click on `Apply` 106 | 14. Add the `proxy.openid.username-attribute` property in the ShinyProxy 107 | configuration: 108 | 109 | ```yaml 110 | proxy: 111 | openid: 112 | username-attribute: "https://shinyproxy.io/nickname" 113 | ``` 114 | 15. Restart ShinyProxy 115 | 116 | When a user now logs in on ShinyProxy, the user is identified by their nickname. 117 | 118 | It's possible to see all claims which are being sent to ShinyProxy, 119 | see [the documentation](https://shinyproxy.io/documentation/troubleshooting/#listing-all-claims-sent-by-the-openid-provider). 120 | 121 | ## Configuring roles 122 | 123 | In Auth0, it is possible to assign (multiple) roles to a user. These roles can 124 | be used in ShinyProxy as groups, e.g. for adding authorization to apps. 125 | 126 | > [!NOTE] 127 | > Previous versions of our documentation, suggested to use a custom property in 128 | > the `app_metadata`, instead of using native Auth0 rules. See the next section 129 | > for an example. 130 | 131 | 1. Follow the steps in [Configuring the username](#configuring-the-username) in 132 | order to setup a custom Action and Flow (you can skip the last step). 133 | 2. In Auth0, click on `Actions`, click on `Library`, click on `Custom` and 134 | finally click on your custom Action 135 | 3. Modify the code to be (you can remove the nickname attribute if you don't 136 | need it): 137 | 138 | ```js 139 | exports.onExecutePostLogin = async (event, api) => { 140 | api.idToken.setCustomClaim("https://shinyproxy.io/nickname", event.user.nickname); 141 | api.idToken.setCustomClaim("https://shinyproxy.io/shinyproxy_roles", event.authorization.roles); 142 | }; 143 | ``` 144 | 145 | 4. Click on `Deploy`. 146 | 5. Add the `proxy.openid.roles-claim` property to the ShinyProxy config: 147 | 148 | ```yaml 149 | proxy: 150 | openid: 151 | roles-claim: "https://shinyproxy.io/shinyproxy_roles" 152 | ``` 153 | 154 | 6. Restart ShinyProxy 155 | 156 | When a user now logs in on ShinyProxy, Auth0 sends the roles of that user to 157 | ShinyProxy. You can check whether this works by starting an app and retrieving 158 | the `SHINYPROXY_USERGROUPS` environment variable. 159 | 160 | ## Adding custom attributes to a user 161 | 162 | Besides using the native roles function of Auth0, it is also possible to use 163 | custom metadata to send roles to ShinyProxy (or any other value): 164 | 165 | 1. Follow the steps in [Configuring the username](#configuring-the-username) in 166 | order to setup a custom Action and Flow (you can skip the last step). 167 | 2. Go to `User Management`, `Users`, select a user 168 | 3. Scroll down to `App Metadata` and add the following JSON: 169 | 170 | ```json 171 | { 172 | "shinyproxy_roles": [ "scientists", "mathematicians" ] 173 | } 174 | ``` 175 | 176 | [![](img/06_app_metadata.png)](img/06_app_metadata.png) 177 | 178 | 4. Click on `Save` 179 | 5. In the left sidebar, click on `Actions`, click on `Library`, click 180 | on `Custom` and finally click on your custom Action 181 | 6. Modify the code to be (you can remove the nickname attribute if you don't 182 | need it): 183 | 184 | ```js 185 | exports.onExecutePostLogin = async (event, api) => { 186 | api.idToken.setCustomClaim("https://shinyproxy.io/nickname", event.user.nickname); 187 | api.idToken.setCustomClaim("https://shinyproxy.io/shinyproxy_roles", event.user.app_metadata.shinyproxy_roles); 188 | }; 189 | ``` 190 | 191 | 7. Click on `Deploy`. 192 | 8. Add the `proxy.openid.roles-claim` property to the ShinyProxy config: 193 | 194 | ```yaml 195 | proxy: 196 | openid: 197 | roles-claim: "https://shinyproxy.io/shinyproxy_roles" 198 | ``` 199 | 200 | 9. Restart ShinyProxy 201 | 202 | When a user now logs in on ShinyProxy, Auth0 sends the value of 203 | the `shinyproxy_roles` property in the `app_metadata` of that user to 204 | ShinyProxy. You can check whether this works by starting an app and retrieving 205 | the ` SHINYPROXY_USERGROUPS` environment variable. 206 | 207 | ## Logout 208 | 209 | When clicking the logout button in the current setup, the user will just be 210 | logged out from ShinyProxy. You can configure ShinyProxy to logout the user in 211 | Auth0: 212 | 213 | 1. Get your Auth0 domain (see the [first section](#configuring-auth0)) 214 | 2. Go to Auth0, click on `Settings` and click on `Advances` 215 | 3. Enable `RP-Initiated Logout End Session Endpoint Discovery` 216 | 4. It's recommend to keep `RP-Initiated Logout End-User Confirmation` enabled 217 | 218 | [![](img/07_logout.png)](img/07_logout.png) 219 | 220 | 5. Click save 221 | 6. Add the `proxy.openid.logout-url` property to the ShinyProxy config: 222 | 223 | ```yaml 224 | proxy: 225 | openid: 226 | # see step 5 (of first section): domain + "oidc/logout?id_token_hint=#{oidcUser.idToken.tokenValue}" 227 | auth-url: https://dev-abc123xyz.eu.auth0.com/oidc/logout?id_token_hint=#{oidcUser.idToken.tokenValue} 228 | ``` 229 | 230 | 7. Restart ShinyProxy 231 | 232 | **Note**: 233 | 234 | - When the user is logged out, Auth0 shows a confirmation screen: 235 | 236 | [![](img/08_logout_confirmation.png)](img/08_logout_confirmation.png) 237 | 238 | You can redirect the user to a different URL by adding 239 | the `post_logout_redirect_uri` parameter to the URL: 240 | 241 | ```yaml 242 | proxy: 243 | openid: 244 | logout-url: https://dev-abc123xyz.eu.auth0.com/oidc/logout?id_token_hint=#{oidcUser.idToken.tokenValue}&post_logout_redirect_uri=http%3A%2F%2Fshinyproxy-demo.local/logout-success 245 | ``` 246 | 247 | You'll also have to add the URL to the `Allowed Logout URLs` of the Auth0 248 | application. 249 | 250 | ## References 251 | 252 | - [Auth0: test Actions](https://auth0.com/docs/customize/actions/test-actions) 253 | - [Auth0: add roles to ID token](https://community.auth0.com/t/how-to-add-roles-and-permissions-to-the-id-token-using-actions/84506) 254 | - [Auth0: logout](https://auth0.com/docs/authenticate/login/logout/log-users-out-of-auth0) 255 | - [ShinyProxy OpenID documentation](https://shinyproxy.io/documentation/configuration/#openid-connect-oidc) 256 | - [ShinyProxy SpEL documentation](https://shinyproxy.io/documentation/spel/) 257 | - [ShinyProxy Troubleshooting OpenID documentation](https://shinyproxy.io/documentation/troubleshooting/#openid-connect-oidc) 258 | - [ShinyProxy Docs on using environment variables](https://shinyproxy.io/documentation/configuration/#config-env-var) 259 | -------------------------------------------------------------------------------- /13-openid-auth0/img/01_create_application.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openanalytics/shinyproxy-config-examples/900c1a7681bed9879d328d7de0c8306223b62474/13-openid-auth0/img/01_create_application.png -------------------------------------------------------------------------------- /13-openid-auth0/img/02_application_urls.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openanalytics/shinyproxy-config-examples/900c1a7681bed9879d328d7de0c8306223b62474/13-openid-auth0/img/02_application_urls.png -------------------------------------------------------------------------------- /13-openid-auth0/img/03_action_create.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openanalytics/shinyproxy-config-examples/900c1a7681bed9879d328d7de0c8306223b62474/13-openid-auth0/img/03_action_create.png -------------------------------------------------------------------------------- /13-openid-auth0/img/04_action_code.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openanalytics/shinyproxy-config-examples/900c1a7681bed9879d328d7de0c8306223b62474/13-openid-auth0/img/04_action_code.png -------------------------------------------------------------------------------- /13-openid-auth0/img/05_flow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openanalytics/shinyproxy-config-examples/900c1a7681bed9879d328d7de0c8306223b62474/13-openid-auth0/img/05_flow.png -------------------------------------------------------------------------------- /13-openid-auth0/img/06_app_metadata.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openanalytics/shinyproxy-config-examples/900c1a7681bed9879d328d7de0c8306223b62474/13-openid-auth0/img/06_app_metadata.png -------------------------------------------------------------------------------- /13-openid-auth0/img/07_logout.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openanalytics/shinyproxy-config-examples/900c1a7681bed9879d328d7de0c8306223b62474/13-openid-auth0/img/07_logout.png -------------------------------------------------------------------------------- /13-openid-auth0/img/08_logout_confirmation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openanalytics/shinyproxy-config-examples/900c1a7681bed9879d328d7de0c8306223b62474/13-openid-auth0/img/08_logout_confirmation.png -------------------------------------------------------------------------------- /20-ecs-minimal/.gitignore: -------------------------------------------------------------------------------- 1 | .terraform 2 | .terraform.lock.hcl 3 | terraform.tfstate 4 | terraform.tfstate.backup 5 | -------------------------------------------------------------------------------- /20-ecs-minimal/README.md: -------------------------------------------------------------------------------- 1 | # Example: running ShinyProxy on ECS (using ECS backend) 2 | 3 | This example demonstrates how to run ShinyProxy on ECS, while using the ECS 4 | backend to host applications. This example uses opentofu/terraform to configure 5 | the infrastructure. Even when not using opentofue/terraform, this is useful, 6 | since the code explains all the configuration that is required for this example. 7 | 8 | ## Overview 9 | 10 | This deployment creates an ECS cluster, running the ShinyProxy server. An 11 | Application LoadBalancer is created to route traffic into ShinyProxy. The 12 | configuration of ShinyProxy is stored 13 | in [`docker/application.yml`](docker/application.yml) and is used to build a 14 | custom Docker image which is stored on ECR (this currently the most 15 | straightforward approach to handling config on ECS, although for non-demo 16 | purposes you could use something better). See [Components](#components) for a 17 | detailed list of resources created. 18 | 19 | ## Steps 20 | 21 | In order to deploy this example: 22 | 23 | 1. Install [opentofu](https://github.com/opentofu/opentofu) 24 | or [terraform](https://github.com/hashicorp/terraform) 25 | 2. Go to the `20-ecs-minimal/terraform` directory: 26 | ```bash 27 | cd 20-ecs-minimal/terraform 28 | ``` 29 | 3. Configure [`terraform/main.tfvars`](terraform/main.tfvars) 30 | - fill in your AWS account id, region and zones 31 | - choose a name for the ECS cluster. Note that other resources use the 32 | cluster name as part of their name. 33 | 4. Initialize: 34 | ```bash 35 | tofu init 36 | ``` 37 | 5. Create the infrastructure by running: 38 | ``` 39 | tofu apply -var-file=main.tfvars 40 | ```` 41 | The final lines of the output will contain `container_image_location=...` 42 | and `hostname=...`, remember these values. 43 | 6. Build and push the Docker image (replace `$container_image_location` by the 44 | output of the previous command): 45 | ```bash 46 | cd docker 47 | docker build -t $container_image_location 48 | docker push $container_image_location 49 | ``` 50 | 7. Wait 1-2 minutes for the ECS task to use the new image 51 | 8. Open ShinyProxy using the hostname from step 4 52 | 9. You can now log using the username `jack` and password `password` 53 | 54 | ## Updating ShinyProxy configuration 55 | 56 | In order to update the ShinyProxy Configuration: 57 | 58 | 1. Update the [`docker/application.yml`](docker/application.yml) file 59 | 2. Build and push the docker image: 60 | ```bash 61 | cd docker 62 | docker build -t $container_image_location 63 | docker push $container_image_location 64 | ``` 65 | 3. Go to the AWS ECS console, click on your cluster 66 | 4. Click on the `shinyproxy` service 67 | 5. Click on the `Update service` in the top right corner 68 | 6. Select `Force new deployment` 69 | 7. Click on `Update` 70 | 8. Wait for the new service to be ready 71 | 72 | ## Components 73 | 74 | This section lists all components of the infrastructure. Note that the numbering 75 | of the files is solely to make it easier to understand, the file name has no 76 | meaning to terraform. 77 | 78 | - [`terraform/1_shinyproxy_image.tf`](terraform/1_shinyproxy_image.tf): ECR 79 | repository to store the ShinyProxy image (containing a 80 | custom `application.yml`) 81 | - [`terraform/2_vpc.tf`](terraform/2_vpc.tf): VPC 82 | - [`terraform/3_ecs.tf`](terraform/3_ecs.tf): ECS Fargate cluster 83 | - [`terraform/4_shinyproxy_task_role.tf`](terraform/4_shinyproxy_task_role.tf): 84 | creates an IAM role that acts as the Task Role for the ShinyProxy service: 85 | - **aws_iam_role.shinyproxy-task-role**: the IAM role used as Task Role, 86 | the `assume_role_policy` (i.e. trust policy) gives permission for ECS to use 87 | this role 88 | - **aws_iam_policy.shinyproxy-ecs-policy**: provides the permission to create 89 | ECS tasks tasks 90 | - **aws_iam_role_policy_attachment.shinyproxy-ecs-policy**: attaches the 91 | policy to the role 92 | - [`terraform/5_shinyproxy_execution_role.tf`](terraform/5_shinyproxy_execution_role.tf): 93 | creates an IAM role (**aws_iam_role.shinyproxy-execution-role**) that acts as 94 | the Execution role (sometimes also called Task Execution role) for the 95 | executor of the ShinyProxy service (the permissions in this role are not given 96 | to ShinyProxy). 97 | - the `assume_role_policy` (i.e. trust policy) gives permission for ECS to use 98 | this role 99 | - the `inline_policy` ensures that the executor has permission to create a 100 | CloudWatch log group for ShinyProxy 101 | - the 102 | [`managed_policy_arns`](https://docs.aws.amazon.com/aws-managed-policy/latest/reference/AmazonECSTaskExecutionRolePolicy.html) 103 | ensures that the executor has permission to use AWS ECR for retrieving the 104 | ShinyProxy image and for pushing the logs into CloudWatch 105 | - [`terraform/6_shinyproxy_lb.tf`](terraform/6_shinyproxy_lb.tf): configures a 106 | LoadBalancer: 107 | - **aws_security_group.lb**: creates a security group for the LoadBalancer, 108 | such that it is accessible from the internet 109 | - **aws_lb.lb**: creates 110 | an [Application Load Balancer (ALB)](https://docs.aws.amazon.com/elasticloadbalancing/latest/application/introduction.html) 111 | - **aws_lb_target_group.lb** creates a target group for the LoadBalancer (ECS 112 | automatically puts the ShinyProxy container as a target in it) 113 | - **aws_lb_listener.lb** creates a listener group for the LoadBalancer (the 114 | port on which it is accessible) 115 | - [`terraform/7_shinyproxy_sg.tf`](terraform/7_shinyproxy_sg.tf): creates a 116 | security group (**aws_security_group.shinyproxy-sg**) for ShinyProxy: 117 | - ensures ShinyProxy can be accessed by the LoadBalancer 118 | - since the ShinyProxy Task is using this security group, it can be used 119 | as `source` in other security group rules 120 | - [`terraform/8_app_sg.tf`](terraform/8_app_sg.tf): creates a 121 | security group (**aws_security_group.app-sg**) for the Shiny apps hosted on 122 | ShinyProxy, it ensures ShinyProxy can access the apps on port 3838 123 | - [`terraform/9_shinyproxy_task_defintion.tf`](terraform/9_shinyproxy_task_defintion.tf): 124 | creates an ECS Task Definition (**aws_ecs_task_definition.shinyproxy**) for 125 | running ShinyProxy on ECS: 126 | - uses the ECR image 127 | - passes the ids of the resources created in terraform as environment 128 | variables to ShinyProxy 129 | - uses CloudWatch for storing the logs of ShinyProxy (not of the apps) 130 | - uses the task and execution role ARNs 131 | - [`terraform/10_shinyproxy_service.tf`](terraform/10_shinyproxy_service.tf): 132 | creates the ECS service that runs ShinyProxy: 133 | - uses the cluster created in step 3 134 | - uses the task definition created in step 8 135 | - uses the subnets created in the VPC in step 2 136 | - uses the security group created in step 7 137 | - connects to the loadbalancer created step 6 138 | 139 | ## Limitations 140 | 141 | The setup in this directory can only use public Docker images for the apps. In 142 | order to use an image stored on ECR, 143 | see [21-ecs-execution-role](../21-ecs-execution-role). 144 | 145 | ## References 146 | 147 | - [ECS Backend configuration](https://shinyproxy.io/documentation/configuration/#ecs) 148 | - [ECS App configuration](https://shinyproxy.io/documentation/configuration/#ecs-1) 149 | -------------------------------------------------------------------------------- /20-ecs-minimal/docker/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM openanalytics/shinyproxy:3.1.1 2 | 3 | COPY application.yml /opt/shinyproxy/ 4 | -------------------------------------------------------------------------------- /20-ecs-minimal/docker/application.yml: -------------------------------------------------------------------------------- 1 | proxy: 2 | title: Open Analytics Shiny Proxy 3 | logo-url: https://www.openanalytics.eu/shinyproxy/logo.png 4 | authentication: simple 5 | containerBackend: ecs 6 | 7 | # The following settings are passed as environment variables 8 | # See 9_shinyproxy-task-definition.tf 9 | ecs: 10 | name: ${CLUSTER_NAME} 11 | region: ${AWS_REGION} 12 | subnets: 13 | - ${SUBNET_0} 14 | - ${SUBNET_1} 15 | security-groups: ${SECURITY_GROUP} 16 | 17 | users: 18 | - name: jack 19 | password: password 20 | groups: mathematicians 21 | specs: 22 | - id: 01_hello 23 | display-name: Hello Application 24 | description: Application which demonstrates the basics of a Shiny app 25 | container-cmd: [ "R", "-e", "shinyproxy::run_01_hello()" ] 26 | container-image: openanalytics/shinyproxy-demo 27 | access-groups: [ scientists, mathematicians ] 28 | # cpu and memory request are required, see: https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task-cpu-memory-error.html 29 | container-cpu-request: 1024 30 | container-memory-request: 2048 31 | -------------------------------------------------------------------------------- /20-ecs-minimal/docker/application.yml.bak: -------------------------------------------------------------------------------- 1 | proxy: 2 | title: Open Analytics Shiny Proxy 3 | logo-url: https://www.openanalytics.eu/shinyproxy/logo.png 4 | authentication: simple 5 | containerBackend: ecs 6 | 7 | # The following settings are passed as environment variables 8 | # See shinyproxy-task-definition.tf 9 | ecs: 10 | name: ${CLUSTER_NAME} 11 | region: ${AWS_REGION} 12 | subnets: 13 | - ${SUBNET_0} 14 | - ${SUBNET_1} 15 | security-groups: ${SECURITY_GROUP} 16 | 17 | users: 18 | - name: jack 19 | password: password 20 | groups: mathematicians 21 | specs: 22 | - id: 01_hello 23 | display-name: Hello Application 24 | description: Application which demonstrates the basics of a Shiny app 25 | container-cmd: [ "R", "-e", "shinyproxy::run_01_hello()" ] 26 | container-image: docker.io/openanalytics/shinyproxy-demo 27 | # container-image: 772435625456.dkr.ecr.eu-central-1.amazonaws.com/shinyproxy-demo:latest 28 | access-groups: [ scientists, mathematicians ] 29 | # cpu and memory request are required, see: https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task-cpu-memory-error.html 30 | container-cpu-request: 1024 31 | container-memory-request: 2048 32 | max-instances: -1 33 | ecs-execution-role: arn:aws:iam::772435625456:role/shinyproxy-ecs-demo-execution-role 34 | ecs-task-role: arn:aws:iam::772435625456:role/shinyproxy-ecs-demo-task-role 35 | # Additional example config (not required) 36 | # 37 | # ecs-execution-role: arn:aws:iam::772435625456:role/shinyproxy-ecs-demo-execution-role 38 | # See: https://docs.aws.amazon.com/AmazonECS/latest/developerguide/ecs-exec.html 39 | # ecs-enable-execute-command: true 40 | # ecs-ephemeral-storage-size: 100 # in gigabytes 41 | # ecs-efs-volumes: 42 | # - name: myvolume 43 | # file-system-id: fs-010750647c19e71e0 # optional 44 | # access-point-id: fsap-0ca1c89c9fd924f51 # optional 45 | # transit-encryption: true # optional 46 | # use-iam: true # optional 47 | # container-volumes: 48 | # - "myvolume:/my/mnt/point" 49 | - id: 06_tabsets 50 | container-cmd: [ "R", "-e", "shinyproxy::run_06_tabsets()" ] 51 | container-image: 772435625456.dkr.ecr.eu-central-1.amazonaws.com/shinyproxy-demo:latest 52 | access-groups: scientists 53 | container-cpu-request: 1024 54 | container-memory-request: 4096 55 | ecs-execution-role: arn:aws:iam::772435625456:role/shinyproxy-ecs-demo-execution-role 56 | -------------------------------------------------------------------------------- /20-ecs-minimal/terraform/.gitignore: -------------------------------------------------------------------------------- 1 | 11_app_execution_role.tf 2 | 12_shinyproxy_task_role_policy.tf 3 | -------------------------------------------------------------------------------- /20-ecs-minimal/terraform/10_shinyproxy_service.tf: -------------------------------------------------------------------------------- 1 | resource "aws_ecs_service" "shinyproxy" { 2 | name = "shinyproxy" 3 | cluster = aws_ecs_cluster.ecs.id 4 | task_definition = aws_ecs_task_definition.shinyproxy.arn 5 | desired_count = 1 6 | 7 | network_configuration { 8 | subnets = module.vpc.private_subnets 9 | security_groups = [aws_security_group.shinyproxy-sg.id] 10 | } 11 | 12 | load_balancer { 13 | target_group_arn = aws_lb_target_group.lb.arn 14 | container_name = "shinyproxy" 15 | container_port = 8080 16 | } 17 | 18 | capacity_provider_strategy { 19 | base = 1 20 | capacity_provider = "FARGATE" 21 | weight = 100 22 | } 23 | 24 | tags = local.common_tags 25 | } 26 | -------------------------------------------------------------------------------- /20-ecs-minimal/terraform/1_shinyproxy_image.tf: -------------------------------------------------------------------------------- 1 | resource "aws_ecr_repository" "shinyproxy-config-examples" { 2 | 3 | name = "shinyproxy-config-examples" 4 | 5 | tags = local.common_tags 6 | } 7 | -------------------------------------------------------------------------------- /20-ecs-minimal/terraform/2_vpc.tf: -------------------------------------------------------------------------------- 1 | module "vpc" { 2 | source = "terraform-aws-modules/vpc/aws" 3 | version = "5.1.2" 4 | 5 | name = "${var.cluster_name}-vpc" 6 | cidr = "10.150.0.0/16" 7 | 8 | azs = var.aws_zones 9 | private_subnets = ["10.150.1.0/24", "10.150.2.0/24"] 10 | public_subnets = ["10.150.10.0/24", "10.150.11.0/24"] 11 | 12 | enable_nat_gateway = true 13 | single_nat_gateway = true 14 | 15 | tags = local.common_tags 16 | } 17 | -------------------------------------------------------------------------------- /20-ecs-minimal/terraform/3_ecs.tf: -------------------------------------------------------------------------------- 1 | resource "aws_ecs_cluster" "ecs" { 2 | name = var.cluster_name 3 | tags = local.common_tags 4 | } 5 | 6 | resource "aws_ecs_cluster_capacity_providers" "ecs" { 7 | cluster_name = aws_ecs_cluster.ecs.name 8 | 9 | capacity_providers = ["FARGATE"] 10 | 11 | default_capacity_provider_strategy { 12 | base = 1 13 | weight = 100 14 | capacity_provider = "FARGATE" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /20-ecs-minimal/terraform/4_shinyproxy_task_role.tf: -------------------------------------------------------------------------------- 1 | resource "aws_iam_role" "shinyproxy-task-role" { 2 | 3 | name = "${var.cluster_name}-task-role" 4 | 5 | assume_role_policy = jsonencode( 6 | { 7 | Version = "2012-10-17" 8 | Statement = [ 9 | { 10 | Effect = "Allow", 11 | Principal = { 12 | Service = [ 13 | "ecs-tasks.amazonaws.com" 14 | ] 15 | }, 16 | Action = "sts:AssumeRole", 17 | Condition = { 18 | ArnLike = { 19 | "aws:SourceArn" = "arn:aws:ecs:${var.aws_region}:${var.aws_account_id}:*" 20 | }, 21 | StringEquals = { 22 | "aws:SourceAccount" = var.aws_account_id 23 | } 24 | } 25 | } 26 | ] 27 | }) 28 | 29 | tags = local.common_tags 30 | } 31 | 32 | resource "aws_iam_policy" "shinyproxy-ecs-policy" { 33 | name = "shinyproxy-ecs-policy" 34 | policy = jsonencode({ 35 | Version = "2012-10-17" 36 | Statement = [ 37 | { 38 | Action = [ 39 | "ecs:RunTask", 40 | "ecs:ListTasks", 41 | "ecs:StopTask", 42 | "ecs:DescribeTasks", 43 | "ecs:ListTagsForResource", 44 | "ecs:RegisterTaskDefinition", 45 | "ecs:DeregisterTaskDefinition", 46 | "ecs:DeleteTaskDefinitions" 47 | ] 48 | Effect = "Allow" 49 | Resource = "*" 50 | } 51 | ] 52 | }) 53 | 54 | tags = local.common_tags 55 | } 56 | 57 | resource "aws_iam_role_policy_attachment" "shinyproxy-ecs-policy" { 58 | role = aws_iam_role.shinyproxy-task-role.name 59 | policy_arn = aws_iam_policy.shinyproxy-ecs-policy.arn 60 | } 61 | -------------------------------------------------------------------------------- /20-ecs-minimal/terraform/5_shinyproxy_execution_role.tf: -------------------------------------------------------------------------------- 1 | resource "aws_iam_role" "shinyproxy-execution-role" { 2 | 3 | name = "${var.cluster_name}-execution-role" 4 | 5 | assume_role_policy = jsonencode( 6 | { 7 | Version = "2012-10-17" 8 | Statement = [ 9 | { 10 | Effect = "Allow", 11 | Principal = { 12 | Service = [ 13 | "ecs-tasks.amazonaws.com" 14 | ] 15 | }, 16 | Action = "sts:AssumeRole", 17 | Condition = { 18 | ArnLike = { 19 | "aws:SourceArn" = "arn:aws:ecs:${var.aws_region}:${var.aws_account_id}:*" 20 | }, 21 | StringEquals = { 22 | "aws:SourceAccount" = var.aws_account_id 23 | } 24 | } 25 | } 26 | ] 27 | }) 28 | 29 | inline_policy { 30 | name = "ecs-logs" 31 | 32 | policy = jsonencode({ 33 | Version = "2012-10-17" 34 | Statement = [ 35 | { 36 | Action = ["logs:CreateLogGroup"] 37 | Effect = "Allow" 38 | Resource = "*" 39 | } 40 | ] 41 | }) 42 | } 43 | 44 | managed_policy_arns = ["arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy"] 45 | 46 | tags = local.common_tags 47 | } 48 | -------------------------------------------------------------------------------- /20-ecs-minimal/terraform/6_shinyproxy_lb.tf: -------------------------------------------------------------------------------- 1 | resource "aws_security_group" "lb" { 2 | name = "${var.cluster_name}-lb-sg" 3 | vpc_id = module.vpc.vpc_id 4 | 5 | ingress { 6 | protocol = "tcp" 7 | from_port = 80 8 | to_port = 80 9 | cidr_blocks = ["0.0.0.0/0"] 10 | } 11 | 12 | egress { 13 | from_port = 0 14 | to_port = 0 15 | protocol = "-1" 16 | cidr_blocks = ["0.0.0.0/0"] 17 | } 18 | 19 | tags = local.common_tags 20 | } 21 | 22 | resource "aws_lb" "lb" { 23 | name = "${var.cluster_name}-lb" 24 | subnets = module.vpc.public_subnets 25 | security_groups = [aws_security_group.lb.id] 26 | 27 | tags = local.common_tags 28 | } 29 | 30 | resource "aws_lb_target_group" "lb" { 31 | name = "${var.cluster_name}-lb-tg" 32 | port = 8080 33 | protocol = "HTTP" 34 | vpc_id = module.vpc.vpc_id 35 | target_type = "ip" 36 | health_check { 37 | path = "/actuator/health/readiness" 38 | port = 9090 39 | } 40 | # Ensures the ShinyProxy container keeps running for max ~1 hour if it' still being used by a websocket connection 41 | # after 1 hour the connections will be dropped 42 | deregistration_delay = "3600" 43 | 44 | tags = local.common_tags 45 | } 46 | 47 | resource "aws_lb_listener" "lb" { 48 | load_balancer_arn = aws_lb.lb.id 49 | port = "80" 50 | protocol = "HTTP" 51 | 52 | default_action { 53 | target_group_arn = aws_lb_target_group.lb.arn 54 | type = "forward" 55 | } 56 | 57 | tags = local.common_tags 58 | } 59 | -------------------------------------------------------------------------------- /20-ecs-minimal/terraform/7_shinyproxy_sg.tf: -------------------------------------------------------------------------------- 1 | resource "aws_security_group" "shinyproxy-sg" { 2 | name = "${var.cluster_name}-shinyproxy-sg" 3 | vpc_id = module.vpc.vpc_id 4 | 5 | ingress { 6 | protocol = "tcp" 7 | from_port = 8080 8 | to_port = 8080 9 | security_groups = [aws_security_group.lb.id] 10 | } 11 | 12 | ingress { 13 | protocol = "tcp" 14 | from_port = 9090 15 | to_port = 9090 16 | security_groups = [aws_security_group.lb.id] 17 | } 18 | 19 | egress { 20 | protocol = "-1" 21 | from_port = 0 22 | to_port = 0 23 | cidr_blocks = ["0.0.0.0/0"] 24 | } 25 | 26 | tags = local.common_tags 27 | } 28 | -------------------------------------------------------------------------------- /20-ecs-minimal/terraform/8_app_sg.tf: -------------------------------------------------------------------------------- 1 | resource "aws_security_group" "app-sg" { 2 | name = "${var.cluster_name}-app-sg" 3 | vpc_id = module.vpc.vpc_id 4 | 5 | ingress { 6 | protocol = "tcp" 7 | from_port = 3838 8 | to_port = 3838 9 | security_groups = [aws_security_group.shinyproxy-sg.id] 10 | } 11 | 12 | egress { 13 | protocol = "-1" 14 | from_port = 0 15 | to_port = 0 16 | cidr_blocks = ["0.0.0.0/0"] 17 | } 18 | 19 | tags = local.common_tags 20 | } 21 | -------------------------------------------------------------------------------- /20-ecs-minimal/terraform/9_shinyproxy_task_defintion.tf: -------------------------------------------------------------------------------- 1 | resource "aws_ecs_task_definition" "shinyproxy" { 2 | family = "shinyproxy" 3 | requires_compatibilities = ["FARGATE"] 4 | network_mode = "awsvpc" 5 | cpu = 1024 6 | memory = 2048 7 | container_definitions = jsonencode([ 8 | { 9 | name = "shinyproxy" 10 | image = aws_ecr_repository.shinyproxy-config-examples.repository_url 11 | portMappings = [ 12 | { 13 | containerPort = 8080 14 | hostPort = 8080 15 | } 16 | ] 17 | environment = [ 18 | { 19 | name = "CLUSTER_NAME" 20 | value = aws_ecs_cluster.ecs.name 21 | }, 22 | { 23 | name = "AWS_REGION" 24 | value = var.aws_region 25 | }, 26 | { 27 | name = "SUBNET_0" 28 | value = module.vpc.private_subnets[0] 29 | }, 30 | { 31 | name = "SUBNET_1" 32 | value = module.vpc.private_subnets[1] 33 | }, 34 | { 35 | name = "SECURITY_GROUP" 36 | value = aws_security_group.app-sg.id 37 | } 38 | ], 39 | logConfiguration = { 40 | logDriver = "awslogs" 41 | options = { 42 | "awslogs-region" = var.aws_region 43 | "awslogs-group" = var.cluster_name 44 | "awslogs-create-group" = "true" 45 | "awslogs-stream-prefix" = "shinyproxy" 46 | } 47 | } 48 | } 49 | ]) 50 | 51 | task_role_arn = aws_iam_role.shinyproxy-task-role.arn 52 | execution_role_arn = aws_iam_role.shinyproxy-execution-role.arn 53 | 54 | tags = local.common_tags 55 | } 56 | -------------------------------------------------------------------------------- /20-ecs-minimal/terraform/aws.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_providers { 3 | aws = { 4 | source = "hashicorp/aws" 5 | version = "~> 5.0" 6 | } 7 | } 8 | } 9 | 10 | provider "aws" { 11 | region = var.aws_region 12 | } 13 | -------------------------------------------------------------------------------- /20-ecs-minimal/terraform/locals.tf: -------------------------------------------------------------------------------- 1 | locals { 2 | common_tags = { 3 | "ecs-cluster-name" = var.cluster_name 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /20-ecs-minimal/terraform/main.tfvars: -------------------------------------------------------------------------------- 1 | aws_account_id = "" 2 | aws_region = "eu-central-1" 3 | aws_zones = ["eu-central-1a", "eu-central-1b"] 4 | cluster_name = "shinyproxy-config-examples" 5 | -------------------------------------------------------------------------------- /20-ecs-minimal/terraform/outputs.tf: -------------------------------------------------------------------------------- 1 | output "container_image_location" { 2 | value = aws_ecr_repository.shinyproxy-config-examples.repository_url 3 | } 4 | 5 | output "hostname" { 6 | value = aws_lb.lb.dns_name 7 | } 8 | -------------------------------------------------------------------------------- /20-ecs-minimal/terraform/variables.tf: -------------------------------------------------------------------------------- 1 | variable "aws_account_id" { 2 | type = string 3 | description = "The AWS account id" 4 | } 5 | 6 | variable "aws_region" { 7 | type = string 8 | description = "The AWS region" 9 | } 10 | 11 | variable "aws_zones" { 12 | type = list(string) 13 | description = "The AWS zones to use, at least two required" 14 | } 15 | 16 | variable "cluster_name" { 17 | type = string 18 | description = "Name of the ECS cluster to create (used as base name for other resources)" 19 | } 20 | -------------------------------------------------------------------------------- /21-ecs-execution-role/README.md: -------------------------------------------------------------------------------- 1 | # Example: using a (Task) Execution role for an app 2 | 3 | This example extends the [minimal ECS example](../20-ecs-minimal) by enabling 4 | CloudWatch logs for apps and allowing to use images stored on ECR. 5 | 6 | ## Steps 7 | 8 | 1. Copy the files in 9 | the [`21-ecs-execution-role/terraform`](terraform) 10 | directory to the [`20-ecs-minimal/terraform`](../20-ecs-minimal/terraform) 11 | directory: 12 | ```bash 13 | cp 21-ecs-execution-role/terraform/* 20-ecs-minimal/terraform 14 | ``` 15 | 2. Go to the `20-ecs-minimal/terraform` directory: 16 | ```bash 17 | cd 20-ecs-minimal/terraform 18 | ``` 19 | 3. Update the infrastructure: 20 | ```bash 21 | tofu apply -var-file=main.tfvars 22 | ``` 23 | The final lines of the output will contain `app_execution_role=...` remember 24 | this value. 25 | 4. Modify the `20-ecs-minimal/docker/application.yml` file: 26 | - add the property [`proxy.ecs.enable-cloudwatch: true`](https://shinyproxy.io/documentation/configuration/#ecs-enable-cloudwatch) 27 | - add the property [`ecs-execution-role`](https://shinyproxy.io/documentation/configuration/#ecs-execution-role) to app `01_hello`, using the ARN shown 28 | in the output of step 3 29 | (e.g. `ecs-execution-role: arn:aws:iam::012345678912:role/shinyproxy-config-examples-app-execution-role`) 30 | 5. [Update the ShinyProxy config](../20-ecs-minimal#updating-shinyproxy-configuration) 31 | 6. After you launch an app, you can view the logs in the AWS console 32 | 33 | ## Components 34 | 35 | This section lists all of the additional components. Note that the numbering of 36 | the files is solely to make it easier to understand, the file name has no 37 | meaning to terraform. 38 | 39 | - [`terraform/11_app_execution_role.tf`](terraform/11_app_execution_role.tf): 40 | creates an IAM role (**aws_iam_role.shinyproxy-app-execution-role**) that can 41 | be used as the execution role for apps. 42 | - the `assume_role_policy` (i.e. trust policy) gives permission for ECS to use 43 | this role 44 | - the `inline_policy` ensures that the executor has permission to create a 45 | CloudWatch log group for the app 46 | - the 47 | [`managed_policy_arns`](https://docs.aws.amazon.com/aws-managed-policy/latest/reference/AmazonECSTaskExecutionRolePolicy.html) 48 | ensures that the executor has permission to use AWS ECR for retrieving the 49 | ShinyProxy image and for pushing the logs into CloudWatch 50 | - this IAM role is very similar to the role created for ShinyProxy itself. 51 | However, for the sake of clarity it is better to use a separate role for 52 | ShinyProxy and for the apps. In addition, this makes it easier to extend 53 | either role. 54 | - [`terraform/12_shinyproxy_task_role_policy.tf`](terraform/12_shinyproxy_task_role_policy.tf): 55 | provides an additional permission to the ShinyProxy Task role, such that 56 | ShinyProxy is allowed to create ECS tasks using the Execution role. 57 | - **aws_iam_policy.shinyproxy-pass-app-execution-role-policy**: an IAM policy 58 | that allows to use the app execution role. 59 | - **aws_iam_role_policy_attachment.shinyproxy-pass-app-execution-role-policy**: 60 | attaches the IAM policy to the ShinyProxy task role 61 | 62 | ## References 63 | 64 | - [ECS Backend configuration](https://shinyproxy.io/documentation/configuration/#ecs) 65 | - [ECS App configuration](https://shinyproxy.io/documentation/configuration/#ecs-1) 66 | -------------------------------------------------------------------------------- /21-ecs-execution-role/terraform/11_app_execution_role.tf: -------------------------------------------------------------------------------- 1 | resource "aws_iam_role" "shinyproxy-app-execution-role" { 2 | 3 | name = "${var.cluster_name}-app-execution-role" 4 | 5 | assume_role_policy = jsonencode( 6 | { 7 | Version = "2012-10-17" 8 | Statement = [ 9 | { 10 | Effect = "Allow", 11 | Principal = { 12 | Service = [ 13 | "ecs-tasks.amazonaws.com" 14 | ] 15 | }, 16 | Action = "sts:AssumeRole", 17 | Condition = { 18 | ArnLike = { 19 | "aws:SourceArn" = "arn:aws:ecs:${var.aws_region}:${var.aws_account_id}:*" 20 | }, 21 | StringEquals = { 22 | "aws:SourceAccount" = var.aws_account_id 23 | } 24 | } 25 | } 26 | ] 27 | }) 28 | 29 | inline_policy { 30 | name = "ecs-logs" 31 | 32 | policy = jsonencode({ 33 | Version = "2012-10-17" 34 | Statement = [ 35 | { 36 | Action = ["logs:CreateLogGroup"] 37 | Effect = "Allow" 38 | Resource = "*" 39 | } 40 | ] 41 | }) 42 | } 43 | 44 | managed_policy_arns = ["arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy"] 45 | 46 | tags = local.common_tags 47 | } 48 | 49 | output "app_execution_role" { 50 | value = aws_iam_role.shinyproxy-app-execution-role.arn 51 | } 52 | -------------------------------------------------------------------------------- /21-ecs-execution-role/terraform/12_shinyproxy_task_role_policy.tf: -------------------------------------------------------------------------------- 1 | resource "aws_iam_policy" "shinyproxy-pass-app-execution-role-policy" { 2 | name = "shinyproxy-pass-app-execution-role-policy" 3 | policy = jsonencode({ 4 | Version = "2012-10-17" 5 | Statement = [ 6 | # allow ShinyProxy to create tasks with the execution role 7 | { 8 | Effect = "Allow", 9 | Action = [ 10 | "iam:GetRole", 11 | "iam:PassRole" 12 | ], 13 | Resource = aws_iam_role.shinyproxy-app-execution-role.arn 14 | } 15 | ] 16 | }) 17 | 18 | tags = local.common_tags 19 | } 20 | 21 | resource "aws_iam_role_policy_attachment" "shinyproxy-pass-app-execution-role-policy" { 22 | role = aws_iam_role.shinyproxy-task-role.name 23 | policy_arn = aws_iam_policy.shinyproxy-pass-app-execution-role-policy.arn 24 | } 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ShinyProxy Configuration Examples 2 | 3 | ShinyProxy can be configured to run in very different scenarios. Consider the following: 4 | 5 | * For your first tryout, maybe you just want to use a single machine with a Java runtime and a docker daemon, and run `java -jar shinyproxy.jar`. And that's perfectly fine! 6 | 7 | * But then you'd like to place ShinyProxy *inside* a container itself, because then you don't need to install a Java runtime on the host... 8 | 9 | * And next up is a bigger deployment, where you cannot rely on a single docker host but instead need a load-balanced cluster to guarantee enough containers are available for all your users. 10 | 11 | * Not wanting to create a _single point of failure_, you also deploy multiple instances of ShinyProxy, load-balanced by a nginx front server. 12 | 13 | As you can see, the configuration of ShinyProxy and its surrounding environment can quickly grow from trivial to not-so-trivial! 14 | This repository offers some ready-to-use examples for various setups. Each example folder contains several configuration files, and an instructional README that explains how to go from a download to a running setup. 15 | 16 | ## Available Examples 17 | 18 | This repository contains examples that are divided by several categories, explained below. 19 | 20 | ### Standalone vs containerized 21 | 22 | In a *standalone* setup, ShinyProxy runs as a Java process on the host. In a *containerized* setup, ShinyProxy runs inside a container. 23 | 24 | ### Docker engine vs docker swarm vs kubernetes 25 | 26 | The term *docker engine* refers to a single, non-clustered docker installation. The engine is managed by a 'docker daemon', a process that can be accessed by the `docker` commandline executable, by a HTTP URL or by a Unix socket. 27 | 28 | *Docker swarm* is a layer that groups multiple docker installations in a 'swarm' that can offer clustering capabilities, including failover, load balancing, etc. 29 | 30 | *Kubernetes* is a container orchestration service that can be used as an alternative to docker swarm. Several major cloud vendors such as Amazon and Google offer ready-to-use kubernetes environments. 31 | 32 | **Note: Always use the [ShinyProxy Operator](https://github.com/openanalytics/shinyproxy-operator) for deploying ShinyProxy on Kubernetes.** 33 | 34 | ## Notes on the Examples Configuration 35 | 36 | All examples use the `simple` authentication method, which defines two users: 37 | 38 | * jack, an administrator, with the password 'password' 39 | * jeff, a regular user, with the password 'password' 40 | 41 | The demo applications all use the `openanalytics/shinyproxy-demo` image which 42 | you can pull using 43 | 44 | ``` 45 | sudo docker pull openanalytics/shinyproxy-demo 46 | ``` 47 | --------------------------------------------------------------------------------