To solve the problem, you can integrate Linux clients directly into an Active Directory domain and manage them from a domain controller. The second option is integration based on data synchronization or domain trust (AD trust). In this case, user accounts with previously defined attributes are replicated to another directory service and made available to Linux clients.
The disadvantages of AD trust
Although considerable developer resources are spent on AD trust, this solution has significant disadvantages and is only really suitable for companies whose infrastructure was originally based on Active Directory. We had been working with FreeIPA, so the implementation would require a lot of work: in fact our whole system for managing our user accounts would have to be remade from scratch.
The second disadvantage is the need for constant support for servers with AD and FreeIPA because if the connection between them fails, the server with FreeIPA becomes useless and the work of the whole company would then be disrupted. Authorization is a sensitive area which must be as fault-tolerant as possible. AD trust in this case is an additional risk of failure.
The pros and cons of Windows sync
The option with the Windows sync mechanism from FreeIPA assumes a complete synchronization of all credentials via LDAP protocol. At the same time, FreeIPA and Active Directory remain standalone solutions, and if any server is damaged, only the services connected to it will fail, not the whole infrastructure. The user works through a single window with FreeIPA. That means, for example, that it is possible to change the password of an account simultaneously for both Windows-based and Linux-based infrastructure.
We settled on this option, but we decided to refine it because Windows sync is not designed for group management.
Unfortunately, the Windows sync mechanism in FreeIPA is not prioritized and is not well developed, especially when it comes to group management. We needed groups in order to organize our staff structure by department, or in any other order. This approach allows rules and policies to be written for each group, rather than per individual user, which greatly simplifies account and data access management, as well as software delivery. Another advantage of groups is that they can be nested.
For example, our DEVOPS department structure consists of the following subgroups, each of which has defined sets of rights and policies, as well as the services they need to work:
We add the created employee account to a certain subgroup, e.g. junior engineers. As a result, the new user ends up in a whole set of groups: Rocket.Chat users, Jira users, etc. He or she automatically gets access to all the services prescribed for the subgroup. It would not be hard to build this scheme by creating fully analogous groups in FreeIPA and AD, but you would have to administer them manually. The disadvantages are obvious: a lot of time will be wasted on routine tasks, and (and this is the most important thing) there will be potential threats to infrastructure security. For example, a simple change of access rights can lead to disruptions in the production process as a result of manual configuration errors.
The solution
We needed automatic synchronization, which we had to implement ourselves based on module manage-freeipa. As a result, we wrote a script which gathers information about the groups in FreeIPA, including nested subgroups, and transfers it to AD. Here is a step-by-step description of the algorithm:
1. Import the manage-freeipa module.
2. After importing the module, we connected to the server using the previously added credentials:
$IPAURL = "freeipa_URL"
$IPAUSER = "USER"
$IPAPASS = "PASSWORD"
$ADOUUSERS = "*OU=users,OU=ipa,DC=win,DC=EXAMPLE,DC=COM"
$ADOUGROUPS = "*OU=groups,OU=ipa,DC=win,DC=EXAMPLE,DC=COM"
3. Then in AD we request a list of OU groups:
$OU = Get-ADOrganizationalUnit -SearchBase "OU=groups,OU=ipa,DC=win,DC=EXAMPLE,DC=COM" -Filter *
4. Filter the trust admins group, which I not included in the data synchronization between FreeIPA and AD:
$groupsAD = $OU | ForEach-Object {Get-ADGroup -SearchBase $_.DistinguishedName -Filter *} | Where {$_.name -notlike "trust admins"}
5. We get a list of all groups in FreeIPA, except for trust admins and groups that are used exclusively in the FreeIPA managed infrastructure. The selection is based on the cn field only:
$groupsIPA = Find-IPAGroup | Where {$_.dn -notlike "cn=invapi*" -and $_.dn -notlike "cn=jira*" -and $_.dn -notlike "cn=trust admins*"} | Select-Object -Property cn
6. There are a number of groups that are only in AD, we look for them and remove them from the general list:
$groupsOnlyAD = $groupsAD.name | ?{$groupsIPA.cn -notcontains $_}
$listGroupsAD = $groupsAD.Name | ?{$groupsOnlyAD -notcontains $_}
7. Take the list from FreeIPA and remove all the groups that are in AD. This leaves only the list of new groups that are in FreeIPA but not in AD:
$listAddGroups = $groupsIPA.cn | ?{$groupsAD.Name -notcontains $_}
8. Next comes the command to add these groups to AD:
$listAddGroups | ForEach-Object {New-ADGroup $_ -path 'OU=groups,OU=ipa,DC=win,DC=EXAMPLE,DC=COM' -GroupScope Global -PassThru -Verbose}
9. Then there is a cycle of operations for each AD group that is in the $ListGroupsAD list.
9.1. We get a user list – group members in AD:
$listGroupsAD |
ForEach { $Groupname = $_
$listGroupMembersUsersAD = Get-ADGroupMember $Groupname | Where {$_.distinguishedName -like $ADOUUSERS} | select SamAccountName
9.2. We get a list of subgroups of group members in AD:
$listGroupMembersGroupsAD = Get-ADGroupMember $Groupname | Where {$_.distinguishedName -like $ADOUGROUPS} | select SamAccountName
9.3. We get the list of the users in this group in FreeIPA:
$listGroupMemberUserIPA = Invoke-FreeIPAAPIgroup_show -group_name $Groupname | Select-Object -Property member_user
9.4. We get the list of subgroups in this group in FreeIPA:
$listGroupMemberGroupIPA = Invoke-FreeIPAAPIgroup_show -group_name $Groupname | Select-Object -Property member_group
9.5 The lists of subgroups and users in FreeIPA and AD are compared (the list of FreeIPA users is subtracted from the list of AD users and vice versa). Lists of users and subgroups to be added or removed are created:
$delListMembers = $listGroupMembersUsersAD.SamAccountName | ?{$listGroupMemberUserIPA.member_user -notcontains $_}
$delListGroups = $listGroupMembersGroupsAD.SamAccountName | ?{$listGroupMemberGroupIPA.member_group -notcontains $_}
$addListMembers = $listGroupMemberUserIPA.member_user| ?{$listGroupMembersUsersAD.SamAccountName -notcontains $_}
$addListGroups = $listGroupMemberGroupIPA.member_group | ?{$listGroupMembersGroupsAD.SamAccountName -notcontains $_}
9.6 The next step is to check if there is someone on the list of users and subgroups to remove: if there is no one, the next step is skipped:
if (! ([string]::isnullorempty($delListMembers)))
{
$delListMembers | ForEach-Object {Remove-ADGroupMember $Groupname $_ -Confirm:$false}
}
if (! ([string]::isnullorempty($delListGroups)))
{
$delListGroups | ForEach-Object {Remove-ADGroupMember $Groupname $_ -Confirm:$false}
}
9.7 The same check is done for adding users and subgroups:
if (! ([string]::isnullorempty($addListGroups))){
$addListGroups | ForEach-Object { Add-AdGroupMember -Identity $Groupname -members $_ }
}
if (! ([string]::isnullorempty($addListMembers)))
{
9.8 Before adding a user to a group in AD, you must verify that the user is in AD. The entire cycle of step 9 is performed for each group in the $listGroupsAD list obtained in step 6. When the session is complete, it disconnects from the FreeIPA server:
$addListMembers |
ForEach-Object { try
{
Get-ADUser -Identity $_
Add-AdGroupMember -Identity $Groupname -members $_
}
catch {}
}
}
}
Disconnect-IPA
The script is run by the job scheduler every 15 minutes.
Conclusions
The script has greatly simplified user account management and new employee onboarding, as well as improved the overall security of HOSTKEY's internal IT infrastructure. Next, we plan to configure FreeIPA integration with Active Directory directly through our API without module manage-freeipa. This will have to be done for security reasons anyway because the last time the module was updated was two years ago and there is a risk that support will be discontinued by the developers.