Parcourir la source

Merged in FD3-671 (pull request #36)

FD3-671 Herramientas para LetsEncrypt + Fixes

Approved-by: Guillermo Espinoza <guillermo@interlink.com.ar>
Approved-by: Maximiliano Schvindt <maximiliano@interlink.com.ar>
Daniel Libonati il y a 6 ans
Parent
commit
1719d4e788
5 fichiers modifiés avec 340 ajouts et 46 suppressions
  1. 28 0
      letsencrypt/flowdat_deploy.sh
  2. 188 0
      letsencrypt/googledns.py
  3. 12 0
      letsencrypt/key.json
  4. 2 2
      tools/playbook.yml
  5. 110 44
      tools/readme.md

+ 28 - 0
letsencrypt/flowdat_deploy.sh

@@ -0,0 +1,28 @@
+#!/bin/sh
+
+set -e
+
+for domain in $RENEWED_DOMAINS; do
+  case $domain in
+  DOMAIN_NAME_REPLACE)
+    nginx_cert_root=/opt/flowdat/extra/nginx/certs
+
+    # Make sure the certificate and private key files are
+    # never world readable, even just for an instant while
+    # we're copying them into daemon_cert_root.
+    umask 077
+
+    cp "$RENEWED_LINEAGE/fullchain.pem" "$nginx_cert_root/fullchain.pem"
+    cp "$RENEWED_LINEAGE/privkey.pem" "$nginx_cert_root/privkey.pem"
+
+    # Apply the proper file ownership and permissions for
+    # the daemon to read its certificate and key.
+    chmod 400 "$nginx_cert_root/fullchain.pem" \
+            "$nginx_cert_root/privkey.pem"
+
+    cd /opt/flowdat && docker-compose restart nginx
+    ;;
+  esac
+done
+
+exit 0 

+ 188 - 0
letsencrypt/googledns.py

@@ -0,0 +1,188 @@
+# This script requires:
+# pip install --upgrade google google-cloud-dns
+# Thanks for using this software! DL.
+
+import sys, getopt, time
+
+try:
+    import google
+    from google.cloud import dns
+except ImportError:
+    print('\nYou must install google and google-cloud-dns modules to run this script. You may run:\n')
+    print('pip install --upgrade google google-cloud-dns\n')
+    sys.exit(2)
+
+# Globals
+google_client = None
+app_subdomains = ['base', 'radius', 'cablemodem', 'ftth', 'dhcp', 'mapas', 'stats', 'grafana', 'geoserver', 'api', 'swagger']
+
+
+def main(argv):
+
+    global google_client
+    json_path = action = client = ip_address = domain = opts = args = None
+
+    try:
+        opts, args = getopt.getopt(argv, None, ["key=", "action=", "client=", "ip_address=", "domain="])
+    except getopt.GetoptError:
+        print_usage()
+        sys.exit(2)
+
+    for opt, arg in opts:
+        if opt in ('--key'):
+            json_path = arg
+        if opt in ('--action'):
+            action = arg
+        if opt in ('--client'):
+            client = arg
+        if opt in ('--ip_address'):
+            ip_address = arg
+        if opt in ('--domain'):
+            domain = arg
+
+    if not json_path:
+        print(bcolors.FAIL + bcolors.BOLD +
+              '\nERROR: You must provide a path to the JSON Key File! Check documentation for further assistance.'+
+              bcolors.ENDC + bcolors.ENDC)
+        print_usage()
+        sys.exit(2)
+
+    google_client = dns.Client.from_service_account_json(json_path)
+
+    if not google_client:
+        print(bcolors.FAIL + bcolors.BOLD +
+              '\nERROR: Failed to create Google Client object.\n'+
+              bcolors.ENDC + bcolors.ENDC)
+        sys.exit(2)
+
+    if action == 'create':
+
+        if not client or not ip_address or not domain:
+            print(bcolors.FAIL + bcolors.BOLD +
+                '\nERROR: create requires client, ip_address and domain!\n'+
+                bcolors.ENDC + bcolors.ENDC)
+            print_usage()
+            sys.exit(2)
+
+        if create(client=client, ip_address=ip_address, domain=domain):
+            print(bcolors.OKGREEN + bcolors.BOLD +
+                  '\nEntities have been successfully created!\n' +
+                  bcolors.ENDC + bcolors.ENDC)
+
+    elif action == 'delete':
+
+        if not client or not ip_address or not domain:
+            print(bcolors.FAIL + bcolors.BOLD +
+                '\nERROR: delete requires client and domain!\n'+
+                bcolors.ENDC + bcolors.ENDC)
+            print_usage()
+            sys.exit(2)
+
+        if delete(client=client, domain=domain):
+            print(bcolors.OKGREEN + bcolors.BOLD +
+                  '\nEntities have been successfully deleted!\n' +
+                  bcolors.ENDC + bcolors.ENDC)
+
+    elif action == 'list':
+        list()
+
+
+def list():
+
+    zone = google_client.zone('flowdatnet')
+    records = zone.list_resource_record_sets()  # API request
+
+    for record in records:
+        print(record.name, record.record_type, record.ttl, record.rrdatas)
+
+    return True
+
+
+def create(client=None, ip_address=None, domain='flowdat.net'):
+
+    zone = google_client.zone('flowdatnet')
+    changes = zone.changes()
+
+    # Add "A" records for domain
+    record_set = zone.resource_record_set('{}.{}.'.format(client, domain), 'A', '3600', ip_address)
+    changes.add_record_set(record_set)
+
+    # Add "A" records for subdomains
+    for subdomain in app_subdomains:
+        record_set = zone.resource_record_set('{}.{}.{}.'.format(subdomain, client, domain), 'A', '3600', ip_address)
+        changes.add_record_set(record_set)
+
+    try:
+        changes.create()
+    except google.api_core.exceptions.Conflict as e:
+        print(bcolors.FAIL + bcolors.BOLD +
+              "\nError: Entity already exist. Please use the delete action to make sure there are no existing records and try again.\n" +
+              bcolors.ENDC + bcolors.ENDC)
+        print("{}\n".format(e))
+        sys.exit(2)
+
+    return True
+
+
+def delete(client=None, domain='flowdat.net'):
+
+    zone = google_client.zone('flowdatnet')
+    changes = zone.changes()
+
+    records = []
+    all_records = zone.list_resource_record_sets()
+
+    for record in all_records:
+
+        # Match "A" record for main domain
+        if record.name == '{}.{}.'.format(client, domain):
+            records.append(record)
+            continue
+
+        # Match "A" records for subdomains
+        for subdomain in app_subdomains:
+            if record.name == '{}.{}.{}.'.format(subdomain, client, domain):
+                records.append(record)
+                break
+
+    for record in records:
+        changes.delete_record_set(record)
+
+    try:
+        changes.create()
+    except ValueError as e:
+        print(bcolors.OKBLUE + bcolors.BOLD +
+                  '\nNothing to change.\n' +
+                  bcolors.ENDC + bcolors.ENDC)
+        return False
+
+    return True
+
+
+def print_usage():
+
+    print("\n")
+    print(bcolors.HEADER + "-- Usage --" + bcolors.ENDC)
+    print(
+        """
+        python googledns.py --key=<Path to JSON Key File> --action=create --client=<Client Name> --ip_address=<IP Address> --domain=<Domain>
+        python googledns.py --key=<Path to JSON Key File> --action=delete --client=<Client Name> --domain=<Domain>
+        python googledns.py --key=<Path to JSON Key File> --action=list
+
+        """)
+
+
+class bcolors:
+
+    HEADER = '\033[95m'
+    OKBLUE = '\033[94m'
+    OKGREEN = '\033[92m'
+    WARNING = '\033[93m'
+    FAIL = '\033[91m'
+    ENDC = '\033[0m'
+    BOLD = '\033[1m'
+    UNDERLINE = '\033[4m'
+
+
+if __name__ == "__main__":
+   main(sys.argv[1:])

Fichier diff supprimé car celui-ci est trop grand
+ 12 - 0
letsencrypt/key.json


+ 2 - 2
tools/playbook.yml

@@ -190,12 +190,12 @@
 
     - name: Load redirections
       set_fact:
-        uris: "{{ uris }} --redirect_uri=https://{{ item }}.{{ lookup('env', 'CLIENT') }}.flowdat.com/login_check"
+        uris: "{{ uris }} --redirect_uri=https://{{ item }}.{{ lookup('env', 'CLIENT') }}.{{ lookup('env', 'DOMAIN') }}/login_check"
       with_items: "{{ lookup('env', 'MODULES_INSTALL').split(',') }}"
 
     - name: Load redirections app_dev
       set_fact:
-        uris: "{{ uris }} --redirect_uri=https://{{ item }}.{{ lookup('env', 'CLIENT') }}.flowdat.com/app_dev.php/login_check"
+        uris: "{{ uris }} --redirect_uri=https://{{ item }}.{{ lookup('env', 'CLIENT') }}.{{ lookup('env', 'DOMAIN') }}/app_dev.php/login_check"
       with_items: "{{ lookup('env', 'MODULES_INSTALL').split(',') }}"
 
     - name: Create oauth client

+ 110 - 44
tools/readme.md

@@ -1,4 +1,4 @@
-#Requerimientos
+# Requerimientos
  * curl
  * git
  * python
@@ -7,10 +7,10 @@
  * docker-compose
  * php/composer
 
-#Instalación de requerimientos:
+# Instalación de requerimientos
 ### curl
     apt-get update && apt-get install curl git python
- DIR_INSTALL=$(pwd) -t dind 
+ DIR_INSTALL=$(pwd) -t dind
 ### pip
     # descargo el  archivo get-pip.py desde una url
     curl https://bootstrap.pypa.io/get-pip.py | python
@@ -30,59 +30,59 @@
 ### docker-compose
     pip install docker-compose
 
-# Pasos:
-####. Crear un directorio para la instalación, puede ser en cualquier lugar del sistema. Tener en cuenta que en este directorio se descargará código fuente.
-    mkdir /opt/flowdat  
+# Pasos
+#### Crear un directorio para la instalación, puede ser en cualquier lugar del sistema. Tener en cuenta que en este directorio se descargará código fuente.
+    mkdir /opt/flowdat
 
-####. Entrar la directorio.
+#### Entrar la directorio.
     cd /opt/flowdat
 
-####. Descargar el fuente de instalación.   
+#### Descargar el fuente de instalación.
     # en código se descarga en el directorio actual
     git clone git@bitbucket.org:ikflowdat/installer.git .
 
-####. Entrar la directorio tools.
+#### Entrar la directorio tools.
     cd tools
 
-####. Construir el docker que se utilizará durante la instalación.
-    docker build --build-arg DIR_INSTALL=$(pwd) -t dind .     
+#### Construir el docker que se utilizará durante la instalación.
+    docker build --build-arg DIR_INSTALL=$(pwd) -t dind .
     # --build-arg: es la forma de pasarle parámetros al docker.
     # -t: es el nombre que tendrá el contenedor.
     # .: indica que el fuente se descargará en el directorio actual.
     # para más información de los parámetros ejecutar "docker build --help
 
-####. Instalamos con composer elementos adicionales.
-    docker run -it -v $(pwd):$(pwd) -v /var/run/docker.sock:/tmp/docker.sock dind composer install     
+#### Instalamos con composer elementos adicionales.
+    docker run -it -v $(pwd):$(pwd) -v /var/run/docker.sock:/tmp/docker.sock dind composer install
     # --build-arg: es la forma de pasarle parámetros al docker.
     # -t: es el nombre que tendrá el contenedor.
     # para más información de los parámetros ejecutar "docker build --help"
 
-####. Crear los archivos necesarios para la instalación. Dentro del directorio actual se crea un nuevo directorio con el nombre de la empresa.
+#### Crear los archivos necesarios para la instalación. Dentro del directorio actual se crea un nuevo directorio con el nombre de la empresa.
 
+**ATENCIÓN**: El siguiente comando requiere que se establezcan los parámetros **client** y **domain**, los cuales determinan la URL a la que deberá accederse para ingresar al sistema. **Si se omiten estos valores la instalación no funcionará**. Por ejemplo, si el cliente es "galvez" y el dominio es "flowdat.net" entonces:
 ####. Poner el nombre del cliente, de lo contrario no va a funcionar correctamente!!!
 ####. Cambiar --client=$CLIENT por alguno de los ejemplos
 
-####.--client=fd3
-####.--client=testfernando
-####.--client=testfer , etc.
-
-
-    docker run -it -v /opt/flowdat:/opt/flowdat -v /var/run/docker.sock:/tmp/docker.sock dind make:install /opt/flowdat --client=$CLIENT
+    docker run -it -v /opt/flowdat:/opt/flowdat -v /var/run/docker.sock:/tmp/docker.sock dind make:install /opt/flowdat --client=galvez --domain=flowdat.net
     # docker run: ejecuta un comando sobre el contenedor.
     # -it: significa que voy a tener un tty interativo.
     # -v $(pwd):$(pwd): monta como un volumen el directorio actual, en el contenedor bajo el mismo directorio.
     # -v /var/run/docker.sock:/tmp/docker.sock: comparte el docker.sock entre los docker's.
     # dind: es el nombre que le pusimos anteriormente al contenedor.
     # make:install: es el comando que se ejecuta dentro del docker para crear los archivos.
-    # $CLIENT: nombre de la empresa que estoy instalando.
+
+    # --client=NOMBRE-CLIENTE: Nombre de la empresa que estoy instalando.
+    # --domain=DOMINIO-INSTALACION: es el nombre del dominio que se utilizará. El dominio final quedaría
+    # base.NOMBRE-CLIENTE.DOMINIO-INSTALACION, ftth.DOMINIO-INSTALACION.flowdat.com, etc.
     # --add-nginx-links: permite crear links nginx en los servicios de los módulos web de FD3, sirve para instalaciones en modo desarrollo. Si no se agrega, por defecto no los crea.
-    # --domain=DOMINIO-INSTALACION: es el nombre del dominio que se utilizará. El dominio final quedaría base.DOMINIO-INSTALACION.flowdat.com, ftth.DOMINIO-INSTALACION.flowdat.com, etc.
     # para mas informacion ejecutar "docker run -it -v $(pwd):$(pwd) -v /var/run/docker.sock:/tmp/docker.sock dind make:install --help"
 
-####. Una vez dentro del docker entramos al directorio de instalación.
+Esto hará que el sistema base quede instalado en base.galvez.flowdat.net.
+
+#### Una vez dentro del docker entramos al directorio de instalación.
     cd /opt/flowdat
 
-####. Obtener los fuentes desde bitbucket
+#### Obtener los fuentes desde bitbucket
     docker run -it -v $(pwd):$(pwd) -v /var/run/docker.sock:/tmp/docker.sock dind get:source $(pwd)/git.ini --timeout=120
     # docker run: ejecuta un comando sobre el contenedor.
     # -it: significa que voy a tener un tty interativo.
@@ -93,7 +93,7 @@
     # $(pwd)/git.ini: es el nombre del archivo que posee los directorios a descargar.
     # para mas informacion ejecutar "docker run -it -v $(pwd):$(pwd) -v /var/run/docker.sock:/tmp/docker.sock dind get:source --help"
 
-####. Renombramos los archivos innecesarios para el nginx.    
+#### Renombramos los archivos innecesarios para el nginx.
     docker run -it -v $(pwd):$(pwd) -v /var/run/docker.sock:/tmp/docker.sock dind fix:nginx $(pwd)
     # docker run: ejecuta un comando sobre el contenedor.
     # -it: significa que voy a tener un tty interativo.
@@ -101,9 +101,9 @@
     # -v /var/run/docker.sock:/tmp/docker.sock: comparte el docker.sock entre los docker's.
     # dind: es el nombre que le pusimos anteriormente al contenedor.
     # fix:nginx: es el comando que se ejecuta dentro del docker para obtener los fuentes.
-    # $(pwd): nombre de la empresa que estoy instalando.            
+    # $(pwd): nombre de la empresa que estoy instalando.
 
-####. Entramos al docker para correr ansible y terminar la configuración
+#### Entramos al docker para correr ansible y terminar la configuración
     docker run -it -v $(pwd):$(pwd) -v /var/run/docker.sock:/tmp/docker.sock dind bash
     # docker run: ejecuta un comando sobre el contenedor.
     # -it: significa que voy a tener un tty interativo.
@@ -112,24 +112,24 @@
     # dind: es el nombre que le pusimos anteriormente al contenedor.
     # bash: significa que se ejecutará un bash.
 
-####. Una vez dentro del docker entramos al directorio de instalación.
+#### Una vez dentro del docker entramos al directorio de instalación.
     cd /opt/flowdat
 
-####. Corremos ansible para finalizar la configuración e instalación
+#### Corremos ansible para finalizar la configuración e instalación
     eval $(cat mysql.host.env running.env) ansible-playbook -i inventory.ini -u root playbook.yml
     # eval $(cat mysql.host.env running.env): se le pasa al ansible-playbook las variables de entorno definidas en los archivos mysql.host.env y running.env
-    # -i inventory.ini: se el especifica el inventory.ini          
+    # -i inventory.ini: se el especifica el inventory.ini
     # -u root: le digo que se ejecutarán las acciones como el usuario "root"
     # playbook.yml: es el nombre del playbook a ejecutar
 
-### Los container se levantan por systemctl
+### Los containers se levantan por systemctl
 ### Ejecutar fuera del dind
     ln -sf $(pwd)/docker-compose.service /etc/systemd/system/docker-compose.service
 
     systemctl enable docker-compose.service
 
 
-## Unistalls
+## Uninstalls
 #### Docker
     # con sudo
     sudo apt-get remove docker docker-engine docker.io docker-ce
@@ -148,6 +148,72 @@
 #### Obtener un certificado wildcard para HTTP + SSL
     https://ikflowdat.atlassian.net/wiki/spaces/DL/pages/9469968
 
+## Alta de Dominios en Google Cloud DNS
+
+** Importante **: Correr estos comandos fuera del entorno de Docker.
+
+** Nota **: Al momento de escribir este documento, Google Cloud DNS unicamente soporta al dominio flowdat.net.
+
+Para crear los registros DNS correspondientes para el subdominio del cliente, correr los siguientes comandos:
+
+Instalar los módulos python necesarios:
+```
+pip install google google-cloud-dns
+```
+
+Cambiarse al directorio de herramientas de LetsEncrypt:
+```
+cd /opt/flowdat/letsencrypt
+```
+
+Correr el script:
+```
+python googledns.py --key=key.json --action=create --client=<NOMBRE-CLIENTE> --ip_address=<IP-CLIENTE> --domain=flowdat.net
+```
+Reemplazar NOMBRE-CLIENTE con el nombre utilizado durante la instalación (ej. "galvez") y IP-CLIENTE con la dirección IP del servidor en el cual se instaló Flowdat. Para detalles de otras funciones del script correr el mismo sin parámetros.
+
+## Obtención de certificado SSL de LetsEncrypt
+** Importante **: Correr estos comandos fuera del entorno de Docker.
+
+
+Antes de continuar, instalar Certbot (https://certbot.eff.org/)
+
+Asegurarse de tener instalado el módulo Python de google-dns para Certbot:
+```
+pip install certbot-dns-google
+```
+
+Cambiar al directorio de herramientas de letsencrypt:
+```
+cd /opt/flowdat/letsencrypt
+```
+
+Alterar el script de renovación para que funcione con nuestro dominio:
+```
+sed -i 's/DOMAIN_NAME_REPLACE/NOMBRE_DOMINIO/g' flowdat_deploy.sh
+```
+**IMPORTANTE**: Reemplazar NOMBRE_DOMINIO con el dominio del cliente, ej: galvez.flowdat.net.
+
+Correr certbot para obtener el certificado por primera vez:
+```
+certbot certonly --cert-name NOMBRE_DOMINIO --dns-google --dns-google-credentials /opt/flowdat/letsencrypt/key.json --server https://acme-v02.api.letsencrypt.org/directory -d "*.NOMBRE_DOMINIO" -d "NOMBRE_DOMINIO" --deploy-hook=/opt/flowdat/letsencrypt/flowdat_deploy.sh
+```
+**IMPORTANTE**: Reemplazar NOMBRE_DOMINIO con el dominio del cliente, ej: galvez.flowdat.net.
+
+Verificar que la siguiente línea aparezca entre las últimas a la salida del comando:
+```
+Running deploy-hook command: /opt/flowdat/letsencrypt/flowdat_deploy.sh
+```
+
+...la cual indica que el hook se ejecutó. Eso significa que los certificados deberían haberse copiado a la carpeta de Nginx. Verificar con el navegador que el sitio web sea seguro.
+
+Probar autorenovación:
+```
+certbot renew --dry-run
+```
+
+
+
 ## Errors
 #### Mysql no arranca
     Este es un problema de permisos del directorio mysql. Realizar un chmod fuera del docker.
@@ -176,37 +242,37 @@
     Salgo del docker
         exit
     Ejecuto nuevamente el playbook (ansible)
-    
+
 #### Pantalla en blanco al ingresar a base luego de una instalación nueva
-    Por algún motivo Base no es capaz de alterar los permisos de la caché durante la instalación.
-    Cambiar los permisos mediante:
+Por algún motivo Base no es capaz de alterar los permisos de la caché durante la instalación.
+Cambiar los permisos mediante:
+
 ```
     cd /opt/flowdat/base
     chmod -R 777 var/cache/ var/logs/ var/sessions/
 ```
 
 #### No aparece el logo de Flowdat en el login y en otras pantallas
-    Por alguna razón los assets manejados por assetic no se copian durante la instalación.
-    Ingresar al container Base:
+
+Por alguna razón los assets manejados por assetic no se copian durante la instalación.
+Ingresar al container Base:
 ```
     cd /opt/flowdat/
     docker-compose exec base bash
 ```
-    Luego correr:
+Luego correr:
 ```
     bin/console assetic:dump
 ```
-    
-
 
 
-#PROCEDIMIENTOS PARA GENERAR BRANCH'S Y TAG'S   
-##Branch y tag de módulos
+# PROCEDIMIENTOS PARA GENERAR BRANCH'S Y TAG'S
+## Branch y tag de módulos
 ##### OBSERVACIONES: debemos tener funcionando los dockers para que se puedan ejecutar los composer update.
 
     Cuando me conecto al servidor, debo conectarme con un ssh -A ... para pasarle mis credenciales actuales.
     Primero debemos estar situados en el directorio /opt/flowdat/tools.
-    Ahora debemos modificar los archivos "composer.json" de cada módulo y ajustarlo a la versión deseada. 
+    Ahora debemos modificar los archivos "composer.json" de cada módulo y ajustarlo a la versión deseada.
     Este proceso ejecuta el composer update, por lo tanto se debe tener en cuenta que se debe ejecutar en alguna máquina que posea los dockers de los módulos creados.
     Si no poseo instalado php (es mejor ejecutarlo de esta forma por el tema de permisos):
         Nos situamos en el directorio
@@ -218,7 +284,7 @@
         Ejecuto la sentencia para cambiar el composer y actualizar el fuente
             php cmd.php composer:require ../modules.ini ik/* vX.Y.Z --composer_update=true --pull=master
         Una vez finalizado la generación de los composer json/lock, debemos hacer un commit de los cambios y para esto ejecutamos la sentencia
-            php cmd.php make:tag:modules ../modules.ini vX.Y.Z    
+            php cmd.php make:tag:modules ../modules.ini vX.Y.Z
 
     Si poseo instalado php:
         Para esto debemos ejecutar la siguiente sentencia