Como conectar aplicativos a instâncias usando SSH

Se você usa contas de serviço em suas instâncias para executar tarefas automatizadas e interagir com outras APIs do Google Cloud Platform, essas contas também podem exigir acesso SSH a outras instâncias do Compute Engine. Neste tutorial, demonstramos como configurar aplicativos para acessar instâncias por meio de conexões SSH. O aplicativo de amostra neste tutorial usa uma conta de serviço e o login do SO para gerenciamento de chaves SSH.

Para pular o exercício e ver a amostra de código completa, visite a página GoogleCloudPlatform/python-docs-samples do GitHub.

Objetivos

Neste tutorial, ensinamos como alcançar os seguintes objetivos:

  • Criar uma conta de serviço e configurá-la para fornecer acesso SSH do login do SO para aplicativos que se conectam a suas instâncias.
  • Criar uma instância associada à conta de serviço.
  • Configurar o aplicativo de amostra na instância para usar a conta de serviço para gerenciar as próprias chaves SSH e estabelecer conexões SSH.
  • Executar o aplicativo em uma instância em que a conta de serviço está associada.
  • Executar o aplicativo fora do Compute Engine, quando é necessário fornecer manualmente a chave da conta de serviço e especificar parâmetros SSH adicionais.

Custos

Neste tutorial, há componentes do Cloud Platform passíveis de cobrança, incluindo o Google Compute Engine.

Novos usuários do Cloud Platform podem estar qualificados para uma avaliação gratuita.

Antes de começar

  1. Faça login na sua Conta do Google.

    Se você ainda não tiver uma, inscreva-se.

  2. Selecione ou crie um projeto do Google Cloud Platform.

    Acessar a página Gerenciar recursos

  3. Verifique se o faturamento foi ativado no projeto do Google Cloud Platform.

    Saiba como ativar o faturamento

  4. A conta de usuário precisa ter permissão para criar, excluir e modificar vários recursos do Compute Engine. Para este tutorial, presumimos que você tenha os seguintes papéis do IAM para o projeto:
    • compute.instanceAdmin.v1
    • compute.networkAdmin
    • compute.osAdminLogin
    • iam.serviceAccountAdmin
    • iam.serviceAccountKeyAdmin
    • iam.serviceAccountUser
  5. Neste tutorial, presumimos que você use o Cloud Shell para executar comandos gcloud.

Criar e configurar a conta de serviço e as instâncias de exemplo

Neste tutorial, usamos uma conta de serviço e duas instâncias para demonstrar como os aplicativos podem executar comandos SSH em instâncias remotas.

Use os comandos a seguir para configurar o ambiente de teste:

  1. Abra o Cloud Shell no console:

    Abrir o Cloud Shell

  2. Exporte uma variável de ambiente para definir o código do projeto para comandos futuros:

    export PROJECT_ID='[PROJECT_ID]'
    
  3. Crie uma nova conta de serviço no projeto. Para este exemplo, crie uma conta de serviço chamada ssh-account:

    gcloud iam service-accounts create ssh-account --project $PROJECT_ID \
       --display-name "ssh-account"
    
  4. Crie uma rede temporária chamada ssh-example para usar apenas neste exemplo:

    gcloud compute networks create ssh-example --project $PROJECT_ID
    
  5. Crie uma regra de firewall que permita todas as conexões SSH a instâncias na rede ssh-example:

    gcloud compute firewall-rules create ssh-all --project $PROJECT_ID \
       --network ssh-example --allow tcp:22
    
  6. Crie uma instância em us-central1-f chamada target. Essa instância serve como a instância remota à qual a conta de serviço se conectará por meio do SSH. A instância precisa ter o login do SO ativado para envolvidos no projeto ou no nível da instância. Neste exemplo, demonstramos como usar a sinalização --metadata para ativar o login do SO nessa instância específica. Inclua as sinalizações --no-service-account e --no-scopes porque essa instância não precisa executar nenhuma solicitação de API para este exemplo 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. Conceda o papel compute.osAdminLogin do IAM à conta de serviço para que ela possa estabelecer conexões SSH especificamente com a instância denominada target. O papel compute.osAdminLogin também concede privilégios de superusuário da sua conta de serviço na instância. É possível conceder esse papel para envolvidos no projeto de modo que ele seja aplicado a todas as instâncias do projeto, mas é melhor conceder o papel especificamente no nível da instância para manter as permissões limitadas neste exemplo:

    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. Crie uma instância em us-central1-f chamada source. Associe a instância à conta de serviço ssh-account. Além disso, especifique o escopo cloud-platform, que é necessário para a conta de serviço executar solicitações de API nesta instância:

    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
    

A conta de serviço agora pode gerenciar os próprios pares de chaves SSH e usar o SSH para se conectar especificamente à instância target. Como a instância source está associada à conta de serviço ssh-account criada, a biblioteca de cliente do Python pode usar Application Default Credentials para autenticação como conta de serviço e usar os papéis que você concedeu a essa conta de serviço anteriormente.

Em seguida, configure e execute um aplicativo que possa usar o SSH entre as instâncias.

Executar um aplicativo SSH em uma instância

Quando aplicativos em execução em suas instâncias exigem acesso SSH a outras instâncias, é possível gerenciar os pares de chaves SSH para a conta de serviço e executar comandos SSH de maneira programática. Para este exemplo, execute um aplicativo de amostra usando o processo a seguir:

  1. Conecte-se à instância source usando a ferramenta de linha de comando gcloud:

    gcloud compute ssh source --project $PROJECT_ID --zone us-central1-f
    
  2. Na instância source, instale o pip e a biblioteca de cliente do Python:

    my-username@source:~$ sudo apt update && sudo apt install python-pip -y && pip install --upgrade google-api-python-client
    
  3. Faça o download do aplicativo de exemplo service_account_ssh.py em GoogleCloudPlatform/python-docs-samples (em inglês):

    my-username@source:~$ curl -O https://raw.githubusercontent.com/GoogleCloudPlatform/python-docs-samples/master/compute/oslogin/service_account_ssh.py
    
  4. Execute o aplicativo de amostra, que usa argparse para aceitar variáveis da linha de comando. Neste exemplo, instrua o aplicativo a instalar e executar o cowsay na instância target. Para este comando, adicione o código do projeto manualmente:

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

Se o aplicativo for executado corretamente, você receberá a saída do aplicativo cowsay. É possível modificar a sinalização --cmd para incluir qualquer comando pretendido. Como alternativa, é possível escrever seu próprio aplicativo que importa service_account_ssh.py e o chama diretamente.

Execute exit para se desconectar da instância source e retornar ao Cloud Shell.

Executar um aplicativo SSH fora do Compute Engine

No exemplo anterior, você executou o aplicativo em uma instância do Compute Engine em que a biblioteca de cliente do Python podia usar Application Default Credentials para utilizar a conta de serviço associada à instância source. Se o aplicativo for executado fora de uma instância do Compute Engine, a biblioteca de cliente não poderá acessar a conta de serviço e as permissões correspondentes, a menos que você forneça a chave da conta de serviço manualmente.

  1. Consiga o endereço IP externo da instância target que você criou anteriormente neste tutorial. É possível encontrar esse endereço na página Instâncias do Console ou executando o comando a seguir na ferramenta de linha de comando gcloud:

    gcloud compute instances describe target \
       --project $PROJECT_ID --zone us-central1-f
    
  2. Crie uma chave de conta de serviço para ssh-account, que você usou no exemplo anterior, e faça o download do arquivo de chave na estação de trabalho local.

  3. Copie a chave da conta de serviço para o sistema em que você quer executar este exemplo.

  4. Abra um terminal no sistema em que você quer executar este exemplo.

  5. Defina a variável de ambiente GOOGLE_APPLICATION_CREDENTIALS de modo que aponte para o caminho em que se encontra o arquivo .json da chave da conta de serviço. Se a chave está na pasta Downloads, é possível definir uma variável de ambiente como o exemplo a seguir:

    $ export GOOGLE_APPLICATION_CREDENTIALS="/home/user/Downloads/key.json"
    
  6. Instale os pré-requisitos neste sistema:

    1. Instale o Python e o pip (links em inglês). Em sistemas com base em Debian, é possível usar apt para concluir esta etapa:

      $ sudo apt update && sudo apt install python python-pip -y
      
    2. Use pip para instalar a biblioteca de cliente do Python:

      $ pip install --upgrade google-api-python-client
      
  7. Faça o download do aplicativo de amostra:

    $ curl -O https://raw.githubusercontent.com/GoogleCloudPlatform/python-docs-samples/master/compute/oslogin/service_account_ssh.py
    
  8. Execute o aplicativo de amostra. Quando você executa o aplicativo fora do Compute Engine, o servidor de metadados não fica disponível. Portanto, é preciso especificar o e-mail da conta de serviço manualmente. Também é preciso especificar o endereço IP externo para a instância de target que você pegou anteriormente.

    $ 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 |
                   ||     ||
    
    

Se o aplicativo for executado corretamente, você receberá a saída do aplicativo cowsay.

Como o aplicativo de amostra funciona

O aplicativo de amostra service_account_ssh.py opera usando o seguinte processo:

  1. Inicialize o objeto da API OS Login.
  2. Se você não fornecer o endereço de e-mail da conta de serviço manualmente, o aplicativo lerá os metadados da instância para identificar a conta de serviço associada à instância. Se você executar esse aplicativo fora do Compute Engine, será preciso fornecer o endereço da conta de serviço manualmente.
  3. Chame o método create_ssh_key() para gerar uma chave SSH temporária para a conta de serviço na instância em que este exemplo é executado e adicione a chave pública à conta de serviço com um timer de expiração que seja possível especificar.
  4. Chame o método getLoginProfile() da API OS Login para conseguir o nome de usuário POSIX usado pela conta de serviço.
  5. Chame o método run_ssh() para executar um comando SSH remoto como a conta de serviço.
  6. Imprima a resposta do comando SSH remoto.
  7. Remova os arquivos de chave SSH temporários.
  8. O login do SO remove automaticamente os arquivos de chave pública quando eles ultrapassam o horário de expiração.
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)

O método create_ssh_key() gera um novo par de chaves SSH. Depois, o método chama users().importSshPublicKey() da API OS Login para associar a chave pública à conta de serviço. O método users().importSshPublicKey() também aceita um valor de expiração, que indica por quanto tempo a chave pública permanece válida.

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ática recomendada, configure as contas de serviço para gerar regularmente novos pares de chaves para elas mesmas. Neste exemplo, a conta de serviço cria um novo par de chaves para cada conexão SSH que ela estabelece, mas é possível modificar isso para que seja executado em uma programação mais adequada às necessidades do aplicativo.

O corpo da solicitação para users().importSshPublicKey() inclui o valor expirationTimeUsec, que informa ao login do SO quando a chave expirará. Cada conta pode ter até 32 KB de dados de chave SSH. Portanto, é melhor configurar as chaves SSH públicas para que expirem logo após a conclusão da operação da conta de serviço.

Depois que a conta de serviço configura as chaves SSH dela, ela pode executar comandos remotos. Neste exemplo, o aplicativo usa o método run_ssh() para executar um comando em uma instância remota e retornar o resultado do 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()

Limpeza

Para evitar que os recursos usados neste tutorial sejam cobrados na conta do Google Cloud Platform, é possível fazer o seguinte:

Use os comandos a seguir para limpar os recursos no ambiente de teste:

  1. Abra o Cloud Shell no console:

    Abrir o Cloud Shell

  2. Exclua a instância denominada source:

    gcloud compute instances delete source \
       --project $PROJECT_ID --zone us-central1-f
    
  3. Exclua a instância denominada target:

    gcloud compute instances delete target \
       --project $PROJECT_ID --zone us-central1-f
    
  4. Exclua a conta de serviço ssh-account:

    gcloud iam service-accounts delete ssh-account --project $PROJECT_ID
    
  5. Exclua a rede chamada ssh-example:

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

A seguir

Esta página foi útil? Conte sua opinião sobre:

Enviar comentários sobre…

Documentação do Compute Engine