2015-03-26 5 views
3

Схема того, что я пытаюсь выполнить:SSH сервер направляет туннель пользователем

$ sftp [email protected] 

SSHCLIENT_JOE --------> GATEWAY_SERVER (does logic by username to determine 
          |   socket to forward the connection to.) 
          | 
          \  127.0.0.1:1030 CONTAINRR_SSHD_SALLY 
           ---> 127.0.0.1:1031 CONTAINER_SSHD_JOE 
            127.0.0.1:1032 CONTAINER_SSHD_MRAYMOND 

Это кажется ближе всего к тому, что я пытаюсь сделать: paramiko server mode port forwarding http://bitprophet.org/blog/2012/11/05/gateway-solutions/

Но вместо клиент, выполняющий ProxyCommand или запрашивающий канал «direct-tcpip», я хочу, чтобы пересылка выполнялась сервером, незаметно для клиента. Я пытаюсь сделать это с помощью сервера paramiko, беря объект транспорта подключаемого клиента и создавая канал прямой tcpip от имени клиента, но я сталкиваюсь с блокпостами. Я Использование https://github.com/paramiko/paramiko/blob/master/demos/demo_server.py в качестве шаблона

# There's a ServerInterface class definition that overrides check_channel_request 
# (allowing for direct-tcpip and session), and the other expected overides like 
# check_auth_password, etc that I'm leaving out for brevity. 

try: 
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 
    sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) 
    sock.bind(('', 2200)) 
except Exception as e: 
    print('*** Bind failed: ' + str(e)) 
    traceback.print_exc() 
    sys.exit(1) 
try: 
    sock.listen(100) 
    print('Listening for connection ...') 
    client, addr = sock.accept() 
except Exception as e: 
    print('*** Listen/accept failed: ' + str(e)) 
    traceback.print_exc() 
    sys.exit(1) 

print('Got a connection!') 
try: 
    t = paramiko.Transport(client) 
    t.add_server_key(host_key) 
    server = Server() 
    try: 
     t.start_server(server=server) 
    except paramiko.SSHException: 
     print('*** SSH negotiation failed.') 
     sys.exit(1) 

    # Waiting for authentication.. returns an unwanted channel object since 
    # it isn't the right "kind" of channel 
    unwanted_chan = t.accept(20) 
    dest_addr = ("127.0.0.1", 1030)  # target container port 
    local_addr = ("127.0.0.1", 1234)  # Arbitrary port on gateway server 

    # Trying to put words in the client's mouth here.. fails 
    # What should I do? 
    print(" Attempting creation of direct-tcpip channel on client Transport") 
    tunnel_chan = t.open_channel("direct-tcpip", dest_addr, local_addr) 
    print("tunnel_chan created.") 
    tunnel_client = SSHClient() 
    tunnel_client.load_host_keys(host_key) 
    print("attempting connection using tunnel_chan") 
    tunnel_client.connect("127.0.0.1", port=1234, sock=tunnel_channel) 
    stdin, stdout, stderr = tunnel_client.exec_command('hostname') 
    print(stdout.readlines()) 
except Exception as e: 
print('*** Caught exception: ' + str(e.__class__) + ': ' + str(e)) 
traceback.print_exc() 
try: 
    t.close() 
except: 
    pass 
sys.exit(1) 

выходного тока:

Read key: bc1112352a682284d04f559b5977fb00 
Listening for connection ... 
Got a connection! 
Auth attempt with key: 5605063f1d81253cddadc77b2a7b0273 
Attempting creation of direct-tcpip channel on client Transport 
*** Caught exception: <class 'paramiko.ssh_exception.ChannelException'>: (1, 'Administratively prohibited') 
Traceback (most recent call last): 
File "./para_server.py", line 139, in <module> 
tunnel_chan = t.open_channel("direct-tcpip", dest_addr, local_addr) 
File "/usr/lib/python2.6/site-packages/paramiko/transport.py", line 740, in open_channel 
raise e 
ChannelException: (1, 'Administratively prohibited') 

В настоящее время у нас есть прямой вперед сервер SFTP, где клиенты подключаются и они CHROOT-й изд к их соответствующему фтпу каталогам. Мы хотим переместить клиентов в контейнеры lxc, но не хотим изменять, как они подключаются к sftp .. (Так как они, вероятно, используют клиентов gui ftp, например filezilla.) Я также не хочу создавать интерфейс моста и назначьте новые ips всем контейнерам. Таким образом, контейнеры не имеют отдельных ips от хоста, они имеют одинаковое сетевое пространство. Клиентские контейнеры sshds будут связываться с отдельными портами на localhost. Таким образом, у них могут быть уникальные порты, и логика выбора порта может быть концептуально перенесена на ... простой сервер на физическом хосте.

Это скорее доказательство концепции, и общее любопытство с моей стороны. SSH сервер

+0

Я не знаю paramico, но, как правило, SSH необходимо «где-то», и если ваш шлюз не является устройством, которое ведет переговоры о соединении (т. Е. Вы не хотите использовать ProxyCommand) Я ожидаю, что вам нужно будет использовать NAT для подключения TCP-соединения к другому хосту. – ghoti

+0

@ghoti Так что подключение двух каналов не является возможным. Я не могу пройти через 2 sshd, если исходный клиент явно не запрашивает ProxyCommand? Кажется, это стена, на которую я нажимаю. Я хотел бы автоматизировать NAT-порты портов, хотя я могу только сделать логику, когда я получу имя пользователя клиента. Из того, что я могу сказать, это получается только после согласования соединения с sshd. Так что это сценарий с курицей и яйцом. – mraymond

+0

Хорошо, не могу сделать это в комментариях, я дал ответ ниже. – ghoti

ответ

1

Как я уже упоминал в комментарии выше, я ничего не знаю о paramiko, но я могу прокомментировать это с точки зрения OpenSSH, и, возможно, вы сможете перевести концепции в то, что вам нужно.

NAT был упомянут в комментариях. NAT - это что-то на более низком уровне, чем SSH, а не на том, что было бы настроено на основе входа SSH (несмотря на SOCK5). Вы реализуете его в своем брандмауэре, а не в своей конфигурации SSH. Способ, которым ProxyCommand работает, заключается в том, чтобы согласовать соединение SSH, а затем передать клиенту следующий прыжок, говоря: «Здесь, обсудите с этим парнем тоже». Это что-то реализовано прямо в протоколе SSH.

Возможно, вам не может быть совершенно не повезло.

Стандартная установка ProxyCommand может выглядеть следующим образом, с целевым портом, указанным на стороне клиента:

host joecontainer 
    User joe 
    ProxyCommand ssh -x -a -q -Wlocalhost:1031 gatewayserver.horse 

Старшего старомодный вариант это может быть использован Netcat:

host joecontainer 
    User joe 
    ProxyCommand ssh -x -a -q gatewayserver.horse nc localhost 1031 

Идея вот что nc localhost 1031 - это команда, которая обеспечивает SSH доступ к «следующему ходу» в цепочке SSH. Вы можете запустить любую команду здесь, пока результатом этой команды является соединение с демоном SSH.

Но вы хотите, чтобы выбор порта обрабатывался GATEWAY, а не клиентом. И в этом кроется хруст, потому что демон SSH использует только целевое имя пользователя, чтобы выбрать файл авторизационного файла учетной записи пользователя для чтения. Это ключи , которые важны, а не пользователь. К моменту, когда сервер обходит сценарий или команду, связанную с пользователем, согласование SSH завершено, и слишком поздно пересылать соединение на следующий скачок.

Итак ... Вы могли бы рассмотреть возможность всех подключений к обычному пользователю, а затем выбор порта осуществляется на основе SSH ключа. Так, например, gitolite обрабатывает пользователей. В вашем случае Джо и Салли могли бы подключиться к [email protected] с помощью своего ключа DSA или RSA.

Замечательная часть состоит в том, что весь ваш выбор порта обрабатывается в файле «общего» пользователя .ssh/authorized_keys. Файл будет выглядеть примерно так:

command="/usr/bin/nc localhost 1030",no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty ssh-rsa ... [email protected] 
command="/usr/bin/nc localhost 1031",no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty ssh-rsa ... [email protected] 

Вы можете прочитать об этом под the "AUTHORIZED_KEYS FILE FORMAT" section of the sshd(8) man page.

Чтобы использовать эту технику, мы все еще нуждаемся в стороне клиента ProxyCommand, но из-за выбора порта происходит на стороне сервера, основанный на ключ клиента, каждый может использовать точно такой же ProxyCommand:

host mycontainer 
    ProxyCommand ssh -xaq [email protected] 

Салли и Джо будет запускать ssh-keygen, чтобы создать пару ключей, если они еще не были. Они отправят вам открытый ключ, который вы добавите в ~ common/.ssh/authorized_keys, используя формат выше.

Когда Joe подключается с помощью своего ключа, сервер ssh запускает только команду nc, связанную с его ключом. И из-за ProxyCommand вывод netcat интерпретируется как «следующая надежда» для SSH.

Я тестировал это с помощью sftp (работает на моей конечной цели, сродни вашему контейнеру) и, похоже, работает для меня.

SSH - это волшебство. :-)

+0

Я не знал о возможностях команды authorized_keys. Наличие обычного пользователя позволит мне настроить всю прокси-серверную сторону и упростить задачу для клиентов. Спасибо за подробный ответ! SSH - это действительно волшебство. – mraymond

1
Attempting creation of direct-tcpip channel on client Transport 
*** Caught exception: <class '[...]'>: (1, 'Administratively prohibited') 

Контейнер отклоняет ваш запрос прямого канала TCPIP, так как он был настроен отказаться от этих запросов. Я понимаю, что здесь нужно проксировать сеансы SFTP в правильный контейнер? И я полагаю, что контейнер SSH-сервера сконфигурирован в the usual fashion, чтобы позволить этим людям делать SFTP? Сеансы SFTP проходят через канал сеанса, а не прямой канал tcpip.

Я не кодер python и не могу дать вам конкретный код paramiko, но ваш ретранслятор должен открыть сеанс канала на сервере контейнера и вызвать подсистему «sftp». И если это возможно, ваш ретранслятор должен делать это только тогда, когда удаленный клиент запрашивал сеанс SFTP, а не для других типов запросов канала.

+0

Спасибо, я вижу, что это будет проблемой, как только я попытаюсь подключить оригинальный клиент. Кажется, что точка отказа до того, как попытка соединения sftp даже сделана на контейнере sshd, сценарий должен напечатать «попытку соединения с помощью tunnel_chan» до того, как попытка sftp будет сделана на контейнере sshd. Который я теперь знаю, потерпит неудачу в любом случае. – mraymond

Смежные вопросы