0

I want to make the setup of my Flask application as easy as possible by providing a custom CLI command for automatically generating SECRET_KEY and saving it to .env. However, it seems that invoking a command, creates an instance of the Flask app, which then needs the not-yet-existing configuration.

Flask documentation says that it's possible to run commands without application context, but it seems like the app is still created, even if the command doesn't have access to it. How can I prevent the web server from running without also preventing the CLI command?

Here's almost-working code. Note, that it requires the python-dotenv package, and it runs with flask run instead of python3 app.py.

import os
import secrets
from flask import Flask, session

app = Flask(__name__)

@app.cli.command(with_appcontext=False)
def create_dotenv():
    cwd = os.getcwd()
    filename = '.env'
    env_path = os.path.join(cwd, filename)

    if os.path.isfile(env_path):
        print('{} exists, doing nothing.'.format(env_path))
        return

    with open(env_path, 'w') as f:
        secret_key = secrets.token_urlsafe()
        f.write('SECRET_KEY={}\n'.format(secret_key))

@app.route('/')
def index():
    counter = session.get('counter', 0)
    counter += 1
    session['counter'] = counter
    return 'You have visited this site {} time(s).'.format(counter)

# putting these under if __name__ == '__main__' doesn't work
# because then they are not executed during `flask run`.

secret_key = os.environ.get('SECRET_KEY')

if not secret_key:
    raise RuntimeError('No secret key')

app.config['SECRET_KEY'] = secret_key

Some alternative options I considered:

  • I could allow running the app without a secret key, so the command works fine, but I don't want to make it possible to accidentally run the actual web server without a secret key. That would result in an error 500 when someone tries to use routes that have cookies and it might not be obvious at the time of starting the server.

  • The app could check the configuration just before the first request comes in as suggested here, but this approach would not be much better than the previous option for the ease of setup.

  • Flask-Script is also suggested, but Flask-Script itself says it's no longer mainained and points to Flask's built-in CLI tool.

  • I could use a short delay before killing the application if the configuration is missing, so the CLI command would be able to run, but a missing secret key would be easy to notice when trying to run the server. This would be quite a cursed approach though and who knows maybe even be illegal.

Am I missing something? Is this a bug in Flask or should I do something completely different for automating secret key generation? Is this abusing the Flask CLI system's philosophy? Is it bad practice to generate environment files in the first place?

Stephen Rauch
  • 47,830
  • 31
  • 106
  • 135
felixbade
  • 1,009
  • 3
  • 10
  • 18

3 Answers3

1

As a workaround, you can use a separate Python/shell script file for generating SECRET_KEY and the rest of .env.

That will likely be the only script that needs to be able to run without the configuration, so your repository shouldn't get too cluttered from doing so. Just mention the script in README and it probably doesn't result in a noticeably different setup experience either.

felixbade
  • 1,009
  • 3
  • 10
  • 18
1

I think you are making it more complicated than it should be. Consider the following code:

import os

app.config['SECRET_KEY'] = os.urandom(24)

It should be sufficient. (But I prefer to use config files for Flask).

AFAIK the secret key does not have to be permanent, it should simply remain stable for the lifetime of Flask because it will be used for session cookies and maybe some internal stuff but nothing critical to the best of my knowledge.

If your app were interrupted users would lose their session but no big deal.

It depends on your current deployment practices, but if you were using Ansible for example you could automate the creation of the config file and/or the environment variables and also make some sanity checks before starting the service.

The problem with your approach is that as I understand it is that you must give the web server privileges to write to the application directory, which is not optimal from a security POV. The web user should not have the right to modify application files.

So I think it makes sense to take care of deployment separately, either using bash scripts or Ansible, and you can also tighten permissions and automate other stuff.

Kate
  • 1,809
  • 1
  • 8
  • 7
0

I agree with precedent answers, anyway you could write something like

secret_key = os.environ.get('SECRET_KEY') or secrets.token_urlsafe(32)

so that you can still use your configured SECRET_KEY variable from environment. If, for any reason, python doesn't find the variable it will be generated by the part after the 'or'.

CyberMat97
  • 11
  • 3