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

feat: docker demo env #1627

Draft
wants to merge 12 commits into
base: trunk
Choose a base branch
from
99 changes: 99 additions & 0 deletions tools/demo-env/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
# Demo Environment

This directory will contain a completely local end-to-end demo environment of
NoPorts using docker containers and networks to isolate various components.

## atSigns

<!-- markdownlint-disable md013 -->

| Purpose / Usage | Atsign | Assigned Ports |
| -------------------------------------- | --------- | ------------------------------- |
| atDirectory | N/A | 64 |
| Supervisord (infrastructure dashboard) | N/A | 9001 |
| atServers | N/A | 25000-25019 |
| Relay 1 | @jagan🛠 | 20000-20099 |
| Relay 2 | @ashish🛠 | 20100-20199 |
| Device 1 | @alice🛠 | 20200-20299 |
| Device 2 | @bob🛠 | 20300-20399 |
| Device 3 (with APKAM) | @srie🛠 | 20400-20499 |
| Policy 1 | @kevin🛠 | 3000 (dashboard), 20500-20599 |
| Policy 2 (in a future version) | @eve🛠 | 9003 (dashboard), 20600-20699 |
| Client 1 (manager) | @colin🛠 | N/A (no container, use on host) |
| Other clients (configure in policy) | any | N/A (no container, use on host) |

### Dashboard Links

1. [Supervisord](http://localhost:9001)
2. [Policy Kevin](http://localhost:3000)

<!-- markdownlint-enable md013 -->

> Note: there are many assigned ports for this demo because we are using the
> ephemeral port range, and docker is not designed well for use with ephemeral
> ports... In the real world, you wouldn't have to do this kind of garbage.

## Usage

There are two services running inside each of the device containers:

1. openssh server
2. nginx server

The @bob🛠 container also supports remote desktop passthrough back to the host.

### Setup

#### One-time setup

Add the following entries to `/etc/hosts` on your host machine:

```txt
# Hostnames in docker that we want to loop back to
127.0.0.1 vip.ve.atsign.zone
127.0.0.1 relay1.atsign.zone
127.0.0.1 relay2.atsign.zone
```

Setup docker environment with docker compose:

```sh
docker compose up -d
```

> You can turn this off later via the docker UI or using the command:
> `docker compose down -d`

After you've started the docker environment for the first time, there will be a
docker volume containing all of the demo atSigns' atKeys files.

1. Via Docker UI (User friendly)
Go to the volumes tab and you will find a volume called
`noports-demo-environment_atkeys`, right-click the key you want and click
"Save as...", then save the key to `$HOME/.atsign/keys`.
Note: `$HOME` is _your_ home folder - this is typically the parent folder of
your `Documents` folder

2. Via Command Line (Advanced Docker users)
Use `docker container cp` to copy files from one of the containers (any
container except blank and the bootstrapper containers), the atKeys are
located at `/home/atsign/mount` on the containers.

#### Per-session utility setup

Source the client utility file:

> Do this step every time you open a new shell

```sh
source client.sh
```

### Run client

Then run `noports` to see the usage. Change the atSigns in the input to
`noports` as needed, refer to table above for options.

> `noports` will not run commands for you, but it will print commands to the
> terminal which you may copy/paste, or wrap with `$()` to run automatically
> e.g. `$(noports @colin @alice @jagan ssh)`
18 changes: 18 additions & 0 deletions tools/demo-env/bootstrapper/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
FROM alpine:3.20
SHELL ["/bin/ash", "-eo", "pipefail", "-c"]
RUN apk add --no-cache git=2.45.2-r0

WORKDIR /home/atsign

ADD "https://api.github.com/repos/atsign-foundation/at_demos/commits?per_page=1" latest_commit

RUN git clone https://github.com/atsign-foundation/at_demos.git /home/atsign/at_demos; \
mkdir -p /home/atsign/mount; \
cp /home/atsign/at_demos/packages/at_demo_data/lib/assets/atkeys/* /home/atsign/mount/;

RUN files="$(ls /home/atsign/mount)"; \
for f in ${files}; do \
mv /home/atsign/mount/"$f" /home/atsign/mount/"$(echo "$f" | sed -e 's/.atKeys/_key.atKeys/g')"; \
done

ENTRYPOINT [ "/bin/true" ]
62 changes: 62 additions & 0 deletions tools/demo-env/client.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
#!/bin/sh

__norm_atsign() {
ATSIGN="$1"
FIRST="${ATSIGN:0:1}"

if ! [ "$FIRST" = "@" ]; then
ATSIGN="@$ATSIGN"
fi

case "$ATSIGN" in
@emoji) printf "$ATSIGN🦄🛠";;
@srie|@sachin) printf "$ATSIGN" ;;
*) printf "$ATSIGN🛠" ;;
esac
}

__usage() {
echo "noports <client> <device> <relay> <service>"
echo ""
echo "<client>, <device>, and <relay> should all be specified without any emojis, they will be added for you"
echo "e.g. '@colin' or 'colin' both yield '@colin🛠'"
echo ""
echo "<service> can be one of 'ssh', 'http', 'vnc', 'rdp'"
echo
}

noports() {
if [ "$#" -ne 4 ]; then
__usage
return
fi

FROM=$(__norm_atsign "$1")
TO=$(__norm_atsign "$2")
RELAY=$(__norm_atsign "$3")
SERVICE="$4"

case $SERVICE in
ssh)
echo "sshnp --root-domain vip.ve.atsign.zone -f $FROM -t $TO -r $RELAY -u atsign -s -i $HOME/.ssh/id_ed25519"
;;
http)
echo "npt --root-domain vip.ve.atsign.zone -f $FROM -t $TO -r $RELAY -d default -p 80 -l 8080 -K"
echo "# Then connect to: http://localhost:8080" 1>&2
;;
vnc)
echo "npt --root-domain vip.ve.atsign.zone -f $FROM -t $TO -r $RELAY -d default -h host.docker.internal -p 5900 -l 59000 -K"
echo "# Then connect to: vnc://localhost:59000" 1>&2
;;
rdp)
echo "npt --root-domain vip.ve.atsign.zone -f $FROM -t $TO -r $RELAY -d default -h host.docker.internal -p 3389 -l 33899 -K"
echo "# Then connect to: rdp://localhost:33899" 1>&2
;;
*)
__usage
return
;;
esac
}


148 changes: 148 additions & 0 deletions tools/demo-env/docker-compose.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
# Good emoji: 🛠
name: "noports-demo-environment"

volumes:
atkeys:

services:
# Just a blank alpine image for running some commands
blank:
image: alpine:3.20
command: tail -f /dev/null

# Virtual environment - a fully local containerized instance with:
# - supervisord to run all services
# - atDirectory (and redis instance for the atDirectory)
# - a bunch of atServers
virtualenv:
image: atsigncompany/virtualenv:vip
hostname: vip.ve.atsign.zone
ports:
- "127.0.0.1:9001:9001" # Service dashboard
# - "127.0.0.1:6379:6379" # Redis (for atDirectory)
- "64:64" # atDirectory
- "25000-25019:25000-25019" # atServers
command:
/bin/sh -c "(sleep 5 && supervisorctl start pkamLoad) & supervisord -n"

# Bootstrapper - provides the cryptographic keys with the other containers
bootstrapper:
build: bootstrapper
volumes:
- atkeys:/home/atsign/mount
depends_on:
virtualenv:
condition: service_started

# Relays
# Note: ephemeral ports for relays must be mapped 1:1
# e.g. 8000:8000 is GOOD
# e.g. 8000:9000 is BAD, since the relay response will resolve to the wrong port
relay-jagan:
build: srvd
volumes:
- atkeys:/home/atsign/mount
depends_on:
bootstrapper:
condition: service_completed_successfully
restart: unless-stopped
sysctls:
net.ipv4.ip_local_port_range: "20000 20099"
ports:
- "20000-20099:20000-20099"
hostname: relay1.atsign.zone
environment:
ATSIGN: "@jagan🛠"
ARGS: "--root-domain vip.ve.atsign.zone"

relay-ashish:
build: srvd
volumes:
- atkeys:/home/atsign/mount
depends_on:
bootstrapper:
condition: service_completed_successfully
restart: unless-stopped
sysctls:
net.ipv4.ip_local_port_range: "20100 20199"
ports:
- "20100-20199:20100-20199"
hostname: relay2.atsign.zone
environment:
ATSIGN: "@ashish🛠"
ARGS: "--root-domain vip.ve.atsign.zone"

# Daemons - ephemeral ports can be mapped however you like
device-alice:
build: sshnpd
volumes:
- atkeys:/home/atsign/mount
depends_on:
bootstrapper:
condition: service_completed_successfully
restart: unless-stopped
sysctls:
net.ipv4.ip_local_port_range: "20000 20099"
ports:
- "2201:22"
- "8001:80"
- "20200-20299:20000-20099"
environment:
ATSIGN: "@alice🛠"
MANAGER: "@colin🛠"
POLICY: "@kevin🛠"
ARGS:
"--root-domain vip.ve.atsign.zone --po
localhost:22,127.0.0.1:22,localhost:80,127.0.0.1:80 -sv"

device-bob:
build: sshnpd
volumes:
- atkeys:/home/atsign/mount
depends_on:
bootstrapper:
condition: service_completed_successfully
restart: unless-stopped
sysctls:
net.ipv4.ip_local_port_range: "20000 20099"
ports:
- "20300-20399:20000-20099"
environment:
ATSIGN: "@bob🛠"
MANAGER: "@colin🛠"
POLICY: "@kevin🛠"
ARGS:
"--root-domain vip.ve.atsign.zone --po
localhost:22,127.0.0.1:22,localhost:80,127.0.0.1:80,host.docker.internal:5900,host.docker.internal:3389
-sv"

# Policy
#
policy-kevin:
build: policy
volumes:
- atkeys:/home/atsign/mount
depends_on:
bootstrapper:
condition: service_completed_successfully
restart: unless-stopped
sysctls:
net.ipv4.ip_local_port_range: "20000 20099"
ports:
- "3000:3000"
- "20500-20599:20000-20099"
environment:
ATSIGN: "@kevin🛠"
ARGS: "--root-domain vip.ve.atsign.zone -v"
API_ARGS: "--root-domain vip.ve.atsign.zone --namespace sshnp"

# This will run before the policy api is ready thus nothing will until it is
# re-run manually, this is intentional
policy-kevin-bootstrapper:
build:
context: policy
dockerfile: ./Dockerfile.bootstrapper
restart: "no"
environment:
HOST: host.docker.internal
PORT: 3000
37 changes: 37 additions & 0 deletions tools/demo-env/policy/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
FROM alpine:3.20
SHELL ["/bin/ash", "-eo", "pipefail", "-c"]

ARG BASE_URL=https://github.com/atsign-foundation/noports/releases/download
ARG RELEASE=v5.7.0-alpha-5
ARG ENTRYPOINT=entrypoint.sh

WORKDIR /home/atsign
ENV HOME=/home/atsign
ENV USER=atsign

COPY ./$ENTRYPOINT /home/atsign/entrypoint.sh

# hadolint ignore=DL3018
RUN apk add --no-cache libc6-compat bind-tools ca-certificates; \
adduser -D -H atsign; \
# activate atsign account
echo "atsign:*" | chpasswd -e; \
chown -R atsign /home/atsign; \
chmod u+x /home/atsign/entrypoint.sh

USER atsign
RUN case $(uname -m) in \
aarch64) ARCH="arm64";; \
*) ARCH="x64";; \
esac; \
wget -q -O /home/atsign/sshnp.tgz "$BASE_URL/$RELEASE/sshnp-linux-$ARCH.tgz"; \
tar -xf /home/atsign/sshnp.tgz;

RUN \
cp -R /home/atsign/sshnp/web /home/atsign/web; \
cp /home/atsign/sshnp/np_admin /home/atsign/np_admin; \
cp /home/atsign/sshnp/npp_atserver /home/atsign/npp_atserver; \
mkdir -p /home/atsign/.atsign/keys;

ENTRYPOINT [ "/home/atsign/entrypoint.sh" ]

9 changes: 9 additions & 0 deletions tools/demo-env/policy/Dockerfile.bootstrapper
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
FROM alpine:3.20

WORKDIR /atsign

COPY ./bootstrap.sh .

RUN chmod u+x /atsign/bootstrap.sh

ENTRYPOINT [ "/atsign/bootstrap.sh" ]
16 changes: 16 additions & 0 deletions tools/demo-env/policy/bootstrap.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
#!/bin/sh
create_api_group() {
wget -O- --post-data "$1" \
--header='Content-Type:application/json' \
"http://$HOST:$PORT/api/policy/group"
}

# TODO: Multiple bob/alice devices, with different groups
# roles around groups
if [ -f /second_run ]; then
create_api_group '{"name":"Barbara","description":"Full access to alice","daemonAtSigns":["@alice🛠"],"devices":[{"name":"default","permitOpens":["localhost:*"]}],"deviceGroups":[],"userAtSigns":["@barbara🛠"]}'
create_api_group '{"name":"Murali","description":"Remote desktop access only","daemonAtSigns":["@bob🛠"],"devices":[{"name":"default","permitOpens":["*:5900","*:3389"]}],"deviceGroups":[{"name":"device group name","permitOpens":[]}],"userAtSigns":["@murali🛠"]}'
create_api_group '{"name":"Developers","description":"SSH access","daemonAtSigns":["@alice🛠","@bob🛠"],"devices":[{"name":"default","permitOpens":["localhost:22"]}],"deviceGroups":[],"userAtSigns":["@sitaram🛠","@purnima🛠"]}'
else
touch /second_run
fi
Loading
Loading