1// Package aaparser is a convenience package interacting with `apparmor_parser`.
2package aaparser // import "github.com/docker/docker/pkg/aaparser"
3
4import (
5	"fmt"
6	"os/exec"
7	"strconv"
8	"strings"
9)
10
11const (
12	binary = "apparmor_parser"
13)
14
15// GetVersion returns the major and minor version of apparmor_parser.
16func GetVersion() (int, error) {
17	output, err := cmd("", "--version")
18	if err != nil {
19		return -1, err
20	}
21
22	return parseVersion(output)
23}
24
25// LoadProfile runs `apparmor_parser -Kr` on a specified apparmor profile to
26// replace the profile. The `-K` is necessary to make sure that apparmor_parser
27// doesn't try to write to a read-only filesystem.
28func LoadProfile(profilePath string) error {
29	_, err := cmd("", "-Kr", profilePath)
30	return err
31}
32
33// cmd runs `apparmor_parser` with the passed arguments.
34func cmd(dir string, arg ...string) (string, error) {
35	c := exec.Command(binary, arg...)
36	c.Dir = dir
37
38	output, err := c.CombinedOutput()
39	if err != nil {
40		return "", fmt.Errorf("running `%s %s` failed with output: %s\nerror: %v", c.Path, strings.Join(c.Args, " "), output, err)
41	}
42
43	return string(output), nil
44}
45
46// parseVersion takes the output from `apparmor_parser --version` and returns
47// a representation of the {major, minor, patch} version as a single number of
48// the form MMmmPPP {major, minor, patch}.
49func parseVersion(output string) (int, error) {
50	// output is in the form of the following:
51	// AppArmor parser version 2.9.1
52	// Copyright (C) 1999-2008 Novell Inc.
53	// Copyright 2009-2012 Canonical Ltd.
54
55	lines := strings.SplitN(output, "\n", 2)
56	words := strings.Split(lines[0], " ")
57	version := words[len(words)-1]
58
59	// trim "-beta1" suffix from version="3.0.0-beta1" if exists
60	version = strings.SplitN(version, "-", 2)[0]
61	// also trim "~..." suffix used historically (https://gitlab.com/apparmor/apparmor/-/commit/bca67d3d27d219d11ce8c9cc70612bd637f88c10)
62	version = strings.SplitN(version, "~", 2)[0]
63
64	// split by major minor version
65	v := strings.Split(version, ".")
66	if len(v) == 0 || len(v) > 3 {
67		return -1, fmt.Errorf("parsing version failed for output: `%s`", output)
68	}
69
70	// Default the versions to 0.
71	var majorVersion, minorVersion, patchLevel int
72
73	majorVersion, err := strconv.Atoi(v[0])
74	if err != nil {
75		return -1, err
76	}
77
78	if len(v) > 1 {
79		minorVersion, err = strconv.Atoi(v[1])
80		if err != nil {
81			return -1, err
82		}
83	}
84	if len(v) > 2 {
85		patchLevel, err = strconv.Atoi(v[2])
86		if err != nil {
87			return -1, err
88		}
89	}
90
91	// major*10^5 + minor*10^3 + patch*10^0
92	numericVersion := majorVersion*1e5 + minorVersion*1e3 + patchLevel
93	return numericVersion, nil
94}
95