Squid, Python и external_acl_type
В популярном прокси-сервере Squid реализована замечательная возможность проверять доступ пользователя к Интернет-ресурсам, используя сторонние программы. Отвечает за сие действо директива external_acl_type. С помощью неё становится возможным прикрутить к сквиду свои скрипты, написанные, например, на питоне. Обзор такого скрипта и самого механизма дальше.
Исходные данные:
Используемая база данных выглядит следующим образом:
В ней есть две таблицы-справочника и одна таблица, в которую и записываются необходимые разрешения доступа. Для сохранения целостности данных в таблице acl объявлены внешние ключи user_id и site_id, а так же изменено поведение по-умолчанию MySQL при работе с такими ключами. Без указания «ON UPDATE CASCADE ON DELETE CASCADE» становится невозможным удаление или изменения записи из таблиц users или sites без предварительной правки таблицы acl.
Создаваемая программа должна читать со стандартного ввода (stdin) данные, передаваемые ей сквидом, и печатать на стандартный вывод (stdout) одну из двух строк: OK или ERR. Получив OK, сквид пропустит пользователя на сайт, получив ERR, запретит доступ. Обе строки можно дополнить необязательными параметрами, например: ERR message=«Access denied» (описание параметров, не входит в рассматриваемую тему, за подробностями, добро пожаловать в squid.conf). Параметры, передающиеся программе описываются директивой сквида external_acl_type. Вот наиболее интересные для нас:
Вот код самой программы:
Небольшие пояснения по коду:
Теперь осталось сделать последнее, а именно прописать созданную acl в конфиг сквида:
После рестарта сквида в списке запущенных процессов появится наша программа:
Теперь заносим в БД правило, запрещающее пользователю testuser заходить на сайт ya.ru:
И проверяем работу программы, читая cache.log:
Как видно, всё работает.
Исходные данные:
- Есть сквид, с настроенной аутентификацией пользователей, логины и пароли хранятся в БД MySQL.
- Есть желание хранить разрешения для каждого пользователя в той же БД, не засоряя squid.conf
- Есть возможность решить поставленную задачу используя Python
- Всё, что явно не запрещено, разрешено
- В дальнейшем есть планы реализовать веб-интерфейс для редактирования ACL
Используемая база данных выглядит следующим образом:
CREATE TABLE users ( id INT NOT NULL AUTO_INCREMENT PRIMARY KEY, login VARCHAR(100), password VARCHAR(100), ) TYPE=innodb; CREATE TABLE sites ( id INT NOT NULL AUTO_INCREMENT PRIMARY KEY, url VARCHAR(100) ) TYPE=innodb; CREATE TABLE acl ( id INT NOT NULL AUTO_INCREMENT PRIMARY KEY, user_id INT NOT NULL, site_id INT NOT NULL, permit BOOLEAN, FOREIGN KEY (user_id) REFERENCES users(id) ON UPDATE CASCADE ON DELETE CASCADE, FOREIGN KEY (site_id) REFERENCES sites(id) ON UPDATE CASCADE ON DELETE CASCADE ) TYPE=innodb;
В ней есть две таблицы-справочника и одна таблица, в которую и записываются необходимые разрешения доступа. Для сохранения целостности данных в таблице acl объявлены внешние ключи user_id и site_id, а так же изменено поведение по-умолчанию MySQL при работе с такими ключами. Без указания «ON UPDATE CASCADE ON DELETE CASCADE» становится невозможным удаление или изменения записи из таблиц users или sites без предварительной правки таблицы acl.
Создаваемая программа должна читать со стандартного ввода (stdin) данные, передаваемые ей сквидом, и печатать на стандартный вывод (stdout) одну из двух строк: OK или ERR. Получив OK, сквид пропустит пользователя на сайт, получив ERR, запретит доступ. Обе строки можно дополнить необязательными параметрами, например: ERR message=«Access denied» (описание параметров, не входит в рассматриваемую тему, за подробностями, добро пожаловать в squid.conf). Параметры, передающиеся программе описываются директивой сквида external_acl_type. Вот наиболее интересные для нас:
- %LOGIN — имя пользователя
- %DST — адрес запрашиваемого сайта
external_acl_type python_test %LOGIN %DST /usr/local/bin/python_test.py
Вот код самой программы:
#!/usr/bin/python2.5 -u # -*- coding: utf-8 -*- import sys import MySQLdb def log(s): sys.stderr.write(s) def main(arg): db=MySQLdb.connect(user='squid', passwd='squid', db='squid', host='10.0.10.100') c=db.cursor() while 1: l = sys.stdin.readline().split() try: login = l[0] url = l[1] except: continue c.execute('SELECT a.permit FROM users u, sites s, acl a WHERE u.login=%s AND s.url=%s AND a.user_id = u.id AND a.site_id = s.id', [login, url]) r = c.fetchone() if r is None: permit = 1 # по умолчанию доступ разрешён else: permit = r[0] log('%s -> %s: %s\n' % (login, url, permit)) if permit: sys.stdout.write('OK\n') else: sys.stdout.write('ERR\n') c.close() if __name__ == '__main__': sys.exit(main(arg=sys.argv[1:]))
Небольшие пояснения по коду:
- Строка #!/usr/bin/python2.5 -u отключает буферизацию вывода в stdout, без этого необходимо после каждого вывода писать дополнительно sys.stdout.flush() иначе работать ничего не будет.
- Переодически на stdin валятся пустые строки, поэтому без обработки ввода программа будет постоянно вылетать, сквид постоянно делать рестарт и как результат не работать.
- Всё, записанное в stderr будет отображено в cache.log сквида, очень удобно во время отладки.
Теперь осталось сделать последнее, а именно прописать созданную acl в конфиг сквида:
acl PythonTest external python_test http_access allow PythonTest
После рестарта сквида в списке запущенных процессов появится наша программа:
25950 ? Ss 0:00 /usr/bin/python2.5 -u /usr/local/bin/python_test.py 25951 ? Ss 0:00 /usr/bin/python2.5 -u /usr/local/bin/python_test.py 25952 ? Ss 0:00 /usr/bin/python2.5 -u /usr/local/bin/python_test.py 25953 ? Ss 0:00 /usr/bin/python2.5 -u /usr/local/bin/python_test.py 25954 ? Ss 0:00 /usr/bin/python2.5 -u /usr/local/bin/python_test.py
Теперь заносим в БД правило, запрещающее пользователю testuser заходить на сайт ya.ru:
INSERT INTO users VALUES (1, 'testuser', 'testpassword', 1); INSERT INTO sites VALUES (2, 'ya.ru'); INSERT INTO acl VALUES (1, 1, 2, 0);
И проверяем работу программы, читая cache.log:
root@proxy:~# tail /var/log/squid/cache.log testuser -> ya.ru: 0 testuser -> habrahabr.ru: 1 testuser -> s009.radikal.ru: 1 testuser -> foss-fest.com: 1
Как видно, всё работает.
1 комментарий
Вот только хотелось бы «обработчик» на С или С++. Как никак должен быстрее работать чем питон.