...
 
Commits (7)
......@@ -3,3 +3,11 @@ venv*/
.coverage
htmlcov/
config.json
# Python byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# Copy of files for local docker
local/files/
# Rights service for X-tee self service portal
This simple service is used by X-tee self service portal to store user rights. You can use Docker to [test service locally](local/README.md).
## Install dependencies
Python virtual environment is an easy way to manage application dependencies. You will need to install support for python venv:
```bash
sudo apt-get install python3-venv
```
Application uses PostgreSQL as a database and Nginx for securing connections with TLS. Install them with a command:
```bash
sudo apt-get install postgresql nginx
```
## Create application user
```bash
sudo useradd xtss-rights
```
## Prepare application files
Create an application directory `/opt/xtss-rights`:
```bash
sudo mkdir -p /opt/xtss-rights
sudo chown -R xtss-rights:xtss-rights /opt/xtss-rights
```
Copy application files `rights.py`, `server.py`, `db.sql`, `db_user.sql` to directory `/opt/xtss-rights`.
Create a directory for logs:
```bash
sudo mkdir -p /var/log/xtss-rights
sudo chown -R xtss-rights:xtss-rights /var/log/xtss-rights
```
## Installing python venv
Install required python modules into venv using user `xtss-rights`:
```bash
sudo su - xtss-rights
cd /opt/xtss-rights
python3 -m venv venv
source venv/bin/activate
pip install -r requirements.txt
```
## Configuration
Create a configuration file `/opt/xtss-rights/config.json` using an example configuration file: [example-config.json](example-config.json).
Configuration parameters:
* `db_host` - database address;
* `db_port` - database port;
* `db_db` - database name;
* `db_user` - database user name;
* `db_pass` - database user password;
* `allow_all` - if "true" then disable certificate DN check, default value: "false";
* `allowed` - list of allowed certificate DN's.
## DB initialization
Create database:
......@@ -10,9 +70,66 @@ sudo -u postgres createdb rights
Run DB initialization SQL:
```bash
sudo -u postgres psql -f db.sql rights
sudo -u postgres psql -f db_user.sql rights
```
Create a password for "rights_app"
```bash
sudo -u postgres psql -c "ALTER USER rights_app WITH PASSWORD '<PASSWORD>'" rights
```
## Configuring Systemd
Add service description `systemd/xtss-rights.service` to `/lib/systemd/system/xtss-rights.service`.
Then start service and enable automatic startup:
```bash
sudo systemctl daemon-reload
sudo systemctl start xtss-rights
sudo systemctl enable xtss-rights
```
## Configuring Nginx
Copy `nginx/xtss-rights.conf` under `/etc/nginx/sites-available/`
Create a certificate for Nginx:
```bash
sudo mkdir -p /etc/nginx/xtss-rights
cd /etc/nginx/xtss-rights
sudo openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout rights.key -out rights.crt
```
Make sure key is accessible to nginx:
```bash
sudo chmod 640 /etc/nginx/xtss-rights/rights.key
sudo chgrp www-data /etc/nginx/xtss-rights/rights.key
```
On client side (XTSS app):
```bash
openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout client.key -out client.crt
```
Note that client DN should be added to the list of `allowed` DN's in the `/opt/xtss-rights/config.json` configuration file.
Copy client.crt to Rights service machine: `/etc/nginx/xtss-rights/client.crt`
Note that you can allow multiple clients (or nodes) by creating certificate bundle. That can be done by concatenating multiple client certificates into single `client.crt` file.
And restart Nginx:
```bash
sudo systemctl start
```
## Testing service
Copy nginx `rights.crt` to client machine. Then issue command to add sample data:
```
curl --cert client.crt --key client.key --cacert rights.crt -i -XPOST -d '{"organization":{"code":"00000000","name":"Org 0"},"person":{"code":"12345678901","first_name":"Firstname","last_name":"Lastname"},"right":{"right_type":"RIGHT1"}}' https://<xtss-rights.hostname>:5443/set-right
```
And then to read sample data:
```
curl --cert client.crt --key client.key --cacert rights.crt -i -XPOST -d '{}' https://<xtss-rights.hostname>:5443/rights
```
......@@ -57,55 +57,43 @@ CREATE TABLE rights."right"
ON DELETE CASCADE
);
CREATE OR REPLACE FUNCTION rights.logger() RETURNS TRIGGER AS $body$
DECLARE
v_old_data TEXT;
v_new_data TEXT;
CREATE OR REPLACE FUNCTION rights.stamper() RETURNS TRIGGER AS $body$
BEGIN
IF (TG_OP = 'UPDATE') THEN
v_old_data := ROW(OLD.*);
v_new_data := ROW(NEW.*);
INSERT INTO rights.change_log (table_name, record_id, operation, old_value, new_value)
VALUES (TG_TABLE_SCHEMA::TEXT||'.'||TG_TABLE_NAME::TEXT, OLD.id, TG_OP, v_old_data, v_new_data);
IF (TG_OP = 'INSERT') THEN
NEW.created := current_timestamp;
NEW.last_modified := current_timestamp;
RETURN NEW;
ELSIF (TG_OP = 'DELETE') THEN
v_old_data := ROW(OLD.*);
INSERT INTO rights.change_log (table_name, record_id, operation, old_value, new_value)
VALUES (TG_TABLE_SCHEMA::TEXT||'.'||TG_TABLE_NAME::TEXT, OLD.id, TG_OP, v_old_data, NULL);
RETURN OLD;
ELSIF (TG_OP = 'INSERT') THEN
v_new_data := ROW(NEW.*);
INSERT INTO rights.change_log (table_name, record_id, operation, old_value, new_value)
VALUES (TG_TABLE_SCHEMA::TEXT||'.'||TG_TABLE_NAME::TEXT, NEW.id, TG_OP, NULL, v_new_data);
ELSIF (TG_OP = 'UPDATE') THEN
NEW.last_modified := current_timestamp;
RETURN NEW;
ELSE
RAISE WARNING '[rights.logger] - Other action occurred: %, at %',TG_OP,now();
RAISE WARNING '[rights.stamper] - Other action occurred: %, at %',TG_OP,now();
RETURN NULL;
END IF;
EXCEPTION
WHEN OTHERS THEN
RAISE WARNING '[rights.logger] - Other error occurred - SQLSTATE: %, SQLERRM: %',SQLSTATE,SQLERRM;
RAISE WARNING '[rights.stamper] - Other error occurred - SQLSTATE: %, SQLERRM: %',SQLSTATE,SQLERRM;
RETURN NULL;
END;
$body$
LANGUAGE plpgsql
SECURITY DEFINER;
DROP TRIGGER IF EXISTS logger on rights.organization;
CREATE TRIGGER logger
AFTER INSERT OR UPDATE OR DELETE ON rights.organization
FOR EACH ROW EXECUTE PROCEDURE rights.logger();
DROP TRIGGER IF EXISTS stamper ON rights.organization;
CREATE TRIGGER stamper
BEFORE INSERT OR UPDATE ON rights.organization
FOR EACH ROW EXECUTE PROCEDURE rights.stamper();
DROP TRIGGER IF EXISTS logger on rights.person;
CREATE TRIGGER logger
AFTER INSERT OR UPDATE OR DELETE ON rights.person
FOR EACH ROW EXECUTE PROCEDURE rights.logger();
DROP TRIGGER IF EXISTS stamper ON rights.person;
CREATE TRIGGER stamper
BEFORE INSERT OR UPDATE ON rights.person
FOR EACH ROW EXECUTE PROCEDURE rights.stamper();
DROP TRIGGER IF EXISTS logger on rights."right";
CREATE TRIGGER logger
AFTER INSERT OR UPDATE OR DELETE ON rights."right"
FOR EACH ROW EXECUTE PROCEDURE rights.logger();
DROP TRIGGER IF EXISTS stamper ON rights."right";
CREATE TRIGGER stamper
BEFORE INSERT OR UPDATE ON rights."right"
FOR EACH ROW EXECUTE PROCEDURE rights.stamper();
CREATE OR REPLACE FUNCTION rights.check_right() RETURNS TRIGGER AS $body$
BEGIN
......@@ -134,18 +122,57 @@ $body$
LANGUAGE plpgsql
SECURITY DEFINER;
DROP TRIGGER IF EXISTS check_right on rights."right";
DROP TRIGGER IF EXISTS check_right ON rights."right";
CREATE TRIGGER check_right
BEFORE INSERT OR UPDATE ON rights."right"
FOR EACH ROW EXECUTE PROCEDURE rights.check_right();
CREATE ROLE rights_app WITH LOGIN;
GRANT CONNECT ON DATABASE rights TO rights_app;
GRANT USAGE ON SCHEMA rights TO rights_app;
GRANT INSERT ON rights.change_log TO rights_app;
GRANT SELECT, INSERT, UPDATE ON rights.organization TO rights_app;
GRANT SELECT, INSERT, UPDATE ON rights.person TO rights_app;
GRANT SELECT, INSERT, UPDATE ON rights."right" TO rights_app;
GRANT USAGE ON SEQUENCE rights.organization_id_seq TO rights_app;
GRANT USAGE ON SEQUENCE rights.person_id_seq TO rights_app;
GRANT USAGE ON SEQUENCE rights.right_id_seq TO rights_app;
CREATE OR REPLACE FUNCTION rights.logger() RETURNS TRIGGER AS $body$
DECLARE
v_old_data TEXT;
v_new_data TEXT;
BEGIN
IF (TG_OP = 'INSERT') THEN
v_new_data := ROW(NEW.*);
INSERT INTO rights.change_log (table_name, record_id, operation, old_value, new_value)
VALUES (TG_TABLE_SCHEMA::TEXT||'.'||TG_TABLE_NAME::TEXT, NEW.id, TG_OP, NULL, v_new_data);
RETURN NEW;
ELSIF (TG_OP = 'UPDATE') THEN
v_old_data := ROW(OLD.*);
v_new_data := ROW(NEW.*);
INSERT INTO rights.change_log (table_name, record_id, operation, old_value, new_value)
VALUES (TG_TABLE_SCHEMA::TEXT||'.'||TG_TABLE_NAME::TEXT, OLD.id, TG_OP, v_old_data, v_new_data);
RETURN NEW;
ELSIF (TG_OP = 'DELETE') THEN
v_old_data := ROW(OLD.*);
INSERT INTO rights.change_log (table_name, record_id, operation, old_value, new_value)
VALUES (TG_TABLE_SCHEMA::TEXT||'.'||TG_TABLE_NAME::TEXT, OLD.id, TG_OP, v_old_data, NULL);
RETURN OLD;
ELSE
RAISE WARNING '[rights.logger] - Other action occurred: %, at %',TG_OP,now();
RETURN NULL;
END IF;
EXCEPTION
WHEN OTHERS THEN
RAISE WARNING '[rights.logger] - Other error occurred - SQLSTATE: %, SQLERRM: %',SQLSTATE,SQLERRM;
RETURN NULL;
END;
$body$
LANGUAGE plpgsql
SECURITY DEFINER;
DROP TRIGGER IF EXISTS logger ON rights.organization;
CREATE TRIGGER logger
AFTER INSERT OR UPDATE OR DELETE ON rights.organization
FOR EACH ROW EXECUTE PROCEDURE rights.logger();
DROP TRIGGER IF EXISTS logger ON rights.person;
CREATE TRIGGER logger
AFTER INSERT OR UPDATE OR DELETE ON rights.person
FOR EACH ROW EXECUTE PROCEDURE rights.logger();
DROP TRIGGER IF EXISTS logger ON rights."right";
CREATE TRIGGER logger
AFTER INSERT OR UPDATE OR DELETE ON rights."right"
FOR EACH ROW EXECUTE PROCEDURE rights.logger();
CREATE ROLE rights_app WITH LOGIN;
GRANT CONNECT ON DATABASE rights TO rights_app;
GRANT USAGE ON SCHEMA rights TO rights_app;
GRANT INSERT ON rights.change_log TO rights_app;
GRANT SELECT, INSERT, UPDATE ON rights.organization TO rights_app;
GRANT SELECT, INSERT, UPDATE ON rights.person TO rights_app;
GRANT SELECT, INSERT, UPDATE ON rights."right" TO rights_app;
GRANT USAGE ON SEQUENCE rights.organization_id_seq TO rights_app;
GRANT USAGE ON SEQUENCE rights.person_id_seq TO rights_app;
GRANT USAGE ON SEQUENCE rights.right_id_seq TO rights_app;
FROM python:3.6
WORKDIR /app
COPY files/requirements.txt files/rights.py files/server.py ./
RUN pip install --no-cache-dir -r requirements.txt
COPY local_config.json config.json
RUN mkdir -p /var/log/xtss-rights
CMD gunicorn --workers 4 --bind 0.0.0.0:5080 server:app
# Docker container for local testing
## Prerequisites
You will need a `docker` and `docker-compose` installed in order to use run service locally
## Running on Linux
Start service by navigating to `local` directory and running:
```
./up.sh
```
Remove service by running:
```
./down.sh
```
## Running on other platforms
Create a directory `local/files`.
Copy files `db.sql requirements.txt rights.py server.py` to `local/files` directory.
Build and run service:
```
docker-compose build
docker-compose up -d
```
Remove service by running:
```
docker-compose down
```
## Testing service
Add sample data:
```
curl -XPOST -d '{"organization":{"code":"00000000","name":"Org 0"},"person":{"code":"12345678901","first_name":"Firstname","last_name":"Lastname"},"right":{"right_type":"RIGHT1"}}' localhost:5080/set-right
```
Read sample data:
```
curl -XPOST -d '{}' localhost:5080/rights
```
version: "3"
services:
rights:
build: ./
ports:
- "5080:5080"
db:
image: postgres
volumes:
- ./files/db.sql:/docker-entrypoint-initdb.d/db.sql
ports:
- "5432:5432"
environment:
POSTGRES_PASSWORD: password
#!/bin/bash
set -e
set -x
docker-compose down
{
"db_host": "db",
"db_port": "5432",
"db_db": "postgres",
"db_user": "postgres",
"db_pass": "password",
"allow_all": true
}
#!/bin/bash
mkdir -p files
cp ../db.sql ../requirements.txt ../rights.py ../server.py ./files
#!/bin/bash
set -e
set -x
bash prepare.sh
docker-compose build
docker-compose up -d
......@@ -60,8 +60,7 @@ def set_person(cur, code, first_name, last_name):
cur.execute(
"""
update rights.person
set first_name=%(first_name)s, last_name=%(last_name)s,
last_modified=current_timestamp
set first_name=%(first_name)s, last_name=%(last_name)s
where id=%(id)s""",
{'first_name': first_name, 'last_name': last_name, 'id': current_id})
return current_id
......@@ -94,7 +93,7 @@ def set_organization(cur, code, name):
cur.execute(
"""
update rights.organization
set name=%(name)s, last_modified=current_timestamp
set name=%(name)s
where id=%(id)s""",
{'name': name, 'id': current_id})
return current_id
......