Automate `lxc-attach` through ssh with Python

Question:

Question:

How do I automate this process, and include all of the password prompts?

Machine 1> ssh user2@machine2
   password:
Machine 2> lxc-attach -n 0x1000
Container> ssh user3@machine3
   password:
Machine 3> get_temperature.sh
   temperature is 65C

Background:

I am running some automated scripts on a computer (Machine 1) with several other test systems cascaded from this one. Only the first computer has internet access and stable state. Anything can be installed here and the automation is mostly python.

The other test systems are not connected to the internet, are shutdown unexpectedly, and have their memory wiped often. Because of this, any settings I change or ssh keys I load outside of this automated script are not retained. I am just testing the hardware; none of this will be in production anywhere, and I don’t have any way to change the state it is reset to.

Diagram of Cascaded System

Attempt:

I have tried to solve this running paramiko on Machine 1. I can connect to Machine 2, but I have no way to answer the password prompt when connecting to Machine 3 from the Machine 2. This is what I have tried:

import paramiko

ssh = paramiko.SSHClient()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())

ssh.connect('192.168.2.2', port=22, username='user2', password='pass2')
print("connect to machine 2")

channel = ssh.invoke_shell()
channel_data = ''
go = 1

while True: 
    if channel.recv_ready():
        channel_data = str(channel.recv(9999))
        print('Channel Data: ', channel_data)
    else:
        continue

    if channel_data.endswith("~ >'"):
        if go == 1:
            channel.send('lxc-attach -n 0x1000')
            channel.send('n')
            go += 1
        else: 
            channel.send('ssh [email protected]')
            print("sending ssh command")
    elif 'assword:' in channel_data:
        channel.send('pass3')
        channel.send('n')
        print("put the pass")
        break

stdin, stdout, stderr = ssh.exec_command('pwd')
print(stdout)

Next I tried using fabric to jump from one host to another. If I try to open a connection to Machine 3 or 4 with gateway settings, this exits with the error: ChannelException(2, 'Connect failed'). If I try to run the lxc-attach command first, it just hangs forever.

import fabric

machine2 = fabric.Connection('192.168.2.2', user='user2', connect_kwargs={"password": "pass2"})
result = machine2.run('hostname')
print('machine2 hostname', result)
result = machine2.run('lxc-attach -n 0x1000')
print('machine2 lxc ', result)

machine3 = fabric.Connection('192.168.3.2', user='user3', gateway=machine2, connect_kwargs={"password": "pass3"})
machine4 = fabric.Connection('192.168.4.2', user='user4', gateway=machine3, connect_kwargs={"password": "pass4"})

result = machine3.run('hostname')
print('machine3 hostname', result)

Meta:

These similar questions about automating password entries:

Are answered with "dont".

Martin pointed me toward jump hosting, but I haven’t gotten it to work. For this topic, most questions seem to point back to this one:

I believe that my problem is trying to jump host through the container. It seems the lxc-attach command opens a raw shell that doesn’t get passed back.

Is there a way to spawn a shell that acts more like it’s interactive counterparts? I tried pexpect, but Machine 1 is Windows, so it doesn’t work. The commands on the Pexpect Windows Documentation exit with module 'pexpect' has no attribute 'popen_spawn'.

Asked By: Alphy13

||

Answers:

Pexpect doesn’t work in Windows.

I created a new ‘Machine 1’ with Linux and the following code works:

child = pexpect.spawn(f'ssh {m2.user}@{m2.ip}')
child.expect('assword:')
child.sendline(m2.pswd)
child.sendline('n')
print('Note: p2 sent')
child.expect(m2.prompt)
child.sendline('hostname')
print('Out: ', child.before)

child.expect(m2.prompt)
child.sendline('lxc-attach -n 0x1000')
child.sendline('n')
print('Note: attach sent')
child.expect(container.prompt)
child.sendline('hostname')
print('Out: ', child.before)

child.expect(container.prompt)
child.sendline(f'ssh {m3.user}@{m3.ip}')
child.expect('assword:')
child.sendline(m3.pswd)
print('Note: p3 sent')
# child.expect(pexpect.EOF)
child.expect_exact(m3.prompt)
child.sendline('hostname')
child.expect_exact(m3.prompt)
print('Before: ', child.before)

child.sendline(f'ssh {m4.ip}')
print('Note: M4 connected')
child.expect_exact(m4.prompt)
child.sendline('hostname')
child.expect_exact(m4.prompt)
print('Host: ', child.before)
Answered By: Alphy13
Categories: questions Tags: , , , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.