Add Authelia auth gateway, portal landing page, and subpath routing
Adds Authelia (forward-auth) and nginx portal container for single-endpoint authenticated access via Caddy reverse proxy. Configures Grafana auth proxy for header-based auto-login. Updates Vue UI base paths and API routes for /exabgp/ and /traffic/ subpath serving. Adds traffic-gen responder container on dedicated Docker network. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
422b98d555
commit
45f4c9859d
@ -92,7 +92,13 @@ services:
|
|||||||
- ${OBMP_DATA_ROOT}/grafana/provisioning:/etc/grafana/provisioning/
|
- ${OBMP_DATA_ROOT}/grafana/provisioning:/etc/grafana/provisioning/
|
||||||
environment:
|
environment:
|
||||||
- GF_SECURITY_ADMIN_PASSWORD=openbmp
|
- GF_SECURITY_ADMIN_PASSWORD=openbmp
|
||||||
- GF_AUTH_ANONYMOUS_ENABLED=true
|
- GF_AUTH_ANONYMOUS_ENABLED=false
|
||||||
|
- GF_SERVER_ROOT_URL=https://bmp.apodacalab.com/grafana/
|
||||||
|
- GF_SERVER_SERVE_FROM_SUB_PATH=true
|
||||||
|
- GF_AUTH_PROXY_ENABLED=true
|
||||||
|
- GF_AUTH_PROXY_HEADER_NAME=Remote-User
|
||||||
|
- GF_AUTH_PROXY_HEADER_PROPERTY=username
|
||||||
|
- GF_AUTH_PROXY_AUTO_SIGN_UP=true
|
||||||
- GF_USERS_HOME_PAGE=d/obmp-home/obmp-home
|
- GF_USERS_HOME_PAGE=d/obmp-home/obmp-home
|
||||||
- GF_INSTALL_PLUGINS=agenty-flowcharting-panel,grafana-piechart-panel,grafana-worldmap-panel,grafana-simple-json-datasource,vonage-status-panel
|
- GF_INSTALL_PLUGINS=agenty-flowcharting-panel,grafana-piechart-panel,grafana-worldmap-panel,grafana-simple-json-datasource,vonage-status-panel
|
||||||
|
|
||||||
@ -275,8 +281,9 @@ services:
|
|||||||
- NET_RAW
|
- NET_RAW
|
||||||
- NET_ADMIN
|
- NET_ADMIN
|
||||||
environment:
|
environment:
|
||||||
- TRAFFIC_GEN_API_PORT=5051
|
- TRAFFIC_GEN_PORT=5051
|
||||||
- TRAFFIC_GEN_MODE=sender
|
- TRAFFIC_GEN_MODE=sender
|
||||||
|
- RESPONDER_URL=http://172.30.0.10:5053
|
||||||
|
|
||||||
traffic-gen-ui:
|
traffic-gen-ui:
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
@ -287,6 +294,26 @@ services:
|
|||||||
network_mode: host
|
network_mode: host
|
||||||
# Serves on port 5002 (host network, defined in nginx.conf)
|
# Serves on port 5002 (host network, defined in nginx.conf)
|
||||||
|
|
||||||
|
traffic-gen-responder:
|
||||||
|
restart: unless-stopped
|
||||||
|
container_name: obmp-traffic-gen-responder
|
||||||
|
build:
|
||||||
|
context: ./traffic-gen
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
cap_add:
|
||||||
|
- NET_RAW
|
||||||
|
- NET_ADMIN
|
||||||
|
environment:
|
||||||
|
- TRAFFIC_GEN_PORT=5053
|
||||||
|
- TRAFFIC_GEN_MODE=responder
|
||||||
|
- TRAFFIC_GEN_RESPONDER_MODE=echo
|
||||||
|
- TRAFFIC_GEN_INTERFACE=eth0
|
||||||
|
networks:
|
||||||
|
traffic-test-net:
|
||||||
|
ipv4_address: 172.30.0.10
|
||||||
|
ports:
|
||||||
|
- "5053:5053"
|
||||||
|
|
||||||
whois:
|
whois:
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
container_name: obmp-whois
|
container_name: obmp-whois
|
||||||
@ -305,3 +332,30 @@ services:
|
|||||||
- POSTGRES_DB=openbmp
|
- POSTGRES_DB=openbmp
|
||||||
- POSTGRES_HOST=obmp-psql
|
- POSTGRES_HOST=obmp-psql
|
||||||
- POSTGRES_PORT=5432
|
- POSTGRES_PORT=5432
|
||||||
|
|
||||||
|
authelia:
|
||||||
|
restart: unless-stopped
|
||||||
|
container_name: obmp-authelia
|
||||||
|
image: authelia/authelia:4.38
|
||||||
|
ports:
|
||||||
|
- "9091:9091"
|
||||||
|
volumes:
|
||||||
|
- ${OBMP_DATA_ROOT}/authelia:/config
|
||||||
|
environment:
|
||||||
|
- TZ=UTC
|
||||||
|
|
||||||
|
portal:
|
||||||
|
restart: unless-stopped
|
||||||
|
container_name: obmp-portal
|
||||||
|
image: nginx:alpine
|
||||||
|
ports:
|
||||||
|
- "8080:80"
|
||||||
|
volumes:
|
||||||
|
- ./portal:/usr/share/nginx/html:ro
|
||||||
|
|
||||||
|
networks:
|
||||||
|
traffic-test-net:
|
||||||
|
driver: bridge
|
||||||
|
ipam:
|
||||||
|
config:
|
||||||
|
- subnet: 172.30.0.0/24
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
const BASE = '/api'
|
const BASE = '/exabgp/api'
|
||||||
|
|
||||||
async function req(method, path, body) {
|
async function req(method, path, body) {
|
||||||
const opts = { method, headers: { 'Content-Type': 'application/json' } }
|
const opts = { method, headers: { 'Content-Type': 'application/json' } }
|
||||||
@ -18,4 +18,7 @@ export const api = {
|
|||||||
announce: payload => req('POST', '/announce', payload),
|
announce: payload => req('POST', '/announce', payload),
|
||||||
withdraw: prefixes => req('POST', '/withdraw', { prefixes }),
|
withdraw: prefixes => req('POST', '/withdraw', { prefixes }),
|
||||||
withdrawAll: () => req('POST', '/withdraw/all'),
|
withdrawAll: () => req('POST', '/withdraw/all'),
|
||||||
|
fullTableStart: (count, batchSize) => req('POST', '/full-table/start', { count, batch_size: batchSize }),
|
||||||
|
fullTableStatus: () => req('GET', '/full-table/status'),
|
||||||
|
fullTableStop: () => req('POST', '/full-table/stop'),
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,6 +2,7 @@ import { defineConfig } from 'vite'
|
|||||||
import vue from '@vitejs/plugin-vue'
|
import vue from '@vitejs/plugin-vue'
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
|
base: '/exabgp/',
|
||||||
plugins: [vue()],
|
plugins: [vue()],
|
||||||
server: {
|
server: {
|
||||||
proxy: {
|
proxy: {
|
||||||
|
|||||||
106
portal/index.html
Normal file
106
portal/index.html
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>OpenBMP Lab Portal</title>
|
||||||
|
<style>
|
||||||
|
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||||
|
body {
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||||
|
background: #111217;
|
||||||
|
color: #d8dee9;
|
||||||
|
min-height: 100vh;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
padding: 2rem;
|
||||||
|
}
|
||||||
|
.header {
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 2.5rem;
|
||||||
|
}
|
||||||
|
.header h1 {
|
||||||
|
font-size: 1.8rem;
|
||||||
|
color: #e2e8f0;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
.header p {
|
||||||
|
color: #7b8da0;
|
||||||
|
font-size: 0.95rem;
|
||||||
|
}
|
||||||
|
.grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
|
||||||
|
gap: 1.25rem;
|
||||||
|
max-width: 900px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.card {
|
||||||
|
background: #1a1d26;
|
||||||
|
border: 1px solid #2a2e3a;
|
||||||
|
border-radius: 10px;
|
||||||
|
padding: 1.5rem;
|
||||||
|
text-decoration: none;
|
||||||
|
color: inherit;
|
||||||
|
transition: border-color 0.2s, transform 0.15s;
|
||||||
|
}
|
||||||
|
.card:hover {
|
||||||
|
border-color: #3b82f6;
|
||||||
|
transform: translateY(-2px);
|
||||||
|
}
|
||||||
|
.card .icon {
|
||||||
|
font-size: 2rem;
|
||||||
|
margin-bottom: 0.75rem;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
.card h2 {
|
||||||
|
font-size: 1.1rem;
|
||||||
|
color: #e2e8f0;
|
||||||
|
margin-bottom: 0.4rem;
|
||||||
|
}
|
||||||
|
.card p {
|
||||||
|
font-size: 0.85rem;
|
||||||
|
color: #7b8da0;
|
||||||
|
line-height: 1.4;
|
||||||
|
}
|
||||||
|
.footer {
|
||||||
|
margin-top: 3rem;
|
||||||
|
color: #4a5568;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="header">
|
||||||
|
<h1>OpenBMP Lab</h1>
|
||||||
|
<p>BGP Monitoring Protocol · Route Analysis · Telemetry</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="grid">
|
||||||
|
<a href="/grafana/" class="card">
|
||||||
|
<span class="icon">📊</span>
|
||||||
|
<h2>Grafana Dashboards</h2>
|
||||||
|
<p>BGP analytics, RR Loc-RIB diff, IS-IS topology, telemetry, and 27+ dashboards.</p>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<a href="/exabgp/" class="card">
|
||||||
|
<span class="icon">🛤</span>
|
||||||
|
<h2>ExaBGP Route Injector</h2>
|
||||||
|
<p>Inject and withdraw BGP routes into the lab fabric via ExaBGP API.</p>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<a href="/traffic/" class="card">
|
||||||
|
<span class="icon">🚀</span>
|
||||||
|
<h2>Traffic Generator</h2>
|
||||||
|
<p>RFC 2544 throughput, latency, and loss testing across the network.</p>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="footer">
|
||||||
|
OpenBMP Docker Stack · 9 IOS-XR Routers · CML Lab
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@ -11,5 +11,11 @@ server {
|
|||||||
|
|
||||||
location / {
|
location / {
|
||||||
try_files $uri $uri/ /index.html;
|
try_files $uri $uri/ /index.html;
|
||||||
|
add_header Cache-Control "no-cache, no-store, must-revalidate";
|
||||||
|
}
|
||||||
|
|
||||||
|
location /assets/ {
|
||||||
|
expires 1y;
|
||||||
|
add_header Cache-Control "public, immutable";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
const BASE = '/api'
|
const BASE = '/traffic/api'
|
||||||
|
|
||||||
async function req(method, path, body) {
|
async function req(method, path, body) {
|
||||||
const opts = { method, headers: { 'Content-Type': 'application/json' } }
|
const opts = { method, headers: { 'Content-Type': 'application/json' } }
|
||||||
@ -12,6 +12,7 @@ export const api = {
|
|||||||
health: () => req('GET', '/healthz'),
|
health: () => req('GET', '/healthz'),
|
||||||
interfaces: () => req('GET', '/interfaces'),
|
interfaces: () => req('GET', '/interfaces'),
|
||||||
mode: () => req('GET', '/mode'),
|
mode: () => req('GET', '/mode'),
|
||||||
|
setMode: (mode) => req('POST', '/mode', { mode }),
|
||||||
|
|
||||||
// Flows
|
// Flows
|
||||||
flows: () => req('GET', '/flows'),
|
flows: () => req('GET', '/flows'),
|
||||||
@ -38,6 +39,9 @@ export const api = {
|
|||||||
// Stats
|
// Stats
|
||||||
statsHistory: () => req('GET', '/stats/history'),
|
statsHistory: () => req('GET', '/stats/history'),
|
||||||
|
|
||||||
|
// Ping
|
||||||
|
ping: (target, count) => req('POST', '/ping', { target, count: count || 5 }),
|
||||||
|
|
||||||
// Responder
|
// Responder
|
||||||
responderStats: () => req('GET', '/responder/stats'),
|
responderStats: () => req('GET', '/responder/stats'),
|
||||||
responderReset: () => req('POST', '/responder/reset'),
|
responderReset: () => req('POST', '/responder/reset'),
|
||||||
|
|||||||
@ -2,6 +2,7 @@ import { defineConfig } from 'vite'
|
|||||||
import vue from '@vitejs/plugin-vue'
|
import vue from '@vitejs/plugin-vue'
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
|
base: '/traffic/',
|
||||||
plugins: [vue()],
|
plugins: [vue()],
|
||||||
server: {
|
server: {
|
||||||
proxy: {
|
proxy: {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user