feat(services): HTTP/2 + HTTP/3 support via Caddy reverse-proxy
Swap Werkzeug for Caddy as the protocol layer for http and https decoy services. Flask keeps owning app logic (fake_app, custom_body, headers, syslog) on 127.0.0.1:8080; Caddy terminates h1/h2/h2c/h3 on the wire with real-world TLS/QUIC fingerprints. - Add `multi_enum` FieldType to ServiceConfigField + _coerce - Add `http_versions` field to HTTPService (h1/h2c) and HTTPSService (h1/h2/h3); selecting h3 emits UDP/443 port mapping in compose - Rewrite both Dockerfiles with multi-stage Caddy binary copy + setcap for port binding as the logrelay user - Entrypoints parse HTTP_VERSIONS JSON, render a Caddyfile, start Flask in background, wait for it, then exec Caddy - https/server.py drops direct TLS handling; Caddy owns the cert - Add ProxyFix to both server.py so Flask sees real attacker IPs - Frontend: multi_enum checkbox-group renderer in ServiceConfigFields; FormValue union extended to string[]; compactPayload skips [] - Fix stale test_smtp_relay_schema_matches_smtp: relay schema is a superset of smtp, not equal; update assertions accordingly
This commit is contained in:
@@ -5,7 +5,7 @@ import './ServiceConfigForm.css';
|
||||
export interface ServiceConfigFieldDTO {
|
||||
key: string;
|
||||
label: string;
|
||||
type: 'string' | 'password' | 'int' | 'bool' | 'textarea' | 'enum';
|
||||
type: 'string' | 'password' | 'int' | 'bool' | 'textarea' | 'enum' | 'multi_enum';
|
||||
default?: unknown;
|
||||
secret?: boolean;
|
||||
help?: string | null;
|
||||
@@ -20,17 +20,19 @@ export interface SchemaResponse {
|
||||
fields: ServiceConfigFieldDTO[];
|
||||
}
|
||||
|
||||
export type FormValue = string | number | boolean;
|
||||
export type FormValue = string | number | boolean | string[];
|
||||
export type FormState = Record<string, FormValue>;
|
||||
|
||||
export function toFormValue(field: ServiceConfigFieldDTO, raw: unknown): FormValue {
|
||||
if (raw === undefined || raw === null) {
|
||||
if (field.type === 'bool') return Boolean(field.default);
|
||||
if (field.type === 'int') return field.default == null ? ('' as unknown as number) : Number(field.default);
|
||||
if (field.type === 'multi_enum') return Array.isArray(field.default) ? (field.default as string[]) : [];
|
||||
return (field.default as string | undefined) ?? '';
|
||||
}
|
||||
if (field.type === 'bool') return Boolean(raw);
|
||||
if (field.type === 'int') return Number(raw);
|
||||
if (field.type === 'multi_enum') return Array.isArray(raw) ? (raw as string[]) : [];
|
||||
return String(raw);
|
||||
}
|
||||
|
||||
@@ -51,6 +53,7 @@ export function compactPayload(
|
||||
for (const f of fields) {
|
||||
const v = state[f.key];
|
||||
if (v === '' || v === undefined || v === null) continue;
|
||||
if (Array.isArray(v) && v.length === 0) continue;
|
||||
out[f.key] = v;
|
||||
}
|
||||
return out;
|
||||
@@ -129,7 +132,31 @@ const ServiceConfigFields: React.FC<Props> = ({
|
||||
{f.label}
|
||||
{f.secret && <span className="svc-cfg-secret-tag">· secret</span>}
|
||||
</label>
|
||||
{f.type === 'bool' ? (
|
||||
{f.type === 'multi_enum' ? (
|
||||
<fieldset className="svc-cfg-multi-enum">
|
||||
{(f.enum ?? []).map((opt) => {
|
||||
const optId = `${id}-${opt}`;
|
||||
const selected = Array.isArray(v) ? (v as string[]) : [];
|
||||
const checked = selected.includes(opt);
|
||||
return (
|
||||
<label key={opt} htmlFor={optId} className="svc-cfg-multi-enum-option">
|
||||
<input
|
||||
id={optId}
|
||||
type="checkbox"
|
||||
checked={checked}
|
||||
onChange={() => {
|
||||
const next = checked
|
||||
? selected.filter((x) => x !== opt)
|
||||
: [...selected, opt];
|
||||
setVal(f.key, next);
|
||||
}}
|
||||
/>
|
||||
{opt}
|
||||
</label>
|
||||
);
|
||||
})}
|
||||
</fieldset>
|
||||
) : f.type === 'bool' ? (
|
||||
<input
|
||||
id={id}
|
||||
type="checkbox"
|
||||
|
||||
@@ -181,3 +181,30 @@ select.svc-cfg-input {
|
||||
font-style: italic;
|
||||
}
|
||||
.svc-cfg-status.alert-text { font-style: normal; }
|
||||
|
||||
/* multi_enum checkbox group — sits in the input column, stacks vertically. */
|
||||
.svc-cfg-multi-enum {
|
||||
grid-column: 2 / 3;
|
||||
grid-row: 1 / 2;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
border: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
.svc-cfg-multi-enum-option {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
font-size: 0.68rem;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
}
|
||||
.svc-cfg-multi-enum-option input[type="checkbox"] {
|
||||
width: 13px;
|
||||
height: 13px;
|
||||
accent-color: var(--violet);
|
||||
cursor: pointer;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user