Русский
Русский
English
Статистика
Реклама

Блог компании qiwi

Leak-Search как и зачем QIWI создала сервис, который ищет утечки исходных кодов компаний

24.09.2020 14:19:46 | Автор: admin

Искать утечки и уязвимости в своих продуктах не только интересно и полезно, но и необходимо. Еще полезнее подключать к таким поискам внешних специалистов и энтузиастов, у которых не настолько замылен глаз, как у сотрудников. Поэтому в свое время мы в QIWI запустили программу bug bounty исследователи писали нам об уязвимостях и получали вознаграждение, а мы закрывали эти уязвимости.

Несколько раз нам присылали выложенный в публичный доступ код в виде ссылок на репозитории с чувствительной информацией. Причины утечек могли быть такими:

  • разработчик писал тестовый пример кода для себя, используя конфигурации боевого сервиса не тестовую среду;

  • админ выкладывал скрипты автоматизации и миграции базы данных потенциально чувствительной информации;

  • стажер неосознанно размещал код в своем публичном репозитории, считая, что это не несет рисков.

При этом такие утечки могут исходить как от работающих в компании разработчиков, так и от тех, кто уже уволился. Например, бывали случаи, когда уже не работающий в компании сотрудник размещал в открытом репозитории код, который когда-то брал домой, чтобы поработать с ним в свободное время. Казалось бы звучит безобидно, но внутри такого кода вполне могли быть пароли от базы данных, конфигурации сетей или какая-то бизнес-логика, в общем, чувствительная для компании информация, которой не должно быть в публичном доступе.

Как показывает практика, большинство компаний уже неплохо защищены от внешних угроз и наибольший вред может нанести именно внутренняя утечка. При этом такая утечка может случиться как злонамеренно, так и по случайности а это как раз то, о чем мы сказали выше.

И в целом безопасность компаний не абсолютна: хорошо защищая свой периметр и информационные системы с помощью с помощью Firewall, SOC, IDS/IPS и сканеров безопасности, компании все равно подвержены многим источникам утечек от внешней разработки и аудиторов до вендорских решений. Конечно, невозможно отвечать за безопасность других компаний, но мониторить случаи утечки вашей информации с их стороны можно и нужно.

Поэтому мы озаботились вопросом утечек по всем источникам. Автоматизировав их поиск, мы сначала сделали продукт для себя, а теперь готовы предлагать его рынку.

Так появился QIWI Leak-Search сервис, который ищет утечки вашего кода на Github и не только.

Как мы его делали и что он умеет читайте в посте.

Предыстория

Сначала мы пошли в рынок и посмотрели, как там дела с такими продуктами: вдруг можно купить что-то работающее, проблема-то, прямо скажем, не только у нас такая. Рынок уверенного ответа не дал, и мы поняли надо делать самим.

В самом начале наш сервис автоматического поиска утечек представлял собой набор скриптов, и в таком виде он просуществовал около двух месяцев. Только потом уже начал вырисовываться полноценный сервис. Сейчас это понятный веб-интерфейс, где представитель любой компании может быстро задать тот или иной запрос и посмотреть, все ли у компании хорошо в плане утечек.

Тестировали Leak-Search мы прежде всего на себе, но затем решили посмотреть, есть ли проблемы с утечками у компаний, которые явно тратят на информационную безопасность немало средств и времени. Мы взяли компании из рейтинга Fortune и проверили их с помощью нашего сервиса. Тогда мы удостоверились, что вне зависимости от того, сколько ресурсов компания вкладывает в ИБ, подобного рода утечки встречаются и у нее.

Затем мы решили проверить отечественные крупные компании, которые своими силами борются с утечками. У них тоже нашлись подобные ИБ-сложности: доступы к базам данных, почтовым серверам, к управлению гипервизорами и многое другое. В общем, если вы представите себе какую-то известную компанию, знайте: скорее всего, у нее ситуация с утечками примерно такая же есть над чем работать.

Вот несколько примеров, которые мы нашли с помощью Leak-Search. Крупная российская компания из сферы ритейла дает доступ к ERP-системам для управления деталями заказов кто хотел бы бесплатно перенаправлять себе чужие заказы на технику и одежду? Один из международных ИТ-гигантов предоставляет исходники операционной системы для IoT-устройств почему бы не поискать уязвимости, обладая максимальными правами доступа? Неназванное западное управление по исследованию космоса без проблем открывает информацию о потенциально опасных астероидах отличная возможность получить доказательства для создателей пугающего контента. Детали и названия этих компаний по понятным причинам мы не разглашаем.

Что и как ищет QIWI Leak-Search

Код для любой компании это интеллектуальная собственность со всеми вытекающими юридическими последствиями. Но самое главное это детали реализации системы, содержащие бизнес-логику, внутреннюю информацию или ключевые фичи в общем, вещи, которым в публичном доступе не место. Совсем.

В нашем случае, да и в случае других банков и финансовых компаний, основное это код, относящийся к механизму процессинга проведения платежей и переводов, к расчету банковских ставок и агентских комиссий, идентификации разных типов клиентов, протоколов взаимодействия с другими банками и провайдерами, то есть ядро финансовой деятельности.

Появление таких данных в открытом доступе может повлечь не только потерю интеллектуальной собственности, но и денег и стать угрозой репутации. Как минимум, какой-нибудь багхантер напишет очередной пост про слив и утечки, как максимум этот кусок кода может содержать скрытую уязвимость, приводящую к реальной эксплуатации и взлому.

Хотя во многих таких ситуациях даже не нужен взлом. Мы часто сталкивались с тем, что админы и разработчики разных компаний сами могут случайно выложить в общий доступ код, содержащий пароли, токены и ключи доступа к системам компании.

Поэтому в Leak-Search мы ищем все, что может относиться к чувствительным данным. Например:

набор определенных ключевых слов smtp, Dockerfile, proxypass, Authorization;

названия пакетов com.qiwi.processing.common;

адреса серверов int.qiwi.com, 10.4.3.255;

названия переменных, характерных именно для внутренней разработки компании QIWISECRET_KEY, qiwiToken.

В ядро системы зашито множество поисковых паттернов, определяющих, что та или иная информация является конфиденциальной. Именно поиск по ряду таких параметров сразу и позволяет обнаруживать что-то полезное утечки, которые нужно брать в работу и устранять.

Особенности поиска системы

Репозиториев существует великое множество. А еще много как просто похожих репозиториев, так и форков. У нас тоже много вещей отдано в Open Source: мы поддерживаем это движение и рады делиться с ИТ-сообществом ценными наработками. Пользователи из наших исходных кодов уже успели сделать больше сотни разных форков.

Поэтому, если начать искать утечки исходного кода таким образом, можно получить выдачу с огромным количеством нерелевантных результатов в ней будут все клонированные репозитории и форки.

Масла в огонь подольет еще и StackOverflow один разработчик придет на портал с вопросом, разместив часть кода, которую он не понял. Второй ответит в комментариях и даст другой отрывок кода в качестве ответа. Третий через пару месяцев найдет эту ветку обсуждения, потому что тоже не понял код, и заберет пример из комментария к себе на Github. И скорее всего, найдет ветку и возьмет код не он один.

Да и, признайтесь, были же случаи, когда вы писали какой-то код для себя, а потом решили посмотреть, как такое же сделал кто-то на Github и вуаля, у вас в репозитории абсолютно такой же кусок. И это нормально.

Что можно найти таким методом сравнения, если очистить и кластеризовать поиск? Если вы ищете следы возможной уязвимости, вы найдете репозитории, в которых она была переиспользована. То же случится, если вы захотите посмотреть, где именно используют ваши идеи.

Но сравнение результатов это не killer feature продукта. Leak-Search может больше: он позволяет пользователю персонализировать поиск и использовать механизм фильтрации, чтобы исключить ложные срабатывания. Во-первых, можно включить определенные репозитории в такой фильтр и не искать фрагменты утечки по ним. Во-вторых, фокусируясь только на конкретных, интересующих вас объектах, вы будете получать нужные результаты.

Разработка и поддержка

Возможно, вы замечали, что после достаточно активного использования open source, в таких сервисах, как Github, система безопасности сервиса могла вас заблокировать за превышение лимитов запросов. Поэтому нам пришлось постараться, чтобы Leak-Search имитировал человеческий поиск.

При этом мы активно добавляем публичные платформы для хранения исходных кодов в список источников, по которым осуществляется поиск. Начали мы с Github но теперь Leak-Search ищет утечки уже и в Gist. В бета-тесте у нас Pastebin и Gitlab, и еще планируем добавить BitBucket. А под каждый источник мы учитываем его специфику поиска и работы с API.

В рамках алгоритма, конечно, бывают и ложные срабатывания как false positive, так и false negative. Пока мы решаем это добавлением новых правил для фильтров, чтобы те работали все точнее.

Также мы планируем сделать черный список самих запросов. К примеру, вас не интересуют конкретные пользователи, репозитории или их связки с определенным типом файла и вы просто настраиваете это в параметрах поиска, чтобы соответствующих результатов не было в выдаче.

Идеальной нам видится система, когда Leak-Search можно будет встроить на саму площадку с репозиториями. Мы делали подобное у себя. На внутренний репозиторий мы вешали Leak-Search как триггер, он срабатывал при попытках выложить код с важной информацией это тоже одно из направлений развития сервиса.

Этическая сторона вопроса

Как и любой сервис, связанный с поиском утечек, Leak-Search можно использовать не только для защиты, но и для нападения. Скажем больше когда мы пилотировали продукт с некоторыми компаниями, сталкивались с тем, что их сотрудники вместо того, чтобы тщательно исследовать свои ИБ-проблемы, сканировали утечки конкурентов.

Такую возможность мы пресекли на старте, и поэтому сервис работает по правилам жесткой модерации. Чтобы человек получил возможность сканировать репозитории конкретной компании, он должен подтвердить, что является ее действующим сотрудником. Мы регистрируем новых пользователей после личной коммуникации, но все равно продолжаем премодерировать поисковые запросы чтобы исключить риск неправомерного использования платформы.

Вот так из сервиса, который мы делали для себя, чтобы мониторить утечки, мы сделали продукт, работающий сейчас уже не только для нас, но и для крупнейших российских компаний в сфере IT, финансов и маркетинга.

Если вам интересно что-то еще, о чем мы не упомянули в посте, спрашивайте, ответим.

Подробнее..

Кастомные декораторы для NestJS от простого к сложному

14.07.2020 12:05:49 | Автор: admin

image


Введение


NestJS стремительно набирающий популярность фрeймворк, построенный на идеях IoC/DI, модульного дизайна и декораторов. Благодаря последним, Nest имеет лаконичный и выразительный синтаксис, что повышает удобство разработки.


Декораторы или аннотации наследники аспектов, которые позволяют декларативно описывать логику, модифицировать поведение классов, их свойств, аргументов и методов.
Технически декораторы это просто функции, но их вызовом полностью управляет компилятор.
Важная особенность заключается в том, что в зависимости от контекста, сигнатуры аргументов будут различаться. Материалов на эту тему существует довольно много, однако мы сосредоточимся на специфике, связанной непосредственно с Nest.


Базовые декораторы


Возьмем простейший http-контроллер. Допустим, нам требуется, чтобы только определенные пользователи могли воспользоваться его методами. Для этого кейса в Nest есть встроенная функциональность гардов.
Guard это комбинация класса, реализующего интерфейс CanActivate и декоратора @UseGuard.


@Injectable()export class RoleGuard implements CanActivate {  canActivate(    context: ExecutionContext,  ): boolean | Promise<boolean> | Observable<boolean> {    const request = context.switchToHttp().getRequest();    return getRole(request) === 'superuser'  }}@Controller()export class MyController {  @Post('secure-path')  @UseGuards(RoleGuard)  async method() {    return  }}

Захардкоженный superuser не самое лучшее решение, куда чаще нужны более универсальные декораторы.
Nest в этом случае предлагает использовать
декоратор @SetMetadata. Как понятно из названия, он позволяет ассоциировать метаданные с декорируемыми объектами классами или методами.
Для доступа к этим данным используется экземпляр класса Reflector, но можно и напрямую через reflect-metadata.


@Injectable()export class RoleGuard implements CanActivate {  constructor(private reflector: Reflector) {}  canActivate(    context: ExecutionContext,  ): boolean | Promise<boolean> | Observable<boolean> {    const role = this.reflector.get<string>('role', context.getHandler());    const request = context.switchToHttp().getRequest();    return getRole(request) === role  }}@Controller()export class MyController {  @Post('secure-path')  @SetMetadata('role', 'superuser')  @UseGuards(RoleGuard)  async test() {    return  }}

Композитные декораторы


Декораторы зачастую применяются в связках.
Обычно это обусловлено тесной связностью эффектов в каком-то бизнес-сценарии. В этом случае имеет смысл объединить несколько декораторов в один.
Для композиции можно воспользоваться утилитной функцией applyDecorators.


const Role = (role) => applyDecorators(UseGuards(RoleGuard), SetMetadata('role', role))

или написать агрегатор самим:


const Role = role => (proto, propName, descriptor) => {  UseGuards(RoleGuard)(proto, propName, descriptor)  SetMetadata('role', role)(proto, propName, descriptor)}@Controller()export class MyController {  @Post('secure-path')  @Role('superuser')  async test() {    return  }}

Полиморфные декораторы


Легко столкнуться с ситуацией, когда оказывается нужным задекорировать все методы класса.


@Controller()@UseGuards(RoleGuard)export class MyController {  @Post('secure-path')  @Role('superuser')  async test1() {    return  }  @Post('almost-securest-path')  @Role('superuser')  async test2() {    return  }  @Post('securest-path')  @Role('superuser')  async test3() {    return  }}

Такой код можно сделать чище, если повесить декоратор на сам класс. И уже внутри декоратора класса обойти прототип, применяя эффекты на все методы, как если бы декораторы были повешены на каждый метод по-отдельности.
Однако для этого обработчику необходимо различать типы объектов применения класс и метод и в зависимости от этого выбирать поведение.
Реализация декораторов в typescript не содержит этот признак в явном виде,
поэтому его приходится выводить из сигнатуры вызова.


type ClassDecorator = <TFunction extends Function>(target: TFunction) => TFunction | void;type MethodDecorator = <T>(target: Object, propertyKey: string | symbol, descriptor: TypedPropertyDescriptor<T>) => TypedPropertyDescriptor<T> | void;type ParameterDecorator = (target: Object, propertyKey: string | symbol, parameterIndex: number) => void;const Role = (role: string): MethodDecorator | ClassDecorator => (...args) => {  if (typeof args[0] === 'function') {    // Получение конструктора    const ctor = args[0]    // Получение прототипа    const proto = ctor.prototype    // Получение методов    const methods = Object      .getOwnPropertyNames(proto)      .filter(prop => prop !== 'constructor')    // Обход и декорирование методов    methods.forEach((propName) => {      RoleMethodDecorator(        proto,        propName,        Object.getOwnPropertyDescriptor(proto, propName),        role,      )    })  } else {    const [proto, propName, descriptor] = args    RoleMethodDecorator(proto, propName, descriptor, role)  }}

Есть вспомогательные библиотеки, которые берут на себя часть этой рутины: lukehorvat/decorator-utils, qiwi/decorator-utils.
Это несколько улучшает читаемость.


import { constructDecorator, CLASS, METHOD } from '@qiwi/decorator-utils'const Role = constructDecorator(  ({ targetType, descriptor, proto, propName, args: [role] }) => {    if (targetType === METHOD) {      RoleMethodDecorator(proto, propName, descriptor, role)    }    if (targetType === CLASS) {      const methods = Object.getOwnPropertyNames(proto)      methods.forEach((propName) => {        RoleMethodDecorator(          proto,          propName,          Object.getOwnPropertyDescriptor(proto, propName),          role,        )      })    }  },)

Совмещение в одном декораторе логики для разных сценариев дает очень весомый плюс для разработки:
вместо @DecForClass, @DecForMethood, @DecForParam получается всего один многофункциональный @Dec.


Так, например, если роль пользователя вдруг потребуется в бизнес-слое контроллера, можно просто расширить логику @Role.
Добавляем в ранее написанную функцию обработку сигнатуры декоратора параметра.
Так как подменить значение параметров вызова напрямую нельзя, createParamDecorator делегирует это вышестоящему декоратору посредством метаданных.
И далее именно декоратор метода / класса будет резолвить аргументы вызова (через очень длинную цепочку от ParamsTokenFactory до RouterExecutionContext).


// Сигнатура параметра  if (typeof args[2] === 'number') {    const [proto, propName, paramIndex] = args    createParamDecorator((_data: unknown, ctx: ExecutionContext) => {      return getRole(ctx.switchToHttp().getRequest())    })()(proto, propName, paramIndex)  }

Также стоит отметить, что при помощи метадаты можно решать разные интересные кейсы, например, вводить ограничения для повторяемости или сочетаемости аннотаций.
Предположим, нам потребовалось ограничение размера запроса, и соответствующий декоратор повесили дважды. Какому значению доверять?
Без знания логики компилятора возникает неопределенность. Правильнее, наверное, было бы бросить ошибку.


class SomeController {   @RequestSize(1000)   @RequestSize(5000)   @Post('foo')   method(@Body() body) {   }}

Вот другой пример: необходимо ограничить работу методов контроллера отдельными портами. Здесь, скорее, требуется не затирать предыдущие значения,
а добавлять новые к имеющимся.


class SomeController {   @Port(9092)   @Port(8080)   @Post('foo')   method(@Body() body) {   }}

Схожая ситуация возникает с ролевой моделью.


class SomeController {  @Post('securest-path')  @Role('superuser')  @Role('usert')  @Role('otheruser')  method(@Role() role) {  }}

Обобщая рассуждения, реализация декоратора для последнего примера с использованием reflect-metadata и полиморфного контракта
может иметь вид:


import { ExecutionContext, createParamDecorator } from '@nestjs/common'import { constructDecorator, METHOD, PARAM } from '@qiwi/decorator-utils'@Injectable()export class RoleGuard implements CanActivate {  canActivate(context: ExecutionContext): boolean | Promise<boolean> {    const roleMetadata = Reflect.getMetadata(      'roleMetadata',      context.getClass().prototype,    )    const request = context.switchToHttp().getRequest()    const role = getRole(request)    return roleMetadata.find(({ value }) => value === role)  }}const RoleMethodDecorator = (proto, propName, decsriptor, role) => {  UseGuards(RoleGuard)(proto, propName, decsriptor)  const meta = Reflect.getMetadata('roleMetadata', proto) || []  Reflect.defineMetadata(    'roleMetadata',    [      ...meta, {        repeatable: true,        value: role,      },    ],    proto,  )}export const Role = constructDecorator(  ({ targetType, descriptor, proto, propName, paramIndex, args: [role] }) => {    if (targetType === METHOD) {      RoleMethodDecorator(proto, propName, descriptor, role)    }    if (targetType === PARAM) {      createParamDecorator((_data: unknown, ctx: ExecutionContext) =>        getRole(ctx.switchToHttp().getRequest()),      )()(proto, propName, paramIndex)    }  },)

Макродекораторы


Nest спроектирован таким образом, что его собственные декораторы удобно расширять и переиспользовать. На первый взгляд довольно сложные кейсы, к примеру, связанные с добавлением поддержки новых протоколов, реализуются парой десятков строк обвязочного кода. Так, стандартный @Controller можно обсахарить
для работы с JSON-RPC.
Не будем останавливаться на этом подробно, это слишком бы далеко вышло за формат этой статьи, но покажу основную идею: на что способны декораторы, в сочетании с Nest.


import {  ControllerOptions,  Controller,  Post,  Req,  Res,  HttpCode,  HttpStatus,} from '@nestjs/common'import { Request, Response } from 'express'import { Extender } from '@qiwi/json-rpc-common'import { JsonRpcMiddleware } from 'expressjs-json-rpc'export const JsonRpcController = (  prefixOrOptions?: string | ControllerOptions,): ClassDecorator => {  return <TFunction extends Function>(target: TFunction) => {    const extend: Extender = (base) => {      @Controller(prefixOrOptions as any)      @JsonRpcMiddleware()      class Extended extends base {        @Post('/')        @HttpCode(HttpStatus.OK)        rpc(@Req() req: Request, @Res() res: Response): any {          return this.middleware(req, res)        }      }      return Extended    }    return extend(target as any)  }}

Далее необходимо извлечь @Req() из rpc-method в мидлваре, найти совпадение с метой, которую добавил декоратор @JsonRpcMethod.
Готово, можно использовать:


import {  JsonRpcController,  JsonRpcMethod,  IJsonRpcId,  IJsonRpcParams,} from 'nestjs-json-rpc'@JsonRpcController('/jsonrpc/endpoint')export class SomeJsonRpcController {  @JsonRpcMethod('some-method')  doSomething(    @JsonRpcId() id: IJsonRpcId,    @JsonRpcParams() params: IJsonRpcParams,  ) {    const { foo } = params    if (foo === 'bar') {      return new JsonRpcError(-100, '"foo" param should not be equal "bar"')    }    return 'ok'  }  @JsonRpcMethod('other-method')  doElse(@JsonRpcId() id: IJsonRpcId) {    return 'ok'  }}

Вывод


Декораторы Nest адаптируются к широкому спектру прикладных задач. В них легко переносится утилитная и бизнесовая логика. Их несложно расширять, композировать, совмещая несколько сценариев. И в этом, без сомнения, одна из сильных сторон фреймворка.
Однако важно помнить, что синтаксис декораторов сегодня все еще является экспериментальным, а их чрезмерное использование может дать обратный эффект, и сделать ваш код более запутанным.

Подробнее..

Как мы в 2020 году изобретали процесс разработки, отладки и доставки в прод изменений базы данных

24.08.2020 14:14:04 | Автор: admin
На дворе 2020 год и фоновым шумом вы уже привыкли слышать: Кубернетес это ответ!, Микросервисы!, Сервис меш!, Сесурити полиси!. Все вокруг бегут в светлое будущее.

Подходы в том, что касается баз данных, в нашей компании более консервативны, чем в прикладных приложениях. Крутится база данных у нас не в кубернетесе, а на железе или в виртуалке. Для изменений базы данных процессинга платежных сервисов у нас есть устоявшийся процесс, который включает в себя множество автоматических проверок, большое ревью и релиз с участием DBA. Количество проверок и привлекаемых людей в этом случае негативно влияет на time-to-market. С другой стороны, он отлажен и позволяет надежно вносить изменения в продакшен, минимизируя вероятность что-то сломать. А если что-то сломалось, то нужные люди уже включены в процесс починки. Этот подход делает работу основного сервиса компании стабильнее.

Большинство новых реляционных баз данных для микросервисов мы заводим на PostgreSQL. Отлаженный процесс для Oracle хоть и надёжный, но несет с собой избыточную сложность для маленьких БД. Тащить тяжёлые процессы из прошлого в светлое будущее никто не хочет. Проработкой процесса для светлого будущего заранее никто не занялся. В итоге получили отсутствие стандарта и разножопицу.



Если хотите узнать, к каким проблемам это привело и как мы их порешали, добро пожаловать под кат.

Проблемы, которые мы решали


Нет единых стандартов версионирования


В лучшем случае это DDL SQL-файлы, которые лежат где-то в директории db в репозитории с микросервисом. Совсем плохо, если это просто текущее состояние БД, разное на тесте и на проде, и эталонных скриптов схемы БД нет.

В ходе отладки ушатываем тестовую базу


Я сейчас немного тестовую БД пошатаю, не пугайтесь там и пошел отлаживать на тестовой базе данных только что написанный код изменения схемы. Иногда долго, и всё это время тестовый контур не работает.

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

Методы DAO не покрываются тестами, не проверяются в CI


При разработке и отладке методы DAO вызываются через дергание за внешние ручки несколькими слоями выше. Это подвергает проверке целые сценарии бизнес-логики вместо конкретного взаимодействия микросервиса и базы данных.

Гарантии, что ничего не развалится в будущем, нет. Страдает качество и поддерживаемость микросервиса.

Неизоморфность сред


Если в тестовый и продакшен контуры изменения поставляются по-разному, то нельзя быть уверенным, что оно будет работать одинаково. Особенно когда на тесте по факту проводится разработка и отладка.

Объекты на тесте могут быть созданы из-под учетки разработчика или приложения. Гранты накидываются как попало, обычно grant all privileges. Гранты приложению выдаются по принципу вижу ошибку в логе даю грант. Часто при релизе забывают про гранты. Иногда после релиза смок-тестирование не покрывает всю новую функциональность и отсутствие гранта выстреливает не сразу.

Тяжелый и ломучий процесс наката в продакшен


Накат в прод сделали ручным, но по аналогии с процессом для Oracle, через согласование DBA, релиз-менеджеров и накат релиз-инженерами.

Это замедляет релиз. А в случае проблем увеличивает даунтайм, усложняя доступ разработчика к БД. Скрипты exec.sql и rollback.sql часто не проверялись на тесте, потому что стандарта патчсетирования для не-Oracle нет, а на тест катилось как попало.

Поэтому бывает такое, что в некритичные сервисы разработчики катят изменения без этого процесса вообще.

Как можно делать, чтобы было хорошо


Отладка на локальной БД в докер-контейнере


Для кого-то могут показаться очевидными вообще все технические решения, описанные в статье. Но почему-то из года в год я вижу людей, которые с энтузиазмом наступают на одни и те же грабли.

Вот вы же не лезете на тестовый сервер по ssh, чтобы писать и дебажить код приложения? Я считаю, что разрабатывать и отлаживать код базы данных на тестовом инстансе БД так же абсурдно. Есть исключения, бывает, что поднять локально базу данных очень сложно. Но обычно, если мы говорим о чем-то легковесном и не-легаси, то поднять локально базу и накатить на нее последовательно все миграции не составляет большого труда. Взамен вы получите стабильный инстанс под боком, который не ушатает другой разработчик, до которого не пропадут доступы и на котором вы имеете нужные для разработки права.

Приведу пример, насколько просто поднять локально БД:

Пишем двухстрочный Dockerfile:
FROM postgres:12.3ADD init.sql /docker-entrypoint-initdb.d/


В init.sql делаем чистую БД, которую рассчитываем получить и на тесте, и в проде. Она должна содержать:

  • Пользователя-владельца схемы и саму схему.
  • Пользователя приложения с грантом на использование схемы.
  • Требуемые EXTENSIONs


Пример init.sql
create role my_awesome_servicewith login password *** NOSUPERUSER inherit CREATEDB CREATEROLE NOREPLICATION;create tablespace my_awesome_service owner my_awesome_service location '/u01/postgres/my_awesome_service_data';create schema my_awesome_service authorization my_awesome_service;grant all on schema my_awesome_service to my_awesome_service;grant usage on schema my_awesome_service to my_awesome_service;alter role my_awesome_service set search_path to my_awesome_service,pg_catalog, public;create user my_awesome_service_app with LOGIN password *** NOSUPERUSER inherit NOREPLICATION;grant usage on schema my_awesome_service to my_awesome_service_app;create extension if not exists "uuid-ossp";



Для удобства можно добавить в Makefile таску db, которая (пере)запустит контейнер с базой и оттопырит порт для соединения:

db:    docker container rm -f my_awesome_service_db || true    docker build -t my_awesome_service_db docker/db/.    docker run -d --name my_awesome_service_db -p 5433:5432 my_awesome_service_db


Версионирование changesetов с помощью чего-то стандартного для индустрии


Тоже выглядит очевидно: нужно писать миграции и содержать их в системе контроля версий. Но очень часто я вижу голые sql-скрипты, без какой-либо обвязки. И это значит, что нет никакого контроля наката и отката, кем, что и когда было накачено. Нет даже гарантии, что ваши SQL-скрипты могут быть выполнены на тестовой и продовой БД, так как ее структура могла измениться.

В общем, нужен контроль. Системы миграции как раз про контроль.
Не будем вдаваться в сравнение разных систем версионирования схем БД. FlyWay vs Liquibase не тема этой статьи. Мы выбрали Liquibase.

Мы версионируем:

  • DDL-структуру объектов бд (create table).
  • DML-содержимое таблиц-справочников (insert, update).
  • DCL-гранты для УЗ Приложения (grant select, insert on ...).


Запуская и отлаживая микросервис на локальной БД, разработчик столкнется с необходимостью позаботиться о грантах. Единственный легальный способ для него завести DCL-скрипт в ченджсет. Это гарантирует нам, что гранты доедут до прода.

Пример sql-патчсета
0_ddl.sql:
create table my_awesome_service.ref_customer_type(    customer_type_code    varchar not null,    customer_type_description varchar not null,    constraint ref_customer_type_pk primary key (customer_type_code)); alter table my_awesome_service.ref_customer_type    add constraint customer_type_code_ck check ( (customer_type_code)::text = upper((customer_type_code)::text) );

1_dcl.sql:
grant select on all tables in schema my_awesome_service to ru_svc_qw_my_awesome_service_app;grant insert, update on my_awesome_service.some_entity to ru_svc_qw_my_awesome_service_app;

2_dml_refs.sql:
insert into my_awesome_service.ref_customer_type (customer_type_code, customer_type_description)values ('INDIVIDUAL', 'Физ. лицо');insert into my_awesome_service.ref_customer_type (customer_type_code, customer_type_description)values ('LEGAL_ENTITY', 'Юр. лицо');insert into my_awesome_service.ref_customer_type (customer_type_code, customer_type_description)values ('FOREIGN_AGENCY', 'Иностранное юр. лицо');

Fixtures. Данные для тестов или отладки идут отдельным ченжсетом с контекстом dev
3_dml_dev.sql:
insert into my_awesome_service.some_entity_state (state_type_code, state_data, some_entity_id)values ('BINDING_IN_PROGRESS', '{}', 1);

rollback.sql:
drop table my_awesome_service.ref_customer_type;


Пример changeset.yaml
databaseChangeLog: - changeSet:     id: 1     author: "mr.awesome"     changes:       - sqlFile:           path: db/changesets/001_init/0_ddl.sql       - sqlFile:           path: db/changesets/001_init/1_dcl.sql       - sqlFile:           path: db/changesets/001_init/2_dml_refs.sql     rollback:       sqlFile:         path: db/changesets/001_init/rollback.sql - changeSet:     id: 2     author: "mr.awesome"     context: dev     changes:       - sqlFile:           path: db/changesets/001_init/3_dml_dev.sql



Liquibase создает на БД таблицу databasechangelog, где отмечает накаченные ченджсеты.
Автоматически вычисляет, сколько ченджсетов нужно докатить до БД.

Есть maven и gradle plugin с возможностью сгенерировать из нескольких ченджсетов скрипт, который нужно докатить до БД.

Интеграция системы миграций БД в фазу запуска приложения


Здесь мог бы быть любой адаптер системы контроля миграций и фреймворка, на котором построено ваше приложение. Со многими фреймворками он идёт в комплекте с ORM. Например, Ruby-On-Rails, Yii2, Nest.JS.

Этот механизм нужен, чтобы катить миграции при старте контекста приложения.
Например:

  1. На тестовой БД патчсеты 001, 002, 003.
  2. Погромист наразрабатывал патчсеты 004, 005 и не деплоил приложение в тест.
  3. Деплоим в тест. Докатываются патчсеты 004, 005.


Если не накатываются приложение не стартует. Rolling update не убивает старые поды.
В нашем стеке JVM + Spring, и мы не используем ORM. Поэтому нам потребовалась интеграция Spring-Liquibase.

У нас в компании есть важное требование безопасности: пользователь приложения должен иметь ограниченный набор грантов и точно не должен иметь доступ уровня владельца схемы. С помощью Spring-Liquibase есть возможность катить миграции от имени пользователя-владельца схемы. При этом пул соединений прикладного уровня приложения не имеет доступа к DataSource'у Liquibase. Поэтому приложение не получит доступ из-под пользователя-владельца схемы.

Пример application-testing.yaml
spring:  liquibase:    enabled: true    database-change-log-lock-table: "databasechangeloglock"    database-change-log-table: "databasechangelog"    user: ${secret.liquibase.user:}    password: ${secret.liquibase.password:}    url: "jdbc:postgresql://my.test.db:5432/my_awesome_service?currentSchema=my_awesome_service"



DAO тесты на CI-этапе verify


В нашей компании есть такой CI-этап verify. На этом этапе происходит проверка изменений на соответствие внутренним стандартам качества. Для микросервисов это обычно прогон линтера для проверки кодстайла и на наличие багов, прогон unit-тестов и запуск приложения с поднятием контекста. Теперь на этапе verify можно проверить миграции БД и взаимодействие DAO-слоя приложения с БД.

Поднятие контейнера с БД и накат патчсетов увеличивает время старта Spring-контекста на 1,5-10 сек, в зависимости от мощности рабочей машины и количества патчсетов.

Это не совсем unit-тесты, это тесты интеграции DAO-слоя приложения с базой данных.
Называя БД частью микросервиса, мы говорим, что это тестирование интеграции двух частей одного микросервиса. Без внешних зависимостей. Таким образом эти тесты стабильны и могут выполняться на этапе verify. Они фиксируют контракт микросервиса и БД, обеспечивая уверенность при будущих доработках.

А еще это удобный способ отладки DAO. Вместо того, чтобы вызывать RestController, имитируя поведения пользователя в каком-то бизнес-сценарии, сразу вызываем DAO с нужными аргументами.

Пример DAO-теста
@Test@Transactional@Rollbackfun `create cheque positive flow`() {      jdbcTemplate.update(       "insert into my_awesome_service.some_entity(inn, registration_source_code)" +               "values (:inn, 'QIWICOM') returning some_entity_id",       MapSqlParameterSource().addValue("inn", "526317984689")   )   val insertedCheque = chequeDao.addCheque(cheque)   val resultCheque = jdbcTemplate.queryForObject(       "select cheque_id from my_awesome_service.cheque " +               "order by cheque_id desc limit 1", MapSqlParameterSource(), Long::class.java   )   Assert.assertTrue(insertedCheque.isRight())   Assert.assertEquals(insertedCheque, Right(resultCheque))}



Есть две сопутствующие задачи для прогона этих тестов в пайплайне на verify:

  1. На билдагенте может быть потенциально занят стандартный порт PostgreSQL 5432 или любой статичный. Мало ли, кто-то не потушил контейнер с базой после завершения тестов.
  2. Из этого вторая задача: нужно тушить контейнер после завершения тестов.

Эти две задачи решает библиотека TestContainers. Она использует существующий докер образ для поднятия контейнера с базой данных в состоянии init.sql.

Пример использования TestContainers
@TestConfigurationpublic class DatabaseConfiguration {   @Bean   GenericContainer postgreSQLContainer() {       GenericContainer container = new GenericContainer("my_awesome_service_db")               .withExposedPorts(5432);       container.start();       return container;   }   @Bean   @Primary   public DataSource onlineDbPoolDataSource(GenericContainer postgreSQLContainer) {       return DataSourceBuilder.create()               .driverClassName("org.postgresql.Driver")               .url("jdbc:postgresql://localhost:"                       + postgreSQLContainer.getMappedPort(5432)                       + "/postgres")               .username("my_awesome_service_app")               .password("my_awesome_service_app_pwd")               .build();   }       @Bean   @LiquibaseDataSource   public DataSource liquibaseDataSource(GenericContainer postgreSQLContainer) {       return DataSourceBuilder.create()               .driverClassName("org.postgresql.Driver")               .url("jdbc:postgresql://localhost:"                       + postgreSQLContainer.getMappedPort(5432)                       + "/postgres")               .username("my_awesome_service")               .password("my_awesome_service_app_pwd")               .build();   }



С разработкой и отладкой разобрались. Теперь нужно доставить изменения схемы БД в продакшен.

Kubernetes это ответ! А какой был ваш вопрос?


Итак, вам надо автоматизировать какой-то CI/CD-процесс. У нас есть обкатанный подход на тимсити. Казалось бы, где тут повод для еще одной статьи?

А повод есть. Кроме обкатанного подхода, есть и поднадоевшие проблемки большой компании.

  • Билдагентов тимсити на всех не хватает.
  • Лицензия стоит денег.
  • Настройки виртуалок билдагентов делаются по старинке, через репозитории с конфигами и puppet.
  • Доступы с билдагентов до целевых сетей пропиливать надо по старинке.
  • Логины-пароли для наката изменений на базу тоже хранятся по старинке.


И во всем этом по старинке проблема все бегут в светлое будущее, а поддержка легаси ну вы знаете. Работает и ладно. Не работает займемся потом. Когда-нибудь. Не сегодня.

Допустим, вы уже одной ногой по колено в светлом будущем и кубернетес-инфраструктура у вас уже есть. Есть даже возможность сгенерировать еще один микросервис, который сразу заведется в этой инфраструктуре, подхватит нужный конфиг и секреты, будет иметь нужные доступы, зарегистрируется в service mesh инфраструктуре. И всё это счастье может получить рядовой разработчик, без привлечения человека с ролью *OPS. Вспоминаем, что в кубернетесе есть тип ворклоада Job, как раз предназначенный для каких-то сервисных работ. Ну и погнали делать приложение на Kotlin+Spring-Liquibase, стараясь максимально переиспользовать существующую в компании инфраструктуру для микросервисов на JVM в кубере.

Переиспользуем следующие аспекты:

  • Генерация проекта.
  • Деплой.
  • Доставку конфигов и секретов.
  • Доступы.
  • Логирование и доставка логов в ELK.


Получаем такой пайплайн:


Кликабельно

Теперь мы имеем:


  • Версионирование ченджсетов.
  • Проверяем их на выполнимость update rollback.
  • Пишем тесты на DAO. Бывает даже следуем TDD: запускаем отладку DAO с помощью тестов. Тесты выполняются на свежеподнятой БД в TestContainers.
  • Запускаем локально БД в докере на стандартном порту. Проводим отладку, смотрим, что осталось в БД. При необходимости можем управлять локальной БД вручную.
  • Накатываем в тест и проводим авторелиз патчсетов стандартным пайплайном в teamcity, по аналогии с микросервисами. Пайплайн является дочерним для микросервиса, которому принадлежит БД.
  • Не храним креды от БД в тимсити. И не заботимся о доступах с виртуалок-билдагентов.


Знаю, что для многих это всё не откровение. Но раз уж вы дочитали, будем рады рассказу о вашем опыте в комментах =)
Подробнее..

Перевод Почему в мире так много отстойного ПО

17.05.2021 14:19:21 | Автор: admin
Мы буквально окружены отстойным программным обеспечением. Пенсионные фонды спотыкаются об написанные десятки лет назад пакетные скрипты с ошибочными допущениями. Из кредитных организаций утекает более сотни миллионов номеров социального обеспечения и других конфиденциальных данных. И это ещё не говоря о куче забагованного и раздражающего ПО, создаваемых и мелкими поставщиками, и крупными корпорациями.

Подобная некомпетентность ни за что бы не сошла с рук в других инженерных дисциплинах. Мы бы ни на каких условиях не смирились бы с мостами, имеющими столько же багов, что и среднестатистическая программная система. Почему же ситуация с проектированием ПО настолько ужасна? Почему в мире так много фиговых программ?

Чтобы понять причину этого, нам сначала нужно понять, как соотносится навык разработчика с полезностью ПО, которое он создаёт для решения определённой задачи.


В первой категории мы видим разработчиков, которые, по сути, некомпетентны. В этой категории можно встретить широкий диапазон владения навыками, но все, кто к ней относится, схожи тем, что их конечный продукт функционально бесполезен. Компьютеры в этом отношении безжалостны если вы не знаете, как заставить своё приложение компилироваться, то с тем же успехом вы могли и вообще его не писать. Если вы не знаете, как создать базовую функциональность своего приложения, то им не будет пользоваться абсолютно никто. Ниже определённого порога навыков вы не сможете создать никакого годного к применению ПО. Большинство разработчиков из этой категории экспериментируют с кодом в своё свободное время, и редко создают профессиональное ПО.

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

И наконец, когда разработчики достигают определённого порога навыков, они пересекают его
и попадают в третью категорию. В категорию, где каждый достиг такого высокого уровня компетентности (относительно решаемой ими задачи), что дальнейший личный рост минимально отразится на конечном продукте. Например, любой случайно выбранный инженер из Google может создать CRUD-приложение столь же качественно, как и Джефф Дин.



В идеальном мире единственными разработчиками в первой и второй категории были бы студенты или молодые специалисты. А все профессиональные программные системы в основном создавались бы разработчиками из третьей категории. Разработчиками, в совершенстве освоившими все навыки, необходимые для решения их задачи и создания решений, очень близких к теоретическому идеалу. В таком изумительном мире всё окружающее нас ПО находилось бы на примерно одинаково высоком уровне качества, работало бы точно в соответствии с ожиданиями, с оптимальной производительностью и без дыр в безопасности. В этом мире общество реагировало бы на всё ПО с радостью, а не с раздражением.

Однако между нами и этой утопией стоят две проблемы.

Во-первых, количество разработчиков из третьей категории чрезвычайно мало по сравнению со второй категорией. Программирование легко изучить, но сложно освоить. Миллионы людей могут написать функционирующий скрипт, но очень немногие мастерски освоили искусство проектирования ПО. К тому же, не существует никаких препятствий к попаданию в отрасль разработки ПО для программистов нет аналога Американской медицинской ассоциации или Международной ассоциации юристов. Неудивительно, что в ней гораздо больше людей с начальной и средней компетентностью по сравнению с количеством компетентных специалистов.

Во-вторых, спрос на разработчиков невероятен. Разработчики программного обеспечения имеют возможность внести огромный вклад практически в любой отрасли. По сравнению с более нишевыми профессиями, например, с астрономией, где возможности серьёзно ограничены, разработка ПО это сфера, в которой очень не хватает талантов. То есть, основная проблема заключается в поиске талантливых разработчиков ПО, а не в поиске для них подходящей работы.

Если соединить эти две проблемы, то получится, что большинство компаний, желающих нанимать разработчиков-экспертов, не имеют возможности это сделать. Для найма не хватает хороших специалистов, а те, что есть на рынке, часто получают несопоставимо более выгодные предложения от компаний уровня FANG или взлетевших стартапов.

Поэтому все остальные компании делают наиболее оптимальный в такой ситуации выбор. Они нанимают разработчиков из второй категории. Разработчиков, которых можно в целом назвать достаточно хорошими. В их приложениях есть баги, уязвимости безопасности и они не справляются с высокими нагрузками. Но, по крайней мере, эти люди способны создать нечто работающее. Нечто более полезное, чем статус-кво. То, что можно выкатить в продакшен с минимальным критическим анализом.



Можно впасть в заблуждение, что подобная ситуация естественна для всего мира и всех профессионалов. Но на самом деле это не так.

Существует множество должностей, требующих серьёзного обучения, но относящихся к категории простых в освоении. Это профессии наподобие водителя такси, строителя или бармена. В таких профессиях есть большая доля работников, достигших категории высокой компетентности, а дальнейшее совершенствование навыков практически не приносит никакой выгоды.

Также существует множество профессий, в которых возможности настолько ограничены, что работодатели предпочитают нанимать только отличных специалистов. Например, это относится к пианистам. Пианистов-любителей можно встретить во многих семьях, но они никогда не будут играть в концертном зале, ведь количество талантливых пианистов намного превышает количество запланированных концертов.

И наконец, есть профессии, которые сложно освоить, и с множеством возможностей, но имеющие нормативные входные барьеры. Например, врачебная деятельность. В мире до Obamacare было много людей, которые не могли позволить себе страхование здоровья и с радостью бы выбрали хоть какое-нибудь здравоохранение вместо полного его отсутствия (стоит ли так делать это уже другой сложный вопрос). Тем не менее, из-за строгих правил предоставлять медицинские услуги могут только доказавшие высокую компетентность. Аналогична ситуация и во многих инженерных дисциплинах, подвергающихся сильному нормативному контролю, например, в строительстве мостов и небоскрёбов, а также в производстве медицинского оборудования.



Разработка программного обеспечения стала любопытным пересечением всего вышеперечисленного. Ему легко научиться, поэтому по всему миру существуют легионы достаточно хороших разработчиков. Разработчиков, создающих достаточно полезные программные решения, однако переполненные багами и уязвимостями безопасности. В то же время, эту профессию сложно освоить на высоком уровне, поэтому гораздо сложнее найти разработчиков, способных избегать подобных ловушек.

Отрасль разработки ПО настолько богата возможностями, что большинство компаний считает почти невозможным найм хороших профессионалов. Она так богата возможностями, что несмотря на наличие легионов новичков-разработчиков по всему миру, для всех и каждого из них есть работа.

И наконец, в разработке ПО нет никакого контроля на входе. Каждый может пройти программу обучения кодингу, изучить несколько онлайн-курсов по созданию кода, и на следующий день начать предлагать свои услуги на Upwork. И продукт их труда можно сразу же выкатить в продакшен без какого-либо нормативного контроля.

Если всё это соединить, то можно понять, почему в мире так много отстойного ПО. Программное обеспечение пожирает мир, сопровождаемое багами и дырами в безопасности.



Дополнение: моя статья может показаться очень критичной, но я не рекомендую, чтобы мы ставили барьеры для входа в профессию разработчика ПО или подвергали все программные системы нормативному контролю. В этой статье представлено описание ситуации, а не способы её решения.

Кроме того, в нём рассматривается только та часть уравнения, которая относится к разработчикам. Менеджеры и CEO, не дающие разработчикам достаточно времени для создания безупречного ПО и предпочитающие выпустить нечто сносное ещё одна серьёзная причина проблемы, которую мы исследуем в другой статье.

image
Подробнее..

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru