Skip to content

Agustin107x/Obligatorio-DevOps-2025

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

34 Commits
 
 
 
 
 
 
 
 

Repository files navigation

Obligatorio – Programación para DevOps

Introducción

Este trabajo obligatorio fue realizado en el marco de la materia Programación para DevOps de la carrera Analista en Infraestructura Informática (Universidad ORT Uruguay).

El objetivo principal es aplicar conocimientos de scripting y automatización tanto a nivel de sistema operativo Linux como en la interacción con servicios en la nube (AWS) mediante herramientas de línea de comandos y programación en Python.

A lo largo del desarrollo se integran distintas tecnologías:

  • Scripting en Bash para administración de archivos y generación de backups
  • Automatización con Python utilizando el SDK de AWS (boto3)
  • Gestión de servicios cloud: S3, EC2, RDS
  • Control de versiones con Git

El trabajo se divide en tres ejercicios, cada uno con un enfoque específico, pero todos alineados a los principios fundamentales de DevOps: automatización, repetibilidad, gestión segura de recursos y documentación.

Estructura del proyecto

El repositorio se organiza en subdirectorios por ejercicio, junto con carpetas auxiliares para manejar archivos sensibles o resultados generados durante la ejecución de los scripts.

.
├── ejercicio1Bash/
│   ├── ej1_encuentra_SetUID.sh       # Script Bash para búsqueda y backup de archivos setuid
│   └── resultados/                   # Carpeta donde se generan los backups .tar.gz
├── ejercicio2Python/
│   └── ej2python.py                  # Script Python para subir backups a S3
├── ejercicio3Python/
│   ├── ej3python.py                  # Script Python para crear una RDS y una EC2 que la consuma
│   └── script_data_base/
│       └── obli.sql                  # Script SQL de creación y carga de base de datos
│   └── secrets/                      # (Generado en tiempo de ejecución) Contiene archivo con contraseña de RDS
│       └──password.txt               # Generado automáticamente por ej3python.py
└── README.md                         # Documento principal de documentación del proyecto

Nota: La carpeta secrets/ y el archivo password.txt no están incluidos en el repositorio ya que se generan dinámicamente durante la ejecución del ejercicio 3 por razones de seguridad.

Ejercicio 1 – Bash: Búsqueda y Backup de archivos con SetUID

Objetivo

Realizar un respaldo de seguridad de archivos del sistema, específicamente, aquellos que son ejecutables, pertenecientes al usuario root, y tienen el permiso especial setuid activado.

Este script en Bash debe buscar de forma recursiva todos los archivos que cumplan las siguientes condiciones:

  • Son archivos regulares ejecutables
  • Pertenecen al usuario root
  • Tienen activado el permiso especial setuid
  • Poseen permiso de ejecución para root y otros usuarios

Además, debe permitir de forma opcional:

  • Generar un log con los caminos absolutos de los archivos encontrados (-c)
  • Filtrar solo scripts de Bash, es decir, que comiencen con #!/bin/bash (-b)
  • Indicar el directorio de búsqueda como parámetro opcional (por defecto se utiliza el directorio actual)

Uso

./ej1_encuentra_SetUID.sh [-c] [-b] [Directorio_donde_buscar]

Parámetros

-c           (opcional) Genera un archivo de log con las rutas absolutas encontradas
-b           (opcional) Filtra únicamente los archivos que sean scripts de Bash
Directorio   (opcional) Directorio donde se realiza la búsqueda. Si no se indica, se usa `.`

Requerimientos

  • Se debe estar ubicado dentro del subdirectorio ejercicio1Bash/ del repositorio para el correcto funcionamiento del script.
cd ejercicio1Bash/
./ej1_encuentra_SetUID.sh [-b] [-c] [ruta]
  • El script requiere permisos para acceder a archivos propiedad de root. Se recomienda ejecutarlo como sudo si se desea un análisis completo del sistema.

Explicación del funcionamiento

El script realiza un backup de archivos críticos del sistema que cumplen condiciones específicas (detallada más arriba en la sección Objetivo). Su funcionamiento puede resumirse en los siguientes pasos:

  1. Validación de parámetros recibidos:

    • Controla que se ingresen como máximo 3 argumentos
    • Acepta los parámetros -c y -b, sin repeticiones
    • Verifica que, si se indica una ruta, esta exista, sea un directorio y tenga permisos de lectura y ejecución
  2. Búsqueda de archivos:

    • Utiliza find para buscar archivos tipo regular, propiedad de root, con permiso setuid y ejecución habilitada
    • Obtiene sus rutas absolutas con readlink -f
  3. Filtrado opcional (-b):

    • Si se indicó -b, se filtran solo los archivos cuya primera línea es #!/bin/bash
  4. Log y Backup:

    • Si se indicó -c, se crea un archivo .rep con todas las rutas encontradas
    • Se copian los archivos encontrados a una carpeta temporal
    • Se genera un archivo comprimido .tar.gz en la carpeta resultados/ del proyecto
    • Se elimina la carpeta temporal de trabajo
  5. Nombres generados automáticamente:

    • Todos los archivos (log, tar.gz) incluyen fecha y hora para evitar sobreescrituras y facilitar auditorías

Códigos de error

A continuación se detallan los códigos de error definidos en el script para diferentes situaciones:

Código | Descripción
-------|-----------------------------------------------
0      | Script finalizó como se esperaba
1      | Cantidad de parámetros recibidos incorrecto
2      | Parámetro repetido
3      | Parámetro inválido
4      | Se ingresaron dos rutas o parámetros no válidos
5      | La ruta no existe
6      | La ruta no es un directorio válido
7      | No se puede acceder al directorio. Permisos denegados
8      | No se encontraron archivos que cumplan los criterios

Script completo

#!/bin/bash
 
hacerLog=0
esBash=0
rutaBuscar=""
contadorRuta=0
 
#Valido cantidad de parametros
if [ $# -gt 3 ]
then
    echo "Error: Cantidad de parámetros recibidos incorrecto" >&2
    echo "Uso: $0 [-c] [-b] [RUTA]" >&2
    exit 1
fi
 
#Analizo parametros activos
for parametro in "$@"; do
    case "$parametro" in
        -c)
            if [ $hacerLog -eq 1 ]
            then
                echo "Error: Parámetro "$parametro" repetido" >&2
                echo "Uso: $0 [-c] [-b] [RUTA]" >&2
                exit 2
            fi
            hacerLog=1
            ;;
        -b)
            if [ $esBash -eq 1 ]
            then
                echo "Error: Parámetro "$parametro" repetido" >&2
                echo "Uso: $0 [-c] [-b] [RUTA]" >&2
                exit 2
            fi
            esBash=1
            ;;
        -*)
            echo "Error: Parámetro inválido '$parametro'" >&2
            echo "Uso: $0 [-c] [-b] [RUTA]" >&2
            exit 3
            ;;
        *)
            if [ "$contadorRuta" -eq 1 ]
            then
                echo "Error: Se ingresaron dos rutas o parámetros no válidos" >&2
                echo "Uso: $0 [-c] [-b] [RUTA]" >&2
                exit 4
            fi
            rutaBuscar=$parametro
            contadorRuta=$((contadorRuta + 1))
            ;;
    esac
done

# Si no se especificó una ruta, se usa el directorio actual
if [ -z "$rutaBuscar" ]
then
    rutaBuscar="."
else
    # Se valida parámetro ruta
    if [ ! -e "$rutaBuscar" ] #Valido si la ruta existe
    then
        echo "Error: La ruta "$rutaBuscar" no existe" >&2
        exit 5
    elif [ ! -d "$rutaBuscar" ] #Valido si la ruta es un directorio
    then
        echo "Error: La ruta "$rutaBuscar" no es un directorio válido." >&2
        exit 6
    elif [ ! -r "$rutaBuscar" ] || [ ! -x "$rutaBuscar" ] #Valido si tengo permisos de lectura y ejecución
    then
        echo "Error: No se puede acceder al directorio "$rutaBuscar". Permisos denegados" >&2
        exit 7
    fi
fi
echo "Ruta a buscar: $rutaBuscar"
 
#Fecha que va a tener archivo de logs y archivo tar.gz si -c
fecha=$(date '+%d-%m-%y_%H-%M-%S')
 
#Lista de las rutas absolutas de archivos regulares, con owner root, permiso setuid y x (ejecutables) para usuario, grupo y otros activados
lista_ejecutables=$(find ${rutaBuscar} -type f -user root -perm -4101 -exec readlink -f {} \; 2>/dev/null)
 
#Filtrado según modificador -b
if [ ! $esBash -eq 1 ]
then
    lista_para_backup=${lista_ejecutables}
else
    for elemento in ${lista_ejecutables}
    do
        if head -n 1 ${elemento} | grep -q "^#!/bin/bash"
        then
            lista_para_backup+="${elemento}"$'\n'
        fi
    done
fi

if [ -z "$lista_para_backup" ]; then
    echo "Error: No se encontraron archivos que cumplan los criterios." >&2
    exit 8
fi

#Carpeta temporal para guardar archivos, y posterior tar.gz
carpTemporal="backupSetUID_${fecha}"
mkdir "./$carpTemporal"
 
#Copio los archivos encontrados a la carpeta temporal
for archivo in $lista_para_backup
do
    cp $archivo $carpTemporal
done
 
#Archivo de log en caso de ingresar modificador -c
if [ $hacerLog -eq 1 ]
then
    echo "$lista_para_backup" > logcaminos_${fecha}.rep
    mv "logcaminos_${fecha}.rep" "./$carpTemporal/"
    echo "Mensaje: Se escribió el archivo de log."
fi
 
#Creo archivo .tar.gz con los archivos encontrados
tar -czf "./resultados/backupSetUID_${fecha}.tar.gz" "./$carpTemporal"
echo "Mensaje: Se creó el backup backupSetUID_${fecha}.tar.gz."
 
#Elimino carpeta temporal
rm -rf "./$carpTemporal"
 
exit 0

Ejercicio 2 – Python: Generar un bucket S3 para subir respaldos

Objetivo

Desarrollar un script en Python que genere un Bucket S3 y suba los backup generados por el script del ejercicio 1.

  • Crear un bucket S3 cuyo nombre debe ser el-maligno-nro_de_estudiante1-nro_de_estudiante2
    (en nuestro caso: el-maligno-177294-321438).
  • Subir al bucket el archivo generado previamente por el script de Bash encargado de crear el backup (tar.gz).
  • El archivo debe subirse con el nombre:
    Log_dia-mes-año-hora-minuto-segundo (por ejemplo: Log_24-06-2025-13-57-22).

Uso

python3 ej2python.py

Requerimientos

  • Se debe estar ubicado dentro del subdirectorio ejercicio2Python/ del repositorio para el correcto funcionamiento del script.
cd ejercicio2Python/
python3 ej2python.py

Explicación del funcionamiento

El script de Python está diseñado para buscar y subir automáticamente el archivo de backup generado por el ejercicio 1 a un bucket S3.

  • Ubicación de los backups:
    El script busca los archivos .tar.gz dentro de la carpeta resultados, ubicada en el directorio ejercicio1Bash/. Por eso, es necesario ejecutar este script desde el subdirectorio ejercicio2Python/ para que las rutas relativas funcionen correctamente.

  • Búsqueda de archivos:
    Utiliza expresiones regulares para identificar el primer archivo cuyo nombre comience con backupSetUID dentro de la carpeta de backups.

  • Verificación de backups:
    Si no se encuentra ningún archivo de backup válido, el script termina con un mensaje de error y un código de salida.

  • Creación del bucket S3:
    El script verifica si el bucket S3 con el nombre correspondiente (el-maligno-177294-321438) ya existe. Si no existe, lo crea automáticamente.

  • Carga del backup:
    El archivo de backup encontrado se sube al bucket S3, asignándole un nombre en el formato Log_dia-mes-año_hora-min-seg.tar.gz.

Códigos de error

A continuación se detallan los códigos de error definidos en el script para diferentes situaciones:

Código | Descripción
-------|-----------------------------------------------
0      | Script finalizó como se esperaba
1      | No se detectaron archivos de backup para subir. Finaliza
2      | Error creando el bucket
3      | Error subiendo el backup al bucket

Código completo

import boto3
import datetime
import re
import os

# Ruta donde se está ejecutando el script
ruta_script = os.path.dirname(os.path.abspath(__file__))

# Ruta donde se encuentran los archivos de backup
directorio_backup = os.path.abspath(os.path.join(ruta_script, "../ejercicio1Bash/resultados"))

#Expresión regular para buscar archivos de backup
patron_backup = re.compile(r"backupSetUID.*")

backup_encontrado = ""

# Se recorre el directorio buscando el primer archivo que cumpla con la expresión regular
for archivo in os.listdir(directorio_backup):
    if patron_backup.match(archivo):
        backup_encontrado = os.path.join(directorio_backup, archivo)
        break

# Se verifica si se encontró un archivo válido
if backup_encontrado == "":
    print("Error: No se encontró ningún archivo de backup para subir.")
    exit (1)

s3_client = boto3.client('s3')

bucket_name = 'el-maligno-177294-321438'
ruta_backup = backup_encontrado
fecha_actual = datetime.datetime.now().strftime("%d-%m-%y_%H-%M-%S") # Se agrega hora para no repetir nombres de archivos
object_name = os.path.basename(backup_encontrado) # Me devuelve el nombre de archivo sin la ruta
nombre_subir = f"Log_{fecha_actual}.tar.gz" # Nombre a subir al bucket

# Se verifica que bucket exista
try:
    s3_client.head_bucket(Bucket=bucket_name)
    print("El bucket", bucket_name, "ya existe.")
    print("Subiendo backup...")
except Exception as e:
    try:
        s3_client.create_bucket(Bucket=bucket_name) # Se crea el bucket
        print("Bucket", bucket_name, "creado correctamente.")
        print("Subiendo backup...")
    except Exception as e:
        print("Error al crear el bucket:", e)
        exit (2)

# Subir archivo
try:
    s3_client.upload_file(ruta_backup, bucket_name, nombre_subir)
    print("Back up '" + object_name + "' subido correctamente como", nombre_subir + ".")
except Exception as e:
    print("Error al subir el archivo:", e)
    exit(3)

Ejercicio 3 – Python: Creación y consumo de base de datos en RDS desde EC2

Objetivo

El script crea una base de datos MySQL en RDS y una instancia EC2, y se encargan de cargar los datos iniciales en la base usando el archivo obli.sql

  • La instancia RDS deberá llamarse Maligno-DB
  • Se debe crear una instancia EC2 llamada Maligno-SRV
  • La instancia EC2 deberá tener instalado el cliente de MySQL para poder conectarse a la base
  • La base de datos deberá cargarse con los datos especificados en el archivo obli.sql desde la instancia EC2

Uso

python3 ej3python.py

Requerimientos

  • Se debe estar ubicado dentro del subdirectorio ejercicio3Python/ para ejecutar el script.
cd ejercicio3Python/
python3 ej3python.py

Explicación del funcionamiento

El script automatiza el aprovisionamiento completo de los recursos necesarios para inicializar una base de datos con contenido predefinido, y sigue este flujo:

  1. Solicitud de contraseña:

    • Solicita al usuario una contraseña para el usuario admin de la instancia RDS (no puede estar vacía).
    • La contraseña se guarda automáticamente en secrets/password.txt.
  2. Creación de grupos de seguridad:

    • Crea un grupo de seguridad para la base de datos RDS (MalignoSQL).
    • Crea un grupo de seguridad para la instancia EC2 (EC2MalignoSRV).
    • Configura la regla que permite a la EC2 acceder al puerto 3306 de la RDS.
  3. Creación de la instancia RDS:

    • Crea una instancia RDS con MySQL y espera a que esté disponible.
    • Recupera y guarda el endpoint para que pueda ser usado desde la EC2.
  4. Preparación del script SQL:

    • Lee el contenido del archivo obli.sql.
  5. Despliegue de la instancia EC2:

    • Genera una instancia Amazon Linux.
    • Usa user-data para:
      • Instalar cliente MySQL (mariadb1011-client-utils)
      • Crear el archivo obli.sql en /tmp/
      • Ejecutar el script sobre la base de datos remota
  6. Mensajes informativos:

    • Se imprimen mensajes de avance y confirmación por consola en cada paso.

La contraseña no se almacena en el código fuente. Se guarda exclusivamente en el archivo secrets/password.txt creado en la ejecución del script.

Códigos de error

Código | Descripción
-------|---------------------------------------------------------
0      | Script finalizó como se esperaba
1      | Error al crear grupo de seguridad de RDS
2      | Error al crear grupo de seguridad de EC2 o al asociarlo
3      | Error al crear la instancia EC2
4      | Error al crear la base de datos RDS

Código completo

import boto3
import os

ec2 = boto3.client('ec2')
security_group_name = "MalignoSQL"

try:
    response = ec2.create_security_group(
        GroupName=security_group_name,
        Description="Permitir acceso al puerto 3306 para MySQL desde EC2 Maligno-SRV",
    )
    security_group_id_MySQL = response['GroupId']
    print("Mensaje: Grupo de seguridad creado con ID:", security_group_id_MySQL)
except Exception as e:
    print("Error: Error al crear el grupo de seguridad:", e)
    exit(1)
    
security_group_name_ec2 = "EC2MalignoSRV"
try:
    response = ec2.create_security_group(
        GroupName=security_group_name_ec2,
        Description="Grupo de seguridad para la instancia EC2 y Acceso a MySQL"
    )
    security_group_id_ec2 = response['GroupId']
    print("Mensaje: Grupo de seguridad EC2 creado con ID:", security_group_id_ec2)
    
    ec2.authorize_security_group_ingress(
        GroupId=security_group_id_MySQL,
        IpPermissions=[
            {
                'IpProtocol': 'tcp',
                'FromPort': 3306,
                'ToPort': 3306,
                'UserIdGroupPairs': [
                    {
                        'GroupId': security_group_id_ec2
                    }
                ]
            }
        ]
    )
    print("Mensaje: Regla de seguridad para MySQL agregada al grupo de seguridad EC2.")
except Exception as e:
    print("Error: Error al crear el grupo de seguridad EC2:", e)
    exit(2)
    
# Crear la instancia RDS
rds = boto3.client('rds')
db_instance_identifier = 'Maligno-DB'

# Pido contraseña para la base de datos, no debe ser vacía
adminPassword=input("Ingrese la contraseña para el usuario admin de la base de datos RDS: ").strip()

# Valido que no esté vacía
while adminPassword == "":
    print("Error: La contraseña no puede estar vacía.")
    adminPassword=input("Ingrese la contraseña para el usuario admin de la base de datos RDS: ").strip()

# Creo carpeta secrets si no existe
os.makedirs("secrets", exist_ok=True)

# Guardar en archivo
with open("secrets/password.txt", "w") as f:
    f.write(adminPassword)

print("Mensaje: Contraseña guardada correctamente en 'secrets/password.txt'.")

#Creo la base de datos
try:
    response = rds.create_db_instance(
        DBInstanceIdentifier=db_instance_identifier,
        AllocatedStorage=20,
        DBInstanceClass='db.t3.micro',
        Engine='mysql',
        MasterUsername='admin',
        MasterUserPassword=adminPassword,
        VpcSecurityGroupIds=[security_group_id_MySQL]
    )
    print("Mensaje: Base de datos RDS creada con ID:" ,db_instance_identifier)
    
    #Espero a que la base de datos esté disponible
    print("Mensaje: Esperando a que la base de datos esté disponible...")
    waiter = rds.get_waiter('db_instance_available')
    waiter.wait(DBInstanceIdentifier=db_instance_identifier)
    db_instance = rds.describe_db_instances(DBInstanceIdentifier=db_instance_identifier)
    db_endpoint = db_instance['DBInstances'][0]['Endpoint']['Address']
    print("Mensaje: Base de datos RDS disponible en:", db_endpoint)
except Exception as e:
    print("Error: Error al crear la base de datos RDS:", e)

# Leo el contenido del archivo SQL
with open("script_data_base/obli.sql", "r", encoding="utf-8") as f:
    obli_sql = f.read()

# Crear la instancia EC2
script_sql = f'''#!/bin/bash
sudo dnf update -y
sudo dnf install -y mariadb1011-client-utils
sudo echo "Conexión a la base de datos RDS: {db_endpoint}" > /home/ec2-user/db_connection.txt
cat << 'EOF' > /tmp/obli.sql
{obli_sql}
EOF
sudo mysql -h {db_endpoint} -u admin -p{adminPassword} < /tmp/obli.sql
'''

try:
    response = ec2.run_instances(
        ImageId='ami-09e6f87a47903347c',  #Amazon Linux ami
        MinCount=1,
        MaxCount=1,
        InstanceType='t2.micro',
        SecurityGroupIds=[security_group_id_ec2],
        UserData=script_sql,
        IamInstanceProfile={
            'Name': 'LabInstanceProfile'
        }
    )
    
    instance_id = response['Instances'][0]['InstanceId']
    print("Mensaje: Instancia EC2 creada con ID:", instance_id)
    
    #Espero a que la EC2 esté disponible
    print("Mensaje: Esperando a que la instancia EC2 esté disponible...")
    waiter = ec2.get_waiter('instance_running')
    waiter.wait(InstanceIds=[instance_id])
    print("Mensaje: La instancia EC2 está en ejecución.")
except Exception as e:
    print("Error: Error al crear la instancia EC2:", e)
    exit(3)

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors