>=18.0.0
+* The RGW policy parser now rejects unknown principals by default. If you are
+ mirroring policies between RGW and AWS, you may wish to set
+ "rgw policy reject invalid principals" to "false". This affects only newly set
+ policies, not policies that are already in place.
* RGW's default backend for `rgw_enable_ops_log` changed from RADOS to file.
The default value of `rgw_ops_log_rados` is now false, and `rgw_ops_log_file_path`
defaults to "/var/log/ceph/ops-log-$cluster-$name.log".
default: tank
services:
- rgw
+- name: rgw_policy_reject_invalid_principals
+ type: bool
+ level: basic
+ desc: Whether to reject policies with invalid principals
+ long_desc: If true, policies with invalid principals will be
+ rejected. We don't support Canonical User identifiers or some
+ other form of policies that Amazon does, so if you are mirroring
+ policies between RGW and AWS, you may wish to set this to false.
+ default: true
+ services:
+ - rgw
}
bufferlist bl = bufferlist::static_from_string(assume_role_doc);
try {
- const rgw::IAM::Policy p(g_ceph_context, tenant, bl);
+ const rgw::IAM::Policy p(
+ g_ceph_context, tenant, bl,
+ g_ceph_context->_conf.get_val<bool>(
+ "rgw_policy_reject_invalid_principals"));
} catch (rgw::IAM::PolicyParseException& e) {
cerr << "failed to parse policy: " << e.what() << std::endl;
return -EINVAL;
bufferlist bl = bufferlist::static_from_string(assume_role_doc);
try {
- const rgw::IAM::Policy p(g_ceph_context, tenant, bl);
+ const rgw::IAM::Policy p(g_ceph_context, tenant, bl,
+ g_ceph_context->_conf.get_val<bool>(
+ "rgw_policy_reject_invalid_principals"));
} catch (rgw::IAM::PolicyParseException& e) {
cerr << "failed to parse policy: " << e.what() << std::endl;
return -EINVAL;
bl = bufferlist::static_from_string(perm_policy_doc);
}
try {
- const rgw::IAM::Policy p(g_ceph_context, tenant, bl);
+ const rgw::IAM::Policy p(g_ceph_context, tenant, bl,
+ g_ceph_context->_conf.get_val<bool>(
+ "rgw_policy_reject_invalid_principals"));
} catch (rgw::IAM::PolicyParseException& e) {
cerr << "failed to parse perm policy: " << e.what() << std::endl;
return -EINVAL;
for (auto it: role.role_policies) {
try {
bufferlist bl = bufferlist::static_from_string(it);
- const rgw::IAM::Policy p(s->cct, role.tenant, bl);
+ const rgw::IAM::Policy p(s->cct, role.tenant, bl, false);
s->iam_user_policies.push_back(std::move(p));
} catch (rgw::IAM::PolicyParseException& e) {
//Control shouldn't reach here as the policy has already been
try {
string policy = this->token_attrs.token_policy;
bufferlist bl = bufferlist::static_from_string(policy);
- const rgw::IAM::Policy p(s->cct, role.tenant, bl);
+ const rgw::IAM::Policy p(s->cct, role.tenant, bl, false);
s->session_policies.push_back(std::move(p));
} catch (rgw::IAM::PolicyParseException& e) {
//Control shouldn't reach here as the policy has already been
void annotate(std::string&& a);
+ boost::optional<Principal> parse_principal(string&& s, string* errmsg);
+
ParseState(PolicyParser* pp, const Keyword* w)
: pp(pp), w(w) {}
Policy& policy;
uint32_t v = 0;
+ const bool reject_invalid_principals;
+
uint32_t seen = 0;
std::string annotation{"No error?"};
v = 0;
}
- PolicyParser(CephContext* cct, const string& tenant, Policy& policy)
- : cct(cct), tenant(tenant), policy(policy) {}
+ PolicyParser(CephContext* cct, const string& tenant, Policy& policy,
+ bool reject_invalid_principals)
+ : cct(cct), tenant(tenant), policy(policy),
+ reject_invalid_principals(reject_invalid_principals) {}
PolicyParser(const PolicyParser& policy) = delete;
bool StartObject() {
// I should just rewrite a few helper functions to use iterators,
// which will make all of this ever so much nicer.
-static boost::optional<Principal> parse_principal(CephContext* cct, TokenID t,
- string&& s) {
- if ((t == TokenID::AWS) && (s == "*")) {
+boost::optional<Principal> ParseState::parse_principal(string&& s,
+ string* errmsg) {
+ if ((w->id == TokenID::AWS) && (s == "*")) {
// Wildcard!
return Principal::wildcard();
- } else if (t == TokenID::CanonicalUser) {
+ } else if (w->id == TokenID::CanonicalUser) {
// Do nothing for now.
- } else if (t == TokenID::AWS || t == TokenID::Federated) {
+ if (errmsg)
+ *errmsg = "RGW does not support canonical users.";
+ return boost::none;
+ } else if (w->id == TokenID::AWS || w->id == TokenID::Federated) {
// AWS and Federated ARNs
if (auto a = ARN::parse(s)) {
if (a->resource == "root") {
return Principal::assumed_role(std::move(a->account), match[2]);
}
}
- } else {
- if (std::none_of(s.begin(), s.end(),
+ } else if (std::none_of(s.begin(), s.end(),
[](const char& c) {
return (c == ':') || (c == '/');
})) {
- // Since tenants are simply prefixes, there's no really good
- // way to see if one exists or not. So we return the thing and
- // let them try to match against it.
- return Principal::tenant(std::move(s));
- }
+ // Since tenants are simply prefixes, there's no really good
+ // way to see if one exists or not. So we return the thing and
+ // let them try to match against it.
+ return Principal::tenant(std::move(s));
}
- }
-
- ldout(cct, 0) << "Supplied principal is discarded: " << s << dendl;
+ if (errmsg)
+ *errmsg =
+ fmt::format(
+ "`{}` is not a supported AWS or Federated ARN. Supported ARNs are "
+ "forms like: "
+ "`arn:aws:iam::tenant:root` or a bare tenant name for a tenant, "
+ "`arn:aws:iam::tenant:role/role-name` for a role, "
+ "`arn:aws:sts::tenant:assumed-role/role-name/role-session-name` "
+ "for an assumed role, "
+ "`arn:aws:iam::tenant:user/user-name` for a user, "
+ "`arn:aws:iam::tenant:oidc-provider/idp-url` for OIDC.", s);
+ }
+
+ if (errmsg)
+ *errmsg = fmt::format("RGW does not support principals of type `{}`.",
+ w->name);
return boost::none;
}
auto& pri = pp->s[pp->s.size() - 2].w->id == TokenID::Principal ?
t->princ : t->noprinc;
- if (auto o = parse_principal(pp->cct, w->id, string(s, l))) {
+ string errmsg;
+ if (auto o = parse_principal({s, l}, &errmsg)) {
pri.emplace(std::move(*o));
+ } else if (pp->reject_invalid_principals) {
+ annotate(std::move(errmsg));
+ return false;
+ } else {
+ ldout(cct, 0) << "Ignored principle `" << std::string_view{s, l} << "`: "
+ << errmsg << dendl;
}
} else {
// Failure
}
Policy::Policy(CephContext* cct, const string& tenant,
- const bufferlist& _text)
+ const bufferlist& _text,
+ bool reject_invalid_principals)
: text(_text.to_str()) {
StringStream ss(text.data());
- PolicyParser pp(cct, tenant, *this);
+ PolicyParser pp(cct, tenant, *this, reject_invalid_principals);
auto pr = Reader{}.Parse<kParseNumbersAsStringsFlag |
kParseCommentsFlag>(ss, pp);
if (!pr) {
std::vector<Statement> statements;
+ // reject_invalid_principals should be set to
+ // `cct->_conf.get_val<bool>("rgw_policy_reject_invalid_principals")`
+ // when executing operations that *set* a bucket policy, but should
+ // be false when reading a stored bucket policy so as not to break
+ // backwards configuration.
Policy(CephContext* cct, const std::string& tenant,
- const bufferlist& text);
+ const bufferlist& text,
+ bool reject_invalid_principals);
Effect eval(const Environment& e,
boost::optional<const rgw::auth::Identity&> ida,
const string& tenant) {
auto i = attrs.find(RGW_ATTR_IAM_POLICY);
if (i != attrs.end()) {
- return Policy(cct, tenant, i->second);
+ return Policy(cct, tenant, i->second, false);
} else {
return none;
}
decode(policy_map, out_bl);
for (auto& it : policy_map) {
bufferlist bl = bufferlist::static_from_string(it.second);
- Policy p(cct, tenant, bl);
+ Policy p(cct, tenant, bl, false);
policies.push_back(std::move(p));
}
}
}
try {
- const Policy p(s->cct, s->bucket_tenant, data);
+ const Policy p(
+ s->cct, s->bucket_tenant, data,
+ s->cct->_conf.get_val<bool>("rgw_policy_reject_invalid_principals"));
rgw::sal::Attrs attrs(s->bucket_attrs);
if (s->bucket_access_conf &&
s->bucket_access_conf->block_public_policy() &&
bufferlist bl = bufferlist::static_from_string(trust_policy);
try {
- const rgw::IAM::Policy p(s->cct, s->user->get_tenant(), bl);
+ const rgw::IAM::Policy p(
+ s->cct, s->user->get_tenant(), bl,
+ s->cct->_conf.get_val<bool>("rgw_policy_reject_invalid_principals"));
}
catch (rgw::IAM::PolicyParseException& e) {
ldpp_dout(this, 20) << "failed to parse policy: " << e.what() << dendl;
}
bufferlist bl = bufferlist::static_from_string(perm_policy);
try {
- const rgw::IAM::Policy p(s->cct, s->user->get_tenant(), bl);
+ const rgw::IAM::Policy p(
+ s->cct, s->user->get_tenant(), bl,
+ s->cct->_conf.get_val<bool>("rgw_policy_reject_invalid_principals"));
}
catch (rgw::IAM::PolicyParseException& e) {
ldpp_dout(this, 20) << "failed to parse policy: " << e.what() << dendl;
//Parse the policy
//TODO - This step should be part of Role Creation
try {
- const rgw::IAM::Policy p(s->cct, s->user->get_tenant(), bl);
+ const rgw::IAM::Policy p(s->cct, s->user->get_tenant(), bl, false);
if (!s->principal_tags.empty()) {
auto res = p.eval(s->env, *s->auth.identity, rgw::IAM::stsTagSession, boost::none);
if (res != rgw::IAM::Effect::Allow) {
if (! policy.empty()) {
bufferlist bl = bufferlist::static_from_string(policy);
try {
- const rgw::IAM::Policy p(s->cct, s->user->get_tenant(), bl);
+ const rgw::IAM::Policy p(
+ s->cct, s->user->get_tenant(), bl,
+ s->cct->_conf.get_val<bool>("rgw_policy_reject_invalid_principals"));
}
catch (rgw::IAM::PolicyParseException& e) {
ldpp_dout(this, 20) << "failed to parse policy: " << e.what() << "policy" << policy << dendl;
if (! policy.empty()) {
bufferlist bl = bufferlist::static_from_string(policy);
try {
- const rgw::IAM::Policy p(s->cct, s->user->get_tenant(), bl);
+ const rgw::IAM::Policy p(
+ s->cct, s->user->get_tenant(), bl,
+ s->cct->_conf.get_val<bool>("rgw_policy_reject_invalid_principals"));
}
catch (rgw::IAM::PolicyParseException& e) {
ldpp_dout(this, 0) << "failed to parse policy: " << e.what() << "policy" << policy << dendl;
}
try {
- const Policy p(s->cct, s->user->get_tenant(), bl);
+ const Policy p(
+ s->cct, s->user->get_tenant(), bl,
+ s->cct->_conf.get_val<bool>("rgw_policy_reject_invalid_principals"));
map<string, string> policies;
if (auto it = user->get_attrs().find(RGW_ATTR_USER_POLICY); it != user->get_attrs().end()) {
bufferlist out_bl = it->second;
boost::optional<Policy> p;
ASSERT_NO_THROW(p = Policy(cct.get(), arbitrary_tenant,
- bufferlist::static_from_string(example1)));
+ bufferlist::static_from_string(example1),
+ true));
ASSERT_TRUE(p);
EXPECT_EQ(p->text, example1);
TEST_F(PolicyTest, Eval1) {
auto p = Policy(cct.get(), arbitrary_tenant,
- bufferlist::static_from_string(example1));
+ bufferlist::static_from_string(example1), true);
Environment e;
ARN arn1(Partition::aws, Service::s3,
boost::optional<Policy> p;
ASSERT_NO_THROW(p = Policy(cct.get(), arbitrary_tenant,
- bufferlist::static_from_string(example2)));
+ bufferlist::static_from_string(example2),
+ true));
ASSERT_TRUE(p);
EXPECT_EQ(p->text, example2);
TEST_F(PolicyTest, Eval2) {
auto p = Policy(cct.get(), arbitrary_tenant,
- bufferlist::static_from_string(example2));
+ bufferlist::static_from_string(example2), true);
Environment e;
auto trueacct = FakeIdentity(
boost::optional<Policy> p;
ASSERT_NO_THROW(p = Policy(cct.get(), arbitrary_tenant,
- bufferlist::static_from_string(example3)));
+ bufferlist::static_from_string(example3), true));
ASSERT_TRUE(p);
EXPECT_EQ(p->text, example3);
TEST_F(PolicyTest, Eval3) {
auto p = Policy(cct.get(), arbitrary_tenant,
- bufferlist::static_from_string(example3));
+ bufferlist::static_from_string(example3), true);
Environment em;
Environment tr = { { "aws:MultiFactorAuthPresent", "true" } };
Environment fa = { { "aws:MultiFactorAuthPresent", "false" } };
boost::optional<Policy> p;
ASSERT_NO_THROW(p = Policy(cct.get(), arbitrary_tenant,
- bufferlist::static_from_string(example4)));
+ bufferlist::static_from_string(example4), true));
ASSERT_TRUE(p);
EXPECT_EQ(p->text, example4);
TEST_F(PolicyTest, Eval4) {
auto p = Policy(cct.get(), arbitrary_tenant,
- bufferlist::static_from_string(example4));
+ bufferlist::static_from_string(example4), true);
Environment e;
ARN arn1(Partition::aws, Service::iam,
boost::optional<Policy> p;
ASSERT_NO_THROW(p = Policy(cct.get(), arbitrary_tenant,
- bufferlist::static_from_string(example5)));
+ bufferlist::static_from_string(example5), true));
ASSERT_TRUE(p);
EXPECT_EQ(p->text, example5);
EXPECT_EQ(p->version, Version::v2012_10_17);
TEST_F(PolicyTest, Eval5) {
auto p = Policy(cct.get(), arbitrary_tenant,
- bufferlist::static_from_string(example5));
+ bufferlist::static_from_string(example5), true);
Environment e;
ARN arn1(Partition::aws, Service::iam,
boost::optional<Policy> p;
ASSERT_NO_THROW(p = Policy(cct.get(), arbitrary_tenant,
- bufferlist::static_from_string(example6)));
+ bufferlist::static_from_string(example6), true));
ASSERT_TRUE(p);
EXPECT_EQ(p->text, example6);
EXPECT_EQ(p->version, Version::v2012_10_17);
TEST_F(PolicyTest, Eval6) {
auto p = Policy(cct.get(), arbitrary_tenant,
- bufferlist::static_from_string(example6));
+ bufferlist::static_from_string(example6), true);
Environment e;
ARN arn1(Partition::aws, Service::iam,
boost::optional<Policy> p;
ASSERT_NO_THROW(p = Policy(cct.get(), arbitrary_tenant,
- bufferlist::static_from_string(example7)));
+ bufferlist::static_from_string(example7), true));
ASSERT_TRUE(p);
EXPECT_EQ(p->text, example7);
TEST_F(PolicyTest, Eval7) {
auto p = Policy(cct.get(), arbitrary_tenant,
- bufferlist::static_from_string(example7));
+ bufferlist::static_from_string(example7), true);
Environment e;
auto subacct = FakeIdentity(
TEST_F(IPPolicyTest, ParseIPAddress) {
boost::optional<Policy> p;
- ASSERT_NO_THROW(p = Policy(cct.get(), arbitrary_tenant,
- bufferlist::static_from_string(ip_address_full_example)));
+ ASSERT_NO_THROW(
+ p = Policy(cct.get(), arbitrary_tenant,
+ bufferlist::static_from_string(ip_address_full_example), true));
ASSERT_TRUE(p);
EXPECT_EQ(p->text, ip_address_full_example);
}
TEST_F(IPPolicyTest, EvalIPAddress) {
- auto allowp = Policy(cct.get(), arbitrary_tenant,
- bufferlist::static_from_string(ip_address_allow_example));
- auto denyp = Policy(cct.get(), arbitrary_tenant,
- bufferlist::static_from_string(ip_address_deny_example));
- auto fullp = Policy(cct.get(), arbitrary_tenant,
- bufferlist::static_from_string(ip_address_full_example));
+ auto allowp =
+ Policy(cct.get(), arbitrary_tenant,
+ bufferlist::static_from_string(ip_address_allow_example), true);
+ auto denyp =
+ Policy(cct.get(), arbitrary_tenant,
+ bufferlist::static_from_string(ip_address_deny_example), true);
+ auto fullp =
+ Policy(cct.get(), arbitrary_tenant,
+ bufferlist::static_from_string(ip_address_full_example), true);
Environment e;
Environment allowedIP, blocklistedIP, allowedIPv6, blocklistedIPv6;
allowedIP.emplace("aws:SourceIp","192.168.1.2");