diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 0000000000000000000000000000000000000000..83c94cd16523acaf42f30d82888f124cb0063aa8 --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,29 @@ +#IMPORTANT: DOCKERFILE_PATH is not needed because we cd into each subdirectory build. This also means Dockerfile +# commands that reference local files (e.g. COPY) must use relative paths to the same directory where the Dockerfile +# is stored. + +variables: + DEBIAN_DISTRO: debian + NGINX_STABLE_VERSION: "1.24" + NGINX_MAINLINE_VERSION: "1.23.4" + NGINX_STABLE_BRANCH: stable + NGINX_MAINLINE_BRANCH: mainline + +.nginx: &nginx + trigger: + include: 'nginx.gitlab-ci.yml' + strategy: depend + +Debian Nginx Stable: + <<: *nginx + variables: + DISTRO: ${DEBIAN_DISTRO} + NGINX_VERSION: ${NGINX_STABLE_VERSION} + NGINX_BRANCH: ${NGINX_STABLE_BRANCH} + +Debian Nginx Mainline: + <<: *nginx + variables: + DISTRO: ${DEBIAN_DISTRO} + NGINX_VERSION: ${NGINX_MAINLINE_VERSION} + NGINX_BRANCH: ${NGINX_MAINLINE_BRANCH} diff --git a/images/nginx/debian/Dockerfile b/images/nginx/debian/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..ebbff39a9af913c77a8b68c129278518a2b62291 --- /dev/null +++ b/images/nginx/debian/Dockerfile @@ -0,0 +1,26 @@ +ARG NGINX_VERSION +FROM debian:bullseye-slim as debian + +FROM nginxinc/nginx-unprivileged:${NGINX_VERSION}-bullseye as nginx + +LABEL maintainer="TNET OSS " + +ARG NGINX_VERSION +ENV IMAGE_VERSION="$NGINX_VERSION" +LABEL version="$NGINX_VERSION" +ARG DISTRO +ENV DISTRO="${DISTRO}" + +# copy rm executable file +COPY --from=debian ["/bin/rm", "./rm"] + +USER root +# Modify default nginx. +RUN ["rm", "-r", "/etc/nginx/nginx.conf", "/etc/nginx/conf.d", "/usr/share/nginx/html"] +COPY ["/nginx/config/default/nginx.conf", "/etc/nginx/nginx.conf"] +COPY ["/nginx/config/default/default.conf", "/etc/nginx/config/default.conf"] +COPY ["/nginx/www/", "/usr/share/nginx/html/"] + +STOPSIGNAL SIGQUIT +USER nginx +CMD ["nginx", "-g", "daemon off;"] \ No newline at end of file diff --git a/nginx.gitlab-ci.yml b/nginx.gitlab-ci.yml new file mode 100644 index 0000000000000000000000000000000000000000..147dda4aeea8cca0502b9ed15349f3ae79811148 --- /dev/null +++ b/nginx.gitlab-ci.yml @@ -0,0 +1,102 @@ +stages: + - build + - test + - publish + - dast + - performance + +.docker_build: &docker_build + image: docker:latest + stage: build + services: + - docker:dind + tags: + - docker + before_script: + - docker info + - docker login -u "${CI_REGISTRY_USER}" -p "${CI_REGISTRY_PASSWORD}" ${CI_REGISTRY} + - export BASE_TAG="build-${CI_COMMIT_REF_NAME}-${CI_COMMIT_SHORT_SHA}-$(echo ${CI_PIPELINE_CREATED_AT} | cut -d "T" -f1)-${NGINX_VERSION}-${NGINX_BRANCH}-${DISTRO}" + script: + - docker build -t ${CI_REGISTRY_IMAGE}:${BASE_TAG} -f images/nginx/${DISTRO}/Dockerfile --build-arg NGINX_VERSION=${NGINX_VERSION} . + - docker push ${CI_REGISTRY_IMAGE}:${BASE_TAG} + +.docker_publish: &docker_publish + image: docker:latest + stage: publish + services: + - docker:dind + tags: + - docker + needs: + - Docker Build + dependencies: + - Docker Build + before_script: + - docker login -u "${CI_REGISTRY_USER}" -p "${CI_REGISTRY_PASSWORD}" ${CI_REGISTRY} + - export BASE_TAG="build-${CI_COMMIT_REF_NAME}-${CI_COMMIT_SHORT_SHA}-$(echo ${CI_PIPELINE_CREATED_AT} | cut -d "T" -f1)-${NGINX_VERSION}-${NGINX_BRANCH}-${DISTRO}" + - docker pull ${CI_REGISTRY_IMAGE}:${BASE_TAG} + +Docker Build: + <<: *docker_build + +Docker Publish: + <<: *docker_publish + only: + - main + script: + - docker tag ${CI_REGISTRY_IMAGE}:${BASE_TAG} ${CI_REGISTRY_IMAGE}:${NGINX_BRANCH}-${DISTRO} + - docker tag ${CI_REGISTRY_IMAGE}:${BASE_TAG} ${CI_REGISTRY_IMAGE}:${NGINX_VERSION}-${NGINX_BRANCH}-${DISTRO} + - docker push $CI_REGISTRY_IMAGE --all-tags + +Docker Publish Stable Tags: + <<: *docker_publish + rules: + - if: '$CI_COMMIT_BRANCH == "main" && $DISTRO == "debian" && $NGINX_BRANCH == "stable"' + script: + - docker tag ${CI_REGISTRY_IMAGE}:${BASE_TAG} ${CI_REGISTRY_IMAGE}:nginx + - docker tag ${CI_REGISTRY_IMAGE}:${BASE_TAG} ${CI_REGISTRY_IMAGE}:${NGINX_VERSION} + - docker tag ${CI_REGISTRY_IMAGE}:${BASE_TAG} ${CI_REGISTRY_IMAGE}:${NGINX_BRANCH} + - docker tag ${CI_REGISTRY_IMAGE}:${BASE_TAG} ${CI_REGISTRY_IMAGE}:${DISTRO} + - docker tag ${CI_REGISTRY_IMAGE}:${BASE_TAG} ${CI_REGISTRY_IMAGE}:${NGINX_VERSION}-${NGINX_BRANCH} + - docker tag ${CI_REGISTRY_IMAGE}:${BASE_TAG} ${CI_REGISTRY_IMAGE}:${NGINX_VERSION}-${DISTRO} + - docker push $CI_REGISTRY_IMAGE --all-tags + +Docker Publish Mainline Tags: + <<: *docker_publish + rules: + - if: '$CI_COMMIT_BRANCH == "main" && $DISTRO == "debian" && $NGINX_BRANCH == "mainline"' + script: + - docker tag ${CI_REGISTRY_IMAGE}:${BASE_TAG} ${CI_REGISTRY_IMAGE}:${NGINX_BRANCH} + - docker tag ${CI_REGISTRY_IMAGE}:${BASE_TAG} ${CI_REGISTRY_IMAGE}:${NGINX_VERSION}-${NGINX_BRANCH} + - docker push $CI_REGISTRY_IMAGE --all-tags + +include: + - template: Jobs/Code-Quality.gitlab-ci.yml # https://gitlab.com/gitlab-org/gitlab/blob/master/lib/gitlab/ci/templates/Jobs/Code-Quality.gitlab-ci.yml + - template: Jobs/Code-Intelligence.gitlab-ci.yml # https://gitlab.com/gitlab-org/gitlab/blob/master/lib/gitlab/ci/templates/Jobs/Code-Intelligence.gitlab-ci.yml + - template: Jobs/Browser-Performance-Testing.gitlab-ci.yml # https://gitlab.com/gitlab-org/gitlab/blob/master/lib/gitlab/ci/templates/Jobs/Browser-Performance-Testing.gitlab-ci.yml + - template: Security/DAST.gitlab-ci.yml # https://gitlab.com/gitlab-org/gitlab/blob/master/lib/gitlab/ci/templates/Security/DAST.gitlab-ci.yml + - template: Jobs/Dependency-Scanning.gitlab-ci.yml # https://gitlab.com/gitlab-org/gitlab/blob/master/lib/gitlab/ci/templates/Jobs/Dependency-Scanning.gitlab-ci.yml + - template: Jobs/License-Scanning.gitlab-ci.yml # https://gitlab.com/gitlab-org/gitlab/blob/master/lib/gitlab/ci/templates/Jobs/License-Scanning.gitlab-ci.yml + - template: Jobs/SAST.gitlab-ci.yml # https://gitlab.com/gitlab-org/gitlab/blob/master/lib/gitlab/ci/templates/Jobs/SAST.gitlab-ci.yml + - template: Jobs/Secret-Detection.gitlab-ci.yml # https://gitlab.com/gitlab-org/gitlab/blob/master/lib/gitlab/ci/templates/Jobs/Secret-Detection.gitlab-ci.yml + +browser_performance: + needs: + - Docker Build + variables: + URL: http://nginx:8080 + services: # use services to link your app container to the dast job + - name: ${CI_REGISTRY_IMAGE}:${BASE_TAG} + alias: nginx + +dast: + dependencies: [] + needs: + - Docker Build + variables: + DAST_WEBSITE: http://nginx:8080 + DAST_FULL_SCAN_ENABLED: "true" # do a full scan + DAST_BROWSER_SCAN: "true" # use the browser-based GitLab DAST crawler + services: # use services to link your app container to the dast job + - name: ${CI_REGISTRY_IMAGE}:${BASE_TAG} + alias: nginx \ No newline at end of file diff --git a/nginx/config/default/default.conf b/nginx/config/default/default.conf new file mode 100644 index 0000000000000000000000000000000000000000..b70e49739a03cfc087fc3107805a691b396f3f41 --- /dev/null +++ b/nginx/config/default/default.conf @@ -0,0 +1,47 @@ +server { + listen *:8080 default_server reuseport; # Cannot be a privileged port (like 80 or 443) as the master process is not root. + server_name _; + + location / { + return 444; + } +} + +server { + listen *:8080; + server_name localhost; + + root /usr/share/nginx/html; + + error_page 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 421 422 423 424 425 426 428 429 431 451 500 501 502 503 504 505 506 507 508 510 511 /error.html; + + location /error.html { + internal; + ssi on; + auth_basic off; + alias /usr/share/nginx/html/error.html; + } + + # Deny . files not part of a path containing .well-known + location ~ /\.(?!well-known) { + deny all; + } + + # Default to denying paths not explicitly whitelisted + location / { + deny all; + } + + location = / {} + location = /index.html {} + location = /robots.txt {} + location = /security.txt {} + location = /favicon.ico {} + # Nginx Metrics Endpoint + location = /.nginx/metrics { + access_log off; + stub_status on; + deny all; + allow 127.0.0.1; + } +} \ No newline at end of file diff --git a/nginx/config/default/nginx.conf b/nginx/config/default/nginx.conf new file mode 100644 index 0000000000000000000000000000000000000000..77fa2a38c6310e02db67599fe6fc5c729d62fc94 --- /dev/null +++ b/nginx/config/default/nginx.conf @@ -0,0 +1,178 @@ +#load_module modules/ngx_http_modsecurity_module.so; + +#user nginx; +#daemon off; +pid /tmp/nginx/nginx.pid; +error_log stderr notice; +include /etc/nginx/modules-enabled/*.conf; + +worker_processes auto; +worker_rlimit_nofile 65535; + +events { + # multi_accept: on; # use when: lots of connections, no reuseport. + worker_connections 65535; + use epoll; +} + +http { + # Nginx WAF with ModSecurity, using OWASP CoreRuleSet as default github.com/coreruleset/coreruleset + #modsecurity on; + #modsecurity_rules_file /etc/nginx/modsec/main.conf; + # Use our request ids instead of generating them within ModSecurity + #modsecurity_transaction_id "$http_x_request_id"; + + real_ip_header X-Forwarded-For; + real_ip_recursive on; + + charset utf-8; + sendfile on; + tcp_nopush on; + tcp_nodelay on; + server_tokens off; + server_name_in_redirect on; + + log_not_found off; + types_hash_max_size 2048; + types_hash_bucket_size 64; + variables_hash_max_size 2048; + variables_hash_bucket_size 64; + # server_names_hash_bucket_size 64; + + client_max_body_size 2M; + client_body_buffer_size 256k; + client_body_in_file_only off; + + client_body_temp_path "/tmp/nginx/client_body" 1 2; + proxy_temp_path "/tmp/nginx/proxy_temp" 1 2; + fastcgi_temp_path "/tmp/nginx/fastcgi_temp" 1 2; + scgi_temp_path "/tmp/nginx/scgi_temp" 1 2; + uwsgi_temp_path "/tmp/nginx/uwsgi_temp" 1 2; + + log_format main '$time_iso8601 - $remote_addr - "$request" $status $body_bytes_sent "$http_referer" "$http_user_agent"'; + access_log /dev/stdout main; + + # Disable autoindex and symlink traversal by default. + autoindex off; + disable_symlinks on; + + # MIME + include /etc/nginx/mime.types; + default_type application/octet-stream; + + # SSL Settings + ssl_protocols TLSv1 TLSv1.1 TLSv1.2 TLSv1.3; # Dropping SSLv3, ref: POODLE + ssl_prefer_server_ciphers on; + + # Gzip + gzip on; + # gzip_vary on; + # gzip_proxied any; + gzip_comp_level 1; + gzip_min_length 2048; + # gzip_buffers 16 8k; + # gzip_http_version 1.1; + gzip_types text/plain text/css text/xml application/json application/javascript application/xml application/rss+xml application/atom+xml image/svg+xml; + + # Security headers + # add_header Content-Security-Policy "default-src 'self'"; + # add_header Cross-Origin-Embedder-Policy "require-corp"; + # add_header Cross-Origin-Opener-Policy "same-origin"; + # add_header Cross-Origin-Resource-Policy "same-origin"; + # add_header X-Content-Type-Options "nosniff"; + # add_header X-DNS-Prefetch-Control "on"; + # add_header X-Download-Options "noopen"; + # add_header X-Frame-Options "SAMEORIGIN"; + # add_header X-XSS-Protection "0"; + + map $status $status_text { + 400 'Bad Request'; + 401 'Unauthorized'; + 402 'Payment Required'; + 403 'Forbidden'; + 404 'Not Found'; + 405 'Method Not Allowed'; + 406 'Not Acceptable'; + 407 'Proxy Authentication Required'; + 408 'Request Timeout'; + 409 'Conflict'; + 410 'Gone'; + 411 'Length Required'; + 412 'Precondition Failed'; + 413 'Payload Too Large'; + 414 'URI Too Long'; + 415 'Unsupported Media Type'; + 416 'Range Not Satisfiable'; + 417 'Expectation Failed'; + 418 'I\'m a teapot'; + 421 'Misdirected Request'; + 422 'Unprocessable Entity'; + 423 'Locked'; + 424 'Failed Dependency'; + 425 'Too Early'; + 426 'Upgrade Required'; + 428 'Precondition Required'; + 429 'Too Many Requests'; + 431 'Request Header Fields Too Large'; + 451 'Unavailable For Legal Reasons'; + 500 'Internal Server Error'; + 501 'Not Implemented'; + 502 'Bad Gateway'; + 503 'Service Unavailable'; + 504 'Gateway Timeout'; + 505 'HTTP Version Not Supported'; + 506 'Variant Also Negotiates'; + 507 'Insufficient Storage'; + 508 'Loop Detected'; + 510 'Not Extended'; + 511 'Network Authentication Required'; + default 'Unknown Error'; + } + + map $status $status_description { + 400 'The server cannot or will not process the request due to something that is perceived to be a client error'; + 401 'The client must authenticate itself to get the requested response'; + 402 'The request cannot be processed until the client makes a payment'; + 403 'The client does not have access rights to the content'; + 404 'The server can not find the requested resource'; + 405 'The request method is known by the server but is not supported by the target resource'; + 406 'The server cannot produce a response matching the list of acceptable values defined in the request\'s proactive content negotiation headers'; + 407 'The request has not been applied because it lacks valid authentication credentials for a proxy server that is between the browser and the server that can access the requested resource'; + 408 'The server would like to shut down this connection'; + 409 'Request conflict with the current state of the target resource'; + 410 'The requested content has been permanently deleted from server'; + 411 'The server rejected the request because the Content-Length header field is not defined and the server requires it'; + 412 'The client has indicated preconditions in its headers which the server does not meet'; + 413 'The request entity is larger than limits defined by server'; + 414 'The URI requested by the client is longer than the server is willing to interpret'; + 415 'The media format of the requested data is not supported by the server'; + 416 'The range specified by the Range header field in the request cannot be fulfilled'; + 417 'The expectation indicated by the Expect request header field cannot be met by the server'; + 418 'The server refuses the attempt to brew coffee with a teapot'; + 421 'The request was directed at a server that is not able to produce a response'; + 422 'The request was well-formed but was unable to be followed due to semantic errors'; + 423 'The resource that is being accessed is locked'; + 424 'The request failed due to failure of a previous request'; + 425 'the server is unwilling to risk processing a request that might be replayed'; + 426 'The server refuses to perform the request using the current protocol but might be willing to do so after the client upgrades to a different protocol'; + 428 'The origin server requires the request to be conditional'; + 429 'The user has sent too many requests in a given amount of time'; + 431 'The server is unwilling to process the request because its header fields are too large'; + 451 'The user agent requested a resource that cannot legally be provided'; + 500 'The server has encountered a situation it does not know how to handle'; + 501 'The request method is not supported by the server and cannot be handled'; + 502 'The server, while working as a gateway to get a response needed to handle the request, got an invalid response'; + 503 'The server is not ready to handle the request. Common causes are a server that is down for maintenance or that is overloaded'; + 504 'The server is acting as a gateway and cannot get a response in time'; + 505 'The HTTP version used in the request is not supported by the server'; + 506 'The server has an internal configuration error: the chosen variant resource is configured to engage in transparent content negotiation itself, and is therefore not a proper end point in the negotiation process'; + 507 'The method could not be performed on the resource because the server is unable to store the representation needed to successfully complete the request'; + 508 'The server detected an infinite loop while processing the request'; + 510 'Further extensions to the request are required for the server to fulfill it'; + 511 'The client needs to authenticate to gain network access'; + default 'It\'s an error'; + } + + # Load configs + include /etc/nginx/config/*.conf; +} \ No newline at end of file diff --git a/nginx/config/templates/example.conf b/nginx/config/templates/example.conf new file mode 100644 index 0000000000000000000000000000000000000000..f6f55438e0fc8d62240847f45976b28428d4ddcf --- /dev/null +++ b/nginx/config/templates/example.conf @@ -0,0 +1,39 @@ +server { + listen *:8080; # Cannot be a privileged port (like 80 or 443) as the master process is not root + server_name _; + + /root /usr/share/nginx/html; + + error_page 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 421 422 423 424 425 426 428 429 431 451 500 501 502 503 504 505 506 507 508 510 511 /error.html; + + location /error.html { + internal; + ssi on; + auth_basic off; + alias /usr/share/nginx/html/error.html; + } + + # Non-exhaustive header list + add_header Expect-CT "enforce, max-age: 604800" always; + add_header Via "$server_protocol nginx-mainline-example" always; + add_header X-Frame-Options "DENY" always; + add_header X-XSS-Protection "1; mode=block" always; + add_header X-Content-Type-Options "nosniff" always; + + # Deny . files not part of a path containing .well-known + location ~ /\.(?!well-known) { + deny all; + } + + # Default to denying paths not explicitly whitelisted + location / { + deny all; + } + + location = / {} + location = /index.html {} + location = /robots.txt {} + location = /security.txt {} + location = /favicon.ico {} + +} \ No newline at end of file diff --git a/nginx/www/error/error.html b/nginx/www/error/error.html new file mode 100644 index 0000000000000000000000000000000000000000..e1b37c4d7de50e154542cbaeb15b8f2e20bd00b0 --- /dev/null +++ b/nginx/www/error/error.html @@ -0,0 +1,103 @@ + + + + + + <!--# echo var="status_code" default="Error" --> - <!--# echo var="host" default="" --> + + + + +
+

: . That's an error.

+

. That's all we know.

+
+
+
+

Debug information (click to copy)

+

/

+
+ + \ No newline at end of file diff --git a/nginx/www/html/50x.html b/nginx/www/html/50x.html new file mode 100644 index 0000000000000000000000000000000000000000..435f1c138790da222fbd251e07ff42653a3ab36a --- /dev/null +++ b/nginx/www/html/50x.html @@ -0,0 +1,50 @@ + + + + Error + + + + + + +
+

An error occurred. That's all we know.

+

Sorry, the page you are looking for is currently unavailable. Please try again later. This is the default error page.

+
+ + \ No newline at end of file diff --git a/nginx/www/html/index.html b/nginx/www/html/index.html new file mode 100644 index 0000000000000000000000000000000000000000..022cec73133f0b2fd0b26a9fdef2d65883e0165f --- /dev/null +++ b/nginx/www/html/index.html @@ -0,0 +1,49 @@ + + + + Welcome to nginx! + + + + + + + +

Welcome to nginx!

+

If you see this page, the nginx web server deployment is successfully installed and working.

+

Please create a config for your site in the /etc/nginx/config folder to disable the welcome page.

+

For online documentation and installation help please refer to the Git repository: GitLab or these mirrors: git.tnet.moe, GitHub. +
+ + \ No newline at end of file diff --git a/nginx/www/html/robots.txt b/nginx/www/html/robots.txt new file mode 100644 index 0000000000000000000000000000000000000000..77470cb39f05f70a5b709b68304d0756bab75a0d --- /dev/null +++ b/nginx/www/html/robots.txt @@ -0,0 +1,2 @@ +User-agent: * +Disallow: / \ No newline at end of file diff --git a/nginx/www/html/security.txt b/nginx/www/html/security.txt new file mode 100644 index 0000000000000000000000000000000000000000..ffb31dd8ef11b96e2f83e7b01fa4cb2cae946dad --- /dev/null +++ b/nginx/www/html/security.txt @@ -0,0 +1,11 @@ +# Updated-At: 2023-05-11T03:11:00.000Z +Contact: mailto:security@tnet.moe +Contact: mailto:oss@tnet.moe +Expires: 2024-01-01T00:01:00.000Z +Encryption: https://security.tnet.moe/encryption/pgp-key.txt +Acknowledgments: https://security.tnet.moe/acknowledgments +Preferred-Languages: en +Canonical: https://security.tnet.moe/security.txt +Policy: https://security.tnet.moe/policy +Policy: https://security.tnet.moe/policy.txt +Hiring: https://security.tnet.moe/jobs \ No newline at end of file