Setup Ingredients
- MinIO
- OIDC
- Keycloak
- Python
- Docker
- curl
- jq
Start a dockerized Keycloack container following the steps outlined in the Keycloak, OIDC, Flask Github repository
Start with followoing the steps until you have Keycloak up and running with realm acme and you can successfully login with users alice and bob.
Now we will need the following Keycloak additions
- Client
minio - Groups
acme_userandacme_super_user
- Log into your Keycloak UI using user
admin - Add a client
minio - Switch to tab "Roles" and add roles
minioUserandminioSuperuser - Switch to tab "Mappers" and add mapper
minio_mapperwith mapper typeUser Client Role, client idminio, token claim namepolicy
The role names minioUser and minioSuperuser will be used on the minio side to link to the S3 bucket access policies using as simple string match.
The semantics of these roles will be defined on the minio side.
For keycloak these are just simple strings without any semantic meaning.
As a result you should end up with the following setup
Clients + 'minio' + Credentials + Client Authenticator 'Client Id and Secret'
| + Secret 'c12 ... 2a2'
+ Roles ------+ 'minioSuperuser'
| + 'minioUser'
+ Mappers ----+ 'minio_mapper' + Protocol 'openid-connect'
+ Name 'minio_mapper'
+ Mapper Type 'User Client Role'
+ Client ID 'minio'
+ Token Claim Name 'policy'The token claim name policy will be used by Keycloak to insert a custom claim attribute into the JWT bearer token that will hold role names minioUser and/or minioSuperuser as values.
- Log into your Keycloak UI using user
admin - Add group
acme_user - Switch to tab "Role Mappings" and in drop down box "Client Roles" select entry
minio - Add available role
minioUserso it in box "Assigned Roles" - Add group
acme_super_user - Switch to tab "Role Mappings" and in drop down box "Client Roles" select entry
minio - Add available role
minioSuperuserso it in box "Assigned Roles"
As a result you should end up with the following setup
Groups + 'acme_super_user' + Role Mappings + Client Roles + 'minio'
| + Assigned Roles + 'minioSuperuser'
+ 'acme_user' ------+ Role Mappings + Client Roles + 'minio'
+ Assigned Roles + 'minioUser'- Log into your Keycloak UI using user
admin - Under "Users" select user
alice - Switch to tab "Groups"
- From the available groups select
acme_super_userand click "Join" - Under "Users" select user
bob - Switch to tab "Groups"
- From the available groups select
acme_userand click "Join"
Users + 'alice' + Groups + 'acme_super_user'
+ 'bob' + Groups + 'acme_user'To verify the setup we use the Keycloak REST api to create a JWT bearer access token for user alice using the curl command shown below.
The client secret value can be obtained from the Keycloak UI under client minio in tab "Credentials"
CLIENT_SECRET=c12506a9-d2a3-4718-8277-4abab500b2a2
PASSWORD_ALICE=password_alice
curl -s \
-d "client_id=minio" -d "client_secret=$CLIENT_SECRET" \
-d "username=alice" -d "password=$PASSWORD_ALICE" \
-d "grant_type=password" \
"http://localhost:8080/auth/realms/acme/protocol/openid-connect/token" | jq -r '.access_token'That should print a longish encoded bearer token similar to
eyJhbGciOi ... xKoACheck the content using some JWT browser. WARNING: Never use some online tool with productive token content!
The plain text token content should the look similar to the example below.
{
"exp": 1631377776,
"iat": 1631377476,
"jti": "c1ec05e0-05be-4234-9052-08fd03cd13c0",
"iss": "http://localhost:8080/auth/realms/acme",
...
"scope": "email profile",
"sid": "1592e5e9-6910-476f-a358-811b96069b8d",
"email_verified": false,
"name": "Alice Anderson",
"preferred_username": "alice",
"given_name": "Alice",
"family_name": "Anderson",
"email": "alice@acme.com",
"policy": [
"minioSuperuser"
]
}The important part is that attribute preferred_username matches with the intended user (alice in our case) and that attribute policy holds value(s) that match with the client roles entered for client minio (minioSuperuser in our case).
Access control with MinIO works via canned policies. Out-of-the-box MinIO has canned policies like
readonlywriteonlyreadwrite
These policies apply to all buckets globally and are not suitable for more fine grained access policies.
The minio server setup is included in the docker run command shown below.
docker run \
-e 'MINIO_ROOT_USER=accesskey' \
-e 'MINIO_ROOT_PASSWORD=secretkey' \
-e 'MINIO_IDENTITY_OPENID_CONFIG_URL=http://192.168.105.112:8080/auth/realms/acme/.well-known/openid-configuration' \
-e 'MINIO_IDENTITY_OPENID_CLIENT_ID=minio' \
-e 'MINIO_IDENTITY_OPENID_CLIENT_SECRET=c12506a9-d2a3-4718-8277-4abab500b2a' \
-e 'MINIO_IDENTITY_OPENID_CLAIM_NAME=policy' \
-v /Users/docker/data/minio_kcl:/data \
-p 9002:9000 \
-d \
minio/minio server /dataThe MinIO client is needed to manage/upload custom policies for the intended setup.
Download and run the MinIO client mc.
docker pull minio/mc
docker run -it --rm --entrypoint=/bin/bash minio/mcInside mc container add the local minio server via name minio.
Use the IP address and port that you used to deploy the MinIO server.
In case your MinIO server is running on localhost you still need the IP address of the localhost.
You may be obtain the IP address using ifconfig | grep inet or similar tools.
mc config host add minio http://192.168.105.112:9002 accesskey secretkeyNow use mc to interact with the minio instance.
Let's start with creating two buckets bucket-a and bucket-b.
mc mb minio/bucket-a
mc mb minio/bucket-b
mc ls minioNext add some inital content to the buckets. At the same time we can check the up- and download funcionality.
echo "hello world!" > hello.txt
mc cp hello.txt minio/bucket-a/hello.a.txt
mc cp hello.txt minio/bucket-b/hello.b.txt
mc ls minio/bucket-a
mc cp minio/bucket-a/hello.a.txt hello.download.txt
cksum hello.*txtFine graned MinIO access control can be achieved via canned policies. See S3 access policies, and or AWS S3 examples for some background.
For a concrete example look at the full definition for the MinIO out-of-the-box canned policy readwrite.
mc admin policy info minio readwriteThis prodces the following output
{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Action":["s3:*"],"Resource":["arn:aws:s3:::*"]}]}As shown above the policy readwrite allows all S3 actions available on all buckets available.
- Users with policy
minio_superuser.jsonhave more or less unrestricted access to bucketsbucket-aandbucket-bbut not on any other buckets. - Users with policy
minio_user.jsonhave read-write access to bucketbucket-aand only read access tobucket-bbut not on any other buckets.
cat > minio_superuser.json << EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Action": [
"s3:ListBucket",
"s3:GetObject",
"s3:PutObject",
"s3:DeleteObject"
],
"Effect": "Allow",
"Resource": [
"arn:aws:s3:::bucket-a/*",
"arn:aws:s3:::bucket-b/*"
],
"Sid": ""
}
]
}
EOF
cat > minio_user.json << EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Action": [
"s3:ListBucket",
"s3:GetObject",
"s3:PutObject"
],
"Effect": "Allow",
"Resource": [
"arn:aws:s3:::bucket-a/*"
],
"Sid": ""
}
]
}
EOFAdd canned policies.
mc admin policy add minio minioSuperuser minio_superuser.json
mc admin policy add minio minioUser minio_user.jsonThe names minioSuperuser and minioUser can now be referenced
via JWT claim attribute policy.