Merge "Parameter to tune mutual authentication in kerberos"
This commit is contained in:
commit
791b7944d4
@ -31,12 +31,19 @@ from keystoneauth1.identity import v3
|
||||
from keystoneauth1.identity.v3 import federation
|
||||
|
||||
|
||||
def _requests_auth():
|
||||
# NOTE(jamielennox): request_kerberos.OPTIONAL allows the plugin to accept
|
||||
# unencrypted error messages where we can't verify the origin of the error
|
||||
# because we aren't authenticated.
|
||||
def _mutual_auth(value):
|
||||
if value is None:
|
||||
return requests_kerberos.OPTIONAL
|
||||
return {
|
||||
'required': requests_kerberos.REQUIRED,
|
||||
'optional': requests_kerberos.OPTIONAL,
|
||||
'disabled': requests_kerberos.DISABLED,
|
||||
}.get(value.lower(), requests_kerberos.OPTIONAL)
|
||||
|
||||
|
||||
def _requests_auth(mutual_authentication):
|
||||
return requests_kerberos.HTTPKerberosAuth(
|
||||
mutual_authentication=requests_kerberos.OPTIONAL)
|
||||
mutual_authentication=_mutual_auth(mutual_authentication))
|
||||
|
||||
|
||||
def _dependency_check():
|
||||
@ -51,7 +58,7 @@ packages. These can be installed with::
|
||||
|
||||
class KerberosMethod(v3.AuthMethod):
|
||||
|
||||
_method_parameters = []
|
||||
_method_parameters = ['mutual_auth']
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
_dependency_check()
|
||||
@ -60,7 +67,7 @@ class KerberosMethod(v3.AuthMethod):
|
||||
def get_auth_data(self, session, auth, headers, request_kwargs, **kwargs):
|
||||
# NOTE(jamielennox): request_kwargs is passed as a kwarg however it is
|
||||
# required and always present when called from keystoneclient.
|
||||
request_kwargs['requests_auth'] = _requests_auth()
|
||||
request_kwargs['requests_auth'] = _requests_auth(self.mutual_auth)
|
||||
return 'kerberos', {}
|
||||
|
||||
|
||||
@ -75,15 +82,16 @@ class MappedKerberos(federation.FederationBaseAuth):
|
||||
use the standard keystone auth process to scope that to any given project.
|
||||
"""
|
||||
|
||||
def __init__(self, auth_url, identity_provider, protocol, **kwargs):
|
||||
def __init__(self, auth_url, identity_provider, protocol,
|
||||
mutual_auth=None, **kwargs):
|
||||
_dependency_check()
|
||||
|
||||
self.mutual_auth = mutual_auth
|
||||
super(MappedKerberos, self).__init__(auth_url, identity_provider,
|
||||
protocol, **kwargs)
|
||||
|
||||
def get_unscoped_auth_ref(self, session, **kwargs):
|
||||
resp = session.get(self.federated_token_url,
|
||||
requests_auth=_requests_auth(),
|
||||
requests_auth=_requests_auth(self.mutual_auth),
|
||||
authenticated=False)
|
||||
|
||||
return access.create(body=resp.json(), resp=resp)
|
||||
|
@ -10,6 +10,7 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from keystoneauth1 import exceptions
|
||||
from keystoneauth1.extras import kerberos
|
||||
from keystoneauth1 import loading
|
||||
|
||||
@ -24,6 +25,29 @@ class Kerberos(loading.BaseV3Loader):
|
||||
def available(self):
|
||||
return kerberos.requests_kerberos is not None
|
||||
|
||||
def get_options(self):
|
||||
options = super(Kerberos, self).get_options()
|
||||
|
||||
options.extend([
|
||||
loading.Opt('mutual-auth',
|
||||
required=False,
|
||||
default='optional',
|
||||
help='Configures Kerberos Mutual Authentication'),
|
||||
])
|
||||
|
||||
return options
|
||||
|
||||
def load_from_options(self, **kwargs):
|
||||
if kwargs.get('mutual_auth'):
|
||||
value = kwargs.get('mutual_auth')
|
||||
if not (value.lower() in ['required', 'optional', 'disabled']):
|
||||
m = ('You need to provide a valid value for kerberos mutual '
|
||||
'authentication. It can be one of the following: '
|
||||
'(required, optional, disabled)')
|
||||
raise exceptions.OptionError(m)
|
||||
|
||||
return super(Kerberos, self).load_from_options(**kwargs)
|
||||
|
||||
|
||||
class MappedKerberos(loading.BaseFederationLoader):
|
||||
|
||||
@ -34,3 +58,26 @@ class MappedKerberos(loading.BaseFederationLoader):
|
||||
@property
|
||||
def available(self):
|
||||
return kerberos.requests_kerberos is not None
|
||||
|
||||
def get_options(self):
|
||||
options = super(MappedKerberos, self).get_options()
|
||||
|
||||
options.extend([
|
||||
loading.Opt('mutual-auth',
|
||||
required=False,
|
||||
default='optional',
|
||||
help='Configures Kerberos Mutual Authentication'),
|
||||
])
|
||||
|
||||
return options
|
||||
|
||||
def load_from_options(self, **kwargs):
|
||||
if kwargs.get('mutual_auth'):
|
||||
value = kwargs.get('mutual_auth')
|
||||
if not (value.lower() in ['required', 'optional', 'disabled']):
|
||||
m = ('You need to provide a valid value for kerberos mutual '
|
||||
'authentication. It can be one of the following: '
|
||||
'(required, optional, disabled)')
|
||||
raise exceptions.OptionError(m)
|
||||
|
||||
return super(MappedKerberos, self).load_from_options(**kwargs)
|
||||
|
@ -31,6 +31,7 @@ class FedKerbLoadingTests(test_utils.TestCase):
|
||||
'protocol',
|
||||
'trust-id',
|
||||
'auth-url',
|
||||
'mutual-auth',
|
||||
]
|
||||
|
||||
self.assertItemsEqual(allowed_opts, opts)
|
||||
|
@ -28,6 +28,7 @@ class KerberosLoadingTests(test_utils.TestCase):
|
||||
'project-domain-name',
|
||||
'trust-id',
|
||||
'auth-url',
|
||||
'mutual-auth',
|
||||
]
|
||||
|
||||
self.assertItemsEqual(allowed_opts, opts)
|
||||
|
@ -75,3 +75,59 @@ class TestMappedAuth(base.TestCase):
|
||||
|
||||
self.assertEqual(scoped_id, tok)
|
||||
self.assertEqual(scoped_body.project_id, proj)
|
||||
|
||||
def test_authenticate_with_mutual_authentication_required(self):
|
||||
self.kerberos_mock.mock_auth_success(url=self.token_url,
|
||||
method='GET')
|
||||
|
||||
scoped_id = uuid.uuid4().hex
|
||||
scoped_body = ks_fixture.V3Token()
|
||||
scoped_body.set_project_scope()
|
||||
|
||||
self.requests_mock.post(
|
||||
'%s/auth/tokens' % self.TEST_V3_URL,
|
||||
json=scoped_body,
|
||||
headers={'X-Subject-Token': scoped_id,
|
||||
'Content-Type': 'application/json'})
|
||||
|
||||
plugin = kerberos.MappedKerberos(
|
||||
auth_url=self.TEST_V3_URL, protocol=self.protocol,
|
||||
identity_provider=self.identity_provider,
|
||||
project_id=scoped_body.project_id,
|
||||
mutual_auth='required')
|
||||
|
||||
sess = session.Session()
|
||||
tok = plugin.get_token(sess)
|
||||
proj = plugin.get_project_id(sess)
|
||||
|
||||
self.assertEqual(scoped_id, tok)
|
||||
self.assertEqual(scoped_body.project_id, proj)
|
||||
self.assertEqual(self.kerberos_mock.called_auth_server, True)
|
||||
|
||||
def test_authenticate_with_mutual_authentication_disabled(self):
|
||||
self.kerberos_mock.mock_auth_success(url=self.token_url,
|
||||
method='GET')
|
||||
|
||||
scoped_id = uuid.uuid4().hex
|
||||
scoped_body = ks_fixture.V3Token()
|
||||
scoped_body.set_project_scope()
|
||||
|
||||
self.requests_mock.post(
|
||||
'%s/auth/tokens' % self.TEST_V3_URL,
|
||||
json=scoped_body,
|
||||
headers={'X-Subject-Token': scoped_id,
|
||||
'Content-Type': 'application/json'})
|
||||
|
||||
plugin = kerberos.MappedKerberos(
|
||||
auth_url=self.TEST_V3_URL, protocol=self.protocol,
|
||||
identity_provider=self.identity_provider,
|
||||
project_id=scoped_body.project_id,
|
||||
mutual_auth='disabled')
|
||||
|
||||
sess = session.Session()
|
||||
tok = plugin.get_token(sess)
|
||||
proj = plugin.get_project_id(sess)
|
||||
|
||||
self.assertEqual(scoped_id, tok)
|
||||
self.assertEqual(scoped_body.project_id, proj)
|
||||
self.assertEqual(self.kerberos_mock.called_auth_server, False)
|
||||
|
@ -36,3 +36,35 @@ class TestKerberosAuth(base.TestCase):
|
||||
self.requests_mock.last_request.headers['Authorization'])
|
||||
self.assertEqual(token_id, a.auth_ref.auth_token)
|
||||
self.assertEqual(token_id, token)
|
||||
|
||||
def test_authenticate_with_kerberos_mutual_authentication_required(self):
|
||||
token_id, token_body = self.kerberos_mock.mock_auth_success()
|
||||
|
||||
a = kerberos.Kerberos(self.TEST_ROOT_URL + 'v3',
|
||||
mutual_auth='required')
|
||||
s = session.Session(a)
|
||||
token = a.get_token(s)
|
||||
|
||||
self.assertRequestBody()
|
||||
self.assertEqual(
|
||||
self.kerberos_mock.challenge_header,
|
||||
self.requests_mock.last_request.headers['Authorization'])
|
||||
self.assertEqual(token_id, a.auth_ref.auth_token)
|
||||
self.assertEqual(token_id, token)
|
||||
self.assertEqual(self.kerberos_mock.called_auth_server, True)
|
||||
|
||||
def test_authenticate_with_kerberos_mutual_authentication_disabled(self):
|
||||
token_id, token_body = self.kerberos_mock.mock_auth_success()
|
||||
|
||||
a = kerberos.Kerberos(self.TEST_ROOT_URL + 'v3',
|
||||
mutual_auth='disabled')
|
||||
s = session.Session(a)
|
||||
token = a.get_token(s)
|
||||
|
||||
self.assertRequestBody()
|
||||
self.assertEqual(
|
||||
self.kerberos_mock.challenge_header,
|
||||
self.requests_mock.last_request.headers['Authorization'])
|
||||
self.assertEqual(token_id, a.auth_ref.auth_token)
|
||||
self.assertEqual(token_id, token)
|
||||
self.assertEqual(self.kerberos_mock.called_auth_server, False)
|
||||
|
@ -54,6 +54,7 @@ class KerberosMock(fixtures.Fixture):
|
||||
return self.challenge_header
|
||||
|
||||
def _authenticate_server(self, response):
|
||||
self.called_auth_server = True
|
||||
return response.headers.get('www-authenticate') == self.pass_header
|
||||
|
||||
def mock_auth_success(
|
||||
@ -67,6 +68,8 @@ class KerberosMock(fixtures.Fixture):
|
||||
if not token_body:
|
||||
token_body = ks_fixture.V3Token()
|
||||
|
||||
self.called_auth_server = False
|
||||
|
||||
response_list = [{'text': 'Fail',
|
||||
'status_code': 401,
|
||||
'headers': {'WWW-Authenticate': 'Negotiate'}},
|
||||
|
Loading…
Reference in New Issue
Block a user