Here's the low-down on authentication in Autotest:

Django has an authentication middleware module.  It has its own
database of user/pass info.  It allows you to check if requests are
authenticated and return a login page if not.

We have that module enabled, because the we use the Django built-in
admin interface, which depends on it.  But we don't really let it do
authentication.  We manually bypass it.

They key to this is the frontend/apache_auth.py module, which contains
the class ApacheAuthMiddleware.  This is enabled in the default
frontend/settings.py in SVN (see MIDDLEWARE_CLASSES in that file).
That middleware essentially assumes that Apache has already authorized
the request and automatically authenticates the user with Django's
auth system.  More precisely, ApacheAuthMiddleware (plus its
superclass, GetApacheUserMiddleware) does this:

* look for a username in the REMOTE_USER header.  This is where Apache
puts the authenticated user when it authenticates a request.
* if no username is found there, look for a username in the
HTTP_AUTHORIZATION header, which comes directly from the
"Authorization:" header on the HTTP request.  This request header
normally would contain authorization info for an auth-protected web
site, but for an unauthenticated request, the server has no idea who's
making the request.  So we hijack this header in the CLI to tell the
server what user is making the request.
* if no username is found there, just default to "debug_user".  That's
what you get when making requests to an unauthenticated Apache server
from the web client (which doesn't stick the username in the
Authorization header), or any other custom client besides the CLI that
doesn't include the Authorization header.

And then we have a Django authentication backend, SimpleAuthBackend
(again specified in settings.py) which does this:
* create a Django User object for the given username, if one doesn't
exist (this is in the auth_user table)
* create an AFE user object, if one doesn't exist (we have our own
User model, in the afe_users table)
* assume the User is authenticated (no password checking or anything)
and log them in (which happens automatically by just returning the
user and not throwing any exception)

I know this sounds unnecessarily complicated.  The key issue that we
want to use the Django admin interface, and that depends on the Django
authentication middleware being used, so we have to ensure that
middleware has a logged-in user without actually relying it on its own
authentication mechanisms (since we defer the actual authentication
responsibility to Apache).  So we have all this nonsense in place to
take the username reported by Apache and trick Django into thinking
that user authenticated through its own system.

If we were to stop using the builtin admin interface app, we could get
rid of all this plumbing for the Django auth system.  Incidentally,
that's also the reason we still have to run syncdb, and the reason
have to have custom code in frontend/afe/management.py, and the reason
we have to deal with the django_sessions table.  Lots of junk would go
away if we replaced the Django admin app with our own admin app.

I hope that made some sense.  I'm happy to clarify if you have
questions.  And then maybe someone can put this into a nice wiki
page...

Steve


On Mon, Feb 1, 2010 at 3:44 PM, Steve Howard <[email protected]> wrote:
> On Mon, Feb 1, 2010 at 3:34 PM, Martin Bligh <[email protected]> wrote:
>> On Mon, Feb 1, 2010 at 3:28 PM, Steve Howard <[email protected]> wrote:
>>> Remove /noauth/ usage in CLI.  Extracted the bit of the CLI that generates 
>>> authorization headers into a function with a site override.
>>>
>>> If you have site-specific authorization in place on your Autotest web 
>>> service, you'll need to write a site-specific function to generate 
>>> authorization info to continue using the CLI.  I'll send a patch in a week 
>>> or so to remove the /noauth/ endpoint entirely.
>>
>> What happens if you don't have a site-specific function, and use the default
>> auth?
>
> Should just keep working like it does now.  CLI commands will be
> attributed to your user, since the CLI by default includes your
> username in the authorization header.  Web interface will continue to
> show up as debug_user, unchanged.  Not sure exactly what auth systems
> if any people have put into place out there on Autotest servers, I've
> never heard anyone ask about it as far as I can remember.
>
> Steve
>
>
>>
>>> Let me know if you have any questions or feedback, I'm happy to help.
>>>
>>> Signed-off-by: Steve Howard <[email protected]>
>>>
>>> --- autotest/cli/cli_mock.py    2010-02-01 11:06:34.000000000 -0800
>>> +++ autotest/cli/cli_mock.py    2010-02-01 11:06:34.000000000 -0800
>>> @@ -32,6 +32,11 @@
>>>         self.god.stub_class_method(rpc.afe_comm, 'run')
>>>         self.god.stub_function(sys, 'exit')
>>>
>>> +        def stub_authorization_headers(*args, **kwargs):
>>> +            return {}
>>> +        self.god.stub_with(rpc, 'authorization_headers',
>>> +                           stub_authorization_headers)
>>> +
>>>
>>>     def tearDown(self):
>>>         super(cli_unittest, self).tearDown()
>>> --- autotest/cli/job_unittest.py        2010-02-01 11:06:34.000000000 -0800
>>> +++ autotest/cli/job_unittest.py        2010-02-01 11:06:34.000000000 -0800
>>> @@ -15,8 +15,6 @@
>>>  class job_unittest(cli_mock.cli_unittest):
>>>     def setUp(self):
>>>         super(job_unittest, self).setUp()
>>> -        self.god.stub_function(getpass, 'getuser')
>>> -        getpass.getuser.expect_call().and_return('user0')
>>>         self.values = copy.deepcopy(self.values_template)
>>>
>>>     results = [{u'status_counts': {u'Aborted': 1},
>>> @@ -103,6 +101,7 @@
>>>
>>>  class job_list_unittest(job_unittest):
>>>     def test_job_list_jobs(self):
>>> +        self.god.stub_function(getpass, 'getuser')
>>>         getpass.getuser.expect_call().and_return('user0')
>>>         self.run_cmd(argv=['atest', 'job', 'list', '--ignore_site_file'],
>>>                      rpcs=[('get_jobs_summary', {'owner': 'user0',
>>> --- autotest/cli/rpc.py 2010-02-01 11:06:34.000000000 -0800
>>> +++ autotest/cli/rpc.py 2010-02-01 11:06:34.000000000 -0800
>>> @@ -5,12 +5,12 @@
>>>  import os, getpass
>>>  from autotest_lib.frontend.afe import rpc_client_lib
>>>  from autotest_lib.frontend.afe.json_rpc import proxy
>>> -from autotest_lib.client.common_lib import global_config
>>> +from autotest_lib.client.common_lib import global_config, utils
>>>
>>>  GLOBAL_CONFIG = global_config.global_config
>>>  DEFAULT_SERVER = 'autotest'
>>> -AFE_RPC_PATH = '/afe/server/noauth/rpc/'
>>> -TKO_RPC_PATH = '/new_tko/server/noauth/rpc/'
>>> +AFE_RPC_PATH = '/afe/server/rpc/'
>>> +TKO_RPC_PATH = '/new_tko/server/rpc/'
>>>
>>>
>>>  def get_autotest_server(web_server=None):
>>> @@ -29,6 +29,20 @@
>>>     return web_server
>>>
>>>
>>> +def base_authorization_headers(username, server):
>>> +    if not username:
>>> +        if 'AUTOTEST_USER' in os.environ:
>>> +            username = os.environ['AUTOTEST_USER']
>>> +        else:
>>> +            username = getpass.getuser()
>>> +    return {'AUTHORIZATION' : username}
>>> +
>>> +
>>> +authorization_headers = utils.import_site_function(
>>> +        __file__, 'autotest_lib.cli.site_rpc', 'authorization_headers',
>>> +        base_authorization_headers)
>>> +
>>> +
>>>  class rpc_comm(object):
>>>     """Shared AFE/TKO RPC class stuff"""
>>>     def __init__(self, web_server, rpc_path, username):
>>> @@ -40,13 +54,7 @@
>>>     def _connect(self, rpc_path):
>>>         # This does not fail even if the address is wrong.
>>>         # We need to wait for an actual RPC to fail
>>> -        if self.username:
>>> -            username = self.username
>>> -        elif 'AUTOTEST_USER' in os.environ:
>>> -            username = os.environ['AUTOTEST_USER']
>>> -        else:
>>> -            username = getpass.getuser()
>>> -        headers = {'AUTHORIZATION' : username}
>>> +        headers = authorization_headers(self.username, self.web_server)
>>>         rpc_server = self.web_server + rpc_path
>>>         return rpc_client_lib.get_proxy(rpc_server, headers=headers)
>>> _______________________________________________
>>> Autotest mailing list
>>> [email protected]
>>> http://test.kernel.org/cgi-bin/mailman/listinfo/autotest
>>>
>>
>
_______________________________________________
Autotest mailing list
[email protected]
http://test.kernel.org/cgi-bin/mailman/listinfo/autotest

Reply via email to