Thunderbird Auto Config Server (TACS)

от автора

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

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

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

Обзор

Основой для TACS является файл описания schema.yaml, в котором задаются основные параметры:

# Directory to search for templates # The search is also performed in subdirectories, but without following symbolic links # Required file extension: *.tmpl #templateDir: templates # Block for local processing of user logins. # Executes first because it doesn't require external requests. # If there is a match, subsequent stages are not checked #local:   # Default fields and properties   #default:     # Name of the GO template (*.tmpl), with insert fields - {{define "templateName"}}     #template: default     # A key-value map (hash table) that is used to fill the template.     # Can be overridden at the user level.     # If the field is not present at the user level, it will be set to the specified value.     # The key is a variable declared in the template - {{ .variableName }}     # Value - what will be substituted for the key     #fields:     #  cn: user     #  mail: mail@example.com     #  telephoneNumber:     #  position: no_body     #  company: "\"Example\""   # List of users to handle requests.   # Is a hash table of "username: params",   # so each iteration of the key overwrites the previous values   #list:   #root:   #  template: root   #  fields:   #    company: ""   #    mail: root@example.com   #    cn: Administrator   #    signatureIsHTML: true   #    signature: 'Sincerely\nroot'  # Block for processing users from LDAP. # If there is a match, subsequent stages are not checked #ldap:   # A unique field that identifies the user, a filter compiled for this:   #  (uid=username), example - (sAMAccountName=username)   #uid: sAMAccountName   # Ldap path where to start the search   #searchBase: OU=SystemUsers,OU=Corp,DC=corp,DC=domain,DC=com   # Additional filter for user search. Added to the filter with uid:   # (&(uid=username)filter)   # In this case, search only for enabled users:   #filter: (!(userAccountControl:1.2.840.113556.1.4.803:=2))(objectCategory=person)(objectClass=user)   # Search in subgroups?   # If true - adds the option ":1.2.840.113556.1.4.1941:" to the filter   #subgroups: true   # Should I generate a response for a user who is not a member of any declared group?   # In this case, a default section must be declared   #allowWithoutGroups: true   # A key-value map (hash table) that is used to fill the template.   # Can be overridden at the ldap group level.   # If the field is not present at the user level, it will be set to the specified value.   # The key is a variable declared in the template - {{ .variableName }}   # Value - fields taken from the user's LDAP profile.   # Note.   # If you prefix an LDAP field name with "raw:", the value will be queried raw and base64 encoded   #default:   #  template: default   #  fields:   #    cn: cn   #    mail: mail   #    telephoneNumber: telephoneNumber   #    position: position   #    company: company   #    photo: raw:jpegPhoto   #list:     # The list of groups by which the user's groups are matched.     # The check is performed one by one, and the first match     # interrupts further processing.     #- group: "CN=Domain Users,OU=SystemUsers,OU=Corp,DC=corp,DC=domain,DC=com"     #  template: default     #  fields:     #    cn: cn     #    mail: mail     #    telephoneNumber: telephoneNumber     #    position: position     #    company: company     #    photo: raw:jpegPhoto 

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

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

  • LDAP — указывается группа из ldap, в которой должен состоять пользователь, LDAP-поля пользователя из которых необходимо брать свойства для заполнения шаблона и соответствующий шаблон. Если логин не определен в локальном списке, будет выполнен LDAP запрос на поиск пользователя и его свойств.

Рассмотрим более подробно оба вида списков:

local

local:   default:     template: users     fields:       cn: user       mail: mail@example.com       telephoneNumber:       position: no_body       company: "\"Example\""   list:

local.default — этот раздел используется как значения по умолчанию для всех участников списка, если на самом участнике списка, не переопределено иное.
local.default.template — задаёт имя шаблона, для формирования ответа
local.default.fields — карта соответствия Go-Template переменных и значений, которые должны быть подставлены вместо них. В примере выше видно, что если в шаблоне будут переменные

{{ .cn }}, {{ .mail }}, {{ .position }}

то они будут заменены указанными значениями:

user, mail@example.com, no_body

local.list — карта со списком логинов, на которые будут отдаваться настройки клиента. Здесь можно переопределить значения по умолчанию, заданные в разделе local.default с тем же синтаксисом, например:

local:   default:     template: default_users     fields:       cn: user       mail: mail@example.com       telephoneNumber:       position: no_body       company: "\"Example\""   list:     user1:     user2:       template: managers     user3:        template: admins        fields:          cn: Вася Пупкин          position: Сетевой администратор
  • local.list.user1: шаблон-default_users, поля заполнены по умолчанию

  • local.list.user2: шаблон-managers, поля всё те же

  • local.list.user3: шаблон-admins, поля — индивидуальные для данного пользователя

LDAP

Если логин пользователя не найден в локальном списке, выполняется поиск в LDAP.
Настройка LDAP списка расширяет локальные списки:

ldap:   uid: sAMAccountName   usersSearchBase: OU=Users,DC=corp,DC=example,DC=com   groupsSearchBase: OU=Groups,DC=corp,DC=example,DC=com   filter: (!(userAccountControl:1.2.840.113556.1.4.803:=2))(objectCategory=person)(objectClass=user)   subgroups: true   allowWithoutGroups: true   default:   list:
  • ldap.uid — уникальный идентификатор для поиска пользователя в LDAP.

  • ldap.usersSearchBase — начальный каталог, от которого выполнять поиск пользователей в LDAP

  • ldap.groupSearchBase — начальный каталог, от которого выполнять поиск групп в LDAP

  • ldap.filter — дополнительный фильтр при поиске пользователей. Базовый поиск выполняется по фильтру ($UID=$username) или (sAMAccountName=pupkin.v). При указании данного поля, он расширяется через аргумент И — (&($UID=$username)$AddFilter) и получается, например:

(&(sAMAccountName=pupkin.v)(!(userAccountControl:1.2.840.113556.1.4.803:=2))(objectCategory=person)(objectClass=user))
  • ldap.subgroups: [true|false] — выполнять ли поиск в дочерних LDAP группах или нет. Например у вас пользователи находятся в общей группе Domain Peoples, а tacs_default — группа для выбора шаблона. Можно было бы tacs_default скриптом навесить на каждого пользователя:

# Условная структура LDAP  user1.memberOf: - Domain Peoples - tacs_default user1.memberOf: - Domain Peoples - tacs_default ...
  • а можно повесить на Domain Peoples, чтобы через наследование её влияние распространялось ниже:

# Условная структура LDAP  # Было: user1.memberOf: - Domain Peoples user2.memberOf: - Domain Peoples # Стало: Domain Peoples.memberOf: - tacs_default

При true, к фильтру добавляется опция :1.2.840.113556.1.4.1941: которая позволяет просмотреть все дочерние подгруппы и найти искомого пользователя, если тот присутствует в дереве дочерних групп.

ldap.allowWithoutGroups: [true|false] — разрешить ли пользователей без группы. Если пользователь не найден в группах LDAP, следует ли применить к нему значения LDAP по умолчанию или выдать 404 ошибку.

ldap.default — так же как и в локальном списке, если не переназначено на группе, применяются данные значения.
ldap.default.template — шаблон по умолчанию для LDAP групп
ldap.default.fields — карта полей вида Go-Template-переменная: ldap-поле-пользователя. Если в локальном списке значение было просто значением, то в LDAP-списке, значения берутся из свойств объекта пользователя. Например:

  default:     template: ldap-default     fields:       cn: cn       position: description
  • Имя шаблона по умолчанию — ldap-default

  • Переменная шаблона {{ .cn }} — соответствует полю cn из LDAP

  • Переменная шаблона {{ .position }} — соответствует полю desccription из LDAP

ldap.default.fields.*: raw:* — если перед именем LDAP-поля указать префикс raw: тогда значение поля будет выгружено в сыром виде и закодировано в base64. Это используется для выгрузки фотографий из поля LDAP jpegPhoto:

  default:     ...     fields:       photo: "raw:jpegPhoto"

Получив фото из LDAP, можно составить html-подпись для пользователя:

<img src="data:image/jpeg;base64,{{ .photo }}"/>

ldap.list — список групп с привязкой к ним шаблонов и необязательной карты свойств

  list:     - group: "CN=tacs-with-photo,OU=tacs,OU=Groups,DC=corp,DC=example,DC=com"       template: with-ldap-photo       fields:         cn: cn         mail: mail         position: position         company: company         photo: raw:jpegPhoto         address: physicalDeliveryOfficeName         telephoneNumber: telephoneNumber

ldap.list.[*].group — полный DN-путь к группе, например: "CN=tacs-with-photo,OU=tacs,OU=Groups,DC=corp,DC=example,DC=com"
ldap.list.[*].template — необязательное переопределение используемого шаблона для указанной группы
ldap.list.[*].fields — карта необязательных переопределений полей, аналогично разделу ldap.default.fields.

Шаблоны

Каталог для поиска шаблонов указывается в scheme.yaml:
templateDir — TACS проходит по всем подкаталогам, кроме символических ссылок, анализируя и загружая в память *.tmpl файлы, которые являются go-template.

  • Объявление шаблона выполняется блоком

{{define "template_name"}}...{{end}} 
  • Имя шаблона указывается в scheme.yaml, в свойстве template и используется при генерации страницы конфигурации:

local:   default:     template: template_name ...   list:     username:       template: template_name ... ldap:   default:     template: template_name ...   list:     - group: ...       template: template_name 
  • Шаблоны могут вызывать другие шаблоны:

{{define "temp1"}} ... {{end}} {{define "temp2"}} ... {{end}} {{define "temp3"}} {{template "temp1"}} {{template "temp2"}} {{end}}
  • Имя шаблона должно быть уникальным по сравнению с остальными, иначе шаблоны могут переписать друг-друга.

  • В шаблоне можно использовать переменные для подстановки значений из scheme.yaml/LDAP:

# Все переменные хранятся в "точке": {{define "default"}} {{.var_name}} {{end}}
  • Переменные объявляются в scheme.yaml, в свойстве fields:

local:   default:     template_key: value ... list:   username:     template_key: value ... ldap: default:   fields:     template_key: ldap_field_with_value     template_key: raw:ldap_field_with_binary_value ... list:   - group: ...     fields:       template_key: ldap_field_with_value

Примеры

Примерная конфигурация приведена в репозитории.

Источники


ссылка на оригинал статьи https://habr.com/ru/articles/812879/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *