diff --git a/vscode/Dockerfile b/vscode/Dockerfile index 1505b35..9181cb8 100755 --- a/vscode/Dockerfile +++ b/vscode/Dockerfile @@ -60,17 +60,22 @@ RUN \ build-essential=12.4ubuntu1 \ colordiff=1.0.18-1 \ git=1:2.17.1-1ubuntu0.4 \ + libnginx-mod-http-lua=1.14.0-0ubuntu1.2 \ locales=2.27-3ubuntu1 \ + luarocks=2.4.2+dfsg-1 \ mosquitto-clients=1.4.15-2ubuntu0.18.04.2 \ net-tools=1.60+git20161116.90da8a0-1ubuntu1 \ + nginx=1.14.0-0ubuntu1.2 \ nmap=7.60-1ubuntu5 \ openssh-client=1:7.6p1-4ubuntu0.3 \ openssl=1.1.0g-2ubuntu4.3 \ - python3=3.6.7-1~18.04 \ python3-dev=3.6.7-1~18.04 \ + python3=3.6.7-1~18.04 \ wget=1.19.4-1ubuntu2.1 \ zsh=5.4.2-3ubuntu3.1 \ \ + && luarocks install lua-resty-http 0.13-0 \ + \ && curl https://bootstrap.pypa.io/get-pip.py | python3 \ \ && locale-gen en_US.UTF-8 \ @@ -106,8 +111,9 @@ RUN \ && pip3 install --no-cache-dir -r /tmp/requirements.txt \ \ && apt-get purge -y --auto-remove \ - python3-dev \ build-essential \ + luarocks \ + python3-dev \ \ && find /usr/local/lib/python3.6/ -type d -name tests -depth -exec rm -rf {} \; \ && find /usr/local/lib/python3.6/ -type d -name test -depth -exec rm -rf {} \; \ diff --git a/vscode/config.json b/vscode/config.json index 2c122a4..7179eca 100755 --- a/vscode/config.json +++ b/vscode/config.json @@ -11,6 +11,7 @@ ], "boot": "auto", "hassio_api": true, + "auth_api": true, "hassio_role": "manager", "homeassistant_api": true, "host_network": false, @@ -26,7 +27,6 @@ "backup:rw" ], "options": { - "password": "", "ssl": true, "certfile": "fullchain.pem", "keyfile": "privkey.pem", @@ -35,7 +35,6 @@ }, "schema": { "log_level": "match(^(trace|debug|info|notice|warning|error|fatal)$)?", - "password": "str", "ssl": "bool", "certfile": "str", "keyfile": "str", diff --git a/vscode/rootfs/etc/cont-init.d/10-requirements.sh b/vscode/rootfs/etc/cont-init.d/10-requirements.sh index 62aad46..f722ae9 100644 --- a/vscode/rootfs/etc/cont-init.d/10-requirements.sh +++ b/vscode/rootfs/etc/cont-init.d/10-requirements.sh @@ -4,24 +4,3 @@ # This files check if all user configuration requirements are met # ============================================================================== bashio::config.require.ssl - -if ! bashio::config.true 'leave_front_door_open'; then - if ! bashio::config.true 'i_like_to_be_pwned'; then - bashio::config.require.safe_password - else - bashio::config.require.password - fi -fi - -if bashio::config.true 'leave_front_door_open' \ - && bashio::config.true 'ssl'; -then - bashio::log.fatal - bashio::log.fatal "Due to a bug in code-server (which this add-on uses)," - bashio::log.fatal "it is impossible to disable authentication while" - bashio::log.fatal "using SSL." - bashio::log.fatal - bashio::log.fatal "Please enable authentication and set a password." - bashio::log.fatal - bashio::exit.nok -fi diff --git a/vscode/rootfs/etc/cont-init.d/11-nginx.sh b/vscode/rootfs/etc/cont-init.d/11-nginx.sh new file mode 100644 index 0000000..422b5a9 --- /dev/null +++ b/vscode/rootfs/etc/cont-init.d/11-nginx.sh @@ -0,0 +1,17 @@ +#!/usr/bin/with-contenv bashio +# ============================================================================== +# Community Hass.io Add-ons: Visual Studio Code +# Configures NGINX for use with code-server +# ============================================================================== +declare certfile +declare keyfile + +mkdir -p /var/log/nginx + +if bashio::config.true 'ssl'; then + certfile=$(bashio::config 'certfile') + keyfile=$(bashio::config 'keyfile') + + sed -i "s/%%certfile%%/${certfile}/g" /etc/nginx/nginx-ssl.conf + sed -i "s/%%keyfile%%/${keyfile}/g" /etc/nginx/nginx-ssl.conf +fi diff --git a/vscode/rootfs/etc/nginx/ha-auth.lua b/vscode/rootfs/etc/nginx/ha-auth.lua new file mode 100644 index 0000000..4c9ca91 --- /dev/null +++ b/vscode/rootfs/etc/nginx/ha-auth.lua @@ -0,0 +1,83 @@ +local http = require "resty.http" +local auths = ngx.shared.auths + +function authenticate() + + --- Test Authentication header is set and with a value + local header = ngx.req.get_headers()['Authorization'] + if header == nil or header:find(" ") == nil then + return false + end + + local divider = header:find(' ') + if header:sub(0, divider-1) ~= 'Basic' then + return false + end + + local auth = ngx.decode_base64(header:sub(divider+1)) + if auth == nil or auth:find(':') == nil then + return false + end + + divider = auth:find(':') + local username = auth:sub(0, divider-1) + local password = auth:sub(divider+1) + + --- Check if authentication is cached + if auths:get(username) == password then + ngx.log(ngx.DEBUG, "Authenticated user against Home Assistant (cache).") + return true + end + + --- HTTP request against Hassio API + local httpc = http.new() + local res, err = httpc:request_uri("http://hassio/auth", { + method = "POST", + body = ngx.encode_args({["username"]=username, ["password"]=password}), + headers = { + ["Content-Type"] = "application/x-www-form-urlencoded", + ["X-HASSIO-KEY"] = os.getenv("HASSIO_TOKEN"), + }, + keepalive_timeout = 60, + keepalive_pool = 10 + }) + + --- Error during API request + if err then + ngx.log(ngx.WARN, "Error during Hassio user authentication.", err) + return false + end + + --- No result? Something went wrong... + if not res then + ngx.log(ngx.WARN, "Error during Hassio user authentication.") + return false + end + + --- Valid response, the username/password is valid + if res.status == 200 then + ngx.log(ngx.INFO, "Authenticated user against Home Assistant.") + auths:set(username, password, 60) + return true + end + + --- Whatever the response is, it is invalid + ngx.log(ngx.WARN, "Authentication against Home Assistant failed!") + return false +end + +-- Only authenticate if its not disabled +if not os.getenv('DISABLE_HA_AUTHENTICATION') then + + --- Try to authenticate against HA + local authenticated = authenticate() + + --- If authentication failed, throw a basic auth + if not authenticated then + ngx.header.content_type = 'text/plain' + ngx.header.www_authenticate = 'Basic realm="Home Assistant"' + ngx.status = ngx.HTTP_UNAUTHORIZED + ngx.say('401 Access Denied') + ngx.exit(ngx.HTTP_UNAUTHORIZED) + end +end diff --git a/vscode/rootfs/etc/nginx/nginx-ssl.conf b/vscode/rootfs/etc/nginx/nginx-ssl.conf new file mode 100644 index 0000000..bfea197 --- /dev/null +++ b/vscode/rootfs/etc/nginx/nginx-ssl.conf @@ -0,0 +1,71 @@ +worker_processes 1; +pid /var/run/nginx.pid; +error_log stderr; +env HASSIO_TOKEN; +env DISABLE_HA_AUTHENTICATION; +load_module "/usr/lib/nginx/modules/ndk_http_module.so"; +load_module "/usr/lib/nginx/modules/ngx_http_lua_module.so"; + +events { + worker_connections 1024; +} + +http { + access_log stdout; + include mime.types; + default_type application/octet-stream; + sendfile on; + keepalive_timeout 65; + lua_shared_dict auths 16k; + resolver 127.0.0.11; + + upstream code { + ip_hash; + server 127.0.0.1:8443; + } + + map $http_upgrade $connection_upgrade { + default upgrade; + '' close; + } + + server { + server_name hassio.local; + listen 1337 default_server ssl; + root /dev/null; + + ssl_certificate /ssl/%%certfile%%; + ssl_certificate_key /ssl/%%keyfile%%; + ssl_protocols TLSv1.2; + ssl_prefer_server_ciphers on; + ssl_ciphers ECDHE-RSA-AES256-GCM-SHA512:DHE-RSA-AES256-GCM-SHA512:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:DHE-RSA-AES256-SHA; + ssl_ecdh_curve secp384r1; + ssl_session_timeout 10m; + ssl_session_cache shared:SSL:10m; + ssl_session_tickets off; + ssl_stapling on; + ssl_stapling_verify on; + + add_header X-Content-Type-Options nosniff; + add_header X-XSS-Protection "1; mode=block"; + add_header X-Robots-Tag none; + + location / { + access_by_lua_file /etc/nginx/ha-auth.lua; + + proxy_redirect off; + proxy_pass http://code; + + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection $connection_upgrade; + proxy_set_header Authorization ""; + + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header Host $http_host; + proxy_set_header X-NginX-Proxy true; + } + } +} diff --git a/vscode/rootfs/etc/nginx/nginx.conf b/vscode/rootfs/etc/nginx/nginx.conf new file mode 100644 index 0000000..a0f8ccf --- /dev/null +++ b/vscode/rootfs/etc/nginx/nginx.conf @@ -0,0 +1,57 @@ +worker_processes 1; +pid /var/run/nginx.pid; +error_log stderr; +env HASSIO_TOKEN; +env DISABLE_HA_AUTHENTICATION; +load_module "/usr/lib/nginx/modules/ndk_http_module.so"; +load_module "/usr/lib/nginx/modules/ngx_http_lua_module.so"; + +events { + worker_connections 1024; +} + +http { + access_log stdout; + include mime.types; + default_type application/octet-stream; + sendfile on; + keepalive_timeout 65; + lua_shared_dict auths 16k; + resolver 127.0.0.11; + + upstream code { + ip_hash; + server 127.0.0.1:8443; + } + + map $http_upgrade $connection_upgrade { + default upgrade; + '' close; + } + + server { + server_name hassio.local; + listen 1337 default_server; + root /dev/null; + + location / { + access_by_lua_file /etc/nginx/ha-auth.lua; + + proxy_redirect off; + proxy_pass http://code; + + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection $connection_upgrade; + proxy_set_header Authorization ""; + + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header Host $http_host; + proxy_set_header X-NginX-Proxy true; + proxy_read_timeout 86400s; + proxy_send_timeout 86400s; + } + } +} diff --git a/vscode/rootfs/etc/services.d/code/run b/vscode/rootfs/etc/services.d/code/run index c5c805f..4506982 100644 --- a/vscode/rootfs/etc/services.d/code/run +++ b/vscode/rootfs/etc/services.d/code/run @@ -7,22 +7,13 @@ declare -a options bashio::log.info 'Starting the code server...' -# Non-interactive -options+=(--port 1337) +options+=(--port 8443) options+=(--data-dir "/data/vscode") +options+=(--host 127.0.0.1) +options+=(--allow-http) -if bashio::config.false 'ssl'; then - options+=(--allow-http) -else - options+=(--cert "/ssl/$(bashio::config 'certfile')") - options+=(--cert-key "/ssl/$(bashio::config 'keyfile')") -fi - -if ! bashio::config.has_value 'password'; then - options+=(--no-auth) -else - options+=(--password "$(bashio::config 'password')") -fi +# Disable code authentication, we use HA authentication +options+=(--no-auth) # Export env variables for the Home Assistant extension export HASS_SERVER="http://hassio/homeassistant" diff --git a/vscode/rootfs/etc/services.d/nginx/finish b/vscode/rootfs/etc/services.d/nginx/finish new file mode 100644 index 0000000..9fe3ffb --- /dev/null +++ b/vscode/rootfs/etc/services.d/nginx/finish @@ -0,0 +1,9 @@ +#!/usr/bin/execlineb -S0 +# ============================================================================== +# Community Hass.io Add-ons: Visual Studio Code +# Take down the S6 supervision tree when Nginx fails +# ============================================================================== +if -n { s6-test $# -ne 0 } +if -n { s6-test ${1} -eq 256 } + +s6-svscanctl -t /var/run/s6/services diff --git a/vscode/rootfs/etc/services.d/nginx/run b/vscode/rootfs/etc/services.d/nginx/run new file mode 100644 index 0000000..653a417 --- /dev/null +++ b/vscode/rootfs/etc/services.d/nginx/run @@ -0,0 +1,28 @@ +#!/usr/bin/with-contenv bashio +# ============================================================================== +# Community Hass.io Add-ons: Visual Studio Code +# Runs the Nginx daemon +# ============================================================================== +declare -a options + +# Wait for code-server to become available +s6-svwait -u -t 5000 /var/run/s6/services/code +timeout 15 \ + bash -c \ + 'until echo > /dev/tcp/localhost/8443 ; do sleep 0.5; done' \ + > /dev/null 2>&1 + +bashio::log.info "Starting NGinx..." + +# Disable HA Authentication if front door is open +if bashio::config.true 'leave_front_door_open'; then + export DISABLE_HA_AUTHENTICATION=true +fi + +options+=(-g "daemon off;") + +if bashio::config.true 'ssl'; then + options+=(-c /etc/nginx/nginx-ssl.conf) +fi + +exec nginx "${options[@]}"