diff --git a/README b/README new file mode 100644 index 0000000000000000000000000000000000000000..82377d85edb09a0167ec91f058f9db8cd21d8058 --- /dev/null +++ b/README @@ -0,0 +1,42 @@ +Why is there 'openvpn' and 'openvpn-frontend'? + +The "openvpn-frontend" group is completely unrelated to float at all, its there +to solve an ansible problem: modules/roles can't export "facts" for other roles +to consume. So, how do you customize a role with data that belongs to something +else? + +In our situation, we want to customize DNS with openvpn-specific data. The +"openvpn" group, where the "openvpn" role runs, might not overlap with the hosts +where dns-servers are running. So we create a new role, and tell ansible to run +it on *top* of the *other* one. The "openvpn-frontend" thus has 'hosts: +frontend', which is the ansible group corresponding to the role that runs dns +servers. If ansible had a way to allow a group to indicate "we have this DNS +information, dns-role please consume it", then this would not be necessary. + +The problematic bit is that we want to schedule the openvpn-frontend role on the +'frontend' groups, and there is no openvpn-frontend service, so instead we are +customizing the 'frontend' role + +If you did not define any groups in hosts.yml, float would still work, it would +turn up nginx somewhere, backends somewhere else, and all work. But it turns +out, when deploying to the real-world, people do care where their frontends run, +as this is your public footprint. So float comes with the assumption that there +will be a "frontend" group in your invetory, and just relies on the fact that +the "frontend" role is scheduled there with num_instances=all + +Why is there a '[openvpn]' group, but no host attached to it? + +You might have noticed that site.yml has a hosts parameter with roles assigned +to them, and the actual hosts defined in site.yml are connected to the hosts.yml +groups parameter. The hosts.yml has floatrp1 with the groups: [frontend], but +there is no host which has the '[openvpn]' group attached to it. + +For the 'openvpn' service, there is a scheduling_group, which sets the *scope* +of the possible hosts that the service will be scheduled onto. Float will create +automatically a 'openvpn' group, containing just the hosts that 'openvpn' is +running on. We did not define an 'openvpn' group in the hosts.yml ansible +inventory, yet such a group is automatically created by float, and you can use +it in Ansible. This 'openvpn' group is a subset of the scheduling_group. + +"where can I run openvpn" -> scheduling_group (frontend) +"where is openvpn actually running" -> "openvpn" group diff --git a/credentials/openvpn/ca.pem b/credentials/openvpn/ca.pem new file mode 100644 index 0000000000000000000000000000000000000000..981acae529fec322e335132042c8e6ba09e07c95 --- /dev/null +++ b/credentials/openvpn/ca.pem @@ -0,0 +1,9 @@ +-----BEGIN CERTIFICATE----- +MIIBRzCB76ADAgECAgEBMAoGCCqGSM49BAMCMBoxGDAWBgNVBAMTD0xFQVAgT3Bl +blZQTiBDQTAeFw0xOTExMjYxODM3MjRaFw0yNDExMjYxODQyMjRaMBoxGDAWBgNV +BAMTD0xFQVAgT3BlblZQTiBDQTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABDsZ +vt/ZWnMv1k4CVrjnmPMbuP5g3449mYGhxjfTJU5ppLxYvnynlbLNX9vkCSLVP24s +bsPVVBvan9upMM9ErTWjJjAkMA4GA1UdDwEB/wQEAwICpDASBgNVHRMBAf8ECDAG +AQH/AgEBMAoGCCqGSM49BAMCA0cAMEQCIHps/xJhxnfoFhcbvX1Kzwtq2MVOdO1m +J2qGeNlQZKw8AiAPVCyN1OClfOIE5kk2xehYHkt3x0V/5BMBnyjMqygk4g== +-----END CERTIFICATE----- diff --git a/credentials/openvpn/ca_private_key.pem b/credentials/openvpn/ca_private_key.pem new file mode 100644 index 0000000000000000000000000000000000000000..813784d8095aadc5deaf109baca95069ab9f0e8d --- /dev/null +++ b/credentials/openvpn/ca_private_key.pem @@ -0,0 +1,17 @@ +$ANSIBLE_VAULT;1.1;AES256 +36333864326664323936323837653065623132653437643362313965336235613061636136313032 +3932353263643534323038373365303065343361323566330a353164613361396461663764313863 +30343464356262323838643130343864623735346436333566383838323139313631343665363762 +3030663031396464360a373461363437336333633639623061623035613432333531646236313332 +62316662643764623465333833306439323239316563613832346231396133393563383862323831 +32306534313562643162633162383963303133396236623763396664386133633430336534336533 +66366630323565623261313835333230346634343861386561353764316135613932653261343436 +61383836396435343130386565303934376530333162666334373662646333336234366263383530 +38376639386164363236376239326566336433306339323165633734353064356363613931376239 +39626135646232373133326630323839613836626135643864333939386537333536356566633039 +66323238336661373935623636633036346339636435653839393436316238643437306632303061 +37643163373732656631333231626132333161623131343135373365383461613366643437323839 +38323537393836666661336532363735313731343461333139633730343339353635353534363936 +38353934316436303162346432623635363261346662346461633136663764303866333964366164 +35316139343436393165396438353434363234366335343432633366343438653666646661663037 +66323932303639326130 diff --git a/credentials/openvpn/cert.pem b/credentials/openvpn/cert.pem new file mode 100644 index 0000000000000000000000000000000000000000..f35df1f48cd2c5d4c02a9fda2229ce1ecf3dd49d --- /dev/null +++ b/credentials/openvpn/cert.pem @@ -0,0 +1,11 @@ +-----BEGIN CERTIFICATE----- +MIIBjTCCATSgAwIBAgIQJvT0h0fXj84tWGMJgQt2/TAKBggqhkjOPQQDAjAaMRgw +FgYDVQQDEw9MRUFQIE9wZW5WUE4gQ0EwHhcNMTkxMTI2MTg0NTA4WhcNMjAxMTI1 +MTg1MDA4WjASMRAwDgYDVQQDEwdvcGVudnBuMFkwEwYHKoZIzj0CAQYIKoZIzj0D +AQcDQgAEc3k3ReiazyfDb7LW7hfuZuq2eQg1jR+aCQiUkALh9y5UP+ipwxPeKHXX +j63r1wgQXsLFqFvqz0+07BpagxmdR6NkMGIwDgYDVR0PAQH/BAQDAgWgMBMGA1Ud +JQQMMAoGCCsGAQUFBwMBMAwGA1UdEwEB/wQCMAAwLQYDVR0RBCYwJIIHb3BlbnZw +boIZb3BlbnZwbi5mbG9hdC5iaXRtYXNrLm5ldDAKBggqhkjOPQQDAgNHADBEAiB1 +ND+gYgaEvVCOQDI7Kv1HohPDTZyLpC9XjjQvNSjBwQIgCjm7VXk9xTKWgJILZwmF +Biv29JcWa1DdrrabzcsWsVY= +-----END CERTIFICATE----- diff --git a/credentials/openvpn/private_key.pem b/credentials/openvpn/private_key.pem new file mode 100644 index 0000000000000000000000000000000000000000..d412350dc05eda0c27a9f8badb767e9a61770463 --- /dev/null +++ b/credentials/openvpn/private_key.pem @@ -0,0 +1,5 @@ +-----BEGIN EC PRIVATE KEY----- +MHcCAQEEIEFIgj1NJg2NxYAAFDdTyr4wYHu3UIXv3ZGb7MmDA5mFoAoGCCqGSM49 +AwEHoUQDQgAEc3k3ReiazyfDb7LW7hfuZuq2eQg1jR+aCQiUkALh9y5UP+ipwxPe +KHXXj63r1wgQXsLFqFvqz0+07BpagxmdRw== +-----END EC PRIVATE KEY----- diff --git a/float/.gitrepo b/float/.gitrepo index 18d3cc786c5fa5507e9412996c599a9bda6e60c0..b8e8c7e91f79be2ded68e6f2558d711f3fcbc243 100644 --- a/float/.gitrepo +++ b/float/.gitrepo @@ -6,6 +6,6 @@ [subrepo] remote = https://git.autistici.org/ai3/float.git branch = master - commit = 15ed5137e3c5b89fe12e3e795706bb0aea721f74 - parent = 7f945908bfbe3622e842386de8686672dac44aa5 + commit = efc7d0d3b82fbd88d781c00ce4ed11d71ca5110e + parent = f8e1df3360fc09f41a0b8eec30769950e05a6677 cmdver = 0.3.1 diff --git a/float/README.it.md b/float/README.it.md index ca15f95d13342cb92a72f6ea37de3d1d544c0716..7da998ed04a168dd3647d837380fbcbdc93500ef 100644 --- a/float/README.it.md +++ b/float/README.it.md @@ -16,7 +16,7 @@ a qualcosa di più sofisticato, come Kubernetes. # Funzionalità Alcune di queste, specialmente se comparate con soluzioni più sofisticate, -come Kubernetes, sono anti-caratteristiche: +come Kubernetes, sono non-funzionalità: * *allocazione statica dei servizi* - lo scheduler del servizio non migra i container a runtime in caso di fallimento di un host, tutti @@ -45,10 +45,9 @@ riusciti... # Obiettivo -Dovrebbe essere chiaro dalla lista delle "Funzionalità" qui sopra: -questo sistema non offre *high availability* senza adoperare alcune -accortezze nei servizi stessi. La principale limitazione, rispetto a -sistemi più evoluti, è il requisito di un'operazione manuale in caso +Dovrebbe essere chiaro dalla lista delle "funzionalità" qui sopra: +questo sistema non punta ad offrire *alta disponibilità*, ma ad avere qualche automazione nei servizi stessi. +La principale limitazione, rispetto asistemi più evoluti, è il requisito di un'operazione manuale in caso di cambiamenti dell'ambiente ad alto livello (fallimento di macchine, cambiamenti nella richiesta/offerta): per fare un esempio, se avete configurato un servizio con un'unica istanza su un server che poi si @@ -70,21 +69,22 @@ Una documentazione più dettagliata è disponible nella sottocartella ### Documentazione generale -* [Guida di partenza](docs/quickstart.md) -* [Guida all'integrazione con Ansible](docs/ansible.md) -* [Configurationi](docs/configuration.md) -* [Protocollo Service discovery](docs/service_mesh.md) -* [HTTP router](docs/http_router.md) -* [Usare Docker](roles/docker/README.md) -* [Usare gli strumenti da CLI](docs/cli.md) -* [Sperimentazione](docs/testing.md) +* [Guida di partenza rapida](docs/quickstart.it.md) +* [Guida all'integrazione con Ansible](docs/ansible.it.md) +* [Note sull'uso in produzione](docs/running.it.md) +* [Riferimenti per le configurazioni](docs/configuration.it.md) +* [Protocollo Service discovery](docs/service_mesh.it.md) +* [HTTP router](docs/http_router.it.md) +* [Usare Docker](roles/docker/README.it.md) +* [Usare gli strumenti da CLI](docs/cli.it.md) +* [Sperimentazione](docs/testing.it.md) ### Documentazione dei servizi integrati -* [Monitoring and alerting](roles/prometheus/README.md) -* [Gestione ed analisi dei Log](roles/log-collector/README.md) -* [DNS pubblici e autoritativi](roles/dns/README.md) -* [Gestione dell'identità e delle autorizzazioni](docs/identity_management.md) +* [Monitoring and alerting](roles/prometheus/README.it.md) +* [Gestione ed analisi dei Log](roles/log-collector/README.it.md) +* [DNS pubblici e autoritativi](roles/dns/README.it.md) +* [Gestione dell'identità e delle autorizzazioni](docs/identity_management.it.md) I servizi integrati sono implementati con ruoli Ansible, e non sono necessariamente eseguiti dentro container. Ma questo è giusto un @@ -111,5 +111,5 @@ Nonostante non siano dei requisiti obbligatori, probabilmente vorrete usare alcuni servizi esterni che non sono forniti da *float* stesso: * git repository hosting -* un sistema di CI per creare in proprio le immagini dei container +* un sistema di CI (continuos integration) per creare in proprio le immagini dei container * un registro Docker diff --git a/float/README.md b/float/README.md index 95fb03f85496915a10e7dbc1cd9bd3e5615a72bf..5f68cb30e44657834528b61c13f08f8366770bec 100644 --- a/float/README.md +++ b/float/README.md @@ -42,7 +42,7 @@ those accounts. # Target It should be clear from the list of "features" above, but this system -isn't meant to provide high availability without some smartness in the +isn't meant to provide *high availability* without some smartness in the services themselves. Its major limitation is the requirement for manual operator action in face of high-level environmental changes (loss of machines, changes in load/demand), so for instance it won't diff --git a/float/docs/ansible.it.md b/float/docs/ansible.it.md new file mode 100644 index 0000000000000000000000000000000000000000..ae59f6533f4c25d26693161d20851c5593a60a89 --- /dev/null +++ b/float/docs/ansible.it.md @@ -0,0 +1,398 @@ +Ansible +======= + +Questo documento descrive come l'infrastruttura usa Ansible, +come lo si usa da utente, come lo si estende come servizio in sviluppo. + + +# Caratteristiche + +Il nostro kit di strumenti è implementato come set di pkugin e ruoli Ansible, +significa che è integrato nelle tue proprie configurazioni Ansible. Questi +plugin e ruoli aumentano le funzionalità di Ansible in diversi modi utili: + +### GPG integrata + +Puoi usare GPG per cifrate i file contenenti le configurazioni dei gruppi e gli host. +Such files must have a `.yml.gpg` extension. Ansible will +then decrypt them at runtime (use of gpg-agent is advised). Same holds +for the *ansible-vault* password file. + +Useful when you're using GPG to manage the root trust repository. + +This functionality is implemented by the +[gpg_vars](../plugins/vars/gpg_vars.py) plugin. + +### Automated credentials management + +Service-level credentials are automatically managed by the toolkit and +are encrypted with *ansible-vault*. This includes an internal X509 PKI +for TLS service authentication, a SSH PKI for hosts, and application +credentials defined in *passwords.yml*. See the *Credentials* section +of the [configuration reference](configuration.md) for specific +details. + +All autogenerated credentials are stored in the *credentials_dir* +specified in the top-level Float configuration, which will usually +point at a separate git repository (or a temporary directory for test +environments). + +### Integration of services and Ansible roles + +The toolkit defines Ansible host groups for each service, to make it +easy to customize services with Ansible roles. For instance, suppose +that a service *foo* is defined in *services.yml*, and you have +created your own *foo* Ansible role to go with it (with some +foo-specific host setup). You can then tie the two together in your +playbook by making use of the *foo* host group: + +```yaml +- hosts: foo + roles: + - foo +``` + + +# Usage + +The toolkit lets you define container-based services, which may not +require any configuration beyond the service definition, as well as +Ansible-based services. It does so primarily by taking over the +Ansible inventory (see the [configuration reference](configuration.md) +for details). It is expected that you will have your own Ansible +configuration, with service-specific roles and configuration, +extending the base roles and making use of the toolkit's features. + +So, to use the toolkit, you will have to include it from your own +Ansible configuration, and specifying the inventory and service +configuration in our own format. + +There are some (minimal) requirements on how your Ansible environment +should be set up for this to work: + +* you must have a *group_vars/all* directory (this is where we'll + write the autogenerated application credentials) +* you must include *playbooks/all.yml* from the toolkit source + directory at the beginning of your playbook +* you must use the *float* wrapper instead of running + *ansible-playbook* directly (it helps setting up the + command line) + +## Ansible environment setup how-to + +Let's walk through creating an example Ansible configuration for your +project. + +First, check out the base *float* repository somewhere. We'll store +everything related to this project below a top-level directory called +*~/myproject*. We'll put the *float* repository in the *float* +subdirectory. + +```shell +$ mkdir ~/myproject +$ git clone ... ~/myproject/float +``` + +Let's create the directory with our own Ansible configuration in the +*ansible* subdirectory: + +```shell +$ mkdir ~/myproject/ansible +``` + +And put a top-level Ansible configuration file (*ansible.cfg*) in +there that refers to the toolkit repository location: + +```shell +$ cat >ansible.cfg <<EOF +[defaults] +roles_path = ../float/roles:./roles +inventory_plugins = ../float/plugins/inventory +action_plugins = ../float/plugins/action +vars_plugins = ../float/plugins/vars +force_handlers = True + +[inventory] +enable_plugins = float +EOF +``` + +This will look for plugins and base roles in *~/myproject/float*, and +it will load our own Ansible roles and config from +*~/myproject/ansible*. + +The *force_handlers* option is important because *float* controls +system status via handlers, and they should run even in case of +errors. + +We're going to need a place to store global configuration, *float* +requires to have a *group_vars/all* directory anyway, so we can use +that and put some global variables in +*~/myproject/ansible/group_vars/all/config.yml*: + +```shell +$ mkdir -p ~/myproject/ansible/group_vars/all +$ cat > ~/myproject/ansible/group_vars/all/config.yml <<EOF +--- +domain: internal.myproject.org +domain_public: + - myproject.org +EOF +``` + +Then you can create the main configuration file (*float.yml*), the +host inventory (*hosts.yml*), and the service definition +(*services.yml*). Check out the [configuration +reference](configuration.md) for details. + +Finally, we are going to set up a basic playbook in *site.yml* that +will just run all the playbooks in the main repository: + +```yaml +--- +- import_playbook: ../float/playbooks/all.yml +``` + +Now you can create your own service-specific Ansible configuration and +roles based on this skeleton. + +## Running playbooks + +The *float* wrapper makes some necessary fixes to the +environment and invokes *ansible-playbook* with the same command-line +arguments, so you should use its *run* command whenever you would use +*ansible-playbook*. + +The ansible-vault setup is mandatory, so you are going to have to pass +the location of the ansible-vault encryption passphrase to Ansible via +the environment. Just use the `ANSIBLE_VAULT_PASSWORD_FILE` variable +as you normally would, with one additional feature: if the filename +ends in *.gpg*, the passphrase will be decrypted using GPG. + +With respect to the previous example: + +```shell +$ echo secret > vault-pw +$ ANSIBLE_VAULT_PASSWORD_FILE=vault-pw \ + ../float/float run --config=config.yml site.yml +``` + +## Initialize the permanent credentials + +Before you can run Ansible to set up the services in your config, +there is one more step that needs to be done. In order to bootstrap +the internal PKIs, and generate the application credentials (which are +also valid forever, or until revoked of course), you need to invoke +the playbook in *playbooks/init-credentials.yml*: + +```shell +$ ANSIBLE_VAULT_PASSWORD_FILE=vault-pw \ + ../float/float run --config=config.yml init-credentials +``` + +This will write a bunch of files in your *credentials_dir*, including +the private keys for the various PKIs (X509, SSH, etc), and a +*secrets.yml* file containing the autogenerated application +credentials. + +These files are of course to be kept private when setting up a +production environment. + + +## Credentials + +The system uses two major types of credentials: + +* *managed* credentials for accounts, services, etc - these can be + autogenerated automatically, based on the top-level description in + *passwords.yml*. They are stored encrypted with *ansible-vault*. +* *root* credentials, that need to be provided externally, including + for instance the *ansible-vault* password used for managed + credentials, and other third-party secrets (like credentials for a + private Docker registry, etc). + +All services that require per-host secrets, such as SSH and the +internal X509 PKI, manage those secrets directly on the hosts +themselves, renewing them when necessary. Those secrets are not stored +in a central location. + +This means that for normal usage (i.e. except when new credentials are +added), the credentials repository is read-only, which makes it easier +to integrate deployment with CI systems. + +The expectation is that, for production environments, it will be saved +in private git repositories. Temporary setups such as test +environments, on the other hand, need no persistence as managed +credentials can simply be re-created every time. + + +# Implementation details + +These are details of how parts of the infrastructure is implemented, +useful if you want to understand how a service is deployed, or how to +write a new one. + +## Scheduler + +The *float* service scheduler is implemented as an Ansible dynamic +inventory plugin. All it does is set a number of global and host +variables for the float Ansible roles to use. It's useful to know what +these are, in case you want to write your own Ansible roles. + +### Variables + +The *float* service scheduler sets a large number of host variables +and global configuration parameters. + +The following global variables are defined: + +* `services` holds all the service metadata, in a dictionary indexed + by service name; + +Other variables are defined in *hostvars* and are different on every +host depending on the service assignments: + +* `float_enable_<service>` for each service that evaluates to + true on the hosts assigned to the service (note: dashes in the + service name are converted to underscores) +* `float_instance_index_<service>` is the progressive index of the + current instance of the service (0-based). +* `float_enabled_services` contains the list of enabled services on this + host +* `float_disabled_services` contains the list of disabled services on + this host +* `float_enabled_containers` contains a list of dictionaries describing the + containers that should be active on this host. The dictionaries have + the following attributes: + * `service` is the service metadata + * `container` is the container metadata +* `float_<service>_is_master` is true on the host where the master + instance is scheduled, and false elsewhere. This variable is only + defined for services using static master election (i.e. where + *master_election* is true in the service metadata) + +### Groups + +The scheduler also defines new dynamic Ansible groups based on +service assignments: + +* For each service, create a host group named after the service, whose + members are the hosts assigned to the service. +* For each network overlay defined in the inventory, create a host + group named `overlay-<name>` whose members are the hosts on that + overlay. + +Float depends on the *frontend* group being defined by the user. + +# Extending the built-in services + +Most configurable aspects of the built-in services (monitoring, HTTP +routing, etc) are parameterized in *services.yml*. But there are +limits to what makes sense to put there, to avoid unbounded complexity +in the service definition spec. Examples could be: + +* new monitoring configuration, such as probers or other custom config +* custom HTTP configuration +* custom DNS zones + +In those cases, the simplest solution is to create a separate Ansible +role to be run on the *frontend* nodes and install whatever +configuration files you need. Unfortunately, due to Ansible not having +"global" handlers, you are then going to have to provide your own +service reload handlers. + +The following chapter shows an example in detail: adding a custom DNS +zone to the built-in DNS service. + +## Custom DNS zone + +If you want to set up a custom DNS zone, one way to do so is with a +dedicated Ansible role (to be run on hosts in the *frontend* group) +that installs your desired zonetool configuration. + +Let's walk through a complete example: suppose we have a service +*myservice* that should serve HTTP requests for the *myservice.org* +domain. This doesn't match the *service_name*.*domain* scheme that is +expected for services described in *services.yml*, so float won't +automatically generate its DNS configuration. + +What we need to do is set up the *myservice.org* DNS zone ourselves, +and then tell float to associate that domain to the *myservice* +service. + +First, we create a new Ansible role that we are going to call +*myservice-dns*, so in the root of your Ansible config: + +```shell +$ mkdir -p roles/myservice-dns/{handlers,tasks,templates} +``` + +The only task in the role should install a zonetool DNS configuration +file into */etc/dns/manual*, so in +*roles/myservice-dns/tasks/main.yml* we'll have: + +```yaml +--- + +- name: Install myservice DNS configuration + template: + src: myservice.yml.j2 + dest: /etc/dns/manual/myservice.yml + notify: reload DNS +``` + +The DNS configuration in our case is very simple and just points "www" +and the top-level domain at the frontends. We do so by extending the +*@base* zone template defined by float. The contents of +*roles/myservice-dns/templates/myservice.yml.j2* should be: + +```yaml +--- + +myservice.org: + EXTENDS: "@base" + www: CNAME www.{{ domain_public[0] }}. +``` + +This points the www domain at the frontends via a CNAME (all the +*domain_public* DNS zones are already autogenerated by float). We +could have just as easily used A records but this is simpler and works +with both IPv4 and IPv6. + +Finally, we need a handler to reload the updated DNS configuration, +which goes in *roles/myservice-dns/handlers/main.yml* and runs a shell +command to update zonetool: + +```yaml +--- + +- listen: reload DNS + shell: "/usr/sbin/update-dns && rndc reload" +``` + +With the above we have a complete Ansible role that configures DNS for +the *myservice.org* domain. We need to tell Ansible that this role +needs to run on the hosts in the *frontend* group, so in your playbook +you should have: + +```yaml +- hosts: frontend + roles: + - myservice-dns +``` + +And to complete our configuration, the service description for +*myservice* should have a *public_endpoint* directive including the +domain, so that the float HTTP router knows where to send the +requests: + +```yaml +myservice: + ... + public_endpoints: + - name: myservice + domains: + - www.myservice.org + - myservice.org + port: ... +``` diff --git a/float/docs/quickstart.it.md b/float/docs/quickstart.it.md new file mode 100644 index 0000000000000000000000000000000000000000..b852583f8edc17551d028932f5717ddbe1a20461 --- /dev/null +++ b/float/docs/quickstart.it.md @@ -0,0 +1,209 @@ +Guida di partenza rapida +=== + +In questo documento useremo *float* con delle semplici configurazioni, +e per usarlo con un servizio HTTP basico su una macchina virtuale, usando +Vagrant e Virtualbox. + +Useremo come servizio di esempio +[docker/okserver](https://git.autistici.org/ai3/docker/okserver), un server HTTP davvero semplice, che risponde "OK" a tutte le richieste. + +## Passo 1: Dipendenze di installazione necessarie + +Avrai bisogno di una versione di ansible aggiornata +[Ansible](https://ansible.com) (>= 2.7), gli strumenti di gestione delle virtual machine (Vagrant and Virtualbox), ed altri piccoli strumenti personalizzati per gestire le credenziali, che ci andremo a costruire. + +Inoltre, se il tuo sistema operativo usa una differente versione di Python rispetto a quella che usa Ansible, come nel caso di Debian Buster (Python +2.7 è di default ma Ansible usa Python 3), ti toccherà installare alcuni pacchetti Python che dovrebbero essere normalmente già installati cone Ansible, come Jinja2 e PyYAML. + +L'ultima versione di Debian stable al momento (*bus git@git.autistici.org:ai3/float.gitter*) non pacchettizza più Virtualbox, quindi dovrai [scaricarlo e installarlo a mano](https://www.virtualbox.org/wiki/Linux_Downloads). Il resto delle dipendenze possono essere installate con questo comando: + +```shell +sudo apt install golang bind9utils ansible vagrant python-jinja2 python-yaml python-six +go get -u git.autistici.org/ale/x509ca +go get -u git.autistici.org/ale/ed25519gen +export PATH=$PATH:$HOME/go/bin +``` + +*Float* dovrebbe lavorare ugualmente bene sia con Python 2 che con Python 3, e supportare lo scenario in cui l'interprete Python usato da Ansible è differente da quello che il sistema operativo usa di default. + +### Alternativa: libvirt + +Se davvero non ti piace Virtualbox e non vuoi installarlo manualmente, c'è l'opzione di utilizzare al suo posto *libvirt*. In Debian, +installa i seguento pacchetti per configurare un ambiente locale per libvirt: + +```shell +sudo apt install libvirt-clients libvirt-daemon-system vagrant-libvirt +``` + +Where specific steps need to be performed for libvirt versus +virtualbox, this will be called out in the text with a *\[libvirt\]* +tag. + +## Passo 2: Configurare un nuovo ambiente + +Un *ambiente* è soltanto un nome per una specifica configurazione di hosts e servizi: per convenienza, poichè è fatto da un consistente numero di file di configurazioni, lo andremo a mettere in una cartella che porta il suo nome. + +Assumiamo che hai scaricato il codice di *float* in `$HOME/float`, +e che vogliamo creare le configurazioni per fare andare il nostro ambiente di test nella cartella `$HOME/float-test`. Possiamo creare le configurazioni del nostro ambiente di test usando il comando `float` dal teminale (CLI): + +```shell +$HOME/float/float create-env \ + --domain=example.com --net=192.168.10.0 \ + --vagrant --num-hosts=1 \ + $HOME/float-test +``` + +> \[libvirt\] Aggiunti nella riga di comando l'opzione +> *--libvirt=localhost* +> nell'invocazione del comando sopra "float create-env". + +Il comando *create-env* creerà un gruppo di file di configurazioni nella cartella *float-test*. Qui noi gli abbiam detto di usare *example.com* come dominio di base per i nostri servizi pubblici e interni, e per generare una configurazione host basata su Vagrant di una singola VM, +usando il network privato 192.168.10.0/24 (usato dalle VMs per parlare una con l'altra). Al *create-env* l'automazione di Vagrant assegnerà gli IPs a quel network di VMs, iniziando dal numero 10, quindi la tua VM di test avrà l'indirizzo 192.168.10.10. + +La cartella *float-test* dovrebbe ora contenere vari file di configurazioni per Ansible e Vagrant, *create-env* li ha riempiti con i valori di default. Andiamo a dare una occhiata più da vicino che cosa sono: + +* `ansible.cfg` è il file di configurazioni di Ansible, che dice ad Ansible + dove trovare il plugin float +* `Vagrantfile` è il file di configurazione di Vagrant che descrive la nostra singola VM (SO, ip, memory,...). +* `config.yml` è il principale file di configurazione di *float* che principalmente solo punta al luogo delle altre configurazioni. Non c'è niente da cambiare qui, *create-env* ha già scritto dei default ragionevoli. +* `hosts.yml` è il file inventario di Ansible(in formato YAML come richiesto da *float*), che già contiene le nostre VM di test. +* `passwords.yml` descrive le credenziali dell'applicazione per i ruoli di Ansible, ma non lo stiamo usando quindi puoi lasciarlo intonso. +* `services.yml` contiene la descrizione dei servizi che vogliamo far andare (nessuno per il momento). +* `site.yml` è il nostro top-level dell'Ansible playbook. +* `group_vars/all/config.yml` contine le configurazini globali di Ansible, + incluse le credentiali per gli utenti amministrativi (operators): + *create-env* automaticamente genera di default un utente *admin*, con + password *password*. + +Si può leggere [Riferimenti per le configurazioni](configuration.it.md) per la sintassi dei file di configurazioni e cosa significa le varie opzioni. + +Questa cartella è anche la tua cartella top-level di Ansible top-level, così è possibile aggiungere host_vars, group_vars, etc. come vuoi. Noi non andiamo a vedere e non ci servono quelle funzionalità per questo esempio. + +## Passo 3: Personalizzare l'ambiente + +Vogliamo dire a *float* di far andare una istanza del nostro semplice servizio HTTP dietro al suo HTTP router pubblico, ed averlo disponibile a +*ok.example.com*. Il servizio è disponibile come immagine Docker con il nome *registry.git.autistici.org/ai3/docker/okserver*. + +Andiamo ad aggiungere il servizio specifico al file *services.yml* che è stato automaticamente creato dentro alla cartella *float-test*. Dato che tutti i servizi in float hanno le porte assegnate staticamente, andiamo a prendere la porta 3100 (che sappiamo essere libera). Il file *services.yml* già contiene una sezione "include" che include la definizione per tutti i servizi integrati, quindi abbiamo bisogno di aggiungere solo un pezzettino alla fine del file YAML: + +```yaml +ok: + scheduling_group: all + num_instances: 1 + containers: + - name: http + image: registry.git.autistici.org/ai3/docker/okserver:master + port: 3100 + env: + PORT: 3100 + public_endpoints: + - name: ok + port: 3100 + scheme: http +``` + +Queste sono tutte le configurazioni che abbiam bisogno per impostare il servizio ed esportarlo tramite il router HTTP pubblico. + +## Passo 4: Inizializzare le credenziali + +Ora che le configurazioni sono pronte, abbiamo bisogno di inizializzare le credenziali a lungo termine come il PKI e la chiave di root dell'SSO, e le password dell'applicazione. Questo è uno step separato (che usa un playbook Ansible dedicato), come credenziali a lungo temine possono essere generate una volta e poi conservate per sempre. Questa separazione non è molto importante ora dato che stiamo lavorando in un ambiente di test, ma è utile per unificare i rispettivi workflow di test e di produzione. Per la stessa ragione, anche se non è strettamente necessario, stiamo andando ad usare Ansible Vault per cifrare le credenziali autogenerate. + +Primo, andiamo a mettre la passphrase di Ansible Vault e salviamola in un file. Puoi utilizzare anche GPG per cifrare questo file (ricordandoti di dargli come estensione `.gpg`), ma non stiamo facendolo così adesso: + +```shell +cd $HOME/float-test +echo -n passphrase > .ansible_vault_pw +export ANSIBLE_VAULT_PASSWORD_FILE=$PWD/.ansible_vault_pw +``` + +Puoi scegliere una qualunque passphrase, certo. La variabile di ambiente +*ANSIBLE_VAULT_PASSWORD_FILE* è ora impostata come abbiamo detto ad +Ansible che passphrase deve usare, e necessiteremo di impostarla tutte le volte che vogliamo invocare Ansible tramite *float*. + +Possiamo inizializzare le credenziali, che di default saranno archiviate dentro la cartella *float-test/credentials/* (il valore indicato in +*credentials_dir* in *config.yml*): + +```shell +cd $HOME/float-test +$HOME/float/float init-credentials --config=config.yml +``` + +Che avrà come risultato la creazione di un numero di file dentro a +*float-test/credentials/*, con i segreti cifrati con la tua passphrase di Ansible Vault. + +## Passo 5: Far partire Ansible + +Ora siam pronti a tirare su la VM di test e fa andare Ansible su di essa per configurare i nostri servizi: + +```shell +cd $HOME/float-test +vagrant up +$HOME/float/float run --config=config.yml site.yml +``` + +> \[libvirt\] dovresti mettere l'opzione *--provider=libvirt* al comando +> "vagrant up". + +Quando Ansible termina con successo (e ci metterò qualche minuto la prima volta, per scaricare i pacchetti e l'immagine Docker), la macchina virtuale di test è propriamente configurata per servirci il nostro servizio +*ok.example.com*! + +## Passo 6: Verificare che funzioni + +Il servizio ok.example.com dovrebbe essere servito dal router HTTP pubblico al nostro IP pubblico del nostro host di test, che Vagrant ha impostato come default che è +192.168.10.10: + +```shell +curl -k --resolve ok.example.com:443:192.168.10.10 https://ok.example.com +``` + +Se il comando risponde "OK", il servizio funziona come dovere. +Però, se ci sono problemi, potresti voler debuggare qualche cosa che è andato storto! Ci sono un buon numero di strumenti che potresti usare per farlo: + +Nell'ambiente di test, abbiamo impostato un SOCKS5 proxy sulla porta 9051 nel primo host del gruppo di *frontend* (quindi in questo caso, comunque +192.168.10.10). Questo è molto utile per simulare se la risoluzione dei DNS è appropriata e per navigare i servizi integrati senza complessi cambi all'ambiente dei tuoi host, puoi per esempio far partire un browser con: + +```shell +chromium --proxy-server=socks5://192.168.10.10:9051 +``` + +Una alternativa può essere aggiungere tutti i servizi integrati al tuo file */etc/hosts*, puntando a 192.168.10.10. + +Utili servizi integrati per debugging: + +* https://logs.example.com/ punta alla Kibana UI per il servizio di collettore di log centralizzato +* https://grafana.example.com/ è un pannello di monitoring +* https://monitor.example.com/ è l'interfaccia grafica minimale del sistema di monitoring di Prometheus + +Ovviamente puoi anche loggarti nella macchina virtuale stessa (*vagrant ssh +host1*) ed esaminare lo stato delle cose da là. Nell'ambiente di test, +syslog logs sono copiati nel file */var/log/remote/*, che a volte potrebbe essere più semplice da vedere che la UI di Kibana. + +Una volta completati i test, non dimenticarsi di fermare le virtual +machines lanciando (sempre dalla directory dell'ambiente di test): + +```shell +vagrant destroy -f +``` + +Per controllare l'esecuzione delle VM, ad esempio per sospendere le VM in +mancanza di memoria i comandi sono questi: + +```shell +vagrant suspend +vagrant resume +``` + +# Prossimi passi + +Vai a leggere [Note sull'uso in produzione](running.it.md), +e i [Riferimenti per le configurazioni](docs/configuration.it.md)! + +# Appendice: ma è così lento! + +Si, Ansible è generalmente abbastanza lento a fare le cose, per un numero di ragioni (tra queste i lfatto che creiamo un gran numero di task magari dato dalla possibilità che non usiamo in modo ottimale i loop). Ma ci sono un po di cose che è possibile fare che aiutono un poco: + +1. Installa [Mitogen](https://mitogen.networkgenomics.com/ansible_detailed.html), che nel nostro caso rende Ansible circa 5-10 più veloce. Per attivarlo, bisogna solo modificare le tue configurazioni nel file *ansible.cfg* come è mostrato nella documentazione di Mitogen docs, o semplicemente fa *--mitogen=PATH* nella linea di comandocome opzione dopo l'invocazione di *float create-env*. +2. Imposta una APT cache (per esempio con *apt-cacher-ng*). Imposta la variabile Ansible *apt_proxy* con host:port della cache. Quando usi Vagrant come nell'esempio fatto sopra, tieni in considerazione che il tuo host è sempre raggiungibile dalla VMs come IP .1 IP nel network privato (quindi dovrebbe essere 192.168.10.1 nell'esempio). + +Ancora, la prima volta che lanci le cose, tutto sarà effettuato tramite trasferimenti via rete e dalle installazioni dei pacchetti (sfortunatamente le immagini Docker sono piuttosto grosse). diff --git a/float/docs/quickstart.md b/float/docs/quickstart.md index e5f64dd70047fd1b36dd7946b06808eb20c682fd..893756a9dfa1a399066039ccb312965babdc1dc1 100644 --- a/float/docs/quickstart.md +++ b/float/docs/quickstart.md @@ -91,7 +91,7 @@ files for Ansible and Vagrant, with default values filled in by * `ansible.cfg` is the Ansible configuration file, which tells Ansible where to find the float plugins * `Vagrantfile` is the Vagrant configuration file describing our - single VM. + single VM (SO, ip, memory,...). * `config.yml` is the main *float* configuration file, which mostly just points at the location of the other configs. There is nothing to change here, as *create-env* already wrote sensible defaults. @@ -250,6 +250,22 @@ host1*) and examine the state of things there. On testing environments, syslog logs are also dumped to files below */var/log/remote/*, which might be simpler than using the Kibana UI. +To manage the VM running, for example to suspend the VM for lack of memory, +you can use this commands: + +```shell +vagrant suspend +vagrant resume +``` + +When you finish the testing, not forget to destroy the virtual machine, running this command +in the CLI (always inside of the directory of test enviroment): + +```shell +vagrant destroy -f +``` + + # Next steps Read on to [running float in a production environment](running.md), diff --git a/float/float b/float/float index 91c3d7e81727d54f1403023959e10c7465c596fc..d87604a3ca82a5b3f92c2eb16b9724c09ae418ff 100755 --- a/float/float +++ b/float/float @@ -75,7 +75,7 @@ group_vars: ansible_become: true ansible_ssh_private_key_file: "~/.vagrant.d/insecure_private_key" {% if libvirt and libvirt != 'localhost' %} - ansible_ssh_extra_args = "-o ProxyJump={{ libvirt }}" + ansible_ssh_extra_args: "-o ProxyJump={{ libvirt }}" {% endif %} {% else %} # REPLACE THESE WITH YOUR OWN HOSTS! @@ -267,6 +267,9 @@ def command_create_env(path, domain, vagrant, mitogen, libvirt, num_hosts, net, 'shard_id': ('host%d' % (i+1)) if i > 0 else None, 'groups': ['frontend', 'vagrant'] if i == 0 else ['backend', 'vagrant'], } for i in range(num_hosts)] + # Special case if there's just one host: add the backend group to it! + if len(hosts) == 1: + hosts[0]['groups'].append('backend') vars['vagrant_box'] = 'debian/%s64' % debian_dist else: hosts = [{ diff --git a/float/playbooks/init-credentials.yml b/float/playbooks/init-credentials.yml index 817f8ad193081731f671cc4f2d6913bfdacc8aed..555b0ba4c0967a9e6645ab0732821dc7a9ac075c 100644 --- a/float/playbooks/init-credentials.yml +++ b/float/playbooks/init-credentials.yml @@ -14,6 +14,10 @@ gather_facts: no become: no tasks: + # Check that "float" is properly set up. + - include_role: + name: float-plugin-check + # Create the paths below credentials_dir that we're going to use. - name: "Create paths below {{ credentials_dir }}" file: diff --git a/float/plugins/action/x509_csr.py b/float/plugins/action/x509_csr.py index c293117f0ec72097b69a86ad7c4f114eca3cda2e..ab9c3614fd21840820dea6d33620a9351269ce95 100644 --- a/float/plugins/action/x509_csr.py +++ b/float/plugins/action/x509_csr.py @@ -15,9 +15,6 @@ class ActionModule(ActionBase): TRANSFERS_FILES = False def run(self, tmp=None, task_vars=None): - # Recover important data from Ansible facts. - hostname = self._templar.template('{{inventory_hostname}}') - # Retrieve action parameters. credentials_name = self._task.args['credentials_name'] domain = self._task.args['domain'] @@ -38,10 +35,10 @@ class ActionModule(ActionBase): elif mode == 'server': is_server = True # For each name, use the short version and the FQDN. - for n in params['names']: + for n in params.get('names', []): names.append('%s.%s' % (n, domain)) names.append(n) - ip_addrs = params['addrs'] + ip_addrs = params.get('addrs', []) else: raise Exception('mode must be client or server') diff --git a/float/plugins/inventory/float.py b/float/plugins/inventory/float.py index 05b072bd4b63a3856f2846ab4b12f9e98531594f..01884794c865fe61b1c1add70c548db461b1e8f1 100644 --- a/float/plugins/inventory/float.py +++ b/float/plugins/inventory/float.py @@ -339,7 +339,7 @@ def _build_horizontal_upstreams_map(services): for ep in svc.get('horizontal_endpoints', []): upstream_name = 'be_%s_%s_%s' % (name, ep['port'], service_name) upstreams[upstream_name] = { - 'namme': upstream_name, + 'name': upstream_name, 'service_name': service_name, 'port': ep['port'], 'enable_sso_proxy': False, @@ -530,6 +530,7 @@ def run_scheduler(config): all_vars = inventory.setdefault('group_vars', {}).setdefault('all', {}) all_vars.update(config['vars']) all_vars.update({ + 'float_plugin_loaded': True, 'services': services, 'default_service_credentials': DEFAULT_SERVICE_CREDENTIALS, 'float_global_dns_map': _global_dns_map(inventory), diff --git a/float/roles/base/meta/main.yml b/float/roles/base/meta/main.yml new file mode 100644 index 0000000000000000000000000000000000000000..c62444d5d89ea0a13c61729463005cc7c3ba3c2e --- /dev/null +++ b/float/roles/base/meta/main.yml @@ -0,0 +1,3 @@ +--- +dependencies: + - { role: float-plugin-check } diff --git a/float/roles/base/tasks/backup.yml b/float/roles/base/tasks/backup.yml index ea901e7e7245823e68e92e4fb6d79c2a71564124..483cad483148da2f1513f028a1634c62d3d94d89 100644 --- a/float/roles/base/tasks/backup.yml +++ b/float/roles/base/tasks/backup.yml @@ -20,6 +20,7 @@ - /etc/tabacco/sources - /etc/tabacco/handlers - /var/lib/tabacco + - /var/cache/tabacco - name: Create backup agent config template: @@ -29,13 +30,6 @@ notify: - reload backup agent -- name: Install backup agent systemd unit - template: - src: tabacco/agent.service.j2 - dest: /etc/systemd/system/tabacco-agent.service - notify: - - restart backup agent - - name: Enable backup agent systemd unit systemd: name: tabacco-agent.service diff --git a/float/roles/base/tasks/prometheus.yml b/float/roles/base/tasks/prometheus.yml index 254c7bbc145cd481f7877b593b128fc69d9a9ba4..dc22d3dadb722315f4fd0b6576f53b87c54d6f56 100644 --- a/float/roles/base/tasks/prometheus.yml +++ b/float/roles/base/tasks/prometheus.yml @@ -38,7 +38,7 @@ - name: Install node-exporter-cron cron job copy: dest: "/etc/cron.d/node-exporter-scripts" - content: "32 * * * * root /usr/local/bin/node-exporter-cron\n" + content: "2-59/5 * * * * root /usr/local/bin/node-exporter-cron\n" - name: Create node-exporter scripts directory file: diff --git a/float/roles/base/templates/tabacco/agent.service.j2 b/float/roles/base/templates/tabacco/agent.service.j2 deleted file mode 100644 index 789eae508d440a5e2bad5f4e0c8f47a85f4ce870..0000000000000000000000000000000000000000 --- a/float/roles/base/templates/tabacco/agent.service.j2 +++ /dev/null @@ -1,15 +0,0 @@ -[Unit] -Description=Backup Agent -After=network.target - -[Service] -Type=simple -ExecStart=/usr/bin/tabacco agent -ExecReload=/bin/kill -HUP $MAINPID -Restart=on-failure -User=root -Group=tabacco -LimitNOFILE=65535 - -[Install] -WantedBy=multi-user.target diff --git a/float/roles/base/templates/tabacco/agent.yml.j2 b/float/roles/base/templates/tabacco/agent.yml.j2 index 31c74913b129f55a13c4a43b22e0b5ef5aa44d90..7350945ec516831fe7aa00eae27dfe9a7ec81308 100644 --- a/float/roles/base/templates/tabacco/agent.yml.j2 +++ b/float/roles/base/templates/tabacco/agent.yml.j2 @@ -17,3 +17,4 @@ repository: params: uri: "{{ backup_repository_uri }}" password: "{{ backup_repository_restic_password }}" + cache_dir: /var/cache/tabacco diff --git a/float/roles/float-plugin-check/tasks/main.yml b/float/roles/float-plugin-check/tasks/main.yml new file mode 100644 index 0000000000000000000000000000000000000000..166ab4baa2dbf43c3401db48ba110f36f43786cf --- /dev/null +++ b/float/roles/float-plugin-check/tasks/main.yml @@ -0,0 +1,6 @@ +--- + +- fail: + msg: "The 'float' plugin was not loaded! This could be due to multiple reasons: wrong paths in ansible.cfg, problems with the Python version used by Ansible, etc..." + when: "not float_plugin_loaded | default(False)" + diff --git a/float/roles/prometheus/files/rules/alerts_backup.conf.yml b/float/roles/prometheus/files/rules/alerts_backup.conf.yml new file mode 100644 index 0000000000000000000000000000000000000000..2ead8172b777f3057c054078d0955ab171ed58a4 --- /dev/null +++ b/float/roles/prometheus/files/rules/alerts_backup.conf.yml @@ -0,0 +1,11 @@ +groups: +- name: roles/prometheus/files/rules/alerts_backup.conf + rules: + - alert: BackupFailed + expr: backup_ok != 1 + for: 15m + labels: + severity: warn + annotations: + summary: '{{ $labels.dataset }} backup failure on {{ $labels.host }}' + description: 'Dataset {{ $labels.dataset }} has failed its backups on {{ $labels.host }}.' diff --git a/playbooks/init-credentials.yml b/playbooks/init-credentials.yml new file mode 100644 index 0000000000000000000000000000000000000000..e4c16b2ff31c8d57f475c206f2447e62cd2d78be --- /dev/null +++ b/playbooks/init-credentials.yml @@ -0,0 +1,55 @@ +- hosts: localhost + gather_facts: no + become: no + tasks: + # Create the openvpn path below credentials_dir that we're going to use. + - name: "Create paths below {{ credentials_dir }}/openvpn" + file: + path: "{{ credentials_dir }}/openvpn" + state: directory + + - name: Generate the X509 CA certificate + local_action: x509_ca ca_subject="{{ x509_ca_subject | default('CN=LEAP OpenVPN CA') }}" ca_cert_path="{{ credentials_dir }}/openvpn/ca.pem" ca_key_path="{{ credentials_dir }}/openvpn/ca_private_key.pem" + + - name: Check the certificate for OpenVPN + x509_csr: + credentials_name: openvpn + domain: "{{ domain_public[0] }}" + mode: server + params: { names: [ 'openvpn' ] } + private_key_path: "{{ credentials_dir}}/openvpn/private_key.pem" + cert_path: "{{ credentials_dir}}/openvpn/cert.pem" + ca_cert_path: "{{ credentials_dir }}/openvpn/ca.pem" + check: true + check_mode: no + register: x509_should_update + + - name: Create the CSR for OpenVPN cert + x509_csr: + credentials_name: openvpn + domain: "{{ domain_public[0] }}" + mode: server + params: { names: [ 'openvpn' ] } + private_key_path: "{{ credentials_dir }}/openvpn/private_key.pem" + check: false + when: "x509_should_update.changed" + register: x509_csr + + - name: Create the certificate for OpenVPN + x509_sign: + csr: "{{ x509_csr.csr }}" + mode: server + ca_cert_path: "{{ credentials_dir }}/openvpn/ca.pem" + ca_key_path: "{{ credentials_dir }}/openvpn/ca_private_key.pem" + when: "x509_should_update.changed" + register: x509_sign + + - name: Install the signed certificate for OpenVPN + copy: + dest: "{{ credentials_dir}}/openvpn/cert.pem" + content: "{{ x509_sign.cert }}" + mode: 0644 + when: "x509_sign.changed" + +- name: Include float init-credentials + import_playbook: ../float/playbooks/init-credentials.yml diff --git a/roles/README b/roles/README new file mode 100644 index 0000000000000000000000000000000000000000..735aae8710c6dee03e6159d01ebacaa6d9368e0a --- /dev/null +++ b/roles/README @@ -0,0 +1,10 @@ +Roles: + +openvpn: this role conains the firewall modifications for openvpn (the firewall +should go with the services, because the port, eg. 1194, "belongs" to the +openvpn service, regardless of where it is scheduled), along with the general +openvpn configuration information + + +openvpn-frontend: this role will be scheduled on http/dns servers, so this role +contains the dns modifications diff --git a/roles/openvpn-frontend/files/openvpn_dns.yml b/roles/openvpn-frontend/files/openvpn_dns.yml new file mode 100644 index 0000000000000000000000000000000000000000..02458407dfa0b11e2e66a10a8ea1941c847b0bec --- /dev/null +++ b/roles/openvpn-frontend/files/openvpn_dns.yml @@ -0,0 +1 @@ +{{ domain }}: openvpn: "{{ groups['openvpn'] | map(attribute='ip') | tojson }}" diff --git a/roles/openvpn-frontend/handlers/main.yml b/roles/openvpn-frontend/handlers/main.yml new file mode 100644 index 0000000000000000000000000000000000000000..7a8122f91bbf8e6362391fa6c13091d26a896f74 --- /dev/null +++ b/roles/openvpn-frontend/handlers/main.yml @@ -0,0 +1,3 @@ + +- listen: reload openvpn DNS config + shell: "/usr/sbin/update-dns && rndc reload" diff --git a/roles/openvpn-frontend/tasks/main.yml b/roles/openvpn-frontend/tasks/main.yml new file mode 100644 index 0000000000000000000000000000000000000000..03872ead6887e537c62a775dc26e5b20639977c4 --- /dev/null +++ b/roles/openvpn-frontend/tasks/main.yml @@ -0,0 +1,6 @@ +- name: Install Openvpn DNS records + file: + src: 'openvpn_dns.yml' + dest: '/etc/dns/manual/openvpn.yml' + notify: reload openvpn DNS config + diff --git a/roles/openvpn-frontend/templates/openvpn_dns.j2 b/roles/openvpn-frontend/templates/openvpn_dns.j2 new file mode 100644 index 0000000000000000000000000000000000000000..490a2b514a58557f0327a4efce4d5466e19b08a0 --- /dev/null +++ b/roles/openvpn-frontend/templates/openvpn_dns.j2 @@ -0,0 +1,5 @@ +{{ domain }}: + _: +{% for h in groups['openvpn'] %} + - {{ hostvars[h].ip }} +{% endfor %} diff --git a/roles/openvpn/files/50openvpn.firewall b/roles/openvpn/files/50openvpn.firewall new file mode 100644 index 0000000000000000000000000000000000000000..e1dfe066adfe4deac621d01cb72e3779a6916e1b --- /dev/null +++ b/roles/openvpn/files/50openvpn.firewall @@ -0,0 +1,12 @@ +# Allow incoming connections to port 1194 for both udp and tcp +allow_port tcp 1194 +allow_port udp 1194 +# there is nothing special with regard to networking here, openvpn is just a process +# iptables by default in float prevents us from forwarding packets (DROP policy on FORWARD) +# so we need something in the FORWARD table that says "allow packets to / from this vpn" +# +# let vpn hosts reach the internet +add_rule -A FORWARD -s 10.41.0.0/24 -o eth0 -j ACCEPT +# allow re/established *inbound* to vpn hosts +add_rule -A FORWARD -d 10.41.0.0/24 -m state --state RELATED,ESTABLISHED -j ACCEPT +add_rule -t nat -A POSTROUTING -s 10.41.0.0/24 -o eth0 -j MASQUERADE diff --git a/roles/openvpn/handlers/main.yml b/roles/openvpn/handlers/main.yml index 01bae4f81d61780ce59f0e2b0a68d39806f83b17..6fa0f9a0efe77baa4cf2c54a356c4a37a378f22f 100644 --- a/roles/openvpn/handlers/main.yml +++ b/roles/openvpn/handlers/main.yml @@ -1,4 +1,11 @@ +--- + - name: reload firewall systemd: name: firewall.service state: restarted + +- listen: "restart docker-openvpn-openvpn" + systemd: + name: "docker-openvpn-openvpn.service" + state: restarted diff --git a/roles/openvpn/tasks/main.yml b/roles/openvpn/tasks/main.yml index 6afdfc9ba79672afac93789256fc311b9a99745a..f34e97041ae6be6b246fa749f5e2eda4589fb656 100644 --- a/roles/openvpn/tasks/main.yml +++ b/roles/openvpn/tasks/main.yml @@ -23,3 +23,48 @@ path: "/srv/leap/shapeshifter-state/obfs4_cert.txt" owner: docker-openvpn mode: 0600 + +- name: Create openvpn directory + file: + path: /etc/openvpn + state: directory + group: docker-openvpn + mode: 0750 + +- name: Install DH parameters + copy: + src: "{{ credentials_dir }}/x509/dhparam" + dest: /etc/openvpn/dh.pem + +- name: Install OpenVPN CA + copy: + src: "{{ credentials_dir }}/openvpn/ca.pem" + dest: /etc/openvpn/ca.pem + +- name: Install OpenVPN key + copy: + src: "{{ credentials_dir }}/openvpn/private_key.pem" + dest: /etc/openvpn/private_key.pem + +- name: Install OpenVPN cert + copy: + src: "{{ credentials_dir }}/openvpn/cert.pem" + dest: /etc/openvpn/cert.pem + +- name: Install firewall config for openvpn + copy: + src: "50openvpn.firewall" + dest: "/etc/firewall/filter.d/50openvpn" + notify: "reload firewall" + +- name: Install openvpn configs + template: + src: "{{ item }}" + dest: "/etc/openvpn" + mode: 0644 + with_items: + - udp.conf + - tcp.conf + - shapeshifter.conf + notify: + - "restart docker-openvpn-openvpn" diff --git a/roles/openvpn/templates/shapeshifter.conf b/roles/openvpn/templates/shapeshifter.conf new file mode 100644 index 0000000000000000000000000000000000000000..81a3fca68ceef4c52f14d21905071689ff8c7410 --- /dev/null +++ b/roles/openvpn/templates/shapeshifter.conf @@ -0,0 +1,33 @@ +mode server +tls-server +port 1194 +auth SHA1 +ca /etc/openvpn/ca.pem +cert /etc/openvpn/cert.pem +key /etc/openvpn/private_key.pem +cipher AES-128-CBC +dev tun +dh /etc/openvpn/dh.pem +duplicate-cn +keepalive 10 30 +local 127.0.0.1 +management 127.0.0.1 1000 +mute-replay-warnings +mute 5 +proto tcp +push "dhcp-option DNS {{ ip }}" +push "redirect-gateway def1" +push "route-ipv6 2000::/3" +push "ignore-unknown-option" +push "block-outside-dns" +script-security 1 +server-ipv6 2001:db8:123::/64 +server 10.41.0.0 255.255.248.0 +status /var/run/openvpn-status-tcp 10 +status-version 3 +tcp-nodelay +tls-cipher DHE-RSA-AES128-SHA +verify-x509-name "CN=UNLIMITED" +topology subnet +tun-ipv6 +verb 3 diff --git a/roles/openvpn/templates/tcp.conf b/roles/openvpn/templates/tcp.conf new file mode 100644 index 0000000000000000000000000000000000000000..436407458ec614750a8a2d3da474947f22492082 --- /dev/null +++ b/roles/openvpn/templates/tcp.conf @@ -0,0 +1,31 @@ +mode server +tls-server +port 1194 +auth SHA1 +ca /etc/openvpn/ca.pem +cert /etc/openvpn/cert.pem +key /etc/openvpn/private_key.pem +cipher AES-128-CBC +dev tun +dh /etc/openvpn/dh.pem +duplicate-cn +keepalive 10 30 +local {{ public_ip|default(ip) }} +mute-replay-warnings +mute 5 +proto tcp +push "dhcp-option DNS {{ ip }}" +push "redirect-gateway def1" +push "route-ipv6 2000::/3" +push "ignore-unknown-option" +push "block-outside-dns" +script-security 1 +server-ipv6 2001:db8:123::/64 +server 10.41.0.0 255.255.248.0 +status /tmp/openvpn-status-tcp 10 +status-version 3 +tcp-nodelay +tls-cipher DHE-RSA-AES128-SHA +verify-x509-name "CN=UNLIMITED" +topology subnet +verb 3 diff --git a/roles/openvpn/templates/udp.conf b/roles/openvpn/templates/udp.conf new file mode 100644 index 0000000000000000000000000000000000000000..155d5dbe250d8845be738ee888b59c9886a201fb --- /dev/null +++ b/roles/openvpn/templates/udp.conf @@ -0,0 +1,32 @@ +mode server +tls-server +port 1194 +auth SHA1 +ca /etc/openvpn/ca.pem +cert /etc/openvpn/cert.pem +key /etc/openvpn/private_key.pem +cipher AES-128-CBC +dev tun +dh /etc/openvpn/dh.pem +duplicate-cn +keepalive 10 30 +local {{ public_ip|default(ip) }} +mute-replay-warnings +mute 5 +proto udp +push "dhcp-option DNS {{ ip }}" +push "redirect-gateway def1" +push "route-ipv6 2000::/3" +push "ignore-unknown-option" +push "block-outside-dns" +script-security 1 +server-ipv6 2001:db8:123::/64 +server 10.41.0.0 255.255.248.0 +status /tmp/openvpn-status-udp 10 +status-version 3 +tcp-nodelay +tls-cipher DHE-RSA-AES128-SHA +verify-x509-name "CN=UNLIMITED" +topology subnet +tun-ipv6 +verb 3 diff --git a/services.yml b/services.yml index a63898dc9034d544f16fad68013f3a462d5d7ccb..37d4cf64977d1e84e6b3d3ce2cd68fb282464081 100644 --- a/services.yml +++ b/services.yml @@ -12,7 +12,7 @@ geoip: public_endpoints: - name: getmyip port: 9001 - scheme: http + scheme: http openvpn: # these are udp/tcp services, so we don't want to put them behind the reverse proxy @@ -20,22 +20,31 @@ openvpn: # 1) set scheduling_group=frontend, # 2) create a 'vpn' group, intended for horizontal scaling of the public vpn nodes, # and run this service there (so you can keep assuming that 'backend' hosts are 'private') + # + # we want to schedule the openvpn-frontend role on the 'frontend' groups, + # there is no openvpn-frontend service, but instead we are customizing the + # 'frontend' role + # + # We do not want to use public_tcp_endpoints for openvpn/shapeshifter, because + # that results in haproxy being setup for the port. Reverse proxies are + # connection based, and we want to also be able to do UDP, so we do not really + # want to have a reverse proxy for openvpn, instead we want to set it up as a + # user-facing service, and just rely on DNS for request routing and balancing. + scheduling_group: frontend num_instances: 1 - public_tcp_endpoints: - - name: shapeshifterlistener - port: 23042 - - name: openvpn - port: 1194 containers: - name: openvpn image: registry.0xacab.org/leap/container-platform/openvpn:latest ports: [1194, 23042] + drop_capabilities: false + docker_options: '--cap-add=NET_ADMIN' volumes: - /srv/leap/shapeshifter-state: /srv/leap/shapeshifter-state + - /etc/openvpn: /etc/openvpn + - /dev/net: /dev/net env: # Shapeshifter specific environment variables LHOST: "{{ ip }}" _CHAP_OPTIONS: --no-syslog LOGLEVEL: DEBUG - diff --git a/site.yml b/site.yml index 6dcbcf6cf28631b57104e7902820b931bdea4c9e..b99fb40d15a5bceb27c193b52d0daecbdc97b25c 100644 --- a/site.yml +++ b/site.yml @@ -1,5 +1,9 @@ --- - import_playbook: ../../float/playbooks/all.yml + - hosts: openvpn roles: [openvpn] - + +- hosts: frontend + roles: [openvpn-frontend] +