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>
{
"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"
}
context
(Mandatory) The context where users will access your api. (Example: https://domain.com/capi/your-api-context).mappingList
(1-N Array) - If you specify more than one mapping then: If fail-over and round-robin are enabled, CAPI will round-robin between all healthy endpoints. If only fail-over is enabled, then one node will be used only as backup.httpProtocol
(Mandatory) (HTTP, HTTPS) - If you are exposing on HTTPS it is important to add your certificate to CAPI trust store. CAPI Manager exposes an API for managing your certificates.httpMethod
(Default ALL) - If no http method is specified, CAPI will expose all standard methods for your API (GET,POST,PUT,DELETE). If you specify POST, only post calls to your API will be load balanced.matchOnUriPrefix
(Default true), if true, you don't need to specify a definition (Swagger) for your API. CAPI will allow all paths. (Example: /your-api-context/clients /your-api-context/customer/foo/bar?action=example).stickySession
(Default false) - If you enable sticky sessions then you also need to provide stickySessionParam
and stickySessionParamInCookie
(Example: stickySession=true
, stickySessionParam=X_KEY
,stickySessionParamInCookie=true
: CAPI will look for a cookie named X_KEY, and associate the value with a random node, subsequent calls with the same cookie value will be forwarded to the same node. If that node becames unavailable CAPI returns a 503 to the client and starts all over again.)ingress
(default false) - If one of your mapping is pointing to a Kubernetes ingress, ingress
should be true. This is because Ingress Controller needs to evaluate the Host header to determine to which service to forward the request. Check the documentation here: https://kubernetes.io/docs/concepts/services-networking/ingress/#ingress-rulesCAPI Metrics are available on http://localhost:8381/metrics
/metrics/routes
/metrics/capi
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:
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:
capi_
in the name)key
and secret
)role
on Keycloak and associate it to your client).There are 2 ways to work with Authorization on CAPI.
oauth2.provider.keys
.
#example
oauth2:
provider:
enabled: true
keys: http://localhost:8080/realms/master/protocol/openid-connect/certs
Manager
) to create roles and groups directly on Keycloak. In this case you need the following properties:
oauth2:
provider:
enabled: true
keys: http://localhost:8080/realms/master/protocol/openid-connect/certs
host: http://localhost:8080
realm: /realms/your-realm
clientId: <a client with realm management permissions>
clientSecret: <the secret used by CAPI to authenticate to Keycloak>
oauth2.provider.keys
.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"
}
subscription
is present and if so, if any group in the subscription list matches the subscriptions-groups of your service. see Service.serviceMeta.subscription-groups
Example for a subscription group called webapp1
:
{
"exp": 1695022079,
"iat": 1695022019,
"iss": "http://localhost:8080/realms/master",
"azp": "example-client",
"subscriptions": [
"/webapp1"
],
"client_id": "example-client"
}
If you want CAPI to perform authorization before routing the traffic to your Service, you will have to do the following:
Authorization
)Service.serviceMeta.secured
) (This is performed during route creation) (See Consul Integration
)Service.serviceMeta.subscriptionGroup
)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:
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
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.
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.
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");
}
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");
}
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.
Service.serviceMeta.type
to websocket
.(See How to declare your Service to CAPI
)
capi:
reverse:
proxy:
enable: true
host: https://your.host
capi:
traces:
enabled: true
endpoint: http://localhost:9411/api/v2/spans
server:
ssl:
enabled: true
key-store-type: PKCS12
key-store: /your/path/capi.p12
key-store-password: capi-lb
key-alias: capi
To create your cluster and resources, please check the documentation on your Kubernetes service. The following charts were tested on Minikube, EKS and OpenShift.
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
Authorization
)$ 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.
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: