surisoft-io / capi

CAPI Load Balancer
Apache License 2.0
20 stars 2 forks source link
CAPI

CAPI-LB License Docker Image Version (latest by date)


Share your use case with us

CAPI Gateway Documentation

Light Apache Camel API Gateway

Supports:

Enable Hashicorp Consul

  consul:
    host: http://localhost:8500
    discovery:
      enabled: true
      timer:
        interval: 10000

The interval will determine how often CAPI will pull Consul for changes. CAPI consumes the Catalog API from Consul to discover new services. Here is an example of how to declare your service to be discovered by CAPI (we will use a Spring Boot application): You need to include all the required Consul dependencies on your project:

<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-starter-consul</artifactId>
  <version></version>
</dependency>
<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-starter-consul-discovery</artifactId>
  <version></version>
</dependency>

Example of an Service definition

  {
    "name": "test-service",
    "context": "/test-service/dev",
    "mappingList": [
      {
        "rootContext": "/",
        "hostname": "domain1",
        "port": 8080,
        "ingress": false
      },
      {
        "rootContext": "/",
        "hostname": "domain2",
        "port": 8080,
        "ingress": false
      }
    ],
    "serviceMeta": {
      "group": "dev",
      "root-context": null,
      "schema": null,
      "secured": false,
      "tenant_aware": false,
      "tenant_id": null,
      "X-B3-TraceId": false,
      "ingress": null,
      "sticky_session_enabled": true,
      "sticky_session_type": "cookie",
      "sticky_session_key": "smkSession",
      "type": "rest",
      "subscription-group": null,
      "keep-group": true
    },
    "roundRobinEnabled": false,
    "failOverEnabled": false,
    "matchOnUriPrefix": true,
    "forwardPrefix": false,
    "registeredBy": "io.surisoft.capi.service.ConsulNodeDiscovery"
}

Field Description

Metrics Endpoint

CAPI Metrics are available on http://localhost:8381/metrics

Manage your trust store

Certificate management is disabled by default, to enable it you need to provide a valid path to a truststore. CAPI will not change JVM default certificate. To enable start CAPI with the following attributes:

capi:
  trust:
    store:
      enabled: true
      path: /your/path/cacerts 
      password: changeit

With the Certificate Management enabled you can:

Manage your Clients (Authorization) Only available if your oauth2 provider is Keycloak

Client management is disabled by default, to enable it you need to enable oidc.provider.enabled. See Authorization With the Client Management enabled you can:

OAUTH2 Authorization (REST and Websocket)

There are 2 ways to work with Authorization on CAPI.

Example for a service called test-service, available on /capi/test-service/dev:

{
  "exp": 1695021769,
  "iat": 1695021709,
  "iss": "http://localhost:8080/realms/master",
  "azp": "example-client",
  "realm_access": {
    "roles": [
      "test-service:dev",
      "default-roles-master",
      "offline_access",
      "uma_authorization"
    ]
  },
  "client_id": "example-client"
}

Protect your Service (REST and Websocket)

If you want CAPI to perform authorization before routing the traffic to your Service, you will have to do the following:

OPA Authorization (REST and Websocket)

OPA is a Policy-based control for cloud native environments. For more information about OPA: https://www.openpolicyagent.org/

CAPI uses OPA Policies.

OPA policies are expressed in a high-level declarative language called Rego. Rego (pronounced “ray-go”) is purpose-built for expressing policies over complex hierarchical data structures. For detailed information on Rego see the Policy Language documentation.

You can provide your own OPA instance or use our helm charts. CAPI only needs to be able to access OPA API's.

To start CAPI with support for OPA, please make sure to provide the following environment properties:

opa
  enabled: true
  endpoint: http://localhost:8181

Imagine the following scenario:

You want CAPI to only allow traffic to your service if the following conditions are met:

For these requirements lets design the following REGO.

package capi.test.dev

import future.keywords.if
import future.keywords.in

default allow := false

jwks := `{
    "keys": [
        {
        "kty": "RSA",
        "e": "AQAB",
        "use": "sig",
        "kid": "test",
        "alg": "RS256",
        "n": "zYF3UBCfWxTKzkK.........."
        }
    ]
}`

clients := ["my-azp" ]

current_time = time.now_ns() / 1000000000

allow if {
    clients[_] = claims.azp
    current_time < claims.exp
}

claims := payload if {
    io.jwt.verify_rs256(input.token, jwks)
    [_, payload, _] := io.jwt.decode(input.token)
}

After creating this REGO you will need to publish on OPA:

curl --request PUT \
  --url http://localhost:8181/v1/policies/capi/test/dev \
  --data 'package capi.eu_search.dev

import future.keywords.if
import future.keywords.in

default allow := false

jwks := `{
    "keys": [
        {
        "kty": "RSA",
        "e": "AQAB",
        "use": "sig",
        "kid": "eucommission",
        "alg": "RS256",
        "n": "zYF3UBCfWxTKzkK-CTK--y98RFwa2uXUFXOZAr35AJ-nzfDUvEM8RaoSqFofCSjzWLvd9OWuAGv59jOgE_uLVqZjr52hs32w9YLjL6vct7lh264omqxfpblsIp-yEug8rYNYdfwyM-AR-htkurjMSTK7NmeKODlekwItv1E4u5VfSr3hf8SIq0SbqDjnaW7yrWn0N9p6B37UkPV_Cahrn5_5kPYqHm_zSaghviqQh_RjaH2B0yRSaRKzDZf4VjtlXgrd3AoWxwrkmcKDWy0_nQhlcK2zTNCuu0stInbtJ79EFUKJkAOUhuoZGHuivnXDVGssZpTzNPe54-ajWthEqw"
        }
    ]
}`

clients := ["pAf3YdVriLyTR5r84dvMGL0Cc8Ua" ]

current_time = time.now_ns() / 1000000000

allow if {
    clients[_] = claims.azp
    current_time < claims.exp
}

claims := payload if {
    io.jwt.verify_rs256(input.token, jwks)
    [_, payload, _] := io.jwt.decode(input.token)
}'

You should be ready to protect your service using OPA. As always, you will need to register your service on Consul, so CAPI can discover. Here is a sample metadata for your service (Spring Boot using Consul Starter).

spring:
  application:
    name: test
  cloud:
    consul:
      enabled: true
      port: 8500
      host: http://localhost
      discovery:
        instance-id: ${info.app.environment}-localhost-${server.port}
        instance-group: ${info.app.environment}
        scheme: http
        hostname: localhost
        port: 8080
        metadata:
          group: dev
          secured: true
          opa-rego: capi/test/dev
        health-check-url: http://localhost:${server.port}/actuator/health

CAPI Websocket Support.

You can have CAPI acting as a Websocket Gateway. The main features of the Websocket Support are:

Websocket is disabled by default, to enable, just run CAPI with the following configuration:

  websocket:
    enabled: true
    server:
      host: localhost
      port: 8381      

With the following configuration, CAPI will be listening for Websocket requests on localhost port 8381.

Important information regarding Websockets.

CAPI will only look into the initial HTTP request, for authorization if needed. After the protocol update, you should manage the connection between your websocket client and your websocket server. If your client or server drops the connection, you will need to start a new request.

Example of a happy path using an anonymous (unprotected) web native connection request to CAPI.

websocket: WebSocket | undefined;
endpoint: string = "ws://localhost:8381/capi/your-websocket-server/your-version/your-path";
this.websocket = new WebSocket(this.endpoint);
this.websocket.onopen = (event: any) => {
   console.log("Connected to Your Websocket Server via CAPI");
}

Example of a happy path using a protected web native connection request to CAPI. An access token needs to be sent.

websocket: WebSocket | undefined;
endpoint: string = "ws://localhost:8381/capi/your-websocket-server/your-version/your-path?access_token=<your JWT access token>";
this.websocket = new WebSocket(this.endpoint);
this.websocket.onopen = (event: any) => {
   console.log("Connected to Your Websocket Server via CAPI");
}

For authentication CAPI supports the standard Authorization header, or a query parameter with the key access_token.

Important info about Authorization: CAPI actively supports Keycloak as an oauth2 provider, but you should still be able to use any oauth2 compliant provider. See Authorization section to know how CAPI authorizes a request.

For CAPI to know that your API is a Websocket, please set Service.serviceMeta.type to websocket.

(See How to declare your Service to CAPI)

Installing and Operating CAPI

Run CAPI behind a reverse proxy? Enable the following:

capi:
  reverse:
      proxy:
        enable: true
        host: https://your.host

Enable Tracing (Tested with Zipkin, OpenTelemetry Collector)

capi:
  traces:
    enabled: true
    endpoint: http://localhost:9411/api/v2/spans

Running CAPI on HTTPS

server:
  ssl:
    enabled: true
    key-store-type: PKCS12
    key-store: /your/path/capi.p12
    key-store-password: capi-lb
    key-alias: capi

Install CAPI on Kubernetes

To create your cluster and resources, please check the documentation on your Kubernetes service. The following charts were tested on Minikube, EKS and OpenShift.

CAPI Helm charts available here: CAPI-LB Helm Charts

Install CAPI Gateway Helm Charts

$ helm install "capi" ./capi-lb-charts

Delete the helm charts

$ helm delete capi
$ eksctl delete cluster --name capi-demo-1

Install CAPI fat jar on a VM

The example below has the following dependencies:

$ mkdir logs
$ git clone this repo
$ mvn clean package
$ java \
     -XX:InitialHeapSize=2g \
     -XX:MaxHeapSize=2g \
     -XX:+HeapDumpOnOutOfMemoryError \
     -XX:HeapDumpPath="$PWD/logs/heap-dump.hprof" \
     -Dspring.profiles.active=prod \
     -Dcapi.consul.discovery.enabled=true \
     -Dcapi.consul.host=http://localhost:8500 \
     -Dcapi.consul.discovery.time.interval=20 \
     -Dcapi.manager.security.enabled=false \
     -Dcapi.disable.redirect=true \
     -Dcapi.trust.store.enabled=true \
     -Dcapi.trust.store.path=$CAPI_HOME/client-truststore.jks \
     -Dcapi.trust.store.password=changeit \
     -Dlog-dir=$LOGS_DIR \
     -Dserver.ssl.key-store-type=JKS \
     -Dserver.ssl.key-store=$CAPI_HOME/capi.jks \
     -Dserver.ssl.key-store-password=changeit \
     -Dserver.ssl.key-alias=capi \
     -Dcapi.traces.enabled=true \
     -Dcapi.traces.endpoint=http://localhost:9411/api/v2/spans \
     -Dserver.ssl.enabled=true \
     -Dserver.port=$CAPI_PORT \
     -Doauth2.cookieName=Authorization-Cookie-Name \
     -Doauth2.provider.enabled=true \
     -Doauth2.provider.keys=https://some-auth-server/.well-known/jwks.json \
     -jar <CAPI_JAR> > $PWD/logs/capi.log 2>&1 & echo $! > capi.pid

In the example above CAPI will be available with CAPI Manager secured and certificate management enabled.

Install CAPI on Docker (with docker-compose)

Create an init.sql file, for CAPI database to be created on start, with the following script:

CREATE DATABASE IF NOT EXISTS capi;

You will see this file mapped in the docker-compose.yml below.

version: "3"
services:
  capi:
    container_name: capi
    image: surisoft/capi-lb:3.0.20
    ports:
      - "8380:8380"
    environment:
      - spring.datasource.url=jdbc:mysql://capi-db:3306/capi
      - spring.datasource.username=root
      - spring.datasource.password=secret
      - capi.manager.security.enabled=false
    volumes:
      - ./logs:/capi/logs
    depends_on:
      - capi-db
    networks:
      capi-network:
  capi-db:
    container_name: capi-db
    image: mysql:latest
    ports:
      - "3306:3306"
    command: --init-file /data/application/init.sql
    volumes:
      - ./init.sql:/data/application/init.sql
    environment:
      - MYSQL_ROOT_USER=root
      - MYSQL_ROOT_PASSWORD=secret
    networks:
      capi-network:
networks:
  capi-network: