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
|
from keystoneauth1.identity.v3 import federation
|
||||||
|
|
||||||
|
|
||||||
def _requests_auth():
|
def _mutual_auth(value):
|
||||||
# NOTE(jamielennox): request_kerberos.OPTIONAL allows the plugin to accept
|
if value is None:
|
||||||
# unencrypted error messages where we can't verify the origin of the error
|
return requests_kerberos.OPTIONAL
|
||||||
# because we aren't authenticated.
|
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(
|
return requests_kerberos.HTTPKerberosAuth(
|
||||||
mutual_authentication=requests_kerberos.OPTIONAL)
|
mutual_authentication=_mutual_auth(mutual_authentication))
|
||||||
|
|
||||||
|
|
||||||
def _dependency_check():
|
def _dependency_check():
|
||||||
@ -51,7 +58,7 @@ packages. These can be installed with::
|
|||||||
|
|
||||||
class KerberosMethod(v3.AuthMethod):
|
class KerberosMethod(v3.AuthMethod):
|
||||||
|
|
||||||
_method_parameters = []
|
_method_parameters = ['mutual_auth']
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
_dependency_check()
|
_dependency_check()
|
||||||
@ -60,7 +67,7 @@ class KerberosMethod(v3.AuthMethod):
|
|||||||
def get_auth_data(self, session, auth, headers, request_kwargs, **kwargs):
|
def get_auth_data(self, session, auth, headers, request_kwargs, **kwargs):
|
||||||
# NOTE(jamielennox): request_kwargs is passed as a kwarg however it is
|
# NOTE(jamielennox): request_kwargs is passed as a kwarg however it is
|
||||||
# required and always present when called from keystoneclient.
|
# 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', {}
|
return 'kerberos', {}
|
||||||
|
|
||||||
|
|
||||||
@ -75,15 +82,16 @@ class MappedKerberos(federation.FederationBaseAuth):
|
|||||||
use the standard keystone auth process to scope that to any given project.
|
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()
|
_dependency_check()
|
||||||
|
self.mutual_auth = mutual_auth
|
||||||
super(MappedKerberos, self).__init__(auth_url, identity_provider,
|
super(MappedKerberos, self).__init__(auth_url, identity_provider,
|
||||||
protocol, **kwargs)
|
protocol, **kwargs)
|
||||||
|
|
||||||
def get_unscoped_auth_ref(self, session, **kwargs):
|
def get_unscoped_auth_ref(self, session, **kwargs):
|
||||||
resp = session.get(self.federated_token_url,
|
resp = session.get(self.federated_token_url,
|
||||||
requests_auth=_requests_auth(),
|
requests_auth=_requests_auth(self.mutual_auth),
|
||||||
authenticated=False)
|
authenticated=False)
|
||||||
|
|
||||||
return access.create(body=resp.json(), resp=resp)
|
return access.create(body=resp.json(), resp=resp)
|
||||||
|
@ -10,6 +10,7 @@
|
|||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
|
from keystoneauth1 import exceptions
|
||||||
from keystoneauth1.extras import kerberos
|
from keystoneauth1.extras import kerberos
|
||||||
from keystoneauth1 import loading
|
from keystoneauth1 import loading
|
||||||
|
|
||||||
@ -24,6 +25,29 @@ class Kerberos(loading.BaseV3Loader):
|
|||||||
def available(self):
|
def available(self):
|
||||||
return kerberos.requests_kerberos is not None
|
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):
|
class MappedKerberos(loading.BaseFederationLoader):
|
||||||
|
|
||||||
@ -34,3 +58,26 @@ class MappedKerberos(loading.BaseFederationLoader):
|
|||||||
@property
|
@property
|
||||||
def available(self):
|
def available(self):
|
||||||
return kerberos.requests_kerberos is not None
|
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',
|
'protocol',
|
||||||
'trust-id',
|
'trust-id',
|
||||||
'auth-url',
|
'auth-url',
|
||||||
|
'mutual-auth',
|
||||||
]
|
]
|
||||||
|
|
||||||
self.assertItemsEqual(allowed_opts, opts)
|
self.assertItemsEqual(allowed_opts, opts)
|
||||||
|
@ -28,6 +28,7 @@ class KerberosLoadingTests(test_utils.TestCase):
|
|||||||
'project-domain-name',
|
'project-domain-name',
|
||||||
'trust-id',
|
'trust-id',
|
||||||
'auth-url',
|
'auth-url',
|
||||||
|
'mutual-auth',
|
||||||
]
|
]
|
||||||
|
|
||||||
self.assertItemsEqual(allowed_opts, opts)
|
self.assertItemsEqual(allowed_opts, opts)
|
||||||
|
@ -75,3 +75,59 @@ class TestMappedAuth(base.TestCase):
|
|||||||
|
|
||||||
self.assertEqual(scoped_id, tok)
|
self.assertEqual(scoped_id, tok)
|
||||||
self.assertEqual(scoped_body.project_id, proj)
|
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.requests_mock.last_request.headers['Authorization'])
|
||||||
self.assertEqual(token_id, a.auth_ref.auth_token)
|
self.assertEqual(token_id, a.auth_ref.auth_token)
|
||||||
self.assertEqual(token_id, 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
|
return self.challenge_header
|
||||||
|
|
||||||
def _authenticate_server(self, response):
|
def _authenticate_server(self, response):
|
||||||
|
self.called_auth_server = True
|
||||||
return response.headers.get('www-authenticate') == self.pass_header
|
return response.headers.get('www-authenticate') == self.pass_header
|
||||||
|
|
||||||
def mock_auth_success(
|
def mock_auth_success(
|
||||||
@ -67,6 +68,8 @@ class KerberosMock(fixtures.Fixture):
|
|||||||
if not token_body:
|
if not token_body:
|
||||||
token_body = ks_fixture.V3Token()
|
token_body = ks_fixture.V3Token()
|
||||||
|
|
||||||
|
self.called_auth_server = False
|
||||||
|
|
||||||
response_list = [{'text': 'Fail',
|
response_list = [{'text': 'Fail',
|
||||||
'status_code': 401,
|
'status_code': 401,
|
||||||
'headers': {'WWW-Authenticate': 'Negotiate'}},
|
'headers': {'WWW-Authenticate': 'Negotiate'}},
|
||||||
|
Loading…
Reference in New Issue
Block a user