diff --git a/.eslintrc.js b/.eslintrc.js index e130e67..b35a259 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1,5 +1,7 @@ -module.exports = { - "extends": [ - "@nqminds/eslint-config-react", - ], -}; +module.exports = /** @type {import("eslint").ESLint.Options} */ ({ + extends: ["@nqminds/eslint-config-react"], + rules: { + "object-curly-spacing": "off", // conflicts with prettier + "comma-dangle": "off", // conflicts with prettier v2 + }, +}); diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index a1e91f1..6cabafc 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -26,7 +26,7 @@ jobs: - name: Install modules (CI-mode) run: npm ci - + - name: Lint run: npm run lint @@ -42,8 +42,8 @@ jobs: needs: build-docs # Grant GITHUB_TOKEN the permissions required to make a Pages deployment permissions: - pages: write # to deploy to Pages - id-token: write # to verify the deployment originates from an appropriate source + pages: write # to deploy to Pages + id-token: write # to verify the deployment originates from an appropriate source # Deploy to the github-pages environment environment: name: github-pages diff --git a/.gitignore b/.gitignore index bc7ee5f..d193c6e 100644 --- a/.gitignore +++ b/.gitignore @@ -106,3 +106,7 @@ dist # TernJS port file .tern-port + +# Docusaurus build output +/build + diff --git a/.husky/pre-commit b/.husky/pre-commit index 2330023..c88b2db 100755 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -1,4 +1,4 @@ #!/usr/bin/env sh . "$(dirname -- "$0")/_/husky.sh" -npx --no -- lint-staged +npx --no -- lint-staged --concurrent false diff --git a/docs/capture.md b/docs/capture.md index 37fbe4c..83ebc17 100644 --- a/docs/capture.md +++ b/docs/capture.md @@ -6,11 +6,12 @@ title: Capture Service The network capture service has the purpose of monitoring network traffic for each connected device. It can be configured to execute custom middlewares. The packet capture implements the actual network sniffing process. Currently, it uses the [libpcap library](https://github.com/the-tcpdump-group/libpcap). However, it also allows interfacing with [PF_RING](https://www.ntop.org/products/packet-capture/pf_ring/) or similar. The capture service can be configured with the below options: + ```ini # absolute path to the capture SQLite db used by the middlewares captureDbPath = "/path_to_capture/capture.sqlite" # the capture filter for the libpcap library -# example filter="src net 10.0 and dst net 10.0" +# example filter="src net 10.0 and dst net 10.0" filter = "" # libpcap options, see https://www.tcpdump.org/manpages/pcap.3pcap.html # if true, captures all data on the LAN interface @@ -25,6 +26,7 @@ immediate = false # Middlewares The captured packet is sent to every configured middleware for additional processing. The user has the choice to develop her own middleware. The middleware API is defined in `middleware.h`: + ```c struct capture_middleware { struct middleware_context *(*const init)(sqlite3 *db, char *db_path, struct eloop_data *eloop, struct pcap_context *pc); @@ -33,13 +35,15 @@ struct capture_middleware { const char *const name; }; ``` + Every middleware needs to define three functions `init`, `process` and `free`. - - The `init` function initialises the middleware, configures the event loop structures and/or creates new tables in the capture database. - - The `process` function receives as input the captured packets and does the required processing. - - Finally, the `free` function frees the allocated memory and removes the created tables if needed. +- The `init` function initialises the middleware, configures the event loop structures and/or creates new tables in the capture database. +- The `process` function receives as input the captured packets and does the required processing. +- Finally, the `free` function frees the allocated memory and removes the created tables if needed. To add a middleware, the user can create a subfolder in the [`src/capture/middlewares` folder](https://github.com/nqminds/edgesec/tree/main/src/capture/middlewares) with the name `example_middleware` and add the main include file `example_middleware.h` with the contents: + ```c #ifndef EXAMPLE_MIDDLEWARE_H #define EXAMPLE_MIDDLEWARE_H @@ -49,7 +53,9 @@ To add a middleware, the user can create a subfolder in the [`src/capture/middle extern struct capture_middleware example_middleware; #endif ``` + and the main source file `example_middleware.c` with the contents: + ```c #include #include @@ -116,7 +122,9 @@ struct capture_middleware example_middleware = { .name = "example middleware", }; ``` + Then the user needs to add the option `option(USE_EXAMPLE_MIDDLEWARE "Use the example middleware" ON)` in the root `CMakeLists.txt` and subsequently add the lines: + ```cmake # write your CMakeLists.txt file to compile your middleware add_subdirectory(./middlewares/example_middleware) @@ -130,6 +138,7 @@ if (USE_EXAMPLE_MIDDLEWARE) ) endif () ``` + in `capture/CMakeLists.txt`. The capture middleware will execute every defined middleware sequentially. First, it will execute the `init` functions. Then, for every packet it will execute all `process` functions and finally, it will run every `free` function. diff --git a/docs/crypt.md b/docs/crypt.md index 52d7a57..9740734 100644 --- a/docs/crypt.md +++ b/docs/crypt.md @@ -6,47 +6,53 @@ title: Crypt Service The crypt service service implements a key/value store for all other services to store and retrieve encrypted keys or data. To encrypt data the crypt service generates keys that are encrypted using the hardware secure element or a user supplied passphrase. # Store DB + The crypt service is implemented as a sqlite database (the path is set by the `config.ini` parameter `cryptDbPath`), which contains two tables: `secrets` and `store`. The schema for the secrets table is as follows: + ```sql CREATE TABLE secrets (id TEXT NOT NULL, value TEXT, salt TEXT, iv TEXT, PRIMARY KEY (id)); ``` + where `id` is the ID of the generated key, `value` is the value of the key, `iv` the initial value (IV) used to encrypt and decrypt the key and `salt` is the salt parameter. An example of the secrets table row is given below: -|ID|VALUE|SALT|IV| -|--|-----|----|--| -|master|LbknNO6o+s+u1b4wg9eGzQjHCanicVtDlDJBWZ0u4VaV25oIUCt1b5bthzLwhQO0|Z95m5G/+jgb3ga0dufa//w|hka2MmSUkJUJBf7TQMYnug| -|rest|a2UiZR/DLYb3hX61ZQ7Mb/vdVVIchJzkuNnoIhLDCHXe9453IlWjOfOymodUZIsq|RLdlnafYj7279lne7A5UoA|lgTKNxgxbeCxg4VySS/7vw| -|94:b9:7e:15:47:95|lPVf5wqMnb9+8Q8Cik5oetOI9MfA6qjPm1tKTR3WGPWgZYtaybEeDKWGX/x4EUUB|gFvI5tfeHANOafJlFsHXpg|mH2yeX/FEvJd1ilg25Zwcg| +| ID | VALUE | SALT | IV | +| ----------------- | ---------------------------------------------------------------- | ---------------------- | ---------------------- | +| master | LbknNO6o+s+u1b4wg9eGzQjHCanicVtDlDJBWZ0u4VaV25oIUCt1b5bthzLwhQO0 | Z95m5G/+jgb3ga0dufa//w | hka2MmSUkJUJBf7TQMYnug | +| rest | a2UiZR/DLYb3hX61ZQ7Mb/vdVVIchJzkuNnoIhLDCHXe9453IlWjOfOymodUZIsq | RLdlnafYj7279lne7A5UoA | lgTKNxgxbeCxg4VySS/7vw | +| 94:b9:7e:15:47:95 | lPVf5wqMnb9+8Q8Cik5oetOI9MfA6qjPm1tKTR3WGPWgZYtaybEeDKWGX/x4EUUB | gFvI5tfeHANOafJlFsHXpg | mH2yeX/FEvJd1ilg25Zwcg | The `value`, `salt` and `iv` are base64 encoded. When a user or service wants to store a key/value, they will need to provide an ID for the key that will be used to encrypt/decrypt the user's value. If such an ID does not exist in the secrets table, the service will randomly generate one and encrypt it using the hardware secure element or the user supplied passphrase. When the user provides the passphrase, the service will generate an encryption key using the `salt` and [Password-Based Key Derivation Function 2 (PBKDF2)](https://en.wikipedia.org/wiki/PBKDF2). The derived key is not stored on the device. If the user instead uses the hardware secure element, the key derivation, encryption and decryption is done in secure memory. Each key/value pair is stored in the store table with the following schema: + ```sql CREATE TABLE store (key TEXT NOT NULL, value TEXT, id TEXT, iv TEXT, PRIMARY KEY (key)); ``` + where `key` is the key for the value, `value` is the value to be stored, `id` is the key id used to encrypt/decrypt the value and `iv` is the IV used to encrypt/decrypt the value. An example of the store table rows is given below. -|KEY|VALUE|ID|IV| -|---|-----|--|--| -|7815f8ce-57b8-49c8-9121-5b98986cbccd|GCM564Ugwyh0bW3f4JuFkw|master|Ja0pz9cdH7p3Q+BBP2MIrw| -|db07c38a-2842-4f45-9672-74d57ec99e63|23cHWe6r033czxopWsv6Ng|master|FU7hUGGbifro65cv0u0OwQ| -|1a35f54d-c5f9-4072-85b0-4b40f8fb4a14|LR3iRw6SrN/pWKSTJvNtrA|master|x9hFentG2Q6iynHXCk2ktA| -|831ffbb1-2e79-422a-bdad-e9e96a56d568|CdoxKK4PbDvWD9cOdRcTXQ|master|RHR1AGsjpWVHDR4VN2PiLA| +| KEY | VALUE | ID | IV | +| ------------------------------------ | ---------------------- | ------ | ---------------------- | +| 7815f8ce-57b8-49c8-9121-5b98986cbccd | GCM564Ugwyh0bW3f4JuFkw | master | Ja0pz9cdH7p3Q+BBP2MIrw | +| db07c38a-2842-4f45-9672-74d57ec99e63 | 23cHWe6r033czxopWsv6Ng | master | FU7hUGGbifro65cv0u0OwQ | +| 1a35f54d-c5f9-4072-85b0-4b40f8fb4a14 | LR3iRw6SrN/pWKSTJvNtrA | master | x9hFentG2Q6iynHXCk2ktA | +| 831ffbb1-2e79-422a-bdad-e9e96a56d568 | CdoxKK4PbDvWD9cOdRcTXQ | master | RHR1AGsjpWVHDR4VN2PiLA | The `value` and `iv` are base64 encoded. Each row of the store DB contains the `id` of the key that was used to encrypt/decrypt the `value`. The encryption algorithm used is AES 256 CBC. # Secure element -In order to enable the use of secure element one needs to set the `USE_CRYPTO_SERVICE` and `USE_*_HSM` options in CMake when compiling edgesec, which corresponds to a particular implementation of the secure element API. + +In order to enable the use of secure element one needs to set the `USE_CRYPTO_SERVICE` and `USE_*_HSM` options in CMake when compiling edgesec, which corresponds to a particular implementation of the secure element API. In order to add an aditional implementation of a secure hardware element, one can use the generic driver interface `generec_hsm_drive.h` as a template. The generic driver interface defines the context: @@ -55,7 +61,9 @@ struct hsm_context { void *hsm_ctx; }; ``` + that is passed to the init and encrypt/decrypt functions as follows: + ```c struct hsm_context *init_hsm(void); int close_hsm(struct hsm_context *context); @@ -64,4 +72,4 @@ int encrypt_hsm_blob(struct hsm_context *context, uint8_t *in, size_t in_size, u int decrypt_hsm_blob(struct hsm_context *context, uint8_t *in, size_t in_size, uint8_t **out, size_t *out_size); ``` -The developer will have to provide a tailored implementation for the above functions in order to use a particular hardware secure element. An example is provided in `zymkey4_driver.h`. \ No newline at end of file +The developer will have to provide a tailored implementation for the above functions in order to use a particular hardware secure element. An example is provided in `zymkey4_driver.h`. diff --git a/docs/management.md b/docs/management.md index 96443b8..6f9d7eb 100644 --- a/docs/management.md +++ b/docs/management.md @@ -23,7 +23,7 @@ The subnet configuration is given in `config.ini` as follows: # Used on OpenWRT systems to define the bridge prefix bridgePrefix = "br" # The prefix for the interface name that corresponds to a VLAN, -# for instance for VLAN 2 and `interfacePrefix = "br"` +# for instance for VLAN 2 and `interfacePrefix = "br"` # the corresponding interface will be `br2`. interfacePrefix = "br" if0 = "0,10.0.0.1,10.0.0.255,255.255.255.0" @@ -38,6 +38,7 @@ if8 = "8,10.0.8.1,10.0.8.255,255.255.255.0" if9 = "9,10.0.9.1,10.0.9.255,255.255.255.0" if10 = "10,10.0.10.1,10.0.10.255,255.255.255.0" ``` + where `ifn` key enumerates parameters for each interface that will be created by the subnet service, where all parameters are separated by commas. The first parameter is the VLAN ID, the second parameter is the gateway IP, the third parameter is the broadcast IP and the fourth parameter is the netmask. For instance given `if8 = "8,10.0.8.1,10.0.8.255,255.255.255.0"` and `interfacePrefix = "br"` the created interface `br8` has the following parameters: ```console @@ -64,7 +65,7 @@ The `config.ini` parameters for the RADIUS server are as follows: [radius] # The UDP port for the RADIUS server port = 1812 -# The IP of the client (in our case it is the software AP) +# The IP of the client (in our case it is the software AP) # that will connect to the RADIUS server clientIP = "127.0.0.1" # The netmask of the client @@ -72,7 +73,7 @@ clientMask = 32 # The IP of the RADIUS server to which it binds to serverIP = "127.0.0.1" serverMask = 32 -# The key used to encrypt the communication between the +# The key used to encrypt the communication between the # client and RADIUS server secret = "radius" ``` @@ -116,6 +117,7 @@ loggerSyslogLevel = 0 ignoreBroadcastSsid = 0 wpaPskRadius = 2 ``` + where for OpenWRT systems `apBinPath=/sbin/wifi` points to the WIFI configuration script and `device = "radio1"` is the parameter denoting the index of the radio used to configure the WIFI modem. The name of the WIFI AP is given by the paremeter `ssid`. If `generateSsid` from `config.ini` is set ot `true`, the `ssid` parameter will be assign to the hostname of the router. The default encryption key for the WIFI is given by the parameter `wpaPassphrase`. This encryption key will be shared by all connected WIFI devices, if the RADIUS server doesn't assign a different encryption key for a specific device. All the remaining parameters `vlanTaggedInterface`, `hwMode`, `channel`, `wmmEnabled`, `authAlgs`, `wpa`, `wpaKeyMgmt`, `rsnPairwise`, `ctrlInterface`, `macaddrAcl`, `dynamicVlan`, `vlanFile`, `loggerStdout`, `loggerStdoutLevel`, `loggerSyslog `, `loggerSyslogLevel`, `ignoreBroadcastSsid` and `wpaPskRadius` are similar to the ones defined for [hostapd.conf](https://w1.fi/cgit/hostap/plain/hostapd/hostapd.conf). @@ -130,15 +132,15 @@ The `config.ini` parameteres for the DHCP service are as follows: ```ini [dhcp] -# The absolute path to the dnsmasq executable +# The absolute path to the dnsmasq executable # (for OpenWRT systems this path is /etc/init.d/dnsmasq) dhcpBinPath = "/usr/sbin/dnsmasq" -# The absolute path to the dnsmasq configuration file +# The absolute path to the dnsmasq configuration file # (for non OpenWRT this file is generated by edgesec) dhcpConfigPath = "/tmp/dnsmasq.conf" # The absolute path to the IP leases file dhcpScriptPath = "/tmp/dnsmasq_exec.sh" -# The absolute path to the DHCP control script file, which has the role of +# The absolute path to the DHCP control script file, which has the role of # sending the allocated IP address to the edgesec dhcpLeasefilePath = "/tmp/dnsmasq.leases" dhcpRange0 = "0,10.0.0.2,10.0.0.254,255.255.255.0,24h" @@ -153,6 +155,7 @@ dhcpRange8 = "8,10.0.8.2,10.0.8.254,255.255.255.0,24h" dhcpRange9 = "9,10.0.9.2,10.0.9.254,255.255.255.0,24h" dhcpRange10 = "10,10.0.10.2,10.0.10.254,255.255.255.0,24h" ``` + where the `dhcpRange*` parameter configures the IP allocation settings for the DHCP server. The first setting denotes the VLAN index. The second and third settings the pool of IP addresses. The last setting denotes the lease time for the allocated IP address. For `dnsmasq` the control script file is as follows: diff --git a/docs/reflector.md b/docs/reflector.md index 6a84bd2..026d935 100644 --- a/docs/reflector.md +++ b/docs/reflector.md @@ -6,19 +6,21 @@ title: mDNS Reflector Service The mDNS reflector service forwards mDNS packets between subnets. It listens to mDNS queries and answers and rebroadcasts them on the available subnets. # Operation + The mDNS reflector services contains three componets: - 1. mDNS packet listener - 2. mDNS packet decoder - 3. mDNS packet forwarder + +1. mDNS packet listener +2. mDNS packet decoder +3. mDNS packet forwarder The mDNS packet listener captures UDP data packets embedded in IP4 or IP6 packets. The UDP data packets are subsequently decoded in order to retrieve the queries and answers fields. The broadcasted/quiered mDNS adresses and the corresponding IP4/IP6 addresses are stored in memory. These addresses are later used for bridge connections between devices across subnets. Finally, the mDNS packet is forwarded to all available subnets. The main reasoning for forwarding the mDNS packet is that when a device broadcast its address, the mDNS packet doesn't cross its allocated subnet (see [RFC6762](https://datatracker.ietf.org/doc/html/rfc6762)). The options `mdnsReflectIp4` and `mdnsReflectIp6` from `config.ini` if set to `true` will configure the mDNS service to forward mDNS IP4 and IP6 packets, respectively. The options `mdnsFilter = "src net 10.0 and dst net 10.0"` set the filter for the mDNS capture to intercept only packets that correspond only to the subnet activity, i.e., inter-device communication. The mDNS reflector service will automatically create a bridge between two devices across two different subnets, if the following conditions are met: - - At least one device in the bridge broadcasted its mDNS address, - - At least one device in the bridge whishes to connect to the IP address that corresponds to the broadcasted mDNS adrress. +- At least one device in the bridge broadcasted its mDNS address, +- At least one device in the bridge whishes to connect to the IP address that corresponds to the broadcasted mDNS adrress. # Example @@ -32,4 +34,4 @@ The device `A` is broadcasting its mDNS address `devicea.local`. However, due to As a result, device `B` will infer that the IP4 address of `devicea.local` is `10.0.2.34`. Subsequently, if device `B` wants to connect to device `A`, the mDNS reflector will create a bridge between `A` and `B` (see figure below). -![Bridge](/img/reflector-bridge.svg) \ No newline at end of file +![Bridge](/img/reflector-bridge.svg) diff --git a/docs/supervisor.md b/docs/supervisor.md index 7f1479c..1f1e71d 100644 --- a/docs/supervisor.md +++ b/docs/supervisor.md @@ -12,6 +12,7 @@ supervisorControlPort = 32001 # Sets the absolute path to the UNIX domain socket supervisorControlPath = "/tmp/edgesec-control-server" ``` + For instance by using the `netcat` utility one can send the `GET_MAP 11:22:33:44:55:66` command to the supervisor's UNIX domain socket located at `/tmp/edgesec-control-server` as follows: ```bash diff --git a/docusaurus.config.js b/docusaurus.config.js index cacd71c..7cb36a1 100644 --- a/docusaurus.config.js +++ b/docusaurus.config.js @@ -68,8 +68,7 @@ module.exports = { routeBasePath: "/docs", // set to "/" if you want to link directly to docs sidebarPath: require.resolve("./sidebars.js"), // Please change this to your repo. - editUrl: - "https://github.com/nqminds/edgesec.info/edit/main/", + editUrl: "https://github.com/nqminds/edgesec.info/edit/main/", remarkPlugins: [ // renders all mermaid code-blocks found in markdown files [require("remark-mermaid-dataurl"), {}], diff --git a/package.json b/package.json index ab8bc35..96473ef 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,7 @@ "clean": "docusaurus clear", "swizzle": "docusaurus swizzle", "deploy": "docusaurus deploy", - "lint": "eslint *.js src/", + "lint": "eslint *.js src/ && npx prettier . --check", "prepare": "husky install" }, "dependencies": { @@ -49,6 +49,6 @@ }, "lint-staged": { "*.js": "eslint --cache --fix", - "*.{md,yaml,yml,json,css,mdx}": "prettier --write" + "*": "prettier --ignore-unknown --write" } } diff --git a/sidebars.js b/sidebars.js index d892942..68ac6b1 100644 --- a/sidebars.js +++ b/sidebars.js @@ -1,6 +1,13 @@ module.exports = { someSidebar: { - Introduction: ["motivation", "supervisor", "management", "capture", "reflector", "crypt"], + Introduction: [ + "motivation", + "supervisor", + "management", + "capture", + "reflector", + "crypt", + ], Development: ["compilation", "running", "config", "issues"], Deployment: ["packages", "deployment", "devices"], }, diff --git a/src/components/activity-graph/activity-graph.js b/src/components/activity-graph/activity-graph.js index 0abf6aa..eee70fe 100644 --- a/src/components/activity-graph/activity-graph.js +++ b/src/components/activity-graph/activity-graph.js @@ -1,9 +1,9 @@ -import React, {useRef, useState, useEffect} from "react"; +import React, { useRef, useState, useEffect } from "react"; import clsx from "clsx"; import styles from "../../pages/styles.module.css"; import useDimensions from "react-cool-dimensions"; -import {ForceGraph3D} from "react-force-graph"; +import { ForceGraph3D } from "react-force-graph"; import activityData from "./activity-data"; import traffic from "./traffic"; @@ -19,13 +19,15 @@ function ActivityGraph() { const graphRef = useRef(); const [counter, setCounter] = useState(0); const [angle, setAngle] = useState(0); - const [graphData, setGraphData] = useState({nodes: [], links: []}); - const [size, setSize] = useState({width: 100, height: 100}); - const {observe} = useDimensions({onResize: ({width, height}) => { - if (size.width !== width || size.height !== height) { - setSize({height, width}); - } - }}); + const [graphData, setGraphData] = useState({ nodes: [], links: [] }); + const [size, setSize] = useState({ width: 100, height: 100 }); + const { observe } = useDimensions({ + onResize: ({ width, height }) => { + if (size.width !== width || size.height !== height) { + setSize({ height, width }); + } + }, + }); // Data loading useEffect(() => { @@ -43,9 +45,11 @@ function ActivityGraph() { }); setAngle(angle + Math.PI / 300); - setCounter(((counter + 1) % (traffic && traffic.rows.length)) || 0); - const {ip_src, ip_dst} = traffic && traffic.rows[counter] || {}; - const link = graphData.links.find(({source, target}) => source.id === ip_src && target.id === ip_dst); + setCounter((counter + 1) % (traffic && traffic.rows.length) || 0); + const { ip_src, ip_dst } = (traffic && traffic.rows[counter]) || {}; + const link = graphData.links.find( + ({ source, target }) => source.id === ip_src && target.id === ip_dst + ); graphRef.current.emitParticle(link); }, 50); @@ -57,7 +61,8 @@ function ActivityGraph() {
-