A customizable REST-API in Python
Note
This application provides a simple Server-Client mechanism for securely exchaning small data amounts.
It can be customized with simple to load python modules to call as API-functions in a RESTful way to your liking.
Server deployment is containerized and allows easy configuration
The application is designed in such a way that little to no knowledge over specific Server-Client relationships are maintained.
- Download
client.py
,gen_key.py
andcient_requirements.txt
fromutil
for your Client - Create a local virtual environment and install requirements
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
- Generate a new key pair named
client.key
(private) andclient.pub
(public) for your client
python3 gen_key.py pair client
- Note your Clients' Public Key
cat client.pub
iSQhxAx4b3IqGZv0sT7n8zK07ugSYe697WSWDg/fb1Q=
- Create a Server instance by pulling the Docker image
docker run -itd --name mini_share_instance -p 8000:8000 ghcr.io/akablur/mini-share-point
- Your Server runs now on
http://localhost:8000/v1
- Open a bash instance in your Server and append the Clients' key to your Server
docker exec -it mini_share_instance /bin/bash
# inside Docker instance
/app$ appendclient iSQhxAx4b3IqGZv0sT7n8zK07ugSYe697WSWDg/fb1Q=
[2024-03-30 11:34:46,540] INFO: Client Hex Secret: 2aed83f3d03b5520ce8e831088b219e333eb74d74243d3e9b4bd8e337229dac727ae4c249990bb6430d1b83cae7221ab19772e13a787087050a1e9529588e621
Server public key:
p5eAkOXqfOX3OHeMIraFtDBE9khXP+1nkTWoAZv64S4=
- Note the Clients' generated
Hex Secret
and the ServersPublic Key
# Clients' Hex Secret
2aed83f3d03b5520ce8e831088b219e333eb74d74243d3e9b4bd8e337229dac727ae4c249990bb6430d1b83cae7221ab19772e13a787087050a1e9529588e621
# Servers Public Key
p5eAkOXqfOX3OHeMIraFtDBE9khXP+1nkTWoAZv64S4=
- Restart Server
docker stop mini_share_point
docker start mini_share_point
- Create a file for the Servers Public Key and the Clients' Hex Secret
# Clients' Hex Secret
printf 2aed83f3d03b5520ce8e831088b219e333eb74d74243d3e9b4bd8e337229dac727ae4c249990bb6430d1b83cae7221ab19772e13a787087050a1e9529588e621 > client.secret
# Servers Public Key
printf p5eAkOXqfOX3OHeMIraFtDBE9khXP+1nkTWoAZv64S4= > server.pub
- Test your connection
python3 client.py http://localhost:8000/v1
[2024-03-30 14:20:53,643] INFO: It is currently:
Sat Mar 30 13:20:53 2024
This app provides a Server that exposes a customizable API-endpoint. Communication with this API is only possible for registered Clients.
Communication between Client and Server is done via a customziable REST-API. The user can implement any functionality into the endpoint on the Server to be used by any client.
To provide security a Client needs some prerequisites to be allowed to communicate with the Server:
- Private-Public-Keypair (for the Client)
- A shared Secret with the Server
- A valid Timestamp
- The Servers Public-Key
For a simple demo there is a possible Client implementation already inside util
. It utizilizes requests
to perform a simple POST
request to a deployed Server instance.
The Server therefore implements a simple demo method to call:
time_test
- Returns the current time
Important
When operation the Server behind a Reverse Proxy, make sure to set the [Proxy]
configuration accordingly!
Otherwise resolving the Clients IP address won't be possible.
Deploying the Server is just as simple as starting a Docker container:
docker run -itd -p 8000:8000 ghcr.io/akablur/mini-share-point:latest
The Server will then run on http://localhost:8000/
.
It is also recommended to use docker-compose
. Therefore a example compose.yaml
is given:
services:
mini-share-point:
build: .
environment:
- PUID=1000
- PGID=1000
ports:
- 8000:8000
volumes:
- ./config-docker:/config
- ./modules:/app/modules
- ./log:/log
- ./sec:/sec
Setting a UID and GID is especially recommended if you want to mount external directories and preserve their ownership.
After a successful deployment some configuration might be adjusted to your liking.
Generally there are four important directories that the Server is depending on. These are the internal Docker directories as following:
/config
/app/modules
/log
/sec
This directory contains config.ini
and modules.ini
config.ini
is the configuration file for the Server. Here all settings regarding the main operation of the Server can be adjusted. Directories should not be changed, as these refer to docker internal directories.
Implemented options:
IPAddressTTL
- time-to-live for internally cached IP addresses (in sec)
- IP addresses are saved and cached for that amount of time
- Every requests from non-cached IPs will generate a log entry
RequestTTL
- time-to-live for a sent request to the Server (in sec)
- Computed from the sent timestamp
- When a request times out
401
will be returned
[Proxy]
-section
- Contains settings for possible Proxy headers
- Set the amount of each header the Server is expected to see (check your proxy for that)
- Highly recommended when operating behind a reverse proxy
modules.ini
- Check
/app/modules
for explanation below 👇
This directory contains all custom functions your Server can use.
Currently all functions are implemented as python
files. Each file needs the following function as entry call:
def entry_call() -> str:
...
The custom function must also return a str
as result.
All modules that should be used by the Server need to be registered inside the modules.ini
. Inside this file all modules are listed in the following manner inside the [Modules]
section:
function_name=module-name
function_name
refers to the name the module will have when calling it via the API.
module-name
is the name of the .py
file inside the module directory containing your custom function for the API.
Well... log directory
Contains all latest logs
This is the main storage for all Server-related keys.
Note
Handling those manually is not recommended as there are some implemented helper functions to assist with key creation and deletion. This directory should be considered as backup directory when you remove the Docker image and don't want to loose all the configured clients.
See Key-Handling for further information.
As this Server uses a Public-Private-Key exchange system keeping your keys save and uncompromized there are some considerations when handling those key files.
For Server-Client communication a pair of Private and Public keys is generated. Both Client and Server will generate such a pair.
The Server will only have one keypair whose Public key will be shared by all Clients for encryption. It should be noted that the Server will automatically create a new key pair, when none is given (e.g. through mounting an external directory).
Automatically created key files will always be base64
encoded 32-bit values. It can be possible to use different key lengths but this is discouraged.
A simple method for generating new key pairs is given under util/gen_key.py
.
This allows the generation of a single private
key file, a public
key file for an existing private key file on disk or the generation of a new key pair
with a given name. Use as following:
python3 gen_key.py pair my_client
This will generate a new key pair named my_client
saved into the current directory. For new clients this is an easy method to prepair a new set of keys.
Warning
It is not recommended to manually add or remove keys!
Instead some methods are given below.
To execute a command simply open a terminal inside the container
docker exec -it my_docker_name /bin/bash
There exist two possible methods:
appendclient
purgeclients
As the name implies this will add a generated Client key to use for a new Client. Just call the function inside the server with the Clients Public Key:
appendclient <CLIENTS-PUBLIC-KEY-BASE64>
This function will also return two values:
- Server Public Key
- Client Hex Secret
The Server Public Key is needed for the Client to encrypt traffic that needs to be sent to the Server. When accessing the API the Client Hex Secret needs also to be given inside the JSON request for authentication. This value should therefore also be saved within you client.
Important
Restart the Server after each change in the key storage!
This function will delete all Clients registered inside the Server. Clients need both a valid encryption key and a registered Secret inside the Server. Those are stored inside the Server without a saved relationship between each Client Key and its respecting Secret. For that reason a single Client can't be removed, but all Clients need to be removed and reinstated instead.
Important
Restart the Server after each change in the key storage!
The API itself is designed in a RESTful way.
The basic endpoint is: http://localhost:8000/v1
Data needs to be sent as JSONified string with appropiate MIME
-Type set.
- Main communication endpoint
Parameter | Value |
---|---|
Method | POST |
MIME -Type |
application/json |
Data | JSON string |
- JSON string that the Server expects to receive
Important
All data values need to be encrypted with the Servers Public Key!
Key | Value |
---|---|
id |
Clients Secret |
check |
CRC-32 of the Secret |
ts |
Timestamp, as Unix time |
entry |
Function to call |
Important
Make sure you only send the actual values!
When reading in your Client Secret and keys remove any potential newline characters and such!
200
on successful request- Data returned is formatted as
JSON
string
Key | Value |
---|---|
value |
Return value of custom function |
401
on every other request (malformed JSON, incorrect Secret, unregistered Key, etc.)
Setup for local dev:
- Export
PYTHONPATH
withmodules
directory to load custom modules - Optional: enable debugging with
MSP_LOGLEVEL=DEBUG
- Start local flask Server:
flask --app mini_share_point run --debug