diff --git a/README.md b/README.md index 4338839..91199b5 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ This repo contains Ansible playbooks and configuration used to manage a group of Multi-Tech Conduits as [Things Network gateways](http://www.thethingsnetwork.org) in an a Things Network -organization. + The [MultiConnect® Conduit™](http://www.multitech.com/brands/multiconnect-conduit) is one of the more popular [LoRa®](http://lora.multitech.com/) Gateways is use. @@ -35,7 +35,7 @@ private keys on cloud hosts. This configuration relies on a *jump host* or ssh tunnel host. For various reasons, including security and the complexity of traversing firewalls, each conduit will set up a reverse SSH tunnel to a jump -host. +host. It is recommended that these ports only be accessible from that jump host. That will mean you need to be logged into the jump host to run @@ -52,7 +52,12 @@ If you do not want to use a jump host, comment out *ssh_tunnel_remote_port* or set it to *0* in your conduit's config file in *host_vars*. -## Branches +The ssh_host playbook will add the SSH RSA public keys of each device to `ssh_tunnel_gateway_user_on_jumphost` authorized_keys, in order to do this it relays on ansible fact caching, so make sure to run the conduits playbook first or increase your `fact_caching_timeout` in ansible.cfg + +*NOTICE* that the jumphost playbook works only on a Ubuntu host. + + +## Branches This repo has a few main branches: @@ -99,7 +104,7 @@ Instructions for installing Ansible ## Fetch the upstream files There is *Makefile* in the root of this repo that can be used to fetch -files from upstream. +files from upstream. ### make all This command will fetch files that are required to run ansible on the @@ -139,7 +144,7 @@ forward keys from your laptop or desktop. 1. Edit *hosts* and change *jumphost.example.com* to the FQDN of your ssh tunnel server, aka jumphost. 2. Copy *group_vars/jumphost.example.com* to -*group_vars/FQDN_OF_YOUR_JUMPHOST.yam* and edit it as necessary. +*group_vars/FQDN_OF_YOUR_JUMPHOST.yml* and edit it as necessary. ## Add each of your gateways to *hosts* Normally you would put them in the *production* group. There is also @@ -186,7 +191,7 @@ local network. DHCP should also supply one or more nameservers. You can override this in *host_vars/**HOST**.yml* by uncommenting and setting the appropriate variable definitions. See the examples in -*host_vars/ttn-org-example.yml*. +*host_vars/ttn-org-example.yml*. Note that if you make a mistake you may render your Conduit unreachable except via the USB serial console. So double check the @@ -216,13 +221,13 @@ configuration, or turning your Conduit into a BotNet node. On the Conduit: ``` mtctd login: root -passwd: +passwd: root@mtcdt:~# passwd Enter new UNIX password: Retype new UNIX password: root@mtcdt:~# ``` -Remember the password you supplied above. +Remember the password you supplied above. ## Provide initial authorizied keys in .root/.ssh/authorized_keys The easy way to do this is to open *authorized_keys* with `gedit` on your host, then copy/paste @@ -256,7 +261,7 @@ $ make apply TAGS=loraconfig TARGET=*HOSTNAME* ``` Specify the name of your Conduit with *HOSTNAME*. If you leave that off, all Conduit's will be registered, or their registration will be -updated. +updated. # Upgrading mLinux It is possible to remotely upgrade to a specific version of mLinux @@ -333,7 +338,7 @@ The available variables are defined in the [conduit role README](roles/conduit/R --- -# Development +# Development This is a temporary section to track development on this repo. @@ -430,4 +435,3 @@ restricting root access. ### Bugs + [ ] Not owner of gateway - diff --git a/bin/catalog b/bin/catalog deleted file mode 100755 index e38eed4..0000000 --- a/bin/catalog +++ /dev/null @@ -1,177 +0,0 @@ -#!/usr/bin/python - -from __future__ import print_function -import argparse -import json -import os -import sys - -""" -MIT License - -Copyright (c) 2017 Jeffrey C Honig - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -""" - -def print_vars(options, parser, nodes): - """Print the specified vars for each node""" - - for host in sorted(options.host if options.host else nodes.keys()): - node = nodes[host] - - for var in options.vars: - parts = var.split(".") - dp = node - for part in parts: - try: - dp = dp[part] - except KeyError as err: - parser.error("%s %s: Can not find %s" % (host, var, part)) - - print("%s.%s: %s" % (host, part, dp)) - -def print_all_vars_sub(options, parser, nodes, names=[]): - """Print all vars for this node""" - - for k, v in sorted(nodes.items()): - if type(v) == type({}): - print_all_vars_sub(options, parser, v, names + [ k ]) - continue - - if type(v) == type([]): - parts = [] - for v1 in v: - if type(v1) == type({}): - print_all_vars_sub(options, parser, v1, names + [ k ]) - continue - - if type(v1) == type(u''): - v1 = v1.decode() - - parts.append(str(v1)) - - if len(parts): - v = ", ".join(parts) - - if type(v) == type(u''): - v = v.decode() - - print("%s: %s" % (".".join(names + [ k ]), v)) - - -def print_all_vars(options, parser, nodes, names=[]): - """Print all the vars for each node""" - - for host in sorted(options.host if options.host else nodes.keys()): - node = nodes[host] - print_all_vars_sub(options, parser, node, [ host ] + names) - -def read_catalogs(options, parser): - """Read in all the catalog files (*.json in options.catalog)""" - - nodes = dict() - - catalog_dir = os.path.expanduser(options.catalog) - if not os.path.exists(catalog_dir): - parser.error("%s does not exist" % options.catalog) - if not os.path.exists(catalog_dir): - parser.error("%s is not a directory" % options.catalog) - for root, dir, files in os.walk(catalog_dir, topdown=False): - for name in files: - path = os.path.join(root, name) - if not path.endswith(".json"): - if options.debug: - print("DBG: Ignoring %s" % path) - continue - if options.debug: - print("DBG: Opending %s" % path) - try: - fp = open(path) - except OSError as err: - parser.error("Opening %s: %s" % (path, err)) - continue - - try: - node = json.load(fp) - except ValueError as err: - parser.error("Reading %s: %s" % (path, err)) - continue - - try: - ansible_facts = node['ansible_facts'] - host = ansible_facts['ansible_nodename'] - except NameError as err: - parser.error("Parsing %s: %s" % (path, err)) - continue - - nodes[host] = ansible_facts - - return nodes - -def main(): - """ Figure out what we should do """ - - parser = argparse.ArgumentParser(description="Register or re-regiter a Conduit with TTN") - - # Debugging - group = parser.add_argument_group("Debugging options") - group.add_argument("-d", "--debug", - dest="debug", default=False, - action='store_true', - help="print debugging messages") - group.add_argument("--nodebug", - dest="debug", - action='store_false', - help="print debugging messages") - group.add_argument("-v", "--verbose", - dest="verbose", default=False, - action='store_true', - help="print verbose messages") - - # Options - group = parser.add_argument_group("Configuration options") - group.add_argument("-a", - dest="all", default=False, - action='store_true', - help="Print all values") - group.add_argument("--catalog", "-C", - dest="catalog", required=True, - help="Catalog directory of json files from Conduits") - group.add_argument("--host", "-H", - dest="host", nargs="+", - help="Limit to a specific host") - group.add_argument(dest="vars", nargs="*", - help="Variables to print") - - options = parser.parse_args() - - nodes = read_catalogs(options, parser) - if options.all: - print_all_vars(options, parser, nodes) - elif options.vars: - print_vars(options, parser, nodes) - -if __name__ == "__main__": - try: - main() - sys.exit(0) - except KeyboardInterrupt: - print() - sys.exit(1) diff --git a/roles/conduit/README.md b/roles/conduit/README.md index 439269b..5d10686 100644 --- a/roles/conduit/README.md +++ b/roles/conduit/README.md @@ -15,7 +15,7 @@ The following setup must be set up performed on the Contduit: + python-distutils + Install authorized keys + This is required to allow secure login to the gateway - + These are maintained in `authorized_keys` or `authorized_keys_GROUP` + + These are maintained in `authorized_keys` + Configure ssh tunnel + If accessing the Conduit remotely and it is not availble on the public Internet (and it should not be), an ssh tunnel needs to @@ -104,7 +104,7 @@ The following tags can be used to run a subset of the playbook.