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, mostramos como configurar aplicativos para acessar instâncias por meio de conexões SSH. O aplicativo de amostra deste tutorial usa uma conta de serviço e Login do SO para realizar o 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 (em inglês) do GitHub.

Objetivos

Neste tutorial, ensinamos como alcançar os objetivos a seguir:

  • Crie uma conta de serviço e configure-a para fornecer Login do SO no acesso SSH para aplicativos que se conectam a suas instâncias.
  • Criar uma instância associada à conta de serviço.
  • Configure o aplicativo de amostra na instância para usar a conta de serviço para gerenciar suas próprias chaves SSH e estabelecer conexões SSH.
  • Execute o aplicativo em uma instância em que a conta de serviço esteja associada.
  • Execute o aplicativo fora do Compute Engine, em que é necessário fornecer a chave da conta de serviço manualmente e especificar outros parâmetros SSH.

Custos

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

Usuários novos do GCP 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 papéis do IAM a seguir para o projeto:
    • compute.instanceAdmin.v1
    • compute.networkAdmin
    • compute.osAdminLogin
    • iam.serviceAccountAdmin
    • iam.serviceAccountKeyAdmin
    • iam.serviceAccountUser
  5. Neste tutorial, presumimos que você está usando o Cloud Shell para executar comandos da ferramenta de linha de comando 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 mostrar 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 (em inglês)

  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 ative todas as conexões SSH para 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 que será conectada à conta de serviço via SSH. A instância precisa ter o login do SO ativado no nível do projeto ou da instância. Neste exemplo, mostramos 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 neste 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 para a instância target. O papel compute.osAdminLogin também concede privilégios de superusuário da 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 que a conta de serviço execute solicitações de API na 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
    

Agora, a conta de serviço pode gerenciar pares de chaves SSH próprios e usar 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. Use a ferramenta de linha de comando gcloud para se conectar à instância source:

    gcloud compute ssh source --project $PROJECT_ID --zone us-central1-f
    
  2. Na instância source, instale 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 amostra 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 ID 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 que você quiser. Como alternativa, é possível gravar 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 você executar o aplicativo fora de uma instância do Compute Engine, a biblioteca de cliente não poderá acessar nem a conta de serviço nem as permissões, a menos que você informe a chave da conta de serviço manualmente.

  1. Encontre o endereço IP externo para a instância target que você criou anteriormente neste tutorial. Esse endereço está disponível no console da página Instâncias (em inglês) 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 estiver 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 baseados em Debian, esta etapa pode ser feita usando apt:

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

      $ pip install --upgrade google-api-python-client
      
  7. Fazer 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 está disponível. Portanto, você precisa especificar o e-mail da conta de serviço manualmente. Você também precisa especificar o endereço IP externo para a instância target recebida 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 funciona o aplicativo de amostra

O aplicativo de amostra service_account_ssh.py funciona conforme o processo a seguir:

  1. Inicialize o objeto da API OS Login.
  2. Se você não informar 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 a ela. Se você executar o aplicativo fora do Google Compute Engine, será necessário informar 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 o exemplo é executado. Em seguida, adicione a chave pública à conta de serviço com um temporizador de expiração especificado por você.
  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 prazo de validade.
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. Em seguida, 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 estabelecida, mas é possível modificá-la para que ela seja executada de modo que atenda melhor à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 (em inglês)

  2. Exclua a instância chamada source:

    gcloud compute instances delete source \
       --project $PROJECT_ID --zone us-central1-f
    
  3. Exclua a instância chamada 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