Connecter des applications à des instances à l'aide de SSH

Vos applications peuvent utiliser des comptes de service pour exécuter des tâches automatisées et interagir avec d'autres API Google Cloud. Permettre à vos applications de gérer leurs propres clés SSH et de se connecter à des instances peut être utile pour automatiser les processus de gestion du système. Ce tutoriel explique comment configurer des applications pour accéder à vos instances via des connexions SSH. L'exemple d'application présenté dans ce tutoriel utilise un compte de service et OS Login pour la gestion des clés SSH.

L'ensemble du code utilisé dans ce tutoriel est hébergé sur la page GitHub GoogleCloudPlatform/python-docs-samples.

Objectifs

Le tutoriel couvre les tâches suivantes :

  • Créer un compte de service et le configurer pour fournir un accès SSH OS Login aux applications qui se connectent à vos instances.
  • Créer une instance associée à votre compte de service.
  • Configurer l'exemple d'application sur votre instance pour utiliser le compte de service afin de gérer ses propres clés SSH et d'établir des connexions SSH.
  • Exécuter l'application sur une instance à laquelle le compte de service est associé.
  • Exécuter l'application en dehors d'une instance Compute Engine où vous devez indiquer manuellement la clé de compte de service et spécifier des paramètres SSH supplémentaires.

Coûts

Ce tutoriel utilise des composants facturables de Google Cloud, y compris Compute Engine.

Les nouveaux utilisateurs de Google Cloud peuvent bénéficier d'un essai gratuit.

Avant de commencer

  1. Connectez-vous à votre compte Google.

    Si vous n'en possédez pas déjà un, vous devez en créer un.

  2. Dans Google Cloud Console, sur la page de sélection du projet, sélectionnez ou créez un projet Google Cloud.

    Accéder à la page de sélection du projet

  3. Assurez-vous que la facturation est activée pour votre projet Cloud. Découvrez comment vérifier que la facturation est activée pour votre projet.

  4. Dans votre compte utilisateur personnel, obtenez les rôles IAM suivants pour votre projet :
    • compute.instanceAdmin.v1
    • compute.networkAdmin
    • compute.osAdminLogin
    • iam.serviceAccountAdmin
    • iam.serviceAccountKeyAdmin
    • iam.serviceAccountUser
  5. Découvrez comment utiliser Cloud Shell pour exécuter les commandes de l'outil de ligne de commande gcloud.

Créer et configurer le compte de service et les exemples d'instances

Créez un compte de service et deux instances à utiliser pour ce tutoriel. Vous utilisez le compte de service pour accorder l'accès SSH à votre application. Cette application se connectera d'une instance à l'autre via SSH.

Procédez comme suit pour configurer l'environnement de test :

  1. Ouvrez Cloud Shell dans la console :

    Ouvrir Cloud Shell

  2. Exportez une variable d'environnement pour définir votre ID de projet pour les commandes futures :

    export PROJECT_ID='[PROJECT_ID]'
    
  3. Créez un compte de service nommé ssh-account :

    gcloud iam service-accounts create ssh-account --project $PROJECT_ID \
       --display-name "ssh-account"
    
  4. Créez un réseau nommé ssh-example à utiliser pour ce tutoriel :

    gcloud compute networks create ssh-example --project $PROJECT_ID
    
  5. Créez une règle de pare-feu qui autorise toutes les connexions SSH aux instances du réseau ssh-example :

    gcloud compute firewall-rules create ssh-all --project $PROJECT_ID \
       --network ssh-example --allow tcp:22
    
  6. Créez une instance dans la zone us-central1-f nommée target. Cette instance sert d'instance distante à laquelle votre compte de service se connectera via SSH. Utilisez l'option --metadata pour activer OS Login sur cette instance spécifique. Incluez les options --no-service-account et --no-scopes, car cette instance n'a pas besoin d'exécuter de requête API pour cet exemple spécifique :

    gcloud compute instances create target --project $PROJECT_ID \
       --zone us-central1-f --network ssh-example \
       --no-service-account --no-scopes \
       --machine-type e2-micro --metadata=enable-oslogin=TRUE
    
  7. Accordez le rôle IAM compute.osAdminLogin au compte de service afin qu'il puisse établir spécifiquement des connexions SSH avec l'instance nommée target. Le rôle compute.osAdminLogin accorde également à votre compte de service les privilèges de super-utilisateur sur l'instance. Bien que vous puissiez accorder ce rôle au niveau du projet afin qu'il s'applique à toutes les instances de votre projet, accordez-le au niveau de l'instance pour contrôler l'accès SSH de manière plus précise. Vous pourrez accorder des autorisations supplémentaires à votre compte de service ultérieurement si vous constatez que vos applications requièrent l'accès à d'autres ressources dans votre projet :

    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. Créez une instance dans la zone us-central1-f nommée source. Associez l'instance au compte de service ssh-account. Spécifiez également le champ d'application de cloud-platform. Cette action est nécessaire pour que le compte de service exécute les requêtes API sur cette instance :

    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 e2-micro
    

Le compte de service peut désormais gérer ses propres paires de clés SSH et utiliser SSH pour se connecter spécifiquement à l'instance target. L'instance source étant associée au compte de service ssh-account que vous avez créé, les bibliothèques clientes Cloud pour Python peuvent utiliser les identifiants par défaut de l'application pour s'authentifier en tant que compte de service et utiliser les rôles que vous avez précédemment attribués à ce compte de service.

Ensuite, configurez et exécutez une application qui peut passer d'une instance à l'autre avec un accès SSH.

Exécuter une application SSH sur une instance

Lorsque les applications exécutées sur vos instances nécessitent un accès SSH à d'autres instances, vous pouvez gérer les paires de clés SSH de votre compte de service et exécuter des commandes SSH par programmation. Pour cet exemple, exécutez un exemple d'application en utilisant le processus suivant :

  1. Connectez-vous à l'instance source à l'aide de l'outil de ligne de commande gcloud :

    gcloud compute ssh source --project $PROJECT_ID --zone us-central1-f
    
  2. Sur l'instance source, installez pip et la bibliothèque cliente Python :

    my-username@source:~$ sudo apt update && sudo apt install python-pip -y && pip install --upgrade google-api-python-client
    
  3. Téléchargez l'exemple d'application service_account_ssh.py à partir 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. Exécutez l'exemple d'application, qui utilise argparse pour accepter les variables depuis la ligne de commande. Dans cet exemple, indiquez à l'application d'installer et d'exécuter cowsay sur l'instance target. Pour cette commande, ajoutez votre ID de projet manuellement :

    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 l'application s'exécute correctement, vous recevez le résultat de l'application cowsay. Vous pouvez modifier l'option --cmd pour inclure toutes les commandes que vous souhaitez. Vous pouvez également écrire votre propre application qui importe service_account_ssh.py et l'appelle directement.

Exécutez exit pour vous déconnecter de l'instance source et revenir à Cloud Shell.

Exécuter une application SSH en dehors de Compute Engine

Dans l'exemple précédent, vous avez exécuté l'application sur une instance Compute Engine sur laquelle les identifiants par défaut de l'application permettaient à la bibliothèque cliente Cloud pour Python d'utiliser le compte de service associé à l'instance source. Si vous exécutez cette application en dehors d'une instance Compute Engine, la bibliothèque cliente ne peut pas accéder au compte de service et à ses autorisations à moins que vous n'indiquiez manuellement la clé du compte de service.

  1. Obtenez l'adresse IP externe de l'instance target créée précédemment dans ce tutoriel. Vous pouvez trouver cette adresse dans la console sur la page Instances ou en exécutant la commande suivante à partir de l'outil de ligne de commande gcloud :

    gcloud compute instances describe target \
       --project $PROJECT_ID --zone us-central1-f
    
  2. Créez une clé de compte de service pour le compte de service ssh-account que vous avez utilisé dans l'exemple précédent et téléchargez le fichier de clé sur votre poste de travail local.

  3. Copiez la clé du compte de service sur le système sur lequel vous souhaitez exécuter cet exemple.

  4. Ouvrez un terminal sur le système sur lequel vous souhaitez exécuter cet exemple.

  5. Définissez la variable d'environnement GOOGLE_APPLICATION_CREDENTIALS pour qu'elle pointe vers le chemin où se trouve le fichier .json de clé de compte de service. Si la clé se trouve dans votre dossier Downloads, vous pouvez définir une variable d'environnement semblable à l'exemple suivant :

    $ export GOOGLE_APPLICATION_CREDENTIALS="/home/user/Downloads/key.json"
    
  6. Installez les prérequis sur ce système :

    1. Installez Python et pip. Sur les systèmes basés sur Debian, vous pouvez utiliser apt pour effectuer cette étape :

      $ sudo apt update && sudo apt install python python-pip -y
      
    2. Utilisez pip pour installer les bibliothèques clientes Cloud pour Python :

      $ pip install --upgrade google-api-python-client
      
  7. Téléchargez l'exemple d'application suivant :

    $ curl -O https://raw.githubusercontent.com/GoogleCloudPlatform/python-docs-samples/master/compute/oslogin/service_account_ssh.py
    
  8. Exécutez l'exemple d'application. Lorsque vous exécutez l'application en dehors de Compute Engine, le serveur de métadonnées n'est pas disponible. Vous devez donc spécifier manuellement l'adresse e-mail du compte de service. Vous devez également spécifier l'adresse IP externe de l'instance target obtenue précédemment.

    $ 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 l'application s'exécute correctement, vous recevrez le résultat de l'application cowsay.

Fonctionnement de l'exemple d'application

Voici comment se déroule l'exécution de l'exemple d'application service_account_ssh.py :

  1. Initialisation de l'objet API OS Login.
  2. Si vous n'indiquez pas l'adresse e-mail du compte de service manuellement, l'application lit les métadonnées de l'instance pour identifier le compte de service associé à l'instance. Si vous exécutez cette application en dehors de Compute Engine, vous devez indiquer l'adresse du compte de service manuellement.
  3. Appelez la méthode create_ssh_key() pour générer une clé SSH temporaire pour le compte de service sur l'instance sur laquelle cet exemple est exécuté et ajoutez la clé publique au compte de service avec un timer d'expiration que vous pouvez spécifier.
  4. Appelez la méthode getLoginProfile() à partir de l'API OS Login pour obtenir le nom d'utilisateur POSIX utilisé par le compte de service.
  5. Appelez la méthode run_ssh() pour exécuter une commande SSH distante en tant que compte de service.
  6. Imprimez la réponse à partir de la commande SSH distante.
  7. Supprimez les fichiers de clé SSH temporaires.
  8. Suppression automatique des fichiers de clé publique par OS Login, lorsqu'ils dépassent le délai d'expiration.
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 located.')
    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)

La méthode create_ssh_key() génère une nouvelle paire de clés SSH. Ensuite, la méthode appelle users().importSshPublicKey() à partir de l'API OS Login pour associer la clé publique au compte de service. La méthode users().importSshPublicKey() accepte également une valeur d'expiration, qui indique la durée pendant laquelle la clé publique reste valide.

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

Il est recommandé de configurer vos comptes de service afin qu'ils génèrent régulièrement de nouvelles paires de clés pour eux-mêmes Dans cet exemple, le compte de service crée une nouvelle paire de clés pour chaque connexion SSH qu'il établit, mais vous pouvez choisir un calendrier spécifique répondant mieux aux besoins de votre application.

Le corps de la requête pour users().importSshPublicKey() inclut la valeur expirationTimeUsec, qui indique à OS Login le moment où la clé doit expirer. Chaque compte ne pouvant contenir que 32 Ko de données maximum de clé SSH, il est préférable de configurer vos clés SSH publiques de manière à ce qu'elles expirent peu de temps après la fin des opérations de votre compte de service.

Une fois que votre compte de service a configuré ses clés SSH, il peut exécuter des commandes à distance. Dans cet exemple, l'application utilise la méthode run_ssh() pour exécuter une commande sur une instance distante et renvoyer le résultat de la commande.

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()

Effectuer un nettoyage

Pour éviter que les ressources utilisées dans ce tutoriel soient facturées sur votre compte Google Cloud Platform :

  1. Ouvrez Cloud Shell dans la console :

    Ouvrir Cloud Shell

  2. Supprimez l'instance nommée source :

    gcloud compute instances delete source \
       --project $PROJECT_ID --zone us-central1-f
    
  3. Supprimez l'instance nommée target :

    gcloud compute instances delete target \
       --project $PROJECT_ID --zone us-central1-f
    
  4. Supprimez le compte de service ssh-account :

    gcloud iam service-accounts delete ssh-account --project $PROJECT_ID
    
  5. Supprimez le réseau nommé ssh-example :

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

Étapes suivantes