Correctly configure Python webserver service in Linux - python

I have a web user that can read my cheroot + Flask webserver files and can bind to ports 80 and 443 (through authbind). The systemd service is configured to run python through authbind as the web user. Problem is, cheroot takes both the SSL certificate and and key file paths to load them, and the only way I can think to make this work is to make them both readable by the web user. This, and the fact that the web user can bind to the two ports, rings alarms bells in me.
From what I understand after some research, the appropriate way to handle this would be to have another user (let's say web-starter for example) that can bind to the ports and read my private key file start the webserver and then "hand over" control to the web user.
How, and at what point do I do this? Can I do this in Python?
Example code:
webserver.service
...
User=web
Environment="FLASK_ENV=production"
ExecStart=/usr/bin/authbind --deep /usr/bin/python3 [PATH_TO_WEBAPP]/main.py
...
main.py
#! /usr/bin/python3
from cheroot.wsgi import Server as WSGIServer
from cheroot.ssl.builtin import BuiltinSSLAdapter as SSLAdapter
from ssl import TLSVersion
from myflaskapp import create_app
from os import environ
# if environment is 'development', use port 5000 and no ssl
DEVEL = environ.get('FLASK_ENV') == 'development'
if __name__ == '__main__':
port = 5000 if DEVEL else 443
server = WSGIServer(('0.0.0.0', port), create_app())
if not DEVEL:
server.ssl_adapter = SSLAdapter('ssl.crt', 'ssl.key')
server.ssl_adapter.context.minimum_version = TLSVersion.TLSv1_2
try:
server.start()
except KeyboardInterrupt:
server.stop()
PS: Also, is the use of environmental variables safe here? In other words, can a compromised web user affect them, or will systemd manage them?
ssl.key
jk ;)
Also, is there a way to protect writable files, such as SQLite databases?

Related

Accessing device on local network through server hosted webhook

I have a python script that acts as a webhook. A part of it is as follows:
import json
import os
import urllib
import socket
import _thread
from flask import Flask
from flask import request
from flask import make_response
app=Flask(__name__)
ip = ('192.168.1.75', 9050)
#app.route('/webhook',methods=['GET','POST'])
def webhook():
_thread.start_new_thread(sendDataToDevice,(ip))
req = request.get_json(silent=True,force=True)
print("Request:")
print(json.dumps(req,indent=4))
res=makeWebHookResult(req)
res=json.dumps(res,indent=4)
r=make_response(res)
r.headers['Content-Type']='application/json'
return r
if __name__ == '__main__':
app.run(port=8080,host='localhost')
The function of the script is to send some data to a device connected to the local network.
It works flawlessly when I open my web browser and type the following on the url bar:
http://localhost:8080/webhook
I want to host the script on a server, eg. Heroku. How can I access the local device in that case?
Note: I know I can run the script on my local machine and make it visible to the internet using ngrok, but I want to keep it accessible even when my computer is switched off. Also, want a fixed link, and the links given by ngrok change on every run.
I've faced a similar issue before with IoT. Unfortunately there is no simple way to make a device be visible online. Here's a simple solution I've used. It might not be the best, but it works.
DDNS + Port Forwarding + Static IP
If you have access to your local WiFi router, then you can setup something called as DDNS (Dynamic Domain Name System). Your router will then connect to a DDNS service provider like no-ip (www.noip.com) and it will be visible on the internet. You can give a custom URL like susmit-home.noip.com.
However susmit-home.noip.com will now point only to your WiFi router and not your WiFi network. So if you want to access the local device_ip and device_port such as "192.168.1.75", 9050. Then you can setup Port Forwarding on your router for that local IP-Port combination. Usually the setup looks like this:
Local IP: device_ip (e.g. 192.168.1.75)
Local Port: device_port (e.g. 9050)
Outbound Port: any_port (e.g. 9050)
Make sure that your device_ip is a static IP on your WiFi router so that it doesn't change.
Finally in your code you can just replace the line ip = ('192.168.1.75', 9050) with ip = ('susmit-home.noip.com', 9050).
Other solutions:
A slightly more complicated solution is setting up a VPN, such that your local network and your remote server (e.g. Heroku) will all be available to each other as if they were within the same local network.
If your device is a computer or a Raspberry Pi, then you can use SSH Remote Port Forwarding to have access to your local device from the remote server.

python flask...My computer couldn't display

This is code from flask homepages.
from flask import Flask
app = Flask(__name__)
#app.route('/')
def hello_world():
return 'Hello World!'
if __name__ == '__main__':
app.run(host='0.0.0.0')
app.run(debug=True)
when I run the code then enter http://0.0.0.0:5000/
My computer couldn't display...
what is the problem?
1) That isn't the code on the flask homepage.
2) Calling run() twice won't work. If the first run() will finish executing and get to the second run(), wouldn't the second run() finish executing and get to the end of the program and terminate?
app.run() is defined like this:
run(host=None, port=None, debug=None, **options)
Changed in version 0.10: The default port is now picked from the SERVER_NAME variable.
Parameters:
host – the hostname to listen on. Defaults to '127.0.0.1'.
Set this to '0.0.0.0' to have the server available externally as well.
port – the port of the webserver. Defaults to 5000 or the port defined in the SERVER_NAME config variable if present.
debug – if given, enable or disable debug mode. See debug.
options – the options to be forwarded to the underlying Werkzeug server. See werkzeug.serving.run_simple() for more information.
You can set all those parameters in one app.run() call.
3) From the flask docs:
If you run the server [with app.run()] you will notice that the
server is only accessible from your own computer, not from any other
in the network. This is the default because in debugging mode a user
of the application can execute arbitrary Python code on your computer.
If you have debug disabled or trust the users on your network, you can
make the server publicly available simply by changing the call of the
run() method to look like this:
app.run(host='0.0.0.0')
This tells your operating system to listen on all public IPs.
If you try setting the host to 0.0.0.0, then start your server, flask will display the message:
Running on http://0.0.0.0:5000/ (Press CTRL+C to quit)
However, that does not mean that 0.0.0.0 is a host name(= an IP address). 0.0.0.0 means that the server is listening for any public host name(= an IP address) on port 5000. The url http://localhost:5000 will also work.
The bottom line: Don't use the host 0.0.0.0 unless you know what you are doing.
I was facing the same problem and i found out that I had to save any code i had written in all my files then after i run then main.py file the web server showed up. So it might be the same situation, just hit cntr-s and save all code in the different .py files then run it

How to get CherryPy to listen only on a specific host

I have a flask app that I want to deploy using CherryPy's built in server. I chose CherryPy so that the app can be deployed without having to reverse proxy (ie. nginx in front).
I'm having trouble getting CherryPy to listen for requests on just a single hostname.
Say I'm serving 2 sites: test1.com and test2.com (and have them set in my hosts file to point back to localhost).
My /etc/hosts file:
127.0.0.1 test1.com test2.com
CherryPy is serving test1.com, test2.com doesn't have anything serving it.
My cherrypy file is as follows:
import cherrypy
from my_test_flask_app import app
if __name__ == '__main__':
cherrypy.tree.graft(app, "/")
cherrypy.server.unsubscribe()
server = cherrypy._cpserver.Server()
server.socket_host = "test1.com"
server.socket_port = 8030
server.thread_pool = 30
server.subscribe()
cherrypy.engine.start()
cherrypy.engine.block()
Set up this way, I go to test1.com:8030 on my browser and it works as expected.
But when I go to test2.com:8030, the same app is served. I expected it not to serve anything, since CherryPy isn't set up to listen for test2.com.
To me, it seems that CherryPy is just listening for everything on the given port (8030), and treating the socket_host part as if its 0.0.0.0
Am I missing something here? I've looked through lots of docs and tutorials, but all things suggest that this code snippet should be working as I expected.
Thanks
Here's how you can setup what you want...
root = Root()
RootApp = cherrypy.Application(root)
Domain2App = cherrypy.Application(root)
SecureApp = cherrypy.Application(Secure())
vhost = cherrypy._cpwsgi.VirtualHost(RootApp,
domains={'www.domain2.example': Domain2App,
'www.domain2.example:443': SecureApp,
})
cherrypy.tree.graft(vhost)
https://cherrypy.readthedocs.org/en/3.3.0/refman/_cpwsgi.html#classes
Hope this helps!
You misunderstand the socket listen address - they are IP addresses only, not on DNS names. Set this way, CherryPy listens to the localhost (127.0.0.1) only - try using your Ethernet/Wlan local address and you should get connection refused.
Also, you can wrap your application with a WSGI middleware that checks the Host header for the proper domain, or use CherryPy virtual host facility to check the host header.

How to very SIMPLY deploy a Twisted Server on OPenshift

I've got the right environment setup, python 2.7.5, Twisted installed and imports work in the Python Shell.
I have a very simple Server instance to display a landing page that Works on local machine fine.
from twisted.web import http
class MyRequestHandler(http.Request):
pages={
'/': '<h1>Geo-Address Server</h1>Twisted Server is Up and Running..',
'/test': '<h1>Test</h1>Test page',
}
def process(self):
print self.path
if self.pages.has_key(self.path):
self.write(self.pages[self.path])
else:
self.setResponseCode(http.NOT_FOUND)
self.write("<h1>Not Found</h1>Sorry, page does not exist")
self.finish()
class MyHttp(http.HTTPChannel):
requestFactory=MyRequestHandler
class MyHttpFactory(http.HTTPFactory):
protocol=MyHttp
if __name__=='__main__':
from twisted.internet import reactor
reactor.listenTCP(8080, MyHttpFactory())
reactor.run()
However, deploying this on the Openshift Server fails to run. If I try to run the script
python script.py &
I get:
reactor.listenTCP(8080, MyHttpFactory()) File
"/var/lib/openshift/5378ea844382ec89da000432/python/virtenv/lib/python2.7/site-packages/twisted/internet/posixbase.py",
line 495, in listenTCP
p.startListening() File "/var/lib/openshift/5378ea844382ec89da000432/python/virtenv/lib/python2.7/site-packages/twisted/internet/tcp.py",
line 979, in startListening
raise CannotListenError(self.interface, self.port, le) twisted.internet.error.CannotListenError: Couldn't listen on any:8080:
[Errno 13] Permission denied.
Reading through SO, most people just say to bind to port 8080(which I have done), but still I get the same error.
As the kb says
Please note: We don't allow arbitrary binding of ports on the
externally accessible IP address.
It is possible to bind to the internal IP with port range: 15000 -
35530. All other ports are reserved for specific processes to avoid conflicts. Since we're binding to the internal IP, you will need to
use port forwarding to access it:
https://openshift.redhat.com/community/blogs/getting-started-with-port-forwarding-on-openshift
Therefor, just find out the $OPENSHIFT_PYTHON_IP
echo $OPENSHIFT_PYTHON_IP
Then add that address to the reactor listener's interface
reactor.listenTCP(15000, MyHttpFactory(), interface='127.X.X.X')
Alternative(and the best) way to do it is by picking the value in the code. That way, if the IP changes dynamically, you still get the current IP
import os
local_hostname =os.getenv("OPENSHIFT_INTERNAL_IP")
..
..
reactor.listenTCP(15000, MyHttpFactory(), interface=local_hostname)
Note: You can only bind to the port range (15000-35530)

Python3 Http Web Server: virtual hosts

I am writing an rather simple http web server in python3. The web server needs to be simple - only basic reading from config files, etc. I am using only standard libraries and for now it works rather ok.
There is only one requirement for this project, which I can't implement on my own - virtual hosts. I need to have at least two virtual hosts, defined in config files. The problem is, that I can't find a way how can I implement them in python. Does anyone have any guides, articles, maybe some simple implementation how can this be done?
I would be grateful for any help.
Virtual hosts work by obeying the Host: header in the HTTP request.
Just read the headers of the request, and take action based on the value of the Host: header
For a simple HTTP web server, you can start with the WSGI reference implementation:
wsgiref is a reference implementation of the WSGI specification that can be used to add WSGI support to a web server or framework. It provides utilities for manipulating WSGI environment variables and response headers, base classes for implementing WSGI servers, a demo HTTP server that serves WSGI applications,...
Modifying the example server to check the HTTP_HOST header, here is a simple app that responds, depending on the virtual host, with a different text. (Extending the example to use a configuration file is left as an exercise).
import wsgiref
from wsgiref.simple_server import make_server
def my_app(environ,start_response):
from io import StringIO
stdout = StringIO()
host = environ["HTTP_HOST"].split(":")[0]
if host == "127.0.0.1":
print("This is virtual host 1", file=stdout)
elif host == "localhost":
print("This is virtual host 2", file=stdout)
else:
print("Unknown virtual host", file=stdout)
print("Hello world!", file=stdout)
print(file=stdout)
start_response(b"200 OK", [(b'Content-Type',b'text/plain; charset=utf-8')])
return [stdout.getvalue().encode("utf-8")]
def test1():
httpd = make_server('', 8000, my_app)
print("Serving HTTP on port 8000...")
# Respond to requests until process is killed
httpd.serve_forever()

Categories