21 Oct 2010

Fabric và ssh_key + passphrase

SSH (OpenSSH client) là công cụ mà bất cứ ai làm việc với Unix, Linux đều phải dùng tới, cho dù là lập trình viên, quản trị hệ thống, hay quản trị cơ sở dữ liệu.
Để ssh tới một máy chủ *nix, thông thường có 2 cách:
1.1. Sử dụng password (Không an toàn, có thể bị lộ, hoặc bị bruteforce, không nên dùng)
1.2. Sử dụng mã hóa công khai (public key authentication, cách này an toàn hơn, tránh được các nguy cơ trên)

Với cách thứ 2, một khi máy làm việc bị nhân nhượng, hoặc đơn giản là ai đó ngồi vào máy tính, lấy được private key, xem như hầu hết mọi server (dùng key này để login) đều bị nguy hiểm (Trừ phi sử dụng truecrypt để mã hóa phân vùng đặc biệt, khi dùng thì mount lên, kẻ nào đó ngồi vào đúng lúc đã mount cũng coi như xong). Do đó, để tăng khả năng an toàn, nên dùng thêm một passphrase cho private key của riêng mình.
Khi dùng private key với passphrase, mỗi khi muốn sử dụng key này, hệ thống sẽ yêu cầu nhập vào một chuỗi password đã ấn định khi tạo key.



Vấn đề: Nếu phải quản lí nhiều máy chủ, nhiều dịch vụ (vài chục đến vài trăm), chỉ dùng private key thì không đủ. Lí do:
2.1 Cần bao nhiêu thởi gian để thực hiện một tác vụ, deploy một software, upload một file, parse một file log... trên 100 servers cùng lúc?
2.2 Đôi khi chúng ta cần thực hiện đa tác vụ chỉ với một lần login.

Vì thế đã có rất nhiều tools, scripts được tạo ra để quản lí việc deployment trên hàng loạt máy chủ đa dịch vụ. Fabric là một trong số đó.
Fabric một một python module, nói ngắn gọn, fabric là một công cụ giúp chúng ta deploy hệ thống một cách nhanh gọn, có hệ thống, an toàn và tiện lợi, và hàng loạt.
Nhưng fabric không cung cấp option để làm việc với ssh_key + passphrase (Hnay mình mới phát hiện ra)

Một đoạn python script thế này:
#!/usr/bin/env python
 
 
from fabric.api import env, hosts, run

def connserv():
     net_part    = '10.30.21.'
     env.hosts   = [net_part + str(i) for i in range(1, 100)]
     env.user    = 'deploy1'
     env.key_filename = "/priv/hung/key"
     env.disable_known_hosts = True 
def check_log():
     run('check_log')


key là private_key có sử dụng passphrase. Khi run fabric

$ fab connserv check_log

ngạc nhiên là fabric không đọc được key này (vì được bảo vệ bằng password) nên...bỏ qua, chuyển sang authenticate bằng password. Nếu sử dụng ssh -i /path/to/key bạn sẽ  được yêu cầu nhập password để access key.

Document của fabric không có đoạn nào nhắc đến vụ này. Nhưng có thể sử dụng module paramiko của python. Paramiko là SSH2 Protocol dành cho python.
Mục tiêu của việc sử dụng paramiko là bằng cách nào đó, mở key file được mã hóa bằng password, giải mã và ghi ra một key file mới, dùng fabric load file này lên, sử dụng. Sau đó xóa key file được tạo đi.

Chú ý là, ssh key của chúng ta là một RSA2 key, api document của paramiko có đề cập tới class RSAKey. Thử load key file, với password:
In [2]: import paramiko
In [3]: pass_ = 'mypass'
In [4]: key_file = open('key', 'rb')
In [5]: decrypt_key = paramiko.RSAKey.from_private_key(key_file, pass_) 
Sẽ nhận được lỗi tương tự
Unknown private key cipher "AES-128-CBC"
Nhưng nếu mở một key không có passphrase thì không xảy ra lỗi gì.
Lỗi xảy ra do:
paramiko only supports des3 encrypted private keys (DES-EDE3-CBC)
Thử mở module RSAKey thì có đoạn thế này:
from paramiko.pkey import PKey
class RSAKey (PKey):
---

Kéo xuống dưới class RSAKey (Pkey): không thấy định nghĩa cipher gì, mở super class Pkey

_CIPHER_TABLE = {
        'DES-EDE3-CBC': { 'cipher': DES3, 'keysize': 24, 'blocksize': 8, 'mode': DES3.MODE_CBC }
    }


Như vậy chỉ cẩn định nghĩa thêm
AES-128-CBC
có lẽ sẽ được. Thêm vào script ban đầu đoạn:
from fabric.api import env, hosts, run
 import paramiko
 from Crypto.Cipher import AES
 paramiko.rsakey.RSAKey._CIPHER_TABLE['AES-128-CBC'] = {'cipher': AES, 'keysize': 16, 'blocksize': 16, 'mode': AES.MODE_CBC}
Mở key file như bình thường:

     encrypt_key = open('/priv/hung/key', 'rb')
     decrypt_key = paramiko.RSAKey.from_private_key(encrypt_key, pass_)
     tmp = open('/tmp/tmp_key', 'wb')
     decrypt_key.write_private_key(tmp)
     tmp.close()


Sau đó trỏ env.key_filename của fabric về key tạm, thế là work.
Tạo key mới ở /tmp sẽ không tốn công để viết thêm hàm xóa file sau khi thực hiện tác vụ xong (vì ta không muốn giữ lại key không có pass, phòng trường hợp bất trắc :-p).

No comments:

Disqus