diff --git a/.gitignore b/.gitignore index b6e4761..d728184 100644 --- a/.gitignore +++ b/.gitignore @@ -1,129 +1,4 @@ -# Byte-compiled / optimized / DLL files __pycache__/ -*.py[cod] -*$py.class - -# C extensions -*.so - -# Distribution / packaging -.Python -build/ -develop-eggs/ -dist/ -downloads/ -eggs/ -.eggs/ -lib/ -lib64/ -parts/ -sdist/ -var/ -wheels/ -pip-wheel-metadata/ -share/python-wheels/ -*.egg-info/ -.installed.cfg -*.egg -MANIFEST - -# PyInstaller -# Usually these files are written by a python script from a template -# before PyInstaller builds the exe, so as to inject date/other infos into it. -*.manifest -*.spec - -# Installer logs -pip-log.txt -pip-delete-this-directory.txt - -# Unit test / coverage reports -htmlcov/ -.tox/ -.nox/ -.coverage -.coverage.* -.cache -nosetests.xml -coverage.xml -*.cover -*.py,cover -.hypothesis/ -.pytest_cache/ - -# Translations -*.mo -*.pot - -# Django stuff: -*.log -local_settings.py -db.sqlite3 -db.sqlite3-journal - -# Flask stuff: -instance/ -.webassets-cache - -# Scrapy stuff: -.scrapy - -# Sphinx documentation -docs/_build/ - -# PyBuilder -target/ - -# Jupyter Notebook -.ipynb_checkpoints - -# IPython -profile_default/ -ipython_config.py - -# pyenv -.python-version - -# pipenv -# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. -# However, in case of collaboration, if having platform-specific dependencies or dependencies -# having no cross-platform support, pipenv may install dependencies that don't work, or not -# install all needed dependencies. -#Pipfile.lock - -# PEP 582; used by e.g. github.com/David-OConnor/pyflow -__pypackages__/ - -# Celery stuff -celerybeat-schedule -celerybeat.pid - -# SageMath parsed files -*.sage.py - -# Environments -.env -.venv -env/ -venv/ -ENV/ -env.bak/ -venv.bak/ - -# Spyder project settings -.spyderproject -.spyproject - -# Rope project settings -.ropeproject - -# mkdocs documentation -/site - -# mypy -.mypy_cache/ -.dmypy.json -dmypy.json - -# Pyre type checker -.pyre/ +*.pyc +.DS_Store +.gitkeep diff --git a/application/Dockerfile b/application/Dockerfile new file mode 100644 index 0000000..0cd9bef --- /dev/null +++ b/application/Dockerfile @@ -0,0 +1,6 @@ +FROM python:3.11-alpine +RUN apk add --no-cache iproute2 iptables tcpdump curl +COPY requirements.txt . +RUN pip install -r requirements.txt +COPY app.py /app/app.py +WORKDIR /app diff --git a/application/app.py b/application/app.py new file mode 100644 index 0000000..ff08bd8 --- /dev/null +++ b/application/app.py @@ -0,0 +1,19 @@ +from flask import Flask +import json + +app = Flask(__name__) + +@app.route('/get', methods=['GET']) +def get_handler(): + return json.dumps({"method": "GET", "status": "ok"}) + +@app.route('/post', methods=['POST']) +def post_handler(): + return json.dumps({"method": "POST", "status": "ok"}) + +@app.route('/put', methods=['PUT']) +def put_handler(): + return json.dumps({"method": "PUT", "status": "ok"}) + +if __name__ == '__main__': + app.run(host='0.0.0.0', port=5000) diff --git a/application/requirements.txt b/application/requirements.txt new file mode 100644 index 0000000..5bd19d3 --- /dev/null +++ b/application/requirements.txt @@ -0,0 +1 @@ +flask==3.0.0 diff --git a/configs/nodeA/entrypoint.sh b/configs/nodeA/entrypoint.sh new file mode 100644 index 0000000..1a800d3 --- /dev/null +++ b/configs/nodeA/entrypoint.sh @@ -0,0 +1,3 @@ +#!/bin/sh +ip route add 192.168.12.0/24 via 192.168.11.1 +python /app/app.py diff --git a/configs/nodeB/entrypoint.sh b/configs/nodeB/entrypoint.sh new file mode 100644 index 0000000..73490bc --- /dev/null +++ b/configs/nodeB/entrypoint.sh @@ -0,0 +1,13 @@ +#!/bin/sh +echo 1 > /proc/sys/net/ipv4/ip_forward + +IFACE_SERVER=$(ip route show 192.168.11.0/24 | awk '{print $3}') +IFACE_CLIENT=$(ip route show 192.168.12.0/24 | awk '{print $3}') + +iptables -A FORWARD -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT +iptables -A FORWARD -i "$IFACE_SERVER" -o "$IFACE_CLIENT" -p tcp --dport 5000 --syn -j ACCEPT +iptables -A FORWARD -i "$IFACE_CLIENT" -o "$IFACE_SERVER" -p tcp --dport 5000 --syn -j ACCEPT +iptables -P FORWARD DROP + +tcpdump -i any port 5000 -n -l & +sleep infinity diff --git a/configs/nodeC/entrypoint.sh b/configs/nodeC/entrypoint.sh new file mode 100644 index 0000000..e76fd43 --- /dev/null +++ b/configs/nodeC/entrypoint.sh @@ -0,0 +1,3 @@ +#!/bin/sh +ip route add 192.168.11.0/24 via 192.168.12.10 +sleep infinity diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..bce1db7 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,57 @@ +services: + node_a: + build: ./application + container_name: linux_a + hostname: golikov_server + networks: + servernet: + ipv4_address: 192.168.11.10 + cap_add: + - NET_ADMIN + entrypoint: ["/bin/sh", "/configs/entrypoint.sh"] + volumes: + - ./configs/nodeA/entrypoint.sh:/configs/entrypoint.sh + restart: always + + node_b: + build: ./application + container_name: linux_b + hostname: golikov_gateway + networks: + servernet: + ipv4_address: 192.168.11.1 + clientnet: + ipv4_address: 192.168.12.10 + privileged: true + entrypoint: ["/bin/sh", "/configs/entrypoint.sh"] + volumes: + - ./configs/nodeB/entrypoint.sh:/configs/entrypoint.sh + restart: always + + node_c: + build: ./application + container_name: linux_c + hostname: golikov_client + networks: + clientnet: + ipv4_address: 192.168.12.100 + cap_add: + - NET_ADMIN + entrypoint: ["/bin/sh", "/configs/entrypoint.sh"] + volumes: + - ./configs/nodeC/entrypoint.sh:/configs/entrypoint.sh + restart: always + +networks: + servernet: + driver: bridge + ipam: + config: + - subnet: 192.168.11.0/24 + gateway: 192.168.11.254 + clientnet: + driver: bridge + ipam: + config: + - subnet: 192.168.12.0/24 + gateway: 192.168.12.254 diff --git a/report.md b/report.md new file mode 100644 index 0000000..2fe8ab0 --- /dev/null +++ b/report.md @@ -0,0 +1,243 @@ +# Лабораторная работа 1 - Администрирование Linux + +## Введение + +Задача: развернуть 3 виртуальные Linux-машины с настроенной сетевой топологией, где машина B (шлюз) маршрутизирует HTTP-трафик между машиной A (сервер) и машиной C (клиент). + +Среда выполнения: Docker Desktop. Подход аналогичен Play with Docker, но без таймаута сессии. + +--- + +## Топология сети + +``` +Linux A (golikov_server) Linux B (golikov_gateway) Linux C (golikov_client) +192.168.11.10/24 ---- [servernet] ---- 192.168.11.1/24 + 192.168.12.10/24 ---- [clientnet] ---- 192.168.12.100/24 +``` + +День и месяц рождения совпадают (11.11), поэтому для второй подсети используется 192.168.12.0/24, чтобы избежать IP-конфликта. + +| Машина | Hostname | IP-адрес(а) | Роль | +|--------|----------|-------------|------| +| Linux A | golikov_server | 192.168.11.10/24 | HTTP-сервер | +| Linux B | golikov_gateway | 192.168.11.1/24, 192.168.12.10/24 | Шлюз + файрвол | +| Linux C | golikov_client | 192.168.12.100/24 | HTTP-клиент | + +![Скриншот 1: docker ps - все три контейнера в статусе Up](screenshots/01_docker_ps.png) + +--- + +## Linux A - HTTP-сервер + +### Конфигурация сети + +Интерфейс eth0 с адресом 192.168.11.10/24. Маршрут к подсети C через шлюз B: + +``` +ip route add 192.168.12.0/24 via 192.168.11.1 +``` + +Результат `ip addr` и `ip route`: + +``` +11: eth0@if54: mtu 1500 qdisc noqueue state UP group default + inet 192.168.11.10/24 brd 192.168.11.255 scope global eth0 + +default via 192.168.11.254 dev eth0 +192.168.11.0/24 dev eth0 proto kernel scope link src 192.168.11.10 +192.168.12.0/24 via 192.168.11.1 dev eth0 +``` + +![Скриншот 2: ip addr и ip route на linux_a](screenshots/02_node_a_network.png) + +### Flask HTTP-сервер (порт 5000) + +Файл `application/app.py`: + +```python +from flask import Flask +import json + +app = Flask(__name__) + +@app.route('/get', methods=['GET']) +def get_handler(): + return json.dumps({"method": "GET", "status": "ok"}) + +@app.route('/post', methods=['POST']) +def post_handler(): + return json.dumps({"method": "POST", "status": "ok"}) + +@app.route('/put', methods=['PUT']) +def put_handler(): + return json.dumps({"method": "PUT", "status": "ok"}) + +if __name__ == '__main__': + app.run(host='0.0.0.0', port=5000) +``` + +Сервис запускается автоматически через entrypoint (`configs/nodeA/entrypoint.sh`) и поднимается при каждом старте контейнера (`restart: always`). + +--- + +## Linux B - Шлюз + +### Конфигурация сети + +Два интерфейса: eth0 (servernet) и eth1 (clientnet): + +``` +11: eth0@if55: mtu 1500 qdisc noqueue state UP group default + inet 192.168.11.1/24 brd 192.168.11.255 scope global eth0 +12: eth1@if56: mtu 1500 qdisc noqueue state UP group default + inet 192.168.12.10/24 brd 192.168.12.255 scope global eth1 + +default via 192.168.12.254 dev eth1 +192.168.11.0/24 dev eth0 proto kernel scope link src 192.168.11.1 +192.168.12.0/24 dev eth1 proto kernel scope link src 192.168.12.10 +``` + +![Скриншот 3: ip addr и ip route на linux_b - два интерфейса](screenshots/03_node_b_network.png) + +### IP Forwarding + +```bash +echo 1 > /proc/sys/net/ipv4/ip_forward +``` + +### Маршрутизация через iptables + +Разрешены только соединения по порту 5000, всё остальное блокируется: + +```bash +iptables -A FORWARD -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT +iptables -A FORWARD -i eth0 -o eth1 -p tcp --dport 5000 --syn -j ACCEPT +iptables -A FORWARD -i eth1 -o eth0 -p tcp --dport 5000 --syn -j ACCEPT +iptables -P FORWARD DROP +``` + +Результат `iptables -L FORWARD -v -n`: + +``` +Chain FORWARD (policy DROP 0 packets, 0 bytes) +pkts bytes target prot opt in out source destination + 99 7857 ACCEPT all -- * * 0.0.0.0/0 0.0.0.0/0 ctstate RELATED,ESTABLISHED + 0 0 ACCEPT tcp -- eth0 eth1 0.0.0.0/0 0.0.0.0/0 tcp dpt:5000 flags:0x17/0x02 + 9 540 ACCEPT tcp -- eth1 eth0 0.0.0.0/0 0.0.0.0/0 tcp dpt:5000 flags:0x17/0x02 +``` + +![Скриншот 4: iptables -L FORWARD на linux_b](screenshots/04_node_b_iptables.png) + +### tcpdump + +Запуск с фильтрацией по порту 5000: + +```bash +tcpdump -i any port 5000 -n +``` + +Пример вывода при передаче запросов от C к A: + +``` +23:21:25 eth0 In IP 192.168.12.100.42580 > 192.168.11.10.5000: Flags [S] +23:21:25 eth1 Out IP 192.168.12.100.42580 > 192.168.11.10.5000: Flags [S] +23:21:25 eth1 In IP 192.168.11.10.5000 > 192.168.12.100.42580: Flags [S.] +23:21:25 eth0 Out IP 192.168.11.10.5000 > 192.168.12.100.42580: Flags [S.] +``` + +Видно форвардинг пакетов: eth0 (In от C) -> eth1 (Out к A) и обратно. + +![Скриншот 5: два окна - tcpdump на linux_b (слева) и curl-запросы с linux_c (справа)](screenshots/05_tcpdump_and_curl.png) + +--- + +## Linux C - Клиент + +### Конфигурация сети + +Интерфейс eth0 с адресом 192.168.12.100/24. Маршрут к подсети A через шлюз B: + +``` +ip route add 192.168.11.0/24 via 192.168.12.10 +``` + +Результат: + +``` +11: eth0@if53: mtu 1500 qdisc noqueue state UP group default + inet 192.168.12.100/24 brd 192.168.12.255 scope global eth0 + +default via 192.168.12.254 dev eth0 +192.168.11.0/24 via 192.168.12.10 dev eth0 +192.168.12.0/24 dev eth0 proto kernel scope link src 192.168.12.100 +``` + +![Скриншот 6: ip addr и ip route на linux_c](screenshots/06_node_c_network.png) + +### Запросы к HTTP-серверу + +```bash +curl -X GET http://192.168.11.10:5000/get +# {"method": "GET", "status": "ok"} + +curl -X POST http://192.168.11.10:5000/post +# {"method": "POST", "status": "ok"} + +curl -X PUT http://192.168.11.10:5000/put +# {"method": "PUT", "status": "ok"} +``` + +### Проверка блокировки файрволом + +Попытка подключиться на порт 9999 (не 5000): + +```bash +curl --connect-timeout 3 http://192.168.11.10:9999/ +# curl: (28) Connection timed out after 3006 milliseconds +``` + +Пакет был отброшен iptables на шлюзе B (политика FORWARD DROP). + +![Скриншот 7: curl на порт 9999 - Connection timed out](screenshots/07_firewall_block.png) + +--- + +## Автозапуск при перезагрузке + +Все три контейнера настроены с `restart: always` в docker-compose.yml. После `docker compose restart` все сервисы и маршруты восстанавливаются автоматически через entrypoint-скрипты. + +Проверка: + +```bash +docker compose restart +# Container linux_a Started +# Container linux_b Started +# Container linux_c Started + +docker exec linux_c curl -s http://192.168.11.10:5000/get +# {"method": "GET", "status": "ok"} +``` + +![Скриншот 8: docker compose restart и curl после - всё работает без ручной настройки](screenshots/08_restart.png) + +--- + +## Структура репозитория + +``` +lab1/ + application/ + app.py - Flask HTTP-сервер с эндпоинтами /get, /post, /put + Dockerfile - образ на python:3.11-alpine с iproute2, iptables, tcpdump, curl + requirements.txt - flask==3.0.0 + configs/ + nodeA/ + entrypoint.sh - маршрут + запуск Flask + nodeB/ + entrypoint.sh - ip_forward + iptables + tcpdump + sleep + nodeC/ + entrypoint.sh - маршрут + sleep + docker-compose.yml - описание трёх сервисов и двух сетей + report.md - данный отчёт +``` diff --git a/screenshots/01_docker_ps.png b/screenshots/01_docker_ps.png new file mode 100644 index 0000000..d05d093 Binary files /dev/null and b/screenshots/01_docker_ps.png differ diff --git a/screenshots/02_node_a_network.png b/screenshots/02_node_a_network.png new file mode 100644 index 0000000..dcf1563 Binary files /dev/null and b/screenshots/02_node_a_network.png differ diff --git a/screenshots/03_node_b_network.png b/screenshots/03_node_b_network.png new file mode 100644 index 0000000..4f9c692 Binary files /dev/null and b/screenshots/03_node_b_network.png differ diff --git a/screenshots/04_node_b_iptables.png b/screenshots/04_node_b_iptables.png new file mode 100644 index 0000000..ebcac40 Binary files /dev/null and b/screenshots/04_node_b_iptables.png differ diff --git a/screenshots/05_tcpdump_and_curl.png b/screenshots/05_tcpdump_and_curl.png new file mode 100644 index 0000000..1bae985 Binary files /dev/null and b/screenshots/05_tcpdump_and_curl.png differ diff --git a/screenshots/06_node_c_network.png b/screenshots/06_node_c_network.png new file mode 100644 index 0000000..01894e3 Binary files /dev/null and b/screenshots/06_node_c_network.png differ diff --git a/screenshots/07_firewall_block.png b/screenshots/07_firewall_block.png new file mode 100644 index 0000000..708eb60 Binary files /dev/null and b/screenshots/07_firewall_block.png differ diff --git a/screenshots/08_restart.png b/screenshots/08_restart.png new file mode 100644 index 0000000..c23516e Binary files /dev/null and b/screenshots/08_restart.png differ