1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
|
===========
STS in Ceph
===========
Secure Token Service is a web service in AWS that returns a set of temporary security credentials for authenticating federated users.
The link to official AWS documentation can be found here: https://docs.aws.amazon.com/STS/latest/APIReference/Welcome.html.
Ceph Object Gateway implements a subset of STS APIs that provide temporary credentials for identity and access management.
These temporary credentials can be used to make subsequent S3 calls which will be authenticated by the STS engine in Ceph Object Gateway.
Permissions of the temporary credentials can be further restricted via an IAM policy passed as a parameter to the STS APIs.
STS REST APIs
=============
The following STS REST APIs have been implemented in Ceph Object Gateway:
1. AssumeRole: Returns a set of temporary credentials that can be used for
cross-account access. The temporary credentials will have permissions that are
allowed by both - permission policies attached with the Role and policy attached
with the AssumeRole API.
Parameters:
**RoleArn** (String/ Required): ARN of the Role to Assume.
**RoleSessionName** (String/ Required): An Identifier for the assumed role
session.
**Policy** (String/ Optional): An IAM Policy in JSON format.
**DurationSeconds** (Integer/ Optional): The duration in seconds of the session.
Its default value is 3600.
**ExternalId** (String/ Optional): A unique Id that might be used when a role is
assumed in another account.
**SerialNumber** (String/ Optional): The Id number of the MFA device associated
with the user making the AssumeRole call.
**TokenCode** (String/ Optional): The value provided by the MFA device, if the
trust policy of the role being assumed requires MFA.
2. AssumeRoleWithWebIdentity: Returns a set of temporary credentials for users that
have been authenticated by a web/mobile app by an OpenID Connect /OAuth2.0 Identity Provider.
Currently Keycloak has been tested and integrated with RGW.
Parameters:
**RoleArn** (String/ Required): ARN of the Role to Assume.
**RoleSessionName** (String/ Required): An Identifier for the assumed role
session.
**Policy** (String/ Optional): An IAM Policy in JSON format.
**DurationSeconds** (Integer/ Optional): The duration in seconds of the session.
Its default value is 3600.
**ProviderId** (String/ Optional): Fully qualified host component of the domain name
of the IDP. Valid only for OAuth2.0 tokens (not for OpenID Connect tokens).
**WebIdentityToken** (String/ Required): The OpenID Connect/ OAuth2.0 token, which the
application gets in return after authenticating its user with an IDP.
Before invoking AssumeRoleWithWebIdentity, an OpenID Connect Provider entity (which the web application
authenticates with), needs to be created in RGW.
The trust between the IDP and the role is created by adding a condition to the role's trust policy, which
allows access only to applications which satisfy the given condition.
All claims of the JWT are supported in the condition of the role's trust policy.
An example of a policy that uses the 'aud' claim in the condition is of the form::
'''{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Principal":{"Federated":["arn:aws:iam:::oidc-provider/<URL of IDP>"]},"Action":["sts:AssumeRoleWithWebIdentity"],"Condition":{"StringEquals":{"<URL of IDP> :app_id":"<aud>"}}}]}'''
The app_id in the condition above must match the 'aud' claim of the incoming token.
An example of a policy that uses the 'sub' claim in the condition is of the form::
"{\"Version\":\"2012-10-17\",\"Statement\":[{\"Effect\":\"Allow\",\"Principal\":{\"Federated\":[\"arn:aws:iam:::oidc-provider/<URL of IDP>\"]},\"Action\":[\"sts:AssumeRoleWithWebIdentity\"],\"Condition\":{\"StringEquals\":{\"<URL of IDP> :sub\":\"<sub>\"\}\}\}\]\}"
Similarly, an example of a policy that uses 'azp' claim in the condition is of the form::
"{\"Version\":\"2012-10-17\",\"Statement\":[{\"Effect\":\"Allow\",\"Principal\":{\"Federated\":[\"arn:aws:iam:::oidc-provider/<URL of IDP>\"]},\"Action\":[\"sts:AssumeRoleWithWebIdentity\"],\"Condition\":{\"StringEquals\":{\"<URL of IDP> :azp\":\"<azp>\"\}\}\}\]\}"
A shadow user is created corresponding to every federated user. The user id is derived from the 'sub' field of the incoming web token.
The user is created in a separate namespace - 'oidc' such that the user id doesn't clash with any other user ids in rgw. The format of the user id
is - <tenant>$<user-namespace>$<sub> where user-namespace is 'oidc' for users that authenticate with oidc providers.
RGW now supports Session tags that can be passed in the web token to AssumeRoleWithWebIdentity call. More information related to Session Tags can be found here
:doc:`session-tags`.
STS Configuration
=================
The following configurable options have to be added for STS integration::
[client.{your-rgw-name}]
rgw_sts_key = {sts key for encrypting the session token}
rgw_s3_auth_use_sts = true
Notes:
* By default, STS and S3 APIs co-exist in the same namespace, and both S3
and STS APIs can be accessed via the same endpoint in Ceph Object Gateway.
* The ``rgw_sts_key`` needs to be a hex-string consisting of exactly 16 characters.
Examples
========
1. In order to get the example to work, make sure that the user TESTER has the ``roles`` capability assigned:
.. code-block:: console
radosgw-admin caps add --uid="TESTER" --caps="roles=*"
2. The following is an example of the AssumeRole API call, which shows steps to create a role, assign a policy to it
(that allows access to S3 resources), assuming a role to get temporary credentials and accessing S3 resources using
those credentials. In this example, TESTER1 assumes a role created by TESTER, to access S3 resources owned by TESTER,
according to the permission policy attached to the role.
.. code-block:: python
import boto3
iam_client = boto3.client('iam',
aws_access_key_id=<access_key of TESTER>,
aws_secret_access_key=<secret_key of TESTER>,
endpoint_url=<IAM URL>,
region_name=''
)
policy_document = '''{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Principal":{"AWS":["arn:aws:iam:::user/TESTER1"]},"Action":["sts:AssumeRole"]}]}'''
role_response = iam_client.create_role(
AssumeRolePolicyDocument=policy_document,
Path='/',
RoleName='S3Access',
)
role_policy = '''{"Version":"2012-10-17","Statement":{"Effect":"Allow","Action":"s3:*","Resource":"arn:aws:s3:::*"}}'''
response = iam_client.put_role_policy(
RoleName='S3Access',
PolicyName='Policy1',
PolicyDocument=role_policy
)
sts_client = boto3.client('sts',
aws_access_key_id=<access_key of TESTER1>,
aws_secret_access_key=<secret_key of TESTER1>,
endpoint_url=<STS URL>,
region_name='',
)
response = sts_client.assume_role(
RoleArn=role_response['Role']['Arn'],
RoleSessionName='Bob',
DurationSeconds=3600
)
s3client = boto3.client('s3',
aws_access_key_id = response['Credentials']['AccessKeyId'],
aws_secret_access_key = response['Credentials']['SecretAccessKey'],
aws_session_token = response['Credentials']['SessionToken'],
endpoint_url=<S3 URL>,
region_name='',)
bucket_name = 'my-bucket'
s3bucket = s3client.create_bucket(Bucket=bucket_name)
resp = s3client.list_buckets()
2. The following is an example of AssumeRoleWithWebIdentity API call, where an external app that has users authenticated with
an OpenID Connect/ OAuth2 IDP (Keycloak in this example), assumes a role to get back temporary credentials and access S3 resources
according to permission policy of the role.
.. code-block:: python
import boto3
iam_client = boto3.client('iam',
aws_access_key_id=<access_key of TESTER>,
aws_secret_access_key=<secret_key of TESTER>,
endpoint_url=<IAM URL>,
region_name=''
)
oidc_response = iam_client.create_open_id_connect_provider(
Url=<URL of the OpenID Connect Provider,
ClientIDList=[
<Client id registered with the IDP>
],
ThumbprintList=[
<Thumbprint of the IDP>
]
)
policy_document = '''{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Principal":{"Federated":["arn:aws:iam:::oidc-provider/localhost:8080/auth/realms/demo"]},"Action":["sts:AssumeRoleWithWebIdentity"],"Condition":{"StringEquals":{"localhost:8080/auth/realms/demo:app_id":"customer-portal"}}}]}'''
role_response = iam_client.create_role(
AssumeRolePolicyDocument=policy_document,
Path='/',
RoleName='S3Access',
)
role_policy = '''{"Version":"2012-10-17","Statement":{"Effect":"Allow","Action":"s3:*","Resource":"arn:aws:s3:::*"}}'''
response = iam_client.put_role_policy(
RoleName='S3Access',
PolicyName='Policy1',
PolicyDocument=role_policy
)
sts_client = boto3.client('sts',
aws_access_key_id=<access_key of TESTER1>,
aws_secret_access_key=<secret_key of TESTER1>,
endpoint_url=<STS URL>,
region_name='',
)
response = client.assume_role_with_web_identity(
RoleArn=role_response['Role']['Arn'],
RoleSessionName='Bob',
DurationSeconds=3600,
WebIdentityToken=<Web Token>
)
s3client = boto3.client('s3',
aws_access_key_id = response['Credentials']['AccessKeyId'],
aws_secret_access_key = response['Credentials']['SecretAccessKey'],
aws_session_token = response['Credentials']['SessionToken'],
endpoint_url=<S3 URL>,
region_name='',)
bucket_name = 'my-bucket'
s3bucket = s3client.create_bucket(Bucket=bucket_name)
resp = s3client.list_buckets()
How to obtain thumbprint of an OpenID Connect Provider IDP
==========================================================
1. Take the OpenID connect provider's URL and add /.well-known/openid-configuration
to it to get the URL to get the IDP's configuration document. For example, if the URL
of the IDP is http://localhost:8000/auth/realms/quickstart, then the URL to get the
document from is http://localhost:8000/auth/realms/quickstart/.well-known/openid-configuration
2. Use the following curl command to get the configuration document from the URL described
in step 1::
curl -k -v \
-X GET \
-H "Content-Type: application/x-www-form-urlencoded" \
"http://localhost:8000/auth/realms/quickstart/.well-known/openid-configuration" \
| jq .
3. From the response of step 2, use the value of "jwks_uri" to get the certificate of the IDP,
using the following code::
curl -k -v \
-X GET \
-H "Content-Type: application/x-www-form-urlencoded" \
"http://$KC_SERVER/$KC_CONTEXT/realms/$KC_REALM/protocol/openid-connect/certs" \
| jq .
3. Copy the result of "x5c" in the response above, in a file certificate.crt, and add
'-----BEGIN CERTIFICATE-----' at the beginning and "-----END CERTIFICATE-----"
at the end.
4. Use the following OpenSSL command to get the certificate thumbprint::
openssl x509 -in certificate.crt -fingerprint -noout
5. The result of the above command in step 4, will be a SHA1 fingerprint, like the following::
SHA1 Fingerprint=F7:D7:B3:51:5D:D0:D3:19:DD:21:9A:43:A9:EA:72:7A:D6:06:52:87
6. Remove the colons from the result above to get the final thumbprint which can be as input
while creating the OpenID Connect Provider entity in IAM::
F7D7B3515DD0D319DD219A43A9EA727AD6065287
Roles in RGW
============
More information for role manipulation can be found here
:doc:`role`.
OpenID Connect Provider in RGW
==============================
More information for OpenID Connect Provider entity manipulation
can be found here
:doc:`oidc`.
Keycloak integration with Radosgw
=================================
Steps for integrating Radosgw with Keycloak can be found here
:doc:`keycloak`.
STSLite
=======
STSLite has been built on STS, and documentation for the same can be found here
:doc:`STSLite`.
|