# cat << EOF > get_web_token.sh
curl -k -q -s -L -X POST "https://<isv-tenant>/v1.0/endpoint/default/token" \
-H 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'client_id=89cbca68-c6f6-4b60-8fdb-d728adbd7e6e' \
--data-urlencode 'grant_type=password' \
--data-urlencode 'client_secret=#####' \
--data-urlencode 'scope=openid' \
--data-urlencode "username=user@gmail.com" \
--data-urlencode "password=XXXX"
EOF
# bash get_web_token_isv.sh | jq .
{
"access_token": "owTttsRJjdereKMjbKuRF3toV_MhAA7dLxfG2U-buY0.A3naOOLA8x-PH5eMAGWPlOFp9N4N7ZOlAz8y6RfN7m0MgO-yOXrW4OXkQH3N-xDN-XJ5FHn9brFDA0pqTR6Tfg.M18xNzAxMDczMDY0XzQy",
"scope": "openid",
"grant_id": "228bb255-d2a7-41bc-b9d8-9176d64e582c",
"id_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6InNlcnZlciJ9.eyJ1c2VyVHlwZSI6InJlZ3VsYXIiLCJ1bmlxdWVTZWN1cml0eU5hbWUiOiI2MDkwMDA3MEw3IiwiZGlzcGxheU5hbWUiOiJEZWVwYWsgUiBUIiwianRpIjoiTERSbHZCSEF6RlNoRnBnSXZiM0Y4YTdXdnIwa01oIiwicmVhbG1OYW1lIjoiY2xvdWRJZGVudGl0eVJlYWxtIiwiYXRfaGFzaCI6IlhJRlRlWjNGa0I3SGxObWhjVFQtT2ciLCJuYW1lIjoiRGVlcGFrIFIgVCIsInByZWZlcnJlZF91c2VybmFtZSI6ImRlZXBhay40NjQxQGdtYWlsLmNvbSIsImlzcyI6Imh0dHBzOi8vYXV0aHN2Yy1pbmxpbmVtZmEuZGV2LnZlcmlmeS5pYm1jbG91ZHNlY3VyaXR5LmNvbS9vaWRjL2VuZHBvaW50L2RlZmF1bHQiLCJhdWQiOlsiODljYmNhNjgtYzZmNi00YjYwLThmZGItZDcyOGFkYmQ3ZTZlIl0sInN1YiI6IjYwOTAwMDcwTDciLCJpYXQiOjE3MDEwNzMwNjQsImV4cCI6MTcwMTA4MDI2NH0.etfFx3zruenKZYcaBu6V95j6psXD7_HjRjpDirqFPZ_3RUFC9oZYagVMjrsRhCV4TpdGYLnZMLcYg4tRfNhduvdmDsLbdGc32mqxLs2-Zrv1v0FZjE-Hc2WUG21QiBxLOYRHUR56jB_bQJ_av9UAvnxB39yvx4rYrQyBivoMkNGbgiq2EV4lz7S2vl4IRL1zN_0gph27lEONZTrs8318IcgiIvi6IcAF4zSZMmmGW4h11DMLNOvINcP6-NpAOiAXPfKe_v6JJRs9WiDugkNcSsr-q_y53BxZQtXoBG0-hoPfAw74LFY9qtbxGDw8XZGFLNYLZkH25TsLm5Mah7x3Sg",
"token_type": "Bearer",
"expires_in": 7199
}
Validation of Authentication to Ceph Object Storage:
# cat << EOF > instropect_token_isv.sh
KC_CLIENT="89cbca68-c6f6-4b60-8fdb-d728adbd7e6e"
KC_CLIENT_SECRET="####"
KC_ACCESS_TOKEN="$(./get_web_token_isv.sh | jq -r '.access_token')"
curl -s -k -q \
-X POST \
-u "$KC_CLIENT:$KC_CLIENT_SECRET" \
-d "token=$KC_ACCESS_TOKEN" \
"https://<isv-tenant>/v1.0/endpoint/default/introspect" | jq .
EOF
# bash instropect_token_isv.sh
{
"at_hash": "P9u_zC8dJKnzrWBC5mTj8Q",
"sub": "60900070L7",
"realmName": "cloudIdentityRealm",
"uniqueSecurityName": "60900070L7",
"iss": "https://<isv-tenant>/oidc/endpoint/default",
"groups": [
"admin",
"developer",
"Cloud Directory only",
"architect"
],
"active": true,
"preferred_username": "XXXXX@gmail.com",
"token_type": "Bearer",
"client_id": "89cbca68-c6f6-4b60-8fdb-d728adbd7e6e",
"aud": [
"89cbca68-c6f6-4b60-8fdb-d728adbd7e6e"
],
"grant_type": "resource_owner",
"token_use": "access_token",
"grant_id": "5bc9c58f-6aad-4ef8-b53a-05ef6cda6c22",
"scope": "openid",
"userType": "regular",
"exp": 1701080301,
"app_id": "8892470245991660012",
"iat": 1701073101,
"email": "XXXX@gmail.com",
"username": "XXXX@gmail.com"
}
From the introspection, you can see that our user has many attributes defined. These attributes can be passed on from the enterprise IDP like LDAP/AD or configured on the ISV. We can use these attributes in Ceph to define what S3 resources the user can access; for example, in the introspection of the JWT we have a group attribute listing all the groups to which this user belongs; we will use that attribute to define what S3 resources users belonging to that group can access, following a traditional RBAC authentication model.
Now that we have verified the ISV configuration, let’s connect Ceph to the ISV using the IAM API to create a new OIDC provider. To be able to achieve this, we need to create a fingerprint from the x5c certificate that we can retrieve from the /jwks well-known URL:
# cat get_fingerprint_isv.sh
#!/bin/bash
set -o pipefail
CERT_FILE=$(mktemp)
cat << EOF > ${CERT_FILE}
-----BEGIN CERTIFICATE-----
$(curl -k https://<isv-tenant>/v1.0/endpoint/default/jwks | jq -r .keys[0].x5c[])
-----END CERTIFICATE-----
EOF
openssl x509 -in ${CERT_FILE} -fingerprint -noout | awk -F = '{ print $NF }' | sed 's/://g'
rm -f ${CERT_FILE}
# bash get_fingerprint_isv.sh
C8C4A3A283F8A4CBD04B46E2215EA98B18748574
We already possess the x5c certificate. To obtain the ISV OIDC provider URL, you can check the ISS attribute in your JWT, perform an initial introspection, or rerun the script.
# bash instropect_token_isv.sh | jq .iss
"https://<isv-tenant>/oidc/endpoint/default"
To create an OIDC provider, a dedicated s3 user with OIDC permissions be created and assigned the OIDC caps.
# radosgw-admin user create --uid oidc --display-name "Open ID Connect Provider" --access-key oidc --secret oidc
# radosgw-admin caps add --uid="oidc" --caps="oidc-provider=*"
Now that we have all the required pre-requisites:
Let’s create the OIDC provider in IBM Storage Ceph Object. I will use the aws cli, but you could use any other method.
# export AWS_ACCESS_KEY_ID=oidc
# export AWS_SECRET_ACCESS_KEY=oidc
# export AWS_ENDPOINT_URL=https://cephproxy1.stg.local:8443
# export URL="https://<isv-tenant>/oidc/endpoint/default"
# export THUMBPRINT=C8C4A3A283F8A4CBD04B46E2215EA98B18748574
# aws iam create-open-id-connect-provider --url $URL --thumbprint-list $THUMBPRINT
# aws --profile oidc2 --endpoint https://cephproxy1.stg.local:8443 iam list-open-id-connect-providers --region default
{
"OpenIDConnectProviderList": [
{
"Arn": "arn:aws:iam:::oidc-provider/<isv-tenant>/oidc/endpoint/default"
},
]
}
We have completed the setup of our OIDC provider. In the next step, we are required to define an IAM role in a JSON file. We are setting up a statement to allow the "AssumeRoleWithWebIdentity" action if the JWT token presented by the user matches the "admin" group attribute condition.
# cat << EOF > role-rgwadmins-app_id_isv.json
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Federated": [
"arn:aws:iam:::oidc-provider/<isv-tenant>/oidc/endpoint/default"
]
},
"Action": [
"sts:AssumeRoleWithWebIdentity"
],
"Condition": {
"StringLike": { "<isv-tenant>/oidc/endpoint/default:sub":"60900070L7" }
}
}
]
}
EOF
# radosgw-admin role create --role-name rgwadmins \
--assume-role-policy-doc=$(jq -rc . /root/role-rgwadmins.json)
{
"RoleId": "e5e2ab57-f57f-46ad-a486-2a5e1435522d",
"RoleName": "rgwadmins4",
"Path": "/",
"Arn": "arn:aws:iam:::role/rgwadmins",
"CreateDate": "2023-11-27T08:28:44.877Z",
"MaxSessionDuration": 3600,
"AssumeRolePolicyDocument": "{\"Version\":\"2012-10-17\",\"Statement\":[{\"Effect\":\"Allow\",\"Principal\":{\"Federated\":[\"arn:aws:iam:::oidc-provider/keycloak-sso.apps.ocp.stg.local/auth/realms/ceph\"]},\"Action\":[\"sts:AssumeRoleWithWebIdentity\"],\"Condition\":{\"StringEquals\":{\"keycloak-sso.apps.ocp.stg.local/auth/realms/ceph:groups:groups\":\"rgwadmins\"}}}]}"
}
Once the role is successfully created, we need to specify what S3 resources the user will have access to once he assumes the IAM rgwadmins role. We provide a simple example in JSON format by which the user assumes the rgwadmins role will have access to all S3 resources, but we can be as granular as we need.
# cat << EOF > policy-rgwadmin.json
{
"Version": "2012-10-17",
"Statement": [
{ "Effect": "Allow",
"Action": [ "s3:*" ],
"Resource": "arn:aws:s3:::*"
}
] }
EOF
# radosgw-admin role policy put --role-name=rgwadmins
--policy-name=admin --policy-doc=$(jq -rc . /root/policy-rgwadmin.json)
We have completed the configuration of the IAM role. Now, we can perform an end-to-end test to authenticate against the ISV and obtain a JWT. After that, we can assume the rgwadmin role using the JWT token, and STS will provide us with credentials to access the role's S3 resources. To create an example script, we'll call it assume_role_rgwadmin.sh. During runtime, we need to specify the IAM role name we want to assume, which, in this example, is rgwadmins.
# cat << EOF > assume_role.sh
#!/bin/bash
export AWS_CA_BUNDLE="/etc/pki/ca-trust/source/anchors/cert.pem"
unset AWS_ACCESS_KEY_ID
unset AWS_SECRET_ACCESS_KEY
unset AWS_SESSION_TOKEN
echo -e "\n"
echo "Getting the JWT from SSO..."
KC_ACCESS_TOKEN="$(./get_web_token_isv.sh | jq -r '.id_token')"
echo -e "\n"
if [ $? != 0 ] ; then echo "Failed to get token from SSO" ; exit 1 ; fi
echo "Trying to Assume Role $1 using provided JWT token.."
echo -e "\n"
IDM_ASSUME_ROLE_CREDS=$(aws sts assume-role-with-web-identity --role-arn "arn:aws:iam:::role/$1" --role-session-name testbr --endpoint=https://cephproxy1.stg.local:8443 --web-identity-token="$KC_ACCESS_TOKEN")
echo "Export AWS ENV variables to use the AWS CLI with the STS creds.. "
export AWS_ACCESS_KEY_ID=$(echo $IDM_ASSUME_ROLE_CREDS | jq -r .Credentials.AccessKeyId)
export AWS_SECRET_ACCESS_KEY=$(echo $IDM_ASSUME_ROLE_CREDS | jq -r .Credentials.SecretAccessKey)
export AWS_SESSION_TOKEN=$(echo $IDM_ASSUME_ROLE_CREDS | jq -r .Credentials.SessionToken)
export AWS_ENDPOINT_URL=https://cephproxy1.stg.local:8443
echo "Also providing the keys to use in the AWS credentials file.."
echo -e "\n"
echo "AWS_ACCESS_KEY_ID: $AWS_ACCESS_KEY_ID"
echo "AWS_SECRET_ACCESS_KEY: $AWS_SECRET_ACCESS_KEY"
echo "AWS_SESSION_TOKEN: $AWS_SESSION_TOKEN"
EOF
# . ./assume_role.sh
Getting the JWT from SSO...
Trying to Assume Role rgwadmins using provided JWT token..
Export AWS ENV variables to use the AWS CLI with the STS creds..
Also, providing the keys to use in the AWS credentials file..
AWS_ACCESS_KEY_ID: KfnALjZDshGBCAbKxVU
AWS_SECRET_ACCESS_KEY: 024CJPUDJ7REHRZSSLB581KC7B0TVTCAAFRWSIP
AWS_SESSION_TOKEN: b52ruURQaSrb3iKk1ZolkR471mWsYC7dGEkJj3FxiWTj39TB5tpEA0Zk2EFKdqzjO5#####
After executing the previous script, we successfully exported the required S3 access and secret keys into our environment session. Consequently, we can now access the IBM Ceph Object resources using our STS temporary token credentials:
# aws s3 ls
# aws s3 mb s3://bucket1
# aws s3 ls
2023-11-23 19:48:21 bucket1
- This article aims to help Ceph administrators set up Single Sign-On (SSO) between IBM Security Verify (ISV) and Ceph easily and straightforwardly.
-
Using the ISV admin console capability, Ceph administrators can manage users with specific roles efficiently.
-
Ceph administrators can implement a powerful and granular authorization model based on JWT attributes and IAM roles.
-
End-users can effortlessly authenticate in Single Sign-On and access Ceph Object Storage.
-
Furthermore, in the future, Ceph administrators will easily introduce Multi-Factor Authentication (MFA) for end users to ensure secure access to Ceph Object Storage.
-
Vivek Jain - vivjain2@in.ibm.com
-
Deepak Thorat – dthorat1@in.ibm.com
-
Daniel Alexander Parkes - dparkes@ibm.com