web-dev-qa-db-ja.com

ルート権限をドロップインPython

Pythonプログラムがポート80でリッスンを開始するようにしたいのですが、その後、ルート権限なしで実行します。ルートを削除するか、それなしでポート80を取得する方法はありますか?

Root権限がないと、ポート80でサーバーを開くことはできません。これはOSレベルの制限です。したがって、唯一の解決策は、ポートを開いた後にroot権限を削除することです。

Pythonでroot権限を削除するための可能な解決策は次のとおりです。 Pythonで権限を削除する 。これは一般的には良い解決策ですが、rootユーザーのグループメンバーシップが保持されないように、関数にos.setgroups([])を追加する必要もあります。

私はコードを少しコピーしてクリーンアップし、ロギングと例外ハンドラーを削除したので、OSErrorを適切に処理する必要があります(有効なUIDの切り替えが許可されていない場合にスローされます)またはGID):

import os, pwd, grp

def drop_privileges(uid_name='nobody', gid_name='nogroup'):
    if os.getuid() != 0:
        # We're not root so, like, whatever dude
        return

    # Get the uid/gid from the name
    running_uid = pwd.getpwnam(uid_name).pw_uid
    running_gid = grp.getgrnam(gid_name).gr_gid

    # Remove group privileges
    os.setgroups([])

    # Try setting the new uid/gid
    os.setgid(running_gid)
    os.setuid(running_uid)

    # Ensure a very conservative umask
    old_umask = os.umask(077)
56
Tamás

Pythonプログラムを開始するには、authbindを使用することをお勧めします。そのため、ルートとして実行する必要はありません。

https://en.wikipedia.org/wiki/Authbind

12
Allen

特権を削除する必要があるときはいつでも、ユーザーにユーザー名とグループを入力するように依頼することはお勧めできません。これは、特権を削除し、Sudoコマンドを開始したユーザーに切り替える、Tamasのコードのわずかに変更されたバージョンです。 Sudoを使用していると想定しています(使用していない場合は、Tamásのコードを使用してください)。

#!/usr/bin/env python3

import os, pwd, grp

#Throws OSError exception (it will be thrown when the process is not allowed
#to switch its effective UID or GID):
def drop_privileges():
    if os.getuid() != 0:
        # We're not root so, like, whatever dude
        return

    # Get the uid/gid from the name
    user_name = os.getenv("Sudo_USER")
    pwnam = pwd.getpwnam(user_name)

    # Remove group privileges
    os.setgroups([])

    # Try setting the new uid/gid
    os.setgid(pwnam.pw_gid)
    os.setuid(pwnam.pw_uid)

    #Ensure a reasonable umask
    old_umask = os.umask(0o22)


#Test by running...
#./drop_privileges
#Sudo ./drop_privileges
if __== '__main__':
    print(os.getresuid())
    drop_privileges()
    print(os.getresuid())
7
Autodidact
  1. systemdはそれを行うことができ、systemdを介してプログラムを開始すると、systemdはすでに開いているリスニングソケットをそれに渡すことができ、最初の接続でプログラムをアクティブ化することもできます。デーモン化する必要すらありません。

  2. スタンドアロンアプローチを使用する場合は、機能CAP_NET_BIND_SERVICEが必要です(機能のマニュアルページを確認してください)。これは、適切なコマンドラインツールを使用してプログラムごとに、またはアプリケーションを(1)suid root(2)起動(3)ポートをリッスン(4)特権/機能を削除。

Suid rootプログラムには、多くのセキュリティ上の考慮事項が伴うことを忘れないでください(クリーンで安全な環境、umask、特権、rlimit、これらはすべて、プログラムが正しくセットアップする必要があるものです)。 systemdのようなものを使用できるのであれば、それならなおさらです。

4
Rudd-O

スーパーユーザーになりたくない他のことを行った後でソケットを要求する必要がない限り、これはほとんど機能します。

少し前に tradesocket というプロジェクトを作りました。これにより、プロセス間でposixシステム上のソケットを前後に渡すことができます。私がやっていることは、スーパーユーザーのままであるプロセスを最初にスピンオフし、残りのプロセスは権限を落とし、次に他のプロセスからソケットを要求します。

2
Brantley Harris

以下は、 Tamásの答え をさらに適応させたもので、次の変更が加えられています。

  • 使用 - python-prctl module Linux機能を、保持する機能の指定リストにドロップします。
  • オプションで、ユーザーをパラメーターとして渡すことができます(デフォルトでは、Sudoを実行したユーザーを検索します)。
  • ユーザーのすべてのグループとHOMEを設定します。
  • オプションでディレクトリを変更します。

(ただし、この機能を使用するのは比較的初心者なので、見落としている可能性があります。古いカーネル(<3.8)またはファイルシステム機能が無効になっているカーネルでは機能しない可能性があります。)

def drop_privileges(user=None, rundir=None, caps=None):
    import os
    import pwd

    if caps:
        import prctl

    if os.getuid() != 0:
        # We're not root
        raise PermissionError('Run with Sudo or as root user')

    if user is None:
        user = os.getenv('Sudo_USER')
        if user is None:
            raise ValueError('Username not specified')
    if rundir is None:
        rundir = os.getcwd()

    # Get the uid/gid from the name
    pwnam = pwd.getpwnam(user)

    if caps:
        prctl.securebits.keep_caps=True
        prctl.securebits.no_setuid_fixup=True

    # Set user's group privileges
    os.setgroups(os.getgrouplist(pwnam.pw_name, pwnam.pw_gid))

    # Try setting the new uid/gid
    os.setgid(pwnam.pw_gid)
    os.setuid(pwnam.pw_uid)

    os.environ['HOME'] = pwnam.pw_dir

    os.chdir(os.path.expanduser(rundir))

    if caps:
        prctl.capbset.limit(*caps)
        try:
            prctl.cap_permitted.limit(*caps)
        except PermissionError:
            pass
        prctl.cap_effective.limit(*caps)

    #Ensure a reasonable umask
    old_umask = os.umask(0o22)

次のように使用できます。

drop_privileges(user='www', rundir='~', caps=[prctl.CAP_NET_BIND_SERVICE])
2
Craig McQueen