# AKAS: API Key Authorization Server [![Software License](https://img.shields.io/badge/license-MIT-informational.svg?style=for-the-badge)](LICENSE) [![semantic-release: angular](https://img.shields.io/badge/semantic--release-angular-e10079?logo=semantic-release&style=for-the-badge)](https://github.com/semantic-release/semantic-release) [![crates.io](https://img.shields.io/crates/v/akas?logo=rust&color=fc8d62&style=for-the-badge)](https://crates.io/crates/akas) [![Pipeline Status](https://img.shields.io/gitlab/pipeline-status/op_so/projects/akas?style=for-the-badge )](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. ``` txt Authorization: Bearer ``` ![akas diagram](https://gitlab.com/op_so/projects/akas/-/raw/main/akas_diag.svg?ref_type=heads "AKAS diagram") 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 ... ``` ![akas internal](https://gitlab.com/op_so/projects/akas/-/raw/main/akas_internal.svg?ref_type=heads "AKAS internal") ## Usage ```bash $ ./akas --help A HTTP API key-Based Authorization Server Usage: akas Commands: serve Start the server load Load keys from file 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 for /load and /status URI [env: AKAS_ADMIN_KEY=] [default: ] --no-admin-key No admin key flag -p, --port Port of the server [env: AKAS_PORT=] [default: 5001] --log-level Log level - default info [env: AKAS_LOG_LEVEL=] [default: info] --length Length of the key [optional] [env: AKAS_LENGTH=] [default: 0] --prefix Prefix of the key [optional] [env: AKAS_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-original-remote-addr $remote_addr; } } ``` 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 " http:///` ## 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 ``` | Field | Description | Required | Default | | ----------------- | ------------------------------------ | -------- | ------- | | `file` | File path of the keys file to upload | Yes | - | | `format` | Format of the keys | 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", "file_hash": "43fcba0b3a5ab1b302f9d13617ae4eec6ae623a7fd52437dd992d5dca115e68d", "file_date": "2024-11-09T14:07:23.416999558+00:00", "file_key_count": 150, "length": 42, "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. ### `/auth-ok` Always return `200 OK` without checking the key (mainly for testing purposes). ### `/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] `/auth-ok`: always return `200 OK` without checking the key. * [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. * [ ] 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 * [ ] 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-.tar.gz : x86_64 (Intel, AMD). * akas-aarch64-linux-.tar.gz : arm64 * 2 C standard library with no dependencies: * akas--linux-gnu.tar.gz : glibc for Debian, Ubuntu, Fedora... * akas--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 default format ``` text 2024-11-01T09:26:17.455655Z WARN HTTP request{http.method=GET http.route=/auth http.flavor=1.1 http.scheme=http http.host=localhost:5001 http.client_ip=127.0.0.1 http.user_agent=curl/7.88.1 http.target=/auth otel.name=GET /auth otel.kind="server" request_id=f8cb807d-666d-457a-957b-26b36ccb1a0f}: akas: sha256_key: f0755926b82353d817f2ca1011fd98179472c2df0fed2225580714d8ad09e336 - unknown-host - unknown-uri - access unauthorized 2024-11-01T09Z:26:20.182590Z INFO HTTP request{http.method=GET http.route=/auth http.flavor=1.1 http.scheme=http http.host=localhost:5001 http.client_ip=127.0.0.1 http.user_agent=curl/7.88.1 http.target=/auth otel.name=GET /auth otel.kind="server" request_id=97199511-7f08-4ee4-b4fa-71d7e669cfe2}: akas: sha256_key: 92c55f4d88b1 - unknown-host - unknown-uri - access authorized ``` | Value | Description | |-------------------------------| --------------------------------------------------------------------- | | `2024-11-01T09:26:17.455655Z` | Date and time in ISO 8601 format | | `WARN` | Log level | | `HTTP request{ ... }` | Request information | | `akas:` | Start of the application log message | | `sha256_key: 92c55f4d1...` | sha256 key of the user, limited to 12 characters when authorized | | `unknown-host` | Extract of `x-original-host` set by nginx, if not set: `unknown-host` | | `unknown-uri` | Extract of `x-original-uri` set by nginx, if not set: `unknown-uri` | | `access authorized` | Message | More details: * [Env logger](https://docs.rs/env_logger/latest/env_logger/) * [tracing-actix-web](https://github.com/LukeMathWalker/tracing-actix-web) ## 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 ``` * To test: ``` shell cargo test cargo tarpaulin --ignore-tests cargo audit ``` * To run: `cargo run -- --file tests/files/plain_key.txt` * 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.