Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Hardening of the Frontend docker image #377

Merged
merged 11 commits into from
Aug 26, 2021
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -24,5 +24,4 @@ npm-debug.log*
yarn-debug.log*
yarn-error.log*
.vscode
env-config.js
chevdor marked this conversation as resolved.
Show resolved Hide resolved
.nyc
13 changes: 13 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,19 @@ If you'd like to get things runing manually using Docker, you can do the followi

**NOTE:** Here we used `SUBSTRATE_TELEMETRY_URL=ws://localhost:8000/feed`. This will work if you test with everything running locally on your machine but NOT if your backend runs on a remote server. Keep in mind that the frontend docker image is serving a static site running your browser. The `SUBSTRATE_TELEMETRY_URL` is the WebSocket url that your browser will use to reach the backend. Say your backend runs on a remote server at `foo.example.com`, you will need to set the IP/url accordingly in `SUBSTRATE_TELEMETRY_URL` (in this case, to `ws://foo.example.com/feed`).

**NOTE:** Running the frontend container in *read-only* mode reduces attack surface that could be used to exploit
a container. It requires however a little more effort and mounting additionnal volumes as shown below:

```
docker run --rm -it -p 80:8000 --name frontend \
-e SUBSTRATE_TELEMETRY_URL=ws://localhost:8000/feed \
--tmpfs /var/cache/nginx:uid=101,gid=101 \
--tmpfs /var/run:uid=101,gid=101 \
--tmpfs /app/tmp:uid=101,gid=101 \
--read-only \
parity/substrate-telemetry-frontend
```

With these running, you'll be able to navigate to [http://localhost:3000](http://localhost:3000) to view the UI. If you'd like to connect a node and have it send telemetry to your running shard, you can run the following:

```sh
Expand Down
5 changes: 5 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,11 @@ services:
build:
dockerfile: Dockerfile
context: ./backend/
read_only: true
tmpfs:
- /var/cache/nginx:uid=101,gid=101
- /var/run:uid=101,gid=101
- /app/tmp:uid=101,gid=101
chevdor marked this conversation as resolved.
Show resolved Hide resolved
command: [
'telemetry_shard',
'--listen', '0.0.0.0:8001',
Expand Down
25 changes: 17 additions & 8 deletions frontend/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,32 +1,41 @@
#### BUILDER IMAGE ####
FROM docker.io/node:12 as builder
LABEL maintainer="Chevdor <chevdor@gmail.com>"
LABEL description="Polkadot Telemetry frontend builder image"
LABEL description="Substrate Telemetry Frontend builder image"

WORKDIR /opt/builder

COPY . .

RUN yarn install && \
yarn build && \
yarn cache clean

#### OUTPUT IMAGE ####
FROM docker.io/nginx:stable-alpine
LABEL maintainer="Chevdor <chevdor@gmail.com>"
LABEL description="Polkadot Telemetry frontend"
LABEL description="Substrate Telemetry Frontend"

# Each time this container is ran, the value that's provided for this env var
# determines where the frontend will try to request feed information from:
ENV SUBSTRATE_TELEMETRY_URL=

WORKDIR /usr/share/nginx/html

COPY --from=builder /opt/builder/env.sh /usr/bin/
RUN apk add --no-cache bash; chmod +x /usr/bin/env.sh
WORKDIR /app

COPY --from=builder /opt/builder/scripts/*.sh /usr/local/bin/
COPY --from=builder /opt/builder/build /app
COPY --from=builder /opt/builder/nginx/nginx.conf /etc/nginx/nginx.conf
COPY --from=builder /opt/builder/build /usr/share/nginx/html

RUN apk add --no-cache bash && \
chown -R nginx:nginx /app && \
chown -R nginx:nginx /var/cache/nginx && \
chown -R nginx:nginx /var/log/nginx && \
chown -R nginx:nginx /etc/nginx/conf.d && \
touch /var/run/nginx.pid && \
chown -R nginx:nginx /var/run/nginx.pid

# UID= 101
USER nginx
EXPOSE 8000

CMD ["/bin/bash", "-c", "/usr/bin/env.sh && nginx -g \"daemon off;\""]
CMD ["/usr/local/bin/start.sh"]
5 changes: 2 additions & 3 deletions frontend/nginx/nginx.conf
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
user nginx;
worker_processes auto;
worker_rlimit_nofile 30000;

Expand All @@ -19,13 +18,13 @@ http {

access_log /var/log/nginx/access.log main;
sendfile on;
#tcp_nopush on;

keepalive_timeout 65;
gzip on;
include /etc/nginx/conf.d/*.conf;

server {
root /usr/share/nginx/html;
root /app;
index index.html;
listen 8000;
listen [::]:8000;
Expand Down
2 changes: 1 addition & 1 deletion frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
"eject": "react-scripts-ts eject",
"pretty:check": "prettier --check src/**/*.{ts,tsx}",
"pretty:fix": "prettier --write src",
"clean": "rm -rf node_modules build .nyc env-config.js report*.json yarn-error.log"
"clean": "rm -rf node_modules build .nyc ./tmp/env-config.js report*.json yarn-error.log"
},
"dependencies": {
"@polkadot/util-crypto": "^2.8.1",
Expand Down
2 changes: 1 addition & 1 deletion frontend/public/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
<meta name="theme-color" content="#000000">
<!-- <link rel="manifest" href="%PUBLIC_URL%/manifest.json"> -->
<title>Polkadot Telemetry</title>
<script type="text/javascript" src="/env-config.js"></script>
<script type="text/javascript" src="/tmp/env-config.js"></script>
<style>
body, html {
background: #fff;
Expand Down
8 changes: 6 additions & 2 deletions frontend/env.sh → frontend/scripts/env.sh
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
#!/bin/bash
#!/usr/bin/env bash

# This script is used when the docker container starts and does the magic to
# bring the ENV variables to the generated static UI.

TARGET=./env-config.js
ENV_DIR=./tmp
mkdir -p "$ENV_DIR"
TARGET="$ENV_DIR/env-config.js"

# Recreate config file
echo -n > $TARGET
Expand All @@ -18,3 +20,5 @@ for VAR in ${vars[@]}; do
echo " $VAR: \"${!VAR}\"," >> $TARGET
done
echo "}" >> $TARGET

chmod 440 $TARGET
15 changes: 15 additions & 0 deletions frontend/scripts/start.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
#!/usr/bin/env sh

ENV_CONFIG=/app/tmp/env-config.js

if test -f $ENV_CONFIG; then
echo Config is locked
else
echo Generate env-config script...
/usr/local/bin/env.sh
echo done
chmod 444 $ENV_CONFIG
fi

echo Starting nginx...
nginx -g "daemon off;"
29 changes: 23 additions & 6 deletions scripts/build-docker-frontend.sh
Original file line number Diff line number Diff line change
@@ -1,11 +1,28 @@
#!/usr/bin/env bash
set -e

cd `git rev-parse --show-toplevel`
pushd "$(git rev-parse --show-toplevel)/frontend" > /dev/null

IMAGE=telemetry-frontend
DOCKER_USER=${DOCKER_USER:-paritytech}
while getopts ":Nsgapv:" arg; do
case "${arg}" in
p)
PUBLISH="true"
;;
esac
done

IMAGE=substrate-telemetry-frontend
DOCKER_USER=${DOCKER_USER:-paritytech}
echo "Publishing $IMAGE as $DOCKER_USER"

docker build -t $IMAGE -f packages/frontend/Dockerfile .
docker tag $IMAGE $DOCKER_USER/$IMAGE
docker push $DOCKER_USER/$IMAGE
docker build -t $DOCKER_USER/$IMAGE -f ./Dockerfile .

if [[ "$PUBLISH" = 'true' ]]; then
docker push $DOCKER_USER/$IMAGE
else
echo 'No -p passed, skipping publishing to docker hub'
fi

popd > /dev/null

docker images | grep $IMAGE