Миграция пользователей Atlassian Jira на LDAP

Date December 26th, 2013 Author Vitaly Agapov

Это будет самая замечательная миграция! Я покажу вам свои любимые забегаловки… А знаете, я поменяю цвет, когда подсохнет грибок на моей шкурке!..

м/ф «Ледниковый период»

migrationМиграция пользователей, хранящихся в локальной директории в 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: , ,
Category: Perl | 2 Comments »

Comments

2 комментариев на “Миграция пользователей Atlassian Jira на LDAP”

  1. Михаил

    А ты пробовал сам запускать его?

  2. Vitaly Agapov

    А как я по-твоему у нас миграцию проводил?
     

Leave a comment

 Comment Form