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:
1kind: Service2metadata:3name: svc14spec:5mode: HTTP6port: 80807config:8upstream:9url: https://example.com
For internal/private upstreams behind NAT, you need to remotely serve them via a connected octelium client or container as discussed here.
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, the request body and the serialized JSON body map if available as well as other information such as the scheme and full URI. Here is a detailed example of a inline Policy that controls access based on HTTP-specific information:
1kind: Service2metadata:3name: svc14spec:5mode: HTTP6port: 80807config:8upstream:9url: https://example.com10authorization:11inlinePolicies:12- spec:13rules:14- effect: ALLOW15condition:16all:17of:18- match: ctx.request.http.method in ["GET", "POST", "PUT", "DELETE"]19- match: ctx.request.http.path.startsWith("/apis")20- match: ctx.request.http.uri == "/apis/users?name=john"21- match: ctx.request.http.queryParams.name == "john"22- match: ctx.request.http.headers["x-custom-header"] == "this-value"23- match: ctx.request.http.scheme == "http"24- match: string(ctx.request.http.body).toLower().contains("value1")25- match: ctx.request.http.bodyMap.key1 == "value1"
Secretless Access
Octelium is capable of supporting secretless 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
You first need to create a Secret that holds your credential via octeliumctl create secret (read more here) as follows:
octeliumctl create secret my-api-key
And then reference that Secret depending on your authentication scheme as shown below.
Basic Authentication
In this scheme, authentication information is injected by the Service as Basic <base64(username:password_secret)> for the Authorization request header when the request is sent to the upstream. Here is an example:
1kind: Service2metadata:3name: svc14spec:5mode: HTTP6port: 807config:8upstream:9url: https://api.example.com10http:11auth:12basic:13username: user114password:15fromSecret: my-api-key
Bearer Authentication
In this scheme, authentication information is injected by the Service as Bearer <bearer_secret> for the Authorization request header when the request is sent to the upstream. Here is an example:
1kind: Service2metadata:3name: svc14spec:5mode: HTTP6port: 807config:8upstream:9url: https://api.example.com10http:11auth:12bearer:13fromSecret: my-api-key
Authentication via a Custom Header
You can also set your Secret value to a custom request header to the upstream. Here is an example:
1kind: Service2metadata:3name: svc14spec:5mode: HTTP6port: 807config:8upstream:9url: https://api.example.com10http:11auth:12custom:13header: X-Custom-Auth14value:15fromSecret: my-api-key
OAuth2 Client Credentials
You can also use OAuth2 client credentials authentication flow (read more here). Here is an example:
1kind: Service2metadata:3name: svc14spec:5mode: HTTP6port: 807config:8upstream:9url: https://api.example.com10http:11auth:12oauth2ClientCredentials:13clientID: my-oauth2-client-id14clientSecret:15fromSecret: my-oauth2-client-key16tokenURL: https://oauth2.example.com/v117scopes: ["scope1", "scope2"]
Sigv4 Authentication
Octelium can also compute and inject AWS Sigv4 auth signatures on the fly for any AWS Sigv4 compliant API for a specific service (e.g. Lambda, S3, etc...) and region. Here is an example for a Lambda function URL that's protected by Sigv4:
1kind: Service2metadata:3name: lambda-014spec:5mode: HTTP6isPublic: true7config:8upstream:9url: https://abcd...efgh.lambda-url.eu-central-1.on.aws10http:11auth:12sigv4:13accessKeyID: ABCD...EFGH14region: eu-central-115service: lambda16secretAccessKey:17fromSecret: lambda-access-key
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:
1kind: Service2metadata:3name: svc14spec:5mode: HTTP6port: 807config:8upstream:9url: https://example.com10http:11listenHTTP2: 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:
1kind: Service2metadata:3name: svc14spec:5mode: HTTP6port: 807config:8upstream:9url: https://example.com10http:11isUpstreamHTTP2: true
Header Manipulation
You can easily manipulate both request and response HTTP headers as follows:
1kind: Service2metadata:3name: svc14spec:5mode: HTTP6port: 807config:8upstream:9url: https://example.com10http:11header:12addRequestHeaders:13- key: X-HEADER-114value: VALUE-115- key: X-HEADER-216value: VALUE-217removeRequestHeaders:18- X-HEADER-319- X-HEADER-420addResponseHeaders:21- key: X-HEADER-522value: VALUE-523- key: X-HEADER-624value: VALUE-625removeResponseHeaders:26- X-HEADER-727- X-HEADER-8
By default addRequestHeaders and addResponseHeaders actually set or "override" any existing header value. To append instead of overriding such values, you can simply use the append boolean option as follows:
1kind: Service2metadata:3name: svc14spec:5mode: HTTP6port: 807config:8upstream:9url: https://example.com10http:11header:12addRequestHeaders:13- key: X-HEADER-114value: VALUE-115append: true16addResponseHeaders:17- key: X-HEADER-218value: VALUE-219append: true
Forwarded Header
By default, Vigil automatically deletes the Forwarded request header (read more here), if set by the downstream, to protect the downstream's privacy and also to protect the upstream from possible spoofing by a malicious downstream. You can, however, explicitly override that default behavior by using the OBFUSCATE mode to obfuscate the for and by values while using the Service public FQDN as the host value. Here is an example:
1kind: Service2metadata:3name: svc14spec:5mode: HTTP6port: 807config:8upstream:9url: https://example.com10http:11forwardedMode: OBFUSCATE
You can also use TRANSPARENT mode to simply pass the Forwarded request header as is, if set by the downstream. Here is an example:
1kind: Service2metadata:3name: svc14spec:5mode: HTTP6port: 807config:8upstream:9url: https://example.com10http:11forwardedMode: TRANSPARENT
Path Manipulation
You can add a prefix to the request's path as follows:
1kind: Service2metadata:3name: svc14spec:5mode: HTTP6port: 807config:8upstream:9url: https://example.com10http:11path:12addPrefix: /prefix
Now a request with the path /v1 will become /prefix/v1.
And you can also remove a prefix from the path as follows:
1kind: Service2metadata:3name: svc14spec:5mode: HTTP6port: 807config:8upstream:9url: https://example.com10http:11path:12removePrefix: /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:
1kind: Service2metadata:3name: svc14spec:5mode: HTTP6port: 807config:8upstream:9url: https://example.com10http:11path:12removePrefix: /v113addPrefix: /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).
1kind: Service2metadata:3name: svc14spec:5mode: HTTP6port: 807config:8upstream:9url: https://example.com10http:11enableRequestBuffering: 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:
1kind: Service2metadata:3name: svc14spec:5mode: HTTP6port: 807config:8upstream:9url: https://example.com10http:11enableRequestBuffering: true12body:13maxRequestSize: 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:
1kind: Service2metadata:3name: svc14spec:5mode: HTTP6port: 807config:8upstream:9url: https://example.com10http:11enableRequestBuffering: true12body:13mode: 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:
1kind: Service2metadata:3name: svc14spec:5mode: HTTP6port: 80807config:8upstream:9url: https://example.com10authorization:11inlinePolicies:12- spec:13rules:14- effect: ALLOW15condition:16all:17of:18- match: ctx.request.http.bodyMap.key1 == "value1"19- match: ctx.request.http.bodyMap.key2 > 220- match: ctx.request.http.bodyMap.key3.key4 == "value4"
Body Validation
JSON Schema Validation
You can validate the JSON body via a JSON schema simply as follows:
Note that this feature is currently experimental and the API might change in the next few releases as of v0.10.0.
1kind: Service2metadata:3name: svc14spec:5mode: HTTP6port: 807config:8upstream:9url: https://example.com10http:11validation:12jsonSchema:13inline: |14{15"$schema": "http://json-schema.org/draft-07/schema#",16"title": "User",17"description": "A user in the system",18"type": "object",19"properties": {20"id": {21"description": "The unique identifier for the user",22"type": "integer"23},24// The rest of your JSON schema
If the body content is not valid according to the schema, then a status code of 400 is returned.
You can use dynamic configuration (read more here), for example for different API REST paths referring to different API resources, to actually use multiple JSON schemas.
Direct Response
Since version v0.16, it is more recommended to use direct response plugins. Read more here
You can also return direct response without proxying the request to the upstream. Here is an example of returning a string:
1kind: Service2metadata:3name: svc14spec:5mode: HTTP6port: 807config:8upstream:9url: https://example.com10http:11response:12direct:13inline: Hello World
You can also return a stream of bytes encoded by base64. Here is an example:
1kind: Service2metadata:3name: svc14spec:5mode: HTTP6port: 807config:8upstream:9url: https://example.com10http:11response:12direct:13inlineBytes: iVBORw0KGgoAAAANS....
you can also set the response status code and content type of the direct response as follows:
1kind: Service2metadata:3name: svc14spec:5mode: HTTP6port: 807config:8upstream:9url: https://example.com10http:11response:12direct:13statusCode: 20014contentType: image/png15inlineBytes: iVBORw0KGgoAAAANS....
You can use dynamic configuration (read more here) to return different direct responses for different request paths or even different identities or contexts.
Retry
By default, the Service does not retry a failed request (e.g. whose response status code is 503). You can, however, enable retries with sensible defaults as follows:
1kind: Service2metadata:3name: svc14spec:5mode: HTTP6port: 807config:8upstream:9url: https://example.com10retry: {}
You can explicitly set your retry-specific configurations. The Service uses exponential backoff that starts with an initialInterval duration. This duration is multiplied on each retry by a multiplier floating point value until it reaches the value of a maxInterval duration. The whole process can have a maximum number of retries that can be set via maxRetries and a deadline duration that is controlled by maxElapsedTime. By default, the Service currently retries on 502, 503 and 504 status codes; however, you can explicitly set the status codes via the statusCodes list and additionally use any 5xx error via the retryOnServerErrors boolean flag. Here is an example:
1kind: Service2metadata:3name: svc14spec:5mode: HTTP6port: 807config:8upstream:9url: https://example.com10retry:11maxRetries: 1012initialInterval:13milliseconds: 50014maxInterval:15seconds: 516maxElapsedTime:17seconds: 1218multiplier: 1.519statusCodes: [503, 429]20retryOnServerErrors: true
HTTPS
By default, the Service listens over plaintext TCP acting as a plaintext HTTP server regardless of whether the upstream is an HTTPS or an HTTP server. You can turn the Service into an HTTPS Service, however, via the isTLS flag as follows:
1kind: Service2metadata:3name: svc14spec:5mode: HTTP6port: 4437isTLS: true8config:9upstream:10url: https://example.com
A Service is accessed over public HTTPS if the public BeyondCorp mode is enabled via isPublic field (read more here), regardless of whether the Service is using isTLS or not. In other words, isTLS is only effective for the private client-based mode. Therefore, if isPublic is enabled while isTLS is not, then the Service is accessed publicly over HTTPS while over plaintext via the client-based mode.
TLS Config
The Service can automatically connect to an HTTPS upstream whose certificate is signed by a public CA. If the upstream is using a private CA, however, you will have to manually add that root CA in a list of trustedCAs. Here is an example:
1kind: Service2metadata:3name: my-api4spec:5mode: HTTP6config:7upstream:8url: https://my-upstream:64439tls:10trustedCAs:11- |12-----BEGIN CERTIFICATE-----13CA14-----END CERTIFICATE-----
You can also skip verifying the upstream's certificate altogether via the insecureSkipVerify which is typically recommended only for troubleshooting/testing use cases. Here is an example:
1kind: Service2metadata:3name: my-api4spec:5mode: HTTP6config:7upstream:8url: https://my-upstream:64439tls:10insecureSkipVerify: true
If the upstream requires mutual TLS (mTLS), you will have to include your client certificate private key as a Secret (read more here), and then reference it as follows:
1kind: Service2metadata:3name: my-api4spec:5mode: HTTP6config:7upstream:8url: https://my-upstream:64439tls:10trustedCAs:11- |12-----BEGIN CERTIFICATE-----13CA14-----END CERTIFICATE-----15clientCertificate:16fromSecret: client-private-key
Cross-Origin Resource Sharing (CORS)
You can also enforce CORS rules as follows:
1kind: Service2metadata:3name: svc14spec:5mode: HTTP6port: 807config:8upstream:9url: https://example.com10http:11cors:12allowMethods: "POST, GET, OPTIONS"13allowHeaders: "X-PINGOTHER, Content-Type"14maxAge: "86400"
Dynamic Configuration
You can use dynamic configuration in order to, for example, route to different upstreams and/or setting 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):
1kind: Service2metadata:3name: my-api4spec:5mode: HTTP6isPublic: true7dynamicConfig:8configs:9- name: v110upstream:11url: https://apiv1.example.com12http:13path:14removePrefix: /v115- name: v216upstream:17url: https://apiv2.example.com18http:19path:20removePrefix: /v221rules:22- condition:23match: ctx.request.http.path.startsWith("/v1")24configName: v125- condition:26match: ctx.request.http.path.startsWith("/v2")27configName: v2
Visibility
The Service emits AccessLogs in real time to the audit collector. Each AccessLog 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"kind": "AccessLog",4"metadata": {5// Omitted for brevity6},7"entry": {8// Omitted for brevity9},10"info": {11"http": {12"request": {13"path": "/",14"userAgent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/139.0.0.0 Safari/537.36",15"method": "GET",16"uri": "/?arg=value"17},18"response": {19"code": 200,20"bodyBytes": "615",21"body": "PCFET0NUWV...",22"contentType": "text/html"23},24"httpVersion": "HTTP11"25}26}27}28}
Visibility Options
By default, the Service does not log the request and response body content. However, you can explicitly enable capturing the request/response body content as well as the serialized JSON body map as follows:
1kind: Service2metadata:3name: my-api4spec:5mode: HTTP6port: 807config:8upstream:9url: https://api.example.com10http:11visibility:12enableRequestBody: true13enableRequestBodyMap: true14enableResponseBody: true15enableResponseBodyMap: true
You can also record all or certain request/response headers inside AccessLogs as follows:
1kind: Service2metadata:3name: my-api4spec:5mode: HTTP6port: 807config:8upstream:9url: https://api.example.com10http:11visibility:12includeAllRequestHeaders: true13includeAllResponseHeaders: true14includeRequestHeaders: ["X-Custom-Header-1", "X-Custom-Header-2"]15includeResponseHeaders: ["X-Custom-Header-3", "X-Custom-Header-4"]16excludeRequestHeaders: ["X-Custom-Header-5", "X-Custom-Header-6"]17excludeResponseHeaders: ["X-Custom-Header-7", "X-Custom-Header-8"]
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:
1kind: Service2metadata:3name: svc14spec:5mode: GRPC6port: 80807config:8upstream:9url: https://my-grpc-api.example.com10authorization:11inlinePolicies:12- spec:13rules:14- effect: ALLOW15condition:16all:17of: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"kind": "AccessLog",4"metadata": {5// Omitted for brevity6},7"entry": {8// Omitted for brevity9},10"info": {11"grpc": {12"http": {13"request": {14"path": "/octelium.api.main.core.v1.MainService/ListService",15"userAgent": "octelium-cli/v0.17.1 grpc-go/1.71.1",16"method": "POST",17"uri": "/octelium.api.main.core.v1.MainService/ListService"18},19"response": {20"code": 200,21"bodyBytes": "19130",22"contentType": "application/grpc"23},24"httpVersion": "HTTP2"25},26"method": "ListService",27"service": "MainService",28"serviceFullName": "octelium.api.main.core.v1.MainService",29"package": "octelium.api.main.core.v1"30}31}32}33}
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:
1kind: Service2metadata:3name: grafana14spec:5mode: WEB6port: 807isPublic: true8config:9upstream:10url: 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.