This commit is contained in:
2026-03-16 14:43:38 +03:00
parent 37eff0ed9b
commit 8aeb9a4244
17 changed files with 1451 additions and 28 deletions

2
lab3/.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
*.pyc
*.pdf

94
lab3/README.md Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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()