Skip to content
Open
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
200 changes: 200 additions & 0 deletions .github/workflows/test-server.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
# Separate from the library CI (ci.yml) so the test-server directory is not
# swept into the coverage-gated library jobs.
name: ESDK TestServer (Node.js)

on:
pull_request:
paths:
- "test-server/**"
- ".github/workflows/test-server.yml"
push:
paths:
- "test-server/**"
- ".github/workflows/test-server.yml"
workflow_dispatch:

permissions:
contents: read

jobs:
javascript-language-server:
name: build + test (live modules)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: "22.x"
cache: "npm"

- name: Install dependencies
run: npm ci --unsafe-perm

- name: Build modules
run: npm run build-node

- name: Build server
working-directory: test-server
run: npx tsc -p tsconfig.json

- name: Test server
working-directory: test-server
run: npm test

# The complete cross-language TestServer run: `make test-server` clones commons and
# the orchestrator materializes + builds + launches every configured Language_Server
# (this working tree is the live server), runs the full pairwise matrix, and tears
# down. Provisions the full toolchain matrix + the commons build caches. Needs the
# COMMONS_REPO_PAT secret (read on commons + java + rust).
test-server:
name: Cross-language TestServer run
runs-on: ubuntu-latest
permissions:
id-token: write
contents: read
env:
AWS_REGION: us-west-2
GRADLE_OPTS: -Dorg.gradle.daemon=false
# Cold builds (Java gradle/maven, Rust aws-lc-sys, .NET dafny transpile) exceed
# the orchestrator's 180s default server-readiness window; widen it so the first
# (cold-cache) run fits. Warm runs are fast via the build caches above.
ESDK_TESTSERVER_READY_TIMEOUT_SECONDS: "1800"
steps:
- uses: actions/checkout@v4
with:
persist-credentials: false

- name: Set up JDK 17 (Temurin) — exposes JAVA_HOME_17_X64 for the MPL stub jar
uses: actions/setup-java@v4
with:
distribution: temurin
java-version: "17"

- name: Set up JDK 21 (Temurin) — the commons orchestrator (smithy-java) needs 21+
uses: actions/setup-java@v4
with:
distribution: temurin
java-version: "21"

- name: Set up Python 3.11 (the materialized Python Language_Server needs >=3.11)
uses: actions/setup-python@v5
with:
python-version: "3.11"

- name: Set up Node.js 22 (the materialized JavaScript Language_Server build needs 17+)
uses: actions/setup-node@v4
with:
node-version: "22"

- name: Set up Go (Go Language_Server + the Rust servers' aws-lc-sys native builds)
uses: actions/setup-go@v5
with:
go-version: "stable"

- name: Set up the Rust toolchain (Rust Language_Server builds)
run: |
set -euo pipefail
rustup toolchain install stable --profile minimal
rustup default stable
cargo --version

- name: Set up .NET (the materialized .NET Language_Server build)
uses: actions/setup-dotnet@v5
with:
dotnet-version: |
6.0.x
8.0.x

- name: Install Dafny 4.9.0 (the .NET library transpile in the materialized clone)
run: |
set -euo pipefail
dotnet tool install --global dafny --version 4.9.0
echo "$HOME/.dotnet/tools" >> "$GITHUB_PATH"
"$HOME/.dotnet/tools/dafny" --version

- name: Build aws-c-common (the materialized C Language_Server cmake requires it)
run: |
set -euo pipefail
git clone --depth 1 https://github.com/awslabs/aws-c-common "$RUNNER_TEMP/aws-c-common"
cmake -S "$RUNNER_TEMP/aws-c-common" -B "$RUNNER_TEMP/aws-c-common-build" \
-DCMAKE_INSTALL_PREFIX="${{ github.workspace }}/.aws-c-common-install" \
-DBUILD_TESTING=OFF
cmake --build "$RUNNER_TEMP/aws-c-common-build" --target install -- -j
echo "CMAKE_PREFIX_PATH=${{ github.workspace }}/.aws-c-common-install" >> "$GITHUB_ENV"

- name: Restore build caches (Gradle/Maven/Cargo/NuGet/Go/npm)
uses: actions/cache/restore@v4
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
~/.m2/repository
~/.cargo/registry
~/.cargo/git
~/.nuget/packages
~/go/pkg/mod
~/.cache/go-build
~/.npm
key: ${{ runner.os }}-esdk-ts-build-${{ github.run_id }}
restore-keys: |
${{ runner.os }}-esdk-ts-build-

- name: Install stub for the unpublished MPL test-vectors artifact
# The materialized Java Language_Server build runs `mvn install`, which
# resolves a test-scope MPL artifact (TestAwsCryptographicMaterialProviders)
# that is not on Maven Central; a stub jar satisfies resolution (tests skip).
run: |
set -euo pipefail
tmp="$(mktemp -d)"
echo placeholder > "$tmp/README.txt"
"$JAVA_HOME_17_X64/bin/jar" cf "$tmp/stub.jar" -C "$tmp" README.txt
mvn -q -e org.apache.maven.plugins:maven-install-plugin:3.1.1:install-file \
-Dfile="$tmp/stub.jar" \
-DgroupId=software.amazon.cryptography \
-DartifactId=TestAwsCryptographicMaterialProviders \
-Dversion=1.7.0 \
-Dpackaging=jar

- name: Authenticate the private clones (commons, java, rust)
env:
PAT: ${{ secrets.COMMONS_REPO_PAT }}
run: |
test -n "$PAT" || { echo "::error::COMMONS_REPO_PAT is empty/unset in this repo"; exit 1; }
git config --global url."https://x-access-token:${PAT}@github.com/".insteadOf "git@github.com:"

- name: Configure AWS credentials via OIDC (required KMS scenarios)
id: aws-credentials
continue-on-error: true
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: ${{ vars.ESDK_TEST_SERVER_KMS_ROLE_ARN || 'arn:aws:iam::370957321024:role/esdk-test-server-ci-kms-role' }}
aws-region: us-west-2

- name: Verify AWS credentials are active (gates the orchestrated run)
run: |
if ! aws sts get-caller-identity; then
echo "::error::No usable AWS credentials for the required KMS scenarios." >&2
exit 1
fi

- name: Run the orchestrated TestServer
working-directory: test-server
run: make test-server

- name: Save build caches
if: always()
uses: actions/cache/save@v4
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
~/.m2/repository
~/.cargo/registry
~/.cargo/git
~/.nuget/packages
~/go/pkg/mod
~/.cache/go-build
~/.npm
key: ${{ runner.os }}-esdk-ts-build-${{ github.run_id }}
4 changes: 4 additions & 0 deletions test-server/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
build/
.server.pid
.server.log
.commons-clone/
98 changes: 98 additions & 0 deletions test-server/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
# Target vocabulary (build-server / start-server / wait-for-server / stop-server)
# matches the commons TestServer orchestration. Recipes are single shell lines
# for the 3.81 Make that ships with macOS.

SHELL := bash

# Port for the server (override: make run-server PORT=9090).
PORT ?= 8095

PID_FILE := .server.pid
LOG_FILE := .server.log

# Bootstrap-then-delegate coordinates for `make test-server`. The commons
# repository coordinates live in commons-configuration.json next to this
# Makefile; COMMONS_BRANCH overrides the configured branch at invocation time.
MAKEFILE_DIR := $(abspath $(dir $(lastword $(MAKEFILE_LIST))))
REPO_ROOT := $(abspath $(MAKEFILE_DIR)/..)
COMMONS_CONFIGURATION := $(MAKEFILE_DIR)/commons-configuration.json
COMMONS_BRANCH ?=
CLONE_DIR ?= $(MAKEFILE_DIR)/.commons-clone
CLONE_ORCH_DIR := $(CLONE_DIR)/esdk/test-server/orchestrator

.PHONY: help build-server run-server start-server wait-for-server stop-server \
test test-server check-java clean

help: ## Show this help
@echo "ESDK TestServer (Node.js) — make targets"
@grep -E '^[a-zA-Z0-9_-]+:.*## ' "$(lastword $(MAKEFILE_LIST))" \
| sort | awk 'BEGIN{FS=":.*## "}{printf " %-18s %s\n", $$1, $$2}'
@echo ""
@echo " PORT=$(PORT)"

build-server: ## Install repo deps (if needed), build the modules, compile the server
@if [ ! -d "$(REPO_ROOT)/node_modules" ]; then cd "$(REPO_ROOT)" && npm ci --unsafe-perm; fi
cd "$(REPO_ROOT)" && npm run build-node
cd "$(MAKEFILE_DIR)" && npx tsc -p tsconfig.json

run-server: build-server ## Run the server in the FOREGROUND on PORT (Ctrl-C to stop)
node "$(MAKEFILE_DIR)/build/src/main.js" $(PORT)

start-server: build-server ## Start the server in the BACKGROUND on PORT (writes .server.pid)
@node "$(MAKEFILE_DIR)/build/src/main.js" $(PORT) >"$(LOG_FILE)" 2>&1 & echo $$! >"$(PID_FILE)"; \
echo "started esdk-test-server (pid $$(cat $(PID_FILE))) on port $(PORT)"

wait-for-server: ## Block until the server accepts connections on PORT (120s timeout)
@for i in $$(seq 1 120); do \
if node -e 'const s=require("net").connect($(PORT),"127.0.0.1");s.on("connect",()=>{s.end();process.exit(0)});s.on("error",()=>process.exit(1))'; then \
echo "server ready on $(PORT)"; exit 0; \
fi; \
sleep 1; \
done; \
echo "timed out waiting for port $(PORT)"; [ -f "$(LOG_FILE)" ] && tail -n 40 "$(LOG_FILE)"; exit 1

stop-server: ## Stop the background server and free PORT
@if [ -f "$(PID_FILE)" ]; then kill "$$(cat $(PID_FILE))" 2>/dev/null || true; rm -f "$(PID_FILE)"; fi; \
pids=$$(lsof -ti tcp:$(PORT) 2>/dev/null || true); \
if [ -n "$$pids" ]; then kill $$pids 2>/dev/null || true; fi; \
echo "stopped server on port $(PORT)"

test: build-server ## Run the server's unit/protocol tests (no AWS credentials)
cd "$(MAKEFILE_DIR)" && npm test

check-java: ## Verify JAVA_HOME points at a JDK 21+ (the commons orchestrator needs it)
@if [ -z "$$JAVA_HOME" ] || [ ! -x "$$JAVA_HOME/bin/java" ]; then \
echo "ERROR: JAVA_HOME must point at a JDK 21+ for the commons orchestrator." >&2; exit 1; \
fi; \
v=$$("$$JAVA_HOME/bin/java" -version 2>&1 | grep -i version | head -1 | sed -E 's/.*version .?([0-9]+).*/\1/'); \
if [ -z "$$v" ] || [ "$$v" -lt 21 ] 2>/dev/null; then \
echo "ERROR: JDK 21+ required, JAVA_HOME has major version '$$v'." >&2; exit 1; \
fi

# Bootstrap-then-delegate (the single orchestrated entry point): parse the
# commons coordinates, clone commons at the branch head, and run the
# orchestrator core in the clone with this working tree as the live JavaScript
# library + server source. The core builds and launches every configured
# Language_Server, runs the full Tests matrix, and tears down; its exit code
# propagates. Needs AWS credentials and a JDK 21+.
test-server: check-java ## Run the complete cross-language TestServer via the commons orchestrator; COMMONS_BRANCH=<b> overrides
@set -eo pipefail; \
if ! coords=$$(python3 -c 'import json,sys; c=json.load(open(sys.argv[1]))["commonsRepository"]; print(c["url"]); print(c["branch"])' "$(COMMONS_CONFIGURATION)" 2>/dev/null); then \
echo "ERROR: missing or unparseable $(COMMONS_CONFIGURATION); halting before any clone." >&2; exit 1; \
fi; \
{ read -r url; read -r branch; } <<< "$$coords"; \
if [ -n "$(strip $(COMMONS_BRANCH))" ]; then branch="$(strip $(COMMONS_BRANCH))"; reason="invocation-override"; else reason="configuration-entry"; fi; \
echo "==> Cloning commons at '$$branch' ($$reason) into $(CLONE_DIR)"; \
rm -rf "$(CLONE_DIR)"; \
if ! git clone --depth 1 --single-branch --branch "$$branch" "$$url" "$(CLONE_DIR)"; then \
echo "ERROR: failed to clone $$url at branch $$branch; no Tests will run." >&2; exit 1; \
fi; \
if [ ! -d "$(CLONE_ORCH_DIR)" ]; then \
echo "ERROR: branch $$branch of $$url has no orchestrator at esdk/test-server/orchestrator." >&2; exit 1; \
fi; \
echo "==> Delegating: context=language:javascript languageRepoRoot=$(REPO_ROOT)"; \
cd "$(CLONE_ORCH_DIR)" && ./gradlew --console=plain run \
--args="context=language:javascript languageRepoRoot=$(REPO_ROOT) commonsOrigin.url=$$url commonsOrigin.branch=$$branch commonsOrigin.reason=$$reason"

clean: ## Remove build output, server scratch files, and the commons clone
rm -rf "$(MAKEFILE_DIR)/build" "$(PID_FILE)" "$(LOG_FILE)" "$(CLONE_DIR)"
60 changes: 60 additions & 0 deletions test-server/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
# ESDK TestServer — Node.js Language_Server

A hand-implemented [rpcv2Cbor](https://smithy.io/2.0/additional-specs/protocols/smithy-rpc-v2.html)
HTTP server that implements the ESDK TestServer Smithy contract and delegates
each operation to the AWS Encryption SDK for JavaScript built from this
repository's modules.

## Embedded reference to the commons TestServer

The single source of truth for the wire contract — the Smithy model, the one
generated Test_Client, and the one Tests suite — lives in the commons repo:

- Repository: [`aws/aws-crypto-tools-commons`](https://github.com/aws/aws-crypto-tools-commons)
- Model: `esdk/test-server/model/esdk-test-server.smithy`
- Tests: `esdk/test-server/tests`

This repo hosts only the Node.js Language_Server; it consumes the commons
contract. The commons Configuration_Set carries a `javascript` entry pointing
back at this repo, closing the loop described in the TestServer factoring
design.

## What it speaks

- `POST /service/ESDKTestServer/operation/{Operation}`
- Header `smithy-protocol: rpc-v2-cbor`, `Content-Type: application/cbor`
- CBOR map request/response bodies; errors as a CBOR map `{__type, message}`
- Operations: `CreateClient`, `Encrypt`, `Decrypt`, `EncryptStream`,
`DecryptStream`. The stream variants drive the library's streaming
`encryptStream`/`decryptStream` APIs (this server is streaming-capable).

## Layout

- `src/cbor.ts` — self-contained CBOR codec (no new dependencies)
- `src/model.ts` — wire shapes + modeled-enum ↔ library-identifier mappings
- `src/bridge.ts` — modeled config → real keyrings/CMMs, operation delegation
- `src/server.ts` — HTTP wire layer, routing, error mapping
- `src/main.ts` — entry point (port from argv, `ESDK_TESTSERVER_PORT`, or 8095)

The server consumes the repo's own built modules (`@aws-crypto/client-node`)
through the root workspace install; it declares no dependencies of its own and
changes nothing about the published packages. Node.js >= 16 is required.

## Running

```bash
make run-server PORT=8095 # foreground (builds modules first)
# or, orchestrated:
make start-server PORT=8095
make wait-for-server PORT=8095
make stop-server PORT=8095
make test # unit/protocol tests, no AWS credentials
```

## Running the full cross-language matrix

```bash
make test-server # clones commons and delegates to its orchestrator
```

Needs AWS credentials and a JDK 21+ (`JAVA_HOME`).
29 changes: 29 additions & 0 deletions test-server/commons-configuration.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
{
"commonsRepository": {
"name": "aws-crypto-tools-commons",
"url": "git@github.com:aws/aws-crypto-tools-commons.git",
"branch": "lucmcdon/esdk-test-server-all-languages"
},
"product": "esdk",
"supportedFeatures": [
"streaming",
"hierarchical",
"raw-aes",
"raw-rsa",
"multi",
"aws-kms",
"aws-kms-multi",
"aws-kms-discovery",
"aws-kms-mrk",
"aws-kms-mrk-multi",
"aws-kms-mrk-discovery",
"caching"
],
"unsupportedFeatures": [
"MPL",
"raw-ecdh",
"aws-kms-rsa",
"aws-kms-ecdh",
"required-encryption-context"
]
}
Loading
Loading