Really strange problem with passing SSL headers in from Apache

743 views
Skip to first unread message

O haya

unread,
Nov 11, 2017, 12:03:26 AM11/11/17
to modwsgi
Hi,

I built mod_wsgi using Python 3.6.3 and also with Apache 2.2.29.  The Apache is configured for client-authenticated SSL, and I am trying to configure Apache to pass some of the SSL_ variables to a small test Flask application and I am having difficulty getting this working.

Here is the VirtualHost:

<VirtualHost *:8443>
.
.
.


    WSGIDaemonProcess webtool user=myuser group=mygroup threads=5 home=/apps/flaskapps/helloflask/wsgi-scripts
    WSGIScriptAlias / /apps/flaskapps/helloflask/wsgi-scripts/webtool.wsgi

#    WSGIPassAuthorization On

    RequestHeader set X-SSL-PROTOCOL "%{SSL_PROTOCOL}s"
    RequestHeader set X-SSL-CIPHER "%{SSL_CIPHER}s"
    RequestHeader set X-SSL-CIPHER1 "%{SSL_CLIENT_S_DN}s"
    RequestHeader set X-SSL-CIPHER2 "%{SSL_CLIENT_I_DN}s"
    RequestHeader set X-SSL-CIPHER3 "%{SSL_CLIENT_CERT}s"
#    RequestHeader set X-SSL-CLIENT_S_DN "%{SSL_CLIENT_S_DN}s"
#    RequestHeader add X-SSL_CLIENT_S_DN "%{SSL_CLIENT_S_DN}e"
#    RequestHeader add X-MYSSL_CLIENT_S_DN "fffffooooooooooooooooooooooooooooooooooo"
#     RequestHeader set X-SSL_CLIENT_I_DN "%{SSL_CLIENT_I_DN}e"
#     RequestHeader set X-SSL_SERVER_S_DN_OU "%{SSL_SERVER_S_DN_OU}e"
#     RequestHeader set X-SSL_CLIENT_VERIFY "%{SSL_CLIENT_VERIFY}e"
#     RequestHeader set X-SSL_CLIENT_CERT "%{SSL_CLIENT_CERT}e"

    <directory /apps/flaskapps/helloflask/wsgi-scripts>
        WSGIProcessGroup webtool

    SSLOptions +StdEnvVars +ExportCertData

        WSGIApplicationGroup %{GLOBAL}
        WSGIScriptReloading On
        Order allow,deny
        Allow from all
    </directory>

Note the bunch of RequestHeader directives.

I originally started with only the 1st two:

    RequestHeader set X-SSL-PROTOCOL "%{SSL_PROTOCOL}s"
    RequestHeader set X-SSL-CIPHER "%{SSL_CIPHER}s"

And that worked, i.e., my test Flask app was able to see those headers, and dumped out those values.

Then, I added a third one:

    RequestHeader set X-SSL-PROTOCOL "%{SSL_PROTOCOL}s"
    RequestHeader set X-SSL-CIPHER "%{SSL_CIPHER}s"
    RequestHeader set X-SSL-CLIENT_S_DN "%{SSL_CLIENT_S_DN}s"

And bounced the Apache and tested, but I still only saw the first two headers :(...

I added the others that you see that are commented out, but still only saw the first two headers in Flask.

So, just on a whim, I tried copying the 2nd one, but changing the header name slightly.

    RequestHeader set X-SSL-PROTOCOL "%{SSL_PROTOCOL}s"
    RequestHeader set X-SSL-CIPHER "%{SSL_CIPHER}s"
    RequestHeader set X-SSL-CIPHER1 "%{SSL_CLIENT_S_DN}s"

And when I tested, I saw all 3 headers in Flask.

So I tried changing the name of the third header:

    RequestHeader set X-SSL-PROTOCOL "%{SSL_PROTOCOL}s"
    RequestHeader set X-SSL-CIPHER "%{SSL_CIPHER}s"
    RequestHeader set X-SSL-CIPHER1_CLIENT_S_DN "%{SSL_CLIENT_S_DN}s"

And then I saw only the first two headers in Flask.

Change the third header name back to X-SSL-CIPHER1 and tested again, and saw 3 headers.

I don't understand why this is happening.  It seems like there is something "special" about the header name in the RequestHeader that is preventing the Apache sending any other header names?

Any ideas why this might be the case?  I have worked with Apache for awhile, and with RequestHeader in the past, and I don't recall anything like this.

Thanks,
Jim

Graham Dumpleton

unread,
Nov 11, 2017, 4:05:08 AM11/11/17
to mod...@googlegroups.com
Why fiddle with RequestHeader and using headers. The directive:

    SSLOptions +StdEnvVars

should result in them being passed through in the WSGI environ dictionary already.

Graham
    
--
You received this message because you are subscribed to the Google Groups "modwsgi" group.
To unsubscribe from this group and stop receiving emails from it, send an email to modwsgi+u...@googlegroups.com.
To post to this group, send email to mod...@googlegroups.com.
Visit this group at https://groups.google.com/group/modwsgi.
For more options, visit https://groups.google.com/d/optout.

O haya

unread,
Nov 11, 2017, 9:21:26 AM11/11/17
to modwsgi
Hi,

I already have the SSLOptions +StdEnvVars in the virtualhost and was not seeing the SSL_ headers.  That was why I started trying to add the RequestHeaders.

Thanks,
Jim

O haya

unread,
Nov 11, 2017, 9:31:53 AM11/11/17
to modwsgi
Hi Graham,

FYI, I am going to be in-transit to another location in a bit, so I will be slow to respond probably until tomorrow.

Graham Dumpleton

unread,
Nov 11, 2017, 3:39:40 PM11/11/17
to mod...@googlegroups.com
Can you use the test program at:


behind your configuration and provide what it responds with back in the browser.

Change any values you think may be sensitive. It will be mainly the keys rather than values am interested in.

Graham

O haya

unread,
Nov 14, 2017, 3:44:22 AM11/14/17
to modwsgi
Hi,

Using your environ.wsgi, if I have:

    RequestHeader set X-SSL-CIPHER "%{SSL_CIPHER}s"

    RequestHeader set X-SSL-CIPHER1 "%{SSL_CLIENT_S_DN}s"
    RequestHeader set X-SSL-CIPHER2 "%{SSL_CLIENT_I_DN}s"
    RequestHeader set X-SSL-CIPHER3 "%{SSL_CLIENT_CERT}s"

The headers with "X_SSL_" I get are:

HTTP_X_SSL_CIPHER: 
HTTP_X_SSL_CIPHER1:
HTTP_X_SSL_CIPHER2:
HTTP_X_SSL_CIPHER3:

[Notice the header names have underscores whereas the header name I had in the httpd-vhosts.conf had dashes ("-").


If I have:

    RequestHeader set X-SSL-CIPHER "%{SSL_CIPHER}s"

    RequestHeader set X-SSL-CLIENT_S_DN "%{SSL_CLIENT_S_DN}s"
     RequestHeader set X-SSL_CLIENT_I_DN "%{SSL_CLIENT_I_DN}s"


The only headers with "X_SSL_" I get are:

HTTP_X_SSL_CIPHER: 
HTTP_X_SSL_PROTOCOL:

Here is the full output:

PID: 5271
UID: 502
GID: 500
CWD: /apps/flaskapps/helloflask/wsgi-scripts

STDOUT: <stdout>
STDERR: <stderr>
ERRORS: <wsgi.errors>

python.version: '3.6.3 (default, Nov  9 2017, 19:17:20) \n[GCC 4.4.7 20120313 (Red Hat 4.4.7-17)]'
python.prefix: '/apps/python-3.6.3'
python.path: ['/apps/flaskapps/helloflask/wsgi-scripts', '/apps/python-3.6.3/lib/python36.zip', '/apps/python-3.6.3/lib/python3.6', '/apps/python-3.6.3/lib/python3.6/lib-dynload', '/apps/python-3.6.3/lib/python3.6/site-packages', '/apps/python-3.6.3/lib/python3.6/site-packages/mod_wsgi-4.5.20-py3.6-linux-x86_64.egg']

apache.version: (2, 2, 29)
mod_wsgi.version: (4, 5, 20)

mod_wsgi.process_group: webtool
mod_wsgi.application_group: 

mod_wsgi.maximum_processes: 1
mod_wsgi.threads_per_process: 5
mod_wsgi.process_metrics: {'pid': 5271, 'request_count': 0, 'request_busy_time': 0.006062, 'memory_max_rss': 10698752, 'memory_rss': 10702848, 'cpu_user_time': 0.019999999552965164, 'cpu_system_time': 0.0, 'restart_time': 1510648086.068054, 'current_time': 1510648090.617293, 'running_time': 4, 'request_threads': 1, 'active_requests': 1, 'threads': [{'thread_id': 1, 'request_count': 1}]}
mod_wsgi.server_metrics: None

apache.description: Apache/2.2.29 (Unix) mod_ssl/2.2.29 OpenSSL/1.0.1e-fips DAV/2 mod_wsgi/4.5.20 Python/3.6
apache.build_date: Nov  9 2017 19:12:16
apache.mpm_name: Prefork
apache.maximum_processes: 256
apache.threads_per_process: 1

PATH: ['/apps/flaskapps/helloflask/wsgi-scripts', '/apps/python-3.6.3/lib/python36.zip', '/apps/python-3.6.3/lib/python3.6', '/apps/python-3.6.3/lib/python3.6/lib-dynload', '/apps/python-3.6.3/lib/python3.6/site-packages', '/apps/python-3.6.3/lib/python3.6/site-packages/mod_wsgi-4.5.20-py3.6-linux-x86_64.egg']

LANG: en_US.UTF-8
LC_ALL: None
sys.getdefaultencoding(): utf-8
sys.getfilesystemencoding(): utf-8
locale.getlocale(): ('en_US', 'UTF-8')
locale.getdefaultlocale(): ('en_US', 'UTF-8')
locale.getpreferredencoding(): UTF-8

DOCUMENT_ROOT: '/apps/httpd-2.2.29/htdocs'
GATEWAY_INTERFACE: 'CGI/1.1'
HTTP_ACCEPT: 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8'
HTTP_ACCEPT_ENCODING: 'gzip, deflate, br'
HTTP_ACCEPT_LANGUAGE: 'en-US,en;q=0.5'
HTTP_CACHE_CONTROL: 'max-age=0'
HTTP_CONNECTION: 'keep-alive'
HTTP_UPGRADE_INSECURE_REQUESTS: '1'
HTTP_USER_AGENT: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:56.0) Gecko/20100101 Firefox/56.0'
HTTP_X_SSL_CIPHER: 'ECDHE-RSA-AES128-GCM-SHA256'
HTTP_X_SSL_PROTOCOL: 'TLSv1.2'
PATH_INFO: '/'
PATH_TRANSLATED: '/apps/flaskapps/helloflask/wsgi-scripts/environ.wsgi/'
QUERY_STRING: ''
REMOTE_ADDR: '192.168.2.51'
REMOTE_PORT: '57238'
REQUEST_METHOD: 'GET'
REQUEST_URI: '/'
SCRIPT_FILENAME: '/apps/flaskapps/helloflask/wsgi-scripts/environ.wsgi'
SCRIPT_NAME: ''
SERVER_ADDR: '192.168.2.144'
SERVER_ADMIN: 'y...@example.com'
SERVER_NAME: 'apache3.whatever.com'
SERVER_PORT: '8443'
SERVER_PROTOCOL: 'HTTP/1.1'
SERVER_SIGNATURE: ''
SERVER_SOFTWARE: 'Apache/2.2.29 (Unix) mod_ssl/2.2.29 OpenSSL/1.0.1e-fips DAV/2 mod_wsgi/4.5.20 Python/3.6'
SSL_CIPHER: 'ECDHE-RSA-AES128-GCM-SHA256'
SSL_CIPHER_ALGKEYSIZE: '128'
SSL_CIPHER_EXPORT: 'false'
SSL_CIPHER_USEKEYSIZE: '128'
SSL_CLIENT_A_KEY: 'rsaEncryption'
SSL_CLIENT_A_SIG: 'sha256WithRSAEncryption'
SSL_CLIENT_CERT: '-----BEGIN CERTIFICATE-----\nMIIDZDCCAkygAwIBAgIGAV+ovfDtMA0GCSqGSIb3DQEBCwUAMEkxCzAJBgNVBAYT\.
.
.
\nbcfp/R/I7A6sAVlIS9BLgCU0ULqCKy91pwVyaUJpfaPg0LXcCKmJ5+u7NgZqa2KX\nEEbdXVjfG5yempsOqaiF7/RKZwfxM1+q2PBysUGIVts6NCGFHEsmAEOH6nTapwLf\nBUoizHvJXzA=\n-----END CERTIFICATE-----\n'
SSL_CLIENT_I_DN: '/C=US/O=simplecao/OU=simplecaou/CN=simpleca'
SSL_CLIENT_I_DN_C: 'US'
SSL_CLIENT_I_DN_CN: 'simpleca'
SSL_CLIENT_I_DN_O: 'simplecao'
SSL_CLIENT_I_DN_OU: 'simplecaou'
SSL_CLIENT_M_SERIAL: '015FA8BDF0ED'
SSL_CLIENT_M_VERSION: '3'
SSL_CLIENT_S_DN: '/C=US/CN=XXXXXX'
SSL_CLIENT_S_DN_C: 'US'
SSL_CLIENT_S_DN_CN: 'XXXXXX'
SSL_CLIENT_VERIFY: 'SUCCESS'
SSL_CLIENT_V_END: 'May  4 01:42:21 2023 GMT'
SSL_CLIENT_V_REMAIN: '1997'
SSL_CLIENT_V_START: 'Nov 11 01:42:21 2017 GMT'
SSL_COMPRESS_METHOD: 'NULL'
SSL_PROTOCOL: 'TLSv1.2'
SSL_SECURE_RENEG: 'true'
SSL_SERVER_A_KEY: 'rsaEncryption'
SSL_SERVER_A_SIG: 'sha256WithRSAEncryption'
SSL_SERVER_CERT: '-----BEGIN CERTIFICATE-----\nMIIDijCCAnKgAwIBAgIGAV+opXRrMA0GCSqGSIb3DQEBCwUAMEkxCzAJBgNVBAYT.
.
.
.
\nwaGkjK2v4bAib8jakrudJfWBdFURfYbiYMzYw8pj5NHuJ8sl25V1n09bLv1aTw==\n-----END CERTIFICATE-----\n'
SSL_SERVER_I_DN: '/C=US/O=simplecao/OU=simplecaou/CN=simpleca'
SSL_SERVER_I_DN_C: 'US'
SSL_SERVER_I_DN_CN: 'simpleca'
SSL_SERVER_I_DN_O: 'simplecao'
SSL_SERVER_I_DN_OU: 'simplecaou'
SSL_SERVER_M_SERIAL: '015FA8A5746B'
SSL_SERVER_M_VERSION: '3'
SSL_SERVER_S_DN: '/C=US/CN=apache2.whatever.com'
SSL_SERVER_S_DN_C: 'US'
SSL_SERVER_S_DN_CN: 'apache2.whatever.com'
SSL_SERVER_V_END: 'May 24 01:15:38 2021 GMT'
SSL_SERVER_V_START: 'Nov 11 01:15:36 2017 GMT'
SSL_TLS_SNI: 'apache3.whatever.com'
SSL_VERSION_INTERFACE: 'mod_ssl/2.2.29'
SSL_VERSION_LIBRARY: 'OpenSSL/1.0.1e-fips'
UNIQUE_ID: 'WgqpGsCoApAAABSaB4AAAAAC'
apache.version: (2, 2, 29)
mod_wsgi.application_group: ''
mod_wsgi.callable_object: 'application'
mod_wsgi.daemon_connects: '1'
mod_wsgi.daemon_restarts: '0'
mod_wsgi.daemon_start: '1510648090610672'
mod_wsgi.enable_sendfile: '0'
mod_wsgi.handler_script: ''
mod_wsgi.ignore_activity: '0'
mod_wsgi.listener_host: ''
mod_wsgi.listener_port: '8443'
mod_wsgi.path_info: '/'
mod_wsgi.process_group: 'webtool'
mod_wsgi.queue_start: '1510648090610136'
mod_wsgi.request_handler: 'wsgi-script'
mod_wsgi.request_start: '1510648090609673'
mod_wsgi.script_name: ''
mod_wsgi.script_reloading: '1'
mod_wsgi.script_start: '1510648090616973'
mod_wsgi.thread_id: 1
mod_wsgi.thread_requests: 0
mod_wsgi.total_requests: 0
mod_wsgi.version: (4, 5, 20)
wsgi.errors: <_io.TextIOWrapper name='<wsgi.errors>' encoding='utf-8'>
wsgi.file_wrapper: <class 'mod_wsgi.FileWrapper'>
wsgi.input: <mod_wsgi.Input object at 0x7f18c377cab0>
wsgi.multiprocess: False
wsgi.multithread: True
wsgi.run_once: False
wsgi.url_scheme: 'https'
wsgi.version: (1, 0)

CVS_RSH: 'ssh'
G_BROKEN_FILENAMES: '1'
HISTCONTROL: 'ignoredups'
HISTSIZE: '1000'
HOME: '/home/oracle'
LANG: 'en_US.UTF-8'
LD_LIBRARY_PATH: '/apps/httpd-2.2.29/lib'
LESSOPEN: '||/usr/bin/lesspipe.sh %s'
LOGNAME: 'oracle'
LS_COLORS: 'rs=0:di=01;34:ln=01;36:mh=00:pi=40;33:so=01;35:do=01;35:bd=40;33;01:cd=40;33;01:or=40;31;01:mi=01;05;37;41:su=37;41:sg=30;43:ca=30;41:tw=30;42:ow=34;42:st=37;44:ex=01;32:*.tar=01;31:*.tgz=01;31:*.arj=01;31:*.taz=01;31:*.lzh=01;31:*.lzma=01;31:*.tlz=01;31:*.txz=01;31:*.zip=01;31:*.z=01;31:*.Z=01;31:*.dz=01;31:*.gz=01;31:*.lz=01;31:*.xz=01;31:*.bz2=01;31:*.tbz=01;31:*.tbz2=01;31:*.bz=01;31:*.tz=01;31:*.deb=01;31:*.rpm=01;31:*.jar=01;31:*.rar=01;31:*.ace=01;31:*.zoo=01;31:*.cpio=01;31:*.7z=01;31:*.rz=01;31:*.jpg=01;35:*.jpeg=01;35:*.gif=01;35:*.bmp=01;35:*.pbm=01;35:*.pgm=01;35:*.ppm=01;35:*.tga=01;35:*.xbm=01;35:*.xpm=01;35:*.tif=01;35:*.tiff=01;35:*.png=01;35:*.svg=01;35:*.svgz=01;35:*.mng=01;35:*.pcx=01;35:*.mov=01;35:*.mpg=01;35:*.mpeg=01;35:*.m2v=01;35:*.mkv=01;35:*.ogm=01;35:*.mp4=01;35:*.m4v=01;35:*.mp4v=01;35:*.vob=01;35:*.qt=01;35:*.nuv=01;35:*.wmv=01;35:*.asf=01;35:*.rm=01;35:*.rmvb=01;35:*.flc=01;35:*.avi=01;35:*.fli=01;35:*.flv=01;35:*.gl=01;35:*.dl=01;35:*.xcf=01;35:*.xwd=01;35:*.yuv=01;35:*.cgm=01;35:*.emf=01;35:*.axv=01;35:*.anx=01;35:*.ogv=01;35:*.ogx=01;35:*.aac=01;36:*.au=01;36:*.flac=01;36:*.mid=01;36:*.midi=01;36:*.mka=01;36:*.mp3=01;36:*.mpc=01;36:*.ogg=01;36:*.ra=01;36:*.wav=01;36:*.axa=01;36:*.oga=01;36:*.spx=01;36:*.xspf=01;36:'
MAIL: '/var/spool/mail/oracle'
PATH: '/usr/lib64/qt-3.3/bin:/usr/local/bin:/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/sbin:/home/oracle/bin'
PWD: '/apps/flaskapps/helloflask/wsgi-scripts'
QTDIR: '/usr/lib64/qt-3.3'
QTINC: '/usr/lib64/qt-3.3/include'
QTLIB: '/usr/lib64/qt-3.3/lib'
SHELL: '/bin/bash'
SHLVL: '2'
SSH_ASKPASS: '/usr/libexec/openssh/gnome-ssh-askpass'
TERM: 'xterm'
USER: 'oracle'
_: '/apps/httpd-2.2.29/bin/httpd'


So it seems like mod_wsgi is passing the default headers but my app/code wasn't retrieving them correctly in my Flask app, but I am still not clear why the RequestHeaders directives don't seem to be working correctly in all cases?

Graham Dumpleton

unread,
Nov 14, 2017, 4:02:58 AM11/14/17
to mod...@googlegroups.com
That is expected behaviour. Any headers using anything other than alpha numerics and dashes are thrown away.

This is because to do otherwise opens up security issues, because multiple possible header keys could map to the same key name as anything except alpha numerics are mapped to underscores. A user could use this fact to send a X_SSL-CIPHER1 header which would map to same name as you are trying to use. If the user one overrides yours, then could be an issue.

For that reason, any good web server, Apache and nginx included, now discards headers using anything other than alpha numerics and dashes when creating CGI like environ, such as occurs with WSGI. There is a CERT advisory about the problem somewhere.

So don't use underscores in header names. Also try and use all the SSL_ values that are already passed in when:

    SSLOptions +StdEnvVars

is used.

O haya

unread,
Nov 14, 2017, 11:33:58 AM11/14/17
to modwsgi
Ahh.  Ok, understood about the header names.

I think that your environ.wsgi is Python, ultimately, we would want the:

WSGIScriptAlias / /apps/flaskapps/helloflask/wsgi-scripts/environ.wsgi

in the httpd-vhosts VirtualHost to point to a Flask app.  

In that case, do we have to do something in the .wsgi that the WSGIScriptAlias is pointing to to pass any of the SSL_ headers to the Flask app to make them available to the Flask app code?

Or, does that happen automatically, and if so , how do we reference such SSL_ header values from inside the Flask app?

I realize/guess that some of those questions may be a little off-topic from pure "mod_wsgi" questions, but I hope that you can answer them.

Thanks!

Jim

Graham Dumpleton

unread,
Nov 14, 2017, 2:41:50 PM11/14/17
to mod...@googlegroups.com
On 15 Nov 2017, at 3:33 am, O haya <jim...@gmail.com> wrote:

Ahh.  Ok, understood about the header names.

I think that your environ.wsgi is Python, ultimately, we would want the:

WSGIScriptAlias / /apps/flaskapps/helloflask/wsgi-scripts/environ.wsgi

in the httpd-vhosts VirtualHost to point to a Flask app.  

Flask is also a Python app. :-)

In that case, do we have to do something in the .wsgi that the WSGIScriptAlias is pointing to to pass any of the SSL_ headers to the Flask app to make them available to the Flask app code?

Or, does that happen automatically, and if so , how do we reference such SSL_ header values from inside the Flask app?

The contents of the environ dictionary are available in Flask as as 'request.environ'. It is a normal Python dictionary. Just look the values up in it.

Graham

O haya

unread,
Nov 14, 2017, 3:52:48 PM11/14/17
to modwsgi
Ahh.  Ok.  I was still getting confused about the header names I had been trying to add and thought I had to somehow pass the environ into the Flask app, so I was going crazy trying to figure out how to do that.  

request.environ.get(<header_name>) works fine now in my Flask app :)!

Jim
Reply all
Reply to author
Forward
0 new messages