1 new commit in galaxy-central: 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.