Merge "Barbican: Add support for master KEK rotation"

This commit is contained in:
Zuul 2021-06-08 14:33:32 +00:00 committed by Gerrit Code Review
commit 9bdd4fe96f
8 changed files with 214 additions and 2 deletions

View File

@ -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:

View File

@ -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 }}

View File

@ -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()

View File

@ -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 }}

View File

@ -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 }}

View File

@ -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 }}

View File

@ -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 secrets 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:

View File

@ -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