Compare commits
No commits in common. "secret" and "docs" have entirely different histories.
8
.gitignore
vendored
@ -1,2 +1,8 @@
|
|||||||
# ignore mkdocs
|
# ignore mkdocs output/gh-pages branch
|
||||||
site/
|
site/
|
||||||
|
|
||||||
|
# ignore secret branch
|
||||||
|
secret/
|
||||||
|
|
||||||
|
# ignore any makefiles
|
||||||
|
Makefile
|
||||||
|
13
LICENSE
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
||||||
|
Version 458.835.16.92, May 2018
|
||||||
|
|
||||||
|
Copyright (C) 2018 Charles Reid <charles@charlesreid1.com>
|
||||||
|
|
||||||
|
Everyone is permitted to copy and distribute verbatim or modified
|
||||||
|
copies of this license document, and changing it is allowed as long
|
||||||
|
as the name is changed.
|
||||||
|
|
||||||
|
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
||||||
|
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||||
|
|
||||||
|
0. You just DO WHAT THE FUCK YOU WANT TO.
|
58
README.md
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
# github-heroku-attack-rabbits
|
||||||
|
|
||||||
|
## What's this business all about, then?
|
||||||
|
|
||||||
|
This repository helps you protect your secret pages by (deep breath):
|
||||||
|
|
||||||
|
hosting your secret page of static and/or dynamic content using a free Heroku app
|
||||||
|
running a Python Flask server that uses Flask-Dance to authenticate visitors
|
||||||
|
with Github which allows you fine-grained access control over your pages based on
|
||||||
|
user attributes like organization or team membership or even things like how many
|
||||||
|
repositories a user has or how many vowels are in their username.
|
||||||
|
|
||||||
|
Also, did I mention the attack rabbits?
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
|
||||||
|
## Where is everything?
|
||||||
|
|
||||||
|
Final pages:
|
||||||
|
|
||||||
|
* The finished product (pages on Heroku protected by attack rabbits)
|
||||||
|
is at [github-heroku-attack-rabbits.herokuapp.com](https://github-heroku-attack-rabbits.herokuapp.com)
|
||||||
|
|
||||||
|
* The documentation is at [pages.charlesreid1.com/github-heroku-attack-rabbits](https://pages.charlesreid1.com/github-heroku-attack-rabbits)
|
||||||
|
|
||||||
|
Two branches in this repo compose the github-heroku-attack-rabbits documentation:
|
||||||
|
|
||||||
|
* (**YOU ARE HERE**) The [docs](https://git.charlesreid1.com/charlesreid1/github-heroku-attack-rabbits/src/branch/docs) branch
|
||||||
|
contains the files needed to generate the
|
||||||
|
[github-heroku-attack-rabbits documentation site](https://pages.charlesreid1.com/github-heroku-attack-rabbits).
|
||||||
|
|
||||||
|
* The [gh-pages](https://git.charlesreid1.com/charlesreid1/github-heroku-attack-rabbits/src/branch/gh-pages) branch
|
||||||
|
contains the static files generated from the documentation.
|
||||||
|
The contents of this branch compose the
|
||||||
|
[github-heroku-attack-rabbits documentation site](https://pages.charlesreid1.com/github-heroku-attack-rabbits).
|
||||||
|
|
||||||
|
Two branches illustrate github-heroku-attack-rabbits in practice:
|
||||||
|
|
||||||
|
* The [secret](https://git.charlesreid1.com/charlesreid1/github-heroku-attack-rabbits/src/branch/secret) branch contains the files needed to create the secret page.
|
||||||
|
This repository is public, so obviously these aren't *actually* secret,
|
||||||
|
but in practice this would be in a protected repository.
|
||||||
|
|
||||||
|
* The [heroku-pages](https://git.charlesreid1.com/charlesreid1/github-heroku-attack-rabbits/src/branch/heroku-pages) branch
|
||||||
|
contains the content that is actually pushed to Heroku - that is,
|
||||||
|
the final Flask app.
|
||||||
|
|
||||||
|
|
||||||
|
## Where do I start?
|
||||||
|
|
||||||
|
See the [documentation](https://pages.charlesreid1.com/github-heroku-attack-rabbits)
|
||||||
|
or [docs/index.md](docs/index.md).
|
||||||
|
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
This is released under the [WTFPL](LICENSE).
|
||||||
|
|
16
Readme.md
@ -1,16 +0,0 @@
|
|||||||
# github-heroku-attack-rabbits: secret branch
|
|
||||||
|
|
||||||
The secret branch contains the mkdocs source files
|
|
||||||
needed to generate the final static content for the
|
|
||||||
secret site.
|
|
||||||
|
|
||||||
The `secret` branch contains the source files,
|
|
||||||
the `heroku-pages` branch contains the finished product.
|
|
||||||
|
|
||||||
The `heroku-pages` branch contains the site that is
|
|
||||||
actually deployed to Heroku at the Github-Heroku attack
|
|
||||||
sheep app:
|
|
||||||
|
|
||||||
* [https://github-heroku-attack-rabbits.herokuapp.com](https://github-heroku-attack-rabbits.herokuapp.com).
|
|
||||||
|
|
||||||
Lost? Visit the [docs](https://git.charlesreid1.com/charlesreid1/github-heroku-attack-rabbits) branch.
|
|
10
docs/css/custom.css
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
body {
|
||||||
|
background-color: #efefef;
|
||||||
|
}
|
||||||
|
div.body {
|
||||||
|
background-color: #efefef;
|
||||||
|
}
|
||||||
|
.md-typeset pre {
|
||||||
|
background-color: #ccc;
|
||||||
|
}
|
||||||
|
|
33
docs/custom_domains.md
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
# Custom Domains
|
||||||
|
|
||||||
|
If you want to use a custom domain:
|
||||||
|
|
||||||
|
* Set up custom domain using Heroku command line interface
|
||||||
|
* This will set up a DNS subdomain specifically for your app
|
||||||
|
* Point your DNS records to the Heroku DNS subdomain
|
||||||
|
|
||||||
|
This also introduces complications with HTTP vs HTTPS:
|
||||||
|
OAuth must happen over HTTPS (required by protocol).
|
||||||
|
But you the user can control your domain and create
|
||||||
|
SSL certificates for it, but Heroku is hosting the app.
|
||||||
|
|
||||||
|
You have two options: the free option, and the pay option.
|
||||||
|
|
||||||
|
**The pay option:** For $7/mo you can upgrade to hobby nodes,
|
||||||
|
which allows you to give Heroku permission to create an SSL
|
||||||
|
certificate for your domain. This is the easiest solution
|
||||||
|
and requires zero setup, zero certificate management.
|
||||||
|
|
||||||
|
**The free option:** You can have your domain (HTTP only)
|
||||||
|
forward to Heroku (HTTPS can't be forwarded - that's ***key***).
|
||||||
|
When you hit the Heroku domain, it will log the user in to Github.
|
||||||
|
When the user logs in successfully, Github will redirect them to
|
||||||
|
the callback URL. This callback URL ***MUST*** be HTTPS, so it cannot
|
||||||
|
redirect back to your (HTTP-only) custom domain.
|
||||||
|
|
||||||
|
That means the userr will, after authenticating with Github,
|
||||||
|
always be redirected to `https://my-cool-app.herokuapp.com`
|
||||||
|
and never `http://my-cool-custom-domain-that-cannot-be-used-as-a-callback-because-it-is-https-only.com`.
|
||||||
|
|
||||||
|
The paid option is much, much simpler in the end
|
||||||
|
and will save you $7/mo in setup time alone.
|
174
docs/flask.md
Normal file
@ -0,0 +1,174 @@
|
|||||||
|
# Create a Flask App using Flask-Dance
|
||||||
|
|
||||||
|
This is the heart of the method.
|
||||||
|
|
||||||
|
The best thing to do here is just to walk you through the script.
|
||||||
|
|
||||||
|
Import statements:
|
||||||
|
|
||||||
|
```
|
||||||
|
import os, json
|
||||||
|
from os.path import join, isfile, isdir
|
||||||
|
from werkzeug.contrib.fixers import ProxyFix
|
||||||
|
from flask import Flask, redirect, url_for, send_from_directory
|
||||||
|
from flask_dance.contrib.github import make_github_blueprint, github
|
||||||
|
```
|
||||||
|
|
||||||
|
Note that flask-dance adds an OAuth login/callback route
|
||||||
|
to your Flask app by creating a `/login` blueprint,
|
||||||
|
meaning all the OAuth stuff is just magically available
|
||||||
|
via `/login`.
|
||||||
|
|
||||||
|
Set paths for static content:
|
||||||
|
|
||||||
|
```
|
||||||
|
PROJECT_ROOT = os.path.dirname(os.path.realpath(__file__))
|
||||||
|
STATIC_PATH = 'content'
|
||||||
|
```
|
||||||
|
|
||||||
|
Create and configure app. This requires confidential information
|
||||||
|
for the Github application you created, specifically the client
|
||||||
|
ID and client secret. These are at the very top of the page when
|
||||||
|
you visit your app's settings page.
|
||||||
|
|
||||||
|
To find this, after you log in, click your profile photo in the
|
||||||
|
upper right > Settings > Developer Settings > OAuth Apps > click the
|
||||||
|
name for your OAuth app.
|
||||||
|
|
||||||
|
```
|
||||||
|
app = Flask(__name__)
|
||||||
|
|
||||||
|
# this worked locally, but not on heroku
|
||||||
|
app.wsgi_app = ProxyFix(app.wsgi_app)
|
||||||
|
|
||||||
|
app.secret_key = os.environ.get("FLASK_SECRET_KEY", "9502861d41e8729c5cae3225920b1b46")
|
||||||
|
|
||||||
|
app.config["RESULT_STATIC_PATH"] = STATIC_PATH #os.path.join(PROJECT_ROOT,STATIC_PATH)
|
||||||
|
app.config["GITHUB_OAUTH_CLIENT_ID"] = os.environ.get("GITHUB_OAUTH_CLIENT_ID")
|
||||||
|
app.config["GITHUB_OAUTH_CLIENT_SECRET"] = os.environ.get("GITHUB_OAUTH_CLIENT_SECRET")
|
||||||
|
```
|
||||||
|
|
||||||
|
Now the magic happens: we use flask-dance to create a blueprint
|
||||||
|
that has methods and settings all ready to go for us to do the
|
||||||
|
OAuth dance.
|
||||||
|
|
||||||
|
`make_github_blueprint()` is part of the contrib module of flask-dance.
|
||||||
|
There are several similar methods to generate blueprints for
|
||||||
|
authenticating with other APIs.
|
||||||
|
|
||||||
|
```
|
||||||
|
github_bp = make_github_blueprint(
|
||||||
|
client_id = os.environ.get('GITHUB_OAUTH_CLIENT_ID'),
|
||||||
|
client_secret = os.environ.get('GITHUB_OAUTH_CLIENT_SECRET'),
|
||||||
|
scope='read:org')
|
||||||
|
|
||||||
|
app.register_blueprint(github_bp, url_prefix="/login")
|
||||||
|
|
||||||
|
contents404 = "<html><body><h1>Status: Error 404 Page Not Found</h1></body></html>"
|
||||||
|
contents403 = "<html><body><h1>Status: Error 403 Access Denied</h1></body></html>"
|
||||||
|
contents200 = "<html><body><h1>Status: OK 200</h1></body></html>"
|
||||||
|
```
|
||||||
|
|
||||||
|
Deal with the `/` route first:
|
||||||
|
|
||||||
|
* Check if authorized, if not, redirect them to the login URL
|
||||||
|
(magical URLs taken care of magically by our `make_github_blueprint`
|
||||||
|
function above)
|
||||||
|
* If user is authorized (i.e., if they have gone through the OAuth
|
||||||
|
process and given their passsword to Github and been redirected
|
||||||
|
to your app with an OAuth token), then the next step is to
|
||||||
|
find out some information about them.
|
||||||
|
* Use the `github` object to call the Github API directly.
|
||||||
|
* Decide what to do from there.
|
||||||
|
|
||||||
|
```
|
||||||
|
@app.route('/')
|
||||||
|
def index():
|
||||||
|
if not github.authorized:
|
||||||
|
return redirect(url_for("github.login"))
|
||||||
|
|
||||||
|
resp = github.get("/user/orgs")
|
||||||
|
if resp.ok:
|
||||||
|
|
||||||
|
all_orgs = resp.json()
|
||||||
|
for org in all_orgs:
|
||||||
|
if org['login']=='rainbow-mind-machine':
|
||||||
|
```
|
||||||
|
|
||||||
|
The next line is important to how the server works:
|
||||||
|
if all of the criteria above have been met, we return
|
||||||
|
a static file:
|
||||||
|
|
||||||
|
```
|
||||||
|
return send_from_directory(STATIC_PATH, 'index.html')
|
||||||
|
```
|
||||||
|
|
||||||
|
This is normally "bad practice," and numerous type A people
|
||||||
|
on the internet will tell you Flask should not be used for
|
||||||
|
serving static files, and that you should use nginx etc.,
|
||||||
|
but these fail to igonore the following:
|
||||||
|
|
||||||
|
* Heroku does not let you run or configure nginx
|
||||||
|
* For crying out loud this example is about attack rabbits
|
||||||
|
stop taking everything so seriously
|
||||||
|
|
||||||
|
Now that we've got that out of the way...
|
||||||
|
|
||||||
|
Here's how we serve up static files. This is a total hack,
|
||||||
|
but it works. It takes any arbitrary path supplied by the
|
||||||
|
user, and attempts to find a corresponding file to serve up
|
||||||
|
on disk.
|
||||||
|
|
||||||
|
If the user passes a file, then that file is served up.
|
||||||
|
|
||||||
|
If the user passes a directory, then `index.html` is served up.
|
||||||
|
|
||||||
|
If the user asks for a non-existent file, a 404 error is shown.
|
||||||
|
|
||||||
|
If the user is not allowed to view the content, they will face
|
||||||
|
the bowel-emptying terrors of the 403 error.
|
||||||
|
|
||||||
|
```
|
||||||
|
@app.route('/<path:path>')
|
||||||
|
def catch_all(path):
|
||||||
|
|
||||||
|
if not github.authorized:
|
||||||
|
return redirect(url_for("github.login"))
|
||||||
|
|
||||||
|
username = github.get("/user").json()['login']
|
||||||
|
|
||||||
|
rsp = app.config["RESULT_STATIC_PATH"]
|
||||||
|
resp = github.get("/user/orgs")
|
||||||
|
if resp.ok:
|
||||||
|
|
||||||
|
all_orgs = resp.json()
|
||||||
|
for org in all_orgs:
|
||||||
|
if org['login']=='dcppc':
|
||||||
|
|
||||||
|
if(path==''):
|
||||||
|
return send_from_directory(rsp, 'index.html')
|
||||||
|
|
||||||
|
elif(isdir(join(rsp,path))):
|
||||||
|
return send_from_directory(join(rsp,path),'index.html')
|
||||||
|
|
||||||
|
elif(isfile(join(rsp,path))):
|
||||||
|
return send_from_directory(rsp, path)
|
||||||
|
|
||||||
|
else:
|
||||||
|
return contents404
|
||||||
|
|
||||||
|
return contents403
|
||||||
|
```
|
||||||
|
|
||||||
|
Last, set a default 404 handler, and run the app:
|
||||||
|
|
||||||
|
```
|
||||||
|
@app.errorhandler(404)
|
||||||
|
def oops(e):
|
||||||
|
return contents404
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
app.run()
|
||||||
|
```
|
||||||
|
|
25
docs/flask_auth_github.md
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
# Authenticate Users on Github Membership
|
||||||
|
|
||||||
|
For the sake of simplicity, will just demonstrate how this
|
||||||
|
works for a single route, but you can combine different
|
||||||
|
rules into multiple routes to provide access to different
|
||||||
|
people on different parts of a site.
|
||||||
|
|
||||||
|
Here is the relevant method that serves up `index.html`
|
||||||
|
if the user is authenticated:
|
||||||
|
|
||||||
|
```
|
||||||
|
@app.route('/')
|
||||||
|
def index():
|
||||||
|
if not github.authorized:
|
||||||
|
return redirect(url_for("github.login"))
|
||||||
|
|
||||||
|
resp = github.get("/user")
|
||||||
|
|
||||||
|
if resp.ok:
|
||||||
|
return send_from_directory(STATIC_PATH, 'index.html')
|
||||||
|
|
||||||
|
return contents403
|
||||||
|
```
|
||||||
|
|
||||||
|
|
22
docs/flask_auth_org.md
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
# Authenticate Users on Organization or Team Membership
|
||||||
|
|
||||||
|
Here is how we can make access to a given page or route
|
||||||
|
conditional on membership in an organization (in this
|
||||||
|
example, membership in the `rainbow-mind-machine` organization
|
||||||
|
is required for access):
|
||||||
|
|
||||||
|
```
|
||||||
|
@app.route('/')
|
||||||
|
def index():
|
||||||
|
if not github.authorized:
|
||||||
|
return redirect(url_for("github.login"))
|
||||||
|
|
||||||
|
resp = github.get("/user/orgs")
|
||||||
|
if resp.ok:
|
||||||
|
|
||||||
|
all_orgs = resp.json()
|
||||||
|
for org in all_orgs:
|
||||||
|
if org['login']=='rainbow-mind-machine':
|
||||||
|
return send_from_directory(STATIC_PATH, 'index.html')
|
||||||
|
```
|
||||||
|
|
27
docs/flask_auth_other.md
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
# Authenticate Users on Other Criteria
|
||||||
|
|
||||||
|
Suppose we wanted to do something silly like restrict
|
||||||
|
access to a page to users with Github handles that were
|
||||||
|
between 5 and 7 letters.
|
||||||
|
|
||||||
|
Here is the relevant method that serves up `index.html`
|
||||||
|
if the user's Github handle is 5-7 letters long:
|
||||||
|
|
||||||
|
```
|
||||||
|
@app.route('/')
|
||||||
|
def index():
|
||||||
|
if not github.authorized:
|
||||||
|
return redirect(url_for("github.login"))
|
||||||
|
|
||||||
|
resp = github.get("/user")
|
||||||
|
|
||||||
|
if resp.ok:
|
||||||
|
username = resp.json()['login']
|
||||||
|
if len(username)>=5 and len(username)<=7:
|
||||||
|
return send_from_directory(STATIC_PATH, 'index.html')
|
||||||
|
|
||||||
|
return contents403
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
64
docs/flask_auth_portions.md
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
# Authenticate Different Users on Different Portions of Site
|
||||||
|
|
||||||
|
**NOTE: if you are authenticating using membership on a team, you will need the
|
||||||
|
team id of the team you are interested in authenticating against.**
|
||||||
|
|
||||||
|
For this example, let's expand the routes we're looking at
|
||||||
|
a bit more.
|
||||||
|
|
||||||
|
Suppose we have two folders, `team_only` and `org_only`.
|
||||||
|
|
||||||
|
The `team_only` folder should only be accessible to your team, `Team Gold`.
|
||||||
|
|
||||||
|
The `org_only` folder should only be accessible to your organization, `Colorful Colors`.
|
||||||
|
|
||||||
|
The main site (i.e., all other files) should be publicly accessible.
|
||||||
|
|
||||||
|
```
|
||||||
|
@app.route('/')
|
||||||
|
def index():
|
||||||
|
return send_from_directory(STATIC_PATH, 'index.html')
|
||||||
|
|
||||||
|
|
||||||
|
@app.route('/team_only/<path:path>')
|
||||||
|
def team_gold(path):
|
||||||
|
|
||||||
|
if not github.authorized:
|
||||||
|
return redirect(url_for("github.login"))
|
||||||
|
|
||||||
|
rsp = app.config["RESULT_STATIC_PATH"]
|
||||||
|
|
||||||
|
resp = github.get("/user")
|
||||||
|
if resp.ok:
|
||||||
|
username = resp.json()['login']
|
||||||
|
|
||||||
|
team_id = 'XXXXX'
|
||||||
|
|
||||||
|
resp = github.get("/teams/%s/members/%s"%( team_id, username ))
|
||||||
|
if resp.code==204:
|
||||||
|
team_gold_dir = os.path.join(STATIC_PATH, 'team_only')
|
||||||
|
return send_from_directory(team_gold_dir, 'index.html')
|
||||||
|
return contents403
|
||||||
|
|
||||||
|
|
||||||
|
@app.route('/org_only/<path:path>')
|
||||||
|
def team_gold(path):
|
||||||
|
|
||||||
|
if not github.authorized:
|
||||||
|
return redirect(url_for("github.login"))
|
||||||
|
|
||||||
|
rsp = app.config["RESULT_STATIC_PATH"]
|
||||||
|
|
||||||
|
resp = github.get("/user")
|
||||||
|
if resp.ok:
|
||||||
|
|
||||||
|
my_org = 'XXXXXXXX'
|
||||||
|
|
||||||
|
all_orgs = resp.json()
|
||||||
|
for org in all_orgs:
|
||||||
|
if org['login']==my_org:
|
||||||
|
color_org_dir = os.path.join(STATIC_PATH, 'org_only')
|
||||||
|
return send_from_directory(color_org_dir, 'index.html')
|
||||||
|
return contents403
|
||||||
|
```
|
||||||
|
|
98
docs/flask_heroku.md
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
# Deploying to Heroku
|
||||||
|
|
||||||
|
Once we have debugged the Flask app and we are happy with it,
|
||||||
|
we are ready to deploy it to Heroku.
|
||||||
|
|
||||||
|
|
||||||
|
## Repository Setup
|
||||||
|
|
||||||
|
To do this, you should set up your repo as follows:
|
||||||
|
|
||||||
|
Clone the repo:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ git clone https://git.charlesreid1.com/charlesreid1/github-heroku-attack-rabbits.git
|
||||||
|
```
|
||||||
|
|
||||||
|
The repo has the following structure:
|
||||||
|
|
||||||
|
```
|
||||||
|
github-heroku-attack-rabbits/
|
||||||
|
LICENSE
|
||||||
|
README.md
|
||||||
|
mkdocs.yml
|
||||||
|
docs/
|
||||||
|
index.md
|
||||||
|
...
|
||||||
|
mkdocs-material/
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
Now, inside the repo, clone the repo again,
|
||||||
|
but this time clone the `heroku-pages` branch
|
||||||
|
to the `site/` directory:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ cd github-heroku-attack-rabbits/
|
||||||
|
$ git clone -b heroku-pages https://git.charlesreid1.com/charlesreid1/github-heroku-attack-rabbits.git site
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## Heroku Deploy Process
|
||||||
|
|
||||||
|
To deploy content to Heroku, we add our Heroku project as a git remote
|
||||||
|
(see the [heroku](heroku.md) page for how to do that) and then push to
|
||||||
|
to the master branch of the heroku remote git repo. Changes are pulled
|
||||||
|
in by Heroku and the app is restarted each time you run `git push`.
|
||||||
|
|
||||||
|
We walk through the steps below.
|
||||||
|
|
||||||
|
|
||||||
|
## Heroku Login
|
||||||
|
|
||||||
|
From the `site/` directory containing the contents of the `heroku-pages` branch,
|
||||||
|
that is, containing the Python flask app, log in to Heroku:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ heroku login
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## Add Heroku Remote to `heroku-pages` Branch
|
||||||
|
|
||||||
|
Now have Heroku add the proper git remote address:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ heroku git:remote -a <heroku-app-name>
|
||||||
|
```
|
||||||
|
|
||||||
|
Now you're ready to deploy to Heroku.
|
||||||
|
|
||||||
|
|
||||||
|
## Deploy to Heroku
|
||||||
|
|
||||||
|
Double check your app is ready, then deploy:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ git push heroku heroku-pages:master
|
||||||
|
```
|
||||||
|
|
||||||
|
This will push the local branch `heroku-pages` to
|
||||||
|
the remote branch `master` on the `heroku` remote.
|
||||||
|
This should begin a pre-commit hook where Heroku
|
||||||
|
compiles your Python app. You should get the green
|
||||||
|
light, if you tested your app locally and everything
|
||||||
|
was good to go.
|
||||||
|
|
||||||
|
|
||||||
|
## Check Your Heroku App
|
||||||
|
|
||||||
|
Your Heroku app will be available at
|
||||||
|
|
||||||
|
```
|
||||||
|
https://<heroku-app-name>.herokuapp.com
|
||||||
|
```
|
||||||
|
|
||||||
|
You should see your Python flask app
|
||||||
|
show up shortly.
|
||||||
|
|
42
docs/flask_local.md
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
# Testing Flask App Locally
|
||||||
|
|
||||||
|
We set up the Github App to use a callback of `https://localhost:5000/login/github/authorized`
|
||||||
|
so that we could test the app locally. Now it is time to test the app locally.
|
||||||
|
|
||||||
|
The application needs access to your Github app id and token. Those are provided
|
||||||
|
via the `GITHUB_OAUTH_CLIENT_{ID,SECRET}` environment variables. Set these
|
||||||
|
when you run the actual python command to run the server:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ GITHUB_OAUTH_CLIENT_ID="xxxxxxx" \
|
||||||
|
GITHUB_OAUTH_CLIENT_SECRET="xxxxxxx" \
|
||||||
|
OAUTHLIB_INSECURE_TRANSPORT=true \
|
||||||
|
python github.py
|
||||||
|
```
|
||||||
|
|
||||||
|
This runs the Flask server on port 5000, where it will wait for a visitor.
|
||||||
|
The way we have our application written in this example, the main `/` route
|
||||||
|
will redirect the user to a Github login screen immediately, but you could
|
||||||
|
also present the user with a friendly welcome page when they go to `/`,
|
||||||
|
and only redirect them to the Github login prompt when they visit a
|
||||||
|
URL like `/login` or `/auth`.
|
||||||
|
|
||||||
|
Once you run the above command, open the following URL in your browser:
|
||||||
|
|
||||||
|
```
|
||||||
|
http://localhost:5000/
|
||||||
|
```
|
||||||
|
|
||||||
|
**NOTE: Make sure you are logged out of Github and that you clear your cookies
|
||||||
|
if you are already logged in as one user and wish to authenticate as another.
|
||||||
|
The login is _very_ persistent so you may need to close and re-open your browser.**
|
||||||
|
|
||||||
|
Visiting the address above will result in your being redirected to a Github
|
||||||
|
login page. Once you login, Github will redirect the user back to the
|
||||||
|
github-heroku-attack-rabbits application with a token that the application
|
||||||
|
can use to perform actions on behalf of the user.
|
||||||
|
|
||||||
|
## Next Step?
|
||||||
|
|
||||||
|
If the app works, the next step is to deploy to Heroku.
|
||||||
|
|
57
docs/github.md
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
## get started with github
|
||||||
|
|
||||||
|
We mentioned on the [heroku](heroku.md) page that heroku
|
||||||
|
creates a remote git repository to hold the files you
|
||||||
|
want to host.
|
||||||
|
|
||||||
|
To use the contents of a Github repository on Heroku,
|
||||||
|
just treat it like another git remote, no special setup
|
||||||
|
is needed.
|
||||||
|
|
||||||
|
However, to set up your Github-Heroku attack rabbits to
|
||||||
|
authenticate a user via Github, and mercilessly attack
|
||||||
|
all intruders, you must create a Github OAuth App.
|
||||||
|
|
||||||
|
### Creating Github OAuth App
|
||||||
|
|
||||||
|
Log into Github
|
||||||
|
|
||||||
|
Go to Settings
|
||||||
|
|
||||||
|
Click "Developer Settings" on the left side
|
||||||
|
|
||||||
|
Click "New OAuth App" button in upper right
|
||||||
|
|
||||||
|
**What do these settings mean?**
|
||||||
|
|
||||||
|
* **Application name** is what will be shown to users when they visit
|
||||||
|
a page protected by the attack rabbits and are prompted for
|
||||||
|
their password by Github.
|
||||||
|
|
||||||
|
* **Homepage URL/Application description** are for users who want to know more
|
||||||
|
about your killer attack rabbit Github app
|
||||||
|
|
||||||
|
* **Authorization callback URL** is the URL that the users will be sent to
|
||||||
|
once they authenticate with Github and they are granted an OAuth token.
|
||||||
|
This is the magic ingredient that allows you to take actions on behalf
|
||||||
|
of the account logging in.
|
||||||
|
|
||||||
|
In this guide we'll cover the case of checking membership in organizations or teams,
|
||||||
|
but what your attack rabbits end up doing to determine if a user is allowed to
|
||||||
|
access your secret pages is up to you.
|
||||||
|
|
||||||
|
## Values to use
|
||||||
|
|
||||||
|
You should set your own values for the **name** and **description** fields.
|
||||||
|
|
||||||
|
The **home URL** is not actually necessary - it is simply provided for users to
|
||||||
|
get more information about your app.
|
||||||
|
|
||||||
|
The most important is the **callback URL**, which should be set to:
|
||||||
|
|
||||||
|
http://localhost:5000/login/github/authorized
|
||||||
|
|
||||||
|
This is for testing locally *only*.
|
||||||
|
|
||||||
|
Don't use HTTPS in the callback URL!
|
||||||
|
|
67
docs/heroku.md
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
# create heroku app
|
||||||
|
|
||||||
|
|
||||||
|
### Heroku toolbelt
|
||||||
|
|
||||||
|
Heroku offers a really nice command line interface tool called
|
||||||
|
Heroku Toolbelt. It is available through Homebrew and Aptitude:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ brew install heroku # from Mac
|
||||||
|
|
||||||
|
$ apt-get install heroku # from Ubuntu
|
||||||
|
```
|
||||||
|
|
||||||
|
It is then available on the command line as `heroku`.
|
||||||
|
|
||||||
|
The first thing you should do is authenticate with
|
||||||
|
your Heroku account by running
|
||||||
|
|
||||||
|
```
|
||||||
|
$ heroku login
|
||||||
|
```
|
||||||
|
|
||||||
|
We will use this command line application for the following tasks:
|
||||||
|
|
||||||
|
* Create a git remote to point to the right Heroku git remote location
|
||||||
|
* Set environment variables (for e.g. secret keys) on the remote Heroku instance
|
||||||
|
* Get information (logs, status, etc.) about your Heroku app
|
||||||
|
|
||||||
|
We will cover these commands as they come up.
|
||||||
|
|
||||||
|
|
||||||
|
### Create heroku app
|
||||||
|
|
||||||
|
Start by creating a heroku app.
|
||||||
|
|
||||||
|
* Each heroku app must have a unique name
|
||||||
|
* Each heroku app creates a remote git repo
|
||||||
|
* Master branch is what Heroku deploys publicly on herokuapps.com
|
||||||
|
* You will also need heroku CLI to link your github repo to your heroku app
|
||||||
|
|
||||||
|
|
||||||
|
### Where heroku app lives
|
||||||
|
|
||||||
|
Suppose you are creating an app called `my-cool-app`
|
||||||
|
on heroku. Then your application will be hosted by
|
||||||
|
Heroku and will be available at the URL:
|
||||||
|
|
||||||
|
```
|
||||||
|
https://my-cool-app.herokuapp.com
|
||||||
|
```
|
||||||
|
|
||||||
|
### How heroku apps works
|
||||||
|
|
||||||
|
If you have used Github Pages before, Heroku uses a similar
|
||||||
|
model (live hosting one particular branch of a git repository).
|
||||||
|
However, Heroku is different because you can run dynamic scripts
|
||||||
|
using Python, Ruby, PHP, etc.
|
||||||
|
|
||||||
|
To change the content of your Heroku app, just change the contents
|
||||||
|
of the repository, and push to master (push to the master branch of
|
||||||
|
the remote Heroku repository).
|
||||||
|
|
||||||
|
You will need to structure your repository carefully.
|
||||||
|
That's what this page is here to help you do.
|
||||||
|
Heroku can figure out the rest from there.
|
||||||
|
|
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 19 KiB |
BIN
docs/img/warnico.png
Normal file
After Width: | Height: | Size: 7.7 KiB |
BIN
docs/img/warning.png
Normal file
After Width: | Height: | Size: 46 KiB |
92
docs/index.md
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
# github-heroku-attack-rabbits
|
||||||
|
|
||||||
|
## What's this business all about, then?
|
||||||
|
|
||||||
|
This repository helps you put access control into place to protect your secret pages by (deep breath):
|
||||||
|
|
||||||
|
hosting your secret page of static and/or dynamic content by using a free Heroku app
|
||||||
|
running a Python Flask server that uses Flask-Dance to authenticate visitors
|
||||||
|
with Github using OAuth which allows you fine-grained access control for your pages
|
||||||
|
using user attributes like organization or team membership or even things like how many
|
||||||
|
vowels a user has in their username.
|
||||||
|
|
||||||
|
Also, did I mention the attack rabbits?
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
|
||||||
|
## Where is everything?
|
||||||
|
|
||||||
|
Final pages:
|
||||||
|
|
||||||
|
* The finished product (pages on Heroku protected by attack rabbits)
|
||||||
|
is at [github-heroku-attack-rabbits.herokuapp.com](https://github-heroku-attack-rabbits.herokuapp.com)
|
||||||
|
|
||||||
|
* The documentation is at [pages.charlesreid1.com/github-heroku-attack-rabbits](https://pages.charlesreid1.com/github-heroku-attack-rabbits)
|
||||||
|
|
||||||
|
Two branches in this repo compose the github-heroku-attack-rabbits documentation:
|
||||||
|
|
||||||
|
* (**YOU ARE HERE**) The [docs](https://git.charlesreid1.com/charlesreid1/github-heroku-attack-rabbits/src/branch/docs) branch
|
||||||
|
contains the files needed to generate the
|
||||||
|
[github-heroku-attack-rabbits documentation site](https://pages.charlesreid1.com/github-heroku-attack-rabbits).
|
||||||
|
|
||||||
|
* The [gh-pages](https://git.charlesreid1.com/charlesreid1/github-heroku-attack-rabbits/src/branch/gh-pages) branch
|
||||||
|
contains the static files generated from the documentation.
|
||||||
|
The contents of this branch compose the
|
||||||
|
[github-heroku-attack-rabbits documentation site](https://pages.charlesreid1.com/github-heroku-attack-rabbits).
|
||||||
|
|
||||||
|
Two branches illustrate github-heroku-attack-rabbits in practice:
|
||||||
|
|
||||||
|
* The [secret](https://git.charlesreid1.com/charlesreid1/github-heroku-attack-rabbits/src/branch/secret) branch contains the files needed to create the secret page.
|
||||||
|
This repository is public, so obviously these aren't *actually* secret,
|
||||||
|
but in practice this would be in a protected repository.
|
||||||
|
|
||||||
|
* The [heroku-pages](https://git.charlesreid1.com/charlesreid1/github-heroku-attack-rabbits/src/branch/heroku-pages) branch
|
||||||
|
contains the content that is actually pushed to Heroku - that is,
|
||||||
|
the final Flask app.
|
||||||
|
|
||||||
|
|
||||||
|
## Contents
|
||||||
|
|
||||||
|
An overview of the steps:
|
||||||
|
|
||||||
|
[Get Started with Heroku](heroku.md)
|
||||||
|
|
||||||
|
[Get Started with Github](github.md)
|
||||||
|
|
||||||
|
[Initialize Repository: Branches](repo.md)
|
||||||
|
|
||||||
|
[Create a Flask App using Flask-Dance](flask.md)
|
||||||
|
|
||||||
|
* [Authenticate users based on Github membership only](flask_auth_github.md)
|
||||||
|
* [Authenticate users based on organization or team membership](flask_auth_org.md)
|
||||||
|
* [Authenticate users based on some other criteria](flask_auth_other.md)
|
||||||
|
* [Protection portions of the site](flask_auth_portions.md)
|
||||||
|
|
||||||
|
[Test Flask App Locally](flask_local.md)
|
||||||
|
|
||||||
|
[Deploying Flask App to Heroku](flask_heroku.md)
|
||||||
|
|
||||||
|
[Custom Domains](custom_domains.md)
|
||||||
|
|
||||||
|
|
||||||
|
## Links
|
||||||
|
|
||||||
|
Python software used:
|
||||||
|
|
||||||
|
* [Flask](http://flask.pocoo.org/)
|
||||||
|
* [Flask-dance](https://github.com/singingwolfboy/flask-dance)
|
||||||
|
* [Flask-dance-github](https://github.com/singingwolfboy/flask-dance-github)
|
||||||
|
* [mkdocs-material (documentation theme)](https://github.com/squidfunk/mkdocs-material)
|
||||||
|
* [mkdocs (documentation)](http://www.mkdocs.org/)
|
||||||
|
|
||||||
|
Commercial services:
|
||||||
|
|
||||||
|
* [Heroku](https://heroku.com)
|
||||||
|
* [Github](https://github.com)
|
||||||
|
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
This is released under the [WTFPL](https://git.charlesreid1.com/charlesreid1/github-heroku-attack-rabbits/src/branch/docs/LICENSE).
|
||||||
|
|
110
docs/repo.md
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
# Initialize Git Repository
|
||||||
|
|
||||||
|
Let's talk through how a repository should be laid out
|
||||||
|
if we're going to be hosting a Flask app on Heroku.
|
||||||
|
|
||||||
|
## Branches
|
||||||
|
|
||||||
|
We will need a minimum of two branches. Here we specify
|
||||||
|
the names that these branches will have in your Github repo,
|
||||||
|
**which is different from the names of the branches on Heroku**:
|
||||||
|
|
||||||
|
Branches on Github:
|
||||||
|
|
||||||
|
* `heroku-pages` - this branch contains the content that Heroku will host.
|
||||||
|
Specifically, it contains the Flask application in a `.py` file,
|
||||||
|
and a few other files to help Heroku determine how to run the app
|
||||||
|
and what to install.
|
||||||
|
|
||||||
|
* `master` - this branch contains the content used to generate the documentation
|
||||||
|
and page content that is being hosted behind the Heroku attack sheep.
|
||||||
|
The documentation you are reading right now is from the master branch,
|
||||||
|
and was made with `mkdocs`.
|
||||||
|
|
||||||
|
On Heroku, we only have a single branch:
|
||||||
|
|
||||||
|
* `master` (Heroku) maps to `heroku-pages` (Github)
|
||||||
|
|
||||||
|
## Repo Layout
|
||||||
|
|
||||||
|
Let's talk about the layout of the repository.
|
||||||
|
|
||||||
|
If you wish to build the site in order to deploy it to Heroku,
|
||||||
|
you should clone the `master` branch (preparing to make the
|
||||||
|
content for your attack sheep-protected page):
|
||||||
|
|
||||||
|
```
|
||||||
|
$ git clone -b master https://git.charlesreid1.com/charlesreid1/github-heroku-attack-rabbits.git
|
||||||
|
$ cd github-heroku-attack-rabbits
|
||||||
|
```
|
||||||
|
|
||||||
|
Once you are *inside* the master branch, clone the repo again,
|
||||||
|
but this time clone the `heroku-pages` branch, and clone it
|
||||||
|
to the `site/` folder:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ git clone -b heroku-pages https://git.charlesreid1.com/charlesreid1/github-heroku-attack-rabbits.git site
|
||||||
|
$ cd site
|
||||||
|
```
|
||||||
|
|
||||||
|
Now you will want to set up the Heroku remote:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ heroku git:remote -a my-cool-project
|
||||||
|
```
|
||||||
|
|
||||||
|
The layout should now be:
|
||||||
|
|
||||||
|
```
|
||||||
|
my-cool-project-repo/ <-- my-cool-project repo pointing to master branch
|
||||||
|
|
||||||
|
docs/ \
|
||||||
|
index.md |
|
||||||
|
heroku.md | <-- mkdocs files
|
||||||
|
... | (can use any static content generator:
|
||||||
|
| pelican, sphinx, etc.)
|
||||||
|
mkdocs.yml /
|
||||||
|
|
||||||
|
site/ <-- my-cool-project repo pointing to heroku-pages branch
|
||||||
|
|
||||||
|
Procfile \
|
||||||
|
github.py | <-- heroku python app files
|
||||||
|
requirements.txt | (can also use ruby, php, js, etc.)
|
||||||
|
runtime.txt | (can also use ruby, php, js, etc.)
|
||||||
|
... /
|
||||||
|
|
||||||
|
content/ \
|
||||||
|
index.html | <-- static content hosted by Flask
|
||||||
|
sitemap.xml |
|
||||||
|
... /
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## Workflow
|
||||||
|
|
||||||
|
Once you have things set up according to the instructions and diagram above,
|
||||||
|
you're ready to run the push-to-deploy workflow and start running your secret
|
||||||
|
site on Heroku.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
29
mkdocs.yml
@ -1,22 +1,29 @@
|
|||||||
site_name: This Is The Secret Site
|
site_name: github-heroku-attack-rabbits
|
||||||
site_url: https://github-heroku-attack-rabbits.herokuapp.com
|
site_url: https://pages.charlesreid1.com/github-heroku-attack-rabbits
|
||||||
repo_name: charlesreid1/github-heroku-attack-rabbits
|
repo_name: charlesreid1/github-heroku-attack-rabbits
|
||||||
repo_url: https://git.charlesreid1.com/charlesreid1/github-heroku-attack-rabbits
|
repo_url: https://git.charlesreid1.com/charlesreid1/github-heroku-attack-rabbits
|
||||||
edit_uri: ""
|
edit_uri: ""
|
||||||
|
|
||||||
# Note: normally this whole branch/repo would be secret,
|
|
||||||
# otherwise everyone could bypass your attack rabbits
|
|
||||||
# by simply browsing your repository.
|
|
||||||
|
|
||||||
pages:
|
pages:
|
||||||
- 'Index' : 'index.md'
|
- 'Index' : 'index.md'
|
||||||
- 'Fish Slapping': 'fishslap.md'
|
- 'Get Started with Heroku': 'heroku.md'
|
||||||
- 'Silly Walks': 'sillywalk.md'
|
- 'Get Started with Github': 'github.md'
|
||||||
|
- 'Initialize Repository: Branches': 'repo.md'
|
||||||
|
- 'Create a Flask App using Flask-Dance': 'flask.md'
|
||||||
|
- 'Flask':
|
||||||
|
- 'Authenticate users based on Github membership only': 'flask_auth_github.md'
|
||||||
|
- 'Authenticate users based on organization or team membership': 'flask_auth_org.md'
|
||||||
|
- 'Authenticate users based on some other criteria': 'flask_auth_other.md'
|
||||||
|
- 'Protection portions of the site': 'flask_auth_portions.md'
|
||||||
|
- 'Test Flask App Locally': 'flask_local.md'
|
||||||
|
- 'Deploying Flask App to Heroku': 'flask_heroku.md'
|
||||||
|
- 'Custom Domains': 'custom_domains.md'
|
||||||
|
|
||||||
copyright: 'Copyright © 2018 Charles Reid, released under the <a href="https://git.charlesreid1.com/charlesreid1/github-heroku-attack-rabbits/src/branch/docs/LICENSE">WTFPL</a>.<br /><br />Many Bothans died to bring us this documentation.<br /><br />'
|
copyright: 'Copyright © 2018 Charles Reid, released under the <a href="https://git.charlesreid1.com/charlesreid1/github-heroku-attack-rabbits/src/branch/docs/LICENSE">WTFPL</a>.<br /><br />Many Bothans died to bring us this documentation.<br /><br />'
|
||||||
|
|
||||||
docs_dir: secret_docs
|
docs_dir: docs
|
||||||
site_dir: site/content
|
site_dir: site
|
||||||
|
|
||||||
theme:
|
theme:
|
||||||
|
|
||||||
@ -25,8 +32,8 @@ theme:
|
|||||||
|
|
||||||
# pretty colors! see https://squidfunk.github.io/mkdocs-material/getting-started/#primary-colors
|
# pretty colors! see https://squidfunk.github.io/mkdocs-material/getting-started/#primary-colors
|
||||||
palette:
|
palette:
|
||||||
primary: 'teal'
|
primary: 'blue grey'
|
||||||
accent: 'teal'
|
accent: 'blue grey'
|
||||||
|
|
||||||
logo: 'img/bunny.png'
|
logo: 'img/bunny.png'
|
||||||
|
|
||||||
|
@ -1,11 +0,0 @@
|
|||||||
.md-typeset h1 { font-weight: 600; }
|
|
||||||
.md-typeset h2 { font-weight: 600; }
|
|
||||||
.md-typeset h3 { font-weight: 600; }
|
|
||||||
.md-typeset h4 { font-weight: 600; }
|
|
||||||
|
|
||||||
body {
|
|
||||||
background-color: #f2f9f7;
|
|
||||||
}
|
|
||||||
div.body {
|
|
||||||
background-color: #f2f9f7;
|
|
||||||
}
|
|
@ -1,8 +0,0 @@
|
|||||||
# The Secret Society of the Fish Slapping Dance
|
|
||||||
|
|
||||||
Welcome, and congratulations on having an even number of vowels in your Github handle.
|
|
||||||
|
|
||||||
And now for something completely expected:
|
|
||||||
|
|
||||||
[Fish-Slapping Dance](https://www.youtube.com/watch?v=T8XeDvKqI4E)
|
|
||||||
|
|
Before Width: | Height: | Size: 598 KiB |
Before Width: | Height: | Size: 621 KiB |
Before Width: | Height: | Size: 34 KiB |
@ -1,16 +0,0 @@
|
|||||||
# Secret Index
|
|
||||||
|
|
||||||
This is an index of secret pages. Note that this page is not protected.
|
|
||||||
|
|
||||||
You will be asked to authenticate when you click the links below.
|
|
||||||
|
|
||||||
If your username has an **even number of vowels**, you are a member
|
|
||||||
of the [Secret Society of the Fish Slappers](fishslap.md).
|
|
||||||
Click the link to log in and proceed, or else be attacked by
|
|
||||||
the Github-Heroku attack rabbits.
|
|
||||||
|
|
||||||
If your username has an **odd number of vowels**, you are a member
|
|
||||||
of the [Secret Ministerial Department for Theoretical Silly Walk Studies](sillywalk.md).
|
|
||||||
Click the link to log in and proceed, or else be attacked by
|
|
||||||
the Github-Heroku attack rabbits.
|
|
||||||
|
|
@ -1,9 +0,0 @@
|
|||||||
# The Secret Ministry of Silly Walks
|
|
||||||
|
|
||||||
Welcome, and congratulations on having an odd number of vowels in your Github handle.
|
|
||||||
|
|
||||||
And now for something completely expected:
|
|
||||||
|
|
||||||
|
|
||||||
[Ministry of Sily Walks](https://www.youtube.com/watch?v=IqhlQfXUk7w)
|
|
||||||
|
|