# AKAS: API Key Authorization Server
[](LICENSE)
[](https://github.com/semantic-release/semantic-release)
[](https://crates.io/crates/akas)
[](https://gitlab.com/op_so/projects/akas/pipelines)
A simple and higth performance server to authorized HTTP requests by API key checks.
- A hight performance server written in [Rust](https://www.rust-lang.org/),
- In-memory keys storage,
- Control authorization bearer with pre-checks,
- Perform a hot reload of the key file.
```txt
Authorization: Bearer <key>
```

The file of the list of the keys to be used for authorization should contain one key per line in plain or [SHA-256](https://en.wikipedia.org/wiki/SHA-2) format:
- **sha256** (default)
```txt
8b89600015b273c28f966f368456e45e01df239a36bf939ff72a16881f775679
fb22be500af1ef0479745bbbce847854da33f5e910361ad278e0282995b95f4d
...
```
- **plain**
```txt
mykey-3532dceb-f38a-491b-814d-9607bc9a947a
mykey-c2d79a40-388e-4709-9e4b-903035b0e71e
...
```

## Usage
```bash
$ ./akas --help
A HTTP API key-Based Authorization Server
Usage: akas <COMMAND>
Commands:
serve Start the server
load Load keys from file (Not yet implemented)
help Print this message or the help of the given subcommand(s)
Options:
-h, --help Print help
-V, --version Print version
$./akas serve --help
Start the server
Usage: akas serve [OPTIONS]
Options:
--admin-key <ADMIN_KEY>
Admin key for /load and /status URI [env: AKAS_ADMIN_KEY=] [default: ]
--no-admin-key
No admin key flag
--local
Bind local adress only [env: AKAS_LOCAL=]
--enable-metrics
Enable Prometheus metrics endpoint [env: AKAS_ENABLE_METRICS=]
-p, --port <PORT>
Port of the server [env: AKAS_PORT=] [default: 5001]
--log-level <LOG_LEVEL>
Log level <error|warn|info|debug|trace> [env: AKAS_LOG_LEVEL=] [default: info]
--original-length <ORIGINAL_LENGTH>
Length of the x-forwarded-for, x-original header fields [env: AKAS_ORIGINAL_LENGTH=] [default: 100]
--metadata-length <METADATA_LENGTH>
Length of the metadata header field [env: AKAS_METADATA_LENGTH=] [default: 0]
--key-length <KEY_LENGTH>
Length of the key [optional] [env: AKAS_KEY_LENGTH=] [default: 0]
--key-prefix <KEY_PREFIX>
Prefix of the key [optional] [env: AKAS_KEY_PREFIX=] [default: ]
-h, --help
Print help
```
### Start akas server with the default port `5001`
```bash
./akas serve --admin-key my-admin-key
```
### Example of configuration of a Nginx server
```text
server {
listen 80;
server_name _;
location / {
auth_request /auth;
auth_request_set $auth_status $upstream_status;
root /usr/share/nginx/html;
index index.html index.htm;
}
location = /auth {
internal;
proxy_pass http://localhost:5001/auth;
proxy_pass_request_body off;
proxy_set_header content-length "";
proxy_set_header x-forwarded-for $proxy_add_x_forwarded_for;
proxy_set_header x-original-host $host;
proxy_set_header x-original-uri $request_uri;
proxy_set_header x-akas-metadata "my-metadata";
}
}
```
More details of Nginx configuration can be found in the [_configuring subrequest authentication_ documentation](https://docs.nginx.com/nginx/admin-guide/security-controls/configuring-subrequest-authentication/)
- Authorized request: `curl -H "Authorization: Bearer <key>" http://<host>/`
## Endpoints URIs
### `/auth`: Authorization endpoint
If the API key is present in the hashset
return `200 OK`,
otherwise return `401 Unauthorized`.
### `/load`
Load new plain/hash keys file and replace the current keys in HashSet. The access is protected by an optional admin key.
Example of a `curl` request:
```bash
curl -v \
-H "Authorization: Bearer my-admin-key" \
--url http://localhost:5001/load \
-F 'json={"format": "sha256", "hash_input_file": "43fcba0b3a ..."};type=application/json' \
-F file=@./tests/files/sha256_key.txt
```
| `file` | File path of the keys file to upload | Yes | - |
| `format` | Format of the keys <sha256\|plain> | No | sha256 |
| `hash_input_file` | sha-256 of the uploaded file | No | - |
If the server is started without an admin key (`--no-admin-key`), the header `Authorization: Bearer` is still required with a fake key.
### `/status`
Return the application state in JSON format. The access is protected by an optional admin key.
Example:
```json
{
"log_level": "INFO",
"original_length": 100,
"metadata_length": 0,
"file_hash": "43fcba0b3a5ab1b302f9d13617ae4eec6ae623a7fd52437dd992d5dca115e68d",
"file_date": "2024-11-09T14:07:23.416999558+00:00",
"file_key_count": 150,
"key_length": 42,
"key_prefix": "mykey-"
}
```
Example of request:
```bash
curl -v \
-H "Authorization: Bearer my-admin-key" \
http://localhost:5001/status
```
If the server is started without an admin key (`--no-admin-key`), the header `Authorization: Bearer` is still required with a fake key.
### `/metrics`
Enabling the `--enable-metrics` flag exposes Prometheus metrics via the `/metrics` endpoint:
``` text
# HELP akas_http_requests_duration_seconds HTTP request duration in seconds for all requests
# TYPE akas_http_requests_duration_seconds histogram
akas_http_requests_duration_seconds_bucket{endpoint="/auth",method="GET",status="401",le="0.005"} 1
akas_http_requests_duration_seconds_bucket{endpoint="/auth",method="GET",status="401",le="0.01"} 2
akas_http_requests_duration_seconds_bucket{endpoint="/auth",method="GET",status="401",le="0.025"} 2
akas_http_requests_duration_seconds_bucket{endpoint="/auth",method="GET",status="401",le="0.05"} 2
akas_http_requests_duration_seconds_bucket{endpoint="/auth",method="GET",status="401",le="0.1"} 2
akas_http_requests_duration_seconds_bucket{endpoint="/auth",method="GET",status="401",le="0.25"} 2
akas_http_requests_duration_seconds_bucket{endpoint="/auth",method="GET",status="401",le="0.5"} 2
akas_http_requests_duration_seconds_bucket{endpoint="/auth",method="GET",status="401",le="1"} 2
akas_http_requests_duration_seconds_bucket{endpoint="/auth",method="GET",status="401",le="2.5"} 2
akas_http_requests_duration_seconds_bucket{endpoint="/auth",method="GET",status="401",le="5"} 2
akas_http_requests_duration_seconds_bucket{endpoint="/auth",method="GET",status="401",le="10"} 2
akas_http_requests_duration_seconds_bucket{endpoint="/auth",method="GET",status="401",le="+Inf"} 2
akas_http_requests_duration_seconds_sum{endpoint="/auth",method="GET",status="401"} 0.005529125
akas_http_requests_duration_seconds_count{endpoint="/auth",method="GET",status="401"} 2
# HELP akas_http_requests_total Total number of HTTP requests
# TYPE akas_http_requests_total counter
akas_http_requests_total{endpoint="/auth",method="GET",status="401"} 2
# HELP auth_akas_requests_total Total number of requests for auth with custom labels
# TYPE auth_akas_requests_total counter
auth_akas_requests_total{endpoint="/auth",metadata="-",method="GET",status="401",x_original_host="-"} 2
```
### `/health`
Returns `200 OK` to indicate the service is healthy and operational.
### `/auth-unauthorized`
Always return `401 Unauthorized` without checking the key (for testing purposes or disable access).
## Features & Limitations
- [x] Authorization by HTTP Bearer key.
- [x] Configuration:
- [x] via command line arguments.
- [x] via environment variables.
- [x] Subcommand `serve`: Start server
- [ ] Subcommand `load`: Load file
- [ ] Subcommand `status`: Get Hash of input file, datetime of load, number of valid keys.
- [x] load plain keys file (plain text) by curl.
- [x] load hash keys file (hashed - sha256) by curl.
- [x] Plain or hashed keys loaded and saved in a Rust [HashSet](https://doc.rust-lang.org/std/collections/struct.HashSet.html) for a fast authorization check.
- [x] Check of the key format during the loading process of the file based keys storage:
- [x] prefix and length for plain keys file.
- [x] SHA-256 for hashed keys file.
- [x] Initial check of the input key format in the header (length and prefix) [optional].
- [x] Endpoints:
- [x] `/auth`: default endpoint.
- [x] `/load`: Load new plain/hash keys file.
- [x] `/status`: Get Hash of input file, datetime of load, number of valid keys.
- [x] `/health`: indicate the service is healthy and operational.
- [x] `/auth-unauthorized`: always return `401 Unauthorized` without checking the key.
- [x] Admin key in header bearer for `/load` and `/status`.
- [x] Binaries compatibility for Linux with no dependencies:
- [x] x86-64 and arm64.
- [x] glibc (debian, ubuntu, fedora...) and musl libc (alpine ...).
- [x] Tests:
- [x] Unit tests (Rust).
- [x] Functional tests (Robot Framework).
- [x] Gitlab CI/CD Pipeline to auto-publish new versions.
- [x] Renovate Bot auto-update dependencies.
- [ ] AKAS packaged in a distroless Docker image.
- [x] Log implemenations with `https://crates.io/crates/tracing-actix-web`
- [x] Log requests:
- [x] All requests: Level Info
- [x] Only unauthorized requests (401): Level Warn
- [x] Json log format
- [x] Length truncation of header fields
- [x] Metadata field
- [x] Prometheus metrics
- [ ] `/load-diff` Load diff file
- [ ] Cache implementation for faster access to key authorization without SHA-256 operation (LRU Cache).
## Installation
- Binary file installation on Linux via the [GitLab package registry of the project](https://gitlab.com/op_so/projects/akas/-/packages):
- 2 architectures:
- akas-x86_64-linux-<gnu|musl>.tar.gz : x86_64 (Intel, AMD).
- akas-aarch64-linux-<gnu|musl>.tar.gz : arm64
- 2 C standard library with no dependencies:
- akas-<x86_64|aarch64>-linux-gnu.tar.gz : glibc for Debian, Ubuntu, Fedora...
- akas-<x86_64|aarch64>-linux-musl.tar.gz : musl libc for Alpine
- With a Rust environment, running this command will globally install the akas binary:
```shell
cargo install akas
```
## Log
The level of log is set with the `RUST_LOG` environment variable:
- error - Requests are not logged.
- warn - Only unauthorized requests (401) are logged.
- info (default) - all requests are logged.
- debug
- trace
- off
## Access log in jsonl format
```json
{
"timestamp": "2025-05-29T22:25:56.133350Z",
"level": "INFO",
"fields": {
"message": "access authorized",
"key_hash": "92c55f4d88b1",
"forwarded_for": "203.0.113.195",
"original_host": "my-host.com",
"original_uri": "/some/path",
"metadata": "my-metadata",
"access": "authorized"
},
"target": "akas"
}
{
"timestamp": "2025-05-29T22:25:56.135564Z",
"level": "WARN",
"fields": {
"message": "access unauthorized",
"key_hash": "24328022c7341148e4d84fab687dff6bd1f7c836a73a307ceb3357a3b9bb2d9d",
"forwarded_for": "203.0.113.195",
"original_host": "my-host.com",
"original_uri": "/some/path",
"metadata": "my-metadata",
"access": "unauthorized"
},
"target": "akas"
}
```
| `timestamp` | Date and time in ISO 8601 format | |
| `level` | Log level | |
| `fields.message` | Message | |
| `fields.key_hash` | sha256 key of the user, limited to 12 characters when authorized | |
| `fields.forwarded_for` | Extract of `x-forwarded-for` header field set by nginx, if not set: `-` | `--original-length` option or `AKAS_ORIGINAL_LENGTH` env. variable [default: 100] |
| `fields.original_host` | Extract of `x-original-host` header field set by nginx, if not set: `-` | `--original-length` option or `AKAS_ORIGINAL_LENGTH` env. variable [default: 100] |
| `fields.original_uri` | Extract of `x-original-uri` header field set by nginx, if not set: `-` | `--original-length` option or `AKAS_ORIGINAL_LENGTH` env. variable [default: 100] |
| `fields.metadata` | Extract of `x-akas-metadata` header field set by nginx, if not set: `-` | `--metadata-length` option or `AKAS_METADATA_LENGTH` env. variable [default: 0] |
| `fields.access` | `authorized`, `unauthorized` | |
| `target` | Component | |
More details:
- [Env logger](https://docs.rs/env_logger/latest/env_logger/)
## Tests
AKAS employs two types of tests to ensure its quality:
- **Unit tests** are written in Rust.
- **Functional tests** are managed via [Robot Framework](https://robotframework.org/) and reside in a dedicated repository: [AKAS Functional Tests](https://gitlab.com/op_so/projects/akas-tests).
## Development
- Clone the source repository: `git clone https://gitlab.com/op_so/projects/akas.git`
- To format and lint:
```shell
cargo fmt # cargo fmt -- --check
cargo clippy # Rust linter
```
- To test:
```shell
cargo test # Unit and integration tests
cargo tarpaulin --ignore-tests # Code coverage
cargo audit # Security audit
```
- To run: `cargo run`
- To build:
```shell
cargo build # Debug binary target/debug/akas
cargo build --release # Release binary target/release/akas
```
## Authors
- **FX Soubirou** - _Initial work_ - [GitLab repositories](https://gitlab.com/op_so)
## License
This program is free software: you can redistribute it and/or modify it under the terms of the MIT License (MIT).
See the [LICENSE](https://opensource.org/licenses/MIT) for details.