From 13664c11d01976d1b711b8bea184ca2d7a55c10e Mon Sep 17 00:00:00 2001 From: Samuel Walladge Date: Wed, 31 May 2023 12:23:11 +0930 Subject: [PATCH] Enable rgw trust forwarded https when https proxy This option is required for server-side encryption to be allowed if radosgw is behind a reverse proxy, such as here when certificates are configured and apache2 is running. ref. https://docs.ceph.com/en/latest/radosgw/encryption/ It is safe to always enable when https is configured in the charm, because it will be securely behind the reverse proxy in the unit. This option must not be enabled when https is not configured in the charm, because this would allow clients to spoof headers. Closes-Bug: #2021560 Change-Id: I940f9b2f424a3d98936b5f185bf8f87b71091317 (cherry picked from commit 541ceec4018e311cef1517a62eefa28cd53bc162) --- hooks/ceph_radosgw_context.py | 1 + templates/ceph.conf | 3 + unit_tests/test_ceph_radosgw_context.py | 112 +++++++++++++++++++++--- 3 files changed, 105 insertions(+), 11 deletions(-) diff --git a/hooks/ceph_radosgw_context.py b/hooks/ceph_radosgw_context.py index c951fb6..e57ceef 100644 --- a/hooks/ceph_radosgw_context.py +++ b/hooks/ceph_radosgw_context.py @@ -295,6 +295,7 @@ def __call__(self): 'rgw_swift_versioning': config('rgw-swift-versioning-enabled'), 'relaxed_s3_bucket_names': config('relaxed-s3-bucket-names'), 'frontend': http_frontend, + 'behind_https_proxy': https(), } # NOTE(dosaboy): these sections must correspond to what is supported in diff --git a/templates/ceph.conf b/templates/ceph.conf index d728ac0..d126b20 100644 --- a/templates/ceph.conf +++ b/templates/ceph.conf @@ -16,6 +16,9 @@ ms bind ipv6 = true {% endif %} rgw swift versioning enabled = {{ rgw_swift_versioning }} rgw relaxed s3 bucket names = {{ relaxed_s3_bucket_names }} +{% if behind_https_proxy -%} +rgw trust forwarded https = true +{% endif %} {% if global -%} # The following are user-provided options provided via the config-flags charm option. # User-provided [global] section config diff --git a/unit_tests/test_ceph_radosgw_context.py b/unit_tests/test_ceph_radosgw_context.py index cfd07e0..f3f9553 100644 --- a/unit_tests/test_ceph_radosgw_context.py +++ b/unit_tests/test_ceph_radosgw_context.py @@ -74,6 +74,7 @@ def test_ctxt(self, _harelation_ids, _ctxtrelation_ids, _haconfig, class MonContextTest(CharmTestCase): + maxDiff = None def setUp(self): super(MonContextTest, self).setUp(context, TO_PATCH) @@ -95,10 +96,16 @@ def plain_list_stub(key): else: return [] + @patch('charmhelpers.contrib.hahelpers.cluster.relation_ids') + @patch('charmhelpers.contrib.hahelpers.cluster.config_get') @patch.object(ceph, 'config', lambda *args: '{"client.radosgw.gateway": {"rgw init timeout": 60}}') @patch.object(context, 'ensure_host_resolvable_v6') - def test_ctxt(self, mock_ensure_rsv_v6): + def test_ctxt( + self, mock_ensure_rsv_v6, mock_config_get, mock_relation_ids + ): + mock_relation_ids.return_value = [] + mock_config_get.side_effect = self.test_config.get self.socket.gethostname.return_value = 'testhost' mon_ctxt = context.MonContext() addresses = ['10.5.4.1', '10.5.4.2', '10.5.4.3'] @@ -136,7 +143,8 @@ def _relation_get(attr, unit, rid): 'frontend': 'beast', 'relaxed_s3_bucket_names': False, 'rgw_zonegroup': 'zonegroup1', - 'rgw_realm': 'realmX' + 'rgw_realm': 'realmX', + 'behind_https_proxy': False, } self.assertEqual(expect, mon_ctxt()) self.assertFalse(mock_ensure_rsv_v6.called) @@ -148,10 +156,72 @@ def _relation_get(attr, unit, rid): self.assertEqual(expect, mon_ctxt()) self.assertTrue(mock_ensure_rsv_v6.called) + @patch('ceph_radosgw_context.https') @patch.object(ceph, 'config', lambda *args: '{"client.radosgw.gateway": {"rgw init timeout": 60}}') @patch.object(context, 'ensure_host_resolvable_v6') - def test_list_of_addresses_from_ceph_proxy(self, mock_ensure_rsv_v6): + def test_ctxt_with_https_proxy(self, mock_ensure_rsv_v6, mock_https): + mock_https.return_value = True + self.socket.gethostname.return_value = 'testhost' + mon_ctxt = context.MonContext() + addresses = ['10.5.4.1', '10.5.4.2', '10.5.4.3'] + + def _relation_get(attr, unit, rid): + if attr == 'ceph-public-address': + return addresses.pop() + elif attr == 'auth': + return 'cephx' + elif attr == 'rgw.testhost_key': + return 'testkey' + elif attr == 'fsid': + return 'testfsid' + + self.relation_get.side_effect = _relation_get + self.relation_ids.return_value = ['mon:6'] + self.related_units.return_value = ['ceph/0', 'ceph/1', 'ceph/2'] + self.multisite.plain_list = self.plain_list_stub + self.determine_api_port.return_value = 70 + expect = { + 'auth_supported': 'cephx', + 'hostname': 'testhost', + 'mon_hosts': '10.5.4.1 10.5.4.2 10.5.4.3', + 'old_auth': False, + 'systemd_rgw': True, + 'unit_public_ip': '10.255.255.255', + 'use_syslog': 'false', + 'loglevel': 1, + 'port': 70, + 'client_radosgw_gateway': {'rgw init timeout': 60}, + 'ipv6': False, + 'rgw_zone': 'default', + 'fsid': 'testfsid', + 'rgw_swift_versioning': False, + 'frontend': 'beast', + 'relaxed_s3_bucket_names': False, + 'rgw_zonegroup': 'zonegroup1', + 'rgw_realm': 'realmX', + 'behind_https_proxy': True, + } + self.assertEqual(expect, mon_ctxt()) + self.assertFalse(mock_ensure_rsv_v6.called) + + self.test_config.set('prefer-ipv6', True) + addresses = ['10.5.4.1', '10.5.4.2', '10.5.4.3'] + expect['ipv6'] = True + expect['port'] = "[::]:%s" % (70) + self.assertEqual(expect, mon_ctxt()) + self.assertTrue(mock_ensure_rsv_v6.called) + + @patch('charmhelpers.contrib.hahelpers.cluster.relation_ids') + @patch('charmhelpers.contrib.hahelpers.cluster.config_get') + @patch.object(ceph, 'config', lambda *args: + '{"client.radosgw.gateway": {"rgw init timeout": 60}}') + @patch.object(context, 'ensure_host_resolvable_v6') + def test_list_of_addresses_from_ceph_proxy( + self, mock_ensure_rsv_v6, mock_config_get, mock_relation_ids + ): + mock_relation_ids.return_value = [] + mock_config_get.side_effect = self.test_config.get self.socket.gethostname.return_value = 'testhost' mon_ctxt = context.MonContext() addresses = ['10.5.4.1 10.5.4.2 10.5.4.3'] @@ -190,7 +260,8 @@ def _relation_get(attr, unit, rid): 'frontend': 'beast', 'relaxed_s3_bucket_names': False, 'rgw_zonegroup': 'zonegroup1', - 'rgw_realm': 'realmX' + 'rgw_realm': 'realmX', + 'behind_https_proxy': False, } self.assertEqual(expect, mon_ctxt()) self.assertFalse(mock_ensure_rsv_v6.called) @@ -202,9 +273,13 @@ def _relation_get(attr, unit, rid): self.assertEqual(expect, mon_ctxt()) self.assertTrue(mock_ensure_rsv_v6.called) + @patch('charmhelpers.contrib.hahelpers.cluster.relation_ids') + @patch('charmhelpers.contrib.hahelpers.cluster.config_get') @patch.object(ceph, 'config', lambda *args: '{"client.radosgw.gateway": {"rgw init timeout": 60}}') - def test_ctxt_missing_data(self): + def test_ctxt_missing_data(self, mock_config_get, mock_relation_ids): + mock_relation_ids.return_value = [] + mock_config_get.side_effect = self.test_config.get self.socket.gethostname.return_value = 'testhost' mon_ctxt = context.MonContext() self.relation_get.return_value = None @@ -212,9 +287,13 @@ def test_ctxt_missing_data(self): self.related_units.return_value = ['ceph/0', 'ceph/1', 'ceph/2'] self.assertEqual({}, mon_ctxt()) + @patch('charmhelpers.contrib.hahelpers.cluster.relation_ids') + @patch('charmhelpers.contrib.hahelpers.cluster.config_get') @patch.object(ceph, 'config', lambda *args: '{"client.radosgw.gateway": {"rgw init timeout": 60}}') - def test_ctxt_inconsistent_auths(self): + def test_ctxt_inconsistent_auths(self, mock_config_get, mock_relation_ids): + mock_relation_ids.return_value = [] + mock_config_get.side_effect = self.test_config.get self.socket.gethostname.return_value = 'testhost' mon_ctxt = context.MonContext() addresses = ['10.5.4.1', '10.5.4.2', '10.5.4.3'] @@ -253,13 +332,18 @@ def _relation_get(attr, unit, rid): 'frontend': 'beast', 'relaxed_s3_bucket_names': False, 'rgw_zonegroup': 'zonegroup1', - 'rgw_realm': 'realmX' + 'rgw_realm': 'realmX', + 'behind_https_proxy': False, } self.assertEqual(expect, mon_ctxt()) + @patch('charmhelpers.contrib.hahelpers.cluster.relation_ids') + @patch('charmhelpers.contrib.hahelpers.cluster.config_get') @patch.object(ceph, 'config', lambda *args: '{"client.radosgw.gateway": {"rgw init timeout": 60}}') - def test_ctxt_consistent_auths(self): + def test_ctxt_consistent_auths(self, mock_config_get, mock_relation_ids): + mock_relation_ids.return_value = [] + mock_config_get.side_effect = self.test_config.get self.socket.gethostname.return_value = 'testhost' mon_ctxt = context.MonContext() addresses = ['10.5.4.1', '10.5.4.2', '10.5.4.3'] @@ -298,7 +382,8 @@ def _relation_get(attr, unit, rid): 'frontend': 'beast', 'relaxed_s3_bucket_names': False, 'rgw_zonegroup': 'zonegroup1', - 'rgw_realm': 'realmX' + 'rgw_realm': 'realmX', + 'behind_https_proxy': False, } self.assertEqual(expect, mon_ctxt()) @@ -360,9 +445,13 @@ def _compare_version(package, version): _test_version = '16.2.0' context.validate_http_frontend('beast') + @patch('charmhelpers.contrib.hahelpers.cluster.relation_ids') + @patch('charmhelpers.contrib.hahelpers.cluster.config_get') @patch.object(ceph, 'config', lambda *args: '{"client.radosgw.gateway": {"rgw init timeout": 60}}') - def test_ctxt_inconsistent_fsids(self): + def test_ctxt_inconsistent_fsids(self, mock_config_get, mock_relation_ids): + mock_relation_ids.return_value = [] + mock_config_get.side_effect = self.test_config.get self.socket.gethostname.return_value = 'testhost' mon_ctxt = context.MonContext() addresses = ['10.5.4.1', '10.5.4.2', '10.5.4.3'] @@ -401,7 +490,8 @@ def _relation_get(attr, unit, rid): 'frontend': 'beast', 'relaxed_s3_bucket_names': False, 'rgw_zonegroup': 'zonegroup1', - 'rgw_realm': 'realmX' + 'rgw_realm': 'realmX', + 'behind_https_proxy': False, } self.assertEqual(expect, mon_ctxt())