Skip to content
Snippets Groups Projects
Commit 0851c245 authored by Mario Cosma Damiano Di Lorenzo's avatar Mario Cosma Damiano Di Lorenzo
Browse files

Upgrade

parent 4a4a486f
Branches
No related tags found
No related merge requests found
# otp_config.php
DB_HOST=edugain-db
DB_DATABASE=otp
USER=otp
PASSWORD=xxx
DB_CONFIG_LOCATION=/var/otp_server_config/otp_config.php
FROM php:8.1-apache
WORKDIR /var/www/html
RUN docker-php-ext-install mysqli
COPY --from=hairyhenderson/gomplate:v3.11.3 /gomplate /bin/gomplate
COPY --from=docker.io/library/composer:latest /usr/bin/composer /usr/bin/composer
COPY composer.json .
RUN apt-get update
RUN apt-get install -y git vim
RUN composer update
COPY otp_server.php otp_server.php
COPY config/ config
COPY otp_server_config/ /var/otp_server_config
EXPOSE 80
COPY templating.sh /templating.sh
RUN chmod a+x /templating.sh
README.md 100644 → 100755
...@@ -2,20 +2,20 @@ ...@@ -2,20 +2,20 @@
This is a simple back-end server meant to run in a secure enviromnent full with access to the database where users' TOTP secrets are stored. This is a simple back-end server meant to run in a secure enviromnent full with access to the database where users' TOTP secrets are stored.
The server uses HTTP to respond to GET queries. It accepts two parameters The server uses HTTP to respond to GET queries. It accepts two parameters
- user - here you provide the user identifier as passed from eduTEAMS - this argument is mandatory - user - here you provide the user identifier as passed from eduTEAMS - this argument is mandatory
- otp - the one-time, time-based code entered by the user, this is optional - otp - the one-time, time-based code entered by the user, this is optional
## Functions and return values ## Functions and return values
The server returns a json-encoded integer: The server returns a json-encoded integer:
* -1 - user not found in the database (otp argument not required)
* 0 - user exists but there was a missmatch in the code (otp argument required) - -1 - user not found in the database (otp argument not required)
* 1 - there was a success in verification of the code against the user secret (otp argument required) - 0 - user exists but there was a missmatch in the code (otp argument required)
* 2 - the code has not been provided - the user has not been verified yet (otp argument not sent) - 1 - there was a success in verification of the code against the user secret (otp argument required)
* 3 - the code has not been provided - just confirming that the user is verified (otp argument not sent) - 2 - the code has not been provided - the user has not been verified yet (otp argument not sent)
* 4 - the code has been used for a second time (otp argument required) - 3 - the code has not been provided - just confirming that the user is verified (otp argument not sent)
- 4 - the code has been used for a second time (otp argument required)
When a code is verified, its value is written into the database as the "last_code" value to prevent reuse. Also the "verified" value When a code is verified, its value is written into the database as the "last_code" value to prevent reuse. Also the "verified" value
is set to 1 (this is an overkill as it only needs to be done on the first succesful verification, but simplefies the code). is set to 1 (this is an overkill as it only needs to be done on the first succesful verification, but simplefies the code).
...@@ -27,26 +27,6 @@ code is required. ...@@ -27,26 +27,6 @@ code is required.
## Installation ## Installation
The server works with the `otp` database which is shared with the technical site installation.
You must define user `otp` or whatever you decide to call it (and set in the otp_config.php).
### Without Docker
All you need is an https server with php and mysql support. The server will need to make outside connections to the database host.
If you run stuff within secure environment (as you should) you do not need https.
When you unpack the code from Git:
- Run composer to pull the TOTP PHP package.
- Create a directory where you will place server config file and copy **otp_config.php.template** there
using a name of your choice; this file contains access details to the OTP database and it must be read by the server,
but should not be placed within the web-server directory, in case a problem with your PHP could lead to exposure.
- In the config directory, copy `config.php.template` to `config.php` and put in the location of the otp_server config file.
- Configure your httpd server to be able to execute otp_server.php
### Using Docker ### Using Docker
We suggest that you use **--network host** Docker run option which will allow address We suggest that you use **--network host** Docker run option which will allow address
...@@ -61,11 +41,12 @@ resolution based on the host machine and standard port 80 for connections. ...@@ -61,11 +41,12 @@ resolution based on the host machine and standard port 80 for connections.
- As root run ` docker run -d --name otp_server --network host --rm otp_server:latest ` - As root run ` docker run -d --name otp_server --network host --rm otp_server:latest `
### Running the docker image at boot ### Running the docker image at boot
For systems using systemctl we suggest to install the new service called ` otp-docker `
to do that create the ` otp-docker.service ` file in ` /usr/lib/systemd/system ` with the For systems using systemctl we suggest to install the new service called ` otp-docker `.
To do that create the ` otp-docker.service ` file in ` /usr/lib/systemd/system ` with the
contents as below: contents as below:
``` ```bash
[Unit] [Unit]
Description=TOTP server for eduGAN technical site Description=TOTP server for eduGAN technical site
After=docker.service After=docker.service
...@@ -82,8 +63,10 @@ ExecStop=/usr/bin/docker stop -t 2 otp_server ...@@ -82,8 +63,10 @@ ExecStop=/usr/bin/docker stop -t 2 otp_server
[Install] [Install]
WantedBy=multi-user.target WantedBy=multi-user.target
``` ```
Next reload the daemon: ` systemctl daemon-reload `, start and enable the servce:
``` Next reload the daemon: ` systemctl daemon-reload `, start and enable the service:
```bash
systemctl start otp-docker systemctl start otp-docker
systemctl enable otp-docker systemctl enable otp-docker
``` ```
...@@ -96,30 +79,19 @@ systemctl enable otp-docker ...@@ -96,30 +79,19 @@ systemctl enable otp-docker
- As root run ` docker build -t gitlab.software.geant.org:5050/edugain/ot/totp-server:<VERSION> . ` - As root run ` docker build -t gitlab.software.geant.org:5050/edugain/ot/totp-server:<VERSION> . `
- As root run ` docker push gitlab.software.geant.org:5050/edugain/ot/totp-server:<VERSION> ` - As root run ` docker push gitlab.software.geant.org:5050/edugain/ot/totp-server:<VERSION> `
## Using Docker Compose
- You need docker and docker compose installed on your host machine
- Modify the settings file ` .otp.env ` as needed
- Update the ` docker-compose.yml ` file as needed
- As root run ` docker compose up -d `
- As root run ` docker exec otp_server /templating.sh `
## Testing ## Testing
From the main technical site run: From the main technical site run:
``` ```bash
wget -O otp.out http://otp_server_address/otp_server.php?user=xxx@example.com wget -O otp.out http://<otp_server_address>/otp_server.php?user=xxx@example.com
``` ```
or or
```bash
curl -o otp.out http://<otp_server_address>/otp_server.php?user=xxx@example.com
``` ```
curl -o otp.out http://otp_server_address/otp_server.php?user=xxx@example.com
```
Your otp.out should contain value "-1" which means - the user not found.
If this works then you are ready to go.
Your otp.out should contain value "-1" which means - the user not found.
If this works then you are ready to go.
# Deployment settings
registry_location: gitlab.software.geant.org:5050
registry_user: gitlab+deploy-token-27
registry_password: !vault |
$ANSIBLE_VAULT;1.1;AES256
64346636353665346562303638336635393830376663303238316638376636343433366432303135
3062373663643737616437323665633539366435643437620a636334353536343936633665383438
32616233373462656565626662626561353137363364343935306630626265366531343438316537
6638313065613536320a303564306231373336656264613639666664373834623338363364363733
34373830386238373865386663396332613736653230363034353531623436636236
# Docker image version
otp_server_version: 1.0.0
# Database Variables
backend_host_ip: 90.147.185.1
database_host_name: edugain-db.aai-test.garr.it
otp_database: otp
otp_user: otp
otp_user_pass: !vault |
$ANSIBLE_VAULT;1.1;AES256
37323962653737376465646261346534323135616464633839636231313333326230393066623466
3530636530303962386261366565326364636162666161610a333763643763316536396339336364
66333636663764386134386161653837623638653733633562393131393462346666396133313533
6135313130346530330a653239303161316235303531396635393062653964373137636462353634
30623930633965383462643731303632613666613933643365643066623332663235
db_config_location: /var/otp_server_config/otp_config.php
# If you want to use Ansible locally
#localhost ansible_connection=local
[remote_hosts]
<HOST_NAME> ansible_host=<HOST_IP> ansible_connection=ssh ansible_user=<INSERT_USERNAME> ansible_ssh_private_key_file=<INSERT_PRIVATE_KEY_LOCATION>
\ No newline at end of file
---
- name: Setup TOTP Server
hosts: remote_hosts #<INSERT_INVENTORY_NAME or localhost>
become: true
become_method: sudo
tasks:
- name: Create deploy directory
ansible.builtin.file:
path: /opt/deploy_otp_server
state: directory
- name: Copy the docker-compose and .env files from templates
ansible.builtin.template:
src: "{{ item }}.j2"
dest: "/opt/deploy_otp_server/{{ item }}"
loop:
- docker-compose.yml
- .otp.env
- name: Login on Container Registry
ansible.builtin.shell: "docker login {{ registry_location }} -u {{ registry_user }} -p {{ registry_password }}"
- name: Run the docker compose
ansible.builtin.shell: "docker compose pull && docker compose up -d"
args:
chdir: /opt/deploy_otp_server
# otp_config.php
DB_HOST={{ backend_host_ip }}
DB_DATABASE={{ otp_database }}
USER=otp
PASSWORD={{ otp_user_pass }}
DB_CONFIG_LOCATION={{ db_config_location }}
\ No newline at end of file
services:
otp_server:
image: "{{ registry_location }}/edugain/ot/totp-server:{{ otp_server_version }}"
container_name: "{{ otp_container_name }}"
hostname: otp_server
env_file:
- ".otp.env"
ports:
- 9080:80
version: '3.9'
services:
otp_server:
image: "gitlab.software.geant.org:5050/edugain/ot/totp-server:1.0.0"
container_name: "edugain_otp_server"
hostname: "edugain_otp_server"
env_file:
- ".otp.env"
ports:
- "8080:80"
FROM php:8.2-alpine
WORKDIR /var/www/html
# Installing required packages
RUN apk add --no-cache \
bash \
git \
vim \
libmcrypt-dev \
libpng-dev \
libjpeg-turbo-dev \
libxml2-dev \
zlib-dev \
&& docker-php-ext-install mysqli
COPY --from=hairyhenderson/gomplate:v3.11.3 /gomplate /bin/gomplate
COPY --from=docker.io/library/composer:latest /usr/bin/composer /usr/bin/composer
COPY docker/composer.json .
RUN composer install --no-dev --optimize-autoloader
# Copy all files into the container
COPY docker/otp_server.php otp_server.php
COPY docker/config/ config
COPY docker/otp_server_config/ /var/otp_server_config
EXPOSE 80
# Copy the start.sh script and make it executable
COPY docker/start.sh /usr/local/bin/start.sh
RUN chmod +x /usr/local/bin/start.sh
CMD ["/usr/local/bin/start.sh"]
File moved
File moved
<?php
/*
* Return codes:
* -1: User not found
* 0: Code mismatch
* 1: Success
* 2: Code not provided, user not verified
* 3: Code not provided, user verified
* 4: Code reused
*/
require_once('vendor/autoload.php');
require_once('config/config.php');
require_once(DB_CONFIG_LOCATION);
use OTPHP\TOTP;
// Abilita eccezioni su mysqli per debugging
mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
try {
$mysqli = new mysqli(DB_HOST, USER, PASSWORD, DB_DATABASE);
} catch (Exception $e) {
http_response_code(500);
echo json_encode(['error' => 'Database connection failed']);
exit;
}
$mysqli->set_charset('utf8');
$mysqli->query("SET time_zone='+00:00'");
// Rate limit base: max 5 tentativi per IP/minuto
$ip = $_SERVER['REMOTE_ADDR'];
$ipFile = sys_get_temp_dir() . "/otp_attempts_" . md5($ip);
$attempts = file_exists($ipFile) ? (int)file_get_contents($ipFile) : 0;
if ($attempts > 5) {
http_response_code(429);
echo json_encode(['error' => 'Too many attempts, try later']);
exit;
}
file_put_contents($ipFile, $attempts + 1);
register_shutdown_function(fn() => unlink($ipFile)); // Pulisci se vuoi, o metti TTL
if (empty($_GET['user'])) {
echo json_encode(['error' => 'Missing username']);
exit;
}
$user = filter_var($_GET['user'], FILTER_SANITIZE_EMAIL);
$out = 0;
$stmt = $mysqli->prepare("SELECT secret, last_code, verified FROM otp WHERE user = ?");
$stmt->bind_param("s", $user);
$stmt->execute();
$result = $stmt->get_result();
if ($result->num_rows === 0) {
$out = -1;
} else {
[$otpSecret, $otpLastCode, $verified] = $result->fetch_row();
$out = 0;
}
$otpCode = isset($_GET['otp']) ? trim($_GET['otp']) : '';
if ($otpCode === '' && $out === 0) {
$out = ($verified == 1) ? 3 : 2;
}
if ($out === 0 && $otpCode !== '') {
$otpObject = OTPHP\TOTP::create($otpSecret);
$otpTestCode = $otpObject->now();
if ($otpCode === $otpTestCode) {
if ($otpCode === $otpLastCode) {
$out = 4; // Reuse
} else {
$stmtUpdate = $mysqli->prepare("UPDATE otp SET verified = 1, last_code = ? WHERE user = ?");
$stmtUpdate->bind_param("ss", $otpCode, $user);
$stmtUpdate->execute();
$out = 1;
}
} else {
$out = 0;
}
}
// Headers sicuri
header('Content-type: application/json; charset=utf-8');
header('Access-Control-Allow-Origin: *');
echo json_encode($out, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
File moved
#!/bin/bash
set -e
# Templates by Gomplate
templates=(
"/var/www/html/config/config.php.template"
"/var/otp_server_config/otp_config.php.template"
)
for src in "${templates[@]}"; do
dst="${src%.template}" # Remove .template extentions
filename=$(basename "$src")
if [ -f "$src" ]; then
echo "[setup][INFO] Template $filename found, processing with gomplate..."
gomplate -f "$src" -o "$dst"
rm "$src"
echo "[setup][OK] Template $filename processed and removed."
else
echo "[setup][INFO] Template $filename not found, skipping gomplate processing."
fi
done
# Start the PHP built-in web server
echo "[INFO] Starting PHP built-in web server."
php -S 0.0.0.0:80 -t /var/www/html
<?php
/*
* The server can dwo two things - it can test if the user is defined or it can
* validate the otp_code against the secret in the database.
*
* Return values:
* -1 - user not found in the DB
* 0 - user exists but there was a missmatch in the code
* 1 - there was a success in verification of the code against the user secret
* 2 - the code has not been provided - the user has not been verified yet
* 3 - the code has not been provided - just confirming that the user is verified
* 4 - the code has been used for a second time
*/
session_start();
require_once('vendor/autoload.php');
require_once('config/config.php');
require_once(DB_CONFIG_LOCATION);
use OTPHP\TOTP;
try {
$mysqli = new mysqli(DB_HOST, USER, PASSWORD, DB_DATABASE);
} catch (Exception $e) {
die("Database connection problem\n");
}
$mysqli->set_charset('utf8');
$mysqli->query("SET time_zone='+00:00'");
if (empty($_GET['user'])) {
print('no username argument');
exit;
}
$user = filter_var($_GET['user'], FILTER_SANITIZE_EMAIL);
$out = 0;
$result = $mysqli->query("SELECT secret, last_code, verified from otp where user ='$user'");
if ($result) {
if ($result->num_rows == 0) {
$out = -1; // the user is not defined
} else {
$r = $result->fetch_row();
$otpSecret = $r[0];
$otpLastCode = intval($r[1]);
$verified = $r[2];
$out = 0; // the user exists in the database - this is a temporary code value
}
} else {
exit;
}
$otpCode = isset($_GET['otp']) ? intval(filter_var($_GET['otp'], FILTER_SANITIZE_NUMBER_INT)) : 0;
// check if any code has been passed and if so update the result code accordingle - again this value is temporary
if ($otpCode == 0 && $out == 0) {
if ($verified == 1) {
$out = 3;
} else {
$out = 2;
}
}
if ($out == 0) { // the otp code must have been provided and the user exists in the DB, the secret is taken form the DB
$otpObject = TOTP::create($otpSecret);
$otpTestCode = intval($otpObject->now());
if ($otpCode === $otpTestCode) {
if($otpCode === $otpLastCode) {
$out = 4;
} else {
$mysqli->query("UPDATE otp SET verified = 1, last_code = $otpCode where user = '$user'");
$out = 1;
}
} else {
// there was a missmatch in the codes
$out = 0;
}
}
header('Content-type: application/json; charset=utf-8');
header('Access-Control-Allow-Origin: *');
print json_encode($out, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
#!/bin/bash
gomplate -f /var/www/html/config/config.php.template -o /var/www/html/config/config.php
rm /var/www/html/config/config.php.template
gomplate -f /var/otp_server_config/otp_config.php.template -o /var/otp_server_config/otp_config.php
rm /var/otp_server_config/otp_config.php.template
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment