Skip to content
This repository has been archived by the owner on Aug 10, 2023. It is now read-only.

Add tutorial - Securing a static website hosted on Google Cloud Storage #1407

Merged
merged 30 commits into from
Sep 24, 2020
Merged
Show file tree
Hide file tree
Changes from 24 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
6101fed
add code for securing-gcs-static-website
xiangshen-dk Aug 21, 2020
8741a75
add eslint file
xiangshen-dk Aug 21, 2020
49d3e07
update url map file
xiangshen-dk Aug 21, 2020
308f986
Merge remote-tracking branch 'upstream/master'
xiangshen-dk Aug 26, 2020
d76a46c
move credential to env vars
xiangshen-dk Aug 26, 2020
ebc434f
add index.md
xiangshen-dk Aug 26, 2020
1adabd9
update README
xiangshen-dk Aug 26, 2020
f8f8f99
update readme
xiangshen-dk Aug 26, 2020
ab70a72
rename README to index
xiangshen-dk Aug 26, 2020
40dc61f
update based on review feedback
xiangshen-dk Aug 26, 2020
6278bad
make lint happy
xiangshen-dk Aug 26, 2020
fd904a0
Merge branch 'master' into master
xiangshen-dk Sep 1, 2020
770774d
Merge branch 'master' into master
xiangshen-dk Sep 3, 2020
2f65cf1
Merge branch 'master' into master
xiangshen-dk Sep 10, 2020
64d0afb
Merge branch 'master' into master
xiangshen-dk Sep 15, 2020
f48a8b3
Merge branch 'master' into master
ToddKopriva Sep 17, 2020
2ca919c
updated image links to public bucket
ToddKopriva Sep 17, 2020
0ef9790
Merge branch 'master' into master
ToddKopriva Sep 24, 2020
b8f6692
edited frontmatter
ToddKopriva Sep 24, 2020
df31189
Google Cloud branding
ToddKopriva Sep 24, 2020
509bd0a
beginning first line-edit pass
ToddKopriva Sep 24, 2020
9c2f182
edited readme
ToddKopriva Sep 24, 2020
7bf437f
Cloud Storage branding
ToddKopriva Sep 24, 2020
4e7d9f0
line edit up through enabling APIs
ToddKopriva Sep 24, 2020
924acb4
fix enable api url
xiangshen-dk Sep 24, 2020
60deb2b
line edit through getting sample code
ToddKopriva Sep 24, 2020
c8d86d8
line edit up to configuring Cloud CDN
ToddKopriva Sep 24, 2020
fef2e25
finished first editing pass
ToddKopriva Sep 24, 2020
8c96002
Update index.md
ToddKopriva Sep 24, 2020
472584e
Merge branch 'master' into master
ToddKopriva Sep 24, 2020
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions tutorials/securing-gcs-static-website/flask_login/.gcloudignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# This file specifies files that are *not* uploaded to Google Cloud using
# gcloud. It follows the same syntax as .gitignore, with the addition of
# "#!include" directives (which insert the entries of the given .gitignore-style
# file at that point).
#
# For more information, run:
# $ gcloud topic gcloudignore
#
.gcloudignore
# If you would like to upload your .git directory, .gitignore file or files
# from your .gitignore file, remove the corresponding line
# below:
.git
.gitignore

# Python pycache:
__pycache__/
# Ignored by the build system
/setup.cfg
7 changes: 7 additions & 0 deletions tutorials/securing-gcs-static-website/flask_login/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
FROM python:3.7
COPY . /app
WORKDIR /app
RUN pip install -r requirements.txt
EXPOSE 4000
ENTRYPOINT [ "python" ]
CMD [ "main.py" ]
138 changes: 138 additions & 0 deletions tutorials/securing-gcs-static-website/flask_login/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
# Copyright 2020 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the 'License');
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an 'AS IS' BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import os
import base64
import datetime
import hashlib
import hmac
import time
from google.cloud import secretmanager
from flask import Flask, flash, redirect, render_template, request, session, abort, make_response, Blueprint

bp = Blueprint('login', __name__, static_folder='static',
template_folder='templates')

sm_client = secretmanager.SecretManagerServiceClient()

project_id = os.environ.get("PROJECT_ID")
cdn_sign_key_name = os.environ.get("CDN_SIGN_KEY")
web_url = os.environ.get("WEB_URL")


@bp.route('/')
@bp.route('/login', methods=['GET'])
def home():
if not session.get('logged_in'):
url = request.headers.get("Referer") or request.url
resp = make_response(render_template(
'login.html', redirect_url=url.replace('http:', 'https:')))
resp.set_cookie('Cloud-CDN-Cookie', '', expires=0)
return resp
else:
return 'You have already logged in. <a href="/logout">Logout</a>'


@bp.route('/login', methods=['POST'])
def do_admin_login():
session.pop('_flashes', None)
# As an example, we get the credential from the env vars.
# You can use a database or any other identity provider as a backend for authentication.
username = os.environ.get("USER_NAME")
password = os.environ.get("USER_PASSWORD")
if request.form['password'] == password and request.form['username'] == username:
session['logged_in'] = True
# Expire in a week
expire_time = int(time.time()) + 3600 * 24 * 7
cdn_sign_key_val = get_secret(cdn_sign_key_name)
cookie = sign_cookie(web_url, cdn_sign_key_name, cdn_sign_key_val,
datetime.datetime.utcfromtimestamp(expire_time))

redirect_url = request.form.get('redirect_url')
resp = make_response(redirect(redirect_url))
resp.set_cookie('Cloud-CDN-Cookie', cookie, expires=expire_time)
return resp

else:
flash('Error: Wrong credential!')
return home()


@bp.route("/logout")
def logout():
session.clear()
return home()


def sign_cookie(url_prefix, key_name, base64_key, expiration_time):
"""Gets the Signed cookie value for the specified URL prefix and configuration.

Args:
url_prefix: URL prefix to sign as a string.
key_name: name of the signing key as a string.
base64_key: signing key as a base64 encoded string.
expiration_time: expiration time as a UTC datetime object.

Returns:
Returns the Cloud-CDN-Cookie value based on the specified configuration.
"""
encoded_url_prefix = base64.urlsafe_b64encode(
url_prefix.strip().encode('utf-8')).decode('utf-8')
epoch = datetime.datetime.utcfromtimestamp(0)
expiration_timestamp = int((expiration_time - epoch).total_seconds())
decoded_key = base64.urlsafe_b64decode(base64_key)

policy_pattern = u'URLPrefix={encoded_url_prefix}:Expires={expires}:KeyName={key_name}'
policy = policy_pattern.format(
encoded_url_prefix=encoded_url_prefix,
expires=expiration_timestamp,
key_name=key_name)

digest = hmac.new(
decoded_key, policy.encode('utf-8'), hashlib.sha1).digest()
signature = base64.urlsafe_b64encode(digest).decode('utf-8')

signed_policy = u'{policy}:Signature={signature}'.format(
policy=policy, signature=signature)
# print(signed_policy)
return signed_policy


def get_secret(secret_id, version_id="latest"):
"""
Access the payload for the given secret version if one exists. The version
can be a version number as a string (e.g. "5") or an alias (e.g. "latest").
"""
# Build the resource name of the secret version.
name = sm_client.secret_version_path(project_id, secret_id, version_id)

# Access the secret version.
response = sm_client.access_secret_version(name)

payload = response.payload.data.decode('UTF-8').rstrip()
return payload


app = Flask(__name__)
app.register_blueprint(bp)


@app.errorhandler(404)
def not_found(e):
return home()


if __name__ == "__main__":
app.secret_key = os.urandom(12)
app.run(debug=True, host='0.0.0.0', port=8080)
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
click==7.1.2
Flask==1.1.2
itsdangerous==1.1.0
Jinja2==2.11.2
MarkupSafe==1.1.1
Werkzeug==1.0.1
google-cloud-secret-manager==1.0.0
82 changes: 82 additions & 0 deletions tutorials/securing-gcs-static-website/flask_login/static/style.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
* {
box-sizing: border-box;
}

*:focus {
outline: none;
}

body {
font-family: Arial;
background-color: #3498DB;
padding: 50px;
}

.login {
margin: 20px auto;
width: 300px;
}

.login-screen {
background-color: #FFF;
padding: 20px;
border-radius: 5px
}

.app-title {
text-align: center;
color: #777;
}

.login-form {
text-align: center;
}

.control-group {
margin-bottom: 10px;
}

input {
text-align: center;
background-color: #ECF0F1;
border: 2px solid transparent;
border-radius: 3px;
font-size: 16px;
font-weight: 200;
padding: 10px 0;
width: 250px;
transition: border .5s;
}

input:focus {
border: 2px solid #3498DB;
box-shadow: none;
}

.btn {
border: 2px solid transparent;
background: #3498DB;
color: #ffffff;
font-size: 16px;
line-height: 25px;
padding: 10px 0;
text-decoration: none;
text-shadow: none;
border-radius: 3px;
box-shadow: none;
transition: 0.25s;
display: block;
width: 250px;
margin: 0 auto;
}

.btn:hover {
background-color: #2980B9;
}

.login-link {
font-size: 12px;
color: #444;
display: block;
margin-top: 12px;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<link rel="stylesheet" href="/static/style.css" type="text/css">
<block body>
<form action="/login" method="POST">
<div class="login">
<div class="login-screen">
<div class="app-title">
<h1>Login</h1>
</div>

<div class="login-form">
<div class="control-group">
<input type="text" class="login-field" value="" placeholder="username" name="username">
<label class="login-field-icon fui-user" for="login-name"></label></div>
<div class="control-group">
<input type="password" class="login-field" value="" placeholder="password" name="password">
<label class="login-field-icon fui-lock" for="login-pass"></label></div>
{% for message in get_flashed_messages() %}
<p>{{ message }}</p>
{% endfor %}
<input type="submit" value="Log in" class="btn btn-primary btn-large btn-block">
<input type="hidden" value="{{ redirect_url }}" id="redirect_url" name="redirect_url">
</div>
</div>
</div>
</form>
<endblock>
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
defaultService: https://www.googleapis.com/compute/v1/projects/<PROJECT_ID>/global/backendBuckets/web-backend-bucket
kind: compute#urlMap
name: web-map-http
hostRules:
- hosts:
- '<DNS_NAME>'
pathMatcher: matcher1
pathMatchers:
- defaultService: https://www.googleapis.com/compute/v1/projects/<PROJECT_ID>/global/backendBuckets/web-backend-bucket
name: matcher1
routeRules:
- matchRules:
- prefixMatch: /
headerMatches:
- headerName: cookie
prefixMatch: 'Cloud-CDN-Cookie'
priority: 0
service: https://www.googleapis.com/compute/v1/projects/<PROJECT_ID>/global/backendBuckets/web-backend-bucket
- matchRules:
- prefixMatch: /
priority: 1
service: https://www.googleapis.com/compute/v1/projects/<PROJECT_ID>/global/backendServices/<LOGIN_BACKEND_SVC_NAME>
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading