mirror of
				https://github.com/laurivosandi/certidude
				synced 2025-10-31 09:29:13 +00:00 
			
		
		
		
	Integrate LEDE image builder
This commit is contained in:
		| @@ -82,7 +82,7 @@ class SessionResource(object): | |||||||
|                 attributes = {} |                 attributes = {} | ||||||
|                 for key in listxattr(path): |                 for key in listxattr(path): | ||||||
|                     if key.startswith(b"user.machine."): |                     if key.startswith(b"user.machine."): | ||||||
|                         attributes[key[13:]] = getxattr(path, key).decode("ascii") |                         attributes[key[13:].decode("ascii")] = getxattr(path, key).decode("ascii") | ||||||
|  |  | ||||||
|                 # Extract lease information from filesystem |                 # Extract lease information from filesystem | ||||||
|                 try: |                 try: | ||||||
| @@ -131,6 +131,9 @@ class SessionResource(object): | |||||||
|             ), |             ), | ||||||
|             request_submission_allowed = config.REQUEST_SUBMISSION_ALLOWED, |             request_submission_allowed = config.REQUEST_SUBMISSION_ALLOWED, | ||||||
|             authority = dict( |             authority = dict( | ||||||
|  |                 builder = dict( | ||||||
|  |                     profiles = config.IMAGE_BUILDER_PROFILES | ||||||
|  |                 ), | ||||||
|                 tagging = [dict(name=t[0], type=t[1], title=t[2]) for t in config.TAG_TYPES], |                 tagging = [dict(name=t[0], type=t[1], title=t[2]) for t in config.TAG_TYPES], | ||||||
|                 lease = dict( |                 lease = dict( | ||||||
|                     offline = 600, # Seconds from last seen activity to consider lease offline, OpenVPN reneg-sec option |                     offline = 600, # Seconds from last seen activity to consider lease offline, OpenVPN reneg-sec option | ||||||
| @@ -208,6 +211,7 @@ def certidude_app(log_handlers=[]): | |||||||
|     from .attrib import AttributeResource |     from .attrib import AttributeResource | ||||||
|     from .bootstrap import BootstrapResource |     from .bootstrap import BootstrapResource | ||||||
|     from .token import TokenResource |     from .token import TokenResource | ||||||
|  |     from .builder import ImageBuilderResource | ||||||
|  |  | ||||||
|     app = falcon.API(middleware=NormalizeMiddleware()) |     app = falcon.API(middleware=NormalizeMiddleware()) | ||||||
|     app.req_options.auto_parse_form_urlencoded = True |     app.req_options.auto_parse_form_urlencoded = True | ||||||
| @@ -240,6 +244,9 @@ def certidude_app(log_handlers=[]): | |||||||
|     # Bootstrap resource |     # Bootstrap resource | ||||||
|     app.add_route("/api/bootstrap/", BootstrapResource()) |     app.add_route("/api/bootstrap/", BootstrapResource()) | ||||||
|  |  | ||||||
|  |     # LEDE image builder resource | ||||||
|  |     app.add_route("/api/build/{profile}/{suggested_filename}", ImageBuilderResource()) | ||||||
|  |  | ||||||
|     # Add CRL handler if we have any whitelisted subnets |     # Add CRL handler if we have any whitelisted subnets | ||||||
|     if config.CRL_SUBNETS: |     if config.CRL_SUBNETS: | ||||||
|         from .revoked import RevocationListResource |         from .revoked import RevocationListResource | ||||||
|   | |||||||
							
								
								
									
										52
									
								
								certidude/api/builder.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										52
									
								
								certidude/api/builder.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,52 @@ | |||||||
|  |  | ||||||
|  | import click | ||||||
|  | import falcon | ||||||
|  | import logging | ||||||
|  | import os | ||||||
|  | import subprocess | ||||||
|  | from certidude import config, const | ||||||
|  | from certidude.auth import login_required, authorize_admin | ||||||
|  | from jinja2 import Template | ||||||
|  |  | ||||||
|  | logger = logging.getLogger(__name__) | ||||||
|  |  | ||||||
|  | class ImageBuilderResource(object): | ||||||
|  |     @login_required | ||||||
|  |     @authorize_admin | ||||||
|  |     def on_get(self, req, resp, profile, suggested_filename): | ||||||
|  |         model = config.cp2.get(profile, "model") | ||||||
|  |         build_script_path = config.cp2.get(profile, "command") | ||||||
|  |         overlay_path = config.cp2.get(profile, "overlay") | ||||||
|  |         site_script_path = config.cp2.get(profile, "script") | ||||||
|  |         suffix = config.cp2.get(profile, "filename") | ||||||
|  |  | ||||||
|  |         build = "/var/lib/certidude/builder/" + profile | ||||||
|  |         if not os.path.exists(build + "/overlay/etc/uci-defaults"): | ||||||
|  |             os.makedirs(build + "/overlay/etc/uci-defaults") | ||||||
|  |         os.system("rsync -av " + overlay_path + "/ " + build + "/overlay/") | ||||||
|  |  | ||||||
|  |         if site_script_path: | ||||||
|  |             template = Template(open(site_script_path).read()) | ||||||
|  |             with open(build + "/overlay/etc/uci-defaults/99-site-config", "w") as fh: | ||||||
|  |                 fh.write(template.render(authority_name=const.FQDN)) | ||||||
|  |  | ||||||
|  |         proc = subprocess.Popen(("/bin/bash", build_script_path), | ||||||
|  |             stdout=open(build + "/build.log", "w"), stderr=subprocess.STDOUT, | ||||||
|  |             close_fds=True, shell=False, | ||||||
|  |             cwd=build, | ||||||
|  |             env={"PROFILE":model, "PATH":"/usr/sbin:/usr/bin:/sbin:/bin"}, | ||||||
|  |             startupinfo=None, creationflags=0) | ||||||
|  |         proc.communicate() | ||||||
|  |  | ||||||
|  |         for dname in os.listdir(build): | ||||||
|  |             if dname.startswith("lede-imagebuilder-"): | ||||||
|  |                 for root, dirs, files in os.walk(os.path.join(build, dname, "bin", "targets")): | ||||||
|  |                     for filename in files: | ||||||
|  |                         if filename.endswith(suffix): | ||||||
|  |                             path = os.path.join(root, filename) | ||||||
|  |                             click.echo("Serving: %s" % path) | ||||||
|  |                             resp.body = open(path, "rb").read() | ||||||
|  |                             resp.set_header("Content-Disposition", ("attachment; filename=%s" % suggested_filename)) | ||||||
|  |                             return | ||||||
|  |         raise falcon.HTTPNotFound() | ||||||
|  |  | ||||||
| @@ -1,5 +1,6 @@ | |||||||
| import falcon | import falcon | ||||||
| import logging | import logging | ||||||
|  | import os | ||||||
| from certidude import const, config, authority | from certidude import const, config, authority | ||||||
| from certidude.decorators import serialize | from certidude.decorators import serialize | ||||||
| from jinja2 import Environment, FileSystemLoader | from jinja2 import Environment, FileSystemLoader | ||||||
| @@ -26,9 +27,10 @@ class ScriptResource(): | |||||||
|         except AttributeError: # No tags |         except AttributeError: # No tags | ||||||
|             pass |             pass | ||||||
|  |  | ||||||
|         script = named_tags.get("script", config.SCRIPT_DEFAULT) |         script = named_tags.get("script", "default.sh") | ||||||
|  |         assert script in os.listdir(config.SCRIPT_DIR) | ||||||
|         resp.set_header("Content-Type", "text/x-shellscript") |         resp.set_header("Content-Type", "text/x-shellscript") | ||||||
|         resp.body = env.get_template(script).render( |         resp.body = env.get_template(os.path.join(script)).render( | ||||||
|             authority_name=const.FQDN, |             authority_name=const.FQDN, | ||||||
|             common_name=cn, |             common_name=cn, | ||||||
|             other_tags=other_tags, |             other_tags=other_tags, | ||||||
|   | |||||||
| @@ -96,7 +96,7 @@ def setup_client(prefix="client_", dh=False): | |||||||
| @click.option("-s", "--skip-self", default=False, is_flag=True, help="Skip self enroll") | @click.option("-s", "--skip-self", default=False, is_flag=True, help="Skip self enroll") | ||||||
| @click.option("-nw", "--no-wait", default=False, is_flag=True, help="Return immideately if server doesn't autosign") | @click.option("-nw", "--no-wait", default=False, is_flag=True, help="Return immideately if server doesn't autosign") | ||||||
| def certidude_enroll(fork, renew, no_wait, kerberos, skip_self): | def certidude_enroll(fork, renew, no_wait, kerberos, skip_self): | ||||||
|     if not skip_self and os.path.exists(const.CONFIG_PATH): |     if not skip_self and os.path.exists(const.SERVER_CONFIG_PATH): | ||||||
|         click.echo("Self-enrolling authority's web interface certificate") |         click.echo("Self-enrolling authority's web interface certificate") | ||||||
|         from certidude import authority |         from certidude import authority | ||||||
|         authority.self_enroll() |         authority.self_enroll() | ||||||
| @@ -944,38 +944,42 @@ def certidude_setup_openvpn_networkmanager(authority, remote, common_name, **pat | |||||||
| @click.option("--directory", help="Directory for authority files") | @click.option("--directory", help="Directory for authority files") | ||||||
| @click.option("--server-flags", is_flag=True, help="Add TLS Server and IKE Intermediate extended key usage flags") | @click.option("--server-flags", is_flag=True, help="Add TLS Server and IKE Intermediate extended key usage flags") | ||||||
| @click.option("--outbox", default="smtp://smtp.%s" % const.DOMAIN, help="SMTP server, smtp://smtp.%s by default" % const.DOMAIN) | @click.option("--outbox", default="smtp://smtp.%s" % const.DOMAIN, help="SMTP server, smtp://smtp.%s by default" % const.DOMAIN) | ||||||
|  | @click.option("--skip-packages", is_flag=True, help="Don't attempt to install apt/pip/npm packages") | ||||||
| @fqdn_required | @fqdn_required | ||||||
| def certidude_setup_authority(username, kerberos_keytab, nginx_config, country, state, locality, organization, organizational_unit, common_name, directory, authority_lifetime, push_server, outbox, server_flags, title): | def certidude_setup_authority(username, kerberos_keytab, nginx_config, country, state, locality, organization, organizational_unit, common_name, directory, authority_lifetime, push_server, outbox, server_flags, title, skip_packages): | ||||||
|     assert os.getuid() == 0 and os.getgid() == 0, "Authority can be set up only by root" |     assert os.getuid() == 0 and os.getgid() == 0, "Authority can be set up only by root" | ||||||
|  |  | ||||||
|     import pwd |     import pwd | ||||||
|     from jinja2 import Environment, PackageLoader |     from jinja2 import Environment, PackageLoader | ||||||
|     env = Environment(loader=PackageLoader("certidude", "templates"), trim_blocks=True) |     env = Environment(loader=PackageLoader("certidude", "templates"), trim_blocks=True) | ||||||
|  |  | ||||||
|     click.echo("Installing packages...") |     if skip_packages: | ||||||
|     os.system("apt-get install -qq -y cython3 python3-dev python3-mimeparse \ |         click.echo("Not attempting to install packages from APT as requested...") | ||||||
|         python3-markdown python3-pyxattr python3-jinja2 python3-cffi \ |  | ||||||
|         software-properties-common libsasl2-modules-gssapi-mit npm nodejs \ |  | ||||||
|         libkrb5-dev libldap2-dev libsasl2-dev") |  | ||||||
|     os.system("pip3 install -q --upgrade gssapi falcon humanize ipaddress simplepam") |  | ||||||
|     os.system("pip3 install -q --pre --upgrade python-ldap") |  | ||||||
|  |  | ||||||
|     if not os.path.exists("/usr/lib/nginx/modules/ngx_nchan_module.so"): |  | ||||||
|         click.echo("Enabling nginx PPA") |  | ||||||
|         os.system("add-apt-repository -y ppa:nginx/stable") |  | ||||||
|         os.system("apt-get update -q") |  | ||||||
|         os.system("apt-get install -y -q libnginx-mod-nchan") |  | ||||||
|     else: |     else: | ||||||
|         click.echo("PPA for nginx already enabled") |         click.echo("Installing packages...") | ||||||
|  |         os.system("apt-get install -qq -y cython3 python3-dev python3-mimeparse \ | ||||||
|  |             python3-markdown python3-pyxattr python3-jinja2 python3-cffi \ | ||||||
|  |             software-properties-common libsasl2-modules-gssapi-mit npm nodejs \ | ||||||
|  |             libkrb5-dev libldap2-dev libsasl2-dev gawk libncurses5-dev") | ||||||
|  |         os.system("pip3 install -q --upgrade gssapi falcon humanize ipaddress simplepam") | ||||||
|  |         os.system("pip3 install -q --pre --upgrade python-ldap") | ||||||
|  |  | ||||||
|     if not os.path.exists("/usr/sbin/nginx"): |         if not os.path.exists("/usr/lib/nginx/modules/ngx_nchan_module.so"): | ||||||
|         click.echo("Installing nginx from PPA") |             click.echo("Enabling nginx PPA") | ||||||
|         os.system("apt-get install -y -q nginx") |             os.system("add-apt-repository -y ppa:nginx/stable") | ||||||
|     else: |             os.system("apt-get update -q") | ||||||
|         click.echo("Web server nginx already installed") |             os.system("apt-get install -y -q libnginx-mod-nchan") | ||||||
|  |         else: | ||||||
|  |             click.echo("PPA for nginx already enabled") | ||||||
|  |  | ||||||
|     if not os.path.exists("/usr/bin/node"): |         if not os.path.exists("/usr/sbin/nginx"): | ||||||
|         os.symlink("/usr/bin/nodejs", "/usr/bin/node") |             click.echo("Installing nginx from PPA") | ||||||
|  |             os.system("apt-get install -y -q nginx") | ||||||
|  |         else: | ||||||
|  |             click.echo("Web server nginx already installed") | ||||||
|  |  | ||||||
|  |         if not os.path.exists("/usr/bin/node"): | ||||||
|  |             os.symlink("/usr/bin/nodejs", "/usr/bin/node") | ||||||
|  |  | ||||||
|     # Generate secret for tokens |     # Generate secret for tokens | ||||||
|     token_secret = ''.join(random.choice(string.ascii_letters + string.digits + '!@#$%^&*()') for i in range(50)) |     token_secret = ''.join(random.choice(string.ascii_letters + string.digits + '!@#$%^&*()') for i in range(50)) | ||||||
| @@ -1036,6 +1040,9 @@ def certidude_setup_authority(username, kerberos_keytab, nginx_config, country, | |||||||
|     else: |     else: | ||||||
|         click.echo("Warning: /etc/krb5.keytab or /etc/samba/smb.conf not found, Kerberos unconfigured") |         click.echo("Warning: /etc/krb5.keytab or /etc/samba/smb.conf not found, Kerberos unconfigured") | ||||||
|  |  | ||||||
|  |     doc_path = os.path.join(os.path.realpath(os.path.dirname(os.path.dirname(__file__))), "doc") | ||||||
|  |     script_dir = os.path.join(os.path.realpath(os.path.dirname(__file__)), "templates", "script") | ||||||
|  |  | ||||||
|     static_path = os.path.join(os.path.realpath(os.path.dirname(__file__)), "static") |     static_path = os.path.join(os.path.realpath(os.path.dirname(__file__)), "static") | ||||||
|     certidude_path = sys.argv[0] |     certidude_path = sys.argv[0] | ||||||
|  |  | ||||||
| @@ -1057,6 +1064,13 @@ def certidude_setup_authority(username, kerberos_keytab, nginx_config, country, | |||||||
|     else: |     else: | ||||||
|         click.echo("Not systemd based OS, don't know how to set up initscripts") |         click.echo("Not systemd based OS, don't know how to set up initscripts") | ||||||
|  |  | ||||||
|  |     if os.path.exists("/etc/certidude/builder.conf"): | ||||||
|  |         click.echo("Image builder config /etc/certidude/builder.conf already exists, remove to regenerate") | ||||||
|  |     else: | ||||||
|  |         with open("/etc/certidude/builder.conf", "w") as fh: | ||||||
|  |             fh.write(env.get_template("server/builder.conf").render(vars())) | ||||||
|  |         click.echo("File /etc/certidude/builder.conf created") | ||||||
|  |  | ||||||
|     assert os.getuid() == 0 and os.getgid() == 0 |     assert os.getuid() == 0 and os.getgid() == 0 | ||||||
|     bootstrap_pid = os.fork() |     bootstrap_pid = os.fork() | ||||||
|     if not bootstrap_pid: |     if not bootstrap_pid: | ||||||
| @@ -1069,7 +1083,10 @@ def certidude_setup_authority(username, kerberos_keytab, nginx_config, country, | |||||||
|                 os.makedirs(subdir) |                 os.makedirs(subdir) | ||||||
|  |  | ||||||
|         # Install JavaScript pacakges |         # Install JavaScript pacakges | ||||||
|         os.system("npm install --silent -g nunjucks@2.5.2 nunjucks-date@1.2.0 node-forge bootstrap@4.0.0-alpha.6 jquery timeago tether font-awesome qrcode-svg") |         if skip_packages: | ||||||
|  |             click.echo("Not attempting to install packages from NPM as requested...") | ||||||
|  |         else: | ||||||
|  |             os.system("npm install --silent -g nunjucks@2.5.2 nunjucks-date@1.2.0 node-forge bootstrap@4.0.0-alpha.6 jquery timeago tether font-awesome qrcode-svg") | ||||||
|  |  | ||||||
|         # Compile nunjucks templates |         # Compile nunjucks templates | ||||||
|         cmd = 'nunjucks-precompile --include ".html$" --include ".svg" %s > %s.part' % (static_path, bundle_js) |         cmd = 'nunjucks-precompile --include ".html$" --include ".svg" %s > %s.part' % (static_path, bundle_js) | ||||||
| @@ -1109,14 +1126,14 @@ def certidude_setup_authority(username, kerberos_keytab, nginx_config, country, | |||||||
|         if not os.path.exists(const.CONFIG_DIR): |         if not os.path.exists(const.CONFIG_DIR): | ||||||
|             click.echo("Creating %s" % const.CONFIG_DIR) |             click.echo("Creating %s" % const.CONFIG_DIR) | ||||||
|             os.makedirs(const.CONFIG_DIR) |             os.makedirs(const.CONFIG_DIR) | ||||||
|         if os.path.exists(const.CONFIG_PATH): |         if os.path.exists(const.SERVER_CONFIG_PATH): | ||||||
|             click.echo("Configuration file %s already exists, remove to regenerate" % const.CONFIG_PATH) |             click.echo("Configuration file %s already exists, remove to regenerate" % const.SERVER_CONFIG_PATH) | ||||||
|         else: |         else: | ||||||
|             os.umask(0o137) |             os.umask(0o137) | ||||||
|             push_token = "".join([random.choice(string.ascii_letters + string.digits) for j in range(0,32)]) |             push_token = "".join([random.choice(string.ascii_letters + string.digits) for j in range(0,32)]) | ||||||
|             with open(const.CONFIG_PATH, "w") as fh: |             with open(const.SERVER_CONFIG_PATH, "w") as fh: | ||||||
|                 fh.write(env.get_template("server/server.conf").render(vars())) |                 fh.write(env.get_template("server/server.conf").render(vars())) | ||||||
|             click.echo("Generated %s" % const.CONFIG_PATH) |             click.echo("Generated %s" % const.SERVER_CONFIG_PATH) | ||||||
|  |  | ||||||
|         # Create directory with 755 permissions |         # Create directory with 755 permissions | ||||||
|         os.umask(0o022) |         os.umask(0o022) | ||||||
| @@ -1183,7 +1200,7 @@ def certidude_setup_authority(username, kerberos_keytab, nginx_config, country, | |||||||
|         from certidude import authority |         from certidude import authority | ||||||
|         authority.self_enroll() |         authority.self_enroll() | ||||||
|         assert os.getuid() == 0 and os.getgid() == 0, "Enroll contaminated environment" |         assert os.getuid() == 0 and os.getgid() == 0, "Enroll contaminated environment" | ||||||
|         click.echo("To enable e-mail notifications install Postfix as sattelite system and set mailer address in %s" % const.CONFIG_PATH) |         click.echo("To enable e-mail notifications install Postfix as sattelite system and set mailer address in %s" % const.SERVER_CONFIG_PATH) | ||||||
|         click.echo() |         click.echo() | ||||||
|         click.echo("Use following commands to inspect the newly created files:") |         click.echo("Use following commands to inspect the newly created files:") | ||||||
|         click.echo() |         click.echo() | ||||||
| @@ -1337,7 +1354,7 @@ def certidude_serve(port, listen, fork): | |||||||
|     if port == 80: |     if port == 80: | ||||||
|         click.echo("WARNING: Please run Certidude behind nginx, remote address is assumed to be forwarded by nginx!") |         click.echo("WARNING: Please run Certidude behind nginx, remote address is assumed to be forwarded by nginx!") | ||||||
|  |  | ||||||
|     click.echo("Using configuration from: %s" % const.CONFIG_PATH) |     click.echo("Using configuration from: %s" % const.SERVER_CONFIG_PATH) | ||||||
|  |  | ||||||
|     log_handlers = [] |     log_handlers = [] | ||||||
|  |  | ||||||
|   | |||||||
| @@ -12,7 +12,7 @@ from random import choice | |||||||
| # Options that are parsed from config file are fetched here | # Options that are parsed from config file are fetched here | ||||||
|  |  | ||||||
| cp = configparser.RawConfigParser() | cp = configparser.RawConfigParser() | ||||||
| cp.readfp(codecs.open(const.CONFIG_PATH, "r", "utf8")) | cp.readfp(open(const.SERVER_CONFIG_PATH, "r")) | ||||||
|  |  | ||||||
| AUTHENTICATION_BACKENDS = set([j for j in | AUTHENTICATION_BACKENDS = set([j for j in | ||||||
|     cp.get("authentication", "backends").split(" ") if j])   # kerberos, pam, ldap |     cp.get("authentication", "backends").split(" ") if j])   # kerberos, pam, ldap | ||||||
| @@ -99,7 +99,10 @@ TOKEN_SECRET = cp.get("token", "secret").encode("ascii") | |||||||
| # TODO: Check if we don't have base or servers | # TODO: Check if we don't have base or servers | ||||||
|  |  | ||||||
| # The API call for looking up scripts uses following directory as root | # The API call for looking up scripts uses following directory as root | ||||||
| SCRIPT_DIR = os.path.join(os.path.dirname(__file__), "templates", "script") | SCRIPT_DIR = cp.get("script", "path") | ||||||
| SCRIPT_DEFAULT = "default.sh" |  | ||||||
|  |  | ||||||
| PROFILES = OrderedDict([[i, [j.strip() for j in cp.get("profile", i).split(",")]] for i in cp.options("profile")]) | PROFILES = OrderedDict([[i, [j.strip() for j in cp.get("profile", i).split(",")]] for i in cp.options("profile")]) | ||||||
|  |  | ||||||
|  | cp2 = configparser.RawConfigParser() | ||||||
|  | cp2.readfp(open(const.BUILDER_CONFIG_PATH, "r")) | ||||||
|  | IMAGE_BUILDER_PROFILES = [(j, cp2.get(j, "title"), cp2.get(j, "rename")) for j in cp2.sections()] | ||||||
|   | |||||||
| @@ -7,7 +7,8 @@ import sys | |||||||
| KEY_SIZE = 1024 if os.getenv("TRAVIS") else 4096 | KEY_SIZE = 1024 if os.getenv("TRAVIS") else 4096 | ||||||
| RUN_DIR = "/run/certidude" | RUN_DIR = "/run/certidude" | ||||||
| CONFIG_DIR = "/etc/certidude" | CONFIG_DIR = "/etc/certidude" | ||||||
| CONFIG_PATH = os.path.join(CONFIG_DIR, "server.conf") | SERVER_CONFIG_PATH = os.path.join(CONFIG_DIR, "server.conf") | ||||||
|  | BUILDER_CONFIG_PATH = os.path.join(CONFIG_DIR, "builder.conf") | ||||||
| CLIENT_CONFIG_PATH = os.path.join(CONFIG_DIR, "client.conf") | CLIENT_CONFIG_PATH = os.path.join(CONFIG_DIR, "client.conf") | ||||||
| SERVICES_CONFIG_PATH = os.path.join(CONFIG_DIR, "services.conf") | SERVICES_CONFIG_PATH = os.path.join(CONFIG_DIR, "services.conf") | ||||||
| SERVER_PID_PATH = os.path.join(RUN_DIR, "server.pid") | SERVER_PID_PATH = os.path.join(RUN_DIR, "server.pid") | ||||||
|   | |||||||
| @@ -1,6 +1,8 @@ | |||||||
|  |  | ||||||
| import falcon | import falcon | ||||||
| import logging | import logging | ||||||
|  | import click | ||||||
|  | from asn1crypto import pem, x509 | ||||||
|  |  | ||||||
| logger = logging.getLogger("api") | logger = logging.getLogger("api") | ||||||
|  |  | ||||||
| @@ -50,6 +52,17 @@ def whitelist_subject(func): | |||||||
|         except IOError: |         except IOError: | ||||||
|             raise falcon.HTTPNotFound() |             raise falcon.HTTPNotFound() | ||||||
|         else: |         else: | ||||||
|  |             # First attempt to authenticate client with certificate | ||||||
|  |             buf = req.get_header("X-SSL-CERT") | ||||||
|  |             if buf: | ||||||
|  |                 header, _, der_bytes = pem.unarmor(buf.replace("\t", "").encode("ascii")) | ||||||
|  |                 origin_cert = x509.Certificate.load(der_bytes) | ||||||
|  |                 if origin_cert.native == cert.native: | ||||||
|  |                     click.echo("Subject authenticated using certificates") | ||||||
|  |                     return func(self, req, resp, cn, *args, **kwargs) | ||||||
|  |  | ||||||
|  |             # For backwards compatibility check source IP address | ||||||
|  |             # TODO: make it disableable | ||||||
|             try: |             try: | ||||||
|                 inner_address = getxattr(path, "user.lease.inner_address").decode("ascii") |                 inner_address = getxattr(path, "user.lease.inner_address").decode("ascii") | ||||||
|             except IOError: |             except IOError: | ||||||
| @@ -58,6 +71,6 @@ def whitelist_subject(func): | |||||||
|                 if req.context.get("remote_addr") != ip_address(inner_address): |                 if req.context.get("remote_addr") != ip_address(inner_address): | ||||||
|                     raise falcon.HTTPForbidden("Forbidden", "Remote address %s mismatch" % req.context.get("remote_addr")) |                     raise falcon.HTTPForbidden("Forbidden", "Remote address %s mismatch" % req.context.get("remote_addr")) | ||||||
|                 else: |                 else: | ||||||
|                    return func(self, req, resp, cn, *args, **kwargs) |                     return func(self, req, resp, cn, *args, **kwargs) | ||||||
|     return wrapped |     return wrapped | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,3 +1,3 @@ | |||||||
| {% for key, value in certificate.attributes %} | {% for key, value in certificate.attributes %} | ||||||
| <span class="attribute icon {{ key | replace('.', ' ') }}" title="{{ key }}={{ value }}">{{ value }}</span> | <span class="badge badge-info" title="{{ key }}={{ value }}">{{ value }}</span> | ||||||
| {% endfor %} | {% endfor %} | ||||||
|   | |||||||
| @@ -28,7 +28,7 @@ curl -f -L -H "Content-type: application/pkcs10" --data-binary @client_req.pem \ | |||||||
|   http://{{ window.location.hostname }}/api/request/?wait=yes > client_cert.pem</code></pre> |   http://{{ window.location.hostname }}/api/request/?wait=yes > client_cert.pem</code></pre> | ||||||
|             </div> |             </div> | ||||||
|  |  | ||||||
|             <h5>OpenWrt/LEDE</h5> |             <h5>Vanilla OpenWrt/LEDE</h5> | ||||||
|  |  | ||||||
|             <p>On OpenWrt/LEDE router to convert it into VPN gateway:</p> |             <p>On OpenWrt/LEDE router to convert it into VPN gateway:</p> | ||||||
|             <div class="highlight"> |             <div class="highlight"> | ||||||
| @@ -45,6 +45,16 @@ curl -f -L -H "Content-type: application/pkcs10" \ | |||||||
|   http://{{ window.location.hostname }}/api/request/?wait=yes</code></pre> |   http://{{ window.location.hostname }}/api/request/?wait=yes</code></pre> | ||||||
|             </div> |             </div> | ||||||
|  |  | ||||||
|  |             {% if session.authority.builder %} | ||||||
|  |             <h5>OpenWrt/LEDE image builder</h5> | ||||||
|  |             <p>Hit a link to generate machine specific image. Note that this might take couple minutes to finish.</p> | ||||||
|  |             <ul> | ||||||
|  |             {% for name, title, filename in session.authority.builder.profiles %} | ||||||
|  |                 <li><a href="/api/build/{{ name }}/{{ filename }}">{{ title }}</a></li> | ||||||
|  |             {% endfor %} | ||||||
|  |             </ul> | ||||||
|  |             {% endif %} | ||||||
|  |  | ||||||
|             <h5>SCEP</h5> |             <h5>SCEP</h5> | ||||||
|             <p>Use following as the enrollment URL: http://{{ window.location.hostname }}/cgi-bin/pkiclient.exe</p> |             <p>Use following as the enrollment URL: http://{{ window.location.hostname }}/cgi-bin/pkiclient.exe</p> | ||||||
|  |  | ||||||
|   | |||||||
| @@ -24,11 +24,17 @@ | |||||||
|         Part of {{ certificate.organizational_unit }} organizational unit. |         Part of {{ certificate.organizational_unit }} organizational unit. | ||||||
|       {% endif %} |       {% endif %} | ||||||
|     </p> |     </p> | ||||||
|  |     <p> | ||||||
|     {% if session.authority.tagging %} |     {% if session.authority.tagging %} | ||||||
|       <p class="tags" data-cn="{{ certificate.common_name }}"> |       <span class="tags" data-cn="{{ certificate.common_name }}"> | ||||||
|         {% include "views/tags.html" %} |         {% include "views/tags.html" %} | ||||||
|       </p> |       </span> | ||||||
|     {% endif %} |     {% endif %} | ||||||
|  |       <span class="attributes" data-cn="{{ certificate.common_name }}"> | ||||||
|  |         {% include "views/attributes.html" %} | ||||||
|  |       </span> | ||||||
|  |     </p> | ||||||
|  |  | ||||||
|     <div class="btn-group"> |     <div class="btn-group"> | ||||||
|       <button type="button" class="btn btn-secondary" data-toggle="collapse" data-target="#details-{{ certificate.sha256sum }}"><i class="fa fa-list"></i> Details</button> |       <button type="button" class="btn btn-secondary" data-toggle="collapse" data-target="#details-{{ certificate.sha256sum }}"><i class="fa fa-list"></i> Details</button> | ||||||
|       <button type="button" class="btn btn-danger" |       <button type="button" class="btn btn-danger" | ||||||
| @@ -46,25 +52,24 @@ | |||||||
|           onclick="javascript:$(this).button('loading');$.ajax({url:'/api/signed/{{certificate.common_name}}/?sha256sum={{ certificate.sha256sum }}&reason=9',type:'delete'});">Revoke due to withdrawn privilege</a> |           onclick="javascript:$(this).button('loading');$.ajax({url:'/api/signed/{{certificate.common_name}}/?sha256sum={{ certificate.sha256sum }}&reason=9',type:'delete'});">Revoke due to withdrawn privilege</a> | ||||||
|       </div> |       </div> | ||||||
|     </div> |     </div> | ||||||
|     <div class="collapse" id="details-{{ certificate.sha256sum }}"> |  | ||||||
|       <p> |  | ||||||
|       <div class="btn-group"> |  | ||||||
|         {% if session.authority.tagging %} |  | ||||||
|           <button type="button" class="btn btn-default" onclick="onNewTagClicked(this);" data-key="other" data-cn="{{ certificate.common_name }}"> |  | ||||||
|            <i class="fa fa-tag"></i> Tag</button> |  | ||||||
|           <button type="button" class="btn btn-default dropdown-toggle dropdown-toggle-split" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"> |  | ||||||
|             <span class="sr-only">Toggle Dropdown</span> |  | ||||||
|           </button> |  | ||||||
|           <div class="dropdown-menu"> |  | ||||||
|             {% for tag_category in session.authority.tagging %} |  | ||||||
|             <a class="dropdown-item" href="#" data-key="{{ tag_category.name }}" data-cn="{{ certificate.common_name }}" |  | ||||||
|               onclick="onNewTagClicked(this);">{{ tag_category.title }}</a> |  | ||||||
|             {% endfor %} |  | ||||||
|           </div> |  | ||||||
|         {% endif %} |  | ||||||
|       </div> |  | ||||||
|       </p> |  | ||||||
|  |  | ||||||
|  |     <div class="btn-group"> | ||||||
|  |       {% if session.authority.tagging %} | ||||||
|  |         <button type="button" class="btn btn-default" onclick="onNewTagClicked(this);" data-key="other" data-cn="{{ certificate.common_name }}"> | ||||||
|  |          <i class="fa fa-tag"></i> Tag</button> | ||||||
|  |         <button type="button" class="btn btn-default dropdown-toggle dropdown-toggle-split" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"> | ||||||
|  |           <span class="sr-only">Toggle Dropdown</span> | ||||||
|  |         </button> | ||||||
|  |         <div class="dropdown-menu"> | ||||||
|  |           {% for tag_category in session.authority.tagging %} | ||||||
|  |           <a class="dropdown-item" href="#" data-key="{{ tag_category.name }}" data-cn="{{ certificate.common_name }}" | ||||||
|  |             onclick="onNewTagClicked(this);">{{ tag_category.title }}</a> | ||||||
|  |           {% endfor %} | ||||||
|  |         </div> | ||||||
|  |       {% endif %} | ||||||
|  |     </div> | ||||||
|  |  | ||||||
|  |     <div class="collapse" id="details-{{ certificate.sha256sum }}"> | ||||||
|       <p>To fetch certificate:</p> |       <p>To fetch certificate:</p> | ||||||
|  |  | ||||||
|       <div class="bd-example"> |       <div class="bd-example"> | ||||||
| @@ -77,7 +82,7 @@ curl http://{{ window.location.hostname }}/api/signed/{{ certificate.common_name | |||||||
|       <pre><code class="language-bash" data-lang="bash">curl http://{{ window.location.hostname }}/api/certificate/ > session.pem |       <pre><code class="language-bash" data-lang="bash">curl http://{{ window.location.hostname }}/api/certificate/ > session.pem | ||||||
| openssl ocsp -issuer session.pem -CAfile session.pem \ | openssl ocsp -issuer session.pem -CAfile session.pem \ | ||||||
|   -url http://{{ window.location.hostname }}/api/ocsp/ \ |   -url http://{{ window.location.hostname }}/api/ocsp/ \ | ||||||
|   -serial 0x{{ certificate.serial }}</span></code></pre> |   -serial 0x{{ certificate.serial }}</code></pre> | ||||||
|  |  | ||||||
|       <p>To fetch script:</p> |       <p>To fetch script:</p> | ||||||
|       <pre><code class="language-bash" data-lang="bash">cd /var/lib/certidude/{{ window.location.hostname }}/ |       <pre><code class="language-bash" data-lang="bash">cd /var/lib/certidude/{{ window.location.hostname }}/ | ||||||
|   | |||||||
| @@ -2,5 +2,5 @@ | |||||||
|   <span data-cn="{{ certificate.common_name }}" |   <span data-cn="{{ certificate.common_name }}" | ||||||
|     title="{{ tag.id }}" |     title="{{ tag.id }}" | ||||||
|     class="badge badge-default" |     class="badge badge-default" | ||||||
|     onClick="onTagClicked(this);">{{ tag.value }}</span> |     onClick="onTagClicked(this);"><i class="fa fa-{{ tag.key }}"></i> {{ tag.value }}</span> | ||||||
| {% endfor %} | {% endfor %} | ||||||
|   | |||||||
							
								
								
									
										31
									
								
								certidude/templates/server/builder.conf
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								certidude/templates/server/builder.conf
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,31 @@ | |||||||
|  | [tpl-archer-c7] | ||||||
|  | # Title shown in the UI | ||||||
|  | title = TP-Link Archer C7 (Access Point) | ||||||
|  |  | ||||||
|  | # Script to build the image, copy file to /etc/certidude/ and make modifications as necessary | ||||||
|  | command = {{ doc_path }}/build-ap.sh | ||||||
|  |  | ||||||
|  | # Path to filesystem overlay, used | ||||||
|  | overlay = {{ doc_path }}/overlay | ||||||
|  |  | ||||||
|  | # Site specific script to be copied to /etc/uci-defaults/99-site-script | ||||||
|  | script = | ||||||
|  |  | ||||||
|  | # Device/model/profile selection | ||||||
|  | model = archer-c7-v2 | ||||||
|  |  | ||||||
|  | # File that will be picked from the bin/ folder | ||||||
|  | filename = archer-c7-v2-squashfs-factory-eu.bin | ||||||
|  |  | ||||||
|  | # And renamed to make it TFTP-friendly | ||||||
|  | rename = ArcherC7v2_tp_recovery.bin | ||||||
|  |  | ||||||
|  | [cf-e380ac] | ||||||
|  | title = Comfast E380AC (Access Point) | ||||||
|  | command = {{ doc_path }}/build-ap.sh | ||||||
|  | overlay = {{ doc_path }}/overlay | ||||||
|  | script = | ||||||
|  | model = cf-e380ac-v2 | ||||||
|  | filename = cf-e380ac-v2-squashfs-factory.bin | ||||||
|  | rename = firmware_auto.bin | ||||||
|  |  | ||||||
| @@ -198,3 +198,9 @@ default = client, 120, | |||||||
| srv = server, 365, Server | srv = server, 365, Server | ||||||
| gw = server, 3, Gateway | gw = server, 3, Gateway | ||||||
| ap = client, 1825, Access Point | ap = client, 1825, Access Point | ||||||
|  |  | ||||||
|  | [script] | ||||||
|  | # Path to the folder with scripts that can be served to the clients, set none to disable scripting | ||||||
|  | path = {{ script_dir }} | ||||||
|  | ;path = /etc/certidude/script | ||||||
|  | ;path = | ||||||
|   | |||||||
							
								
								
									
										58
									
								
								doc/build-ap.sh
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										58
									
								
								doc/build-ap.sh
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,58 @@ | |||||||
|  | #!/bin/bash | ||||||
|  |  | ||||||
|  | set -e | ||||||
|  | set -x | ||||||
|  | umask 022 | ||||||
|  |  | ||||||
|  | VERSION=17.01.4 | ||||||
|  | BASENAME=lede-imagebuilder-$VERSION-ar71xx-generic.Linux-x86_64 | ||||||
|  | FILENAME=$BASENAME.tar.xz | ||||||
|  | URL=http://downloads.lede-project.org/releases/$VERSION/targets/ar71xx/generic/$FILENAME | ||||||
|  |  | ||||||
|  | PACKAGES="luci luci-app-commands \ | ||||||
|  |     collectd collectd-mod-conntrack collectd-mod-interface \ | ||||||
|  |     collectd-mod-iwinfo collectd-mod-load collectd-mod-memory \ | ||||||
|  |     collectd-mod-network collectd-mod-protocols collectd-mod-tcpconns \ | ||||||
|  |     collectd-mod-uptime \ | ||||||
|  |     openssl-util openvpn-openssl curl ca-certificates \ | ||||||
|  |     htop iftop tcpdump nmap nano -odhcp6c -odhcpd -dnsmasq \ | ||||||
|  |     -luci-app-firewall \ | ||||||
|  |     -pppd -luci-proto-ppp -kmod-ppp -ppp -ppp-mod-pppoe \ | ||||||
|  |     -kmod-ip6tables -ip6tables -luci-proto-ipv6 -kmod-iptunnel6 -kmod-ipsec6" | ||||||
|  |  | ||||||
|  |  | ||||||
|  | if [ ! -e $FILENAME ]; then | ||||||
|  |     wget -q $URL | ||||||
|  | fi | ||||||
|  |  | ||||||
|  | if [ ! -e $BASENAME ]; then | ||||||
|  |     tar xf $FILENAME | ||||||
|  | fi | ||||||
|  |  | ||||||
|  | cd $BASENAME | ||||||
|  |  | ||||||
|  | # Copy CA certificate | ||||||
|  | AUTHORITY=$(hostname -f) | ||||||
|  | CERTIDUDE_DIR=/var/lib/certidude/$AUTHORITY | ||||||
|  | if [ -d "$CERTIDUDE_DIR" ]; then | ||||||
|  |     mkdir -p overlay/$CERTIDUDE_DIR | ||||||
|  |     cp $CERTIDUDE_DIR/ca_cert.pem overlay/$CERTIDUDE_DIR | ||||||
|  | fi | ||||||
|  |  | ||||||
|  | cat < EOF > overlay/etc/config/certidude | ||||||
|  |  | ||||||
|  | config authority | ||||||
|  |     option url http://$AUTHORITY | ||||||
|  |     option authority_path /var/lib/certidude/$AUTHORITY/ca_cert.pem | ||||||
|  |     option request_path /var/lib/certidude/$AUTHORITY/client_req.pem | ||||||
|  |     option certificate_path /var/lib/certidude/$AUTHORITY/client_cert.pem | ||||||
|  |     option key_path /var/lib/certidude/$AUTHORITY/client_key.pem | ||||||
|  |     option key_type rsa | ||||||
|  |     option key_length 1024 | ||||||
|  |     option red_led gl-connect:red:wlan | ||||||
|  |     option green_led gl-connect:green:lan | ||||||
|  |  | ||||||
|  | EOF | ||||||
|  |  | ||||||
|  | make image FILES=../overlay/ PACKAGES="$PACKAGES" PROFILE="$PROFILE" | ||||||
|  |  | ||||||
							
								
								
									
										32
									
								
								doc/overlay/etc/profile
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								doc/overlay/etc/profile
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,32 @@ | |||||||
|  | #!/bin/sh | ||||||
|  | [ -f /etc/banner ] && cat /etc/banner | ||||||
|  | [ -e /tmp/.failsafe ] && cat /etc/banner.failsafe | ||||||
|  |  | ||||||
|  | export PATH=/usr/bin:/usr/sbin:/bin:/sbin | ||||||
|  | export HOME=$(grep -e "^${USER:-root}:" /etc/passwd | cut -d ":" -f 6) | ||||||
|  | export HOME=${HOME:-/root} | ||||||
|  | export PS1='\u@\h:\w\$ ' | ||||||
|  |  | ||||||
|  | [ -z "$KSH_VERSION" -o \! -s /etc/mkshrc ] || . /etc/mkshrc | ||||||
|  | [ -x /bin/more ] || alias more=less | ||||||
|  | [ -x /usr/bin/vim ] && alias vi=vim || alias vim=vi | ||||||
|  | [ -x /usr/bin/arp ] || arp() { cat /proc/net/arp; } | ||||||
|  | [ -x /usr/bin/ldd ] || ldd() { LD_TRACE_LOADED_OBJECTS=1 $*; } | ||||||
|  |  | ||||||
|  | HOSTNAME=$(uci get system.@system[0].hostname) | ||||||
|  | DOMAIN=$(uci -q get dhcp.@dnsmasq[0].domain) | ||||||
|  |  | ||||||
|  | if [ $? -eq 0 ]; then | ||||||
|  |     FQDN=$HOSTNAME.$DOMAIN | ||||||
|  | else | ||||||
|  |     FQDN=$HOSTNAME | ||||||
|  | fi | ||||||
|  |  | ||||||
|  | export PS1='\[\033[01;31m\]$FQDN\[\033[01;34m\] \W #\[\033[00m\] ' | ||||||
|  | case "$TERM" in | ||||||
|  |     xterm*|rxvt*) | ||||||
|  |         echo -ne "\033]0;${USER}@${FQDN}:${PWD}\007" | ||||||
|  |     ;; | ||||||
|  |     *) | ||||||
|  |     ;; | ||||||
|  | esac | ||||||
							
								
								
									
										21
									
								
								doc/overlay/etc/uci-defaults/40-hostname
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								doc/overlay/etc/uci-defaults/40-hostname
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,21 @@ | |||||||
|  | MODEL=$(cat /etc/board.json | jsonfilter -e '@["model"]["id"]') | ||||||
|  |  | ||||||
|  | # Hostname prefix | ||||||
|  | case $MODEL in | ||||||
|  |     tl-*|archer-*)  VENDOR=tplink ;; | ||||||
|  |     cf-*) VENDOR=comfast ;; | ||||||
|  |     *) VENDOR=ap ;; | ||||||
|  | esac | ||||||
|  |  | ||||||
|  | # Network interface with relevant MAC address | ||||||
|  | case $MODEL in | ||||||
|  |     tl-wdr*) NIC=wlan1 ;; | ||||||
|  |     archer-*)  NIC=eth1 ;; | ||||||
|  |     cf-e380ac-v2) NIC=eth0 ;; | ||||||
|  |     *) NIC=wlan0 ;; | ||||||
|  | esac | ||||||
|  |  | ||||||
|  | HOSTNAME=$VENDOR-$(cat /sys/class/net/$NIC/address | cut -d : -f 4- | sed -e 's/://g') | ||||||
|  | uci set system.@system[0].hostname=$HOSTNAME | ||||||
|  | uci set network.lan.hostname=$HOSTNAME | ||||||
|  |  | ||||||
							
								
								
									
										66
									
								
								doc/overlay/etc/uci-defaults/50-access-point
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										66
									
								
								doc/overlay/etc/uci-defaults/50-access-point
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,66 @@ | |||||||
|  | # Disable DHCP servers | ||||||
|  | /etc/init.d/odhcpd disable | ||||||
|  | /etc/init.d/dnsmasq disable | ||||||
|  |  | ||||||
|  | # Remove firewall rules since AP bridges ethernet to wireless anyway | ||||||
|  | uci delete firewall.@zone[1] | ||||||
|  | uci delete firewall.@zone[0] | ||||||
|  | uci delete firewall.@forwarding[0] | ||||||
|  | for j in $(seq 0 10); do uci delete firewall.@rule[0]; done | ||||||
|  |  | ||||||
|  | # Remove WAN interface | ||||||
|  | uci delete network.wan | ||||||
|  | uci delete network.wan6 | ||||||
|  |  | ||||||
|  | # Reconfigure DHCP client for bridge over LAN and WAN ports | ||||||
|  | uci delete network.lan.ipaddr | ||||||
|  | uci delete network.lan.netmask | ||||||
|  | uci delete network.lan.ip6assign | ||||||
|  | uci delete network.globals.ula_prefix | ||||||
|  | uci delete network.@switch_vlan[1] | ||||||
|  | uci delete dhcp.@dnsmasq[0].domain | ||||||
|  | uci set network.lan.proto=dhcp | ||||||
|  | uci set network.lan.ipv6=0 | ||||||
|  | uci set network.lan.ifname='eth0' | ||||||
|  | uci set network.lan.stp=1 | ||||||
|  |  | ||||||
|  | # Radio ordering differs among models | ||||||
|  | case $(uci get wireless.radio0.hwmode) in | ||||||
|  |     11a) uci rename wireless.radio0=radio5ghz;; | ||||||
|  |     11g) uci rename wireless.radio0=radio2ghz;; | ||||||
|  | esac | ||||||
|  | case $(uci get wireless.radio1.hwmode) in | ||||||
|  |     11a) uci rename wireless.radio1=radio5ghz;; | ||||||
|  |     11g) uci rename wireless.radio1=radio2ghz;; | ||||||
|  | esac | ||||||
|  |  | ||||||
|  | # Reset virtual SSID-s | ||||||
|  | uci delete wireless.@wifi-iface[1] | ||||||
|  | uci delete wireless.@wifi-iface[0] | ||||||
|  |  | ||||||
|  | # Pseudorandomize channel selection, should work with 80MHz on 5GHz band | ||||||
|  | case $(uci get system.@system[0].hostname | md5sum) in | ||||||
|  |    1*|2*|3*|4*) uci set wireless.radio2ghz.channel=1; uci set wireless.radio5ghz.channel=36 ;; | ||||||
|  |    5*|6*|7*|8*) uci set wireless.radio2ghz.channel=5; uci set wireless.radio5ghz.channel=52 ;; | ||||||
|  |    9*|0*|a*|b*) uci set wireless.radio2ghz.channel=9; uci set wireless.radio5ghz.channel=100 ;; | ||||||
|  |    c*|d*|e*|f*) uci set wireless.radio2ghz.channel=13; uci set wireless.radio5ghz.channel=132 ;; | ||||||
|  | esac | ||||||
|  |  | ||||||
|  | # Create bridge for guests | ||||||
|  | uci set network.guest=interface | ||||||
|  | uci set network.guest.proto='static' | ||||||
|  | uci set network.guest.address='0.0.0.0' | ||||||
|  | uci set network.guest.type='bridge' | ||||||
|  | uci set network.guest.ifname='eth0.156' # tag id 156 for guest network | ||||||
|  | uci set network.guest.ipaddr='0.0.0.0' | ||||||
|  | uci set network.guest.ipv6=0 | ||||||
|  | uci set network.guest.stp=1 | ||||||
|  |  | ||||||
|  | # Disable switch tagging and bridge all ports on TP-Link WDR3600/WDR4300 | ||||||
|  | case $(cat /etc/board.json | jsonfilter -e '@["model"]["id"]') in | ||||||
|  |     tl-wdr*) | ||||||
|  |         uci set network.@switch[0].enable_vlan=0 | ||||||
|  |         uci set network.@switch_vlan[0].ports='0 1 2 3 4 5 6' | ||||||
|  |     ;; | ||||||
|  |     *) ;; | ||||||
|  | esac | ||||||
		Reference in New Issue
	
	Block a user