Сделай сам ACL. История разработки и написания несложной системы контроля доступов

Есть у нас в поддержке внутренний проект, где используется очень неудобная система контроля доступа.
Так называемая ACL.
Неудобная она тем, что для предоставления любого доступа администратору нужно совершать очень много действий для каждого отдельного ресурса требующего доступа.
Ну и после того, как жалобы пользователей начали достигать критической отметки мне была поставлена задача упростить, либо переделать систему.
Посмотрел я существующий код, желания в себе не почувствовал для переделки, — и решил подыскать готовое решение. Сразу скажу, — я большой противник «велосипедов», прежде чем что-то написать я всегда стараюсь найти готовое и подходящее решение. Так было и на этот раз, но достаточно безуспешно. Несколько решений есть, например, в коллекции PEAR, но подходящего я не выбрал. Много кода устарело (мы используем версию PHP 5.3), поэтому было принято решение писать ACL самостоятельно.
Об этом и хочу рассказать. Примеры кода постараюсь приводить минимально, — расскажу только идею и принципы работы.
Но весь код библиотеки желающие смогут взять upd-2011 ( utf8 ), посмотреть и, при желании, использовать.
Для начала я решил уяснить для себя общий принцип её работы, для чего представил себе такую аналогию с реальным миром:
КПП. (контрольно-пропускной пункт)
Сидит бабуська, перед ней журнал. В журнале есть список помещений, в которые проходят посетители через КПП.
На самом деле помещений на объекте много, в журнале же у бабуськи их меньше. Там только те, куда нужно контролировать доступ.
К каждому помещению, которое есть в журнале приложен список посетителей, которым разрешено туда входить,
где кроме конкретных людей указаны и группы. (Например, — в кабинет 303 разрешён вход всем сотрудникам тех. отдела).
Бабуська руководствуясь данным журналом, и документами на руках у посетителей осуществляет контроль доступа на объект.
Это клиентская часть системы.
Теперь административная часть:
Периодически на КПП приходит начальник охраны. Он вносит изменения в журнал.
Требования, которые я поставил перед будущей системой:
Требования определены, становится ясной и структура базы данных:
Таблицы: (указан минимум необходимых полей)
Схема базы данных для MySQL находится в архиве с библиотекой в директории docs.
Здесь сразу нужно оговорить, что систему авторизации я написал отдельно, сначала вообще использовал PEAR::Auth, и именно под неё делал ACL, но в существующей версии PEAR::Auth тоже достаточно много устаревшего (для PHP 5.3) кода, поэтому и библиотеку для авторизации решил написать собственную. Она получилась вообще простейшей. В рамках данной статьи описывать не стану. Скажу лишь только, что я постарался от системы авторизации также абстрагироваться, может быть использована любая система, единственное условие, — она должна реализовывать интерфейс IAuth (он находится в архиве описываемой библиотеки)
Кроме того, — описанная схемы базы данных нужна была временно, только для организации тестирования разработки, так как сразу же решено было писать систему не зависящую от контейнеров хранения данных. То-есть реально данные с пользователями, группами, ресурсами, правами могут храниться в разных местах, в разных базах данных, у кого-то может просто в файлах, у кого-то даже непосредственно где-то в коде (ну мало ли, ресурс такой маленький, заведомо известно, что пара-тройка пользователей будут работать, может база данных и не нужна). Смысл только в том, что система ничего не знает, и ей не нужно знать где и как хранятся данные.
Отсюда следует, что система сразу должна быть абстрагирована от хранения данных, и работать с данными через адаптеры.
Поэтому, — необходимо описать интерфейс адаптера, и реализовать пока один адаптер, скажем для MySQL.
Интерфейс для адаптера примерно такой:
и так далее и тому подобное.
Должно быть ясно какие действия понадобятся от адаптеров.
Пример реализации одного метода в адаптере для MySQL
Сама система имеет два класса, наследников общего предка:
1. Клиент
2. Администратор
Задача клиента:
Проверить права на запрашиваемый ресурс для текущего пользователя, и либо дать ему доступ, либо отправить на страницу «Доступ запрещён».
Кстати, — выходы из системы при наличии отсутствия доступа определяются в отдельных функциях, а в систему при конфигурации передаются только имена этих функций.
Есть необходимость только в двух функциях: отправить на страницу логина() и доступ запрещён().
Я, кстати, как правило в своих собственных проектах обычно делаю одну страницу для ошибок 403 и 404, — «Страница не найдена».
Мне кажется, — это более надёжный вариант, потому как при отображении «Доступ запрещён» (403 ошибка), — потенциальному злоумышленнику мы уже предоставляем лишнюю информацию о том, что «… такая страница существует, только тебе, Вася, туда не положено. «
Задача административной части:
Управление системой.
Добавление групп, пользователей в группы, ресурсов, прав, удаление всего перечисленного и так далее.
Использование системы в клиентском коде.
Система в клиентском коде используется очень просто, например так:
index.php (точка входа приложения)
То-есть до работы клиентского приложения код либо вообще не дойдёт, в случае отсутствия прав у пользователя, запросившего ресурс (отобразится, например, страница 404), либо приложение продолжит работу.
В конструкторе объекта $acl мы передаём необходимые параметры, это как правило массив, который в простейшем случае вообще может отсутствовать, так как в самой системе есть значения по умолчанию для большинства его элементов.
В методе start() происходит примерно следующая работа:
Я всегда стараюсь по максимуму покрывать свой код тестами, так было и в этот раз. Были написаны
юнит-тесты (находятся в архиве), после чего был реализован весь задуманный функционал.
Вот, в принципе и всё, о чём хотелось рассказать.
Код всей библиотеки приложил к статье.
Буду очень рад, если кому-то пригодится подобный опыт. Также с удовольствием услышу критику слабых мест предложенного решения.
Урок 39. Списки контроля доступа ACL (Access List Control)
Запретить/разрешить мы можем на основе IP адресов, портов и задействованных протоколов. На этом принципе и работают списки управления доступом ACL ( Access Control List ).
Другим примером использования списков доступа является запрет поступающих на маршрутизатор пакетов протокола ICMP. Как мы знаем с помощью ICMP работают утилиты Ping, Traceroute/Tracert. С помощью данных утилит можно просканировать сеть, а это нежелательно с точки зрения политики безопасности каждой сети.
Списки доступа позволяют фильтровать трафик на входе и выходе интерфейса маршрутизатора.
И в чем же разница?
Разница в том, что на входе весь поступающий трафик подвергается фильтрации. Нежелательные пакеты отбрасываются и уже только потом остальные пакеты маршрутизируются:
Когда ACL настроены на выходе интерфейса, то трафик фильтруется сразу же после процесса маршрутизации:
Данная особенность может быть полезна при определенных обстоятельствах.
Так что же из себя представляют списки доступа и как они работают?
Списки доступа содержат просто набор инструкций какие порты и адреса блокировать, а какие наоборот разрешить. Этих инструкций может от нескольких единиц до десятков.
При поступлении трафика проверка списка доступа начинается сверху вниз, то есть с первой инструкции. Как только будет найдено совпадение проверка списка прекратится и будет выполнено действие, указанное в инструкции (заблокировать или пропустить).
Внизу списка всегда следует неявная инструкция по блокировке всего трафика. Данная инструкция добавляется автоматически самой системой. В настройках она не видна, но нужно знать, что она есть.
Cisco IOS поддерживает 3 типа ACL:
Стандартные списки позволяют проверять только IP адрес отправителя. Например,в нашем 1-ом примере доступ в интернет Алисе и Кате можно закрыть с помощью стандартного списка. В данном случае блокируется абсолютно весь трафик, проходящий через маршрутизатор. Стандартные списки доступа рекомендуется устанавливать как можно ближе к отправителю.
Расширенные списки позволяют фильтровать пакеты на основе адресов, портов и протоколов получателя и отправителя.
Например, Алисе разрешается использовать электронную почту, но все еще запрещается использовать остальной доступ в интернет. Кате же разрешены звонки по интернет и запрещены все остальные службы интернет.
Электронная почта работает по протоколам POP/SMTP (порты 110/ 25). Следовательно можно внести исключение в списки доступа исходя из выше описанных условий.
Расширенные списки рекомендуется устанавливать ближе к получателю.
Именованные списки являются теми же стандартными и расширенными ACL, однако предоставляют более гибкие возможности для редактирования (об этом немного позже).
Рассмотрим какие команды используются в каждом типе ACL, а затем применим их на примере.
Инструкция задается следующей командой:
Router(config)# access-list номер permit | deny IP_адрес_отправителя инвертированная_маска (wildcard mask)
Router(config)# access-list 1 deny 192.168.1.0 0.0.0.255
Router(config)# access-list 1 permit 10.1.0.0 0.0.255.255
Router(config)# access-list 1 deny any
Номер списка принимает значения от 1 до 99. Цифры не означают приоритет или упорядоченность. Это просто номер списка. Затем следует команда permit (разрешить) или deny (запретить). С помощью инвертированной маски (wildcard mask) мы можем определить диапазон адресов, на которые будет распространяться запрет/разрешение.
В первой команде мы запрещаем сеть 192.168.1.0/24, а во второй разрешаем сеть 10.1.0.0/16.
Таких команд (инструкций) можно добавлять сколько угодно. Как было отмечено ранее работа всегда начинается с самой первой команды и далее идет вниз по списку. В конце списка всегда стоит неявная команда запрещающая весь остальной трафик, поэтому необходимо это учитывать при планировании списков доступа. При добавлении новой команды в список она добавляется всегда в конец списка.
А как работает инвертированная маска (wildcard mask)?
Работа инвертированной маски основана на следующем принципе.
На тех битовых позициях, где установлен 0 IP адрес устройства должен совпадать с адресом, указанным в настройках ACL.
На тех битовых позициях, где установлена 1 IP адрес устройства может не совпадать с адресом, указанным в настройках ACL, то есть может принимать любые значения. Поясню сказанное на примерах.
Пример №1
Пример №2
Теперь у нас должны совпадать первые 2 октета полностью и первые 6 бит третьего октета. Поэтому маска у нас 0.0.3.255. А в настройках ACL мы укажем адрес 192.168.0.0. Можно, конечно, указать сразу 2 отдельные команды на каждый диапазон, с помощью маски мы можем сократить запись

Пример №3
После того, как список создан, необходимо определить направление (входящий или исходящий) трафика и на каком интерфейсе он будет фильтроваться:
Router(config-if)# ip access-group номер_применяемого_списка in | out
Алгоритм работы стандартного списка выглядит так:
Расширенный список доступа
Расширенные списки доступа имеют номера от 100 до 199.
Команды имеют довольно широкий набор опций, поэтому покажу наиболее общий пример команды:
Router(config)#access-list номер permit | deny протокол IP_адрес_отправителя инвертированная_маска порт_отправителя IP_адрес_получателя инвертированная_маска порт_получателя
Router(config)#access-list 100 permit tcp 192.168.1.0 0.0.0.255 eq 80 10.1.1.0 0.0.0.255 eq 443
Router(config)#access-list 100 deny tcp any host 172.16.1.5 gt 5000
А вот эта команда запрещает весь TCP трафик от любого хоста на конкретный хост с адресом 172.16.1.5. Причем запрет действует при условии, что запросы идут на порты получателя от 5001 и выше.
В конце списка всегда следует неявная команда, запрещающая весь трафик.
Не забудь определить интерфейс и направление трафика для фильтрации.
Вот как выглядит алгоритм работы расширенных списков:

Для просмотра настроек используй следующие команды:
Router# show running-config
Router# show ip access-lists
Router# show ip access-lists interface название_интерфейса
Ничем не отличаются от стандартных и расширенных списков, однако позволяют гибко редактировать вновь созданные списки.
Именованный список позволяет использовать названия списков вместо их номеров. Все введенные команды нумеруются, что позволяет легко добавлять и удалять команды.
Синтаксис команд представлен ниже.
Для стандартных списков:
Router(config)# ip access-list standard название
Router(config-std-nacl)# permit host IP_адрес_отправителя
Router(config-std-nacl)# deny IP_адрес_отправителя инвертированная_маска
Для расширенных списков:
Router(config)# ip access-list extended название
Router(config-ext-nacl)# permit ip IP_адрес_отправителя инвертированная_маска IP_адрес_получателя инвертированная_маска
Router(config-ext-nacl)# deny tcp IP_адрес_отправителя инвертированная_маска порт_отправителя IP_адрес_получателя инвертированная_маска порт_получателя
Чтобы удалить ненужную команду достаточно узнать ее номер. Чтобы узнать номер введи команду:
Router# show ip access-list название
а затем укажи ее номер при удалении:
Ну а чтобы добавить команду достаточно тоже указать номер и затем саму команду.
Обращаю твое внимание, что удаление и добавление строк списка происходит в режиме настройки ACL, например так:
Router(config-ext-nacl)# 5 deny ip any any
Пример использования списков доступа

Сеть состоит из 3 частей:
Именно по этому принципу и строятся сети предприятий.
Задача у нас следующая:
Пункт №1 можно решить с помощью расширенного списка на маршрутизаторе LAN_Router
На самом деле в данном примере серверы не имеет доступа к внутренним компьютерам, потому что подключены к различным интерфейсам маршрутизатора Firewall. Данный маршрутизатор ничего не знает о сети 192.168.1.0/24, поэтому не может перенаправить пакеты серверов к маршрутизатору LAN_Router. Однако достаточно маленькой ошибки в конфигурации и внутренние компьютеры станут доступны не только для DMZ, но и для внешней сети.
Поэтому список доступа на маршрутизаторе LAN_router безусловно необходим.
А какие ошибки могут привести к тому, что сеть LAN будет доступна для DMZ?
Например, на маршрутизаторе Firewall при настройке статического маршрута может быть по ошибке указан интерфейс, ведущий к маршрутизатору LAN_Router.
Пункты №2 и №3 не требуют списков доступа. Достаточно настроить правильно маршрутизацию и NAT на маршрутизаторе Firewall

Пункт №4. Внутренние пользователи уже имеют доступ в зону DMZ, однако связь будет работать только в одну сторону, так как мы закрыли доступ еще в п.1.
Как же сделать так, чтобы внутренние пользователи имели полноценный доступ ко всем серверам и в то же время запретить серверам доступ во внутреннюю сеть?
Пункт №5 решается с помощью стандартного списка
Role Based Access Control in PHP
Free JavaScript Book!
Write powerful, clean and maintainable JavaScript.
There are several different approaches when it comes to managing user permissions, and each have their own positives and negatives. For example, using bit masking is extremely efficient but also limits you to 32 or 64 permissions (the number of bits in a 32- or 64-bit integer). Another approach is to use an access control list (ACL), however you can only assign permissions to objects rather than to specific or meaningful operations.
In this article I will discuss my personal favorite approach: role based access control (RBAC). RBAC is a model in which roles are created for various job functions, and permissions to perform certain operations are then tied to roles. A user can be assigned one or multiple roles which restricts their system access to the permissions for which they have been authorized.
The downside to using RBAC is that if not properly managed, your roles and permissions can easily become a chaotic mess. In a rapidly changing business environment, it can be a job in itself to keep track of assigning appropriate roles to new employees and removing them in a timely manner from former employees or those switching positions. Additionally, identifying new roles for unique job duties and revising or removing requires regular review. Failure to properly manage your roles can open the door to many security risks.
I will begin by discussing the necessary database tables, then I’ll create two class files: ( Role.php ) which will perform a few tasks specific to roles, and ( PrivilegedUser.php ) that will extend your existing user class. Finally I’ll walk through some examples of how you might integrate the code into your application. Role management and user management go hand in hand, and so in this article I’ll assume that you already have some type of user authentication system in place.
Database
You need four tables to store role and permission information: the roles table stores a role ID and role name, the permissions table stores a permission ID and description, the role_perm table associates which permissions belong to which roles, and the user_role table associates which roles are assigned to which users.
Using this schema, you can have an unlimited number of roles and permissions and each user can be assigned multiple roles.
These are the CREATE TABLE statements for the database:
You don’t need to make any modifications to your users table to store role information as that information is stored separately in these new tables. Contrary to some other RBAC systems, a user here is not required to have a role by default; instead, the user simply won’t have any privileges until a role has been specifically assigned. Alternatively, it would be possible in the PrivilegedUser class to detect an empty role and respond with a default unprivileged role when required, or you could opt to write a short SQL script to copy over user IDs and initialize them by assigning a default unprivileged role.
Role Class
The primary focus of the Role class is to return a role object that is populated with each roles corresponding permissions. This will allow you to easily check whether a permission is available without having to perform redundant SQL queries with every request.
Use the following code to create Role.php :
The getRolePerms() method creates a new Role object based on a specific role ID, and then uses a JOIN clause to combine the role_perm and perm_desc tables. For each permission associated with the given role, the description is stored as the key and its value is set to true. The hasPerm() method accepts a permission description and returns the value based on the current object.
Privileged User Class
By creating a new class that extends your existing user class, you can reuse your existing code logic for managing users and then add some additional methods on top of those which are geared specifically towards working with privileges.
Use the following code to create the file PrivilegedUser.php :
With the preceding two classes in place, checking if a user has a specific privilege is as simple as follows:
Here the username is stored in the active session and a new PrivilegedUser object is created for that user on which the hasPrivilege() method can be called. Depending on the information in your database, your object output will look similar to the following:
Keeping Things Organized
One of the many benefits of using an OOP approach with RBAC is that it allows you to separate code logic and validation from object specific tasks. For example, you could add the following methods to your Role class to help manage role specific operations such as inserting new roles, deleting roles and so on:
Likewise, you could add onto your PrivilegedUser class with similar methods:
Because permissions are tied directly to the application’s underlying code logic, new permissions should be manually inserted into or deleted from the database as required. Roles on the other hand can be easily created, modified or deleted via an administration interface.
The more roles and permissions you have the more difficult things will be to manage; keeping the lists minimal is important, but sometimes the contrary is unavoidable. I can only advise that you use your best judgement and try not to get carried away.
Summary
You now have an understanding of role based access control and how to implement roles and permissions into an existing application. Furthermore, you’ve learned some tips to help manage your roles and to keep things well organized. As always, I encourage you to experiment and ask questions if you get stuck. We’re all here to learn from each other and I am happy to help when I can!
PHP ACL Library: Manage permission access control lists
This class can manage permission access control lists.
It provides classes for defining resources that will have restricted access, permissions that define different actions that can be done on resources, roles that define types of users that will have certain permissions to access resources.
The resources, permissions and roles can be added or removed. The whole set of objects can be serialized to a string so it can be saved and be loaded later.
![]() |
Nominee: 7x
Acl

Samshal\Acl adds a role based permission system for user authentication. In general, it provides a lightweight access control list for privileges and permission management.
Why you might need it
Access Control Lists allow an application to control access to its areas, they provide a flexible interface for creating Permissions, Roles, Resources and assigning the created permissions on roles.
This component is an implementation of an ACL, it makes it easy for you to get up and running with user authorization.
Class Features
Resources are objects which acts in accordance to the permissions defined on them by the ACLs. Roles are objects that requests access to resources and can be allowed or denied by the ACL layers. Permissions are just rules defined on Resources.
Metrics of master branch
License
This software is distributed under the MIT license. Please read LICENSE for information on the software availability and distribution.
Installation
Samshal\Acl is available via Composer/Packagist, so just add this line to your composer.json file:
Getting Started
Creating an ACL
Adding objects (Roles, Permissions and Resources) to the ACL.
The ACL provides an add method for adding new objects generically. In other words, to add a new role to the Acl, just pass in a Role Object to the ACL s add` method. You can also do the same for Resources and Permissions.
A Role Object is an instance of the \Samshal\Acl\Role\DefaultRole object or more generally, an object that implements the \Samshal\Acl\Role\RoleInterface and \Samshal\Acl\ObjectInterface contracts. It accepts the name of the Role to create as parameter and the description for the created role as optional second parameter.
Similarly Resource objects are instances of the \Samshal\Acl\Resource\DefaultResource object which also implements the \Samshal\Acl\Resource\ResourceInterface and \Samshal\Acl\ObjectInterface interfaces, Likewise for permissions, they must implement the \Samshal\Acl\Permission\PermissionInterface and the \Samshal\Acl\ObjectInterface contracts or be new instances of the \Samshal\Acl\Permission\DefaultPermission class.
Generally, Roles, Resources and Permissions are referred to as Objects. They must all implement the \Samshal\Acl\ObjectInterface contract.
Internally, the created objects are stored in Registries which are fully serializable. This makes it easy to transfer/get the objects from anywhere; a persistent storage, a database and anywhere else data can be stored/received. More on this later.
Cool right? The add methods (addRole, addResource, addPermission and add) are variadic, they can accept an unlimited number of arguments at a time. So we could even make our lives less more boring by doing this while adding the Roles
or this for the Permissions.
Setting Permissions.
The reason why this component exists is to set permissions on resources and grant/deny these permissions to roles. The snippet below gives an example of how to set these permissions.
Checking Permissions
To check the permission a role has on a certain resource, you can use a snippet similar to the following:
Keeping your ACL persistent and safe.
\Samshal\Acl stores objects including the permissions on objects in registries which are fully serializable. This means you can convert your entire acl into a string and store that in a database or session and make it exist infinitely until you are ready to destroy it or __never use it again__.
How-To
How to retrieve it
Other Interesting Features
\Samshal\acl allows roles to inherit permissions from other roles. The Acl component has an inherits method that accepts a Role object as parameter. You can also pass in a string, but it must be the id of an already existent role object.
Automatically grant permissions on all resources.
You can also call a Permission object without any parameter. This grants the permission in question on all resources defined within the ACL on the Role in session.
Maintainer of this Library
This library is currently developed and maintained by Samuel Adeshina
ROAD MAP
Road Map draft in progress.
HOW TO CONTRIBUTE
Support follows PSR-2 PHP coding standards.
Please report any issue you find in the issues page. Pull requests are welcome.















