diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 00000000..749445be --- /dev/null +++ b/.dockerignore @@ -0,0 +1,140 @@ +### Node template +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +lerna-debug.log* +.pnpm-debug.log* + +# Diagnostic reports (https://nodejs.org/api/report.html) +report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage +*.lcov + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ +jspm_packages/ + +# Snowpack dependency directory (https://snowpack.dev/) +web_modules/ + +# TypeScript cache +*.tsbuildinfo + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional stylelint cache +.stylelintcache + +# Microbundle cache +.rpt2_cache/ +.rts2_cache_cjs/ +.rts2_cache_es/ +.rts2_cache_umd/ + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variable files +.env.development.local +.env.test.local +.env.production.local +.env.local + +# parcel-bundler cache (https://parceljs.org/) +.cache +.parcel-cache + +# Next.js build output +.next +out + +# Nuxt.js build / generate output +.nuxt +dist + +# Gatsby files +.cache/ +# Comment in the public line in if your project uses Gatsby and not Next.js +# https://nextjs.org/blog/next-9-1#public-directory-support +# public + +# vuepress build output +.vuepress/dist + +# vuepress v2.x temp and cache directory +.temp + +# Docusaurus cache and generated files +.docusaurus + +# Serverless directories +.serverless/ + +# FuseBox cache +.fusebox/ + +# DynamoDB Local files +.dynamodb/ + +# TernJS port file +.tern-port + +# Stores VSCode versions used for testing VSCode extensions +.vscode-test + +# yarn v2 +.yarn/cache +.yarn/unplugged +.yarn/build-state.yml +.yarn/install-state.gz +.pnp.* + +# IntelliJ project files +.idea +*.iml +gen + +cypress/ +.husky/ +.github/ +doc/ +e2e/ diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 6f2ba271..1d2fff5d 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -3,19 +3,50 @@ on: push: branches-ignore: - gh-pages +env: + REGISTRY: ghcr.io + IMAGE_NAME: ${{ github.repository }} jobs: - build: + build-and-publish: name: Build runs-on: ubuntu-latest + permissions: + contents: read + packages: write steps: - name: Checkout code uses: actions/checkout@v4 - - name: Build an image from Dockerfile - run: docker build -t trivy-explorer . + - name: Log in to the Container registry + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Extract metadata (tags, labels) for Docker + id: meta + uses: docker/metadata-action@v5 + with: + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + tags: | + type=raw,value=latest,enable={{is_default_branch}} + type=semver,pattern={{version}},enable={{is_default_branch}} + type=semver,pattern={{major}}.{{minor}},enable={{is_default_branch}} + type=semver,pattern={{major}},enable={{is_default_branch}} + type=sha,format=long + + - name: Build and push Docker image + id: build-and-push + uses: docker/build-push-action@v5 + with: + context: . + push: 'true' + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} - name: Run Trivy vulnerability scanner uses: aquasecurity/trivy-action@0.29.0 with: - image-ref: "trivy-explorer" + image-ref: "${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.build-and-push.outputs.digest }}" output: trivy-report.json format: json exit-code: "0" diff --git a/Dockerfile b/Dockerfile index 3a0f4ead..6ab511b1 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,21 +1,21 @@ -FROM node:22.13.1-alpine3.21 -# install simple http server for serving static content -RUN npm install -g http-server +# Build stage +FROM node:22.13.1-alpine3.21 as build -# make the 'app' folder the current working directory WORKDIR /app - -# copy both 'package.json' and 'package-lock.json' (if available) COPY package*.json ./ +RUN npm ci +COPY . . +RUN npm run build -# install project dependencies -RUN npm install +# Production stage +FROM nginx:alpine-slim -# copy project files and folders to the current working directory (i.e. 'app' folder) -COPY . . +# Copy built files from build stage +COPY --from=build /app/dist /usr/share/nginx/html -# build app for production with minification -RUN npm run build +# Copy nginx configuration +COPY nginx.conf /etc/nginx/nginx.conf +RUN rm -r /etc/nginx/conf.d EXPOSE 8080 -CMD [ "http-server", "dist" ] +CMD ["nginx", "-g", "daemon off;"] diff --git a/nginx.conf b/nginx.conf new file mode 100644 index 00000000..2012c0d4 --- /dev/null +++ b/nginx.conf @@ -0,0 +1,72 @@ +user nginx; +worker_processes auto; + +error_log /var/log/nginx/error.log notice; +pid /var/run/nginx.pid; + +events { + worker_connections 1024; +} + +http { + include /etc/nginx/mime.types; + default_type application/octet-stream; + + # Security headers + add_header X-Frame-Options "SAMEORIGIN" always; + add_header X-XSS-Protection "1; mode=block" always; + add_header X-Content-Type-Options "nosniff" always; + add_header Referrer-Policy "no-referrer-when-downgrade" always; + add_header Content-Security-Policy "default-src 'self' 'unsafe-inline' 'unsafe-eval'; img-src 'self' data:;" always; + add_header Permissions-Policy "geolocation=(), midi=(), sync-xhr=(), microphone=(), camera=(), magnetometer=(), gyroscope=(), fullscreen=(self), payment=()" always; + + # Logging + log_format main '$remote_addr - $remote_user [$time_local] "$request" ' + '$status $body_bytes_sent "$http_referer" ' + '"$http_user_agent" "$http_x_forwarded_for"'; + + access_log /var/log/nginx/access.log main; + + sendfile on; + tcp_nopush on; + tcp_nodelay on; + + keepalive_timeout 65; + + # Gzip compression + gzip on; + gzip_vary on; + gzip_min_length 10240; + gzip_proxied expired no-cache no-store private auth; + gzip_types text/plain text/css text/xml text/javascript application/x-javascript application/xml application/javascript; + gzip_disable "MSIE [1-6]\."; + + server { + listen 8080; + server_name localhost; + root /usr/share/nginx/html; + location /trivy-vulnerability-explorer { + # Remove root directive since base path is already set in root above + # root /usr/share/nginx/html; + alias /usr/share/nginx/html; + index index.html; + try_files $uri $uri/ /trivy-vulnerability-explorer/index.html; + } + + # Security measures + location ~ /\. { + deny all; + } + + location = /favicon.ico { + log_not_found off; + access_log off; + } + + # Error pages + error_page 500 502 503 504 /50x.html; + location = /50x.html { + root /usr/share/nginx/html; + } + } +}