Commit 9f966b19 authored by Julien Muchembled's avatar Julien Muchembled

theia: make /public/ really public, but prevent JS execution

parent 00ac69e6
...@@ -15,7 +15,7 @@ ...@@ -15,7 +15,7 @@
[instance-theia] [instance-theia]
_update_hash_filename_ = instance-theia.cfg.jinja.in _update_hash_filename_ = instance-theia.cfg.jinja.in
md5sum = 3648844f372a96974582e7281c9987dd md5sum = a9d4ace568acdd5002d587816ab91737
[instance] [instance]
_update_hash_filename_ = instance.cfg.in _update_hash_filename_ = instance.cfg.in
......
...@@ -15,7 +15,6 @@ theia-environment-parts = ...@@ -15,7 +15,6 @@ theia-environment-parts =
theia-parts = theia-parts =
frontend-instance frontend-instance
python-server
promises promises
parts = parts =
...@@ -123,8 +122,7 @@ config-port = $${frontend-instance:port} ...@@ -123,8 +122,7 @@ config-port = $${frontend-instance:port}
<= monitor-promise-base <= monitor-promise-base
promise = check_socket_listening promise = check_socket_listening
name = $${:_buildout_section_name_}.py name = $${:_buildout_section_name_}.py
config-host = $${python-server-port:ip} config-pathname = $${python-server:socket}
config-port = $${python-server-port:port}
[frontend-authentication-promise] [frontend-authentication-promise]
<= monitor-promise-base <= monitor-promise-base
...@@ -262,13 +260,14 @@ content = ...@@ -262,13 +260,14 @@ content =
log global log global
bind $${:ip}:$${:port} ssl crt $${frontend-instance-certificate:cert-file} alpn h2,http/1.1 bind $${:ip}:$${:port} ssl crt $${frontend-instance-certificate:cert-file} alpn h2,http/1.1
# writing twice the same ACL is doing OR # writing twice the same ACL is doing OR
acl is_public path_beg /public/
acl is_public path /$${frontend-instance-favicon.ico:filename} acl is_public path /$${frontend-instance-favicon.ico:filename}
acl is_public path /$${frontend-instance-theia.webmanifest:filename} acl is_public path /$${frontend-instance-theia.webmanifest:filename}
acl is_public path /$${frontend-instance-theia-serviceworker.js:filename} acl is_public path /$${frontend-instance-theia-serviceworker.js:filename}
acl auth_ok http_auth(basic-auth-list) acl auth_ok http_auth(basic-auth-list)
# No authentication for some files # No authentication for public folder
http-request auth unless auth_ok || is_public http-request auth unless auth_ok || is_public
use_backend static if { path_beg /$${frontend-instance-fonts:folder-name} } || { path_beg /$${frontend-instance-slapos.css:folder-name} } || { path /$${frontend-instance-logo:filename} } || { path_beg /public/ } || is_public use_backend static if { path_beg /$${frontend-instance-fonts:folder-name} } || { path_beg /$${frontend-instance-slapos.css:folder-name} } || { path /$${frontend-instance-logo:filename} } || is_public
default_backend nodejs default_backend nodejs
backend nodejs backend nodejs
...@@ -277,7 +276,9 @@ content = ...@@ -277,7 +276,9 @@ content =
backend static backend static
log global log global
server static_backend $${python-server-port:ip}:$${python-server-port:port} server static_backend $${python-server:socket}
option forwardfor
http-response set-header Content-Security-Policy "default-src 'self'; img-src 'self' data:; script-src 'none'"
ip = $${frontend-instance-port:ip} ip = $${frontend-instance-port:ip}
hostname = [$${:ip}] hostname = [$${:ip}]
...@@ -387,17 +388,28 @@ filename = favicon.ico ...@@ -387,17 +388,28 @@ filename = favicon.ico
# Local Python Server # Local Python Server
# ------------------- # -------------------
[python-server-port]
recipe = slapos.cookbook:free_port
minimum = 3000
maximum = 3100
ip = {{ ipv4_random }}
[python-server] [python-server]
recipe = slapos.cookbook:wrapper recipe = slapos.recipe.template
wrapper-path = $${directory:services}/$${:_buildout_section_name_} output = $${directory:services}/$${:_buildout_section_name_}
command-line = $${buildout:executable} -m http.server $${python-server-port:port} --bind $${python-server-port:ip} --directory $${directory:frontend-static} socket = $${directory:run}/$${:_buildout_section_name_}.sock
inline =
#!$${buildout:executable}
import atexit, os, socketserver
from http import server
class Server(socketserver.ThreadingUnixStreamServer):
daemon_threads = True
class Handler(server.SimpleHTTPRequestHandler):
def address_string(self): # insecure but ok for logging
return self.headers.get("X-Forwarded-For", "local")
s = "$${:socket}"
os.chdir("$${directory:frontend-static}")
def cleanup():
try:
os.remove(s)
except FileNotFoundError:
pass
atexit.register(cleanup)()
Server(s, Handler).serve_forever()
# Common Environment # Common Environment
# ------------------ # ------------------
......
...@@ -146,16 +146,16 @@ class TestTheia(TheiaTestCase): ...@@ -146,16 +146,16 @@ class TestTheia(TheiaTestCase):
)).geturl() )).geturl()
self.get(authenticated_url) self.get(authenticated_url)
# there's a public folder to serve file # there's a public folder to serve file (no need for authentication)
with open('{}/srv/frontend-static/public/test_file'.format( with open(self.getPath() + '/srv/frontend-static/public/test_file',
self.getPath()), 'w') as f: 'w') as f:
f.write("hello") f.write("hello")
resp = self.get(urljoin(authenticated_url, '/public/')) def get(path_info):
self.assertIn('test_file', resp.text) resp = self.get(urljoin(url, path_info))
resp = self.get(urljoin(authenticated_url, '/public/test_file')) self.assertIn('Content-Security-Policy', resp.headers)
self.assertEqual('hello', resp.text) return resp.text
# make sure public folder is protected self.assertIn('test_file', get('/public/'))
resp = self.get(urljoin(url, '/public/test_file'), requests.codes.unauthorized) self.assertEqual('hello', get('/public/test_file'))
# there's a (not empty) favicon (no need for authentication) # there's a (not empty) favicon (no need for authentication)
resp = self.get(urljoin(url, '/favicon.ico')) resp = self.get(urljoin(url, '/favicon.ico'))
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment