この記事では、コンピューターのコマンドを電子メールでリモート制御するためのデーモンを作成した経験を共有したいと思います。
はじめに
私の仕事では、多くのリモートマシンを使用しています。 多くの場合、それらへのアクセスはIPフィルターによって制限されるため、ホストからの長いチェーンを使用してマシンに入る必要があります。
このクエストをもう一度完了した後、いくつかのコマンドを完了するために、何かを変更する必要があることに気付きました。 もちろん、最も簡単な解決策は、直接SSHトンネルを作成してすべての困難を忘れることですが、まず第一に、これは厳格なセキュリティポリシーによって妨げられます。第二に、柔軟で独立したシステムが必要です。
時間が経つにつれて、多くの要件が開発されました。
- システムのセキュリティ。
- 不要なジェスチャー(電話、他人のコンピューターなど)なしでシステムに簡単にアクセスできます。
- 実行されたコマンドの履歴と実行結果。
要件を詳細に分析した結果、電子メールで動作するボットが最適なソリューションであるという結論に達しました。 結局のところ、それは非常に便利です。 最近では、画面とキーボードを備えたデバイス(コンピューター、電話、Kindle、さらにはテレビでもこの方法を知っている)から電子メールを送信できます。 手紙のリストはサーバー上で常に利用可能であり、システムの状態の変化を便利に分析できます。
既製のソリューションの中で、適切なものが見つからなかったため、自分でそれを行うことにしました。
実装
Pythonはプログラミング言語として選択されました。選択基準は、言語自体の柔軟性だけでなく、実際に使用したいという長年の願望でもありました。
プログラムのアルゴリズムは非常に簡単です:
- 電子メールコマンドの受信
- コマンド実行
- 結果をユーザーに送り返す
1.電子メールでコマンドを受信する
まず、サーバーへの接続を確立します。POP3またはIMAP4の2つのオプションがあります。 選択は、メールサーバーでサポートされているプロトコルと、ターゲットマシンのポートの開放性に依存します。
POP3プロトコルのサーバー接続
if is_enabled(self.get_param_str("Mail", "USE_SSL")): session = poplib.POP3_SSL(self.get_param_str("Mail", "POP_SERVER"), self.get_param_int("Mail", "POP_SSL_PORT")) else: session = poplib.POP3(self.get_param_str("Mail", "POP_SERVER"), self.get_param_int("Mail", "POP_PORT")) #if if is_enabled(self.get_param_str("Debug", "NETWORK_COMM_LOGGING")): session.set_debuglevel(10) #if try: session.user(self.get_param_str("Mail", "EMAIL_USER")) session.pass_(self.get_param_str("Mail", "EMAIL_PASS")) except poplib.error_proto as e: sys.stderr.write("Got an error while connecting to POP server: '%s'\n" % (e)) return False #try
IMAP4プロトコルのサーバー接続
if is_enabled(self.get_param_str("Mail", "USE_SSL")): session = imaplib.IMAP4_SSL(self.get_param_str("Mail", "IMAP_SERVER"), self.get_param_int("Mail", "IMAP_SSL_PORT")) else: session = imaplib.IMAP4(self.get_param_str("Mail", "IMAP_SERVER"), self.get_param_int("Mail", "IMAP_PORT")) #if if is_enabled(self.get_param_str("Debug", "NETWORK_COMM_LOGGING")): session.debug = 10 #if try: session.login(self.get_param_str("Mail", "EMAIL_USER"), self.get_param_str("Mail", "EMAIL_PASS")) except imaplib.IMAP4.error as e: sys.stderr.write("Got an error while connecting to IMAP server: '%s'\n" % (e)) return False #try
接続が確立されたら、ボットのコマンドからすべてのメッセージを除外する必要があります。 3レベルのフィルタリングを使用することにしました。
- メッセージの件名によるフィルタリング。
- ホワイトリストおよびブラックリストに従って送信者をフィルタリングします。
- ログイン+パスワード認証。
POP3の場合のトピックによるフィルタリングのアルゴリズムは次のとおりです。メッセージヘッダーのみを取得し、「Subject:」フィールドをチェックします。トピックが正しい場合は、メッセージを完全に取得し、さらに処理するために送信します。
numMessages = len(session.list()[1]) for i in range(numMessages): m_parsed = Parser().parsestr("\n".join(session.top(i+1, 0)[1])) if self.get_param_str('Main', 'SUBJECT_CODE_PHRASE') == m_parsed['subject']: #Looks like valid cmd for bot, continue if self._process_msg("\n".join(session.retr(i+1)[1])): session.dele(i+1) #if #if #for
IMAPの場合、すべてが少し簡単です。プロトコルにより、サーバー側で選択を実行できます。つまり、件名を指定するだけで、サーバー自体が適切な文字をすべて提供します。
session.select(self.get_param_str('Mail', 'IMAP_MAILBOX_NAME')) typ, data = session.search(None, 'SUBJECT', self.get_param_str("Main", "SUBJECT_CODE_PHRASE"))
次のステップでは、ホワイトリストとブラックリストで送信者をフィルタリングします(正規表現を使用できます)
そして最後の要塞は、ログインの許可です:パスワードのペア。コマンドの文字の最初の行に入力する必要があります。
パスワードの代わりに、md5ハッシュのみがクライアントに保存されます。
はい、私はこれがあまりにも妄想的であることを理解していますが、一方で、安全保障の問題における過度の妄想について話すことができますか?
2.コマンドの実行
一部のコマンドの実行にはかなり時間がかかる可能性があるため、各コマンドを個別のプロセスで実行することにしました。 アクティブなプロセスの数に上から制限が導入されました。
任意のコマンドを実行することの欠点は、対話型プログラム(mc、htopなど)を実行することによりシステムを中断できることです。 私はまだこれに対処する方法を理解していません。
3.結果をユーザーに送り返す
ユーザーコマンドが完了すると、ユーザーにはコマンドのすべての出力と戻りコードを含むレポートが送信されます。
smtplibモジュールは送信に使用されます
self.__send_lock.acquire() if not msg is None: print "[%s] Sending response to '%s'" % (datetime.today().strftime('%d/%m/%y %H:%M'), email_from) recipients = [email_from, self.get_param_str('Mail', 'SEND_COPY_TO')] message = "%s%s%s\n%s" % ('From: %s \n' % (self.get_param_str('Main', 'BOT_NAME')), 'To: %s \n' % (email_from), 'Subject: Report %s \n' % (datetime.today().strftime('%d/%m/%y %H:%M')), msg) # Currently in python SMTP_SSL is broken, so always using usual version session = smtplib.SMTP(self.get_param_str("Mail", "SMTP_SERVER"), self.get_param_int("Mail", "SMTP_PORT")) if is_enabled(self.get_param_str("Debug", "NETWORK_COMM_LOGGING")): session.set_debuglevel(10) #if session.login(self.get_param_str("Mail", "EMAIL_USER"), self.get_param_str("Mail", "EMAIL_PASS")) session.sendmail(self.get_param_str("Mail", "EMAIL_USER"), recipients, message) session.quit() #if self.__send_lock.release()
このクラスは、デーモンの作成に使用されました。
おわりに
例として、ボットにコマンドを送信します。
しばらくすると、答えがわかります。
プロジェクトコードはgithubで入手できます
この情報が誰かに役立つことを願っています。
ご意見をお寄せいただきありがとうございます。
UPD:マルチパートメッセージの誤った処理に関連するバグを修正しました。githubのユーザーmegresに感謝します。
UPD2:コマンドに任意のタイムアウトを設定する機能が追加されました。 使用するには、コマンドの前に接頭辞「:time = x」を追加する必要があります。 「:time = 10 make」、ビルドに10秒を与えてから撮影します。
アイデアをありがとう。