acl
The acl
plugin allows or denies request access to upstream resources by verifying whether the user initiating the request is in the access control lists.
Examples
The examples below demonstrate how you can use the acl
plugin for different scenarios.
Control Access by Examining Consumer Labels
The following example demonstrates how to control consumer access based on consumer labels, upon a successful authentication.
Create two consumers, john
and jane
, each with their own labels for organizations and projects:
curl "http://127.0.0.1:9180/apisix/admin/consumers" -X PUT \
-H "X-API-KEY: ${ADMIN_API_KEY}" \
-d '{
"username": "john",
"labels": {
"org": "[\"opensource\",\"apache\"]",
"project": "[\"tomcat\",\"web-server\",\"http,server\"]"
}
}'
curl "http://127.0.0.1:9180/apisix/admin/consumers" -X PUT \
-H "X-API-KEY: ${ADMIN_API_KEY}" \
-d '{
"username": "jane",
"labels": {
"org": "apache",
"project": "gateway,apisix,web-server"
}
}'
Create key-auth
credentials for john
and jane
:
curl "http://127.0.0.1:9180/apisix/admin/consumers/john/credentials" -X PUT \
-H "X-API-KEY: ${ADMIN_API_KEY}" \
-d '{
"id": "cred-john-key-auth",
"plugins": {
"key-auth": {
"key": "john-key"
}
}
}'
curl "http://127.0.0.1:9180/apisix/admin/consumers/jane/credentials" -X PUT \
-H "X-API-KEY: ${ADMIN_API_KEY}" \
-d '{
"id": "cred-jane-key-auth",
"plugins": {
"key-auth": {
"key": "jane-key"
}
}
}'
Consumer labels can be configured with either of the two approaches:
- comma-separated string value, such as
{"project": "gateway,apisix"}
- character escaped string array,such as
{"project": "[\"gateway\",\"apisix\"]"}
Create a route with key-auth
enabled, and configure the acl
plugin:
curl "http://127.0.0.1:9180/apisix/admin/routes" -X PUT \
-H "X-API-KEY: ${ADMIN_API_KEY}" \
-d '{
"id": "acl-route",
"uri": "/get",
"plugins": {
"key-auth": {},
"acl": {
"allow_labels": {
"org": ["opensource"]
}
}
},
"upstream": {
"type": "roundrobin",
"nodes": {
"httpbin.org:80": 1
}
}
}'
❶ allow only consumer with org
label value opensource
to access the upstream resource.
Send a request to the route as consumer jane
:
curl -i "http://127.0.0.1:9080/get" -H 'apikey: jane-key'
You should see an HTTP/1.1 403 Forbidden
response, as consumer jane
was not configured with the required label to access the route.
Send a request to the route as consumer john
:
curl -i "http://127.0.0.1:9080/get" -H 'apikey: john-key'
You should see an HTTP/1.1 200 OK
response, as consumer john
was configured with the required label to access the route.
Control Access by Examining User Information from External Identity Provider
The following example demonstrates how to control user access based on user labels, upon a successful authentication with an external identity provider. Specifically, the example uses Keycloak and user groups as labels.
Follow the steps in set up SSO with Keycloak how-to guide to create a realm, a client, and a user.
Go to Groups and create two new groups, apisix
and opensource
:
To add the user to group memberships, click into the user and go to the Groups tab. Select each group in turn and click join:
To include the group membership when user info is requested from Keycloak, go to the client and go to the Mappers tab. Create a new mapper:
Fill in the name for the protocol mapper, select Group Membership as the mapper type, use groups
as the token claim name, and click Save:
To verify if the attribute will be visible when requesting user info, first obtain an access token from Keycloak:
OIDC_USER=quickstart-user
OIDC_PASSWORD=quickstart-user-pass
OIDC_CLIENT_ID=apisix-quickstart-client
OIDC_CLIENT_SECRET=bi9NFscFT4k0ljaRzQWlJWthrlygUn3x # replace with your client secret
curl "http://$KEYCLOAK_IP:8080/realms/quickstart-realm/protocol/openid-connect/token" -X POST \
-d 'grant_type=password' \
-d 'client_id='$OIDC_CLIENT_ID'' \
-d 'client_secret='$OIDC_CLIENT_SECRET'' \
-d 'username='$OIDC_USER'' \
-d 'password='$OIDC_PASSWORD''
Save the access token to an environment variable called ACCESS_TOKEN
and send a request to the Keycloak user info endpoint with the token:
curl "http://$KEYCLOAK_IP:8080/realms/quickstart-realm/protocol/openid-connect/userinfo" -H "Authorization: Bearer $ACCESS_TOKEN"
You should see a response similar to the following:
{
"sub":"4310e97c-d4c3-479b-bbbd-8c66120e6cee",
"email_verified":false,
"groups":["/apisix", "/opensource"],
"preferred_username":"quickstart-user"
}
Suppose you would like to only allow users with /apisix
value in the groups
attribute to access upstream resources.
Create a route with openid-connect
and acl
plugins as such:
KEYCLOAK_IP=192.168.1.81 # replace with your host IP
OIDC_DISCOVERY=http://${KEYCLOAK_IP}:8080/realms/quickstart-realm/.well-known/openid-configuration
curl "http://127.0.0.1:9180/apisix/admin/routes" -X PUT \
-H "X-API-KEY: ${ADMIN_API_KEY}" \
-d '{
"id": "acl-route",
"uri":"/anything/*",
"plugins": {
"openid-connect": {
"bearer_only": true,
"client_id": "'"$OIDC_CLIENT_ID"'",
"client_secret": "'"$OIDC_CLIENT_SECRET"'",
"discovery": "'"$OIDC_DISCOVERY"'",
"scope": "openid profile",
"redirect_uri": "http://localhost:9080/anything/callback"
},
"acl": {
"external_user_label_field": "groups",
"allow_labels": {
"groups": ["/apisix"]
}
}
},
"upstream":{
"type":"roundrobin",
"nodes":{
"httpbin.org:80":1
}
}
}'
❶ configure external_user_label_field
to the name mapped to the group.
❷ allow only users with groups
attribute value /apisix
to access the upstream resource.
Send a request to the route with a valid access token:
curl -i "http://127.0.0.1:9080/anything/test" -H "Authorization: Bearer $ACCESS_TOKEN"
You should see an HTTP/1.1 200 OK
response, which verifies that the API call was authorized and allowed to access upstream resources.
To see how the acl
plugin restricts access, update the plugin to require an attribute that the user info does not include:
curl "http://127.0.0.1:9180/apisix/admin/routes/acl-route" -X PATCH \
-H "X-API-KEY: ${ADMIN_API_KEY}" \
-d '{
"plugins": {
"acl": {
"allow_labels": {
"groups": ["foobar"]
}
}
}
}'
Send another request to the same route with a valid access token:
curl -i "http://127.0.0.1:9080/anything/test" -H "Authorization: Bearer $ACCESS_TOKEN"
You should see an HTTP/1.1 403 Forbidden
response, which shows the access is restricted.
Control Access by Examining Nested User Information from External Identity Provider
The following example demonstrates how you can configure the acl
plugin to fetch labels from a nested JSON structure and control access by examining whether these labels are whitelisted.
If you are using Keycloak as the identity provider, the steps of configuration are similar to the last example, except that when you create the Group Membership, configure the Token Claim Name as a fully qualified name for group membership to be displayed in the nested JSON object:
The earlier versions of Keycloak have a bug that caused Keycloak to return the dots in Token Claim Name as string literals when requesting user information, instead of returning a nested JSON structure. Please use Keycloak >= 23.0.0
.
Next, request user access token and user information from /userinfo
endpoint, similar to the last example. You should see Keycloak returning user information similar to the following:
{
"sub": "f62086ef-29e1-4401-8609-451a2d724bd7",
"email_verified": false,
"acl_labels": {
"nested": {
"groups": [
"/apisix",
"/opensource"
]
}
},
"preferred_username": "quickstart-user"
}
In APISIX, create a route with openid-connect
to authenticate with Keycloak and configure acl
plugins as such:
KEYCLOAK_IP=192.168.1.81 # replace with your host IP
OIDC_DISCOVERY=http://${KEYCLOAK_IP}:8080/realms/quickstart-realm/.well-known/openid-configuration
curl "http://127.0.0.1:9180/apisix/admin/routes" -X PUT \
-H "X-API-KEY: ${ADMIN_API_KEY}" \
-d '{
"id": "acl-route",
"uri":"/anything/*",
"plugins": {
"openid-connect": {
"bearer_only": true,
"client_id": "'"$OIDC_CLIENT_ID"'",
"client_secret": "'"$OIDC_CLIENT_SECRET"'",
"discovery": "'"$OIDC_DISCOVERY"'",
"scope": "openid profile",
"redirect_uri": "http://localhost:9080/anything/callback"
},
"acl": {
"external_user_label_field": "$..groups",
"external_user_label_field_key": "groups",
"external_user_label_field_parser": "json",
"allow_labels": {
"groups": ["/apisix"]
}
}
},
"upstream":{
"type":"roundrobin",
"nodes":{
"httpbin.org:80":1
}
}
}'
❶ configure user label field with a valid JSONPath to the nested field with labels.
❷ set the field key to the name of the nested field.
❸ set the parser to json
to be consistent with the user information data structure.
❹ allow only users with groups
attribute value /apisix
to access the upstream resource.
To verify the ACL, send a request to the route with a valid access token:
curl -i "http://127.0.0.1:9080/anything/test" -H "Authorization: Bearer $ACCESS_TOKEN"
You should see an HTTP/1.1 200 OK
response, which suggests that the API call was authorized and allowed to access upstream resources.
To see how the acl
plugin restricts access, update the plugin to require an attribute that the user info does not include:
curl "http://127.0.0.1:9180/apisix/admin/routes/acl-route" -X PATCH \
-H "X-API-KEY: ${ADMIN_API_KEY}" \
-d '{
"plugins": {
"acl": {
"allow_labels": {
"groups": ["foobar"]
}
}
}
}'
Send another request to the same route with a valid access token:
curl -i "http://127.0.0.1:9080/anything/test" -H "Authorization: Bearer $ACCESS_TOKEN"
You should see an HTTP/1.1 403 Forbidden
response, which shows the access is restricted.