Skip to content

Commit

Permalink
Merge pull request rancher#14 from crobby/migrationreview3
Browse files Browse the repository at this point in the history
Migration review updates 3
  • Loading branch information
nflynt authored Aug 9, 2023
2 parents c0cdc07 + 86330c6 commit 2d59ac6
Show file tree
Hide file tree
Showing 4 changed files with 136 additions and 9 deletions.
58 changes: 58 additions & 0 deletions cleanup/ad-guid-README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# Active Directory GUID -> DN reverse migration utility

**It is recommended to take a snapshot of Rancher before performing this in the event that a restore is required.**

## Purpose

In order to reverse the effects of migrating Active Directory principalIDs to be based on GUID rather than DN this
utility is required. It can be run manually via Rancher Agent, or it will automatically run inside Rancher at startup
time if no previous run is detected.
This utility will:
* Remove any users that were duplicated during the original migration toward GUID-based principalIDs in Rancher 2.7.5
* Update objects that referenced a GUID-based principalID to reference the correct distinguished name based principalID


## Detailed description

This utility will go through all Rancher users and perform an Active Directory lookup using the configured service account to
get the user's distinguished name. Next, it will perform lookups inside Rancher for all the user's Tokens,
ClusterRoleTemplateBindings, and ProjectRoleTemplateBindings. If any of those objects, including the user object
itself are referencing a principalID based on the GUID of that user, those objects will be updated to reference
the distinguished name-based principalID (unless the utility is run with -dry-run, in that case the only results
are log messages indicating the changes that would be made by a run without that flag).

This utility will also detect and correct the case where a single ActiveDirectory GUID is mapped to multiple Rancher
users. That condition was likely caused by a race in the original migration to use GUIDs and resulted in a second
Rancher user being created. This caused Rancher logins to fail for the duplicated user. The utility remedies
that situation by mapping any tokens and bindings to the original user before removing the newer user, which was
created in error.


## Requirements

A Rancher environment that has Active Directory set up as the authentication provider. For any environment where
Active Directory is not the authentication provider, this utility will take no action and will exit immediately.


## Usage via Rancher Agent

```bash
./ad-guid-unmigration.sh <AGENT IMAGE> [-dry-run] [-delete-missing]
```
* The Agent image can be found at: docker.io/rancher/rancher-agent:/v2.7.6
* The -dry-run flag will run the migration utility, but no changes to Rancher data will take place. The potential changes will be indicated in the log file.
* The -delete-missing flag will delete Rancher users that can not be found by looking them up in Active Directory. If -dry-run is set, that will prevent users from being deleted regardless of this flag.


## Additional notes
* The utility will create a configmap named ad-guid-migration in the cattle-system namespace. This configmap contains
a data entry with a key named "ad-guid-migration-status". If the utility is currently active, that status will be
set to "Running". After the utility has completed, the status will be set to "Finished". If a run is interrupted
prior to completion, that configmap will retain the status of "Running" and subsequent attempts to run the script will
immediately exit. In order to allow it to run again, you can either edit the configmap to remove that key or you can
delete the configmap entirely.

* When migrating ClusterRoleTemplateBindings and ProjectRoleTemplateBindings, it is necessary to perform the action
as a delete/create rather than an update. **This may cause issues if you use tooling that relies on the names of the objects**.
When a ClusterRoleTemplateBinding or a ProjectRoleTemplateBinding is migrated to a new name, the newly created object
will contain a label, "ad-guid-previous-name", that will have a value of the name of the object that was deleted.
39 changes: 39 additions & 0 deletions cleanup/ad-guid-unmigration.sh
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,26 @@
# set -x
set -e

# Text to display in the banner
banner_text="This utility will go through all Rancher users and perform an Active Directory lookup using
the configured service account to get the user's distinguished name. Next, it will perform lookups inside Rancher
for all the user's Tokens, ClusterRoleTemplateBindings, and ProjectRoleTemplateBindings. If any of those objects,
including the user object itself are referencing a principalID based on the GUID of that user, those objects will be
updated to reference the distinguished name-based principalID (unless the utility is run with -dry-run, in that case
the only results are log messages indicating the changes that would be made by a run without that flag).
This utility will also detect and correct the case where a single ActiveDirectory GUID is mapped to multiple Rancher
users. That condition was likely caused by a race in the original migration to use GUIDs and resulted in a second
Rancher user being created. This caused Rancher logins to fail for the duplicated user. The utility remedies
that situation by mapping any tokens and bindings to the original user before removing the newer user, which was
created in error.
It is also important to note that migration of ClusterRoleTemplateBindings and ProjectRoleTemplateBindings require
a delete/create operation rather than an update. This will result in new object names for the migrated bindings.
A label with the former object name will be included in the migrated bindings.
It is recommended that you perform a Rancher backup prior to running this utility."

CLEAR='\033[0m'
RED='\033[0;31m'

Expand All @@ -26,6 +46,25 @@ show_usage() {
echo -e "\t-delete-missing Permanently remove user objects whose GUID cannot be found in Active Directory"
}

# Function to display text in a banner format
display_banner() {
local text="$1"
local border_char="="
local text_width=$(($(tput cols)))
local border=$(printf "%${text_width}s" | tr " " "$border_char")

echo "$border"
printf "%-${text_width}s \n" "$text"
echo "$border"
}

display_banner "${banner_text}"
read -p "Do you want to continue? (y/n): " choice
if [[ ! $choice =~ ^[Yy]$ ]]; then
echo "Exiting..."
exit 0
fi

if [ $# -lt 1 ]
then
show_usage "AGENT_IMAGE is a required argument"
Expand Down
6 changes: 6 additions & 0 deletions cleanup/ad-guid-unmigration.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: cattle-cleanup-binding
namespace: default
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
Expand Down Expand Up @@ -59,10 +60,15 @@ apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: cattle-cleanup-role
namespace: default
rules:
- apiGroups:
- '*'
resources:
- '*'
verbs:
- '*'
- nonResourceURLs:
- '*'
verbs:
- '*'
42 changes: 33 additions & 9 deletions pkg/agent/clean/active_directory.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,17 +14,17 @@ import (
"strings"
"time"

"github.com/rancher/rancher/pkg/auth/providers/activedirectory"

corev1 "k8s.io/api/core/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"

ldapv3 "github.com/go-ldap/ldap/v3"
"github.com/mitchellh/mapstructure"
"github.com/pkg/errors"
v3 "github.com/rancher/rancher/pkg/apis/management.cattle.io/v3"
"github.com/rancher/rancher/pkg/auth/providers/activedirectory"
"github.com/rancher/rancher/pkg/auth/providers/common"
"github.com/rancher/rancher/pkg/auth/providers/common/ldap"
"github.com/rancher/rancher/pkg/auth/tokens"
v3client "github.com/rancher/rancher/pkg/client/generated/management/v3"
"github.com/rancher/rancher/pkg/types/config"
"github.com/sirupsen/logrus"
Expand Down Expand Up @@ -661,7 +661,10 @@ func migrateTokens(workunit *migrateUserWorkUnit, sc *config.ScaledContext, dryR
dnPrincipalID := activeDirectoryPrefix + workunit.distinguishedName
for _, userToken := range workunit.guidTokens {
if dryRun {
logrus.Infof("[%v] DRY RUN: would migrate token '%v' from GUID principal '%v' to DN principal '%v'", migrateTokensOperation, userToken.Name, userToken.UserPrincipal.Name, dnPrincipalID)
logrus.Infof("[%v] DRY RUN: would migrate token '%v' from GUID principal '%v' to DN principal '%v'. "+
"Additionally, it would add an annotation, %v, indicating the former principalID of this token "+
"and a label, %v, to indicate that this token has been migrated",
migrateTokensOperation, userToken.Name, userToken.UserPrincipal.Name, dnPrincipalID, adGUIDMigrationAnnotation, adGUIDMigrationLabel)
} else {
latestToken, err := tokenInterface.Get(userToken.Name, metav1.GetOptions{})
if err != nil {
Expand All @@ -674,8 +677,10 @@ func migrateTokens(workunit *migrateUserWorkUnit, sc *config.ScaledContext, dryR
if latestToken.Labels == nil {
latestToken.Labels = make(map[string]string)
}
latestToken.Labels[tokens.UserIDLabel] = workunit.originalUser.Name
latestToken.Labels[adGUIDMigrationLabel] = migratedLabelValue
latestToken.UserPrincipal.Name = dnPrincipalID
latestToken.UserID = workunit.originalUser.Name
_, err = tokenInterface.Update(latestToken)
if err != nil {
return fmt.Errorf("[%v] unable to update token: %w", migrateTokensOperation, err)
Expand All @@ -686,7 +691,10 @@ func migrateTokens(workunit *migrateUserWorkUnit, sc *config.ScaledContext, dryR
localPrincipalID := localPrefix + workunit.originalUser.Name
for _, userToken := range workunit.duplicateLocalTokens {
if dryRun {
logrus.Infof("[%v] DRY RUN: would migrate Token '%v' from duplicate local user '%v' to original user '%v'", migrateTokensOperation, userToken.Name, userToken.UserPrincipal.Name, localPrincipalID)
logrus.Infof("[%v] DRY RUN: would migrate Token '%v' from duplicate local user '%v' to original user '%v'"+
"Additionally, it would add an annotation, %v, indicating the former principalID of this token "+
"and a label, %v, to indicate that this token has been migrated",
migrateTokensOperation, userToken.Name, userToken.UserPrincipal.Name, localPrincipalID, adGUIDMigrationAnnotation, adGUIDMigrationLabel)
} else {
latestToken, err := tokenInterface.Get(userToken.Name, metav1.GetOptions{})
if err != nil {
Expand All @@ -699,8 +707,10 @@ func migrateTokens(workunit *migrateUserWorkUnit, sc *config.ScaledContext, dryR
if latestToken.Labels == nil {
latestToken.Labels = make(map[string]string)
}
latestToken.Labels[tokens.UserIDLabel] = workunit.originalUser.Name
latestToken.Labels[adGUIDMigrationLabel] = migratedLabelValue
latestToken.UserPrincipal.Name = localPrincipalID
latestToken.UserID = workunit.originalUser.Name
_, err = tokenInterface.Update(latestToken)
if err != nil {
return fmt.Errorf("[%v] unable to update token: %w", migrateTokensOperation, err)
Expand All @@ -721,7 +731,7 @@ func collectTokens(workunits *[]migrateUserWorkUnit, sc *config.ScaledContext) e
for i, workunit := range *workunits {
guidPrincipal := activeDirectoryPrefix + workunit.guid
for _, token := range tokenList.Items {
if guidPrincipal == token.UserPrincipal.Name {
if guidPrincipal == token.UserPrincipal.Name || workunit.originalUser.Name == token.UserID {
workunit.guidTokens = append(workunit.guidTokens, token)
} else {
for _, duplicateLocalUser := range workunit.duplicateUsers {
Expand Down Expand Up @@ -833,7 +843,10 @@ func migrateCRTBs(workunit *migrateUserWorkUnit, sc *config.ScaledContext, dryRu
dnPrincipalID := activeDirectoryPrefix + workunit.distinguishedName
for _, oldCrtb := range workunit.guidCRTBs {
if dryRun {
logrus.Infof("[%v] DRY RUN: would migrate CRTB '%v' from GUID principal '%v' to DN principal '%v'", migrateCrtbsOperation, oldCrtb.Name, oldCrtb.UserPrincipalName, dnPrincipalID)
logrus.Infof("[%v] DRY RUN: would migrate CRTB '%v' from GUID principal '%v' to DN principal '%v'. "+
"Additionally, an annotation, %v, would be added containing the principal being migrated from and"+
"labels, %v and %v, that will contain the name of the previous CRTB and indicate that this CRTB has been migrated.",
migrateCrtbsOperation, oldCrtb.Name, oldCrtb.UserPrincipalName, dnPrincipalID, adGUIDMigrationAnnotation, migrationPreviousName, adGUIDMigrationLabel)
} else {
newAnnotations := oldCrtb.Annotations
if newAnnotations == nil {
Expand Down Expand Up @@ -874,7 +887,10 @@ func migrateCRTBs(workunit *migrateUserWorkUnit, sc *config.ScaledContext, dryRu
localPrincipalID := localPrefix + workunit.originalUser.Name
for _, oldCrtb := range workunit.duplicateLocalCRTBs {
if dryRun {
logrus.Infof("[%v] DRY RUN: would migrate CRTB '%v' from duplicate local user '%v' to original user '%v'", migrateCrtbsOperation, oldCrtb.Name, oldCrtb.UserPrincipalName, localPrincipalID)
logrus.Infof("[%v] DRY RUN: would migrate CRTB '%v' from duplicate local user '%v' to original user '%v'"+
"Additionally, an annotation, %v, would be added containing the principal being migrated from and"+
"labels, %v and %v, that will contain the name of the previous CRTB and indicate that this CRTB has been migrated.",
migrateCrtbsOperation, oldCrtb.Name, oldCrtb.UserPrincipalName, localPrincipalID, adGUIDMigrationAnnotation, migrationPreviousName, adGUIDMigrationLabel)
} else {
newAnnotations := oldCrtb.Annotations
if newAnnotations == nil {
Expand Down Expand Up @@ -919,7 +935,11 @@ func migratePRTBs(workunit *migrateUserWorkUnit, sc *config.ScaledContext, dryRu
dnPrincipalID := activeDirectoryPrefix + workunit.distinguishedName
for _, oldPrtb := range workunit.guidPRTBs {
if dryRun {
logrus.Infof("[%v] DRY RUN: would migrate PRTB '%v' from GUID principal '%v' to DN principal '%v'", migratePrtbsOperation, oldPrtb.Name, oldPrtb.UserPrincipalName, dnPrincipalID)
logrus.Infof("[%v] DRY RUN: would migrate PRTB '%v' from GUID principal '%v' to DN principal '%v'. "+
"Additionally, an annotation, %v, would be added containing the principal being migrated from and"+
"labels, %v and %v, that will contain the name of the previous PRTB and indicate that this PRTB has been migrated.",
migrateCrtbsOperation, oldPrtb.Name, oldPrtb.UserPrincipalName, dnPrincipalID, adGUIDMigrationAnnotation, migrationPreviousName, adGUIDMigrationLabel)

} else {
newAnnotations := oldPrtb.Annotations
if newAnnotations == nil {
Expand Down Expand Up @@ -960,7 +980,11 @@ func migratePRTBs(workunit *migrateUserWorkUnit, sc *config.ScaledContext, dryRu
localPrincipalID := localPrefix + workunit.originalUser.Name
for _, oldPrtb := range workunit.duplicateLocalPRTBs {
if dryRun {
logrus.Infof("[%v] DRY RUN: would migrate PRTB '%v' from duplicate local user '%v' to original user '%v'", migratePrtbsOperation, oldPrtb.Name, oldPrtb.UserPrincipalName, localPrincipalID)
logrus.Infof("[%v] DRY RUN: would migrate PRTB '%v' from duplicate local user '%v' to original user '%v'"+
"Additionally, an annotation, %v, would be added containing the principal being migrated from and"+
"labels, %v and %v, that will contain the name of the previous PRTB and indicate that this PRTB has been migrated.",
migrateCrtbsOperation, oldPrtb.Name, oldPrtb.UserPrincipalName, localPrincipalID, adGUIDMigrationAnnotation, migrationPreviousName, adGUIDMigrationLabel)

} else {
newAnnotations := oldPrtb.Annotations
if newAnnotations == nil {
Expand Down

0 comments on commit 2d59ac6

Please sign in to comment.