Previous Entry Share Next Entry
VCS: access control system
and_cesbo
Чтобы управлять доступом можно использовать различные решения gitosys, gitolite, mercurial-server, но эти решения работают через SSH, что не всегда удобно (должен быть ключ). В добавок не хватает гибкости у подобных решений.

Основные требования:
  • управления правами через веб интерфейс
  • контроль прав на чтение/запись
  • публичный/приватный проект
  • доступ по логину/паролю (HTTPS)
  • все данные (информация о проекте и пользователях) должны храниться в базе (MySQL)
Для решения этой задачи на cesbo.com сделал следующую систему...



Собрал nginx с модулем Auth Request.
Параметры конфигурации nginx для сборки:
./configure --prefix=/usr \
--conf-path=/etc/nginx/nginx.conf \
--error-log-path=/var/log/nginx/error.log \
--http-client-body-temp-path=/var/lib/nginx/body \
--http-fastcgi-temp-path=/var/lib/nginx/fastcgi \
--http-log-path=/var/log/nginx/access.log \
--http-proxy-temp-path=/var/lib/nginx/proxy \
--lock-path=/var/lock/nginx.lock \
--pid-path=/var/run/nginx.pid \
--with-http_dav_module --with-http_gzip_static_module \
--with-http_realip_module --with-http_stub_status_module \
--with-http_ssl_module --with-http_sub_module --with-ipv6 \
--add-module=../ngx_http_auth_request_module/

Кусок файла конфигурации для nginx:
server {
listen 443;
server_name cesbo.com;
...
location = /auth {
include /etc/nginx/uwsgi_params;
uwsgi_pass unix:/tmp/uwsgi.sock;
}
location /hg {
set $project "";
if ( $uri ~ "^/hg/([a-zA-Z0-9_\-]+).*$" ) {
set $project $1;
}
if ( $project = "" ) {
return 403;
}
if ( !-d /var/projects/$repo ) {
return 404;
}
auth_request /auth;
include /etc/nginx/proxy_params;
proxy_redirect off;
proxy_pass http://127.0.0.1:8002;
}
}

Вся магия кроется в uwsgi-скрипте. Его задача проверить данные полученные от nginx. И вернуть код в зависимости от результата проверки (200,401,403):
#!/usr/bin/python
 
# configure
db_host = 'localhost'
db_name = 'authdb'
db_user = 'test'
db_pass = 'test'
 
role_id_read = 1 # member
role_id_write = 2 # developer
 
#
 
import re
import os
import base64
import MySQLdb
 
db_conn = MySQLdb.connect(host = db_host,
user = db_user,
passwd = db_pass,
db = db_name)
 
def get_project_info(project_name):
global db_conn
cursor = db_conn.cursor()
query = 'SELECT id,is_private FROM projects WHERE name=%s'
cursor.execute(query, (project_name, ))
row = cursor.fetchone()
cursor.close()
return row
 
def get_role_id(project_id, username, password):
global db_conn
cursor = db_conn.cursor()
query = 'SELECT id FROM users WHERE name=%s AND pass=sha1(%s)'
cursor.execute(query, (username, password, ))
row = cursor.fetchone()
if row == None:
return -1
user_id = row[0]
query = 'SELECT role_id FROM project_user_perm WHERE project_id=%s AND user_id=%s'
cursor.execute(query, (project_id, user_id, ))
row = cursor.fetchone()
cursor.close()
if row == None:
return 0
return row[0]
 
def can_user_read(project_id, username, password):
role_id = get_role_id(project_id, username, password)
if role_id == -1:
return -1
if role_id == role_id_read or role_id == role_id_write :
return 1
return 0
 
def can_user_write(project_id, username, password):
role_id = get_role_id(project_id, username, password)
if role_id == -1:
return -1
if role_id == role_id_write :
return 1
return 0
 
def ok200(callback):
callback('200 OK', [])
return []
 
def err401(callback):
callback('401 Unauthorized', [('WWW-Authenticate', 'Basic realm="Restrict"')])
return []
 
def err403(callback):
callback('403 Forbidden', [])
return []
 
def application(env, resp):
req_uri = env.get('REQUEST_URI', '')
m = re.match('^/(w+)/(w+).*$', req_uri)
 
if m == None:
return err403(resp)
project_name = m.group(2)
 
project_info = get_project_info(project_name)
if project_info == None:
return err403(resp)
 
req_method = env.get('REQUEST_METHOD', '')
if req_method == 'GET' and project_info[1] == 0:
return ok200(resp)
 
req_http_auth = env.get('HTTP_AUTHORIZATION', '')
if req_http_auth == '':
return err401(resp)
 
m = re.match('^Basic ([a-zA-Z0-9=]+)$', req_http_auth)
if m == None:
return err403(resp)
userpass = base64.b64decode(m.group(1))
m = re.match('^(w+):(S+)$', userpass)
if m == None:
return err403(resp)
username = m.group(1)
password = m.group(2)
 
result = 0
if req_method == 'GET':
result = can_user_read(project_info[0], username, password)
else:
result = can_user_write(project_info[0], username, password)
 
if result == -1:
return err401(resp)
elif result == 1:
return ok200(resp)
else:
return err403(resp)
 


Скрипт запускается с помощью uWSGI:
uwsgi -s /tmp/uwsgi.sock \
--wsgi-file /var/projects/vcs_auth/vcs_auth_app.py \
--uid www-data --gid www-data \
--pidfile /var/run/vcs_auth_app.pid \
-d /var/log/uwsgi/vcs_auth_app.log

Структура базы данных:


?

Log in

No account? Create an account