This repository has been archived by the owner on Aug 1, 2019. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 112
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
The current key storage can't handle separate keys for ssh and secrets. Nor can it handle key rollover. Upgrade the directory layout to support both, and support future upgrades with schema versions. Change-Id: Ifb292335b1b34d5e16be1c4d4e29aa843761411b
- Loading branch information
James E. Blair
committed
Sep 4, 2018
1 parent
85f6598
commit 55c4786
Showing
8 changed files
with
245 additions
and
34 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
# Copyright 2018 Red Hat, Inc. | ||
# | ||
# 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. | ||
|
||
import os | ||
import fixtures | ||
|
||
from zuul.lib import keystorage | ||
|
||
from tests.base import BaseTestCase | ||
|
||
|
||
class TestKeyStorage(BaseTestCase): | ||
|
||
def _setup_keys(self, root, connection_name, project_name): | ||
cn = os.path.join(root, connection_name) | ||
if '/' in project_name: | ||
pn = os.path.join(cn, os.path.dirname(project_name)) | ||
os.makedirs(pn) | ||
fn = os.path.join(cn, project_name + '.pem') | ||
with open(fn, 'w'): | ||
pass | ||
|
||
def assertFile(self, root, path, contents=None): | ||
fn = os.path.join(root, path) | ||
self.assertTrue(os.path.exists(fn)) | ||
if contents: | ||
with open(fn) as f: | ||
self.assertEqual(contents, f.read()) | ||
|
||
def assertPaths(self, root, paths): | ||
seen = set() | ||
for dirpath, dirnames, filenames in os.walk(root): | ||
for d in dirnames: | ||
seen.add(os.path.join(dirpath[len(root) + 1:], d)) | ||
for f in filenames: | ||
seen.add(os.path.join(dirpath[len(root) + 1:], f)) | ||
self.assertEqual(set(paths), seen) | ||
|
||
def test_key_storage(self): | ||
root = self.useFixture(fixtures.TempDir()).path | ||
self._setup_keys(root, 'gerrit', 'org/example') | ||
keystorage.KeyStorage(root) | ||
self.assertFile(root, '.version', '1') | ||
self.assertPaths(root, [ | ||
'.version', | ||
'secrets', | ||
'secrets/project', | ||
'secrets/project/gerrit', | ||
'secrets/project/gerrit/org', | ||
'secrets/project/gerrit/org/example', | ||
'secrets/project/gerrit/org/example/0.pem', | ||
'ssh', | ||
'ssh/project', | ||
'ssh/tenant', | ||
]) | ||
# It shouldn't need to upgrade this time | ||
keystorage.KeyStorage(root) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,135 @@ | ||
# Copyright 2018 Red Hat, Inc. | ||
# | ||
# 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. | ||
|
||
import tempfile | ||
import logging | ||
import os | ||
|
||
|
||
class Migration(object): | ||
log = logging.getLogger("zuul.KeyStorage") | ||
version = 0 | ||
parent = None | ||
|
||
def verify(self, root): | ||
fn = os.path.join(root, '.version') | ||
if not os.path.exists(fn): | ||
return False | ||
with open(fn) as f: | ||
data = int(f.read().strip()) | ||
if data == self.version: | ||
return True | ||
return False | ||
|
||
def writeVersion(self, root): | ||
fn = os.path.join(root, '.version') | ||
with open(fn, 'w') as f: | ||
f.write(str(self.version)) | ||
|
||
def upgrade(self, root): | ||
pass | ||
|
||
def verifyAndUpgrade(self, root): | ||
if self.verify(root): | ||
return | ||
if self.parent: | ||
self.parent.verifyAndUpgrade(root) | ||
self.log.info("Upgrading key storage to version %s" % self.version) | ||
self.upgrade(root) | ||
self.writeVersion(root) | ||
self.log.info("Finished upgrading key storage to version %s" % | ||
self.version) | ||
if not self.verify(root): | ||
raise Exception("Inconsistent result after migration") | ||
|
||
|
||
class MigrationV1(Migration): | ||
version = 1 | ||
parent = None | ||
|
||
"""Upgrade from the unversioned schema to version 1. | ||
The original schema had secret keys in key_dir/connection/project.pem | ||
This updates us to: | ||
key_dir/ | ||
secrets/ | ||
project/ | ||
<connection>/ | ||
<project>/ | ||
<keyid>.pem | ||
ssh/ | ||
project/ | ||
<connection>/ | ||
<project>/ | ||
<keyid>.pem | ||
tenant/ | ||
<tenant>/ | ||
<keyid>.pem | ||
Where keyids are integers to support future key rollover. In this | ||
case, they will all be 0. | ||
""" | ||
|
||
def upgrade(self, root): | ||
tmpdir = tempfile.mkdtemp(dir=root) | ||
tmpdirname = os.path.basename(tmpdir) | ||
connection_names = [] | ||
for connection_name in os.listdir(root): | ||
if connection_name == tmpdirname: | ||
continue | ||
# Move existing connections out of the way (in case one of | ||
# them was called 'secrets' or 'ssh'. | ||
os.rename(os.path.join(root, connection_name), | ||
os.path.join(tmpdir, connection_name)) | ||
connection_names.append(connection_name) | ||
os.makedirs(os.path.join(root, 'secrets', 'project'), 0o700) | ||
os.makedirs(os.path.join(root, 'ssh', 'project'), 0o700) | ||
os.makedirs(os.path.join(root, 'ssh', 'tenant'), 0o700) | ||
for connection_name in connection_names: | ||
connection_root = os.path.join(tmpdir, connection_name) | ||
for (dirpath, dirnames, filenames) in os.walk(connection_root): | ||
subdir = os.path.relpath(dirpath, connection_root) | ||
for fn in filenames: | ||
key_name = os.path.join(subdir, fn) | ||
project_name = key_name[:-len('.pem')] | ||
key_dir = os.path.join(root, 'secrets', 'project', | ||
connection_name, project_name) | ||
os.makedirs(key_dir, 0o700) | ||
old = os.path.join(tmpdir, connection_name, key_name) | ||
new = os.path.join(key_dir, '0.pem') | ||
self.log.debug("Moving key from %s to %s", old, new) | ||
os.rename(old, new) | ||
for (dirpath, dirnames, filenames) in os.walk( | ||
connection_root, topdown=False): | ||
os.rmdir(dirpath) | ||
os.rmdir(tmpdir) | ||
|
||
|
||
class KeyStorage(object): | ||
current_version = MigrationV1 | ||
|
||
def __init__(self, root): | ||
self.root = root | ||
migration = self.current_version() | ||
migration.verifyAndUpgrade(root) | ||
|
||
def getProjectSecretsKeyFile(self, connection, project, version=None): | ||
"""Return the path to the private key used for the project's secrets""" | ||
# We don't actually support multiple versions yet | ||
if version is None: | ||
version = '0' | ||
return os.path.join(self.root, 'secrets', 'project', | ||
connection, project, version + '.pem') |
Oops, something went wrong.