Skip to content

Commit 1f95ea1

Browse files
authored
Merge pull request ceph#56576 from pritha-srivastava/wip-rgw-assume-role-multisite
rgw/sts: correcting authentication in case s3 ops are directed to a primary from secondary after assumerole.
2 parents 022b64a + 855db87 commit 1f95ea1

File tree

11 files changed

+151
-18
lines changed

11 files changed

+151
-18
lines changed

qa/suites/rgw/multisite/overrides.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,5 +20,7 @@ overrides:
2020
rgw sync obj etag verify: true
2121
rgw sync meta inject err probability: 0
2222
rgw sync data inject err probability: 0
23+
rgw s3 auth use sts: true
24+
rgw sts key: abcdefghijklmnoq
2325
rgw:
2426
compression type: random

src/rgw/rgw_auth.cc

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1307,6 +1307,9 @@ void rgw::auth::RoleApplier::modify_request_state(const DoutPrefixProvider *dpp,
13071307
for (auto& it : token_attrs.token_claims) {
13081308
s->token_claims.emplace_back(it);
13091309
}
1310+
if (is_system_request) {
1311+
s->system_request = true;
1312+
}
13101313
}
13111314

13121315
rgw::auth::Engine::result_t

src/rgw/rgw_auth.h

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -797,17 +797,20 @@ class RoleApplier : public IdentityApplier {
797797
rgw::sal::Driver* driver;
798798
Role role;
799799
TokenAttrs token_attrs;
800+
bool is_system_request;
800801

801802
public:
802803

803804
RoleApplier(CephContext* const cct,
804805
rgw::sal::Driver* driver,
805806
const Role& role,
806-
const TokenAttrs& token_attrs)
807+
const TokenAttrs& token_attrs,
808+
bool is_system_request)
807809
: cct(cct),
808810
driver(driver),
809811
role(role),
810-
token_attrs(token_attrs) {}
812+
token_attrs(token_attrs),
813+
is_system_request(is_system_request) {}
811814

812815
ACLOwner get_aclowner() const override;
813816
uint32_t get_perms_from_aclspec(const DoutPrefixProvider* dpp, const aclspec_t& aclspec) const override {
@@ -839,11 +842,12 @@ class RoleApplier : public IdentityApplier {
839842

840843
struct Factory {
841844
virtual ~Factory() {}
842-
virtual aplptr_t create_apl_role(CephContext* cct,
843-
const req_state* s,
844-
Role role,
845-
TokenAttrs token_attrs) const = 0;
846-
};
845+
virtual aplptr_t create_apl_role( CephContext* cct,
846+
const req_state* s,
847+
Role role,
848+
TokenAttrs token_attrs,
849+
bool is_system_request) const = 0;
850+
};
847851
};
848852

849853
class ServiceIdentity : public Identity {

src/rgw/rgw_auth_s3.h

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,9 +70,10 @@ class STSAuthStrategy : public rgw::auth::Strategy,
7070
aplptr_t create_apl_role(CephContext* const cct,
7171
const req_state* const s,
7272
RoleApplier::Role role,
73-
RoleApplier::TokenAttrs token_attrs) const override {
73+
RoleApplier::TokenAttrs token_attrs,
74+
bool is_system_request) const override {
7475
auto apl = rgw::auth::add_sysreq(cct, driver, s,
75-
rgw::auth::RoleApplier(cct, driver, std::move(role), std::move(token_attrs)));
76+
rgw::auth::RoleApplier(cct, driver, std::move(role), std::move(token_attrs), is_system_request));
7677
return aplptr_t(new decltype(apl)(std::move(apl)));
7778
}
7879

src/rgw/rgw_rest_s3.cc

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6962,6 +6962,7 @@ rgw::auth::s3::STSEngine::authenticate(
69626962
const req_state* const s,
69636963
optional_yield y) const
69646964
{
6965+
bool is_system_request{false};
69656966
if (! s->info.args.exists("x-amz-security-token") &&
69666967
! s->info.env->exists("HTTP_X_AMZ_SECURITY_TOKEN") &&
69676968
s->auth.s3_postobj_creds.x_amz_security_token.empty()) {
@@ -6973,10 +6974,36 @@ rgw::auth::s3::STSEngine::authenticate(
69736974
return result_t::reject(ret);
69746975
}
69756976
//Authentication
6977+
std::string secret_access_key;
69766978
//Check if access key is not the same passed in by client
69776979
if (token.access_key_id != _access_key_id) {
6978-
ldpp_dout(dpp, 0) << "Invalid access key" << dendl;
6979-
return result_t::reject(-EPERM);
6980+
/* In case the request is forwarded from secondary in case of multi-site,
6981+
we by-pass authentication using the session token credentials,
6982+
instead we use the system user's credentials that was used to sign
6983+
this request */
6984+
std::unique_ptr<rgw::sal::User> user;
6985+
const std::string access_key_id(_access_key_id);
6986+
if (driver->get_user_by_access_key(dpp, access_key_id, y, &user) < 0) {
6987+
ldpp_dout(dpp, 5) << "error reading user info, uid=" << access_key_id
6988+
<< " can't authenticate" << dendl;
6989+
return result_t::reject(-ERR_INVALID_ACCESS_KEY);
6990+
}
6991+
// only allow system users as this could be a forwarded request from secondary
6992+
if (user->get_info().system && driver->is_meta_master()) {
6993+
const auto iter = user->get_info().access_keys.find(access_key_id);
6994+
if (iter == std::end(user->get_info().access_keys)) {
6995+
ldpp_dout(dpp, 0) << "ERROR: access key not encoded in user info" << dendl;
6996+
return result_t::reject(-EPERM);
6997+
}
6998+
const RGWAccessKey& k = iter->second;
6999+
secret_access_key = k.key;
7000+
is_system_request = true;
7001+
} else {
7002+
ldpp_dout(dpp, 0) << "Invalid access key" << dendl;
7003+
return result_t::reject(-EPERM);
7004+
}
7005+
} else {
7006+
secret_access_key = token.secret_access_key;
69807007
}
69817008
//Check if the token has expired
69827009
if (! token.expiration.empty()) {
@@ -6997,7 +7024,7 @@ rgw::auth::s3::STSEngine::authenticate(
69977024
}
69987025
//Check for signature mismatch
69997026
const VersionAbstractor::server_signature_t server_signature = \
7000-
signature_factory(cct, token.secret_access_key, string_to_sign);
7027+
signature_factory(cct, secret_access_key, string_to_sign);
70017028
auto compare = signature.compare(server_signature);
70027029

70037030
ldpp_dout(dpp, 15) << "string_to_sign="
@@ -7061,7 +7088,7 @@ rgw::auth::s3::STSEngine::authenticate(
70617088
t_attrs.token_issued_at = std::move(token.issued_at);
70627089
t_attrs.principal_tags = std::move(token.principal_tags);
70637090
auto apl = role_apl_factory->create_apl_role(cct, s, std::move(r),
7064-
std::move(t_attrs));
7091+
std::move(t_attrs), is_system_request);
70657092
return result_t::grant(std::move(apl), completer_factory(token.secret_access_key));
70667093
} else { // This is for all local users of type TYPE_RGW|ROOT|NONE
70677094
if (token.user.empty()) {

src/test/rgw/rgw_multi/conn.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
import boto
22
import boto.s3.connection
33
import boto.iam.connection
4+
import boto.sts.connection
45
import boto3
6+
from boto.regioninfo import RegionInfo
57

68
def get_gateway_connection(gateway, credentials):
79
""" connect to the given gateway """
@@ -45,6 +47,20 @@ def get_gateway_iam_connection(gateway, credentials, region):
4547
use_ssl = False)
4648
return gateway.iam_connection
4749

50+
def get_gateway_sts_connection(gateway, credentials, region):
51+
""" connect to sts api of the given gateway """
52+
if gateway.sts_connection is None:
53+
endpoint = f'http://{gateway.host}:{gateway.port}'
54+
print(endpoint)
55+
gateway.sts_connection = boto3.client(
56+
service_name = 'sts',
57+
aws_access_key_id = credentials.access_key,
58+
aws_secret_access_key = credentials.secret,
59+
endpoint_url = endpoint,
60+
region_name=region,
61+
use_ssl = False)
62+
return gateway.sts_connection
63+
4864

4965
def get_gateway_s3_client(gateway, credentials, region):
5066
""" connect to boto3 s3 client api of the given gateway """
@@ -68,3 +84,13 @@ def get_gateway_sns_client(gateway, credentials, region):
6884
aws_secret_access_key=credentials.secret,
6985
region_name=region)
7086
return gateway.sns_client
87+
88+
def get_gateway_temp_s3_client(gateway, credentials, session_token, region):
89+
""" connect to boto3 s3 client api using temporary credntials """
90+
gateway.temp_s3_client = boto3.client('s3',
91+
endpoint_url='http://' + gateway.host + ':' + str(gateway.port),
92+
aws_access_key_id=credentials.access_key,
93+
aws_secret_access_key=credentials.secret,
94+
aws_session_token = session_token,
95+
region_name=region)
96+
return gateway.temp_s3_client

src/test/rgw/rgw_multi/multisite.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
import json
55

6-
from .conn import get_gateway_connection, get_gateway_iam_connection, get_gateway_secure_connection, get_gateway_s3_client, get_gateway_sns_client
6+
from .conn import get_gateway_connection, get_gateway_iam_connection, get_gateway_secure_connection, get_gateway_s3_client, get_gateway_sns_client, get_gateway_sts_connection, get_gateway_temp_s3_client
77

88
class Cluster:
99
""" interface to run commands against a distinct ceph cluster """
@@ -29,6 +29,7 @@ def __init__(self, host = None, port = None, cluster = None, zone = None, ssl_po
2929
self.iam_connection = None
3030
self.s3_client = None
3131
self.sns_client = None
32+
self.sts_connection = None
3233

3334
@abstractmethod
3435
def start(self, args = []):
@@ -195,7 +196,7 @@ def __init__(self, zone, credentials):
195196
self.iam_conn = get_gateway_iam_connection(self.zone.gateways[0], self.credentials, region)
196197
self.s3_client = get_gateway_s3_client(self.zone.gateways[0], self.credentials, region)
197198
self.sns_client = get_gateway_sns_client(self.zone.gateways[0], self.credentials, region)
198-
199+
self.temp_s3_client = None
199200
# create connections for the rest of the gateways (if exist)
200201
for gw in list(self.zone.gateways):
201202
get_gateway_connection(gw, self.credentials)
@@ -209,6 +210,11 @@ def get_connection(self):
209210
def get_iam_connection(self):
210211
return self.iam_conn
211212

213+
def get_temp_s3_connection(self, credentials, session_token):
214+
region = "" if self.zone.zonegroup is None else self.zone.zonegroup.name
215+
self.temp_s3_client = get_gateway_temp_s3_client(self.zone.gateways[0], credentials, session_token, region)
216+
return self.temp_s3_client
217+
212218
def get_bucket(self, bucket_name, credentials):
213219
raise NotImplementedError
214220

src/test/rgw/rgw_multi/tests.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2258,6 +2258,40 @@ def test_object_acl():
22582258
after_set_acl = bucket2.get_acl(k)
22592259
assert(len(after_set_acl.acl.grants) == 2) # read grant added on AllUsers
22602260

2261+
def test_assume_role_after_sync():
2262+
zonegroup = realm.master_zonegroup()
2263+
zonegroup_conns = ZonegroupConns(zonegroup)
2264+
access_key = 'abcd'
2265+
secret_key = 'efgh'
2266+
tenant = 'testx'
2267+
uid = 'test'
2268+
cmd = ['user', 'create', '--tenant', tenant, '--uid', uid, '--access-key', access_key, '--secret-key', secret_key, '--display-name', 'tenanted-user']
2269+
zonegroup_conns.master_zone.zone.cluster.admin(cmd)
2270+
credentials = Credentials(access_key, secret_key)
2271+
2272+
role_name = gen_role_name()
2273+
log.info('create role zone=%s name=%s', zonegroup_conns.master_zone.name, role_name)
2274+
policy_document = "{\"Version\":\"2012-10-17\",\"Statement\":[{\"Effect\":\"Allow\",\"Principal\":{\"AWS\":[\"arn:aws:iam::testx:user/test\"]},\"Action\":[\"sts:AssumeRole\"]}]}"
2275+
role = zonegroup_conns.master_zone.create_role("/", role_name, policy_document, "")
2276+
policy_document = "{\"Version\":\"2012-10-17\",\"Statement\":[{\"Effect\":\"Allow\",\"Resource\":\"*\",\"Action\":\"s3:*\"}]}"
2277+
zonegroup_conns.master_zone.put_role_policy(role_name, "Policy1", policy_document)
2278+
2279+
zonegroup_meta_checkpoint(zonegroup)
2280+
2281+
for zone in zonegroup_conns.zones:
2282+
log.info(f'checking if zone: {zone.name} has role: {role_name}')
2283+
assert(zone.has_role(role_name))
2284+
log.info(f'success, zone: {zone.name} has role: {role_name}')
2285+
2286+
for zone in zonegroup_conns.zones:
2287+
if zone == zonegroup_conns.master_zone:
2288+
log.info(f'creating bucket in primary zone')
2289+
bucket = "bucket1"
2290+
zone.assume_role_create_bucket(bucket, role['Role']['Arn'], "primary", credentials)
2291+
if zone != zonegroup_conns.master_zone:
2292+
log.info(f'creating bucket in secondary zone')
2293+
bucket = "bucket2"
2294+
zone.assume_role_create_bucket(bucket, role['Role']['Arn'], "secondary", credentials)
22612295

22622296
@attr('fails_with_rgw')
22632297
@attr('data_sync_init')

src/test/rgw/rgw_multi/zone_cloud.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -310,6 +310,9 @@ def delete_role(self, role_name):
310310
def has_role(self, role_name):
311311
assert False
312312

313+
def put_role_policy(self, rolename, policyname, policy_document):
314+
assert False
315+
313316
def create_topic(self, topicname, attributes):
314317
assert False
315318

@@ -331,6 +334,9 @@ def delete_notifications(self, bucket_name):
331334
def list_notifications(self, bucket_name):
332335
assert False
333336

337+
def assume_role(self, role_arn, session_name, policy, duration_seconds):
338+
assert False
339+
334340
def get_conn(self, credentials):
335341
return self.Conn(self, credentials)
336342

src/test/rgw/rgw_multi/zone_es.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -252,6 +252,9 @@ def delete_role(self, role_name):
252252
def has_role(self, role_name):
253253
assert False
254254

255+
def put_role_policy(self, rolename, policyname, policy_document):
256+
assert False
257+
255258
def create_topic(self, topicname, attributes):
256259
assert False
257260

@@ -273,6 +276,9 @@ def delete_notification(self, bucket_name):
273276
def list_notifications(self, bucket_name):
274277
assert False
275278

279+
def assume_role(self, role_arn, session_name, policy, duration_seconds):
280+
assert False
281+
276282
def get_conn(self, credentials):
277283
return self.Conn(self, credentials)
278284

0 commit comments

Comments
 (0)