3 new commits in galaxy-central: https://bitbucket.org/galaxy/galaxy-central/commits/7a889340aba7/ Changeset: 7a889340aba7 User: erasche2 Date: 2014-12-17 21:25:07+00:00 Summary: Allow for shared secret between Galaxy and upstream proxies This prevents an impersonation attack available on Galaxies with command line users and REMOTE_USER authentication. It is not uncommon to run Galaxy on a "lab server" (or similar), where there are command line users in addition to the Galaxy service. With REMOTE_USER authentication, Galaxy blindly (and correctly) trusts the upstream value of REMOTE_USER, enabling such attacks as: curl -H "REMOTE_USER: admin.user@domain.edu" http://localhost:8000/galaxy/ This changest adds a shared secret between Galaxy and the upstream proxy, thereby preventing that attack. Only requests from the upstream proxy with the correct secret key are recognised, everything else is tossed out with a 403 Forbidden. This was implemented as part of the REMOTE_USER stack as the situation only applies if that middleware is in use. Affected #: 9 files diff -r 5e5c0cf930bb96bf8af0bd19daad1a999b40c374 -r 7a889340aba7e078d31963aa99139c7ddaea07c2 config/galaxy.ini.sample --- a/config/galaxy.ini.sample +++ b/config/galaxy.ini.sample @@ -713,6 +713,13 @@ # *not* include 'HTTP_' at the beginning of the header name. #remote_user_header = HTTP_REMOTE_USER +# If use_remote_user is enabled, anyone who can log in to the Galaxy host may +# impersonate any other user by simply sending the appropriate header. Thus a +# secret shared between the upstream proxy server, and Galaxy is required. +# If anyone other than the Galaxy user is using the server, then apache/nginx should +# pass a value in the header 'GX_SECRET' that is identical the one below +#remote_user_secret = USING THE DEFAULT IS NOT SECURE! + # If use_remote_user is enabled, you can set this to a URL that will log your # users out. #remote_user_logout_href = None diff -r 5e5c0cf930bb96bf8af0bd19daad1a999b40c374 -r 7a889340aba7e078d31963aa99139c7ddaea07c2 config/tool_shed.ini.sample --- a/config/tool_shed.ini.sample +++ b/config/tool_shed.ini.sample @@ -67,6 +67,13 @@ # https://wiki.galaxyproject.org/Admin/Config/ApacheProxy #use_remote_user = False +# If use_remote_user is enabled, anyone who can log in to the Galaxy host may +# impersonate any other user by simply sending the appropriate header. Thus a +# secret shared between the upstream proxy server, and Galaxy is required. +# If anyone other than the Galaxy user is using the server, then apache/nginx should +# pass a value in the header 'GX_SECRET' that is identical the one below +#remote_user_secret = changethisinproductiontoo + # Configuration for debugging middleware debug = true use_lint = false diff -r 5e5c0cf930bb96bf8af0bd19daad1a999b40c374 -r 7a889340aba7e078d31963aa99139c7ddaea07c2 lib/galaxy/config.py --- a/lib/galaxy/config.py +++ b/lib/galaxy/config.py @@ -132,6 +132,7 @@ self.remote_user_maildomain = kwargs.get( "remote_user_maildomain", None ) self.remote_user_header = kwargs.get( "remote_user_header", 'HTTP_REMOTE_USER' ) self.remote_user_logout_href = kwargs.get( "remote_user_logout_href", None ) + self.remote_user_secret = kwargs.get( "remote_user_secret", None ) self.require_login = string_as_bool( kwargs.get( "require_login", "False" ) ) self.allow_user_creation = string_as_bool( kwargs.get( "allow_user_creation", "True" ) ) self.allow_user_deletion = string_as_bool( kwargs.get( "allow_user_deletion", "False" ) ) diff -r 5e5c0cf930bb96bf8af0bd19daad1a999b40c374 -r 7a889340aba7e078d31963aa99139c7ddaea07c2 lib/galaxy/util/__init__.py --- a/lib/galaxy/util/__init__.py +++ b/lib/galaxy/util/__init__.py @@ -1119,6 +1119,8 @@ def safe_str_cmp(a, b): + """safely compare two strings in a timing-attack-resistant manner + """ if len(a) != len(b): return False rv = 0 diff -r 5e5c0cf930bb96bf8af0bd19daad1a999b40c374 -r 7a889340aba7e078d31963aa99139c7ddaea07c2 lib/galaxy/web/framework/middleware/remoteuser.py --- a/lib/galaxy/web/framework/middleware/remoteuser.py +++ b/lib/galaxy/web/framework/middleware/remoteuser.py @@ -3,6 +3,7 @@ """ import socket +from galaxy.util import safe_str_cmp errorpage = """ <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> @@ -37,12 +38,13 @@ class RemoteUser( object ): - def __init__( self, app, maildomain=None, display_servers=None, admin_users=None, remote_user_header=None ): + def __init__( self, app, maildomain=None, display_servers=None, admin_users=None, remote_user_header=None, remote_user_secret_header=None ): self.app = app self.maildomain = maildomain self.display_servers = display_servers or [] self.admin_users = admin_users or [] self.remote_user_header = remote_user_header or 'HTTP_REMOTE_USER' + self.config_secret_header = remote_user_secret_header def __call__( self, environ, start_response ): # Allow display servers @@ -59,6 +61,34 @@ # Rewrite* method for passing REMOTE_USER and a user is # un-authenticated. Any other possible values need to go here as well. path_info = environ.get('PATH_INFO', '') + + # If the secret header is enabled, we expect upstream to send along some key + # in HTTP_GX_SECRET, so we'll need to compare that here to the correct value + # + # This is not an ideal location for this function. The reason being + # that because this check is done BEFORE the REMOTE_USER check, it is + # possible to attack the GX_SECRET key without having correct + # credentials. However, that's why it's not "ideal", but it is "good + # enough". The only users able to exploit this are ones with access to + # the local system (unless Galaxy is listening on 0.0.0.0....). It + # seems improbable that an attacker with access to the server hosting + # Galaxy would not have acess to Galaxy itself, and be attempting to + # attack the system + if self.config_secret_header is not None: + if not safe_str_cmp(environ.get('HTTP_GX_SECRET'), self.config_secret_header): + title = "Access to Galaxy is denied" + message = """ + Galaxy is configured to authenticate users via an external + method (such as HTTP authentication in Apache), but an + incorrect shared secret key was provided by the + upstream (proxy) server.</p> + <p>Please contact your local Galaxy administrator. The + variable <code>remote_user_secret</code> and + <code>GX_SECRET</code> header must be set before you may + access Galaxy. + """ + return self.error( start_response, title, message ) + if not environ.get(self.remote_user_header, '(null)').startswith('(null)'): if not environ[ self.remote_user_header ].count( '@' ): if self.maildomain is not None: diff -r 5e5c0cf930bb96bf8af0bd19daad1a999b40c374 -r 7a889340aba7e078d31963aa99139c7ddaea07c2 lib/galaxy/webapps/galaxy/buildapp.py --- a/lib/galaxy/webapps/galaxy/buildapp.py +++ b/lib/galaxy/webapps/galaxy/buildapp.py @@ -503,8 +503,8 @@ app = RemoteUser( app, maildomain = conf.get( 'remote_user_maildomain', None ), display_servers = util.listify( conf.get( 'display_servers', '' ) ), admin_users = conf.get( 'admin_users', '' ).split( ',' ), - remote_user_header = conf.get( 'remote_user_header', 'HTTP_REMOTE_USER' ) ) - log.debug( "Enabling 'remote user' middleware" ) + remote_user_header = conf.get( 'remote_user_header', 'HTTP_REMOTE_USER' ), + remote_user_secret_header = conf.get('remote_user_secret', None) ) # The recursive middleware allows for including requests in other # requests or forwarding of requests, all on the server side. if asbool(conf.get('use_recursive', True)): diff -r 5e5c0cf930bb96bf8af0bd19daad1a999b40c374 -r 7a889340aba7e078d31963aa99139c7ddaea07c2 lib/galaxy/webapps/tool_shed/buildapp.py --- a/lib/galaxy/webapps/tool_shed/buildapp.py +++ b/lib/galaxy/webapps/tool_shed/buildapp.py @@ -157,7 +157,8 @@ from galaxy.webapps.tool_shed.framework.middleware.remoteuser import RemoteUser app = RemoteUser( app, maildomain = conf.get( 'remote_user_maildomain', None ), display_servers = util.listify( conf.get( 'display_servers', '' ) ), - admin_users = conf.get( 'admin_users', '' ).split( ',' ) ) + admin_users = conf.get( 'admin_users', '' ).split( ',' ), + remote_user_secret_header = conf.get('remote_user_secret', None) ) log.debug( "Enabling 'remote user' middleware" ) # The recursive middleware allows for including requests in other # requests or forwarding of requests, all on the server side. diff -r 5e5c0cf930bb96bf8af0bd19daad1a999b40c374 -r 7a889340aba7e078d31963aa99139c7ddaea07c2 lib/galaxy/webapps/tool_shed/config.py --- a/lib/galaxy/webapps/tool_shed/config.py +++ b/lib/galaxy/webapps/tool_shed/config.py @@ -83,6 +83,7 @@ self.remote_user_maildomain = kwargs.get( "remote_user_maildomain", None ) self.remote_user_header = kwargs.get( "remote_user_header", 'HTTP_REMOTE_USER' ) self.remote_user_logout_href = kwargs.get( "remote_user_logout_href", None ) + self.remote_user_secret = kwargs.get( "remote_user_secret", None ) self.require_login = string_as_bool( kwargs.get( "require_login", "False" ) ) self.allow_user_creation = string_as_bool( kwargs.get( "allow_user_creation", "True" ) ) self.allow_user_deletion = string_as_bool( kwargs.get( "allow_user_deletion", "False" ) ) diff -r 5e5c0cf930bb96bf8af0bd19daad1a999b40c374 -r 7a889340aba7e078d31963aa99139c7ddaea07c2 lib/galaxy/webapps/tool_shed/framework/middleware/remoteuser.py --- a/lib/galaxy/webapps/tool_shed/framework/middleware/remoteuser.py +++ b/lib/galaxy/webapps/tool_shed/framework/middleware/remoteuser.py @@ -3,6 +3,7 @@ """ import socket +from galaxy.util import safe_str_cmp errorpage = """ <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> @@ -36,11 +37,13 @@ """ class RemoteUser( object ): - def __init__( self, app, maildomain=None, display_servers=None, admin_users=None ): + def __init__( self, app, maildomain=None, display_servers=None, admin_users=None, remote_user_secret_header=None ): self.app = app self.maildomain = maildomain self.display_servers = display_servers or [] self.admin_users = admin_users or [] + self.config_secret_header = remote_user_secret_header + def __call__( self, environ, start_response ): environ[ 'webapp' ] = 'tool_shed' # Allow display servers @@ -53,6 +56,34 @@ if host in self.display_servers: environ[ 'HTTP_REMOTE_USER' ] = 'remote_display_server@%s' % ( self.maildomain or 'example.org' ) return self.app( environ, start_response ) + + # If the secret header is enabled, we expect upstream to send along some key + # in HTTP_GX_SECRET, so we'll need to compare that here to the correct value + # + # This is not an ideal location for this function. The reason being + # that because this check is done BEFORE the REMOTE_USER check, it is + # possible to attack the GX_SECRET key without having correct + # credentials. However, that's why it's not "ideal", but it is "good + # enough". The only users able to exploit this are ones with access to + # the local system (unless Galaxy is listening on 0.0.0.0....). It + # seems improbable that an attacker with access to the server hosting + # Galaxy would not have acess to Galaxy itself, and be attempting to + # attack the system + if self.config_secret_header is not None: + if not safe_str_cmp(environ.get('HTTP_GX_SECRET'), self.config_secret_header): + title = "Access to Galaxy is denied" + message = """ + Galaxy is configured to authenticate users via an external + method (such as HTTP authentication in Apache), but an + incorrect shared secret key was provided by the + upstream (proxy) server.</p> + <p>Please contact your local Galaxy administrator. The + variable <code>remote_user_secret</code> and + <code>GX_SECRET</code> header must be set before you may + access Galaxy. + """ + return self.error( start_response, title, message ) + # Apache sets REMOTE_USER to the string '(null)' when using the Rewrite* method for passing REMOTE_USER and a user is # un-authenticated. Any other possible values need to go here as well. path_info = environ.get('PATH_INFO', '') @@ -88,6 +119,7 @@ <p>Contact your local Galaxy tool shed administrator. """ return self.error( start_response, title, message ) + def error( self, start_response, title="Access denied", message="Contact your local Galaxy tool shed administrator." ): start_response( '403 Forbidden', [('Content-type', 'text/html')] ) return [errorpage % (title, message)] https://bitbucket.org/galaxy/galaxy-central/commits/b7deaa9fb8b7/ Changeset: b7deaa9fb8b7 User: erasche2 Date: 2014-12-18 19:43:00+00:00 Summary: Fixed typos in documentation Affected #: 2 files diff -r 7a889340aba7e078d31963aa99139c7ddaea07c2 -r b7deaa9fb8b7cadd141fd07b2c50654716c88720 lib/galaxy/web/framework/middleware/remoteuser.py --- a/lib/galaxy/web/framework/middleware/remoteuser.py +++ b/lib/galaxy/web/framework/middleware/remoteuser.py @@ -72,7 +72,7 @@ # enough". The only users able to exploit this are ones with access to # the local system (unless Galaxy is listening on 0.0.0.0....). It # seems improbable that an attacker with access to the server hosting - # Galaxy would not have acess to Galaxy itself, and be attempting to + # Galaxy would not have access to Galaxy itself, and be attempting to # attack the system if self.config_secret_header is not None: if not safe_str_cmp(environ.get('HTTP_GX_SECRET'), self.config_secret_header): diff -r 7a889340aba7e078d31963aa99139c7ddaea07c2 -r b7deaa9fb8b7cadd141fd07b2c50654716c88720 lib/galaxy/webapps/tool_shed/framework/middleware/remoteuser.py --- a/lib/galaxy/webapps/tool_shed/framework/middleware/remoteuser.py +++ b/lib/galaxy/webapps/tool_shed/framework/middleware/remoteuser.py @@ -67,7 +67,7 @@ # enough". The only users able to exploit this are ones with access to # the local system (unless Galaxy is listening on 0.0.0.0....). It # seems improbable that an attacker with access to the server hosting - # Galaxy would not have acess to Galaxy itself, and be attempting to + # Galaxy would not have access to Galaxy itself, and be attempting to # attack the system if self.config_secret_header is not None: if not safe_str_cmp(environ.get('HTTP_GX_SECRET'), self.config_secret_header): https://bitbucket.org/galaxy/galaxy-central/commits/ac1859e27eb0/ Changeset: ac1859e27eb0 User: jmchilton Date: 2015-01-01 19:16:50+00:00 Summary: Merged in erasche2/galaxy-central2 (pull request #619) Allow for shared secret between Galaxy and upstream proxies Affected #: 9 files diff -r 84d5a72790735ae4da03f27735c52182bb925d9b -r ac1859e27eb0217ae5dabd7495a3b9314f65a1f7 config/galaxy.ini.sample --- a/config/galaxy.ini.sample +++ b/config/galaxy.ini.sample @@ -713,6 +713,13 @@ # *not* include 'HTTP_' at the beginning of the header name. #remote_user_header = HTTP_REMOTE_USER +# If use_remote_user is enabled, anyone who can log in to the Galaxy host may +# impersonate any other user by simply sending the appropriate header. Thus a +# secret shared between the upstream proxy server, and Galaxy is required. +# If anyone other than the Galaxy user is using the server, then apache/nginx should +# pass a value in the header 'GX_SECRET' that is identical the one below +#remote_user_secret = USING THE DEFAULT IS NOT SECURE! + # If use_remote_user is enabled, you can set this to a URL that will log your # users out. #remote_user_logout_href = None diff -r 84d5a72790735ae4da03f27735c52182bb925d9b -r ac1859e27eb0217ae5dabd7495a3b9314f65a1f7 config/tool_shed.ini.sample --- a/config/tool_shed.ini.sample +++ b/config/tool_shed.ini.sample @@ -67,6 +67,13 @@ # https://wiki.galaxyproject.org/Admin/Config/ApacheProxy #use_remote_user = False +# If use_remote_user is enabled, anyone who can log in to the Galaxy host may +# impersonate any other user by simply sending the appropriate header. Thus a +# secret shared between the upstream proxy server, and Galaxy is required. +# If anyone other than the Galaxy user is using the server, then apache/nginx should +# pass a value in the header 'GX_SECRET' that is identical the one below +#remote_user_secret = changethisinproductiontoo + # Configuration for debugging middleware debug = true use_lint = false diff -r 84d5a72790735ae4da03f27735c52182bb925d9b -r ac1859e27eb0217ae5dabd7495a3b9314f65a1f7 lib/galaxy/config.py --- a/lib/galaxy/config.py +++ b/lib/galaxy/config.py @@ -133,6 +133,7 @@ self.remote_user_maildomain = kwargs.get( "remote_user_maildomain", None ) self.remote_user_header = kwargs.get( "remote_user_header", 'HTTP_REMOTE_USER' ) self.remote_user_logout_href = kwargs.get( "remote_user_logout_href", None ) + self.remote_user_secret = kwargs.get( "remote_user_secret", None ) self.require_login = string_as_bool( kwargs.get( "require_login", "False" ) ) self.allow_user_creation = string_as_bool( kwargs.get( "allow_user_creation", "True" ) ) self.allow_user_deletion = string_as_bool( kwargs.get( "allow_user_deletion", "False" ) ) diff -r 84d5a72790735ae4da03f27735c52182bb925d9b -r ac1859e27eb0217ae5dabd7495a3b9314f65a1f7 lib/galaxy/util/__init__.py --- a/lib/galaxy/util/__init__.py +++ b/lib/galaxy/util/__init__.py @@ -1119,6 +1119,8 @@ def safe_str_cmp(a, b): + """safely compare two strings in a timing-attack-resistant manner + """ if len(a) != len(b): return False rv = 0 diff -r 84d5a72790735ae4da03f27735c52182bb925d9b -r ac1859e27eb0217ae5dabd7495a3b9314f65a1f7 lib/galaxy/web/framework/middleware/remoteuser.py --- a/lib/galaxy/web/framework/middleware/remoteuser.py +++ b/lib/galaxy/web/framework/middleware/remoteuser.py @@ -3,6 +3,7 @@ """ import socket +from galaxy.util import safe_str_cmp errorpage = """ <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> @@ -37,12 +38,13 @@ class RemoteUser( object ): - def __init__( self, app, maildomain=None, display_servers=None, admin_users=None, remote_user_header=None ): + def __init__( self, app, maildomain=None, display_servers=None, admin_users=None, remote_user_header=None, remote_user_secret_header=None ): self.app = app self.maildomain = maildomain self.display_servers = display_servers or [] self.admin_users = admin_users or [] self.remote_user_header = remote_user_header or 'HTTP_REMOTE_USER' + self.config_secret_header = remote_user_secret_header def __call__( self, environ, start_response ): # Allow display servers @@ -59,6 +61,34 @@ # Rewrite* method for passing REMOTE_USER and a user is # un-authenticated. Any other possible values need to go here as well. path_info = environ.get('PATH_INFO', '') + + # If the secret header is enabled, we expect upstream to send along some key + # in HTTP_GX_SECRET, so we'll need to compare that here to the correct value + # + # This is not an ideal location for this function. The reason being + # that because this check is done BEFORE the REMOTE_USER check, it is + # possible to attack the GX_SECRET key without having correct + # credentials. However, that's why it's not "ideal", but it is "good + # enough". The only users able to exploit this are ones with access to + # the local system (unless Galaxy is listening on 0.0.0.0....). It + # seems improbable that an attacker with access to the server hosting + # Galaxy would not have access to Galaxy itself, and be attempting to + # attack the system + if self.config_secret_header is not None: + if not safe_str_cmp(environ.get('HTTP_GX_SECRET'), self.config_secret_header): + title = "Access to Galaxy is denied" + message = """ + Galaxy is configured to authenticate users via an external + method (such as HTTP authentication in Apache), but an + incorrect shared secret key was provided by the + upstream (proxy) server.</p> + <p>Please contact your local Galaxy administrator. The + variable <code>remote_user_secret</code> and + <code>GX_SECRET</code> header must be set before you may + access Galaxy. + """ + return self.error( start_response, title, message ) + if not environ.get(self.remote_user_header, '(null)').startswith('(null)'): if not environ[ self.remote_user_header ].count( '@' ): if self.maildomain is not None: diff -r 84d5a72790735ae4da03f27735c52182bb925d9b -r ac1859e27eb0217ae5dabd7495a3b9314f65a1f7 lib/galaxy/webapps/galaxy/buildapp.py --- a/lib/galaxy/webapps/galaxy/buildapp.py +++ b/lib/galaxy/webapps/galaxy/buildapp.py @@ -503,8 +503,8 @@ app = RemoteUser( app, maildomain = conf.get( 'remote_user_maildomain', None ), display_servers = util.listify( conf.get( 'display_servers', '' ) ), admin_users = conf.get( 'admin_users', '' ).split( ',' ), - remote_user_header = conf.get( 'remote_user_header', 'HTTP_REMOTE_USER' ) ) - log.debug( "Enabling 'remote user' middleware" ) + remote_user_header = conf.get( 'remote_user_header', 'HTTP_REMOTE_USER' ), + remote_user_secret_header = conf.get('remote_user_secret', None) ) # The recursive middleware allows for including requests in other # requests or forwarding of requests, all on the server side. if asbool(conf.get('use_recursive', True)): diff -r 84d5a72790735ae4da03f27735c52182bb925d9b -r ac1859e27eb0217ae5dabd7495a3b9314f65a1f7 lib/galaxy/webapps/tool_shed/buildapp.py --- a/lib/galaxy/webapps/tool_shed/buildapp.py +++ b/lib/galaxy/webapps/tool_shed/buildapp.py @@ -157,7 +157,8 @@ from galaxy.webapps.tool_shed.framework.middleware.remoteuser import RemoteUser app = RemoteUser( app, maildomain = conf.get( 'remote_user_maildomain', None ), display_servers = util.listify( conf.get( 'display_servers', '' ) ), - admin_users = conf.get( 'admin_users', '' ).split( ',' ) ) + admin_users = conf.get( 'admin_users', '' ).split( ',' ), + remote_user_secret_header = conf.get('remote_user_secret', None) ) log.debug( "Enabling 'remote user' middleware" ) # The recursive middleware allows for including requests in other # requests or forwarding of requests, all on the server side. diff -r 84d5a72790735ae4da03f27735c52182bb925d9b -r ac1859e27eb0217ae5dabd7495a3b9314f65a1f7 lib/galaxy/webapps/tool_shed/config.py --- a/lib/galaxy/webapps/tool_shed/config.py +++ b/lib/galaxy/webapps/tool_shed/config.py @@ -73,6 +73,7 @@ self.remote_user_maildomain = kwargs.get( "remote_user_maildomain", None ) self.remote_user_header = kwargs.get( "remote_user_header", 'HTTP_REMOTE_USER' ) self.remote_user_logout_href = kwargs.get( "remote_user_logout_href", None ) + self.remote_user_secret = kwargs.get( "remote_user_secret", None ) self.require_login = string_as_bool( kwargs.get( "require_login", "False" ) ) self.allow_user_creation = string_as_bool( kwargs.get( "allow_user_creation", "True" ) ) self.allow_user_deletion = string_as_bool( kwargs.get( "allow_user_deletion", "False" ) ) diff -r 84d5a72790735ae4da03f27735c52182bb925d9b -r ac1859e27eb0217ae5dabd7495a3b9314f65a1f7 lib/galaxy/webapps/tool_shed/framework/middleware/remoteuser.py --- a/lib/galaxy/webapps/tool_shed/framework/middleware/remoteuser.py +++ b/lib/galaxy/webapps/tool_shed/framework/middleware/remoteuser.py @@ -3,6 +3,7 @@ """ import socket +from galaxy.util import safe_str_cmp errorpage = """ <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> @@ -36,11 +37,13 @@ """ class RemoteUser( object ): - def __init__( self, app, maildomain=None, display_servers=None, admin_users=None ): + def __init__( self, app, maildomain=None, display_servers=None, admin_users=None, remote_user_secret_header=None ): self.app = app self.maildomain = maildomain self.display_servers = display_servers or [] self.admin_users = admin_users or [] + self.config_secret_header = remote_user_secret_header + def __call__( self, environ, start_response ): environ[ 'webapp' ] = 'tool_shed' # Allow display servers @@ -53,6 +56,34 @@ if host in self.display_servers: environ[ 'HTTP_REMOTE_USER' ] = 'remote_display_server@%s' % ( self.maildomain or 'example.org' ) return self.app( environ, start_response ) + + # If the secret header is enabled, we expect upstream to send along some key + # in HTTP_GX_SECRET, so we'll need to compare that here to the correct value + # + # This is not an ideal location for this function. The reason being + # that because this check is done BEFORE the REMOTE_USER check, it is + # possible to attack the GX_SECRET key without having correct + # credentials. However, that's why it's not "ideal", but it is "good + # enough". The only users able to exploit this are ones with access to + # the local system (unless Galaxy is listening on 0.0.0.0....). It + # seems improbable that an attacker with access to the server hosting + # Galaxy would not have access to Galaxy itself, and be attempting to + # attack the system + if self.config_secret_header is not None: + if not safe_str_cmp(environ.get('HTTP_GX_SECRET'), self.config_secret_header): + title = "Access to Galaxy is denied" + message = """ + Galaxy is configured to authenticate users via an external + method (such as HTTP authentication in Apache), but an + incorrect shared secret key was provided by the + upstream (proxy) server.</p> + <p>Please contact your local Galaxy administrator. The + variable <code>remote_user_secret</code> and + <code>GX_SECRET</code> header must be set before you may + access Galaxy. + """ + return self.error( start_response, title, message ) + # Apache sets REMOTE_USER to the string '(null)' when using the Rewrite* method for passing REMOTE_USER and a user is # un-authenticated. Any other possible values need to go here as well. path_info = environ.get('PATH_INFO', '') @@ -88,6 +119,7 @@ <p>Contact your local Galaxy tool shed administrator. """ return self.error( start_response, title, message ) + def error( self, start_response, title="Access denied", message="Contact your local Galaxy tool shed administrator." ): start_response( '403 Forbidden', [('Content-type', 'text/html')] ) return [errorpage % (title, message)] Repository URL: https://bitbucket.org/galaxy/galaxy-central/ -- This is a commit notification from bitbucket.org. You are receiving this because you have the service enabled, addressing the recipient of this email.