diff --git a/.circleci/config.yml b/.circleci/config.yml index 4186d4ff2..28fc9ba8f 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -65,6 +65,10 @@ aliases: - &runner_config # Location of checked-out files within "runner" container. working_directory: &working_directory ~/project + parameters: + shipshape_run_audit: + default: false + type: boolean environment: DREVOPS_DEPLOY_SSH_FINGERPRINT: *deploy_ssh_fingerprint DEPLOY_SSH_FINGERPRINT1: *deploy_ssh_fingerprint1 @@ -269,6 +273,13 @@ job-build: &job-build docker compose exec -T cli php -d memory_limit=-1 vendor/bin/behat --colors --strict --rerun --profile="${DREVOPS_CI_BEHAT_PROFILE:-default}" || \ [ "${DREVOPS_CI_BEHAT_IGNORE_FAILURE:-0}" -eq 1 ] no_output_timeout: 30m + # Optionally run Shipshape audit. + - when: + condition: << parameters.shipshape_run_audit >> + steps: + - run: + name: Audit code with shipshape + command: docker compose exec -T cli sh -c "/usr/local/bin/shipshape -e -o junit > /app/.logs/test_results/shipshape-results.xml" || [ "${DREVOPS_CI_SHIPSHAPE_IGNORE_FAILURE:-0}" -eq 1 ] - run: name: Process test logs and artifacts command: | @@ -360,6 +371,10 @@ jobs: # GovCMS profile, no sub-theme. build-govcms: <<: *runner_config + parameters: + shipshape_run_audit: + default: true + type: boolean environment: DRUPAL_PROFILE: govcms CIVICTHEME_SUBTHEME_ACTIVATION_SKIP: 1 @@ -370,6 +385,10 @@ jobs: # GovCMS profile, sub-theme. Longest test run. build-govcms-subtheme: <<: *runner_config + parameters: + shipshape_run_audit: + default: true + type: boolean environment: DRUPAL_PROFILE: govcms DREVOPS_CI_DRUPAL_THEME_CONFIG_LINT_IGNORE_FAILURE: 1 diff --git a/.docker/cli.dockerfile b/.docker/cli.dockerfile index 36d18dbbe..afd54391d 100644 --- a/.docker/cli.dockerfile +++ b/.docker/cli.dockerfile @@ -63,6 +63,9 @@ RUN mkdir -p web/themes/contrib/civictheme \ && mkdir -p web/modules/custom/civictheme_dev \ && mkdir -p web/modules/custom/cs_generated_content +# Add shipshape binary so that we can execute audits. +COPY --from=ghcr.io/salsadigitalauorg/shipshape:latest /usr/local/bin/shipshape /usr/local/bin/shipshape + # Copy files required for PHP dependencies resolution. # Note that composer.lock is not explicitly copied, allowing to run the stack # without existing lock file (this is not advisable, but allows to build diff --git a/.docker/cli.onlytheme.dockerfile b/.docker/cli.onlytheme.dockerfile index 8bb59f866..0c1b825cc 100644 --- a/.docker/cli.onlytheme.dockerfile +++ b/.docker/cli.onlytheme.dockerfile @@ -62,6 +62,9 @@ RUN mkdir -p web/themes/contrib/civictheme \ && mkdir -p web/modules/custom/civictheme_dev \ && mkdir -p web/modules/custom/cs_generated_content +# Add shipshape binary so that we can execute audits. +COPY --from=ghcr.io/salsadigitalauorg/shipshape:latest /usr/local/bin/shipshape /usr/local/bin/shipshape + # Copy files required for PHP dependencies resolution. # Note that composer.lock is not explicitly copied, allowing to run the stack # without existing lock file (this is not advisable, but allows to build diff --git a/.docker/cli.sibling.dockerfile b/.docker/cli.sibling.dockerfile index 17285fc74..18fac874b 100644 --- a/.docker/cli.sibling.dockerfile +++ b/.docker/cli.sibling.dockerfile @@ -63,6 +63,9 @@ RUN mkdir -p web/themes/contrib/civictheme \ && mkdir -p web/modules/custom/civictheme_dev \ && mkdir -p web/modules/custom/cs_generated_content +# Add shipshape binary so that we can execute audits. +COPY --from=ghcr.io/salsadigitalauorg/shipshape:latest /usr/local/bin/shipshape /usr/local/bin/shipshape + # Copy files required for PHP dependencies resolution. # Note that composer.lock is not explicitly copied, allowing to run the stack # without existing lock file (this is not advisable, but allows to build diff --git a/phpstan-govcms.neon b/phpstan-govcms.neon new file mode 100644 index 000000000..9d70a8db6 --- /dev/null +++ b/phpstan-govcms.neon @@ -0,0 +1,99 @@ +parameters: + # Disable all phpstan rules. + customRulesetUsed: true + fileExtensions: + - php + - theme + - inc + reportUnmatchedIgnoredErrors: false + ignoreErrors: + - message: '#^Calling debug_backtrace\(\) is forbidden, please change the code$#' + count: 1 + path: /app/web/themes/custom/bootstrap/src/Bootstrap.php + - message: '#^Calling print_r\(\) is forbidden, please change the code$#' + count: 1 + path: /app/web/themes/custom/bootstrap/src/Bootstrap.php + disallowedFunctionCalls: + - function: 'curl_exec()' + message: 'please change the code' + - function: 'curl_init()' + message: 'please change the code' + - function: 'curl_multi_exec()' + message: 'please change the code' + - function: 'db_*()' + message: 'please change the code' + - function: 'dd()' + message: 'please change the code' + - function: 'debug_backtrace()' + message: 'please change the code' + - function: 'dump()' + message: 'please change the code' + - function: 'escapeshellcmd()' + message: 'please change the code' + - function: 'eval()' + message: 'please change the code' + - function: 'exec()' + message: 'please change the code' + - function: 'ftp_*()' + message: 'please change the code' + - function: 'mysql_*()' + message: 'please change the code' + - function: 'mysqli_*()' + message: 'please change the code' + - function: 'passthru()' + message: 'please change the code' + - function: 'pcntl_*()' + message: 'please change the code' + - function: 'phpinfo()' + message: 'please change the code' + - function: 'popen()' + message: 'please change the code' + - function: 'posix_getpwuid()' + message: 'please change the code' + - function: 'posix_kill()' + message: 'please change the code' + - function: 'posix_mkfifo()' + message: 'please change the code' + - function: 'posix_setpgid()' + message: 'please change the code' + - function: 'posix_setsid()' + message: 'please change the code' + - function: 'posix_setuid()' + message: 'please change the code' + - function: 'posix_uname()' + message: 'please change the code' + - function: 'print_r()' + message: 'please change the code' + - function: 'proc_open()' + message: 'please change the code' + - function: 'proc_get_status()' + message: 'please change the code' + - function: 'proc_terminate()' + message: 'please change the code' + - function: 'proc_close()' + message: 'please change the code' + - function: 'proc_nice()' + message: 'please change the code' + - function: 'shell_exec()' + message: 'please change the code' + - function: 'sleep()' + message: 'please change the code' + - function: 'system()' + message: 'please change the code' + - function: 'var_dump()' + message: 'please change the code' + disallowedMethodCalls: + - method: 'mysqli::*()' + message: 'please change the code' + - method: 'SQLite3::*()' + message: 'please change the code' + - method: 'SQLite3Stmt::*()' + message: 'please change the code' + - method: 'SQLite3Result::*()' + message: 'please change the code' + disallowedStaticCalls: + - method: 'Drupal::httpClient()' + message: 'please change the code' + disallowedNamespaces: + - class: 'GuzzleHttp\Client' + message: 'please change the code' diff --git a/scripts/drevops/build.sh b/scripts/drevops/build.sh index c9bcbfba2..823a19e5d 100755 --- a/scripts/drevops/build.sh +++ b/scripts/drevops/build.sh @@ -129,6 +129,8 @@ docker compose cp -L behat.yml cli:/app/ 2>"${composer_verbose_output}" docker compose cp -L phpcs.xml cli:/app/ 2>"${composer_verbose_output}" docker compose cp -L phpmd.xml cli:/app/ 2>"${composer_verbose_output}" docker compose cp -L phpstan.neon cli:/app/ 2>"${composer_verbose_output}" +docker compose cp -L phpstan-govcms.neon cli:/app/ 2>"${composer_verbose_output}" +docker compose cp -L shipshape.yml cli:/app/ 2>"${composer_verbose_output}" docker compose cp -L phpunit.xml cli:/app/ 2>"${composer_verbose_output}" docker compose cp -L rector.php cli:/app/ 2>"${composer_verbose_output}" docker compose cp -L tests cli:/app/ 2>"${composer_verbose_output}" diff --git a/shipshape.yml b/shipshape.yml new file mode 100644 index 000000000..e6692969c --- /dev/null +++ b/shipshape.yml @@ -0,0 +1,278 @@ +checks: + file: + - name: '[FILE] Illegal files' + severity: high + path: web/themes/contrib/civictheme + disallowed-pattern: '^(adminer|phpmyadmin|bigdump)?\.php$' +# - name: '[FILE] Executable files' +# severity: normal +# path: ./ +# disallowed-pattern: '.*\.(bin|deb|dmg|elf|exe|msi|sh)+$' +# exclude-pattern: '^(vendor|web/core|web/modules/contrib)+.*' +# - name: '[FILE] Sensitive public files' +# path: web/sites/default/files +# disallowed-pattern: '.*\.(sql|php|sh|py|bz2|gz|tar|tgz|zip)+$' +# exclude-pattern: '.*\.(css|js)\.gz?$' +# skip-dir: +# - private + - name: '[FILE] Executable files' + severity: normal + path: web/themes/contrib/civictheme + disallowed-pattern: '.*\.(bin|deb|dmg|elf|exe|msi|sh)+$' + exclude-pattern: '^(vendor|web/core|web/modules/contrib)+.*' + skip-dir: + - node_modules + - .devtools + - name: '[FILE] Sensitive public files' + path: web/themes/contrib/civictheme + disallowed-pattern: '.*\.(sql|gz|tar|tgz|zip)+$' + exclude-pattern: '.*\.(css|js)\.gz?$' + skip-dir: + - node_modules/.cache/storybook + - tests + yaml: + - name: '[FILE] Validate install profile' + file: core.extension.yml + ignore-missing: true + path: config/default + values: + - key: profile + value: govcms + - name: '[FILE] Disallowed permissions' + severity: high + pattern: user.role.*.yml + ignore-missing: true + path: config/default + values: + - key: is_admin + value: false + truthy: true + optional: true + - key: permissions + is-list: true + optional: true + disallowed: + - administer config permissions + - administer modules + - administer permissions + - administer seckit + - administer site configuration + - administer software updates + - import configuration + - synchronize configuration + - use PHP for google analytics tracking visibility + - name: '[FILE] Disallowed permissions for anonymous role' + file: user.role.anonymous.yml + ignore-missing: true + path: config/default + values: + - key: permissions + is-list: true + optional: true + disallowed: + - 'access administration pages' + - 'access content overview' + - 'access site reports' + - 'access user profiles' + - 'administer account settings' + - 'administer blocks' + - 'administer comment types' + - 'administer comments' + - 'administer contact forms' + - 'administer content types' + - 'administer filters' + - 'administer image styles' + - 'administer menu' + - 'administer nodes' + - 'administer search' + - 'administer shortcuts' + - 'administer taxonomy' + - 'administer themes' + - 'administer url aliases' + - 'administer users' + - 'administer views' + - 'bypass node access' + - 'create url aliases' + - 'delete all revisions' + - 'revert all revisions' + - 'view all revisions' + - 'view the administration theme' + - 'view user email addresses' +# - name: '[FILE] Validate TFA config' +# severity: high +# file: tfa.settings.yml +# ignore-missing: true +# path: config/default +# values: +# - key: enabled +# value: true +# truthy: true +# - key: required_roles.authenticated +# value: authenticated + - name: '[FILE] Ensure only admins can register accounts' + file: user.settings.yml + ignore-missing: true + path: config/default + values: + - key: register + value: admin_only + - name: '[FILE] Ensure CSS & JS aggregations are enabled' + file: system.performance.yml + ignore-missing: true + path: config/default + values: + - key: css.preprocess + value: true + truthy: true + - key: js.preprocess + value: true + truthy: true + - name: '[FILE] Ensure no error log displayed' + file: system.logging.yml + ignore-missing: true + path: config/default + values: + - key: error_level + value: hide + - name: '[FILE] Detect module files in theme folder' + pattern: '.*.info.yml' + ignore-missing: true + path: 'themes' + values: + - key: type + value: theme + drush-yaml: + - name: '[DATABASE] Validate active install profile' + command: 'config:get --include-overridden core.extension' + config-name: core.extension + values: + - key: profile + value: govcms +# - name: '[DATABASE] Validate active TFA' +# severity: high +# command: 'config:get --include-overridden tfa.settings' +# config-name: tfa.settings +# values: +# - key: enabled +# value: true +# truthy: true +# - key: required_roles.authenticated +# value: authenticated + - name: '[DATABASE] Ensure only admins can register accounts' + command: 'config:get --include-overridden user.settings' + config-name: user.settings + values: + - key: register + value: admin_only + - name: '[DATABASE] Ensure CSS & JS aggregations are enabled' + command: 'config:get --include-overridden system.performance' + config-name: system.performance + values: + - key: css.preprocess + value: true + truthy: true + - key: js.preprocess + value: true + truthy: true + - name: '[DATABASE] Ensure no error log displayed' + command: 'config:get --include-overridden system.logging' + config-name: user.settings + values: + - key: error_level + value: hide +# drupal-file-module: +# - name: '[FILE] Verify enabled modules' +# severity: high +# path: config/default +# required: +# - govcms_security +# - httpav +# - lagoon_logs +# - tfa +# disallowed: +# - clamav +# - dblog +# - devel +# - module_permissions_ui +# - statistics +# - update +# - redirect_404 + - name: '[FILE] Deprecated modules' + path: config/default + required: [] + disallowed: + - redirect_404 +# drupal-db-module: +# - name: '[DATABASE] Active modules audit' +# severity: high +# required: +# - govcms_security +# - httpav +# - lagoon_logs +# - tfa +# disallowed: +# - clamav +# - dblog +# - devel +# - module_permissions_ui +# - statistics +# - update +# - redirect_404 + - name: '[DATABASE] Deprecated modules' + required: [] + disallowed: + - redirect_404 + drupal-db-permissions: + - name: '[DATABASE] Disallowed permissions on active site' + severity: high + disallowed: + - administer config permissions + - administer modules + - administer permissions + - administer seckit + - administer site configuration + - administer software updates + - import configuration + - synchronize configuration + - use PHP for google analytics tracking visibility +# drupal-role-permissions: +# - name: '[DATABASE] Authenticated role check' +# severity: high +# rid: 'authenticated' +# required-permissions: +# - 'setup own tfa' + drupal-admin-user: + - name: '[DATABASE] Active user roles admin check' + severity: high + drupal-user-forbidden: + - name: '[DATABASE] Active User 1 check' + yamllint: + - name: '[FILE] Yaml lint platform files' + severity: high + files: + - .lagoon.yml + - docker-compose.yml + ignore-missing: true + - name: '[FILE] Yaml lint theme files' + severity: high + path: web/themes/contrib/civictheme + pattern: ".*.yml" + exclude-pattern: node_modules + ignore-missing: true +# - name: '[FILE] Yaml lint theme files (no web prefix)' +# severity: high +# path: themes +# pattern: ".*.yml" +# exclude-pattern: node_modules +# ignore-missing: true + phpstan: + - name: '[FILE] Banned PHP functions and methods list' + configuration: ./phpstan-govcms.neon + paths: + - web/themes/contrib/civictheme +# - name: '[FILE] Banned PHP methods and classes' +# severity: low +# configuration: vendor/govcms/scaffold-tooling/phpstan-extra.neon +# paths: +# - web/themes/custom +# - themes