how to add authentication to a wsgiref python web service - python

I am working on a sample code given in the python documentation, the code is:
from wsgiref.simple_server import make_server, demo_app
httpd = make_server('', 8000, demo_app)
print "Serving HTTP on port 8000..."
# Respond to requests until process is killed
httpd.serve_forever()
# Alternative: serve one request, then exit
httpd.handle_request()
I can access this through the localhost on port 8000, but now if I want to pass username/password with the "localhost:8000 username, password" how do I do this. I have figured out how I would get to know if the authentication was unsuccessful but not how to actually receive the username/password for checking..
Any hints, and tips.....
Cheers,

If you pass username/password in the query string like http://localhost:8000?username=x&password=y, you can retrieve them in your WSGI handler function from the environ dict: environ['QUERY_STRING']. You can use urlparse.parse_qs from the standard library to parse it. If this is code that's going into production, I second Joran, you should use at least HTTP Basic Authentication and some authentication middleware like barrel.

Related

How to tell when a request is coming from a local https reverse proxy as opposed to directly to the http port

I have a REST API flask, configured to listed on http at port 7001
I have also setup an apache as a reverse proxy to supply https
My flask is also using the ProxyFix to be able to correctly detect the incoming IP address in order to use flask.limiter
REST_API = Flask(__name__)
REST_API.wsgi_app = ProxyFix(REST_API.wsgi_app, x_for=1)
I want to reject all requests which are not coming from my reverse proxy, but rather directly to my 7001 port. Without the ProxyFix, I could do this method
#REST_API.before_request
def limit_remote_addr():
logger.debug(request.remote_addr)
if request.remote_addr != '127.0.0.1':
error_msg = get_error(ServerErrors.NO_PROXY)
abort(403, error_msg)
But now with the ProxyFix, I am always seeing the original IP.
If there a way to detect if the requestor is coming via my reverse proxy in this setup?

Why cannot I receive any POST request on my Telegram bot written with Flask (Python)?

I don't want to use getUpdates method to retrieve updates from Telegram, but a webhook instead.
Error from getWebhookInfo is:
has_custom_certificate: false,
pending_update_count: 20,
last_error_date: 1591888018,
last_error_message: "SSL error {error:1416F086:SSL routines:tls_process_server_certificate:certificate verify failed}"
My code is:
from flask import Flask
from flask import request
from flask import Response
app = Flask(__name__)
#app.route('/', methods=['POST', 'GET'])
def bot():
if request.method == 'POST':
return Response('Ok', status=200)
else:
return f'--- GET request ----'
if __name__ == "__main__":
app.run(host='0.0.0.0', port='8443', debug=True, ssl_context=('./contract.crt', '.private.key'))
When I hit https://www.mydomain.ext:8443/ I can see GET requests coming but not POST ones when I write something on my telegram-bot chat
Also that's how I set a webhook for telegram as follow:
https://api.telegram.org/botNUMBER:TELEGRAM_KEY/setWebhook?url=https://www.mydomain.ext:8443
result:
{
ok: true,
result: true,
description: "Webhook was set"
}
Any suggestion or something wrong I've done?
https://core.telegram.org/bots/api#setwebhook
I'm wondering if the problem it's caused because I'm using 0.0.0.0, the reason it's that if I use 127.0.0.0 the url/www.mydomain.ext cannot be reached
Update
ca_certitificate = {'certificate': open('./folder/ca.ca-bundle', 'rb')}
r = requests.post(url, files=ca_certitificate)
print(r.text)
that print gives me:
{
"ok": false,
"error_code": 400,
"description": "Bad Request: bad webhook: Failed to set custom certificate file"
}
I deployed a Telegram chatbot without Flask a while ago.
I remember that the POST and GET requests required /getUpdates and /sendMessage added to the bot url. Maybe it will help.
Telegram bots only works with full chained certificates. And the error in your getWebHookInfo:
"last_error_message":"SSL error {337047686, error:1416F086:SSL routines:tls_process_server_certificate:certificate verify failed}"
Is Telegram saying that it needs the whole certificate chain (it's also called CA Bundle or full chained certificate). as answered on the question.
If you validate your certificate using the SSLlabs you will see that your domain have chain issues:
https://www.ssllabs.com/ssltest/analyze.html?d=www.vallotta-party-bot.com&hideResults=on
To solve this need you need to set the CA Certificate. In this way, you need to find the CA certificate file with your CA provider.
Also, the best option in production sites is to use gunicorn instead of Flask.
If you are using gunicorn, you can do this with command line arguments:
$ gunicorn --certfile cert.pem --keyfile key.pem --ca_certs cert.ca-bundle -b 0.0.0.0:443 hello:app
Or create a gunicorn.py with the following content:
import multiprocessing
bind = "0.0.0.0:443"
workers = multiprocessing.cpu_count() * 2 + 1
timeout = 120
certfile = "cert/certfile.crt"
keyfile = "cert/service-key.pem"
ca_certs = "cert/cert.ca-bundle"
loglevel = 'info'
and run as follows:
gunicorn --config=gunicorn.py hello:app
If you use Nginx as a reverse proxy, then you can configure the certificate with Nginx, and then Nginx can "terminate" the encrypted connection, meaning that it will accept encrypted connections from the outside, but then use regular unencrypted connections to talk to your Flask backend. This is a very useful setup, as it frees your application from having to deal with certificates and encryption. The configuration items for Nginx are as follows:
server {
listen 443 ssl;
server_name example.com;
ssl_certificate /path/to/cert.pem;
ssl_certificate_key /path/to/key.pem;
# ...
}
Another important item you need to consider is how are clients that connect through regular HTTP going to be handled. The best solution, in my opinion, is to respond to unencrypted requests with a redirect to the same URL but on HTTPS. For a Flask application, you can achieve that using the Flask-SSLify extension. With Nginx, you can include another server block in your configuration:
server {
listen 80;
server_name example.com;
location / {
return 301 https://$host$request_uri;
}
}
A good tutorial of how setup your application with https can be found here: Running Your Flask Application Over HTTPS
I had similar case. I was developing bot on localhost (yet without SSL) and tunneled it to web through ngrok. In beginning all was OK, but once I found no POST-requests are coming. It turned out time of tunneling expired. I laughed and restarted tunneling. But requests weren't coming. It turned out, I forgot to change address of webhook (it switches every ngrok session). Don't repeat my errors.

Bottle-WebSocket: How to ensure an HTTP request is from the same session as ws connection?

I built an web application using Python Bottle framework.
I used bottle-websocket plugin for WebSocket communication with clients.
Here is a part of my code.
from bottle import Bottle, request, run
from bottle.ext.websocket import GeventWebSocketServer, websocket
class MyHandler():
...
class MyServer(Bottle):
...
def _serve_websocket(self, ws):
handler = MyHandler()
some_data = request.cookies.get('some_key') # READ SOME DATA FROM HTTP REQUEST
while True:
msg = ws.receive()
handler.do_sth_on(msg, some_data) # USE THE DATA FROM HTTP REQUEST
ws.send(msg)
del(handler)
if __name__ == '__main__':
run(app=MyServer(), server=GeventWebSocketServer, host=HOST, port=PORT)
As the code shows, I need to read some data from the browser (cookies or anything in the HTTP request headers) and use it for WebSocket message processing.
How can I ensure the request is from the same browser session as the one where WebSocket connection comes?
NOTE
As I do not have much knowledge of HTTP and WebSocket, I'd love to here detailed answere as much as possible.
How can I ensure the request is from the same browser session as the one where WebSocket connection comes?
Browser session is a bit abstract since HTTP does not have a concept of sessions. HTTP and RESTful APIs is designed to be stateless, but there is options.
Usually, what you usually want to know is what user the request comes from. This is usually solved by authentication e.g. by using OpenID Connect and let the user send his JWT-token in the Authorization: header, this works for all HTTP requests, including when setting up a Websocket connection.
bottle-oauthlib seem to be a library for authenticating end-users using OAuth2 / OpenID Connect.
Another option is to identify the "browser session" using cookies but this depends on a state somewhere on the server side and is harder to implement on cloud native platforms like e.g. Kubernetes that prefer stateless workloads.

Using Python requests to GET not working - web client and browser works

I have my web app API running.
If I go to http://127.0.0.1:5000/ via any browser I get the right response.
If I use the Advanced REST Client Chrome app and send a GET request to my app at that address I get the right response.
However this gives me a 503:
import requests
response = requests.get('http://127.0.0.1:5000/')
I read to try this for some reason:
s = requests.Session()
response = s.get('http://127.0.0.1:5000/')
But I still get a 503 response.
Other things I've tried: Not prefixing with http://, not using a port in the URL, running on a different port, trying a different API call like Post, etc.
Thanks.
Is http://127.0.0.1:5000/ your localhost? If so, try 'http://localhost:5000' instead
Just in case someone is struggling with this as well, what finally worked was running the application on my local network ip.
I.e., I just opened up the web app and changed the app.run(debug=True) line to app.run(host="my.ip.address", debug = True).
I'm guessing the requests library perhaps was trying to protect me from a localhost attack? Or our corporate proxy or firewall was preventing communication from unknown apps to the 127 address. I had set NO_PROXY to include the 127.0.0.1 address, so I don't think that was the problem. In the end I'm not really sure why it is working now, but I'm glad that it is.

python tornado user authentication and then reverse proxy via apache

I am implementing a two-way SSL authentication and then additional authentication via Kerberos after which it redirects the user to an internal server via reverse proxy.
i.e:
SSL auth <--> Apache Server + kerberos auth using login/password <--reverse proxy-->> internal server
This setup currently works:
Now my idea is to use this configuration as I can control the behavior of the user via Tornado
SSL auth <--> Apache server <---> Tornado webserver for kerberos auth <---> reverse proxy <---> internal server
And I have got the SSL authentication and the Kerberos authentication working.
However, how do I tell Tornado to reverse proxy(apache) to the internal server?
Tornado doesn't have any built-in reverse proxy functionality, but in the simple case a reverse proxy is just a RequestHandler that passes through to an HTTP client:
class ReverseProxyHandler(RequestHandler):
#gen.coroutine
def get(self):
resp = AsyncHTTPClient().fetch(self.convert_url(self.request),
headers=self.request.headers)
self.set_status(resp.code)
for k,v in resp.headers.get_all():
self.add_header(k, v)
self.write(resp.body)
It could get a lot more complicated than that depending on what your requirements are. This is only a simple thing to build if you can be sure that your internal server doesn't do anything tricky.

Categories