├── .gitignore ├── Dockerfile ├── LICENSE ├── README.md ├── docker-compose.yml ├── manage.py ├── project ├── __init__.py ├── api │ ├── __init__.py │ ├── main.py │ ├── models.py │ └── users.py └── config.py ├── requirements.txt └── run.sh /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | __pycache__ 3 | .vscode 4 | .env 5 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | ########### 2 | # BUILDER # 3 | ########### 4 | 5 | # Base Image 6 | FROM python:3.10 as builder 7 | 8 | # Install Requirements 9 | COPY requirements.txt / 10 | RUN pip wheel --no-cache-dir --no-deps --wheel-dir /wheels -r requirements.txt 11 | 12 | 13 | ######### 14 | # FINAL # 15 | ######### 16 | 17 | # Base Image 18 | FROM python:3.10-slim 19 | 20 | # Install curl 21 | RUN apt-get update && apt-get install -y curl 22 | 23 | # Create directory for the app user 24 | RUN mkdir -p /home/app 25 | 26 | # Create the app user 27 | RUN groupadd app && useradd -g app app 28 | 29 | # Create the home directory 30 | ENV HOME=/home/app 31 | ENV APP_HOME=/home/app/web 32 | RUN mkdir $APP_HOME 33 | WORKDIR $APP_HOME 34 | 35 | # Install Requirements 36 | COPY --from=builder /wheels /wheels 37 | COPY --from=builder requirements.txt . 38 | RUN pip install --no-cache /wheels/* 39 | 40 | # Copy in the Flask code 41 | COPY . $APP_HOME 42 | 43 | # Chown all the files to the app user 44 | RUN chown -R app:app $APP_HOME 45 | 46 | # Change to the app user 47 | USER app 48 | 49 | # run server 50 | CMD gunicorn --log-level=debug -b 0.0.0.0:5000 manage:app 51 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 TestDriven Labs 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Dynamic Secret Generation with Vault and Flask 2 | 3 | Check out the [post](https://testdriven.io/dynamic-secret-generation-with-vault-and-flask) to learn how to build and use this project. 4 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.8' 2 | 3 | services: 4 | 5 | web: 6 | build: 7 | context: . 8 | dockerfile: Dockerfile 9 | ports: 10 | - 5000:5000 11 | environment: 12 | - FLASK_ENV=production 13 | - APP_SETTINGS=project.config.ProductionConfig 14 | env_file: 15 | - .env 16 | -------------------------------------------------------------------------------- /manage.py: -------------------------------------------------------------------------------- 1 | from flask.cli import FlaskGroup 2 | 3 | from project import create_app, db 4 | from project.api.models import User 5 | 6 | app = create_app() 7 | cli = FlaskGroup(create_app=create_app) 8 | 9 | 10 | @cli.command() 11 | def recreate_db(): 12 | db.drop_all() 13 | db.create_all() 14 | db.session.commit() 15 | 16 | 17 | @cli.command() 18 | def seed_db(): 19 | """Seeds the database.""" 20 | db.session.add(User( 21 | username='michael', 22 | email='michael@notreal.com', 23 | )) 24 | db.session.commit() 25 | 26 | 27 | if __name__ == '__main__': 28 | cli() 29 | -------------------------------------------------------------------------------- /project/__init__.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from flask import Flask 4 | from flask_sqlalchemy import SQLAlchemy 5 | from flask_migrate import Migrate 6 | 7 | 8 | # instantiate the extensions 9 | db = SQLAlchemy() 10 | migrate = Migrate() 11 | 12 | 13 | def create_app(script_info=None): 14 | 15 | # instantiate the app 16 | app = Flask(__name__) 17 | 18 | # set config 19 | app_settings = os.getenv('APP_SETTINGS') 20 | app.config.from_object(app_settings) 21 | 22 | # set up extensions 23 | db.init_app(app) 24 | migrate.init_app(app, db) 25 | 26 | # register blueprints 27 | from project.api.main import main_blueprint 28 | app.register_blueprint(main_blueprint) 29 | from project.api.users import users_blueprint 30 | app.register_blueprint(users_blueprint) 31 | 32 | # shell context for flask cli 33 | app.shell_context_processor({'app': app, 'db': db}) 34 | return app 35 | -------------------------------------------------------------------------------- /project/api/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/testdrivenio/vault-consul-flask/c0ff71781cd4726ef16158e99d3d2f8c846e4149/project/api/__init__.py -------------------------------------------------------------------------------- /project/api/main.py: -------------------------------------------------------------------------------- 1 | from flask import Blueprint, jsonify 2 | 3 | 4 | main_blueprint = Blueprint('main', __name__) 5 | 6 | 7 | @main_blueprint.route('/ping', methods=['GET']) 8 | def ping_pong(): 9 | return jsonify({ 10 | 'status': 'success', 11 | 'message': 'pong!' 12 | }) 13 | -------------------------------------------------------------------------------- /project/api/models.py: -------------------------------------------------------------------------------- 1 | from project import db 2 | 3 | 4 | class User(db.Model): 5 | __tablename__ = 'users' 6 | 7 | id = db.Column(db.Integer, primary_key=True, autoincrement=True) 8 | username = db.Column(db.String(128), unique=True, nullable=False) 9 | email = db.Column(db.String(128), unique=True, nullable=False) 10 | active = db.Column(db.Boolean, default=True, nullable=False) 11 | admin = db.Column(db.Boolean, default=False, nullable=False) 12 | 13 | def __init__(self, username, email): 14 | self.username = username 15 | self.email = email 16 | 17 | def to_json(self): 18 | return { 19 | 'id': self.id, 20 | 'username': self.username, 21 | 'email': self.email, 22 | 'active': self.active, 23 | 'admin': self.admin 24 | } 25 | -------------------------------------------------------------------------------- /project/api/users.py: -------------------------------------------------------------------------------- 1 | from flask import Blueprint, jsonify 2 | 3 | from project.api.models import User 4 | 5 | 6 | users_blueprint = Blueprint('users', __name__) 7 | 8 | 9 | @users_blueprint.route('/users', methods=['GET']) 10 | def get_all_users(): 11 | response_object = { 12 | 'status': 'success', 13 | 'users': [user.to_json() for user in User.query.all()] 14 | } 15 | return jsonify(response_object), 200 16 | -------------------------------------------------------------------------------- /project/config.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | USER = os.environ.get('DB_USER') 4 | PASSWORD = os.environ.get('DB_PASSWORD') 5 | SERVER = os.environ.get('DB_SERVER') 6 | 7 | class ProductionConfig(): 8 | """Production configuration""" 9 | SQLALCHEMY_TRACK_MODIFICATIONS = False 10 | SQLALCHEMY_DATABASE_URI = f'postgresql://{USER}:{PASSWORD}@{SERVER}:5432/users_db' 11 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | Flask==2.0.2 2 | flask-migrate==3.1.0 3 | Flask-SQLAlchemy==2.5.1 4 | gunicorn==20.1.0 5 | psycopg2-binary==2.9.2 6 | -------------------------------------------------------------------------------- /run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | rm -f .env 4 | 5 | echo DB_SERVER= >> .env 6 | 7 | user=$(curl -H "X-Vault-Token: $VAULT_TOKEN" \ 8 | -X GET http://:8200/v1/database/creds/mynewrole) 9 | echo DB_USER=$(echo $user | jq -r .data.username) >> .env 10 | echo DB_PASSWORD=$(echo $user | jq -r .data.password) >> .env 11 | 12 | docker-compose up -d --build 13 | --------------------------------------------------------------------------------