diff --git a/barbican/Chart.yaml b/barbican/Chart.yaml index bab804b198..b539e94306 100644 --- a/barbican/Chart.yaml +++ b/barbican/Chart.yaml @@ -14,7 +14,7 @@ apiVersion: v1 appVersion: v1.0.0 description: OpenStack-Helm Barbican name: barbican -version: 0.2.2 +version: 0.2.3 home: https://docs.openstack.org/barbican/latest/ icon: https://www.openstack.org/themes/openstack/images/project-mascots/Barbican/OpenStack_Project_Barbican_vertical.png sources: diff --git a/barbican/templates/bin/_db-sync.sh.tpl b/barbican/templates/bin/_db-sync.sh.tpl index 7f03ab7c40..3fa5da53e3 100644 --- a/barbican/templates/bin/_db-sync.sh.tpl +++ b/barbican/templates/bin/_db-sync.sh.tpl @@ -17,3 +17,11 @@ limitations under the License. set -ex barbican-db-manage upgrade + +{{- $kek := (index (index .Values.conf.barbican "simple_crypto_plugin" | default dict) "kek") | default "" }} +{{- $old_kek := index .Values.conf.simple_crypto_kek_rewrap "old_kek" | default ""}} +{{- if and (not (empty $old_kek)) (not (empty $kek)) }} +set +x +echo "Ensuring that project KEKs are wrapped with the target global KEK" +/tmp/simple_crypto_kek_rewrap.py --old-kek="$(cat /tmp/old_kek)" +{{- end }} diff --git a/barbican/templates/bin/_simple_crypto_kek_rewrap.py.tpl b/barbican/templates/bin/_simple_crypto_kek_rewrap.py.tpl new file mode 100644 index 0000000000..7a52175270 --- /dev/null +++ b/barbican/templates/bin/_simple_crypto_kek_rewrap.py.tpl @@ -0,0 +1,158 @@ +#!/usr/bin/env python + +# 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 argparse +import base64 +import sys + +from cryptography import fernet +from oslo_db.sqlalchemy import session +from sqlalchemy import orm +from sqlalchemy.orm import scoping + +from barbican.common import utils +from barbican.model import models +from barbican.plugin.crypto import simple_crypto + +# Use config values from simple_crypto +CONF = simple_crypto.CONF + + +class KekRewrap(object): + + def __init__(self, conf, old_kek): + self.dry_run = False + self.db_engine = session.create_engine(conf.sql_connection) + self._session_creator = scoping.scoped_session( + orm.sessionmaker( + bind=self.db_engine, + autocommit=True + ) + ) + self.crypto_plugin = simple_crypto.SimpleCryptoPlugin(conf) + self.plugin_name = utils.generate_fullname_for(self.crypto_plugin) + self.decryptor = fernet.Fernet(old_kek.encode('utf-8')) + self.encryptor = fernet.Fernet(self.crypto_plugin.master_kek) + + def rewrap_kek(self, project, kek): + with self.db_session.begin(): + plugin_meta = kek.plugin_meta + + # try to unwrap with the target kek, and if successful, skip + try: + if self.encryptor.decrypt(plugin_meta.encode('utf-8')): + print('Project KEK {} is already wrapped with target KEK, skipping'.format(kek.id)) + return + except fernet.InvalidToken: + pass + + # decrypt with the old kek + print('Unwrapping Project KEK {}'.format(kek.id)) + try: + decrypted_plugin_meta = self.decryptor.decrypt(plugin_meta.encode('utf-8')) + except fernet.InvalidToken: + print('Failed to unwrap Project KEK {}'.format(kek.id)) + raise + + # encrypt with the new kek + print('Rewrapping Project KEK {}'.format(kek.id)) + try: + new_plugin_meta = self.encryptor.encrypt(decrypted_plugin_meta).decode('utf-8') + except fernet.InvalidToken: + print('Failed to wrap Project KEK {}'.format(kek.id)) + raise + + if self.dry_run: + return + + # Update KEK metadata in DB + print('Storing updated Project KEK {}'.format(kek.id)) + kek.plugin_meta = new_plugin_meta + + def get_keks_for_project(self, project): + keks = [] + with self.db_session.begin() as transaction: + print('Retrieving KEKs for Project {}'.format(project.external_id)) + query = transaction.session.query(models.KEKDatum) + query = query.filter_by(project_id=project.id) + query = query.filter_by(plugin_name=self.plugin_name) + + keks = query.all() + + return keks + + def get_projects(self): + print('Retrieving all available projects') + + projects = [] + with self.db_session.begin() as transaction: + projects = transaction.session.query(models.Project).all() + + return projects + + @property + def db_session(self): + return self._session_creator() + + def execute(self, dry_run=True): + self.dry_run = dry_run + if self.dry_run: + print('-- Running in dry-run mode --') + + projects = self.get_projects() + successes = [] + failures = [] + + for project in projects: + keks = self.get_keks_for_project(project) + for kek in keks: + try: + self.rewrap_kek(project, kek) + successes.append(kek.id) + except Exception: + failures.append(kek.id) + + if successes: + print('Sucessfully processed the following KEKs:') + print('\n'.join(successes)) + + if failures: + print('Failed to rewrap the following KEKs:') + print('\n'.join(failures)) + sys.exit(1) + + +def main(): + script_desc = 'Utility to re-wrap Project KEKs after rotating the global KEK.' + + parser = argparse.ArgumentParser(description=script_desc) + parser.add_argument( + '--dry-run', + action='store_true', + help='Displays changes that will be made (Non-destructive)' + ) + parser.add_argument( + '--old-kek', + default='dGhpcnR5X3R3b19ieXRlX2tleWJsYWhibGFoYmxhaGg=', + help='Old key encryption key previously used by Simple Crypto Plugin. ' + '(32 bytes, base64-encoded)' + ) + args = parser.parse_args() + + rewrapper = KekRewrap(CONF, args.old_kek) + rewrapper.execute(args.dry_run) + + +if __name__ == '__main__': + main() diff --git a/barbican/templates/configmap-bin.yaml b/barbican/templates/configmap-bin.yaml index f56e8f4d58..0b86060d62 100644 --- a/barbican/templates/configmap-bin.yaml +++ b/barbican/templates/configmap-bin.yaml @@ -46,4 +46,6 @@ data: {{- include "helm-toolkit.scripts.keystone_user" . | indent 4 }} rabbit-init.sh: | {{- include "helm-toolkit.scripts.rabbit_init" . | indent 4 }} + simple_crypto_kek_rewrap.py: | +{{ tuple "bin/_simple_crypto_kek_rewrap.py.tpl" . | include "helm-toolkit.utils.template" | indent 4 }} {{- end }} diff --git a/barbican/templates/configmap-etc.yaml b/barbican/templates/configmap-etc.yaml index 9a689a1484..aa30afbbe2 100644 --- a/barbican/templates/configmap-etc.yaml +++ b/barbican/templates/configmap-etc.yaml @@ -95,4 +95,5 @@ data: api_audit_map.conf: {{ include "helm-toolkit.utils.to_ini" .Values.conf.audit_map | b64enc }} policy.yaml: {{ toYaml .Values.conf.policy | b64enc }} barbican-api.ini: {{ include "helm-toolkit.utils.to_ini" .Values.conf.barbican_api | b64enc }} + old_kek: {{ index .Values.conf.simple_crypto_kek_rewrap "old_kek" | default "" | b64enc | quote }} {{- end }} diff --git a/barbican/templates/job-db-sync.yaml b/barbican/templates/job-db-sync.yaml index 86fe7f35fc..a59a84ae76 100644 --- a/barbican/templates/job-db-sync.yaml +++ b/barbican/templates/job-db-sync.yaml @@ -19,7 +19,11 @@ helm.sh/hook-weight: "-4" {{- end }} {{- end }} +{{- $podVolMounts := .Values.pod.mounts.barbican_db_sync.barbican_db_sync.volumeMounts | default list }} +{{- $podVolMounts = append $podVolMounts (dict "name" "db-sync-sh" "mountPath" "/tmp/simple_crypto_kek_rewrap.py" "subPath" "simple_crypto_kek_rewrap.py" "readOnly" true) }} +{{- $podVolMounts = append $podVolMounts (dict "name" "db-sync-conf" "mountPath" "/tmp/old_kek" "subPath" "old_kek" "readOnly" true) }} + {{- if .Values.manifests.job_db_sync }} -{{- $dbSyncJob := dict "envAll" . "serviceName" "barbican" "podVolMounts" .Values.pod.mounts.barbican_db_sync.barbican_db_sync.volumeMounts "podVols" .Values.pod.mounts.barbican_db_sync.barbican_db_sync.volumes "jobAnnotations" (include "metadata.annotations.job.db_sync" . | fromYaml) -}} +{{- $dbSyncJob := dict "envAll" . "serviceName" "barbican" "podVolMounts" $podVolMounts "podVols" .Values.pod.mounts.barbican_db_sync.barbican_db_sync.volumes "jobAnnotations" (include "metadata.annotations.job.db_sync" . | fromYaml) -}} {{ $dbSyncJob | include "helm-toolkit.manifests.job_db_sync" }} {{- end }} diff --git a/barbican/values.yaml b/barbican/values.yaml index c2a031e37b..c8ff66cf1e 100644 --- a/barbican/values.yaml +++ b/barbican/values.yaml @@ -470,6 +470,44 @@ conf: bind_port: null oslo_policy: policy_file: /etc/barbican/policy.yaml + # When using the simple_crypto_plugin, a kek must be provided as: + # .conf.barbican.simple_crypto_plugin.kek + # If no kek is provided, barbican will use a well-known default. + # If upgrading the chart with a new kek, the old kek must be provided as: + # .conf.simple_crypto_plugin_rewrap.old_kek + # Please refer to the .conf.simple_crypto_key_rewrap section below. + # The barbican defaults are included here as a reference: + # secretstore: + # enabled_secretstore_plugins: + # - store_crypto + # crypto: + # enabled_crypto_plugins: + # - simple_crypto + # simple_crypto_plugin: + # # The kek should be a 32-byte value which is base64 encoded. + # kek: "dGhpcnR5X3R3b19ieXRlX2tleWJsYWhibGFoYmxhaGg=" + # KEK rotation for the simple_crypto plugin + simple_crypto_kek_rewrap: + + # To allow for chart upgrades when modifying the Key Encryption Key, the + # db-sync job can rewrap the existing project keys with the new kek, leaving + # each secret’s encrypted data unchanged. + + # This feature is enabled automatically, if a kek is specified at: + # .conf.barbican.simple_crypto_plugin.kek + # and the previous kek is also specified at: + # .conf.simple_crypto_kek_rewrap.old_kek + + # The project keys are decrypted with 'old_kek' and re-encrypted with the + # target kek (as defined in barbican.conf). + # This resembles the lightweight rotation described here, which was never + # implemented for the simple crypto plugin: + # https://specs.openstack.org/openstack/barbican-specs/specs/liberty/add-crypto-mkek-rotation-support-lightweight.html + + # The KEK value "dGhpcnR5X3R3b19ieXRlX2tleWJsYWhibGFoYmxhaGg=" matches the + # plugin default, and is retained here for convenience, in case the chart was + # previously installed without explicitly specifying a kek. + old_kek: "dGhpcnR5X3R3b19ieXRlX2tleWJsYWhibGFoYmxhaGg=" logging: loggers: keys: diff --git a/releasenotes/notes/barbican.yaml b/releasenotes/notes/barbican.yaml index d898918cf4..20fd86bda7 100644 --- a/releasenotes/notes/barbican.yaml +++ b/releasenotes/notes/barbican.yaml @@ -6,3 +6,4 @@ barbican: - 0.2.0 Remove support for releases before T - 0.2.1 Use policies in yaml format - 0.2.2 Add helm hook conditional + - 0.2.3 Add support for master kek rotation