diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 7dd895c61..66814f366 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -13,10 +13,10 @@ name: "CodeQL" on: push: - branches: [ main ] + branches: [main] pull_request: # The branches below must be a subset of the branches above - branches: [ main ] + branches: [main] jobs: analyze: @@ -30,40 +30,40 @@ jobs: strategy: fail-fast: false matrix: - language: [ 'javascript', 'python' ] + language: ["javascript", "python"] # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ] # Learn more: # https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed steps: - - name: Checkout repository - uses: actions/checkout@v2 + - name: Checkout repository + uses: actions/checkout@v2 - # Initializes the CodeQL tools for scanning. - - name: Initialize CodeQL - uses: github/codeql-action/init@v1 - with: - languages: ${{ matrix.language }} - # If you wish to specify custom queries, you can do so here or in a config file. - # By default, queries listed here will override any specified in a config file. - # Prefix the list here with "+" to use these queries and those in the config file. - # queries: ./path/to/local/query, your-org/your-repo/queries@main + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v1 + with: + languages: ${{ matrix.language }} + # If you wish to specify custom queries, you can do so here or in a config file. + # By default, queries listed here will override any specified in a config file. + # Prefix the list here with "+" to use these queries and those in the config file. + # queries: ./path/to/local/query, your-org/your-repo/queries@main - # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). - # If this step fails, then you should remove it and run the build manually (see below) - - name: Autobuild - uses: github/codeql-action/autobuild@v1 + # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). + # If this step fails, then you should remove it and run the build manually (see below) + - name: Autobuild + uses: github/codeql-action/autobuild@v1 - # ℹ️ Command-line programs to run using the OS shell. - # 📚 https://git.io/JvXDl + # ℹ️ Command-line programs to run using the OS shell. + # 📚 https://git.io/JvXDl - # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines - # and modify them (or add more) to build your code if your project - # uses a compiled language + # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines + # and modify them (or add more) to build your code if your project + # uses a compiled language - #- run: | - # make bootstrap - # make release + #- run: | + # make bootstrap + # make release - - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v1 + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v1 diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index d55d756b1..c7a09ecc5 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -3,9 +3,9 @@ name: Validate Main on: # Triggers the workflow on push or pull request events for the master branch push: - branches: [ main ] + branches: [main] pull_request: - branches: [ main ] + branches: [main] # Allows you to run this workflow manually from the Actions tab workflow_dispatch: @@ -24,7 +24,7 @@ jobs: - name: Setup nodejs uses: actions/setup-node@v2 with: - node-version: '14' + node-version: "14" - name: Run makefile run: | @@ -38,7 +38,6 @@ jobs: name: colte-package path: BUILD/colte_*.deb - package-install: runs-on: ubuntu-latest needs: build @@ -60,7 +59,6 @@ jobs: sudo dpkg -i colte_*.deb || /bin/true sudo apt-get install --yes -f - run-tests: runs-on: ubuntu-latest services: @@ -78,7 +76,7 @@ jobs: - name: Setup nodejs uses: actions/setup-node@v2 with: - node-version: '14' + node-version: "14" - name: Checkout repo uses: actions/checkout@v2 diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 000000000..b9daecb01 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,17 @@ +**/node_modules + +# Vendored css and js files +emergency/homepage/bootstrap.css +emergency/registry/polls/static/css/bootstrap.css +webadmin/public/bootstrap-4.5.3 +webgui/public/stylesheets/bootstrap.min.css + +# Ignore Jekyll build products Should be moved into the docs directory once +# https://github.com/prettier/prettier/issues/4081 is merged. +docs/_site +docs/.sass-cache +docs/.jekyll-cache +docs/.jekyll-metadata + +# Ignore python files, that will be formatted via black +python/**/*.py \ No newline at end of file diff --git a/.prettierrc.yml b/.prettierrc.yml new file mode 100644 index 000000000..7b3b9a903 --- /dev/null +++ b/.prettierrc.yml @@ -0,0 +1,5 @@ +trailingComma: "es5" +printWidth: 100 +tabWidth: 2 +semi: true +bracketSpacing: false diff --git a/README.md b/README.md index c209ba511..d523f34ea 100644 --- a/README.md +++ b/README.md @@ -1,25 +1,31 @@ # CoLTE + CoLTE is the Community LTE Project. It is designed to be an all-in-one turnkey solution that sets up a small-scale locally-run LTE network. CoLTE consists of several main elements working together: -1) An all-in-one software EPC, powered by [open5gs] (https://github.com/open5gs/open5gs). -2) Network monitoring software, powered by [haulage](https://github.com/uw-ictd/haulage), to keep track of how many bytes each user uses and bill appropriately. -3) A Web GUI that lets network users check the status of their account, top up, transfer/resell credit, and buy data packages. -4) A Web-based admin tool that lets administrators manage all the information above. -5) Local Web and DNS serving/caching via Nginx and BIND. + +1. An all-in-one software EPC, powered by [open5gs] (https://github.com/open5gs/open5gs). +2. Network monitoring software, powered by [haulage](https://github.com/uw-ictd/haulage), to keep track of how many bytes each user uses and bill appropriately. +3. A Web GUI that lets network users check the status of their account, top up, transfer/resell credit, and buy data packages. +4. A Web-based admin tool that lets administrators manage all the information above. +5. Local Web and DNS serving/caching via Nginx and BIND. # Installation + ### Basic System Requirements: + We now support Ubuntu 18.04 (bionic), Ubuntu 20.04 (focal), and Debian 10 (buster). Our primary deployments are currently on bionic and buster, and we have better test coverage for those distributions. We recommend buster for new installs. ### Apt Packages + To ease deployment, we host apt packages on our server. You will need to add our apt repository to get colte and haulage, and you will also need to add the open5gs repository separately. To do this, use the following commands according to your distribution: #### Debian 10 (buster) (Recommended) + ```shell echo "deb [signed-by=/usr/share/keyrings/colte-archive-keyring.gpg] http://colte.cs.washington.edu $(lsb_release -sc) main" | sudo tee /etc/apt/sources.list.d/colte.list sudo wget -O /usr/share/keyrings/colte-archive-keyring.gpg http://colte.cs.washington.edu/colte-archive-keyring.gpg @@ -32,6 +38,7 @@ sudo apt install colte ``` #### Ubuntu 18.04 or 20.04 (bionic or focal) + ```shell echo "deb [signed-by=/usr/share/keyrings/colte-archive-keyring.gpg] http://colte.cs.washington.edu $(lsb_release -sc) main" | sudo tee /etc/apt/sources.list.d/colte.list sudo wget -O /usr/share/keyrings/colte-archive-keyring.gpg http://colte.cs.washington.edu/colte-archive-keyring.gpg @@ -46,52 +53,67 @@ After installation, the admin tool will be running and listening on [http://loca Haulage can be started with `sudo haulage` or `sudo systemctl start haulage`, but will likely fail if not first configured for your system (see [configuration](#Configuration)). To start Open5Gs, refer to the docs [here](https://open5gs.org/open5gs/docs/). ### Working With The Source: + The top-level Makefile will compile all source and generate a `.deb` package if you type `make`. If you want to run the webgui or webadmin from source in a local terminal without doing a system-wide installation, `cd` into the corresponding directory and then do the following: + ``` npm install npm start ``` # Configuration + After installation, pointers to all the various config files can be found in `/etc/colte/`. The main config file is `config.yml`. After you edit this file, run `colteconf` to reconfigure all components and restart them as necessary. Note that you **must** run colteconf at least once after installing CoLTE, because there is no way for us to know some of the options (e.g. upstream and downstream interfaces). ## Basic Configs: + Conceptually, your machine will need two network connections: one to the Internet (the upstream WAN) and another to the eNodeB (the downstream LAN) - these can actually be the same interface, it doesn't matter. Set `wan_iface` to your upstream (Internet) interface, and `enb_iface_addr` to the downstream interface's address/subnet. Don't worry about matching `lte_subnet` to any value in particular, because this subnet is created and assigned to the virtual `ogstun` interface used by the Open5Gs pgw. ## Adding Users + Once you’ve configured the system, you will have to add user accounts. The best way to do this is to use `coltedb`. You will have to provide the user’s IMSI, phone number (can be any value you choose), static IP address, and security values (KI and OPC). ## Configuring The Phone -Once you’ve added a user, you *may* have to configure the phone’s APN settings as well. This is easy to do: go to Settings -> Mobile Network -> Advanced -> APN Settings and add an APN. The values for name and apn should both just be “internet”, you can leave everything else alone as it is. + +Once you’ve added a user, you _may_ have to configure the phone’s APN settings as well. This is easy to do: go to Settings -> Mobile Network -> Advanced -> APN Settings and add an APN. The values for name and apn should both just be “internet”, you can leave everything else alone as it is. # Money And Accounting + ## System Architecture -Setting the `metered` variable to `true` (this is on by default) turns on three services: `haulage`, `colte-webgui`, and `colte-webadmin`. [haulage](https://github.com/uw-ictd/haulage) monitors the `ogstun` interface, draws user accounts down from a quota, and turns them off when they hit zero. Users can interact with their account (transfer money, buy data packages, etc) via the webgui - accessible by default at `http://(your IP address):7999`. Similarly, we provide a separate web-based tool for network administrators to change account balances, enable or disable specific users, topup accounts, and transfer money from one user to another. It is accessible *only* from the EPC, at `http://(your IP address):7998`. The username is `admin` and the default password is `password`. + +Setting the `metered` variable to `true` (this is on by default) turns on three services: `haulage`, `colte-webgui`, and `colte-webadmin`. [haulage](https://github.com/uw-ictd/haulage) monitors the `ogstun` interface, draws user accounts down from a quota, and turns them off when they hit zero. Users can interact with their account (transfer money, buy data packages, etc) via the webgui - accessible by default at `http://(your IP address):7999`. Similarly, we provide a separate web-based tool for network administrators to change account balances, enable or disable specific users, topup accounts, and transfer money from one user to another. It is accessible _only_ from the EPC, at `http://(your IP address):7998`. The username is `admin` and the default password is `password`. ## Administration + To add money to a user’s account, use `coltedb topup` or the webadmin tool. By default, users start out with a zero money balance and 10MB of data-balance. To configure the data packages users can buy, as well as their price-points, edit `/etc/colte/pricing.json`. ## WebAdmin and WebGUI + Both of these services are started automatically after installation. They have detailed configurations in `/etc/colte/{webgui.env|webadmin.env}`; for more details, consult `/{webadmin|webgui}/README`. You will have to restart these services after you change any of these variables, and you can do so with: + ``` sudo systemctl {start|stop} {colte-webadmin|colte-webgui} ``` # Log Files + `/var/log/colte`. # Known Issues + - Right now, the webservices are only hosted as high-number ports. In the interest of not dominating your system we do not currently integrate with any apps that serve DNS or HTTP (e.g. `bind` or `nginx`); we plan to eventually support this. In the meantime, you are responsible for either (a) changing the app to listen on 80 or (b) plumbing a port-forwarding solution using `nginx` or something similar. - **open5gs-pgwd conflicts with systemd-networkd:** The open5gs package has a known issue wherein open5gs-pgwd conflicts with systemd-networkd. This is already fixed in source, and will be fixed in the packages as soon as Sukchan drafts a new release of open5gs. Until then, you will see an error after running colteconf, but the following commands will fix it: + ``` sudo systemctl stop open5gs-pgwd sudo systemctl restart systemd-networkd sudo systemctl start open5gs-pgwd ``` + - **systemd-networkd sometimes does not bring up the tun IP address:** -I have seen this issue occasionally, but have not been able to reproduce it consistently. I am not entirely sure what causes it, but have found some other discussion about a similar issue. The issue is claimed to have been fixed in systemd-241, but Ubuntu 18.04 ships with version 237. This issue pops up occasionally, but usually right after you change the `lte_subnet` variable and run `colteconf`. Sometimes, but not always, starting (or restarting) `open5gs-pgwd` and/or `systemd-networkd` will fix this issue. If not, a system reboot usually does the trick. + I have seen this issue occasionally, but have not been able to reproduce it consistently. I am not entirely sure what causes it, but have found some other discussion about a similar issue. The issue is claimed to have been fixed in systemd-241, but Ubuntu 18.04 ships with version 237. This issue pops up occasionally, but usually right after you change the `lte_subnet` variable and run `colteconf`. Sometimes, but not always, starting (or restarting) `open5gs-pgwd` and/or `systemd-networkd` will fix this issue. If not, a system reboot usually does the trick. # What about OAI? diff --git a/colte-common-models/customer.js b/colte-common-models/customer.js index eae0b19de..a956f3b11 100644 --- a/colte-common-models/customer.js +++ b/colte-common-models/customer.js @@ -1,37 +1,31 @@ // database connection will be injected externally. let knex = null; -const { attachPaginate } = require('knex-paginate'); +const {attachPaginate} = require("knex-paginate"); attachPaginate(); -var fs = require('fs'); -var dateTime = require('date-time'); +var fs = require("fs"); +var dateTime = require("date-time"); var transaction_log = process.env.TRANSACTION_LOG || "/var/log/colte/transaction_log.txt"; function transfer_balance_impl(sender_imsi, receiver_imsi, amount, kind) { function fetch_bals(trx) { - return trx.select( - 'balance' - ).forUpdate( - ).where( - 'imsi', sender_imsi - ).from( - 'customers' - ).then((sender_bal) => { - return trx.select( - 'balance' - ).where( - 'imsi', receiver_imsi - ).from( - 'customers' - ).then((receiver_bal) => { - return [sender_bal, receiver_bal]; + return trx + .select("balance") + .forUpdate() + .where("imsi", sender_imsi) + .from("customers") + .then((sender_bal) => { + return trx + .select("balance") + .where("imsi", receiver_imsi) + .from("customers") + .then((receiver_bal) => { + return [sender_bal, receiver_bal]; + }); }); - }); } function transfer_func(trx) { - return fetch_bals( - trx - ).then((data) => { + return fetch_bals(trx).then((data) => { var err = null; var sender_bal; var receiver_bal; @@ -51,33 +45,36 @@ function transfer_balance_impl(sender_imsi, receiver_imsi, amount, kind) { } } if (err) { - return new Promise((res, rej) => { rej(err) }); + return new Promise((res, rej) => { + rej(err); + }); } sender_bal = Number(sender_bal) - Number(amount); receiver_bal = Number(receiver_bal) + Number(amount); - return trx.update( - { balance: sender_bal } - ).where( - 'imsi', sender_imsi - ).from( - 'customers' - ).then((unused_data) => { - // note we're still using the data argument from the fetch_bals promise - return trx.update({ balance: receiver_bal }).where('imsi', receiver_imsi).from('customers') - }).then((data2) => { - var result = "Transfered " + amount + ". New balances are " + sender_bal + " and " + receiver_bal; - console.log(result); + return trx + .update({balance: sender_bal}) + .where("imsi", sender_imsi) + .from("customers") + .then((unused_data) => { + // note we're still using the data argument from the fetch_bals promise + return trx.update({balance: receiver_bal}).where("imsi", receiver_imsi).from("customers"); + }) + .then((data2) => { + var result = + "Transfered " + amount + ". New balances are " + sender_bal + " and " + receiver_bal; + console.log(result); - fs.appendFile( - transaction_log, dateTime() + " " + kind + " " + sender_imsi + " " + receiver_imsi + " " + amount + "\n", - function(err) { - if(err) { - return console.log(err); + fs.appendFile( + transaction_log, + dateTime() + " " + kind + " " + sender_imsi + " " + receiver_imsi + " " + amount + "\n", + function (err) { + if (err) { + return console.log(err); + } } - } - ); - }) + ); + }); }); } @@ -90,115 +87,133 @@ var customer = { }, all(page) { - return knex.select( - 'imsi', 'msisdn', 'raw_down', 'raw_up', 'balance', 'data_balance', 'enabled', 'bridged', 'admin', 'username' - ).from( - 'customers' - ).paginate( - {perPage: 10, currentPage: page, isLengthAware: true} - ); + return knex + .select( + "imsi", + "msisdn", + "raw_down", + "raw_up", + "balance", + "data_balance", + "enabled", + "bridged", + "admin", + "username" + ) + .from("customers") + .paginate({perPage: 10, currentPage: page, isLengthAware: true}); }, find_by_ip(ip) { - return knex.select( - 'customers.imsi', 'raw_up', 'raw_down', 'balance', 'data_balance', 'msisdn', 'admin', 'username' - ).from( - 'customers' - ).join( - 'static_ips', "customers.imsi", "=", "static_ips.imsi" - ).where( - 'static_ips.ip', ip - ); + return knex + .select( + "customers.imsi", + "raw_up", + "raw_down", + "balance", + "data_balance", + "msisdn", + "admin", + "username" + ) + .from("customers") + .join("static_ips", "customers.imsi", "=", "static_ips.imsi") + .where("static_ips.ip", ip); }, find(imsi) { - return knex.select( - 'imsi', 'raw_up', 'raw_down', 'balance', 'data_balance', 'msisdn', 'bridged', 'enabled' - ).where( - 'imsi', imsi - ).from( - 'customers' - ); + return knex + .select( + "imsi", + "raw_up", + "raw_down", + "balance", + "data_balance", + "msisdn", + "bridged", + "enabled" + ) + .where("imsi", imsi) + .from("customers"); }, update(imsi, bridged, enabled, balance, data_balance, username) { - return knex.update( - {balance: balance, data_balance: data_balance, bridged: bridged, enabled: enabled, username: username} - ).where( - 'imsi', imsi - ).from( - 'customers' - ).catch(function(error) { - throw new Error(error.sqlMessage); - }); + return knex + .update({ + balance: balance, + data_balance: data_balance, + bridged: bridged, + enabled: enabled, + username: username, + }) + .where("imsi", imsi) + .from("customers") + .catch(function (error) { + throw new Error(error.sqlMessage); + }); }, change_enabled(msisdn, isEnabled) { - return knex.select( - 'enabled' - ).where( - 'msisdn', msisdn - ).from( - 'customers' - ).catch(function (error) { - throw new Error(error.sqlMessage); - }).then( - function(rows) { + return knex + .select("enabled") + .where("msisdn", msisdn) + .from("customers") + .catch(function (error) { + throw new Error(error.sqlMessage); + }) + .then(function (rows) { if (rows.length != 1) { console.log("msisdn error"); throw new Error("msisdn error"); } return rows; - } - ).then(function(rows) { - return knex.update( - {enabled: isEnabled} - ).where( - 'msisdn', msisdn - ).from( - 'customers' - ).catch(function(error) { - throw new Error(error.sqlMessage); + }) + .then(function (rows) { + return knex + .update({enabled: isEnabled}) + .where("msisdn", msisdn) + .from("customers") + .catch(function (error) { + throw new Error(error.sqlMessage); + }); }); - }) }, top_up(imsi, delta) { - return knex.select( - 'balance' - ).where( - 'imsi', imsi - ).from( - 'customers' - ).catch(function(error) { - throw new Error(error.sqlMessage); - }).then(function(rows) { - if (rows.length != 1) { - throw new Error("imsi error"); - } - return rows; - }).then(function(rows) { - var newBalance = parseInt(rows[0].balance) + parseInt(delta); - var rval = knex.update( - {balance: newBalance} - ).where( - 'imsi', imsi - ).from( - 'customers' - ).catch(function (error) { + return knex + .select("balance") + .where("imsi", imsi) + .from("customers") + .catch(function (error) { throw new Error(error.sqlMessage); - }); + }) + .then(function (rows) { + if (rows.length != 1) { + throw new Error("imsi error"); + } + return rows; + }) + .then(function (rows) { + var newBalance = parseInt(rows[0].balance) + parseInt(delta); + var rval = knex + .update({balance: newBalance}) + .where("imsi", imsi) + .from("customers") + .catch(function (error) { + throw new Error(error.sqlMessage); + }); - fs.appendFile( - transaction_log, dateTime() + " TOPUP " + imsi + " " + delta + "\n", - function(err) { - if(err) { - return console.log(err); + fs.appendFile( + transaction_log, + dateTime() + " TOPUP " + imsi + " " + delta + "\n", + function (err) { + if (err) { + return console.log(err); + } } - } - ); - return rval; - }) + ); + return rval; + }); }, // moves "amount" from the customer with sender_imsi to the customer with receiver_imsi @@ -214,77 +229,75 @@ var customer = { }, transfer_balance_msisdn(sender_imsi, receiver_msisdn, amount) { - return knex.select( - 'imsi' - ).where( - 'msisdn', receiver_msisdn - ).from( - 'customers' - ).catch(function(error) { - throw new Error(error.sqlMessage); - }).then(function(rows) { - if (rows.length != 1) { - throw new Error("msisdn error"); - } - return rows; - }).then(function(rows) { - return customer.transfer_balance(sender_imsi, rows[0].imsi, amount); - }) + return knex + .select("imsi") + .where("msisdn", receiver_msisdn) + .from("customers") + .catch(function (error) { + throw new Error(error.sqlMessage); + }) + .then(function (rows) { + if (rows.length != 1) { + throw new Error("msisdn error"); + } + return rows; + }) + .then(function (rows) { + return customer.transfer_balance(sender_imsi, rows[0].imsi, amount); + }); }, -// ASSUMPTION: all three of these values are already sanitized/validated. -// We can cancel the transaction if (for some reason) the user doesn't -// have enough funds, otherwise no logic is really needed. + // ASSUMPTION: all three of these values are already sanitized/validated. + // We can cancel the transaction if (for some reason) the user doesn't + // have enough funds, otherwise no logic is really needed. purchase_package(imsi, cost, data) { - function purchase_func(trx) { console.log("IMSI = " + imsi + " cost = " + cost + " data = " + data); - return trx.select( - 'balance', 'data_balance' - ).forUpdate( - ).where( - 'imsi', imsi - ).from( - 'customers' - ).catch(function(error) { - throw new Error(error.sqlMessage); - }).then(function(rows) { - if (rows.length != 1) { - throw new Error("IMSI error"); - } - return rows; - }).then(function(rows) { - var newBalance = parseInt(rows[0].balance) - parseInt(cost); - var newData = parseInt(rows[0].data_balance) + parseInt(data); + return trx + .select("balance", "data_balance") + .forUpdate() + .where("imsi", imsi) + .from("customers") + .catch(function (error) { + throw new Error(error.sqlMessage); + }) + .then(function (rows) { + if (rows.length != 1) { + throw new Error("IMSI error"); + } + return rows; + }) + .then(function (rows) { + var newBalance = parseInt(rows[0].balance) - parseInt(cost); + var newData = parseInt(rows[0].data_balance) + parseInt(data); - if (newBalance < 0) { - throw new Error("Insufficient funds for transfer!"); - } + if (newBalance < 0) { + throw new Error("Insufficient funds for transfer!"); + } - var rval = trx.update( - {balance: newBalance, data_balance: newData} - ).where( - 'imsi', imsi - ).from( - 'customers' - ).catch((error) => { - throw new Error(error.sqlMessage); - }); + var rval = trx + .update({balance: newBalance, data_balance: newData}) + .where("imsi", imsi) + .from("customers") + .catch((error) => { + throw new Error(error.sqlMessage); + }); - fs.appendFile( - transaction_log, - dateTime() + " PURCHASE " + imsi + " " + data + " " + cost + "\n", - function(err) { - if(err) { - return console.log(err); + fs.appendFile( + transaction_log, + dateTime() + " PURCHASE " + imsi + " " + data + " " + cost + "\n", + function (err) { + if (err) { + return console.log(err); + } } - }); - return rval; - }); + ); + return rval; + }); } return knex.transaction(purchase_func); - } -} + }, +}; module.exports = customer; diff --git a/colte-common-models/db/migrations/20210222213205_init.js b/colte-common-models/db/migrations/20210222213205_init.js index a855afafc..2e8ab036a 100644 --- a/colte-common-models/db/migrations/20210222213205_init.js +++ b/colte-common-models/db/migrations/20210222213205_init.js @@ -1,30 +1,29 @@ - -exports.up = function(knex) { +exports.up = function (knex) { return knex.schema .createTable("customers", (table) => { - table.string("imsi", 16).notNullable().primary(); - table.string("username", 50).notNullable(); - table.bigInteger("raw_down").notNullable().unsigned().defaultTo(0); - table.bigInteger("raw_up").notNullable().unsigned().defaultTo(0); - table.bigInteger("data_balance").notNullable().defaultTo(10000000); - table.decimal("balance", 15,4).notNullable().defaultTo(0); - table.boolean("bridged").notNullable().defaultTo(true); - table.boolean("enabled").notNullable().defaultTo(true); - table.boolean("admin").notNullable().defaultTo(false); - table.string("msisdn", 16).notNullable(); + table.string("imsi", 16).notNullable().primary(); + table.string("username", 50).notNullable(); + table.bigInteger("raw_down").notNullable().unsigned().defaultTo(0); + table.bigInteger("raw_up").notNullable().unsigned().defaultTo(0); + table.bigInteger("data_balance").notNullable().defaultTo(10000000); + table.decimal("balance", 15, 4).notNullable().defaultTo(0); + table.boolean("bridged").notNullable().defaultTo(true); + table.boolean("enabled").notNullable().defaultTo(true); + table.boolean("admin").notNullable().defaultTo(false); + table.string("msisdn", 16).notNullable(); }) .createTable("static_ips", (table) => { - table.string("imsi", 16).notNullable().primary(); - table.string("ip", 16).notNullable().unique(); - table.foreign("imsi").references("customers.imsi"); + table.string("imsi", 16).notNullable().primary(); + table.string("ip", 16).notNullable().unique(); + table.foreign("imsi").references("customers.imsi"); }); }; -exports.down = function(knex) { +exports.down = function (knex) { return knex.schema .table("static_ips", (table) => { - table.dropForeign("imsi"); + table.dropForeign("imsi"); }) .dropTable("customers") - .dropTable("static_ips") + .dropTable("static_ips"); }; diff --git a/colte-common-models/db/seeds/00-baseline.js b/colte-common-models/db/seeds/00-baseline.js index 3d6f78c32..a4b3e48a4 100644 --- a/colte-common-models/db/seeds/00-baseline.js +++ b/colte-common-models/db/seeds/00-baseline.js @@ -1,25 +1,82 @@ - -exports.seed = function(knex) { +exports.seed = function (knex) { // Deletes ALL existing entries - return knex('static_ips').del() - .then(() => { return knex("customers").del(); }) + return knex("static_ips") + .del() .then(() => { - return knex('customers').insert([ - {imsi: "000000000000001", username: "localAdmin", raw_down: 0, raw_up: 0, data_balance: 100000000, balance: 0, bridged: true, enabled: true, admin: false, msisdn: "99"}, - {imsi: "000000000000002", username: "User2", raw_down: 0, raw_up: 0, data_balance: 100000000, balance: 2500, bridged: true, enabled: true, admin: false, msisdn: "2"}, - {imsi: "000000000000003", username: "User3", raw_down: 0, raw_up: 0, data_balance: 100000000, balance: 0, bridged: true, enabled: true, admin: false, msisdn: "3"}, - {imsi: "000000000000004", username: "User4", raw_down: 0, raw_up: 0, data_balance: 100000000, balance: 0, bridged: true, enabled: true, admin: false, msisdn: "4"}, - {imsi: "000000000000005", username: "User5", raw_down: 0, raw_up: 0, data_balance: 100000000, balance: 25000000, bridged: true, enabled: true, admin: false, msisdn: "5"} + return knex("customers").del(); + }) + .then(() => { + return knex("customers").insert([ + { + imsi: "000000000000001", + username: "localAdmin", + raw_down: 0, + raw_up: 0, + data_balance: 100000000, + balance: 0, + bridged: true, + enabled: true, + admin: false, + msisdn: "99", + }, + { + imsi: "000000000000002", + username: "User2", + raw_down: 0, + raw_up: 0, + data_balance: 100000000, + balance: 2500, + bridged: true, + enabled: true, + admin: false, + msisdn: "2", + }, + { + imsi: "000000000000003", + username: "User3", + raw_down: 0, + raw_up: 0, + data_balance: 100000000, + balance: 0, + bridged: true, + enabled: true, + admin: false, + msisdn: "3", + }, + { + imsi: "000000000000004", + username: "User4", + raw_down: 0, + raw_up: 0, + data_balance: 100000000, + balance: 0, + bridged: true, + enabled: true, + admin: false, + msisdn: "4", + }, + { + imsi: "000000000000005", + username: "User5", + raw_down: 0, + raw_up: 0, + data_balance: 100000000, + balance: 25000000, + bridged: true, + enabled: true, + admin: false, + msisdn: "5", + }, ]); }) .then(() => { // Inserts seed entries - return knex('static_ips').insert([ + return knex("static_ips").insert([ {imsi: "000000000000001", ip: "127.0.0.1"}, {imsi: "000000000000002", ip: "192.168.151.2"}, {imsi: "000000000000003", ip: "192.168.151.3"}, {imsi: "000000000000004", ip: "192.168.151.4"}, - {imsi: "000000000000005", ip: "192.168.151.5"} + {imsi: "000000000000005", ip: "192.168.151.5"}, ]); }); }; diff --git a/colte-common-models/index.js b/colte-common-models/index.js index 19011c5b4..91d1a51da 100644 --- a/colte-common-models/index.js +++ b/colte-common-models/index.js @@ -1,16 +1,16 @@ -'use strict' -const env = process.env.NODE_ENV || 'development'; +"use strict"; +const env = process.env.NODE_ENV || "development"; function buildCustomer() { - let config = require("./knexfile.js")[env] - config.connection.database = process.env.DB_NAME; - const knex = require('knex')(config); - const customer = require('./customer.js'); + let config = require("./knexfile.js")[env]; + config.connection.database = process.env.DB_NAME; + const knex = require("knex")(config); + const customer = require("./customer.js"); - customer.register_knex(knex) - return customer + customer.register_knex(knex); + return customer; } module.exports.buildCustomer = buildCustomer; -module.exports.getKnexInstance = require('knex'); -module.exports.knexFile = require("./knexfile.js"); \ No newline at end of file +module.exports.getKnexInstance = require("knex"); +module.exports.knexFile = require("./knexfile.js"); diff --git a/colte-common-models/knexfile.js b/colte-common-models/knexfile.js index 2a04bdf49..3e7cf7c92 100644 --- a/colte-common-models/knexfile.js +++ b/colte-common-models/knexfile.js @@ -1,66 +1,64 @@ // Update with your config settings. module.exports = { - test: { - client: 'mysql', + client: "mysql", connection: { host: process.env.DB_HOST, port: process.env.DB_PORT || 3306, database: process.env.DB_NAME, user: process.env.DB_USER, - password: process.env.DB_PASSWORD + password: process.env.DB_PASSWORD, }, migrations: { - directory: './db/migrations' + directory: "./db/migrations", }, seeds: { - directory: "./db/seeds" - } + directory: "./db/seeds", + }, }, development: { - client: 'mysql', + client: "mysql", connection: { host: process.env.DB_HOST, database: process.env.DB_NAME, user: process.env.DB_USER, - password: process.env.DB_PASSWORD - } + password: process.env.DB_PASSWORD, + }, }, staging: { - client: 'mysql', + client: "mysql", connection: { host: process.env.DB_HOST, database: process.env.DB_NAME, user: process.env.DB_USER, - password: process.env.DB_PASSWORD + password: process.env.DB_PASSWORD, }, pool: { min: 2, - max: 10 + max: 10, }, migrations: { - tableName: 'knex_migrations' - } + tableName: "knex_migrations", + }, }, production: { - client: 'mysql', + client: "mysql", connection: { host: process.env.DB_HOST, database: process.env.DB_NAME, user: process.env.DB_USER, - password: process.env.DB_PASSWORD + password: process.env.DB_PASSWORD, }, pool: { min: 2, - max: 10 + max: 10, }, migrations: { - tableName: 'knex_migrations' - } - } - + tableName: "knex_migrations", + }, + }, }; diff --git a/docs/404.html b/docs/404.html index af2774423..a0a278f7c 100644 --- a/docs/404.html +++ b/docs/404.html @@ -23,4 +23,4 @@
Page not found :(
The requested page could not be found.
- \ No newline at end of file + diff --git a/docs/README.md b/docs/README.md index ebd2e2814..0d8df72bd 100644 --- a/docs/README.md +++ b/docs/README.md @@ -4,4 +4,4 @@ This directory contains higher-level user-facing documentation for the CoLTE project. The intent is to present an approachable version of the documentation targeted at _users_ of CoLTE, not developers. Development documentation for now is probably most appropriately captured in README.md or ARCHITECTURE.md files as -appropriate. \ No newline at end of file +appropriate. diff --git a/docs/_config.yml b/docs/_config.yml index 363db6f34..68da72f45 100644 --- a/docs/_config.yml +++ b/docs/_config.yml @@ -23,7 +23,7 @@ description: >- # this means to ignore newlines until "baseurl:" the University of Washington ICTD Lab. baseurl: "" # the subpath of your site, e.g. /blog twitter_username: "" -github_username: "uw-ictd" +github_username: "uw-ictd" # For local development consider including the theme as a gem below instead of # using ghpages remote themes, and allowing the url to default diff --git a/docs/about.md b/docs/about.md index 2f4833e34..a07f9340f 100644 --- a/docs/about.md +++ b/docs/about.md @@ -8,18 +8,18 @@ CoLTE is the Community LTE Project. It is designed to be an all-in-one turnkey solution that sets up a small-scale locally-run LTE/5G network. CoLTE consists of several main elements working together: - 1. An all-in-one software EPC, powered by [open5gs] (https://github.com/open5gs/open5gs). +1. An all-in-one software EPC, powered by [open5gs] (https://github.com/open5gs/open5gs). - 2. Network monitoring software, powered by haulage, to keep track of how many - bytes each user uses and bill appropriately. +2. Network monitoring software, powered by haulage, to keep track of how many + bytes each user uses and bill appropriately. - 3. A Web GUI that lets network users check the status of their account, top - up, transfer/resell credit, and buy data packages. +3. A Web GUI that lets network users check the status of their account, top + up, transfer/resell credit, and buy data packages. - 4. A Web-based admin tool that lets administrators manage all the information - above. +4. A Web-based admin tool that lets administrators manage all the information + above. - 5. Local Web and DNS serving/caching via Nginx and BIND. +5. Local Web and DNS serving/caching via Nginx and BIND. For details of the history of the colte project, checkout the CoLTE blog at [blog.colte.network](https://blog.colte.network). diff --git a/docs/index.md b/docs/index.md index 6fa4cf895..638513b5c 100644 --- a/docs/index.md +++ b/docs/index.md @@ -12,4 +12,4 @@ detailed information about CoLTE, check out the bar on the left. If you would like to contribute to this documentation, we value your input! Please reach out with suggestions to `colte@cs.washington.edu` or open an issue [https://github.com/uw-ictd/colte/issues/new](https://github.com/uw-ictd/colte/issues/new) -in our repository. \ No newline at end of file +in our repository. diff --git a/docs/tutorials/epc-setup.md b/docs/tutorials/epc-setup.md index 991fb30f1..6eb5f778b 100644 --- a/docs/tutorials/epc-setup.md +++ b/docs/tutorials/epc-setup.md @@ -1,5 +1,5 @@ --- -title: "EPC \"Network in a Box\" Setup" +title: 'EPC "Network in a Box" Setup' parent: Tutorials layout: page --- @@ -19,7 +19,6 @@ to version conflicts. ![Diagram of LTE architecture including 4 main sections: User equipment (UE), eNodeB base station, Evolved Packet Core (EPC), Upstream IP networks/Internet](https://i.imgur.com/dMZQVDl.png) - CoLTE simplifies implementation and configuration of the Evolved Packet Core (EPC) elements of an LTE network using the Open5GS package. @@ -29,6 +28,7 @@ internet. It connects to the radio base station, known as the eNodeB, which in turn will associate with User Equipment (UE). ## II. CoLTE Installation + Ensure all Ubuntu packages are up-to-date: ```bash @@ -51,6 +51,7 @@ sudo apt install colte ## III. Network Interface Configuration ### A. Recommendation Configuration + This requires an EPC machine with 2 or more ethernet ports (here named enp1s0 and enp4s0). The upstream interface receives an IP address via DHCP as usual from the upstream router, which passes its traffic to and from the Internet. The @@ -97,6 +98,7 @@ the first configuration is recommended for deployments for security reasons. **The alternative should be used for testing only**. ### B. NOT Recommended for deployment + If you don’t yet have a machine with 2 ethernet ports or a USB to ethernet adapter dongle, you can temporarily use a machine with a single ethernet port along with a simple switch or router. If using a simple switch, you can follow @@ -127,6 +129,7 @@ network daemon to apply the configuration changes: $ sudo netplan try $ sudo netplan apply ``` + If the eNB will be plugged into its own dedicated EPC ethernet port, as in the recommended configuration above, you may need to connect that EPC ethernet port to something (e.g. the eNB, a switch, another machine) via an ethernet cable to @@ -138,6 +141,7 @@ the MME will continually throw errors if you try to run it. ## IV. CoLTE Configuration ### A. Using `colteconf` + CoLTE simplifies LTE network configuration by consolidating relevant configuration files into the directory `/etc/colte`. The primary configuration file is `/etc/colte/config.yml`. Update this file as below: @@ -178,6 +182,7 @@ $ sudo colteconf update This will update the configuration and reload services. ### B. Monitoring CoLTE + Ubuntu’s built-in logging and monitoring services can be used to monitor the core network services: @@ -191,7 +196,7 @@ OR $ sudo systemctl status open5gs-mmed.service ``` -*Tab complete may be able to fill in the service name for systemctl at least* +_Tab complete may be able to fill in the service name for systemctl at least_ ## V. 'Persist' CoLTE Configuration @@ -219,6 +224,7 @@ $ sudo iptables-restore < /etc/iptables/rules.v4 ## VI. User Administration and Management ### A. Command line using `coltedb` + CoLTE comes with the command `coltedb` which can be used to modify the user database via the command line. Run `coltedb` without any arguments to see a summary of the available commands. @@ -229,27 +235,27 @@ spreadsheet or text file by the SIM card manufacturer when you buy them. **PLEASE KEEP THIS INFO SECRET!!!** This is essential for the privacy and security of your network. -* IMSI - * unique identifier for SIM card - * manufacturer provides -* MSISDN - * an arbitrary number representing the user’s “phone number” - * could be the last 5 or more digits of the IMSI- make this up if not - provided to you -* IP Address - * this value sets a private static IP for each SIM card - * you’re also free to set this -* Key - * user’s private key used in LTE encryption - * manufacturer provides -* OPC - * “carrier” private key used in LTE encryption - * manufacturer provides -* APN (*optional)* - * access point name - * for some CBRS LTE phone models such as the LG G8 ThinQ, the APN sent by - the phone is hard-coded to be the string “ims”, so the only solution we’ve - found is to set the APN on the EPC to match. +- IMSI + - unique identifier for SIM card + - manufacturer provides +- MSISDN + - an arbitrary number representing the user’s “phone number” + - could be the last 5 or more digits of the IMSI- make this up if not + provided to you +- IP Address + - this value sets a private static IP for each SIM card + - you’re also free to set this +- Key + - user’s private key used in LTE encryption + - manufacturer provides +- OPC + - “carrier” private key used in LTE encryption + - manufacturer provides +- APN (_optional)_ + - access point name + - for some CBRS LTE phone models such as the LG G8 ThinQ, the APN sent by + the phone is hard-coded to be the string “ims”, so the only solution we’ve + found is to set the APN on the EPC to match. To add a single new user in the command line, use the following command format: @@ -264,6 +270,7 @@ $ sudo coltedb add 460660003400030 30 192.168.151.30 0x00112233445566778899AABBC ``` ### B. Bulk add using a script + The shell script “bulk_add.sh” is provided for your convenience in the [conf/](https://github.com/uw-ictd/colte/tree/main/conf) folder of the github repo. It takes a single argument, the filename (full path if not in the same @@ -278,6 +285,7 @@ info, and the APN set for each user): 460660003400033 33 192.168.151.33 0x00112233445566778899AABBCCDDEEFF 0x000102030405060708090A0B0C0D0E0F ims 460660003400034 34 192.168.151.34 0x00112233445566778899AABBCCDDEEFF 0x000102030405060708090A0B0C0D0E0F ims ``` + Then, to add them all at once to the database, you would run: ```bash diff --git a/emergency/README.md b/emergency/README.md index 672e73e31..44117f611 100644 --- a/emergency/README.md +++ b/emergency/README.md @@ -1,13 +1,16 @@ # emergency + Our Mozilla Challenge proposal was to create a standalone LTE Network-in-a-Box to be deployed in the wake of a natural disaster. This box will provide all sorts of emergency services to their users. Compared to normal LTE networks, there are two main features we need to investigate/create/connect: emergency mode connections and emergency webservices. ## Main Architecture: + In the interests of simplicity and ease of development/deployment, we're using a microservice architecture wherein every independent webapp is provisioned in its own independent Docker container. Each container is forwarded a separate host port (e.g. chat runs on localhost:8081->container:80, wiki runs on localhost:8082->container:80). The main landing page (TODO.local) is static HTML consisting of simple button-links to independent subdomains (e.g. chat.TODO.local) and then each site creates/installs its own apache .conf file in /etc/apache2/sites-available that uses an Apache VirtualHost entry to redirect/port-forward the correct subdomain (e.g. chat.TODO.local -> localhost:8081). ## Creating A Webapp: -Creating/installing a new webapp are pretty straightforward under this system. First, write your webapp in whatever framework/system you choose, and once complete, package it up as a Docker image. Once created, all that's left to do is (1) write a corresponding Apache .conf file for the site, (2) write the correct Ansible .yml file to fetch/install/start the service, and (3) add a pointer in /system_setup/$OS/ansible/emergency.yml. For great and simple examples of how to do this, check out the rocketchat.* files. \ No newline at end of file + +Creating/installing a new webapp are pretty straightforward under this system. First, write your webapp in whatever framework/system you choose, and once complete, package it up as a Docker image. Once created, all that's left to do is (1) write a corresponding Apache .conf file for the site, (2) write the correct Ansible .yml file to fetch/install/start the service, and (3) add a pointer in /system_setup/$OS/ansible/emergency.yml. For great and simple examples of how to do this, check out the rocketchat.\* files. diff --git a/emergency/homepage.yml b/emergency/homepage.yml index 6c8750885..9cf6abd8b 100644 --- a/emergency/homepage.yml +++ b/emergency/homepage.yml @@ -4,26 +4,26 @@ # NOTE: you need to already have docker installed somehow (varies per system, goddamn it) - - name: create emergencell directory - file: - src: "{{ emergency_dir }}/homepage/" - dest: "/var/www/emergencell" - state: link - become: yes +- name: create emergencell directory + file: + src: "{{ emergency_dir }}/homepage/" + dest: "/var/www/emergencell" + state: link + become: yes - - name: copy website configuration file - copy: - src: "{{ emergency_dir }}/homepage.conf" - dest: "/etc/nginx/sites-available/emergencell_homepage" - become: yes +- name: copy website configuration file + copy: + src: "{{ emergency_dir }}/homepage.conf" + dest: "/etc/nginx/sites-available/emergencell_homepage" + become: yes - - name: symlink website into sites-enabled - file: - src: "/etc/nginx/sites-available/emergencell_homepage" - dest: "/etc/nginx/sites-enabled/emergencell_homepage" - state: link - become: yes +- name: symlink website into sites-enabled + file: + src: "/etc/nginx/sites-available/emergencell_homepage" + dest: "/etc/nginx/sites-enabled/emergencell_homepage" + state: link + become: yes - - name: restart nginx - service: name=nginx state=reloaded - become: yes +- name: restart nginx + service: name=nginx state=reloaded + become: yes diff --git a/emergency/homepage/bootstrap.js b/emergency/homepage/bootstrap.js index 534d53343..00a0e9a24 100644 --- a/emergency/homepage/bootstrap.js +++ b/emergency/homepage/bootstrap.js @@ -1,7 +1,2335 @@ /*! - * Bootstrap v4.0.0 (https://getbootstrap.com) - * Copyright 2011-2018 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors) - * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) - */ -!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports,require("jquery"),require("popper.js")):"function"==typeof define&&define.amd?define(["exports","jquery","popper.js"],e):e(t.bootstrap={},t.jQuery,t.Popper)}(this,function(t,e,n){"use strict";function i(t,e){for(var n=0;n{{ person.name_text }} | -{{ person.phone_number }} | -||||||||||||||||
-
+ {% for person in persons %}
+ {{ person.name_text }} |
+ {{ person.phone_number }} |
+
+ |
-
|