Flask is an amazingly lightweight framework, and in my opinion it's a great option for writing simple applications in Python.
An application in my company performs some tasks and then updates a MySql database accordingly. So to know how the application is progressing with the day's tasks, one has to use SSH (Secure Shell protocol) to get into the application server and run the appropriate queries using MySQL client. This is because the application only starts creating reports once all tasks are finished.
My objective was to figure out a way for someone without system administration knowledge to access this data securely without additional assistance.
My plan was to create a flask application that would be accessible from any web browser. In this application, when the correct path is accessed, the software will run some predefined shell command and return the results to the browser. Now this output might be a sensitive information, so we just can't allow anyone to be able to access this. To implement a basic layer of security I have used IP whitelisting, or only allowing certain IPs to acccess the application.
I ran the below commands on Ubuntu 14.04 Trusty, if you are using a different OS then your commands may vary accordingly.
To go along with this tutorial you must have the following installed in your system.
Use the following commands to prepare your virtual environment (virtualenv) -
1# go to your workspace directory
2cd ~/workspace/
3# create a virtualenv using python3
4virtualenv -p /usr/bin/python3 flaskshell
5# enter the virtualenv directory and perform the basic package installations and tasks
6cd flaskshell
7# activate virtualenv
8source bin/activate
9# install flask
10pip install flask
11# create src and logs directory
12mkdir src logs
Since we will soon run a MySQL query, we need to prepare a sample database for our task.
1# create the database
2mysql -u root -p -e "CREATE DATABASE flasktest; GRANT ALL ON flasktest.* TO flaskuser@localhost IDENTIFIED BY 'flask123'; FLUSH PRIVILEGES"
3# create a sample table
4mysql -uflaskuser -pflask123 -e "CREATE TABLE flasktest.tasks (task_id INT NOT NULL AUTO_INCREMENT, task_title VARCHAR(50), task_status VARCHAR(50), PRIMARY KEY (task_id));"
5# insert some sample data
6mysql -uflaskuser -pflask123 -e "INSERT INTO flasktest.tasks (task_title, task_status) VALUES ('Task 1', 'Success');"
7mysql -uflaskuser -pflask123 -e "INSERT INTO flasktest.tasks (task_title, task_status) VALUES ('Task 2', 'Pending');"
8mysql -uflaskuser -pflask123 -e "INSERT INTO flasktest.tasks (task_title, task_status) VALUES ('Task 3', 'Failed');"
Let's check whether the database and table creations went according to plan.
1mysql -uflaskuser -pflask123 -e "USE flasktest; SELECT COUNT(*) FROM tasks WHERE task_status='Success';"
2
3# the above command should print this,
4
5+----------+
6| COUNT(*) |
7+----------+
8| 1 |
9+----------+
You may run the commands with statements for 'Pending' and 'Failed' as well just to double-check. All the queries should return the same result.
Create a file called app.py inside the src directory,
1touch ~/workspace/flaskshell/src/app.py
Inside the file put in the code listed below.
1from flask import Flask
2from flask import request
3import subprocess
4
5
6app = Flask('flaskshell')
7ip_whitelist = ['192.168.1.2', '192.168.1.3']
8query_success = "SELECT COUNT(*) FROM flasktest.tasks WHERE task_status='Success'"
9query_pending = "SELECT COUNT(*) FROM flasktest.tasks WHERE task_status='Pending'"
10query_failed = "SELECT COUNT(*) FROM flasktest.tasks WHERE task_status='Failed'"
11
12
13def valid_ip():
14 client = request.remote_addr
15 if client in ip_whitelist:
16 return True
17 else:
18 return False
19
20
21@app.route('/status/')
22def get_status():
23 if valid_ip():
24 command_success = "mysql -uflaskuser -pflask123 -e '{0}'".format(
25 query_success)
26 command_pending = "mysql -uflaskuser -pflask123 -e '{0}'".format(
27 query_pending)
28 command_failed = "mysql -uflaskuser -pflask123 -e '{0}'".format(
29 query_failed)
30
31 try:
32 result_success = subprocess.check_output(
33 [command_success], shell=True)
34 result_pending = subprocess.check_output(
35 [command_pending], shell=True)
36 result_failed = subprocess.check_output(
37 [command_failed], shell=True)
38 except subprocess.CalledProcessError as e:
39 return "An error occurred while trying to fetch task status updates."
40
41 return 'Success %s, Pending %s, Failed %s' % (result_success, result_pending, result_failed)
42 else:
43 return """<title>404 Not Found</title>
44 <h1>Not Found</h1>
45 <p>The requested URL was not found on the server.
46 If you entered the URL manually please check your
47 spelling and try again.</p>""", 404
48
49
50if __name__ == '__main__':
51 app.run()
Line 7 >> This is the array for the whitelisted IPs. You should replace the IPs as needed. You may put in virtually as many ips as you want in this array
Lines 8-10 >> I'm defining the queries here. You may change the query to suit your needs.
Lines 13-18 >> The valid_ip() method returns true if the client's IP belongs in the white list, otherwise it returns false. It gets the client's IP using the request package from Flask. This request package is defined on line 2
Line 21 >> Defines the route for accessing the application
Line 23 >> Before processing the request check if the client's IP belongs to the white list. If it does not, show Flask's default 404 page (lines 43-47)
Lines 24-29 >> Compose the shell commands using the queries defined earlier.
Lines 32-37 >> Try running the shell commands. The application will either throw an error or, if execution is successful, it will return the results (line 41)
To run the application as a service I used Supervisor. This is a matter of personal preference; feel free to use any other process control system.
Define a program on Supervisor.
Create a new Supervisor config file.
1sudo vim /etc/supervisor/conf.d/flaskshell.conf
Copy and paste the following code into the file. At this point, you must put the app in - /home/user/workspace/flaskshell.
1[program:stats]
2directory = /home/user/workspace/flaskshell/src
3command = /home/user/workspace/flaskshell/bin/python app.py
4redirect_stderr = true
5stdout_logfile = /home/user/workspace/flaskshell/logs/out.log
6stderr_logfile = /home/user/workspace/flaskshell/logs/error.log
Now you should update the Supervisor config and start the application.
1sudo supervisorctl update stats
2sudo supervisorctl start stats
Since we have not defined any port for the application, it will default to port 5000. To change this, follow the instructions I found on this Stack Overflow page.
You can find the application at http://SERVER_IP:5000. And the status updates should be available at http://SERVER_IP:5000/status/.
I hope my post was enjoyable and of help to you. Please feel free to leave any comments below with thoughts and feedback.