[Системное администрирование, Программирование, IT-инфраструктура, DevOps] Самоподписные сертификаты кровавого энтерпрайза против вашего лампового CI/CD
Автор
Сообщение
news_bot ®
Стаж: 6 лет 9 месяцев
Сообщений: 27286
Многие компании используют сертификаты, подписанные внутренними удостоверяющими центрами (Certificate Authority) для ресурсов в приватных сетях. Поскольку такие сертификаты по умолчанию не могут быть доверенными, почти на каждом этапе вокруг пайплайна могут возникать ошибки такого рода: x509 certificate signed by unknown authority. Из-за этого до каждого компонента необходимо доставлять корневые сертификаты, используемые в компании. В статье расскажу, как это можно сделать.
Статья подготовлена на основе моего материала в курсе «CI/CD на примере Gitlab CI».
Обратите внимание: настройка инфраструктуры выходит за рамки этой статьи, поэтому в примерах, где не идёт речи о динамическом получении сертификата, я ограничился предположением, что на серверах компании внутренний корневой сертификат всегда расположен по пути /usr/local/share/ca-certificates/RootCA.crt.
Поскольку курс в основе этого материала строится вокруг Gitlab-CI, то и нижеследующие разделы рассматривают доставку сертификатов до тех или иных этапов или компонентов, входящих в пайплайн, построенный в гитлабе с раннерами, запущенными внутри корпоративной инфраструктуры. Поехали!
gitlab-runner
Передача корневого сертификата в управляющий компонент gitlab-runner — наиболее простая задача. gitlab-runner открывает HTTPS-соединения только с самим сервером GitLab и, возможно, с кластером Kubernetes. Однако последний явным образом требует указания своего корневого сертификата. Для верификации подлинности сервера GitLab используется сертификат, указанный в config.toml:
[[runners]]
executor = "kubernetes"
name = "Kubernetes Runner"
tls-ca-file = "/usr/local/share/ca-certificates/RootCA.crt" # <===
Если gitlab-runner запущен непосредственно на хосте, этого достаточно. Если он запускается в докер-контейнере, в контейнер нужно смонтировать сертификат с хоста:
docker run \
...
-v /usr/local/share/ca-certificates:/usr/local/share/ca-certificates:ro \
...
gitlab/gitlab-runner
Наконец, если он запускается в Kubernetes, один из вариантов — монтировать директорию с хоста
В спецификации контейнера:
volumeMounts:
- mountPath: /usr/local/share/ca-certificates
name: certs
В спецификации пода:
volumes:
- hostPath:
path: /usr/local/share/ca-certificates
name: certs
Если нет прав на монтирование hostPath, сертификат придётся положить в Kubernetes как секрет и монтировать его оттуда, поменяв содержимое ключа volumes. Например, если секрет создан командой
kubectl create secret generic -n runner-namespace certs --from-file=/usr/local/share/ca-certificates
ключ volumes будет выглядеть так:
volumes:
- secret:
secretName: certs
name: certs
gitlab-runner helper image
gitlab-runner-helper — вспомогательная утилита, которая используется в пайплайнах для клонирования репозитория и работы с кэшем и артефактами. Обычно проблемы с сертификатами возникают тогда, когда используется собственное S3-хранилище (например, на базе ceph или minio). Как правило, происходит обращение к S3-хранилищу по HTTPS, и сервер S3 представляется корпоративным сертификатом. Единственный способ доставить туда сертификаты — пересобрать образ.
Helper image используется в тандеме с docker или kubernetes экзекьюторами. Чтобы настроить использование кастомного образа, укажите следующие строки в config.toml:
[[runners]]
(...)
executor = "docker" # (или kubernetes)
[runners.docker] # (или kubernetes)
(...)
helper_image = "my.registry.local/gitlab/gitlab-runner-helper:tag"
Пайплайны
Действия выше выполняются относительно редко, однако если используется Docker или Kubernetes экзекьютор, то каждый джоб будет выполняться в свежесозданном контейнере, в котором снова нет нужных сертификатов. Поэтому необходим способ динамически доставлять сертификаты в контейнеры. Есть несколько таких способов:
- Монтировать с хостов.
- Получать из источника, их создающего.
- Создать сервер, предоставляющий сертификаты.
- Использовать образы со вшитыми сертификатами.
Монтирование с хостов
Если прописать в config.toml
[[runners]]
executor = "kubernetes"
(...)
[runners.kubernetes]
(...)
[runners.kubernetes.volumes]
[[runners.kubernetes.volumes.host_path]]
host_path = "/usr/local/share/ca-certificates"
mount_path = "/usr/local/share/ca-certificates"
name = "certs-volume"
read_only = true
то сертификаты будут монтироваться в основные и сервисные контейнеры запускаемых джобов. В случае с докер-экзекьютором аналогичный результат можно получить так:
[runners.docker]
(...)
volumes = ["/usr/local/share/ca-certificates:/usr/local/share/ca-certificates:ro"]
Минус подхода в том, что ответственность за актуальность сертификатов сдвигается на конфигурацию инфраструктуры, поэтому подход удобен, если вы этой инфраструктурой управляете. Если же вы, например, работаете в отдельной команде, которая в многопользовательском кластере Kubernetes сама подняла свой раннер, но не имеет админского доступа туда, вы не можете быть уверены в актуальности сертификатов на хосте, да и можете вовсе не иметь прав на монтирование hostPath. Тогда можно аналогично примеру для gitlab-runner создать в кубе секрет, содержащий ключи (имена файлов сертификатов) и значения (их содержимое):
kubectl create secret generic -n runner-namespace certs --from-file=/usr/local/share/ca-certificates
а config.toml исправить следующим образом:
[[runners]]
executor = "kubernetes"
(...)
[runners.kubernetes]
(...)
[runners.kubernetes.volumes]
[[runners.kubernetes.volumes.secret]]
name = "certs"
mount_path = "/usr/local/share/ca-certificates"
read_only = true
Если используется докер-экзекьютор на недоступном вам build-сервере, из этого тут же следует, что у вас нет доступа к конфигурации раннера, если только не используется экзотичный вариант подключения к сокету докер-демона по tcp (подобный доступ почти всегда означает, что и доступ к хосту получить тривиально). Лучше всего договориться с администраторами build-сервера о гарантированном наличии свежих сертификатов в определённой директории, которая будет монтироваться в контейнеры пайплайна.
Один из плюсов такого подхода в том, что можно делать следующее:
build:
stage: build
image: docker:stable
services:
- name: docker:18.09.6-dind
entrypoint:
- /bin/sh
command:
- -c
- update-ca-certificates && dockerd-entrypoint.sh
Ключевой шаг, естественно, update-ca-certificates.
Поскольку для сервисных контейнеров нельзя прописать последовательность команд аналогично script, а писать длинные скрипты для установки сертификатов под ключом command не очень удобно, меньшим из зол является подобное монтирование сертификатов и сравнительно короткая последовательность команд.
Отступление про сборку docker-образов
Если вы в пайплайнах собираете докер-образы, то, независимо от того, используется docker-in-docker, kaniko или другой сборщик в пайплайне, в контексте сборки сертификаты снова отсутствуют, если их не вшили в базовый образ. Предположим, что каким-либо образом в контейнер пайплайна сертификаты были доставлены (либо примонтированы с хоста, либо получены из другого источника командами в before_script). Тогда конфигурация пайплайнов должна будет выглядеть так:
build:
stage: build
# допустим, сборка докером
image: docker:stable
services:
- name: docker:18.09.6-dind
entrypoint:
- /bin/sh
command:
- -c
- update-ca-certificates && dockerd-entrypoint.sh
# before_script:
# - script_to_get_certs.sh
script:
- mkdir -p certs
- cp /usr/local/share/ca-certificates/* certs/
- docker build (...) # или канико или что угодно ещё
- (...)
а в докер-файлах придётся всегда указывать что-нибудь вроде
FROM base-image
COPY certs/* /usr/local/share/ca-certificates/
RUN update-ca-certificates
RUN wget https://artifacts.company.ru/some/artifact # команда, которой требуется проверить сертификат
RUN go get github.com/golang/package # команда, которой может потребоваться доверять сертификату MITM-proxy
(...)
Эти добавления нужны, если во время сборки происходят обращения к внутренним сервисам, которые представляются корпоративными сертификатами. Кроме того, бывает, что выход в интернет осуществляется через MITM-proxy, который для инспекции трафика расшифровывает передаваемые данные и, соответственно, подменяет сертификат. Тогда, если во время сборки нужно обращаться в интернет, например, для доставки каких-либо пакетов, нужно доверять сертификату, который используется MITM-proxy. Поэтому для сборок в начале каждого докер-файла может потребоваться добавлять эти строки:
COPY certs/* /usr/local/share/ca-certificates/
RUN update-ca-certificates
Получать сертификаты из первоисточника
Если источник корневых сертификатов предоставляет удобный API для их получения, можно не утруждать себя хранением сертификатов на своих серверах с последующим монтированием в пайплайны. Допустим, корневой сертификат можно получить командой
curl http://pki.company.ru/api?name=RootCA -O /usr/local/share/ca-certificates/RootCA.crt
Пример со сборкой докер-образа примет такой вид:
build:
stage: build
# допустим, сборка докером
image: docker:stable
services:
- name: docker:18.09.6-dind
entrypoint:
- /bin/sh
command:
- -c
- curl http://pki.company.ru/api?name=RootCA -O /usr/local/share/ca-certificates/RootCA.crt && update-ca-certificates && dockerd-entrypoint.sh
# before_script:
# - script_to_get_certs.sh
script:
- mkdir -p certs
- curl http://pki.company.ru/api?name=RootCA -O /usr/local/share/ca-certificates/RootCA.crt
- cp /usr/local/share/ca-certificates/* certs/
- docker build (...) # или канико или что угодно ещё
- (...)
То есть добавился шаг скачивания сертификатов.
Получать сертификаты из самописного сервиса
Если API центра сертификации недостаточно функционален или требует авторизации, которая в пайплайнах слишком громоздкая, можно запустить простейший веб-сервер, откуда сертификаты можно будет скачать почти таким же образом, как в предыдущем примере. Принципиального отличия от получения их из первоисточника нет, поэтому рассмотрим последний, наиболее эффективный вариант.
Использовать образы со вшитыми сертификатами
Рано или поздно многие компании обнаруживают потребность контролировать свою инфраструктуру. Админам надоедает отвечать на одинаковые вопросы разработчиков («почему у меня не качает с гитхаба?, «что значит x509?»), безопасникам надоедает наблюдать за тем, как все докер-файлы начинаются со строчки FROM vasyapupkin/zvercd, а разработчикам надоедает решать головоломки с изменчивыми промежуточными сертификатами и прочими вещами вне их области экспертизы. Тогда ответственные за платформу идут и пишут много-много докер-файлов наподобие такого:
FROM python:3.7
RUN curl http://pki.company.ru/api?name=RootCA -O /usr/local/share/ca-certificates/RootCA.crt \
&& update-ca-certificates
Эти образы сохраняются в корпоративный репозиторий, а разработчики компании начинают пользоваться только этими образами в качестве базовых. Дополнительным бонусом идёт возможность преднастроить во внутренних образах всевозможные кэши и проксирующие репозитории (nexus, artifactory и прочие).
Наличие базовых образов не отменяет необходимость получать сертификаты динамически, любым из остальных перечисленных способов. Это нужно инженерам, поддерживающим актуальность корпоративных базовых образов, чтобы своевременно обновлять в них сертификаты или создавать новые базовые образы.
Совместив два-три описанных подхода вы, наконец, сможете спать спокойно и не объяснять всем в сотый раз, что означает x509: certificate signed by unknown authority.
===========
Источник:
habr.com
===========
Похожие новости:
- [Системное администрирование, IT-инфраструктура, Серверное администрирование, Хранение данных] Функция AppsON в Dell EMC PowerStore: запускаем приложения прямо на массиве
- [Информационная безопасность, Системное администрирование, Софт, IT-компании] Microsoft выпустила обновления для серверов Exchange с неподдерживаемыми версиями CU, уязвимыми для атаки ProxyLogon
- [Отладка, Программирование микроконтроллеров] Полноценная GDB отладка через USB на плате BluePill (STM32F103С8T6)
- [IT-инфраструктура, Хранение данных, Хранилища данных, Облачные сервисы] Как выбрать облачную систему хранения данных, чтобы получить лучшую производительность и оптимизировать стоимость
- [Программирование, Java] Поддержка Null в Protobuf (перевод)
- [JavaScript, Программирование] Программная генерация изображений с помощью API CSS Painting (перевод)
- [Программирование, C++] Динамический полиморфизм с использованием std::variant и std::visit (перевод)
- [Программирование, Java] Итак, вы хотите оптимизировать gRPC. Часть 1 (перевод)
- [JavaScript, Программирование, Расширения для браузеров, Браузеры] Hello, Word! Разрабатываем браузерное расширение в 2021-м
- [Python, Программирование, Программирование микроконтроллеров] Маленькие Python для маленьких embedded-программистов: CircuitPython и MicroPython для MeowBit
Теги для поиска: #_sistemnoe_administrirovanie (Системное администрирование), #_programmirovanie (Программирование), #_itinfrastruktura (IT-инфраструктура), #_devops, #_gitlabci, #_tls, #_sertifikaty (сертификаты), #_pajplajn (пайплайн), #_ci/cd, #_blog_kompanii_southbridge (
Блог компании Southbridge
), #_sistemnoe_administrirovanie (
Системное администрирование
), #_programmirovanie (
Программирование
), #_itinfrastruktura (
IT-инфраструктура
), #_devops
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 04:18
Часовой пояс: UTC + 5
Автор | Сообщение |
---|---|
news_bot ®
Стаж: 6 лет 9 месяцев |
|
Многие компании используют сертификаты, подписанные внутренними удостоверяющими центрами (Certificate Authority) для ресурсов в приватных сетях. Поскольку такие сертификаты по умолчанию не могут быть доверенными, почти на каждом этапе вокруг пайплайна могут возникать ошибки такого рода: x509 certificate signed by unknown authority. Из-за этого до каждого компонента необходимо доставлять корневые сертификаты, используемые в компании. В статье расскажу, как это можно сделать. Статья подготовлена на основе моего материала в курсе «CI/CD на примере Gitlab CI». Обратите внимание: настройка инфраструктуры выходит за рамки этой статьи, поэтому в примерах, где не идёт речи о динамическом получении сертификата, я ограничился предположением, что на серверах компании внутренний корневой сертификат всегда расположен по пути /usr/local/share/ca-certificates/RootCA.crt. Поскольку курс в основе этого материала строится вокруг Gitlab-CI, то и нижеследующие разделы рассматривают доставку сертификатов до тех или иных этапов или компонентов, входящих в пайплайн, построенный в гитлабе с раннерами, запущенными внутри корпоративной инфраструктуры. Поехали! gitlab-runner Передача корневого сертификата в управляющий компонент gitlab-runner — наиболее простая задача. gitlab-runner открывает HTTPS-соединения только с самим сервером GitLab и, возможно, с кластером Kubernetes. Однако последний явным образом требует указания своего корневого сертификата. Для верификации подлинности сервера GitLab используется сертификат, указанный в config.toml: [[runners]]
executor = "kubernetes" name = "Kubernetes Runner" tls-ca-file = "/usr/local/share/ca-certificates/RootCA.crt" # <=== Если gitlab-runner запущен непосредственно на хосте, этого достаточно. Если он запускается в докер-контейнере, в контейнер нужно смонтировать сертификат с хоста: docker run \
... -v /usr/local/share/ca-certificates:/usr/local/share/ca-certificates:ro \ ... gitlab/gitlab-runner Наконец, если он запускается в Kubernetes, один из вариантов — монтировать директорию с хоста В спецификации контейнера: volumeMounts:
- mountPath: /usr/local/share/ca-certificates name: certs В спецификации пода: volumes:
- hostPath: path: /usr/local/share/ca-certificates name: certs Если нет прав на монтирование hostPath, сертификат придётся положить в Kubernetes как секрет и монтировать его оттуда, поменяв содержимое ключа volumes. Например, если секрет создан командой kubectl create secret generic -n runner-namespace certs --from-file=/usr/local/share/ca-certificates
ключ volumes будет выглядеть так: volumes:
- secret: secretName: certs name: certs gitlab-runner helper image gitlab-runner-helper — вспомогательная утилита, которая используется в пайплайнах для клонирования репозитория и работы с кэшем и артефактами. Обычно проблемы с сертификатами возникают тогда, когда используется собственное S3-хранилище (например, на базе ceph или minio). Как правило, происходит обращение к S3-хранилищу по HTTPS, и сервер S3 представляется корпоративным сертификатом. Единственный способ доставить туда сертификаты — пересобрать образ. Helper image используется в тандеме с docker или kubernetes экзекьюторами. Чтобы настроить использование кастомного образа, укажите следующие строки в config.toml: [[runners]]
(...) executor = "docker" # (или kubernetes) [runners.docker] # (или kubernetes) (...) helper_image = "my.registry.local/gitlab/gitlab-runner-helper:tag" Пайплайны Действия выше выполняются относительно редко, однако если используется Docker или Kubernetes экзекьютор, то каждый джоб будет выполняться в свежесозданном контейнере, в котором снова нет нужных сертификатов. Поэтому необходим способ динамически доставлять сертификаты в контейнеры. Есть несколько таких способов:
Монтирование с хостов Если прописать в config.toml [[runners]]
executor = "kubernetes" (...) [runners.kubernetes] (...) [runners.kubernetes.volumes] [[runners.kubernetes.volumes.host_path]] host_path = "/usr/local/share/ca-certificates" mount_path = "/usr/local/share/ca-certificates" name = "certs-volume" read_only = true то сертификаты будут монтироваться в основные и сервисные контейнеры запускаемых джобов. В случае с докер-экзекьютором аналогичный результат можно получить так: [runners.docker]
(...) volumes = ["/usr/local/share/ca-certificates:/usr/local/share/ca-certificates:ro"] Минус подхода в том, что ответственность за актуальность сертификатов сдвигается на конфигурацию инфраструктуры, поэтому подход удобен, если вы этой инфраструктурой управляете. Если же вы, например, работаете в отдельной команде, которая в многопользовательском кластере Kubernetes сама подняла свой раннер, но не имеет админского доступа туда, вы не можете быть уверены в актуальности сертификатов на хосте, да и можете вовсе не иметь прав на монтирование hostPath. Тогда можно аналогично примеру для gitlab-runner создать в кубе секрет, содержащий ключи (имена файлов сертификатов) и значения (их содержимое): kubectl create secret generic -n runner-namespace certs --from-file=/usr/local/share/ca-certificates
а config.toml исправить следующим образом: [[runners]]
executor = "kubernetes" (...) [runners.kubernetes] (...) [runners.kubernetes.volumes] [[runners.kubernetes.volumes.secret]] name = "certs" mount_path = "/usr/local/share/ca-certificates" read_only = true Если используется докер-экзекьютор на недоступном вам build-сервере, из этого тут же следует, что у вас нет доступа к конфигурации раннера, если только не используется экзотичный вариант подключения к сокету докер-демона по tcp (подобный доступ почти всегда означает, что и доступ к хосту получить тривиально). Лучше всего договориться с администраторами build-сервера о гарантированном наличии свежих сертификатов в определённой директории, которая будет монтироваться в контейнеры пайплайна. Один из плюсов такого подхода в том, что можно делать следующее: build:
stage: build image: docker:stable services: - name: docker:18.09.6-dind entrypoint: - /bin/sh command: - -c - update-ca-certificates && dockerd-entrypoint.sh Ключевой шаг, естественно, update-ca-certificates. Поскольку для сервисных контейнеров нельзя прописать последовательность команд аналогично script, а писать длинные скрипты для установки сертификатов под ключом command не очень удобно, меньшим из зол является подобное монтирование сертификатов и сравнительно короткая последовательность команд. Отступление про сборку docker-образов Если вы в пайплайнах собираете докер-образы, то, независимо от того, используется docker-in-docker, kaniko или другой сборщик в пайплайне, в контексте сборки сертификаты снова отсутствуют, если их не вшили в базовый образ. Предположим, что каким-либо образом в контейнер пайплайна сертификаты были доставлены (либо примонтированы с хоста, либо получены из другого источника командами в before_script). Тогда конфигурация пайплайнов должна будет выглядеть так: build:
stage: build # допустим, сборка докером image: docker:stable services: - name: docker:18.09.6-dind entrypoint: - /bin/sh command: - -c - update-ca-certificates && dockerd-entrypoint.sh # before_script: # - script_to_get_certs.sh script: - mkdir -p certs - cp /usr/local/share/ca-certificates/* certs/ - docker build (...) # или канико или что угодно ещё - (...) а в докер-файлах придётся всегда указывать что-нибудь вроде FROM base-image
COPY certs/* /usr/local/share/ca-certificates/ RUN update-ca-certificates RUN wget https://artifacts.company.ru/some/artifact # команда, которой требуется проверить сертификат RUN go get github.com/golang/package # команда, которой может потребоваться доверять сертификату MITM-proxy (...) Эти добавления нужны, если во время сборки происходят обращения к внутренним сервисам, которые представляются корпоративными сертификатами. Кроме того, бывает, что выход в интернет осуществляется через MITM-proxy, который для инспекции трафика расшифровывает передаваемые данные и, соответственно, подменяет сертификат. Тогда, если во время сборки нужно обращаться в интернет, например, для доставки каких-либо пакетов, нужно доверять сертификату, который используется MITM-proxy. Поэтому для сборок в начале каждого докер-файла может потребоваться добавлять эти строки: COPY certs/* /usr/local/share/ca-certificates/
RUN update-ca-certificates Получать сертификаты из первоисточника Если источник корневых сертификатов предоставляет удобный API для их получения, можно не утруждать себя хранением сертификатов на своих серверах с последующим монтированием в пайплайны. Допустим, корневой сертификат можно получить командой curl http://pki.company.ru/api?name=RootCA -O /usr/local/share/ca-certificates/RootCA.crt
Пример со сборкой докер-образа примет такой вид: build:
stage: build # допустим, сборка докером image: docker:stable services: - name: docker:18.09.6-dind entrypoint: - /bin/sh command: - -c - curl http://pki.company.ru/api?name=RootCA -O /usr/local/share/ca-certificates/RootCA.crt && update-ca-certificates && dockerd-entrypoint.sh # before_script: # - script_to_get_certs.sh script: - mkdir -p certs - curl http://pki.company.ru/api?name=RootCA -O /usr/local/share/ca-certificates/RootCA.crt - cp /usr/local/share/ca-certificates/* certs/ - docker build (...) # или канико или что угодно ещё - (...) То есть добавился шаг скачивания сертификатов. Получать сертификаты из самописного сервиса Если API центра сертификации недостаточно функционален или требует авторизации, которая в пайплайнах слишком громоздкая, можно запустить простейший веб-сервер, откуда сертификаты можно будет скачать почти таким же образом, как в предыдущем примере. Принципиального отличия от получения их из первоисточника нет, поэтому рассмотрим последний, наиболее эффективный вариант. Использовать образы со вшитыми сертификатами Рано или поздно многие компании обнаруживают потребность контролировать свою инфраструктуру. Админам надоедает отвечать на одинаковые вопросы разработчиков («почему у меня не качает с гитхаба?, «что значит x509?»), безопасникам надоедает наблюдать за тем, как все докер-файлы начинаются со строчки FROM vasyapupkin/zvercd, а разработчикам надоедает решать головоломки с изменчивыми промежуточными сертификатами и прочими вещами вне их области экспертизы. Тогда ответственные за платформу идут и пишут много-много докер-файлов наподобие такого: FROM python:3.7
RUN curl http://pki.company.ru/api?name=RootCA -O /usr/local/share/ca-certificates/RootCA.crt \ && update-ca-certificates Эти образы сохраняются в корпоративный репозиторий, а разработчики компании начинают пользоваться только этими образами в качестве базовых. Дополнительным бонусом идёт возможность преднастроить во внутренних образах всевозможные кэши и проксирующие репозитории (nexus, artifactory и прочие). Наличие базовых образов не отменяет необходимость получать сертификаты динамически, любым из остальных перечисленных способов. Это нужно инженерам, поддерживающим актуальность корпоративных базовых образов, чтобы своевременно обновлять в них сертификаты или создавать новые базовые образы. Совместив два-три описанных подхода вы, наконец, сможете спать спокойно и не объяснять всем в сотый раз, что означает x509: certificate signed by unknown authority. =========== Источник: habr.com =========== Похожие новости:
Блог компании Southbridge ), #_sistemnoe_administrirovanie ( Системное администрирование ), #_programmirovanie ( Программирование ), #_itinfrastruktura ( IT-инфраструктура ), #_devops |
|
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 04:18
Часовой пояс: UTC + 5