mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-04-03 20:32:07 -04:00
- Added pre-run checks for greeter and setup commands to enforce policy restrictions - Created cli-policy.default.json to define blocked commands and user messages for immutable environments.
272 lines
6.5 KiB
Go
272 lines
6.5 KiB
Go
package main
|
|
|
|
import (
|
|
"bufio"
|
|
_ "embed"
|
|
"encoding/json"
|
|
"fmt"
|
|
"os"
|
|
"strings"
|
|
"sync"
|
|
|
|
"github.com/spf13/cobra"
|
|
)
|
|
|
|
const (
|
|
cliPolicyPackagedPath = "/usr/share/dms/cli-policy.json"
|
|
cliPolicyAdminPath = "/etc/dms/cli-policy.json"
|
|
)
|
|
|
|
var (
|
|
immutablePolicyOnce sync.Once
|
|
immutablePolicy immutableCommandPolicy
|
|
immutablePolicyErr error
|
|
)
|
|
|
|
//go:embed assets/cli-policy.default.json
|
|
var defaultCLIPolicyJSON []byte
|
|
|
|
type immutableCommandPolicy struct {
|
|
ImmutableSystem bool
|
|
ImmutableReason string
|
|
BlockedCommands []string
|
|
Message string
|
|
}
|
|
|
|
type cliPolicyFile struct {
|
|
PolicyVersion int `json:"policy_version"`
|
|
ImmutableSystem *bool `json:"immutable_system"`
|
|
BlockedCommands *[]string `json:"blocked_commands"`
|
|
Message *string `json:"message"`
|
|
}
|
|
|
|
func normalizeCommandSpec(raw string) string {
|
|
normalized := strings.ToLower(strings.TrimSpace(raw))
|
|
normalized = strings.TrimPrefix(normalized, "dms ")
|
|
return strings.Join(strings.Fields(normalized), " ")
|
|
}
|
|
|
|
func normalizeBlockedCommands(raw []string) []string {
|
|
normalized := make([]string, 0, len(raw))
|
|
seen := make(map[string]bool)
|
|
|
|
for _, cmd := range raw {
|
|
spec := normalizeCommandSpec(cmd)
|
|
if spec == "" || seen[spec] {
|
|
continue
|
|
}
|
|
seen[spec] = true
|
|
normalized = append(normalized, spec)
|
|
}
|
|
|
|
return normalized
|
|
}
|
|
|
|
func commandBlockedByPolicy(commandPath string, blocked []string) bool {
|
|
normalizedPath := normalizeCommandSpec(commandPath)
|
|
if normalizedPath == "" {
|
|
return false
|
|
}
|
|
|
|
for _, entry := range blocked {
|
|
spec := normalizeCommandSpec(entry)
|
|
if spec == "" {
|
|
continue
|
|
}
|
|
if normalizedPath == spec || strings.HasPrefix(normalizedPath, spec+" ") {
|
|
return true
|
|
}
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
func loadPolicyFile(path string) (*cliPolicyFile, error) {
|
|
data, err := os.ReadFile(path)
|
|
if err != nil {
|
|
if os.IsNotExist(err) {
|
|
return nil, nil
|
|
}
|
|
return nil, fmt.Errorf("failed to read %s: %w", path, err)
|
|
}
|
|
|
|
var policy cliPolicyFile
|
|
if err := json.Unmarshal(data, &policy); err != nil {
|
|
return nil, fmt.Errorf("failed to parse %s: %w", path, err)
|
|
}
|
|
|
|
return &policy, nil
|
|
}
|
|
|
|
func mergePolicyFile(base *immutableCommandPolicy, path string) error {
|
|
policyFile, err := loadPolicyFile(path)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if policyFile == nil {
|
|
return nil
|
|
}
|
|
|
|
if policyFile.ImmutableSystem != nil {
|
|
base.ImmutableSystem = *policyFile.ImmutableSystem
|
|
}
|
|
if policyFile.BlockedCommands != nil {
|
|
base.BlockedCommands = normalizeBlockedCommands(*policyFile.BlockedCommands)
|
|
}
|
|
if policyFile.Message != nil {
|
|
msg := strings.TrimSpace(*policyFile.Message)
|
|
if msg != "" {
|
|
base.Message = msg
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func readOSReleaseMap(path string) map[string]string {
|
|
values := make(map[string]string)
|
|
|
|
file, err := os.Open(path)
|
|
if err != nil {
|
|
return values
|
|
}
|
|
defer file.Close()
|
|
|
|
scanner := bufio.NewScanner(file)
|
|
for scanner.Scan() {
|
|
line := strings.TrimSpace(scanner.Text())
|
|
if line == "" || strings.HasPrefix(line, "#") {
|
|
continue
|
|
}
|
|
parts := strings.SplitN(line, "=", 2)
|
|
if len(parts) != 2 {
|
|
continue
|
|
}
|
|
key := strings.ToUpper(strings.TrimSpace(parts[0]))
|
|
value := strings.Trim(strings.TrimSpace(parts[1]), "\"")
|
|
values[key] = strings.ToLower(value)
|
|
}
|
|
|
|
return values
|
|
}
|
|
|
|
func hasAnyToken(text string, tokens ...string) bool {
|
|
if text == "" {
|
|
return false
|
|
}
|
|
for _, token := range tokens {
|
|
if strings.Contains(text, token) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func detectImmutableSystem() (bool, string) {
|
|
if _, err := os.Stat("/run/ostree-booted"); err == nil {
|
|
return true, "/run/ostree-booted is present"
|
|
}
|
|
|
|
osRelease := readOSReleaseMap("/etc/os-release")
|
|
if len(osRelease) == 0 {
|
|
return false, ""
|
|
}
|
|
|
|
id := osRelease["ID"]
|
|
idLike := osRelease["ID_LIKE"]
|
|
variantID := osRelease["VARIANT_ID"]
|
|
name := osRelease["NAME"]
|
|
prettyName := osRelease["PRETTY_NAME"]
|
|
|
|
immutableIDs := map[string]bool{
|
|
"bluefin": true,
|
|
"bazzite": true,
|
|
"silverblue": true,
|
|
"kinoite": true,
|
|
"sericea": true,
|
|
"onyx": true,
|
|
"aurora": true,
|
|
"fedora-iot": true,
|
|
"fedora-coreos": true,
|
|
}
|
|
if immutableIDs[id] {
|
|
return true, "os-release ID=" + id
|
|
}
|
|
|
|
markers := []string{"silverblue", "kinoite", "sericea", "onyx", "bazzite", "bluefin", "aurora", "ostree", "atomic"}
|
|
if hasAnyToken(variantID, markers...) {
|
|
return true, "os-release VARIANT_ID=" + variantID
|
|
}
|
|
if hasAnyToken(idLike, "ostree", "rpm-ostree") {
|
|
return true, "os-release ID_LIKE=" + idLike
|
|
}
|
|
if hasAnyToken(name, markers...) || hasAnyToken(prettyName, markers...) {
|
|
return true, "os-release identifies an atomic/ostree variant"
|
|
}
|
|
|
|
return false, ""
|
|
}
|
|
|
|
func getImmutablePolicy() (*immutableCommandPolicy, error) {
|
|
immutablePolicyOnce.Do(func() {
|
|
detectedImmutable, reason := detectImmutableSystem()
|
|
immutablePolicy = immutableCommandPolicy{
|
|
ImmutableSystem: detectedImmutable,
|
|
ImmutableReason: reason,
|
|
BlockedCommands: []string{"greeter install", "greeter enable", "greeter sync", "setup"},
|
|
Message: "This command is disabled on immutable/image-based systems. Use your distro-native workflow for system-level changes.",
|
|
}
|
|
|
|
var defaultPolicy cliPolicyFile
|
|
if err := json.Unmarshal(defaultCLIPolicyJSON, &defaultPolicy); err != nil {
|
|
immutablePolicyErr = fmt.Errorf("failed to parse embedded default CLI policy: %w", err)
|
|
return
|
|
}
|
|
if defaultPolicy.BlockedCommands != nil {
|
|
immutablePolicy.BlockedCommands = normalizeBlockedCommands(*defaultPolicy.BlockedCommands)
|
|
}
|
|
if defaultPolicy.Message != nil {
|
|
msg := strings.TrimSpace(*defaultPolicy.Message)
|
|
if msg != "" {
|
|
immutablePolicy.Message = msg
|
|
}
|
|
}
|
|
|
|
if err := mergePolicyFile(&immutablePolicy, cliPolicyPackagedPath); err != nil {
|
|
immutablePolicyErr = err
|
|
return
|
|
}
|
|
if err := mergePolicyFile(&immutablePolicy, cliPolicyAdminPath); err != nil {
|
|
immutablePolicyErr = err
|
|
return
|
|
}
|
|
})
|
|
|
|
if immutablePolicyErr != nil {
|
|
return nil, immutablePolicyErr
|
|
}
|
|
return &immutablePolicy, nil
|
|
}
|
|
|
|
func requireMutableSystemCommand(cmd *cobra.Command, _ []string) error {
|
|
policy, err := getImmutablePolicy()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if !policy.ImmutableSystem {
|
|
return nil
|
|
}
|
|
|
|
commandPath := normalizeCommandSpec(cmd.CommandPath())
|
|
if !commandBlockedByPolicy(commandPath, policy.BlockedCommands) {
|
|
return nil
|
|
}
|
|
|
|
reason := ""
|
|
if policy.ImmutableReason != "" {
|
|
reason = "Detected immutable system: " + policy.ImmutableReason + "\n"
|
|
}
|
|
|
|
return fmt.Errorf("%s%s\nCommand: dms %s\nPolicy files:\n %s\n %s", reason, policy.Message, commandPath, cliPolicyPackagedPath, cliPolicyAdminPath)
|
|
}
|