UMGUM.COM 

Настройка сервиса ( Вторая часть решения задачи развёртывания сервиса "Eduroam" - настройка серверов "Freeradius". )

2 августа 2019  (обновлено 12 сентября 2019)

OS: "Linux Debian 9", "Linux Ubuntu Server 18 LTS".
Apps: "Freeradius v3".

Здесь рассматривается вторая (основная) часть решения задачи развёртывания совместимой с международным сервисом "Eduroam" инфраструктуры предоставления доступа в интернет через беспроводную WiFi-сеть - настройка серверов "Freeradius", являющихся главным компонентом инфраструктуры.

Последовательность дальнейших действий такова:

1. Настройка проксирования вышестоящим серверам "Eduroam" запросов сторонних пользователей.
2. Настройка аутентификации локальных пользователей с данными аккаунтов хранимых в LDAP.

Напоминаю, что локальная аутентификация пользователей и маршрутизация запросов таковых для аутентификации на серверах контролирующих сторонние сегменты в международной сети "Eduroam" осуществляется посредством протокола RADIUS (Remote Authentication Dial In User Service).


Для справки, протокол RADIUS предназначен для аутентификации, авторизации и учёта (Authentication, Authorization & Accounting) пользователей сетевых служб. Описан в стандартах RFC 2865 и RFC 2866, и перед настройкой его компонентов лучше бы разобраться в роли каждого:

"authentication" - проверка подлинности субъекта по его идентификационным данным, например, по логину и паролю.
"authorization" - определение уровня полномочий аутентифицированного (идентифицированного) субъекта на доступ к определённым объектам или сервисам.
"accounting" - сбор сведений об использованных субъектом ресурсах (обычно это объём входящего и исходящего трафиков).

Служба RADIUS принимает запросы по протоколу UDP. По умолчанию порт "UDP:1812" предназначен для приёма запросов проверки подлинности (Authentication & Authorization), а порт "UDP:1813" для приёма сведений о потреблении ресурсов (Accounting). Международная сеть "Eduroam" строится на принципах бесплатного равноправного использования, потому учёт потребляемых ресурсов в неё не ведётся и для работоспособности её radius-серверов достаточно доступного извне сетевого порта "UDP:1812".

Настраиваем проксирование запросов внешним сервисам "Eduroam".

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

Прежде всего слегка дополним конфигурацию "Freeradius" и убедимся, что функционал проксирования включен:

# vi /etc/freeradius/3.0/radiusd.conf

....
proxy_requests = yes
....
security {
  ....
  # Предупреждая DDoS потоком некорректных запросов отвергаем их с задержкой в три секунды
  reject_delay = 3

  # Разрешаем отвечать на простейшие запросы "Status-Server"
  status_server = yes
....
log {
  ....
  # Во избежание утечек паролей обязательно явно запрещаем их сохранение в журнале событий
  auth_badpass = no
  auth_goodpass = no
....

Настроим фильтрацию входящих запросов и распределение таковых по службам обработки (я бы удалил дистрибутивный перенасыщенный примерами конфигурационный файл и создал вместо него свой):

# vi /etc/freeradius/3.0/proxy.conf

# Глобальные настройки "прокси-сервера"
proxy server {
  default_fallback = no
  retry_delay = 2
}

# # Блок описания связки с вышестоящей точкой распределения: begin

# Первичный вышестоящий radius-маршрутизатор
home_server uplink-1-eduroam {
  type = auth
  ipaddr = 12.34.56.123
  port = 1812
  secret = connectionPassword
  status_check = status-server
}

# Вторичный вышестоящий radius-маршрутизатор
home_server uplink-2-eduroam {
  type = auth
  ipaddr = 12.34.123.234
  port = 1812
  secret = connectionPassword
  status_check = status-server
}

# Собираем radius-маршрутизаторы в пул с балансировкой нагрузки и автопереключением на действующий в случае выхода из строя какого-то
home_server_pool pool-uplink-eduroam {
  #type = fail-over
  type = client-balance
  home_server = uplink-1-eduroam
  home_server = uplink-2-eduroam
}

# # Блок описания связки с вышестоящей точкой распределения: end

# # Блок описания правил распределения запросов: begin

# Явно запрещаем обработку запросов локальных "доменов", отправляя их в "чёрную дыру"
realm "~.*\.local" {
  virtual_server = outer-blackhole
  nostrip
}

# Явно запрещаем обработку запросов без "доменов", отправляя их в "чёрную дыру"
realm NULL {
  virtual_server = outer-blackhole
  nostrip
}

# Явно обозначаем "домен" клиентов, обслуживаемых местными сервисами
# (не задавая правил обработки подразумеваем тем самым, что это будет сделано локально)
#realm "example.net" {
realm "~(^|.+\.+)example\.net$" {
  authhost = LOCAL
  nostrip
}

# Всё остальное отправляем вышестоящему сервису
realm "~.+$" {
  auth_pool = pool-uplink-eduroam
  nostrip
}

# # Блок описания правил распределения запросов: end

Уже сейчас можно применить конфигурацию и попробовать произвести аутентификацию с учётными записями пользователей "Eduroam" сторонних сегментов:

# freeradius -C -X && systemctl restart freeradius

Формализуем подход к аутентификации местных пользователей.

Ранее в описании задачи мы рассмотрели способы организации подключения пользователей к WiFi-сети уровня предприятия и пришли к тому, что для "Eduroam" подходит лишь технология "WPA2 Enterprise (AES)" в двухфазной реализации, с инкапсуляцией запросов в шифрованный туннель ("EAP-TTLS" и "EAP-PEAP") и проверкой подлинности во второй фазе. Итого, для клиентов будут доступны следующие параметры подключения:

Настройки подключения к беспроводной WiFi-сети:
  SSID (имя сети): "eduroam"
  Тип сетевой аутентификации: "WPA2 Enterprise/WPA2-EAP"
  Метод шифрования данных: AES
Параметры проверки подлинности пользователей:
  Метод инкапсуляции в первой фазе: "EAP-TTLS" или "EAP-PEAP" (перед аутентификацией образуют TLS-туннель между клиентом и сервером аутентификации)
  Метод проверки подлинности: "EAP-GTC", "MS-CHAPv2" или PAP (наиболее совместимые со всеми распространёнными операционными системами)

Настраиваем модули аутентификации.

Как решено было выше аутентификацию клиентов "Eduroam" будем проводить только посредством современного протокола EAP (Extensible Authentication Protocol).

Слегка откорректируем конфигурацию соответствующего модуля "rlm_eap", избавляясь от ненужного, повышая тем самым уровень безопасности:

# vi /etc/freeradius/3.0/modules-enabled/eap

eap {
  ....
  # default_eap_type = md5
  default_eap_type = ttls
  ....

  # Очень желательно для TLS-туннеля первой фазы аутентификации применить действующий сертификат
  tls-config tls-common {
    ....
    # Контейнер с "закрытым (private)" ключем (опционально защищённый паролем)
    private_key_file = /etc/ssl/freeradius/wildcard.example.net.key.decrypt
    # private_key_password = whatever

    # Контейнер с "открытым (public)" ключем
    certificate_file = /etc/ssl/freeradius/wildcard.example.net.crt

    # Сертификат (или цепочка таковых) центра сертификации
    ca_file = /etc/ssl/freeradius/intermediate.crt
    ....

    # Включаем кеширование SSL-сессий
    cache (
      enable = yes
      lifetime = 24 # (hours)
    )
    ....
    # Проверку валидности сертификата пока не поддерживаем
    ocsp (
      enable = no
      ....
    )  
  }

  # # Оставляем только два наиболее распространённых безопасных метода (ttls и peap) - все остальные деактивируем # #

  # pwd { }

  # leap { }

  # fast { }

  # EAP-TTLS
  ttls {
    tls = tls-common
    default_eap_type = mschapv2
    copy_request_to_tunnel = yes
    use_tunneled_reply = yes
    virtual_server = "inner-tunnel-eduroam"
  }

  # EAP-PEAP
  peap {
    tls = tls-common
    default_eap_type = mschapv2
    copy_request_to_tunnel = yes
    use_tunneled_reply = yes
    virtual_server = "inner-tunnel-eduroam"
  }

  # Sub-module for EAP
  gtc {
    auth_type = PAP
  }

  # Sub-module for EAP
  mschapv2 {
    send_error = yes
  }

  # Sub-module for EAP
  # (может пригодится для аутентификации тестовых пользователей хранимых в "files", с паролем в "Cleartext-Password")
  md5 {
  }
  ....
}

Несмотря на то, что очень небезопасный метод аутентификации PAP в конфигурации сервиса может и не использоваться, деактивировать соответствующий модуль "rlm_pap" нельзя. Дело в том, что помимо своего прямого назначения он ещё исполняет роль инструмента нормализации предоставленных другими модулями паролей, находя их в потоке поступающих сведений и помещая их в соответствующим образом именованные переменные, оснащая унифицированными заголовками (headres), указывающими на тип хеширования пароля (буде такое применяется). Исчерпывающие сведения о поддерживаемых типах хеширования имеются в сопроводительной документации.

Модуль должен быть включен и обязательно активирована опция "нормализации паролей":

# vi /etc/freeradius/3.0/modules-enabled/pap

pap {
  normalise = yes # (default: yes)
}

Настраиваем модуль поиска и выборки данных пользователя из LDAP.

Прежде всего есть смысл проверить, возможен ли вообще запрос к LDAP-серверу сведений о потенциальном целевом пользователе, желательно прямо с сервера аутентификации "Eduroam":

$ ldapsearch -D "uid=username,ou=People,dc=example,dc=net" -w userPassword -h ldap.example.net -p 389 -b dc=example,dc=net "(uid=username)"

Дистрибутивный конфигурационный файл модуля LDAP для наших целей избыточен - я предпочитаю написать свой:

# vi /etc/freeradius/3.0/mods-available/ldap-example-eduroam

# Lightweight Directory Access Protocol (LDAP)
#
# Конфигурация подключения к первичному LDAP-серверу "ldap.example.net"
#
ldap ldap {
  server = "ldap://ldap.example.net:389/"
  port = 389
  identity = "uid=eduroam,..."
  password = "connectionLDAPPassword"
  base_dn = "dc=example,dc=net"

  # Mapping of LDAP directory attributes to RADIUS dictionary attributes.
  # (достаём хеши пароля из специфичных для нашего LDAP атрибутов и объявляем таковые в специализированных атрибутах "Freeradius")
  update {
    # (публикуем хеш пароля в атрибуте, требуемом модулем MS-CHAPv2)
    control:NT-Password := 'nthash'
    # (опционально публикуем хеш пароля в атрибуте, требуемом модулем PAP)
    control:MD5-Password := 'md5password'
  }

  # User object identification.
  user {
    #  Where to start searching in the tree for users
    base_dn = "${..base_dn}"
    filter = "(&(accountstatus=active)(objectclass=examplespecific)(uid=%{LDAP-User-Name}))"
  }

  # LDAP connection-specific options.
  options {
    rebind = yes
  }

  # LDAP pool connection-specific options.
  pool {
    # Для профилактики утечки памяти закрываем соединение с сервером после определённого числа его использований
    uses = 128

    # Время ожидания повторной попытки установить соединение до того момента, как оно будет закрыто принудительно
    retry_delay = 1 # (seconds; default: 30)

    #  Не ограничиваем время жизни работающего соединения
    lifetime = 0 # (seconds)

    # Продлеваем жизнь бездействующего соединения, чтобы оно было готово при первой необходимости
    idle_timeout = 600 # (seconds; default: 60)
  }
}

# Почти идентичная конфигурация подключения ко вторичному LDAP-серверу "ldap2.example.net"
#
ldap ldap2 {
  server = "ldap://ldap2.example.net:389/"
  ....
}

Активируем нашу специфичную конфигурацию модуля аутентификации посредством LDAP:

# ln -s  /etc/freeradius/3.0/mods-available/ldap-example-eduroam /etc/freeradius/3.0/mods-enabled/ldap-example-eduroam

О логике работы модуля LDAP в "Freeradius".

Немного о способах использования модуля LDAP в "Freeradius". Важно понимать, что происходит "под капотом" применяемых технологий.

Как и многие другие в "Freeradius", модуль "rlm_ldap" работает в двух режимах, в зависимости от того, в контексте какой функциональной логики он вызывается.

Когда LDAP-модуль используется в разделе "authorize", происходит следующее:

1. Модуль пытается подключится LDAP-серверу в режиме "binding" используя заданный в конфигурации модуля идентификатор (параметр "identity") или в качестве гостевого пользователя.
2. Ищется запись соответствующая логину аутентифицирующегося в "Eduroam" пользователя с использованием настроенных к конфигурации модуля фильтров.
3. Извлекаются небольшой предопределённый набор LDAP-атрибутов аутентифицируемого пользователя, которые преобразуются и добавляются в список атрибутов обрабатываемого пакета RADIUS.

После того, как от LDAP-сервера в виде одного или более атрибутов был выгружен пароль пользователя (в любом виде - открытым текстом или шифрованный, как правило), таковой передаётся модулям аутентификации в блоке "authenticate", которые сверят его всеми доступными способами с предоставленным пользователем паролем и сообщат о результате процесса аутентификации.

Если LDAP-модуль используется в разделе "authenticate", происходит следующее:

1. Модуль пытается подключится к LDAP-серверу непосредственно от имени аутентифицирующегося в "Eduroam" пользователя.
2. Если подключение состоялось, то это считается успешной аутентификацией.

Несмотря на видимую простоту реализации процесса аутентификации вторым вариантом использования LDAP-модуля оно почти нефункционально. Дело в том, что подключение к LDAP-серверу напрямую может состоятся только в том случае, если пароль предоставлен пользователем "Eduroam" в открытом виде (plain-text). Разумеется, это небезопасно и в производственных схемах таких подходов следует избегать. На практике сверку паролей лучше производить через максимально широко распространённый в клиентских устройствах механизм обмена хешами "MS-CHAPv2", а это приводит нас к безальтернативному использованию первого варианта применения LDAP-модуля - в блоке "authorize".

Воспользуемся модулем хранения аккаунтов в файле.

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

# /etc/freeradius/3.0/users

test-eduroam@example.net Cleartext-Password := "userPassword"
....

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

Описываем "виртуальный сайт" с набором правил локальной аутентификации.

Подбивая рассуждения о целесообразности применения тех или иных решений строим логику обработки сервером "Freeradius" клиентских запросов аутентификации в "Eduroam" следующим образом:

1. Вначале ВСЕ входящие запросы обрабатываются на уровне виртуального сервера "outer-example-eduroam", где проверяется корректность логина и устанавливается TLS-туннель от клиента к серверу.
2. Когда клиент в процессе EAP-согласования присылает требование осуществлять её в TLS-туннеле, то вступает в игру виртуальный сервер "inner-tunnel-eduroam", в котором описываются правила аутентификации для набора простых и EAP-протоколов - причём этот виртуальный сайт самостоятельно запросы обрабатывать не должен и не может, а вызывается лишь из модуля EAP, установившего туннель в первой фазе.

Описываем "виртуальный сайт" набора правил аутентификации локальных пользователей:

# vi /etc/freeradius/3.0/sites-available/example-eduroam

# Описываем виртуальный сервер принимающий запросы извне
server outer-example-eduroam {

  # Активируем точку приёма подключений к сервису
  listen {
    type = auth
    ipaddr = *
    port = 1812
    limit {
      max_connections = 1024
      lifetime = 0
      idle_timeout = 30
    }
  }

  # Первая фаза авторизации, с приёмом запросов извне и проксированием выше или переходом к локальной авторизации на второй фазе
  authorize {

    # Проверяем источник запроса и дополняем его недостающими атрибутами при необходимости
    # (это нужно делать первым делом, чтобы в журнал событий попало наименование провайдера услуги)
    if ( "%{client:shortname}" != "uplink-1-eduroam" && "%{client:shortname}" != "uplink-2-eduroam" ) {
      update request {
        # Задаём символическое имя оператора, в самом простом случае отталкиваясь от основного обслуживаемого "realm"
        # (чтобы по возможности избежать путаницы между "realm" и "operator-name" к последнему рекомендуют прибавлять префикс, например "1")
        Operator-Name := "1example.net"
      }
    }

    # Logging the initial EAP request
    if ( !&session-state: ) {
      update session-state {
        &Linelog-Key := "recv-request"
      }
      linelog
    }

    # Авторизацию отрабатываем только для логинов с корректными FQDN (для совместимости с мульти-доменными системами)
    if ( User-Name =~ /^(.+)@([a-zA-Z0-9]+(-[a-zA-Z0-9]+)*\.)+[a-zA-Z]{2,}$/ ) {

      # Отрабатываем правила проксирования, опирающиеся на "realm"
      suffix # (модификатор выделения "realm" из полного логина пользователя)

      # Первая фаза авторизации только посредством EAP (с вызовом "inner-tunnel-eduroam")
      # (отрабатывается, если предыдущая логика проксирования не выдала ответа об успешной авторизации)
      eap {
        ok = return
        updated = return
      }

    } else {
      update reply { Reply-Message := "Username should be in format username@domain.tld" }
      reject
    }

    # Безусловно отвечаем на запросы проверки доступности сервиса
    Autz-Type Status-Server {
      ok
    }
  }

  # Первая фаза аутентификация только посредством EAP (с вызовом "inner-tunnel-eduroam")
  authenticate {
    Auth-Type EAP {
      eap
    }
  }

  # Аккаунтинг не требуется
  accounting { }

  # Logging local Accept or Reject
  post-auth {
    # Logging local Accept
    if ( Response-Packet-Type == Access-Accept ) {
      update session-state {
        &Linelog-Key := "send-accept"
      }
      linelog
    }
    # Logging local Reject
    Post-Auth-Type REJECT {
      update session-state {
        &Linelog-Key := "send-reject"
      }
      linelog
    }
  }

  # Logging proxied Send Request
  pre-proxy {
    if ( !&State ) {
      update session-state {
        &Linelog-Key := "send-proxy-request"
      }
    linelog
    }
  }

  # Logging proxied Accept or Reject
  post-proxy {
    if ( "%{proxy-reply:Response-Packet-Type}" == "Access-Accept" || "%{proxy-reply:Response-Packet-Type}" == "Access-Reject" ) {
      update session-state {
        &Linelog-Key := "recv-proxy-request"
      }
      linelog
    }
  }

}

# Описываем виртуальный сервер обрабатывающий запросы инкапсулированные в EAP
server inner-tunnel-eduroam {

  # Активируем точку прямого подключения к "туннельному" сервису, только для тестирования
  # (в цепочке аутентификации реальных клиентов этот блок не используется)
  #listen {
  #  type = auth
  #  ipaddr = 127.0.0.1
  #  port = 18120
  #}

  # Вторая фаза проверки подлинности внутри TLS-туннеля
  authorize {

    # Запоминаем в кешируемой сессии для журнала событий исходного сеанса первой фазы (outer) реальное имя пользователя
    # (это полезно в случе, если первая фаза аутентификации - туннелирование TTLS/PEAP - проходится клиентом анонимно)
    # (конструктивно контексты первой и второй фаз не связаны, для безопасности - так что это единственный путь вывести что-то из "inner")
    update outer.session-state {
      Inner-User-Name := "%{User-Name}"
    }

    # Отвергаем некорректно настроенные подключения с логином "anonymous", применимым только на первой фазе (их много, оказывается)
    # (на первой фазе процедуры аутентификации - туннелировании - имя пользователя может быть неперсонифицированным)
    if ( User-Name =~ /^anonymous@.+$/ ) {
      update reply { Reply-Message := "Anonymous connections in the second phase are not supported!" }
      reject
    }

    # Авторизацию отрабатываем только для местных доменов
    # (откидываем домены со "звёздочками" - их часто вводят опираясь на некорректные инструкции)
    if ( User-Name =~ /^(.+)@(.+(?<!\*)\.+)*example\.net$/ ) {

      # В нашем LDAP логины хранятся без указания доменного имени, так что приходится перед самой авторизацией их подрезать
      update request {
        LDAP-User-Name := "%{1}"
      }

      # Подключаем модуль, обеспечивающий доступ к учётным записям в текстовом файле
      files

      # Подключаем пул модулей, обеспечивающих доступ к основным хранилищам учётных записей в LDAP
      redundant-load-balance {
        -ldap
        -ldap2
      }

      # Подключаем модули, оперирующие с обнаруженными учётными записями
      eap # (используется в комбинациях EAP-TTLS/EAP-MSCHAPv2, например)
      mschap # (используется в основном процессинге проверки подлинности)
      pap # (также используется для нормализации атрибутов; должен быть последним, чтобы получить уже полный набор "header"-ов от предыдущих модулей)

    } else {
      update reply { Reply-Message := "Authentication is possible only for for existing users!" }
      reject
    }

  }

  # Аутентификация внутри TLS-туннеля может быть и небезопасной
  authenticate {
    Auth-Type EAP {
      eap
    }
    Auth-Type PAP {
      pap
    }
    Auth-Type MS-CHAP {
      mschap
    }
  }

}

# Описываем "виртуальный сервер" - заглушку для запросов, по которым мы решили дать отказ ещё до проверки логина и пароля
server outer-blackhole {
  authorize {
    suffix
    switch "&Realm" {
      case "NULL" { update reply { Reply-Message := "No Realm" } }
      case "DEFAULT" { update reply { Reply-Message := "ERROR !!! Rejected in server auth-reject by DEFAULT realm !!!" } }
      case { update reply { Reply-Message := "Request Blackholed" } }
    }
    reject
  }
}

Отключаем предустановленные "виртуальные сайты":

# rm /etc/freeradius/3.0/sites-enabled/default
# rm /etc/freeradius/3.0/sites-enabled/inner-tunnel

Включаем нашу конфигурацию:

# ln -s /etc/freeradius/3.0/sites-available/example-eduroam /etc/freeradius/3.0/sites-enabled/example-eduroam

Проверяем и применяем конфигурацию "Freeradius":

# freeradius -C -X && systemctl restart freeradius

Уже сейчас схема способна обслужить запросы аутентификации как локальных пользователей, так и тех, кто принадлежит сторонним сегментам "Eduroam". В первом случае аутентификация будет осуществлена посредством сопоставления с данными профилей пользователей в LDAP или локальном текстовом файле, а во втором запрос будет отправлен (проксирован) контролирующему сегмент пользователя raduis-серверу.

Обеспечиваем приём запросов из других сегментов "Eduroam".

До сих пор мы настраивали аутентификацию пользователей, находящихся в нашей локальной инфраструктуре, посредством обращения к местным сервисам хранения учётных записей или отправки запросов во внешние сегменты "Eduroam".

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

# vi /etc/freeradius/3.0/clients.conf

....
# # Блок описания связки с вышестоящей точкой распределения: begin

# Первичный вышестоящий radius-маршрутизатор
client uplink-1-eduroam {
  ipaddr = 12.34.56.123
  secret = connectionPassword
  require_message_authenticator = no
  shortname = uplink-1-eduroam
  nas_type = other
  virtual_server = outer-example-eduroam
}

# Вторичный вышестоящий radius-маршрутизатор
client uplink-2-eduroam {
  ipaddr = 12.34.123.234
  secret = connectionPassword
  require_message_authenticator = no
  shortname = uplink-2-eduroam
  nas_type = other
  virtual_server = outer-example-eduroam
}

# # Блок описания связки с вышестоящей точкой распределения: end
....

Применение изменений описаний клиентских устройств потребует перезапуска сервиса:

# freeradius -C -X && systemctl restart freeradius

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

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


Заметки и комментарии к публикации:


Оставьте свой комментарий ( выразите мнение относительно публикации, поделитесь дополнительными сведениями или укажите на ошибку )