From 921f2098ce3afbea4f4b59a8d20b195f150b31ca Mon Sep 17 00:00:00 2001 From: norohind <60548839+norohind@users.noreply.github.com> Date: Fri, 4 Apr 2025 21:37:45 +0000 Subject: [PATCH] collection? --- README.md | 111 +----------------- galaxy.yml | 69 +++++++++++ meta/runtime.yml | 52 ++++++++ plugins/README.md | 31 +++++ roles/configure/README.md | 110 +++++++++++++++++ .../configure/defaults}/main.yml | 0 .../configure/handlers}/main.yml | 0 {meta => roles/configure/meta}/main.yml | 0 {tasks => roles/configure/tasks}/main.yml | 0 {tests => roles/configure/tests}/inventory | 0 {tests => roles/configure/tests}/test.yml | 0 {vars => roles/configure/vars}/main.yml | 0 12 files changed, 264 insertions(+), 109 deletions(-) create mode 100644 galaxy.yml create mode 100644 meta/runtime.yml create mode 100644 plugins/README.md create mode 100644 roles/configure/README.md rename {defaults => roles/configure/defaults}/main.yml (100%) rename {handlers => roles/configure/handlers}/main.yml (100%) rename {meta => roles/configure/meta}/main.yml (100%) rename {tasks => roles/configure/tasks}/main.yml (100%) rename {tests => roles/configure/tests}/inventory (100%) rename {tests => roles/configure/tests}/test.yml (100%) rename {vars => roles/configure/vars}/main.yml (100%) diff --git a/README.md b/README.md index 2001710..9ff8066 100644 --- a/README.md +++ b/README.md @@ -1,110 +1,3 @@ -Nebula role -========= +# Ansible Collection - norohind.nebula -A role to install and manage [nebula vpn](https://nebula.defined.net/) nodes. It automatically detects and eliminates drift between -what groups, host name, ip address set in inventory and what actually is present in -host's cert. When it reissues a cert due to drift, the role will show you what exactly have drifted away. -The role generates nebula and embedded sshd private keys on remote hosts and never copies them off hosts. - -It doesn't support password encrypted CAs. - -It does not generate configs for you, you are expected to create hosts configs before running the role. It expects -to find config for every host in `configs/{{ inventory_hostname }}.yaml`. - -It does use provided by a distro nebula package. Tested on Fedora 40, Debian 12 and Void Linux. On Void -nebula package was manually installed prior to running the role. - -Requirements ------------- - -You have to manually generate CA before using this role. One of options is -```shell -nebula-cert ca -name "My CA for nebula" -``` - -It will place files ca.crt and ca.key in the current directory. - -Role Variables --------------- - -| Parameter name | Default value | Description | -|---------------------|---------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| ca_fingerprint | | You must provide a value. It is used to conditionally reissue certificate for a host during CA rotation. You can get fingerprint for your CA using `nebula-cert print -path ca.crt -json \| jq .fingerprint -r` | -| days_left_threshold | 30 | Amount of days before host cert expires the role will attempt to reissue cert for the host. It doesn't make much sense if you don't limit duration of host certs, because then nebula issues certificates with the same expiration time as CA. | -| config_prefix | /etc/nebula | Directory where on the host configuration for nebula should be located | -| ca_crt | ca.crt | Location of ca.crt on localhost | -| ca_key | ca.key | Location of ca.key on localhost | -| ct_log_file | ct.log | Location of file where the role will write information about all issued certificates. It can be helpful if you would need to blacklist a host certificate before it expires. Then you can just look into this look, find line of appropriate certificates, extract its fingerprint and put it into `pki.blocklist` | -| nebula_service_name | nebula | On some systems you might want to adjust service name. On fedora by default it's `nebula`. On debian it would be `nebula@config` | -| service_manager | systemd | Supports systemd and runit (Void Linux). Influences how nebula service will be enabled and reloaded | -| duration | | Value for argument `-duration`, directly passed to `nebula-cert`. If no value is supplied, it gets omitted from `nebula-cert` command, and `nebula-cert` defaults to CA expiration time. | -| pub_dir | pubkeys | Directory where role will copy nebula public keys from remote hosts to issue certificates, it doesn't remove these pub keys, so you could put some automation around it to track changes of keypairs on existing hosts | -| configs_dir | configs | Directory where role will get configs for hosts. I.e. for host "server-a" it will grab `"{{ configs_dir }}/server-a.yaml` file as config. | -| do_reissue | false | It's mostly internal variable, you can override it with value `true` if you need unconditionally reissue certificate. It is defined in vars/main.yaml, meaning it has higher position in precedence order, thus you can't overwrite it as easy as other variables. Please reference [variable precedence](https://docs.ansible.com/ansible/latest/playbook_guide/playbooks_variables.html#variable-precedence-where-should-i-put-a-variable) | -| nebula_addr | | You must provide a value. It's a host level var, you should set this in inventory. Example: `10.1.0.1/24` | -| nebula_groups | | It's a host level var, you should set this in inventory. Example: `servers,vps`. You might leave it empty | - - -Dependencies ------------- - -`community.general.runit` is used for service reload on runit systems. - -Example Playbook ----------------- - - --- - - name: Setup nodes - hosts: linux - strategy: free - gather_facts: false # The role don't need facts - roles: - - role: nebula - ca_fingerprint: abaecbeac8e8fa98bde42a2c4ccff1dcc9a07b7c3392396aaa4039fb6ed570ee # Acquired with `nebula-cert print -path ca.crt -json | jq` - # ct_log_file: /dev/null # If you don't need ct.log - # do_reissue: true # If you need to unconditionally reissue certificate - # nebula_service_name: nebula@config # For debian systems name of the service might actually be nebula@config - -Inventory example, sorry, only JSON. - -```json5 -{ - "linux": { - "hosts": { - "myserver-1.internal": { // inventory_hostname is used as name in nebula certificates and to locate configs - "ansible_host": "my-server-1-ssh", // You might have this host configured under another name in your ssh config - "nebula_addr": "10.1.0.1/24", - "nebula_groups": "server,vps,region:eu" - }, - "laptop": { - "nebula_addr": "10.1.0.2/24", - "nebula_groups": "laptop", - "service_manager": "runit" // This laptop is running Void Linux - }, - } - } -} -``` - -CA Rotation ------------ - -There are [official guide](https://nebula.defined.net/docs/guides/rotating-certificate-authority/) about this. Please read it first. - -1. Generate new CA. -2. Append new CA cert to trust bundle in configs, as described in official guide. Configs generation is out of scope for this project. -3. Apply role with new configs. -4. Update variable `ca_crt` and `ca_key` to reference new CA. -5. Apply role. It will detect mismatch in CA fingerprint and reissue certificates. You might want to start by applying role on one host first to check it works as expected. -6. Remove old CA cert from trust bundle in configs. -7. Apply role with new configs. - -License -------- - -BSD-3-Clause - -Author Information ------------------- - -60548839+norohind@users.noreply.github.com \ No newline at end of file +Documentation for the collection. diff --git a/galaxy.yml b/galaxy.yml new file mode 100644 index 0000000..5c4e89d --- /dev/null +++ b/galaxy.yml @@ -0,0 +1,69 @@ +### REQUIRED +# The namespace of the collection. This can be a company/brand/organization or product namespace under which all +# content lives. May only contain alphanumeric lowercase characters and underscores. Namespaces cannot start with +# underscores or numbers and cannot contain consecutive underscores +namespace: norohind + +# The name of the collection. Has the same character restrictions as 'namespace' +name: nebula + +# The version of the collection. Must be compatible with semantic versioning +version: 1.0.0 + +# The path to the Markdown (.md) readme file. This path is relative to the root of the collection +readme: README.md + +# A list of the collection's content authors. Can be just the name or in the format 'Full Name (url) +# @nicks:irc/im.site#channel' +authors: +- 60548839+norohind@users.noreply.github.com + + +### OPTIONAL but strongly recommended +# A short summary description of the collection +description: your collection description + +# Either a single license or a list of licenses for content inside of a collection. Ansible Galaxy currently only +# accepts L(SPDX,https://spdx.org/licenses/) licenses. This key is mutually exclusive with 'license_file' +license: +- BSD-3-Clause + +# The path to the license file for the collection. This path is relative to the root of the collection. This key is +# mutually exclusive with 'license' +license_file: '' + +# A list of tags you want to associate with the collection for indexing/searching. A tag name has the same character +# requirements as 'namespace' and 'name' +tags: [] + +# Collections that this collection requires to be installed for it to be usable. The key of the dict is the +# collection label 'namespace.name'. The value is a version range +# L(specifiers,https://python-semanticversion.readthedocs.io/en/latest/#requirement-specification). Multiple version +# range specifiers can be set and are separated by ',' +dependencies: {} + +# The URL of the originating SCM repository +repository: https://github.com/norohind/norohind.nebula.git + +# The URL to any online docs +documentation: https://github.com/norohind/norohind.nebula.git + +# The URL to the homepage of the collection/project +homepage: https://github.com/norohind/norohind.nebula.git + +# The URL to the collection issue tracker +issues: https://github.com/norohind/norohind.nebula.git/issues + +# A list of file glob-like patterns used to filter any files or directories that should not be included in the build +# artifact. A pattern is matched from the relative path of the file or directory of the collection directory. This +# uses 'fnmatch' to match the files or directories. Some directories and files like 'galaxy.yml', '*.pyc', '*.retry', +# and '.git' are always filtered. Mutually exclusive with 'manifest' +build_ignore: [] + +# A dict controlling use of manifest directives used in building the collection artifact. The key 'directives' is a +# list of MANIFEST.in style +# L(directives,https://packaging.python.org/en/latest/guides/using-manifest-in/#manifest-in-commands). The key +# 'omit_default_directives' is a boolean that controls whether the default directives are used. Mutually exclusive +# with 'build_ignore' +# manifest: null + diff --git a/meta/runtime.yml b/meta/runtime.yml new file mode 100644 index 0000000..20f709e --- /dev/null +++ b/meta/runtime.yml @@ -0,0 +1,52 @@ +--- +# Collections must specify a minimum required ansible version to upload +# to galaxy +# requires_ansible: '>=2.9.10' + +# Content that Ansible needs to load from another location or that has +# been deprecated/removed +# plugin_routing: +# action: +# redirected_plugin_name: +# redirect: ns.col.new_location +# deprecated_plugin_name: +# deprecation: +# removal_version: "4.0.0" +# warning_text: | +# See the porting guide on how to update your playbook to +# use ns.col.another_plugin instead. +# removed_plugin_name: +# tombstone: +# removal_version: "2.0.0" +# warning_text: | +# See the porting guide on how to update your playbook to +# use ns.col.another_plugin instead. +# become: +# cache: +# callback: +# cliconf: +# connection: +# doc_fragments: +# filter: +# httpapi: +# inventory: +# lookup: +# module_utils: +# modules: +# netconf: +# shell: +# strategy: +# terminal: +# test: +# vars: + +# Python import statements that Ansible needs to load from another location +# import_redirection: +# ansible_collections.ns.col.plugins.module_utils.old_location: +# redirect: ansible_collections.ns.col.plugins.module_utils.new_location + +# Groups of actions/modules that take a common set of options +# action_groups: +# group_name: +# - module1 +# - module2 diff --git a/plugins/README.md b/plugins/README.md new file mode 100644 index 0000000..67a66d4 --- /dev/null +++ b/plugins/README.md @@ -0,0 +1,31 @@ +# Collections Plugins Directory + +This directory can be used to ship various plugins inside an Ansible collection. Each plugin is placed in a folder that +is named after the type of plugin it is in. It can also include the `module_utils` and `modules` directory that +would contain module utils and modules respectively. + +Here is an example directory of the majority of plugins currently supported by Ansible: + +``` +└── plugins + ├── action + ├── become + ├── cache + ├── callback + ├── cliconf + ├── connection + ├── filter + ├── httpapi + ├── inventory + ├── lookup + ├── module_utils + ├── modules + ├── netconf + ├── shell + ├── strategy + ├── terminal + ├── test + └── vars +``` + +A full list of plugin types can be found at [Working With Plugins](https://docs.ansible.com/ansible-core/2.16/plugins/plugins.html). diff --git a/roles/configure/README.md b/roles/configure/README.md new file mode 100644 index 0000000..2001710 --- /dev/null +++ b/roles/configure/README.md @@ -0,0 +1,110 @@ +Nebula role +========= + +A role to install and manage [nebula vpn](https://nebula.defined.net/) nodes. It automatically detects and eliminates drift between +what groups, host name, ip address set in inventory and what actually is present in +host's cert. When it reissues a cert due to drift, the role will show you what exactly have drifted away. +The role generates nebula and embedded sshd private keys on remote hosts and never copies them off hosts. + +It doesn't support password encrypted CAs. + +It does not generate configs for you, you are expected to create hosts configs before running the role. It expects +to find config for every host in `configs/{{ inventory_hostname }}.yaml`. + +It does use provided by a distro nebula package. Tested on Fedora 40, Debian 12 and Void Linux. On Void +nebula package was manually installed prior to running the role. + +Requirements +------------ + +You have to manually generate CA before using this role. One of options is +```shell +nebula-cert ca -name "My CA for nebula" +``` + +It will place files ca.crt and ca.key in the current directory. + +Role Variables +-------------- + +| Parameter name | Default value | Description | +|---------------------|---------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| ca_fingerprint | | You must provide a value. It is used to conditionally reissue certificate for a host during CA rotation. You can get fingerprint for your CA using `nebula-cert print -path ca.crt -json \| jq .fingerprint -r` | +| days_left_threshold | 30 | Amount of days before host cert expires the role will attempt to reissue cert for the host. It doesn't make much sense if you don't limit duration of host certs, because then nebula issues certificates with the same expiration time as CA. | +| config_prefix | /etc/nebula | Directory where on the host configuration for nebula should be located | +| ca_crt | ca.crt | Location of ca.crt on localhost | +| ca_key | ca.key | Location of ca.key on localhost | +| ct_log_file | ct.log | Location of file where the role will write information about all issued certificates. It can be helpful if you would need to blacklist a host certificate before it expires. Then you can just look into this look, find line of appropriate certificates, extract its fingerprint and put it into `pki.blocklist` | +| nebula_service_name | nebula | On some systems you might want to adjust service name. On fedora by default it's `nebula`. On debian it would be `nebula@config` | +| service_manager | systemd | Supports systemd and runit (Void Linux). Influences how nebula service will be enabled and reloaded | +| duration | | Value for argument `-duration`, directly passed to `nebula-cert`. If no value is supplied, it gets omitted from `nebula-cert` command, and `nebula-cert` defaults to CA expiration time. | +| pub_dir | pubkeys | Directory where role will copy nebula public keys from remote hosts to issue certificates, it doesn't remove these pub keys, so you could put some automation around it to track changes of keypairs on existing hosts | +| configs_dir | configs | Directory where role will get configs for hosts. I.e. for host "server-a" it will grab `"{{ configs_dir }}/server-a.yaml` file as config. | +| do_reissue | false | It's mostly internal variable, you can override it with value `true` if you need unconditionally reissue certificate. It is defined in vars/main.yaml, meaning it has higher position in precedence order, thus you can't overwrite it as easy as other variables. Please reference [variable precedence](https://docs.ansible.com/ansible/latest/playbook_guide/playbooks_variables.html#variable-precedence-where-should-i-put-a-variable) | +| nebula_addr | | You must provide a value. It's a host level var, you should set this in inventory. Example: `10.1.0.1/24` | +| nebula_groups | | It's a host level var, you should set this in inventory. Example: `servers,vps`. You might leave it empty | + + +Dependencies +------------ + +`community.general.runit` is used for service reload on runit systems. + +Example Playbook +---------------- + + --- + - name: Setup nodes + hosts: linux + strategy: free + gather_facts: false # The role don't need facts + roles: + - role: nebula + ca_fingerprint: abaecbeac8e8fa98bde42a2c4ccff1dcc9a07b7c3392396aaa4039fb6ed570ee # Acquired with `nebula-cert print -path ca.crt -json | jq` + # ct_log_file: /dev/null # If you don't need ct.log + # do_reissue: true # If you need to unconditionally reissue certificate + # nebula_service_name: nebula@config # For debian systems name of the service might actually be nebula@config + +Inventory example, sorry, only JSON. + +```json5 +{ + "linux": { + "hosts": { + "myserver-1.internal": { // inventory_hostname is used as name in nebula certificates and to locate configs + "ansible_host": "my-server-1-ssh", // You might have this host configured under another name in your ssh config + "nebula_addr": "10.1.0.1/24", + "nebula_groups": "server,vps,region:eu" + }, + "laptop": { + "nebula_addr": "10.1.0.2/24", + "nebula_groups": "laptop", + "service_manager": "runit" // This laptop is running Void Linux + }, + } + } +} +``` + +CA Rotation +----------- + +There are [official guide](https://nebula.defined.net/docs/guides/rotating-certificate-authority/) about this. Please read it first. + +1. Generate new CA. +2. Append new CA cert to trust bundle in configs, as described in official guide. Configs generation is out of scope for this project. +3. Apply role with new configs. +4. Update variable `ca_crt` and `ca_key` to reference new CA. +5. Apply role. It will detect mismatch in CA fingerprint and reissue certificates. You might want to start by applying role on one host first to check it works as expected. +6. Remove old CA cert from trust bundle in configs. +7. Apply role with new configs. + +License +------- + +BSD-3-Clause + +Author Information +------------------ + +60548839+norohind@users.noreply.github.com \ No newline at end of file diff --git a/defaults/main.yml b/roles/configure/defaults/main.yml similarity index 100% rename from defaults/main.yml rename to roles/configure/defaults/main.yml diff --git a/handlers/main.yml b/roles/configure/handlers/main.yml similarity index 100% rename from handlers/main.yml rename to roles/configure/handlers/main.yml diff --git a/meta/main.yml b/roles/configure/meta/main.yml similarity index 100% rename from meta/main.yml rename to roles/configure/meta/main.yml diff --git a/tasks/main.yml b/roles/configure/tasks/main.yml similarity index 100% rename from tasks/main.yml rename to roles/configure/tasks/main.yml diff --git a/tests/inventory b/roles/configure/tests/inventory similarity index 100% rename from tests/inventory rename to roles/configure/tests/inventory diff --git a/tests/test.yml b/roles/configure/tests/test.yml similarity index 100% rename from tests/test.yml rename to roles/configure/tests/test.yml diff --git a/vars/main.yml b/roles/configure/vars/main.yml similarity index 100% rename from vars/main.yml rename to roles/configure/vars/main.yml