diff --git a/defaults/main.yml b/defaults/main.yml index 0c1b194..bf819b1 100644 --- a/defaults/main.yml +++ b/defaults/main.yml @@ -1,17 +1,28 @@ -mumble_web_domain: "mumble.example.com" +acme_enabled: yes +acme_domains: + - "{{ umurmur_domain }}" + - "{{ mumble_web_domain }}" + +mumble_web_enabled: yes +mumble_web_domain: "{{ inventory_hostname }}.local" mumble_web_owner: mumble-web mumble_web_group: "{{ mumble_web_owner }}" -mumble_web_certificate: "{{ acme_certs_dir }}/{{ mumble_web_domain }}.d/fullchain.pem" +mumble_web_certificate: "{{ acme_certs_dir }}/{{ mumble_web_domain }}.d/{{ acme_enabled | ternary('fullchain','cert') }}.pem" mumble_web_trusted_certificate: "{{ acme_certs_dir }}/{{ mumble_web_domain }}.d/chain.pem" mumble_web_private_key: "{{ acme_keys_dir }}/{{ mumble_web_domain }}.pem" -mumble_web_dhparam: "/etc/nginx/ssl/dhparam.pem" mumble_web_www_dir: /var/www/mumble-web mumble_web_version: master mumble_web_websockify_port: "64737" -murmur_domain: "{{ mumble_web_domain }}" +mumble_web_nginx_enabled: yes +mumble_web_nginx_config_dir: /etc/nginx +mumble_web_nginx_generate_dhparam: yes +mumble_web_nginx_dhparam: "{{ mumble_web_nginx_config_dir }}/ssl/dhparam.pem" +mumble_web_nginx_dhparam_size: 2048 + murmur_enabled: yes -# murmur_superuser_password: "supersecret" +murmur_domain: "{{ mumble_web_domain }}" +murmur_superuser_password: supersecret murmur_database: path: "/var/lib/mumble-server/mumble-server.sqlite" # driver: "QMYSQL" @@ -20,18 +31,23 @@ murmur_database: # host: "localhost" # port: 3306 # prefix: "murmur_" -murmur_ice: "tcp -h 127.0.0.1 -p 6502" -# murmur_ice_secret_read: "somesecret" -# murmur_ice_secret_write: "anothersecret" +murmur_ice_host: "127.0.0.1" +murmur_ice_port: 6502 +# Give read permission on ICE connection. Empty secret will deny read permission to anybody. +murmur_ice_secret_read: "{{ lookup('password', '/tmp/murmur-ice-read.secret length=50') }}" +# Give write permission on ICE connection. Empty secret will deny write permission to anybody. +murmur_ice_secret_write: "{{ lookup('password', '/tmp/murmur-ice-write.secret length=50') }}" + murmur_autoban_attempts: 10 murmur_autoban_timeframe: 120 murmur_autoban_time: 300 murmur_log_file: "/var/log/mumble-server/mumble-server.log" murmur_pid_file: "/var/run/mumble-server/mumble-server.pid" murmur_welcome_text: "Welcome on the {{ inventory_hostname }} Mumble server!" -murmur_port: 64738 # Leave blank to let Murmur bind to all available addresses murmur_host: "" +murmur_port: 64738 +# Password to join the server murmur_server_password: "" murmur_max_bandwidth: 72000 murmur_max_users: 100 @@ -47,14 +63,15 @@ murmur_allow_html: yes # Set to 0 to keep logs forever, or -1 to disable logging to the DB. murmur_log_days: -1 # Name for root channel and entry in mumble main server list -# murmur_register: -# name: "MyMumbleServerRegisterName" -# password: "password" -# url: "https://mymumbleserverurl.org" -# hostname: "mymumblehostname.domain.org" +murmur_register_enabled: no +murmur_register: + name: "MyMumbleServerRegisterName" + password: "password" + url: "https://mymumbleserverurl.org" + hostname: "mymumblehostname.domain.org" # Enable Bonjour for dev purpose murmur_bonjour_enabled: no -murmur_certificate: "{{ acme_certs_dir }}/{{ murmur_domain }}.d/fullchain.pem" +murmur_certificate: "{{ acme_certs_dir }}/{{ murmur_domain }}.d/{{ acme_enabled | ternary('fullchain', 'cert') }}.pem" murmur_trusted_certificate: "{{ acme_certs_dir }}/{{ murmur_domain }}.d/chain.pem" murmur_private_key: "{{ acme_keys_dir }}/{{ murmur_domain }}.pem" murmur_dhparam: "@ffdhe4096" @@ -62,13 +79,26 @@ murmur_owner: "mumble-server" murmur_group: "{{ murmur_owner }}" murmur_client_certificate_required: no murmur_send_server_version: yes -murmur_ice_warn_unknown_properties: 1 +murmur_ice_warn_unknown_properties: yes murmur_ice_message_size_max: 65536 -umurmur_domain: "{{ mumble_web_domain }}" umurmur_enabled: yes +umurmur_domain: "{{ mumble_web_domain }}" umurmur_max_bandwidth: 48000 umurmur_server_password: "" +umurmur_channels: +- name: Root + description: Root channel. No entry. + noenter: yes +- name: Welcome + parent: Root + description: Welcome channel + position: 0 +- name: Silent + parent: Root + description: Silent channel + silent: yes + position: 1 umurmur_channel_links: - source: "{{ umurmur_default_channel }}" destinations: >- @@ -79,8 +109,8 @@ umurmur_channel_links: | list }} umurmur_ssl_group: "{{ acme_ssl_group }}" -umurmur_certificate: "{{ acme_certs_dir }}/{{ umurmur_domain }}.d/fullchain.pem" +umurmur_certificate: "{{ acme_certs_dir }}/{{ umurmur_domain }}.d/{{ acme_enabled | ternary('fullchain', 'cert') }}.pem" umurmur_private_key: "{{ acme_keys_dir }}/{{ umurmur_domain }}.pem" umurmur_version: master umurmur_ispublic: yes -umurmur_port: "64738" +umurmur_port: 64738 diff --git a/files/murmur/mice.yml b/files/murmur/mice.yml new file mode 100644 index 0000000..5d47323 --- /dev/null +++ b/files/murmur/mice.yml @@ -0,0 +1,42 @@ +channels: +- name: Root + description: Root channel. No entry. + groups: + admin: + inherit: true + inheritable: true + permissions: + - group: all + deny: + - Enter + recursive: false + enabled: true + - group: all + allow: + - RegisterSelf + - MakeTempChannel + - group: admin + allow: + - MuteDeafen + - Move + - Kick + - Ban + - Register +- name: Welcome + parent: Root + description: Welcome channel + position: 0 + isdefault: true +- name: Silent + parent: Root + description: Silent channel + position: 1 + permissions: + - group: all + deny: + - Speak + - MakeTempChannel + enabled: true + - group: admin + allow: + - MakeTempChannel \ No newline at end of file diff --git a/files/murmur/scripts/mice.py b/files/murmur/scripts/mice.py new file mode 100644 index 0000000..26b5cb9 --- /dev/null +++ b/files/murmur/scripts/mice.py @@ -0,0 +1,345 @@ +#!/usr/bin/python3 + +import argparse +import logging +import os +import sys +from typing import List, Dict + +import Ice +import yaml + +import Murmur + +CONFIG_SEARCH_PATH = [".", "/etc"] +CONFIG_DEFAULT_NAME = "mice.yml" + +ICE_DEFAULT_HOST = "127.0.0.1" +ICE_DEFAULT_PORT = 6502 +ICE_DEFAULT_TIMEOUT = 1000 + + +class Mice: + def __init__(self, host: str = ICE_DEFAULT_HOST, + port: int = ICE_DEFAULT_PORT, secret: str = None, + timeout: int = ICE_DEFAULT_TIMEOUT): + logging.debug("Initializing ICE connection") + properties = Ice.createProperties() + properties.setProperty("Ice.ImplicitContext", "Shared") + init_data = Ice.InitializationData() + init_data.properties = properties + self._communicator = Ice.initialize(init_data) + + if secret: + logging.debug("Providing ICE secret") + self._communicator.getImplicitContext().put("secret", secret) + + logging.debug("Creating meta proxy") + proxy_str = f"Meta: tcp -h {host} -p {port} -t {timeout}" + proxy = self._communicator.stringToProxy(proxy_str) + self.meta = Murmur.MetaPrx.checkedCast(proxy) + + if not self.meta: + self._communicator.destroy() + raise RuntimeError(f"Invalid Proxy: {proxy_str}") + + try: + logging.debug("Retrieving the list of Mumble servers") + servers = self.meta.getAllServers() + except Murmur.InvalidSecretException: + self._communicator.destroy() + raise RuntimeError(f"Failed to connect to {host} " + f"on port {port}: Invalid secret.") from None + self.servers = [MurmurServer(server) for server in servers] + + if len(self.servers) == 0: + self._communicator.destroy() + raise RuntimeError("No Mumble server found") + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_value, traceback): + self._communicator.destroy() + + +class ChannelGroup: + def __init__(self, name: str, inherit: bool = True, + inheritable: bool = True): + self.name = name + self.inherit = inherit + self.inheritable = inheritable + + def get_state(self) -> Murmur.Group: + return Murmur.Group( + name=self.name, + inherit=self.inherit, + inheritable=self.inheritable + ) + + @staticmethod + def from_dict(group_dict: Dict[str, object]) -> "ChannelGroup": + return ChannelGroup( + name=group_dict["name"], + inherit=group_dict.get("inherit", True), + inheritable=group_dict.get("inheritable", True) + ) + + +class ChannelPermission: + def __init__(self, group: str = "all", userid: int = -1, + allow: int = 0, deny: int = 0, + recursive: bool = True, enabled: bool = True): + self.group = group + self.userid = userid + self.allow = allow + self.deny = deny + self.recursive = recursive + self.enabled = enabled + + def get_acl(self) -> Murmur.ACL: + return Murmur.ACL( + applyHere=self.enabled, + applySubs=self.recursive, + userid=self.userid, + group=self.group, + allow=self.allow, + deny=self.deny) + + @staticmethod + def from_dict(permission_dict: Dict[str, object]) -> "ChannelPermission": + allow = ChannelPermission._parse_rules( + permission_dict.get("allow", [])) + deny = ChannelPermission._parse_rules( + permission_dict.get("deny", [])) + + return ChannelPermission( + group=permission_dict["group"], + allow=allow, + deny=deny, + recursive=permission_dict.get("recursive", True), + enabled=permission_dict.get("enabled", True)) + + @staticmethod + def _parse_rule(rule: str) -> int: + return getattr(Murmur, "Permission" + rule) + + @staticmethod + def _parse_rules(rules: List[str]) -> int: + result = 0 + for rule in rules: + result |= ChannelPermission._parse_rule(rule) + return result + + +class Channel: + def __init__(self, name: str, description: str = "", position: int = 0, + groups: List[ChannelGroup] = [], + permissions: List[ChannelPermission] = [], + inherit: bool = True, isdefault: bool = False, + parent: "Channel" = None): + self.id = -1 + self.name = name + self.description = description + self.position = position + self.groups = groups + self.permissions = permissions + self.inherit = inherit + self.isdefault = isdefault + self.parent = parent + self.children = [] + + def __str__(self) -> str: + return self.name + + def get_state(self) -> Murmur.Channel: + if self.parent is None: + parent_id = 0 + else: + parent_id = self.parent.id + + return Murmur.Channel( + id=self.id, + name=self.name, + description=self.description, + position=self.position, + parent=parent_id) + + def add(self, channel: "Channel") -> None: + self.children.append(channel) + channel.parent = self + + @staticmethod + def from_dict(channel_dict: Dict[str, object]) -> "Channel": + if "parent" in channel_dict: + parent = Channel(channel_dict["parent"]) + else: + parent = None + + groups = [] + for group_name, group_dict in channel_dict.get("groups", {}).items(): + group_dict["name"] = group_name + group = ChannelGroup.from_dict(group_dict) + groups.append(group) + + permissions = [] + for permission_dict in channel_dict.get("permissions", []): + permission = ChannelPermission.from_dict(permission_dict) + permissions.append(permission) + + return Channel( + name=channel_dict["name"], + description=channel_dict.get("description", ''), + position=channel_dict.get("position", 0), + groups=groups, + permissions=permissions, + inherit=channel_dict.get("inherit", True), + isdefault=channel_dict.get("isdefault", False), + parent=parent) + + +class MurmurServer: + def __init__(self, server: Murmur.Server): + self._server = server + + def clear_acls(self, channel_id=0): + _, groups, inherit = self._server.getACL(0) + self._server.setACL(channel_id, [], groups, inherit) + + def clear_channels(self) -> None: + for channel_state in self._server.getChannels().values(): + if channel_state.id != 0 and channel_state.parent == 0: + self._server.removeChannel(channel_state.id) + self.clear_acls() + + def set_channel_acls(self, channel: Channel) -> None: + acls = [] + for permission in channel.permissions: + acl = permission.get_acl() + acls.append(acl) + + _, current_groups, _ = self._server.getACL(channel.id) + current_group_names = {group.name: group for group in current_groups} + + groups = [] + for group in channel.groups: + group_state = group.get_state() + if group.name in current_group_names: + current_group_state = current_group_names[group.name] + group_state.add = current_group_state.add + group_state.remove = current_group_state.remove + del current_group_names[group.name] + groups.append(group_state) + groups.extend(current_group_names.values()) + + self._server.setACL(channel.id, acls, groups, channel.inherit) + + def create_channels(self, channel: Channel) -> None: + if channel.parent is None: + channel.id = 0 + else: + channel.id = self._server.addChannel( + channel.name, + channel.parent.id) + + self._server.setChannelState(channel.get_state()) + self.set_channel_acls(channel) + + if channel.isdefault: + self._server.setConf("defaultchannel", str(channel.id)) + + for child in channel.children: + self.create_channels(child) + + +class Config: + def __init__(self, filename: str = None): + self.filename = filename + self._data = {} + + self.load(filename) + + def load(self, filename: str = None): + if not filename: + for config_path in CONFIG_SEARCH_PATH: + filename = os.path.join(config_path, CONFIG_DEFAULT_NAME) + if os.path.exists(filename) and os.path.isfile(filename): + break + + with open(filename, 'r') as ifd: + self._data = yaml.safe_load(ifd) + + def parse_channels(self) -> Channel: + channels = [] + for channel_dict in self._data["channels"]: + channel = Channel.from_dict(channel_dict) + channels.append(channel) + + channel_names = {channel.name: channel for channel in channels} + for channel in channels: + if channel.parent is None: + continue + if channel.parent.name not in channel_names: + continue + channel_names[channel.parent.name].add(channel) + + root_channels = [channel for channel in channels + if channel.parent is None] + + if len(root_channels) == 0: + raise RuntimeError("No root channel found.") + + if len(root_channels) > 1: + root_names = root_channels.join(', ') + raise RuntimeError(f"Multiple root channels found: {root_names}") + + default_channels = [channel for channel in channels + if channel.isdefault] + + if len(default_channels) > 1: + default_names = default_channels.join(', ') + raise RuntimeError("Multiple default channels found: " + f"{default_names}") + + return root_channels[0] + + +def main(): + parser = argparse.ArgumentParser( + description="Manage Mumble server through ICE connection", + formatter_class=argparse.ArgumentDefaultsHelpFormatter + ) + parser.add_argument("--host", "-H", default=ICE_DEFAULT_HOST, + help="ICE host or ip") + parser.add_argument("--port", "-p", default=ICE_DEFAULT_PORT, + help="ICE port number") + parser.add_argument("--timeout", "-t", default=ICE_DEFAULT_TIMEOUT, + help="Connection timeout in milliseconds") + parser.add_argument("--secret", "-s", + default=os.environ.get("MICE_SECRET"), + help="ICE write secret") + parser.add_argument("--server-id", "-i", type=int, default=0, + help="Server ID to manage") + parser.add_argument("--config", "-c", + help="Path to the config file.") + args = parser.parse_args() + + config = Config(args.config) + root_channel = config.parse_channels() + + server_id = args.server_id + try: + with Mice(args.host, args.port, args.secret, args.timeout) as mice: + if not 0 <= server_id < len(mice.servers): + raise RuntimeError(f"Cannot find server with ID {server_id}") + server = mice.servers[server_id] + + server.clear_channels() + server.create_channels(root_channel) + except RuntimeError as e: + logging.error(e, exc_info=e) + return 1 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/files/scripts/mice.py b/files/scripts/mice.py deleted file mode 100644 index aff7cca..0000000 --- a/files/scripts/mice.py +++ /dev/null @@ -1,54 +0,0 @@ -#!/usr/bin/python3 - -import argparse -import logging -import sys - -import Ice - -import Murmur - - -def init(host, port, timeout): - logging.debug("Initializing ICE connection") - proxy_str = f"Meta: tcp -h {host} -p {port} -t {timeout}" - with Ice.initialize() as communicator: - proxy = communicator.stringToProxy(proxy_str) - mice = Murmur.MetaPrx.checkedCast(proxy) - - if not mice: - raise RuntimeError(f"Invalid Proxy: {proxy_str}") - - servers = mice.getAllServers() - - if len(servers) == 0: - raise RuntimeError("No Mumble server found") - - return servers - - -def main(): - parser = argparse.ArgumentParser( - description="Manage Mumble server through ICE connection", - formatter_class=argparse.ArgumentDefaultsHelpFormatter - ) - parser.add_argument("--host", "-H", default="127.0.0.1", - help="ICE host or ip") - parser.add_argument("--port", "-p", default=6502, - help="ICE port number") - parser.add_argument("--timeout", "-t", default=1000, - help="Connection timeout in milliseconds") - args = parser.parse_args() - - try: - servers = init(args.host, args.port, args.timeout) - except RuntimeError as e: - logging.error(e, exc_info=e) - return 1 - - for server in servers: - print(server.getChannels()) - - -if __name__ == "__main__": - sys.exit(main()) diff --git a/handlers/main.yml b/handlers/main.yml index abde375..e2c4a57 100644 --- a/handlers/main.yml +++ b/handlers/main.yml @@ -33,4 +33,11 @@ command: murmurd -ini /etc/mumble-server.ini -supw "{{ murmur_superuser_password }}" # See https://github.com/mumble-voip/mumble/issues/3911 # Should be fixed in 1.3.1 - failed_when: no \ No newline at end of file + failed_when: no + +- name: create murmur channels + command: + cmd: ./mice.py -c {{ murmur_ice_config_path | quote }} + chdir: "{{ murmur_ice_script_dir }}" + environment: + MICE_SECRET: "{{ murmur_ice_secret_write }}" \ No newline at end of file diff --git a/meta/main.yml b/meta/main.yml index e2d533c..8ef7244 100644 --- a/meta/main.yml +++ b/meta/main.yml @@ -1,7 +1,7 @@ dependencies: - role: ppbe.acme - tags: [acme] + tags: [never] - role: geerlingguy.nodejs vars: nodejs_install_npm_user: root diff --git a/molecule/default/INSTALL.rst b/molecule/default/INSTALL.rst new file mode 100644 index 0000000..0c4bf5c --- /dev/null +++ b/molecule/default/INSTALL.rst @@ -0,0 +1,23 @@ +********************************* +Vagrant driver installation guide +********************************* + +Requirements +============ + +* Vagrant +* Virtualbox, Parallels, VMware Fusion, VMware Workstation or VMware Desktop + +Install +======= + +Please refer to the `Virtual environment`_ documentation for installation best +practices. If not using a virtual environment, please consider passing the +widely recommended `'--user' flag`_ when invoking ``pip``. + +.. _Virtual environment: https://virtualenv.pypa.io/en/latest/ +.. _'--user' flag: https://packaging.python.org/tutorials/installing-packages/#installing-to-the-user-site + +.. code-block:: bash + + $ pip install 'molecule_vagrant' diff --git a/molecule/default/converge.yml b/molecule/default/converge.yml new file mode 100644 index 0000000..4a2ab18 --- /dev/null +++ b/molecule/default/converge.yml @@ -0,0 +1,12 @@ +--- +- name: Converge + hosts: all + become: yes + + vars: + murmur_enabled: yes + umurmur_enabled: no + acme_enabled: no + + roles: + - ansible-role-mumble \ No newline at end of file diff --git a/molecule/default/molecule.yml b/molecule/default/molecule.yml new file mode 100644 index 0000000..10db698 --- /dev/null +++ b/molecule/default/molecule.yml @@ -0,0 +1,57 @@ +--- +.hardware: &hardware + memory: 1024 + cpu: 2 + +dependency: + name: galaxy +driver: + name: vagrant + provider: + name: virtualbox +platforms: + - name: buster-mumble-molecule + box: debian/buster64 + <<: *hardware + interfaces: + - network_name: forwarded_port + guest: 80 + host: 8080 + - network_name: forwarded_port + guest: 443 + host: 8443 + - network_name: forwarded_port + guest: 64738 + host: 14738 + protocol: udp + - network_name: forwarded_port + guest: 64738 + host: 14738 + protocol: tcp + - name: focal-mumble-molecule + box: ubuntu/focal64 + <<: *hardware + interfaces: + - network_name: forwarded_port + guest: 80 + host: 9080 + - network_name: forwarded_port + guest: 443 + host: 9443 + - network_name: forwarded_port + guest: 64738 + host: 24738 + protocol: udp + - network_name: forwarded_port + guest: 64738 + host: 24738 + protocol: tcp +provisioner: + name: ansible + config_options: + defaults: + interpreter_python: /usr/bin/python3 + ssh_connection: + pipelining: true +verifier: + name: ansible \ No newline at end of file diff --git a/molecule/default/prepare.yml b/molecule/default/prepare.yml new file mode 100644 index 0000000..3ebb94c --- /dev/null +++ b/molecule/default/prepare.yml @@ -0,0 +1,14 @@ +- name: Prepare + hosts: all + become: yes + + tasks: + - name: Update apt cache + apt: + update_cache: yes + cache_valid_time: 3600 + + - name: Install nginx package + package: + name: nginx-light + state: present \ No newline at end of file diff --git a/tasks/dhparam/begin.yml b/tasks/dhparam/begin.yml new file mode 100644 index 0000000..48d7c39 --- /dev/null +++ b/tasks/dhparam/begin.yml @@ -0,0 +1,21 @@ +- name: Ensure Diffie-Hellman directory exists + file: + path: "{{ mumble_web_nginx_dhparam | dirname }}" + state: directory + owner: root + group: www-data + mode: "755" + +- name: Generate Diffie-Hellman parameters + # This can take a long time... So we are doing it in async mode + openssl_dhparam: + path: "{{ mumble_web_nginx_dhparam }}" + state: present + owner: root + group: www-data + mode: "640" + size: "{{ mumble_web_nginx_dhparam_size }}" + async: 3600 + poll: 0 + changed_when: no + register: _mumble_web_nginx_dhparam \ No newline at end of file diff --git a/tasks/dhparam/end.yml b/tasks/dhparam/end.yml new file mode 100644 index 0000000..0279618 --- /dev/null +++ b/tasks/dhparam/end.yml @@ -0,0 +1,7 @@ +- name: Waiting for Diffie-Hellman task to complete… + async_status: + jid: "{{ _mumble_web_nginx_dhparam.ansible_job_id }}" + register: _mumble_web_nginx_dhparam_job + retries: 60 + delay: 30 # will retry every 30s for 30min (60 retries) + until: _mumble_web_nginx_dhparam_job.finished \ No newline at end of file diff --git a/tasks/main.yml b/tasks/main.yml index b8d5753..b9b3bd8 100644 --- a/tasks/main.yml +++ b/tasks/main.yml @@ -1,3 +1,28 @@ +- name: Generate Diffie-Hellman parameters in async mode + import_tasks: dhparam/begin.yml + when: mumble_web_enabled and mumble_web_nginx_generate_dhparam + tags: + - certificate + - dhparam + +- name: Install ACME certificates + import_role: + name: ppbe.acme + when: acme_enabled + tags: + - certificate + - acme + +- name: Install Self-Signed certificates + include_tasks: self_signed.yml + loop: "{{ acme_domains | unique }}" + loop_control: + loop_var: domain_name + when: not acme_enabled + tags: + - certificate + - self_signed + - name: Deploy uMurmur server import_tasks: umurmur.yml when: umurmur_enabled @@ -13,7 +38,14 @@ when: mumble_web_enabled tags: mumble_web +- name: Wait for Diffie-Hellman task to complete + import_tasks: dhparam/end.yml + when: mumble_web_enabled and mumble_web_nginx_generate_dhparam + tags: + - certificate + - dhparam + - name: Configure Nginx for mumble web client import_tasks: nginx.yml - when: mumble_web_enabled + when: mumble_web_enabled and mumble_web_nginx_enabled tags: nginx \ No newline at end of file diff --git a/tasks/mumble_web.yml b/tasks/mumble_web.yml index df835a9..229c230 100644 --- a/tasks/mumble_web.yml +++ b/tasks/mumble_web.yml @@ -1,5 +1,11 @@ +- name: Install git package + package: + name: git + state: present + tags: mumble_web_install + - name: Install websockify - apt: + package: name: websockify state: present notify: reload mumble-web @@ -36,13 +42,18 @@ become_user: "{{ mumble_web_owner }}" tags: mumble_web_install +- name: Check if mumble-web dist directory exists + stat: + path: "{{ mumble_web_www_dir }}/dist" + register: _mumble_web_dist + - name: Build mumble-web from sources command: npm clean-install args: chdir: "{{ mumble_web_www_dir }}" register: _mumble_web_installed become_user: "{{ mumble_web_owner }}" - when: mumble_web_cloned is changed + when: mumble_web_cloned is changed or not _mumble_web_dist.stat.exists tags: [mumble_web_install,mumble_web_build] - name: Copy mumble-web config file diff --git a/tasks/murmur.yml b/tasks/murmur.yml index 7514ce6..81c2b8b 100644 --- a/tasks/murmur.yml +++ b/tasks/murmur.yml @@ -1,15 +1,19 @@ - name: Install mumble-server package - apt: - pkg: mumble-server + package: + name: mumble-server state: present register: murmur_installed - name: Install ICE dependencies for Python - apt: - pkg: - - python3-zeroc-ice - - zeroc-ice-compilers + package: + name: "{{ package }}" state: present + loop: + - python3-zeroc-ice + - python3-yaml + - zeroc-ice-compilers + loop_control: + loop_var: package - name: Append ssl-cert group to {{ murmur_owner }} user user: @@ -46,9 +50,12 @@ when: murmur_superuser_password is defined notify: change murmur superuser password +- name: Trigger Murmur handlers + meta: flush_handlers + - name: Create mumble-ice directory file: - path: /opt/mice/ + path: "{{ murmur_ice_script_dir }}" state: directory owner: root group: root @@ -56,12 +63,13 @@ - name: Copy mice Python script copy: - src: scripts/mice.py - dest: /opt/mice/mice.py + src: murmur/scripts/mice.py + dest: "{{ murmur_ice_script_dir }}/mice.py" owner: root group: root mode: "755" - register: _murmur_mice_copied + notify: create murmur channels + register: _murmur_mice_script_copied - name: Compile Murmur.ice slice file command: |- @@ -71,5 +79,17 @@ -I/usr/share/ice/slice \ /usr/share/slice/Murmur.ice args: - chdir: /opt/mice/ - when: _murmur_mice_copied is changed \ No newline at end of file + chdir: "{{ murmur_ice_script_dir }}" + when: _murmur_mice_script_copied is changed + +- name: Copy mice config + copy: + src: murmur/mice.yml + dest: "{{ murmur_ice_config_path }}" + owner: root + group: root + mode: "644" + notify: create murmur channels + +- name: Trigger Mice handlers + meta: flush_handlers \ No newline at end of file diff --git a/tasks/nginx.yml b/tasks/nginx.yml index 5e89113..5f9f7aa 100644 --- a/tasks/nginx.yml +++ b/tasks/nginx.yml @@ -1,7 +1,7 @@ - name: Copy Nginx config file template: src: nginx.conf.j2 - dest: /etc/nginx/sites-available/mumble.conf + dest: "{{ mumble_web_nginx_config_dir }}/sites-available/mumble.conf" owner: root group: www-data mode: "755" @@ -9,8 +9,8 @@ - name: Enable Nginx config file file: - src: /etc/nginx/sites-available/mumble.conf - path: /etc/nginx/sites-enabled/mumble.conf + src: "{{ mumble_web_nginx_config_dir }}/sites-available/mumble.conf" + path: "{{ mumble_web_nginx_config_dir }}/sites-enabled/mumble.conf" state: link notify: reload nginx diff --git a/tasks/self_signed.yml b/tasks/self_signed.yml new file mode 100644 index 0000000..c3b38df --- /dev/null +++ b/tasks/self_signed.yml @@ -0,0 +1,64 @@ +- name: Install SSL dependencies + package: + name: "{{ package }}" + state: present + loop: + - ssl-cert + - python3-openssl + loop_control: + loop_var: package + tags: selfsigned_install + +- name: Create SSL config directories + file: + path: "{{ config_dir }}" + state: directory + owner: root + group: "{{ acme_ssl_group }}" + mode: "711" + loop: + - "{{ acme_config_dir }}" + - "{{ acme_keys_dir }}" + - "{{ acme_accounts_dir }}" + - "{{ acme_csr_dir }}" + loop_control: + loop_var: config_dir + tags: selfsigned_install + +- name: Create {{ domain_name }} certificates directory + file: + path: "{{ acme_certs_dir }}/{{ domain_name }}.d" + state: directory + owner: root + group: "{{ acme_ssl_group }}" + mode: "755" + tags: selfsigned_install + +- name: Generate private key for {{ domain_name }} certificate + openssl_privatekey: + path: "{{ acme_keys_dir }}/{{ domain_name }}.pem" + owner: root + group: "{{ acme_ssl_group }}" + mode: "640" + type: RSA + size: 4096 + +- name: Generate CSR for {{ domain_name }} certificate + openssl_csr: + path: "{{ acme_csr_dir }}/{{ domain_name }}.csr" + owner: root + group: "{{ acme_ssl_group }}" + mode: "644" + privatekey_path: "{{ acme_keys_dir }}/{{ domain_name }}.pem" + common_name: "{{ domain_name }}" + +- name: Generate self-signed certificate + openssl_certificate: + path: "{{ acme_certs_dir }}/{{ domain_name }}.d/cert.pem" + csr_path: "{{ acme_csr_dir }}/{{ domain_name }}.csr" + privatekey_path: "{{ acme_keys_dir }}/{{ domain_name }}.pem" + provider: selfsigned + state: present + owner: root + group: "{{ acme_ssl_group }}" + mode: "644" \ No newline at end of file diff --git a/tasks/umurmur.yml b/tasks/umurmur.yml index 2996b35..e54119d 100644 --- a/tasks/umurmur.yml +++ b/tasks/umurmur.yml @@ -1,6 +1,7 @@ - name: Install umurmur build dependencies - apt: + package: name: "{{ package }}" + state: present loop: - git - build-essential @@ -87,5 +88,5 @@ - name: Open umurmur port with UFW ufw: rule: allow - port: "{{ umurmur_port }}" + port: "{{ umurmur_port | str }}" when: umurmur_ispublic | bool \ No newline at end of file diff --git a/templates/mumble-server.ini.j2 b/templates/mumble-server.ini.j2 index 2d643ce..7218af8 100644 --- a/templates/mumble-server.ini.j2 +++ b/templates/mumble-server.ini.j2 @@ -72,7 +72,7 @@ dbOpts={{ murmur_database.opts | default('') | to_json }} ; with ICE, you should only use it if you trust all the users who have ; shell access to your machine. ; Please see the ICE documentation on how to specify endpoints. -ice="{{ murmur_ice }}" +ice="tcp -h {{ murmur_ice_host | quote }} -p {{ murmur_ice_port | int }}" ; Ice primarily uses local sockets. This means anyone who has a ; user account on your machine can connect to the Ice services. @@ -227,7 +227,7 @@ allowhtml={{ murmur_allow_html }} ;logdays=31 logdays={{ murmur_log_days }} -{% if murmur_register.name is defined %} +{% if murmur_register_enabled %} ; To enable public server registration, the serverpassword must be blank, and ; this must all be filled out. ; The password here is used to create a registry for the server name; subsequent @@ -256,7 +256,9 @@ bonjour={{ murmur_bonjour_enabled }} ;sslKey= sslCert={{ murmur_certificate | to_json }} sslKey={{ murmur_private_key | to_json }} +{% if acme_enabled %} sslCa={{ murmur_trusted_certificate | to_json }} +{% endif %} ; The sslDHParams option allows you to specify a PEM-encoded file with ; Diffie-Hellman parameters, which will be used as the default Diffie- @@ -319,5 +321,5 @@ sendversion={{ murmur_send_server_version }} ; Please note that this section has to be last in the configuration file. ; [Ice] -Ice.Warn.UnknownProperties={{ murmur_ice_warn_unknown_properties }} +Ice.Warn.UnknownProperties={{ murmur_ice_warn_unknown_properties | int }} Ice.MessageSizeMax={{ murmur_ice_message_size_max }} diff --git a/templates/nginx.conf.j2 b/templates/nginx.conf.j2 index 976abe9..b82c172 100644 --- a/templates/nginx.conf.j2 +++ b/templates/nginx.conf.j2 @@ -8,7 +8,7 @@ server { return 301 https://$host$request_uri; } -{% if acme_challenge_dir is defined %} +{% if acme_enabled and acme_challenge_dir is defined %} location ^~ /.well-known/acme-challenge/ { allow all; root {{ acme_challenge_dir }}; @@ -30,8 +30,8 @@ server { ssl_session_cache shared:AnsibleSSL:10m; # about 40000 sessions ssl_session_tickets off; -{% if mumble_web_dhparam is defined and mumble_web_dhparam != '' %} - ssl_dhparam {{ mumble_web_dhparam }}; +{% if mumble_web_nginx_dhparam %} + ssl_dhparam {{ mumble_web_nginx_dhparam }}; {% endif %} ssl_protocols TLSv1.2 TLSv1.3; @@ -44,8 +44,10 @@ server { ssl_stapling on; ssl_stapling_verify on; +{% if acme_enabled %} # Verify chain of trust of OCSP response using Root CA and Intermediate certs ssl_trusted_certificate {{ mumble_web_trusted_certificate }}; +{% endif %} location / { root /var/www/mumble-web/dist/; diff --git a/vars/main.yml b/vars/main.yml new file mode 100644 index 0000000..3dcd39e --- /dev/null +++ b/vars/main.yml @@ -0,0 +1,2 @@ +murmur_ice_script_dir: /opt/mice +murmur_ice_config_path: /etc/mice.yml \ No newline at end of file