diff --git a/.release.yaml b/.release.yaml new file mode 100644 index 0000000000..49c851ab13 --- /dev/null +++ b/.release.yaml @@ -0,0 +1,2 @@ +application_template: .release/application_template.yaml +environment_variables: .release/environment_variables.yaml diff --git a/.release/application_template.yaml b/.release/application_template.yaml new file mode 100644 index 0000000000..ef485d3af1 --- /dev/null +++ b/.release/application_template.yaml @@ -0,0 +1,103 @@ +--- +app: example-voting-app +auto_deploy: true +context: release-handsup-us-east-1 +domain: app.releaseapp.gethandsup.com +mode: development +repo_name: dustyspace/example-voting-app +hostnames: +- vote: vote-${env_id}-${domain} +- result: result-${env_id}-${domain} +environment_templates: +- name: ephemeral +- name: permanent +resources: + cpu: + limits: 1000m + requests: 100m + memory: + limits: 1Gi + requests: 100Mi + replicas: 1 +services: +- name: vote + image: dustyspace/example-voting-app/vote + build: + context: "./vote" + has_repo: true + static: false + args: + - python + - app.py + depends_on: + - worker + ports: + - type: node_port + target_port: '80' + port: '5000' +- name: result + image: dustyspace/example-voting-app/result + build: + context: "./result" + has_repo: true + static: false + args: + - nodemon + - server.js + depends_on: + - worker + ports: + - type: node_port + target_port: '80' + port: '5001' +# - type: node_port +# target_port: '5858' +# port: '5858' +- name: worker + image: dustyspace/example-voting-app/worker + build: + context: "./worker" + has_repo: true + static: false + depends_on: + - redis + - db +- name: redis + image: redis:alpine + ports: + - type: container_port + port: '6379' +- name: db + image: postgres:9.6.1 + volumes: + - type: persistent + name: db-data + mount_path: "/var/lib/postgresql/data" + ports: + - type: container_port + port: '5432' + storage: + size: 10Gi + type: aws-efs +workflows: +- name: setup + parallelize: + - step: data + tasks: + - services.db + - services.redis + - step: worker + tasks: + - services.worker + - step: frontend + tasks: + - services.result + - services.vote +- name: patch + parallelize: + - step: all + tasks: + - services.worker + - services.result + - services.vote + diff --git a/.release/clusters/default.yaml b/.release/clusters/default.yaml new file mode 100644 index 0000000000..6e4d01252f --- /dev/null +++ b/.release/clusters/default.yaml @@ -0,0 +1,11 @@ +--- +charts: +- name: argo-wf + repo_url: https://argoproj.github.io/argo-helm + add: argo + install: argo/argo-workflows + version: "0.2.12" + include_crds: true + directory: helm/argo + values: values.yaml + name_template: "argo-workflows" diff --git a/.release/clusters/release-test-us-west-2.yaml b/.release/clusters/release-test-us-west-2.yaml new file mode 100644 index 0000000000..1c55ac49e9 --- /dev/null +++ b/.release/clusters/release-test-us-west-2.yaml @@ -0,0 +1,13 @@ +--- +charts: +- name: argo-wf + repo_url: https://argoproj.github.io/argo-helm + add: argo + install: argo/argo-workflows + version: "0.2.12" + include_crds: true + directory: helm/argo + values: values.yaml + name_template: "argo-workflows" +context: release-test-us-west-2 +namespace: argo-wf-test diff --git a/.release/env-config.yaml b/.release/env-config.yaml new file mode 100644 index 0000000000..45a3d41a7c --- /dev/null +++ b/.release/env-config.yaml @@ -0,0 +1,31 @@ +--- +defaults: +- key: FRESH + value: 'and so clean' +- key: POSTGRES_DB + value: database +- key: POSTGRES_HOST + value: db +- key: POSTGRES_PORT + value: '5432' +- key: POSTGRES_USER + value: postgres +- key: POSTGRES_HOST_AUTH_METHOD + value: trust +- key: PGDATA + value: "/var/lib/postgresql/data/pgdata" +services: + db: + - key: POSTGRES_DB + value: database + - key: POSTGRES_HOST + value: db + - key: POSTGRES_PORT + value: '5432' + - key: POSTGRES_USER + value: postgres + - key: POSTGRES_HOST_AUTH_METHOD + value: trust + - key: PGDATA + value: "/var/lib/postgresql/data/pgdata" + diff --git a/README.md b/README.md index 54059ce809..4896e5e252 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ Example Voting App ========= - + A simple distributed application running across multiple Docker containers. Getting started diff --git a/docker-compose.yml b/docker-compose.yml index 6c477aa25b..1c93e55685 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,37 +1,38 @@ version: "3" services: + echo: + build: ./echo + ports: + - "8080:8080" vote: - image: docker.giffin.org/tmcclung/example-voting-app-vote build: ./vote - command: - - python - - app.py + command: python app.py volumes: - ./vote:/app ports: - "5000:80" + depends_on: + - "worker" networks: - front-tier - back-tier result: - image: docker.giffin.org/tmcclung/example-voting-app-result build: ./result - command: - - nodemon - - server.js + command: nodemon server.js volumes: - ./result:/app ports: - "5001:80" - "5858:5858" + depends_on: + - "worker" networks: - front-tier - back-tier worker: - image: docker.giffin.org/tmcclung/example-voting-app-worker build: context: ./worker depends_on: @@ -40,6 +41,20 @@ services: networks: - back-tier + uploader: + image: mayth/simple-upload-server + ports: + - "3000:3000" + command: + - "/usr/local/bin/app" + - "-port" + - '3000' + - "-upload_limit" + - '10485760' + - "-token" + - f9403fc5f537b4ab332d + - "/tmp" + redis: image: redis:alpine container_name: redis @@ -48,8 +63,14 @@ services: - back-tier db: - image: postgres:9.4 + image: postgres:9.6.1 container_name: db + ports: ["5432"] + environment: + - POSTGRES_DB=database + - POSTGRES_HOST=db + - POSTGRES_PORT=5432 + - POSTGRES_USER=postgres volumes: - "db-data:/var/lib/postgresql/data" networks: diff --git a/echo/.dockerignore b/echo/.dockerignore new file mode 100644 index 0000000000..5c3f51279f --- /dev/null +++ b/echo/.dockerignore @@ -0,0 +1,8 @@ +# Ignore everything +** + +# Except +!generate-cert.sh +!index.js +!package.json +!package-lock.json diff --git a/echo/.gitattributes b/echo/.gitattributes new file mode 100644 index 0000000000..1312090792 --- /dev/null +++ b/echo/.gitattributes @@ -0,0 +1,2 @@ +* text=auto +*.sh text eol=lf \ No newline at end of file diff --git a/echo/.gitignore b/echo/.gitignore new file mode 100644 index 0000000000..fe959ccc72 --- /dev/null +++ b/echo/.gitignore @@ -0,0 +1,4 @@ +.*.sw* +node_modules/ +*.log +testarea \ No newline at end of file diff --git a/echo/CHANGELOG.md b/echo/CHANGELOG.md new file mode 100644 index 0000000000..004638dfde --- /dev/null +++ b/echo/CHANGELOG.md @@ -0,0 +1,42 @@ +## Version `19` - 2021-04-08 + +* You can run the container as a different user than the one defined in the image. + +## Version `18` - 2021-02-26 + +* You can pass a `x-set-response-delay-ms` to set a custom delay in milliseconds. + +## Version `17` - 2021-01-15 + +* You can pass a `x-set-response-status-code` header to set the response status code + +## Version `16` - 2020-12-22 + +* Dockerfile optimisation, slightly smaller image size +* This changelog added to the repo + +## Version `15` - 2020-12-15 + +* The image now runs as a non-root user by default. + +## Version `14` - 2020-11-26 + +* Optionally allow running as a non root user. + +``` +docker run --user node -e HTTP_PORT=8080 -e HTTPS_PORT=8443 -p 8080:8080 -p 8443:8443 --rm mendhak/http-https-echo:issue-14-non-root +#or +docker run --user node --sysctl net.ipv4.ip_unprivileged_port_start=0 -p 8080:80 -p 8443:443 --rm mendhak/http-https-echo:issue-14-non-root +``` + +## Version `latest` and others + +_Note: The `latest` tag is no longer being built, I've removed it from the automated builds. Please don't use the `latest` tag any longer._ + +* JWT header +* Choose your own ports +* Choose your own certs +* Ignore a specific path +* JSON payloads +* Single line log output + diff --git a/echo/Dockerfile b/echo/Dockerfile new file mode 100644 index 0000000000..4ed6a059e7 --- /dev/null +++ b/echo/Dockerfile @@ -0,0 +1,26 @@ +FROM node:14-alpine AS build + +WORKDIR /app +COPY . /app + +RUN set -ex \ + # Build JS-Application + && npm install --production \ + # Generate SSL-certificate (for HTTPS) + && apk --no-cache add openssl \ + && sh generate-cert.sh \ + && apk del openssl \ + && rm -rf /var/cache/apk/* \ + # Delete unnecessary files + && rm package* generate-cert.sh \ + # Correct User's file access + && chown -R node:node /app \ + && chmod +r /app/privkey.pem + +FROM node:14-alpine AS final +WORKDIR /app +COPY --from=build /app /app +ENV HTTP_PORT=8080 HTTPS_PORT=8443 +EXPOSE $HTTP_PORT $HTTPS_PORT +USER 1000 +CMD ["node", "./index.js"] diff --git a/echo/README.md b/echo/README.md new file mode 100644 index 0000000000..f471af1d43 --- /dev/null +++ b/echo/README.md @@ -0,0 +1,170 @@ +[![pulls](https://img.shields.io/docker/pulls/mendhak/http-https-echo.svg?style=for-the-badge&logo=docker)](https://hub.docker.com/r/mendhak/http-https-echo) +[![Docker Image Version (latest semver)](https://img.shields.io/docker/v/mendhak/http-https-echo?color=lightblue&label=latest&sort=semver&style=for-the-badge)](https://hub.docker.com/r/mendhak/http-https-echo) +[![GitHub Workflow Status](https://img.shields.io/github/workflow/status/mendhak/docker-http-https-echo/Build?color=darkgreen&style=for-the-badge)](https://github.com/mendhak/docker-http-https-echo/actions?query=workflow%3ABuild) + + + + +[`mendhak/http-https-echo`](https://hub.docker.com/r/mendhak/http-https-echo) is a Docker image that can echo various HTTP request properties back to client, as well as in the Docker container logs. +You can use your own certificates, choose your ports, decode JWT headers and filter out certain paths. + +![browser](https://raw.githubusercontent.com/mendhak/docker-http-https-echo/master/screenshots/screenshot.png) + +This image is executed as non root by default and is fully compliant with Kubernetes or Openshift deployment. + +Please do not use the `:latest` tag as it will break without warning, use a specific version instead. + +## Basic Usage + +Run with Docker + + docker run -p 8080:8080 -p 8443:8443 --rm -t mendhak/http-https-echo:18 + +Or run with Docker Compose + + docker-compose up + +Then, issue a request via your browser or curl, and watch the response, as well as container log output. + + curl -k -X PUT -H "Arbitrary:Header" -d aaa=bbb https://localhost:8443/hello-world + + +## Choose your ports + +You can choose a different internal port instead of 8080 and 8443 with the `HTTP_PORT` and `HTTPS_PORT` environment variables. + +In this example I'm setting http to listen on 8888, and https to listen on 9999. + + docker run -e HTTP_PORT=8888 -e HTTPS_PORT=9999 -p 8080:8888 -p 8443:9999 --rm -t mendhak/http-https-echo:18 + + +With docker compose, this would be: + + my-http-listener: + image: mendhak/http-https-echo:18 + environment: + - HTTP_PORT=8888 + - HTTPS_PORT=9999 + ports: + - "8080:8888" + - "8443:9999" + + +## Use your own certificates + +Use volume mounting to substitute the certificate and private key with your own. This example uses the snakeoil cert. + + my-http-listener: + image: mendhak/http-https-echo:18 + ports: + - "8080:8080" + - "8443:8443" + volumes: + - /etc/ssl/certs/ssl-cert-snakeoil.pem:/app/fullchain.pem + - /etc/ssl/private/ssl-cert-snakeoil.key:/app/privkey.pem + + + +## Decode JWT header + +If you specify the header that contains the JWT, the echo output will contain the decoded JWT. Use the `JWT_HEADER` environment variable for this. + + docker run -e JWT_HEADER=Authentication -p 8080:8080 -p 8443:8443 --rm -it mendhak/http-https-echo:18 + + +Now make your request with `Authentication: eyJ...` header (it should also work with the `Authentication: Bearer eyJ...` schema too): + + curl -k -H "Authentication: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c" http://localhost:8080/ + +And in the output you should see a `jwt` section. + +## Do not log specific path + +Set the environment variable `LOG_IGNORE_PATH` to a path you would like to exclude from verbose logging to stdout. +This can help reduce noise from healthchecks in orchestration/infrastructure like Swarm, Kubernetes, ALBs, etc. + + docker run -e LOG_IGNORE_PATH=/ping -p 8080:8080 -p 8443:8443 --rm -t mendhak/http-https-echo:18 + + +With docker compose, this would be: + + my-http-listener: + image: mendhak/http-https-echo:18 + environment: + - LOG_IGNORE_PATH=/ping + ports: + - "8080:8080" + - "8443:8443" + + +## JSON payloads and JSON output + +If you submit a JSON payload in the body of the request, with Content-Type: application/json, then the response will contain the escaped JSON as well. + +For example, + + curl -X POST -H "Content-Type: application/json" -d '{"a":"b"}' http://localhost:8080/ + +Will contain a `json` property in the response/output. + + ... + "xhr": false, + "connection": {}, + "json": { + "a": "b" + } + } + +## Custom status code + +Use `x-set-response-status-code` to set a custom status code. For example, + + +```bash +curl -v -H "x-set-response-status-code: 401" http://localhost:8080/ +``` + +Will cause the reponse status code to be: + +``` + HTTP/1.1 401 Unauthorized +``` + +## Add a delay before response + +Use `x-set-response-delay-ms` to set a custom delay in milliseconds. This will allow you to simulate slow responses. + +```bash +curl -v -H "x-set-response-delay-ms: 6000" http://localhost:8080/ +``` + + +## Output + +#### Curl output + +![curl](https://raw.githubusercontent.com/mendhak/docker-http-https-echo/master/screenshots/screenshot2.png) + +#### `docker logs` output + +![dockerlogs](https://raw.githubusercontent.com/mendhak/docker-http-https-echo/master/screenshots/screenshot3.png) + + + +## Building + + docker build -t mendhak/http-https-echo . + +Run some tests to make sure features are working as expected. + + ./tests.sh + +To create a new image on Docker Hub, I need to create a tag and push it. + + git tag -s 16 + git push --tags + + +## Changelog + +See the [changelog](CHANGELOG.md) \ No newline at end of file diff --git a/echo/docker-compose.yml b/echo/docker-compose.yml new file mode 100644 index 0000000000..d65fee393d --- /dev/null +++ b/echo/docker-compose.yml @@ -0,0 +1,11 @@ +my-http-listener: + image: mendhak/http-https-echo:18 + environment: + - HTTP_PORT=8888 + - HTTPS_PORT=9999 + ports: + - "8080:8888" + - "8443:9999" + # volumes: + # - /etc/ssl/certs/ssl-cert-snakeoil.pem:/app/fullchain.pem + # - /etc/ssl/private/ssl-cert-snakeoil.key:/app/privkey.pem \ No newline at end of file diff --git a/echo/generate-cert.sh b/echo/generate-cert.sh new file mode 100644 index 0000000000..6d6f7a619c --- /dev/null +++ b/echo/generate-cert.sh @@ -0,0 +1,44 @@ +#!/bin/sh +# source : https://raw.githubusercontent.com/Daplie/nodejs-self-signed-certificate-example/master/make-root-ca-and-certificates.sh + +set -e + +# Create your very own Root Certificate Authority +openssl genrsa \ + -out root-ca.key.pem \ + 2048 + +# Self-sign your Root Certificate Authority +# Since this is private, the details can be as bogus as you like +openssl req \ + -x509 \ + -new \ + -nodes \ + -key root-ca.key.pem \ + -days 9999 \ + -out root-ca.crt.pem \ + -subj "/C=US/ST=Utah/L=Provo/O=ACME Signing Authority Inc/CN=example.com" + +# Create a Device Certificate +openssl genrsa \ + -out privkey.pem \ + 2048 + +# Create a request from your Device, which your Root CA will sign +openssl req -new \ + -key privkey.pem \ + -out device-csr.pem \ + -subj "/C=US/ST=Utah/L=Provo/O=ACME Tech Inc/CN=my.example.com" + +# Sign the request from Device with your Root CA +# -CAserial certs/ca/my-root-ca.srl +openssl x509 \ + -req -in device-csr.pem \ + -CA root-ca.crt.pem \ + -CAkey root-ca.key.pem \ + -CAcreateserial \ + -out cert.pem \ + -days 9999 + +# Put things in their proper place +cat cert.pem root-ca.crt.pem > fullchain.pem \ No newline at end of file diff --git a/echo/index.js b/echo/index.js new file mode 100644 index 0000000000..5e1f92a91e --- /dev/null +++ b/echo/index.js @@ -0,0 +1,116 @@ +var express = require('express') +const morgan = require('morgan'); +var http = require('http') +var https = require('https') +var app = express() +const os = require('os'); +const jwt = require('jsonwebtoken'); +var concat = require('concat-stream'); +const { promisify } = require('util'); +const sleep = promisify(setTimeout); + +app.set('json spaces', 2); +app.set('trust proxy', ['loopback', 'linklocal', 'uniquelocal']); + +app.use(morgan('combined')); + +app.use(function(req, res, next){ + req.pipe(concat(function(data){ + req.body = data.toString('utf8'); + next(); + })); +}); + +app.all('*', (req, res) => { + const echo = { + path: req.path, + headers: req.headers, + method: req.method, + body: req.body, + cookies: req.cookies, + fresh: req.fresh, + hostname: req.hostname, + ip: req.ip, + ips: req.ips, + protocol: req.protocol, + query: req.query, + subdomains: req.subdomains, + xhr: req.xhr, + os: { + hostname: os.hostname() + }, + connection: { + servername: req.connection.servername + } + }; + + if(req.is('application/json')){ + echo.json = JSON.parse(req.body) + } + + if (process.env.JWT_HEADER) { + let token = req.headers[process.env.JWT_HEADER.toLowerCase()]; + if (!token) { + echo.jwt = token; + } else { + token = token.split(" ").pop(); + const decoded = jwt.decode(token, {complete: true}); + echo.jwt = decoded; + } + } + const setResponseStatusCode = parseInt(req.headers["x-set-response-status-code"], 10) + if (100 <= setResponseStatusCode && setResponseStatusCode < 600) { + res.status(setResponseStatusCode) + } + + const sleepTime = parseInt(req.headers["x-set-response-delay-ms"], 0) + sleep(sleepTime).then(() => { + + res.json(echo); + + if (process.env.LOG_IGNORE_PATH != req.path) { + console.log('-----------------') + + let spacer = 4; + if(process.env.LOG_WITHOUT_NEWLINE){ + spacer = null; + } + + console.log(JSON.stringify(echo, null, spacer)); + } + }); + + +}); + +const sslOpts = { + key: require('fs').readFileSync('privkey.pem'), + cert: require('fs').readFileSync('fullchain.pem'), +}; + +var httpServer = http.createServer(app).listen(process.env.HTTP_PORT || 80, '0.0.0.0'); +var httpsServer = https.createServer(sslOpts,app).listen(process.env.HTTPS_PORT || 443, '0.0.0.0'); + +let calledClose = false; + +process.on('exit', function () { + if (calledClose) return; + console.log('Got exit event. Trying to stop Express server.'); + server.close(function() { + console.log("Express server closed"); + }); +}); + +process.on('SIGINT', shutDown); +process.on('SIGTERM', shutDown); + +function shutDown(){ + console.log('Got a kill signal. Trying to exit gracefully.'); + calledClose = true; + httpServer.close(function() { + httpsServer.close(function() { + console.log("HTTP and HTTPS servers closed. Asking process to exit."); + process.exit() + }); + }); +} diff --git a/echo/package-lock.json b/echo/package-lock.json new file mode 100644 index 0000000000..9d2af6768c --- /dev/null +++ b/echo/package-lock.json @@ -0,0 +1,560 @@ +{ + "name": "@mendhak/http-https-echo", + "version": "1.0.1", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "accepts": { + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", + "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==", + "requires": { + "mime-types": "~2.1.24", + "negotiator": "0.6.2" + } + }, + "array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" + }, + "basic-auth": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz", + "integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==", + "requires": { + "safe-buffer": "5.1.2" + } + }, + "body-parser": { + "version": "1.19.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz", + "integrity": "sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==", + "requires": { + "bytes": "3.1.0", + "content-type": "~1.0.4", + "debug": "2.6.9", + "depd": "~1.1.2", + "http-errors": "1.7.2", + "iconv-lite": "0.4.24", + "on-finished": "~2.3.0", + "qs": "6.7.0", + "raw-body": "2.4.0", + "type-is": "~1.6.17" + } + }, + "buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk=" + }, + "buffer-from": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", + "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==" + }, + "bytes": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", + "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==" + }, + "concat-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-2.0.0.tgz", + "integrity": "sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==", + "requires": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.0.2", + "typedarray": "^0.0.6" + } + }, + "content-disposition": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz", + "integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==", + "requires": { + "safe-buffer": "5.1.2" + } + }, + "content-type": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", + "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==" + }, + "cookie": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz", + "integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==" + }, + "cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" + }, + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + }, + "depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" + }, + "destroy": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", + "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" + }, + "ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "requires": { + "safe-buffer": "^5.0.1" + } + }, + "ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" + }, + "encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=" + }, + "escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" + }, + "etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=" + }, + "express": { + "version": "4.17.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz", + "integrity": "sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==", + "requires": { + "accepts": "~1.3.7", + "array-flatten": "1.1.1", + "body-parser": "1.19.0", + "content-disposition": "0.5.3", + "content-type": "~1.0.4", + "cookie": "0.4.0", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "~1.1.2", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "~1.1.2", + "fresh": "0.5.2", + "merge-descriptors": "1.0.1", + "methods": "~1.1.2", + "on-finished": "~2.3.0", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.7", + "proxy-addr": "~2.0.5", + "qs": "6.7.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.1.2", + "send": "0.17.1", + "serve-static": "1.14.1", + "setprototypeof": "1.1.1", + "statuses": "~1.5.0", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + } + }, + "finalhandler": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", + "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", + "requires": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "~2.3.0", + "parseurl": "~1.3.3", + "statuses": "~1.5.0", + "unpipe": "~1.0.0" + } + }, + "forwarded": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", + "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=" + }, + "fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=" + }, + "http-errors": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz", + "integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==", + "requires": { + "depd": "~1.1.2", + "inherits": "2.0.3", + "setprototypeof": "1.1.1", + "statuses": ">= 1.5.0 < 2", + "toidentifier": "1.0.0" + }, + "dependencies": { + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" + } + } + }, + "iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==" + }, + "jsonwebtoken": { + "version": "8.5.1", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz", + "integrity": "sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w==", + "requires": { + "jws": "^3.2.2", + "lodash.includes": "^4.3.0", + "lodash.isboolean": "^3.0.3", + "lodash.isinteger": "^4.0.4", + "lodash.isnumber": "^3.0.3", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.once": "^4.0.0", + "ms": "^2.1.1", + "semver": "^5.6.0" + }, + "dependencies": { + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + } + } + }, + "jwa": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", + "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", + "requires": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "jws": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", + "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", + "requires": { + "jwa": "^1.4.1", + "safe-buffer": "^5.0.1" + } + }, + "lodash.includes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", + "integrity": "sha1-YLuYqHy5I8aMoeUTJUgzFISfVT8=" + }, + "lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha1-bC4XHbKiV82WgC/UOwGyDV9YcPY=" + }, + "lodash.isinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha1-YZwK89A/iwTDH1iChAt3sRzWg0M=" + }, + "lodash.isnumber": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha1-POdoEMWSjQM1IwGsKHMX8RwLH/w=" + }, + "lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=" + }, + "lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha1-1SfftUVuynzJu5XV2ur4i6VKVFE=" + }, + "lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha1-DdOXEhPHxW34gJd9UEyI+0cal6w=" + }, + "media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" + }, + "merge-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", + "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" + }, + "methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=" + }, + "mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==" + }, + "mime-db": { + "version": "1.44.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.44.0.tgz", + "integrity": "sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg==" + }, + "mime-types": { + "version": "2.1.27", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.27.tgz", + "integrity": "sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w==", + "requires": { + "mime-db": "1.44.0" + } + }, + "morgan": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.10.0.tgz", + "integrity": "sha512-AbegBVI4sh6El+1gNwvD5YIck7nSA36weD7xvIxG4in80j/UoK8AEGaWnnz8v1GxonMCltmlNs5ZKbGvl9b1XQ==", + "requires": { + "basic-auth": "~2.0.1", + "debug": "2.6.9", + "depd": "~2.0.0", + "on-finished": "~2.3.0", + "on-headers": "~1.0.2" + }, + "dependencies": { + "depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==" + } + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + }, + "negotiator": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", + "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==" + }, + "on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", + "requires": { + "ee-first": "1.1.1" + } + }, + "on-headers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", + "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==" + }, + "parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==" + }, + "path-to-regexp": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", + "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" + }, + "proxy-addr": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.6.tgz", + "integrity": "sha512-dh/frvCBVmSsDYzw6n926jv974gddhkFPfiN8hPOi30Wax25QZyZEGveluCgliBnqmuM+UJmBErbAUFIoDbjOw==", + "requires": { + "forwarded": "~0.1.2", + "ipaddr.js": "1.9.1" + } + }, + "qs": { + "version": "6.7.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", + "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==" + }, + "range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==" + }, + "raw-body": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz", + "integrity": "sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==", + "requires": { + "bytes": "3.1.0", + "http-errors": "1.7.2", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + } + }, + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" + }, + "send": { + "version": "0.17.1", + "resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz", + "integrity": "sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==", + "requires": { + "debug": "2.6.9", + "depd": "~1.1.2", + "destroy": "~1.0.4", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "~1.7.2", + "mime": "1.6.0", + "ms": "2.1.1", + "on-finished": "~2.3.0", + "range-parser": "~1.2.1", + "statuses": "~1.5.0" + }, + "dependencies": { + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" + } + } + }, + "serve-static": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.1.tgz", + "integrity": "sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==", + "requires": { + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.17.1" + } + }, + "setprototypeof": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", + "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==" + }, + "statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=" + }, + "string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "requires": { + "safe-buffer": "~5.2.0" + }, + "dependencies": { + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" + } + } + }, + "toidentifier": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", + "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==" + }, + "type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "requires": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + } + }, + "typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=" + }, + "unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" + }, + "utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=" + }, + "vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=" + } + } +} diff --git a/echo/package.json b/echo/package.json new file mode 100644 index 0000000000..3707e4848e --- /dev/null +++ b/echo/package.json @@ -0,0 +1,25 @@ +{ + "name": "@mendhak/http-https-echo", + "repository": { + "type": "git", + "url": "https://github.com/mendhak/docker-http-echo" + }, + "version": "1.0.1", + "description": "JSON service for debugging a web setup", + "main": "index.js", + "scripts": { + "start": "node index.js", + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "Mendhak ", + "license": "BSD-3-Clause", + "engines": { + "node": ">=6.3.0" + }, + "dependencies": { + "concat-stream": "^2.0.0", + "express": "^4.17.1", + "jsonwebtoken": "^8.5.0", + "morgan": "^1.10.0" + } +} diff --git a/echo/screenshots/screenshot.png b/echo/screenshots/screenshot.png new file mode 100644 index 0000000000..902a612808 Binary files /dev/null and b/echo/screenshots/screenshot.png differ diff --git a/echo/screenshots/screenshot2.png b/echo/screenshots/screenshot2.png new file mode 100644 index 0000000000..0af97aa54a Binary files /dev/null and b/echo/screenshots/screenshot2.png differ diff --git a/echo/screenshots/screenshot3.png b/echo/screenshots/screenshot3.png new file mode 100644 index 0000000000..a83c00d459 Binary files /dev/null and b/echo/screenshots/screenshot3.png differ diff --git a/echo/tests.sh b/echo/tests.sh new file mode 100755 index 0000000000..252723f088 --- /dev/null +++ b/echo/tests.sh @@ -0,0 +1,240 @@ +#!/usr/bin/env bash + +set -euo pipefail + +function message { + echo "" + echo "---------------------------------------------------------------" + echo $1 + echo "---------------------------------------------------------------" +} + +RESTORE=$(echo -en '\033[0m') +RED=$(echo -en '\033[01;31m') +GREEN=$(echo -en '\033[01;32m') + +function failed { + echo ${RED}✗$1${RESTORE} +} + +function passed { + echo ${GREEN}✓$1${RESTORE} +} + +if ! [ -x "$(command -v jq)" ]; then + message "JQ not installed. Installing..." + sudo apt -y install jq +fi + + +message " Build image " +docker build -t mendhak/http-https-echo:latest . + +mkdir -p testarea +pushd testarea + +message " Cleaning up from previous test run " +docker ps -q --filter "name=http-echo-tests" | grep -q . && docker stop http-echo-tests + +message " Start container normally " +docker run -d --rm --name http-echo-tests -p 8080:8080 -p 8443:8443 -t mendhak/http-https-echo +sleep 5 + + +message " Make http(s) request, and test the path, method, header and status code. " +REQUEST=$(curl -s -k -X PUT -H "Arbitrary:Header" -d aaa=bbb https://localhost:8443/hello-world) +if [ $(echo $REQUEST | jq -r '.path') == '/hello-world' ] && \ + [ $(echo $REQUEST | jq -r '.method') == 'PUT' ] && \ + [ $(echo $REQUEST | jq -r '.headers.arbitrary') == 'Header' ] +then + passed "HTTPS request passed." +else + failed "HTTPS request failed." + echo $REQUEST | jq + exit 1 +fi +REQUEST_WITH_STATUS_CODE=$(curl -s -k -o /dev/null -w "%{http_code}" -H "x-set-response-status-code: 404" https://localhost:8443/hello-world) +REQUEST_WITH_STATUS_CODE_V=$(curl -v -k -o /dev/null -w "%{http_code}" -H "x-set-response-status-code: 404" https://localhost:8443/hello-world) +if [ $(echo $REQUEST_WITH_STATUS_CODE == '404') ] +then + passed "HTTPS status code request passed." +else + failed "HTTPS status code request failed." + echo $REQUEST_WITH_STATUS_CODE_V + exit 1 +fi + +REQUEST_WITH_SLEEP_MS=$(curl -o /dev/null -Ss -H "x-set-response-delay-ms: 6000" -k https://localhost:8443/ -w '%{time_total}') +if [[ $(echo "$REQUEST_WITH_SLEEP_MS>5" | bc -l) == 1 ]]; then + passed "Request with response delay passed" +else + failed "Request with response delay failed" + echo $REQUEST_WITH_SLEEP_MS + exit 1 +fi + +REQUEST_WITH_INVALID_SLEEP_MS=$(curl -o /dev/null -Ss -H "x-set-response-delay-ms: XXXX" -k https://localhost:8443/ -w '%{time_total}') +if [[ $(echo "$REQUEST_WITH_INVALID_SLEEP_MS<2" | bc -l) == 1 ]]; then + passed "Request with invalid response delay passed" +else + failed "Request with invalid response delay failed" + echo $REQUEST_WITH_INVALID_SLEEP_MS + exit 1 +fi + +REQUEST=$(curl -s -X PUT -H "Arbitrary:Header" -d aaa=bbb http://localhost:8080/hello-world) +if [ $(echo $REQUEST | jq -r '.path') == '/hello-world' ] && \ + [ $(echo $REQUEST | jq -r '.method') == 'PUT' ] && \ + [ $(echo $REQUEST | jq -r '.headers.arbitrary') == 'Header' ] +then + passed "HTTP request with arbitrary header passed." +else + failed "HTTP request with arbitrary header failed." + echo $REQUEST | jq + exit 1 +fi + +message " Make JSON request, and test that json is in the output. " +REQUEST=$(curl -s -X POST -H "Content-Type: application/json" -d '{"a":"b"}' http://localhost:8080/) +if [ $(echo $REQUEST | jq -r '.json.a') == 'b' ] +then + passed "JSON test passed." +else + failed "JSON test failed." + echo $REQUEST | jq + exit 1 +fi + +message " Stop containers " +docker stop http-echo-tests + +message " Start container with different internal ports " +docker run -d --rm -e HTTP_PORT=8888 -e HTTPS_PORT=9999 --name http-echo-tests -p 8080:8888 -p 8443:9999 -t mendhak/http-https-echo +sleep 5 + +message " Make http(s) request, and test the path, method and header. " +REQUEST=$(curl -s -k -X PUT -H "Arbitrary:Header" -d aaa=bbb https://localhost:8443/hello-world) +if [ $(echo $REQUEST | jq -r '.path') == '/hello-world' ] && \ + [ $(echo $REQUEST | jq -r '.method') == 'PUT' ] && \ + [ $(echo $REQUEST | jq -r '.headers.arbitrary') == 'Header' ] +then + passed "HTTPS request passed." +else + failed "HTTPS request failed." + echo $REQUEST | jq + exit 1 +fi + +REQUEST=$(curl -s -X PUT -H "Arbitrary:Header" -d aaa=bbb http://localhost:8080/hello-world) +if [ $(echo $REQUEST | jq -r '.path') == '/hello-world' ] && \ + [ $(echo $REQUEST | jq -r '.method') == 'PUT' ] && \ + [ $(echo $REQUEST | jq -r '.headers.arbitrary') == 'Header' ] +then + passed "HTTP request passed." +else + failed "HTTP request failed." + echo $REQUEST | jq + exit 1 +fi + + +message " Stop containers " +docker stop http-echo-tests + + +message " Start container with JWT_HEADER " +docker run -d --rm -e JWT_HEADER=Authentication --name http-echo-tests -p 8080:8080 -p 8443:8443 -t mendhak/http-https-echo +sleep 5 + +REQUEST=$(curl -s -k -H "Authentication: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c" https://localhost:8443/ ) +if [ $(echo $REQUEST | jq -r '.jwt.header.typ') == 'JWT' ] && \ + [ $(echo $REQUEST | jq -r '.jwt.header.alg') == 'HS256' ] && \ + [ $(echo $REQUEST | jq -r '.jwt.payload.sub') == '1234567890' ] +then + passed "JWT request passed." +else + failed "JWT request failed." + echo $REQUEST | jq + exit 1 +fi + +message " Stop containers " +docker stop http-echo-tests + + +message " Start container with LOG_IGNORE_PATH " +docker run -d --rm -e LOG_IGNORE_PATH=/ping --name http-echo-tests -p 8080:8080 -p 8443:8443 -t mendhak/http-https-echo +sleep 5 +curl -s -k -X POST -d "banana" https://localhost:8443/ping > /dev/null + +if [ $(docker logs http-echo-tests | wc -l) == 1 ] && \ + ! [ $(docker logs http-echo-tests | grep banana) ] +then + passed "LOG_IGNORE_PATH ignored the /ping path" +else + failed "LOG_IGNORE_PATH failed" + docker logs http-echo-tests + exit 1 +fi + + +message " Stop containers " +docker stop http-echo-tests + +message " Start container with LOG_WITHOUT_NEWLINE " +docker run -d --rm -e LOG_WITHOUT_NEWLINE=1 --name http-echo-tests -p 8080:8080 -p 8443:8443 -t mendhak/http-https-echo +sleep 5 +curl -s -k -X POST -d "tiramisu" https://localhost:8443/ > /dev/null + +if [ $(docker logs http-echo-tests | wc -l) == 3 ] && \ + [ $(docker logs http-echo-tests | grep tiramisu) ] +then + passed "LOG_WITHOUT_NEWLINE logged output in single line" +else + failed "LOG_WITHOUT_NEWLINE failed" + docker logs http-echo-tests + exit 1 +fi + + +message " Stop containers " +docker stop http-echo-tests + +message " Check that container is running as a NON ROOT USER by default" +docker run -d --name http-echo-tests --rm mendhak/http-https-echo + +WHOAMI=$(docker exec http-echo-tests whoami) + +if [ "$WHOAMI" == "node" ] +then + passed "Running as non root user" +else + failed "Running as root user" + exit 1 +fi + +message " Stop containers " +docker stop http-echo-tests + +message " Check that container is running as user different that the user defined in image" +IMAGE_USER="$(docker image inspect mendhak/http-https-echo -f '{{ .ContainerConfig.User }}')" +CONTAINER_USER="$((IMAGE_USER + 1000000))" +docker run -d --name http-echo-tests --rm -u "${CONTAINER_USER}" -p 8080:8080 mendhak/http-https-echo +sleep 5 +curl -s http://localhost:8080 > /dev/null + +WHOAMI="$(docker exec http-echo-tests id -u)" + +if [ "$WHOAMI" == "$CONTAINER_USER" ] +then + passed "Running as $CONTAINER_USER user" +else + failed "Not running as $CONTAINER_USER user or failed to start" + exit 1 +fi + +message " Stop containers " +docker stop http-echo-tests + +popd +rm -rf testarea diff --git a/helm/argo/values.yaml b/helm/argo/values.yaml new file mode 100644 index 0000000000..7fefdb23ae --- /dev/null +++ b/helm/argo/values.yaml @@ -0,0 +1,22 @@ +singleNamespace: true + +workflow: + rbac: + create: false + +controller: + metricsConfig: + enabled: true + serviceAccount: + name: "argo" + workflowNamespaces: [] + clusterWorkflowTemplates: + enabled: false + +clusterWorkflowTemplates: + enabled: false + enableEditing: false + +server: + serviceAccount: + name: "argo-server" diff --git a/result/views/index.html b/result/views/index.html index 4427ffa1af..dd468e68a6 100644 --- a/result/views/index.html +++ b/result/views/index.html @@ -20,12 +20,12 @@
-
Cats
+
Fred
{{aPercent | number:1}}%
-
Dogs
+
Savage
{{bPercent | number:1}}%
diff --git a/vote/Dockerfile b/vote/Dockerfile index ad14ad92fb..cb548609cb 100644 --- a/vote/Dockerfile +++ b/vote/Dockerfile @@ -16,3 +16,6 @@ EXPOSE 80 # Define our command to be run when launching the container CMD ["gunicorn", "app:app", "-b", "0.0.0.0:80", "--log-file", "-", "--access-logfile", "-", "--workers", "4", "--keep-alive", "0"] + + + diff --git a/vote/app.py b/vote/app.py index 1d194b649b..eeed6d3fdb 100644 --- a/vote/app.py +++ b/vote/app.py @@ -5,8 +5,8 @@ import random import json -option_a = os.getenv('OPTION_A', "Cats") -option_b = os.getenv('OPTION_B', "Dogs") +option_a = os.getenv('OPTION_A', "Fredie") +option_b = os.getenv('OPTION_B', "Savagie") hostname = socket.gethostname() app = Flask(__name__) diff --git a/worker/Dockerfile b/worker/Dockerfile index 85c33a5e9e..f8d9b36c5b 100644 --- a/worker/Dockerfile +++ b/worker/Dockerfile @@ -15,3 +15,4 @@ FROM openjdk:8-jre-alpine COPY --from=build /code/target/worker-jar-with-dependencies.jar / CMD ["java", "-XX:+UnlockExperimentalVMOptions", "-XX:+UseCGroupMemoryLimitForHeap", "-jar", "/worker-jar-with-dependencies.jar"] + diff --git a/worker/src/Worker/Program.cs b/worker/src/Worker/Program.cs index e8ca4c1f3e..a9161ca412 100644 --- a/worker/src/Worker/Program.cs +++ b/worker/src/Worker/Program.cs @@ -8,6 +8,7 @@ using Npgsql; using StackExchange.Redis; + namespace Worker { public class Program diff --git a/worker/src/main/java/worker/Worker.java b/worker/src/main/java/worker/Worker.java index 040c1a2628..7c38a4bb85 100644 --- a/worker/src/main/java/worker/Worker.java +++ b/worker/src/main/java/worker/Worker.java @@ -6,6 +6,7 @@ import org.json.JSONObject; class Worker { + public static void main(String[] args) { try { Jedis redis = connectToRedis("redis"); @@ -19,7 +20,7 @@ public static void main(String[] args) { String voterID = voteData.getString("voter_id"); String vote = voteData.getString("vote"); - System.err.printf("Processing vote for '%s' by '%s'\n", vote, voterID); + System.err.printf("Craft Processing vote for '%s' by '%s' \n", vote, voterID); updateVote(dbConn, voterID, vote); } } catch (SQLException e) {