Skip to content

Commit

Permalink
Mirror script status to authconfig
Browse files Browse the repository at this point in the history
  • Loading branch information
crobby committed Aug 15, 2023
1 parent 5bc29d5 commit 8854263
Show file tree
Hide file tree
Showing 3 changed files with 126 additions and 22 deletions.
61 changes: 51 additions & 10 deletions pkg/agent/clean/ad_unmigration/ldap.go
Expand Up @@ -3,32 +3,34 @@ package ad_unmigration
import (
"bytes"
"crypto/x509"
"encoding/json"
"fmt"
"os"
"regexp"
"strings"
"time"

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/common"
"github.com/rancher/rancher/pkg/auth/providers/common/ldap"
v3client "github.com/rancher/rancher/pkg/client/generated/management/v3"
"github.com/rancher/rancher/pkg/types/config"
"github.com/sirupsen/logrus"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/util/wait"
restclient "k8s.io/client-go/rest"
"k8s.io/client-go/tools/clientcmd"

v3 "github.com/rancher/rancher/pkg/apis/management.cattle.io/v3"
"github.com/rancher/rancher/pkg/auth/providers/common"
"github.com/rancher/rancher/pkg/auth/providers/common/ldap"
v3client "github.com/rancher/rancher/pkg/client/generated/management/v3"
"github.com/rancher/rancher/pkg/types/config"
)

// Rancher 2.7.5 serialized binary GUIDs from LDAP using this pattern, so this
// is what we should match. Notably this differs from Active Directory GUID
// strings, which have dashes and braces as delimiters.
var validRancherGuidPattern = regexp.MustCompile("^[0-9a-f]+$")
var validRancherGUIDPattern = regexp.MustCompile("^[0-9a-f]+$")

type LdapErrorNotFound struct{}

Expand Down Expand Up @@ -169,7 +171,7 @@ func adConfiguration(sc *config.ScaledContext) (*v3.ActiveDirectoryConfig, error
storedADConfigMap := u.UnstructuredContent()

storedADConfig := &v3.ActiveDirectoryConfig{}
err = mapstructure.Decode(storedADConfigMap, storedADConfig)
err = common.Decode(storedADConfigMap, storedADConfig)
if err != nil {
logrus.Debugf("[%v] errors while decoding stored AD config: %v", migrateAdUserOperation, err)
}
Expand All @@ -180,7 +182,7 @@ func adConfiguration(sc *config.ScaledContext) (*v3.ActiveDirectoryConfig, error
}

typemeta := &metav1.ObjectMeta{}
err = mapstructure.Decode(metadataMap, typemeta)
err = common.Decode(metadataMap, typemeta)
if err != nil {
logrus.Debugf("[%v] errors while decoding typemeta: %v", migrateAdUserOperation, err)
}
Expand Down Expand Up @@ -243,5 +245,44 @@ func isGUID(principalID string) bool {
logrus.Errorf("[%v] failed to parse invalid PrincipalID: %v", identifyAdUserOperation, principalID)
return false
}
return validRancherGuidPattern.MatchString(parts[1])
return validRancherGUIDPattern.MatchString(parts[1])
}

func updateADConfigMigrationStatus(status map[string]string, sc *config.ScaledContext) error {
authConfigObj, err := sc.Management.AuthConfigs("").ObjectClient().UnstructuredClient().Get("activedirectory", metav1.GetOptions{})
if err != nil {
logrus.Errorf("[%v] failed to obtain activedirecotry authConfigObj: %v", migrateAdUserOperation, err)
return err
}

authConfigJSON, err := json.Marshal(authConfigObj)
if err != nil {
return fmt.Errorf("failed to marshal authConfig object to JSON: %v", err)
}

// Create an empty unstructured object to hold the decoded JSON
storedADConfig := &unstructured.Unstructured{}

// Decode the JSON string into the unstructured object because mapstructure is dropping the metadata
if err := json.Unmarshal(authConfigJSON, storedADConfig); err != nil {
return fmt.Errorf("failed to unmarshal JSON into storedADConfig: %v", err)
}

// Update annotations with migration status
annotations := storedADConfig.GetAnnotations()
if annotations == nil {
annotations = make(map[string]string)
}
for annotation, value := range status {
annotations[adGUIDMigrationPrefix+annotation] = value
}
storedADConfig.SetAnnotations(annotations)

// Update the AuthConfig object using the unstructured client
_, err = sc.Management.AuthConfigs("").ObjectClient().UnstructuredClient().Update(storedADConfig.GetName(), storedADConfig)
if err != nil {
return fmt.Errorf("failed to update authConfig object: %v", err)
}

return nil
}
41 changes: 29 additions & 12 deletions pkg/agent/clean/ad_unmigration/migrate.go
Expand Up @@ -34,6 +34,7 @@ const (
localPrefix = "local://"
adGUIDMigrationLabel = "ad-guid-migration"
adGUIDMigrationAnnotation = "ad-guid-migration-data"
adGUIDMigrationPrefix = "migration-"
migratedLabelValue = "migrated"
migrationPreviousName = "ad-guid-previous-name"
AttributeObjectClass = "objectClass"
Expand Down Expand Up @@ -139,7 +140,10 @@ func UnmigrateAdGUIDUsers(clientConfig *restclient.Config, dryRun bool, deleteMi
}
defer lConn.Close()

// set the status to running and reset the unmigrated fields
err = updateMigrationStatus(sc, activedirectory.StatusMigrationField, activedirectory.StatusMigrationRunning)
updateUnmigratedUsers("", migrateStatusSkipped, true, sc)
updateUnmigratedUsers("", migrateStatusMissing, true, sc)
if err != nil {
return fmt.Errorf("unable to update migration status configmap: %v", err)
}
Expand All @@ -165,19 +169,19 @@ func UnmigrateAdGUIDUsers(clientConfig *restclient.Config, dryRun bool, deleteMi

for _, user := range skippedUsers {
logrus.Errorf("[%v] unable to migrate user '%v' due to a connection failure; this user will be skipped", migrateAdUserOperation, user.originalUser.Name)
updateUnmigratedUsers(user.originalUser.Name, migrateStatusSkipped, sc)
updateUnmigratedUsers(user.originalUser.Name, migrateStatusSkipped, false, sc)
}
for _, missingUser := range missingUsers {
if deleteMissingUsers && !dryRun {
logrus.Infof("[%v] user '%v' with GUID '%v' does not seem to exist in Active Directory. deleteMissingUsers is true, proceeding to delete this user permanently", migrateAdUserOperation, missingUser.originalUser.Name, missingUser.guid)
updateUnmigratedUsers(missingUser.originalUser.Name, migrateStatusMissing, sc)
updateUnmigratedUsers(missingUser.originalUser.Name, migrateStatusMissing, false, sc)
err = sc.Management.Users("").Delete(missingUser.originalUser.Name, &metav1.DeleteOptions{})
if err != nil {
logrus.Errorf("[%v] failed to delete missing user '%v' with: %v", migrateAdUserOperation, missingUser.originalUser.Name, err)
}
} else {
logrus.Errorf("[%v] User '%v' with GUID '%v' does not seem to exist in Active Directory. this user will be skipped", migrateAdUserOperation, missingUser.originalUser.Name, missingUser.guid)
updateUnmigratedUsers(missingUser.originalUser.Name, migrateStatusMissing, sc)
updateUnmigratedUsers(missingUser.originalUser.Name, migrateStatusMissing, false, sc)
}
}

Expand Down Expand Up @@ -410,28 +414,41 @@ func updateMigrationStatus(sc *config.ScaledContext, status string, value string
}
}
}

err = updateADConfigMigrationStatus(cm.Data, sc)
if err != nil {
return fmt.Errorf("unable to update AuthConfig status: %v", err)
}
return nil
}

// updateUnmigratedUsers will add a user to the list for the specified migration status in the migration status configmap
func updateUnmigratedUsers(user string, status string, sc *config.ScaledContext) {
// updateUnmigratedUsers will add a user to the list for the specified migration status in the migration status configmap.
// If reset is set to true, it will empty the list.
func updateUnmigratedUsers(user string, status string, reset bool, sc *config.ScaledContext) {
cm, err := sc.Core.ConfigMaps(activedirectory.StatusConfigMapNamespace).Get(activedirectory.StatusConfigMapName, metav1.GetOptions{})
if err != nil {
logrus.Errorf("[%v] unable to fetch configmap to update %v users: %v", migrateAdUserOperation, status, err)
}
currentList := cm.Data[status]
if currentList == "" {
currentList = currentList + user
var currentList string
if reset {
delete(cm.Data, status)
} else {
currentList = currentList + "," + user
currentList = cm.Data[status]
if currentList == "" {
currentList = currentList + user
} else {
currentList = currentList + "," + user
}
cm.Data[status] = currentList
}
cm.Data[status] = currentList
cm.Data[migrationStatusLastUpdate] = metav1.Now().Format(time.RFC3339)

cm.Data[migrationStatusLastUpdate] = metav1.Now().Format(time.RFC3339)
if _, err := sc.Core.ConfigMaps(activedirectory.StatusConfigMapNamespace).Update(cm); err != nil {
if err != nil {
logrus.Errorf("[%v] unable to update migration status configmap: %v", migrateAdUserOperation, err)
}
}
err = updateADConfigMigrationStatus(cm.Data, sc)
if err != nil {
logrus.Errorf("unable to update AuthConfig status: %v", err)
}
}
46 changes: 46 additions & 0 deletions pkg/auth/providers/common/provider_util.go
@@ -0,0 +1,46 @@
package common

import (
"fmt"
"reflect"
"time"

"github.com/mitchellh/mapstructure"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

// Decode will decode to the output structure by creating a custom decoder
// that uses the stringToK8sTimeHookFunc to handle the metav1.Time field properly.
func Decode(input, output any) error {
decoder, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{
DecodeHook: stringToK8sTimeHookFunc(),
Result: output,
})
if err != nil {
return fmt.Errorf("unable to create decoder for Config: %w", err)
}
err = decoder.Decode(input)
if err != nil {
return fmt.Errorf("unable to decode Config: %w", err)
}
return nil
}

// stringToTimeHookFunc returns a DecodeHookFunc that converts strings to metav1.Time.
func stringToK8sTimeHookFunc() mapstructure.DecodeHookFunc {
return func(
f reflect.Type,
t reflect.Type,
data interface{}) (interface{}, error) {
if f.Kind() != reflect.String {
return data, nil
}
if t != reflect.TypeOf(metav1.Time{}) {
return data, nil
}

// Convert it by parsing
stdTime, err := time.Parse(time.RFC3339, data.(string))
return metav1.Time{Time: stdTime}, err
}
}

0 comments on commit 8854263

Please sign in to comment.