Merge pull request #15991 from dillaman/wip-rbd-auth-profile

mon,osd: new rbd-based cephx cap profiles

Reviewed-by: Sage Weil <sage@redhat.com>
This commit is contained in:
Sage Weil 2017-07-21 22:38:42 -05:00 committed by GitHub
commit 4e6487cad4
18 changed files with 557 additions and 193 deletions

View File

@ -239,4 +239,9 @@
* The 'mon_warn_osd_usage_min_max_delta' config option has been
removed and the associated health warning has been disabled because
it does not address clusters undergoing recovery or CRUSH rules that do
not target all devices in the cluster.
not target all devices in the cluster.
* Specifying user authorization capabilities for RBD clients has been
simplified. The general syntax for using RBD capability profiles is
"mon 'profile rbd' osd 'profile rbd[-read-only][ pool={pool-name}[, ...]]'".
For more details see "User Management" in the documentation.

View File

@ -98,24 +98,24 @@ capabilities when creating or updating a user.
Capability syntax follows the form::
{daemon-type} 'allow {capability}' [{daemon-type} 'allow {capability}']
{daemon-type} '{capspec}[, {capspec} ...]'
- **Monitor Caps:** Monitor capabilities include ``r``, ``w``, ``x`` and
``allow profile {cap}``. For example::
- **Monitor Caps:** Monitor capabilities include ``r``, ``w``, ``x`` access
settings or ``profile {name}``. For example::
mon 'allow rwx'
mon 'allow profile osd'
mon 'profile osd'
- **OSD Caps:** OSD capabilities include ``r``, ``w``, ``x``, ``class-read``,
``class-write`` and ``profile osd``. Additionally, OSD capabilities also
allow for pool and namespace settings. ::
- **OSD Caps:** OSD capabilities include ``r``, ``w``, ``x``, ``class-read``,
``class-write`` access settings or ``profile {name}``. Additionally, OSD
capabilities also allow for pool and namespace settings. ::
osd 'allow {capability}' [pool={poolname}] [namespace={namespace-name}]
osd 'allow {access} [pool={pool-name} [namespace={namespace-name}]]'
osd 'profile {name} [pool={pool-name} [namespace={namespace-name}]]'
- **Metadata Server Caps:** Metadata server capability simply requires ``allow``,
or blank and does not parse anything further. ::
mds 'allow'
@ -168,20 +168,20 @@ The following entries describe each capability.
admin commands.
``profile osd``
``profile osd`` (Monitor only)
:Description: Gives a user permissions to connect as an OSD to other OSDs or
monitors. Conferred on OSDs to enable OSDs to handle replication
heartbeat traffic and status reporting.
``profile mds``
``profile mds`` (Monitor only)
:Description: Gives a user permissions to connect as a MDS to other MDSs or
monitors.
``profile bootstrap-osd``
``profile bootstrap-osd`` (Monitor only)
:Description: Gives a user permissions to bootstrap an OSD. Conferred on
deployment tools such as ``ceph-disk``, ``ceph-deploy``, etc.
@ -189,13 +189,23 @@ The following entries describe each capability.
bootstrapping an OSD.
``profile bootstrap-mds``
``profile bootstrap-mds`` (Monitor only)
:Description: Gives a user permissions to bootstrap a metadata server.
Conferred on deployment tools such as ``ceph-deploy``, etc.
so they have permissions to add keys, etc. when bootstrapping
a metadata server.
``profile rbd`` (Monitor and OSD)
:Description: Gives a user permissions to manipulate RBD images. When used
as a Monitor cap, it provides the minimal privileges required
by an RBD client application. When used as an OSD cap, it
provides read-write access to an RBD client application.
``profile rbd-read-only`` (OSD only)
:Description: Gives a user read-only permissions to an RBD image.
Pool

View File

@ -71,11 +71,11 @@ To configure Ceph for use with ``libvirt``, perform the following steps:
rbd pool init <pool-name>
#. `Create a Ceph User`_ (or use ``client.admin`` for version 0.9.7 and
earlier). The following example uses the Ceph user name ``client.libvirt``
#. `Create a Ceph User`_ (or use ``client.admin`` for version 0.9.7 and
earlier). The following example uses the Ceph user name ``client.libvirt``
and references ``libvirt-pool``. ::
ceph auth get-or-create client.libvirt mon 'allow r' osd 'allow class-read object_prefix rbd_children, allow rwx pool=libvirt-pool'
ceph auth get-or-create client.libvirt mon 'profile rbd' osd 'profile rbd pool=libvirt-pool'
Verify the name exists. ::

View File

@ -25,6 +25,30 @@ Create a Block Device Pool
.. note:: The ``rbd`` tool assumes a default pool name of 'rbd' when not
provided.
Create a Block Device User
==========================
Unless specified, the ``rbd`` command will access the Ceph cluster using the ID
``admin``. This ID allows full administrative access to the cluster. It is
recommended that you utilize a more restricted user wherever possible.
To `create a Ceph user`_, with ``ceph`` specify the ``auth get-or-create``
command, user name, monitor caps, and OSD caps::
ceph auth get-or-create client.{ID} mon 'profile rbd' osd 'profile {profile name} [pool={pool-name}][, profile ...]'
For example, to create a user ID named ``qemu`` with read-write access to the
pool ``vms`` and read-only access to the pool ``images``, execute the
following::
ceph auth get-or-create client.qemu mon 'profile rbd' osd 'profile rbd pool=vms, profile rbd-read-only pool=images'
The output from the ``ceph auth get-or-create`` command will be the keyring for
the specified user, which can be written to ``/etc/ceph/ceph.client.{ID}.keyring``.
.. note:: The user ID can be specified when using the ``rbd`` command by
providing the ``--id {id}`` optional argument.
Creating a Block Device Image
=============================
@ -33,7 +57,7 @@ the :term:`Ceph Storage Cluster` first. To create a block device image, execute
the following::
rbd create --size {megabytes} {pool-name}/{image-name}
For example, to create a 1GB image named ``bar`` that stores information in a
pool named ``swimmingpool``, execute the following::
@ -126,3 +150,4 @@ For example::
.. _create a pool: ../../rados/operations/pools/#create-a-pool
.. _Storage Pools: ../../rados/operations/pools
.. _RBD Manage RADOS Block Device (RBD) Images: ../../man/8/rbd/
.. _create a Ceph user: ../../rados/operations/user-management#add-a-user

View File

@ -81,7 +81,7 @@ credentials to access the ``cloudstack`` pool we just created. Although we could
use ``client.admin`` for this, it's recommended to create a user with only
access to the ``cloudstack`` pool. ::
ceph auth get-or-create client.cloudstack mon 'allow r' osd 'allow class-read object_prefix rbd_children, allow rwx pool=cloudstack'
ceph auth get-or-create client.cloudstack mon 'profile rbd' osd 'profile rbd pool=cloudstack'
Use the information returned by the command in the next step when adding the
Primary Storage.

View File

@ -132,17 +132,9 @@ Setup Ceph Client Authentication
If you have `cephx authentication`_ enabled, create a new user for Nova/Cinder
and Glance. Execute the following::
ceph auth get-or-create client.glance mon 'allow r' osd 'allow class-read object_prefix rbd_children, allow rwx pool=images'
ceph auth get-or-create client.cinder-backup mon 'allow r' osd 'allow class-read object_prefix rbd_children, allow rwx pool=backups'
If you run an OpenStack version before Mitaka, create the following ``client.cinder`` key::
ceph auth get-or-create client.cinder mon 'allow r' osd 'allow class-read object_prefix rbd_children, allow rwx pool=volumes, allow rwx pool=vms, allow rx pool=images'
Since Mitaka introduced the support of RBD snapshots while doing a snapshot of a Nova instance,
we need to allow the ``client.cinder`` key write access to the ``images`` pool; therefore, create the following key::
ceph auth get-or-create client.cinder mon 'allow r' osd 'allow class-read object_prefix rbd_children, allow rwx pool=volumes, allow rwx pool=vms, allow rwx pool=images'
ceph auth get-or-create client.glance mon 'profile rbd' osd 'profile rbd pool=images'
ceph auth get-or-create client.cinder mon 'profile rbd' osd 'profile rbd pool=volumes, profile rbd pool=vms, profile rbd pool=images'
ceph auth get-or-create client.cinder-backup mon 'profile rbd' osd 'profile rbd pool=backups'
Add the keyrings for ``client.cinder``, ``client.glance``, and
``client.cinder-backup`` to the appropriate nodes and change their ownership::

View File

@ -1,6 +1,11 @@
meta:
- desc: run the rbd_mirror_ha.sh workunit to test the rbd-mirror daemon
tasks:
- exec:
cluster1.client.mirror:
- ceph --cluster cluster1 auth caps client.mirror mon 'profile rbd' osd 'profile rbd'
cluster2.client.mirror:
- ceph --cluster cluster2 auth caps client.mirror mon 'profile rbd' osd 'profile rbd'
- workunit:
clients:
cluster1.client.mirror: [rbd/rbd_mirror_ha.sh]

View File

@ -8,6 +8,11 @@ overrides:
admin socket: /var/run/ceph/$cluster-$name.asok
pid file: /var/run/ceph/$cluster-$name.pid
tasks:
- exec:
cluster1.client.mirror:
- ceph --cluster cluster1 auth caps client.mirror mon 'profile rbd' osd 'profile rbd'
cluster2.client.mirror:
- ceph --cluster cluster2 auth caps client.mirror mon 'profile rbd' osd 'profile rbd'
- rbd-mirror:
client: cluster1.client.mirror
- rbd-mirror:

View File

@ -388,7 +388,7 @@ request_resync_image ${CLUSTER1} ${POOL} ${image} image_id
wait_for_status_in_pool_dir ${CLUSTER1} ${POOL} ${image} 'up+replaying' 'master_position'
testlog "TEST: no blacklists"
ceph --cluster ${CLUSTER1} osd blacklist ls 2>&1 | grep -q "listed 0 entries"
ceph --cluster ${CLUSTER2} osd blacklist ls 2>&1 | grep -q "listed 0 entries"
CEPH_ARGS='--id admin' ceph --cluster ${CLUSTER1} osd blacklist ls 2>&1 | grep -q "listed 0 entries"
CEPH_ARGS='--id admin' ceph --cluster ${CLUSTER2} osd blacklist ls 2>&1 | grep -q "listed 0 entries"
echo OK

View File

@ -69,6 +69,8 @@ CLUSTER2=cluster2
POOL=mirror
PARENT_POOL=mirror_parent
TEMPDIR=
USER_ID=mirror
export CEPH_ARGS="--id ${USER_ID}"
CEPH_ROOT=$(readlink -f $(dirname $0)/../../../src)
CEPH_BIN=.
@ -190,8 +192,15 @@ setup()
if [ -z "${RBD_MIRROR_USE_EXISTING_CLUSTER}" ]; then
cd ${CEPH_ROOT}
${CEPH_SRC}/mstart.sh ${CLUSTER1} -n ${RBD_MIRROR_VARGS}
${CEPH_SRC}/mstart.sh ${CLUSTER2} -n ${RBD_MIRROR_VARGS}
CEPH_ARGS='' ${CEPH_SRC}/mstart.sh ${CLUSTER1} -n ${RBD_MIRROR_VARGS}
CEPH_ARGS='' ${CEPH_SRC}/mstart.sh ${CLUSTER2} -n ${RBD_MIRROR_VARGS}
CEPH_ARGS='' ceph --conf run/${CLUSTER1}/ceph.conf \
auth get-or-create client.${USER_ID} mon 'profile rbd' osd 'profile rbd' >> \
run/${CLUSTER1}/keyring
CEPH_ARGS='' ceph --conf run/${CLUSTER2}/ceph.conf \
auth get-or-create client.${USER_ID} mon 'profile rbd' osd 'profile rbd' >> \
run/${CLUSTER2}/keyring
rm -f ${TEMPDIR}/${CLUSTER1}.conf
ln -s $(readlink -f run/${CLUSTER1}/ceph.conf) \
@ -203,10 +212,10 @@ setup()
cd ${TEMPDIR}
fi
ceph --cluster ${CLUSTER1} osd pool create ${POOL} 64 64
ceph --cluster ${CLUSTER1} osd pool create ${PARENT_POOL} 64 64
ceph --cluster ${CLUSTER2} osd pool create ${PARENT_POOL} 64 64
ceph --cluster ${CLUSTER2} osd pool create ${POOL} 64 64
CEPH_ARGS='' ceph --cluster ${CLUSTER1} osd pool create ${POOL} 64 64
CEPH_ARGS='' ceph --cluster ${CLUSTER1} osd pool create ${PARENT_POOL} 64 64
CEPH_ARGS='' ceph --cluster ${CLUSTER2} osd pool create ${PARENT_POOL} 64 64
CEPH_ARGS='' ceph --cluster ${CLUSTER2} osd pool create ${POOL} 64 64
rbd --cluster ${CLUSTER1} mirror pool enable ${POOL} pool
rbd --cluster ${CLUSTER2} mirror pool enable ${POOL} pool
@ -234,13 +243,13 @@ cleanup()
if [ -z "${RBD_MIRROR_USE_EXISTING_CLUSTER}" ]; then
cd ${CEPH_ROOT}
${CEPH_SRC}/mstop.sh ${CLUSTER1}
${CEPH_SRC}/mstop.sh ${CLUSTER2}
CEPH_ARGS='' ${CEPH_SRC}/mstop.sh ${CLUSTER1}
CEPH_ARGS='' ${CEPH_SRC}/mstop.sh ${CLUSTER2}
else
ceph --cluster ${CLUSTER1} osd pool rm ${POOL} ${POOL} --yes-i-really-really-mean-it
ceph --cluster ${CLUSTER2} osd pool rm ${POOL} ${POOL} --yes-i-really-really-mean-it
ceph --cluster ${CLUSTER1} osd pool rm ${PARENT_POOL} ${PARENT_POOL} --yes-i-really-really-mean-it
ceph --cluster ${CLUSTER2} osd pool rm ${PARENT_POOL} ${PARENT_POOL} --yes-i-really-really-mean-it
CEPH_ARGS='' ceph --cluster ${CLUSTER1} osd pool rm ${POOL} ${POOL} --yes-i-really-really-mean-it
CEPH_ARGS='' ceph --cluster ${CLUSTER2} osd pool rm ${POOL} ${POOL} --yes-i-really-really-mean-it
CEPH_ARGS='' ceph --cluster ${CLUSTER1} osd pool rm ${PARENT_POOL} ${PARENT_POOL} --yes-i-really-really-mean-it
CEPH_ARGS='' ceph --cluster ${CLUSTER2} osd pool rm ${PARENT_POOL} ${PARENT_POOL} --yes-i-really-really-mean-it
fi
test "${RBD_MIRROR_TEMDIR}" = "${TEMPDIR}" ||
rm -Rf ${TEMPDIR}
@ -257,6 +266,7 @@ start_mirror()
rbd-mirror \
--cluster ${cluster} \
--id mirror \
--pid-file=$(daemon_pid_file "${cluster}:${instance}") \
--log-file=${TEMPDIR}/rbd-mirror.${cluster}_daemon.${instance}.log \
--admin-socket=${TEMPDIR}/rbd-mirror.${cluster}_daemon.${instance}.\$cluster.asok \

View File

@ -108,6 +108,11 @@ EOF
chmod 0755 ${STACK_HOME_PATH}/start.sh
sudo -H -u ${STACK_USER} ${STACK_HOME_PATH}/start.sh
# switch to rbd profile caps
ceph auth caps client.cinder mon 'profile rbd' osd 'profile rbd pool=volumes, profile rbd pool=vms, profile rbd pool=images'
ceph auth caps client.cinder-bak mon 'profile rbd' osd 'profile rbd pool=backups, profile rbd pool=volumes'
ceph auth caps client.glance mon 'profile rbd' osd 'profile rbd pool=images'
# execute tempest
chown -R ${TEMPEST_USER}:${STACK_GROUP} ${STACK_OPT_PATH}/tempest
chown -R ${TEMPEST_USER}:${STACK_GROUP} ${STACK_OPT_PATH}/data/tempest

View File

@ -27,6 +27,9 @@
#include <algorithm>
#include <boost/regex.hpp>
#include "include/assert.h"
static inline bool is_not_alnum_space(char c)
{
return !(isalpha(c) || isdigit(c) || (c == '-') || (c == '_'));
@ -60,10 +63,17 @@ ostream& operator<<(ostream& out, const mon_rwxa_t& p)
ostream& operator<<(ostream& out, const StringConstraint& c)
{
if (c.prefix.length())
return out << "prefix " << c.prefix;
else
switch (c.match_type) {
case StringConstraint::MATCH_TYPE_EQUAL:
return out << "value " << c.value;
case StringConstraint::MATCH_TYPE_PREFIX:
return out << "prefix " << c.value;
case StringConstraint::MATCH_TYPE_REGEX:
return out << "regex " << c.value;
default:
break;
}
return out;
}
ostream& operator<<(ostream& out, const MonCapGrant& m)
@ -79,10 +89,22 @@ ostream& operator<<(ostream& out, const MonCapGrant& m)
for (map<string,StringConstraint>::const_iterator p = m.command_args.begin();
p != m.command_args.end();
++p) {
if (p->second.value.length())
out << " " << maybe_quote_string(p->first) << "=" << maybe_quote_string(p->second.value);
else
out << " " << maybe_quote_string(p->first) << " prefix " << maybe_quote_string(p->second.prefix);
switch (p->second.match_type) {
case StringConstraint::MATCH_TYPE_EQUAL:
out << " " << maybe_quote_string(p->first) << "="
<< maybe_quote_string(p->second.value);
break;
case StringConstraint::MATCH_TYPE_PREFIX:
out << " " << maybe_quote_string(p->first) << " prefix "
<< maybe_quote_string(p->second.value);
break;
case StringConstraint::MATCH_TYPE_REGEX:
out << " " << maybe_quote_string(p->first) << " regex "
<< maybe_quote_string(p->second.value);
break;
default:
break;
}
}
}
}
@ -108,8 +130,8 @@ BOOST_FUSION_ADAPT_STRUCT(MonCapGrant,
(mon_rwxa_t, allow))
BOOST_FUSION_ADAPT_STRUCT(StringConstraint,
(std::string, value)
(std::string, prefix))
(StringConstraint::MatchType, match_type)
(std::string, value))
// </magic>
@ -176,26 +198,25 @@ void MonCapGrant::expand_profile_mon(const EntityName& name) const
profile_grants.push_back(MonCapGrant("osd", MON_CAP_R | MON_CAP_W));
profile_grants.push_back(MonCapGrant("auth", MON_CAP_R | MON_CAP_X));
profile_grants.push_back(MonCapGrant("config-key", MON_CAP_R | MON_CAP_W));
string prefix = string("daemon-private/mgr/");
profile_grants.push_back(MonCapGrant("config-key get", "key",
StringConstraint("", prefix)));
profile_grants.push_back(MonCapGrant("config-key put", "key",
StringConstraint("", prefix)));
profile_grants.push_back(MonCapGrant("config-key exists", "key",
StringConstraint("", prefix)));
profile_grants.push_back(MonCapGrant("config-key delete", "key",
StringConstraint("", prefix)));
StringConstraint constraint(StringConstraint::MATCH_TYPE_PREFIX,
"daemon-private/mgr/");
profile_grants.push_back(MonCapGrant("config-key get", "key", constraint));
profile_grants.push_back(MonCapGrant("config-key put", "key", constraint));
profile_grants.push_back(MonCapGrant("config-key exists", "key", constraint));
profile_grants.push_back(MonCapGrant("config-key delete", "key", constraint));
}
if (profile == "osd" || profile == "mds" || profile == "mon" ||
profile == "mgr") {
StringConstraint constraint(StringConstraint::MATCH_TYPE_PREFIX,
string("daemon-private/") + stringify(name) +
string("/"));
string prefix = string("daemon-private/") + stringify(name) + string("/");
profile_grants.push_back(MonCapGrant("config-key get", "key", StringConstraint("", prefix)));
profile_grants.push_back(MonCapGrant("config-key put", "key", StringConstraint("", prefix)));
profile_grants.push_back(MonCapGrant("config-key exists", "key", StringConstraint("", prefix)));
profile_grants.push_back(MonCapGrant("config-key delete", "key", StringConstraint("", prefix)));
profile_grants.push_back(MonCapGrant("config-key get", "key", constraint));
profile_grants.push_back(MonCapGrant("config-key put", "key", constraint));
profile_grants.push_back(MonCapGrant("config-key exists", "key", constraint));
profile_grants.push_back(MonCapGrant("config-key delete", "key", constraint));
}
if (profile == "bootstrap-osd") {
string prefix = "dm-crypt/osd";
profile_grants.push_back(MonCapGrant("mon", MON_CAP_R)); // read monmap
profile_grants.push_back(MonCapGrant("osd", MON_CAP_R)); // read osdmap
profile_grants.push_back(MonCapGrant("mon getmap"));
@ -206,27 +227,36 @@ void MonCapGrant::expand_profile_mon(const EntityName& name) const
profile_grants.push_back(MonCapGrant("osd", MON_CAP_R)); // read osdmap
profile_grants.push_back(MonCapGrant("mon getmap"));
profile_grants.push_back(MonCapGrant("auth get-or-create")); // FIXME: this can expose other mds keys
profile_grants.back().command_args["entity"] = StringConstraint("", "mds.");
profile_grants.back().command_args["caps_mon"] = StringConstraint("allow profile mds", "");
profile_grants.back().command_args["caps_osd"] = StringConstraint("allow rwx", "");
profile_grants.back().command_args["caps_mds"] = StringConstraint("allow", "");
profile_grants.back().command_args["entity"] = StringConstraint(
StringConstraint::MATCH_TYPE_PREFIX, "mds.");
profile_grants.back().command_args["caps_mon"] = StringConstraint(
StringConstraint::MATCH_TYPE_EQUAL, "allow profile mds");
profile_grants.back().command_args["caps_osd"] = StringConstraint(
StringConstraint::MATCH_TYPE_EQUAL, "allow rwx");
profile_grants.back().command_args["caps_mds"] = StringConstraint(
StringConstraint::MATCH_TYPE_EQUAL, "allow");
}
if (profile == "bootstrap-mgr") {
profile_grants.push_back(MonCapGrant("mon", MON_CAP_R)); // read monmap
profile_grants.push_back(MonCapGrant("osd", MON_CAP_R)); // read osdmap
profile_grants.push_back(MonCapGrant("mon getmap"));
profile_grants.push_back(MonCapGrant("auth get-or-create")); // FIXME: this can expose other mgr keys
profile_grants.back().command_args["entity"] = StringConstraint("", "mgr.");
profile_grants.back().command_args["caps_mon"] = StringConstraint("allow profile mgr", "");
profile_grants.back().command_args["entity"] = StringConstraint(
StringConstraint::MATCH_TYPE_PREFIX, "mgr.");
profile_grants.back().command_args["caps_mon"] = StringConstraint(
StringConstraint::MATCH_TYPE_EQUAL, "allow profile mgr");
}
if (profile == "bootstrap-rgw") {
profile_grants.push_back(MonCapGrant("mon", MON_CAP_R)); // read monmap
profile_grants.push_back(MonCapGrant("osd", MON_CAP_R)); // read osdmap
profile_grants.push_back(MonCapGrant("mon getmap"));
profile_grants.push_back(MonCapGrant("auth get-or-create")); // FIXME: this can expose other mds keys
profile_grants.back().command_args["entity"] = StringConstraint("", "client.rgw.");
profile_grants.back().command_args["caps_mon"] = StringConstraint("allow rw", "");
profile_grants.back().command_args["caps_osd"] = StringConstraint("allow rwx", "");
profile_grants.back().command_args["entity"] = StringConstraint(
StringConstraint::MATCH_TYPE_PREFIX, "client.rgw.");
profile_grants.back().command_args["caps_mon"] = StringConstraint(
StringConstraint::MATCH_TYPE_EQUAL, "allow rw");
profile_grants.back().command_args["caps_osd"] = StringConstraint(
StringConstraint::MATCH_TYPE_EQUAL, "allow rwx");
}
if (profile == "fs-client") {
profile_grants.push_back(MonCapGrant("mon", MON_CAP_R));
@ -239,6 +269,18 @@ void MonCapGrant::expand_profile_mon(const EntityName& name) const
profile_grants.push_back(MonCapGrant("osd", MON_CAP_R));
profile_grants.push_back(MonCapGrant("pg", MON_CAP_R));
}
if (profile == "rbd") {
profile_grants.push_back(MonCapGrant("mon", MON_CAP_R));
profile_grants.push_back(MonCapGrant("osd", MON_CAP_R));
profile_grants.push_back(MonCapGrant("pg", MON_CAP_R));
// exclusive lock dead-client blacklisting (IP+nonce required)
profile_grants.push_back(MonCapGrant("osd blacklist"));
profile_grants.back().command_args["blacklistop"] = StringConstraint(
StringConstraint::MATCH_TYPE_EQUAL, "add");
profile_grants.back().command_args["addr"] = StringConstraint(
StringConstraint::MATCH_TYPE_REGEX, "^[^/]/[0-9]*$");
}
if (profile == "role-definer") {
// grants ALL caps to the auth subsystem, read-only on the
@ -275,14 +317,25 @@ mon_rwxa_t MonCapGrant::get_allowed(CephContext *cct,
// argument must be present if a constraint exists
if (q == c_args.end())
return 0;
if (p->second.value.length()) {
// match value
switch (p->second.match_type) {
case StringConstraint::MATCH_TYPE_EQUAL:
if (p->second.value != q->second)
return 0;
} else {
// match prefix
if (q->second.find(p->second.prefix) != 0)
break;
case StringConstraint::MATCH_TYPE_PREFIX:
if (q->second.find(p->second.value) != 0)
return 0;
break;
case StringConstraint::MATCH_TYPE_REGEX:
{
boost::regex pattern(p->second.value,
boost::regex::basic | boost::regex::no_except);
if (pattern.empty() || !boost::regex_match(q->second, pattern))
return 0;
}
break;
default:
break;
}
}
return MON_CAP_ALL;
@ -427,9 +480,12 @@ struct MonCapParser : qi::grammar<Iterator, MonCap()>
spaces = +(lit(' ') | lit('\n') | lit('\t'));
// command := command[=]cmd [k1=v1 k2=v2 ...]
str_match = '=' >> str >> qi::attr(string());
str_prefix = spaces >> lit("prefix") >> spaces >> qi::attr(string()) >> str;
kv_pair = str >> (str_match | str_prefix);
str_match = '=' >> qi::attr(StringConstraint::MATCH_TYPE_EQUAL) >> str;
str_prefix = spaces >> lit("prefix") >> spaces >>
qi::attr(StringConstraint::MATCH_TYPE_PREFIX) >> str;
str_regex = spaces >> lit("regex") >> spaces >>
qi::attr(StringConstraint::MATCH_TYPE_REGEX) >> str;
kv_pair = str >> (str_match | str_prefix | str_regex);
kv_map %= kv_pair >> *(spaces >> kv_pair);
command_match = -spaces >> lit("allow") >> spaces >> lit("command") >> (lit('=') | spaces)
>> qi::attr(string()) >> qi::attr(string())
@ -444,7 +500,8 @@ struct MonCapParser : qi::grammar<Iterator, MonCap()>
>> spaces >> rwxa;
// profile foo
profile_match %= -spaces >> lit("allow") >> spaces >> lit("profile") >> (lit('=') | spaces)
profile_match %= -spaces >> -(lit("allow") >> spaces)
>> lit("profile") >> (lit('=') | spaces)
>> qi::attr(string())
>> str
>> qi::attr(string())
@ -481,7 +538,7 @@ struct MonCapParser : qi::grammar<Iterator, MonCap()>
qi::rule<Iterator, string()> unquoted_word;
qi::rule<Iterator, string()> str;
qi::rule<Iterator, StringConstraint()> str_match, str_prefix;
qi::rule<Iterator, StringConstraint()> str_match, str_prefix, str_regex;
qi::rule<Iterator, pair<string, StringConstraint>()> kv_pair;
qi::rule<Iterator, map<string, StringConstraint>()> kv_map;

View File

@ -35,12 +35,20 @@ struct mon_rwxa_t {
ostream& operator<<(ostream& out, const mon_rwxa_t& p);
struct StringConstraint {
enum MatchType {
MATCH_TYPE_NONE,
MATCH_TYPE_EQUAL,
MATCH_TYPE_PREFIX,
MATCH_TYPE_REGEX
};
MatchType match_type = MATCH_TYPE_NONE;
string value;
string prefix;
StringConstraint() {}
StringConstraint(string a, string b)
: value(std::move(a)), prefix(std::move(b)) {}
StringConstraint(MatchType match_type, string value)
: match_type(match_type), value(value) {
}
};
ostream& operator<<(ostream& out, const StringConstraint& c);

View File

@ -25,7 +25,7 @@ using std::ostream;
using std::vector;
ostream& operator<<(ostream& out, const osd_rwxa_t& p)
{
{
if (p == OSD_CAP_ANY)
return out << "*";
@ -53,42 +53,79 @@ ostream& operator<<(ostream& out, const OSDCapSpec& s)
return out;
}
ostream& operator<<(ostream& out, const OSDCapMatch& m)
ostream& operator<<(ostream& out, const OSDCapPoolNamespace& pns)
{
if (m.auid != -1LL) {
out << "auid " << m.auid << " ";
if (!pns.pool_name.empty()) {
out << "pool " << pns.pool_name << " ";
}
if (m.object_prefix.length()) {
out << "object_prefix " << m.object_prefix << " ";
}
if (m.pool_name.length()) {
out << "pool " << m.pool_name << " ";
}
if (m.is_nspace) {
if (pns.nspace) {
out << "namespace ";
if (m.nspace.length() == 0)
if (pns.nspace->empty()) {
out << "\"\"";
else
out << m.nspace;
} else {
out << *pns.nspace;
}
out << " ";
}
return out;
}
bool OSDCapMatch::is_match(const string& pn, const string& ns, int64_t pool_auid, const string& object) const
ostream& operator<<(ostream& out, const OSDCapMatch& m)
{
if (m.auid != -1LL) {
out << "auid " << m.auid << " ";
} else {
out << m.pool_namespace;
}
if (m.object_prefix.length()) {
out << "object_prefix " << m.object_prefix << " ";
}
return out;
}
ostream& operator<<(ostream& out, const OSDCapProfile& m)
{
out << "profile " << m.name;
out << m.pool_namespace;
return out;
}
bool OSDCapPoolNamespace::is_match(const std::string& pn,
const std::string& ns) const
{
if (!pool_name.empty()) {
if (pool_name != pn) {
return false;
}
}
if (nspace) {
if (*nspace != ns) {
return false;
}
}
return true;
}
bool OSDCapPoolNamespace::is_match_all() const
{
if (!pool_name.empty())
return false;
if (nspace)
return false;
return true;
}
bool OSDCapMatch::is_match(const string& pn, const string& ns,
int64_t pool_auid, const string& object) const
{
if (auid >= 0) {
if (auid != pool_auid)
return false;
} else if (!pool_namespace.is_match(pn, ns)) {
return false;
}
if (pool_name.length()) {
if (pool_name != pn)
return false;
}
if (is_nspace) {
if (nspace != ns)
return false;
}
if (object_prefix.length()) {
if (object.find(object_prefix) != 0)
return false;
@ -98,28 +135,144 @@ bool OSDCapMatch::is_match(const string& pn, const string& ns, int64_t pool_auid
bool OSDCapMatch::is_match_all() const
{
if (auid >= 0)
if (auid >= 0) {
return false;
if (pool_name.length())
} else if (!pool_namespace.is_match_all()) {
return false;
if (is_nspace)
return false;
if (object_prefix.length())
}
if (object_prefix.length()) {
return false;
}
return true;
}
ostream& operator<<(ostream& out, const OSDCapGrant& g)
{
return out << "grant(" << g.match << g.spec << ")";
out << "grant(";
if (g.profile.is_valid()) {
out << g.profile << " [";
for (auto it = g.profile_grants.cbegin();
it != g.profile_grants.cend(); ++it) {
if (it != g.profile_grants.cbegin()) {
out << ",";
}
out << *it;
}
out << "]";
} else {
out << g.match << g.spec;
}
out << ")";
return out;
}
bool OSDCapGrant::allow_all() const
{
if (profile.is_valid()) {
return std::any_of(profile_grants.cbegin(), profile_grants.cend(),
[](const OSDCapGrant& grant) {
return grant.allow_all();
});
}
return (match.is_match_all() && spec.allow_all());
}
bool OSDCapGrant::is_capable(const string& pool_name, const string& ns,
int64_t pool_auid, const string& object,
bool op_may_read, bool op_may_write,
const std::vector<OpRequest::ClassInfo>& classes,
std::vector<bool>* class_allowed) const
{
osd_rwxa_t allow = 0;
if (profile.is_valid()) {
return std::any_of(profile_grants.cbegin(), profile_grants.cend(),
[&](const OSDCapGrant& grant) {
return grant.is_capable(pool_name, ns, pool_auid, object, op_may_read,
op_may_write, classes, class_allowed);
});
} else {
if (match.is_match(pool_name, ns, pool_auid, object)) {
allow = allow | spec.allow;
if ((op_may_read && !(allow & OSD_CAP_R)) ||
(op_may_write && !(allow & OSD_CAP_W))) {
return false;
}
if (!classes.empty()) {
// check 'allow *'
if (spec.allow_all()) {
return true;
}
// compare this grant to each class in the operation
for (size_t i = 0; i < classes.size(); ++i) {
// check 'allow class foo'
if (!spec.class_name.empty() && classes[i].name == spec.class_name) {
(*class_allowed)[i] = true;
continue;
}
// check 'allow x | class-{rw}': must be on whitelist
if (!classes[i].whitelisted) {
continue;
}
if ((classes[i].read && !(allow & OSD_CAP_CLS_R)) ||
(classes[i].write && !(allow & OSD_CAP_CLS_W))) {
continue;
}
(*class_allowed)[i] = true;
}
if (!std::all_of(class_allowed->cbegin(), class_allowed->cend(),
[](bool v) { return v; })) {
return false;
}
}
return true;
}
}
return false;
}
void OSDCapGrant::expand_profile()
{
if (profile.name == "read-only") {
// grants READ-ONLY caps to the OSD
profile_grants.emplace_back(OSDCapMatch(profile.pool_namespace),
OSDCapSpec(osd_rwxa_t(OSD_CAP_R)));
return;
}
if (profile.name == "read-write") {
// grants READ-WRITE caps to the OSD
profile_grants.emplace_back(OSDCapMatch(profile.pool_namespace),
OSDCapSpec(osd_rwxa_t(OSD_CAP_R | OSD_CAP_W)));
}
if (profile.name == "rbd") {
// RBD read-write grant
profile_grants.emplace_back(OSDCapMatch("", "", "rbd_children"),
OSDCapSpec(osd_rwxa_t(OSD_CAP_CLS_R)));
profile_grants.emplace_back(OSDCapMatch("", "", "rbd_mirroring"),
OSDCapSpec(osd_rwxa_t(OSD_CAP_CLS_R)));
profile_grants.emplace_back(OSDCapMatch(profile.pool_namespace),
OSDCapSpec(osd_rwxa_t(OSD_CAP_R |
OSD_CAP_W |
OSD_CAP_X)));
}
if (profile.name == "rbd-read-only") {
// RBD read-only grant
profile_grants.emplace_back(OSDCapMatch(profile.pool_namespace),
OSDCapSpec(osd_rwxa_t(OSD_CAP_R |
OSD_CAP_CLS_R)));
}
}
bool OSDCap::allow_all() const
{
for (vector<OSDCapGrant>::const_iterator p = grants.begin(); p != grants.end(); ++p)
if (p->match.is_match_all() && p->spec.allow_all())
for (auto &grant : grants) {
if (grant.allow_all()) {
return true;
}
}
return false;
}
@ -129,47 +282,15 @@ void OSDCap::set_allow_all()
grants.push_back(OSDCapGrant(OSDCapMatch(), OSDCapSpec(OSD_CAP_ANY)));
}
bool OSDCap::is_capable(const string& pool_name, const string& ns, int64_t pool_auid,
const string& object, bool op_may_read, bool op_may_write,
bool OSDCap::is_capable(const string& pool_name, const string& ns,
int64_t pool_auid, const string& object,
bool op_may_read, bool op_may_write,
const std::vector<OpRequest::ClassInfo>& classes) const
{
const size_t num_classes = classes.size();
std::vector<bool> class_allowed(num_classes, false);
osd_rwxa_t allow = 0;
for (vector<OSDCapGrant>::const_iterator p = grants.begin();
p != grants.end(); ++p) {
if (p->match.is_match(pool_name, ns, pool_auid, object)) {
allow = allow | p->spec.allow;
if ((op_may_read && !(allow & OSD_CAP_R)) ||
(op_may_write && !(allow & OSD_CAP_W)))
continue;
if (num_classes) {
// check 'allow *'
if (p->spec.allow_all())
return true;
// compare this grant to each class in the operation
for (size_t i = 0; i < num_classes; i++) {
// check 'allow class foo'
if (classes[i].name == p->spec.class_name &&
!p->spec.class_name.empty()) {
class_allowed[i] = true;
continue;
}
// check 'allow x | class-{rw}': must be on whitelist
if (!classes[i].whitelisted)
continue;
if ((classes[i].read && !(allow & OSD_CAP_CLS_R)) ||
(classes[i].write && !(allow & OSD_CAP_CLS_W))) {
continue;
}
class_allowed[i] = true;
}
if (std::all_of(class_allowed.cbegin(), class_allowed.cend(),
[](bool v) { return v; }))
return true;
else
continue;
}
std::vector<bool> class_allowed(classes.size(), false);
for (auto &grant : grants) {
if (grant.is_capable(pool_name, ns, pool_auid, object, op_may_read,
op_may_write, classes, &class_allowed)) {
return true;
}
}
@ -210,16 +331,17 @@ struct OSDCapParser : qi::grammar<Iterator, OSDCap()>
spaces = +ascii::space;
// match := [pool[=]<poolname> [namespace[=]<namespace>] | auid <123>] [object_prefix <prefix>]
pool_name %= -(spaces >> lit("pool") >> (lit('=') | spaces) >> str);
nspace %= (spaces >> lit("namespace") >> (lit('=') | spaces) >> estr);
// match := [pool[=]<poolname> [namespace[=]<namespace>] | auid <123>] [object_prefix <prefix>]
auid %= (spaces >> lit("auid") >> spaces >> int_);
object_prefix %= -(spaces >> lit("object_prefix") >> spaces >> str);
match = ( (auid >> object_prefix) [_val = phoenix::construct<OSDCapMatch>(_1, _2)] |
(pool_name >> nspace >> object_prefix) [_val = phoenix::construct<OSDCapMatch>(_1, _2, _3)] |
(pool_name >> object_prefix) [_val = phoenix::construct<OSDCapMatch>(_1, _2)]);
match = (
(auid >> object_prefix) [_val = phoenix::construct<OSDCapMatch>(_1, _2)] |
(pool_name >> nspace >> object_prefix) [_val = phoenix::construct<OSDCapMatch>(_1, _2, _3)] |
(pool_name >> object_prefix) [_val = phoenix::construct<OSDCapMatch>(_1, _2)]);
// rwxa := * | [r][w][x] [class-read] [class-write]
rwxa =
@ -234,19 +356,27 @@ struct OSDCapParser : qi::grammar<Iterator, OSDCap()>
(spaces >> lit("class-write")[_val |= OSD_CAP_CLS_W]) ));
// capspec := * | rwx | class <name> [classcap]
capspec =
rwxa [_val = phoenix::construct<OSDCapSpec>(_1)] |
( spaces >> lit("class") >> spaces >> ((str >> spaces >> str) [_val = phoenix::construct<OSDCapSpec>(_1, _2)] |
str [_val = phoenix::construct<OSDCapSpec>(_1, string())] ));
class_name %= (spaces >> lit("class") >> spaces >> str);
class_cap %= -(spaces >> str);
capspec = (
(rwxa) [_val = phoenix::construct<OSDCapSpec>(_1)] |
(class_name >> class_cap) [_val = phoenix::construct<OSDCapSpec>(_1, _2)]);
// profile := profile <name> [pool[=]<pool> [namespace[=]<namespace>]]
profile_name %= (lit("profile") >> (lit('=') | spaces) >> str);
profile = (
(profile_name >> pool_name >> nspace) [_val = phoenix::construct<OSDCapProfile>(_1, _2, _3)] |
(profile_name >> pool_name) [_val = phoenix::construct<OSDCapProfile>(_1, _2)]);
// grant := allow match capspec
grant = (*ascii::blank >> lit("allow") >>
((capspec >> match) [_val = phoenix::construct<OSDCapGrant>(_2, _1)] |
(match >> capspec) [_val = phoenix::construct<OSDCapGrant>(_1, _2)]) >>
*ascii::blank);
grant = (*ascii::blank >>
((lit("allow") >> capspec >> match) [_val = phoenix::construct<OSDCapGrant>(_2, _1)] |
(lit("allow") >> match >> capspec) [_val = phoenix::construct<OSDCapGrant>(_1, _2)] |
(profile) [_val = phoenix::construct<OSDCapGrant>(_1)]
) >> *ascii::blank);
// osdcap := grant [grant ...]
grants %= (grant % (lit(';') | lit(',')));
osdcap = grants [_val = phoenix::construct<OSDCap>(_1)];
osdcap = grants [_val = phoenix::construct<OSDCap>(_1)];
}
qi::rule<Iterator> spaces;
qi::rule<Iterator, unsigned()> rwxa;
@ -254,11 +384,15 @@ struct OSDCapParser : qi::grammar<Iterator, OSDCap()>
qi::rule<Iterator, string()> unquoted_word;
qi::rule<Iterator, string()> str, estr;
qi::rule<Iterator, int()> auid;
qi::rule<Iterator, string()> class_name;
qi::rule<Iterator, string()> class_cap;
qi::rule<Iterator, OSDCapSpec()> capspec;
qi::rule<Iterator, string()> pool_name;
qi::rule<Iterator, string()> nspace;
qi::rule<Iterator, string()> object_prefix;
qi::rule<Iterator, OSDCapMatch()> match;
qi::rule<Iterator, string()> profile_name;
qi::rule<Iterator, OSDCapProfile()> profile;
qi::rule<Iterator, OSDCapGrant()> grant;
qi::rule<Iterator, std::vector<OSDCapGrant>()> grants;
qi::rule<Iterator, OSDCap()> osdcap;

View File

@ -1,4 +1,4 @@
// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
// vim: ts=8 sw=2 smarttab
/*
* Ceph - scalable distributed file system
@ -7,10 +7,10 @@
*
* This is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License version 2.1, as published by the Free Software
* License version 2.1, as published by the Free Software
* Foundation. See file COPYING.
*
* OSDCaps: Hold the capabilities associated with a single authenticated
*
* OSDCaps: Hold the capabilities associated with a single authenticated
* user key. These are specified by text strings of the form
* "allow r" (which allows reading anything on the OSD)
* "allow rwx auid foo" (which allows full access to listed auids)
@ -33,6 +33,10 @@ using std::ostream;
#include "include/types.h"
#include "OpRequest.h"
#include <list>
#include <vector>
#include <boost/optional.hpp>
static const __u8 OSD_CAP_R = (1 << 1); // read
static const __u8 OSD_CAP_W = (1 << 2); // write
static const __u8 OSD_CAP_CLS_R = (1 << 3); // class read
@ -74,22 +78,39 @@ struct OSDCapSpec {
ostream& operator<<(ostream& out, const OSDCapSpec& s);
struct OSDCapPoolNamespace {
std::string pool_name;
boost::optional<std::string> nspace = boost::none;
OSDCapPoolNamespace() {
}
OSDCapPoolNamespace(const std::string& pool_name,
const boost::optional<std::string>& nspace = boost::none)
: pool_name(pool_name), nspace(nspace) {
}
bool is_match(const std::string& pn, const std::string& ns) const;
bool is_match_all() const;
};
ostream& operator<<(ostream& out, const OSDCapPoolNamespace& pns);
struct OSDCapMatch {
// auid and pool_name/nspace are mutually exclusive
int64_t auid;
std::string pool_name;
bool is_nspace; // true if nspace is defined; false if not constrained.
std::string nspace;
int64_t auid = CEPH_AUTH_UID_DEFAULT;
OSDCapPoolNamespace pool_namespace;
std::string object_prefix;
OSDCapMatch() : auid(CEPH_AUTH_UID_DEFAULT), is_nspace(false) {}
OSDCapMatch(std::string pl, std::string pre) :
auid(CEPH_AUTH_UID_DEFAULT), pool_name(pl), is_nspace(false), object_prefix(pre) {}
OSDCapMatch(std::string pl, std::string ns, std::string pre) :
auid(CEPH_AUTH_UID_DEFAULT), pool_name(pl), is_nspace(true), nspace(ns), object_prefix(pre) {}
OSDCapMatch(uint64_t auid, std::string pre) : auid(auid), is_nspace(false), object_prefix(pre) {}
OSDCapMatch() {}
OSDCapMatch(const OSDCapPoolNamespace& pns) : pool_namespace(pns) {}
OSDCapMatch(const std::string& pl, const std::string& pre)
: pool_namespace(pl), object_prefix(pre) {}
OSDCapMatch(const std::string& pl, const std::string& ns,
const std::string& pre)
: pool_namespace(pl, ns), object_prefix(pre) {}
OSDCapMatch(uint64_t auid, const std::string& pre)
: auid(auid), object_prefix(pre) {}
/**
* check if given request parameters match our constraints
@ -100,19 +121,55 @@ struct OSDCapMatch {
* @param object object name
* @return true if we match, false otherwise
*/
bool is_match(const std::string& pool_name, const std::string& nspace_name, int64_t pool_auid, const std::string& object) const;
bool is_match(const std::string& pool_name, const std::string& nspace_name,
int64_t pool_auid, const std::string& object) const;
bool is_match_all() const;
};
ostream& operator<<(ostream& out, const OSDCapMatch& m);
struct OSDCapProfile {
std::string name;
OSDCapPoolNamespace pool_namespace;
OSDCapProfile() {
}
OSDCapProfile(const std::string& name,
const std::string& pool_name,
const boost::optional<std::string>& nspace = boost::none)
: name(name), pool_namespace(pool_name, nspace) {
}
inline bool is_valid() const {
return !name.empty();
}
};
ostream& operator<<(ostream& out, const OSDCapProfile& m);
struct OSDCapGrant {
OSDCapMatch match;
OSDCapSpec spec;
OSDCapProfile profile;
// explicit grants that a profile grant expands to; populated as
// needed by expand_profile() and cached here.
std::list<OSDCapGrant> profile_grants;
OSDCapGrant() {}
OSDCapGrant(OSDCapMatch m, OSDCapSpec s) : match(m), spec(s) {}
OSDCapGrant(const OSDCapMatch& m, const OSDCapSpec& s) : match(m), spec(s) {}
OSDCapGrant(const OSDCapProfile& profile) : profile(profile) {
expand_profile();
}
bool allow_all() const;
bool is_capable(const string& pool_name, const string& ns, int64_t pool_auid,
const string& object, bool op_may_read, bool op_may_write,
const std::vector<OpRequest::ClassInfo>& classes,
std::vector<bool>* class_allowed) const;
void expand_profile();
};
ostream& operator<<(ostream& out, const OSDCapGrant& g);

View File

@ -1877,8 +1877,10 @@ bool PG::op_has_sufficient_caps(OpRequestRef& op)
op->need_write_cap(),
op->classes());
dout(20) << "op_has_sufficient_caps pool=" << pool.id << " (" << pool.name
<< " " << req->get_hobj().nspace
dout(20) << "op_has_sufficient_caps "
<< "session=" << session
<< " pool=" << pool.id << " (" << pool.name
<< " " << req->get_hobj().nspace
<< ") owner=" << pool.auid
<< " need_read_cap=" << op->need_read_cap()
<< " need_write_cap=" << op->need_write_cap()

View File

@ -43,6 +43,8 @@ const char *parse_good[] = {
"allow command abc with arg=foo arg2=bar",
"allow command abc with arg=foo arg2 prefix bar arg3 prefix baz",
"allow command abc with arg=foo arg2 prefix \"bar bingo\" arg3 prefix baz",
"allow command abc with arg regex \"^[0-9a-z.]*$\"",
"allow command abc with arg regex \"\(invaluid regex\"",
"allow service foo x",
"allow service foo x; allow service bar x",
"allow service foo w ;allow service bar x",
@ -55,6 +57,8 @@ const char *parse_good[] = {
"allow command abc.def with arg=foo arg2=bar, allow service foo r",
"allow command \"foo bar\" with arg=\"baz\"",
"allow command \"foo bar\" with arg=\"baz.xx\"",
"profile osd",
"profile \"mds-bootstrap\", profile foo",
0
};
@ -238,3 +242,19 @@ TEST(MonCap, ProfileOSD) {
name, "", "config-key delete", ca, true, true, true));
}
TEST(MonCap, CommandRegEx) {
MonCap cap;
ASSERT_FALSE(cap.is_allow_all());
ASSERT_TRUE(cap.parse("allow command abc with arg regex \"^[0-9a-z.]*$\"", NULL));
EntityName name;
name.from_str("osd.123");
ASSERT_TRUE(cap.is_capable(nullptr, CEPH_ENTITY_TYPE_OSD, name, "",
"abc", {{"arg", "12345abcde"}}, true, true, true));
ASSERT_FALSE(cap.is_capable(nullptr, CEPH_ENTITY_TYPE_OSD, name, "",
"abc", {{"arg", "~!@#$"}}, true, true, true));
ASSERT_TRUE(cap.parse("allow command abc with arg regex \"[*\"", NULL));
ASSERT_FALSE(cap.is_capable(nullptr, CEPH_ENTITY_TYPE_OSD, name, "",
"abc", {{"arg", ""}}, true, true, true));
}

View File

@ -70,6 +70,7 @@ const char *parse_good[] = {
"allow pool foo namespace=nfoo rwx; allow pool bar namespace nbar object_prefix rbd r",
"allow pool foo namespace=\"\" rwx; allow pool bar namespace='' object_prefix rbd r",
"allow pool foo namespace \"\" rwx; allow pool bar namespace '' object_prefix rbd r",
"profile abc, profile abc pool=bar, profile abc pool=bar namespace=foo",
0
};
@ -1006,3 +1007,31 @@ TEST(OSDCap, AllowClassMultiRWX) {
ASSERT_FALSE(cap.is_capable("bar", "", 0, "foo", false, false, {{"foo", false, false, false}, {"bar", false, true, false}}));
ASSERT_FALSE(cap.is_capable("bar", "", 0, "foo", false, false, {{"foo", false, false, false}, {"bar", false, false, false}}));
}
TEST(OSDCap, AllowProfile) {
OSDCap cap;
ASSERT_TRUE(cap.parse("profile read-only, profile read-write pool abc", NULL));
ASSERT_FALSE(cap.allow_all());
ASSERT_FALSE(cap.is_capable("foo", "", 0, "asdf", true, true, {}));
ASSERT_TRUE(cap.is_capable("foo", "", 0, "asdf", true, false, {}));
ASSERT_TRUE(cap.is_capable("abc", "", 0, "asdf", false, true, {}));
// RBD
cap.grants.clear();
ASSERT_TRUE(cap.parse("profile rbd pool abc", NULL));
ASSERT_FALSE(cap.allow_all());
ASSERT_FALSE(cap.is_capable("foo", "", 0, "asdf", true, true, {}));
ASSERT_FALSE(cap.is_capable("foo", "", 0, "rbd_children", true, false, {}));
ASSERT_TRUE(cap.is_capable("foo", "", 0, "rbd_children", false, false,
{{"rbd", true, false, true}}));
ASSERT_TRUE(cap.is_capable("abc", "", 0, "asdf", true, true,
{{"rbd", true, true, true}}));
cap.grants.clear();
ASSERT_TRUE(cap.parse("profile rbd-read-only pool abc", NULL));
ASSERT_FALSE(cap.allow_all());
ASSERT_FALSE(cap.is_capable("foo", "", 0, "rbd_children", true, false, {}));
ASSERT_TRUE(cap.is_capable("abc", "", 0, "asdf", true, false,
{{"rbd", true, false, true}}));
}