Skip to content

Commit

Permalink
Merge pull request #26 from mohsenasm/master
Browse files Browse the repository at this point in the history
Security features: HTTPS, AUTHENTICATION, and redact docker event data before sending to the client
  • Loading branch information
mohsenasm authored Oct 17, 2023
2 parents 7f32d03 + e3ccbd6 commit 38f3d6b
Show file tree
Hide file tree
Showing 15 changed files with 3,480 additions and 453 deletions.
41 changes: 41 additions & 0 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# config from https://github.com/docker/metadata-action

name: Publish Docker image

on:
workflow_dispatch:
push:
branches:
- 'master'
- 'dev_security'
tags:
- 'v*'

jobs:
docker:
runs-on: ubuntu-latest
steps:
-
name: Checkout
uses: actions/checkout@v4
-
name: Docker meta
id: meta
uses: docker/metadata-action@v5
with:
images: mohsenasm/swarm-dashboard
-
name: Login to DockerHub
if: github.event_name != 'pull_request'
uses: docker/login-action@v3
with:
username: mohsenasm
password: ${{ secrets.DOCKERHUB_TOKEN }}
-
name: Build and push
uses: docker/build-push-action@v5
with:
context: .
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
node_modules
elm-stuff
npm-debug.log
sample-data
.DS_Store
11 changes: 9 additions & 2 deletions Components.elm
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,16 @@ serviceNode service taskAllocations node =
let
tasks =
Maybe.withDefault [] (Dict.get ( node.id, service.id ) taskAllocations)
forThisService (n, s) =
s == service.id
tasksOfThisService = List.filter forThisService (Dict.keys taskAllocations)
noTaskNowhere = List.length tasksOfThisService == 0
in
td []
[ ul [] (List.map (task service) tasks) ]
if noTaskNowhere then
td [ class "empty-service" ] []
else
td []
[ ul [] (List.map (task service) tasks) ]


serviceRow : List Node -> TaskIndex -> Networks.Connections -> Service -> Html msg
Expand Down
30 changes: 16 additions & 14 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,40 +1,42 @@
FROM node:8-alpine AS base

FROM node:10-alpine AS base
RUN apk add --update tini curl \
&& rm -r /var/cache
ENTRYPOINT ["/sbin/tini", "--"]
WORKDIR /home/node/app

FROM base AS dependencies

ENV NODE_ENV production

COPY package.json yarn.lock ./
RUN yarn install --production

# elm doesn't work under alpine 6 or 8
FROM node:6-slim AS elm-build
FROM node:10.16.0-buster-slim AS elm-build
WORKDIR /home/node/app

RUN npm install -g elm --silent

RUN npm install --unsafe-perm -g [email protected] --silent
RUN apt-get update; apt-get install -y netbase
COPY elm-package.json ./
RUN elm package install -y

COPY . .

RUN elm make Main.elm --output=client/index.js

FROM base AS release

WORKDIR /home/node/app
RUN wget -O lego_v4.14.2_linux_amd64.tar.gz https://github.com/go-acme/lego/releases/download/v4.14.2/lego_v4.14.2_linux_amd64.tar.gz \
&& tar -xzf lego_v4.14.2_linux_amd64.tar.gz \
&& mv ./lego /usr/local/bin/lego \
&& rm lego_v4.14.2_linux_amd64.tar.gz
ENV LEGO_PATH=/lego-files

COPY --from=dependencies /home/node/app/node_modules node_modules
COPY --from=elm-build /home/node/app/client/ client
COPY server server
COPY server.sh server.sh
COPY crontab /var/spool/cron/crontabs/root

HEALTHCHECK --interval=5s --timeout=3s \
CMD curl --fail http://localhost:$PORT/_health || exit 1
# HEALTHCHECK --interval=5s --timeout=3s \
# CMD curl --fail http://localhost:$PORT/_health || exit 1
# HEALTHCHECK --interval=5s --timeout=3s \
# CMD curl --insecure --fail https://localhost:$PORT/_health || exit 1

# Run under Tini
CMD ["node", "server/index.js"]
CMD ["sh", "server.sh"]
34 changes: 29 additions & 5 deletions Main.elm
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import WebSocket
import Docker.Types exposing (..)
import Docker exposing (fromJson)
import Components as UI

import Http

localWebsocket : Navigation.Location -> String
localWebsocket location =
Expand All @@ -20,46 +20,70 @@ localWebsocket location =

type alias Model =
{ webSocketUrl : String
, authToken : String
, swarm : Docker
, tasks : TaskIndex
, errors : List String
}


type Msg
= UrlChange Navigation.Location
= GetAuthToken
| AuthTokenReceived (Result Http.Error String)
| UrlChange Navigation.Location
| Receive String

authTokenGetter : Cmd Msg
authTokenGetter =
Http.send AuthTokenReceived (Http.getString "/auth_token")

init : Navigation.Location -> ( Model, Cmd Msg )
init location =
( { webSocketUrl = localWebsocket location
, authToken = ""
, swarm = Docker.empty
, tasks = Dict.empty
, errors = []
}
, Cmd.none
, authTokenGetter
)


update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
case msg of
GetAuthToken ->
( model, authTokenGetter )

AuthTokenReceived result ->
case result of
Ok authToken ->
( { model | authToken = authToken }, Cmd.none )

Err httpError ->
( { model | errors = (toString httpError) :: model.errors }, Cmd.none )

Receive serverJson ->
case fromJson serverJson of
Ok serverData ->
( { model | swarm = serverData, tasks = groupBy taskIndexKey serverData.assignedTasks }, Cmd.none )

Err error ->
( { model | errors = error :: model.errors }, Cmd.none )
if String.contains "WrongAuthToken" error then
( { model | errors = error :: model.errors }, authTokenGetter )
else
( { model | errors = error :: model.errors }, Cmd.none )

UrlChange location ->
( model, Cmd.none )


subscriptions : Model -> Sub Msg
subscriptions model =
WebSocket.listen model.webSocketUrl Receive
if String.isEmpty model.authToken then
Sub.none
else
WebSocket.listen (model.webSocketUrl ++ "?authToken=" ++ model.authToken) Receive


view : Model -> Html Msg
Expand Down
36 changes: 32 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,18 +30,34 @@ version: "3"

services:
dashboard:
image: charypar/swarm-dashboard
image: mohsenasm/swarm-dashboard
volumes:
- "/var/run/docker.sock:/var/run/docker.sock"
- "/var/run/docker.sock:/var/run/docker.sock"
- lego-files:/lego-files
ports:
- 8080:8080
- 8081:8081
environment:
PORT: 8080
PORT: 8081
ENABLE_AUTHENTICATION: "false"
# ENABLE_AUTHENTICATION: "true"
# AUTHENTICATION_REALM: "KuW2i9GdLIkql"
# USERNAME: "admin"
# PASSWORD: "supersecret"
ENABLE_HTTPS: "false"
# ENABLE_HTTPS: "true"
# HTTPS_HOSTNAME: "example.com"
# LEGO_NEW_COMMAND_ARGS: "--accept-tos [email protected] --domains=example.com --dns cloudflare run"
# LEGO_RENEW_COMMAND_ARGS: "--accept-tos [email protected] --domains=example.com --dns cloudflare renew"
# CLOUDFLARE_EMAIL: "[email protected]"
# CLOUDFLARE_API_KEY: "yourprivatecloudflareapikey"
deploy:
replicas: 1
placement:
constraints:
- node.role == manager

volumes:
lego-files:
```
and deploy with
Expand All @@ -50,6 +66,17 @@ and deploy with
$ docker stack deploy -c compose.yml svc
```

## Security

In this fork we have added some security measures:

+ We don't send the whole docker event data. The [main repo](https://github.com/charypar/swarm-dashboard) sends everything, including environment variables (someone might have stored some passwords in them, by mistake!).

+ Using the `ENABLE_AUTHENTICATION` environment variable, there is an option to use `Basic Auth`. The WebSocket server will close the connection if it does not receive a valid authentication token.

+ Using the `ENABLE_HTTPS` environment variable, there is an option to use `HTTPS` and `WSS`. We have Let’s Encrypt integration with the DNS challenge.


## Production use

There are two considerations for any serious deployment of the dashboard:
Expand All @@ -63,6 +90,7 @@ There are two considerations for any serious deployment of the dashboard:
is probably a better way to look for changes in the Swarm that could be used
in the future.


## Rough roadmap

* Show more service details (published port, image name and version)
Expand Down
7 changes: 7 additions & 0 deletions client/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,13 @@
vertical-align: top;
}

.empty-service {
background: radial-gradient(circle, transparent 25%, #ffffff 26%),linear-gradient(45deg, transparent 46%, #000000 47%, #000000 52%, transparent 53%), linear-gradient(135deg, transparent 46%, #000000 47%, #000000 52%, transparent 53%);
background-size: 1.4em 1.4em;
background-color: #ffffff;
opacity: 0.6;
}

td.networks {
position: relative;
padding: 0;
Expand Down
20 changes: 18 additions & 2 deletions compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,24 @@ services:
build: .
image: localhost:5000/dashboard
volumes:
- "/var/run/docker.sock:/var/run/docker.sock"
- "/var/run/docker.sock:/var/run/docker.sock"
- lego-files:/lego-files
ports:
- 8081:8081
- 8081:8081
environment:
PORT: 8081
ENABLE_AUTHENTICATION: "false"
# ENABLE_AUTHENTICATION: "true"
# AUTHENTICATION_REALM: "KuW2i9GdLIkql"
# USERNAME: "admin"
# PASSWORD: "supersecret"
ENABLE_HTTPS: "false"
# ENABLE_HTTPS: "true"
# HTTPS_HOSTNAME: "example.com"
# LEGO_NEW_COMMAND_ARGS: "--accept-tos [email protected] --domains=example.com --dns cloudflare run"
# LEGO_RENEW_COMMAND_ARGS: "--accept-tos [email protected] --domains=example.com --dns cloudflare renew"
# CLOUDFLARE_EMAIL: "[email protected]"
# CLOUDFLARE_API_KEY: "yourprivatecloudflareapikey"
deploy:
replicas: 1
update_config:
Expand All @@ -19,3 +32,6 @@ services:
placement:
constraints:
- node.role == manager

volumes:
lego-files:
9 changes: 9 additions & 0 deletions crontab
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# do daily/weekly/monthly maintenance
# min hour day month weekday command
*/15 * * * * run-parts /etc/periodic/15min
0 * * * * run-parts /etc/periodic/hourly
0 2 * * * run-parts /etc/periodic/daily
0 3 * * 6 run-parts /etc/periodic/weekly
0 5 1 * * run-parts /etc/periodic/monthly
*/15 * * * * run-parts /etc/periodic/15min
17 2 1 * * /usr/local/bin/lego --path $LEGO_PATH $LEGO_RENEW_COMMAND_ARGS
3 changes: 2 additions & 1 deletion elm-package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,10 @@
"dependencies": {
"elm-lang/core": "5.1.1 <= v < 6.0.0",
"elm-lang/html": "2.0.0 <= v < 3.0.0",
"elm-lang/http": "1.0.0 <= v < 2.0.0",
"elm-lang/navigation": "2.1.0 <= v < 3.0.0",
"elm-lang/svg": "2.0.0 <= v < 3.0.0",
"elm-lang/websocket": "1.0.2 <= v < 2.0.0"
},
"elm-version": "0.18.0 <= v < 0.19.0"
}
}
Loading

0 comments on commit 38f3d6b

Please sign in to comment.