├── .gitignore
├── version.txt
├── ubuntu-auto-install
├── meta-data
├── vendor-data
├── user-data
└── README.md
├── ripe-app-frontend
├── .gitignore
├── app
│ ├── workspaces
│ │ ├── 404
│ │ │ ├── index.html
│ │ │ └── index.js
│ │ ├── network
│ │ │ ├── index.html
│ │ │ ├── index.js
│ │ │ └── network_diagram.svg
│ │ ├── legal
│ │ │ ├── index.html
│ │ │ └── index.js
│ │ ├── admin
│ │ │ ├── index.js
│ │ │ └── index.html
│ │ └── dashboard
│ │ │ ├── index.js
│ │ │ └── index.html
│ ├── components
│ │ ├── scratch-pad.html
│ │ ├── vm-console.html
│ │ ├── vm-console.js
│ │ └── scratch-pad.js
│ ├── styles
│ │ └── styles.css
│ ├── index.js
│ ├── menu.json
│ └── router.js
├── build
│ ├── app_workspaces_404_index_html.bundle.js
│ ├── app_workspaces_404_index_js.bundle.js
│ ├── app_workspaces_legal_index_js.bundle.js
│ ├── app_workspaces_network_index_js.bundle.js
│ ├── app_workspaces_legal_index_html.bundle.js
│ ├── app_workspaces_admin_index_js.bundle.js
│ ├── app_components_scratch-pad_html.bundle.js
│ ├── app_workspaces_dashboard_index_js.bundle.js
│ ├── app_workspaces_admin_index_html.bundle.js
│ ├── app_workspaces_network_index_html.bundle.js
│ ├── app_components_vm-console_js.bundle.js
│ ├── app_components_scratch-pad_js.bundle.js
│ ├── app_components_vm-console_html.bundle.js
│ ├── app_workspaces_dashboard_index_html.bundle.js
│ ├── main.css
│ ├── index.html
│ └── 38f376c724439f780378.svg
├── README.md
├── package.json
├── webpack.config.js
└── html
│ └── index.html
├── ansible
├── files
│ ├── nginx
│ │ ├── connection_upgrade.conf
│ │ └── ttydconsole.conf
│ ├── tmux.conf
│ └── lxd_preseed.yaml
├── templates
│ └── systemd
│ │ ├── ttyd-container.target
│ │ ├── ttyd-container.generator
│ │ ├── ttyd-admin.service
│ │ └── ttyd-container@.service
├── inventory.ini
└── site.yaml
├── .github
└── workflows
│ ├── release-please.yml
│ └── vagrant-build.yml
├── Vagrantfile-in-box
├── Vagrantfile
├── README.md
└── CHANGELOG.md
/.gitignore:
--------------------------------------------------------------------------------
1 | .vagrant
2 |
--------------------------------------------------------------------------------
/version.txt:
--------------------------------------------------------------------------------
1 | 0.20240816.0
2 |
--------------------------------------------------------------------------------
/ubuntu-auto-install/meta-data:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/ubuntu-auto-install/vendor-data:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/ripe-app-frontend/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules
3 |
--------------------------------------------------------------------------------
/ripe-app-frontend/app/workspaces/404/index.html:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/ripe-app-frontend/app/workspaces/network/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/ansible/files/nginx/connection_upgrade.conf:
--------------------------------------------------------------------------------
1 | map $http_upgrade $connection_upgrade {
2 | default upgrade;
3 | '' close;
4 | }
5 |
--------------------------------------------------------------------------------
/ansible/templates/systemd/ttyd-container.target:
--------------------------------------------------------------------------------
1 | [Unit]
2 | Description=ttyd-container target
3 |
4 | [Install]
5 | WantedBy=default.target
6 |
--------------------------------------------------------------------------------
/ansible/inventory.ini:
--------------------------------------------------------------------------------
1 | default ansible_connection=local
2 |
3 | [containers]
4 | hostA
5 | hostB
6 | hostC
7 |
8 | [containers:vars]
9 | ansible_connection=lxd
10 |
11 | [all:vars]
12 | ansible_python_interpreter=/usr/bin/python3
13 |
--------------------------------------------------------------------------------
/ansible/files/tmux.conf:
--------------------------------------------------------------------------------
1 | #unbind-key -a
2 | #set-option -g status off
3 | set-option -g set-titles on
4 | set-option -g set-titles-string "#S"
5 | set-option -g terminal-overrides "xterm*:smcup@:rmcup@"
6 | set-option -s default-terminal "screen-256color"
7 |
--------------------------------------------------------------------------------
/ripe-app-frontend/app/workspaces/legal/index.html:
--------------------------------------------------------------------------------
1 |
10 |
13 |
--------------------------------------------------------------------------------
/ripe-app-frontend/app/workspaces/404/index.js:
--------------------------------------------------------------------------------
1 | export const loadWorkSpace = async () => {
2 | const wsElm = document.querySelector("div#csr-workspace");
3 |
4 | // put template in the DOM first.
5 | const wsContent = await import("./index.html");
6 | wsElm.innerHTML = `${wsContent.default}`;
7 | };
8 |
--------------------------------------------------------------------------------
/ripe-app-frontend/app/workspaces/legal/index.js:
--------------------------------------------------------------------------------
1 | export const loadWorkSpace = async () => {
2 | const wsElm = document.querySelector("div#csr-workspace");
3 |
4 | // put template in the DOM first.
5 | const wsContent = await import("./index.html");
6 | wsElm.innerHTML = `${wsContent.default}`;
7 | };
8 |
--------------------------------------------------------------------------------
/ripe-app-frontend/app/workspaces/network/index.js:
--------------------------------------------------------------------------------
1 | export const loadWorkSpace = async () => {
2 | const wsElm = document.querySelector("div#csr-workspace");
3 |
4 | // put template in the DOM first.
5 | const wsContent = await import("./index.html");
6 | wsElm.innerHTML = wsContent.default;
7 | };
8 |
--------------------------------------------------------------------------------
/ripe-app-frontend/build/app_workspaces_404_index_html.bundle.js:
--------------------------------------------------------------------------------
1 | "use strict";(self.webpackChunkworkbench_shell=self.webpackChunkworkbench_shell||[]).push([["app_workspaces_404_index_html"],{"./app/workspaces/404/index.html":(e,s,n)=>{n.r(s),n.d(s,{default:()=>h});const h="\n"}}]);
--------------------------------------------------------------------------------
/ripe-app-frontend/app/workspaces/admin/index.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | export const loadWorkSpace = async () => {
4 | await import("../../components/vm-console");
5 | const wsElm = document.querySelector("div#csr-workspace");
6 | const wsContent = await import("./index.html");
7 | wsElm.innerHTML = `${wsContent.default}`;
8 | };
9 |
--------------------------------------------------------------------------------
/ripe-app-frontend/app/workspaces/dashboard/index.js:
--------------------------------------------------------------------------------
1 | export const loadWorkSpace = async () => {
2 | await import("../../components/vm-console");
3 | await import("../../components/scratch-pad");
4 | const wsElm = document.querySelector("div#csr-workspace");
5 | const wsContent = await import("./index.html");
6 | wsElm.innerHTML = `${wsContent.default}`;
7 | };
8 |
9 |
--------------------------------------------------------------------------------
/.github/workflows/release-please.yml:
--------------------------------------------------------------------------------
1 | name: Release Please
2 |
3 | on:
4 | push:
5 | branches: [ main ]
6 | workflow_dispatch:
7 |
8 | jobs:
9 | release-please:
10 | runs-on: ubuntu-latest
11 | steps:
12 | - uses: GoogleCloudPlatform/release-please-action@v2
13 | with:
14 | release-type: simple
15 | package-name: ipv6-security-lab
16 |
--------------------------------------------------------------------------------
/ansible/templates/systemd/ttyd-container.generator:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | set -eu
3 |
4 | GENDIR="$1"
5 | WANTDIR="$1/ttyd-container.target.wants"
6 | SERVICEFILE="/etc/systemd/system/ttyd-container@.service"
7 |
8 | mkdir -p "$WANTDIR"
9 |
10 | for INSTANCE in {% for host in groups['containers'] %}{{host}} {% endfor %}; do
11 | ln -s "$SERVICEFILE" "$WANTDIR/ttyd-container@$INSTANCE.service"
12 | done
13 |
--------------------------------------------------------------------------------
/ripe-app-frontend/app/components/scratch-pad.html:
--------------------------------------------------------------------------------
1 |
12 |
Scratchpad
13 |
14 |
--------------------------------------------------------------------------------
/ansible/templates/systemd/ttyd-admin.service:
--------------------------------------------------------------------------------
1 | [Unit]
2 | Description=ttyd admin instance
3 |
4 | [Service]
5 | User={{ ansible_env.SUDO_USER or ansible_user_id }}
6 | Group=www-data
7 | RuntimeDirectory=ttyd-admin
8 | UMask=0002
9 | ExecStart=/opt/ttyd -i /run/ttyd-admin/admin.sock -t fontSize=14 -t disableLeaveAlert=true tmux -2 new -A -D -s admin
10 | Type=simple
11 | Restart=always
12 |
13 | [Install]
14 | WantedBy=default.target
15 |
--------------------------------------------------------------------------------
/ripe-app-frontend/build/app_workspaces_404_index_js.bundle.js:
--------------------------------------------------------------------------------
1 | "use strict";(self.webpackChunkworkbench_shell=self.webpackChunkworkbench_shell||[]).push([["app_workspaces_404_index_js"],{"./app/workspaces/404/index.js":(e,s,a)=>{a.r(s),a.d(s,{loadWorkSpace:()=>c});const c=async()=>{const e=document.querySelector("div#csr-workspace"),s=await a.e("app_workspaces_404_index_html").then(a.bind(a,"./app/workspaces/404/index.html"));e.innerHTML=`${s.default}`}}}]);
--------------------------------------------------------------------------------
/ripe-app-frontend/build/app_workspaces_legal_index_js.bundle.js:
--------------------------------------------------------------------------------
1 | "use strict";(self.webpackChunkworkbench_shell=self.webpackChunkworkbench_shell||[]).push([["app_workspaces_legal_index_js"],{"./app/workspaces/legal/index.js":(e,s,a)=>{a.r(s),a.d(s,{loadWorkSpace:()=>l});const l=async()=>{const e=document.querySelector("div#csr-workspace"),s=await a.e("app_workspaces_legal_index_html").then(a.bind(a,"./app/workspaces/legal/index.html"));e.innerHTML=`${s.default}`}}}]);
--------------------------------------------------------------------------------
/ripe-app-frontend/build/app_workspaces_network_index_js.bundle.js:
--------------------------------------------------------------------------------
1 | "use strict";(self.webpackChunkworkbench_shell=self.webpackChunkworkbench_shell||[]).push([["app_workspaces_network_index_js"],{"./app/workspaces/network/index.js":(e,s,n)=>{n.r(s),n.d(s,{loadWorkSpace:()=>r});const r=async()=>{const e=document.querySelector("div#csr-workspace"),s=await n.e("app_workspaces_network_index_html").then(n.bind(n,"./app/workspaces/network/index.html"));e.innerHTML=s.default}}}]);
--------------------------------------------------------------------------------
/ripe-app-frontend/build/app_workspaces_legal_index_html.bundle.js:
--------------------------------------------------------------------------------
1 | "use strict";(self.webpackChunkworkbench_shell=self.webpackChunkworkbench_shell||[]).push([["app_workspaces_legal_index_html"],{"./app/workspaces/legal/index.html":(e,l,n)=>{n.r(l),n.d(l,{default:()=>s});const s='\n\n'}}]);
--------------------------------------------------------------------------------
/ansible/templates/systemd/ttyd-container@.service:
--------------------------------------------------------------------------------
1 | [Unit]
2 | Description=ttyd-container instance %I
3 |
4 | [Service]
5 | User={{ ansible_env.SUDO_USER or ansible_user_id }}
6 | Group=www-data
7 | RuntimeDirectory=ttyd-container
8 | UMask=0002
9 | ExecStart=/opt/ttyd -i /run/ttyd-container/%i.sock -t fontSize=14 -t disableLeaveAlert=true tmux -2 -L %i set -g default-command "lxc exec %i -t -- bash" \; new -A -D -s %i
10 | Type=simple
11 | Restart=always
12 |
13 | [Install]
14 | WantedBy=default.target
15 |
--------------------------------------------------------------------------------
/ripe-app-frontend/build/app_workspaces_admin_index_js.bundle.js:
--------------------------------------------------------------------------------
1 | "use strict";(self.webpackChunkworkbench_shell=self.webpackChunkworkbench_shell||[]).push([["app_workspaces_admin_index_js"],{"./app/workspaces/admin/index.js":(e,n,s)=>{s.r(n),s.d(n,{loadWorkSpace:()=>a});const a=async()=>{await s.e("app_components_vm-console_js").then(s.t.bind(s,"./app/components/vm-console.js",23));const e=document.querySelector("div#csr-workspace"),n=await s.e("app_workspaces_admin_index_html").then(s.bind(s,"./app/workspaces/admin/index.html"));e.innerHTML=`${n.default}`}}}]);
--------------------------------------------------------------------------------
/ripe-app-frontend/app/workspaces/admin/index.html:
--------------------------------------------------------------------------------
1 |
15 |
16 |
17 | Admin console
18 |
19 |
20 |
--------------------------------------------------------------------------------
/ripe-app-frontend/build/app_components_scratch-pad_html.bundle.js:
--------------------------------------------------------------------------------
1 | "use strict";(self.webpackChunkworkbench_shell=self.webpackChunkworkbench_shell||[]).push([["app_components_scratch-pad_html"],{"./app/components/scratch-pad.html":(e,t,n)=>{n.r(t),n.d(t,{default:()=>a});const a='\nScratchpad \n\n'}}]);
--------------------------------------------------------------------------------
/ripe-app-frontend/README.md:
--------------------------------------------------------------------------------
1 | How to compile HTML frontend
2 | ============================
3 |
4 | **WARNING:** The build process depends on internal RIPE NCC components.
5 | Do not attempt to build it outside RIPE NCC, you might accidentally inject
6 | malicious code from a public repository of the same name.
7 |
8 |
9 | 1. Install [Node.js](https://nodejs.org/)
10 | 2. Setup **internal components registry** path in `~/.npmrc`
11 | 3. Run `npm install`
12 | 4. Run development server on port 4042 by running `npm run start`. It will
13 | proxy console access to `http://localhost:8080/`.
14 | 5. Build a new version by running `npm run build`
15 |
--------------------------------------------------------------------------------
/ripe-app-frontend/build/app_workspaces_dashboard_index_js.bundle.js:
--------------------------------------------------------------------------------
1 | "use strict";(self.webpackChunkworkbench_shell=self.webpackChunkworkbench_shell||[]).push([["app_workspaces_dashboard_index_js"],{"./app/workspaces/dashboard/index.js":(s,a,e)=>{e.r(a),e.d(a,{loadWorkSpace:()=>p});const p=async()=>{await e.e("app_components_vm-console_js").then(e.t.bind(e,"./app/components/vm-console.js",23)),await e.e("app_components_scratch-pad_js").then(e.t.bind(e,"./app/components/scratch-pad.js",23));const s=document.querySelector("div#csr-workspace"),a=await e.e("app_workspaces_dashboard_index_html").then(e.bind(e,"./app/workspaces/dashboard/index.html"));s.innerHTML=`${a.default}`}}}]);
--------------------------------------------------------------------------------
/ripe-app-frontend/build/app_workspaces_admin_index_html.bundle.js:
--------------------------------------------------------------------------------
1 | "use strict";(self.webpackChunkworkbench_shell=self.webpackChunkworkbench_shell||[]).push([["app_workspaces_admin_index_html"],{"./app/workspaces/admin/index.html":(n,e,s)=>{s.r(e),s.d(e,{default:()=>o});const o=' \n \n'}}]);
--------------------------------------------------------------------------------
/ansible/files/lxd_preseed.yaml:
--------------------------------------------------------------------------------
1 | networks:
2 | - type: bridge
3 | config:
4 | ipv4.address: 192.0.2.1/24
5 | ipv4.nat: true
6 | ipv6.address: 2001:db8:f:1::1/64
7 | ipv6.nat: true
8 | ipv6.dhcp: false
9 | raw.dnsmasq: ra-param=lxdbr0,120
10 | bridge.hwaddr: "00:16:3e:ee:00:01"
11 | description: "Default network"
12 | name: lxdbr0
13 | storage_pools:
14 | - config:
15 | size: 15GB
16 | description: ""
17 | name: default
18 | driver: btrfs
19 | profiles:
20 | - config: {}
21 | description: ""
22 | devices:
23 | eth0:
24 | name: eth0
25 | nictype: bridged
26 | parent: lxdbr0
27 | type: nic
28 | root:
29 | path: /
30 | pool: default
31 | type: disk
32 | name: default
33 |
--------------------------------------------------------------------------------
/Vagrantfile-in-box:
--------------------------------------------------------------------------------
1 | # -*- mode: ruby -*-
2 | # vi: set ft=ruby :
3 |
4 | Vagrant.configure("2") do |config|
5 | # Create a forwarded port mapping which allows access to a specific port
6 | # within the machine from a port on the host machine and only allow access
7 | # via 127.0.0.1 to disable public access
8 | config.vm.network "forwarded_port", guest: 80, host: 8080, host_ip: "127.0.0.1", id: "web"
9 |
10 | # Disable shared folder to avoid scary warning
11 | config.vm.synced_folder ".", "/vagrant", disabled: true
12 |
13 | # Workaround issue with Windows, see:
14 | # https://github.com/hashicorp/vagrant/issues/8604
15 | config.vm.provider "virtualbox" do |vb|
16 | vb.customize [ "modifyvm", :id, "--uartmode1", "disconnected" ]
17 | end
18 | end
19 |
--------------------------------------------------------------------------------
/ripe-app-frontend/build/app_workspaces_network_index_html.bundle.js:
--------------------------------------------------------------------------------
1 | "use strict";(self.webpackChunkworkbench_shell=self.webpackChunkworkbench_shell||[]).push([["app_workspaces_network_index_html"],{"./app/workspaces/network/index.html":(e,s,t)=>{t.r(s),t.d(s,{default:()=>a});var n=t("./node_modules/html-loader/dist/runtime/getUrl.js"),r=t.n(n),o=new URL(t("./app/workspaces/network/network_diagram.svg"),t.b);const a='\n \n \n'},"./node_modules/html-loader/dist/runtime/getUrl.js":e=>{e.exports=function(e,s){return s||(s={}),e?(e=String(e.__esModule?e.default:e),s.hash&&(e+=s.hash),s.maybeNeedQuotes&&/[\t\n\f\r "'=<>`]/.test(e)?'"'.concat(e,'"'):e):e}},"./app/workspaces/network/network_diagram.svg":(e,s,t)=>{e.exports=t.p+"38f376c724439f780378.svg"}}]);
--------------------------------------------------------------------------------
/ripe-app-frontend/build/app_components_vm-console_js.bundle.js:
--------------------------------------------------------------------------------
1 | "use strict";(self.webpackChunkworkbench_shell=self.webpackChunkworkbench_shell||[]).push([["app_components_vm-console_js"],{"./app/components/vm-console.js":(e,t,o)=>{if(!customElements.get("vm-console")){let e=document.createElement("template");o.e("app_components_vm-console_html").then(o.bind(o,"./app/components/vm-console.html")).then((t=>{e.innerHTML=`${t.default}`,customElements.define("vm-console",class extends HTMLElement{constructor(){super();const t=e.content.cloneNode(!0),o=t.querySelector("iframe");t.getElementById("refresher").onclick=()=>(o.contentDocument.location.reload(),!1),this.attachShadow({mode:"open"}).appendChild(t)}static get observedAttributes(){return["src"]}attributeChangedCallback(e,t,o){"src"==e&&(this.shadowRoot.getElementById("popout").href=o,this.shadowRoot.querySelector("iframe").src=o)}})}))}}}]);
--------------------------------------------------------------------------------
/ripe-app-frontend/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "workbench-shell",
3 | "version": "0.1",
4 | "description": "The locally-installed RIPE NCC Workbench",
5 | "main": "index.js",
6 | "scripts": {
7 | "start": "webpack serve --progress --port 4042",
8 | "build:clean": "rimraf build/*",
9 | "build": "webpack"
10 | },
11 | "keywords": [
12 | "RIPE NCC",
13 | "Workbench"
14 | ],
15 | "author": "RIPE NCC L&D",
16 | "license": "ISC",
17 | "dependencies": {
18 | "@technical-design/ripe-app-webcomponents": "^1.5.3",
19 | "copy-webpack-plugin": "^9.0.1",
20 | "css-loader": "^6.2.0",
21 | "html-loader": "^2.1.2",
22 | "mini-css-extract-plugin": "^2.2.0",
23 | "style-loader": "^3.2.1",
24 | "webpack": "^5.50.0",
25 | "webpack-dev-server": "^4.3.1"
26 | },
27 | "devDependencies": {
28 | "css-minimizer-webpack-plugin": "^3.0.2",
29 | "webpack-cli": "^4.8.0"
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/ripe-app-frontend/app/components/vm-console.html:
--------------------------------------------------------------------------------
1 |
27 |
34 |
--------------------------------------------------------------------------------
/ripe-app-frontend/build/app_components_scratch-pad_js.bundle.js:
--------------------------------------------------------------------------------
1 | "use strict";(self.webpackChunkworkbench_shell=self.webpackChunkworkbench_shell||[]).push([["app_components_scratch-pad_js"],{"./app/components/scratch-pad.js":(e,t,s)=>{if(!customElements.get("scratch-pad")){let e=document.createElement("template");s.e("app_components_scratch-pad_html").then(s.bind(s,"./app/components/scratch-pad.html")).then((t=>{e.innerHTML=`${t.default}`,customElements.define("scratch-pad",class extends HTMLElement{constructor(){super();const t=e.content.cloneNode(!0);this.scratchPad=t.querySelector("textarea"),this.attachShadow({mode:"open"}).appendChild(t),this.storage_key="ripencc-workbench-scratchpad",this.load_notes()}static get observedAttributes(){return["id"]}attributeChangedCallback(e,t,s){"id"==e&&(this.storage_key=s,this.load_notes())}load_notes(){const e=sessionStorage.getItem(this.storage_key);null!=e&&(this.scratchPad.value=e),this.scratchPad.onchange=()=>{sessionStorage.setItem(this.storage_key,this.scratchPad.value)}}})}))}}}]);
--------------------------------------------------------------------------------
/ripe-app-frontend/build/app_components_vm-console_html.bundle.js:
--------------------------------------------------------------------------------
1 | "use strict";(self.webpackChunkworkbench_shell=self.webpackChunkworkbench_shell||[]).push([["app_components_vm-console_html"],{"./app/components/vm-console.html":(n,e,o)=>{o.r(e),o.d(e,{default:()=>i});const i='\n\n'}}]);
--------------------------------------------------------------------------------
/ripe-app-frontend/app/components/vm-console.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | if (!customElements.get("vm-console")) {
4 | let vcTemplate = document.createElement("template");
5 | import("./vm-console.html")
6 | .then((content) => {
7 | vcTemplate.innerHTML = `${content.default}`
8 | customElements.define(
9 | "vm-console",
10 | class extends HTMLElement {
11 | constructor() {
12 | super();
13 | const vcClone = vcTemplate.content.cloneNode(true);
14 | const vcIframe = vcClone.querySelector("iframe");
15 | vcClone.getElementById("refresher").onclick = () => {
16 | vcIframe.contentDocument.location.reload();
17 | return false;
18 | };
19 | this.attachShadow({ mode: "open" }).appendChild(vcClone);
20 | }
21 | static get observedAttributes() {
22 | return ["src"];
23 | }
24 | attributeChangedCallback(name, oldValue, newValue) {
25 | if (name == "src") {
26 | this.shadowRoot.getElementById("popout").href = newValue;
27 | this.shadowRoot.querySelector("iframe").src = newValue;
28 | }
29 | }
30 | }
31 | );
32 | });
33 | }
34 |
--------------------------------------------------------------------------------
/ansible/files/nginx/ttydconsole.conf:
--------------------------------------------------------------------------------
1 | # Ansible managed
2 |
3 | location ~ ^(/console/[A-Za-z0-9]+)$ {
4 | return 301 $1/;
5 | }
6 |
7 | location ^~ /console/admin/ {
8 | proxy_pass http://unix:/run/ttyd-admin/admin.sock:;
9 | proxy_http_version 1.1;
10 | proxy_set_header X-Real-IP $remote_addr;
11 | proxy_set_header Host $host;
12 | proxy_set_header X-Forwarded-For $remote_addr;
13 | # needed for websocket
14 | proxy_set_header Upgrade $http_upgrade;
15 | proxy_set_header Connection $connection_upgrade;
16 | # websockets can be silent for a while
17 | proxy_read_timeout 3600s;
18 | proxy_send_timeout 3600s;
19 | rewrite ^/console/[A-Za-z0-9]+/?(.*)$ /$1 break;
20 | }
21 |
22 | location ~ ^/console/(?P[a-zA-Z0-9]+)/ {
23 | proxy_pass http://unix:/run/ttyd-container/$router.sock:;
24 | proxy_http_version 1.1;
25 | proxy_set_header X-Real-IP $remote_addr;
26 | proxy_set_header Host $host;
27 | proxy_set_header X-Forwarded-For $remote_addr;
28 | # needed for websocket
29 | proxy_set_header Upgrade $http_upgrade;
30 | proxy_set_header Connection $connection_upgrade;
31 | # websockets can be silent for a while
32 | proxy_read_timeout 3600s;
33 | proxy_send_timeout 3600s;
34 | rewrite ^/console/[A-Za-z0-9]+/?(.*)$ /$1 break;
35 | }
36 |
--------------------------------------------------------------------------------
/ripe-app-frontend/app/components/scratch-pad.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | if (!customElements.get("scratch-pad")) {
4 | let spTemplate = document.createElement("template");
5 | import("./scratch-pad.html")
6 | .then((content) => {
7 | spTemplate.innerHTML = `${content.default}`
8 | customElements.define(
9 | "scratch-pad",
10 | class extends HTMLElement {
11 | constructor() {
12 | super();
13 | const spClone = spTemplate.content.cloneNode(true);
14 | this.scratchPad = spClone.querySelector("textarea");
15 | this.attachShadow({ mode: "open" }).appendChild(spClone);
16 | this.storage_key = "ripencc-workbench-scratchpad";
17 | this.load_notes();
18 | }
19 | static get observedAttributes() {
20 | return ["id"];
21 | }
22 | attributeChangedCallback(name, oldValue, newValue) {
23 | if (name == "id") {
24 | this.storage_key = newValue;
25 | this.load_notes();
26 | }
27 | }
28 | load_notes() {
29 | const scratchPadData = sessionStorage.getItem(this.storage_key);
30 | if (scratchPadData != null) {
31 | this.scratchPad.value = scratchPadData;
32 | }
33 | this.scratchPad.onchange = () => {
34 | sessionStorage.setItem(this.storage_key, this.scratchPad.value);
35 | }
36 | }
37 | }
38 | );
39 | });
40 | }
41 |
--------------------------------------------------------------------------------
/ripe-app-frontend/app/workspaces/dashboard/index.html:
--------------------------------------------------------------------------------
1 |
11 |
12 | Dashboard
13 |
14 |
Host A
15 |
Host B
16 |
Host C
17 |
18 |
Availaible tools
19 |
25 |
Hints
26 |
27 | Feel free to resize terminal windows by dragging (does not work in Safari)
28 | To scroll inside the tmux, use Ctrl-B and PageUp/PageDown (Fn + Up/Down on Mac)
29 | To open new tmux window, use Ctrl-B c
30 | See tmux cheatsheet
31 |
32 |
33 |
34 |
35 |
36 |
--------------------------------------------------------------------------------
/ripe-app-frontend/build/app_workspaces_dashboard_index_html.bundle.js:
--------------------------------------------------------------------------------
1 | "use strict";(self.webpackChunkworkbench_shell=self.webpackChunkworkbench_shell||[]).push([["app_workspaces_dashboard_index_html"],{"./app/workspaces/dashboard/index.html":(e,o,n)=>{n.r(o),n.d(o,{default:()=>s});const s=' \n \n Dashboard \n \n
Host A \n
Host B \n
Host C \n
\n
Availaible tools \n
\n
Hints \n
\n Feel free to resize terminal windows by dragging (does not work in Safari) \n To scroll inside the tmux, use Ctrl-B and PageUp/PageDown (Fn + Up/Down on Mac) \n To open new tmux window, use Ctrl-B c \n See tmux cheatsheet \n \n
\n
\n
\n \n'}}]);
--------------------------------------------------------------------------------
/Vagrantfile:
--------------------------------------------------------------------------------
1 | # -*- mode: ruby -*-
2 | # vi: set ft=ruby :
3 |
4 | # All Vagrant configuration is done below. The "2" in Vagrant.configure
5 | # configures the configuration version (we support older styles for
6 | # backwards compatibility). Please don't change it unless you know what
7 | # you're doing.
8 | Vagrant.configure("2") do |config|
9 | config.vm.box = "ubuntu/jammy64"
10 | # config.vm.box_version = "20210603.0.0"
11 |
12 | config.vm.synced_folder ".", "/vagrant", type: "rsync",
13 | rsync__exclude: ".git/"
14 |
15 | # Create a forwarded port mapping which allows access to a specific port
16 | # within the machine from a port on the host machine and only allow access
17 | # via 127.0.0.1 to disable public access
18 | config.vm.network "forwarded_port", guest: 80, host: 8080, host_ip: "127.0.0.1", id: "web"
19 |
20 | # Provider-specific configuration so you can fine-tune various
21 | # backing providers for Vagrant. These expose provider-specific options.
22 | # Example for VirtualBox:
23 | #
24 | # config.vm.provider "virtualbox" do |vb|
25 | # # Display the VirtualBox GUI when booting the machine
26 | # vb.gui = true
27 | #
28 | # # Customize the amount of memory on the VM:
29 | # vb.memory = "1024"
30 | # end
31 | config.vm.provision "shell", reset: true, inline: <<-SHELL
32 | gpasswd -a vagrant lxd
33 | add-apt-repository -y ppa:ansible/ansible
34 | export DEBIAN_FRONTEND=noninteractive
35 | apt-get -y install ansible-core python3-packaging
36 | ansible-galaxy collection install community.general -p /usr/share/ansible/collections
37 | SHELL
38 | config.vm.provision "ansible_local" do |ansible|
39 | ansible.install = false
40 | ansible.playbook = "ansible/site.yaml"
41 | ansible.inventory_path = "ansible/inventory.ini"
42 | ansible.verbose = false
43 | ansible.limit = "all"
44 | end
45 | end
46 |
--------------------------------------------------------------------------------
/ubuntu-auto-install/user-data:
--------------------------------------------------------------------------------
1 | #cloud-config
2 | autoinstall:
3 | snaps:
4 | - name: lxd
5 | packages:
6 | - python3-packaging
7 | - python3-pip
8 | - git
9 | identity:
10 | hostname: ipv6seclab
11 | username: ubuntu
12 | password: $6$exDY1mhS4KUYCE/2$zmn9ToZwTKLhCw.b4/b.ZRTIZM30JZ4QrOQ2aOXJ8yk96xpcCof0kxKwuX1kqLG/ygbJ1f8wxED22bTL4F46P0
13 | late-commands:
14 | - "curtin in-target -- bash -c 'mkdir -p /etc/systemd/system/getty@.service.d/; echo -e \"[Service]\nTTYVTDisallocate=no\nExecStart=\nExecStart=-/sbin/agetty -o '\\''-p -- \\\\u'\\'' --autologin ubuntu --noclear %I \\$TERM\" > /etc/systemd/system/getty@.service.d/override.conf'"
15 | - "curtin in-target -- bash -c 'echo -e \"#!/bin/bash\nif [[ \\$USER == ubuntu && \\\"\\$XDG_SESSION_TYPE\\\" == tty ]]; then\n\techo\n\techo -n Please wait until cloud-init finishes with \\\"status: done\\\"\n\tcloud-init status --wait\nfi\" > /etc/profile.d/ZZ-wait-cloud-init.sh'"
16 | user-data:
17 | users:
18 | - name: ubuntu
19 | sudo: ALL=(ALL) NOPASSWD:ALL
20 | runcmd:
21 | - [ passwd, -d, ubuntu ]
22 | ansible:
23 | install_method: pip
24 | package_name: ansible
25 | run_user: ubuntu
26 | galaxy:
27 | actions:
28 | - ["ansible-galaxy", "collection", "install", "community.general", "ansible.posix"]
29 | setup_controller:
30 | repositories:
31 | - path: /home/ubuntu/ipv6-security-lab/
32 | source: https://github.com/RIPE-NCC/ipv6-security-lab.git
33 | run_ansible:
34 | - playbook_dir: /home/ubuntu/ipv6-security-lab/ansible/
35 | playbook_name: site.yaml
36 | inventory: inventory.ini
37 | source:
38 | id: ubuntu-server-minimal
39 | search_drivers: false
40 | storage:
41 | swap:
42 | size: 0
43 | layout:
44 | name: direct
45 | updates: all
46 | version: 1
47 |
--------------------------------------------------------------------------------
/ripe-app-frontend/app/styles/styles.css:
--------------------------------------------------------------------------------
1 | :root {
2 | --ripe-app-font-family: "Public Sans";
3 | }
4 |
5 | @media only screen and (max-width: 700px) {
6 | #app-grid {
7 | grid-template-columns: [left] 300px [right] auto;
8 | }
9 |
10 | #app-banners-container {
11 | grid-column: left / 2 span;
12 | top: 40px;
13 | }
14 |
15 | #app-workspace {
16 | grid-column: left / 2 span;
17 | }
18 | }
19 |
20 | @media only screen and (min-width: 701px) {
21 | #app-grid {
22 | grid-template-columns: [left] max-content [right] auto;
23 | }
24 |
25 | #app-banners-container {
26 | grid-column: right;
27 | top: 70px;
28 | }
29 |
30 | #app-workspace {
31 | grid-column: right;
32 | }
33 | }
34 |
35 | @media only screen and (max-width: 400px) {
36 | #atlas-logo-title {
37 | max-width: 100px;
38 | }
39 | }
40 |
41 | #app-grid {
42 | width: 100%;
43 | display: grid;
44 | grid-template-rows: [bc] min-content [ws] auto;
45 | background-color: #fafafa;
46 | }
47 |
48 | app-nav-bar {
49 | grid-column: left;
50 | grid-row: bc / 1000 span;
51 | }
52 |
53 | app-switcher {
54 | font-weight: 400;
55 | }
56 |
57 | #app-banners-container {
58 | position: sticky;
59 | grid-row: bc;
60 | background-color: white;
61 | }
62 |
63 | #app-workspace {
64 | grid-row: ws;
65 | margin: 0;
66 | background-color: var(--ripe-app-main-background, #fafafa);
67 | }
68 |
69 | #csr-workspace {
70 | padding: 24px;
71 | }
72 |
73 | html {
74 | /* overflow-x: hidden; */
75 | }
76 |
77 | html,
78 | body {
79 | font-family: var(--ripe-app-font-family), sans-serif;
80 | font-size: 16px;
81 | line-height: 1.5rem;
82 | box-sizing: border-box;
83 | margin: 0;
84 | width: 100%;
85 | /* height: 100%; */
86 | }
87 |
88 | #fb div {
89 | display: inline-block;
90 | }
91 |
92 | .accent {
93 | color: var(--ripe-app-positive);
94 | }
95 |
96 | .alarmed {
97 | color: var(--ripe-app-alarmed);
98 | }
99 |
100 | roll-over a:visited {
101 | color: var(--ripe-app-secondary-20);
102 | }
103 |
104 | roll-over a {
105 | color: var(--ripe-app-secondat-100);
106 | }
107 |
108 | roll-over p {
109 | line-height: 1.4rem;
110 | }
111 |
--------------------------------------------------------------------------------
/.github/workflows/vagrant-build.yml:
--------------------------------------------------------------------------------
1 | name: Build a release
2 | # Source: https://github.com/jonashackt/vagrant-github-actions
3 | on:
4 | push:
5 | branches:
6 | - main
7 | paths:
8 | - 'version.txt'
9 | workflow_dispatch:
10 |
11 | jobs:
12 | build-release:
13 | runs-on: ubuntu-22.04
14 | steps:
15 | - name: Install Vagrant and Virtualbox
16 | run: |
17 | sudo apt update && sudo apt install virtualbox
18 | wget -O- https://apt.releases.hashicorp.com/gpg | sudo gpg --dearmor -o /usr/share/keyrings/hashicorp-archive-keyring.gpg
19 | echo "deb [signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] https://apt.releases.hashicorp.com $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/hashicorp.list
20 | sudo apt update && sudo apt install vagrant
21 | - name: Checkout repository
22 | uses: actions/checkout@v4
23 |
24 | - name: Cache Vagrant boxes
25 | uses: actions/cache@v4
26 | with:
27 | path: ~/.vagrant.d/boxes
28 | key: ${{ runner.os }}-vagrant-${{ hashFiles('Vagrantfile') }}
29 | restore-keys: |
30 | ${{ runner.os }}-vagrant-
31 |
32 | - name: Show Vagrant version
33 | run: vagrant --version
34 |
35 | - name: Show VirtualBox version
36 | run: VBoxManage --version
37 |
38 | - name: Build the environment
39 | run: vagrant up
40 |
41 | - name: Create a Vagrant box
42 | run: vagrant package --vagrantfile Vagrantfile-in-box --output ipv6-security-lab-$(cat version.txt).box
43 |
44 | - name: Store box file
45 | uses: actions/upload-artifact@v4
46 | with:
47 | name: vagrant-box
48 | path: "ipv6-security-lab-*.box"
49 |
50 | - name: Publish box file in the release
51 | if: startsWith(github.ref, 'refs/tags/')
52 | uses: djn24/add-asset-to-release@v2
53 | continue-on-error: true
54 | with:
55 | token: ${{ secrets.GITHUB_TOKEN }}
56 | args: "ipv6-security-lab-*.box"
57 |
58 | - name: Publish box file in the Vagrant Cloud
59 | env:
60 | ATLAS_TOKEN: ${{ secrets.VAGRANT_CLOUD_TOKEN }}
61 | run: vagrant cloud publish ripencc/ipv6seclab $(cat version.txt) virtualbox ipv6-security-lab-$(cat version.txt).box --force
62 |
--------------------------------------------------------------------------------
/ripe-app-frontend/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require("path");
2 | const webpack = require("webpack");
3 | const CopyWebpackPlugin = require("copy-webpack-plugin");
4 | const MiniCssExtractPlugin = require("mini-css-extract-plugin");
5 | const OptimizeCSSAssetsPlugin = require("css-minimizer-webpack-plugin");
6 | const TerserPlugin = require("terser-webpack-plugin");
7 |
8 | var dir_app = path.resolve(__dirname, "app");
9 | var dir_build = path.resolve(__dirname, "build");
10 | var dir_html = path.resolve(__dirname, "html");
11 |
12 | module.exports = {
13 | mode: "development",
14 | devtool: "cheap-module-source-map",
15 | devServer: {
16 | static: dir_build,
17 | compress: true,
18 | historyApiFallback: true,
19 | port: 4022,
20 | proxy: [{
21 | context: ['/console/hostA', '/console/hostB', '/console/hostC', '/console/admin', '/version.txt'],
22 | target: 'http://127.0.0.1:8080',
23 | ws: true
24 | }]
25 | },
26 | optimization: {
27 | minimize: true,
28 | minimizer: [
29 | new TerserPlugin({
30 | terserOptions: {
31 | output: {
32 | comments: false,
33 | },
34 | },
35 | extractComments: false,
36 | }),
37 | new OptimizeCSSAssetsPlugin({}),
38 | ],
39 | },
40 | entry: [path.resolve(dir_app, "index.js")],
41 | module: {
42 | rules: [
43 | {
44 | test: /\.js[x]?$/,
45 | include: [/app/],
46 | // includes don't work with linked (local) modules,
47 | // such as the @ripe-rnd/ui-components
48 | // so excluding is the way to go.
49 | exclude: /.*node_modules\/((?!@ripe-rnd).)*$/,
50 | },
51 | {
52 | test: /\.css$/,
53 | use: [
54 | MiniCssExtractPlugin.loader, // instead of style-loader
55 | "css-loader",
56 | ],
57 | },
58 | { test: /\.html$/, loader: "html-loader", options: { esModule: true } },
59 | ],
60 | },
61 | resolve: {
62 | extensions: ["*", ".js", ".jsx"],
63 | symlinks: false,
64 | },
65 | output: {
66 | path: dir_build,
67 | publicPath: "/",
68 | filename: "bundle.js",
69 | clean: true,
70 | },
71 | plugins: [
72 | new CopyWebpackPlugin({
73 | patterns: [{ from: dir_html }],
74 | }),
75 | new MiniCssExtractPlugin(),
76 | ],
77 | };
78 |
--------------------------------------------------------------------------------
/ripe-app-frontend/app/index.js:
--------------------------------------------------------------------------------
1 | import { RipeAppWebComponents } from "@technical-design/ripe-app-webcomponents";
2 | import { default as appNavBarMenu } from "./menu.json";
3 | import { initRouter, routeTo, popStateRouter } from "./router";
4 |
5 | // All css files imported here,
6 | // will be concatenated into a file `main.css`
7 | // by the webpack configs.
8 | // Note that main.css is the only filename that's tolerated by nginx.
9 | import "./styles/styles.css";
10 | import "../node_modules/@technical-design/ripe-app-webcomponents/src/webcomponents/shared/ripe-app-colors.css";
11 |
12 |
13 | window.addEventListener("onpopstate", function(e) {
14 | e.preventDefault();
15 | console.log("DOM not ready yet. Refusing to navigate.");
16 | });
17 |
18 | const navBarOpenCookieName = "ripencc-workbench-navbar-open";
19 | const readCookie = name => {
20 | return (document.cookie.match(`(^|; )${name}=([^;]*)`) || 0)[2];
21 | };
22 | const setCookie = (name, val) => {
23 | document.cookie = `${name}=${val}; path=/`;
24 | };
25 |
26 | const navbarCookie = readCookie(navBarOpenCookieName);
27 |
28 | const mobileUserAgentRegex = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i;
29 |
30 | window.addEventListener("DOMContentLoaded", e => {
31 | const header = document.querySelector("ripe-header");
32 | const navBar = document.querySelector("app-nav-bar");
33 |
34 | if (navbarCookie === undefined || navbarCookie === "true") {
35 | // always start with closed menu on mobile devices
36 | if(mobileUserAgentRegex.test(navigator.userAgent)) {
37 | header.open = false;
38 | navBar.open = false;
39 | } else {
40 | header.open = true;
41 | navBar.open = true;
42 | }
43 | } else {
44 | header.open = false;
45 | navBar.open = false;
46 | }
47 |
48 | navBar.menu = JSON.stringify(appNavBarMenu);
49 | header.addEventListener("ripe-header-menu-toggle", e => {
50 | navBar.open = e.detail.open;
51 | setCookie(navBarOpenCookieName, e.detail.open ? "true" : "false");
52 | });
53 | navBar.addEventListener("app-nav-bar-select", e => {
54 | routeTo(e.detail.selected.url);
55 | });
56 | window.onpopstate = popStateRouter;
57 | fetch('/version.txt')
58 | .then(response => response.text())
59 | .then(text => {
60 | document.querySelector("#release-string").textContent = `Lab version ${text}`;
61 | });
62 |
63 | initRouter();
64 | });
65 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Vagrant-based personal network lab - IPv6 Security
2 | ==================================================
3 |
4 | This is a small self contained lab for playing with IPv6 security tools. It uses [VirtualBox](https://www.virtualbox.org/) to run a virtual
5 | Linux server hosting a few [LXD](https://linuxcontainers.org/lxd/) containers
6 | running Ubuntu Linux. For a convenient access to
7 | the consoles of the virtual routers, [ttyd](https://github.com/tsl0922/ttyd) is
8 | used to provide web-based terminal access.
9 |
10 | The lab is highly customizable using [Vagrant](https://www.vagrantup.com/) to
11 | provide the virtual server and [Ansible](https://www.ansible.com/) to do the
12 | configuration. It should work on Windows, macOS and Linux.
13 |
14 | Installation of the pre-packaged release (recommended)
15 | ------------------------------------------------------
16 |
17 | 1. Install [VirtualBox](https://www.virtualbox.org/)
18 | 2. Install [Vagrant](https://www.vagrantup.com/)
19 | 3. Create an empty folder (for instance `~/ripencc/ipv6seclab`)
20 | 4. Open a terminal window, enter the empty directory (using `cd` command) directory and run `vagrant init ripencc/ipv6seclab` followed by `vagrant up`
21 | 5. Wait a few minutes until vagrant finishes downloading the VM image. The
22 | size of the image is approximatelly 2 GiB.
23 | 6. Access the lab environment by pointing your web browser to [`http://localhost:8080/`](http://localhost:8080/)
24 |
25 | Stopping, restarting and destroying the lab
26 | -------------------------------------------
27 |
28 | Then, you can turn off the VM by running `vagrant halt` in the same directory
29 | you run `vagrant up` before. You can use the latter command to restart the lab
30 | later.
31 |
32 | You can destroy the lab environment by issuing `vagrant destroy`. A subsequent
33 | call of `vagrant up` will bring up a completely fresh environment.
34 |
35 | Upgrading to a new version of the lab
36 | -------------------------------------
37 |
38 | From time to time, a new version of the lab is released. You can spot it by
39 | the contents of `version.txt` file. If you want to upgrade, run these commands:
40 |
41 | vagrant destroy
42 | vagrant box update
43 | vagrant up
44 |
45 | Resolving port conflict
46 | -----------------------
47 |
48 | In case there is some other software listening on local port 8080, Vagrant will refuse
49 | to start. You can either stop the offending software or adjust Vagrantfile to change
50 | the port number. For example, this is how you move the environment from port 8080 to
51 | port 8081:
52 |
53 | ```ruby
54 | config.vm.network "forwarded_port", guest: 80, host: 8080, host_ip: "127.0.0.1", id: "web", disabled: true
55 | config.vm.network "forwarded_port", guest: 80, host: 8081, host_ip: "127.0.0.1"
56 | ```
57 |
58 | Then you can find the lab environment on [`http://localhost:8081/`](http://localhost:8081/)
59 |
60 | Running the development version
61 | -------------------------------
62 |
63 | You can also clone or download this repository and run `vagrant up` there. This
64 | should build a completely new environment from scratch.
65 |
--------------------------------------------------------------------------------
/ripe-app-frontend/build/main.css:
--------------------------------------------------------------------------------
1 | /*!*************************************************************************!*\
2 | !*** css ./node_modules/css-loader/dist/cjs.js!./app/styles/styles.css ***!
3 | \*************************************************************************/:root{--ripe-app-font-family:"Public Sans"}@media only screen and (max-width:700px){#app-grid{grid-template-columns:[left] 300px [right] auto}#app-banners-container{top:40px}#app-banners-container,#app-workspace{grid-column:left/span 2}}@media only screen and (min-width:701px){#app-grid{grid-template-columns:[left] max-content [right] auto}#app-banners-container{grid-column:right;top:70px}#app-workspace{grid-column:right}}@media only screen and (max-width:400px){#atlas-logo-title{max-width:100px}}#app-grid{background-color:#fafafa;display:grid;grid-template-rows:[bc] min-content [ws] auto;width:100%}app-nav-bar{grid-column:left;grid-row:bc/span 1000}app-switcher{font-weight:400}#app-banners-container{background-color:#fff;grid-row:bc;position:sticky}#app-workspace{background-color:var(--ripe-app-main-background,#fafafa);grid-row:ws;margin:0}#csr-workspace{padding:24px}body,html{box-sizing:border-box;font-family:var(--ripe-app-font-family),sans-serif;font-size:16px;line-height:1.5rem;margin:0;width:100%}#fb div{display:inline-block}.accent{color:var(--ripe-app-positive)}.alarmed{color:var(--ripe-app-alarmed)}roll-over a:visited{color:var(--ripe-app-secondary-20)}roll-over a{color:var(--ripe-app-secondat-100)}roll-over p{line-height:1.4rem}
4 |
5 | /*!******************************************************************************************************************************************************!*\
6 | !*** css ./node_modules/css-loader/dist/cjs.js!./node_modules/@technical-design/ripe-app-webcomponents/src/webcomponents/shared/ripe-app-colors.css ***!
7 | \******************************************************************************************************************************************************/:root{--ripe-app-accent:#25316a;--ripe-app-darkbase:#070033;--ripe-app-backgrounds:var(--ripe-app-primary-100);--ripe-app-info:#29abe2;--ripe-app-positive:#6ebe49;--ripe-app-worried:#ff6a00;--ripe-app-alarmed:#ed1c24;--ripe-app-accent:var(--ripe-app-primary-80);--ripe-app-primary-100:#080037;--ripe-app-primary-80:#1b204c;--ripe-app-primary-60:#2f376b;--ripe-app-primary-40:#9ea5c2;--ripe-app-primary-20:#d7dce8;--ripe-app-primary-0:#eceff4;--ripe-app-secondary-100:#009ba6;--ripe-app-secondary-80:#00b0ba;--ripe-app-secondary-60:#3dbdc6;--ripe-app-secondary-40:#7bd1db;--ripe-app-secondary-20:#c2e8ed;--ripe-app-secondary-0:#e1eff4;--ripe-app-positive-100:#6ebe49;--ripe-app-positive-80:#7cce5d;--ripe-app-positive-60:#9bd78b;--ripe-app-positive-40:#c4e4bc;--ripe-app-positive-20:#ddeddc;--ripe-app-positive-0:#edf3ed;--ripe-app-worried-100:#ff6a00;--ripe-app-worried-80:#ff7a36;--ripe-app-worried-60:#ff9660;--ripe-app-worried-40:#ffb491;--ripe-app-worried-20:#ffdacc;--ripe-app-worried-0:#ffeae7;--ripe-app-navbar-background:var(--ripe-app-primary-100);--ripe-app-navbar-menuitem:var(--ripe-app-primary-20);--ripe-app-navbar-active-menuitem:#fff;--ripe-app-navbar-footer-item:#9d9aac;--ripe-app-navbar-indicator:#f59331;--ripe-app-navrail-tooltip-bg:#363738;--ripe-app-navrail-tooltip-text:#fff;--ripe-app-navrail-tooltip-border:#363738;--ripe-app-header-icon-bg:silver;--ripe-app-header-icon-hover:var(--ripe-app-primary-100);--ripe-app-border-line:#2f2c56;--ripe-header-padding-top:0px}
--------------------------------------------------------------------------------
/ubuntu-auto-install/README.md:
--------------------------------------------------------------------------------
1 | Autoinstall files for Ubuntu
2 | ============================
3 |
4 | The contents of this directory can be used to automate fresh installation
5 | of the lab environment without the help of Vagrant or Virtualbox. It uses
6 | [Automated Server
7 | installation](https://ubuntu.com/server/docs/install/autoinstall) feature
8 | of Ubuntu server together with [cloud-init](https://cloud-init.io/). It can
9 | run anywhere where Ubuntu 22.04 is supported, including ARM architecture
10 | computers like M1/M2 Apple Hardware. It has been tested using
11 | [UTM](https://mac.getutm.app/) hypervisor on both arm64 and x86_64
12 | architectures. Other hypervisors and/or architectures might or might not
13 | work.
14 |
15 | Prerequisites for building UTM-based Virtual Machine
16 | ----------------------------------------------------
17 | - A computer running macOS based on either Intel or Apple Silicon
18 | - At least 10 GB of free disk space
19 | - [UTM](https://mac.getutm.app/)
20 | - Ubuntu Server ISO file for particular architecture:
21 | [Intel](https://ubuntu.com/download/server) or
22 | [ARM](https://ubuntu.com/download/server/arm)
23 |
24 | Preparing virtual machine from scratch
25 | --------------------------------------
26 |
27 | 1. In UTM, press "Create a New Virtual Machine."
28 | 2. Select "Virtualize", then "Linux". Provide ISO image. Provision at least 2048
29 | GB RAM and 10 GB disk. The rest can stay on default.
30 | 3. Edit machine settings, remove sound card by right clicking it and edit QEMU
31 | options providing path to Autoinstall files. At the very bottom, in the field
32 | with prompt "New…", put this path:
33 |
34 | ```
35 | -smbios type=1,serial=ds=nocloud-net;s=https://raw.githubusercontent.com/RIPE-NCC/ipv6-security-lab/main/ubuntu-auto-install/
36 | ```
37 | 4. In case of Intel computer, adjust virtual machine setup like this:
38 | - Display: Emulated Display Card: `virtio-vga-gl`
39 | - Network: Emulated Network Card: `virtio-net-pci`
40 | - Hard disk: Interface: `VirtIO`
41 |
42 | 5. Run the VM. It should start booting from the ISO image and spit a lot of
43 | text.
44 | 6. You will be prompted "Continue with autoinstall? (yes/no)". Answer `yes`.
45 | 7. After several minutes, the installer will reboot the virtual machine. Do NOT
46 | boot the ISO image again, eject it or select "Boot from next volume."
47 | 8. During the first start, the installation of the lab environment takes place.
48 | Please wait at least minutes. You can log in using password "ubuntu" or
49 | just wait. Don't get bothered by "Login timed out after 60 seconds."
50 | 9. After installation is done, you will be logged in automatically. The last
51 | line before prompt should read: "status: done."
52 |
53 | The lab environment should be fully accessible on a random IP address the
54 | Virtual Machine got assigned. You can see the IP address by using command
55 | `ip -br addr show enp0s1`. You can stop the VM gracefully by issuing `poweroff`.
56 |
57 | Final touches
58 | -------------
59 |
60 | After the installation is done, you can prepare a VM image that would be
61 | suitable for distribution. In this, we change network setup so it uses localhost
62 | port 8080 and we remove display so the VM can run headless.
63 |
64 | 1. Shut down the image gracefully by issuing `poweroff` in the console or `sudo
65 | poweroff` in the admin console of the web interface.
66 | 2. In VM settings, change Network Mode to "Emulated VLAN". A new section called
67 | "Port Forwarding" appears in the menu.
68 | 3. In the "Port Forwarding" menu, add a new item and fill in: Protocol TCP,
69 | `10.0.2.15`, `80`, `127.0.0.1`, `8080`.
70 | 4. Remove display device
71 | 5. In VirtIO Drive, press Compress. This will significantly reduce the disk
72 | image.
73 | 6. Add descriptive name and put version as well as architecture into Notes.
74 | 7. Press Share button to share UTM image. Since the image is actually a folder,
75 | compress it with zip before uploading it somewhere.
76 |
77 | Troubleshooting
78 | ---------------
79 |
80 | - From time to time, automated OS installation (the first step) fails for no
81 | obvious reason. Resetting the VM and running it again usually helps.
82 | - The Ansible provisioning part can get stuck many ways since it downloads data
83 | from several sources around the Internet. You can see the logs only after it
84 | fails in `/var/log/cloud-init.log`. You can also try to run Ansible manually
85 | to see the exact cause of errors/delays :
86 | ```
87 | $ ansible-playbook -i ipv6-security-lab/ansible/inventory.ini ipv6-security-lab/ansible/site.yaml
88 | ```
89 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | ## [0.20240816.0](https://www.github.com/RIPE-NCC/ipv6-security-lab/compare/v0.20230403.0...v0.20240816.0) (2024-08-16)
4 |
5 |
6 | ### Bug Fixes
7 |
8 | * Add warning about RIPE NCC internal dependency of the frontend ([3ab20e3](https://www.github.com/RIPE-NCC/ipv6-security-lab/commit/3ab20e3088ab23d1d233f535d645da75a26f96b2))
9 | * fix Debian Stretch repo location ([f42f93d](https://www.github.com/RIPE-NCC/ipv6-security-lab/commit/f42f93d4661e7d40b30b2604374b3ecc2e9decd0))
10 | * restore kernel-level IPv6 workaround ([b816308](https://www.github.com/RIPE-NCC/ipv6-security-lab/commit/b816308a71ef2ba56e194e877b749d1411e56f98))
11 | * switch to official Ubuntu images ([5b152de](https://www.github.com/RIPE-NCC/ipv6-security-lab/commit/5b152de185cea89735d313d8231443b2e5eedbc4))
12 | * update npm dependencies ([85e48f5](https://www.github.com/RIPE-NCC/ipv6-security-lab/commit/85e48f5cc0d4956d8c4089d703907fdb931eb8bc))
13 |
14 | ## [0.20230403.0](https://www.github.com/RIPE-NCC/ipv6-security-lab/compare/v0.20221114.0...v0.20230403.0) (2023-04-03)
15 |
16 |
17 | ### Features
18 |
19 | * support Ubuntu auto-install, upgrade host to Ubuntu 22.04 ([b34e011](https://www.github.com/RIPE-NCC/ipv6-security-lab/commit/b34e0116adf75bf18aada1e71f320d6c0884d69c))
20 |
21 |
22 | ### Bug Fixes
23 |
24 | * remove obsolete Ansible syntax ([ddb8601](https://www.github.com/RIPE-NCC/ipv6-security-lab/commit/ddb86017a5e66b0b4c679dac4526cb51af2cb440))
25 |
26 |
27 | ### Miscellaneous Chores
28 |
29 | * release 0.20230403.0 ([c13aad6](https://www.github.com/RIPE-NCC/ipv6-security-lab/commit/c13aad6162ae6713fdf7d5d4fdd90ab4514fa4c9))
30 |
31 | ## [0.20221114.0](https://www.github.com/RIPE-NCC/ipv6-security-lab/compare/v0.20211020.1...v0.20221114.0) (2022-11-14)
32 |
33 |
34 | ### Features
35 |
36 | * frontend update ([d4464f5](https://www.github.com/RIPE-NCC/ipv6-security-lab/commit/d4464f5b5e61474efb9225c033d52d7175ff8f73))
37 | * Upgrade ttyd to 1.7.2 ([28991b9](https://www.github.com/RIPE-NCC/ipv6-security-lab/commit/28991b91f42ec52b87479abe1d2bffeb1b302a2d))
38 |
39 | ### [0.20211020.1](https://www.github.com/RIPE-NCC/ipv6-security-lab/compare/v0.20211020.0...v0.20211020.1) (2021-10-20)
40 |
41 |
42 | ### Bug Fixes
43 |
44 | * use macos-10.15 in GitHub action ([741758c](https://www.github.com/RIPE-NCC/ipv6-security-lab/commit/741758cc50ea57b23fbd97d26f78e7c7988418cb))
45 |
46 | ## [0.20211020.0](https://www.github.com/RIPE-NCC/ipv6-security-lab/compare/v0.20210814.0...v0.20211020.0) (2021-10-20)
47 |
48 |
49 | ### Features
50 |
51 | * update Termshark to 2.3.0 ([5026edc](https://www.github.com/RIPE-NCC/ipv6-security-lab/commit/5026edc61a88618dca4a72c718e22bd21ddccd92))
52 |
53 |
54 | ### Bug Fixes
55 |
56 | * ansible-base got renamed to ansible-core ([b7f39c0](https://www.github.com/RIPE-NCC/ipv6-security-lab/commit/b7f39c0a90c4f0ec29b1d17c291c38cb83e73d07))
57 | * update web frontend to the latest version ([6c213b9](https://www.github.com/RIPE-NCC/ipv6-security-lab/commit/6c213b9214a017442f49353adc1a06aa603a3bdb))
58 | * Use rsync instead of shared folders ([ee1de25](https://www.github.com/RIPE-NCC/ipv6-security-lab/commit/ee1de255f5780d974aa88b59f2293855014c6e65))
59 |
60 | ## [0.20210814.0](https://www.github.com/RIPE-NCC/ipv6-security-lab/compare/v0.20210813.0...v0.20210814.0) (2021-08-17)
61 |
62 |
63 | ### Features
64 |
65 | * Disable multicast snooping on lxdbr0 by default ([2580b2c](https://www.github.com/RIPE-NCC/ipv6-security-lab/commit/2580b2c63d704a147e5dcc7c1a54bebe9040b9ae))
66 | * frontend update ([c47e88e](https://www.github.com/RIPE-NCC/ipv6-security-lab/commit/c47e88eeacd48ee296ee9ab67a61aea1c87e59ad))
67 |
68 |
69 | ### Bug Fixes
70 |
71 | * disable shared folder, workaround serial port issue on Windows ([5938407](https://www.github.com/RIPE-NCC/ipv6-security-lab/commit/5938407dcd8921839591ce9ae16018cb160d53ff))
72 |
73 | ## [0.20210813.0](https://www.github.com/RIPE-NCC/ipv6-security-lab/compare/v0.20210709.0...v0.20210813.0) (2021-08-13)
74 |
75 |
76 | ### Bug Fixes
77 |
78 | * ensure exact version of base box ([972b4e1](https://www.github.com/RIPE-NCC/ipv6-security-lab/commit/972b4e1125cf4566d37b71e51d02bd81b05d41c0))
79 | * workaround ignored ICMPv6 redirects ([60313d1](https://www.github.com/RIPE-NCC/ipv6-security-lab/commit/60313d14532f414794289dcbcee06caf3be99f60))
80 |
81 | ## 0.20210709.0 (2021-07-09)
82 |
83 |
84 | ### Features
85 |
86 | * add Vagrantfile-in-box to be part of the box ([526e619](https://www.github.com/RIPE-NCC/ipv6-security-lab/commit/526e619ac00eb774e438eef8bc8865b6f4b3fe89))
87 | * fixed MAC address of the virtual router ([520f09a](https://www.github.com/RIPE-NCC/ipv6-security-lab/commit/520f09a4fc51470254a39873069b03dc9190bc14))
88 | * replace the frontend with a RIPE NCC styled one ([a10183f](https://www.github.com/RIPE-NCC/ipv6-security-lab/commit/a10183f9f388a4b1690a015f0e79cdf7dae48760))
89 | * switch to ubuntu/focal64 base box ([5fa8f70](https://www.github.com/RIPE-NCC/ipv6-security-lab/commit/5fa8f70003cbe54eefdb142833dbb66bbcfa1615))
90 |
91 |
92 | ### Bug Fixes
93 |
94 | * frontend: small adjustments ([bb221aa](https://www.github.com/RIPE-NCC/ipv6-security-lab/commit/bb221aa61c429c2f95e869259449234ebb5f73fb))
95 | * frontend: Workaround broken iframe resize on Firefox ([0cbdbc6](https://www.github.com/RIPE-NCC/ipv6-security-lab/commit/0cbdbc61ae4afac4212f4c0c9d01310d95502218))
96 |
--------------------------------------------------------------------------------
/ripe-app-frontend/app/menu.json:
--------------------------------------------------------------------------------
1 | {
2 | "main": [
3 | {
4 | "title": "Dashboard",
5 | "subtitle": "Access to the servers' consoles",
6 | "url": "/",
7 | "icon": " "
8 | },
9 | {
10 | "title": "Network diagram",
11 | "subtitle": "How are the machines connected",
12 | "url": "/network/",
13 | "icon": " "
14 | }
15 | ],
16 | "footer": [
17 | {
18 | "title": "Admin console",
19 | "subtitle": "Control the VM itself",
20 | "url": "/admin/",
21 | "icon": " "
22 | },
23 | {
24 | "title": "Legal",
25 | "url": "/legal/",
26 | "subtitle": "Copyright, Privacy, Terms and Cookies",
27 | "icon": " "
28 | }
29 | ]
30 | }
31 |
--------------------------------------------------------------------------------
/ripe-app-frontend/app/router.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | import { default as appNavBarMenu } from "./menu.json";
4 |
5 | export const initRouter = (reregister = false) => {
6 | console.log(
7 | `Router: init router ${
8 | (reregister && "(reregister)") || ""
9 | }`
10 | );
11 |
12 | // initial client-side url to iframe routing
13 | if (!reregister) {
14 | routeTo(
15 | `${document.location.pathname}${document.location.search}${document.location.hash}`,
16 | { initial: true, pushState: false }
17 | );
18 | }
19 | };
20 |
21 | export const popStateRouter = function (e) {
22 | e.preventDefault();
23 | const pathname = document.location.pathname;
24 | const search = document.location.search;
25 | const hash = document.location.hash;
26 | console.log(`Router: User pops to ${pathname}${search}${hash}`);
27 | console.log(`or ${document.location.href}`);
28 |
29 | routeTo(pathname, {
30 | pushState: false,
31 | search: search,
32 | hash: hash,
33 | forceReload: true,
34 | });
35 | };
36 |
37 | export const setActiveMenuItemByUrl = (newPathName) => {
38 | // Search for the url in the menubar and make the item active if found,
39 | // excluding the search params and hash from the new url and
40 | // excluding any integers (measurement or probe ids)
41 | const menuItems = appNavBarMenu.main.concat(appNavBarMenu.footer);
42 | const matchPath = newPathName
43 | .replace(/(\?.+)/, "")
44 | .replace(/(\#.+)/, "");
45 | console.log(`Router: matching URL ${matchPath}`);
46 | const newActiveItem = menuItems.find(
47 | (i) =>
48 | i.url === (matchPath || matchPath.replace(/^\/([^\/]+)\/(.+)/, "/$1/"))
49 | );
50 | if (newActiveItem) {
51 | console.log(`Router: found menu item ${newActiveItem.title}`);
52 | document.querySelector("app-nav-bar").active = newActiveItem.title;
53 | } else {
54 | console.log(`Router: no menu item found for ${matchPath}`);
55 | }
56 | return newActiveItem;
57 | };
58 |
59 | export const routeTo = (newUrl, options = {}) => {
60 | /*
61 | * Arguments: (URL, OPTIONS)
62 | *
63 | * URL
64 | *
65 | * OPTIONS
66 | *
67 | * pushState : if set to false, do not change the URL in the addressbar.
68 | * inital : if set to true load the iframe, even if it's already in the addressbar.
69 | * forceReload: if set to true will *always* reload the frame,
70 | * even if nothing of the three-tuple pathname, search * hash changed.
71 | * search : search parameters for newUrl
72 | * hash : hash parameter for newUrl
73 | */
74 |
75 | //newUrl = newUrl.replace(/(.+)\/$/, "$1");
76 |
77 | const currentCsrWorkSpace = document.querySelector("div#csr-workspace");
78 | const currentUrlPath = document.location.pathanme;
79 | const currentSearch = document.location.search;
80 | const currentHash = document.location.hash;
81 |
82 | updateTitle(newUrl);
83 | if (
84 | !options.initial &&
85 | !options.forceReload &&
86 | `${newUrl}${options.search || ""}${options.hash || ""}` ===
87 | `${currentUrlPath}${currentSearch}${currentHash}`
88 | ) {
89 | console.log(
90 | `Router: Nothing changed. Staying at ${currentUrlPath}${currentSearch}${currentHash}`
91 | );
92 | return;
93 | }
94 |
95 | let optionsPushState = options && options.pushState;
96 | if (typeof optionsPushState === "undefined") {
97 | optionsPushState = true;
98 | }
99 | console.log(`Router: change pushState=${optionsPushState}`);
100 |
101 | // OTOH only the hashpart or the search parameters
102 | // might have changed. So update only the URL if that's the case
103 | if (
104 | !options.initial &&
105 | !options.forceReload &&
106 | (options.hash || options.search) &&
107 | currentUrlPath === newUrl
108 | ) {
109 | if (optionsPushState && (options.hash || options.search)) {
110 | console.log("History: Push history");
111 | window.history.pushState(
112 | {
113 | changeFrame: false,
114 | fromUrl: window.location.pathname,
115 | hash: window.location.hash,
116 | search: window.location.search,
117 | },
118 | "",
119 | `${newUrl}${(options.search && options.search) || ""}${
120 | (options.hash && options.hash) || ""
121 | }`
122 | );
123 | }
124 |
125 | console.log(
126 | `Router: Changed search parms and/or hash to ${options.search}${options.hash}.`
127 | );
128 |
129 | return;
130 | }
131 |
132 | // Go over the list of client-side rendered pages in /workspaces.
133 | // If the URL is in there, we invoke the function that handles this route
134 | // client-side
135 | const cleanUrl = newUrl.replace(/\//g, "").replace(/^$/, "dashboard");
136 | console.log(`Router: routing to: ${cleanUrl}`);
137 | fetchWorkSpace(cleanUrl).then((m) => {
138 | m.loadWorkSpace();
139 | console.log(
140 | `Router: Push state ${newUrl}`
141 | );
142 | window.history.pushState(
143 | {},
144 | "",
145 | `${newUrl}`
146 | );
147 | }, (m) => {
148 | console.log(`Router: Workspace not found`);
149 | fetchWorkSpace("404").then((m) => {
150 | m.loadWorkSpace();
151 | });
152 | }
153 | );
154 | currentCsrWorkSpace.style.display = "block";
155 | setActiveMenuItemByUrl(newUrl);
156 | };
157 |
158 | // update the document title to something more specific
159 | // based on the new URL of the loaded frame
160 | const updateTitle = newUrl => {
161 | if (newUrl.slice(-1) !== "/") {
162 | newUrl = `${newUrl}/`;
163 | }
164 | // find matching title
165 | const menuItems = appNavBarMenu.main.concat(appNavBarMenu.footer);
166 | // not all URLs have trailing slash in menuItems
167 | // have to add them here for easier match comparison
168 | const titleItems = menuItems.map(i => {
169 | return {
170 | url: i.url.slice(-1) === "/" ? i.url : `${i.url}/`,
171 | title: i.title,
172 | };
173 | });
174 | const matchItem = titleItems.find(i => i.url === newUrl);
175 |
176 | // if we find a matching side menu entry, use that title
177 | if (matchItem) {
178 | document.title = `${matchItem.title} | RIPE NCC Workbench`;
179 | }
180 | }
181 |
182 | const fetchWorkSpace = async (path) => {
183 | let wsHandler = await import(`./workspaces/${path}/index.js`);
184 | return wsHandler;
185 | };
186 |
187 |
--------------------------------------------------------------------------------
/ripe-app-frontend/html/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
13 |
14 | RIPE NCC Workbench
15 |
16 |
17 |
18 |
19 |
20 |
22 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
45 |
46 |
47 |
50 |
51 |
54 |
55 |
56 |
59 |
62 |
64 |
67 |
71 |
74 |
77 |
80 |
82 |
83 |
84 |
85 |
86 | About RIPE NCC Workbench
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
97 |
98 |
99 |
100 |
--------------------------------------------------------------------------------
/ripe-app-frontend/build/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
13 |
14 | RIPE NCC Workbench
15 |
16 |
17 |
18 |
19 |
20 |
22 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
45 |
46 |
47 |
50 |
51 |
54 |
55 |
56 |
59 |
62 |
64 |
67 |
71 |
74 |
77 |
80 |
82 |
83 |
84 |
85 |
86 | About RIPE NCC Workbench
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
97 |
98 |
99 |
100 |
--------------------------------------------------------------------------------
/ansible/site.yaml:
--------------------------------------------------------------------------------
1 | ---
2 |
3 | - name: Deploy LXD
4 | hosts: default
5 | become: false
6 | tags: container
7 | handlers:
8 | - name: reboot hostA
9 | delegate_to: hostA
10 | reboot:
11 |
12 | - name: reload udev rules
13 | command: udevadm control --reload-rules
14 | become: true
15 |
16 | tasks:
17 | - name: Disable lxdbr0 multicast snooping
18 | become: true
19 | copy:
20 | dest: /etc/udev/rules.d/99-disable-lxdbr0-mcast-snooping.rules
21 | content: SUBSYSTEM=="net", ACTION=="add", KERNEL=="lxdbr0", RUN+="/bin/sh -c 'echo 0 > /sys/class/net/lxdbr0/bridge/multicast_snooping'"
22 | notify: reload udev rules
23 |
24 | - meta: flush_handlers
25 |
26 | - name: Initialize LXD
27 | command:
28 | cmd: lxd init --preseed
29 | stdin: "{{ lookup('file', 'lxd_preseed.yaml') }}"
30 | creates: /var/snap/lxd/common/state
31 |
32 | - name: Create first container
33 | lxd_container:
34 | name: hostA
35 | state: started
36 | config:
37 | volatile.eth0.hwaddr: "00:16:3e:ee:00:0a"
38 | source:
39 | type: image
40 | mode: pull
41 | server: https://cloud-images.ubuntu.com/releases/
42 | protocol: simplestreams
43 | alias: focal
44 | profiles: ["default"]
45 | wait_for_ipv4_addresses: true
46 |
47 | - name: Disable systemd-networkd IPv6 handling (workaround broken redirects)
48 | delegate_to: hostA
49 | lineinfile:
50 | path: /etc/netplan/50-cloud-init.yaml
51 | insertafter: "^ dhcp4: true"
52 | line: " accept-ra: false"
53 | notify: reboot hostA
54 |
55 | - name: Enable kernel-level IPv6 autoconf (workaround broken redirects)
56 | delegate_to: hostA
57 | copy:
58 | dest: /etc/sysctl.d/99-accept_ra.conf
59 | content: "net.ipv6.conf.eth0.accept_ra = 1"
60 | notify: reboot hostA
61 |
62 | - name: Hack enable kernel-level IPv6 autoconf (workaround broken redirects)
63 | delegate_to: hostA
64 | copy:
65 | dest: /etc/rc.local
66 | mode: u=rwx,g=rx,o=rx
67 | content: '#!/bin/sh
68 |
69 | sysctl -w net.ipv6.conf.eth0.accept_ra=1
70 |
71 | '
72 | notify: reboot hostA
73 |
74 | - meta: flush_handlers
75 |
76 | - name: Install python3-apt
77 | delegate_to: hostA
78 | command:
79 | cmd: apt-get -y install python3-apt
80 | creates: /usr/lib/python3/dist-packages/apt/package.py
81 |
82 | - name: Install necessary packages
83 | delegate_to: hostA
84 | package:
85 | update_cache: true
86 | name:
87 | - python3-pip
88 | - python3
89 | - tcpdump
90 | - wget
91 | - libpcap-dev
92 | - libssl-dev
93 | - gcc
94 | - python3-setuptools
95 | - git
96 | - make
97 | - man
98 | - nftables
99 | - iptables
100 | - nmap
101 | - tshark
102 | - nano
103 | - joe
104 | - vim
105 |
106 | - name: Upgrade pip3
107 | delegate_to: hostA
108 | pip:
109 | executable: pip3
110 | state: latest
111 | name: pip
112 |
113 | - name: Install python packages
114 | delegate_to: hostA
115 | pip:
116 | executable: pip3
117 | state: latest
118 | name:
119 | - scapy[basic]
120 | - cryptography
121 | - netaddr
122 |
123 | - name: Clone IPv6 toolkit repository
124 | delegate_to: hostA
125 | git:
126 | repo: https://github.com/fgont/ipv6toolkit
127 | dest: /usr/src/ipv6toolkit
128 | register: clone_toolkit
129 |
130 | - name: Compile the IPv6 toolkit
131 | delegate_to: hostA
132 | command:
133 | cmd: make all
134 | chdir: /usr/src/ipv6toolkit
135 | when: clone_toolkit.changed
136 | register: compile_toolkit
137 |
138 | - name: Install the IPv6 toolkit
139 | delegate_to: hostA
140 | command:
141 | cmd: make install
142 | chdir: /usr/src/ipv6toolkit
143 | when: compile_toolkit.changed
144 |
145 | - name: Clone the THC-IPv6 repository
146 | delegate_to: hostA
147 | git:
148 | repo: https://github.com/vanhauser-thc/thc-ipv6
149 | dest: /usr/src/thc-ipv6
150 | register: clone_thc
151 |
152 | - name: Compile the THC-IPv6 toolkit
153 | delegate_to: hostA
154 | command:
155 | cmd: make
156 | chdir: /usr/src/thc-ipv6
157 | when: clone_thc.changed
158 | register: compile_thc
159 |
160 | - name: Install the THC-IPv6 toolkit
161 | delegate_to: hostA
162 | command:
163 | cmd: make install
164 | chdir: /usr/src/thc-ipv6
165 | when: compile_thc.changed
166 |
167 | - name: Download Termshark x64
168 | delegate_to: hostA
169 | when: ansible_architecture == "x86_64"
170 | unarchive:
171 | src: https://github.com/gcla/termshark/releases/download/v2.4.0/termshark_2.4.0_linux_x64.tar.gz
172 | remote_src: yes
173 | dest: /usr/local/bin/
174 | extra_opts:
175 | - --transform
176 | - "s_^.*/__"
177 |
178 | - name: Download Termshark arm64
179 | delegate_to: hostA
180 | when: ansible_architecture == "aarch64"
181 | unarchive:
182 | src: https://github.com/gcla/termshark/releases/download/v2.4.0/termshark_2.4.0_linux_arm64.tar.gz
183 | remote_src: yes
184 | dest: /usr/local/bin/
185 | extra_opts:
186 | - --transform
187 | - "s_^.*/__"
188 |
189 | - name: Clone container
190 | lxd_container:
191 | name: "{{ item.name }}"
192 | config:
193 | volatile.eth0.hwaddr: "{{ item.mac }}"
194 | state: started
195 | source:
196 | type: copy
197 | source: hostA
198 | profiles: ["default"]
199 | wait_for_ipv4_addresses: true
200 | with_items:
201 | - name: hostB
202 | mac: 00:16:3e:ee:00:0b
203 | - name: hostC
204 | mac: 00:16:3e:ee:00:0c
205 |
206 | - name: Host provision
207 | hosts: default
208 | become: true
209 | tags: host
210 | tasks:
211 | - name: Install python3-apt
212 | command:
213 | cmd: apt-get -y install python3-apt
214 | creates: /usr/lib/python3/dist-packages/apt/package.py
215 |
216 | - name: Import Debian Stretch repo key (for tmux 2.3)
217 | apt_key:
218 | url: "https://ftp-master.debian.org/keys/archive-key-9.asc"
219 |
220 | - name: Enable Debian Stretch repo (for tmux 2.3)
221 | apt_repository:
222 | repo: deb http://archive.debian.org/debian stretch main
223 |
224 | - name: Check tmux version
225 | command: tmux -V
226 | changed_when: false
227 | ignore_errors: true
228 | register: tmux_v
229 |
230 | - name: Uninstall newer tmux
231 | apt:
232 | package: tmux
233 | state: absent
234 | autoremove: yes
235 | when: tmux_v.stdout != "tmux 2.3"
236 | ignore_errors: true
237 |
238 | - name: Install tmux dependency
239 | apt:
240 | package: libtinfo5
241 |
242 | - name: Install tmux from Debian Stretch
243 | apt:
244 | package: tmux
245 | default_release: stretch
246 |
247 | - name: Install necessary packages
248 | apt:
249 | package:
250 | - nginx
251 | - libwebsockets8
252 |
253 | - name: Setup WebSockets connection upgrade
254 | copy:
255 | src: nginx/connection_upgrade.conf
256 | dest: "/etc/nginx/conf.d/"
257 | mode: 0644
258 | notify: reload nginx
259 |
260 | - name: Disable absolute redirects
261 | lineinfile:
262 | line: "absolute_redirect off;"
263 | dest: "/etc/nginx/conf.d/no_absolute_redirects.conf"
264 | create: yes
265 | mode: 0644
266 | notify: reload nginx
267 |
268 | - name: Setup ttyd proxy snippet
269 | copy:
270 | src: nginx/ttydconsole.conf
271 | dest: "/etc/nginx/snippets/"
272 | mode: 0644
273 | notify: reload nginx
274 |
275 | - name: Enable ttyd proxy snippet
276 | lineinfile:
277 | path: "/etc/nginx/sites-available/default"
278 | line: "include /etc/nginx/snippets/ttydconsole.conf;"
279 | insertbefore: "^\\s*location / {.*$"
280 | notify: reload nginx
281 |
282 | - name: Deploy static website
283 | copy:
284 | src: ../../ripe-app-frontend/build/
285 | dest: /var/www/html/
286 | owner: www-data
287 | group: www-data
288 | mode: 0644
289 |
290 | - name: Deploy version file
291 | copy:
292 | src: ../../version.txt
293 | dest: /var/www/html/
294 | owner: www-data
295 | group: www-data
296 | mode: 0644
297 |
298 | - name: Route everything to index.html page
299 | lineinfile:
300 | path: "/etc/nginx/sites-available/default"
301 | line: " try_files $uri /index.html;"
302 | regexp: '^\s*try_files \$uri'
303 | notify: reload nginx
304 |
305 | - name: Start Nginx
306 | service:
307 | name: nginx
308 | state: started
309 | enabled: on
310 |
311 | - name: Download ttyd
312 | get_url:
313 | url: "https://github.com/tsl0922/ttyd/releases/download/1.7.3/ttyd.{{ ansible_architecture }}"
314 | dest: /opt/ttyd
315 | mode: 0755
316 |
317 | - name: Deploy tmux config file
318 | copy:
319 | src: tmux.conf
320 | dest: "/etc/"
321 | mode: 0644
322 |
323 | - name: Deploy ttyd-container unit files
324 | template:
325 | src: "systemd/{{ item }}"
326 | dest: "/etc/systemd/system/"
327 | mode: 0644
328 | notify: systemctl daemon-reload
329 | with_items:
330 | - ttyd-container@.service
331 | - ttyd-admin.service
332 | - ttyd-container.target
333 |
334 | - name: Deploy ttyd-container generator
335 | template:
336 | src: systemd/ttyd-container.generator
337 | dest: "/etc/systemd/system-generators/"
338 | mode: 0755
339 | notify: systemctl daemon-reload
340 |
341 | - name: Enable and start ttyd-container target
342 | service:
343 | name: ttyd-container.target
344 | state: started
345 | enabled: on
346 |
347 | - name: Enable and start ttyd-admin
348 | service:
349 | name: ttyd-admin.service
350 | state: started
351 | enabled: on
352 |
353 | - name: Trim unused Btrfs space for LXD
354 | command: nsenter -m/run/snapd/ns/lxd.mnt fstrim -av
355 | become: true
356 | changed_when: false
357 |
358 | - name: Trim unused host space
359 | command: fstrim -av
360 | become: true
361 | changed_when: false
362 |
363 | handlers:
364 | - name: reload nginx
365 | service:
366 | name: nginx
367 | state: reloaded
368 |
369 | - name: systemctl daemon-reload
370 | systemd:
371 | daemon_reload: yes
372 |
373 |
374 | ...
375 |
--------------------------------------------------------------------------------
/ripe-app-frontend/build/38f376c724439f780378.svg:
--------------------------------------------------------------------------------
1 |
2 | image/svg+xml hostA ROUTER hostB hostC ::1 eth0 eth0 eth0 Network Pre fi x: 2001:db8:f:1::/64 RA ::216:3e ff :feee:a ::216:3e ff :feee:b ::216:3e ff :feee:c
402 |
--------------------------------------------------------------------------------
/ripe-app-frontend/app/workspaces/network/network_diagram.svg:
--------------------------------------------------------------------------------
1 |
2 | image/svg+xml hostA ROUTER hostB hostC ::1 eth0 eth0 eth0 Network Pre fi x: 2001:db8:f:1::/64 RA ::216:3e ff :feee:a ::216:3e ff :feee:b ::216:3e ff :feee:c
402 |
--------------------------------------------------------------------------------