From 2650ebe8afa2c9a4e72789fc877995edb91e1a6c Mon Sep 17 00:00:00 2001 From: Sergio de Carvalho Date: Mon, 14 Oct 2019 11:39:45 +0100 Subject: [PATCH] rgw: improvements to SSE-KMS with Vault * add 'rgw crypt vault prefix' config setting to allow restricting secret space in Vault where RGW can retrieve keys from * refuse Vault token file if permissions are too open * improve concatenation of URL paths to avoid constructing an invalid URL (missing or double '/') * doc: clarify SSE-KMS keys must be 256-bit long and base64 encoded, document Vault policies and tokens, plus other minor doc improvements * qa: check SHA256 signature of Vault zip download * qa: fix teuthology tests broken by previous PR which made SSE-KMS backend default to Barbican Signed-off-by: Andrea Baglioni Signed-off-by: Sergio de Carvalho --- doc/radosgw/barbican.rst | 2 + doc/radosgw/config-ref.rst | 11 +- doc/radosgw/encryption.rst | 2 + doc/radosgw/vault.rst | 111 ++++++++++++------ qa/suites/rgw/crypt/2-kms/vault.yaml | 9 +- qa/suites/rgw/crypt/4-tests/s3tests.yaml | 4 +- .../smoke/basic/tasks/rgw_ec_s3tests.yaml | 1 + qa/suites/smoke/basic/tasks/rgw_s3tests.yaml | 1 + qa/tasks/rgw.py | 2 + qa/tasks/vault.py | 46 ++++---- src/common/legacy_config_opts.h | 3 +- src/common/options.cc | 13 +- src/rgw/rgw_kms.cc | 74 +++++++----- src/test/rgw/test_rgw_kms.cc | 24 ++++ src/vstart.sh | 3 +- 15 files changed, 208 insertions(+), 98 deletions(-) diff --git a/doc/radosgw/barbican.rst b/doc/radosgw/barbican.rst index 62055670763..a90d063fb97 100644 --- a/doc/radosgw/barbican.rst +++ b/doc/radosgw/barbican.rst @@ -39,6 +39,8 @@ Create a key in Barbican See Barbican documentation for `How to Create a Secret`_. Requests to Barbican must include a valid Keystone token in the ``X-Auth-Token`` header. +.. note:: Server-side encryption keys must be 256-bit long and base64 encoded. + Example request:: POST /v1/secrets HTTP/1.1 diff --git a/doc/radosgw/config-ref.rst b/doc/radosgw/config-ref.rst index 15d21b0f3b7..c3f17d62824 100644 --- a/doc/radosgw/config-ref.rst +++ b/doc/radosgw/config-ref.rst @@ -953,7 +953,7 @@ Barbican Settings HashiCorp Vault Settings ======================== -``rgw crypt vault auth``` +``rgw crypt vault auth`` :Description: Type of authentication method to be used. The only method currently supported is ``token``. @@ -969,7 +969,14 @@ HashiCorp Vault Settings ``rgw crypt vault addr`` -:Description: Base URL to the Vault server. +:Description: Vault server base address, e.g. ``http://vaultserver:8200``. +:Type: String +:Default: None + +``rgw crypt vault prefix`` + +:Description: The Vault secret URL prefix, which can be used to restrict access + to a particular subset of the secret space, e.g. ``/v1/secret/data``. :Type: String :Default: None diff --git a/doc/radosgw/encryption.rst b/doc/radosgw/encryption.rst index 151f3b5efe5..124b57b2169 100644 --- a/doc/radosgw/encryption.rst +++ b/doc/radosgw/encryption.rst @@ -14,6 +14,8 @@ Object Gateway stores that data in the Ceph Storage Cluster in encrypted form. for SSL termination, ``rgw trust forwarded https`` must be enabled before forwarded requests will be trusted as secure. +.. note:: Server-side encryption keys must be 256-bit long and base64 encoded. + Customer-Provided Keys ====================== diff --git a/doc/radosgw/vault.rst b/doc/radosgw/vault.rst index d14b455c7e7..6bd1a9081f7 100644 --- a/doc/radosgw/vault.rst +++ b/doc/radosgw/vault.rst @@ -5,34 +5,85 @@ HashiCorp Vault Integration HashiCorp `Vault`_ can be used as a secure key management service for `Server-Side Encryption`_ (SSE-KMS). -#. `Vault authentication`_ -#. `Create a key in Vault`_ +#. `Configure Vault`_ #. `Configure the Ceph Object Gateway`_ +#. `Create a key in Vault`_ #. `Upload object`_ -Vault authentication -==================== +Configure Vault +=============== + +Vault provides several Secret Engines, which can store, generate, and encrypt +data. Currently, the Object Gateway supports the `KV Secrets engine`_ version 2 +only. To enable the KV engine version 2 in Vault, use the Vault command line +tool:: + + vault secrets enable kv-v2 + +Vault also provides several authentication mechanisms. Currently, the Object +Gateway supports the `token authentication method`_ only. When authenticating +using the token method, a token must be obtained for the Gateway and saved in a +file as plain-text. + +For security reasons, the Object Gateway should be given a Vault token with a +restricted policy that allows it to fetch secrets only. Such a policy can be +created in Vault using the Vault command line utility:: + + vault policy write rgw-policy -</dev/null | base64) + vault kv put secret/myproject/mybucketkey key=$(openssl rand -base64 32) Output:: @@ -49,32 +100,24 @@ Output:: --- ----- key Ak5dRyLQjwX/wb7vo6Fq1qjsfk1dh2CiSicX+gLAhwk= -The URL to the secret in Vault must be provided in the Gateway configuration -file (see below). - -Configure the Ceph Object Gateway -================================= - -Edit the Ceph configuration file to enable Vault as a KMS for server-side -encryption:: - - rgw crypt s3 kms backend = vault - rgw crypt vault auth = token - rgw crypt vault addr = http://vaultserver:8200 - rgw crypt vault token file = /path/to/token.file - Upload object ============= When uploading an object, provide the SSE key ID in the request. As an example, using the AWS command-line client:: - aws --endpoint=http://radosgw:8000 s3 cp plaintext.txt s3://mybucket/encrypted.txt --sse=aws:kms --sse-kms-key-id /v1/secret/data/myproject/mybucketkey + aws --endpoint=http://radosgw:8000 s3 cp plaintext.txt s3://mybucket/encrypted.txt --sse=aws:kms --sse-kms-key-id myproject/mybucketkey + +The Object Gateway will fetch the key from Vault, encrypt the object and store +it in the bucket. Any request to downlod the object will require the correct key +ID for the Gateway to successfully decrypt it. + +Note that the secret will be fetched from Vault using a URL constructed by +concatenating the base address (``rgw crypt vault addr``), the (optional) +URL prefix (``rgw crypt vault prefix``), and finally the key ID. In the example +above, the Gateway will fetch the secret from:: -The object gateway will fetch the key from Vault (using the token for -authentication), encrypt the object and store it in the bucket. Any request to -downlod the object will require the correct key ID for the Gateway to -successfully the decrypt it. + http://vaultserver:8200/v1/secret/data/myproject/mybucketkey .. _Server-Side Encryption: ../encryption .. _Vault: https://www.vaultproject.io/docs/ diff --git a/qa/suites/rgw/crypt/2-kms/vault.yaml b/qa/suites/rgw/crypt/2-kms/vault.yaml index b7168acfe19..c80e31dfb83 100644 --- a/qa/suites/rgw/crypt/2-kms/vault.yaml +++ b/qa/suites/rgw/crypt/2-kms/vault.yaml @@ -4,6 +4,7 @@ overrides: client: rgw crypt s3 kms backend: vault rgw crypt vault auth: token + rgw crypt vault prefix: /v1/kv/data rgw: client.0: use-vault-role: client.0 @@ -11,10 +12,12 @@ overrides: tasks: - vault: client.0: - version: 1.2.2 + install_url: https://releases.hashicorp.com/vault/1.2.2/vault_1.2.2_linux_amd64.zip + install_sha256: 7725b35d9ca8be3668abe63481f0731ca4730509419b4eb29fa0b0baa4798458 root_token: test_root_token + prefix: /v1/kv/data/ secrets: - - path: /v1/kv/data/my-key-1 + - path: my-key-1 secret: a2V5MS5GcWVxKzhzTGNLaGtzQkg5NGVpb1FKcFpGb2c= - - path: /v1/kv/data/my-key-2 + - path: my-key-2 secret: a2V5Mi5yNUNNMGFzMVdIUVZxcCt5NGVmVGlQQ1k4YWg= diff --git a/qa/suites/rgw/crypt/4-tests/s3tests.yaml b/qa/suites/rgw/crypt/4-tests/s3tests.yaml index c59ee2ec922..d2a66ca35a7 100644 --- a/qa/suites/rgw/crypt/4-tests/s3tests.yaml +++ b/qa/suites/rgw/crypt/4-tests/s3tests.yaml @@ -6,5 +6,5 @@ tasks: kms_key: my-key-1 kms_key2: my-key-2 vault: - key_path: /v1/kv/data/my-key-1 - key_path2: /v1/kv/data/my-key-2 + key_path: my-key-1 + key_path2: my-key-2 diff --git a/qa/suites/smoke/basic/tasks/rgw_ec_s3tests.yaml b/qa/suites/smoke/basic/tasks/rgw_ec_s3tests.yaml index dda9aad0d3b..1455b51b4dc 100644 --- a/qa/suites/smoke/basic/tasks/rgw_ec_s3tests.yaml +++ b/qa/suites/smoke/basic/tasks/rgw_ec_s3tests.yaml @@ -15,5 +15,6 @@ overrides: conf: client: rgw lc debug interval: 10 + rgw crypt s3 kms backend: testing rgw crypt s3 kms encryption keys: testkey-1=YmluCmJvb3N0CmJvb3N0LWJ1aWxkCmNlcGguY29uZgo= testkey-2=aWIKTWFrZWZpbGUKbWFuCm91dApzcmMKVGVzdGluZwo= rgw crypt require ssl: false diff --git a/qa/suites/smoke/basic/tasks/rgw_s3tests.yaml b/qa/suites/smoke/basic/tasks/rgw_s3tests.yaml index 4fca78c883f..3d30774ed96 100644 --- a/qa/suites/smoke/basic/tasks/rgw_s3tests.yaml +++ b/qa/suites/smoke/basic/tasks/rgw_s3tests.yaml @@ -11,5 +11,6 @@ overrides: conf: client: rgw lc debug interval: 10 + rgw crypt s3 kms backend: testing rgw crypt s3 kms encryption keys: testkey-1=YmluCmJvb3N0CmJvb3N0LWJ1aWxkCmNlcGguY29uZgo= testkey-2=aWIKTWFrZWZpbGUKbWFuCm91dApzcmMKVGVzdGluZwo= rgw crypt require ssl: false diff --git a/qa/tasks/rgw.py b/qa/tasks/rgw.py index f1b65877be6..d666a5e6a01 100644 --- a/qa/tasks/rgw.py +++ b/qa/tasks/rgw.py @@ -136,6 +136,8 @@ def start_rgw(ctx, config, clients): raise ConfigError('vault: no "root_token" specified') # create token on file ctx.cluster.only(client).run(args=['echo', '-n', ctx.vault.root_token, run.Raw('>'), token_path]) + log.info("Restrict access to token file") + ctx.cluster.only(client).run(args=['chmod', '600', token_path]) log.info("Token file content") ctx.cluster.only(client).run(args=['cat', token_path]) diff --git a/qa/tasks/vault.py b/qa/tasks/vault.py index 748164b9cb2..96f134ebde7 100644 --- a/qa/tasks/vault.py +++ b/qa/tasks/vault.py @@ -6,9 +6,10 @@ import argparse import contextlib import logging import time - import httplib import json +from os import path +from urlparse import urljoin from teuthology import misc as teuthology from teuthology import contextutil @@ -45,24 +46,27 @@ def download(ctx, config): testdir = teuthology.get_testdir(ctx) for (client, cconf) in config.items(): - vault_version = cconf.get('version', '1.2.2') - + install_url = cconf.get('install_url') + install_sha256 = cconf.get('install_sha256') + if not install_url or not install_sha256: + raise ConfigError("Missing Vault install_url and/or install_sha256") + install_zip = path.join(testdir, 'vault.zip') + install_dir = path.join(testdir, 'vault') + + log.info('Downloading Vault...') ctx.cluster.only(client).run( - args=['mkdir', '-p', '{tdir}/vault'.format(tdir=testdir)]) + args=['curl', '-L', install_url, '-o', install_zip]) - cmd = [ - 'curl', '-L', - 'https://releases.hashicorp.com/vault/{version}/vault_{version}_linux_amd64.zip'.format(version=vault_version), '-o', - '{tdir}/vault_{version}.zip'.format(tdir=testdir, version=vault_version) - ] - ctx.cluster.only(client).run(args=cmd) + log.info('Verifying SHA256 signature...') + ctx.cluster.only(client).run( + args=['echo', ' '.join([install_sha256, install_zip]), run.Raw('|'), + 'sha256sum', '--check', '--status']) log.info('Extracting vault...') + ctx.cluster.only(client).run(args=['mkdir', '-p', install_dir]) # Using python in case unzip is not installed on hosts - cmd = ['python', '-m', 'zipfile', '-e', - '{tdir}/vault_{version}.zip'.format(tdir=testdir, version=vault_version), - '{tdir}/vault'.format(tdir=testdir)] - ctx.cluster.only(client).run(args=cmd) + ctx.cluster.only(client).run( + args=['python', '-m', 'zipfile', '-e', install_zip, install_dir]) try: yield @@ -71,13 +75,7 @@ def download(ctx, config): testdir = teuthology.get_testdir(ctx) for client in config: ctx.cluster.only(client).run( - args=[ - 'rm', - '-rf', - '{tdir}/vault_{version}.zip'.format(tdir=testdir, version=vault_version), - '{tdir}/vault'.format(tdir=testdir), - ], - ) + args=['rm', '-rf', install_dir, install_zip]) def get_vault_dir(ctx): @@ -148,7 +146,7 @@ def send_req(ctx, cconfig, client, path, body, method='POST'): host, port = ctx.vault.endpoints[client] req = httplib.HTTPConnection(host, port, timeout=30) token = cconfig.get('root_token', 'atoken') - log.info("Send request to Vault: %s:%s with token: %s", host, port, token) + log.info("Send request to Vault: %s:%s at %s with token: %s", host, port, path, token) headers = {'X-Vault-Token': token} req.request(method, path, headers=headers, body=body) resp = req.getresponse() @@ -161,6 +159,7 @@ def send_req(ctx, cconfig, client, path, body, method='POST'): @contextlib.contextmanager def create_secrets(ctx, config): (cclient, cconfig) = config.items()[0] + prefix = cconfig.get('prefix') secrets = cconfig.get('secrets') if secrets is None: raise ConfigError("No secrets specified, please specify some.") @@ -175,7 +174,7 @@ def create_secrets(ctx, config): except KeyError: raise ConfigError('vault.secrets must have "secret" field') try: - send_req(ctx, cconfig, cclient, secret['path'], json.dumps(data)) + send_req(ctx, cconfig, cclient, urljoin(prefix, secret['path']), json.dumps(data)) except KeyError: raise ConfigError('vault.secrets must have "path" field') @@ -220,6 +219,7 @@ def task(ctx, config): ctx.vault = argparse.Namespace() ctx.vault.endpoints = assign_ports(ctx, config, 8200) ctx.vault.root_token = None + ctx.vault.prefix = config[client].get('prefix') with contextutil.nested( lambda: download(ctx=ctx, config=config), diff --git a/src/common/legacy_config_opts.h b/src/common/legacy_config_opts.h index 01db8d78ce4..a30e206fd5c 100644 --- a/src/common/legacy_config_opts.h +++ b/src/common/legacy_config_opts.h @@ -1499,7 +1499,8 @@ OPTION(rgw_crypt_default_encryption_key, OPT_STR) // base64 encoded key for encr OPTION(rgw_crypt_s3_kms_backend, OPT_STR) // Where SSE-KMS encryption keys are stored OPTION(rgw_crypt_vault_auth, OPT_STR) // Type of authentication method to be used with Vault OPTION(rgw_crypt_vault_token_file, OPT_STR) // Path to the token file for Vault authentication -OPTION(rgw_crypt_vault_addr, OPT_STR) // URL to Vault server endpoint +OPTION(rgw_crypt_vault_addr, OPT_STR) // Vault server base address +OPTION(rgw_crypt_vault_prefix, OPT_STR) // Optional URL prefix to Vault secret path OPTION(rgw_crypt_s3_kms_encryption_keys, OPT_STR) // extra keys that may be used for aws:kms // defined as map "key1=YmluCmJvb3N0CmJvb3N0LQ== key2=b3V0CnNyYwpUZXN0aW5nCg==" diff --git a/src/common/options.cc b/src/common/options.cc index f20568501cf..1d2e93bed14 100644 --- a/src/common/options.cc +++ b/src/common/options.cc @@ -6604,11 +6604,20 @@ std::vector