mirror of
https://github.com/digital-asset/daml.git
synced 2024-09-20 09:17:43 +03:00
webide: bugfix for content policy (#1262)
* safari has stricter intepretations of the policy. This adds explicit web socket connect-src field * bugfix for multiple requests for webpage in a very small amount of time. This would previously start up multiple containers
This commit is contained in:
parent
9684e1325e
commit
b12b94163d
@ -4,6 +4,7 @@
|
||||
"port" : 3000,
|
||||
"managementPort" : 3001,
|
||||
"httpToHttpsPort" : 3002,
|
||||
"hostname" : "localhost",
|
||||
"secureHeaders" :true
|
||||
},
|
||||
"session" : {
|
||||
|
@ -77,6 +77,7 @@ export default class Docker {
|
||||
const createOptions = { HostConfig: config.docker.hostConfig }
|
||||
if (!this.onInternalNetwork && config.devMode) createOptions.HostConfig.PublishAllPorts=true
|
||||
|
||||
debug("sending run api command")
|
||||
const runner = this.api.run(imageId, ["code-server", "--no-auth", "--allow-http", "--disable-telemetry"], [], createOptions, (err :any, result :any) => {
|
||||
if (err) reject(err)
|
||||
})
|
||||
@ -86,7 +87,7 @@ export default class Docker {
|
||||
cResolve(container)
|
||||
})
|
||||
})
|
||||
runner.on('container', (container :Container) => debug(`created container ${container.id}`))
|
||||
runner.on('container', (container :Container) => { debug(`created container ${container.id}`) })
|
||||
runner.on('stream', (stream :Stream) => {
|
||||
let started = false
|
||||
stream.pipe(process.stdout) //TODO should we create context aware log messages per container (including the user session info)??
|
||||
|
@ -48,7 +48,8 @@ http.createServer(httpToHttpsApp).listen(conf.http.httpToHttpsPort, () => {
|
||||
|
||||
if (!conf.http.port) throw new Error("MUST configure port for webide: 'conf.http.port'")
|
||||
const webideServer = createWebIdeServer()
|
||||
if (conf.secureHeaders || conf.secureHeaders === undefined) {
|
||||
if (conf.http.secureHeaders || conf.http.secureHeaders === undefined) {
|
||||
console.log("INFO adding security headers")
|
||||
webIdeApp.use((req, res, next) => {
|
||||
addSecureHeaders(res, conf)
|
||||
next()
|
||||
@ -143,5 +144,5 @@ function addSecureHeaders(res :Response, config :any) {
|
||||
res.setHeader("X-Frame-Options", "sameorigin")
|
||||
res.setHeader("X-XSS-Protection", "1; mode=block")
|
||||
res.setHeader("Referrer-Policy", "no-referrer-when-downgrade")
|
||||
res.setHeader("Content-Security-Policy", `default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data:;`)
|
||||
res.setHeader("Content-Security-Policy", `default-src 'self'; connect-src 'self' ws://${config.http.hostname}:${config.http.port}/; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self' data:;`)
|
||||
}
|
||||
|
@ -13,6 +13,7 @@ import { ImageInspectInfo } from "dockerode";
|
||||
import request, { HttpArchiveRequest } from "request"
|
||||
import zlib from "zlib"
|
||||
import { Readable } from "stream"
|
||||
import NodeCache from "node-cache"
|
||||
|
||||
const conf = require('../config').read()
|
||||
const debug = require('debug')('webide:route')
|
||||
@ -80,7 +81,7 @@ export default class WebIdeRoute {
|
||||
const route = this
|
||||
debug("requesting %s", req.url)
|
||||
Session.session(req, res, (err :any, state :any, sessionId :string, saveSession :any) => {
|
||||
route.getImage()
|
||||
route.getDockerImage()
|
||||
.then(image => route.ensureDockerContainer(req, state, saveSession, sessionId, image))
|
||||
.then(containerInfo => {
|
||||
const url = route.docker.getContainerUrl(containerInfo, 'http')
|
||||
@ -88,15 +89,11 @@ export default class WebIdeRoute {
|
||||
})
|
||||
.catch(err => {
|
||||
console.error(`could not initiate connection to web-ide: ${err}`)
|
||||
if (err instanceof ProxyError) res.statusCode = err.status
|
||||
else res.statusCode = 500
|
||||
res.end()
|
||||
route.sendErrorResponse(err, req, res)
|
||||
})
|
||||
})
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
res.statusCode = 500
|
||||
res.end()
|
||||
this.sendErrorResponse(error, req, res)
|
||||
}
|
||||
}
|
||||
|
||||
@ -125,7 +122,7 @@ export default class WebIdeRoute {
|
||||
//doing nothing we let the proxy handle the response
|
||||
}
|
||||
|
||||
private getImage() :Promise<ImageInspectInfo> {
|
||||
private getDockerImage() :Promise<ImageInspectInfo> {
|
||||
return this.docker.getImage(conf.docker.webIdeReference)
|
||||
}
|
||||
|
||||
@ -169,7 +166,9 @@ export default class WebIdeRoute {
|
||||
|
||||
private ensureDockerContainer(req :Request, state :any, saveSession :Session.SaveSession, sessionId :string, image :ImageInspectInfo) {
|
||||
if (!state.docker) {
|
||||
if (!state.initializing) {
|
||||
//double check current state whether it is initializing or not
|
||||
const currentState :any = Session.getStateSync(sessionId) || {}
|
||||
if (!currentState.initializing) {
|
||||
state.initializing = true;
|
||||
saveSession(state);
|
||||
return this.docker.api.listContainers({all: false, filters: { ancestor: [`${conf.docker.webIdeReference}`] }})
|
||||
@ -177,7 +176,7 @@ export default class WebIdeRoute {
|
||||
if (containers.length >= conf.docker.maxInstances) {
|
||||
state.initializing = false;
|
||||
saveSession(state);
|
||||
return Promise.reject(new ProxyError(`Breach max instances ${conf.docker.maxInstances}`, 503))
|
||||
return Promise.reject(new ProxyError(`Breach max instances ${conf.docker.maxInstances}`, 503, "There is unusually high server load. Please try again in a couple of minutes."))
|
||||
}
|
||||
return this.docker.startContainer(image.Id).then(c => {
|
||||
console.log("INFO attaching container %s to session %s", c.Id, sessionId)
|
||||
@ -188,16 +187,16 @@ export default class WebIdeRoute {
|
||||
});
|
||||
});
|
||||
} else {
|
||||
//this occurs sporadically (perhaps when developer tools is open) sending another request
|
||||
//TODO create better promise handling without timeout
|
||||
console.log("INFO request sent during initialization...waiting for docker to come up")
|
||||
return new Promise((resolve, reject) => {
|
||||
Session.readSession(req, (err, state, sessionId) => {
|
||||
const wait = setTimeout(() => {
|
||||
clearTimeout(wait);
|
||||
resolve(state.docker)
|
||||
}, 10000)
|
||||
})
|
||||
const interval = setInterval(() => {
|
||||
Session.readSession(req, (err, state, sessionId) => {
|
||||
if (state.docker && !state.initializing) {
|
||||
clearInterval(interval);
|
||||
resolve(state.docker);
|
||||
}
|
||||
})
|
||||
}, 1000)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -28,8 +28,12 @@ export function onTimeout(callback: Callback<any>) {
|
||||
});
|
||||
}
|
||||
|
||||
function remove(sessionId :string) {
|
||||
store.del(sessionId)
|
||||
/**
|
||||
* explicit synchronous getter in case we need state current state
|
||||
* @param sessionId
|
||||
*/
|
||||
export function getStateSync(sessionId :string) :any {
|
||||
return store.get(sessionId)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -122,7 +126,7 @@ function save(sessionId :string, state :any) :boolean {
|
||||
store.del(sessionId)
|
||||
}, delay)
|
||||
}
|
||||
return store.set(sessionId, state)
|
||||
return store.set(sessionId, state, 0)
|
||||
}
|
||||
|
||||
function generateSessionId() {
|
||||
|
Loading…
Reference in New Issue
Block a user