Conecta aplicaciones a instancias mediante SSH

Si usas cuentas de servicio en las instancias para ejecutar tareas automatizadas y, además, interactuar con otras API de Google Cloud Platform, esa cuenta de servicio también puede requerir acceso SSH a otras instancias de Compute Engine. En este instructivo, se muestra cómo configurar apps para acceder a tus instancias a través de conexiones SSH. La app de muestra de este instructivo usa una cuenta de servicio y acceso a SO para la administración de Llaves SSH.

Para omitir el ejercicio y ver la muestra de código completo, visita la página de GitHub GoogleCloudPlatform/python-docs-samples.

Objetivos

En este instructivo, aprenderás a lograr los siguientes objetivos:

  • Crear una cuenta de servicio y configurarla con el objetivo de proporcionar acceso SSH administrado con acceso a SO para las aplicaciones que se conectan a tus instancias
  • Crear una instancia asociada con tu cuenta de servicio
  • Configurar la app de muestra en tu instancia para que use la cuenta de servicio a fin de administrar sus propias Llaves SSH y establecer conexiones SSH.
  • Ejecutar la app en una instancia en la que esté asociada la cuenta de servicio
  • Ejecutar la app fuera de Compute Engine, donde debe proporcionar la clave de la cuenta de servicio de forma manual y especificar parámetros SSH adicionales.

Costos

En este instructivo, se usan componentes facturables de GCP, incluido Compute Engine.

Los usuarios nuevos de GCP podrían cumplir con los requisitos para obtener una prueba gratuita.

Antes de comenzar

  1. Accede a tu Cuenta de Google.

    Si todavía no tienes una cuenta, regístrate para obtener una nueva.

  2. Selecciona o crea un proyecto de GCP.

    Ir a la página Administrar recursos

  3. Asegúrate de tener habilitada la facturación para tu proyecto.

    Aprende a habilitar la facturación

  4. Tu cuenta de usuario debe tener permiso para crear, borrar y modificar varios recursos de Compute Engine. En este instructivo, se supone que tienes las siguientes funciones de IAM para tu proyecto:
    • compute.instanceAdmin.v1
    • compute.networkAdmin
    • compute.osAdminLogin
    • iam.serviceAccountAdmin
    • iam.serviceAccountKeyAdmin
    • iam.serviceAccountUser
  5. En este instructivo, se supone que usas Cloud Shell para ejecutar los comandos de la herramienta de línea de comandos de gcloud.

Crea y configura la cuenta de servicio y las instancias de ejemplo

En este instructivo, se usa una cuenta de servicio y dos instancias para mostrar cómo tus apps pueden ejecutar comandos SSH en instancias remotas.

Usa los siguientes comandos para configurar el entorno de prueba:

  1. Abre Cloud Shell en la consola:

    Abrir Cloud Shell

  2. Exporta una variable de entorno a fin de establecer el ID del proyecto para futuros comandos:

    export PROJECT_ID='[PROJECT_ID]'
    
  3. Crea una cuenta de servicio nueva en el proyecto. Para este ejemplo, crea una cuenta de servicio llamada ssh-account:

    gcloud iam service-accounts create ssh-account --project $PROJECT_ID \
       --display-name "ssh-account"
    
  4. Crea una red temporal llamada ssh-example para usarla solo en este ejemplo:

    gcloud compute networks create ssh-example --project $PROJECT_ID
    
  5. Crea una regla de firewall que habilite todas las conexiones SSH a las instancias de la red ssh-example:

    gcloud compute firewall-rules create ssh-all --project $PROJECT_ID \
       --network ssh-example --allow tcp:22
    
  6. Crea una instancia en us-central1-f llamada target. Esta instancia sirve como la instancia remota a la que se conectará tu cuenta de servicio a través de SSH. La instancia debe tener habilitado el acceso a SO a nivel del proyecto o de la instancia. En este ejemplo, se muestra cómo usar la marca --metadata para habilitar el acceso a SO en esta instancia específica. Incluye las marcas --no-service-account y --no-scopes, ya que esta instancia no necesita ejecutar ninguna solicitud a la API para este ejemplo específico:

    gcloud compute instances create target --project $PROJECT_ID \
       --zone us-central1-f --network ssh-example \
       --no-service-account --no-scopes \
       --machine-type f1-micro --metadata=enable-oslogin=TRUE
    
  7. Otorga la función de IAM compute.osAdminLogin a la cuenta de servicio para que pueda establecer conexiones SSH específicamente con la instancia llamada target. La función compute.osAdminLogin también otorga a la cuenta de servicio privilegios de superusuario en la instancia. Aunque podrías otorgar esta función a nivel del proyecto para que se aplique a todas las instancias del proyecto, otorga la función específicamente a nivel de la instancia a fin de mantener los permisos limitados en este ejemplo:

    gcloud compute instances add-iam-policy-binding target \
       --project $PROJECT_ID --zone us-central1-f \
       --member serviceAccount:ssh-account@$PROJECT_ID.iam.gserviceaccount.com \
       --role roles/compute.osAdminLogin
    
  8. Crea una instancia en us-central1-f llamada source. Asocia la instancia con la cuenta de servicio ssh-account. Además, especifica el alcance cloud-platform, que se requiere para que la cuenta de servicio ejecute solicitudes a la API en esta instancia:

    gcloud compute instances create source \
       --project $PROJECT_ID --zone us-central1-f \
       --service-account ssh-account@$PROJECT_ID.iam.gserviceaccount.com  \
       --scopes https://www.googleapis.com/auth/cloud-platform \
       --network ssh-example --machine-type f1-micro
    

Ahora, la cuenta de servicio puede administrar sus propios pares de Llaves SSH y puede usar SSH para conectarse específicamente a la instancia target. Debido a que la instancia source está asociada con la cuenta de servicio ssh-account que creaste, las bibliotecas cliente de Cloud para Python pueden usar credenciales predeterminadas de la aplicación a fin de autenticarse como la cuenta de servicio y usar las funciones que otorgaste a esa cuenta de servicio con anterioridad.

A continuación, configura y ejecuta una app que pueda establecer una conexión SSH de una instancia a otra.

Ejecuta una app SSH en una instancia

Cuando las apps que se ejecutan en tus instancias requieren acceso SSH a otras instancias, puedes administrar los pares de Llaves SSH para la cuenta de servicio y ejecutar comandos SSH de manera programática. Para este ejemplo, ejecuta una app de muestra con el siguiente proceso:

  1. Conéctate a la instancia source con la herramienta de línea de comandos de gcloud:

    gcloud compute ssh source --project $PROJECT_ID --zone us-central1-f
    
  2. En la instancia source, instala pip y la biblioteca cliente de Python:

    my-username@source:~$ sudo apt update && sudo apt install python-pip -y && pip install --upgrade google-api-python-client
    
  3. Descarga la aplicación de muestra service_account_ssh.py de GoogleCloudPlatform/python-docs-samples:

    my-username@source:~$ curl -O https://raw.githubusercontent.com/GoogleCloudPlatform/python-docs-samples/master/compute/oslogin/service_account_ssh.py
    
  4. Ejecuta la app de muestra, que usa argparse para aceptar variables desde la línea de comandos. En este ejemplo, indica a la app que instale y ejecute cowsay en la instancia target. Para este comando, agrega el ID del proyecto de forma manual:

    my-username@source:~$ python service_account_ssh.py \
        --cmd 'sudo apt install cowsay -y && cowsay "It works!"' \
        --project [PROJECT_ID] --zone us-central1-f --instance target
    
    ⋮
    ___________
      It works!
    -----------
           \   ^__^
            \  (oo)\_______
               (__)\       )\/\
                   ||----w |
                   ||     ||
    
    

Si la aplicación se ejecuta de forma correcta, recibirás el resultado de la app cowsay. Puedes modificar la marca --cmd para incluir cualquier comando que desees. De manera alternativa, puedes escribir tu propia app que importe service_account_ssh.py y la llame de forma directa.

Ejecuta exit para desconectarte de la instancia source y volver a Cloud Shell.

Ejecuta una app SSH fuera de Compute Engine

En el ejemplo anterior, ejecutaste la app en una instancia de Compute Engine en la que las bibliotecas cliente de Cloud para Python podían usar credenciales predeterminadas de la aplicación a fin de usar la cuenta de servicio asociada con la instancia source. Si ejecutas esta app fuera de una instancia de Compute Engine, la biblioteca cliente no puede acceder a la cuenta de servicio ni a sus permisos, a menos que proporciones la clave de la cuenta de servicio de forma manual.

  1. Obtén la dirección IP externa de la instancia target que creaste con anterioridad en este instructivo. Puedes encontrar esta dirección en la consola en la página Instancias o ejecuta el siguiente comando desde la herramienta de línea de comandos de gcloud:

    gcloud compute instances describe target \
       --project $PROJECT_ID --zone us-central1-f
    
  2. Crea una clave de cuenta de servicio para la cuenta de servicio ssh-account que usaste en el ejemplo anterior y descarga el archivo de claves en tu estación de trabajo local.

  3. Copia la clave de la cuenta de servicio en el sistema en el que deseas ejecutar la muestra.

  4. Abre una terminal en el sistema en el que deseas ejecutar esta muestra.

  5. Configura la variable de entorno GOOGLE_APPLICATION_CREDENTIALS para que apunte a la ruta en la que se encuentra el archivo de claves .json de tu cuenta de servicio. Si su clave está en la carpeta Downloads, puedes establecer una variable de entorno como se muestra en el siguiente ejemplo:

    $ export GOOGLE_APPLICATION_CREDENTIALS="/home/user/Downloads/key.json"
    
  6. Instala los requisitos previos en este sistema:

    1. Instala Python y pip. En los sistemas basados en Debian, puedes usar apt para completar este paso:

      $ sudo apt update && sudo apt install python python-pip -y
      
    2. Usa pip a fin de instalar las bibliotecas cliente de Cloud para Python:

      $ pip install --upgrade google-api-python-client
      
  7. Descarga la app de muestra:

    $ curl -O https://raw.githubusercontent.com/GoogleCloudPlatform/python-docs-samples/master/compute/oslogin/service_account_ssh.py
    
  8. Ejecuta la app de muestra. Cuando ejecutas la app fuera de Compute Engine, el servidor de metadatos no está disponible, por lo que debes especificar el correo electrónico de la cuenta de servicio de forma manual. También debes especificar la dirección IP externa para la instancia target que obtuviste con anterioridad.

    $ python service_account_ssh.py \
        --cmd 'sudo apt install cowsay -y && cowsay "It works!"' \
        --account ssh-account@[PROJECT_ID].iam.gserviceaccount.com \
        --project [PROJECT_ID] --hostname [TARGET_EXTERNAL_IP]
    
    ⋮
    ___________
      It works!
    -----------
           \   ^__^
            \  (oo)\_______
               (__)\       )\/\
                   ||----w |
                   ||     ||
    
    

Si la aplicación se ejecuta de manera correcta, recibirás el resultado de la app cowsay.

Cómo funciona la app de muestra

La app de muestra service_account_ssh.py funciona mediante el siguiente proceso:

  1. Inicializa el objeto de la API de acceso a SO.
  2. Si no proporcionas la dirección de correo electrónico de la cuenta de servicio de forma manual, la app lee los metadatos de la instancia para identificar la cuenta de servicio asociada con la instancia. Si ejecutas esta app fuera de Compute Engine, debes proporcionar la dirección de la cuenta de servicio de forma manual.
  3. Llama al método create_ssh_key() a fin de generar una Llave SSH temporal para la cuenta de servicio en la instancia en la que se ejecuta este ejemplo y agrega la clave pública a la cuenta de servicio con un temporizador de vencimiento que puedes especificar.
  4. Llama al método getLoginProfile() desde la API de acceso a SO para obtener el nombre de usuario POSIX que usa la cuenta de servicio.
  5. Llama al método run_ssh() para ejecutar un comando SSH remoto como la cuenta de servicio.
  6. Imprime la respuesta desde el comando SSH remoto.
  7. Quita los archivos de Llave SSH temporales.
  8. El Acceso a SO quita los archivos de claves públicos de manera automática cuando pasa la fecha de vencimiento.
def main(cmd, project, instance=None, zone=None,
         oslogin=None, account=None, hostname=None):
    """Run a command on a remote system."""

    # Create the OS Login API object.
    oslogin = oslogin or googleapiclient.discovery.build('oslogin', 'v1')

    # Identify the service account ID if it is not already provided.
    account = account or requests.get(
        SERVICE_ACCOUNT_METADATA_URL, headers=HEADERS).text
    if not account.startswith('users/'):
        account = 'users/' + account

    # Create a new SSH key pair and associate it with the service account.
    private_key_file = create_ssh_key(oslogin, account)

    # Using the OS Login API, get the POSIX user name from the login profile
    # for the service account.
    profile = oslogin.users().getLoginProfile(name=account).execute()
    username = profile.get('posixAccounts')[0].get('username')

    # Create the hostname of the target instance using the instance name,
    # the zone where the instance is located, and the project that owns the
    # instance.
    hostname = hostname or '{instance}.{zone}.c.{project}.internal'.format(
        instance=instance, zone=zone, project=project)

    # Run a command on the remote instance over SSH.
    result = run_ssh(cmd, private_key_file, username, hostname)

    # Print the command line output from the remote instance.
    # Use .rstrip() rather than end='' for Python 2 compatability.
    for line in result:
        print(line.decode('utf-8').rstrip('\n\r'))

    # Shred the private key and delete the pair.
    execute(['shred', private_key_file])
    execute(['rm', private_key_file])
    execute(['rm', private_key_file + '.pub'])

if __name__ == '__main__':

    parser = argparse.ArgumentParser(
        description=__doc__,
        formatter_class=argparse.RawDescriptionHelpFormatter)
    parser.add_argument(
        '--cmd', default='uname -a',
        help='The command to run on the remote instance.')
    parser.add_argument(
        '--project',
        help='Your Google Cloud project ID.')
    parser.add_argument(
        '--zone',
        help='The zone where the target instance is locted.')
    parser.add_argument(
        '--instance',
        help='The target instance for the ssh command.')
    parser.add_argument(
        '--account',
        help='The service account email.')
    parser.add_argument(
        '--hostname',
        help='The external IP address or hostname for the target instance.')
    args = parser.parse_args()

    main(args.cmd, args.project, instance=args.instance, zone=args.zone,
         account=args.account, hostname=args.hostname)

El método create_ssh_key() genera un nuevo par de Llaves SSH. Luego, el método llama a users().importSshPublicKey() desde la API de acceso a SO para asociar la clave pública con la cuenta de servicio. El método users().importSshPublicKey() también acepta un valor de vencimiento, que indica durante cuánto tiempo permanece válida la clave pública.

def create_ssh_key(oslogin, account, private_key_file=None, expire_time=300):
    """Generate an SSH key pair and apply it to the specified account."""
    private_key_file = private_key_file or '/tmp/key-' + str(uuid.uuid4())
    execute(['ssh-keygen', '-t', 'rsa', '-N', '', '-f', private_key_file])

    with open(private_key_file + '.pub', 'r') as original:
        public_key = original.read().strip()

    # Expiration time is in microseconds.
    expiration = int((time.time() + expire_time) * 1000000)

    body = {
        'key': public_key,
        'expirationTimeUsec': expiration,
    }
    oslogin.users().importSshPublicKey(parent=account, body=body).execute()
    return private_key_file

Como práctica recomendada, configura tus cuentas de servicio a fin de generar nuevos pares de claves para ellas con regularidad. En este ejemplo, la cuenta de servicio crea un par de claves nuevo para cada conexión SSH que establece, pero puedes modificar esto a fin de que se ejecute en un horario que se adapte mejor las necesidades de tu app.

El cuerpo de la solicitud para users().importSshPublicKey() incluye el valor expirationTimeUsec, que le indica al acceso a SO cuándo debe caducar la clave. Cada cuenta puede tener solo hasta 32 KB de datos de Llave SSH, por lo que es mejor configurar tus Llaves SSH públicas para que caduquen poco después de que tu cuenta de servicio haya completado sus operaciones.

Después de que tu cuenta de servicio configure las Llaves SSH, podrá ejecutar comandos remotos. En este ejemplo, la app usa el método run_ssh() para ejecutar un comando en una instancia remota y mostrar el resultado del comando.

def run_ssh(cmd, private_key_file, username, hostname):
    """Run a command on a remote system."""
    ssh_command = [
        'ssh', '-i', private_key_file, '-o', 'StrictHostKeyChecking=no',
        '{username}@{hostname}'.format(username=username, hostname=hostname),
        cmd,
    ]
    ssh = subprocess.Popen(
        ssh_command, shell=False, stdout=subprocess.PIPE,
        stderr=subprocess.PIPE)
    result = ssh.stdout.readlines()
    return result if result else ssh.stderr.readlines()

Realiza una limpieza

Sigue estos pasos para evitar que se apliquen cargos a tu cuenta de Google Cloud Platform por los recursos que usaste en este instructivo:

Usa los siguientes comandos para limpiar los recursos en tu entorno de prueba:

  1. Abre Cloud Shell en la consola:

    Abrir Cloud Shell

  2. Borra la instancia llamada source:

    gcloud compute instances delete source \
       --project $PROJECT_ID --zone us-central1-f
    
  3. Borra la instancia llamada target:

    gcloud compute instances delete target \
       --project $PROJECT_ID --zone us-central1-f
    
  4. Borra la cuenta de servicio ssh-account:

    gcloud iam service-accounts delete ssh-account --project $PROJECT_ID
    
  5. Borra la red llamada ssh-example:

    gcloud compute networks delete ssh-example --project $PROJECT_ID
    

Pasos siguientes

¿Te sirvió esta página? Envíanos tu opinión:

Enviar comentarios sobre…

Documentación de Compute Engine