Authorization
Configuring authorization by using role based access control (RBAC) in Red Hat Developer Hub
Abstract
- Preface
- 1. Enabling and giving access to the Role-Based Access Control (RBAC) feature
- 2. Managing role-based access controls (RBAC) using the Red Hat Developer Hub Web UI
- 3. Managing authorizations by using the REST API
- 4. Permission policies in Red Hat Developer Hub
- 5. Conditional policies in Red Hat Developer Hub
- 6. User statistics in Red Hat Developer Hub
Preface
In Authentication, you learnt how to authenticate users to Red Hat Developer Hub. Developer Hub knowns who the users are.
In this book, learn how to authorize users to perform actions in Developer Hub. Define what users can do in Developer Hub.
Role-Based Access Control (RBAC) is a security concept that controls access to resources in a system, and specifies a mapping between users of the system, and the actions they can perform on resources in the system. You define roles with specific permissions, and then assign the roles to users and groups.
RBAC on Developer Hub is built on top of the Permissions framework, which defines RBAC policies in code. Rather than defining policies in code, the Developer Hub RBAC feature allows you to define policies in a declarative fashion using a simple CSV based format. You can define the policies by using Developer Hub web interface or REST API, rather than editing the CSV directly.
To apply RBAC in Developer Hub:
The Developer Hub administrator sets up the RBAC feature:
- Enable the RBAC feature
- Configure Policy Administrators
The Developer Hub policy administrator configures your RBAC policies:
- Define roles with specific permissions
- Assign the roles to users and groups
Chapter 1. Enabling and giving access to the Role-Based Access Control (RBAC) feature
The Role-Based Access Control (RBAC) feature is disabled by default. Enable the RBAC plugin and declare policy administrators to start using RBAC features.
The permission policies for users and groups in the Developer Hub are managed by permission policy administrators. Only permission policy administrators can access the Role-Based Access Control REST API.
Prerequisites
- You have added a custom Developer Hub application configuration, and have sufficient permissions to modify it.
- You have enabled an authentication provider.
Procedure
The RBAC plugin is installed but disabled by default. To enable the
./dynamic-plugins/dist/janus-idp-backstage-plugin-rbac
plugin, edit yourdynamic-plugins.yaml
with the following content.dynamic-plugins.yaml
fragmentplugins: - package: ./dynamic-plugins/dist/janus-idp-backstage-plugin-rbac disabled: false
Declare policy administrators to enable a select number of authenticated users to configure RBAC policies through the REST API or Web UI, instead of modifying the CSV file directly. The permissions can be specified in a separate CSV file referenced in the
app-config-rhdh
ConfigMap, or permissions can be created using the REST API or Web UI.To declare users such as <your_policy_administrator_name> as policy administrators, edit your custom Developer Hub ConfigMap, such as
app-config-rhdh
, and add following code to theapp-config-rhdh.yaml
content:app-config.yaml
fragmentpermission: enabled: true rbac: admin: users: - name: user:default/<your_policy_administrator_name>
Verification
- Sign out from the existing Red Hat Developer Hub session and log in again using the declared policy administrator account.
With RBAC enabled, most features are disabled by default.
- Navigate to the Catalog page in RHDH. The Create button is not visible. You cannot create new components.
- Navigate to the API page. The Register button is not visible.
Next steps
- Explicitly enable permissions to resources in Developer Hub.
Chapter 2. Managing role-based access controls (RBAC) using the Red Hat Developer Hub Web UI
Policy administrators can use the Developer Hub web interface (Web UI) to allocate specific roles and permissions to individual users or groups. Allocating roles ensures that access to resources and functionalities is regulated across the Developer Hub.
With the policy administrator role in Developer Hub, you can assign permissions to users and groups. This role allows you to view, create, modify, and delete the roles using Developer Hub Web UI.
2.1. Creating a role in the Red Hat Developer Hub Web UI
You can create a role in the Red Hat Developer Hub using the Web UI.
Prerequisites
Procedure
Go to Administration at the bottom of the sidebar in the Developer Hub.
The RBAC tab appears, displaying all the created roles in the Developer Hub.
- (Optional) Click any role to view the role information on the OVERVIEW page.
- Click CREATE to create a role.
- Enter the name and description of the role in the given fields and click NEXT.
- Add users and groups using the search field, and click NEXT.
- Select Plugin and Permission from the drop-downs in the Add permission policies section.
- Select or clear the Policy that you want to set in the Add permission policies section, and click NEXT.
- Review the added information in the Review and create section.
- Click CREATE.
Verification
The created role appears in the list available in the RBAC tab.
2.2. Editing a role in the Red Hat Developer Hub Web UI
You can edit a role in the Red Hat Developer Hub using the Web UI.
The policies generated from a policy.csv
or ConfigMap file cannot be edited or deleted using the Developer Hub Web UI.
Prerequisites
- You have enabled RBAC and have a policy administrator role in Developer Hub.
- The role that you want to edit is created in the Developer Hub.
Procedure
Go to Administration at the bottom of the sidebar in the Developer Hub.
The RBAC tab appears, displaying all the created roles in the Developer Hub.
- (Optional) Click any role to view the role information on the OVERVIEW page.
- Select the edit icon for the role that you want to edit.
- Edit the details of the role, such as name, description, users and groups, and permission policies, and click NEXT.
- Review the edited details of the role and click SAVE.
After editing a role, you can view the edited details of a role on the OVERVIEW page of a role. You can also edit a role’s users and groups or permissions by using the edit icon on the respective cards on the OVERVIEW page.
2.3. Deleting a role in the Red Hat Developer Hub Web UI
You can delete a role in the Red Hat Developer Hub using the Web UI.
The policies generated from a policy.csv
or ConfigMap file cannot be edited or deleted using the Developer Hub Web UI.
Prerequisites
- You have enabled RBAC and have a policy administrator role in Developer Hub.
- The role that you want to delete is created in the Developer Hub.
Procedure
Go to Administration at the bottom of the sidebar in the Developer Hub.
The RBAC tab appears, displaying all the created roles in the Developer Hub.
- (Optional) Click any role to view the role information on the OVERVIEW page.
Select the delete icon from the Actions column for the role that you want to delete.
Delete this role? pop-up appears on the screen.
- Click DELETE.
Chapter 3. Managing authorizations by using the REST API
To automate the maintenance of Red Hat Developer Hub permission policies and roles, you can use Developer Hub role-based access control (RBAC) REST API.
You can perform the following actions with the REST API:
Retrieve information about:
- All permission policies
- Specific permission policies
- Specific roles
- Static plugins permission policies
Create, update, or delete:
- Permission policy
- Role
3.1. Sending requests to the RBAC REST API by using the curl utility
You can send RBAC REST API requests by using the curl utility.
Prerequisites
Procedure
Find your Bearer token to authenticate to the REST API.
- In your browser, open the web console Network tab.
- In the main screen, reload the Developer Hub Homepage.
-
In the web console Network tab, search for the
query?term=
network call. - Save the token in the response JSON for the next steps.
In a terminal, run the curl command and review the response:
GET
orDELETE
requestcurl -v \ -H "Authorization: Bearer <token>" \ -X <method> "https://<my_developer_hub_url>/<endpoint>" \
POST
orPUT
request requiring JSON body datacurl -v -H "Content-Type: application/json" \ -H "Authorization: Bearer <token>" \ -X POST "https://<my_developer_hub_url>/<endpoint>" \ -d <body>
- <token>
- Enter your saved authorization token.
- <method>
Enter the HTTP method for your API endpoint.
-
GET
: To retrieve specified information from a specified resource endpoint. -
POST
: To create or update a resource. -
PUT
: To update a resource. -
DELETE
: To delete a resource.
-
- https://<my_developer_hub_url>
- Enter your Developer Hub URL.
- <endpoint>
-
Enter the API endpoint to which you want to send a request, such as
/api/permission/policies
. - <body>
-
Enter the JSON body with data that your API endpoint might need with the HTTP
POST
orPUT
request.
Example request to create a role
curl -v -H "Content-Type: application/json" \ -H "Authorization: Bearer <token>" \ -X POST "https://<my_developer_hub_url>/api/permission/roles" \ -d '{ "memberReferences": ["group:default/example"], "name": "role:default/test", "metadata": { "description": "This is a test role" } }'
Example request to update a role
curl -v -H "Content-Type: application/json" \ -H "Authorization: Bearer <token>" \ -X PUT "https://<my_developer_hub_url>/api/permission/roles/role/default/test" \ -d '{ "oldRole": { "memberReferences": [ "group:default/example" ], "name": "role:default/test" }, "newRole": { "memberReferences": [ "group:default/example", "user:default/test" ], "name": "role:default/test" } }'
Example request to create a permission policy
curl -v -H "Content-Type: application/json" \ -H "Authorization: Bearer $token" \ -X POST "https://<my_developer_hub_url>/api/permission/policies" \ -d '[{ "entityReference":"role:default/test", "permission": "catalog-entity", "policy": "read", "effect":"allow" }]'
Example request to update a permission policy
curl -v -H "Content-Type: application/json" \ -H "Authorization: Bearer $token" \ -X PUT "https://<my_developer_hub_url>/api/permission/policies/role/default/test" \ -d '{ "oldPolicy": [ { "permission": "catalog-entity", "policy": "read", "effect": "allow" } ], "newPolicy": [ { "permission": "policy-entity", "policy": "read", "effect": "allow" } ] }'
Example request to create a condition
curl -v -H "Content-Type: application/json" \ -H "Authorization: Bearer $token" \ -X POST "https://<my_developer_hub_url>/api/permission/roles/conditions" \ -d '{ "result": "CONDITIONAL", "roleEntityRef": "role:default/test", "pluginId": "catalog", "resourceType": "catalog-entity", "permissionMapping": ["read"], "conditions": { "rule": "IS_ENTITY_OWNER", "resourceType": "catalog-entity", "params": {"claims": ["group:default/janus-authors"]} } }'
Example request to update a condition
curl -v -H "Content-Type: application/json" \ -H "Authorization: Bearer $token" \ -X PUT "https://<my_developer_hub_url>/api/permission/roles/conditions/1" \ -d '{ "result":"CONDITIONAL", "roleEntityRef":"role:default/test", "pluginId":"catalog", "resourceType":"catalog-entity", "permissionMapping": ["read", "update", "delete"], "conditions": { "rule": "IS_ENTITY_OWNER", "resourceType": "catalog-entity", "params": {"claims": ["group:default/janus-authors"]} } }'
Verification
Review the returned HTTP status code:
200
OK- The request was successful.
201
Created- The request resulted in a new resource being successfully created.
204
No Content- The request was successful, and the response payload has no more content.
400
Bad Request- Input error with the request.
401
Unauthorized- Lacks valid authentication for the requested resource.
403
Forbidden- Refusal to authorize request.
404
Not Found- Could not find requested resource.
409
Conflict- Request conflict with the current state and the target resource.
3.2. Sending requests to the RBAC REST API by using a REST client
You can send RBAC REST API requests using any REST client.
Prerequisites
Procedure
Find your Bearer token to authenticate to the REST API.
- In your browser, open the web console Network tab.
- In the main screen, reload the Developer Hub Homepage.
-
In the web console Network tab, search for the
query?term=
network call. - Save the token in the response JSON for the next steps.
In your REST client, run a command with the following parameters and review the response:
- Authorization
- Enter your saved authorization token.
- HTTP method
Enter the HTTP method for your API endpoint.
-
GET
: To retrieve specified information from a specified resource endpoint. -
POST
: To create or update a resource. -
PUT
: To update a resource. -
DELETE
: To delete a resource.
-
- URL
-
Enter your Developer Hub URL and API endpoint: https://<my_developer_hub_url>/<endpoint>, such as
https://<my_developer_hub_url>/api/permission/policies
. - Body
-
Enter the JSON body with data that your API endpoint might need with the HTTP
POST
request.
3.3. Supported RBAC REST API endpoints
The RBAC REST API provides endpoints for managing roles, permissions, and conditional policies in the Developer Hub and for retrieving information about the roles and policies.
3.3.1. Roles
The RBAC REST API supports the following endpoints for managing roles in the Red Hat Developer Hub.
- [GET] /api/permission/roles
Returns all roles in Developer Hub.
Example response (JSON)
[ { "memberReferences": ["user:default/username"], "name": "role:default/guests" }, { "memberReferences": [ "group:default/groupname", "user:default/username" ], "name": "role:default/rbac_admin" } ]
- [GET] /api/permission/roles/<kind>/<namespace>/<name>
Returns information for a single role in Developer Hub.
Example response (JSON)
[ { "memberReferences": [ "group:default/groupname", "user:default/username" ], "name": "role:default/rbac_admin" } ]
- [POST] /api/permission/roles/<kind>/<namespace>/<name>
Creates a role in Developer Hub.
Table 3.1. Request parameters
Name Description Type Presence body
The
memberReferences
,group
,namespace
, andname
the new role to be created.Request body
Required
Example request body (JSON)
{ "memberReferences": ["group:default/test"], "name": "role:default/test_admin" }
Example response
201 Created
- [PUT] /api/permission/roles/<kind>/<namespace>/<name>
Updates
memberReferences
,kind
,namespace
, orname
for a role in Developer Hub.Request parameters
The request body contains the
oldRole
andnewRole
objects:Name Description Type Presence body
The
memberReferences
,group
,namespace
, andname
the new role to be created.Request body
Required
Example request body (JSON)
{ "oldRole": { "memberReferences": ["group:default/test"], "name": "role:default/test_admin" }, "newRole": { "memberReferences": ["group:default/test", "user:default/test2"], "name": "role:default/test_admin" } }
Example response
200 OK
- [DELETE] /api/permission/roles/<kind>/<namespace>/<name>?memberReferences=<VALUE>
Deletes the specified user or group from a role in Developer Hub.
Table 3.2. Request parameters
Name Description Type Presence kind
Kind of the entity
String
Required
namespace
Namespace of the entity
String
Required
name
Name of the entity
String
Required
memberReferences
Associated group information
String
Required
Example response
204
- [DELETE] /api/permission/roles/<kind>/<namespace>/<name>
Deletes a specified role from Developer Hub.
Table 3.3. Request parameters
Name Description Type Presence kind
Kind of the entity
String
Required
namespace
Namespace of the entity
String
Required
name
Name of the entity
String
Required
Example response
204
3.3.2. Permission policies
The RBAC REST API supports the following endpoints for managing permission policies in the Red Hat Developer Hub.
- [GET] /api/permission/policies
Returns permission policies list for all users.
Example response (JSON)
[ { "entityReference": "role:default/test", "permission": "catalog-entity", "policy": "read", "effect": "allow", "metadata": { "source": "csv-file" } }, { "entityReference": "role:default/test", "permission": "catalog.entity.create", "policy": "use", "effect": "allow", "metadata": { "source": "csv-file" } }, ]
- [GET] /api/permission/policies/<kind>/<namespace>/<name>
Returns permission policies related to the specified entity reference.
Table 3.4. Request parameters
Name Description Type Presence kind
Kind of the entity
String
Required
namespace
Namespace of the entity
String
Required
name
Name related to the entity
String
Required
Example response (JSON)
[ { "entityReference": "role:default/test", "permission": "catalog-entity", "policy": "read", "effect": "allow", "metadata": { "source": "csv-file" } }, { "entityReference": "role:default/test", "permission": "catalog.entity.create", "policy": "use", "effect": "allow", "metadata": { "source": "csv-file" } } ]
- [POST] /api/permission/policies
Creates a permission policy for a specified entity.
Table 3.5. Request parameters
Name Description Type Presence entityReference
Reference values of an entity including
kind
,namespace
, andname
String
Required
permission
Permission from a specific plugin, resource type, or name
String
Required
policy
Policy action for the permission, such as
create
,read
,update
,delete
, oruse
String
Required
effect
Indication of allowing or not allowing the policy
String
Required
Example request body (JSON)
[ { "entityReference": "role:default/test", "permission": "catalog-entity", "policy": "read", "effect": "allow" } ]
Example response
201 Created
- [PUT] /api/permission/policies/<kind>/<namespace>/<name>
Updates a permission policy for a specified entity.
Request parameters
The request body contains the
oldPolicy
andnewPolicy
objects:Name Description Type Presence permission
Permission from a specific plugin, resource type, or name
String
Required
policy
Policy action for the permission, such as
create
,read
,update
,delete
, oruse
String
Required
effect
Indication of allowing or not allowing the policy
String
Required
Example request body (JSON)
{ "oldPolicy": [ { "permission": "catalog-entity", "policy": "read", "effect": "allow" }, { "permission": "catalog.entity.create", "policy": "create", "effect": "allow" } ], "newPolicy": [ { "permission": "catalog-entity", "policy": "read", "effect": "deny" }, { "permission": "policy-entity", "policy": "read", "effect": "allow" } ] }
Example response
200
- [DELETE] /api/permission/policies/<kind>/<namespace>/<name>?permission={value1}&policy={value2}&effect={value3}
Deletes a permission policy added to the specified entity.
Table 3.6. Request parameters
Name Description Type Presence kind
Kind of the entity
String
Required
namespace
Namespace of the entity
String
Required
name
Name related to the entity
String
Required
permission
Permission from a specific plugin, resource type, or name
String
Required
policy
Policy action for the permission, such as
create
,read
,update
,delete
, oruse
String
Required
effect
Indication of allowing or not allowing the policy
String
Required
Example response
204 No Content
- [DELETE] /api/permission/policies/<kind>/<namespace>/<name>
Deletes all permission policies added to the specified entity.
Table 3.7. Request parameters
Name Description Type Presence kind
Kind of the entity
String
Required
namespace
Namespace of the entity
String
Required
name
Name related to the entity
String
Required
Example response
204 No Content
- [GET] /api/permission/plugins/policies
Returns permission policies for all static plugins.
Example response (JSON)
[ { "pluginId": "catalog", "policies": [ { "isResourced": true, "permission": "catalog-entity", "policy": "read" }, { "isResourced": false, "permission": "catalog.entity.create", "policy": "create" }, { "isResourced": true, "permission": "catalog-entity", "policy": "delete" }, { "isResourced": true, "permission": "catalog-entity", "policy": "update" }, { "isResourced": false, "permission": "catalog.location.read", "policy": "read" }, { "isResourced": false, "permission": "catalog.location.create", "policy": "create" }, { "isResourced": false, "permission": "catalog.location.delete", "policy": "delete" } ] }, ... ]
3.3.3. Conditional policies
The RBAC REST API supports the following endpoints for managing conditional policies in the Red Hat Developer Hub.
- [GET] /api/permission/plugins/condition-rules
Returns available conditional rule parameter schemas for the available plugins that are enabled in Developer Hub.
Example response (JSON)
[ { "pluginId": "catalog", "rules": [ { "name": "HAS_ANNOTATION", "description": "Allow entities with the specified annotation", "resourceType": "catalog-entity", "paramsSchema": { "type": "object", "properties": { "annotation": { "type": "string", "description": "Name of the annotation to match on" }, "value": { "type": "string", "description": "Value of the annotation to match on" } }, "required": [ "annotation" ], "additionalProperties": false, "$schema": "http://json-schema.org/draft-07/schema#" } }, { "name": "HAS_LABEL", "description": "Allow entities with the specified label", "resourceType": "catalog-entity", "paramsSchema": { "type": "object", "properties": { "label": { "type": "string", "description": "Name of the label to match on" } }, "required": [ "label" ], "additionalProperties": false, "$schema": "http://json-schema.org/draft-07/schema#" } }, { "name": "HAS_METADATA", "description": "Allow entities with the specified metadata subfield", "resourceType": "catalog-entity", "paramsSchema": { "type": "object", "properties": { "key": { "type": "string", "description": "Property within the entities metadata to match on" }, "value": { "type": "string", "description": "Value of the given property to match on" } }, "required": [ "key" ], "additionalProperties": false, "$schema": "http://json-schema.org/draft-07/schema#" } }, { "name": "HAS_SPEC", "description": "Allow entities with the specified spec subfield", "resourceType": "catalog-entity", "paramsSchema": { "type": "object", "properties": { "key": { "type": "string", "description": "Property within the entities spec to match on" }, "value": { "type": "string", "description": "Value of the given property to match on" } }, "required": [ "key" ], "additionalProperties": false, "$schema": "http://json-schema.org/draft-07/schema#" } }, { "name": "IS_ENTITY_KIND", "description": "Allow entities matching a specified kind", "resourceType": "catalog-entity", "paramsSchema": { "type": "object", "properties": { "kinds": { "type": "array", "items": { "type": "string" }, "description": "List of kinds to match at least one of" } }, "required": [ "kinds" ], "additionalProperties": false, "$schema": "http://json-schema.org/draft-07/schema#" } }, { "name": "IS_ENTITY_OWNER", "description": "Allow entities owned by a specified claim", "resourceType": "catalog-entity", "paramsSchema": { "type": "object", "properties": { "claims": { "type": "array", "items": { "type": "string" }, "description": "List of claims to match at least one on within ownedBy" } }, "required": [ "claims" ], "additionalProperties": false, "$schema": "http://json-schema.org/draft-07/schema#" } } ] } ... <another plugin condition parameter schemas> ]
- [GET] /api/permission/roles/conditions/:id
Returns conditions for the specified ID.
Example response (JSON)
{ "id": 1, "result": "CONDITIONAL", "roleEntityRef": "role:default/test", "pluginId": "catalog", "resourceType": "catalog-entity", "permissionMapping": ["read"], "conditions": { "anyOf": [ { "rule": "IS_ENTITY_OWNER", "resourceType": "catalog-entity", "params": { "claims": ["group:default/team-a"] } }, { "rule": "IS_ENTITY_KIND", "resourceType": "catalog-entity", "params": { "kinds": ["Group"] } } ] } }
- [GET] /api/permission/roles/conditions
Returns list of all conditions for all roles.
Example response (JSON)
[ { "id": 1, "result": "CONDITIONAL", "roleEntityRef": "role:default/test", "pluginId": "catalog", "resourceType": "catalog-entity", "permissionMapping": ["read"], "conditions": { "anyOf": [ { "rule": "IS_ENTITY_OWNER", "resourceType": "catalog-entity", "params": { "claims": ["group:default/team-a"] } }, { "rule": "IS_ENTITY_KIND", "resourceType": "catalog-entity", "params": { "kinds": ["Group"] } } ] } } ]
- [POST] /api/permission/roles/conditions
Creates a conditional policy for the specified role.
Table 3.8. Request parameters
Name Description Type Presence result
Always has the value
CONDITIONAL
String
Required
roleEntityRef
String entity reference to the RBAC role, such as
role:default/dev
String
Required
pluginId
Corresponding plugin ID, such as
catalog
String
Required
permissionMapping
Array permission action, such as
['read', 'update', 'delete']
String array
Required
resourceType
Resource type provided by the plugin, such as
catalog-entity
String
Required
conditions
Condition JSON with parameters or array parameters joined by criteria
JSON
Required
name
Name of the role
String
Required
metadata.description
The description of the role
String
Optional
Example request body (JSON)
{ "result": "CONDITIONAL", "roleEntityRef": "role:default/test", "pluginId": "catalog", "resourceType": "catalog-entity", "permissionMapping": ["read"], "conditions": { "rule": "IS_ENTITY_OWNER", "resourceType": "catalog-entity", "params": { "claims": ["group:default/team-a"] } } }
Example response (JSON)
{ "id": 1 }
- [PUT] /permission/roles/conditions/:id
Updates a condition policy for a specified ID.
Table 3.9. Request parameters
Name Description Type Presence result
Always has the value
CONDITIONAL
String
Required
roleEntityRef
String entity reference to the RBAC role, such as
role:default/dev
String
Required
pluginId
Corresponding plugin ID, such as
catalog
String
Required
permissionMapping
Array permission action, such as
['read', 'update', 'delete']
String array
Required
resourceType
Resource type provided by the plugin, such as
catalog-entity
String
Required
conditions
Condition JSON with parameters or array parameters joined by criteria
JSON
Required
name
Name of the role
String
Required
metadata.description
The description of the role
String
Optional
Example request body (JSON)
{ "result": "CONDITIONAL", "roleEntityRef": "role:default/test", "pluginId": "catalog", "resourceType": "catalog-entity", "permissionMapping": ["read"], "conditions": { "anyOf": [ { "rule": "IS_ENTITY_OWNER", "resourceType": "catalog-entity", "params": { "claims": ["group:default/team-a"] } }, { "rule": "IS_ENTITY_KIND", "resourceType": "catalog-entity", "params": { "kinds": ["Group"] } } ] } }
Example response
200
- [DELETE] /api/permission/roles/conditions/:id
Deletes a conditional policy for the specified ID.
Example response
204
3.3.4. User statistics
The licensed-users-info-backend
plugin exposes various REST API endpoints to retrieve data related to logged-in users.
No additional configuration is required for the licensed-users-info-backend
plugin. If the RBAC backend plugin is enabled, then an administrator role must be assigned to access the endpoints, as the endpoints are protected by the policy.entity.read
permission.
The base URL for user statistics endpoints is http://SERVER:PORT/api/licensed-users-info
, such as http://localhost:7007/api/licensed-users-info
.
- [GET] /users/quantity
Returns the total number of logged-in users.
Example request
curl -X GET "http://localhost:7007/api/licensed-users-info/users/quantity" \ -H "Content-Type: application/json" \ -H "Authorization: Bearer $token"
Example response
{ "quantity": "2" }
- [GET] /users
Returns a list of logged-in users with their details.
Example request
curl -X GET "http://localhost:7007/api/licensed-users-info/users" \ -H "Content-Type: application/json" \ -H "Authorization: Bearer $token"
Example response
[ { "userEntityRef": "user:default/dev", "lastTimeLogin": "Thu, 22 Aug 2024 16:27:41 GMT", "displayName": "John Leavy", "email": "dev@redhat.com" } ]
- [GET] /users
Returns a list of logged-in users in CSV format.
Example request
curl -X GET "http://localhost:7007/api/licensed-users-info/users" \ -H "Content-Type: text/csv" \ -H "Authorization: Bearer $token"
Example response
userEntityRef,displayName,email,lastTimeLogin user:default/dev,John Leavy,dev@redhat.com,"Thu, 22 Aug 2024 16:27:41 GMT"
Chapter 4. Permission policies in Red Hat Developer Hub
Permission policies in Red Hat Developer Hub are a set of rules to govern access to resources or functionalities. These policies state the authorization level that is granted to users based on their roles. The permission policies are implemented to maintain security and confidentiality within a given environment.
You can define the following types of permissions in Developer Hub:
- resource type
- basic
The distinction between the two permission types depends on whether a permission includes a defined resource type.
You can define the resource type permission using either the associated resource type or the permission name as shown in the following example:
Example resource type permission definition
p, role:default/myrole, catalog.entity.read, read, allow g, user:default/myuser, role:default/myrole p, role:default/another-role, catalog-entity, read, allow g, user:default/another-user, role:default/another-role
You can define the basic permission in Developer Hub using the permission name as shown in the following example:
Example basic permission definition
p, role:default/myrole, catalog.entity.create, create, allow g, user:default/myuser, role:default/myrole
The following permission policies are supported in the Developer Hub:
- Catalog permissions
Name | Resource type | Policy | Description |
---|---|---|---|
|
|
read |
Allows user or role to read from the catalog |
|
create |
Allows user or role to create catalog entities, including registering an existing component in the catalog | |
|
|
update |
Allows user or role to refresh a single or multiple entities from the catalog |
|
|
delete |
Allows user or role to delete a single or multiple entities from the catalog |
|
read |
Allows user or role to read a single or multiple locations from the catalog | |
|
create |
Allows user or role to create locations within the catalog | |
|
delete |
Allows user or role to delete locations from the catalog |
- Bulk import permissions
Name | Resource type | Policy | Description |
---|---|---|---|
|
|
Allows the user to access the bulk import endpoints, such as listing all repositories and organizations accessible by all GitHub integrations and managing the import requests. |
- Scaffolder permissions
Name | Resource type | Policy | Description |
---|---|---|---|
|
|
Allows the execution of an action from a template | |
|
|
read |
Allows user or role to read a single or multiple one parameters from a template |
|
|
read |
Allows user or role to read a single or multiple steps from a template |
|
create |
Allows the user or role to trigger software templates which create new scaffolder tasks | |
|
Allows the user or role to cancel currently running scaffolder tasks | ||
|
read |
Allows user or role to read all scaffolder tasks and their associated events and logs |
- RBAC permissions
Name | Resource type | Policy | Description |
---|---|---|---|
|
|
read |
Allows user or role to read permission policies and roles |
|
|
create |
Allows user or role to create a single or multiple permission policies and roles |
|
|
update |
Allows user or role to update a single or multiple permission policies and roles |
|
|
delete |
Allows user or role to delete a single or multiple permission policies and roles |
- Kubernetes permissions
Name | Resource type | Policy | Description |
---|---|---|---|
|
Allows user or role to access the proxy endpoint |
- OCM permissions
-
Basic OCM permissions only restrict access to the cluster view, but they do not prevent access to the Kubernetes clusters in the resource view. For more effective permissions, consider applying a conditional policy to restrict access to catalog entities that are of type
kubernetes-cluster
. Access restriction is dependent on the set of permissions granted to a role. For example, if the role had full permissions (read
,update
, anddelete
), then you must specify all its permissions in thepermissionMapping
field.
Example permissionMapping definition
result: CONDITIONAL roleEntityRef: 'role:default/<YOUR_ROLE>' pluginId: catalog resourceType: catalog-entity permissionMapping: - read - update - delete conditions: not: rule: HAS_SPEC resourceType: catalog-entity params: key: type value: kubernetes-cluster
Name | Resource type | Policy | Description |
---|---|---|---|
|
read |
Allows user or role to read from the OCM plugin | |
|
read |
Allows user or role to read the cluster information in the OCM plugin |
- Topology permissions
Name | Resource type | Policy | Description |
---|---|---|---|
|
read |
Allows user or role to view the topology plugin | |
|
Allows user or role to access the proxy endpoint, allowing them to read pod logs and events within RHDH |
4.1. Permission policies configuration
There are two approaches to configure the permission policies in Red Hat Developer Hub, including:
- Configuration of permission policies administrators
- Configuration of permission policies defined in an external file
4.1.1. Configuration of permission policies defined in an external file
You can configure the permission policies before starting the Red Hat Developer Hub. If permission policies are defined in an external file, then you can import the same file in the Developer Hub. You must define the permission policies using the following Casbin rules format:
--- `p, <ROLE>, <PERMISSION_NAME or PERMISSION_RESOURCE_TYPE>, <PERMISSION_POLICY_ACTION>, <ALLOW or DENY>` ---
You can define roles using the following Casbin rules format:
--- `g, <USER or GROUP>, <ROLE>` ---
For information about the Casbin rules format, see Basics of Casbin rules.
The following is an example of permission policies configuration:
--- `p, role:default/guests, catalog-entity, read, allow`
p, role:default/guests, catalog.entity.create, create, allow
g, user:default/<USER_TO_ROLE>, role:default/guests
g, group:default/<GROUP_TO_ROLE>, role:default/guests
---
If a defined permission does not contain an action associated with it, then add use
as a policy. See the following example:
--- `p, role:default/guests, kubernetes.proxy, use, allow` ---
You can define the policy.csv
file path in the app-config.yaml
file:
permission: enabled: true rbac: policies-csv-file: /some/path/rbac-policy.csv
You can use an optional configuration value that enables reloading the CSV file without restarting the Developer Hub instance.
Set the value of the policyFileReload
option in the app-config.yaml
file:
# ... permission: enabled: true rbac: policies-csv-file: /some/path/rbac-policy.csv policyFileReload: true # ...
4.1.1.1. Mounting policy.csv
file using the Developer Hub Operator
When the Red Hat Developer Hub is deployed with the Operator, you can add your policy.csv
file using the Developer Hub Operator by creating a ConfigMap
and mounting it through your Custom Resource (CR).
Prerequisites
- You are logged in to your OpenShift Container Platform account using the OpenShift Container Platform web console.
- Red Hat Developer Hub is installed and deployed using the Operator.
- You have added a custom configuration file to OpenShift Container Platform. For more information, see Adding a custom configuration file to OpenShift Container Platform.
Procedure
In OpenShift Container Platform, create a ConfigMap to hold the policies as shown in the following example:
Example
ConfigMap
kind: ConfigMap apiVersion: v1 metadata: name: rbac-policy data: rbac-policy.csv: | p, role:default/guests, catalog-entity, read, allow p, role:default/guests, catalog.entity.create, create, allow g, user:default/<YOUR_USER>, role:default/guests
Update the policy path in your custom
app-config.yaml
ConfigMap as follows:Example
app-config.yaml
filepermission: enabled: true rbac: policies-csv-file: ./rbac-policy.csv
- From the Developer perspective in the OpenShift Container Platform web console, select the Topology view.
- Click the overflow menu for the Red Hat Developer Hub instance that you want to use and select Edit Backstage to load the YAML view of the Red Hat Developer Hub instance.
In the CR, enter the name of the custom
rbac-policy
ConfigMap as the value for thespec.application.extraFiles.configMaps
field. For example:Example custom resource
apiVersion: rhdh.redhat.com/v1alpha1 kind: Backstage metadata: name: example spec: application: appConfig: mountPath: /opt/app-root/src configMaps: - name: app-config-rhdh extraEnvs: secrets: - name: secrets-rhdh extraFiles: mountPath: /opt/app-root/src configMaps: - name: rbac-policy replicas: 1 route: enabled: true database: enableLocalDb: true
- Click Save.
Verification
- Navigate back to the Topology view and wait for the Red Hat Developer Hub pod to start.
- Click the Open URL icon to access the Red Hat Developer Hub platform with the updated configuration settings.
4.1.1.2. Mounting policy.csv
file to the Developer Hub Helm chart
When the Red Hat Developer Hub is deployed with the Helm chart, you must define the policy.csv
file by mounting it to the Developer Hub Helm chart.
You can add your policy.csv
file to the Developer Hub Helm Chart by creating a configMap
and mounting it.
Prerequisites
- You are logged in to your OpenShift Container Platform account using the OpenShift Container Platform web console.
- Red Hat Developer Hub is installed and deployed using Helm Chart.
Procedure
In OpenShift Container Platform, create a ConfigMap to hold the policies as shown in the following example:
Example
ConfigMap
kind: ConfigMap apiVersion: v1 metadata: name: rbac-policy namespace: rhdh data: rbac-policy.csv: | p, role:default/guests, catalog-entity, read, allow p, role:default/guests, catalog.entity.create, create, allow g, user:default/<YOUR_USER>, role:default/guests
- In the Developer Hub Helm Chart, go to Root Schema → Backstage chart schema → Backstage parameters → Backstage container additional volume mounts.
Select Add Backstage container additional volume mounts and add the following values:
-
mountPath:
opt/app-root/src/rbac
-
Name:
rbac-policy
-
mountPath:
Add the RBAC policy to the Backstage container additional volumes in the Developer Hub Helm Chart:
-
name:
rbac-policy
configMap
-
defaultMode:
420
-
name:
rbac-policy
-
defaultMode:
-
name:
Update the policy path in the
app-config.yaml
file as follows:Example
app-config.yaml
filepermission: enabled: true rbac: policies-csv-file: ./rbac/rbac-policy.csv
Chapter 5. Conditional policies in Red Hat Developer Hub
The permission framework in Red Hat Developer Hub provides conditions, supported by the RBAC backend plugin (backstage-plugin-rbac-backend
). The conditions work as content filters for the Developer Hub resources that are provided by the RBAC backend plugin.
The RBAC backend API stores conditions assigned to roles in the database. When you request to access the frontend resources, the RBAC backend API searches for the corresponding conditions and delegates them to the appropriate plugin using its plugin ID. If you are assigned to multiple roles with different conditions, then the RBAC backend merges the conditions using the anyOf
criteria.
- Conditional criteria
A condition in Developer Hub is a simple condition with a rule and parameters. However, a condition can also contain a parameter or an array of parameters combined by conditional criteria. The supported conditional criteria includes:
-
allOf
: Ensures that all conditions within the array must be true for the combined condition to be satisfied. -
anyOf
: Ensures that at least one of the conditions within the array must be true for the combined condition to be satisfied. -
not
: Ensures that the condition within it must not be true for the combined condition to be satisfied.
-
- Conditional object
The plugin specifies the parameters supported for conditions. You can access the conditional object schema from the RBAC API endpoint to understand how to construct a conditional JSON object, which is then used by the RBAC backend plugin API.
A conditional object contains the following parameters:
Table 5.1. Conditional object parameters
Parameter Type Description result
String
Always has the value
CONDITIONAL
roleEntityRef
String
String entity reference to the RBAC role, such as
role:default/dev
pluginId
String
Corresponding plugin ID, such as
catalog
permissionMapping
String array
Array permission actions, such as
['read', 'update', 'delete']
resourceType
String
Resource type provided by the plugin, such as
catalog-entity
conditions
JSON
Condition JSON with parameters or array parameters joined by criteria
- Conditional policy aliases
The RBAC backend plugin (
backstage-plugin-rbac-backend
) supports the use of aliases in conditional policy rule parameters. The conditional policy aliases are dynamically replaced with the corresponding values during policy evaluation. Each alias in conditional policy is prefixed with a$
sign indicating its special function.The supported conditional aliases include:
-
$currentUser
: This alias is replaced with the user entity reference for the user who requests access to the resource. For example, if user Tom from the default namespace requests access,$currentUser
becomesuser:default/tom
.
-
Example conditional policy object with $currentUser
alias
{ "result": "CONDITIONAL", "roleEntityRef": "role:default/developer", "pluginId": "catalog", "resourceType": "catalog-entity", "permissionMapping": ["delete"], "conditions": { "rule": "IS_ENTITY_OWNER", "resourceType": "catalog-entity", "params": { "claims": ["$currentUser"] } } }
-
$ownerRefs
: This alias is replaced with ownership references, usually as an array that includes the user entity reference and the user’s parent group entity reference. For example, for user Tom from team-a,$ownerRefs
becomes['user:default/tom', 'group:default/team-a']
.
Example conditional policy object with $ownerRefs
alias
{ "result": "CONDITIONAL", "roleEntityRef": "role:default/developer", "pluginId": "catalog", "resourceType": "catalog-entity", "permissionMapping": ["delete"], "conditions": { "rule": "IS_ENTITY_OWNER", "resourceType": "catalog-entity", "params": { "claims": ["$ownerRefs"] } } }
5.1. Conditional policies definition
You can access API endpoints for conditional policies in Red Hat Developer Hub. For example, to retrieve the available conditional rules, which can help you define these policies, you can access the GET [api/plugins/condition-rules]
endpoint.
The api/plugins/condition-rules
returns the condition parameters schemas, for example:
[ { "pluginId": "catalog", "rules": [ { "name": "HAS_ANNOTATION", "description": "Allow entities with the specified annotation", "resourceType": "catalog-entity", "paramsSchema": { "type": "object", "properties": { "annotation": { "type": "string", "description": "Name of the annotation to match on" }, "value": { "type": "string", "description": "Value of the annotation to match on" } }, "required": [ "annotation" ], "additionalProperties": false, "$schema": "http://json-schema.org/draft-07/schema#" } }, { "name": "HAS_LABEL", "description": "Allow entities with the specified label", "resourceType": "catalog-entity", "paramsSchema": { "type": "object", "properties": { "label": { "type": "string", "description": "Name of the label to match on" } }, "required": [ "label" ], "additionalProperties": false, "$schema": "http://json-schema.org/draft-07/schema#" } }, { "name": "HAS_METADATA", "description": "Allow entities with the specified metadata subfield", "resourceType": "catalog-entity", "paramsSchema": { "type": "object", "properties": { "key": { "type": "string", "description": "Property within the entities metadata to match on" }, "value": { "type": "string", "description": "Value of the given property to match on" } }, "required": [ "key" ], "additionalProperties": false, "$schema": "http://json-schema.org/draft-07/schema#" } }, { "name": "HAS_SPEC", "description": "Allow entities with the specified spec subfield", "resourceType": "catalog-entity", "paramsSchema": { "type": "object", "properties": { "key": { "type": "string", "description": "Property within the entities spec to match on" }, "value": { "type": "string", "description": "Value of the given property to match on" } }, "required": [ "key" ], "additionalProperties": false, "$schema": "http://json-schema.org/draft-07/schema#" } }, { "name": "IS_ENTITY_KIND", "description": "Allow entities matching a specified kind", "resourceType": "catalog-entity", "paramsSchema": { "type": "object", "properties": { "kinds": { "type": "array", "items": { "type": "string" }, "description": "List of kinds to match at least one of" } }, "required": [ "kinds" ], "additionalProperties": false, "$schema": "http://json-schema.org/draft-07/schema#" } }, { "name": "IS_ENTITY_OWNER", "description": "Allow entities owned by a specified claim", "resourceType": "catalog-entity", "paramsSchema": { "type": "object", "properties": { "claims": { "type": "array", "items": { "type": "string" }, "description": "List of claims to match at least one on within ownedBy" } }, "required": [ "claims" ], "additionalProperties": false, "$schema": "http://json-schema.org/draft-07/schema#" } } ] } ... <another plugin condition parameter schemas> ]
The RBAC backend API constructs a condition JSON object based on the previous condition schema.
5.1.1. Examples of conditional policies
In Red Hat Developer Hub, you can define conditional policies with or without criteria. You can use the following examples to define the conditions based on your use case:
- A condition without criteria
Consider a condition without criteria displaying catalogs only if user is a member of the owner group. To add this condition, you can use the catalog plugin schema
IS_ENTITY_OWNER
as follows:Example condition without criteria
{ "rule": "IS_ENTITY_OWNER", "resourceType": "catalog-entity", "params": { "claims": ["group:default/team-a"] } }
In the previous example, the only conditional parameter used is
claims
, which contains a list of user or group entity references.You can apply the previous example condition to the RBAC REST API by adding additional parameters as follows:
{ "result": "CONDITIONAL", "roleEntityRef": "role:default/test", "pluginId": "catalog", "resourceType": "catalog-entity", "permissionMapping": ["read"], "conditions": { "rule": "IS_ENTITY_OWNER", "resourceType": "catalog-entity", "params": { "claims": ["group:default/team-a"] } } }
- A condition with criteria
Consider a condition with criteria, which displays catalogs only if user is a member of owner group OR displays list of all catalog user groups.
To add the criteria, you can add another rule as
IS_ENTITY_KIND
in the condition as follows:Example condition with criteria
{ "anyOf": [ { "rule": "IS_ENTITY_OWNER", "resourceType": "catalog-entity", "params": { "claims": ["group:default/team-a"] } }, { "rule": "IS_ENTITY_KIND", "resourceType": "catalog-entity", "params": { "kinds": ["Group"] } } ] }
NoteRunning conditions in parallel during creation is not supported. Therefore, consider defining nested conditional policies based on the available criteria.
Example of nested conditions
{ "anyOf": [ { "rule": "IS_ENTITY_OWNER", "resourceType": "catalog-entity", "params": { "claims": ["group:default/team-a"] } }, { "rule": "IS_ENTITY_KIND", "resourceType": "catalog-entity", "params": { "kinds": ["Group"] } } ], "not": { "rule": "IS_ENTITY_KIND", "resourceType": "catalog-entity", "params": { "kinds": ["Api"] } } }
You can apply the previous example condition to the RBAC REST API by adding additional parameters as follows:
{ "result": "CONDITIONAL", "roleEntityRef": "role:default/test", "pluginId": "catalog", "resourceType": "catalog-entity", "permissionMapping": ["read"], "conditions": { "anyOf": [ { "rule": "IS_ENTITY_OWNER", "resourceType": "catalog-entity", "params": { "claims": ["group:default/team-a"] } }, { "rule": "IS_ENTITY_KIND", "resourceType": "catalog-entity", "params": { "kinds": ["Group"] } } ] } }
The following examples can be used with Developer Hub plugins. These examples can help you determine how to define conditional policies:
Conditional policy defined for Keycloak plugin
{ "result": "CONDITIONAL", "roleEntityRef": "role:default/developer", "pluginId": "catalog", "resourceType": "catalog-entity", "permissionMapping": ["update", "delete"], "conditions": { "not": { "rule": "HAS_ANNOTATION", "resourceType": "catalog-entity", "params": { "annotation": "keycloak.org/realm", "value": "<YOUR_REALM>" } } } }
The previous example of Keycloak plugin prevents users in the role:default/developer
from updating or deleting users that are ingested into the catalog from the Keycloak plugin.
In the previous example, the annotation keycloak.org/realm
requires the value of <YOUR_REALM>
.
Conditional policy defined for Quay plugin
{ "result": "CONDITIONAL", "roleEntityRef": "role:default/developer", "pluginId": "scaffolder", "resourceType": "scaffolder-action", "permissionMapping": ["use"], "conditions": { "not": { "rule": "HAS_ACTION_ID", "resourceType": "scaffolder-action", "params": { "actionId": "quay:create-repository" } } } }
The previous example of Quay plugin prevents the role role:default/developer
from using the Quay scaffolder action. Note that permissionMapping
contains use
, signifying that scaffolder-action
resource type permission does not have a permission policy.
For more information about permissions in Red Hat Developer Hub, see Chapter 4, Permission policies in Red Hat Developer Hub.
5.2. Configuring conditional policies defined in an external file
You can configure and manage conditional policies that are defined in an external file. To define conditional policies, you can directly edit the configuration files and pass them to Developer Hub, instead of using the Developer Hub web UI or API. You can configure Developer Hub to use these files instead of the default files.
Prerequisites
- You are logged in to your OpenShift Container Platform account using the OpenShift Container Platform web console.
You have defined roles and associated policies in a CSV file that serves as a basis for creating roles and permissions. Ensure that you mount the CSV file to Developer Hub.
For more information, see Conditional policies definition and Configuration of permission policies defined in an external file.
Procedure
Define conditional policies in a YAML file, which includes role references, permission mappings, and conditions.
The following is an example of a YAML file defining conditional policies:
Example YAML file defining conditional policies
--- result: CONDITIONAL roleEntityRef: 'role:default/test' pluginId: catalog resourceType: catalog-entity permissionMapping: - read - update conditions: rule: IS_ENTITY_OWNER resourceType: catalog-entity params: claims: - 'group:default/team-a' - 'group:default/team-b' --- result: CONDITIONAL roleEntityRef: 'role:default/test' pluginId: catalog resourceType: catalog-entity permissionMapping: - delete conditions: rule: IS_ENTITY_OWNER resourceType: catalog-entity params: claims: - 'group:default/team-a'
In OpenShift Container Platform, create a ConfigMap to hold the policies as shown in the following example:
Example ConfigMap
kind: ConfigMap apiVersion: v1 metadata: name: rbac-conditional-policy namespace: rhdh data: rbac-policy.yaml: | p, role:default/guests, catalog-entity, read, allow result: CONDITIONAL roleEntityRef: 'role:default/test' pluginId: catalog resourceType: catalog-entity permissionMapping: - read - update conditions: rule: IS_ENTITY_OWNER resourceType: catalog-entity params: claims: - 'group:default/team-a' - 'group:default/team-b'
Open
app-config.yaml
file and specify the path toconditionalPoliciesFile
as shown in the following example:Example
app-config.yaml
filepermission: enabled: true rbac: conditionalPoliciesFile: /some/path/conditional-policies.yaml
To enable automatic reloading of the policy file without restarting the application, add the
policyFileReload
option and set it totrue
:Example
app-config.yaml
filepermission: enabled: true rbac: conditionalPoliciesFile: /some/path/conditional-policies.yaml policies-csv-file: /some/path/rbac-policy.csv policyFileReload: true
Optional: Define nested conditional policies in the YAML file as needed.
Example for nested conditional policies
{ "result": "CONDITIONAL", "roleEntityRef": "role:default/developer", "pluginId": "catalog", "resourceType": "catalog-entity", "permissionMapping": ["delete"], "conditions": { "allOf": [ { "anyOf": [ { "rule": "IS_ENTITY_KIND", "resourceType": "catalog-entity", "params": { "kinds": [ "group" ] } }, { "rule": "IS_ENTITY_OWNER", "resourceType": "catalog-entity", "params": { "claims": [ "$ownerRefs" ] } } ] }, { "not": { "rule": "IS_ENTITY_KIND", "resourceType": "catalog-entity", "params": { "kinds": [ "api" ] } } } ] } }
In the previous example, the
role:default/developer
is granted the condition to delete catalog entities only if they are the entity owner or if the catalog entity belongs to a group. However, this condition does not apply if the catalog entity is an API.
Chapter 6. User statistics in Red Hat Developer Hub
In Red Hat Developer Hub, the licensed-users-info-backend
plugin provides statistical information about the logged-in users using the Web UI or REST API endpoints.
The licensed-users-info-backend
plugin enables administrators to monitor the number of active users on Developer Hub. Using this feature, organizations can compare their actual usage with the number of licenses they have purchased. Additionally, you can share the user metrics with Red Hat for transparency and accurate licensing.
The licensed-users-info-backend
plugin is enabled by default. This plugin enables a Download User List link at the bottom of the Administration → RBAC tab.
6.1. Downloading active users list in Red Hat Developer Hub
You can download the list of users in CSV format using the Developer Hub web interface.
Prerequisites
-
RBAC plugins (
@janus-idp/backstage-plugin-rbac
and@janus-idp/backstage-plugin-rbac-backend
) must be enabled in Red Hat Developer Hub. - An administrator role must be assigned.
Procedure
- In Red Hat Developer Hub, navigate to Administration and select the RBAC tab.
- At the bottom of the RBAC page, click Download User List.
- Optional: Modify the file name in the Save as field and click Save.
- To access the downloaded users list, go to the Downloads folder on your local machine and open the CSV file.