Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions extensions/composer/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ require (
gopkg.in/yaml.v3 v3.0.1
)

require github.com/creack/pty v1.1.24

require (
github.com/agnivade/levenshtein v1.2.1 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
Expand Down
2 changes: 2 additions & 0 deletions extensions/composer/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ github.com/corazawaf/coraza/v3 v3.7.0 h1:LIQqu1r+l6e/U/gyiZeykWaNNBY1TzRLz+aaI+Q
github.com/corazawaf/coraza/v3 v3.7.0/go.mod h1:dOSt5evqC7EstouEv6ghhui01+oVUwp9X1vybWwqTlo=
github.com/corazawaf/libinjection-go v0.3.2 h1:9rrKt0lpg4WvUXt+lwS06GywfqRXXsa/7JcOw5cQLwI=
github.com/corazawaf/libinjection-go v0.3.2/go.mod h1:Ik/+w3UmTWH9yn366RgS9D95K3y7Atb5m/H/gXzzPCk=
github.com/creack/pty v1.1.24 h1:bJrF4RRfyJnbTJqzRLHzcGaZK1NeM5kTC9jGgovnR1s=
github.com/creack/pty v1.1.24/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE=
github.com/crewjam/saml v0.5.1 h1:g+mfp0CrLuLRZCK793PgJcZeg5dS/0CDwoeAX2zcwNI=
github.com/crewjam/saml v0.5.1/go.mod h1:r0fDkmFe5URDgPrmtH0IYokva6fac3AUdstiPhyEolQ=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
Expand Down
1 change: 1 addition & 0 deletions extensions/composer/plugins.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,5 @@ import (
_ "github.com/tetratelabs/built-on-envoy/extensions/composer/saml/embedded" // SAML SP plugin.
_ "github.com/tetratelabs/built-on-envoy/extensions/composer/token-exchange/embedded" // OAuth2 Token Exchange plugin.
_ "github.com/tetratelabs/built-on-envoy/extensions/composer/waf/embedded" // WAF plugin.
_ "github.com/tetratelabs/built-on-envoy/extensions/composer/web-terminal/embedded" // Web Terminal plugin.
)
31 changes: 31 additions & 0 deletions extensions/composer/web-terminal/config.schema.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://github.com/tetratelabs/built-on-envoy/extensions/composer/web-terminal/config.schema.json",
"title": "Web Terminal Configuration",
"description": "Configuration for the web-terminal extension. Streams a PTY to the browser over SSE and takes input via POST. A browser terminal is remote code execution by design: set a token unless the listener is otherwise protected.",
"type": "object",
"additionalProperties": false,
"properties": {
"command": {
"type": "string",
"description": "Executable to run inside the PTY (e.g. \"/bin/bash\").",
"minLength": 1,
"default": "/bin/bash"
},
"args": {
"type": "array",
"description": "Arguments passed to the command.",
"items": { "type": "string" }
},
"writable": {
"type": "boolean",
"description": "Whether client keystrokes are written to the PTY. Set to false for a read-only terminal.",
"default": true
},
"serve_frontend": {
"type": "boolean",
"description": "Whether to serve the bundled xterm.js client at \"/\". Disabled by default; the /stream, /input and /resize endpoints are the primary interface. Set to true to serve the bundled client.",
"default": false
}
}
}
32 changes: 32 additions & 0 deletions extensions/composer/web-terminal/config_schema_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// Copyright Built On Envoy
// SPDX-License-Identifier: Apache-2.0
// The full text of the Apache license is available in the LICENSE file at
// the root of the repo.

package webterminal

import (
"testing"

internaltesting "github.com/tetratelabs/built-on-envoy/extensions/composer/internal/testing"
)

func TestConfigSchema(t *testing.T) {
t.Run("empty config is valid (defaults apply)", func(t *testing.T) {
internaltesting.AssertSchemaValid(t, "config.schema.json", `{}`)
})
t.Run("full config", func(t *testing.T) {
internaltesting.AssertSchemaValid(t, "config.schema.json", `{
"command": "/bin/bash",
"args": ["-l"],
"writable": false,
"serve_frontend": true
}`)
})
t.Run("empty command is invalid", func(t *testing.T) {
internaltesting.AssertSchemaInvalid(t, "config.schema.json", `{"command": ""}`)
})
t.Run("unknown property is invalid", func(t *testing.T) {
internaltesting.AssertSchemaInvalid(t, "config.schema.json", `{"nope": true}`)
})
}
18 changes: 18 additions & 0 deletions extensions/composer/web-terminal/embedded/host.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// Copyright Built On Envoy
// SPDX-License-Identifier: Apache-2.0
// The full text of the Apache license is available in the LICENSE file at
// the root of the repo.

// Package host contains the code to register the plugin with the host binary.
package host

import (
sdk "github.com/envoyproxy/envoy/source/extensions/dynamic_modules/sdk/go"

impl "github.com/tetratelabs/built-on-envoy/extensions/composer/web-terminal"
)

// Register this plugin to the host registry if this is built into the host binary.
func init() {
sdk.RegisterHttpFilterConfigFactories(impl.WellKnownHttpFilterConfigFactories())
}
19 changes: 19 additions & 0 deletions extensions/composer/web-terminal/frontend.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// Copyright Built On Envoy
// SPDX-License-Identifier: Apache-2.0
// The full text of the Apache license is available in the LICENSE file at
// the root of the repo.

package webterminal

import _ "embed"

//go:embed www/index.html
var indexHTML []byte

func (f *terminalFilter) serveFrontend() {
f.handle.SendResponseHeaders([][2]string{
{":status", "200"},
{"content-type", "text/html; charset=utf-8"},
}, false)
f.handle.SendResponseData(indexHTML, true)
}
70 changes: 70 additions & 0 deletions extensions/composer/web-terminal/manifest.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
# Copyright Built On Envoy
# SPDX-License-Identifier: Apache-2.0
# The full text of the Apache license is available in the LICENSE file at
# the root of the repo.

name: web-terminal
parent: composer
categories:
- Misc
author: Anton Kanugalawattage
description: Host an interactive terminal inside Envoy, streamed over SSE.
longDescription: |
Hosts an interactive shell (default `/bin/bash`) inside Envoy and exposes it to
the browser without any backend server. The PTY runs in the Envoy process; its
output is streamed to the client over a never-closing HTTP response as
Server-Sent Events, and input and resize events come back as short POST
requests correlated by a session id. Because it runs as a composer HTTP filter,
no upstream cluster or extra port is required — Envoy hosts the session directly.

## Protocol

The transport is plain HTTP and client-agnostic — any client can implement it:

- `GET /stream?sid=<id>&cols=<n>&rows=<n>`
A `text/event-stream` where each `data:` event is a base64-encoded chunk of
PTY output. Opening the stream spawns the PTY for `sid`; closing it (client
disconnect) tears the PTY down. A final `event: exit` is sent on shell exit.
- `POST /input?sid=<id>`
The request body is written to the PTY.
- `POST /resize?sid=<id>&cols=<n>&rows=<n>`
Resizes the PTY.

## Frontend

A small `xterm.js` client is bundled but is not served by default — the
endpoints above are the primary interface, so bring your own client (e.g. an
application's own terminal view). Set `serve_frontend: true` to serve the
bundled client at `/`.

## Security

A browser terminal is remote code execution by design, and this extension has
no built-in authentication — restrict access to the listener (network policy or
an auth filter earlier in the chain) before exposing it.
type: go
tags:
- go
- http
- terminal
- pty
- sse
license: Apache-2.0
examples:
- title: API only (default), bring your own client
description: |
Drive the /stream, /input and /resize endpoints from your own client. The
bundled frontend is not served.
code: |
boe run --extension web-terminal --config '{
"command": "/bin/bash"
}'
- title: Serve the bundled xterm.js client
description: |
Enable the bundled frontend, then open it in the browser at
http://localhost:10000/
code: |
boe run --extension web-terminal --config '{
"command": "/bin/bash",
"serve_frontend": true
}'
Loading