[FEATURE] Add Content-Security-Policy meta to login portal. (#822)
CSP is used to avoid some attacks where the hacker tries to execute untrusted code in the browser. The policy is to use assets hosted on the the original website and in order to make CSP work with material UI, a nonce is generated at each request of index.html and injected in the template as well as provided in the Content-Security-Policy header (https://material-ui.com/styles/advanced/#how-does-one-implement-csp) Fix #815pull/888/head^2
parent
2e784084c7
commit
b12d9d405f
|
@ -36,7 +36,7 @@ WORKDIR /node/src/app
|
||||||
COPY web .
|
COPY web .
|
||||||
|
|
||||||
# Install the dependencies and build
|
# Install the dependencies and build
|
||||||
RUN yarn install --frozen-lockfile && yarn build
|
RUN yarn install --frozen-lockfile && INLINE_RUNTIME_CHUNK=false yarn build
|
||||||
|
|
||||||
# ===================================
|
# ===================================
|
||||||
# ===== Authelia official image =====
|
# ===== Authelia official image =====
|
||||||
|
|
|
@ -39,7 +39,7 @@ WORKDIR /node/src/app
|
||||||
COPY web .
|
COPY web .
|
||||||
|
|
||||||
# Install the dependencies and build
|
# Install the dependencies and build
|
||||||
RUN yarn install --frozen-lockfile && yarn build
|
RUN yarn install --frozen-lockfile && INLINE_RUNTIME_CHUNK=false yarn build
|
||||||
|
|
||||||
# ===================================
|
# ===================================
|
||||||
# ===== Authelia official image =====
|
# ===== Authelia official image =====
|
||||||
|
|
|
@ -39,7 +39,7 @@ WORKDIR /node/src/app
|
||||||
COPY web .
|
COPY web .
|
||||||
|
|
||||||
# Install the dependencies and build
|
# Install the dependencies and build
|
||||||
RUN yarn install --frozen-lockfile && yarn build
|
RUN yarn install --frozen-lockfile && INLINE_RUNTIME_CHUNK=false yarn build
|
||||||
|
|
||||||
# ===================================
|
# ===================================
|
||||||
# ===== Authelia official image =====
|
# ===== Authelia official image =====
|
||||||
|
|
|
@ -38,7 +38,7 @@ WORKDIR /node/src/app
|
||||||
COPY web .
|
COPY web .
|
||||||
|
|
||||||
# Install the dependencies and build
|
# Install the dependencies and build
|
||||||
RUN yarn install --frozen-lockfile && yarn build
|
RUN yarn install --frozen-lockfile && INLINE_RUNTIME_CHUNK=false yarn build
|
||||||
|
|
||||||
# ===================================
|
# ===================================
|
||||||
# ===== Authelia official image =====
|
# ===== Authelia official image =====
|
||||||
|
|
|
@ -34,6 +34,7 @@ func buildFrontend() {
|
||||||
// Then build the frontend
|
// Then build the frontend
|
||||||
cmd = utils.CommandWithStdout("yarn", "build")
|
cmd = utils.CommandWithStdout("yarn", "build")
|
||||||
cmd.Dir = "web"
|
cmd.Dir = "web"
|
||||||
|
cmd.Env = append(os.Environ(), "INLINE_RUNTIME_CHUNK=false")
|
||||||
|
|
||||||
if err := cmd.Run(); err != nil {
|
if err := cmd.Run(); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
|
|
|
@ -0,0 +1,46 @@
|
||||||
|
package server
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"html/template"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/valyala/fasthttp"
|
||||||
|
|
||||||
|
"github.com/authelia/authelia/internal/logging"
|
||||||
|
"github.com/authelia/authelia/internal/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
var alphaNumericRunes = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789")
|
||||||
|
|
||||||
|
// ServeIndex serve the index.html file with nonce generated for supporting
|
||||||
|
// restrictive CSP while using material-ui.
|
||||||
|
func ServeIndex(publicDir string) fasthttp.RequestHandler {
|
||||||
|
f, err := os.Open(publicDir + "/index.html")
|
||||||
|
if err != nil {
|
||||||
|
logging.Logger().Fatalf("Unable to open index.html: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
b, err := ioutil.ReadAll(f)
|
||||||
|
if err != nil {
|
||||||
|
logging.Logger().Fatalf("Unable to read index.html: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
tmpl, err := template.New("index").Parse(string(b))
|
||||||
|
if err != nil {
|
||||||
|
logging.Logger().Fatalf("Unable to parse index.html template: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return func(ctx *fasthttp.RequestCtx) {
|
||||||
|
nonce := utils.RandomString(32, alphaNumericRunes)
|
||||||
|
ctx.SetContentType("text/html; charset=utf-8")
|
||||||
|
ctx.Response.Header.Add("Content-Security-Policy", fmt.Sprintf("default-src 'self'; style-src 'self' 'nonce-%s'", nonce))
|
||||||
|
err := tmpl.Execute(ctx.Response.BodyWriter(), struct{ CSPNonce string }{CSPNonce: nonce})
|
||||||
|
if err != nil {
|
||||||
|
ctx.Error("An error occurred", 503)
|
||||||
|
logging.Logger().Errorf("Unable to execute template: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -28,7 +28,8 @@ func StartServer(configuration schema.Configuration, providers middlewares.Provi
|
||||||
logging.Logger().Infof("Selected public_html directory is %s", publicDir)
|
logging.Logger().Infof("Selected public_html directory is %s", publicDir)
|
||||||
|
|
||||||
router := router.New()
|
router := router.New()
|
||||||
router.GET("/", fasthttp.FSHandler(publicDir, 0))
|
|
||||||
|
router.GET("/", ServeIndex(publicDir))
|
||||||
router.ServeFiles("/static/{filepath:*}", publicDir+"/static")
|
router.ServeFiles("/static/{filepath:*}", publicDir+"/static")
|
||||||
|
|
||||||
router.GET("/api/state", autheliaMiddleware(handlers.StateGet))
|
router.GET("/api/state", autheliaMiddleware(handlers.StateGet))
|
||||||
|
|
|
@ -23,6 +23,7 @@ services:
|
||||||
- 'traefik.http.services.authelia_backend.loadbalancer.server.scheme=https'
|
- 'traefik.http.services.authelia_backend.loadbalancer.server.scheme=https'
|
||||||
environment:
|
environment:
|
||||||
- ENVIRONMENT=dev
|
- ENVIRONMENT=dev
|
||||||
|
- PUBLIC_DIR=/tmp/authelia-web
|
||||||
networks:
|
networks:
|
||||||
authelianet:
|
authelianet:
|
||||||
ipv4_address: 192.168.240.50
|
ipv4_address: 192.168.240.50
|
||||||
|
|
|
@ -5,6 +5,11 @@ set -x
|
||||||
echo "Use hot reloaded version of Authelia backend"
|
echo "Use hot reloaded version of Authelia backend"
|
||||||
go get github.com/cespare/reflex
|
go get github.com/cespare/reflex
|
||||||
|
|
||||||
|
# Fake index.html because Authelia reads it as a template at startup to inject nonces.
|
||||||
|
# This prevents a crash of Authelia in dev mode.
|
||||||
|
mkdir -p /tmp/authelia-web
|
||||||
|
touch /tmp/authelia-web/index.html
|
||||||
|
|
||||||
# Sleep 10 seconds to wait the end of npm install updating web directory
|
# Sleep 10 seconds to wait the end of npm install updating web directory
|
||||||
# and making reflex reload multiple times.
|
# and making reflex reload multiple times.
|
||||||
sleep 10
|
sleep 10
|
||||||
|
|
|
@ -13,6 +13,10 @@ http {
|
||||||
set $backend_endpoint https://authelia-backend:9091;
|
set $backend_endpoint https://authelia-backend:9091;
|
||||||
|
|
||||||
location / {
|
location / {
|
||||||
|
# We don't want to apply CSP in dev mode because the frontend is served by CRA
|
||||||
|
# and thus cannot have the nonce injected.
|
||||||
|
proxy_hide_header Content-Security-Policy;
|
||||||
|
|
||||||
proxy_set_header Host $http_host;
|
proxy_set_header Host $http_host;
|
||||||
proxy_pass $backend_endpoint;
|
proxy_pass $backend_endpoint;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
|
<meta property="csp-nonce" content="{{.CSPNonce}}" />
|
||||||
|
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
|
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
|
Loading…
Reference in New Issue