lab3
This commit is contained in:
2
lab3/.gitignore
vendored
Normal file
2
lab3/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
*.pyc
|
||||
*.pdf
|
||||
94
lab3/README.md
Normal file
94
lab3/README.md
Normal file
@@ -0,0 +1,94 @@
|
||||
# Lab 3 — DAC + MAC
|
||||
|
||||
Система доступа к конфиденциальным данным с дискреционным (DAC) и мандатным (MAC) управлением доступом. Основана на Lab 2.
|
||||
|
||||
## Структура каталогов
|
||||
|
||||
```
|
||||
$PRACTICE3_DIR/ # по умолчанию /usr/local/practice3
|
||||
├── etc/
|
||||
│ ├── passwd # логин:sha256:id:права:ФИО
|
||||
│ ├── access_mode # BOTH | DAC_ONLY | MAC_ONLY
|
||||
│ ├── acl # матрица доступа (DAC)
|
||||
│ ├── subject_labels # метки субъектов (MAC)
|
||||
│ └── object_labels # метки объектов (MAC)
|
||||
├── confdata/ # конфиденциальные файлы
|
||||
├── bin/ # usermgr, confaccess, bruteforce
|
||||
└── log/ # usermgr.log, access.log
|
||||
```
|
||||
|
||||
## Режимы проверки доступа
|
||||
|
||||
| Режим | DAC | MAC |
|
||||
|-------|-----|-----|
|
||||
| BOTH (по умолчанию) | да | да |
|
||||
| DAC_ONLY | да | нет |
|
||||
| MAC_ONLY | нет | да |
|
||||
|
||||
Режим задаётся через `usermgr set-mode` (только root).
|
||||
|
||||
## Установка
|
||||
|
||||
```bash
|
||||
chmod +x setup.sh
|
||||
|
||||
# для пути по умолчанию нужен root:
|
||||
sudo ./setup.sh
|
||||
|
||||
# для тестирования без root:
|
||||
PRACTICE3_DIR=/tmp/practice3 ./setup.sh
|
||||
```
|
||||
|
||||
## Использование
|
||||
|
||||
### usermgr — управление пользователями
|
||||
|
||||
```bash
|
||||
usermgr add alice
|
||||
usermgr list
|
||||
usermgr edit alice --full-name "Иванов Иван" --permissions rw
|
||||
usermgr edit alice --label 1 # метка субъекта (root only)
|
||||
usermgr set-label report.txt 2 # метка объекта (root only)
|
||||
usermgr set-mode DAC_ONLY # режим проверки (root only)
|
||||
usermgr show-mode # текущий режим (root only)
|
||||
usermgr passwd alice
|
||||
usermgr delete alice
|
||||
```
|
||||
|
||||
### confaccess — доступ к данным
|
||||
|
||||
```bash
|
||||
confaccess
|
||||
PRACTICE3_DIR=/tmp/practice3 confaccess
|
||||
```
|
||||
|
||||
Команды после аутентификации:
|
||||
|
||||
```
|
||||
create <file> создать файл (владелец = текущий пользователь)
|
||||
read <file> прочитать файл
|
||||
append <file> <text> дописать в файл
|
||||
copy <src> <dst> скопировать в confdata
|
||||
remove <file> удалить файл
|
||||
grant <user> <path> <perms> выдать права (только владелец)
|
||||
help / exit
|
||||
```
|
||||
|
||||
### DAC (дискреционный доступ)
|
||||
|
||||
- Каждый объект имеет владельца (создатель)
|
||||
- Владелец может выдавать права через `grant <user> <path> <perms>`
|
||||
- Права: r (чтение), w (запись), d (удаление)
|
||||
|
||||
### MAC (мандатный доступ, Белл–Лападула)
|
||||
|
||||
- Метки: 0 — несекретно, 1 — ДСП, 2 — секретно
|
||||
- Нет чтения сверху: субъект читает только объекты с уровнем ≤ своего
|
||||
- Нет записи вниз: субъект пишет только в объекты с уровнем ≥ своего
|
||||
|
||||
### bruteforce
|
||||
|
||||
```bash
|
||||
bruteforce alice
|
||||
bruteforce alice --max-length 4
|
||||
```
|
||||
138
lab3/bruteforce.py
Normal file
138
lab3/bruteforce.py
Normal file
@@ -0,0 +1,138 @@
|
||||
#!/usr/bin/env python3
|
||||
import argparse
|
||||
import itertools
|
||||
import shutil
|
||||
import string
|
||||
import subprocess
|
||||
import sys
|
||||
import time
|
||||
from pathlib import Path
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).parent))
|
||||
|
||||
from config import BIN_DIR
|
||||
|
||||
CHARSET = string.ascii_letters + string.digits + "!@#$%^&*()"
|
||||
FIRST_CHARS = string.ascii_letters
|
||||
|
||||
MAX_HOURS = 8
|
||||
|
||||
|
||||
def get_access_cmd() -> list[str]:
|
||||
"""Resolve path to confaccess utility."""
|
||||
for name in ("confaccess", "access"):
|
||||
path = shutil.which(name)
|
||||
if path:
|
||||
return [path]
|
||||
script_dir = Path(__file__).resolve().parent
|
||||
confaccess_script = script_dir / "confaccess.py"
|
||||
if confaccess_script.exists():
|
||||
return [sys.executable, str(confaccess_script)]
|
||||
for name in ("confaccess", "access"):
|
||||
bin_cmd = BIN_DIR / name
|
||||
if bin_cmd.exists():
|
||||
return [str(bin_cmd)]
|
||||
return ["confaccess"]
|
||||
|
||||
|
||||
def max_combinations(length: int) -> int:
|
||||
if length == 1:
|
||||
return len(FIRST_CHARS)
|
||||
return len(FIRST_CHARS) * (len(CHARSET) ** (length - 1))
|
||||
|
||||
|
||||
def brute_force_length(
|
||||
login: str, length: int, proc: subprocess.Popen[str]
|
||||
) -> tuple[str, int, float] | None:
|
||||
"""Try all passwords of given length via batch process. proc = confaccess --check."""
|
||||
count = 0
|
||||
start = time.perf_counter()
|
||||
stdin = proc.stdin
|
||||
stdout = proc.stdout
|
||||
assert stdin is not None and stdout is not None
|
||||
|
||||
def check(password: str) -> bool:
|
||||
nonlocal count
|
||||
count += 1
|
||||
stdin.write(password + "\n")
|
||||
stdin.flush()
|
||||
line = stdout.readline()
|
||||
if not line:
|
||||
return False
|
||||
return line.strip() == "1"
|
||||
|
||||
if length == 1:
|
||||
for first in FIRST_CHARS:
|
||||
if time.perf_counter() - start > MAX_HOURS * 3600:
|
||||
return None
|
||||
if check(first):
|
||||
return first, count, time.perf_counter() - start
|
||||
return None
|
||||
|
||||
for first in FIRST_CHARS:
|
||||
for rest in itertools.product(CHARSET, repeat=length - 1):
|
||||
if time.perf_counter() - start > MAX_HOURS * 3600:
|
||||
return None
|
||||
password = first + "".join(rest)
|
||||
if check(password):
|
||||
return password, count, time.perf_counter() - start
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def main() -> None:
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Brute force password cracker (via confaccess utility)"
|
||||
)
|
||||
parser.add_argument("login", help="Target username")
|
||||
parser.add_argument(
|
||||
"--max-length",
|
||||
type=int,
|
||||
default=6,
|
||||
help="Maximum password length to try (default: 6)",
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
cmd = get_access_cmd() + ["--check", args.login]
|
||||
proc = subprocess.Popen(
|
||||
cmd,
|
||||
stdin=subprocess.PIPE,
|
||||
stdout=subprocess.PIPE,
|
||||
text=True,
|
||||
bufsize=1,
|
||||
)
|
||||
try:
|
||||
print(f"Target: {args.login}")
|
||||
print(f"Charset size: {len(CHARSET)} ({len(FIRST_CHARS)} valid for first char)")
|
||||
print()
|
||||
|
||||
total_start = time.perf_counter()
|
||||
for length in range(1, args.max_length + 1):
|
||||
total = max_combinations(length)
|
||||
print(f"Length {length}: max {total:>15,} combinations")
|
||||
|
||||
result = brute_force_length(args.login, length, proc)
|
||||
|
||||
if result is not None:
|
||||
password, count, elapsed = result
|
||||
total_elapsed = time.perf_counter() - total_start
|
||||
print(f" >>> FOUND: '{password}'")
|
||||
print(f" Iterations: {count:,}")
|
||||
print(f" Time (len): {elapsed:.4f}s")
|
||||
print(f" Time (total): {total_elapsed:.4f}s")
|
||||
print(f" Speed: {count / elapsed:,.0f} attempts/s")
|
||||
return
|
||||
else:
|
||||
print(f" Not found at length {length} (timeout or exhausted)")
|
||||
|
||||
print(f"\nPassword not found within length {args.max_length}.")
|
||||
finally:
|
||||
if proc.stdin:
|
||||
proc.stdin.close()
|
||||
if proc.poll() is None:
|
||||
proc.terminate()
|
||||
proc.wait()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
518
lab3/confaccess.py
Normal file
518
lab3/confaccess.py
Normal file
@@ -0,0 +1,518 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Access to confidential data with DAC and MAC."""
|
||||
|
||||
import argparse
|
||||
import getpass
|
||||
import hashlib
|
||||
import shutil
|
||||
import signal
|
||||
import sys
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).parent))
|
||||
|
||||
from config import (
|
||||
ACL_FILE,
|
||||
ACCESS_MODE_FILE,
|
||||
CONFDATA_DIR,
|
||||
LOG_DIR,
|
||||
OBJECT_LABELS_FILE,
|
||||
PASSWD_FILE,
|
||||
SUBJECT_LABELS_FILE,
|
||||
)
|
||||
|
||||
HELP_TEXT = """\
|
||||
Commands:
|
||||
create <file> create new empty file
|
||||
read <file> print file contents
|
||||
append <file> <text> append text line to file
|
||||
copy <src> <dst> copy file into confdata
|
||||
remove <file> delete file from confdata
|
||||
grant <user> <path> <perms> grant access (owner only)
|
||||
help show this help
|
||||
exit exit"""
|
||||
|
||||
|
||||
def hash_password(password: str) -> str:
|
||||
return hashlib.sha256(password.encode("ascii")).hexdigest()
|
||||
|
||||
|
||||
def log_action(login: str, action: str) -> None:
|
||||
LOG_DIR.mkdir(parents=True, exist_ok=True)
|
||||
timestamp = datetime.now().isoformat(sep=" ", timespec="seconds")
|
||||
with open(LOG_DIR / "access.log", "a") as f:
|
||||
f.write(f"{timestamp} [{login}] {action}\n")
|
||||
|
||||
|
||||
def read_users() -> dict[str, dict]:
|
||||
users: dict[str, dict] = {}
|
||||
if not PASSWD_FILE.exists():
|
||||
return users
|
||||
with open(PASSWD_FILE) as f:
|
||||
for line in f:
|
||||
line = line.strip()
|
||||
if not line:
|
||||
continue
|
||||
parts = line.split(":", 4)
|
||||
if len(parts) != 5:
|
||||
continue
|
||||
users[parts[0]] = {
|
||||
"password_hash": parts[1],
|
||||
"id": parts[2],
|
||||
"permissions": parts[3],
|
||||
"full_name": parts[4],
|
||||
}
|
||||
return users
|
||||
|
||||
|
||||
def read_access_mode() -> str:
|
||||
if not ACCESS_MODE_FILE.exists():
|
||||
return "BOTH"
|
||||
raw = ACCESS_MODE_FILE.read_text().strip().upper()
|
||||
if raw in ("BOTH", "DAC_ONLY", "MAC_ONLY"):
|
||||
return raw
|
||||
return "BOTH"
|
||||
|
||||
|
||||
def normalize_path(path: str) -> str:
|
||||
"""Normalize path relative to confdata (no ./, no trailing /)."""
|
||||
p = path.strip().lstrip("./")
|
||||
parts = [x for x in p.split("/") if x]
|
||||
return "/".join(parts) if parts else ""
|
||||
|
||||
|
||||
def read_acl() -> dict[str, dict]:
|
||||
"""Read ACL: path -> {owner, perms: {user: perms_str}}."""
|
||||
acl: dict[str, dict] = {}
|
||||
if not ACL_FILE.exists():
|
||||
return acl
|
||||
for line in ACL_FILE.read_text().splitlines():
|
||||
line = line.strip()
|
||||
if not line:
|
||||
continue
|
||||
parts = line.split(":", 2)
|
||||
if len(parts) < 3:
|
||||
continue
|
||||
path, owner, assignments = parts[0], parts[1], parts[2]
|
||||
path = normalize_path(path)
|
||||
perms: dict[str, str] = {}
|
||||
for ass in assignments.split(","):
|
||||
if ":" in ass:
|
||||
u, p = ass.split(":", 1)
|
||||
perms[u.strip()] = p.strip()
|
||||
acl[path] = {"owner": owner, "perms": perms}
|
||||
return acl
|
||||
|
||||
|
||||
def write_acl(acl: dict[str, dict]) -> None:
|
||||
ACL_FILE.parent.mkdir(parents=True, exist_ok=True)
|
||||
lines = []
|
||||
for path in sorted(acl.keys()):
|
||||
entry = acl[path]
|
||||
owner = entry["owner"]
|
||||
perms = entry["perms"]
|
||||
ass = ",".join(f"{u}:{p}" for u, p in sorted(perms.items()))
|
||||
lines.append(f"{path}:{owner}:{ass}")
|
||||
ACL_FILE.write_text("\n".join(lines) + "\n")
|
||||
|
||||
|
||||
def read_subject_labels() -> dict[str, int]:
|
||||
labels: dict[str, int] = {}
|
||||
if not SUBJECT_LABELS_FILE.exists():
|
||||
return labels
|
||||
for line in SUBJECT_LABELS_FILE.read_text().splitlines():
|
||||
line = line.strip()
|
||||
if not line:
|
||||
continue
|
||||
parts = line.split(":", 1)
|
||||
if len(parts) == 2 and parts[1] in ("0", "1", "2"):
|
||||
labels[parts[0]] = int(parts[1])
|
||||
return labels
|
||||
|
||||
|
||||
def read_object_labels() -> dict[str, int]:
|
||||
labels: dict[str, int] = {}
|
||||
if not OBJECT_LABELS_FILE.exists():
|
||||
return labels
|
||||
for line in OBJECT_LABELS_FILE.read_text().splitlines():
|
||||
line = line.strip()
|
||||
if not line:
|
||||
continue
|
||||
parts = line.split(":", 1)
|
||||
if len(parts) == 2 and parts[1] in ("0", "1", "2"):
|
||||
labels[parts[0]] = int(parts[1])
|
||||
return labels
|
||||
|
||||
|
||||
def write_object_labels(labels: dict[str, int]) -> None:
|
||||
OBJECT_LABELS_FILE.parent.mkdir(parents=True, exist_ok=True)
|
||||
lines = [f"{path}:{level}" for path, level in sorted(labels.items())]
|
||||
OBJECT_LABELS_FILE.write_text("\n".join(lines) + "\n")
|
||||
|
||||
|
||||
def dac_allows(login: str, action: str, rel_path: str) -> bool:
|
||||
"""Check DAC: does login have required permission on object?"""
|
||||
rel_path = normalize_path(rel_path)
|
||||
acl = read_acl()
|
||||
if action in ("create", "create_dst"):
|
||||
# New object: not in ACL yet, any authenticated user can create
|
||||
if rel_path not in acl:
|
||||
return True
|
||||
entry = acl.get(rel_path)
|
||||
if not entry:
|
||||
return False
|
||||
perms = entry["perms"].get(login, "")
|
||||
if action == "read":
|
||||
return "r" in perms
|
||||
if action in ("write", "append", "create_dst"):
|
||||
return "w" in perms
|
||||
if action == "remove":
|
||||
return "d" in perms
|
||||
if action == "grant":
|
||||
return entry["owner"] == login
|
||||
return False
|
||||
|
||||
|
||||
def dac_owner(rel_path: str) -> str | None:
|
||||
"""Return owner of path or None."""
|
||||
rel_path = normalize_path(rel_path)
|
||||
acl = read_acl()
|
||||
entry = acl.get(rel_path)
|
||||
return entry["owner"] if entry else None
|
||||
|
||||
|
||||
def mac_allows(login: str, action: str, rel_path: str, obj_level: int | None = None) -> bool:
|
||||
"""Check MAC (Bell-LaPadula)."""
|
||||
labels = read_subject_labels()
|
||||
subj_level = labels.get(login, 0)
|
||||
obj_labels = read_object_labels()
|
||||
norm_path = normalize_path(rel_path)
|
||||
if obj_level is None:
|
||||
obj_level = obj_labels.get(norm_path)
|
||||
if action == "read":
|
||||
# No read up: subject_level >= object_level
|
||||
obj_lev = obj_level if obj_level is not None else 0
|
||||
return subj_level >= obj_lev
|
||||
if action in ("write", "append", "remove"):
|
||||
obj_lev = obj_level if obj_level is not None else 0
|
||||
return subj_level <= obj_lev
|
||||
if action == "create_dst":
|
||||
# New object gets subject's label; subject can write to same level
|
||||
return True
|
||||
return True
|
||||
|
||||
|
||||
def check_access(login: str, action: str, rel_path: str, mode: str) -> bool:
|
||||
if mode in ("BOTH", "DAC_ONLY"):
|
||||
if not dac_allows(login, action, rel_path):
|
||||
return False
|
||||
if mode in ("BOTH", "MAC_ONLY"):
|
||||
if not mac_allows(login, action, rel_path):
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def authenticate() -> tuple[str, dict]:
|
||||
users = read_users()
|
||||
while True:
|
||||
try:
|
||||
login = input("Login: ").strip()
|
||||
password = getpass.getpass("Password: ")
|
||||
except (EOFError, KeyboardInterrupt):
|
||||
print("\nBye.")
|
||||
sys.exit(0)
|
||||
|
||||
user = users.get(login)
|
||||
if user and user["password_hash"] == hash_password(password):
|
||||
return login, user
|
||||
log_action(login if login else "-", "LOGIN_FAILED")
|
||||
print("Invalid credentials. Try again.")
|
||||
|
||||
|
||||
def confdata_path(arg: str) -> Path:
|
||||
p = Path(arg)
|
||||
if not p.is_absolute():
|
||||
p = CONFDATA_DIR / p
|
||||
return p.resolve()
|
||||
|
||||
|
||||
def rel_path_from_confdata(path: Path) -> str:
|
||||
try:
|
||||
rel = path.relative_to(CONFDATA_DIR.resolve())
|
||||
return "/".join(rel.parts)
|
||||
except ValueError:
|
||||
return ""
|
||||
|
||||
|
||||
def is_in_confdata(path: Path) -> bool:
|
||||
try:
|
||||
path.relative_to(CONFDATA_DIR.resolve())
|
||||
return True
|
||||
except ValueError:
|
||||
return False
|
||||
|
||||
|
||||
def cmd_read(args: list[str], login: str, mode: str) -> None:
|
||||
if len(args) != 1:
|
||||
print("Usage: read <file>")
|
||||
return
|
||||
path = confdata_path(args[0])
|
||||
if not is_in_confdata(path):
|
||||
print("Access denied: file must be inside confdata")
|
||||
return
|
||||
if not path.exists():
|
||||
print(f"File not found: {path.name}")
|
||||
return
|
||||
rel = rel_path_from_confdata(path)
|
||||
if not check_access(login, "read", rel, mode):
|
||||
print("Permission denied")
|
||||
return
|
||||
print(path.read_text(), end="")
|
||||
log_action(login, f"READ {path}")
|
||||
|
||||
|
||||
def cmd_create(args: list[str], login: str, mode: str) -> None:
|
||||
if len(args) != 1:
|
||||
print("Usage: create <file>")
|
||||
return
|
||||
path = confdata_path(args[0])
|
||||
if not is_in_confdata(path):
|
||||
print("Access denied: file must be inside confdata")
|
||||
return
|
||||
if path.exists():
|
||||
print(f"File already exists: {path.name}")
|
||||
return
|
||||
rel = rel_path_from_confdata(path)
|
||||
# DAC: new object, add to ACL with owner=login
|
||||
# MAC: new object gets subject's label
|
||||
if mode in ("BOTH", "DAC_ONLY"):
|
||||
acl = read_acl()
|
||||
acl[rel] = {"owner": login, "perms": {login: "rwd"}}
|
||||
write_acl(acl)
|
||||
if mode in ("BOTH", "MAC_ONLY"):
|
||||
labels = read_subject_labels()
|
||||
subj_level = labels.get(login, 0)
|
||||
obj_labels = read_object_labels()
|
||||
obj_labels[rel] = subj_level
|
||||
write_object_labels(obj_labels)
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
path.touch()
|
||||
log_action(login, f"CREATE {path}")
|
||||
print("Done.")
|
||||
|
||||
|
||||
def cmd_append(args: list[str], login: str, mode: str) -> None:
|
||||
if len(args) < 2:
|
||||
print("Usage: append <file> <text>")
|
||||
return
|
||||
path = confdata_path(args[0])
|
||||
if not is_in_confdata(path):
|
||||
print("Access denied: file must be inside confdata")
|
||||
return
|
||||
if not path.exists():
|
||||
print(f"File not found: {path.name}. Use 'create' first.")
|
||||
return
|
||||
rel = rel_path_from_confdata(path)
|
||||
if not check_access(login, "append", rel, mode):
|
||||
print("Permission denied")
|
||||
return
|
||||
text = " ".join(args[1:])
|
||||
with open(path, "a") as f:
|
||||
f.write(text + "\n")
|
||||
log_action(login, f"APPEND {path}")
|
||||
print("Done.")
|
||||
|
||||
|
||||
def cmd_copy(args: list[str], login: str, mode: str) -> None:
|
||||
if len(args) != 2:
|
||||
print("Usage: copy <src> <dst>")
|
||||
return
|
||||
|
||||
src = Path(args[0]).resolve()
|
||||
dst = confdata_path(args[1])
|
||||
|
||||
if not is_in_confdata(dst):
|
||||
print("Access denied: destination must be inside confdata")
|
||||
return
|
||||
if dst.is_dir():
|
||||
dst = dst / src.name
|
||||
if dst.exists():
|
||||
print(f"Destination already exists: {dst.name}")
|
||||
return
|
||||
if not src.exists():
|
||||
print(f"Source not found: {args[0]}")
|
||||
return
|
||||
if src.is_dir():
|
||||
print("Copying directories is not supported")
|
||||
return
|
||||
|
||||
dst_rel = rel_path_from_confdata(dst)
|
||||
src_rel = rel_path_from_confdata(src) if is_in_confdata(src) else None
|
||||
|
||||
if src_rel is not None:
|
||||
if not check_access(login, "read", src_rel, mode):
|
||||
print("Permission denied (read source)")
|
||||
return
|
||||
if not check_access(login, "create_dst", dst_rel, mode):
|
||||
print("Permission denied (create destination)")
|
||||
return
|
||||
|
||||
shutil.copy2(src, dst)
|
||||
if mode in ("BOTH", "DAC_ONLY"):
|
||||
acl = read_acl()
|
||||
acl[dst_rel] = {"owner": login, "perms": {login: "rwd"}}
|
||||
write_acl(acl)
|
||||
if mode in ("BOTH", "MAC_ONLY"):
|
||||
labels = read_subject_labels()
|
||||
subj_level = labels.get(login, 0)
|
||||
obj_labels = read_object_labels()
|
||||
obj_labels[dst_rel] = subj_level
|
||||
write_object_labels(obj_labels)
|
||||
log_action(login, f"COPY {src} -> {dst}")
|
||||
print("Done.")
|
||||
|
||||
|
||||
def cmd_remove(args: list[str], login: str, mode: str) -> None:
|
||||
if len(args) != 1:
|
||||
print("Usage: remove <file>")
|
||||
return
|
||||
path = confdata_path(args[0])
|
||||
if not is_in_confdata(path):
|
||||
print("Access denied: file must be inside confdata")
|
||||
return
|
||||
if not path.exists():
|
||||
print(f"File not found: {path.name}")
|
||||
return
|
||||
rel = rel_path_from_confdata(path)
|
||||
if not check_access(login, "remove", rel, mode):
|
||||
print("Permission denied")
|
||||
return
|
||||
path.unlink()
|
||||
if mode in ("BOTH", "DAC_ONLY"):
|
||||
acl = read_acl()
|
||||
acl.pop(rel, None)
|
||||
write_acl(acl)
|
||||
if mode in ("BOTH", "MAC_ONLY"):
|
||||
obj_labels = read_object_labels()
|
||||
obj_labels.pop(rel, None)
|
||||
write_object_labels(obj_labels)
|
||||
log_action(login, f"REMOVE {path}")
|
||||
print("Done.")
|
||||
|
||||
|
||||
def cmd_grant(args: list[str], login: str, mode: str) -> None:
|
||||
if mode not in ("BOTH", "DAC_ONLY"):
|
||||
print("grant is only available in DAC or BOTH mode")
|
||||
return
|
||||
if len(args) != 3:
|
||||
print("Usage: grant <user> <path> <perms>")
|
||||
return
|
||||
target_user, path_arg, perms = args[0], args[1], args[2]
|
||||
for ch in perms:
|
||||
if ch not in "rwd":
|
||||
print(f"Invalid permission {ch!r}; allowed: r, w, d")
|
||||
return
|
||||
path = confdata_path(path_arg)
|
||||
if not is_in_confdata(path):
|
||||
print("Access denied: path must be inside confdata")
|
||||
return
|
||||
if not path.exists():
|
||||
print(f"File not found: {path_arg}")
|
||||
return
|
||||
rel = rel_path_from_confdata(path)
|
||||
if not dac_allows(login, "grant", rel):
|
||||
print("Permission denied (only owner can grant)")
|
||||
return
|
||||
acl = read_acl()
|
||||
entry = acl.get(rel)
|
||||
if not entry:
|
||||
print("ACL entry not found")
|
||||
return
|
||||
entry["perms"][target_user] = perms
|
||||
write_acl(acl)
|
||||
log_action(login, f"GRANT {target_user} {rel} {perms}")
|
||||
print("Done.")
|
||||
|
||||
|
||||
def check_credentials(login: str, password: str) -> bool:
|
||||
"""Check login+password against passwd. Used by --check mode."""
|
||||
users = read_users()
|
||||
user = users.get(login)
|
||||
if user and user["password_hash"] == hash_password(password):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def main() -> None:
|
||||
parser = argparse.ArgumentParser(description="Access confidential data")
|
||||
parser.add_argument(
|
||||
"--check",
|
||||
metavar="LOGIN",
|
||||
help="Batch mode: read passwords line-by-line from stdin, output 0 or 1 per line; exit 0 on first match",
|
||||
)
|
||||
args, _ = parser.parse_known_args()
|
||||
|
||||
if args.check is not None:
|
||||
users = read_users()
|
||||
user = users.get(args.check)
|
||||
target_hash = user["password_hash"] if user else None
|
||||
|
||||
for line in sys.stdin:
|
||||
password = line.rstrip("\n")
|
||||
if target_hash and hash_password(password) == target_hash:
|
||||
sys.stdout.write("1\n")
|
||||
sys.stdout.flush()
|
||||
sys.exit(0)
|
||||
sys.stdout.write("0\n")
|
||||
sys.stdout.flush()
|
||||
sys.exit(1)
|
||||
|
||||
signal.signal(signal.SIGINT, lambda _s, _f: (print("\nBye."), sys.exit(0)))
|
||||
|
||||
login, user = authenticate()
|
||||
full_name = user["full_name"]
|
||||
mode = read_access_mode()
|
||||
|
||||
log_action(login, "LOGIN")
|
||||
print(f"\nПривет, {full_name}")
|
||||
print(HELP_TEXT)
|
||||
|
||||
while True:
|
||||
try:
|
||||
line = input(f"\n{login}> ").strip()
|
||||
except (EOFError, KeyboardInterrupt):
|
||||
log_action(login, "EXIT")
|
||||
print("\nBye.")
|
||||
break
|
||||
|
||||
if not line:
|
||||
continue
|
||||
|
||||
parts = line.split()
|
||||
command, cmd_args = parts[0], parts[1:]
|
||||
|
||||
if command == "exit":
|
||||
log_action(login, "EXIT")
|
||||
print("Bye.")
|
||||
break
|
||||
elif command == "help":
|
||||
print(HELP_TEXT)
|
||||
elif command == "create":
|
||||
cmd_create(cmd_args, login, mode)
|
||||
elif command == "read":
|
||||
cmd_read(cmd_args, login, mode)
|
||||
elif command == "append":
|
||||
cmd_append(cmd_args, login, mode)
|
||||
elif command == "copy":
|
||||
cmd_copy(cmd_args, login, mode)
|
||||
elif command == "remove":
|
||||
cmd_remove(cmd_args, login, mode)
|
||||
elif command == "grant":
|
||||
cmd_grant(cmd_args, login, mode)
|
||||
else:
|
||||
print(f"Unknown command: {command!r}. Type 'help' for available commands.")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
15
lab3/config.py
Normal file
15
lab3/config.py
Normal file
@@ -0,0 +1,15 @@
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
BASE_DIR = Path(os.environ.get("PRACTICE3_DIR", "/usr/local/practice3"))
|
||||
|
||||
ETC_DIR = BASE_DIR / "etc"
|
||||
CONFDATA_DIR = BASE_DIR / "confdata"
|
||||
BIN_DIR = BASE_DIR / "bin"
|
||||
LOG_DIR = BASE_DIR / "log"
|
||||
|
||||
PASSWD_FILE = ETC_DIR / "passwd"
|
||||
ACCESS_MODE_FILE = ETC_DIR / "access_mode"
|
||||
ACL_FILE = ETC_DIR / "acl"
|
||||
SUBJECT_LABELS_FILE = ETC_DIR / "subject_labels"
|
||||
OBJECT_LABELS_FILE = ETC_DIR / "object_labels"
|
||||
108
lab3/lab3.md
Normal file
108
lab3/lab3.md
Normal file
@@ -0,0 +1,108 @@
|
||||
|
||||
# Практическая работа №3
|
||||
**Дисциплина:** Защита информации
|
||||
**Тема:** Реализация моделей дискреционного и мандатного управления доступом
|
||||
|
||||
**Преподаватель:** Силиненко А.В.
|
||||
**Email:** a_silinenko@mail.ru
|
||||
|
||||
---
|
||||
|
||||
## 1. Цели работы
|
||||
|
||||
- Изучить особенности моделей дискреционного (DAC) и мандатного (MAC) управления доступом.
|
||||
- На базе разработанной в практической работе 2 системы аутентификации и авторизации реализовать DAC и MAC.
|
||||
|
||||
---
|
||||
|
||||
## 2. Задачи работы
|
||||
|
||||
### 2.1
|
||||
Изучить особенности дискреционного (произвольного) управления доступом, а также модель Харрисона–Руззо–Ульмана, основанную на DAC.
|
||||
|
||||
### 2.2
|
||||
На базе разработанной в практической работе 2 системы аутентификации и авторизации разработать систему, реализующую DAC, включая:
|
||||
|
||||
- владение объектами;
|
||||
- произвольное назначение прав доступа к объектам;
|
||||
- контроль на основе матрицы или списка доступа.
|
||||
|
||||
### 2.3
|
||||
Изучить особенности мандатного (принудительного) управления доступом, а также модель Белла–Лападулы, основанную на MAC.
|
||||
|
||||
### 2.4
|
||||
На базе разработанной в практической работе 2 системы аутентификации и авторизации разработать систему, реализующую MAC, включая:
|
||||
|
||||
- метки безопасности для субъектов и объектов;
|
||||
- свойства безопасности модели Белла–Лападулы.
|
||||
|
||||
---
|
||||
|
||||
## 3. Требования к работе
|
||||
|
||||
### 3.1
|
||||
Работа выполняется в ОС **Linux** или **MacOS**.
|
||||
|
||||
### 3.2
|
||||
Для выполнения работы необходимо создать отдельное дерево каталогов, например:
|
||||
|
||||
```
|
||||
/usr/local/practice3/etc – файлы настроек
|
||||
/usr/local/practice3/confdata – файлы с конфиденциальной информацией
|
||||
/usr/local/practice3/bin – исполняемые файлы
|
||||
/usr/local/practice3/log – файлы регистрации
|
||||
```
|
||||
|
||||
Права доступа к созданным каталогам: **чтение, запись и выполнение только для пользователя root**.
|
||||
|
||||
При необходимости возможно создание дополнительных каталогов.
|
||||
|
||||
---
|
||||
|
||||
### 3.3 Требования к системе DAC
|
||||
|
||||
- у каждого объекта должен быть владелец, который может произвольно назначать и передавать права доступа к этому объекту;
|
||||
- контроль доступа к объектам должен осуществляться на основе **матрицы или списка доступа**, где хранится информация о правах доступа субъектов к объектам.
|
||||
|
||||
---
|
||||
|
||||
### 3.4 Требования к системе MAC
|
||||
|
||||
- каждому субъекту и объекту должна присваиваться **метка безопасности суперпользователем**;
|
||||
- предусмотреть **не менее трех уровней меток безопасности**, например:
|
||||
- несекретно
|
||||
- для служебного пользования (ДСП)
|
||||
- секретно
|
||||
- субъекты не могут менять метки безопасности;
|
||||
- контроль доступа должен осуществляться на основе свойств безопасности **модели Белла–Лападулы**:
|
||||
- «нет чтения сверху»
|
||||
- «нет записи вниз»
|
||||
- дискреционное свойство
|
||||
|
||||
---
|
||||
|
||||
## 4. Требования к отчету
|
||||
|
||||
В разделе отчета должны быть приведены:
|
||||
|
||||
- актуальность темы работы в контексте дисциплины;
|
||||
- цель и задачи работы;
|
||||
- требования к разрабатываемым системам;
|
||||
- краткие сведения о DAC и MAC;
|
||||
- примеры компиляции и сборки приложений (если используется компилируемый язык);
|
||||
- примеры запуска и работы утилит доступа к конфиденциальной информации;
|
||||
- примеры содержимого служебных файлов:
|
||||
- матрицы (или списка) доступа для DAC
|
||||
- меток конфиденциальности для MAC;
|
||||
- выводы по проделанной работе.
|
||||
|
||||
---
|
||||
|
||||
Примечание
|
||||
|
||||
Та система котрая нами уже разработана в lab2
|
||||
1. Необходимо выпустить ее реинкарнацию с учетом требований к тому что это должен быть дискреционный подход к управлению доступа
|
||||
Свойства системы которые необходимо будет реализовать будет в задании
|
||||
2. Необходимо реализовать мандатный подход к управлению доступом
|
||||
|
||||
Структура остается прежней нам просто нужно добавить компоненты для первого и второго подходов
|
||||
43
lab3/setup.sh
Executable file
43
lab3/setup.sh
Executable file
@@ -0,0 +1,43 @@
|
||||
#!/usr/bin/env bash
|
||||
set -e
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
BASE_DIR="${PRACTICE3_DIR:-/usr/local/practice3}"
|
||||
|
||||
echo "Setting up directory structure at: $BASE_DIR"
|
||||
|
||||
mkdir -p "$BASE_DIR/etc" "$BASE_DIR/confdata" "$BASE_DIR/bin" "$BASE_DIR/log"
|
||||
|
||||
touch "$BASE_DIR/etc/passwd"
|
||||
echo "BOTH" > "$BASE_DIR/etc/access_mode"
|
||||
touch "$BASE_DIR/etc/acl"
|
||||
touch "$BASE_DIR/etc/subject_labels"
|
||||
touch "$BASE_DIR/etc/object_labels"
|
||||
|
||||
cp "$SCRIPT_DIR/config.py" "$BASE_DIR/bin/config.py"
|
||||
cp "$SCRIPT_DIR/usermgr.py" "$BASE_DIR/bin/usermgr"
|
||||
cp "$SCRIPT_DIR/confaccess.py" "$BASE_DIR/bin/confaccess"
|
||||
cp "$SCRIPT_DIR/bruteforce.py" "$BASE_DIR/bin/bruteforce"
|
||||
|
||||
chmod +x "$BASE_DIR/bin/usermgr" "$BASE_DIR/bin/confaccess" "$BASE_DIR/bin/bruteforce"
|
||||
|
||||
if [ "$(id -u)" -eq 0 ]; then
|
||||
chmod 700 "$BASE_DIR" "$BASE_DIR/etc" "$BASE_DIR/confdata" "$BASE_DIR/bin" "$BASE_DIR/log"
|
||||
chmod 600 "$BASE_DIR/etc/passwd" "$BASE_DIR/etc/access_mode" \
|
||||
"$BASE_DIR/etc/acl" "$BASE_DIR/etc/subject_labels" "$BASE_DIR/etc/object_labels"
|
||||
echo "Permissions set (root-only)."
|
||||
else
|
||||
echo "Warning: not running as root; skipping permission hardening."
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "Done. Directory layout:"
|
||||
ls -la "$BASE_DIR"
|
||||
echo ""
|
||||
echo "Next steps:"
|
||||
echo " $BASE_DIR/bin/usermgr add <login>"
|
||||
echo " $BASE_DIR/bin/confaccess"
|
||||
echo " $BASE_DIR/bin/bruteforce <login>"
|
||||
echo ""
|
||||
echo "To add bin to PATH temporarily:"
|
||||
echo " export PATH=\"$BASE_DIR/bin:\$PATH\""
|
||||
378
lab3/usermgr.py
Normal file
378
lab3/usermgr.py
Normal file
@@ -0,0 +1,378 @@
|
||||
#!/usr/bin/env python3
|
||||
import argparse
|
||||
import getpass
|
||||
import hashlib
|
||||
import os
|
||||
import sys
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).parent))
|
||||
|
||||
from config import (
|
||||
ACCESS_MODE_FILE,
|
||||
ETC_DIR,
|
||||
LOG_DIR,
|
||||
OBJECT_LABELS_FILE,
|
||||
PASSWD_FILE,
|
||||
SUBJECT_LABELS_FILE,
|
||||
)
|
||||
|
||||
ALLOWED_CHARS = set(
|
||||
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*()"
|
||||
)
|
||||
FIRST_CHAR_ALLOWED = set("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz")
|
||||
VALID_PERM_CHARS = set("rwd")
|
||||
VALID_LABELS = frozenset({"0", "1", "2"})
|
||||
|
||||
|
||||
def require_root() -> None:
|
||||
"""Exit if not running as root."""
|
||||
if os.geteuid() != 0:
|
||||
print("Must run as root", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def hash_password(password: str) -> str:
|
||||
return hashlib.sha256(password.encode("ascii")).hexdigest()
|
||||
|
||||
|
||||
def validate_password(password: str) -> str | None:
|
||||
if not password:
|
||||
return "password cannot be empty"
|
||||
if password[0] not in FIRST_CHAR_ALLOWED:
|
||||
return "first character must be a letter (A-Z, a-z)"
|
||||
invalid = [ch for ch in password if ch not in ALLOWED_CHARS]
|
||||
if invalid:
|
||||
return f"invalid characters: {''.join(set(invalid))!r}"
|
||||
return None
|
||||
|
||||
|
||||
def validate_permissions(perms: str) -> str | None:
|
||||
if not perms:
|
||||
return "permissions cannot be empty"
|
||||
for ch in perms:
|
||||
if ch not in VALID_PERM_CHARS:
|
||||
return f"invalid permission {ch!r}; allowed: r, w, d"
|
||||
if len(set(perms)) != len(perms):
|
||||
return "duplicate permissions"
|
||||
return None
|
||||
|
||||
|
||||
def log_action(action: str) -> None:
|
||||
LOG_DIR.mkdir(parents=True, exist_ok=True)
|
||||
timestamp = datetime.now().isoformat(sep=" ", timespec="seconds")
|
||||
with open(LOG_DIR / "usermgr.log", "a") as f:
|
||||
f.write(f"{timestamp} {action}\n")
|
||||
|
||||
|
||||
def read_users() -> list[dict]:
|
||||
if not PASSWD_FILE.exists():
|
||||
return []
|
||||
users = []
|
||||
with open(PASSWD_FILE) as f:
|
||||
for line in f:
|
||||
line = line.strip()
|
||||
if not line:
|
||||
continue
|
||||
parts = line.split(":", 4)
|
||||
if len(parts) != 5:
|
||||
continue
|
||||
users.append(
|
||||
{
|
||||
"login": parts[0],
|
||||
"password_hash": parts[1],
|
||||
"id": parts[2],
|
||||
"permissions": parts[3],
|
||||
"full_name": parts[4],
|
||||
}
|
||||
)
|
||||
return users
|
||||
|
||||
|
||||
def write_users(users: list[dict]) -> None:
|
||||
PASSWD_FILE.parent.mkdir(parents=True, exist_ok=True)
|
||||
with open(PASSWD_FILE, "w") as f:
|
||||
for u in users:
|
||||
f.write(
|
||||
f"{u['login']}:{u['password_hash']}:{u['id']}:{u['permissions']}:{u['full_name']}\n"
|
||||
)
|
||||
|
||||
|
||||
def find_user(users: list[dict], login: str) -> dict | None:
|
||||
return next((u for u in users if u["login"] == login), None)
|
||||
|
||||
|
||||
def next_uid(users: list[dict]) -> str:
|
||||
if not users:
|
||||
return "1"
|
||||
return str(max(int(u["id"]) for u in users) + 1)
|
||||
|
||||
|
||||
def prompt_password() -> str:
|
||||
while True:
|
||||
password = getpass.getpass("Password: ")
|
||||
err = validate_password(password)
|
||||
if err:
|
||||
print(f"Invalid password: {err}")
|
||||
continue
|
||||
confirm = getpass.getpass("Confirm password: ")
|
||||
if password != confirm:
|
||||
print("Passwords do not match.")
|
||||
continue
|
||||
return password
|
||||
|
||||
|
||||
def read_access_mode() -> str:
|
||||
"""Read access mode; default BOTH if file missing."""
|
||||
if not ACCESS_MODE_FILE.exists():
|
||||
return "BOTH"
|
||||
raw = ACCESS_MODE_FILE.read_text().strip().upper()
|
||||
if raw in ("BOTH", "DAC_ONLY", "MAC_ONLY"):
|
||||
return raw
|
||||
return "BOTH"
|
||||
|
||||
|
||||
def write_access_mode(mode: str) -> None:
|
||||
ETC_DIR.mkdir(parents=True, exist_ok=True)
|
||||
ACCESS_MODE_FILE.write_text(mode + "\n")
|
||||
|
||||
|
||||
def read_subject_labels() -> dict[str, int]:
|
||||
"""Read subject labels; login -> level (0, 1, 2)."""
|
||||
labels: dict[str, int] = {}
|
||||
if not SUBJECT_LABELS_FILE.exists():
|
||||
return labels
|
||||
for line in SUBJECT_LABELS_FILE.read_text().splitlines():
|
||||
line = line.strip()
|
||||
if not line:
|
||||
continue
|
||||
parts = line.split(":", 1)
|
||||
if len(parts) == 2 and parts[1] in VALID_LABELS:
|
||||
labels[parts[0]] = int(parts[1])
|
||||
return labels
|
||||
|
||||
|
||||
def write_subject_labels(labels: dict[str, int]) -> None:
|
||||
ETC_DIR.mkdir(parents=True, exist_ok=True)
|
||||
lines = [f"{login}:{level}" for login, level in sorted(labels.items())]
|
||||
SUBJECT_LABELS_FILE.write_text("\n".join(lines) + "\n")
|
||||
|
||||
|
||||
def read_object_labels() -> dict[str, int]:
|
||||
"""Read object labels; path (relative to confdata) -> level."""
|
||||
labels: dict[str, int] = {}
|
||||
if not OBJECT_LABELS_FILE.exists():
|
||||
return labels
|
||||
for line in OBJECT_LABELS_FILE.read_text().splitlines():
|
||||
line = line.strip()
|
||||
if not line:
|
||||
continue
|
||||
parts = line.split(":", 1)
|
||||
if len(parts) == 2 and parts[1] in VALID_LABELS:
|
||||
labels[parts[0]] = int(parts[1])
|
||||
return labels
|
||||
|
||||
|
||||
def write_object_labels(labels: dict[str, int]) -> None:
|
||||
ETC_DIR.mkdir(parents=True, exist_ok=True)
|
||||
lines = [f"{path}:{level}" for path, level in sorted(labels.items())]
|
||||
OBJECT_LABELS_FILE.write_text("\n".join(lines) + "\n")
|
||||
|
||||
|
||||
def cmd_add(args: argparse.Namespace) -> None:
|
||||
users = read_users()
|
||||
login = args.login
|
||||
|
||||
if find_user(users, login):
|
||||
print(f"User '{login}' already exists.")
|
||||
sys.exit(1)
|
||||
|
||||
full_name = input("Full name: ").strip()
|
||||
if not full_name:
|
||||
print("Full name cannot be empty.")
|
||||
sys.exit(1)
|
||||
|
||||
perms = input("Permissions (any combination of r, w, d): ").strip()
|
||||
err = validate_permissions(perms)
|
||||
if err:
|
||||
print(f"Invalid permissions: {err}")
|
||||
sys.exit(1)
|
||||
|
||||
password = prompt_password()
|
||||
|
||||
uid = next_uid(users)
|
||||
users.append(
|
||||
{
|
||||
"login": login,
|
||||
"password_hash": hash_password(password),
|
||||
"id": uid,
|
||||
"permissions": perms,
|
||||
"full_name": full_name,
|
||||
}
|
||||
)
|
||||
write_users(users)
|
||||
log_action(f"ADD login={login} id={uid} permissions={perms} full_name='{full_name}'")
|
||||
print(f"User '{login}' added (id={uid}).")
|
||||
|
||||
|
||||
def cmd_edit(args: argparse.Namespace) -> None:
|
||||
if args.label is not None:
|
||||
require_root()
|
||||
|
||||
users = read_users()
|
||||
user = find_user(users, args.login)
|
||||
if not user:
|
||||
print(f"User '{args.login}' not found.")
|
||||
sys.exit(1)
|
||||
|
||||
changed: list[str] = []
|
||||
|
||||
if args.full_name is not None:
|
||||
if not args.full_name:
|
||||
print("Full name cannot be empty.")
|
||||
sys.exit(1)
|
||||
user["full_name"] = args.full_name
|
||||
changed.append("full_name")
|
||||
|
||||
if args.permissions is not None:
|
||||
err = validate_permissions(args.permissions)
|
||||
if err:
|
||||
print(f"Invalid permissions: {err}")
|
||||
sys.exit(1)
|
||||
user["permissions"] = args.permissions
|
||||
changed.append("permissions")
|
||||
|
||||
if args.label is not None:
|
||||
if args.label not in VALID_LABELS:
|
||||
print(f"Invalid label {args.label!r}; allowed: 0, 1, 2")
|
||||
sys.exit(1)
|
||||
labels = read_subject_labels()
|
||||
labels[args.login] = int(args.label)
|
||||
write_subject_labels(labels)
|
||||
changed.append("label")
|
||||
|
||||
if not changed:
|
||||
print("Nothing to change. Use --full-name, --permissions, and/or --label.")
|
||||
sys.exit(0)
|
||||
|
||||
if "full_name" in changed or "permissions" in changed:
|
||||
write_users(users)
|
||||
log_action(f"EDIT login={args.login} changed={','.join(changed)}")
|
||||
print(f"User '{args.login}' updated: {', '.join(changed)}.")
|
||||
|
||||
|
||||
def cmd_passwd(args: argparse.Namespace) -> None:
|
||||
users = read_users()
|
||||
user = find_user(users, args.login)
|
||||
if not user:
|
||||
print(f"User '{args.login}' not found.")
|
||||
sys.exit(1)
|
||||
|
||||
password = prompt_password()
|
||||
user["password_hash"] = hash_password(password)
|
||||
write_users(users)
|
||||
log_action(f"PASSWD login={args.login}")
|
||||
print(f"Password for '{args.login}' updated.")
|
||||
|
||||
|
||||
def cmd_delete(args: argparse.Namespace) -> None:
|
||||
users = read_users()
|
||||
if not find_user(users, args.login):
|
||||
print(f"User '{args.login}' not found.")
|
||||
sys.exit(1)
|
||||
|
||||
users = [u for u in users if u["login"] != args.login]
|
||||
write_users(users)
|
||||
log_action(f"DELETE login={args.login}")
|
||||
print(f"User '{args.login}' deleted.")
|
||||
|
||||
|
||||
def cmd_list(_args: argparse.Namespace) -> None:
|
||||
users = read_users()
|
||||
if not users:
|
||||
print("No users.")
|
||||
return
|
||||
print(f"{'Login':<16} {'ID':<4} {'Perms':<6} Full Name")
|
||||
print("-" * 52)
|
||||
for u in users:
|
||||
print(f"{u['login']:<16} {u['id']:<4} {u['permissions']:<6} {u['full_name']}")
|
||||
|
||||
|
||||
def cmd_set_mode(args: argparse.Namespace) -> None:
|
||||
require_root()
|
||||
mode = args.mode.upper()
|
||||
if mode not in ("BOTH", "DAC_ONLY", "MAC_ONLY"):
|
||||
print(f"Invalid mode {args.mode!r}; allowed: BOTH, DAC_ONLY, MAC_ONLY")
|
||||
sys.exit(1)
|
||||
write_access_mode(mode)
|
||||
log_action(f"SET_MODE mode={mode}")
|
||||
print(f"Access mode set to {mode}.")
|
||||
|
||||
|
||||
def cmd_show_mode(_args: argparse.Namespace) -> None:
|
||||
require_root()
|
||||
mode = read_access_mode()
|
||||
print(f"Current access mode: {mode}")
|
||||
|
||||
|
||||
def cmd_set_label(args: argparse.Namespace) -> None:
|
||||
require_root()
|
||||
if args.label not in VALID_LABELS:
|
||||
print(f"Invalid label {args.label!r}; allowed: 0, 1, 2")
|
||||
sys.exit(1)
|
||||
path = args.path
|
||||
if path.startswith("./"):
|
||||
path = path[2:]
|
||||
path = "/".join(p for p in path.split("/") if p)
|
||||
labels = read_object_labels()
|
||||
labels[path] = int(args.label)
|
||||
write_object_labels(labels)
|
||||
log_action(f"SET_LABEL path={path} label={args.label}")
|
||||
print(f"Object label for '{path}' set to {args.label}.")
|
||||
|
||||
|
||||
def main() -> None:
|
||||
parser = argparse.ArgumentParser(description="User management utility for practice3")
|
||||
sub = parser.add_subparsers(dest="command", required=True)
|
||||
|
||||
p_add = sub.add_parser("add", help="Add a new user")
|
||||
p_add.add_argument("login", help="Username (login)")
|
||||
p_add.set_defaults(func=cmd_add)
|
||||
|
||||
p_edit = sub.add_parser("edit", help="Edit an existing user")
|
||||
p_edit.add_argument("login", help="Username to edit")
|
||||
p_edit.add_argument("--full-name", dest="full_name", help="New full name")
|
||||
p_edit.add_argument("--permissions", help="New permissions (e.g. rwd)")
|
||||
p_edit.add_argument("--label", help="Security label (0, 1, 2) - root only")
|
||||
p_edit.set_defaults(func=cmd_edit)
|
||||
|
||||
p_passwd = sub.add_parser("passwd", help="Change user password")
|
||||
p_passwd.add_argument("login", help="Username")
|
||||
p_passwd.set_defaults(func=cmd_passwd)
|
||||
|
||||
p_del = sub.add_parser("delete", help="Delete a user")
|
||||
p_del.add_argument("login", help="Username to delete")
|
||||
p_del.set_defaults(func=cmd_delete)
|
||||
|
||||
p_list = sub.add_parser("list", help="List all users")
|
||||
p_list.set_defaults(func=cmd_list)
|
||||
|
||||
p_set_mode = sub.add_parser("set-mode", help="Set access mode (BOTH/DAC_ONLY/MAC_ONLY) - root only")
|
||||
p_set_mode.add_argument("mode", help="BOTH, DAC_ONLY, or MAC_ONLY")
|
||||
p_set_mode.set_defaults(func=cmd_set_mode)
|
||||
|
||||
p_show_mode = sub.add_parser("show-mode", help="Show current access mode - root only")
|
||||
p_show_mode.set_defaults(func=cmd_show_mode)
|
||||
|
||||
p_set_label = sub.add_parser("set-label", help="Set object security label - root only")
|
||||
p_set_label.add_argument("path", help="Path relative to confdata")
|
||||
p_set_label.add_argument("label", help="0, 1, or 2")
|
||||
p_set_label.set_defaults(func=cmd_set_label)
|
||||
|
||||
args = parser.parse_args()
|
||||
args.func(args)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user