This tutorial introduces an additional security generic enabler - Authzforce and adds fine grained control to the security rules generated by Keyrock. Access to the entities created in the previous tutorial is now configured and controlled using an XACML access control policy - this creates a flexible ruleset which can be uploaded and reinterpreted on the fly so complex business rules can be created and changed according to current circumstances.
The tutorial discusses code showing how to integrate Authzforce within a web application and demonstrates examples of Authzforce XACML Server-PDP interactions. cUrl commands are used to show the interactions between generic enablers. Postman documentation is available.
- このチュートリアルは日本語でもご覧いただけます。
Details
"Say: Come, I will rehearse what Allah hath prohibited you from:
- Join not anything as equal with Him
- Be good to your parents
- Kill not your children on a plea of want - We provide sustenance for you and for them
- Come not nigh to shameful deeds. Whether open or secret
- Take not life, which Allah hath made sacred, except by way of justice and law
thus doth He command you, that ye may learn wisdom."
— Quran 6.151, Sūrat al-Anʻām
Previous tutorials have introduced a simple access control system based on authentication (level 1) or basic authorization access to resources based on a role (level 2). These policies are easy to create, but the rules within them are very black and white, rules cannot rely on one another, have exception clauses or offer access based on time limits or attribute values. There is also no mechanism to resolve different rules in the case of conflict.
To satisfy a complex access control scenario, an additional arbitration microservice is required, which is able to come to a judgement on each Permit/Deny policy decision by reading and interpreting the full set of access control rules, and based their judgement on the evidence provided by the requesting service.
FIWARE Authzforce is a service which is able to provide such an interpretive Policy Decision Point (PDP). It is an advanced access control Generic Enabler which is able to interpret rules supplied using the XACML standard. Rulesets can be amended and uploaded at any time providing a flexible method to maintain security policies which can change according to business need. Furthermore the language used to describe the access policy is designed to be highly extensible and cover any access control scenario.
eXtensible Access Control Markup Language (XACML) is a vendor neutral declarative access control policy language. It was created to promote common access control terminology and interoperability.1 The architectural naming conventions for elements such as Policy Execution Point (PEP) and Policy Decision Point (PDP) come from the XACML specifications.
XACML policies are split into a hierarchy of three levels - <PolicySet>
, <Policy>
and <Rule>
, the <PolicySet>
is
a collection of <Policy>
elements each of which contain one or more <Rule>
elements.
Each <Rule>
within a <Policy>
is evaluated as to whether it should grant access to a resource - the overall
<Policy>
result is defined by the overall result of all <Rule>
elements processed in turn. Separate <Policy>
results are then evaluated against each other using combining algorithms define which <Policy>
wins in case of
conflict.
A <Rule>
element consists of a <Target>
and a <Condition>
. This is an example <Rule>
, it states access will be
granted (Effect="Permit"
) when a POST request is sent to the /bell/ring
endpoint, provided that the subject:role
has been provided and that the role=security-role-0000-0000-000000000000
:
<Rule RuleId="alrmbell-ring-0000-0000-000000000000" Effect="Permit">
<Description>Ring Alarm Bell</Description>
<Target>
<AnyOf>
<AllOf>
<Match MatchId="urn:oasis:names:tc:xacml:1.0:function:string-equal">
<AttributeValue DataType="http://www.w3.org/2001/XMLSchema#string">/bell/ring</AttributeValue>
<AttributeDesignator
Category="urn:oasis:names:tc:xacml:3.0:attribute-category:resource"
AttributeId="urn:thales:xacml:2.0:resource:sub-resource-id"
DataType="http://www.w3.org/2001/XMLSchema#string"
MustBePresent="true"
/>
</Match>
</AllOf>
</AnyOf>
<AnyOf>
<AllOf>
<Match MatchId="urn:oasis:names:tc:xacml:1.0:function:string-equal">
<AttributeValue DataType="http://www.w3.org/2001/XMLSchema#string">POST</AttributeValue>
<AttributeDesignator
Category="urn:oasis:names:tc:xacml:3.0:attribute-category:action"
AttributeId="urn:oasis:names:tc:xacml:1.0:action:action-id"
DataType="http://www.w3.org/2001/XMLSchema#string"
MustBePresent="true"
/>
</Match>
</AllOf>
</AnyOf>
</Target>
<Condition>
<Apply FunctionId="urn:oasis:names:tc:xacml:3.0:function:any-of">
<Function FunctionId="urn:oasis:names:tc:xacml:1.0:function:string-equal" />
<AttributeValue
DataType="http://www.w3.org/2001/XMLSchema#string"
>security-role-0000-0000-000000000000</AttributeValue>
<AttributeDesignator
Category="urn:oasis:names:tc:xacml:1.0:subject-category:access-subject"
AttributeId="urn:oasis:names:tc:xacml:2.0:subject:role"
DataType="http://www.w3.org/2001/XMLSchema#string"
MustBePresent="false"
/>
</Apply>
</Condition>
</Rule>
This is a very verbose method for creating a simple Verb-Resource access rule, but unlike simple Verb-Resource rules,
with XACML, other more complex comparisons can be made, for example checking that time is before a certain hour of day,
or checking that a URL starts with or contains a certain string. Conditions can be specified down to the attribute level
or combined to make complex calculations, for example - an XACML <Rule>
could be created to apply the following
policy:
A store manager is able to amend Product prices only the first of the month, and can only alter prices of products she or her immediate superior has created in the first place
Such a <Rule>
would require that the <Condition>
includes separate clauses/clarifications for the following:
- What is the User's role? (e.g.
manager
) - What action is being invoked? (e.g. PATCH or PUT)
- Which resource is being protected URL string? (e.g.
/v2/entities
) - What other information must be present in the body of the request? (e.g. Entity
type
must equalProduct
) - When is the resource being requested? (e.g. the current date)
- What other additional information must be retrieved from elsewhere prior to making the request
- Who created the entity? Is it me or my manager?
As you can see these rules can quickly become very complex. For this initial introduction to XACML, the basic rule set used will be kept as simple as possible to avoid unnecessary confusion, suffice it to say that an access policy based on XACML can be expanded to fit the security needs of any complex system.
Further information can be found within the XACML standard and additional resources can be found on the web.
To keep things simple all components will be run using Docker. Docker is a container technology which allows to different components isolated into their respective environments.
- To install Docker on Windows follow the instructions here
- To install Docker on Mac follow the instructions here
- To install Docker on Linux follow the instructions here
Docker Compose is a tool for defining and running multi-container Docker applications. A YAML file is used configure the required services for the application. This means all container services can be brought up in a single command. Docker Compose is installed by default as part of Docker for Windows and Docker for Mac, however Linux users will need to follow the instructions found here
We will start up our services using a simple bash script. Windows users should download cygwin to provide a command-line functionality similar to a Linux distribution on Windows.
This application adds level 3 Advanced Authorization security into the existing Stock Management and Sensors-based application created in previous tutorials and secures access to the context broker behind a PEP Proxy. It will make use of five FIWARE components - the Orion Context Broker,the IoT Agent for UltraLight 2.0, the Keyrock Identity Manager, the Wilma PEP Proxy and the Authzforce XACML Server. All access control decisions will be delegated to Authzforce which will read the ruleset from a previously uploaded policy domain.
Both the Orion Context Broker and the IoT Agent rely on open source MongoDB technology to keep persistence of the information they hold. We will also be using the dummy IoT devices created in the previous tutorial. Keyrock uses its own MySQL database.
Therefore the overall architecture will consist of the following elements:
- The FIWARE Orion Context Broker which will receive requests using NGSI-v2
- The FIWARE IoT Agent for UltraLight 2.0 which will receive southbound requests using NGSI-v2 and convert them to UltraLight 2.0 commands for the devices
- FIWARE Keyrock offer a complement Identity Management System
including:
- An OAuth2 authentication system for Applications and Users
- A site graphical frontend for Identity Management Administration
- An equivalent REST API for Identity Management via HTTP requests
- FIWARE Authzforce is a XACML Server providing an interpretive Policy Decision Point (PDP) protecting access to resources such as Orion and the tutorial application.
- FIWARE Wilma is a PEP Proxy securing access to the Orion microservices, it delegates the passing of authorization decisions to Authzforce PDP
- The underlying MongoDB database :
- Used by the Orion Context Broker to hold context data information such as data entities, subscriptions and registrations
- Used by the IoT Agent to hold device information such as device URLs and Keys
- A MySQL database :
- Used to persist user identities, applications, roles and permissions
- The Stock Management Frontend does the following:
- Displays store information
- Shows which products can be bought at each store
- Allows users to "buy" products and reduce the stock count.
- Allows authorized users into restricted areas, it also delegates authorization decisions to the Authzforce PDP
- A webserver acting as set of dummy IoT devices using the UltraLight 2.0 protocol running over HTTP - access to certain resources is restricted.
Since all interactions between the elements are initiated by HTTP requests, the entities can be containerized and run from exposed ports.
The specific architecture of each section of the tutorial is discussed below.
keyrock:
image: quay.io/fiware/idm
container_name: fiware-keyrock
hostname: keyrock
networks:
default:
ipv4_address: 172.18.1.5
depends_on:
- mysql-db
- authzforce
ports:
- '3005:3005'
environment:
- DEBUG=idm:*
- DATABASE_HOST=mysql-db
- IDM_DB_PASS_FILE=/run/secrets/my_secret_data
- IDM_DB_USER=root
- IDM_HOST=http://localhost:3005
- IDM_PORT=3005
- IDM_ADMIN_USER=alice
- IDM_ADMIN_EMAIL=alice-the-admin@test.com
- IDM_ADMIN_PASS=test
- IDM_PDP_LEVEL=advanced
- IDM_AUTHZFORCE_ENABLED=true
- IDM_AUTHZFORCE_HOST=authzforce
- IDM_AUTHZFORCE_PORT=8080
secrets:
- my_secret_data
The keyrock
container is a web application server listening on a single port:
- Port
3005
has been exposed for HTTP traffic so we can display the web page and interact with the REST API.
The keyrock
container is connecting to Authzforce and is driven by environment variables as shown:
Key | Value | Description |
---|---|---|
IDM_PDP_LEVEL | advanced |
Flag indicating that Keyrock should delegate PDP decisions to Authzforce |
IDM_AUTHZFORCE_ENABLED | true |
Flag indicating that Authzforce is available |
IDM_AUTHZFORCE_HOST | authzforce |
This is URL where the Authzforce is found |
IDM_AUTHZFORCE_PORT | 8080 |
Port that Authzforce is listening on |
The other keyrock
container configuration values described in the YAML file have been described in previous tutorials
orion-proxy:
image: quay.io/fiware/pep-proxy
container_name: fiware-orion-proxy
hostname: orion-proxy
networks:
default:
ipv4_address: 172.18.1.10
depends_on:
- keyrock
- authzforce
ports:
- '1027:1027'
expose:
- '1027'
environment:
- PEP_PROXY_APP_HOST=orion
- PEP_PROXY_APP_PORT=1026
- PEP_PROXY_PORT=1027
- PEP_PROXY_IDM_HOST=keyrock
- PEP_PROXY_HTTPS_ENABLED=false
- PEP_PROXY_IDM_SSL_ENABLED=false
- PEP_PROXY_IDM_PORT=3005
- PEP_PROXY_APP_ID=tutorial-dckr-site-0000-xpresswebapp
- PEP_PROXY_USERNAME=pep_proxy_00000000-0000-0000-0000-000000000000
- PEP_PASSWORD=test
- PEP_PROXY_PDP=authzforce
- PEP_PROXY_AUTH_ENABLED=true
- PEP_PROXY_MAGIC_KEY=1234
- PEP_PROXY_AZF_PROTOCOL=http
- PEP_PROXY_AZF_HOST=authzforce
- PEP_PROXY_AZF_PORT=8080
The orion-proxy
container is an instance of FIWARE Wilma listening on port 1027
, it is configured to forward
traffic to orion
on port 1026
, which is the default port that the Orion Context Broker is listening to for NGSI
Requests.
The orion-proxy
container is delegating PDP decisions to Authzforce and is driven by environment variables as
shown:
Key | Value | Description |
---|---|---|
PEP_PROXY_PDP | authzforce |
Flag ensuring that the PEP Proxy uses Authzforce as a PDP |
PEP_PROXY_AZF_PROTOCOL | http |
Protocol that Authzforce uses |
PEP_PROXY_AZF_HOST | authzforce |
This is URL where the Authzforce is found |
PEP_PROXY_AZF_PORT | 8080 |
Port that Authzforce is listening on |
The other orion-proxy
container configuration values described in the YAML file have been described in previous
tutorials
authzforce:
image: fiware/authzforce-ce-server
hostname: authzforce
container_name: fiware-authzforce
networks:
default:
ipv4_address: 172.18.1.12
ports:
- '8080:8080'
volumes:
- ./authzforce/domains:/opt/authzforce-ce-server/data/domains
The authzforce
container is listening on port 8080
, where it receives requests to make PDP decisions. A volume has
been exposed to upload a pre-configured domain so that a set of XACML access control policies has already been supplied.
tutorial:
image: quay.io/fiware/tutorials.context-provider
hostname: iot-sensors
container_name: fiware-tutorial
networks:
default:
ipv4_address: 172.18.1.7
expose:
- '3000'
- '3001'
ports:
- '3000:3000'
- '3001:3001'
environment:
- 'DEBUG=tutorial:*'
- 'WEB_APP_PORT=3000'
- 'KEYROCK_URL=http://localhost'
- 'KEYROCK_IP_ADDRESS=http://172.18.1.5'
- 'KEYROCK_PORT=3005'
- 'KEYROCK_CLIENT_ID=tutorial-dckr-site-0000-xpresswebapp'
- 'KEYROCK_CLIENT_SECRET=tutorial-dckr-site-0000-clientsecret'
- 'CALLBACK_URL=http://localhost:3000/login'
- 'AUTHZFORCE_ENABLED=true'
- 'AUTHZFORCE_URL=http://authzforce'
- 'AUTHZFORCE_PORT=8080'
The tutorial
container is listening on two ports:
- Port
3000
is exposed so we can see the web page displaying the Dummy IoT devices. - Port
3001
is exposed purely for tutorial access - so that cUrl or Postman can make UltraLight commands without being part of the same network.
The tutorial
container is now secured by Authzforce, and is driven by environment variables as shown:
Key | Value | Description |
---|---|---|
AUTHZFORCE_ENABLED | true |
Flag to enable use of the XACML PDP |
AUTHZFORCE_URL | http://authzforce |
This is URL where the Authzforce is found |
AUTHZFORCE_PORT | 8080 |
Port that Authzforce is listening on |
The other tutorial
container configuration values described in the YAML file have been described in previous tutorials
To start the installation, do the following:
git clone https://github.com/FIWARE/tutorials.XACML-Access-Rules.git
cd tutorials.XACML-Access-Rules
git checkout NGSI-v2
./services create
Note
The initial creation of Docker images can take up to three minutes
Thereafter, all services can be initialized from the command-line by running the services Bash script provided within the repository:
./services start
Note
If you want to clean up and start over again you can do so with the following command:
./services stop
The following people at test.com
legitimately have accounts within the Application
- Alice, she will be the Administrator of the Keyrock Application
- Bob, the Regional Manager of the supermarket chain - he has several store managers under him:
- Manager1
- Manager2
- Charlie, the Head of Security of the supermarket chain - he has several store detectives under him:
- Detective1
- Detective2
The following people at example.com
have signed up for accounts, but have no reason to be granted access
- Eve - Eve the Eavesdropper
- Mallory - Mallory the malicious attacker
- Rob - Rob the Robber
For more details (Click to expand)
Name | Password | |
---|---|---|
alice | alice-the-admin@test.com |
test |
bob | bob-the-manager@test.com |
test |
charlie | charlie-security@test.com |
test |
manager1 | manager1@test.com |
test |
manager2 | manager2@test.com |
test |
detective1 | detective1@test.com |
test |
detective2 | detective2@test.com |
test |
Name | Password | |
---|---|---|
eve | eve@example.com |
test |
mallory | mallory@example.com |
test |
rob | rob@example.com |
test |
Once Authzforce is running, you can check the status by making an HTTP request to the exposed administration port
(usually 8080
). If the response is blank, this is usually because Authzforce is not running or is listening on
another port.
curl -X GET \
http://localhost:8080/authzforce-ce/version \
-H 'Accept: application/xml'
The response returns information about the version of Authzforce.
<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
<productMetadata
xmlns="http://authzforce.github.io/rest-api-model/xmlns/authz/5"
xmlns:ns2="http://www.w3.org/2005/Atom"
xmlns:ns3="urn:oasis:names:tc:xacml:3.0:core:schema:wd-17"
xmlns:ns4="http://authzforce.github.io/core/xmlns/pdp/6.0"
xmlns:ns5="http://authzforce.github.io/pap-dao-flat-file/xmlns/properties/3.6"
name="AuthzForce CE Server"
version="8.0.1"
release_date="2017-12-05"
uptime="P0Y0M0DT0H8M47.642S"
doc="https://authzforce.github.io/fiware/authorization-pdp-api-spec/5.2/"
/>
Authzforce is a Policy Decision Point (PDP) Generic Enabler, which makes authorization decisions based on
<PolicySet>
information written in XACML. This
example starts with a running XACML server containing an existing set of rules. An XACML server should offer an API to
administrate policies and invoke access control policy decisions. This tutorial is mainly concerned with the decision
making side - the creation and administration of access control policies will be dealt with in a subsequent tutorial.
A single XACML server can be used to administrate access control policies for multiple applications. Authzforce is
implicitly multi-tenant, in that it allows separate organizations to work on their policies in isolation from one
another. This is done by separating the security policies for each application into a separate domain where they can
access their own <PolicySets>
. A domain holds metadata about the secured application along with versions of the
policies themselves (effectively a series of files which can be accessed by a file server). The domain management API
can be used to query Authzforce about the domains served and policies held.
To request domain information from Authzforce, make a request to the /authzforce-ce/domains
endpoint.
curl -X GET \
http://localhost:8080/authzforce-ce/domains
The response lists the domains which are available in Authzforce. This corresponds to the directory structure uploaded to Authzforce on start-up.
<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
<resources
xmlns="http://authzforce.github.io/rest-api-model/xmlns/authz/5"
xmlns:ns2="http://www.w3.org/2005/Atom"
xmlns:ns3="urn:oasis:names:tc:xacml:3.0:core:schema:wd-17"
xmlns:ns4="http://authzforce.github.io/core/xmlns/pdp/6.0"
xmlns:ns5="http://authzforce.github.io/pap-dao-flat-file/xmlns/properties/3.6"
>
<ns2:link rel="item" href="gQqnLOnIEeiBFQJCrBIBDA" title="gQqnLOnIEeiBFQJCrBIBDA" />
</resources>
To read information about a domain, and to explore further, make a request to the authzforce-ce/domains/{{domain-id}}
endpoint. The following request obtains information about the gQqnLOnIEeiBFQJCrBIBDA
domain, which has been generated
using a random key by an external Policy Administration Point in this case Keyrock has been used as the PAP, and
pre-generated the rule sets.
curl -X GET \
http://localhost:8080/authzforce-ce/domains/gQqnLOnIEeiBFQJCrBIBDA
The response lists more information about the domain, including the ID used within Keyrock
(tutorial-dckr-site-0000-xpresswebapp
)
<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
<domain
xmlns="http://authzforce.github.io/rest-api-model/xmlns/authz/5"
xmlns:ns2="http://www.w3.org/2005/Atom"
xmlns:ns3="urn:oasis:names:tc:xacml:3.0:core:schema:wd-17"
xmlns:ns4="http://authzforce.github.io/core/xmlns/pdp/6.0"
xmlns:ns5="http://authzforce.github.io/pap-dao-flat-file/xmlns/properties/3.6"
>
<properties externalId="tutorial-dckr-site-0000-xpresswebapp" />
<childResources>
<ns2:link rel="item" href="/properties" title="Domain properties" />
<ns2:link rel="item" href="/pap" title="Policy Administration Point" />
<ns2:link rel="http://docs.oasis-open.org/ns/xacml/relation/pdp" href="/pdp" title="Policy Decision Point" />
</childResources>
</domain>
To list the generated IDs for all of the PolicySets found within a domain make a request to the
authzforce-ce/domains/{{domain-id}}/pap/policies
endpoint. The following request obtains a list of a given policy IDs
found within the gQqnLOnIEeiBFQJCrBIBDA
domain.
curl -X GET \
http://localhost:8080/authzforce-ce/domains/gQqnLOnIEeiBFQJCrBIBDA/pap/policies
The response returns a list of available revisions of the given policy which are available within the Authzforce
container. This corresponds the named XML files 1.xml
, 2.xml
etc.
<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
<resources
xmlns="http://authzforce.github.io/rest-api-model/xmlns/authz/5"
xmlns:ns2="http://www.w3.org/2005/Atom"
xmlns:ns3="urn:oasis:names:tc:xacml:3.0:core:schema:wd-17"
xmlns:ns4="http://authzforce.github.io/core/xmlns/pdp/6.0"
xmlns:ns5="http://authzforce.github.io/pap-dao-flat-file/xmlns/properties/3.6"
>
<ns2:link rel="item" href="f8194af5-8a07-486a-9581-c1f05d05483c" />
<ns2:link rel="item" href="root" />
</resources>
To list the available revisions of a policy, make a request to the
authzforce-ce/domains/{{domain-id}}/pap/policies/{{policy-id}}
endpoint. Available policy ID are randomly generated,
and can be obtained by drilling down using the previous request. The following request obtains a list revision of a
given policy found within the gQqnLOnIEeiBFQJCrBIBDA
domain.
curl -X GET \
http://localhost:8080/authzforce-ce/domains/gQqnLOnIEeiBFQJCrBIBDA/pap/policies/f8194af5-8a07-486a-9581-c1f05d05483c
The response returns a list of available revisions of the given policy which are available within the Authzforce
container. This corresponds the named XML files 1.xml
, 2.xml
etc.
<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
<resources
xmlns="http://authzforce.github.io/rest-api-model/xmlns/authz/5"
xmlns:ns2="http://www.w3.org/2005/Atom"
xmlns:ns3="urn:oasis:names:tc:xacml:3.0:core:schema:wd-17"
xmlns:ns4="http://authzforce.github.io/core/xmlns/pdp/6.0"
xmlns:ns5="http://authzforce.github.io/pap-dao-flat-file/xmlns/properties/3.6"
>
<ns2:link rel="item" href="2" />
<ns2:link rel="item" href="1" />
</resources>
To obtain a single revision of a <PolicySet>
, make a request to the
authzforce-ce/domains/{{domain-id}}/pap/policies/{{policy-id}}/{{revision-number}}
endpoint. The following request
obtains the second revision of the given policy found within the gQqnLOnIEeiBFQJCrBIBDA
domain.
curl -X GET \
http://localhost:8080/authzforce-ce/domains/gQqnLOnIEeiBFQJCrBIBDA/pap/policies/f8194af5-8a07-486a-9581-c1f05d05483c/2
The response contains the full <PolicySet>
for the given revision. This is a copy of
the file
held within Authzforce.
<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
<ns3:PolicySet
xmlns="http://authzforce.github.io/rest-api-model/xmlns/authz/5"
xmlns:ns2="http://www.w3.org/2005/Atom"
xmlns:ns3="urn:oasis:names:tc:xacml:3.0:core:schema:wd-17"
xmlns:ns4="http://authzforce.github.io/core/xmlns/pdp/6.0"
xmlns:ns5="http://authzforce.github.io/pap-dao-flat-file/xmlns/properties/3.6"
PolicySetId="f8194af5-8a07-486a-9581-c1f05d05483c"
Version="2"
PolicyCombiningAlgId="urn:oasis:names:tc:xacml:3.0:policy-combining-algorithm:deny-unless-permit"
>
<ns3:Description>Policy Set for application tutorial-dckr-site-0000-xpresswebapp</ns3:Description>
<ns3:Target />
<ns3:Policy
PolicyId="security-role-0000-0000-000000000000"
Version="1.0"
RuleCombiningAlgId="urn:oasis:names:tc:xacml:3.0:rule-combining-algorithm:deny-unless-permit"
>
<ns3:Description
>Role security-role-0000-0000-000000000000 from application tutorial-dckr-site-0000-xpresswebapp</ns3:Description>
<ns3:Target>
...etc
</ns3:Target>
<ns3:Rule RuleId="alrmbell-ring-0000-0000-000000000000" Effect="Permit">
...etc
</ns3:Rule>
..etc
</ns3:Policy>
</ns3:PolicySet>
For the purpose of this tutorial, Authzforce has been just been supplied with a simple set of basic role-based rules in a similar fashion to the level 2 authorization example found in the previous Securing Access tutorial:
- The unlock door command can only be sent by Security staff.
- Access to the price-change and order-stock areas are only available to Managers
- People with either the Manager or Security role can ring the bell
- Both Manager or Security can access and interact with the store data
The only difference is that access to all store entities is now restricted to users with an assigned role rather than being based on level 1 authentication access.
To request a decision from Authzforce, a structured request containing all relevant information must be sent to the
domains/{domain-id}/pdp
endpoint. In this case, the Body of the request includes information such as the roles that
the User has, the application ID that is being requested (tutorial-dckr-site-0000-xpresswebapp
) and the HTTP verb and
resource that are being requested ( a GET request on the /app/price-change
URL). Obviously the information passed in
the Body can be expanded as the rules become more complex.
To request a decision from Authzforce, make a POST request to the domains/{domain-id}/pdp
endpoint. In this case the
user has the managers-role-0000-0000-000000000000
and is requesting access the /app/price-change
resource.
curl -X POST \
http://localhost:8080/authzforce-ce/domains/gQqnLOnIEeiBFQJCrBIBDA/pdp \
-H 'Content-Type: application/xml' \
-d '<?xml version="1.0" encoding="UTF-8"?>
<Request xmlns="urn:oasis:names:tc:xacml:3.0:core:schema:wd-17" CombinedDecision="false" ReturnPolicyIdList="false">
<Attributes Category="urn:oasis:names:tc:xacml:1.0:subject-category:access-subject">
<Attribute AttributeId="urn:oasis:names:tc:xacml:2.0:subject:role" IncludeInResult="false">
<AttributeValue DataType="http://www.w3.org/2001/XMLSchema#string">managers-role-0000-0000-000000000000</AttributeValue>
</Attribute>
</Attributes>
<Attributes Category="urn:oasis:names:tc:xacml:3.0:attribute-category:resource">
<Attribute AttributeId="urn:oasis:names:tc:xacml:1.0:resource:resource-id" IncludeInResult="false">
<AttributeValue DataType="http://www.w3.org/2001/XMLSchema#string">tutorial-dckr-site-0000-xpresswebapp</AttributeValue>
</Attribute>
<Attribute AttributeId="urn:thales:xacml:2.0:resource:sub-resource-id" IncludeInResult="false">
<AttributeValue DataType="http://www.w3.org/2001/XMLSchema#string">/app/price-change</AttributeValue>
</Attribute>
</Attributes>
<Attributes Category="urn:oasis:names:tc:xacml:3.0:attribute-category:action">
<Attribute AttributeId="urn:oasis:names:tc:xacml:1.0:action:action-id" IncludeInResult="false">
<AttributeValue DataType="http://www.w3.org/2001/XMLSchema#string">GET</AttributeValue>
</Attribute>
</Attributes>
<Attributes Category="urn:oasis:names:tc:xacml:3.0:attribute-category:environment" />
</Request>'
The managers-role-0000-0000-000000000000
permits access to the /app/price-change
endpoint. The response for a
successful request includes a <Decision>
element to Permit
access to the resource.
<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
<ns3:Response
xmlns="http://authzforce.github.io/rest-api-model/xmlns/authz/5"
xmlns:ns2="http://www.w3.org/2005/Atom"
xmlns:ns3="urn:oasis:names:tc:xacml:3.0:core:schema:wd-17"
xmlns:ns4="http://authzforce.github.io/core/xmlns/pdp/6.0"
xmlns:ns5="http://authzforce.github.io/pap-dao-flat-file/xmlns/properties/3.6"
>
<ns3:Result>
<ns3:Decision>Permit</ns3:Decision>
</ns3:Result>
</ns3:Response>
To request a decision from Authzforce, make a POST request to the domains/{domain-id}/pdp
endpoint. In this case the
user has the security-role-0000-0000-000000000000
and is requesting access the /app/price-change
resource.
curl -X POST \
http://localhost:8080/authzforce-ce/domains/gQqnLOnIEeiBFQJCrBIBDA/pdp \
-H 'Content-Type: application/xml' \
-d '<?xml version="1.0" encoding="UTF-8"?>
<Request xmlns="urn:oasis:names:tc:xacml:3.0:core:schema:wd-17" CombinedDecision="false" ReturnPolicyIdList="false">
<Attributes Category="urn:oasis:names:tc:xacml:1.0:subject-category:access-subject">
<Attribute AttributeId="urn:oasis:names:tc:xacml:2.0:subject:role" IncludeInResult="false">
<AttributeValue DataType="http://www.w3.org/2001/XMLSchema#string">security-role-0000-0000-000000000000</AttributeValue>
</Attribute>
</Attributes>
<Attributes Category="urn:oasis:names:tc:xacml:3.0:attribute-category:resource">
<Attribute AttributeId="urn:oasis:names:tc:xacml:1.0:resource:resource-id" IncludeInResult="false">
<AttributeValue DataType="http://www.w3.org/2001/XMLSchema#string">tutorial-dckr-site-0000-xpresswebapp</AttributeValue>
</Attribute>
<Attribute AttributeId="urn:thales:xacml:2.0:resource:sub-resource-id" IncludeInResult="false">
<AttributeValue DataType="http://www.w3.org/2001/XMLSchema#string">/app/price-change</AttributeValue>
</Attribute>
</Attributes>
<Attributes Category="urn:oasis:names:tc:xacml:3.0:attribute-category:action">
<Attribute AttributeId="urn:oasis:names:tc:xacml:1.0:action:action-id" IncludeInResult="false">
<AttributeValue DataType="http://www.w3.org/2001/XMLSchema#string">GET</AttributeValue>
</Attribute>
</Attributes>
<Attributes Category="urn:oasis:names:tc:xacml:3.0:attribute-category:environment" />
</Request>'
The security-role-0000-0000-000000000000
does not permit access to the /app/price-change
endpoint. The response for
an unsuccessful request includes a <Decision>
element which will Deny
access to the resource.
<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
<ns3:Response
xmlns="http://authzforce.github.io/rest-api-model/xmlns/authz/5"
xmlns:ns2="http://www.w3.org/2005/Atom"
xmlns:ns3="urn:oasis:names:tc:xacml:3.0:core:schema:wd-17"
xmlns:ns4="http://authzforce.github.io/core/xmlns/pdp/6.0"
xmlns:ns5="http://authzforce.github.io/pap-dao-flat-file/xmlns/properties/3.6"
>
<ns3:Result>
<ns3:Decision>Deny</ns3:Decision>
</ns3:Result>
</ns3:Response>
As a reminder, there are three Levels of PDP Access Control:
- Level 1: Authentication Access - Allow all actions to every signed in user and no actions to an anonymous user.
- Level 2: Basic Authorization - Check which resources and verbs the currently logged in user should have access to
- Level 3: Advanced Authorization - Fine grained control through XACML
Within FIWARE, Level 3 access control can be provided by adding Authzforce to the existing security microservices (IDM and PEP Proxy) within the Smart Application infrastructure. Access control levels 1 and 2 have been covered in previous tutorials and can be fulfilled using Keyrock alone or with or without an associated PEP Proxy.
Advanced Authorization is able to deal with complex rulesets. Permissions are no longer merely based on a fixed role, resource and an action, but can be extended as necessary.
For example users in role XXX
can access URL starting with YYY
provided that the HTTP verb is either GET
,
PUT
or POST
. Such users may also DELETE
provided that they were the creator in the first place.
Within the tutorial programmatic example we are using our own trusted instance of Keyrock - once a user has signed
in and obtained an access_token
, the access_token
can be stored in session and used to retrieve user details on
demand. All access to the Orion context broker is hidden behind a PEP Proxy. Whenever a request is made to Orion, the
access_token
is passed in the header of the request, and the PEP proxy handles the decision to whether to execute the
request.
In order to identify themselves, every user must obtain an access token, in order to do so, they must use one of the OAuth2 access grants described in a previous tutorial.
To log in using the user-credentials flow send a POST request to the oauth2/token
endpoint of Keyrock with the
grant_type=password
curl -X POST \
http://localhost:3005/oauth2/token \
-H 'Accept: application/json' \
-H 'Authorization: Basic dHV0b3JpYWwtZGNrci1zaXRlLTAwMDAteHByZXNzd2ViYXBwOnR1dG9yaWFsLWRja3Itc2l0ZS0wMDAwLWNsaWVudHNlY3JldA==' \
-H 'Content-Type: application/x-www-form-urlencoded' \
-d 'username=bob-the-manager@test.com&password=test&grant_type=password'
The response returns an access_token
to identify the user (in this case Bob the Manager)
{
"access_token": "08fef363c429cb34cfff3f56dfe751a8d1890690",
"token_type": "Bearer",
"expires_in": 3599,
"refresh_token": "35a644094b598cb0d720fcb323369a53820a6a44",
"scope": ["bearer"]
}
If a user has logged in, the access_token
can be used in combination with the /user
endpoint to obtain access
permissions to a resource. This example retrieves Bob's permissions to a given resource.
curl -X GET \
'http://localhost:3005/user?access_token={{access_token}}&app_id={{app-id}}&authzforce=true'
Where :
{{access-token}}
is the current access token of a logged in user (e.g.08fef363c429cb34cfff3f56dfe751a8d1890690
){{app-id}}
holds the application to requesttutorial-dckr-site-0000-xpresswebapp
andauthzforce=true
indicates that we want to obtain an Authzforce Domain from Keyrock
The response include an authorization_decision
attribute which denies direct access for the request, but includes
additional information so that an additional request a decision from Authzforce
In the example below the access token used belonged to Bob the manager, and his roles and the app_azf_domain
associated to the app-id
are returned.
{
"organizations": [],
"displayName": "",
"roles": [
{
"id": "managers-role-0000-0000-000000000000",
"name": "Management"
}
],
"app_id": "tutorial-dckr-site-0000-xpresswebapp",
"trusted_apps": [],
"isGravatarEnabled": false,
"email": "bob-the-manager@test.com",
"id": "bbbbbbbb-good-0000-0000-000000000000",
"authorization_decision": "",
"app_azf_domain": "gQqnLOnIEeiBFQJCrBIBDA",
"eidas_profile": {},
"username": "bob"
}
To request a decision from Authzforce, a structured request containing all relevant information must be sent to the
domains/{domain-id}/pdp
endpoint. In this case, the Body of the request includes information such as the roles that
the User has (managers-role-0000-0000-000000000000
), the application ID that is being requested
(tutorial-dckr-site-0000-xpresswebapp
) and the HTTP verb and resource that are being requested ( a POST request on the
/v2/entities
URL)
curl -X POST \
http://localhost:8080/authzforce-ce/domains/gQqnLOnIEeiBFQJCrBIBDA/pdp \
-H 'Content-Type: application/xml' \
-d '<?xml version="1.0" encoding="UTF-8"?>
<Request xmlns="urn:oasis:names:tc:xacml:3.0:core:schema:wd-17" CombinedDecision="false" ReturnPolicyIdList="false">
<Attributes Category="urn:oasis:names:tc:xacml:1.0:subject-category:access-subject">
<Attribute AttributeId="urn:oasis:names:tc:xacml:2.0:subject:role" IncludeInResult="false">
<AttributeValue DataType="http://www.w3.org/2001/XMLSchema#string">managers-role-0000-0000-000000000000</AttributeValue>
</Attribute>
</Attributes>
<Attributes Category="urn:oasis:names:tc:xacml:3.0:attribute-category:resource">
<Attribute AttributeId="urn:oasis:names:tc:xacml:1.0:resource:resource-id" IncludeInResult="false">
<AttributeValue DataType="http://www.w3.org/2001/XMLSchema#string">tutorial-dckr-site-0000-xpresswebapp</AttributeValue>
</Attribute>
<Attribute AttributeId="urn:thales:xacml:2.0:resource:sub-resource-id" IncludeInResult="false">
<AttributeValue DataType="http://www.w3.org/2001/XMLSchema#string">/v2/entities</AttributeValue>
</Attribute>
</Attributes>
<Attributes Category="urn:oasis:names:tc:xacml:3.0:attribute-category:action">
<Attribute AttributeId="urn:oasis:names:tc:xacml:1.0:action:action-id" IncludeInResult="false">
<AttributeValue DataType="http://www.w3.org/2001/XMLSchema#string">POST</AttributeValue>
</Attribute>
</Attributes>
<Attributes Category="urn:oasis:names:tc:xacml:3.0:attribute-category:environment" />
</Request>'
The response includes a <Decision>
element which will either Permit
or Deny
the request.
<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
<ns3:Response
xmlns="http://authzforce.github.io/rest-api-model/xmlns/authz/5"
xmlns:ns2="http://www.w3.org/2005/Atom"
xmlns:ns3="urn:oasis:names:tc:xacml:3.0:core:schema:wd-17"
xmlns:ns4="http://authzforce.github.io/core/xmlns/pdp/6.0"
xmlns:ns5="http://authzforce.github.io/pap-dao-flat-file/xmlns/properties/3.6"
>
<ns3:Result>
<ns3:Decision>Permit</ns3:Decision>
</ns3:Result>
</ns3:Response>
Programmatically, any Policy Execution Point consists of two parts, an OAuth request to Keyrock retrieves information about the user (such as the assigned roles) as well as the policy domain to be queried.
A second request is sent to the relevant domain endpoint within Authzforce, providing all of the information necessary for Authzforce to provide a judgement. Authzforce responds with a permit or deny response, and the decision whether to continue can be made thereafter.
function authorizeAdvancedXACML(req, res, next, resource = req.url) {
const keyrockUserUrl =
'http://keyrock/user?access_token=' + req.session.access_token + '&app_id=' + clientId + '&authzforce=true';
return oa
.get(keyrockUserUrl)
.then((response) => {
const user = JSON.parse(response);
return azf.policyDomainRequest(user.app_azf_domain, user.roles, resource, req.method);
})
.then((authzforceResponse) => {
res.locals.authorized = authzforceResponse === 'Permit';
return next();
})
.catch((error) => {
debug(error);
res.locals.authorized = false;
return next();
});
}
The full code to supply each request to Authzforce can be found within the tutorials' Git Repository - the actual information to supply will depend on business use case - it could be expanded to include temporal information, relationships between records and so on, but in this very simple example only roles are necessary.
const xml2js = require('xml2js');
const request = require('request');
function policyDomainRequest(domain, roles, resource, action) {
let body =
'<?xml version="1.0" encoding="UTF-8"?>\n' +
'<Request xmlns="urn:oasis:names:tc:xacml:3.0:core:schema:wd-17" CombinedDecision="false" ReturnPolicyIdList="false">\n';
// Code to create the XML body for the request is omitted
body = body + '</Request>';
const options = {
method: 'POST',
url: 'http://authzforceUrl/authzforce-ce/domains/' + domain + '/pdp',
headers: { 'Content-Type': 'application/xml' },
body
};
return new Promise((resolve, reject) => {
request(options, function (error, response, body) {
let decision;
xml2js.parseString(body, { tagNameProcessors: [xml2js.processors.stripPrefix] }, function (err, jsonRes) {
// The decision is found within the /Response/Result[0]/Decision[0] XPath
decision = jsonRes.Response.Result[0].Decision[0];
});
decision = String(decision);
return error ? reject(error) : resolve(decision);
});
});
}
Applying advanced authorization within a PEP proxy requires very similar code to the programmatic example described above. The Wilma generic enabler extracts a token from the header supplied by the request and makes a request to Keyrock to obtain further information about the user. A PDP request is then made to Authzforce to decide whether to proceed.
Obviously any scalable solution should also cache information about the PDP requests made and the responses to avoid making unnecessary requests.
Note
Five resources have been secured at level 3:
- sending the unlock door command
- sending the ring bell command
- access to the price-change area
- access to the order-stock area
- access to Orion (behind a PEP Proxy)
Eve has an account, but no roles in the application.
Note
As Eve has a recognized account, she gains full authentication access. This means she is able to view the Store page, even though her account has no roles attached.
- From
http://localhost:3000
, log in aseve@example.com
with the passwordtest
-
Click on any store page - access to view the page is permitted for any logged in users, however access to retrieve Orion data is now denied since Eve has no role which permits access.
-
Click on the restricted access links at
http://localhost:3000
- access is denied -
Open the Device Monitor on
http://localhost:3000/device/monitor
- Unlock a door - access is denied
- Ring a bell - access is denied
Bob has the management role
- From
http://localhost:3000
, log in asbob-the-manager@test.com
with the passwordtest
- Click on the restricted access links at
http://localhost:3000
- access is permitted - This is a management only permission - Open the Device Monitor on
http://localhost:3000/device/monitor
- Unlock a door - access is denied. - This is a security only permission
- Ring a bell - access is permitted - This is permitted to management users
Charlie has the security role
- From
http://localhost:3000
, log in ascharlie-security@test.com
with the passwordtest
- Click on the restricted access links at
http://localhost:3000
- access is denied - This is a management only permission - Open the Device Monitor on
http://localhost:3000/device/monitor
- Unlock a door - access is permitted - This is a security only permission
- Ring a bell - access is permitted - This is permitted to security users
Want to learn how to add more complexity to your application by adding advanced features? You can find out by reading the other tutorials in this series
MIT © 2018-2024 FIWARE Foundation e.V.
- Wikipedia: XACML - stands for "eXtensible Access Control Markup Language".