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)
Pythonプログラムを開始するには、authbind
を使用することをお勧めします。そのため、ルートとして実行する必要はありません。
特権を削除する必要があるときはいつでも、ユーザーにユーザー名とグループを入力するように依頼することはお勧めできません。これは、特権を削除し、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())
systemdはそれを行うことができ、systemdを介してプログラムを開始すると、systemdはすでに開いているリスニングソケットをそれに渡すことができ、最初の接続でプログラムをアクティブ化することもできます。デーモン化する必要すらありません。
スタンドアロンアプローチを使用する場合は、機能CAP_NET_BIND_SERVICEが必要です(機能のマニュアルページを確認してください)。これは、適切なコマンドラインツールを使用してプログラムごとに、またはアプリケーションを(1)suid root(2)起動(3)ポートをリッスン(4)特権/機能を削除。
Suid rootプログラムには、多くのセキュリティ上の考慮事項が伴うことを忘れないでください(クリーンで安全な環境、umask、特権、rlimit、これらはすべて、プログラムが正しくセットアップする必要があるものです)。 systemdのようなものを使用できるのであれば、それならなおさらです。
スーパーユーザーになりたくない他のことを行った後でソケットを要求する必要がない限り、これはほとんど機能します。
少し前に tradesocket というプロジェクトを作りました。これにより、プロセス間でposixシステム上のソケットを前後に渡すことができます。私がやっていることは、スーパーユーザーのままであるプロセスを最初にスピンオフし、残りのプロセスは権限を落とし、次に他のプロセスからソケットを要求します。
以下は、 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])