feat(docker): auto-bind container ports to host ports (#17307)
Drive-by: make sure docker container does not expose ports on `0.0.0.0` and instead registers to localhost. This way websocket and vnc ports are not exposed to the public internet.
This commit is contained in:
parent
462fa7d79d
commit
705bc28e92
|
|
@ -96,18 +96,31 @@ interface ContainerInfo {
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function containerInfo(): Promise<ContainerInfo|undefined> {
|
export async function containerInfo(): Promise<ContainerInfo|undefined> {
|
||||||
const containerId = await findRunningDockerContainerId();
|
const container = await findRunningDockerContainer();
|
||||||
if (!containerId)
|
if (!container)
|
||||||
return undefined;
|
return undefined;
|
||||||
const logLines = await dockerApi.getContainerLogs(containerId);
|
const logLines = await dockerApi.getContainerLogs(container.containerId);
|
||||||
|
|
||||||
|
const containerUrlToHostUrl = (address: string) => {
|
||||||
|
const url = new URL(address);
|
||||||
|
const portBinding = container.portBindings.find(binding => binding.containerPort === +url.port);
|
||||||
|
if (!portBinding)
|
||||||
|
return undefined;
|
||||||
|
|
||||||
|
url.host = portBinding.ip;
|
||||||
|
url.port = portBinding.hostPort + '';
|
||||||
|
return url.toString();
|
||||||
|
};
|
||||||
|
|
||||||
const WS_LINE_PREFIX = 'Listening on ws://';
|
const WS_LINE_PREFIX = 'Listening on ws://';
|
||||||
const webSocketLine = logLines.find(line => line.startsWith(WS_LINE_PREFIX));
|
const webSocketLine = logLines.find(line => line.startsWith(WS_LINE_PREFIX));
|
||||||
const NOVNC_LINE_PREFIX = 'novnc is listening on ';
|
const NOVNC_LINE_PREFIX = 'novnc is listening on ';
|
||||||
const novncLine = logLines.find(line => line.startsWith(NOVNC_LINE_PREFIX));
|
const novncLine = logLines.find(line => line.startsWith(NOVNC_LINE_PREFIX));
|
||||||
return novncLine && webSocketLine ? {
|
if (!novncLine || !webSocketLine)
|
||||||
wsEndpoint: 'ws://' + webSocketLine.substring(WS_LINE_PREFIX.length),
|
return undefined;
|
||||||
vncSession: novncLine.substring(NOVNC_LINE_PREFIX.length),
|
const wsEndpoint = containerUrlToHostUrl('ws://' + webSocketLine.substring(WS_LINE_PREFIX.length));
|
||||||
} : undefined;
|
const vncSession = containerUrlToHostUrl(novncLine.substring(NOVNC_LINE_PREFIX.length));
|
||||||
|
return wsEndpoint && vncSession ? { wsEndpoint, vncSession } : undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function ensureContainerOrDie(): Promise<ContainerInfo> {
|
export async function ensureContainerOrDie(): Promise<ContainerInfo> {
|
||||||
|
|
@ -149,11 +162,11 @@ export async function ensureContainerOrDie(): Promise<ContainerInfo> {
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function stopContainer() {
|
export async function stopContainer() {
|
||||||
const containerId = await findRunningDockerContainerId();
|
const container = await findRunningDockerContainer();
|
||||||
if (!containerId)
|
if (!container)
|
||||||
return;
|
return;
|
||||||
await dockerApi.stopContainer({
|
await dockerApi.stopContainer({
|
||||||
containerId,
|
containerId: container.containerId,
|
||||||
waitUntil: 'removed',
|
waitUntil: 'removed',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -176,10 +189,10 @@ async function findDockerImage(imageName: string): Promise<dockerApi.DockerImage
|
||||||
return images.find(image => image.names.includes(imageName));
|
return images.find(image => image.names.includes(imageName));
|
||||||
}
|
}
|
||||||
|
|
||||||
async function findRunningDockerContainerId(): Promise<string|undefined> {
|
async function findRunningDockerContainer(): Promise<dockerApi.DockerContainer|undefined> {
|
||||||
const containers = await dockerApi.listContainers();
|
const containers = await dockerApi.listContainers();
|
||||||
const dockerImage = await findDockerImage(VRT_IMAGE_NAME);
|
const dockerImage = await findDockerImage(VRT_IMAGE_NAME);
|
||||||
const container = dockerImage ? containers.find(container => container.imageId === dockerImage.imageId) : undefined;
|
const container = dockerImage ? containers.find(container => container.imageId === dockerImage.imageId) : undefined;
|
||||||
return container?.state === 'running' ? container.containerId : undefined;
|
return container?.state === 'running' ? container : undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -25,11 +25,18 @@ export interface DockerImage {
|
||||||
names: string[];
|
names: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface PortBinding {
|
||||||
|
ip: string;
|
||||||
|
hostPort: number;
|
||||||
|
containerPort: number;
|
||||||
|
}
|
||||||
|
|
||||||
export interface DockerContainer {
|
export interface DockerContainer {
|
||||||
containerId: string;
|
containerId: string;
|
||||||
imageId: string;
|
imageId: string;
|
||||||
state: 'created'|'restarting'|'running'|'removing'|'paused'|'exited'|'dead';
|
state: 'created'|'restarting'|'running'|'removing'|'paused'|'exited'|'dead';
|
||||||
names: string[];
|
names: string[];
|
||||||
|
portBindings: PortBinding[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function listContainers(): Promise<DockerContainer[]> {
|
export async function listContainers(): Promise<DockerContainer[]> {
|
||||||
|
|
@ -38,7 +45,12 @@ export async function listContainers(): Promise<DockerContainer[]> {
|
||||||
containerId: container.Id,
|
containerId: container.Id,
|
||||||
imageId: container.ImageID,
|
imageId: container.ImageID,
|
||||||
state: container.State,
|
state: container.State,
|
||||||
names: container.Names
|
names: container.Names,
|
||||||
|
portBindings: container.Ports?.map((portInfo: any) => ({
|
||||||
|
ip: portInfo.IP,
|
||||||
|
hostPort: portInfo.PublicPort,
|
||||||
|
containerPort: portInfo.PrivatePort,
|
||||||
|
})) ?? [],
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -56,7 +68,7 @@ export async function launchContainer(options: LaunchContainerOptions): Promise<
|
||||||
const PortBindings: any = {};
|
const PortBindings: any = {};
|
||||||
for (const port of (options.ports ?? [])) {
|
for (const port of (options.ports ?? [])) {
|
||||||
ExposedPorts[`${port}/tcp`] = {};
|
ExposedPorts[`${port}/tcp`] = {};
|
||||||
PortBindings[`${port}/tcp`] = [{ HostPort: port + '' }];
|
PortBindings[`${port}/tcp`] = [{ HostPort: '0', HostIp: '127.0.0.1' }];
|
||||||
}
|
}
|
||||||
const container = await postJSON(`/containers/create` + (options.name ? '?name=' + options.name : ''), {
|
const container = await postJSON(`/containers/create` + (options.name ? '?name=' + options.name : ''), {
|
||||||
Cmd: options.command,
|
Cmd: options.command,
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue