Миграция пользователей Atlassian Jira на LDAP
Date December 26th, 2013 Author Vitaly Agapov
Миграция пользователей, хранящихся в локальной директории в Jira на LDAP-сервер (AD либо OpenLDAP) не вызовет никаких трудностей, если логины этих пользователей совпадают в Jira и на LDAP-сервере. В этом случае надо просто настроить новую User Directory в Jira и поменять ей приоритет на самый высокий, чтобы аутентификация сперва проходила через неё. Если же есть пользователи, у которых в Jira один логин, а в том же Active Directory – другой, то тут вылезет ряд проблем. В основном эти проблемы будут связаны с тем, что LDAP-аккаунт Джирой будет восприниматься как совершенно отдельный пользователь, а все задачи, комментарии и история останутся привязанными к старому пользователю. Посмотрим, как решить эту проблему.
Подготовка маппинга логинов пользователей
Для начала нужно получить соответсвие всех старых логинов в Jira всем новых логинам в LDAP. Если пользователей два, пять или даже десять, то это можно сделать и вручную. Если же их гораздо больше, то поможет набросанный мною Perl-скрипт create-user-map.pl, который выбирает из БД Джиры всех пользователей и по их электронной почте производит поиск по LDAP (в моём случае – в AD) соответствующего пользователя. Потом всё это он выплёвывает в виде хэша, который пригодится непосредственно для миграции, хэша, который пригодится для оповещений по почте, списка пользователей, у которых логин не меняется и списка пользователей Jira, для которых в AD не найдено соответствие.
01.
#!/usr/bin/perl
02.
03.
use
Net::LDAP;
04.
use
DBI;
05.
use
Data::Dumper;
06.
07.
glob
$dbName
=
"jiraDB"
;
08.
glob
$dbHost
=
"127.0.0.1"
;
09.
glob
$dbUser
=
"jiraUser"
;
10.
glob
$dbPass
=
"jiraPass"
;
11.
12.
glob
$ADUrl
=
"x.x.x.x"
;
13.
glob
$ADBaseDN
=
"OU=Users,DC=example,DC=com"
;
14.
glob
$ADDNattr
=
"sAMAccountName"
;
15.
glob
$ADBindDN
=
"CN=address book,OU=Users,DC=example,DC=com"
;
16.
glob
$ADBindPWD
=
"password"
;
17.
18.
my
%map
;
19.
my
%BCmails
;
20.
21.
glob
$dbh
= DBI->
connect
(
"DBI:mysql:database=$dbName;host=$dbHost"
,
$dbUser
,
$dbPass
,{
'RaiseError'
=> 1});
22.
$dbh
->
do
(
"SET NAMES utf8"
);
23.
$dbh
->
do
(
"SET CHARACTER SET utf8"
);
24.
$dbh
->
do
(
"SET character_set_connection=utf8"
);
25.
26.
my
$sql
=
"SELECT user_name, CONCAT(first_name, ' ', last_name) AS name, email_address FROM cwd_user WHERE directory_id=1 AND active=1"
;
27.
my
$sth
=
$dbh
->prepare(
$sql
);
28.
$sth
->execute;
29.
while
(
my
$ref
=
$sth
->fetchrow_hashref()) {
30.
my
$mail
=
$ref
->{email_address};
31.
$map
{
$mail
}->{jiraLogin} =
$ref
->{user_name};
32.
$map
{
$mail
}->{jiraName} =
$ref
->{name};
33.
my
$entry
=searchADbyMail(
$mail
);
34.
if
(
$entry
) {
35.
$map
{
$mail
}->{ADDN}=
$entry
->dn;
36.
$map
{
$mail
}->{ADLogin}=
$entry
->get_value(
$ADDNattr
);
37.
$map
{
$mail
}->{ADName}=
$entry
->get_value(
'name'
);
38.
#$entry->dump;
39.
}
40.
}
41.
$sth
->finish;
42.
$dbh
->disconnect();
43.
44.
print
"Mapped accounts with different logins\n"
;
45.
print
"glob %map = (\n"
;
46.
foreach
my
$mail
(
sort
keys
%map
) {
47.
if
(
$map
{
$mail
}->{ADLogin} &&
$map
{
$mail
}->{jiraLogin} ne
$map
{
$mail
}->{ADLogin} ) {
48.
print
" '"
.
$map
{
$mail
}->{jiraLogin}.
"' => '"
.
$map
{
$mail
}->{ADLogin}.
"',\n"
;
49.
$BCmails
{
$mail
} =
$map
{
$mail
}->{ADLogin};
50.
}
51.
}
52.
print
");\n"
;
53.
54.
print
"\nMapped accounts with same logins\n"
;
55.
foreach
my
$mail
(
sort
keys
%map
) {
56.
if
(
$map
{
$mail
}->{jiraLogin} eq
$map
{
$mail
}->{ADLogin} ) {
57.
print
$map
{
$mail
}->{jiraLogin}.
' '
.
$mail
.
"\n"
;
58.
$BCmails
{
$mail
} =
$map
{
$mail
}->{ADLogin};
59.
}
60.
}
61.
62.
print
"\nNot mapped accounts\n"
;
63.
foreach
my
$mail
(
sort
keys
%map
) {
64.
unless
(
$map
{
$mail
}->{ADLogin} ) {
65.
print
$map
{
$mail
}->{jiraName}.
' '
.
$mail
.
"\n"
;
66.
}
67.
}
68.
69.
print
"\nMails for broadcast\n"
;
70.
print
"glob %mails = (\n"
;
71.
foreach
my
$mail
(
sort
keys
%BCmails
) {
72.
print
" '"
.
$mail
.
"' => '"
.
$BCmails
{
$mail
}.
"',\n"
;
73.
}
74.
print
");\n"
;
75.
76.
exit
(0);
77.
78.
sub
searchADbyMail {
79.
my
$mail
=
shift
;
80.
my
$ldap
= Net::LDAP->new(
$ADUrl
);
81.
unless
(
$ldap
) {
print
"$@. Could not connect to AD"
;
return
; }
82.
my
$mesg
=
$ldap
->
bind
(
$ADBindDN
, password =>
$ADBindPWD
, version => 3 );
83.
$mesg
->{resultCode} &&
return
undef
;
84.
$mesg
=
$ldap
->search(
85.
base =>
$ADBaseDN
,
86.
attrs => [
$ADDNattr
,
"name"
],
87.
filter =>
"mail="
.
$mail
,
88.
);
89.
$mesg
->code &&
return
undef
;
90.
my
$entry
=
$mesg
->shift_entry;
91.
$ldap
->unbind;
92.
return
$entry
;
93.
}
Само собой в скрипте надо вставить свои параметры БД и LDAP.
Рассылка оповещений
Пользователям Jira, которых коснётся миграция, было бы неплохо послать письма с информацией о смене способа аутентификации. И ещё лучше при этом сообщить, какой же новый логин будет использоваться дальше.
Для этого надо хэш %mails из результата работы предыдущего скрипта вида:
1.
glob
%mails
= (
2.
'user1@example.com'
=>
'user1'
,
3.
# .......
4.
'user2@example.com'
=>
'user2'
,
5.
);
Поместить в файл config.pl и запустить такой вот Perl-скрипт send-broadcast.pl:
01.
#!/usr/bin/perl
02.
use
MIME::Lite;
03.
04.
require
"config.pl"
;
05.
06.
$text
= qq ~
07.
Добрый день,
08.
<br/><br/>
09.
С утра xxxx.xx.xx изменится принцип авторизации в Jira. Теперь для входа необходимо будет использовать логин и пароль из доменной учётной записи.
10.
<br/><br/>
11.
Ваш доменный логин -
%USERLOGIN
%
12.
<br/><br/>
13.
Все задачи, статьи, комментарии, дашборды и прочие элементы останутся доступны и будут привязаны к новым учётным записям. Старые аккаунты будут удалены.
14.
<br/>
15.
---<br/>
16.
С уважением,<br/>
17.
Подпись
18.
~;
19.
20.
foreach
my
$mail
(
sort
keys
%mails
) {
21.
print
$mail
.
"\n"
;
22.
my
$data
=
$text
;
23.
$data
=~ s/
%USERLOGIN
%/
$mails
{
$mail
}/;
24.
my
$msg
= MIME::Lite->new(
25.
From =>
'noreply@example.com'
,
26.
To =>
$mail
,
27.
Subject =>
'Новая схема авторизации в Jira'
,
28.
Data =>
$data
,
29.
);
30.
#$msg->attr('content-type.charset' => 'UTF-8');
31.
$msg
->attr(
"Content-type"
=>
"text/html; charset=UTF-8"
);
32.
$msg
->
send
(
'smtp'
,
'x.x.x.x'
);
33.
}
Миграция
Допустим, новая user directory в Jira уже настроена и работает. Тогда останавливаем Джиру, делаем бэкап её базы данных и запускаем следующий Perl-скрипт. Только перед началом в config.pl добавляем ещё и хэш %map, полученный от запуска первого скрипта или просто заполненный вручную. В общем выглядеть config.pl должен примерно так:
01.
glob
$dbName
=
"jiraDB"
;
02.
glob
$dbHost
=
"127.0.0.1"
;
03.
glob
$dbUser
=
"jiraUser"
;
04.
glob
$dbPass
=
"jiraPass"
;
05.
06.
glob
%map
= (
07.
'jirauser1'
=>
'ldapuser1'
,
08.
# .......
09.
'jirauser2'
=>
'ldapuser2'
,
10.
);
А вот самый главный скрипт migrate-jira-to-ad.pl, непосредственно производящий миграцию. Он проглядывает все таблицы (кроме тех, которые начинаются на cwd_, то есть содержат учётки и группы) и во всех полях varchar или text меняет старые логины на новые – в Jira привязка элементов к пользователю сделана не по id, а по юзернейму.
01.
#!/usr/bin/perl
02.
03.
use
DBI;
04.
use
Data::Dumper;
05.
06.
require
"config.pl"
;
07.
08.
glob
$dbh
= DBI->
connect
(
"DBI:mysql:database=$dbName;host=$dbHost"
,
$dbUser
,
$dbPass
,{
'RaiseError'
=> 1});
09.
$dbh
->
do
(
"SET NAMES utf8"
);
10.
$dbh
->
do
(
"SET CHARACTER SET utf8"
);
11.
$dbh
->
do
(
"SET character_set_connection=utf8"
);
12.
13.
my
$tables
=
$dbh
->selectall_arrayref(
"SHOW TABLES"
);
14.
15.
foreach
( @{
$tables
} ) {
16.
my
$table
=
$_
->[0];
17.
next
if
(
$table
=~ /^cwd_/);
18.
19.
my
$sth
=
$dbh
->prepare(
"DESCRIBE $table"
);
20.
$sth
->execute;
21.
while
(
my
$ref
=
$sth
->fetchrow_hashref()) {
22.
my
$col
=
$ref
->{Field};
23.
my
$type
=
$ref
->{Type};
24.
next
unless
(
$type
=~ /^varchar/ ||
$type
=~ /text/);
25.
foreach
my
$username
(
keys
%map
) {
26.
my
$sql
=
"SELECT COUNT(*) FROM $table WHERE `$col` = '$username'"
;
27.
my
$sth2
=
$dbh
->prepare(
$sql
);
28.
$sth2
->execute;
29.
my
$count
=
$sth2
->fetchrow();
30.
if
(
$count
) {
31.
print
"Table "
.
$table
.
"\n"
;
32.
print
" $col - $type :$count\n"
;
33.
$sql
=
"UPDATE $table SET `$col` = '$map{$username}' WHERE `$col` = '$username'"
;
34.
print
$sql
.
"\n\n"
;
35.
eval
{
$dbh
->
do
(
$sql
); };
warn
$@
if
$@;
36.
}
37.
$sth2
->finish;
38.
}
39.
}
40.
$sth
->finish;
41.
}
42.
$dbh
->disconnect();
Запускаем Jira и производим реиндексацию из админки. Останется только назначить новым пользователям старые группы, а старых пользователей деактивировать или удалить. Если есть пользователи, у которых логины в разных User Directories совпадают, то, как уже говорилось, надо поставить повысить LDAP’у приоритет. Только сначала нужно запомнить, в каких группах состояли старые аккаунты, так как при назначении групп новым аккаунтам будет не с чем сверяться.
Другие продукты Atlassian
Для других продуктов, независимо от того, используют они для аутентификации Джиру или будут ходить напрямую в LDAP-сервер, тоже придётся провернуть кое какие действия.
Для миграции статей и комментариев в Atlassian Confluence можно воспользоваться тем же самым скриптом migrate-jira-to-ad.pl. Нужно будет только в config.pl поменять параметры подключения к базе на базу Confluence. Работает, проверено.
В Atlassian Stash всё чуть сложнее. Там привязка прав на репозитории завязана на id аккаунтов, а не юзернеймы. Поэтому мой скрипт не подойдёт. Можно написать другой скрипт, а можно поменять все права вручную. Также надо у старых аккаунтов удалить SSH-ключи, иначе эти же ключи нельзя будет привязать к новым аккаунтам. Ещё можно в базе передать личные репозитории новым аккаунтам. Но это простая задача, на ней можно не останавливаться.
В Bamboo всё наоборот проще. Там надо обеспечить только нужные права новым аккаунтам. Если они рулятся группами в Jira, то можно сказать, что и делать ничего не надо – всё уже сделано.
Tags: Atlassian, LDAP, Perl
Category:
Perl |
2 Comments »
3 April 2015 - 15:02
А ты пробовал сам запускать его?
3 April 2015 - 15:13
А как я по-твоему у нас миграцию проводил?