Skip to content

Commit 444f865

Browse files
authored
Merge pull request ceph#62284 from yuvalif/wip-yuval-70086
rgw/logging: use bucket policy for logging Reviewed-by: Casey Bodley <[email protected]>
2 parents 5d01518 + 09786e4 commit 444f865

File tree

11 files changed

+515
-217
lines changed

11 files changed

+515
-217
lines changed

doc/radosgw/bucket_logging.rst

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,12 @@ in different objects in the log bucket.
2020
- The log bucket must be created before enabling logging on a bucket
2121
- The log bucket cannot be the same as the bucket being logged
2222
- The log bucket cannot have logging enabled on it
23+
- The log bucket cannot have any encryption set on in (including SSE-S3 with AES-256)
24+
- The log bucket cannot have any compression set on it
25+
- The log bucket must not have RequestPayer enabled
26+
- Source and log bucket must be in the same zonegroup
27+
- Source and log buckets may belong to different accounts (with proper bucket policy set)
28+
- The log bucket may have object lock enabled with default retention period
2329

2430

2531
.. toctree::
@@ -51,6 +57,46 @@ Journal mode supports filtering out records based on matches of the prefixes and
5157
Note that it may happen that the log records were successfully written, but the bucket operation failed, since the logs are written.
5258

5359

60+
Bucket Logging Policy
61+
---------------------
62+
On the source bucket, only its owner is allowed to enable or disable bucket logging.
63+
For a bucket to be used as a log bucket, it must have bucket policy that allows that (even if the source bucket and the log bucket are owned by the same user or account).
64+
The bucket policy must allow the `s3:PutObject` action for the log bucket, to be perfomed by the `logging.s3.amazonaws.com` service principal.
65+
It should also specify the source bucket and account that are expected to write logs to it. For example:
66+
67+
::
68+
69+
{
70+
"Version": "2012-10-17",
71+
"Statement": [
72+
{
73+
"Sid": "AllowLoggingFromSourceBucket",
74+
"Effect": "Allow",
75+
"Principal": {
76+
"Service": "logging.s3.amazonaws.com"
77+
},
78+
"Action": "s3:PutObject",
79+
"Resource": "arn:aws:s3:::log-bucket-name/prefix*",
80+
"Condition": {
81+
"StringEquals": {
82+
"aws:SourceAccount": "source-account-id"
83+
},
84+
"ArnLike": {
85+
"aws:SourceArn": "arn:aws:s3:::source-bucket-name"
86+
}
87+
}
88+
}
89+
]
90+
}
91+
92+
93+
Bucket Logging Quota
94+
--------------------
95+
Bucket and user quota are applied on the log bucket. Quota is checked every time a log record is written,
96+
and updated when the log object is added to the log bucket. In "Journal" mode, if the quota is exceeded, the logging operation will fail
97+
and as a result the bucket operation will also fail. In "Standard" mode, the logging operation will be skipped, but the bucket operation will continue.
98+
99+
54100
Bucket Logging REST API
55101
-----------------------
56102
Detailed under: `Bucket Operations`_.

src/rgw/driver/rados/rgw_sal_rados.cc

Lines changed: 19 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1072,7 +1072,7 @@ int RadosBucket::get_logging_object_name(std::string& obj_name,
10721072
rgw_pool data_pool;
10731073
const auto obj_name_oid = bucketlogging::object_name_oid(this, prefix);
10741074
if (!store->getRados()->get_obj_data_pool(get_placement_rule(), rgw_obj{get_key(), obj_name_oid}, &data_pool)) {
1075-
ldpp_dout(dpp, 1) << "ERROR: failed to get data pool for bucket '" << get_name() <<
1075+
ldpp_dout(dpp, 1) << "ERROR: failed to get data pool for bucket '" << get_key() <<
10761076
"' when getting logging object name" << dendl;
10771077
return -EIO;
10781078
}
@@ -1088,6 +1088,10 @@ int RadosBucket::get_logging_object_name(std::string& obj_name,
10881088
nullptr,
10891089
nullptr);
10901090
if (ret < 0) {
1091+
if (ret == -ENOENT) {
1092+
ldpp_dout(dpp, 20) << "INFO: logging object name '" << obj_name_oid << "' not found. ret = " << ret << dendl;
1093+
return ret;
1094+
}
10911095
ldpp_dout(dpp, 1) << "ERROR: failed to get logging object name from '" << obj_name_oid << "'. ret = " << ret << dendl;
10921096
return ret;
10931097
}
@@ -1104,7 +1108,7 @@ int RadosBucket::set_logging_object_name(const std::string& obj_name,
11041108
rgw_pool data_pool;
11051109
const auto obj_name_oid = bucketlogging::object_name_oid(this, prefix);
11061110
if (!store->getRados()->get_obj_data_pool(get_placement_rule(), rgw_obj{get_key(), obj_name_oid}, &data_pool)) {
1107-
ldpp_dout(dpp, 1) << "ERROR: failed to get data pool for bucket '" << get_name() <<
1111+
ldpp_dout(dpp, 1) << "ERROR: failed to get data pool for bucket '" << get_key() <<
11081112
"' when setting logging object name" << dendl;
11091113
return -EIO;
11101114
}
@@ -1136,7 +1140,7 @@ int RadosBucket::remove_logging_object_name(const std::string& prefix,
11361140
rgw_pool data_pool;
11371141
const auto obj_name_oid = bucketlogging::object_name_oid(this, prefix);
11381142
if (!store->getRados()->get_obj_data_pool(get_placement_rule(), rgw_obj{get_key(), obj_name_oid}, &data_pool)) {
1139-
ldpp_dout(dpp, 1) << "ERROR: failed to get data pool for bucket '" << get_name() <<
1143+
ldpp_dout(dpp, 1) << "ERROR: failed to get data pool for bucket '" << get_key() <<
11401144
"' when setting logging object name" << dendl;
11411145
return -EIO;
11421146
}
@@ -1159,7 +1163,7 @@ int RadosBucket::remove_logging_object(const std::string& obj_name, optional_yie
11591163
const auto placement_rule = get_placement_rule();
11601164

11611165
if (!store->getRados()->get_obj_data_pool(placement_rule, head_obj, &data_pool)) {
1162-
ldpp_dout(dpp, 1) << "ERROR: failed to get data pool for bucket '" << get_name() <<
1166+
ldpp_dout(dpp, 1) << "ERROR: failed to get data pool for bucket '" << get_key() <<
11631167
"' when deleting logging object" << dendl;
11641168
return -EIO;
11651169
}
@@ -1178,8 +1182,8 @@ int RadosBucket::commit_logging_object(const std::string& obj_name, optional_yie
11781182
const auto placement_rule = get_placement_rule();
11791183

11801184
if (!store->getRados()->get_obj_data_pool(placement_rule, head_obj, &data_pool)) {
1181-
ldpp_dout(dpp, 1) << "ERROR: failed to get data pool for bucket '" << get_name() <<
1182-
"' when comitting logging object" << dendl;
1185+
ldpp_dout(dpp, 1) << "ERROR: failed to get data pool for bucket '" << get_key() <<
1186+
"' when committing logging object" << dendl;
11831187
return -EIO;
11841188
}
11851189

@@ -1197,7 +1201,7 @@ int RadosBucket::commit_logging_object(const std::string& obj_name, optional_yie
11971201
dpp,
11981202
&obj_attrs,
11991203
nullptr); ret < 0 && ret != -ENOENT) {
1200-
ldpp_dout(dpp, 1) << "ERROR: failed to read logging data when comitting object '" << temp_obj_name
1204+
ldpp_dout(dpp, 1) << "ERROR: failed to read logging data when committing object '" << temp_obj_name
12011205
<< ". error: " << ret << dendl;
12021206
return ret;
12031207
} else if (ret == -ENOENT) {
@@ -1216,13 +1220,13 @@ int RadosBucket::commit_logging_object(const std::string& obj_name, optional_yie
12161220
nullptr, // no special placment for tail
12171221
get_key(),
12181222
head_obj); ret < 0) {
1219-
ldpp_dout(dpp, 1) << "ERROR: failed to create manifest when comitting logging object. error: " <<
1223+
ldpp_dout(dpp, 1) << "ERROR: failed to create manifest when committing logging object. error: " <<
12201224
ret << dendl;
12211225
return ret;
12221226
}
12231227

12241228
if (const auto ret = manifest_gen.create_next(size); ret < 0) {
1225-
ldpp_dout(dpp, 1) << "ERROR: failed to add object to manifest when comitting logging object. error: " <<
1229+
ldpp_dout(dpp, 1) << "ERROR: failed to add object to manifest when committing logging object. error: " <<
12261230
ret << dendl;
12271231
return ret;
12281232
}
@@ -1252,7 +1256,10 @@ int RadosBucket::commit_logging_object(const std::string& obj_name, optional_yie
12521256
// TODO: head_obj_wop.meta.ptag
12531257
// the owner of the logging object is the bucket owner
12541258
// not the user that wrote the log that triggered the commit
1255-
const ACLOwner owner{bucket_info.owner, ""}; // TODO: missing display name
1259+
ACLOwner owner{bucket_info.owner, ""};
1260+
if (auto i = get_attrs().find(RGW_ATTR_ACL); i != get_attrs().end()) {
1261+
std::ignore = store->getRados()->decode_policy(dpp, i->second, &owner);
1262+
}
12561263
head_obj_wop.meta.owner = owner;
12571264
const auto etag = TOPNSPC::crypto::digest<TOPNSPC::crypto::MD5>(bl_data).to_str();
12581265
bufferlist bl_etag;
@@ -1262,7 +1269,7 @@ int RadosBucket::commit_logging_object(const std::string& obj_name, optional_yie
12621269
jspan_context trace{false, false};
12631270
if (const auto ret = head_obj_wop.write_meta(0, size, obj_attrs, rctx, trace); ret < 0) {
12641271
ldpp_dout(dpp, 1) << "ERROR: failed to commit logging object '" << temp_obj_name <<
1265-
"' to bucket id '" << get_info().bucket <<"'. error: " << ret << dendl;
1272+
"' to bucket '" << get_key() <<"'. error: " << ret << dendl;
12661273
return ret;
12671274
}
12681275
ldpp_dout(dpp, 20) << "INFO: committed logging object '" << temp_obj_name <<
@@ -1300,7 +1307,7 @@ int RadosBucket::write_logging_object(const std::string& obj_name,
13001307
rgw_pool data_pool;
13011308
rgw_obj obj{get_key(), obj_name};
13021309
if (!store->getRados()->get_obj_data_pool(get_placement_rule(), obj, &data_pool)) {
1303-
ldpp_dout(dpp, 1) << "ERROR: failed to get data pool for bucket '" << get_name() <<
1310+
ldpp_dout(dpp, 1) << "ERROR: failed to get data pool for bucket '" << get_key() <<
13041311
"' when writing logging object" << dendl;
13051312
return -EIO;
13061313
}

src/rgw/rgw_auth.h

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -842,6 +842,67 @@ class RoleApplier : public IdentityApplier {
842842
};
843843
};
844844

845+
class ServiceIdentity : public Identity {
846+
const std::string service_id;
847+
public:
848+
ServiceIdentity(const std::string& s) : service_id(s) {}
849+
virtual ~ServiceIdentity() = default;
850+
851+
ACLOwner get_aclowner() const override {
852+
return {};
853+
}
854+
855+
uint32_t get_perms_from_aclspec(const DoutPrefixProvider* dpp, const aclspec_t& aclspec) const override {
856+
return RGW_PERM_NONE;
857+
}
858+
859+
bool is_admin_of(const rgw_owner& o) const override {
860+
return false;
861+
}
862+
863+
bool is_owner_of(const rgw_owner& o) const override {
864+
return false;
865+
}
866+
867+
uint32_t get_perm_mask() const override {
868+
return RGW_PERM_NONE;
869+
}
870+
871+
virtual void to_str(std::ostream& out) const override {
872+
out << "rgw::auth::ServiceIdentity(id=" << service_id << ")";
873+
}
874+
875+
bool is_identity(const Principal& p) const override {
876+
return p.is_service() && p.get_service() == service_id;
877+
}
878+
879+
uint32_t get_identity_type() const override {
880+
return TYPE_RGW;
881+
}
882+
883+
std::string get_acct_name() const override {
884+
return {};
885+
}
886+
887+
std::string get_subuser() const override {
888+
return {};
889+
}
890+
891+
const std::string& get_tenant() const override {
892+
static const std::string no_tenant;
893+
return no_tenant;
894+
}
895+
896+
const std::optional<RGWAccountInfo>& get_account() const override {
897+
static constexpr std::optional<RGWAccountInfo> no_account;
898+
return no_account;
899+
}
900+
901+
bool is_root() const override {
902+
return false;
903+
}
904+
};
905+
845906
/* The anonymous abstract engine. */
846907
class AnonymousEngine : public Engine {
847908
CephContext* const cct;

src/rgw/rgw_basic_types.cc

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,9 @@ ostream& operator <<(ostream& m, const Principal& p) {
170170
if (p.is_wildcard()) {
171171
return m << "*";
172172
}
173+
if (p.is_service()) {
174+
return m << p.get_service();
175+
}
173176

174177
m << "arn:aws:iam:" << p.get_account() << ":";
175178
if (p.is_account()) {

src/rgw/rgw_basic_types.h

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -143,10 +143,11 @@ extern void decode_json_obj(rgw_placement_rule& v, JSONObj *obj);
143143
namespace rgw {
144144
namespace auth {
145145
class Principal {
146-
enum types { User, Role, Account, Wildcard, OidcProvider, AssumedRole };
146+
enum types { User, Role, Account, Wildcard, OidcProvider, AssumedRole, Service };
147147
types t;
148148
rgw_user u;
149149
std::string idp_url;
150+
std::string service_id;
150151

151152
explicit Principal(types t)
152153
: t(t) {}
@@ -183,6 +184,12 @@ class Principal {
183184
return Principal(AssumedRole, std::move(t), std::move(u));
184185
}
185186

187+
static Principal service(std::string&& s) {
188+
auto p = Principal(Service);
189+
p.service_id = std::move(s);
190+
return p;
191+
}
192+
186193
bool is_wildcard() const {
187194
return t == Wildcard;
188195
}
@@ -207,6 +214,10 @@ class Principal {
207214
return t == AssumedRole;
208215
}
209216

217+
bool is_service() const {
218+
return t == Service;
219+
}
220+
210221
const std::string& get_account() const {
211222
return u.tenant;
212223
}
@@ -227,6 +238,10 @@ class Principal {
227238
return u.id;
228239
}
229240

241+
const std::string& get_service() const {
242+
return service_id;
243+
}
244+
230245
bool operator ==(const Principal& o) const {
231246
return (t == o.t) && (u == o.u);
232247
}

0 commit comments

Comments
 (0)