diff --git a/internal/policies/privilege/privilege.go b/internal/policies/privilege/privilege.go index fd7609055..f5a0b0259 100644 --- a/internal/policies/privilege/privilege.go +++ b/internal/policies/privilege/privilege.go @@ -126,7 +126,7 @@ func (m *Manager) ApplyPolicy(ctx context.Context, objectName string, isComputer log.Debugf(ctx, "Applying privilege policy to %s", objectName) - // We don’t create empty files if there is no entries. Still remove any previous version. + // We don't create empty files if there is no entries. Still remove any previous version. if len(entries) == 0 { if err := os.Remove(sudoersConf); err != nil && !errors.Is(err, fs.ErrNotExist) { return err @@ -175,6 +175,25 @@ func (m *Manager) ApplyPolicy(ctx context.Context, objectName string, isComputer allowLocalAdmins := true var polkitAdditionalUsersGroups []string + // Check if any entry needs hostname substitution + needsHostname := false + for _, entry := range entries { + if entry.Key == "client-admins" && strings.Contains(entry.Value, "$HOSTNAME") { + needsHostname = true + break + } + } + + // Get hostname for variable substitution (only if needed) + var hostname string + if needsHostname { + var hostnameErr error + hostname, hostnameErr = os.Hostname() + if hostnameErr != nil { + return fmt.Errorf("can't get hostname: %w", hostnameErr) + } + } + for _, entry := range entries { var contentSudo string @@ -195,8 +214,11 @@ func (m *Manager) ApplyPolicy(ctx context.Context, objectName string, isComputer continue } + // Substitute $HOSTNAME with actual hostname + value := strings.ReplaceAll(entry.Value, "$HOSTNAME", hostname) + var polkitElem []string - for _, e := range splitAndNormalizeUsersAndGroups(ctx, entry.Value) { + for _, e := range splitAndNormalizeUsersAndGroups(ctx, value) { contentSudo += fmt.Sprintf("\"%s\" ALL=(ALL:ALL) ALL\n", e) polkitID := fmt.Sprintf("unix-user:%s", e) if strings.HasPrefix(e, "%") { diff --git a/internal/policies/privilege/privilege_test.go b/internal/policies/privilege/privilege_test.go index 3a7da19dc..f7b3eff7c 100644 --- a/internal/policies/privilege/privilege_test.go +++ b/internal/policies/privilege/privilege_test.go @@ -4,6 +4,7 @@ import ( "context" "os" "path/filepath" + "strings" "testing" "github.com/stretchr/testify/require" @@ -38,6 +39,9 @@ func TestApplyPolicy(t *testing.T) { "Set client multiple users admins": {entries: []entry.Entry{{Key: "client-admins", Value: "alice@domain.com,domain\\bob,carole cosmic@otherdomain.com"}}}, "Set client group admins": {entries: []entry.Entry{{Key: "client-admins", Value: "%group@domain.com"}}}, "Set client mixed with users and group admins": {entries: []entry.Entry{{Key: "client-admins", Value: "alice@domain.com,%group@domain.com"}}}, + "Set client admins with HOSTNAME substitution": {entries: []entry.Entry{{Key: "client-admins", Value: "secLocalAdmin-$HOSTNAME@domain.com"}}}, + "Set client admins with multiple HOSTNAME substitutions": {entries: []entry.Entry{{Key: "client-admins", Value: "x-$HOSTNAME-y-$HOSTNAME@domain.com"}}}, + "Set client group admins with HOSTNAME substitution": {entries: []entry.Entry{{Key: "client-admins", Value: "%admins-$HOSTNAME@domain.com"}}}, "Empty client AD admins": {entries: []entry.Entry{{Key: "client-admins", Value: ""}}}, "No client AD admins": {entries: []entry.Entry{{Key: "client-admins", Disabled: true}}}, @@ -131,7 +135,75 @@ func TestApplyPolicy(t *testing.T) { } require.NoError(t, err, "ApplyPolicy failed but shouldn't have") + // Replace hostname in generated files for tests using $HOSTNAME substitution + // This ensures golden files are machine-independent + needsReplacement := false + for _, entry := range tc.entries { + if entry.Key == "client-admins" && strings.Contains(entry.Value, "$HOSTNAME") { + needsReplacement = true + break + } + } + if needsReplacement { + replaceHostnameInGeneratedFiles(t, tmpRootDir, "testhost") + } + testutils.CompareTreesWithFiltering(t, filepath.Join(tmpRootDir, "etc"), testutils.GoldenPath(t), testutils.UpdateEnabled()) }) } } + +// replaceHostnameInGeneratedFiles replaces the actual hostname with a generic one in generated files +// to ensure golden files are machine-independent. +func replaceHostnameInGeneratedFiles(t *testing.T, rootDir, replacement string) { + t.Helper() + + // Get the actual hostname + hostname, err := os.Hostname() + require.NoError(t, err, "Setup: Failed to get hostname") + if hostname == replacement { + // Already using the replacement, no need to replace + return + } + + // Files that might contain the hostname + files := []string{ + filepath.Join(rootDir, "etc", "sudoers.d", "99-adsys-privilege-enforcement"), + filepath.Join(rootDir, "etc", "polkit-1", "rules.d", "00-adsys-privilege-enforcement.rules"), + filepath.Join(rootDir, "etc", "polkit-1", "localauthority.conf.d", "99-adsys-privilege-enforcement.conf"), + } + + for _, file := range files { + // Check if file exists + if _, err := os.Stat(file); os.IsNotExist(err) { + continue + } + + // Read file content + content, err := os.ReadFile(file) + require.NoError(t, err, "Setup: Failed to read file %s", file) + + // Replace hostname with generic one + newContent := strings.ReplaceAll(string(content), hostname, replacement) + + // Write back if changed + if string(content) != newContent { + // Get original file permissions + info, err := os.Stat(file) + require.NoError(t, err, "Setup: Failed to stat file %s", file) + originalMode := info.Mode() + + // Make file writable temporarily + err = os.Chmod(file, 0644) + require.NoError(t, err, "Setup: Failed to make file writable %s", file) + + // Write the modified content + err = os.WriteFile(file, []byte(newContent), originalMode) + require.NoError(t, err, "Setup: Failed to write file %s", file) + + // Restore original permissions + err = os.Chmod(file, originalMode) + require.NoError(t, err, "Setup: Failed to restore file permissions %s", file) + } + } +} diff --git a/internal/policies/privilege/testdata/TestApplyPolicy/golden/set_client_admins_with_hostname_substitution/polkit-1/rules.d/00-adsys-privilege-enforcement.rules b/internal/policies/privilege/testdata/TestApplyPolicy/golden/set_client_admins_with_hostname_substitution/polkit-1/rules.d/00-adsys-privilege-enforcement.rules new file mode 100644 index 000000000..caf0d99bd --- /dev/null +++ b/internal/policies/privilege/testdata/TestApplyPolicy/golden/set_client_admins_with_hostname_substitution/polkit-1/rules.d/00-adsys-privilege-enforcement.rules @@ -0,0 +1,7 @@ +// This file is managed by adsys. +// Do not edit this file manually. +// Any changes will be overwritten. + +polkit.addAdminRule(function(action, subject){ + return ["unix-user:secLocalAdmin-testhost@domain.com"]; +}); diff --git a/internal/policies/privilege/testdata/TestApplyPolicy/golden/set_client_admins_with_hostname_substitution/sudoers.d/99-adsys-privilege-enforcement b/internal/policies/privilege/testdata/TestApplyPolicy/golden/set_client_admins_with_hostname_substitution/sudoers.d/99-adsys-privilege-enforcement new file mode 100644 index 000000000..ef7c9c720 --- /dev/null +++ b/internal/policies/privilege/testdata/TestApplyPolicy/golden/set_client_admins_with_hostname_substitution/sudoers.d/99-adsys-privilege-enforcement @@ -0,0 +1,6 @@ +# This file is managed by adsys. +# Do not edit this file manually. +# Any changes will be overwritten. + +"secLocalAdmin-testhost@domain.com" ALL=(ALL:ALL) ALL + diff --git a/internal/policies/privilege/testdata/TestApplyPolicy/golden/set_client_admins_with_multiple_hostname_substitutions/polkit-1/rules.d/00-adsys-privilege-enforcement.rules b/internal/policies/privilege/testdata/TestApplyPolicy/golden/set_client_admins_with_multiple_hostname_substitutions/polkit-1/rules.d/00-adsys-privilege-enforcement.rules new file mode 100644 index 000000000..fe365b6bc --- /dev/null +++ b/internal/policies/privilege/testdata/TestApplyPolicy/golden/set_client_admins_with_multiple_hostname_substitutions/polkit-1/rules.d/00-adsys-privilege-enforcement.rules @@ -0,0 +1,7 @@ +// This file is managed by adsys. +// Do not edit this file manually. +// Any changes will be overwritten. + +polkit.addAdminRule(function(action, subject){ + return ["unix-user:x-testhost-y-testhost@domain.com"]; +}); diff --git a/internal/policies/privilege/testdata/TestApplyPolicy/golden/set_client_admins_with_multiple_hostname_substitutions/sudoers.d/99-adsys-privilege-enforcement b/internal/policies/privilege/testdata/TestApplyPolicy/golden/set_client_admins_with_multiple_hostname_substitutions/sudoers.d/99-adsys-privilege-enforcement new file mode 100644 index 000000000..1721ecdd2 --- /dev/null +++ b/internal/policies/privilege/testdata/TestApplyPolicy/golden/set_client_admins_with_multiple_hostname_substitutions/sudoers.d/99-adsys-privilege-enforcement @@ -0,0 +1,6 @@ +# This file is managed by adsys. +# Do not edit this file manually. +# Any changes will be overwritten. + +"x-testhost-y-testhost@domain.com" ALL=(ALL:ALL) ALL + diff --git a/internal/policies/privilege/testdata/TestApplyPolicy/golden/set_client_group_admins_with_hostname_substitution/polkit-1/rules.d/00-adsys-privilege-enforcement.rules b/internal/policies/privilege/testdata/TestApplyPolicy/golden/set_client_group_admins_with_hostname_substitution/polkit-1/rules.d/00-adsys-privilege-enforcement.rules new file mode 100644 index 000000000..ce5b75d2f --- /dev/null +++ b/internal/policies/privilege/testdata/TestApplyPolicy/golden/set_client_group_admins_with_hostname_substitution/polkit-1/rules.d/00-adsys-privilege-enforcement.rules @@ -0,0 +1,7 @@ +// This file is managed by adsys. +// Do not edit this file manually. +// Any changes will be overwritten. + +polkit.addAdminRule(function(action, subject){ + return ["unix-group:admins-testhost@domain.com"]; +}); diff --git a/internal/policies/privilege/testdata/TestApplyPolicy/golden/set_client_group_admins_with_hostname_substitution/sudoers.d/99-adsys-privilege-enforcement b/internal/policies/privilege/testdata/TestApplyPolicy/golden/set_client_group_admins_with_hostname_substitution/sudoers.d/99-adsys-privilege-enforcement new file mode 100644 index 000000000..59e5f8d13 --- /dev/null +++ b/internal/policies/privilege/testdata/TestApplyPolicy/golden/set_client_group_admins_with_hostname_substitution/sudoers.d/99-adsys-privilege-enforcement @@ -0,0 +1,6 @@ +# This file is managed by adsys. +# Do not edit this file manually. +# Any changes will be overwritten. + +"%admins-testhost@domain.com" ALL=(ALL:ALL) ALL +