HTTP

Octelium supports the HTTP mode for any HTTP-based upstreams (e.g. HTTP APIs, web-apps, websockets, gRPC, etc...). You can create an HTTP Service simply as follows:

1
kind: Service
2
metadata:
3
name: svc1
4
spec:
5
mode: HTTP
6
port: 8080
7
config:
8
upstream:
9
url: https://example.com

Access Control

You can control access based on the HTTP request information. Such information are stored in ctx.request.http where it contains the method, path, headers map as well as the scheme values. Here is a detailed example of a inline Policy that controls access based on HTTP-specific information:

1
kind: Service
2
metadata:
3
name: svc1
4
spec:
5
mode: HTTP
6
port: 8080
7
config:
8
upstream:
9
url: https://example.com
10
authorization:
11
inlinePolicies:
12
- spec:
13
rules:
14
- effect: ALLOW
15
condition:
16
all:
17
of:
18
- match: ctx.request.http.method in ["GET", "POST", "PUT", "DELETE"]
19
- match: ctx.request.http.path.startsWith("/apis")
20
- match: ctx.request.http.headers["x-custom-header"] == "this-value"
21
- match: ctx.request.http.scheme == "http"

Secret-less Access

Octelium is capable of supporting secret-less access to protected upstreams (read more here) by injecting HTTP-based credentials such as API keys, which are stored and represented in the Cluster as Secrets (read more about Secrets here), on-the-fly to the protected upstream. This eliminates the need to share, mange, distribute and store such typically long-lived, privileged and prone-to-misuse credentials. Octelium supports the following authentication methods:

  • Basic authentication
  • Bearer authentication
  • Custom header authentication
  • OAuth2 client credentials flow

Basic Authentication

Here is an example of basic authentication:

1
kind: Service
2
metadata:
3
name: svc1
4
spec:
5
mode: HTTP
6
port: 80
7
config:
8
upstream:
9
url: https://api.example.com
10
http:
11
auth:
12
basic:
13
username: user1
14
password:
15
fromSecret: password-secret

Bearer Authentication

Here is an example of bearer authentication:

1
kind: Service
2
metadata:
3
name: svc1
4
spec:
5
mode: HTTP
6
port: 80
7
config:
8
upstream:
9
url: https://api.example.com
10
http:
11
auth:
12
bearer:
13
fromSecret: apikey1

Authentication via a Custom Header

You can also set your Secret value to a custom request header to the upstream. Here is an example:

1
kind: Service
2
metadata:
3
name: svc1
4
spec:
5
mode: HTTP
6
port: 80
7
config:
8
upstream:
9
url: https://api.example.com
10
http:
11
auth:
12
custom:
13
header: X-AUTH-1
14
value:
15
fromSecret: apikey1

OAuth2 Client Credentials

You can also use OAuth2 client credentials authentication flow (read more here). Here is an example:

1
kind: Service
2
metadata:
3
name: svc1
4
spec:
5
mode: HTTP
6
port: 80
7
config:
8
upstream:
9
url: https://api.example.com
10
http:
11
auth:
12
oauth2ClientCredentials:
13
clientID: X-AUTH-1
14
clientSecret:
15
fromSecret: apikey1
16
tokenURL: https://example.com/auth/v1
17
scopes: ["scope1", "scope2"]

HTTP 2/0

Listening on HTTP 2/0

By default, the Service listens to HTTP 1.1 connections unless TLS is enabled (read more here). However you can force the Service to accept HTTP 2.0 connections as follows:

1
kind: Service
2
metadata:
3
name: svc1
4
spec:
5
mode: HTTP
6
port: 80
7
config:
8
upstream:
9
url: https://example.com
10
http:
11
listenHTTP2: true

HTTP 2/0 Upstream

A Service by default assumes that the upstream accepts HTTP 1.1 connections unless it's serving TLS connections. You can force the Service to forward the requests over HTTP 2/0 as follows:

1
kind: Service
2
metadata:
3
name: svc1
4
spec:
5
mode: HTTP
6
port: 80
7
config:
8
upstream:
9
url: https://example.com
10
http:
11
isUpstreamHTTP2: true

Header Manipulation

You can easily manipulate both request and response HTTP headers as follows:

1
kind: Service
2
metadata:
3
name: svc1
4
spec:
5
mode: HTTP
6
port: 80
7
config:
8
upstream:
9
url: https://example.com
10
http:
11
header:
12
addRequestHeaders:
13
- key: X-HEADER-1
14
value: VALUE-1
15
- key: X-HEADER-2
16
value: VALUE-2
17
removeRequestHeaders:
18
- X-HEADER-3
19
- X-HEADER-4
20
addResponseHeaders:
21
- key: X-HEADER-5
22
value: VALUE-5
23
- key: X-HEADER-6
24
value: VALUE-6
25
removeResponseHeaders:
26
- X-HEADER-7
27
- X-HEADER-8

Path Manipulation

You can add a prefix to the request's path as follows:

1
kind: Service
2
metadata:
3
name: svc1
4
spec:
5
mode: HTTP
6
port: 80
7
config:
8
upstream:
9
url: https://example.com
10
http:
11
path:
12
addPrefix: /prefix

Now a request with the path /v1 will become /prefix/v1.

And you can also remove a prefix from the path as follows:

1
kind: Service
2
metadata:
3
name: svc1
4
spec:
5
mode: HTTP
6
port: 80
7
config:
8
upstream:
9
url: https://example.com
10
http:
11
path:
12
removePrefix: /prefix

Now a request with the path /prefix/v1 will become /v1.

And you can replace a prefix by another by combining both removePrefix and addPrefix as follows:

1
kind: Service
2
metadata:
3
name: svc1
4
spec:
5
mode: HTTP
6
port: 80
7
config:
8
upstream:
9
url: https://example.com
10
http:
11
path:
12
removePrefix: /v1
13
addPrefix: /v2

Now a request with the path /v1/path will become /v2/path.

Request Body

Octelium enables you to buffer the entire request body before forwarding it to the upstream for it to be checked and used in your access Policies (read more here) and dynamic configuration (read more here).

1
kind: Service
2
metadata:
3
name: svc1
4
spec:
5
mode: HTTP
6
port: 80
7
config:
8
upstream:
9
url: https://example.com
10
http:
11
enableRequestBuffering: true

This will include your entire body as base64 string inside the ctx.request.http.body (read more here) field used by your Policies and dynamic configuration rules.

Request Body Maximum Size

You can set the limit on the maximum number of bytes for a request body as follows:

1
kind: Service
2
metadata:
3
name: svc1
4
spec:
5
mode: HTTP
6
port: 80
7
config:
8
upstream:
9
url: https://example.com
10
http:
11
enableRequestBuffering: true
12
body:
13
maxRequestSize: 100000

JSON Request Body

In many cases such as in REST APIs, the request body is represented by a JSON format. You can enable the body mode as JSON to verify that the request body is a valid JSON as follows:

1
kind: Service
2
metadata:
3
name: svc1
4
spec:
5
mode: HTTP
6
port: 80
7
config:
8
upstream:
9
url: https://example.com
10
http:
11
enableRequestBuffering: true
12
body:
13
mode: JSON

The JSON modes lets you to directly use the fields of the JSON structure in your Policies and dynamic configuration rules inside the ctx.request.http.bodyMap field. Here is an example, for a request body with a schema that looks as following:

1
{
2
"key1": "value1",
3
"key2": 5,
4
"key3:": {
5
"key4": "value4"
6
}
7
}

You can set your access Policy rules as follows:

1
kind: Service
2
metadata:
3
name: svc1
4
spec:
5
mode: HTTP
6
port: 8080
7
config:
8
upstream:
9
url: https://example.com
10
authorization:
11
inlinePolicies:
12
- spec:
13
rules:
14
- effect: ALLOW
15
condition:
16
all:
17
of:
18
- match: ctx.request.http.bodyMap.key1 == "value1"
19
- match: ctx.request.http.bodyMap.key2 > 2
20
- match: ctx.request.http.bodyMap.key3.key4 == "value4"

Cross-Origin Resource Sharing (CORS)

You can also enforce CORS rules as follows:

1
kind: Service
2
metadata:
3
name: svc1
4
spec:
5
mode: HTTP
6
port: 80
7
config:
8
upstream:
9
url: https://example.com
10
http:
11
cors:
12
allowMethods: "POST, GET, OPTIONS"
13
allowHeaders: "X-PINGOTHER, Content-Type"
14
maxAge: "86400"

Dynamic Configuration

You can use dynamic configuration in order to, for example, route to different upstreams and/or setting different different upstream credentials, set different request/response headers, etc... depending on the request's context (read more about dynamic configuration here). Here is a simple example for an API gateway (read more here):

1
kind: Service
2
metadata:
3
name: my-api
4
spec:
5
mode: HTTP
6
isPublic: true
7
dynamicConfig:
8
configs:
9
- name: v1
10
upstream:
11
url: https://apiv1.example.com
12
http:
13
path:
14
removePrefix: /v1
15
- name: v2
16
upstream:
17
url: https://apiv2.example.com
18
http:
19
path:
20
removePrefix: /v2
21
rules:
22
- condition:
23
match: ctx.request.http.path.startsWith("/v1")
24
configName: v1
25
- condition:
26
match: ctx.request.http.path.startsWith("/v2")
27
configName: v2

Visibility

The Service emits access logs in real time to the audit collector. Each log provides application-layer aware information about the request such as the request path, method, etc... as well as the response code and body size. Here is an example:

1
{
2
"apiVersion": "core/v1",
3
"entry": {
4
"common": {
5
"endedAt": "2025-03-21T20:20:57.970432905Z",
6
"isAuthenticated": true,
7
"isAuthorized": true,
8
"namespaceRef": {
9
"apiVersion": "core/v1",
10
"kind": "Namespace",
11
"name": "default",
12
"resourceVersion": "0195b81d-43ec-7ba7-81f7-9b77202b3612",
13
"uid": "bff0768e-eec8-4044-a972-9f7af81b79d8"
14
},
15
"reason": {
16
"details": {
17
"policyMatch": {
18
"inlinePolicy": {
19
"name": "allow-all",
20
"resourceRef": {
21
"apiVersion": "core/v1",
22
"kind": "Group",
23
"name": "g1",
24
"resourceVersion": "0195b821-209b-76f7-bde9-b078e870621c",
25
"uid": "972e1808-7248-468a-ba7c-8da43d33c626"
26
}
27
}
28
}
29
},
30
"type": "POLICY_MATCH"
31
},
32
"regionRef": {
33
"apiVersion": "core/v1",
34
"kind": "Region",
35
"name": "default",
36
"uid": "6e233d12-2fd6-4670-a82d-8c712e1f7374"
37
},
38
"serviceRef": {
39
"apiVersion": "core/v1",
40
"kind": "Service",
41
"name": "portal.default",
42
"resourceVersion": "0195b820-357c-7753-a9fb-240102cb642c",
43
"uid": "f1e03fcc-4a8c-45e5-9771-59a0c5924627"
44
},
45
"sessionRef": {
46
"apiVersion": "core/v1",
47
"kind": "Session",
48
"name": "usr1-ver9fi",
49
"resourceVersion": "0195ba26-805a-76ff-966f-a8ee4158f3d5",
50
"uid": "e04bb910-9bb7-483f-a3fe-6c0610b7e358"
51
},
52
"startedAt": "2025-03-21T20:20:57.917525056Z",
53
"userRef": {
54
"apiVersion": "core/v1",
55
"kind": "User",
56
"name": "usr1",
57
"resourceVersion": "0195b821-228d-79b2-92f3-4bb458944f97",
58
"uid": "e0f251b2-6952-4a46-a771-2712a45c47f5"
59
}
60
},
61
"info": {
62
"http": {
63
"httpVersion": "HTTP11",
64
"request": {
65
"method": "GET",
66
"path": "/services",
67
"userAgent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36"
68
},
69
"response": {
70
"bodyBytes": "471",
71
"code": 200
72
}
73
}
74
}
75
},
76
"kind": "AccessLog",
77
"metadata": {
78
"actorRef": {
79
"apiVersion": "core/v1",
80
"kind": "Session",
81
"name": "usr1-ver9fi",
82
"resourceVersion": "0195ba26-805a-76ff-966f-a8ee4158f3d5",
83
"uid": "e04bb910-9bb7-483f-a3fe-6c0610b7e358"
84
},
85
"createdAt": "2025-03-21T20:20:57.970426960Z",
86
"id": "qx24-gtvf-0ow6d4fc4tyrskc2eco6rafs-sne8-xi11",
87
"targetRef": {
88
"apiVersion": "core/v1",
89
"kind": "Service",
90
"name": "portal.default",
91
"resourceVersion": "0195b820-357c-7753-a9fb-240102cb642c",
92
"uid": "f1e03fcc-4a8c-45e5-9771-59a0c5924627"
93
}
94
}
95
}

gRPC Mode

In the GRPC mode, the Service automatically listens over HTTP 2/0 as required by gRPC protocol. Furthermore, authorization and visibility take advantage of decoding the requests as gRPC requests.

You can control access based on the gRPC request information. Such information are stored in ctx.request.grpc where it contains the service, method and package values. Additionally all the HTTP related information that are exposed in the HTTP mode are also exposed in the variable ctx.request.grpc.http. Here is a detailed example of a inline Policy that controls access based on gRPC-specific information:

1
kind: Service
2
metadata:
3
name: svc1
4
spec:
5
mode: GRPC
6
port: 8080
7
config:
8
upstream:
9
url: https://my-grpc-api.example.com
10
authorization:
11
inlinePolicies:
12
- spec:
13
rules:
14
- effect: ALLOW
15
condition:
16
all:
17
of:
18
- match: ctx.request.grpc.method == "GetUser"
19
- match: ctx.request.grpc.service == "MainService"
20
- match: ctx.request.grpc.package == "octelium.api.main.core.v1"
21
- match: ctx.request.grpc.serviceFullName == "octelium.api.main.core.v1.MainService"
22
- match: ctx.request.grpc.http.path.startsWith("/octelium.api")
23
- match: ctx.request.grpc.http.headers["x-custom-header"] == "this-value"

As for visibility, the access log contains gRPC-specific information such as the package, service and method in addition to the underlying HTTP information. Here is an example:

1
{
2
"apiVersion": "core/v1",
3
"entry": {
4
"common": {
5
"endedAt": "2025-03-21T20:18:56.435354869Z",
6
"isAuthenticated": true,
7
"isAuthorized": true,
8
"namespaceRef": {
9
"apiVersion": "core/v1",
10
"kind": "Namespace",
11
"name": "octelium-api",
12
"resourceVersion": "0195b81d-43f8-7197-a231-eff3c78c072b",
13
"uid": "879cf77c-e94e-4303-a8e6-78a816496cbe"
14
},
15
"reason": {
16
"details": {
17
"policyMatch": {
18
"inlinePolicy": {
19
"name": "allow-all",
20
"resourceRef": {
21
"apiVersion": "core/v1",
22
"kind": "Group",
23
"name": "g1",
24
"resourceVersion": "0195b821-209b-76f7-bde9-b078e870621c",
25
"uid": "972e1808-7248-468a-ba7c-8da43d33c626"
26
}
27
}
28
}
29
},
30
"type": "POLICY_MATCH"
31
},
32
"regionRef": {
33
"apiVersion": "core/v1",
34
"kind": "Region",
35
"name": "default",
36
"uid": "6e233d12-2fd6-4670-a82d-8c712e1f7374"
37
},
38
"serviceRef": {
39
"apiVersion": "core/v1",
40
"kind": "Service",
41
"name": "default.octelium-api",
42
"resourceVersion": "0195b820-35a1-78bd-bf79-e5149b097a70",
43
"uid": "8a39db66-5d1f-4f8e-b51c-ee7202e06462"
44
},
45
"sessionRef": {
46
"apiVersion": "core/v1",
47
"kind": "Session",
48
"name": "usr1-3pt93j",
49
"resourceVersion": "0195ba59-7cae-7487-b96f-48b1440c7749",
50
"uid": "e8bea7be-b858-4457-a8ee-a0cb7f6c91da"
51
},
52
"startedAt": "2025-03-21T20:18:56.393205783Z",
53
"userRef": {
54
"apiVersion": "core/v1",
55
"kind": "User",
56
"name": "usr1",
57
"resourceVersion": "0195b821-228d-79b2-92f3-4bb458944f97",
58
"uid": "e0f251b2-6952-4a46-a771-2712a45c47f5"
59
}
60
},
61
"info": {
62
"grpc": {
63
"http": {
64
"httpVersion": "HTTP2",
65
"request": {
66
"method": "POST",
67
"path": "/octelium.api.main.core.v1.MainService/ListService",
68
"userAgent": "grpc-go/1.69.2"
69
},
70
"response": {
71
"bodyBytes": "3292",
72
"code": 200
73
}
74
},
75
"method": "ListService",
76
"package": "octelium.api.main.core.v1",
77
"service": "MainService",
78
"serviceFullName": "octelium.api.main.core.v1.MainService"
79
}
80
}
81
},
82
"kind": "AccessLog",
83
"metadata": {
84
"actorRef": {
85
"apiVersion": "core/v1",
86
"kind": "Session",
87
"name": "usr1-3pt93j",
88
"resourceVersion": "0195ba59-7cae-7487-b96f-48b1440c7749",
89
"uid": "e8bea7be-b858-4457-a8ee-a0cb7f6c91da"
90
},
91
"createdAt": "2025-03-21T20:18:56.435340690Z",
92
"id": "rwa9-yu4j-zoxzkgh25oegh4ebyyctxi16-kd3p-4z3b",
93
"targetRef": {
94
"apiVersion": "core/v1",
95
"kind": "Service",
96
"name": "default.octelium-api",
97
"resourceVersion": "0195b820-35a1-78bd-bf79-e5149b097a70",
98
"uid": "8a39db66-5d1f-4f8e-b51c-ee7202e06462"
99
}
100
}
101
}

Web App Mode

You can use the WEB mode to denote that the Service is a web application that can be accessed by HUMAN Users via their browsers. This mode enables the web Portal to include a "Visit" button to open the Service homepage in a new tab. Here is an example:

1
kind: Service
2
metadata:
3
name: grafana1
4
spec:
5
mode: WEB
6
port: 80
7
isPublic: true
8
config:
9
upstream:
10
url: https://grafana.mycluster.svc

Without explicitly using the WEB mode, no "Visit" button will be shown in the Portal and Users will have to manually type the https://grafana1.<DOMAIN> in their browsers to visit the Service homepage.

© 2025 octelium.comOctelium Labs, LLCAll rights reserved
Octelium and Octelium logo are trademarks of Octelium Labs, LLC.
WireGuard is a registered trademark of Jason A. Donenfeld