Barbican: Add support for master KEK rotation
When using the simple_crypto_plugin (which is enabled by default), secrets are encrypted with per-project keys, and those keys are encrypted (or wrapped) with a master key encryption key (KEK, or MKEK). The wrapped project keys are stored in the database. The KEK is stored in the barbican configuration file. If no KEK is specified, a well-known default is used. There is no native Barbican support for rotating the KEK. Changing the KEK would cause loss of access to all secrets, because Barbican would be unable to unwrap the project keys. This change adds support for upgrading the Helm chart while changing the KEK. A script can be executed during the db-sync job that decrypts the project keys with the old KEK, and rewraps them with the new KEK. Note that no secrets are actually modified during this procedure, and the project keys are not actually changed. To use this feature, specify the following values: conf: barbican: simple_crypto_plugin: kek: # new KEK, 32-bytes of data, base64-encoded simple_crypto_kek_rewrap: old_kek: # old KEK, 32-bytes of data, base64-encoded Change-Id: I462085b89ef80985b42149cccf865e6c5f0f5a53
This commit is contained in:
parent
43f24adf57
commit
ce1b2630d2
@ -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:
|
||||
|
@ -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 }}
|
||||
|
158
barbican/templates/bin/_simple_crypto_kek_rewrap.py.tpl
Normal file
158
barbican/templates/bin/_simple_crypto_kek_rewrap.py.tpl
Normal 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()
|
@ -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 }}
|
||||
|
@ -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 }}
|
||||
|
@ -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 }}
|
||||
|
@ -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:
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user