Copia de seguridad de archivos de configuración en GitHub

Ejemplo de código de Python para enviar archivos de configuración críticos del servidor a un repositorio de GitHub

C05348A3-9AB8-42C9-A6E0-81DB3AC59FEB
           

Este ejercicio es algo inverso al descrito en Administración de servidores distribuidos con GitHub . Esta vez, queremos hacer una copia de seguridad de los archivos de configuración críticos del servidor, como archivos HAproxy o nginx, en GitHub. La edición de estos archivos generalmente se realiza mediante prueba y error... edite el archivo, verifique la sintaxis, reinicie el proceso y verifique que el cambio tuvo el comportamiento previsto. Obviamente, esto se hace en un sistema de desarrollo, no en producción.

Si bien es absolutamente una buena idea almacenar estos archivos en GitHub, el flujo de trabajo normal de GitHub para confirmar un cambio en el repositorio y extraerlo de los servidores realmente no se aplica. Estos archivos de configuración también suelen ser específicos del entorno, no puede simplemente llevar el archivo de configuración DEV al servidor de producción, hay nombres DNS, diferentes parámetros de registro y muchas cosas que los hacen diferentes. Personalmente, descubrí que el mejor flujo de trabajo para usar es casi un reverso del flujo de trabajo normal de GitHub, realice el cambio en el archivo, pruebe y asegúrese de que funcione según lo previsto, y luego confirme la nueva versión del archivo en GitHub, como un respaldo. La copia de seguridad se puede utilizar en caso de que sea necesario reconstruir el servidor, o también como una opción de restitución, para revertir el próximo cambio a la versión de trabajo anterior si las cosas salen mal.

Este es el propósito básico de este script de Python, enviar estos archivos a GitHub cuando han cambiado. El script también puede redactar parámetros específicos, como contraseñas, que, por supuesto, nunca desea almacenar en GitHub ni en ningún otro repositorio de código. Otra ventaja de este script es que puede cargar archivos en un solo repositorio de GitHub, en un directorio con el nombre del servidor, en lugar de requerir un repositorio separado para cada servidor.

El script usa la biblioteca PyGitHub , así que comencemos instalando eso, así como la biblioteca Yaml.

pip3 install PyGithub

Obtención de un token API de GitHub

Para usar el siguiente script, primero deberá obtener un token de API de GitHub. En su cuenta de GitHub, vaya a "Configuración" y "Configuración del desarrollador", y luego haga clic en "token de acceso personal" y cree un nuevo token. Según este artículo , seleccione "repo" como ámbito.

Definitivamente necesita proteger ese token de acceso personal, ya que permite el acceso a su cuenta, no solo a cualquier repositorio en particular. Para esto, decidí usar la biblioteca RSA-crypto para encriptarlo. El siguiente código instalará la biblioteca, creará un nuevo conjunto de claves RSA y cifrará el token de acceso personal en un archivo de configuración como un valor de la opción denominada ghe_token . Consulte Cifrado RSA de clave pública/privada y Cifrado de Python para obtener detalles adicionales sobre el uso de esa biblioteca y la herramienta de línea de comandos asociada.

Primero crearemos un nuevo cifrado, asumiendo que aún no tiene uno, y luego estableceremos el valor del token API de GitHub obtenido anteriormente como una entrada (opción) denominada ghe_token en el archivo de configuración.

pip3 install rsa-crypto 
 
rsa_crypto create
Enter key password: 
Re-enter key password: 
Creating key...
Created password-protected private/public keys file /Users/me/rsa_key.bin
Use the "extract" keyword to create public and private key files.
 
rsa_crypto set -o ghe_token
Using key: /Users/me/rsa_key.bin
Opening encrypted key /Users/me/rsa_key.bin
Enter key password: 
Enter value: 
set section:  option:ghe_token
Updated /Users/me/.rsa_values.conf

El script de Python se configurará para leer y descifrar la "opción" de configuración ghe_token para recuperar y usar el token de acceso personal. Si esto parece demasiado complejo, siéntase libre de encontrar una solución diferente, pero nunca incruste ningún token de acceso directamente en su secuencia de comandos, ¡existe una alta probabilidad de que termine en GitHub para que cualquiera lo vea y lo use!

El guión de Python

El código fuente también está disponible en GitHub.

Las variables de configuración son bastante sencillas, file_list contiene la lista de archivos para enviar a GitHub si existen localmente.

La variable ghe_repo debe apuntar a su repositorio de GitHub. Le sugiero encarecidamente que utilice un repositorio diferente con el fin de realizar una copia de seguridad de los archivos de configuración y un repositorio diferente si utiliza el script de administración de servidores distribuidos con GitHub .

La variable ghe_branch le permite apuntar a una rama diferente, dependiendo de cómo use GitHub.

La variable remote_base_path existe solo debido a un error anterior en la biblioteca PyGitHub donde la ruta remota tenía que comenzar con /, pero este ya no es el caso.

El script creará un archivo de registro y también intentará determinar el nombre del usuario que ejecuta el script para incluirlo como comentario de confirmación.

#!/usr/bin/env python
# -*- coding: utf-8 -*-
from __future__ import (absolute_import, division,
                        print_function, unicode_literals)
 
__author__ = "Christophe Gauge"
__version__ = "1.0.4"
 
'''
Backup HAproxy and nginx configuration files.
'''
 
 
# I M P O R T S ###############################################################
 
 
import github
import os
import sys
import time
import io
import logging
import traceback
import hashlib
import rsa_crypto
import argparse
import socket
import getpass
import base64
if (sys.version_info > (3, 0)):
    from urllib.parse import urljoin
else:
    from urlparse import urljoin
 
 
# G L O B A L S ###############################################################
 
file_list = ['/tmp/test.txt', '/etc/haproxy/haproxy.cfg', '/etc/nginx/nginx.conf']
 
# # Uncomment if you are using your own internal GitHub Repository
# ghe_organization = 'my_ghe_repo'
# ghe_hostname = mydomain.com
 
ghe_repo = 'Christophe-Gauge/GitHub'
remote_base_path = ''
 
ghe_branch = 'main'
 
logger = logging.getLogger()
logging.basicConfig(level=logging.INFO)
logger.info("Path:    %s" % (os.path.realpath(__file__)))
logger.info("Version:  %s" % (__version__))
 
args = argparse.Namespace(option='ghe_token')
ghe_token = rsa_crypto.decrypt_value(args)
 
 
# C O D E #####################################################################
 
 
def main():
    """Main function."""
    global logger
    global repo
    global args
 
    # Get the server name to use as a directory in GitHub
    server_name = socket.gethostname().split('.')[0].lower()
    # Get the username to log who made the change, nobody will be a Cron task or such
    try:
        user_name = getpass.getuser()
    except Exception as e:
        user_name = None
    if user_name is None:
        try:
            user_name = os.getlogin()
        except OSError as e:
            user_name = 'nobody'
        except Exception as e:
            user_name = 'unknown'
 
    gh = github.Github(login_or_token=ghe_token)
    repo = gh.get_repo(ghe_repo)
 
    # # Uncomment if you are using your own internal GitHub Repository
    # gh = github.Github(base_url=f"https://{ghe_hostname}/api/v3", login_or_token=ghe_token)
    # org = gh.get_organization(ghe_organization)
    # repo = org.get_repo(ghe_repo)
 
    for local_file_name in file_list:
        file_content = ''
        if os.path.exists(local_file_name):
            logger.info('File %s exists, processing.' % local_file_name)
            # Redacting HAproxy auth passwords, more may be needed for your use-case
            with io.open(local_file_name, "r", encoding="utf-8") as f:
                for line in f:
                    if 'auth' in line:
                        file_content += line[:line.find('auth')] + 'auth <REMOVED>\n'
                    else:
                        file_content += line
            # print(file_content)
 
            data = file_content.encode('utf-8', 'ignore')
            filesize = len(data)
            content = "blob " + str(filesize) + "\0" + data.decode('utf-8')
            encoded_content = content.encode('utf-8')
            localSHA = hashlib.sha1(encoded_content).hexdigest()
 
            remote_file_name = urljoin(remote_base_path, server_name + '/' + os.path.basename(local_file_name))
            logger.info(f"Saving local file {local_file_name} to remote GitHub repo {repo.full_name} file {remote_file_name}")
 
            try:
                remoteSHA = repo.get_contents(remote_file_name, ref=ghe_branch).sha
            except github.UnknownObjectException as e:
                logger.error(f"Remote file not found {remote_file_name}")
                remoteSHA = None
            except Exception as e:
                logger.error("Error {0}".format(str(e)))
                logger.error(traceback.format_exc())
                remoteSHA = None
 
            if remoteSHA == localSHA:
                logger.info('Remote file is present, hash is the same, NOT updating.')
                continue
            else:
                try:
                    if remoteSHA is None:
                        logger.info('Remote file file is NOT present, creating new file')
                        repo.create_file(remote_file_name, f"Updated by {user_name}", data, branch=ghe_branch)
                    else:
                        logger.info('Remote file file is present but hash has changed, updating file')
                        repo.update_file(remote_file_name, f"Updated by {user_name}", data, remoteSHA, branch=ghe_branch)
 
                except Exception as e:
                    logger.error("Error {0}".format(str(e)))
                    logger.error(traceback.format_exc())
                logger.info('Done updating GitHub')
 
        else:
            logger.warning('File does not exist %s' % local_file_name)
 
    logger.info('*** DONE ***')
    sys.exit(0)
 
###############################################################################
 
 
if __name__ == "__main__":
    main()
 
# E N D   O F   F I L E #######################################################
Comentarios publicados: 0