Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
10fccd7
Initial plan
Copilot Oct 31, 2025
4e0af73
Upgrade Guacamole auth-azure to use 1.6.0 API with RequestDetails
Copilot Oct 31, 2025
c950a3f
Fix failing tests with JVM module access and upgrade porter.yaml
Copilot Oct 31, 2025
c202077
Add comprehensive unit tests for TreUserContext and TokenInjectingCon…
Copilot Oct 31, 2025
45e4fce
Fix security issues: credential cleanup, exception logging, and add e…
Copilot Oct 31, 2025
cc82b0f
Add local integration tests that run without cloud dependencies
Copilot Oct 31, 2025
311e630
Add RDP credential injection integration tests
Copilot Oct 31, 2025
86fa08d
Update Dockerfile to use Guacamole 1.6.0 components
Copilot Oct 31, 2025
1c3174f
Update Dockerfile to use Java 17 with updated CA certificates to fix …
Copilot Oct 31, 2025
2a197f0
Upgrade Tomcat to 9.0.111 and add comprehensive E2E test infrastructu…
Copilot Oct 31, 2025
17966f6
Add devcontainer for Guacamole development with Java 17, Node.js, and…
Copilot Oct 31, 2025
2eab5d2
Switch Maven base image from Alpine to Debian for better compatibility
Copilot Oct 31, 2025
bcfee6a
Fix SSL certificate chain issues in Docker build by updating CA certi…
Copilot Oct 31, 2025
d0c8a4d
Fix Maven Central SSL certificate validation by importing certificate…
Copilot Oct 31, 2025
4c7e4d5
Revert build stage to Alpine-based image with SSL certificate fix
Copilot Oct 31, 2025
fb92380
Fix Alpine EOL issue by updating repositories to v3.20 for both build…
Copilot Oct 31, 2025
78e8b5d
Add from-source Dockerfile for building Guacamole 1.6.0 with RDP-only…
Copilot Oct 31, 2025
68d6427
Addressing PR comments
Copilot Oct 31, 2025
af49adf
Fix s6-overlay installation order in Debian from-source Dockerfile to…
Copilot Oct 31, 2025
ee0e5b6
Update E2E tests to use Playwright 1.56.1 with Debian from-source Doc…
Copilot Oct 31, 2025
86cd381
Add RDP connection test with xrdp container and update E2E test scree…
Copilot Nov 1, 2025
3a247b4
Fix E2E tests: remove nginx proxy, add Playwright custom headers, fix…
Copilot Nov 1, 2025
bb547ce
Working, needs review.
marrobi Nov 12, 2025
7dd03fc
Remove dev container
marrobi Nov 12, 2025
0c2efb3
update test configurations
marrobi Nov 13, 2025
d351bac
PR comments and remove s6
marrobi Nov 13, 2025
fbd5070
Merge branch 'main' of https://github.com/microsoft/AzureTRE into cop…
marrobi Nov 13, 2025
172a59b
update java version in workflow and validate service
marrobi Nov 13, 2025
77fa1ac
Fix tests
marrobi Nov 13, 2025
05546e3
fix lining issues.
marrobi Nov 13, 2025
492b800
fix review comments
marrobi Nov 13, 2025
0771475
Fix some more linting.
marrobi Nov 13, 2025
6262998
Update templates/workspace_services/guacamole/e2e-tests/run-tests.sh
marrobi Nov 13, 2025
d1152ca
Merge branch 'main' into copilot/update-guacamole-auth-azure-credentials
marrobi Nov 17, 2025
390428b
Add override
marrobi Nov 17, 2025
83fc6bd
Merge branch 'copilot/update-guacamole-auth-azure-credentials' of htt…
marrobi Nov 17, 2025
d77d4d2
Merge branch 'main' of https://github.com/microsoft/AzureTRE into cop…
marrobi Nov 18, 2025
7b5dd8d
Update script permissions and changelog
marrobi Nov 18, 2025
791418d
Merge branch 'main' into copilot/update-guacamole-auth-azure-credentials
marrobi Nov 20, 2025
fd3aff5
Merge branch 'main' into copilot/update-guacamole-auth-azure-credentials
marrobi Nov 20, 2025
ef117e7
only one of javascript and typescript
marrobi Nov 20, 2025
6e8b056
Trigger codeql
marrobi Nov 20, 2025
6070eaf
trigger
marrobi Nov 20, 2025
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
9 changes: 8 additions & 1 deletion .github/workflows/codeql-analysis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ jobs:
strategy:
fail-fast: false
matrix:
language: ["python", "java", "javascript", "typescript", "actions"]
language: ["python", "java", "typescript", "actions"]

steps:
- name: Checkout repository
Expand All @@ -51,6 +51,13 @@ jobs:
with:
languages: ${{ matrix.language }}

- if: matrix.language == 'java'
name: Set up JDK 17
uses: actions/setup-java@v4
with:
distribution: temurin
java-version: "17"

- if: matrix.language == 'java'
name: Build Java
working-directory: templates/workspace_services/guacamole/guacamole-server/guacamole-auth-azure
Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
**BREAKING CHANGES**

ENHANCEMENTS:
* Upgrade Guacamole to v1.6.0 with Java 17 and other security updates ([#4754](https://github.com/microsoft/AzureTRE/pull/4754))
* API: Replace HTTP_422_UNPROCESSABLE_ENTITY response with HTTP_422_UNPROCESSABLE_CONTENT as per RFC 9110 ([#4742](https://github.com/microsoft/AzureTRE/issues/4742))

BUG FIXES:
Expand Down
22 changes: 22 additions & 0 deletions templates/workspace_services/guacamole/e2e-tests/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Ignore node modules
node_modules/

# Ignore test results
test-results/
playwright-report/

# Ignore screenshots and videos from local runs
screenshots/*.png
videos/*.webm

# Ignore package-lock if using npm
package-lock.json

# Ignore OS files
.DS_Store
Thumbs.db

# Ignore IDE files
.vscode/
.idea/
*.sw
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
[
{
"httpRequest": {
"method": "GET",
"path": "/api/workspaces/test-workspace/workspace-services/vm-service-001/user-resources"
},
"httpResponse": {
"statusCode": 200,
"headers": {
"Content-Type": [
"application/json"
]
},
"body": {
"userResources": [
{
"id": "vm-resource-001",
"templateName": "tre-service-guacamole-windowsvm",
"templateVersion": "1.0.0",
"properties": {
"hostname": "vm-resource-001",
"ip": "xrdp",
"display_name": "Test Virtual Machine",
"vm_resource_id": "/subscriptions/test/resourceGroups/test/providers/Microsoft.Compute/virtualMachines/test-vm",
"is_exposed_externally": true
}
}
]
}
}
}
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
services:
# Mock xrdp server for testing
xrdp:
image: danielguerra/ubuntu-xrdp:20.04
hostname: xrdp-test
ports:
- "3390:3389"
environment:
- TZ=UTC
networks:
- guac-test-network

# Guacamole backend server with TRE auth extension (Debian-based from-source build)
guacamole-backend:
image: guacamole-tre:latest
environment:
# Azure TRE Configuration (mock values for testing)
- TRE_ID=test-tre
- API_URL=http://mock-api:1080
- WORKSPACE_ID=test-workspace
- SERVICE_ID=vm-service-001
- KEYVAULT_URL=https://mock-keyvault.vault.azure.net/
- AAD_TENANT_ID=${GUAC_OIDC_TENANT_ID:?Set GUAC_OIDC_TENANT_ID (run ./run-tests.sh setup-auth)}
# Disable oauth2-proxy for E2E tests (routes are skipped via configuration)
- AUDIENCE=${GUAC_OIDC_AUDIENCE:?Set GUAC_OIDC_AUDIENCE (run ./run-tests.sh setup-auth)}
- ISSUER=${GUAC_OIDC_ISSUER_URL:?Set GUAC_OIDC_ISSUER_URL (run ./run-tests.sh setup-auth)}
- OAUTH2_PROXY_CLIENT_ID=${GUAC_OIDC_CLIENT_ID:?Set GUAC_OIDC_CLIENT_ID (run ./run-tests.sh setup-auth)}
- OAUTH2_PROXY_CLIENT_SECRET=${GUAC_OIDC_CLIENT_SECRET:?Set GUAC_OIDC_CLIENT_SECRET (run ./run-tests.sh setup-auth)}
- OAUTH2_PROXY_REDIRECT_URI=${GUAC_OIDC_REDIRECT_URI:?Set GUAC_OIDC_REDIRECT_URI (run ./run-tests.sh setup-auth)}
- OAUTH2_PROXY_EMAIL_DOMAIN=${GUAC_OIDC_EMAIL_DOMAIN:-*}
- OAUTH2_PROXY_OIDC_ISSUER_URL=${GUAC_OIDC_ISSUER_URL:?Set GUAC_OIDC_ISSUER_URL (run ./run-tests.sh setup-auth)}
- OAUTH2_PROXY_JWKS_ENDPOINT=${GUAC_OIDC_JWKS_URL:?Set GUAC_OIDC_JWKS_URL (run ./run-tests.sh setup-auth)}
Comment thread
marrobi marked this conversation as resolved.
ports:
- "8080:8085" # Expose OAuth2 proxy (HTTP)
- "8443:8086" # Optional HTTPS endpoint from oauth2-proxy
depends_on:
- xrdp
- mock-api
healthcheck:
test: ["CMD-SHELL", "wget -qO- http://localhost:8085/ping >/dev/null"]
interval: 10s
timeout: 5s
retries: 15
start_period: 90s
networks:
- guac-test-network

# Mock TRE API server
mock-api:
image: mockserver/mockserver:5.15.0
ports:
- "8000:1080"
environment:
- MOCKSERVER_LOG_LEVEL=INFO
networks:
- guac-test-network

# Playwright test runner
playwright:
build:
context: ./playwright
dockerfile: Dockerfile
depends_on:
guacamole-backend:
condition: service_healthy
xrdp:
condition: service_started
mock-api:
condition: service_started
environment:
- GUACAMOLE_URL=http://guacamole-backend:8085
volumes:
- ./playwright/screenshots:/screenshots
- ./playwright/videos:/videos
networks:
- guac-test-network
command: npx playwright test --reporter=html

networks:
guac-test-network:
driver: bridge
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
[
{
"httpRequest": {
"method": "GET",
"path": "/api/workspaces/test-workspace/workspace-services/.*"
},
"httpResponse": {
"statusCode": 200,
"headers": {
"Content-Type": ["application/json"]
},
"body": {
"workspaceServices": [
{
"id": "vm-service-001",
"templateName": "tre-service-guacamole",
"properties": {
"vm_name": "test-vm",
"vm_resource_id": "/subscriptions/test/resourceGroups/test/providers/Microsoft.Compute/virtualMachines/test-vm",
"is_exposed_externally": true
}
}
]
}
}
},
{
"httpRequest": {
"method": "GET",
"path": "/api/workspaces/test-workspace/workspace-services/.*/user-resources"
},
"httpResponse": {
"statusCode": 200,
"headers": {
"Content-Type": ["application/json"]
},
"body": {
"userResources": [
{
"id": "vm-001",
"templateName": "tre-service-windows-vm",
"properties": {
"vm_name": "testvm001",
"fqdn": "xrdp",
"connection_port": "3389",
"is_exposed_externally": true
}
}
]
}
}
}
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
FROM mcr.microsoft.com/playwright:v1.56.1-noble

WORKDIR /app

# Copy package files
COPY package.json package-lock.json* ./

RUN npm install

# Copy Playwright project files (tests live under ./tests)
COPY . .

# Run tests
CMD ["npx", "playwright", "test"]

Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"name": "guacamole-e2e-tests",
"version": "1.0.0",
"description": "End-to-end tests for Guacamole TRE integration",
"scripts": {
"test": "playwright test",
"test:headed": "playwright test --headed",
"test:debug": "playwright test --debug",
"test:report": "playwright show-report"
},
"devDependencies": {
"@playwright/test": "1.56.1",
"@types/node": "^20.11.0"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { defineConfig, devices } from '@playwright/test';

const GUACAMOLE_URL = process.env.GUACAMOLE_URL || 'http://localhost:8080';
const TEST_DIR = process.env.PLAYWRIGHT_TEST_DIR || 'tests';

export default defineConfig({
testDir: TEST_DIR,
timeout: 60000,
expect: {
timeout: 10000,
},
fullyParallel: false,
forbidOnly: !!process.env.CI,
retries: process.env.CI ? 2 : 0,
workers: 1,
reporter: [
['html', { outputFolder: 'test-results/html' }],
['json', { outputFile: 'test-results/results.json' }],
['list']
],
use: {
baseURL: GUACAMOLE_URL,
trace: 'on-first-retry',
screenshot: 'only-on-failure',
video: 'retain-on-failure',
},
projects: [
{
name: 'chromium',
use: { ...devices['Desktop Chrome'] },
},
],
outputDir: 'test-results/output',
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import { test, expect } from '@playwright/test';

const OAUTH_LOGIN_URL_REGEX = /(login\.microsoftonline\.com|oauth2\/(start|auth|callback))/i;

test.describe('Guacamole TRE OAuth-protected E2E checks', () => {
test('redirects unauthenticated users to Azure AD', async ({ request }) => {
const response = await request.get('/guacamole/', {
failOnStatusCode: false,
maxRedirects: 0,
});

expect([200, 302, 303]).toContain(response.status());

const locationHeader = response.headers()['location'] ?? '';
if (locationHeader) {
expect(locationHeader).toMatch(OAUTH_LOGIN_URL_REGEX);
} else {
expect(response.url()).toMatch(OAUTH_LOGIN_URL_REGEX);
}
});

test('browser navigation tries to reach Azure AD login', async ({ page }) => {
await page.route('https://login.microsoftonline.com/**', route => {
route.fulfill({ status: 200, contentType: 'text/html', body: '<html><body>Azure AD Stub</body></html>' });
});

const response = await page.goto('/guacamole/', { waitUntil: 'commit' });
const redirectLocation = response?.headers()['location'];

if (redirectLocation) {
expect(response?.status()).toBe(302);
expect(redirectLocation).toMatch(OAUTH_LOGIN_URL_REGEX);
} else {
// When Playwright follows the redirect, assert the current URL instead.
expect(page.url()).toMatch(OAUTH_LOGIN_URL_REGEX);
}

await page.screenshot({ path: '/screenshots/01-login-redirect.png', fullPage: true });
});

test('blocks REST API access without authentication', async ({ request }) => {
const response = await request.get('/guacamole/api/session/data', {
failOnStatusCode: false,
maxRedirects: 0,
});

expect([200, 302, 303, 401, 403]).toContain(response.status());

if (response.status() === 200) {
const body = await response.text();
expect(body).toMatch(/<html/i);
expect(response.url()).toMatch(OAUTH_LOGIN_URL_REGEX);
}
});

test('exposes oauth2-proxy ping endpoint', async ({ request }) => {
const response = await request.get('http://guacamole-backend:8085/ping');
expect(response.status()).toBe(200);
expect(await response.text()).toContain('OK');
});

test('enforces security headers on protected resources', async ({ request }) => {
const response = await request.get('/guacamole/', {
failOnStatusCode: false,
maxRedirects: 0,
});

const headers = response.headers();

expect(headers['cache-control']).toBeDefined();
expect(response.status()).toBeLessThan(500);
});
});
Loading
Loading