Authorize Pacemaker actions with SO_PEERCRED
This commit is contained in:
+120
-4
@@ -4,6 +4,7 @@ import json
|
||||
import os
|
||||
import signal
|
||||
import socket
|
||||
import struct
|
||||
import traceback
|
||||
|
||||
from registry import Registry, RegistryError
|
||||
@@ -17,6 +18,8 @@ SOCKET_PATH = os.environ.get(
|
||||
server_socket = None
|
||||
registry = None
|
||||
pacemaker_resource_id = None
|
||||
pacemaker_peer_uid = None
|
||||
pacemaker_peer_gid = None
|
||||
|
||||
PROTOCOL_VERSION = 1
|
||||
|
||||
@@ -65,7 +68,66 @@ def load_pacemaker_resource_binding(
|
||||
return resource_id
|
||||
|
||||
|
||||
def handle_request(req):
|
||||
def load_pacemaker_peer_authorization(
|
||||
environ=None,
|
||||
):
|
||||
"""
|
||||
Load the peer credentials that authorize Pacemaker resource actions.
|
||||
|
||||
In the rootless keep-id user namespace, host root is intentionally
|
||||
unmapped and therefore appears as the configured overflow UID/GID.
|
||||
Socket path permissions remain part of this authorization boundary.
|
||||
"""
|
||||
if environ is None:
|
||||
environ = os.environ
|
||||
|
||||
raw_uid = environ.get("PACEMAKER_PEER_UID")
|
||||
raw_gid = environ.get("PACEMAKER_PEER_GID")
|
||||
|
||||
if raw_uid is None or raw_gid is None:
|
||||
raise RegistryError(
|
||||
"PACEMAKER_PEER_UID and "
|
||||
"PACEMAKER_PEER_GID must both be configured"
|
||||
)
|
||||
|
||||
try:
|
||||
peer_uid = int(raw_uid)
|
||||
peer_gid = int(raw_gid)
|
||||
|
||||
except ValueError as exc:
|
||||
raise RegistryError(
|
||||
"PACEMAKER_PEER_UID and "
|
||||
"PACEMAKER_PEER_GID must be integers"
|
||||
) from exc
|
||||
|
||||
if peer_uid < 0 or peer_gid < 0:
|
||||
raise RegistryError(
|
||||
"PACEMAKER peer credentials must be non-negative"
|
||||
)
|
||||
|
||||
return peer_uid, peer_gid
|
||||
|
||||
|
||||
def get_peer_credentials(conn):
|
||||
raw = conn.getsockopt(
|
||||
socket.SOL_SOCKET,
|
||||
socket.SO_PEERCRED,
|
||||
struct.calcsize("3i"),
|
||||
)
|
||||
|
||||
pid, uid, gid = struct.unpack("3i", raw)
|
||||
|
||||
return {
|
||||
"pid": pid,
|
||||
"uid": uid,
|
||||
"gid": gid,
|
||||
}
|
||||
|
||||
|
||||
def handle_request(
|
||||
req,
|
||||
peer_credentials=None,
|
||||
):
|
||||
if not isinstance(req, dict):
|
||||
return protocol_response({
|
||||
"ok": False,
|
||||
@@ -133,6 +195,35 @@ def handle_request(req):
|
||||
"action": action,
|
||||
})
|
||||
|
||||
if peer_credentials is None:
|
||||
return protocol_response({
|
||||
"ok": False,
|
||||
"error_code": "peer_credentials_unavailable",
|
||||
"error": (
|
||||
"peer credentials are required for "
|
||||
"Pacemaker resource actions"
|
||||
),
|
||||
"resource": resource,
|
||||
"action": action,
|
||||
})
|
||||
|
||||
if (
|
||||
peer_credentials.get("uid")
|
||||
!= pacemaker_peer_uid
|
||||
or peer_credentials.get("gid")
|
||||
!= pacemaker_peer_gid
|
||||
):
|
||||
return protocol_response({
|
||||
"ok": False,
|
||||
"error_code": "peer_credential_mismatch",
|
||||
"error": (
|
||||
"peer credentials are not authorized "
|
||||
"for Pacemaker resource actions"
|
||||
),
|
||||
"resource": resource,
|
||||
"action": action,
|
||||
})
|
||||
|
||||
return protocol_response(
|
||||
registry.execute(
|
||||
resource,
|
||||
@@ -170,16 +261,27 @@ def serve():
|
||||
global server_socket
|
||||
global registry
|
||||
global pacemaker_resource_id
|
||||
global pacemaker_peer_uid
|
||||
global pacemaker_peer_gid
|
||||
|
||||
registry = Registry()
|
||||
pacemaker_resource_id = (
|
||||
load_pacemaker_resource_binding(registry)
|
||||
)
|
||||
(
|
||||
pacemaker_peer_uid,
|
||||
pacemaker_peer_gid,
|
||||
) = load_pacemaker_peer_authorization()
|
||||
|
||||
socket_directory = os.path.dirname(
|
||||
SOCKET_PATH
|
||||
)
|
||||
|
||||
os.makedirs(
|
||||
os.path.dirname(SOCKET_PATH),
|
||||
socket_directory,
|
||||
exist_ok=True,
|
||||
)
|
||||
os.chmod(socket_directory, 0o700)
|
||||
|
||||
cleanup_socket()
|
||||
|
||||
@@ -192,7 +294,7 @@ def serve():
|
||||
)
|
||||
|
||||
server_socket.bind(SOCKET_PATH)
|
||||
os.chmod(SOCKET_PATH, 0o660)
|
||||
os.chmod(SOCKET_PATH, 0o600)
|
||||
server_socket.listen(16)
|
||||
|
||||
print(
|
||||
@@ -212,12 +314,23 @@ def serve():
|
||||
flush=True,
|
||||
)
|
||||
|
||||
print(
|
||||
"Pacemaker peer authorization: "
|
||||
f"uid={pacemaker_peer_uid} "
|
||||
f"gid={pacemaker_peer_gid}",
|
||||
flush=True,
|
||||
)
|
||||
|
||||
try:
|
||||
while True:
|
||||
conn, _ = server_socket.accept()
|
||||
|
||||
with conn:
|
||||
try:
|
||||
peer_credentials = (
|
||||
get_peer_credentials(conn)
|
||||
)
|
||||
|
||||
raw = b""
|
||||
|
||||
while b"\n" not in raw:
|
||||
@@ -232,7 +345,10 @@ def serve():
|
||||
raw.decode("utf-8").strip()
|
||||
)
|
||||
|
||||
response = handle_request(req)
|
||||
response = handle_request(
|
||||
req,
|
||||
peer_credentials,
|
||||
)
|
||||
|
||||
except (
|
||||
RegistryError,
|
||||
|
||||
Reference in New Issue
Block a user