SAML2 Service Provider on non standard port behind a reverse proxy - python

I have a SAML2 service provider (Open edX Platform if it makes a difference), configured according to docs and otherwise working normally. It runs at http://lms.local:8000 and works just fine with TestShib test Identity Provider and other 3rd party providers.
Problems begin when nginx reverse proxy is introduced. The setup is as follows:
nginx, obviously, runs on port 80
LMS (the service provider) runs on port 8000
lms.local is aliased to localhost via hosts file
Nginx have the following site config:
server {
listen 80;
server_name lms.local;
location / {
proxy_pass http://localhost:8000;
proxy_set_header Host $host;
if ($request_method = 'OPTIONS') {
return 204;
}
}
}
The problem is the following: python-social-auth detects that the server runs on lms.local:8000 (via request.META['HTTP_PORT']). So, if an attempt was made to use SAML SSO via the nginx proxy, it fails with the following message:
Authentication failed: SAML login failed: ['invalid_response'] (The response was received at http://lms.local:8000/auth/complete/tpa-saml/ instead of http://lms.local/auth/complete/tpa-saml/)
If that helps, an exception that causes this message is thrown in python-saml.OneLogin_Saml2_Response.is_valid.
The questions is: is that possible to run SP behind a reverse proxy on the same domain, but on different port? Shibboleth wiki says it is totally possible to run a SP behind the reverse proxy on different domain, but says nothing about ports.

In this particular case reverse proxy was sending X-Forwarded-Host and X-Forwarded-Port headers, so I just modified django strategy to use those values instead of what Django provides (i.e. request.get_host and request.META['SERVER_PORT']), which yielded two pull requests:
https://github.com/edx/edx-platform/pull/9848
https://github.com/omab/python-social-auth/pull/741

Related

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.

Flask url handling for port

I have Kibana (part of elasticsearch stack) running on xx.xxx.xxx.xxx:5601. Since Kibana does not have authentication of its own, I am trying to wrap it under my flask login setup. In other words, if someone tries to visit xx.xxx.xxx.xxx:5601, I need the page to be redirected to my flask login page. I can use the #login_required decorator on the URL to achieve this...but I don't know how to setup the flask route URL to handle the port 5601 since it needs to begin with a leading slash.
#app.route("/")
#login_required
Any suggestions?
EDIT
#senaps: App 1 is flask that runs on 0.0.0.0, port 9500, App 2 is node.js based Kibana that I can choose to either run on localhost port 5601 and then expose via nginx, or i can directly make public on IP:5601. Either way, it is running as a "service" on startup and listening on 5601 at all times.
Problem statement - App 2 to be wrapped under App 1 login. I do not want to use nginx for authentication of App 2 but rather the App 1 flask login setup.
I'm currently using gunicorn to serve flask app and have nginx reverse proxy setup to route to flask app. Guide followed is digitalocean
Option 1 - Node.js Kibana application exposed to public on IP:5601.
server {
listen 80;
server_name example.com;
location / {
include proxy_params;
proxy_pass http://unix:/home/ubuntu/myproject/myproject.sock;
}}
If I visit IP, it goes to my flask app, great. I'm unable to figure out how to handle flask view URL if someone visits IP:5601. Instead of taking them to Kibana, it should redirect to my flask app for authentication.
I tried adding another server block to listen at 5601 and proxy_pass to the flask sock file, I get a nginx error that says it cannot bind to 5601 and asks me to kill the listener at 5601. But I need Kibana running at 5601 at all times (unless I can figure out a way to launch this service via python flask).
Option 2 - Kibana application runs on localhost port 5601 mounted at "/kibana" in order to not conflict with "/" needed for flask. Then it is exposed via nginx reverse proxy.
server {
listen 80;
server_name example.com;
location / {
include proxy_params;
proxy_pass http://unix:/home/ubuntu/myproject/myproject.sock;
}
location /kibana/ {
proxy_pass http://localhost:5601;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
rewrite /kibana/(.*)$ /$1 break;
}}
With this setup, one can access Kibana by going to IP/kibana. But problem with Option 2 is if I have a /kibana view in my flask app to catch it, it does not take effect as redirection to Kibana happens at nginx, so flask never gets involved.
I coudln't find much info on stack etc. since most solutions deal with using nginx to authenticate Kibana and not any other python applications.
Given this, how would I corporate your solution? Many thanks in advance for looking into this.
so you have 2 separate apps right?
you want the second app to only work if user is authenticated with first app.
simplest way would be, to use the same db.this way, flask login would check for the user's authentication based on the same db. with that being said, you may not be able to handle session's perfectly okay.
the trick is in uwsgi nad nginx. you should use Emperor mode of uwsgi so both apps are deployed.
#app.route("/")
#login_required
def function()
now, the question might be how would we get the second app's / route if the first app has that route too. well, this will not be a problem since the url is different. but you need your nginx configured to relay requests for xx.x.x.x to the first app and x.x.x.x:y to the second app.
server {
listen 80;
server_name example.org www.example.org;
root /var/www/port80/
}
server {
listen 5601;
server_name example.org www.example.org;
root /var/www/port81/
}
since you asked for suggestions on how to do it, i don't include codes. so you can figure out based on your setup. or you should tell us how you setup and serve the two apps, so we could provide more of code.
One approach is to proxy all traffic to the Kibana server through the Flask application. You can use a catch-all route to handle forwarding of the different paths. You would disallow access to Kibana from sources other than from the Flask application.
import requests # may require `pip install requests`
kibana_server_baseurl = 'https://xxx.xxx.xxx.xxx:5601/'
#app.route('/', defaults={'path': ''})
#app.route('/<path:path>')
#login_required
def proxy_kibana_requests(path):
# ref http://flask.pocoo.org/snippets/118/
url = kibana_server_baseurl + path
req = requests.get(url, stream = True)
return Response(stream_with_context(req.iter_content()), content_type = req.headers['content-type'])
Another options is to use Nginx as a reverse proxy and use Nginx to handle authentication. The simplest, if it meets your needs, is to use basic auth. https://www.nginx.com/resources/admin-guide/restricting-access-auth-basic/.
Alternatively you could check for a custom header in the Nginx config on access to the Kibana application and redirect to the Flask application if it were missing.
Another option is to use an existing Kibana authentication proxy. A commercial option, Elastic x-pack is a popular option. Another OSS option is https://github.com/fangli/kibana-authentication-proxy. I have not personally used either.

Error in connecting domain name to nginx

I'm trying to connect my droplets on Digital Ocean to a domain name (example.com)
Currently using uwsgi, nginx and the web apps is in python (flask, MySQL)
I have configured my project .conf as such:
server {
listen 80;
server_name ip-address example.com www.example.com;
}
location {
include uwsgi_params;
uwsgi_pass unix:///home/user/example/example.sock;
}
I have added hosts:
127.0.0.1 perhatian.com www.perhatian.com
The site currently is not reachable, however, when i access the IP its working.
Any help ?
It looks like you do not have proper DNS setup towards that domain name (perhatian.com) as seen from https://intodns.com/perhatian.com
If you are trying to load perhatian.com in a browser from any where, you'll need to setup A records pointing towards the servers IP on which you are able to load the website.

django-allauth: how to modify email confirmation url?

I'm running django on port 8001, while nginx is handling webserver duties on port 80. nginx proxies views and some REST api calls to Django. I'm using django-allauth for user registration/authentication.
When a new user registers, django-allauth sends the user an email with a link to click. Because django is running on port 8001, the link looks like http://machine-hostname:8001/accounts/confirm-email/xxxxxxxxxxxxxx
How can I make the url look like http://www.example.com/accounts/confirm-email/xxxxxxxx ?
Thanks!
Django get hostname and port from HTTP headers.
Add proxy_set_header Host $http_host; into your nginx configuration before options proxy_pass.
I had the same problem, and also found that ferrangb's solution had no effect on outgoing allauth emails. mr_tron's answer got me halfway, but I had to do a little bit more:
1) In nginx configuration, put
proxy_set_header Host $http_host
before options proxy_pass.
2) In settings.py, add the domain name to ALLOWED_HOSTS. I also added the www version of my domain name, since I get traffic to both addresses.
ALLOWED_HOSTS = ['127.0.0.1', 'example.com', 'www.example.com']
And, of course, restart nginx and gunicorn or whatever is serving your Django. With the first step but not the second, all hits to the site were instant 400 errors (unless DEBUG = True in settings.py.)

How to dynamically load HTTP routing into NGINX from your webframework?

I've been following the Web Frameworks Benchmark and have noticed that a number of web framework suffer from the same performance penalty, that being they do HTTP routing within the framework itself and not leverage the highly performant HTTP server of NGINX to do routing.
For example, in the Flask python framework, you might have:
#app.route('/add', methods=['POST'])
def add_entry():
...
Which makes your application much easier to follow than doing it directly within NGINX config file like so:
server {
listen 80;
server_name example.com;
location /add {
... // defer to Flask (python) app
}
Question: How can you gain the performance of NGINX built-in HTTP routing (using NGINX own config file to define routing), while also keeping the easy of application development by defining the HTTP routing within your web framework?
Is there a way you can dynamically load into NGINX from INSERT_NAME_OF_YOUR_WEBFRAMEWORK the HTTP routing?
I don't know a ready to use library. But it seems pretty easy to write a script, which generates an Nginx config file from application's routes (for example, during application setup). This file can be included into main configuration of server using "include" command of Nginx config:
server {
listen 80;
server_name example.com;
include /path/to/application/routes.conf
}

Categories