Skip to content

Commit

Permalink
Encrypt keys before saving in OMAP file.
Browse files Browse the repository at this point in the history
Fixes #960

Signed-off-by: Gil Bregman <[email protected]>
  • Loading branch information
gbregman committed Dec 1, 2024
1 parent 9f1c300 commit ee5208a
Show file tree
Hide file tree
Showing 17 changed files with 632 additions and 306 deletions.
2 changes: 2 additions & 0 deletions .env
Original file line number Diff line number Diff line change
Expand Up @@ -101,3 +101,5 @@ DHCHAP_KEY6="DHHC-1:01:Bu4tZd7X2oW7XxmVH5tGCdoS30pDX6bZvexHYoudeVlJW9yz:"
DHCHAP_KEY7="DHHC-1:01:JPJkDQ2po2FfLmKYlTF/sJ2HzVO/FKWxgXKE/H6XfL8ogQ1T:"
DHCHAP_KEY8="DHHC-1:01:e0B0vDxKleDzYVtG42xqFvoWZfiufkoywmfRKrETzayRdf1j:"
DHCHAP_KEY9="DHHC-1:01:KD+sfH3/o2bRQoV0ESjBUywQlMnSaYpZISUbVa0k0nsWpNST:"
DHCHAP_KEY10="DHHC-1:00:rWf0ZFYO7IgWGttM8w6jUrAY4cTQyqyXPdmxHeOSve3w5QU9:"
DHCHAP_KEY11="DHHC-1:02:j3uUz05r5aQy42vX4tDXqVf9HgUPPdEp3kXTgUWl9EphsG7jwpr9KSIt3bmRLXBijPTIDQ==:"
4 changes: 3 additions & 1 deletion ceph-nvmeof.conf
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ state_update_notify = True
state_update_timeout_in_msec = 2000
state_update_interval_sec = 5
enable_spdk_discovery_controller = False
enable_key_encryption = True
encryption_key = /etc/ceph/encryption.key
#omap_file_lock_duration = 20
#omap_file_lock_retries = 30
#omap_file_lock_retry_sleep_interval = 1.0
Expand All @@ -29,7 +31,7 @@ enable_spdk_discovery_controller = False
#verify_nqns = True
#allowed_consecutive_spdk_ping_failures = 1
#spdk_ping_interval_in_seconds = 2.0
#max_hosts_per_namespace = 1
#max_hosts_per_namespace = 8
#max_namespaces_with_netmask = 1000
#max_subsystems = 128
#max_namespaces = 1024
Expand Down
5 changes: 4 additions & 1 deletion control/discovery.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
from .config import GatewayConfig
from .state import GatewayState, LocalGatewayState, OmapGatewayState, GatewayStateHandler
from .utils import GatewayLogger
from .utils import GatewayUtilsCrypto
from .proto import gateway_pb2 as pb2

import rados
Expand Down Expand Up @@ -1129,8 +1130,10 @@ def start_service(self):
t.start()

local_state = LocalGatewayState()
dummy_crypto = GatewayUtilsCrypto(None)
gateway_state = GatewayStateHandler(self.config, local_state,
self.omap_state, self._state_notify_update, f"discovery-{socket.gethostname()}")
self.omap_state, self._state_notify_update,
dummy_crypto, f"discovery-{socket.gethostname()}")
gateway_state.start_update()

try:
Expand Down
47 changes: 37 additions & 10 deletions control/grpc.py
Original file line number Diff line number Diff line change
Expand Up @@ -324,13 +324,14 @@ def __init__(self, config: GatewayConfig, gateway_state: GatewayStateHandler, rp
self.host_name = socket.gethostname()
self.verify_nqns = self.config.getboolean_with_default("gateway", "verify_nqns", True)
self.gateway_group = self.config.get_with_default("gateway", "group", "")
self.max_hosts_per_namespace = self.config.getint_with_default("gateway", "max_hosts_per_namespace", 1)
self.max_hosts_per_namespace = self.config.getint_with_default("gateway", "max_hosts_per_namespace", 8)
self.max_namespaces_with_netmask = self.config.getint_with_default("gateway", "max_namespaces_with_netmask", 1000)
self.max_subsystems = self.config.getint_with_default("gateway", "max_subsystems", GatewayService.MAX_SUBSYSTEMS_DEFAULT)
self.max_namespaces = self.config.getint_with_default("gateway", "max_namespaces", GatewayService.MAX_NAMESPACES_DEFAULT)
self.max_namespaces_per_subsystem = self.config.getint_with_default("gateway", "max_namespaces_per_subsystem", GatewayService.MAX_NAMESPACES_PER_SUBSYSTEM_DEFAULT)
self.max_hosts_per_subsystem = self.config.getint_with_default("gateway", "max_hosts_per_subsystem", GatewayService.MAX_HOSTS_PER_SUBSYS_DEFAULT)
self.gateway_pool = self.config.get_with_default("ceph", "pool", "")
self.enable_key_encryption = self.config.getboolean_with_default("gateway", "enable_key_encryption", True)
self.ana_map = defaultdict(dict)
self.cluster_nonce = {}
self.bdev_cluster = {}
Expand Down Expand Up @@ -672,6 +673,7 @@ def create_bdev(self, anagrp: int, name, uuid, rbd_pool_name, rbd_image_name, bl
self.logger.exception(errmsg)
return BdevStatus(status=errcode, error_message=f"Failure creating bdev {name}: {errmsg}")

cluster_name = None
try:
cluster_name=self._get_cluster(anagrp)
bdev_name = rpc_bdev.bdev_rbd_create(
Expand All @@ -689,7 +691,8 @@ def create_bdev(self, anagrp: int, name, uuid, rbd_pool_name, rbd_image_name, bl

self.logger.debug(f"bdev_rbd_create: {bdev_name}, cluster_name {cluster_name}")
except Exception as ex:
self._put_cluster(cluster_name)
if cluster_name != None:
self._put_cluster(cluster_name)
errmsg = f"bdev_rbd_create {name} failed"
self.logger.exception(errmsg)
errmsg = f"{errmsg} with:\n{ex}"
Expand Down Expand Up @@ -867,7 +870,7 @@ def create_subsystem_safe(self, request, context):
peer_msg = self.get_peer_message(context)

self.logger.info(
f"Received request to create subsystem {request.subsystem_nqn}, enable_ha: {request.enable_ha}, max_namespaces: {request.max_namespaces}, no group append: {request.no_group_append}, dhchap_key: {request.dhchap_key}, context: {context}{peer_msg}")
f"Received request to create subsystem {request.subsystem_nqn}, enable_ha: {request.enable_ha}, max_namespaces: {request.max_namespaces}, no group append: {request.no_group_append}, context: {context}{peer_msg}")

if not request.enable_ha:
errmsg = f"{create_subsystem_error_prefix}: HA must be enabled for subsystems"
Expand Down Expand Up @@ -978,6 +981,9 @@ def create_subsystem_safe(self, request, context):
if context:
# Update gateway state
try:
if self.enable_key_encryption and request.dhchap_key:
request.dhchap_key = self.gateway_state.crypto.encrypt_text(request.dhchap_key)
request.key_encrypted = True
json_req = json_format.MessageToJson(
request, preserving_proto_field_name=True, including_default_value_fields=True)
self.gateway_state.add_subsystem(request.subsystem_nqn, json_req)
Expand Down Expand Up @@ -2357,7 +2363,7 @@ def add_host_safe(self, request, context):
self.logger.info(f"Received request to allow any host access for {request.subsystem_nqn}, context: {context}{peer_msg}")
else:
self.logger.info(
f"Received request to add host {request.host_nqn} to {request.subsystem_nqn}, psk: {request.psk}, dhchap: {request.dhchap_key}, context: {context}{peer_msg}")
f"Received request to add host {request.host_nqn} to {request.subsystem_nqn}, context: {context}{peer_msg}")

all_host_failure_prefix=f"Failure allowing open host access to {request.subsystem_nqn}"
host_failure_prefix=f"Failure adding host {request.host_nqn} to {request.subsystem_nqn}"
Expand Down Expand Up @@ -2547,6 +2553,13 @@ def add_host_safe(self, request, context):
if context:
# Update gateway state
try:
if self.enable_key_encryption:
if request.dhchap_key:
request.dhchap_key = self.gateway_state.crypto.encrypt_text(request.dhchap_key)
request.key_encrypted = True
if request.psk:
request.psk = self.gateway_state.crypto.encrypt_text(request.psk)
request.psk_encrypted = True
json_req = json_format.MessageToJson(
request, preserving_proto_field_name=True, including_default_value_fields=True)
self.gateway_state.add_host(request.subsystem_nqn, request.host_nqn, json_req)
Expand Down Expand Up @@ -2678,7 +2691,7 @@ def change_host_key_safe(self, request, context):
peer_msg = self.get_peer_message(context)
failure_prefix=f"Failure changing DH-HMAC-CHAP key for host {request.host_nqn} on subsystem {request.subsystem_nqn}"
self.logger.info(
f"Received request to change inband authentication key for host {request.host_nqn} on subsystem {request.subsystem_nqn}, dhchap: {request.dhchap_key}, context: {context}{peer_msg}")
f"Received request to change inband authentication key for host {request.host_nqn} on subsystem {request.subsystem_nqn}, context: {context}{peer_msg}")

if request.host_nqn == "*":
errmsg=f"{failure_prefix}: Host NQN can't be '*'"
Expand Down Expand Up @@ -2793,12 +2806,22 @@ def change_host_key_safe(self, request, context):
if context:
# Update gateway state
try:
key_encrypted = False
if self.enable_key_encryption and request.dhchap_key:
request.dhchap_key = self.gateway_state.crypto.encrypt_text(request.dhchap_key)
key_encrypted = True
psk_encrypted = False
if self.enable_key_encryption and host_psk:
host_psk = self.gateway_state.crypto.encrypt_text(host_psk)
psk_encrypted = True
add_req = pb2.add_host_req(subsystem_nqn=request.subsystem_nqn,
host_nqn=request.host_nqn,
psk=host_psk,
dhchap_key=request.dhchap_key)
dhchap_key=request.dhchap_key,
key_encrypted=key_encrypted,
psk_encrypted=psk_encrypted)
json_req = json_format.MessageToJson(
add_req, preserving_proto_field_name=True, including_default_value_fields=True)
add_req, preserving_proto_field_name=True, including_default_value_fields=True)
self.gateway_state.add_host(request.subsystem_nqn, request.host_nqn, json_req)
except Exception as ex:
errmsg = f"Error persisting host change key for host {request.host_nqn} in {request.subsystem_nqn}"
Expand Down Expand Up @@ -3454,7 +3477,7 @@ def change_subsystem_key_safe(self, request, context):
peer_msg = self.get_peer_message(context)
failure_prefix=f"Failure changing DH-HMAC-CHAP key for subsystem {request.subsystem_nqn}"
self.logger.info(
f"Received request to change inband authentication key for subsystem {request.subsystem_nqn}, dhchap: {request.dhchap_key}, context: {context}{peer_msg}")
f"Received request to change inband authentication key for subsystem {request.subsystem_nqn}, context: {context}{peer_msg}")

if not GatewayState.is_key_element_valid(request.subsystem_nqn):
errmsg = f"{failure_prefix}: Invalid subsystem NQN \"{request.subsystem_nqn}\", contains invalid characters"
Expand Down Expand Up @@ -3501,12 +3524,17 @@ def change_subsystem_key_safe(self, request, context):

assert subsys_entry, f"Can't find entry for subsystem {request.subsystem_nqn}"
try:
key_encrypted = False
if self.enable_key_encryption and request.dhchap_key:
request.dhchap_key = self.gateway_state.crypto.encrypt_text(request.dhchap_key)
key_encrypted = True
create_req = pb2.create_subsystem_req(subsystem_nqn=request.subsystem_nqn,
serial_number=subsys_entry["serial_number"],
max_namespaces=subsys_entry["max_namespaces"],
enable_ha=subsys_entry["enable_ha"],
no_group_append=subsys_entry["no_group_append"],
dhchap_key=request.dhchap_key)
dhchap_key=request.dhchap_key,
key_encrypted=key_encrypted)
json_req = json_format.MessageToJson(
create_req, preserving_proto_field_name=True, including_default_value_fields=True)
self.gateway_state.add_subsystem(request.subsystem_nqn, json_req)
Expand All @@ -3532,7 +3560,6 @@ def change_subsystem_key_safe(self, request, context):
except Excpetion:
pass


return pb2.req_status(status=0, error_message=os.strerror(0))

def change_subsystem_key(self, request, context=None):
Expand Down
3 changes: 3 additions & 0 deletions control/proto/gateway.proto
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,7 @@ message create_subsystem_req {
bool enable_ha = 4;
optional bool no_group_append = 5;
optional string dhchap_key = 6;
optional bool key_encrypted = 7;
}

message delete_subsystem_req {
Expand All @@ -215,6 +216,8 @@ message add_host_req {
string host_nqn = 2;
optional string psk = 3;
optional string dhchap_key = 4;
optional bool psk_encrypted = 5;
optional bool key_encrypted = 6;
}

message change_host_key_req {
Expand Down
27 changes: 26 additions & 1 deletion control/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
from .config import GatewayConfig
from .utils import GatewayLogger
from .utils import GatewayUtils
from .utils import GatewayUtilsCrypto
from .cephutils import CephUtils
from .prometheus import start_exporter

Expand Down Expand Up @@ -114,6 +115,20 @@ def __init__(self, config: GatewayConfig):
self.monitor_client_log_file_path = None
self.omap_state = None
self.omap_lock = None
enc_key = None
enc_key_file = self.config.get_with_default("gateway", "encryption_key", "")
if enc_key_file:
try:
enc_key = GatewayUtilsCrypto.read_encryption_key(enc_key_file)
except Exception:
self.logger.exception(f"Got an error trying to read encryption key {enc_key_file}, will use fallback key")
if enc_key:
self.logger.info(f"Read encryption key from {enc_key_file}")
else:
# Use this key as the fallback in case the user didn't set his/her own key
self.logger.warning(f"No valid key file was set, will use fallback encryption key")
enc_key = b"Ubp_Ll9pnyXtluCvqyBrnta2Y7ju9qexc_f0iWYnhWI="
self.crypto = GatewayUtilsCrypto(enc_key)

self.name = self.config.get("gateway", "name")
if not self.name:
Expand Down Expand Up @@ -235,7 +250,8 @@ def serve(self):
self._start_discovery_service()

# Register service implementation with server
gateway_state = GatewayStateHandler(self.config, local_state, omap_state, self.gateway_rpc_caller, f"gateway-{self.name}")
gateway_state = GatewayStateHandler(self.config, local_state, omap_state,
self.gateway_rpc_caller, self.crypto, f"gateway-{self.name}")
self.omap_lock = OmapLock(omap_state, gateway_state, self.rpc_lock)
self.gateway_rpc = GatewayService(self.config, gateway_state, self.rpc_lock, self.omap_lock, self.group_id, self.spdk_rpc_client, self.spdk_rpc_subsystems_client, self.ceph_utils)
self.server = self._grpc_server(self._gateway_address())
Expand Down Expand Up @@ -764,6 +780,9 @@ def gateway_rpc_caller(self, requests, is_add_req):
if key.startswith(GatewayState.SUBSYSTEM_PREFIX):
if is_add_req:
req = json_format.Parse(val, pb2.create_subsystem_req(), ignore_unknown_fields=True)
if req.key_encrypted:
req.dhchap_key = self.crypto.decrypt_text(req.dhchap_key)
req.key_encrypted = False
self.gateway_rpc.create_subsystem(req)
else:
req = json_format.Parse(val,
Expand All @@ -789,6 +808,12 @@ def gateway_rpc_caller(self, requests, is_add_req):
elif key.startswith(GatewayState.HOST_PREFIX):
if is_add_req:
req = json_format.Parse(val, pb2.add_host_req(), ignore_unknown_fields=True)
if req.key_encrypted:
req.dhchap_key = self.crypto.decrypt_text(req.dhchap_key)
req.key_encrypted = False
if req.psk_encrypted:
req.psk = self.crypto.decrypt_text(req.psk)
req.psk_encrypted = False
self.gateway_rpc.add_host(req)
else:
req = json_format.Parse(val, pb2.remove_host_req(), ignore_unknown_fields=True)
Expand Down
Loading

0 comments on commit ee5208a

Please sign in to comment.