This project is a robust risk assessment engine designed to evaluate and manage risks associated with various authentication events. It provides a flexible and configurable framework for defining risk assessment rules and aggregating risk scores. The engine supports multiple risk assessment strategies and can be easily extended to incorporate new rules and services. It solves the problem of efficiently and accurately assessing risks in real-time, enabling proactive security measures and informed decision-making.
An authentication server can send http requests with event data to the server. The server will evaluate the configured ruleset to return a score associated with the event. The authentication server can decide on how to continue depending on the result.
Depending on the configured ruleset, different supporting services can be included. For example, for rate-limiting logins from an IP address, a supporting redis service is required.
- Configurable Risk Rules: Define risk assessment rules using a YAML configuration file (
rules.yaml). Supports various rule types like velocity and denylist. - Real-time Risk Assessment: Processes incoming events and evaluates risks based on the defined rules.
- External Service Integration: Integrates with external services like NATS (for message publishing) and Redis (for data storage and rate limiting).
- Concurrent Processing: Executes risk assessment handlers concurrently to minimize latency.
- Flexible Risk Strategies: Supports multiple risk aggregation strategies, such as
averageandoverride. - Graceful Shutdown: Handles graceful shutdown of the server to prevent data loss.
- Health Check Endpoint: Provides a
/healthendpoint for monitoring the server's health. - CORS Support: Handles Cross-Origin Resource Sharing (CORS) to allow requests from different domains.
- Denylist Support: Block specific IPs or IP ranges using the denylist rule.
- Velocity Support: Rate limit actions from specific IPs using the velocity rule.
- Backend:
- Go
- Configuration:
- YAML (
rules.yaml)
- YAML (
- HTTP Router:
github.com/go-chi/chi/v5
- Middleware:
github.com/go-chi/chi/v5/middleware
- CORS:
github.com/go-chi/cors
- Message Queue:
- NATS (
github.com/nats-io/nats.go)
- NATS (
- Data Store:
- Redis (
github.com/redis/go-redis/v9)
- Redis (
- Environment Variables:
github.com/joho/godotenv
- YAML Parsing:
gopkg.in/yaml.v3
- Go (version 1.20 or higher)
- Docker (for running Redis and NATS locally)
If you use asdf there is a tool-versions file with the correct golang version. To install:
asdf plugin add golang https://github.com/asdf-community/asdf-golang.gitasdf install
If using the GeoIP service an mmdb file is required to do database lookups. This can be configured in the services block:
geoIP:
enabled: true
fileType: mmdb
path: ./GeoLite2-City2.mmdb
download: falseFor local running, set download: false and configure path to point to your file. When running on other servers, you can either use a volume mount for this file at the provided path, or configure it to be downloaded at startup from an s3 bucket.
-
Create a
.envfile (optional) to configure environment variables. Example:PORT=8080 -
Run services:
docker-compose up -
Check config: When running locally, connection host for services (e.g. redis) will be "localhost" in rules.yaml
-
Live reload the application:
make watch
make buildmake run
make test
Send a POST request to the /event endpoint with a JSON payload containing the event data.
Example:
{
"event": "login",
"data": {
"ip": "192.168.1.1"
}
}The server will process the event, evaluate the risk, and return a response with the risk score.
βββ cmd
β βββ api
β βββ main.go # Main application entry point
βββ internal
β βββ server
β βββ routes.go # Defines HTTP routes and request handlers
β βββ server.go # Defines the HTTP server and its configuration
βββ rules
β βββ denylist.go # Implements the denylist risk rule
β βββ import.go # Loads and parses risk rule configurations
β βββ velocity.go # Implements the velocity risk rule
β βββ velocity.go # Implements the velocity risk rule
βββ services
β βββ natsClient.go # Manages the NATS client connection
β βββ redisClient.go # Manages the Redis client connection
βββ util
β βββ constants.go # Defines constant values
β βββ helpers.go # Provides utility functions
β βββ types.go # Defines custom data types
βββ rules.yaml # Configuration file for risk assessment rules
βββ go.mod # Go module definition
βββ go.sum # Go module checksums
βββ README.md # This file
Measures the number of logins over a timeinterval from the same IP address, and fails if over the threshold.
Settings:
- intervalSeconds: The time interval in seconds to watch for logins
- limit: The maximum number of allowed attempts over the interval. An amount greater than this will fail.
Measures failed authentication attempts across different accounts from the same IP address.
Settings:
- intervalSeconds: The time interval in seconds to watch for failed attempts
- distinctAccounts: The maximum number of accounts for the IP to fail to authenticate to over the interval. An amount greater than this will fail.
Measures if an account has travelled faster than a given speed between logins. This rule uses the geopIP service, see here for more information on configuration.
Settings:
- speedKilometersPerHour: The maximum possible speed to allow users to have travelled.
Contributions are welcome! Please follow these steps:
- Fork the repository.
- Create a new branch for your feature or bug fix.
- Make your changes and commit them with descriptive messages.
- Push your changes to your fork.
- Submit a pull request.
Thank you for checking out this project! We hope it's helpful for your risk assessment needs.
IP location lookups currently only support mmdb file formats. When configuring the GeoIP service, you can either mount an mmdb file in a volume at a configured path, or configure to download it from an s3 bucket at startup. If using the donwload option, you can either authenticate via AWS roles (e.g. giving an ECS task role privileges to read your bucket), or if external to AWS providing the environment variables below:
AWS_REGION
AWS_ACCESS_KEY_ID
AWS_SECRET_ACCESS_KEY
To configure which file to download, you can either set it in the config file, e.g:
source:
sourceType: s3
bucketName: mybucket
bucketKey: myfile.mmdbOr set the bucket and key using the environment variables:
GEOIP_S3_BUCKET_KEY=myfile.mmdb
GEOIP_S3_BUCKET_NAME=mybucket
If both are provided, the environment variable will be used.
The server can be configured to use either HMAC or JWTs for authentication. Configure this in rules.yaml, e.g:
auth:
enabled: true
method: jwtIf using HMAC, the server should be provided an env variable in the format <API_KEY_ID>=<API_SECRET>. e.g. KEY_1=secret1. You can use this format to have multiple keys on the same server.
If ALLOWED_SKEW_MINUTES is set to 0 it will be ignored (useful for local development).
You can use ./.bin/hmac_generator.sh or the function below to generate a key and signature set for testing:
func print() {
keyID := "abcd1234" // Match to what's in your .env
secret := []byte("supersecret1") // match to what's in your .env
timestamp := fmt.Sprintf("%d", time.Now().Unix())
message := timestamp // or timestamp + body if you include body
mac := hmac.New(sha256.New, secret)
mac.Write([]byte(message))
signature := hex.EncodeToString(mac.Sum(nil))
fmt.Println("X-Key-ID:", keyID)
fmt.Println("X-Timestamp:", timestamp)
fmt.Println("X-Signature:", signature)
}If using JWTs for auth, you should provide JWKS_URL and JWT_AUD environment variables.