Implement swauth

This change implements the alternative authentication system,
swauth in addition to adding an action to add users to swauth

Change-Id: Ib752cd3a2a58f6c8cb06119c6be595cfc07ddc9f
This commit is contained in:
Chris MacNaughton 2016-09-08 11:08:47 -04:00
parent 2a3b0857b6
commit ce3f15310e
14 changed files with 247 additions and 14 deletions

View File

@ -1,3 +1,21 @@
add-user:
description: |
Add a user to swauth.
This adds a given user / pass to swauth. Auth-type must be set to swauth.
params:
account:
type: string
description: account to add this user to
username:
type: string
description: username for the newly created user
password:
type: string
description: password for the newly created user
required:
- account
- username
- password
pause:
description: |
Pause swift-proxy services.

1
actions/add-user Symbolic link
View File

@ -0,0 +1 @@
add_user.py

70
actions/add_user.py Executable file
View File

@ -0,0 +1,70 @@
#!/usr/bin/python
#
# Copyright 2016 Canonical Ltd
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from subprocess import (
check_call,
CalledProcessError
)
from charmhelpers.core.hookenv import (
action_get,
config,
action_set,
action_fail,
leader_get,
log,
)
from lib.swift_utils import (
try_initialize_swauth,
)
from charmhelpers.contrib.hahelpers.cluster import (
determine_api_port,
)
def add_user():
"""Add a swauth user to swift."""
if config('auth-type') == 'swauth':
try_initialize_swauth()
account = action_get('account')
username = action_get('username')
password = action_get('password')
bind_port = config('bind-port')
bind_port = determine_api_port(bind_port, singlenode_mode=True)
success = True
try:
check_call([
"swauth-add-user",
"-A", "http://localhost:{}/auth/".format(bind_port),
"-K", leader_get('swauth-admin-key'),
"-a", account, username, password])
except CalledProcessError as e:
success = False
log("Has a problem adding user: {}".format(e.output))
action_fail(
"Adding user {} failed with: \"{}\""
.format(username, e.message))
if success:
message = "Successfully added the user {}".format(username)
action_set({
'add-user.result': 'Success',
'add-user.message': message,
})
if __name__ == '__main__':
add_user()

View File

@ -110,7 +110,11 @@ options:
auth-type:
default: tempauth
type: string
description: Auth method to use, tempauth or keystone
description: Auth method to use, tempauth, swauth or keystone
swauth-admin-key:
default:
type: string
description: The secret key to use to authenticate as an swauth admin
delay-auth-decision:
default: true
type: boolean

View File

@ -52,6 +52,7 @@ from lib.swift_utils import (
is_most_recent_timestamp,
timestamps_available,
assess_status,
try_initialize_swauth,
)
import charmhelpers.contrib.openstack.utils as openstack
@ -62,11 +63,9 @@ from charmhelpers.contrib.openstack.ha.utils import (
from charmhelpers.contrib.hahelpers.cluster import (
get_hacluster_config,
)
from charmhelpers.contrib.hahelpers.cluster import (
is_elected_leader,
)
from charmhelpers.core.hookenv import (
config,
local_unit,
@ -183,6 +182,7 @@ def config_changed():
for r_id in relation_ids('object-store'):
object_store_joined(relation_id=r_id)
try_initialize_swauth()
@hooks.hook('identity-service-relation-joined')
@ -233,6 +233,7 @@ def storage_joined():
# possibility of storage nodes getting out-of-date rings by deprecating
# any existing ones from the www dir.
mark_www_rings_deleted()
try_initialize_swauth()
def get_host_ip(rid=None, unit=None):

View File

@ -9,6 +9,7 @@ from charmhelpers.core.hookenv import (
relation_get,
unit_get,
service_name,
leader_get,
)
from charmhelpers.contrib.openstack.context import (
OSContextGenerator,
@ -107,9 +108,13 @@ class SwiftIdentityContext(OSContextGenerator):
'delay_auth_decision': config('delay-auth-decision'),
'node_timeout': config('node-timeout'),
'recoverable_node_timeout': config('recoverable-node-timeout'),
'log_headers': config('log-headers')
'log_headers': config('log-headers'),
}
admin_key = leader_get('swauth-admin-key')
if admin_key is not None:
ctxt['swauth_admin_key'] = admin_key
if config('debug'):
ctxt['log_level'] = 'DEBUG'
else:
@ -126,6 +131,8 @@ class SwiftIdentityContext(OSContextGenerator):
ctxt['ssl'] = False
auth_type = config('auth-type')
ctxt['auth_type'] = auth_type
auth_host = config('keystone-auth-host')
admin_user = config('keystone-admin-user')
admin_password = config('keystone-admin-user')

View File

@ -34,19 +34,23 @@ from charmhelpers.contrib.openstack.utils import (
from charmhelpers.contrib.hahelpers.cluster import (
is_elected_leader,
peer_units,
determine_api_port,
)
from charmhelpers.core.hookenv import (
log,
DEBUG,
INFO,
WARNING,
config,
local_unit,
relation_get,
unit_get,
relation_set,
relation_ids,
related_units,
config,
is_leader,
leader_set,
leader_get,
)
from charmhelpers.fetch import (
apt_update,
@ -116,7 +120,7 @@ BASE_PACKAGES = [
'python-keystone',
]
# > Folsom specific packages
FOLSOM_PACKAGES = BASE_PACKAGES + ['swift-plugin-s3']
FOLSOM_PACKAGES = BASE_PACKAGES + ['swift-plugin-s3', 'swauth']
SWIFT_HA_RES = 'grp_swift_vips'
TEMPLATES = 'templates/'
@ -304,6 +308,29 @@ class SwiftProxyClusterRPC(object):
return rq
def try_initialize_swauth():
if is_leader() and config('auth-type') == 'swauth':
if leader_get('swauth-init') is not True:
try:
admin_key = config('swauth-admin-key')
if admin_key == '' or admin_key is None:
admin_key = leader_get('swauth-admin-key')
if admin_key is None:
admin_key = uuid.uuid4()
leader_set({'swauth-admin-key': admin_key})
bind_port = config('bind-port')
bind_port = determine_api_port(bind_port, singlenode_mode=True)
subprocess.check_call([
'swauth-prep',
'-A',
'http://localhost:{}/auth'.format(bind_port),
'-K', admin_key])
leader_set({'swauth-init': True})
except subprocess.CalledProcessError:
log("had a problem initializing swauth!")
def get_first_available_value(responses, key, default=None):
for r in responses:
if key in r:

View File

@ -12,7 +12,7 @@ key_file = {{ ssl_key }}
pipeline = healthcheck cache swift3 s3token authtoken keystone container-quotas account-quotas proxy-server
{% else %}
[pipeline:main]
pipeline = healthcheck cache tempauth container-quotas account-quotas proxy-server
pipeline = healthcheck cache {{ auth_type }} container-quotas account-quotas proxy-server
{% endif %}
[app:proxy-server]
@ -70,3 +70,11 @@ admin_token = {{ admin_token }}
[filter:swift3]
use = egg:swift3#swift3
{% endif %}
{% if auth_type == 'swauth' %}
[filter:swauth]
use = egg:swauth#swauth
set log_name = swauth
super_admin_key = {{ swauth_admin_key }}
default_swift_cluster = local#https://{{ proxy_ip }}:8080/v1
{% endif %}

View File

@ -12,7 +12,7 @@ key_file = {{ ssl_key }}
pipeline = healthcheck cache swift3 authtoken keystoneauth container-quotas account-quotas proxy-server
{% else %}
[pipeline:main]
pipeline = healthcheck cache tempauth container-quotas account-quotas proxy-server
pipeline = healthcheck cache {{ auth_type }} container-quotas account-quotas proxy-server
{% endif %}
[app:proxy-server]
@ -71,3 +71,11 @@ admin_token = {{ admin_token }}
[filter:swift3]
use = egg:swift3#swift3
{% endif %}
{% if auth_type == 'swauth' %}
[filter:swauth]
use = egg:swauth#swauth
set log_name = swauth
super_admin_key = {{ swauth_admin_key }}
default_swift_cluster = local#https://{{ proxy_ip }}:8080/v1
{% endif %}

View File

@ -19,7 +19,7 @@ key_file = {{ ssl_key }}
pipeline = gatekeeper healthcheck proxy-logging cache swift3 s3token container_sync bulk tempurl slo dlo formpost authtoken keystoneauth staticweb container-quotas account-quotas proxy-logging proxy-server
{% else %}
[pipeline:main]
pipeline = gatekeeper healthcheck proxy-logging cache container_sync bulk tempurl slo dlo formpost tempauth staticweb container-quotas account-quotas proxy-logging proxy-server
pipeline = gatekeeper healthcheck proxy-logging cache container_sync bulk tempurl slo dlo formpost {{ auth_type }} staticweb container-quotas account-quotas proxy-logging proxy-server
{% endif %}
[app:proxy-server]
@ -104,3 +104,11 @@ admin_token = {{ admin_token }}
[filter:swift3]
use = egg:swift3#swift3
{% endif %}
{% if auth_type == 'swauth' %}
[filter:swauth]
use = egg:swauth#swauth
set log_name = swauth
super_admin_key = {{ swauth_admin_key }}
default_swift_cluster = local#https://{{ proxy_ip }}:8080/v1
{% endif %}

View File

@ -19,7 +19,7 @@ key_file = {{ ssl_key }}
pipeline = gatekeeper healthcheck proxy-logging cache swift3 s3token container_sync bulk tempurl slo dlo formpost authtoken keystoneauth staticweb container-quotas account-quotas proxy-logging proxy-server
{% else %}
[pipeline:main]
pipeline = gatekeeper healthcheck proxy-logging cache container_sync bulk tempurl slo dlo formpost tempauth staticweb container-quotas account-quotas proxy-logging proxy-server
pipeline = gatekeeper healthcheck proxy-logging cache container_sync bulk tempurl slo dlo formpost {{ auth_type }} staticweb container-quotas account-quotas proxy-logging proxy-server
{% endif %}
[app:proxy-server]
@ -106,3 +106,11 @@ auth_uri = {{ auth_protocol }}://{{ keystone_host }}:{{ auth_port }}
[filter:swift3]
use = egg:swift3#swift3
{% endif %}
{% if auth_type == 'swauth' %}
[filter:swauth]
use = egg:swauth#swauth
set log_name = swauth
super_admin_key = {{ swauth_admin_key }}
default_swift_cluster = local#https://{{ proxy_ip }}:8080/v1
{% endif %}

View File

@ -15,12 +15,13 @@
import argparse
import sys
import tempfile
import subprocess
import mock
import yaml
import unittest
from mock import patch, MagicMock
from mock import patch, MagicMock, call
# python-apt is not installed as part of test-requirements but is imported by
# some charmhelpers modules so create a fake import.
@ -32,6 +33,7 @@ with patch('charmhelpers.contrib.hardening.harden.harden') as mock_dec, \
mock_dec.side_effect = (lambda *dargs, **dkwargs: lambda f:
lambda *args, **kwargs: f(*args, **kwargs))
import actions.actions
import actions.add_user
class CharmTestCase(unittest.TestCase):
@ -202,3 +204,46 @@ class MainTestCase(CharmTestCase):
with mock.patch.dict(actions.actions.ACTIONS, {"foo": dummy_action}):
actions.actions.main([])
self.assertEqual(dummy_calls, ["uh oh"])
class AddUserTestCase(CharmTestCase):
def setUp(self):
super(AddUserTestCase, self).setUp(
actions.add_user, ["action_get", "action_set",
"action_fail", "check_call",
"try_initialize_swauth", "config",
"determine_api_port", "leader_get"])
def test_success(self):
"""Ensure that the action_set is called on succees."""
self.config.return_value = "swauth"
self.action_get.return_value = "test"
self.determine_api_port.return_value = 8070
actions.add_user.add_user()
self.leader_get.assert_called_with("swauth-admin-key")
calls = [call("account"), call("username"), call("password")]
self.action_get.assert_has_calls(calls)
self.action_set.assert_called_once_with({
'add-user.result': 'Success',
'add-user.message': "Successfully added the user test",
})
def test_failure(self):
"""Ensure that action_fail is called on failure."""
self.config.return_value = "swauth"
self.action_get.return_value = "test"
self.determine_api_port.return_value = 8070
self.CalledProcessError = ValueError
self.check_call.side_effect = subprocess.CalledProcessError(0,
"hi",
"no")
actions.add_user.add_user()
self.leader_get.assert_called_with("swauth-admin-key")
calls = [call("account"), call("username"), call("password")]
self.action_get.assert_has_calls(calls)
self.action_set.assert_not_called()
self.action_fail.assert_called_once_with(
'Adding user test failed with: ""')

View File

@ -25,6 +25,7 @@ with mock.patch('charmhelpers.core.hookenv.config'):
class SwiftIdentityContextTest(unittest.TestCase):
@mock.patch('lib.swift_context.leader_get')
@mock.patch('lib.swift_context.relation_get')
@mock.patch('lib.swift_context.related_units')
@mock.patch('lib.swift_context.relation_ids')
@ -36,7 +37,8 @@ class SwiftIdentityContextTest(unittest.TestCase):
def test_context_api_v2(self, mock_config, mock_get_host_ip,
mock_unit_get, mock_determine_api_port,
mock_IdentityServiceContext, mock_relation_ids,
mock_related_units, mock_relation_get):
mock_related_units, mock_relation_get,
mock_leader_get):
_relinfo = {
'auth_protocol': 'http',
'service_protocol': 'http',
@ -57,6 +59,7 @@ class SwiftIdentityContextTest(unittest.TestCase):
ctxt = swift_context.SwiftIdentityContext()
self.assertEqual(ctxt()['api_version'], '2')
@mock.patch('lib.swift_context.leader_get')
@mock.patch('lib.swift_context.relation_get')
@mock.patch('lib.swift_context.related_units')
@mock.patch('lib.swift_context.relation_ids')
@ -68,7 +71,8 @@ class SwiftIdentityContextTest(unittest.TestCase):
def test_context_api_v3(self, mock_config, mock_get_host_ip,
mock_unit_get, mock_determine_api_port,
mock_IdentityServiceContext, mock_relation_ids,
mock_related_units, mock_relation_get):
mock_related_units, mock_relation_get,
mock_leader_get):
_relinfo = {
'auth_protocol': 'http',
'service_protocol': 'http',

View File

@ -18,6 +18,7 @@ import shutil
import tempfile
import uuid
import unittest
import subprocess
with mock.patch('charmhelpers.core.hookenv.config'):
import lib.swift_utils as swift_utils
@ -421,3 +422,26 @@ class SwiftUtilsTestCase(unittest.TestCase):
'test-config', required_interfaces,
charm_func=swift_utils.customer_check_assess_status,
services='s1', ports=None)
@mock.patch.object(swift_utils, 'leader_set')
@mock.patch.object(swift_utils, 'determine_api_port')
@mock.patch.object(swift_utils, 'is_leader')
@mock.patch.object(swift_utils, 'config')
@mock.patch.object(swift_utils, 'leader_get')
@mock.patch.object(subprocess, 'check_call')
def test_config_and_leader_get(self, check_call, leader_get, config,
is_leader, determine_api_port, leader_set):
"""Ensure that we config_get, and then leader_get."""
config.side_effect = lambda key: {
'auth-type': 'swauth',
'swauth-admin-key': None,
'bind-port': 8080}[key]
determine_api_port.return_value = 8080
is_leader.return_value = True
leader_get.return_value = "Test"
swift_utils.try_initialize_swauth()
check_call.assert_called_with(['swauth-prep',
'-A',
'http://localhost:8080/auth',
'-K',
'Test'])