From 6db5455762203d14ce2f2a01e0c1c81057dc7b50 Mon Sep 17 00:00:00 2001 From: Amir Zarrinkafsh Date: Thu, 19 Nov 2020 12:50:34 +1100 Subject: [PATCH] [CI] Collect coverage from frontend during integration tests (#1472) This change will allow us to collect frontend code coverage from our Selenium based integration tests. Given that the frontend is embedded into the Go binary and the integration tests run with a compiled binary in Docker this poses some issues with the instrumented code and the ability for it to run in this manner. To fix this we need to relax Authelia's CSP for the integration tests. This is achieved by setting the env variable `ENVIRONMENT` to `dev`. --- .buildkite/hooks/post-command | 7 +---- Dockerfile.coverage | 2 +- cmd/authelia-scripts/cmd_suites.go | 15 ++++++++++ internal/server/const.go | 3 ++ internal/server/index.go | 8 +++++- internal/server/server.go | 2 +- internal/suites/LDAP/configuration.yml | 1 + .../kube/authelia/configs/configuration.yml | 1 + .../example/kube/authelia/deployment.yml | 2 ++ internal/suites/webdriver.go | 28 ++++++++++++++++++- web/craco.config.js | 5 ++++ web/package.json | 5 +++- web/yarn.lock | 16 +++++++++++ 13 files changed, 84 insertions(+), 11 deletions(-) create mode 100644 internal/server/const.go create mode 100644 web/craco.config.js diff --git a/.buildkite/hooks/post-command b/.buildkite/hooks/post-command index eaf475755..a843d66ed 100755 --- a/.buildkite/hooks/post-command +++ b/.buildkite/hooks/post-command @@ -10,16 +10,11 @@ if [[ $BUILDKITE_PULL_REQUEST != "false" ]]; then fi if [[ ! $BUILDKITE_BRANCH =~ ^(v.*) ]] && [[ $BUILDKITE_COMMAND_EXIT_STATUS == 0 ]]; then - if [[ $BUILDKITE_LABEL == ":hammer_and_wrench: Unit Test" ]]; then + if [[ $BUILDKITE_LABEL == ":hammer_and_wrench: Unit Test" ]] || [[ $BUILDKITE_LABEL =~ ":selenium:" ]]; then echo "--- :codecov: Upload coverage reports" bash <(curl -s --connect-timeout 10 --retry 10 --retry-max-time 0 https://codecov.io/bash) -v -Z -c -f "coverage.txt" -F backend bash <(curl -s --connect-timeout 10 --retry 10 --retry-max-time 0 https://codecov.io/bash) -v -Z -c -F frontend fi - - if [[ $BUILDKITE_LABEL =~ ":selenium:" ]]; then - echo "--- :codecov: Upload coverage reports" - bash <(curl -s --connect-timeout 10 --retry 10 --retry-max-time 0 https://codecov.io/bash) -v -Z -c -f "coverage.txt" -F backend - fi fi if [[ $BUILDKITE_LABEL =~ ":selenium:" ]] || [[ $BUILDKITE_LABEL =~ ":docker: Build Image" ]]; then diff --git a/Dockerfile.coverage b/Dockerfile.coverage index f7af796f5..8b6307bf6 100644 --- a/Dockerfile.coverage +++ b/Dockerfile.coverage @@ -7,7 +7,7 @@ WORKDIR /node/src/app COPY web . # Install the dependencies and build -RUN yarn install --frozen-lockfile && INLINE_RUNTIME_CHUNK=false yarn build +RUN yarn install --frozen-lockfile && INLINE_RUNTIME_CHUNK=false yarn coverage # ======================================= # ===== Build image for the backend ===== diff --git a/cmd/authelia-scripts/cmd_suites.go b/cmd/authelia-scripts/cmd_suites.go index aa2842855..93d01f0e4 100644 --- a/cmd/authelia-scripts/cmd_suites.go +++ b/cmd/authelia-scripts/cmd_suites.go @@ -138,6 +138,21 @@ func runSuiteSetupTeardown(command string, suite string) error { s := suites.GlobalRegistry.Get(selectedSuite) + if command == "teardown" { + if _, err := os.Stat("../../web/.nyc_output"); err == nil { + log.Infof("Generating frontend coverage reports for suite %s...", suite) + + cmd := utils.Command("yarn", "report") + cmd.Dir = "web" + cmd.Env = os.Environ() + + err := cmd.Run() + if err != nil { + log.Fatal(err) + } + } + } + cmd := utils.CommandWithStdout("go", "run", "cmd/authelia-suites/main.go", command, selectedSuite) cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr diff --git a/internal/server/const.go b/internal/server/const.go new file mode 100644 index 000000000..d092ebf7d --- /dev/null +++ b/internal/server/const.go @@ -0,0 +1,3 @@ +package server + +const dev = "dev" diff --git a/internal/server/index.go b/internal/server/index.go index fbf09e9cf..ad4f36ac0 100644 --- a/internal/server/index.go +++ b/internal/server/index.go @@ -3,6 +3,7 @@ package server import ( "fmt" "io/ioutil" + "os" "text/template" "github.com/valyala/fasthttp" @@ -36,7 +37,12 @@ func ServeIndex(publicDir, base, rememberMe, resetPassword string) fasthttp.Requ nonce := utils.RandomString(32, alphaNumericRunes) ctx.SetContentType("text/html; charset=utf-8") - ctx.Response.Header.Add("Content-Security-Policy", fmt.Sprintf("default-src 'self'; object-src 'none'; style-src 'self' 'nonce-%s'", nonce)) + + if os.Getenv("ENVIRONMENT") == dev { + ctx.Response.Header.Add("Content-Security-Policy", fmt.Sprintf("default-src 'self' 'unsafe-eval'; object-src 'none'; style-src 'self' 'nonce-%s'", nonce)) + } else { + ctx.Response.Header.Add("Content-Security-Policy", fmt.Sprintf("default-src 'self'; object-src 'none'; style-src 'self' 'nonce-%s'", nonce)) + } err := tmpl.Execute(ctx.Response.BodyWriter(), struct{ Base, CSPNonce, RememberMe, ResetPassword string }{Base: base, CSPNonce: nonce, RememberMe: rememberMe, ResetPassword: resetPassword}) if err != nil { diff --git a/internal/server/server.go b/internal/server/server.go index 7225456c0..f2921b1f9 100644 --- a/internal/server/server.go +++ b/internal/server/server.go @@ -99,7 +99,7 @@ func StartServer(configuration schema.Configuration, providers middlewares.Provi // Configure DUO api endpoint only if configuration exists. if configuration.DuoAPI != nil { var duoAPI duo.API - if os.Getenv("ENVIRONMENT") == "dev" { + if os.Getenv("ENVIRONMENT") == dev { duoAPI = duo.NewDuoAPI(duoapi.NewDuoApi( configuration.DuoAPI.IntegrationKey, configuration.DuoAPI.SecretKey, diff --git a/internal/suites/LDAP/configuration.yml b/internal/suites/LDAP/configuration.yml index 80645ee4d..5e20f7e30 100644 --- a/internal/suites/LDAP/configuration.yml +++ b/internal/suites/LDAP/configuration.yml @@ -24,6 +24,7 @@ authentication_backend: groups_filter: (&(member={dn})(objectclass=groupOfNames)) group_name_attribute: cn mail_attribute: mail + display_name_attribute: displayName user: cn=admin,dc=example,dc=com password: password diff --git a/internal/suites/example/kube/authelia/configs/configuration.yml b/internal/suites/example/kube/authelia/configs/configuration.yml index ce9e1fa8a..6be69cd9b 100644 --- a/internal/suites/example/kube/authelia/configs/configuration.yml +++ b/internal/suites/example/kube/authelia/configs/configuration.yml @@ -22,6 +22,7 @@ authentication_backend: groups_filter: (&(member={dn})(objectclass=groupOfNames)) group_name_attribute: cn mail_attribute: mail + display_name_attribute: displayName user: cn=admin,dc=example,dc=com access_control: diff --git a/internal/suites/example/kube/authelia/deployment.yml b/internal/suites/example/kube/authelia/deployment.yml index 2945205f0..5530fc3d9 100644 --- a/internal/suites/example/kube/authelia/deployment.yml +++ b/internal/suites/example/kube/authelia/deployment.yml @@ -40,6 +40,8 @@ spec: value: /app/secrets/session - name: AUTHELIA_STORAGE_MYSQL_PASSWORD_FILE value: /app/secrets/sql_password + - name: ENVIRONMENT + value: dev volumes: - name: config-volume configMap: diff --git a/internal/suites/webdriver.go b/internal/suites/webdriver.go index 40896eebf..25fc8b02b 100644 --- a/internal/suites/webdriver.go +++ b/internal/suites/webdriver.go @@ -2,12 +2,15 @@ package suites import ( "context" + "encoding/json" "errors" "fmt" + "io/ioutil" "os" "strconv" "strings" "testing" + "time" "github.com/stretchr/testify/require" "github.com/tebeka/selenium" @@ -82,8 +85,31 @@ func StartWebDriver() (*WebDriverSession, error) { // Stop stop the selenium session. func (wds *WebDriverSession) Stop() error { - err := wds.WebDriver.Quit() + var coverage map[string]interface{} + coverageDir := "../../web/.nyc_output" + time := time.Now() + + resp, err := wds.WebDriver.ExecuteScriptRaw("return JSON.stringify(window.__coverage__)", nil) + if err != nil { + return err + } + + err = json.Unmarshal(resp, &coverage) + if err != nil { + return err + } + + coverageData := fmt.Sprintf("%s", coverage["value"]) + + _ = os.MkdirAll(coverageDir, 0775) + + err = ioutil.WriteFile(fmt.Sprintf("%s/coverage-%d.json", coverageDir, time.Unix()), []byte(coverageData), 0664) //nolint:gosec + if err != nil { + return err + } + + err = wds.WebDriver.Quit() if err != nil { return err } diff --git a/web/craco.config.js b/web/craco.config.js new file mode 100644 index 000000000..67c5744e0 --- /dev/null +++ b/web/craco.config.js @@ -0,0 +1,5 @@ +module.exports = { + babel: { + plugins: [ "babel-plugin-istanbul" ] + } +}; \ No newline at end of file diff --git a/web/package.json b/web/package.json index c13467b93..1eb0e0dad 100644 --- a/web/package.json +++ b/web/package.json @@ -3,6 +3,7 @@ "version": "0.1.0", "private": true, "dependencies": { + "@craco/craco": "^5.8.0", "@fortawesome/fontawesome-svg-core": "^1.2.32", "@fortawesome/free-regular-svg-icons": "^5.15.1", "@fortawesome/free-solid-svg-icons": "^5.15.1", @@ -40,9 +41,11 @@ "u2f-api": "^1.1.1" }, "scripts": { - "start": "react-scripts start", + "start": "craco start", "build": "react-scripts build", + "coverage": "craco build", "test": "react-scripts test --coverage --no-cache", + "report": "nyc report -r clover -r json -r lcov -r text", "eject": "react-scripts eject" }, "eslintConfig": { diff --git a/web/yarn.lock b/web/yarn.lock index fe50ce222..6e1cb5ca4 100644 --- a/web/yarn.lock +++ b/web/yarn.lock @@ -1159,6 +1159,15 @@ exec-sh "^0.3.2" minimist "^1.2.0" +"@craco/craco@^5.8.0": + version "5.8.0" + resolved "https://registry.yarnpkg.com/@craco/craco/-/craco-5.8.0.tgz#2a0f551290a5eab353b615de4d7093dd83785777" + integrity sha512-4rhusETLD7rJ195GxOK9VmVdv/VD4jawFxc9hcQ9TrZ3/9ny+qwc0uW+08qu9GYwEF9Eb9meSeSvpWjaqdDr1Q== + dependencies: + cross-spawn "^7.0.0" + lodash "^4.17.15" + webpack-merge "^4.2.2" + "@csstools/convert-colors@^1.4.0": version "1.4.0" resolved "https://registry.yarnpkg.com/@csstools/convert-colors/-/convert-colors-1.4.0.tgz#ad495dc41b12e75d588c6db8b9834f08fa131eb7" @@ -11979,6 +11988,13 @@ webpack-manifest-plugin@2.2.0: object.entries "^1.1.0" tapable "^1.0.0" +webpack-merge@^4.2.2: + version "4.2.2" + resolved "https://registry.yarnpkg.com/webpack-merge/-/webpack-merge-4.2.2.tgz#a27c52ea783d1398afd2087f547d7b9d2f43634d" + integrity sha512-TUE1UGoTX2Cd42j3krGYqObZbOD+xF7u28WB7tfUordytSjbWTIjK/8V0amkBfTYN4/pB/GIDlJZZ657BGG19g== + dependencies: + lodash "^4.17.15" + webpack-sources@^1.1.0, webpack-sources@^1.3.0, webpack-sources@^1.4.0, webpack-sources@^1.4.1, webpack-sources@^1.4.3: version "1.4.3" resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-1.4.3.tgz#eedd8ec0b928fbf1cbfe994e22d2d890f330a933"