diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..5f0889c --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,11 @@ +# To get started with Dependabot version updates, you'll need to specify which +# package ecosystems to update and where the package manifests are located. +# Please see the documentation for all configuration options: +# https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file + +version: 2 +updates: + - package-ecosystem: "npm" # See documentation for possible values + directory: "/" # Location of package manifests + schedule: + interval: "weekly" diff --git a/.github/workflows/dev-build.yml b/.github/workflows/dev-build.yml index d724294..5510c28 100644 --- a/.github/workflows/dev-build.yml +++ b/.github/workflows/dev-build.yml @@ -16,8 +16,8 @@ jobs: - name: Create SSL certificate and key run: | mkdir -p ./ssl/private - echo "${{ secrets.TEST_SSL_KEY }}" > ./ssl/private/secret.key - echo "${{ secrets.TEST_SSL_CERT }}" > ./ssl/private/cert.cer + openssl genrsa -out ./ssl/private/secret.key 2048 + openssl req -new -x509 -key ./ssl/private/secret.key -out ./ssl/private/cert.cer -days 30 -subj "/CN=contoso.com" - name: Create .env file run: | @@ -51,6 +51,8 @@ jobs: EXPECTED_RESPONSE='{"method":"POST","url":"/","clientIP":"::1","headers":{"host":"localhost:8443","user-agent":"curl/8.5.0","accept":"*/*","content-type":"application/json","content-length":"27"}}' if [ "$RESPONSE" != "$EXPECTED_RESPONSE" ]; then echo "Test failed: Response does not match POST" + echo "Expected: $EXPECTED_RESPONSE" + echo "Got: $RESPONSE" exit 1 fi @@ -82,6 +84,8 @@ jobs: EXPECTED_RESPONSE='{"method":"POST","url":"/","clientIP":"::1","headers":{"host":"localhost:8080","user-agent":"curl/8.5.0","accept":"*/*","content-type":"application/json","content-length":"27"}}' if [ "$RESPONSE" != "$EXPECTED_RESPONSE" ]; then echo "Test failed: Response does not match POST body (No SSL)" + echo "Expected: $EXPECTED_RESPONSE" + echo "Got: $RESPONSE" exit 1 fi @@ -94,13 +98,17 @@ jobs: id: org-name run: echo "ORG_LC=$(echo ${{ github.repository_owner }} | tr '[:upper:]' '[:lower:]')" >> $GITHUB_ENV + + - name: Set Timestamp for use in image tag + run: echo "IMAGE_TAG=$(date +'%Y%m%d%H%M%S')" >> $GITHUB_ENV + - name: Build Docker image run: | - docker build -t ghcr.io/${{ env.ORG_LC }}/headers-app:latest-dev . + docker build -t ghcr.io/${{ env.ORG_LC }}/headers-app:${{ env.IMAGE_TAG }}-latest-dev . - name: Log in to GitHub Container Registry run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin - name: Push Docker image run: | - docker push ghcr.io/${{ env.ORG_LC }}/headers-app:latest-dev + docker push ghcr.io/${{ env.ORG_LC }}/headers-app:${{ env.IMAGE_TAG }}-latest-dev diff --git a/.github/workflows/release-build.yml b/.github/workflows/release-build.yml index 9b2b15c..f085faf 100644 --- a/.github/workflows/release-build.yml +++ b/.github/workflows/release-build.yml @@ -17,8 +17,8 @@ jobs: - name: Create SSL certificate and key run: | mkdir -p ./ssl/private - echo "${{ secrets.TEST_SSL_KEY }}" > ./ssl/private/secret.key - echo "${{ secrets.TEST_SSL_CERT }}" > ./ssl/private/cert.cer + openssl genrsa -out ./ssl/private/secret.key 2048 + openssl req -new -x509 -key ./ssl/private/secret.key -out ./ssl/private/cert.cer -days 30 -subj "/CN=contoso.com" - name: Create .env file run: | @@ -49,9 +49,11 @@ jobs: -H "Content-Type: application/json" \ --data "$POST_BODY") echo "Response: $RESPONSE" - EXPECTED_RESPONSE='{"method":"POST","url":"/","clientIP":"::1","headers":{"host":"localhost","user-agent":"curl/8.5.0","accept":"*/*","content-type":"application/json","content-length":"27"}}' + EXPECTED_RESPONSE='{"method":"POST","url":"/","clientIP":"::1","headers":{"host":"localhost:8443","user-agent":"curl/8.5.0","accept":"*/*","content-type":"application/json","content-length":"27"}}' if [ "$RESPONSE" != "$EXPECTED_RESPONSE" ]; then echo "Test failed: Response does not match POST body" + echo "Expected: $EXPECTED_RESPONSE" + echo "Got: $RESPONSE" exit 1 fi @@ -83,6 +85,8 @@ jobs: EXPECTED_RESPONSE='{"method":"POST","url":"/","clientIP":"::1","headers":{"host":"localhost:8080","user-agent":"curl/8.5.0","accept":"*/*","content-type":"application/json","content-length":"27"}}' if [ "$RESPONSE" != "$EXPECTED_RESPONSE" ]; then echo "Test failed: Response does not match POST body (No SSL)" + echo "Expected: $EXPECTED_RESPONSE" + echo "Got: $RESPONSE" exit 1 fi @@ -91,24 +95,29 @@ jobs: kill $(cat app.pid) || true # Kill the application using the saved PID rm -f app.pid # Clean up the PID file - - name: Build the application - run: npm run build - - - name: List build artifacts - run: ls -la ./build - - name: Set lower case org name id: org-name run: echo "ORG_LC=$(echo ${{ github.repository_owner }} | tr '[:upper:]' '[:lower:]')" >> $GITHUB_ENV + - name: Set lower case release version + id: release_version + run: echo "release_version=$(echo ${{ github.ref_name }} | tr '[:upper:]' '[:lower:]')" >> $GITHUB_ENV + - name: Build Docker image run: | - docker build -t ghcr.io/${{ env.ORG_LC }}/headers-app:latest-dev . + docker build -t ghcr.io/${{ env.ORG_LC }}/headers-app:prod-latest -t ghcr.io/${{ env.ORG_LC }}/headers-app:prod-${{ env.release_version }} -t ghcr.io/${{ env.ORG_LC }}/headers-app:latest . - name: Log in to GitHub Container Registry run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin - - name: Push Docker image + - name: Push Specific version Prod Docker image run: | - docker push ghcr.io/${{ env.ORG_LC }}/headers-app:latest-dev + docker push ghcr.io/${{ env.ORG_LC }}/headers-app:prod-${{ env.release_version }} + - name: Push Prod-Latest Docker image + run: | + docker push ghcr.io/${{ env.ORG_LC }}/headers-app:prod-latest + + - name: Push latest Docker image + run: | + docker push ghcr.io/${{ env.ORG_LC }}/headers-app:latest \ No newline at end of file diff --git a/.gitignore b/.gitignore index bcbfe97..a029e35 100644 --- a/.gitignore +++ b/.gitignore @@ -127,3 +127,9 @@ dist .yarn/build-state.yml .yarn/install-state.gz .pnp.* + +# Ignore npm executable folders +node_modules/ + +# Ignore local package-lock files +package-lock.json \ No newline at end of file diff --git a/README.md b/README.md index 75bac81..6648a1d 100644 --- a/README.md +++ b/README.md @@ -55,9 +55,9 @@ HeaderApp is a Node.js application that mirrors all request headers sent to it. 3. Run the Docker container: ```sh - docker run -p 3000:3000 headerapp + docker run -p 8080:8080 headerapp -4. The application will be running on [http://localhost:3000](http://localhost:3000) +4. The application will be running on [http://localhost:8080](http://localhost:8080) ### HTTP and HTTPS @@ -66,7 +66,7 @@ HeaderApp is a Node.js application that mirrors all request headers sent to it. 1. Clone the repository: ```sh - git clone https://github.com/yourusername/headerapp.git + git clone https://github.com/Adal8819/headerapp.git cd headerapp 2. Install dependencies: @@ -98,7 +98,7 @@ HeaderApp is a Node.js application that mirrors all request headers sent to it. 1. Clone the repository: ```sh - git clone https://github.com/yourusername/headerapp.git + git clone https://github.com/Adal8819/headerapp.git cd headerapp 2. Make sure that your SSL Certificates are in place. The application is designed to work with an unencrypted SSL key currently. @@ -123,7 +123,31 @@ HeaderApp is a Node.js application that mirrors all request headers sent to it. ```sh docker run -v 'C:/path/to/your/ssl/files:/etc/ssl/certs' -p 8080:8080 -p 8443:8443 headerapp -6. The application will be running on [http://localhost:8080](http://localhost:8080) and [https://localhost:8443](https://localhost:8443). +6. The application will be running on [http://localhost:8080](http://localhost:8080) and [https://localhost:8443](https://localhost:8443) + +## Environment configuration options + +Headerapp uses a .ENV file to house configurable values. There are several values that can be configured there. Below lists out the configurable values and what they control. + +| Keys | Accepted Values | Definitions| +|-----------|:-----------:|:-----------| +| SSL_KEY | string | This is the path defined to the SSL private key file. Currently the application expects the key file to be stored in plain text | +| SSL_CERT | string | This is the path defined to the SSL public key file | +| USESSL | boolean | This enables or disables TLS. If this is set the SSL_KEY and SSL_CERT values must be defined. | +| PORTHTTPS | interger | This defines the HTTPS port. It is only used if the USESSL flag is set to true. If this is not set the applicatino will default to 8443. | +| PORTHTTP | interger | This defines the HTTP port. If this is not set the application will default to 8080. | +| DEBUG | boolean | This enables HTTP request debug logging to write to the console. This can be useful if you are troubleshooting what the application is seeing. | + +Below is a sample .ENV file + +``` text +SSL_KEY=./ssl/private/leaf.key +SSL_CERT=./ssl/private/chain.cer +USESSL=true +PORTHTTPS=8443 +PORTHTTP=8080 +DEBUG=true +``` ## Usage diff --git a/index.js b/index.js index 6134434..036a1d9 100644 --- a/index.js +++ b/index.js @@ -1,7 +1,6 @@ import dotenv from "dotenv"; import express from "express" import bodyParser from "body-parser"; -import morgan from "morgan"; import { dirname } from "path"; // this is to handle file path import { fileURLToPath } from "url"; // this is to handle file path import { url } from "inspector"; @@ -26,32 +25,27 @@ function logger(req, res, next) { next() } -app.use(logger) +if (process.env.DEBUG === "true") { + // Log incoming requests + app.use((req, res, next) => { + console.log(`Request received: ${req.method} ${req.originalUrl}\n Client IP: ${req.ip}\n Headers: ${JSON.stringify(req.headers, null, 2)}`); + next(); + }); + // Log response status after the response is sent + app.use((req, res, next) => { + const originalSend = res.send; + res.send = function (body) { + const protocol = req.protocol; // 'http' or 'https' + console.log(`Response sent for ${protocol.toUpperCase()} request: ${req.method} ${req.originalUrl} with status ${res.statusCode}`); + originalSend.call(this, body); + }; + next(); + }); +} // use body parser middleware we can use to mess with the body. app.use(bodyParser.urlencoded({ extended: true})); -// using Morgan middleware is a logging middleware -app.use(morgan('combined')) - -// Add logging middleware to differentiate HTTP and HTTPS requests -app.use((req, res, next) => { - const protocol = req.protocol; // 'http' or 'https' - console.log(`Received a ${protocol.toUpperCase()} request: ${req.method} ${req.originalUrl}`); - next(); -}); - -// Middleware to log response status after the response is sent -app.use((req, res, next) => { - const originalSend = res.send; - res.send = function (body) { - const protocol = req.protocol; // 'http' or 'https' - console.log(`Response sent for ${protocol.toUpperCase()} request: ${req.method} ${req.originalUrl} with status ${res.statusCode}`); - originalSend.call(this, body); - }; - next(); -}); - // handling GET /headers request app.get("/", (req, res) => { res.json({ @@ -59,8 +53,8 @@ app.get("/", (req, res) => { url: req.originalUrl, clientIP: req.ip, headers: req.headers - }) -}) + }); +}); // handling POST /headers request app.post("/", (req, res) => { @@ -69,8 +63,8 @@ app.post("/", (req, res) => { url: req.originalUrl, clientIP: req.ip, headers: req.headers - }) -}) + }); +}); // Create HTTP server const httpServer = http.createServer(app);