Skip to content

Commit

Permalink
fixup! Support for Polkit 124
Browse files Browse the repository at this point in the history
  • Loading branch information
denisonbarbosa committed Nov 29, 2024
1 parent ca99886 commit b5781d7
Showing 1 changed file with 87 additions and 62 deletions.
149 changes: 87 additions & 62 deletions internal/policies/privilege/privilege.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,15 +50,23 @@ import (
Both are installed under respective /etc directories.
*/

const adsysBaseSudoersName = "99-adsys-privilege-enforcement"

const (
adsysBaseSudoersName = "99-adsys-privilege-enforcement"

adsysOldPolkitName = "99-adsys-privilege-enforcement"
adsysBasePolkitName = "00-adsys-privilege-enforcement"

polkitSystemReservedPath = "/usr/share/polkit-1"
)

// Templates to generate the polkit configuration files.
const (
policyKitConfTemplate = "%s[Configuration]\nAdminIdentities=%s"
policyKitRulesTemplate = `%spolkit.addAdminRule(function(action, subject){
return [%s];
});`
)

type option struct {
policyKitSystemDir string
}
Expand Down Expand Up @@ -299,6 +307,8 @@ func splitAndNormalizeUsersAndGroups(ctx context.Context, v string) []string {
return elems
}

// getSystemPolkitAdminIdentities parses the system polkit configuration (based on its version) to get the
// list of admin identities.
func (m *Manager) getSystemPolkitAdminIdentities(ctx context.Context, policyKitDir string, oldPolkit bool) (adminIdentities string, err error) {
if oldPolkit {
return polkitAdminIdentitiesFromConf(ctx, policyKitDir)
Expand All @@ -309,74 +319,91 @@ func (m *Manager) getSystemPolkitAdminIdentities(ctx context.Context, policyKitD
})
}

// polkitAdminIdentitiesFromRules parses the polkit rules files to get the list of admin identities.
//
// Since polkit >= 124 now only cares about the first valid return, this function will sort the files from all the
// specified directories (priority: lesser ascii value, higher priority), parse them and identify the first valid return.
func polkitAdminIdentitiesFromRules(ctx context.Context, rulesDirPaths []string) (adminIdentities string, err error) {
currentRulesFile := "99"
for _, rulesDirPath := range rulesDirPaths {
ruleFiles, err := filepath.Glob(filepath.Join(rulesDirPath, "rules.d", "*.rules"))
// Compile the regex needed to parse the polkit admin rules.
// Matches: polkit.addAdminRule(function(action, subject){(.*)});
adminRulesRegex, err := regexp.Compile(`polkit\.addAdminRule\s*\(\s*function\s*\(\s*action\s*\,\s*subject\s*\)\s*{\s*[^\}]*}\s*\)\s*\;`)
if err != nil {
return "", err
}
// Matches for: { return [(.*)] }
returnRegex, err := regexp.Compile(`\{\s*return\s*\[(\s*([^\]]*))*\s*\]\s*;\s*\}`)
if err != nil {
return "", err
}
// Matches for: "someuser" or 'someuser'
userRegex, err := regexp.Compile(`(["']+([^,]*)["']+)`)
if err != nil {
return "", err
}

var ruleFiles []string
for _, path := range rulesDirPaths {
files, err := filepath.Glob(filepath.Join(path, "rules.d", "*.rules"))
if err != nil {
return "", err
}
ruleFiles = append(ruleFiles, files...)
}

slices.Sort(ruleFiles)
ruleFilesRange:
for _, path := range ruleFiles {
if filepath.Base(path) >= currentRulesFile {
break
}

if filepath.Base(path) == adsysBasePolkitName+".rules" {
continue
}
// Sort the files respecting the priority that Polkit assigns to them.
slices.SortFunc(ruleFiles, func(i, j string) int {
// If the files have different name, we return the one with the lowest ascii value.
if order := strings.Compare(filepath.Base(i), filepath.Base(j)); order != 0 {
return order
}

pathStat, err := os.Stat(path)
if err != nil {
return "", err
}
if pathStat.IsDir() {
log.Warning(ctx, gotext.Get("%s is a directory. Ignoring.", path))
continue
// If the files have the same name, we respect the directory priority in rulesDirPaths (lesser index, higher prio).
var idxI, idxJ int
for idx, dir := range rulesDirPaths {
if strings.Contains(i, dir) {
idxI = idx
}

b, err := os.ReadFile(path)
if err != nil {
return "", err
if strings.Contains(j, dir) {
idxJ = idx
}
rules := string(b)
}
return idxI - idxJ
})

// Check if the file contains the rule we are looking for
if !strings.Contains(rules, "polkit.addAdminRule") {
continue
}
for _, path := range ruleFiles {
if filepath.Base(path) == adsysBasePolkitName+".rules" {
continue
}

// Extract the return value of the polkit.addAdminRule function
// Matches: polkit.addAdminRule(function(action, subject){(.*)});
adminRulesRegex, err := regexp.Compile(`polkit\.addAdminRule\s*\(\s*function\s*\(\s*action\s*\,\s*subject\s*\)\s*{\s*[^\}]*}\s*\)\s*\;`)
if err != nil {
return "", err
}
// Matches for: { return [(.*)] }
returnRegex, err := regexp.Compile(`\{\s*return\s*\[(\s*([^\]]*))*\s*\]\s*;\s*\}`)
if err != nil {
return "", err
}
// Matches for: "someuser" or 'someuser'
userRegex, err := regexp.Compile(`(["']+([^,]*)["']+)`)
if err != nil {
b, err := os.ReadFile(path)
if err != nil {
pathErr := &os.PathError{}
if errors.As(err, &pathErr) && pathErr.Op == "open" {
// This means that we couldn't open the file for reading, likely due to permission errors.
// If so, we can not ensure that we will match the expected admin identities from the system
// and we should return an error.
return "", err
}
// If we get an error when reading the file, it's likely due to it being a directory.
// This case we can ignore and continue to the next file.
log.Debugf(ctx, gotext.Get("Ignoring %s: %v", path, err))

Check failure on line 389 in internal/policies/privilege/privilege.go

View workflow job for this annotation

GitHub Actions / Code sanity

printf: non-constant format string in call to github.com/ubuntu/adsys/internal/grpc/logstreamer.Debugf (govet)
continue
}
rules := string(b)

for _, adminRule := range adminRulesRegex.FindAllString(rules, -1) {
returnStmt := returnRegex.FindString(adminRule)
if returnStmt == "" {
continue
}

adminIdentities = strings.Join(userRegex.FindAllString(returnStmt, -1), ",")
currentRulesFile = filepath.Base(path)
// Check if the file contains the rule we are looking for
if !strings.Contains(rules, "polkit.addAdminRule") {
continue
}

// Since we only care about the first returned value, we can skip the other files in the directory.
break ruleFilesRange
for _, adminRule := range adminRulesRegex.FindAllString(rules, -1) {
returnStmt := returnRegex.FindString(adminRule)
if returnStmt == "" {
continue
}

log.Debugf(ctx, gotext.Get("Using polkit admin identities from %q", path))

Check failure on line 405 in internal/policies/privilege/privilege.go

View workflow job for this annotation

GitHub Actions / Code sanity

printf: non-constant format string in call to github.com/ubuntu/adsys/internal/grpc/logstreamer.Debugf (govet)
return strings.Join(userRegex.FindAllString(returnStmt, -1), ","), nil
}
}

Expand Down Expand Up @@ -436,19 +463,17 @@ func isOldPolkit(policyKitDir, policyKitReservedDir string) bool {
return false
}

// If the directory only contains the adsys generated file, we can assume that the version is >= 124
if nEntries == 1 && dirEntries[0].Name() == adsysOldPolkitName+".conf" {
return false
}

// If the old directory isn't empty and there's no new configuration file, we can assume that the version is < 124.
if _, err := os.Stat(filepath.Join(policyKitReservedDir, "rules.d/49-ubuntu-admin.rules")); err != nil {
return true
}

// If the new configuration file exists but the old directory is not empty, it likely means that the user
// installed the compatibility package (polkitd-pkla), but polkit version is still >= 124.
return false
}

const (
policyKitConfTemplate = "%s[Configuration]\nAdminIdentities=%s"
policyKitRulesTemplate = `%spolkit.addAdminRule(function(action, subject){
return [%s];
});`
)

0 comments on commit b5781d7

Please sign in to comment.