Skip to content

Commit b54c9e8

Browse files
committed
Merge PR ceph#57192 into main
* refs/pull/57192/head: PendingReleaseNotes: add note on the client incompatibility health warning and feature bit doc/cephfs: add client_mds_auth_caps client feature bit doc/cephfs: add missing client feature bits doc/cephfs: document MDS_CLIENTS_BROKEN_ROOTSQUASH health error qa: add tests for MDS_CLIENTS_BROKEN_ROOTSQUASH mds: raise health warning if client lacks feature for root_squash mon/MDSMonitor: add note about missing metadata inclusion mds: check relevant caps for fs include root_squash mds: refactor out fs_name match in MDSAuthCaps qa: test for root_squash with multiple caps qa: pass kwargs to mount from remount qa: simplify update_attrs and only update relevant keys client: allow overriding client features Reviewed-by: Xiubo Li <[email protected]> Reviewed-by: Rishabh Dave <[email protected]>
2 parents dee6e6a + e70f005 commit b54c9e8

File tree

17 files changed

+291
-73
lines changed

17 files changed

+291
-73
lines changed

PendingReleaseNotes

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,13 @@ CephFS: Disallow delegating preallocated inode ranges to clients. Config
186186
confirmation flag when some MDSs exhibit health warning MDS_TRIM or
187187
MDS_CACHE_OVERSIZED. This is to prevent accidental MDS failover causing
188188
further delays in recovery.
189+
* CephFS: fixes to the implementation of the ``root_squash`` mechanism enabled
190+
via cephx ``mds`` caps on a client credential require a new client feature
191+
bit, ``client_mds_auth_caps``. Clients using credentials with ``root_squash``
192+
without this feature will trigger the MDS to raise a HEALTH_ERR on the
193+
cluster, MDS_CLIENTS_BROKEN_ROOTSQUASH. See the documentation on this warning
194+
and the new feature bit for more information.
195+
189196

190197
>=18.0.0
191198

doc/cephfs/administration.rst

Lines changed: 50 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -306,31 +306,47 @@ Clients that are missing newly added features will be evicted automatically.
306306

307307
Here are the current CephFS features and first release they came out:
308308

309-
+------------------+--------------+-----------------+
310-
| Feature | Ceph release | Upstream Kernel |
311-
+==================+==============+=================+
312-
| jewel | jewel | 4.5 |
313-
+------------------+--------------+-----------------+
314-
| kraken | kraken | 4.13 |
315-
+------------------+--------------+-----------------+
316-
| luminous | luminous | 4.13 |
317-
+------------------+--------------+-----------------+
318-
| mimic | mimic | 4.19 |
319-
+------------------+--------------+-----------------+
320-
| reply_encoding | nautilus | 5.1 |
321-
+------------------+--------------+-----------------+
322-
| reclaim_client | nautilus | N/A |
323-
+------------------+--------------+-----------------+
324-
| lazy_caps_wanted | nautilus | 5.1 |
325-
+------------------+--------------+-----------------+
326-
| multi_reconnect | nautilus | 5.1 |
327-
+------------------+--------------+-----------------+
328-
| deleg_ino | octopus | 5.6 |
329-
+------------------+--------------+-----------------+
330-
| metric_collect | pacific | N/A |
331-
+------------------+--------------+-----------------+
332-
| alternate_name | pacific | PLANNED |
333-
+------------------+--------------+-----------------+
309+
+----------------------------+--------------+-----------------+
310+
| Feature | Ceph release | Upstream Kernel |
311+
+============================+==============+=================+
312+
| jewel | jewel | 4.5 |
313+
+----------------------------+--------------+-----------------+
314+
| kraken | kraken | 4.13 |
315+
+----------------------------+--------------+-----------------+
316+
| luminous | luminous | 4.13 |
317+
+----------------------------+--------------+-----------------+
318+
| mimic | mimic | 4.19 |
319+
+----------------------------+--------------+-----------------+
320+
| reply_encoding | nautilus | 5.1 |
321+
+----------------------------+--------------+-----------------+
322+
| reclaim_client | nautilus | N/A |
323+
+----------------------------+--------------+-----------------+
324+
| lazy_caps_wanted | nautilus | 5.1 |
325+
+----------------------------+--------------+-----------------+
326+
| multi_reconnect | nautilus | 5.1 |
327+
+----------------------------+--------------+-----------------+
328+
| deleg_ino | octopus | 5.6 |
329+
+----------------------------+--------------+-----------------+
330+
| metric_collect | pacific | N/A |
331+
+----------------------------+--------------+-----------------+
332+
| alternate_name | pacific | 6.5 |
333+
+----------------------------+--------------+-----------------+
334+
| notify_session_state | quincy | 5.19 |
335+
+----------------------------+--------------+-----------------+
336+
| op_getvxattr | quincy | 6.0 |
337+
+----------------------------+--------------+-----------------+
338+
| 32bits_retry_fwd | reef | 6.6 |
339+
+----------------------------+--------------+-----------------+
340+
| new_snaprealm_info | reef | UNKNOWN |
341+
+----------------------------+--------------+-----------------+
342+
| has_owner_uidgid | reef | 6.6 |
343+
+----------------------------+--------------+-----------------+
344+
| client_mds_auth_caps | squid+bp | PLANNED |
345+
+----------------------------+--------------+-----------------+
346+
347+
..
348+
Comment: use `git describe --tags --abbrev=0 <commit>` to lookup release
349+
334350

335351
CephFS Feature Descriptions
336352

@@ -388,6 +404,15 @@ Clients can send performance metric to MDS if MDS support this feature.
388404
Clients can set and understand "alternate names" for directory entries. This is
389405
to be used for encrypted file name support.
390406

407+
::
408+
409+
client_mds_auth_caps
410+
411+
To effectively implement ``root_squash`` in a client's ``mds`` caps, the client
412+
must understand that it is enforcing ``root_squash`` and other cap metadata.
413+
Clients without this feature are in danger of dropping updates to files. It is
414+
recommend to set this feature bit.
415+
391416

392417
Global settings
393418
---------------

doc/cephfs/health-messages.rst

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -252,3 +252,20 @@ other daemons, please see :ref:`health-checks`.
252252
dirty data for cap revokes). If ``defer_client_eviction_on_laggy_osds`` is
253253
set to true (default true), client eviction will not take place and thus
254254
this health warning will be generated.
255+
256+
``MDS_CLIENTS_BROKEN_ROOTSQUASH``
257+
---------------------------------
258+
Message
259+
"X client(s) with broken root_squash implementation (MDS_CLIENTS_BROKEN_ROOTSQUASH)"
260+
261+
Description
262+
A bug was discovered in root_squash which would potentially lose changes made by a
263+
client restricted with root_squash caps. The fix required a change to the protocol
264+
and a client upgrade is required.
265+
266+
This is a HEALTH_ERR warning because of the danger of inconsistency and lost
267+
data. It is recommended to either upgrade your clients, discontinue using
268+
root_squash in the interim, or silence the warning if desired.
269+
270+
To evict and permanently block broken clients from connecting to the
271+
cluster, set the ``required_client_feature`` bit ``client_mds_auth_caps``.

qa/tasks/cephfs/mount.py

Lines changed: 16 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -544,30 +544,21 @@ def _verify_attrs(self, **kwargs):
544544
raise RuntimeError('value of attributes should be either str '
545545
f'or None. {k} - {v}')
546546

547-
def update_attrs(self, client_id=None, client_keyring_path=None,
548-
client_remote=None, hostfs_mntpt=None, cephfs_name=None,
549-
cephfs_mntpt=None):
550-
if not (client_id or client_keyring_path or client_remote or
551-
cephfs_name or cephfs_mntpt or hostfs_mntpt):
552-
return
553-
554-
self._verify_attrs(client_id=client_id,
555-
client_keyring_path=client_keyring_path,
556-
hostfs_mntpt=hostfs_mntpt, cephfs_name=cephfs_name,
557-
cephfs_mntpt=cephfs_mntpt)
558-
559-
if client_id:
560-
self.client_id = client_id
561-
if client_keyring_path:
562-
self.client_keyring_path = client_keyring_path
563-
if client_remote:
564-
self.client_remote = client_remote
565-
if hostfs_mntpt:
566-
self.hostfs_mntpt = hostfs_mntpt
567-
if cephfs_name:
568-
self.cephfs_name = cephfs_name
569-
if cephfs_mntpt:
570-
self.cephfs_mntpt = cephfs_mntpt
547+
def update_attrs(self, **kwargs):
548+
verify_keys = [
549+
'client_id',
550+
'client_keyring_path',
551+
'hostfs_mntpt',
552+
'cephfs_name',
553+
'cephfs_mntpt',
554+
]
555+
556+
self._verify_attrs(**{key: kwargs[key] for key in verify_keys if key in kwargs})
557+
558+
for k in verify_keys:
559+
v = kwargs.get(k)
560+
if v is not None:
561+
setattr(self, k, v)
571562

572563
def remount(self, **kwargs):
573564
"""
@@ -590,7 +581,7 @@ def remount(self, **kwargs):
590581

591582
self.update_attrs(**kwargs)
592583

593-
retval = self.mount(mntopts=mntopts, check_status=check_status)
584+
retval = self.mount(mntopts=mntopts, check_status=check_status, **kwargs)
594585
# avoid this scenario (again): mount command might've failed and
595586
# check_status might have silenced the exception, yet we attempt to
596587
# wait which might lead to an error.

qa/tasks/cephfs/test_admin.py

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1478,6 +1478,101 @@ def test_multifs_single_path_rootsquash(self):
14781478
self.captester2.conduct_neg_test_for_chown_caps()
14791479
self.captester2.conduct_neg_test_for_truncate_caps()
14801480

1481+
def test_multifs_rootsquash_nofeature(self):
1482+
"""
1483+
That having root_squash on one fs doesn't prevent access to others.
1484+
"""
1485+
1486+
if not isinstance(self.mount_a, FuseMount):
1487+
self.skipTest("only FUSE client has CEPHFS_FEATURE_MDS_AUTH_CAPS "
1488+
"needed to enforce root_squash MDS caps")
1489+
1490+
self.fs1 = self.fs
1491+
self.fs2 = self.mds_cluster.newfs('testcephfs2')
1492+
1493+
self.mount_a.umount_wait()
1494+
1495+
# Authorize client to fs1
1496+
FS_AUTH_CAPS = (('/', 'rw'),)
1497+
self.fs1.authorize(self.client_id, FS_AUTH_CAPS)
1498+
1499+
FS_AUTH_CAPS = (('/', 'rw', 'root_squash'),)
1500+
keyring = self.fs2.authorize(self.client_id, FS_AUTH_CAPS)
1501+
1502+
CEPHFS_FEATURE_MDS_AUTH_CAPS_CHECK = 21
1503+
# all but CEPHFS_FEATURE_MDS_AUTH_CAPS_CHECK
1504+
features = ",".join([str(i) for i in range(CEPHFS_FEATURE_MDS_AUTH_CAPS_CHECK)])
1505+
mntargs = [f"--client_debug_inject_features={features}"]
1506+
1507+
# should succeed
1508+
with self.assert_cluster_log("report clients with broken root_squash", present=False):
1509+
keyring_path = self.mount_a.client_remote.mktemp(data=keyring)
1510+
self.mount_a.remount(client_id=self.client_id, client_keyring_path=keyring_path, mntargs=mntargs, cephfs_name=self.fs1.name)
1511+
1512+
captester = CapTester(self.mount_a, '/')
1513+
captester.conduct_pos_test_for_read_caps()
1514+
captester.conduct_pos_test_for_open_caps()
1515+
1516+
def test_rootsquash_nofeature(self):
1517+
"""
1518+
That having root_squash on an fs without the feature bit raises a HEALTH_ERR warning.
1519+
"""
1520+
1521+
if not isinstance(self.mount_a, FuseMount):
1522+
self.skipTest("only FUSE client has CEPHFS_FEATURE_MDS_AUTH_CAPS "
1523+
"needed to enforce root_squash MDS caps")
1524+
1525+
self.mount_a.umount_wait()
1526+
self.mount_b.umount_wait()
1527+
1528+
FS_AUTH_CAPS = (('/', 'rw', 'root_squash'),)
1529+
keyring = self.fs.authorize(self.client_id, FS_AUTH_CAPS)
1530+
1531+
CEPHFS_FEATURE_MDS_AUTH_CAPS_CHECK = 21
1532+
# all but CEPHFS_FEATURE_MDS_AUTH_CAPS_CHECK
1533+
features = ",".join([str(i) for i in range(CEPHFS_FEATURE_MDS_AUTH_CAPS_CHECK)])
1534+
mntargs = [f"--client_debug_inject_features={features}"]
1535+
1536+
# should succeed
1537+
with self.assert_cluster_log("with broken root_squash implementation"):
1538+
keyring_path = self.mount_a.client_remote.mktemp(data=keyring)
1539+
self.mount_a.remount(client_id=self.client_id, client_keyring_path=keyring_path, mntargs=mntargs, cephfs_name=self.fs.name)
1540+
self.wait_for_health("MDS_CLIENTS_BROKEN_ROOTSQUASH", 60)
1541+
self.assertFalse(self.mount_a.is_blocked())
1542+
1543+
self.mount_a.umount_wait()
1544+
self.wait_for_health_clear(60)
1545+
1546+
def test_rootsquash_nofeature_evict(self):
1547+
"""
1548+
That having root_squash on an fs without the feature bit can be evicted.
1549+
"""
1550+
1551+
if not isinstance(self.mount_a, FuseMount):
1552+
self.skipTest("only FUSE client has CEPHFS_FEATURE_MDS_AUTH_CAPS "
1553+
"needed to enforce root_squash MDS caps")
1554+
1555+
self.mount_a.umount_wait()
1556+
self.mount_b.umount_wait()
1557+
1558+
FS_AUTH_CAPS = (('/', 'rw', 'root_squash'),)
1559+
keyring = self.fs.authorize(self.client_id, FS_AUTH_CAPS)
1560+
1561+
CEPHFS_FEATURE_MDS_AUTH_CAPS_CHECK = 21
1562+
# all but CEPHFS_FEATURE_MDS_AUTH_CAPS_CHECK
1563+
features = ",".join([str(i) for i in range(CEPHFS_FEATURE_MDS_AUTH_CAPS_CHECK)])
1564+
mntargs = [f"--client_debug_inject_features={features}"]
1565+
1566+
# should succeed
1567+
keyring_path = self.mount_a.client_remote.mktemp(data=keyring)
1568+
self.mount_a.remount(client_id=self.client_id, client_keyring_path=keyring_path, mntargs=mntargs, cephfs_name=self.fs.name)
1569+
self.wait_for_health("MDS_CLIENTS_BROKEN_ROOTSQUASH", 60)
1570+
1571+
self.fs.required_client_features("add", "client_mds_auth_caps")
1572+
self.wait_for_health_clear(60)
1573+
self.assertTrue(self.mount_a.is_blocked())
1574+
1575+
14811576
def test_single_path_rootsquash_issue_56067(self):
14821577
"""
14831578
That a FS client using root squash MDS caps allows non-root user to write data

src/client/Client.cc

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -392,6 +392,12 @@ Client::Client(Messenger *m, MonClient *mc, Objecter *objecter_)
392392
if (cct->_conf->client_acl_type == "posix_acl")
393393
acl_type = POSIX_ACL;
394394

395+
if (auto str = cct->_conf->client_debug_inject_features; !str.empty()) {
396+
myfeatures = feature_bitset_t(str);
397+
} else {
398+
myfeatures = feature_bitset_t(CEPHFS_FEATURES_CLIENT_SUPPORTED);
399+
}
400+
395401
lru.lru_set_midpoint(cct->_conf->client_cache_mid);
396402

397403
// file handles
@@ -2354,7 +2360,7 @@ MetaSessionRef Client::_open_mds_session(mds_rank_t mds)
23542360

23552361
auto m = make_message<MClientSession>(CEPH_SESSION_REQUEST_OPEN);
23562362
m->metadata = metadata;
2357-
m->supported_features = feature_bitset_t(CEPHFS_FEATURES_CLIENT_SUPPORTED);
2363+
m->supported_features = myfeatures;
23582364
m->metric_spec = feature_bitset_t(CEPHFS_METRIC_FEATURES_ALL);
23592365
session->con->send_message2(std::move(m));
23602366
return session;

src/client/Client.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1936,6 +1936,8 @@ class Client : public Dispatcher, public md_config_obs_t {
19361936
uint64_t nr_write_request = 0;
19371937

19381938
std::vector<MDSCapAuth> cap_auths;
1939+
1940+
feature_bitset_t myfeatures;
19391941
};
19401942

19411943
/**

src/common/options/mds-client.yaml.in

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -251,6 +251,14 @@ options:
251251
default: 0
252252
services:
253253
- mds_client
254+
- name: client_debug_inject_features
255+
type: str
256+
level: dev
257+
services:
258+
- mds_client
259+
flags:
260+
- startup
261+
with_legacy: true
254262
- name: client_max_inline_size
255263
type: size
256264
level: dev

src/mds/Beacon.cc

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -486,6 +486,30 @@ void Beacon::notify_health(MDSRank const *mds)
486486
health.metrics.push_back(m);
487487
}
488488

489+
// Report a health warning if clients have broken root_squash
490+
if (auto c = mds->sessionmap.num_broken_root_squash_clients(); c > 0) {
491+
std::vector<MDSHealthMetric> metrics;
492+
493+
for (auto&& session : mds->sessionmap.get_broken_root_squash_clients()) {
494+
CachedStackStringStream css;
495+
*css << "Client " << session->get_human_name() << " has broken root_squash implementation";
496+
MDSHealthMetric m(MDS_HEALTH_CLIENTS_BROKEN_ROOTSQUASH, HEALTH_ERR, css->strv());
497+
m.metadata["client_id"] = stringify(session->get_client());
498+
metrics.emplace_back(std::move(m));
499+
}
500+
501+
if (metrics.size() <= (size_t)g_conf()->mds_health_summarize_threshold) {
502+
health.metrics.insert(std::end(health.metrics), std::make_move_iterator(std::begin(metrics)), std::make_move_iterator(std::end(metrics)));
503+
} else {
504+
CachedStackStringStream css;
505+
*css << "There are " << c << " clients with broken root_squash implementations";
506+
dout(20) << css->strv() << dendl;
507+
MDSHealthMetric m(MDS_HEALTH_CLIENTS_BROKEN_ROOTSQUASH, HEALTH_ERR, css->strv());
508+
m.metadata["client_count"] = stringify(c);
509+
health.metrics.push_back(std::move(m));
510+
}
511+
}
512+
489513
// Report if we have significantly exceeded our cache size limit
490514
if (mds->mdcache->cache_overfull()) {
491515
CachedStackStringStream css;

src/mds/MDSAuthCaps.h

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,10 @@ struct MDSCapMatch {
158158
bool match_path(std::string_view target_path) const;
159159
std::string to_string();
160160

161+
bool match_fs(std::string_view target_fs) const {
162+
return fs_name == target_fs || fs_name.empty() || fs_name == "*";
163+
}
164+
161165
void encode(ceph::buffer::list& bl) const {
162166
ENCODE_START(1, 1, bl);
163167
encode(uid, bl);
@@ -276,8 +280,7 @@ class MDSAuthCaps
276280
}
277281

278282
for (const MDSCapGrant &g : grants) {
279-
if (g.match.fs_name == fs_name || g.match.fs_name.empty() ||
280-
g.match.fs_name == "*") {
283+
if (g.match.match_fs(fs_name)) {
281284
if (mask & MAY_READ && g.spec.allow_read()) {
282285
return true;
283286
}
@@ -300,10 +303,12 @@ class MDSAuthCaps
300303
}
301304
}
302305

303-
bool root_squash_in_caps() const {
304-
for (const MDSCapGrant &g : grants) {
305-
if (g.match.root_squash) {
306-
return true;
306+
bool root_squash_in_caps(std::string_view fs_name) const {
307+
for (const MDSCapGrant& g : grants) {
308+
if (g.match.match_fs(fs_name)) {
309+
if (g.match.root_squash) {
310+
return true;
311+
}
307312
}
308313
}
309314
return false;

0 commit comments

Comments
 (0)