Skip to content

Commit c8c15bd

Browse files
authored
Merge pull request ceph#61828 from clwluvw/datasync-useracl
rgw: prevent data sync from replicating to buckets not owned by the user Signed-off-by: Shilpa Jagannath <[email protected]>
2 parents f1a8960 + a3f45c7 commit c8c15bd

File tree

5 files changed

+234
-21
lines changed

5 files changed

+234
-21
lines changed

qa/tasks/rgw_multisite_tests.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,8 +78,19 @@ def setup(self):
7878
user.create(master_zone, ['--display-name', 'TestUser',
7979
'--gen-access-key', '--gen-secret'])
8080

81+
# create non-account user
82+
log.info('creating non-account user..')
83+
non_account_user = multisite.User('rgw-multisite-test-non-account-user')
84+
non_account_user.create(master_zone, ['--display-name', 'NonAccountUser',
85+
'--gen-access-key', '--gen-secret'])
86+
# create non-account alt user
87+
log.info('creating non-account alt user..')
88+
non_account_alt_user = multisite.User('rgw-multisite-test-non-account-alt-user')
89+
non_account_alt_user.create(master_zone, ['--display-name', 'NonAccountAltUser',
90+
'--gen-access-key', '--gen-secret'])
91+
8192
config = self.config.get('config', {})
82-
tests.init_multi(realm, user, tests.Config(**config))
93+
tests.init_multi(realm, user, non_account_user, non_account_alt_user, tests.Config(**config))
8394
tests.realm_meta_checkpoint(realm)
8495

8596
def begin(self):

src/rgw/driver/rados/rgw_data_sync.cc

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2694,8 +2694,8 @@ class RGWUserPermHandler {
26942694

26952695
ret = RGWUserPermHandler::policy_from_attrs(
26962696
sync_env->cct, user->get_attrs(), &info->user_acl);
2697-
if (ret == -ENOENT) {
2698-
info->user_acl.create_default(uid, user->get_display_name());
2697+
if (ret < 0 && ret != -ENOENT) {
2698+
return ret;
26992699
}
27002700

27012701
return 0;

src/test/rgw/rgw_multi/conn.py

Lines changed: 17 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,16 @@
55

66
def get_gateway_connection(gateway, credentials):
77
""" connect to the given gateway """
8+
# Always create a new connection to the gateway to ensure each set of credentials gets its own connection
9+
conn = boto.connect_s3(aws_access_key_id = credentials.access_key,
10+
aws_secret_access_key = credentials.secret,
11+
host = gateway.host,
12+
port = gateway.port,
13+
is_secure = False,
14+
calling_format = boto.s3.connection.OrdinaryCallingFormat())
815
if gateway.connection is None:
9-
gateway.connection = boto.connect_s3(
10-
aws_access_key_id = credentials.access_key,
11-
aws_secret_access_key = credentials.secret,
12-
host = gateway.host,
13-
port = gateway.port,
14-
is_secure = False,
15-
calling_format = boto.s3.connection.OrdinaryCallingFormat())
16-
return gateway.connection
16+
gateway.connection = conn
17+
return conn
1718

1819
def get_gateway_secure_connection(gateway, credentials):
1920
""" secure connect to the given gateway """
@@ -47,13 +48,15 @@ def get_gateway_iam_connection(gateway, credentials, region):
4748

4849
def get_gateway_s3_client(gateway, credentials, region):
4950
""" connect to boto3 s3 client api of the given gateway """
51+
# Always create a new connection to the gateway to ensure each set of credentials gets its own connection
52+
s3_client = boto3.client('s3',
53+
endpoint_url='http://' + gateway.host + ':' + str(gateway.port),
54+
aws_access_key_id=credentials.access_key,
55+
aws_secret_access_key=credentials.secret,
56+
region_name=region)
5057
if gateway.s3_client is None:
51-
gateway.s3_client = boto3.client('s3',
52-
endpoint_url='http://' + gateway.host + ':' + str(gateway.port),
53-
aws_access_key_id=credentials.access_key,
54-
aws_secret_access_key=credentials.secret,
55-
region_name=region)
56-
return gateway.s3_client
58+
gateway.s3_client = s3_client
59+
return s3_client
5760

5861

5962
def get_gateway_sns_client(gateway, credentials, region):

src/test/rgw/rgw_multi/tests.py

Lines changed: 183 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,12 +42,18 @@ def __init__(self, **kwargs):
4242
# implementations of these interfaces by calling init_multi()
4343
realm = None
4444
user = None
45+
non_account_user = None
46+
non_account_alt_user = None
4547
config = None
46-
def init_multi(_realm, _user, _config=None):
48+
def init_multi(_realm, _user, _non_account_user, _non_account_alt_user, _config=None):
4749
global realm
4850
realm = _realm
4951
global user
5052
user = _user
53+
global non_account_user
54+
non_account_user = _non_account_user
55+
global non_account_alt_user
56+
non_account_alt_user = _non_account_alt_user
5157
global config
5258
config = _config or Config()
5359
realm_meta_checkpoint(realm)
@@ -468,17 +474,31 @@ class ZonegroupConns:
468474
def __init__(self, zonegroup):
469475
self.zonegroup = zonegroup
470476
self.zones = []
477+
self.non_account_zones = []
478+
self.non_account_alt_zones = []
471479
self.ro_zones = []
480+
self.non_account_ro_zones = []
481+
self.non_account_alt_ro_zones = []
472482
self.rw_zones = []
483+
self.non_account_rw_zones = []
484+
self.non_account_alt_rw_zones = []
473485
self.master_zone = None
474486

475487
for z in zonegroup.zones:
476488
zone_conn = z.get_conn(user.credentials)
489+
non_account_zone_conn = z.get_conn(non_account_user.credentials)
490+
non_account_alt_zone_conn = z.get_conn(non_account_alt_user.credentials)
477491
self.zones.append(zone_conn)
492+
self.non_account_zones.append(non_account_zone_conn)
493+
self.non_account_alt_zones.append(non_account_alt_zone_conn)
478494
if z.is_read_only():
479495
self.ro_zones.append(zone_conn)
496+
self.non_account_ro_zones.append(non_account_zone_conn)
497+
self.non_account_alt_ro_zones.append(non_account_alt_zone_conn)
480498
else:
481499
self.rw_zones.append(zone_conn)
500+
self.non_account_rw_zones.append(non_account_zone_conn)
501+
self.non_account_alt_rw_zones.append(non_account_alt_zone_conn)
482502

483503
if z == zonegroup.master_zone:
484504
self.master_zone = zone_conn
@@ -3703,3 +3723,165 @@ def test_bucket_create_location_constraint():
37033723
Bucket=bucket_name,
37043724
CreateBucketConfiguration={'LocationConstraint': zg.name})
37053725
assert e.response['ResponseMetadata']['HTTPStatusCode'] == 400
3726+
3727+
def allow_bucket_replication(function):
3728+
def wrapper(*args, **kwargs):
3729+
zonegroup = realm.master_zonegroup()
3730+
if len(zonegroup.zones) < 2:
3731+
raise SkipTest("More than one zone needed in any one or multiple zone(s).")
3732+
3733+
zones = ",".join([z.name for z in zonegroup.zones])
3734+
z = zonegroup.zones[0]
3735+
c = z.cluster
3736+
3737+
create_sync_policy_group(c, "sync-group")
3738+
create_sync_group_flow_symmetrical(c, "sync-group", "sync-flow", zones)
3739+
create_sync_group_pipe(c, "sync-group", "sync-pipe", zones, zones)
3740+
3741+
zonegroup.period.update(z, commit=True)
3742+
realm_meta_checkpoint(realm)
3743+
3744+
try:
3745+
function(*args, **kwargs)
3746+
finally:
3747+
remove_sync_group_pipe(c, "sync-group", "sync-pipe")
3748+
remove_sync_group_flow_symmetrical(c, "sync-group", "sync-flow")
3749+
remove_sync_policy_group(c, "sync-group")
3750+
3751+
zonegroup.period.update(z, commit=True)
3752+
realm_meta_checkpoint(realm)
3753+
3754+
return wrapper
3755+
3756+
@allow_bucket_replication
3757+
def test_bucket_replication_normal():
3758+
zonegroup = realm.master_zonegroup()
3759+
zonegroup_conns = ZonegroupConns(zonegroup)
3760+
3761+
source = zonegroup_conns.non_account_rw_zones[0]
3762+
dest = zonegroup_conns.non_account_rw_zones[1]
3763+
3764+
source_bucket = source.create_bucket(gen_bucket_name())
3765+
dest_bucket = dest.create_bucket(gen_bucket_name())
3766+
zonegroup_meta_checkpoint(zonegroup)
3767+
3768+
# create replication configuration
3769+
response = source.s3_client.put_bucket_replication(
3770+
Bucket=source_bucket.name,
3771+
ReplicationConfiguration={
3772+
'Role': '',
3773+
'Rules': [{
3774+
'ID': 'rule1',
3775+
'Status': 'Enabled',
3776+
'Destination': {
3777+
'Bucket': f'arn:aws:s3:::{dest_bucket.name}',
3778+
}
3779+
}]
3780+
}
3781+
)
3782+
assert response['ResponseMetadata']['HTTPStatusCode'] == 200
3783+
zonegroup_meta_checkpoint(zonegroup)
3784+
3785+
# upload an object and wait for sync.
3786+
objname = 'dummy'
3787+
k = new_key(source, source_bucket, objname)
3788+
k.set_contents_from_string('foo')
3789+
zone_data_checkpoint(dest.zone, source.zone)
3790+
3791+
# check that object exists in destination bucket
3792+
k = get_key(dest, dest_bucket, objname)
3793+
assert_equal(k.get_contents_as_string().decode('utf-8'), 'foo')
3794+
3795+
@allow_bucket_replication
3796+
def test_bucket_replication_alt_user_forbidden():
3797+
zonegroup = realm.master_zonegroup()
3798+
zonegroup_conns = ZonegroupConns(zonegroup)
3799+
3800+
source = zonegroup_conns.non_account_rw_zones[0]
3801+
dest = zonegroup_conns.non_account_alt_rw_zones[1]
3802+
3803+
source_bucket = source.create_bucket(gen_bucket_name())
3804+
dest_bucket = dest.create_bucket(gen_bucket_name())
3805+
zonegroup_meta_checkpoint(zonegroup)
3806+
3807+
# create replication configuration
3808+
response = source.s3_client.put_bucket_replication(
3809+
Bucket=source_bucket.name,
3810+
ReplicationConfiguration={
3811+
'Role': '',
3812+
'Rules': [{
3813+
'ID': 'rule1',
3814+
'Status': 'Enabled',
3815+
'Destination': {
3816+
'Bucket': f'arn:aws:s3:::{dest_bucket.name}',
3817+
}
3818+
}]
3819+
}
3820+
)
3821+
assert response['ResponseMetadata']['HTTPStatusCode'] == 200
3822+
zonegroup_meta_checkpoint(zonegroup)
3823+
3824+
# upload an object and wait for sync.
3825+
objname = 'dummy'
3826+
k = new_key(source, source_bucket, objname)
3827+
k.set_contents_from_string('foo')
3828+
zone_data_checkpoint(dest.zone, source.zone)
3829+
3830+
# check that object does not exist in destination bucket
3831+
e = assert_raises(ClientError, dest.s3_client.get_object, Bucket=dest_bucket.name, Key=objname)
3832+
assert e.response['Error']['Code'] == 'NoSuchKey'
3833+
3834+
@allow_bucket_replication
3835+
def test_bucket_replication_alt_user():
3836+
zonegroup = realm.master_zonegroup()
3837+
zonegroup_conns = ZonegroupConns(zonegroup)
3838+
3839+
source = zonegroup_conns.non_account_rw_zones[0]
3840+
dest = zonegroup_conns.non_account_alt_rw_zones[1]
3841+
3842+
source_bucket = source.create_bucket(gen_bucket_name())
3843+
dest_bucket = dest.create_bucket(gen_bucket_name())
3844+
zonegroup_meta_checkpoint(zonegroup)
3845+
3846+
# create replication configuration
3847+
response = source.s3_client.put_bucket_replication(
3848+
Bucket=source_bucket.name,
3849+
ReplicationConfiguration={
3850+
'Role': '',
3851+
'Rules': [{
3852+
'ID': 'rule1',
3853+
'Status': 'Enabled',
3854+
'Destination': {
3855+
'Bucket': f'arn:aws:s3:::{dest_bucket.name}',
3856+
'AccessControlTranslation': {
3857+
'Owner': non_account_alt_user.id,
3858+
},
3859+
}
3860+
}]
3861+
}
3862+
)
3863+
assert response['ResponseMetadata']['HTTPStatusCode'] == 200
3864+
# give access to user to write to alt user's bucket
3865+
dest.s3_client.put_bucket_policy(
3866+
Bucket=dest_bucket.name,
3867+
Policy=json.dumps({
3868+
'Version': '2012-10-17',
3869+
'Statement': [{
3870+
'Effect': 'Allow',
3871+
'Principal': {'AWS': [f"arn:aws:iam:::user/{user.id}"]},
3872+
'Action': 's3:PutObject',
3873+
'Resource': f'arn:aws:s3:::{dest_bucket.name}/*',
3874+
}]
3875+
})
3876+
)
3877+
zonegroup_meta_checkpoint(zonegroup)
3878+
3879+
# upload an object and wait for sync.
3880+
objname = 'dummy'
3881+
k = new_key(source, source_bucket, objname)
3882+
k.set_contents_from_string('foo')
3883+
zone_data_checkpoint(dest.zone, source.zone)
3884+
3885+
# check that object exists in destination bucket
3886+
k = get_key(dest, dest_bucket, objname)
3887+
assert_equal(k.get_contents_as_string().decode('utf-8'), 'foo')

src/test/rgw/test_multi.py

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -248,6 +248,12 @@ def init(parse_args):
248248
user_creds = gen_credentials()
249249
user = multisite.User('tester', tenant=args.tenant, account='RGW11111111111111111')
250250

251+
non_account_user_creds = gen_credentials()
252+
non_account_user = multisite.User('nonaccounttester', tenant=args.tenant)
253+
254+
non_account_alt_user_creds = gen_credentials()
255+
non_account_alt_user = multisite.User('nonaccountalttester', tenant=args.tenant)
256+
251257
realm = multisite.Realm('r')
252258
if bootstrap:
253259
# create the realm on c1
@@ -388,13 +394,24 @@ def init(parse_args):
388394
arg = ['--display-name', 'TestUser']
389395
arg += user_creds.credential_args()
390396
user.create(zone, arg)
397+
# create non-account test user
398+
arg = ['--display-name', 'NonAccountTestUser']
399+
arg += non_account_user_creds.credential_args()
400+
non_account_user.create(zone, arg)
401+
# create non-account alt test user
402+
arg = ['--display-name', 'NonAccountAltTestUser']
403+
arg += non_account_alt_user_creds.credential_args()
404+
non_account_alt_user.create(zone, arg)
391405
else:
392406
# read users and update keys
393407
admin_user.info(zone)
394408
admin_creds = admin_user.credentials[0]
395-
arg = []
396-
user.info(zone, arg)
409+
user.info(zone)
397410
user_creds = user.credentials[0]
411+
non_account_user.info(zone)
412+
non_account_user_creds = non_account_user.credentials[0]
413+
non_account_alt_user.info(zone)
414+
non_account_alt_user_creds = non_account_alt_user.credentials[0]
398415

399416
if not bootstrap:
400417
period.get(c1)
@@ -403,7 +420,7 @@ def init(parse_args):
403420
checkpoint_delay=args.checkpoint_delay,
404421
reconfigure_delay=args.reconfigure_delay,
405422
tenant=args.tenant)
406-
init_multi(realm, user, config)
423+
init_multi(realm, user, non_account_user, non_account_alt_user, config)
407424

408425
def setup_module():
409426
init(False)

0 commit comments

Comments
 (0)