├── .gitignore ├── README.md ├── gke_k8s ├── Dockerfile ├── README.md ├── k8s │ ├── deployment_1.yml │ ├── deployment_2.yml │ └── whitelist.yml └── setup_k8s.sh ├── local ├── .gitignore ├── README.md ├── _quarto.yml ├── about.qmd ├── emails │ └── email_list.txt ├── index.qmd ├── local_app.png └── styles.css └── simple ├── README.md ├── app_setup.png └── render_blueprint.png /.gitignore: -------------------------------------------------------------------------------- 1 | oauth.env 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Make Static Sites Private With OAuth For Free 2 | 3 | Like GitHub Pages, but you choose who can see it, without usernames & passwords :magic_wand: :tophat: :rabbit: 4 | 5 | ## Background 6 | 7 | Do you want to serve a static site semi-privately so only specific users can see it? For example, you may want to host private docs or offer a paid course. There are many complicated solutions that involve building a login flow and maintaining a database of usernames/passwords. Thankfully, there is a much easier way with [Oauth2 Proxy](https://oauth2-proxy.github.io/oauth2-proxy/docs/). 8 | 9 | Concretely, this tutorial shows how to use the Oauth2 Proxy to make a static site private with minimal dependencies and secure it with an email whitelist (a [text file with emails](./local/emails/email_list.txt)). There are many other authorization schemes in addition to an email whitelist, [which you can read about here](https://oauth2-proxy.github.io/oauth2-proxy/docs/configuration/overview). 10 | 11 | [This section](./local/README.md#how-does-this-work) describes how OAuth works in the context of this tutorial. 12 | 13 | # Tutorial 14 | 15 | This tutorial has three parts that become progressively complex depending on your goals. However, you can stop at any lesson once you are satisfied. 16 | 17 | Prerequisites: knowledge of Docker and familiarity with hosting static sites (like on GitHub Pages). 18 | 19 | 1. **[Running OAuth Locally](./local/README.md):** this runs a minimal static site locally, secured with the OAuth2 Proxy. This allows you to gain an intuition of how things work before proceeding to the next step. 20 | 21 | 2. **[Serve The Private Site (For Free!)](./simple/README.md):** You will host the same site you created locally **for free!** You will also learn how to set up SSL for `https` with a custom domain. 22 | 23 | 3. **(Optional) [Hosting on Kubernetes](./gke_k8s/README.md)**: Finally, you will deploy a website secured by OAuth on Kubernetes. This assumes some experience with Kubernetes. 24 | 25 | # FAQ 26 | 27 | 1. **Does the proxy only work for static sites?** No! You can put applications behind the proxy too. See [this explanation](./local/README.md#is-this-only-for-static-sites). 28 | 29 | 2. **Does GitHub Pages have something like this?**: Only if you [purchase GitHub Enterprise Cloud](https://docs.github.com/en/enterprise-cloud@latest/pages/getting-started-with-github-pages/changing-the-visibility-of-your-github-pages-site) which is absurdly expensive if you want it solely for the purposes of securing a static site (> $100/month for just 5 users!). 30 | 31 | 3. **Can't you do this with [Netlify](https://www.netlify.com/)?**: To do something similar on Netlify, you have to use [invite-only private sites](https://docs.netlify.com/visitor-access/identity/registration-login/#set-registration-preferences), which triggers [identity pricing](https://www.netlify.com/pricing/#add-ons-identity), which means that **you need to pay over $99 per month if you have over 5 Active users!** That is ridiculous. 32 | 33 | 34 | -------------------------------------------------------------------------------- /gke_k8s/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python 2 | RUN echo "

Hello Folks!

" > index.html 3 | CMD ["python", "-m", "http.server", "8083"] 4 | -------------------------------------------------------------------------------- /gke_k8s/README.md: -------------------------------------------------------------------------------- 1 | # OAuth In Kubernetes 2 | 3 | This section shows how to set up the [OAuth2 Proxy](https://oauth2-proxy.github.io/oauth2-proxy/docs/configuration/overview) on [GKE](https://cloud.google.com/kubernetes-engine) to secure a website. The motivation for this is that in many companies and enterprises, serving a site on Kubernetes may be the only option. 4 | 5 | Unlike previous examples, the static site is served via its own web server rather than directly through the OAuth2 Proxy. This web server's networking restricts access to be visible only internally within the Kubernetes cluster. The proxy forwards traffic to this web server. This is a more general pattern in case you want to host other web applications that are not static sites (like dashboards). 6 | 7 | :point_right: If you are unfamiliar with Kubernetes or OAuth, start with [Minimal Oauth](../local/README.md). :point_left: 8 | 9 | ## Pre-requisites 10 | 11 | - A domain name. If you don't have one, you [can buy one here](https://domains.google.com/) 12 | - A google cloud account 13 | - Deploy a hello-world Kubernetes cluster with [these instructions](https://cloud.google.com/kubernetes-engine/docs/deploy-app-cluster) 14 | 15 | ## Steps 16 | 17 | You can see the commands I used in [setup_k8s.sh](./setup_k8s.sh) 18 | 19 | ### 1. Build Docker Container 20 | 21 | ```bash 22 | DOCKER_NM= # ex: hamelsmu/hello-web 23 | docker build -t $DOCKER_NM --platform=linux/amd64 . && docker push $DOCKER_NM 24 | ``` 25 | 26 | ### 2. Create Google Kubernetes Cluster & Static IP 27 | 28 | ```bash 29 | gcloud container clusters create-auto oauth-demo \ 30 | --region us-west1 \ 31 | --project= 32 | 33 | gcloud compute addresses create oauth-ip-address --global 34 | ``` 35 | 36 | ### 3. Setup GitHub OAuth and configuration variables 37 | 38 | Create an [OAuth App](https://github.com/settings/applications/new), and fill out the following fields: 39 | 40 | - **Application Name:** Any name you want, I called it `k8s-oauth` 41 | - **Homepage URL:** this is the url of the website `https://hamel.page` 42 | - **Application Description:** you can skip this 43 | - **Authorization callback URL:** your url with the path `/oauth2/callback` for example, I put `https://hamel.page/oauth2/callback`. 44 | 45 | Take note of the `ClientID` and `Client secret`, which you will use below. 46 | 47 | Create a file named `oauth.env` with the following contents: 48 | 49 | ```text 50 | OAUTH_CLIENT_ID=your-client-id # from GitHub 51 | OAUTH_CLIENT_SECRET=your-client-secret # from GitHub 52 | OAUTH2_PROXY_COOKIE_SECRET=your-cookie-secret # you generate this locally, see below. 53 | OAUTH2_PROXY_SKIP_PROVIDER_BUTTON=true 54 | ``` 55 | 56 | You can generate the `OAUTH2_PROXY_COOKIE_SECRET` by running this: 57 | 58 | ```bash 59 | python -c 'import os,base64; print(base64.urlsafe_b64encode(os.urandom(32)).decode())' 60 | ``` 61 | 62 | Finally, you can store these in your Kubernetes cluster: 63 | 64 | ```bash 65 | kl create configmap oauth-env-file --from-env-file oauth.env 66 | ``` 67 | 68 | Note: you should use [secrets](https://kubernetes.io/docs/concepts/configuration/secret/) instead, but the goal is to keep things as minimal as possible. 69 | 70 | ### 4. Deploy your application 71 | 72 | We can first deploy our web app with [k8s/deployment_1.yml](./k8s/deployment_1.yml) without any OAuth security. You will need to edit the image name in the `Deployment` as well as the domains in `ManagedCertificate`. After some time, you will be able to see your web page at your domain name with `https` properly working. 73 | 74 | ``` 75 | kubectl apply -f k8s/deployment_1.yml 76 | ``` 77 | 78 | Next, we can deploy the web app with an OAuth reverse proxy in front of it with [k8s/deployment_2.yml](./k8s/deployment_2.yml): 79 | 80 | ``` 81 | kubectl apply -f k8s/whitelist.yml # this is a whitelist of all the email addresses you want to allow to access your app. 82 | kubectl apply -f k8s/deployment_2.yml 83 | ``` 84 | 85 | ## Demo 86 | 87 | See [https://hamel.page](https://hamel.page) 88 | 89 | It will ask you to authenticate via GitHub, but will give you a 403 if your email is not in [k8s/whitelist.yml](./k8s/whitelist.yml). 90 | 91 | 92 | -------------------------------------------------------------------------------- /gke_k8s/k8s/deployment_1.yml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: hello 5 | labels: 6 | app: hello 7 | spec: 8 | replicas: 2 9 | selector: 10 | matchLabels: 11 | app: hello 12 | template: 13 | metadata: 14 | labels: 15 | app: hello 16 | spec: 17 | containers: 18 | - name: website 19 | image: docker.io/hamelsmu/hello-web 20 | ports: 21 | - containerPort: 8083 22 | --- 23 | ## Sets up networking for the app 24 | apiVersion: v1 25 | kind: Service 26 | metadata: 27 | name: hamelapp 28 | spec: 29 | selector: 30 | app: hello 31 | ports: 32 | - port: 8083 33 | targetPort: 8083 34 | --- 35 | # This is the managed certificate 36 | apiVersion: networking.gke.io/v1 37 | kind: ManagedCertificate 38 | metadata: 39 | name: managed-cert 40 | spec: 41 | domains: 42 | - hamel.page 43 | --- 44 | # boilerplate from https://cloud.google.com/kubernetes-engine/docs/how-to/managed-certs 45 | apiVersion: networking.k8s.io/v1 46 | kind: Ingress 47 | metadata: 48 | name: managed-cert-ingress 49 | annotations: 50 | kubernetes.io/ingress.global-static-ip-name: oauth-ip-address 51 | networking.gke.io/managed-certificates: managed-cert 52 | kubernetes.io/ingress.class: "gce" 53 | spec: 54 | defaultBackend: 55 | service: 56 | name: hamelapp 57 | port: 58 | number: 8083 59 | -------------------------------------------------------------------------------- /gke_k8s/k8s/deployment_2.yml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: hello 5 | labels: 6 | app: hello 7 | spec: 8 | replicas: 1 9 | selector: 10 | matchLabels: 11 | app: hello 12 | template: 13 | metadata: 14 | labels: 15 | app: hello 16 | spec: 17 | containers: 18 | - name: website 19 | image: docker.io/hamelsmu/hello-web 20 | ports: 21 | - containerPort: 8083 22 | --- 23 | ## Sets up networking for the app 24 | apiVersion: v1 25 | kind: Service 26 | metadata: 27 | name: hamelapp 28 | spec: 29 | selector: 30 | app: hello 31 | ports: 32 | - port: 8083 33 | targetPort: 8083 34 | --- 35 | # This is the managed certificate for https 36 | apiVersion: networking.gke.io/v1 37 | kind: ManagedCertificate 38 | metadata: 39 | name: managed-cert 40 | spec: 41 | domains: 42 | - hamel.page 43 | --- 44 | # boilerplate from https://cloud.google.com/kubernetes-engine/docs/how-to/managed-certs 45 | apiVersion: networking.k8s.io/v1 46 | kind: Ingress 47 | metadata: 48 | name: managed-cert-ingress 49 | annotations: 50 | kubernetes.io/ingress.global-static-ip-name: oauth-ip-address 51 | networking.gke.io/managed-certificates: managed-cert 52 | kubernetes.io/ingress.class: "gce" 53 | spec: 54 | defaultBackend: 55 | service: 56 | name: oauth2-proxy 57 | port: 58 | name: oauth2-proxy 59 | --- 60 | apiVersion: apps/v1 61 | kind: Deployment 62 | metadata: 63 | name: oauth2-proxy 64 | labels: 65 | app: oauth2-proxy 66 | spec: 67 | replicas: 1 68 | selector: 69 | matchLabels: 70 | app: oauth2-proxy 71 | template: 72 | metadata: 73 | labels: 74 | app: oauth2-proxy 75 | spec: 76 | containers: 77 | - name: oauth2-proxy 78 | image: quay.io/oauth2-proxy/oauth2-proxy #:v7.2.0 79 | readinessProbe: # Probes are set at the container level. 80 | httpGet: 81 | path: /ping # This is an HTTP GET, using the health URL. 82 | port: 4180 83 | periodSeconds: 5 # The probe fires every five seconds. 84 | envFrom: 85 | - configMapRef: 86 | name: oauth-env-file 87 | args: ["--provider", "github", 88 | "--reverse-proxy", "true", 89 | "--upstream", "http://hamelapp:8083", 90 | "--http-address=0.0.0.0:4180", 91 | "--https-address=0.0.0.0:4443", 92 | "--scope", "user:email", 93 | "--cookie-expire", "0h0m30s", 94 | "--session-cookie-minimal", "true", 95 | "--skip-provider-button", "true", 96 | "--authenticated-emails-file", "/app/emails/email_list.txt"] 97 | ports: 98 | - containerPort: 4180 # this is port this container serves on by default 99 | volumeMounts: 100 | - name: emails 101 | mountPath: "/app/emails" 102 | readOnly: true 103 | volumes: 104 | - name: emails 105 | configMap: 106 | name: email-whitelist 107 | --- 108 | ## Sets up networking for the proxy 109 | apiVersion: v1 110 | kind: Service 111 | metadata: 112 | name: oauth2-proxy 113 | spec: 114 | selector: 115 | app: oauth2-proxy 116 | ports: 117 | - port: 4180 118 | name: oauth2-proxy 119 | targetPort: 4180 120 | -------------------------------------------------------------------------------- /gke_k8s/k8s/whitelist.yml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | metadata: 3 | name: email-whitelist 4 | data: 5 | email_list.txt: | 6 | hamel.husain@gmail.com 7 | inc007@gmail.com 8 | mwlorgat@gmail.com 9 | kind: ConfigMap 10 | -------------------------------------------------------------------------------- /gke_k8s/setup_k8s.sh: -------------------------------------------------------------------------------- 1 | #setup container 2 | docker build -t hamelsmu/hello-web --platform=linux/amd64 . && docker push hamelsmu/hello-web 3 | 4 | gcloud container clusters create-auto oauth-demo \ 5 | --region us-west1 \ 6 | --project=kubeflow-dev 7 | 8 | # Create a static IP address 9 | gcloud compute addresses create oauth-ip-address --global 10 | 11 | # Get the IP address 12 | 13 | gcloud compute addresses describe oauth-ip-address --global 14 | # 34.110.194.239 15 | # Setup your DNS with an A record for your domain pointing to this IP address 16 | # This IP will be referenced by the ingress resource by name "oauth-ip-address" 17 | 18 | ### Create a file named oauth.env with the following contents 19 | # generate the cookie secret like this: 20 | # python -c 'import os,base64; print(base64.urlsafe_b64encode(os.urandom(32)).decode())' 21 | 22 | # OAUTH_CLIENT_ID=your-client-id 23 | # OAUTH_CLIENT_SECRET=your-client-secret 24 | # OAUTH2_PROXY_COOKIE_SECRET=your-cookie-secret 25 | 26 | # Create a configmap from the oauth.env file 27 | kl create configmap oauth-env-file --from-env-file oauth.env 28 | -------------------------------------------------------------------------------- /local/.gitignore: -------------------------------------------------------------------------------- 1 | /.quarto/ 2 | .env 3 | _site/ -------------------------------------------------------------------------------- /local/README.md: -------------------------------------------------------------------------------- 1 | # Minimal OAuth Locally 2 | 3 | We will use the Oauth2 Proxy to make a static site private with minimal dependencies. In this case, I'm going to serve a [Quarto](https://quarto.org/) site (my favorite static site generator), and secure it with an email whitelist (a [text file with emails](./local/emails/email_list.txt)). There are many other authorization schemes in addition to an email whitelist, [which you can read about here](https://oauth2-proxy.github.io/oauth2-proxy/docs/configuration/overview). 4 | 5 | [This section](../README.md#how-does-this-work) describes how OAuth works in the context of this example. 6 | 7 | ## Steps 8 | 9 | ### 1. Generate the static site 10 | 11 | First, install [Quarto](https://quarto.org/), and run the following command: 12 | 13 | ```bash 14 | quarto render 15 | ``` 16 | 17 | This will create the directory `_site/` with a static site in it (HTML, CSS, etc). 18 | 19 | ### 2. Create an OAuth App 20 | 21 | Create an [OAuth App](https://github.com/settings/applications/new), but fill out the fields like this for local testing: 22 | 23 | > ![](local_app.png) 24 | 25 | _The `oauth2/callback` path on the Callback URL is something specific to the OAuth2 proxy. It is the [endpoint](https://oauth2-proxy.github.io/oauth2-proxy/docs/features/endpoints) this proxy uses to handle the callbacks from the OAuth provider._ 26 | 27 | Make sure you store the `Client ID` and `Client Secret` into the enviornment variables `OAUTH2_PROXY_CLIENT_ID` and `OAUTH2_PROXY_CLIENT_SECRET`, respectively. 28 | 29 | ### 3. Start The Proxy + WebServer Locally 30 | 31 | First, add your email address to [emails/email_list.txt](./emails/email_list.txt) to whitelist yourself. The OAuth2 proxy uses this list to determine who is authorized to see your site. There are many other authorization schemes in addition to an email whitelist, [which you can read about here](https://oauth2-proxy.github.io/oauth2-proxy/docs/configuration/overview). 32 | 33 | Next, generate a cookie secret by running `python -c 'import os,base64; print(base64.urlsafe_b64encode(os.urandom(32)).decode())'` and store that value in the `OAUTH2_PROXY_COOKIE_SECRET` environment variable. 34 | 35 | Next, run the following command from this directory: 36 | 37 | ```bash 38 | docker run -v $(pwd)/_site:/app \ 39 | -v $(pwd)/emails:/site_config \ 40 | -p 4180:4180 \ 41 | quay.io/oauth2-proxy/oauth2-proxy \ 42 | --provider github \ 43 | --upstream "file:///app/#/" \ 44 | --http-address=":4180" \ 45 | --authenticated-emails-file "/site_config/email_list.txt" \ 46 | --scope user:email \ 47 | --cookie-expire 0h0m30s \ 48 | --session-cookie-minimal true \ 49 | --skip-provider-button true \ 50 | --cookie-secret $OAUTH2_PROXY_COOKIE_SECRET \ 51 | --client-id $OAUTH2_PROXY_CLIENT_ID \ 52 | --client-secret $OAUTH2_PROXY_CLIENT_SECRET \ 53 | --cookie-csrf-per-request=true \ 54 | --redirect-url="http://localhost:4180/oauth2/callback" \ 55 | --cookie-secure=false \ 56 | --cookie-csrf-expire=5m 57 | ``` 58 | 59 | These flags are annotated below (but you have to copy and paste the above version for it to work.) 60 | 61 | ```bash 62 | # COPY & PASTE THE ABOVE VERSION, THIS IS JUST AN EXPLANATION 63 | 64 | docker run -v $(pwd)/_site:/app \ # the directory with the static site 65 | -v $(pwd)/emails:/site_config \ # the dirctory with the email list 66 | -p 4180:4180 # bind the ports 67 | quay.io/oauth2-proxy/oauth2-proxy \ # the official docker image for Oauth2 proxy 68 | --provider github \ # use GitHub as the Oauth provider 69 | --upstream "file:///app/#/" \ # The location of the static site files 70 | --http-address=":4180" \ # Bind the 4180 port on all interfaces which is necessary for Docker (we aren't using https for local testing). 71 | --authenticated-emails-file "/site_config/email_list.txt" \ # This is the email whitelist 72 | --scope user:email \ # This tells the Oauth provider, GitHub, to share your email with the Oauth proxy 73 | --cookie-expire 0h0m30s \ # Optional: This helps the cookie expire more quickly which could be helpful for security 74 | --session-cookie-minimal true \ # Optional: don't store uncessary info in cookie since we aren't using features that require it 75 | --skip-provider-button true \ # Don't need a seperate "login with GitHub" screen 76 | --cookie-secret $OAUTH2_PROXY_COOKIE_SECRET \ # This is the secret you generate, see https://oauth2-proxy.github.io/oauth2-proxy/docs/configuration/overview 77 | --client-id $OAUTH2_PROXY_CLIENT_ID \ # This is the ID of your Oauth App from GitHub 78 | --client-secret $OAUTH2_PROXY_CLIENT_SECRET \ # This is the secret of your Oauth App from GitHub 79 | --cookie-csrf-per-request=true \ # allows for parallel requests 80 | # THE BELOW FLAGS ARE ONLY FOR LOCAL TESTING \ 81 | --redirect-url="http://localhost:4180/oauth2/callback" \ # this is necessary for local testing only 82 | --cookie-secure=false \ # this is necessary for local testing only 83 | --cookie-csrf-expire=5m # this is necessary for local testing only 84 | ``` 85 | 86 | Note how the OAuth2 Proxy doubles as a web server also! That is what `--upstream` flag enables.[^1] 87 | 88 | ### 4. Test Security / Access 89 | 90 | Preview your site at [http://localhost:4180](http://localhost:4180). 91 | 92 | There is a file named [emails/email_list.txt](./emails/email_list.txt) that contains a list of the email identities that are allowed to view your site. Try misspelling your email on purpose and see what happens when you do a hard refresh after a few seconds. Try changing it back. 93 | 94 | If you are curious how all this works, [read this](#how-does-this-work). 95 | 96 | # Next Lesson: Deploy It! 97 | 98 | Now that you have a minimal idea of how this works locally, you can proceed to host your static site on a VM or other hosted solution. 99 | 100 | **:point_right: [See Lesson 2: Serving Your Site](../simple/README.md). :point_left:** 101 | 102 | In that lesson, we will show you how to: 103 | 104 | - Use a hosting provider to deploy your secure static site, **for free**. 105 | - Enable `https` and set up a custom domain. 106 | 107 | # Appendix 108 | 109 | ## Is this only for static sites? 110 | 111 | No! You can put applications behind the proxy, like a dashboard. Instead of passing `file://...` to the `--upstream` flag, you pass a URL, like `http://your.internal.app`. You can see an [example of this on Kuberenetes here](https://github.com/hamelsmu/k8s-oauth/blob/main/gke_k8s/k8s/deployment_2.yml#L89) and you can read more about this in [the docs on configuring the upstream](https://oauth2-proxy.github.io/oauth2-proxy/docs/configuration/overview#upstreams-configuration). 112 | 113 | ## How does this work? 114 | 115 | With regards to security, [OAuth](https://oauth.net/) is often used for **[authentication](https://www.okta.com/identity-101/authentication-vs-authorization/)**[^2], or identifying who you are. You may have seen sites that have you sign in with GitHub, Google, etc. These [OAuth providers](https://en.wikipedia.org/wiki/List_of_OAuth_providers) pass your identity (and other information if you consent) to the site you are trying to access. In our case, we just want to verify a user's email address so we can determine if they are allowed to see our site. 116 | 117 | There is another step in the security flow referred to as **[authorization](https://www.okta.com/identity-101/authentication-vs-authorization/#:~:text=Authorization%20in%20system%20security%20is,access%20control%20or%20client%20privilege.)** which only shows content the verified user is allowed to see (which is typically implemented in your website's code). Thankfully, [Oauth2 Proxy](https://oauth2-proxy.github.io/oauth2-proxy/docs/) offers many ways of **authorization** that's built-in. This is nice as it doesn't force you to change your website's code (like adding login forms, conditional logic, etc). 118 | 119 | A common reason for using OAuth for authentication is to save users from creating an additional username/password for a site. 120 | 121 | 122 | [^1]: For performance purposes, you may want to put the proxy behind another webserver like [Caddy](https://caddyserver.com/) or [Nginx](https://www.nginx.com/), but it's not required in this example. 123 | 124 | [^2]: OAuth may be used for more than authentication. OAuth allows a user to grant third parties access to query data or interact with APIs relevant to the OAuth provider. For example, when a user signs into your website with GitHub, OAuth gives your app (which is the OAuth2-proxy) a token with a scope (permission level) that the user consents to. In this example, this is what the consent screen looks like, which indicates that the app wishes to get your email:

You can set [other scopes](https://docs.github.com/en/apps/oauth-apps/building-oauth-apps/scopes-for-oauth-apps) for the OAuth application. In our case, we only want to verify the user's email address so we set the scope to `user:email` with the argument `--scope user:email`. 125 | -------------------------------------------------------------------------------- /local/_quarto.yml: -------------------------------------------------------------------------------- 1 | project: 2 | type: website 3 | 4 | website: 5 | title: "local" 6 | navbar: 7 | left: 8 | - href: index.qmd 9 | text: Home 10 | - about.qmd 11 | 12 | format: 13 | html: 14 | theme: cosmo 15 | css: styles.css 16 | toc: true 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /local/about.qmd: -------------------------------------------------------------------------------- 1 | --- 2 | title: "About" 3 | --- 4 | 5 | About this site 6 | -------------------------------------------------------------------------------- /local/emails/email_list.txt: -------------------------------------------------------------------------------- 1 | hamel.husain@gmail.com 2 | inc007@gmail.com 3 | mwlorgat@gmail.com 4 | -------------------------------------------------------------------------------- /local/index.qmd: -------------------------------------------------------------------------------- 1 | --- 2 | title: "local" 3 | --- 4 | 5 | This is a Quarto website. 6 | 7 | To learn more about Quarto websites visit . 8 | -------------------------------------------------------------------------------- /local/local_app.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hamelsmu/oauth-tutorial/c1fa4ef4d5d2ff3b141b0ad7ed20294bafafe612/local/local_app.png -------------------------------------------------------------------------------- /local/styles.css: -------------------------------------------------------------------------------- 1 | /* css styles */ 2 | -------------------------------------------------------------------------------- /simple/README.md: -------------------------------------------------------------------------------- 1 | # Serving Your Site 2 | 3 | We are going to serve a [Quarto](https://quarto.org/) site behind an OAuth proxy to make it private, as [described here](../README.md). 4 | 5 | ## Prerequisites 6 | 7 | 1. You have taken the [first tutorial](../local/README.md). 8 | 9 | 2. A custom domain that you don't mind experimenting with. If you don't have one, you can [buy one here](https://domains.google.com). In this example, I'm using the custom domain `hamel.rsvp`. 10 | 11 | ## Render (Free) 12 | 13 | [Render](https://render.com/) is a hosting service that provides a generous free tier. We just have to learn a little bit about their YAML, but if you did the [first tutorial](../local/README.md), it will be approachable. 14 | 15 | Render also has [Oauth2 Proxy tutorial](https://render.com/blog/password-protect-with-oauth2-proxy) which is pretty close to what we want. We are going to simplify that example considerably, while also getting rid of things that aren't free. 16 | 17 | ### Setup 18 | 19 | 1. Create another OAuth application and save the `Client ID` and `Client Secret` as you did in the [minimal example](../local/README.md). You can fill it out like this: 20 | 21 | ![](app_setup.png) 22 | 23 | 2. [fork this repo](https://github.com/hamelsmu/oauth-render-quarto/tree/main). 24 | 25 | 3. **Optional:** Change the content of your site by editing one of the `.qmd` files, then run `quarto render` to re-generate the content into the `_site/` folder. **Make sure you check-in any changes, including the `_site` folder into your repo.** 26 | 27 | 4. [Click this link to deploy the app](https://dashboard.render.com/blueprints), and grant Render access to the repo you just forked. Next, fill in values for the `OAUTH2_PROXY_CLIENT_ID` and `OAUTH2_PROXY_CLIENT_SECRET`: 28 | 29 | ![](render_blueprint.png) 30 | 31 | 5. Set up your custom domain by navigating to [your dashboard](https://dashboard.render.com/) and clicking on this project, which is named `oauth2-proxy-render` (unless you changed it). On the left-hand side click `Settings`. Under `Settings`, scroll down to the section named `Custom Domains`. Add your domain there and follow the instructions. Render will take care of provisioning an SSL certificate to enable `https` on your domain for you. 32 | 33 | ### Testing Your Site 34 | 35 | Anytime your push a change to your repo, your site will rebuild. Try misspelling your email in the `email_list.txt` file and see what happens, then try changing it back. **Warning: if you revoke/grant access, it takes 2-3 minutes for it to take effect, and you may have to clear your cache - be patient!** 36 | 37 | ### How does it work? 38 | 39 | See the [repo's README](https://github.com/hamelsmu/oauth-render-quarto). 40 | 41 | 42 | ### Alternatives to Render 43 | 44 | These are some alternatives to Render that work similarly. 45 | 46 | - [Fly.io](https://fly.io/). Doesn't have a free tier, but is anything _really_ free? 47 | - [Railway](https://railway.app/) 48 | 49 | ## Hosting on A VM 50 | 51 | You might not like serverless solutions. They can often be harder to debug. However, you can host a blog on a VM as well. Here are some tips if you are hosting things on a VM. 52 | 53 | 1. You might want to use [Caddy](https://caddyserver.com/) or [Nginx](https://www.nginx.com/) as your web server. Caddy is easier to use and just as powerful. Both of these servers facilitate [automatically provisioning SSL certificates](https://caddyserver.com/docs/automatic-https#issuer-fallback) for `https` (but it requires some setup). Even though OAuth2 Proxy can be a web server itself, it is convenient to put one of these in front of the proxy for the automatic SSL functionality. Furthermore, this setup gives you the flexibility to host a mix of private and public sites from the same VM. 54 | 55 | 2. If you have a web server that handles SSL for you that forwards traffic to the OAuth2 Proxy, your OAuth2 proxy will likely receive `http` traffic from the web server. Therefore, you might want to configure your `docker run` command accordingly: 56 | 57 | The below command assumes: 58 | - There is an `app/emails` and `app/site` directory in the current directory. 59 | - You have set the `OAUTH2_PROXY_COOKIE_SECRET`, `OAUTH2_PROXY_CLIENT_ID`, and `OAUTH2_PROXY_CLIENT_SECRET` environment variables. 60 | 61 | ```bash 62 | docker run -v $(pwd)/app/site:/app \ 63 | -v $(pwd)/app/emails:/site_config \ 64 | -p 4180:4180 \ 65 | quay.io/oauth2-proxy/oauth2-proxy \ 66 | --provider github \ 67 | --upstream "file:///app/#/" \ 68 | --http-address=":4180" \ 69 | --authenticated-emails-file "/site_config/email_list.txt" \ 70 | --scope user:email \ 71 | --cookie-expire 0h0m30s \ 72 | --session-cookie-minimal true \ 73 | --skip-provider-button true \ 74 | --cookie-secret $OAUTH2_PROXY_COOKIE_SECRET \ 75 | --client-id $OAUTH2_PROXY_CLIENT_ID \ 76 | --client-secret $OAUTH2_PROXY_CLIENT_SECRET \ 77 | --cookie-csrf-per-request=true 78 | ``` 79 | 80 | In the above example, you would have your webserver forward traffic to `localhost:4180`. 81 | 82 | 3. Use [rsync](https://linuxize.com/post/how-to-use-rsync-for-local-and-remote-data-transfer-and-synchronization/) to update your static site or email white list when necessary. For example, this is how I would sync my local files with my VM in this case: 83 | 84 | ```bash 85 | rsync -a email_list.txt myvm:/home/ubuntu/app/emails/ 86 | rsync -a _site/* myvm:/home/ubuntu/app/site 87 | ``` 88 | 89 | I recommend updating your `~/.ssh/config` file so that you can reference your VM with a name like `myvm`, as shown above. For example, this is the relevant part of my `~/.ssh/config`, which has the IP, username, and location of the private key I need to access my VM. 90 | 91 | ``` 92 | Host myvm 93 | HostName 111.22.333.44 # your VM's Public IP Address 94 | User ubuntu # The username you need to login 95 | IdentityFile /Users/hamelsmu/.ssh/vm_private_key.rsa 96 | ``` 97 | 98 | 99 | # Next Steps 100 | 101 | As an advanced exercise, I show how to do this [same thing on Kubernetes](../gke_k8s/README.md). That example: 102 | 103 | - Deploys a website on Kubernetes behind a load balancer with the OAuth proxy. 104 | - Sets up automated SSL for `https` with Google Managed Certificates 105 | - Deploys a separate webserver for the website to make the pattern more generalizable. 106 | 107 | Deploying this on Kubernetes is much more complicated, but is something I wanted to play with. 108 | 109 | **:point_right: [See Lesson 3: Deploying The OAuth2 Proxy On Kubernetes](../gke_k8s/README.md). :point_left:** 110 | -------------------------------------------------------------------------------- /simple/app_setup.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hamelsmu/oauth-tutorial/c1fa4ef4d5d2ff3b141b0ad7ed20294bafafe612/simple/app_setup.png -------------------------------------------------------------------------------- /simple/render_blueprint.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hamelsmu/oauth-tutorial/c1fa4ef4d5d2ff3b141b0ad7ed20294bafafe612/simple/render_blueprint.png --------------------------------------------------------------------------------