Skip to content

bcgov/risk-based-authn

Repository files navigation

Risk Assessment Engine πŸ›‘οΈ

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.

Overview

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.

alt text

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.

πŸš€ Key Features

  • 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 average and override.
  • Graceful Shutdown: Handles graceful shutdown of the server to prevent data loss.
  • Health Check Endpoint: Provides a /health endpoint 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.

πŸ› οΈ Tech Stack

  • Backend:
    • Go
  • Configuration:
    • YAML (rules.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)
  • Data Store:
    • Redis (github.com/redis/go-redis/v9)
  • Environment Variables:
    • github.com/joho/godotenv
  • YAML Parsing:
    • gopkg.in/yaml.v3

πŸ“¦ Getting Started

Prerequisites

  • 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.git
  • asdf install

Running Locally

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: false

For 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.

  1. Create a .env file (optional) to configure environment variables. Example:

    PORT=8080
    
  2. Run services: docker-compose up

  3. Check config: When running locally, connection host for services (e.g. redis) will be "localhost" in rules.yaml

  4. Live reload the application: make watch

Build and Run Application

  • make build
  • make run

Run Tests

make test

πŸ’» Usage

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.

πŸ“‚ Project Structure

β”œβ”€β”€ 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

Rules

Velocity

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.

Horizontal Brute Force

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.

Impossible Travel

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.

🀝 Contributing

Contributions are welcome! Please follow these steps:

  1. Fork the repository.
  2. Create a new branch for your feature or bug fix.
  3. Make your changes and commit them with descriptive messages.
  4. Push your changes to your fork.
  5. Submit a pull request.

πŸ’– Thanks

Thank you for checking out this project! We hope it's helpful for your risk assessment needs.

GeoIP

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.mmdb

Or 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.

Generating sig

The server can be configured to use either HMAC or JWTs for authentication. Configure this in rules.yaml, e.g:

auth: 
  enabled: true
  method: jwt

If 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.

About

This is a risk based authentication engine

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors