mirror of
https://github.com/ceph/ceph
synced 2025-01-18 17:12:29 +00:00
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:
commit
4e6487cad4
@ -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.
|
||||
|
@ -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
|
||||
|
@ -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. ::
|
||||
|
||||
|
@ -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
|
||||
|
@ -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.
|
||||
|
@ -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::
|
||||
|
@ -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]
|
||||
|
@ -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:
|
||||
|
@ -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
|
||||
|
@ -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 \
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -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()
|
||||
|
@ -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));
|
||||
}
|
||||
|
@ -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}}));
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user