Table of Contents
- REST Endpoints vs WebSocket Endpoints
- Denial-of-Service Attacks
- Rate Limits
- Count Limits
- Creating a secure WebSocket API from a target service
- Creating a secure WebSocket and HTTPS API from a target service
- Example API and Product YAMLs
REST Endpoints vs WebSocket Endpoints
Traditional REST endpoints allow only a single request to be sent and a single response to be received. This usually leads to a small amount of time to complete a REST request/response cycle. In comparison, a WebSocket starts out as a normal GET request that is later upgraded into a WebSocket request, where either the client or the server can send a message at any point in time. Given this asynchronous messaging, the main use for a WebSocket connection is to subscribe to an event and be notified when that event occurs, usually with some additional information about the event as well.
An example of this could be subscribing to a stock price change of a particular stock. A client would start by opening a WebSocket connection to the server. After the connection was established, the client would send a message over the connection with the name of the stock that it would want to get price updates on. Once the server received the request it would send an updated price for that stock every time it changed in the stock market. Without either the client or the server initiating a closure of the connection, the client in theory could receive stock price updates forever. Since, WebSocket connections could stay open forever this poses unique security concerns compared to REST endpoints.
Denial-of-Service Attacks
A common attack vector for a REST endpoint is a Denial-of-Service attack (DoS). A DoS attack is when an attacker maliciously floods a server with requests, causing the system to be overwhelmed and deny service to other, valid users. A specific way an attacker can perform a DoS attack is by using the memory needed to open a connection as an attack vector. Since a server has a finite amount of memory at its disposal and each request takes a certain amount of memory to complete, if a large number of requests came in at the same time, then the system could run out of memory and crash trying to handle too many requests.
Rate Limits
A common solution to a DoS attack that is used by REST endpoints is a rate limit. A rate limit allows a client a certain number of requests per some time period. For example, a server could limit the number of requests from a particular client to 5 requests per 3 seconds. This effectively constrains the amount of stress on the system to be dependent on the number of clients trying to access the server at once.
When applying this rate limit on a WebSocket endpoint a DoS attack is still possible. Compared to a REST connection, a WebSocket connection stays open indefinitely. So even when a rate limit is enforced, a single client could still overwhelm the system by continuing to open connections until the server runs out of memory. Even with a very low rate limit of 1 request per minute, the system would eventually accumulate thousands of connections by the end of a week.
Count Limits
Instead, the proper security measure to apply on a WebSocket endpoint is a count limit. A count limit allows each client only a particular number of requests to be open at once. For example, a client could be limited to 10 connections at any given time. The server keeps track of how many connections are open for a particular client, and replenishes the count when connections are closed so that the client can open new connections in its place when it desires. When a count limit is enforced on a WebSocket endpoint, that endpoint is now safe from DoS attacks that use connection memory as a way to bring down the system.
Creating a secure WebSocket API from a target service
You can use API Manager to create an API that can handle WebSocket connections securely to a backend that supports WebSocket connections.
- Create a new
API (REST, GraphQL, or SOAP)
and specify From target service
.
- Specify a target service URL that has the scheme
https
so that it passes the URL validation checks.
- Add the
wss
scheme to the schemes list in the API.
- Change the
invoke
policy in the assembly to a websocket-upgrade
policy.
- Add a
ratelimit
policy that references a countlimit
definition before the websocket-upgrade policy.
- Add
ratelimit
policies that reference a regular ratelimit
definition in both the request and response sub-assembly within the websocket-upgrade
policy.
- Test your new API with your favorite WebSocket tester (e.g. wscat npm cli tool)
Below is a screenshot showing the resulting API Assembly after following the steps above.
Creating a secure WebSocket and HTTPS API from a target service
You can use API Manager to create an API that can handle both WebSocket connections and regular HTTPS connections securely to a backend that supports both WebSocket and regular HTTPS connections.
- Create a new
API (REST, GrapQL, or SOAP)
and specify From target service
.
- Specify a target service URL that has the scheme
https
so that it passes the URL validation checks.
- Add the
wss
scheme to the schemes list in the API.
- Add a
switch
policy in the assembly with a condition that checks when request.websocket
is set to true
.
- Move the
invoke
policy into the otherwise
section of the new switch
that was added.
- Copy the
invoke
policy and put the new duplicate in the condition that was created for request.websocket = true
.
- Change the new duplicate
invoke
policy in the assembly to a websocket-upgrade
policy.
- Add a
ratelimit
policy that references a countlimit
definition before the websocket-upgrade
policy.
- Add
ratelimit
policies that reference a regular ratelimit
definition in both the request and response sub-assembly within the websocket-upgrade
policy.
- Add a
ratelimit
policy that references a regular ratelimit
definition before the invoke policy.
- Test your new API with your favorite WebSocket tester (e.g. wscat npm cli tool)
Below is a screenshot showing the resulting API Assembly after following the steps above.
Example API and Product YAMLs
Here is and API and Product YAML that I created following the "Creating a secure WebSocket API from a target service part of this blog. You should replace the target-url.value: https://mywebsocketendpointhere.com/ with your own valid WebSocket endpoint.
API YAML
swagger: '2.0'
info:
version: 1.0.0
title: test-yahel-websocket-api
x-ibm-name: test-yahel-websocket-api
basePath: /test-yahel-websocket-api
x-ibm-configuration:
properties:
target-url:
value: https://mywebsocketendpointhere.com/
description: URL of the proxy policy
encoded: false
cors:
enabled: true
gateway: datapower-api-gateway
type: rest
phase: realized
enforced: true
testable: true
assembly:
execute:
- ratelimit:
version: 2.2.0
title: count limit
source: plan-named
count-limit:
- name: websocket-connections
operation: inc
- websocket-upgrade:
title: websocket-upgrade
version: 2.0.0
verb: keep
target-url: $(target-url)
follow-redirects: false
timeout: 60
parameter-control:
type: allowlist
values: []
header-control:
type: blocklist
values: []
inject-proxy-headers: true
request-assembly:
execute:
- ratelimit:
version: 2.2.0
title: request rate limit
source: plan-named
rate-limit:
- name: websocket-request-ratelimit
operation: consume
response-assembly:
execute:
- ratelimit:
version: 2.2.0
title: response rate limit
source: plan-named
rate-limit:
- name: websocket-response-ratelimit
operation: consume
finally: []
activity-log:
enabled: true
success-content: activity
error-content: payload
paths:
/:
get:
responses:
'200':
description: success
schema:
type: string
consumes: []
produces: []
put:
responses:
'200':
description: success
schema:
type: string
consumes: []
produces: []
post:
responses:
'200':
description: success
schema:
type: string
consumes: []
produces: []
delete:
responses:
'200':
description: success
schema:
type: string
consumes: []
produces: []
head:
responses:
'200':
description: success
schema:
type: string
consumes: []
produces: []
patch:
responses:
'200':
description: success
schema:
type: string
consumes: []
produces: []
securityDefinitions:
clientID:
type: apiKey
in: header
name: X-IBM-Client-Id
security:
- clientID: []
schemes:
- https
- wss
Product YAML
info:
version: 1.0.0
title: test-yahel-websocket-product
name: test-yahel-websocket-product
gateways:
- datapower-api-gateway
plans:
default-plan:
title: Default Plan
description: Default Plan
rate-limits:
default:
value: 100/1hour
hard-limit: false
assembly-rate-limits:
websocket-request-ratelimit:
- value: 3/1minute
cache-only: true
is-client: true
use-api-name: false
use-app-id: false
use-client-id: false
hard-limit: true
websocket-response-ratelimit:
- value: 5/1minute
cache-only: true
is-client: true
use-api-name: false
use-app-id: false
use-client-id: false
hard-limit: true
assembly-count-limits:
websocket-connections:
- hard-limit: true
cache-only: true
is-client: true
use-api-name: false
use-app-id: false
use-client-id: false
auto-decrement: true
value: 2
apis:
test-yahel-websocket-api1.0.0: {}
apis:
test-yahel-websocket-api1.0.0:
$ref: test-yahel-websocket-api_1.0.0.yaml
visibility:
view:
type: public
orgs: []
tags: []
enabled: true
subscribe:
type: authenticated
orgs: []
tags: []
enabled: true
product: 1.0.0
#APIConnect
#RESTAPI