Remove admin_token_auth from keystone pipelines

The presence of the admin_token_auth middleware is needed only to apply
the keystone puppet task during the restore phase. This middleware
allows to authorize with admin_token and configure things in Keystone.
After the successful re-initialization of Keystone this middleware
should be removed from pipelines alongside with restarting the keystone
service because of security reasons.

Change-Id: Id98d8f25270538ab850af936eff749f0277e0c58
This commit is contained in:
Ilya Kharin 2016-07-26 23:52:56 +03:00
parent 9c44fad4f4
commit 7fbc2e57ef
6 changed files with 201 additions and 25 deletions

View File

@ -75,12 +75,9 @@ class KeystoneArchivator(PostgresArchivator):
def restore(self):
keystone.unset_default_domain_id(magic_consts.KEYSTONE_CONF)
keystone.add_admin_token_auth(magic_consts.KEYSTONE_PASTE, [
"pipeline:public_api",
"pipeline:admin_api",
"pipeline:api_v3",
])
super(KeystoneArchivator, self).restore()
with keystone.admin_token_auth(magic_consts.KEYSTONE_PASTE,
magic_consts.KEYSTONE_PIPELINES):
super(KeystoneArchivator, self).restore()
class DatabasesArchivator(base.CollectionArchivator):

View File

@ -11,7 +11,9 @@
# under the License.
from octane.handlers.backup_restore import base
from octane import magic_consts
from octane.util import auth
from octane.util import keystone
from octane.util import puppet
from octane.util import subprocess
@ -31,5 +33,7 @@ class PuppetApplyTasks(base.Base):
def restore(self):
subprocess.call(["systemctl", "stop"] + self.services)
with auth.set_astute_password(self.context):
with auth.set_astute_password(self.context), \
keystone.admin_token_auth(magic_consts.KEYSTONE_PASTE,
magic_consts.KEYSTONE_PIPELINES):
puppet.apply_all_tasks()

View File

@ -90,3 +90,9 @@ NOVA_PATCHES = [
SFTP_SERVER_BIN = '/usr/lib/sftp-server'
FUEL_KEYS_BASE_PATH = "/var/lib/fuel/keys"
KEYSTONE_PIPELINES = [
"pipeline:public_api",
"pipeline:admin_api",
"pipeline:api_v3",
]

View File

@ -267,11 +267,11 @@ def test_postgres_restore(mocker, cls, db, services):
member = TestMember("postgres/{0}.sql".format(db), True, True)
archive = TestArchive([member], cls)
mock_keystone = mock.Mock()
mock_keystone = mock.MagicMock()
mocker.patch("octane.util.keystone.unset_default_domain_id",
new=mock_keystone.unset)
mocker.patch("octane.util.keystone.add_admin_token_auth",
new=mock_keystone.add)
mocker.patch("octane.util.keystone.admin_token_auth",
new=mock_keystone.admin_token)
mock_subprocess = mock.MagicMock()
mocker.patch("octane.util.subprocess.call", new=mock_subprocess.call)
@ -287,7 +287,7 @@ def test_postgres_restore(mocker, cls, db, services):
cls(archive, mock_context).restore()
member.assert_extract()
assert mock_subprocess.mock_calls == [
expected_calls = [
mock.call.call(["systemctl", "stop"] + services),
mock.call.call(["sudo", "-u", "postgres", "dropdb", "--if-exists",
db]),
@ -296,6 +296,7 @@ def test_postgres_restore(mocker, cls, db, services):
mock.call.popen().__enter__(),
mock.call.popen().__exit__(None, None, None),
]
assert mock_subprocess.mock_calls == expected_calls
mock_copyfileobj.assert_called_once_with(
member,
mock_subprocess.popen.return_value.__enter__.return_value.stdin,
@ -312,13 +313,17 @@ def test_postgres_restore(mocker, cls, db, services):
assert not mock_keystone.called
else:
assert not mock_patch.called
expected_pipelines = [
"pipeline:public_api",
"pipeline:admin_api",
"pipeline:api_v3",
]
assert mock_keystone.mock_calls == [
mock.call.unset("/etc/keystone/keystone.conf"),
mock.call.add("/etc/keystone/keystone-paste.ini", [
"pipeline:public_api",
"pipeline:admin_api",
"pipeline:api_v3",
]),
mock.call.admin_token(
"/etc/keystone/keystone-paste.ini", expected_pipelines),
mock.call.admin_token().__enter__(),
mock.call.admin_token().__exit__(None, None, None),
]
mock_set_astute_password.assert_called_once_with(mock_context)
@ -561,13 +566,23 @@ def test_post_restore_puppet_apply_tasks(mocker, mock_subprocess):
mock_set_astute_password = mocker.patch(
"octane.util.auth.set_astute_password")
mock_apply = mocker.patch("octane.util.puppet.apply_all_tasks")
mock_admin_token = mocker.patch("octane.util.keystone.admin_token_auth")
archivator = puppet.PuppetApplyTasks(None, context)
archivator.restore()
mock_subprocess.assert_called_once_with(["systemctl", "stop", "ostf"])
assert mock_apply.called
mock_set_astute_password.assert_called_once_with(context)
expected_pipelines = [
"pipeline:public_api",
"pipeline:admin_api",
"pipeline:api_v3",
]
mock_admin_token.assert_called_once_with(
"/etc/keystone/keystone-paste.ini", expected_pipelines)
assert mock_subprocess.call_args_list == [
mock.call(["systemctl", "stop", "ostf"]),
]
@pytest.mark.parametrize("nodes", [

View File

@ -11,6 +11,7 @@
# under the License.
import contextlib
import copy
import mock
import pytest
@ -58,6 +59,45 @@ def test_unset_default_domain_id(mocker, parameters, writes):
mock_update_file.assert_called_once_with("fakefilename")
def test_admin_token_auth(mocker):
mock_calls = mock.Mock()
mocker.patch("octane.util.keystone.add_admin_token_auth",
new=mock_calls.add)
mocker.patch("octane.util.keystone.remove_admin_token_auth",
new=mock_calls.remove)
mocker.patch("octane.util.subprocess.call", new=mock_calls.subprocess)
with keystone.admin_token_auth("fakefilename", "fakepipelines"):
mock_calls.let()
expected_calls = [
mock.call.add("fakefilename", "fakepipelines"),
mock.call.let(),
mock.call.remove("fakefilename", "fakepipelines"),
mock.call.subprocess(["systemctl", "restart", "openstack-keystone"]),
]
assert mock_calls.mock_calls == expected_calls
@pytest.mark.parametrize(("items", "expected_items"), [
(
[
["request_id", "token_auth"],
["admin_token_auth", "token_auth"],
],
[
["request_id", "admin_token_auth", "token_auth"],
["admin_token_auth", "token_auth"],
],
),
])
def test_add_admin_token_auth(mocker, items, expected_items):
items = copy.deepcopy(items)
mock_replace = mocker.patch("octane.util.keystone.replace_pipeline_items")
mock_replace.return_value.__enter__.return_value = items
keystone.add_admin_token_auth("fakefilename", "fakepipelines")
assert items == expected_items
mock_replace.assert_called_once_with("fakefilename", "fakepipelines")
@pytest.mark.parametrize(("parameters", "writes"), [
([
("[pipeline:public_api]\n", "pipeline:public_api", None, None),
@ -81,10 +121,96 @@ def test_unset_default_domain_id(mocker, parameters, writes):
"pipeline = request_id token_auth service_v3\n",
])
])
def test_add_admin_token_auth(mocker, parameters, writes):
def test_add_admin_token_auth_functional(mocker, parameters, writes):
with verify_update_file(mocker, parameters, writes) as mock_update_file:
keystone.add_admin_token_auth("fakefilename", [
"pipeline:public_api",
"pipeline:admin_api",
])
mock_update_file.assert_called_once_with("fakefilename")
@pytest.mark.parametrize(("items", "expected_items"), [
(
[
["request_id", "token_auth"],
["admin_token_auth", "token_auth"],
],
[
["request_id", "token_auth"],
["token_auth"],
],
),
])
def test_remove_admin_token_auth(mocker, items, expected_items):
items = copy.deepcopy(items)
mock_replace = mocker.patch("octane.util.keystone.replace_pipeline_items")
mock_replace.return_value.__enter__.return_value = items
keystone.remove_admin_token_auth("fakefilename", "fakepipelines")
assert items == expected_items
mock_replace.assert_called_once_with("fakefilename", "fakepipelines")
@pytest.mark.parametrize(("parameters", "writes"), [
([
("[pipeline:public_api]\n", "pipeline:public_api", None, None),
("pipeline = request_id admin_token_auth token_auth public_service\n",
"pipeline:public_api", "pipeline",
"request_id admin_token_auth token_auth public_service"),
("[pipeline:admin_api]\n", "pipeline:admin_api", None, None),
("pipeline = request_id token_auth admin_service\n",
"pipeline:admin_api", "pipeline",
"request_id token_auth admin_service"),
("[pipeline:api_v3]\n", "pipeline:api_v3", None, None),
("pipeline = request_id admin_token_auth token_auth service_v3\n",
"pipeline:api_v3", "pipeline",
"request_id admin_token_auth token_auth service_v3"),
], [
"[pipeline:public_api]\n",
"pipeline = request_id token_auth public_service\n",
"[pipeline:admin_api]\n",
"pipeline = request_id token_auth admin_service\n",
"[pipeline:api_v3]\n",
"pipeline = request_id admin_token_auth token_auth service_v3\n",
])
])
def test_remove_admin_token_auth_functional(mocker, parameters, writes):
with verify_update_file(mocker, parameters, writes) as mock_update_file:
keystone.remove_admin_token_auth("fakefilename", [
"pipeline:public_api",
"pipeline:admin_api",
])
mock_update_file.assert_called_once_with("fakefilename")
@pytest.mark.parametrize(("parameters", "writes"), [
([
("[pipeline:public_api]\n", "pipeline:public_api", None, None),
("pipeline = token_auth public_service\n",
"pipeline:public_api", "pipeline", "token_auth public_service"),
("[pipeline:admin_api]\n", "pipeline:admin_api", None, None),
("pipeline = request_id token_auth admin_service\n",
"pipeline:admin_api", "pipeline",
"request_id token_auth admin_service"),
("[pipeline:api_v3]\n", "pipeline:api_v3", None, None),
("pipeline = token_auth service_v3\n",
"pipeline:api_v3", "pipeline", "token_auth service_v3"),
], [
"[pipeline:public_api]\n",
"pipeline = token_auth public_service\n",
"[pipeline:admin_api]\n",
"pipeline = a token_auth admin_service c\n",
"[pipeline:api_v3]\n",
"pipeline = token_auth service_v3\n",
])
])
def test_replace_pipelines_items(mocker, parameters, writes):
pipelines = ["pipeline:admin_api"]
with verify_update_file(mocker, parameters, writes) as mock_update_file:
with keystone.replace_pipeline_items("fakefilename", pipelines) as \
pipeline_items:
for items in pipeline_items:
items.insert(0, "a")
items.remove("request_id")
items.append("c")
mock_update_file.assert_called_once_with("fakefilename")

View File

@ -10,6 +10,8 @@
# License for the specific language governing permissions and limitations
# under the License.
import contextlib
from octane.util import helpers
from octane.util import subprocess
@ -22,14 +24,40 @@ def unset_default_domain_id(filename):
new.write(line)
@contextlib.contextmanager
def admin_token_auth(filename, pipelines):
add_admin_token_auth(filename, pipelines)
yield
remove_admin_token_auth(filename, pipelines)
subprocess.call(["systemctl", "restart", "openstack-keystone"])
def add_admin_token_auth(filename, pipelines):
with subprocess.update_file(filename) as (old, new):
with replace_pipeline_items(filename, pipelines) as pipeline_items:
for items in pipeline_items:
if "admin_token_auth" in items:
continue
token_auth_idx = items.index("token_auth")
items.insert(token_auth_idx, "admin_token_auth")
def remove_admin_token_auth(filename, pipelines):
with replace_pipeline_items(filename, pipelines) as pipeline_items:
for items in pipeline_items:
if "admin_token_auth" in items:
items.remove("admin_token_auth")
@contextlib.contextmanager
def replace_pipeline_items(filename, pipelines):
def iterate_pipeline_items(old, new):
for line, section, parameter, value in helpers.iterate_parameters(old):
if section in pipelines and parameter == "pipeline" and \
"admin_token_auth" not in value:
if section in pipelines and parameter == "pipeline":
items = value.split()
token_auth_idx = items.index("token_auth")
items.insert(token_auth_idx, "admin_token_auth")
value = " ".join(items)
line = "{0} = {1}\n".format(parameter, value)
yield items
new_value = " ".join(items)
line = "{0} = {1}\n".format(parameter, new_value)
new.write(line)
with subprocess.update_file(filename) as (old, new):
yield iterate_pipeline_items(old, new)