Signed-off-by: Jessica Frazelle <acidburn@docker.com>
| 1 | 1 |
deleted file mode 100644 |
| ... | ... |
@@ -1,161 +0,0 @@ |
| 1 |
-// +build linux |
|
| 2 |
- |
|
| 3 |
-package native |
|
| 4 |
- |
|
| 5 |
-import ( |
|
| 6 |
- "bufio" |
|
| 7 |
- "io" |
|
| 8 |
- "os" |
|
| 9 |
- "os/exec" |
|
| 10 |
- "path" |
|
| 11 |
- "strings" |
|
| 12 |
- "text/template" |
|
| 13 |
- |
|
| 14 |
- "github.com/docker/docker/pkg/aaparser" |
|
| 15 |
- "github.com/opencontainers/runc/libcontainer/apparmor" |
|
| 16 |
-) |
|
| 17 |
- |
|
| 18 |
-const ( |
|
| 19 |
- apparmorProfilePath = "/etc/apparmor.d/docker" |
|
| 20 |
-) |
|
| 21 |
- |
|
| 22 |
-type data struct {
|
|
| 23 |
- Name string |
|
| 24 |
- ExecPath string |
|
| 25 |
- Imports []string |
|
| 26 |
- InnerImports []string |
|
| 27 |
- MajorVersion int |
|
| 28 |
- MinorVersion int |
|
| 29 |
-} |
|
| 30 |
- |
|
| 31 |
-const baseTemplate = ` |
|
| 32 |
-{{range $value := .Imports}}
|
|
| 33 |
-{{$value}}
|
|
| 34 |
-{{end}}
|
|
| 35 |
- |
|
| 36 |
-profile {{.Name}} flags=(attach_disconnected,mediate_deleted) {
|
|
| 37 |
-{{range $value := .InnerImports}}
|
|
| 38 |
- {{$value}}
|
|
| 39 |
-{{end}}
|
|
| 40 |
- |
|
| 41 |
- network, |
|
| 42 |
- capability, |
|
| 43 |
- file, |
|
| 44 |
- umount, |
|
| 45 |
- |
|
| 46 |
- deny @{PROC}/* w, # deny write for all files directly in /proc (not in a subdir)
|
|
| 47 |
- # deny write to files not in /proc/<number>/** or /proc/sys/** |
|
| 48 |
- deny @{PROC}/{[^1-9],[^1-9][^0-9],[^1-9s][^0-9y][^0-9s],[^1-9][^0-9][^0-9][^0-9]*}/** w,
|
|
| 49 |
- deny @{PROC}/sys/[^k]** w, # deny /proc/sys except /proc/sys/k* (effectively /proc/sys/kernel)
|
|
| 50 |
- deny @{PROC}/sys/kernel/{?,??,[^s][^h][^m]**} w, # deny everything except shm* in /proc/sys/kernel/
|
|
| 51 |
- deny @{PROC}/sysrq-trigger rwklx,
|
|
| 52 |
- deny @{PROC}/mem rwklx,
|
|
| 53 |
- deny @{PROC}/kmem rwklx,
|
|
| 54 |
- deny @{PROC}/kcore rwklx,
|
|
| 55 |
- |
|
| 56 |
- deny mount, |
|
| 57 |
- |
|
| 58 |
- deny /sys/[^f]*/** wklx, |
|
| 59 |
- deny /sys/f[^s]*/** wklx, |
|
| 60 |
- deny /sys/fs/[^c]*/** wklx, |
|
| 61 |
- deny /sys/fs/c[^g]*/** wklx, |
|
| 62 |
- deny /sys/fs/cg[^r]*/** wklx, |
|
| 63 |
- deny /sys/firmware/efi/efivars/** rwklx, |
|
| 64 |
- deny /sys/kernel/security/** rwklx, |
|
| 65 |
- |
|
| 66 |
-{{if ge .MajorVersion 2}}{{if ge .MinorVersion 8}}
|
|
| 67 |
- # suppress ptrace denials when using 'docker ps' or using 'ps' inside a container |
|
| 68 |
- ptrace (trace,read) peer=docker-default, |
|
| 69 |
-{{end}}{{end}}
|
|
| 70 |
-{{if ge .MajorVersion 2}}{{if ge .MinorVersion 9}}
|
|
| 71 |
- # docker daemon confinement requires explict allow rule for signal |
|
| 72 |
- signal (receive) set=(kill,term) peer={{.ExecPath}},
|
|
| 73 |
-{{end}}{{end}}
|
|
| 74 |
-} |
|
| 75 |
-` |
|
| 76 |
- |
|
| 77 |
-func generateProfile(out io.Writer) error {
|
|
| 78 |
- compiled, err := template.New("apparmor_profile").Parse(baseTemplate)
|
|
| 79 |
- if err != nil {
|
|
| 80 |
- return err |
|
| 81 |
- } |
|
| 82 |
- data := &data{
|
|
| 83 |
- Name: "docker-default", |
|
| 84 |
- } |
|
| 85 |
- if tunablesExists() {
|
|
| 86 |
- data.Imports = append(data.Imports, "#include <tunables/global>") |
|
| 87 |
- } else {
|
|
| 88 |
- data.Imports = append(data.Imports, "@{PROC}=/proc/")
|
|
| 89 |
- } |
|
| 90 |
- if abstractionsExists() {
|
|
| 91 |
- data.InnerImports = append(data.InnerImports, "#include <abstractions/base>") |
|
| 92 |
- } |
|
| 93 |
- data.MajorVersion, data.MinorVersion, err = aaparser.GetVersion() |
|
| 94 |
- if err != nil {
|
|
| 95 |
- return err |
|
| 96 |
- } |
|
| 97 |
- data.ExecPath, err = exec.LookPath("docker")
|
|
| 98 |
- if err != nil {
|
|
| 99 |
- return err |
|
| 100 |
- } |
|
| 101 |
- if err := compiled.Execute(out, data); err != nil {
|
|
| 102 |
- return err |
|
| 103 |
- } |
|
| 104 |
- return nil |
|
| 105 |
-} |
|
| 106 |
- |
|
| 107 |
-// check if the tunables/global exist |
|
| 108 |
-func tunablesExists() bool {
|
|
| 109 |
- _, err := os.Stat("/etc/apparmor.d/tunables/global")
|
|
| 110 |
- return err == nil |
|
| 111 |
-} |
|
| 112 |
- |
|
| 113 |
-// check if abstractions/base exist |
|
| 114 |
-func abstractionsExists() bool {
|
|
| 115 |
- _, err := os.Stat("/etc/apparmor.d/abstractions/base")
|
|
| 116 |
- return err == nil |
|
| 117 |
-} |
|
| 118 |
- |
|
| 119 |
-func installAppArmorProfile() error {
|
|
| 120 |
- if !apparmor.IsEnabled() {
|
|
| 121 |
- return nil |
|
| 122 |
- } |
|
| 123 |
- |
|
| 124 |
- // Make sure /etc/apparmor.d exists |
|
| 125 |
- if err := os.MkdirAll(path.Dir(apparmorProfilePath), 0755); err != nil {
|
|
| 126 |
- return err |
|
| 127 |
- } |
|
| 128 |
- |
|
| 129 |
- f, err := os.OpenFile(apparmorProfilePath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644) |
|
| 130 |
- if err != nil {
|
|
| 131 |
- return err |
|
| 132 |
- } |
|
| 133 |
- if err := generateProfile(f); err != nil {
|
|
| 134 |
- f.Close() |
|
| 135 |
- return err |
|
| 136 |
- } |
|
| 137 |
- f.Close() |
|
| 138 |
- |
|
| 139 |
- if err := aaparser.LoadProfile(apparmorProfilePath); err != nil {
|
|
| 140 |
- return err |
|
| 141 |
- } |
|
| 142 |
- |
|
| 143 |
- return nil |
|
| 144 |
-} |
|
| 145 |
- |
|
| 146 |
-func hasAppArmorProfileLoaded(profile string) error {
|
|
| 147 |
- file, err := os.Open("/sys/kernel/security/apparmor/profiles")
|
|
| 148 |
- if err != nil {
|
|
| 149 |
- return err |
|
| 150 |
- } |
|
| 151 |
- r := bufio.NewReader(file) |
|
| 152 |
- for {
|
|
| 153 |
- p, err := r.ReadString('\n')
|
|
| 154 |
- if err != nil {
|
|
| 155 |
- return err |
|
| 156 |
- } |
|
| 157 |
- if strings.HasPrefix(p, profile+" ") {
|
|
| 158 |
- return nil |
|
| 159 |
- } |
|
| 160 |
- } |
|
| 161 |
-} |
| ... | ... |
@@ -21,6 +21,7 @@ import ( |
| 21 | 21 |
"github.com/docker/docker/pkg/reexec" |
| 22 | 22 |
sysinfo "github.com/docker/docker/pkg/system" |
| 23 | 23 |
"github.com/docker/docker/pkg/term" |
| 24 |
+ aaprofile "github.com/docker/docker/profiles/apparmor" |
|
| 24 | 25 |
"github.com/opencontainers/runc/libcontainer" |
| 25 | 26 |
"github.com/opencontainers/runc/libcontainer/apparmor" |
| 26 | 27 |
"github.com/opencontainers/runc/libcontainer/cgroups/systemd" |
| ... | ... |
@@ -33,6 +34,8 @@ import ( |
| 33 | 33 |
const ( |
| 34 | 34 |
DriverName = "native" |
| 35 | 35 |
Version = "0.2" |
| 36 |
+ |
|
| 37 |
+ defaultApparmorProfile = "docker-default" |
|
| 36 | 38 |
) |
| 37 | 39 |
|
| 38 | 40 |
// Driver contains all information for native driver, |
| ... | ... |
@@ -57,13 +60,13 @@ func NewDriver(root string, options []string) (*Driver, error) {
|
| 57 | 57 |
} |
| 58 | 58 |
|
| 59 | 59 |
if apparmor.IsEnabled() {
|
| 60 |
- if err := installAppArmorProfile(); err != nil {
|
|
| 61 |
- apparmorProfiles := []string{"docker-default"}
|
|
| 60 |
+ if err := aaprofile.InstallDefault(defaultApparmorProfile); err != nil {
|
|
| 61 |
+ apparmorProfiles := []string{defaultApparmorProfile}
|
|
| 62 | 62 |
|
| 63 | 63 |
// Allow daemon to run if loading failed, but are active |
| 64 | 64 |
// (possibly through another run, manually, or via system startup) |
| 65 | 65 |
for _, policy := range apparmorProfiles {
|
| 66 |
- if err := hasAppArmorProfileLoaded(policy); err != nil {
|
|
| 66 |
+ if err := aaprofile.IsLoaded(policy); err != nil {
|
|
| 67 | 67 |
return nil, fmt.Errorf("AppArmor enabled on system but the %s profile could not be loaded.", policy)
|
| 68 | 68 |
} |
| 69 | 69 |
} |
| 70 | 70 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,110 @@ |
| 0 |
+// +build linux |
|
| 1 |
+ |
|
| 2 |
+package apparmor |
|
| 3 |
+ |
|
| 4 |
+import ( |
|
| 5 |
+ "bufio" |
|
| 6 |
+ "io" |
|
| 7 |
+ "os" |
|
| 8 |
+ "path" |
|
| 9 |
+ "strings" |
|
| 10 |
+ "text/template" |
|
| 11 |
+ |
|
| 12 |
+ "github.com/docker/docker/pkg/aaparser" |
|
| 13 |
+) |
|
| 14 |
+ |
|
| 15 |
+var ( |
|
| 16 |
+ // profileDirectory is the file store for apparmor profiles and macros. |
|
| 17 |
+ profileDirectory = "/etc/apparmor.d" |
|
| 18 |
+ // defaultProfilePath is the default path for the apparmor profile to be saved. |
|
| 19 |
+ defaultProfilePath = path.Join(profileDirectory, "docker") |
|
| 20 |
+) |
|
| 21 |
+ |
|
| 22 |
+// profileData holds information about the given profile for generation. |
|
| 23 |
+type profileData struct {
|
|
| 24 |
+ // Name is profile name. |
|
| 25 |
+ Name string |
|
| 26 |
+ // ExecPath is the path to the docker binary. |
|
| 27 |
+ ExecPath string |
|
| 28 |
+ // Imports defines the apparmor functions to import, before defining the profile. |
|
| 29 |
+ Imports []string |
|
| 30 |
+ // InnerImports defines the apparmor functions to import in the profile. |
|
| 31 |
+ InnerImports []string |
|
| 32 |
+ // MajorVersion is the apparmor_parser major version. |
|
| 33 |
+ MajorVersion int |
|
| 34 |
+ // MinorVersion is the apparmor_parser minor version. |
|
| 35 |
+ MinorVersion int |
|
| 36 |
+} |
|
| 37 |
+ |
|
| 38 |
+// generateDefault creates an apparmor profile from ProfileData. |
|
| 39 |
+func (p *profileData) generateDefault(out io.Writer) error {
|
|
| 40 |
+ compiled, err := template.New("apparmor_profile").Parse(baseTemplate)
|
|
| 41 |
+ if err != nil {
|
|
| 42 |
+ return err |
|
| 43 |
+ } |
|
| 44 |
+ if macroExists("tunables/global") {
|
|
| 45 |
+ p.Imports = append(p.Imports, "#include <tunables/global>") |
|
| 46 |
+ } else {
|
|
| 47 |
+ p.Imports = append(p.Imports, "@{PROC}=/proc/")
|
|
| 48 |
+ } |
|
| 49 |
+ if macroExists("abstractions/base") {
|
|
| 50 |
+ p.InnerImports = append(p.InnerImports, "#include <abstractions/base>") |
|
| 51 |
+ } |
|
| 52 |
+ if err := compiled.Execute(out, p); err != nil {
|
|
| 53 |
+ return err |
|
| 54 |
+ } |
|
| 55 |
+ return nil |
|
| 56 |
+} |
|
| 57 |
+ |
|
| 58 |
+// macrosExists checks if the passed macro exists. |
|
| 59 |
+func macroExists(m string) bool {
|
|
| 60 |
+ _, err := os.Stat(path.Join(profileDirectory, m)) |
|
| 61 |
+ return err == nil |
|
| 62 |
+} |
|
| 63 |
+ |
|
| 64 |
+// InstallDefault generates a default profile and installs it in the |
|
| 65 |
+// ProfileDirectory with `apparmor_parser`. |
|
| 66 |
+func InstallDefault(name string) error {
|
|
| 67 |
+ // Make sure the path where they want to save the profile exists |
|
| 68 |
+ if err := os.MkdirAll(profileDirectory, 0755); err != nil {
|
|
| 69 |
+ return err |
|
| 70 |
+ } |
|
| 71 |
+ |
|
| 72 |
+ p := profileData{
|
|
| 73 |
+ Name: name, |
|
| 74 |
+ } |
|
| 75 |
+ |
|
| 76 |
+ f, err := os.OpenFile(defaultProfilePath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644) |
|
| 77 |
+ if err != nil {
|
|
| 78 |
+ return err |
|
| 79 |
+ } |
|
| 80 |
+ if err := p.generateDefault(f); err != nil {
|
|
| 81 |
+ f.Close() |
|
| 82 |
+ return err |
|
| 83 |
+ } |
|
| 84 |
+ f.Close() |
|
| 85 |
+ |
|
| 86 |
+ if err := aaparser.LoadProfile(defaultProfilePath); err != nil {
|
|
| 87 |
+ return err |
|
| 88 |
+ } |
|
| 89 |
+ |
|
| 90 |
+ return nil |
|
| 91 |
+} |
|
| 92 |
+ |
|
| 93 |
+// IsLoaded checks if a passed profile as been loaded into the kernel. |
|
| 94 |
+func IsLoaded(name string) error {
|
|
| 95 |
+ file, err := os.Open("/sys/kernel/security/apparmor/profiles")
|
|
| 96 |
+ if err != nil {
|
|
| 97 |
+ return err |
|
| 98 |
+ } |
|
| 99 |
+ r := bufio.NewReader(file) |
|
| 100 |
+ for {
|
|
| 101 |
+ p, err := r.ReadString('\n')
|
|
| 102 |
+ if err != nil {
|
|
| 103 |
+ return err |
|
| 104 |
+ } |
|
| 105 |
+ if strings.HasPrefix(p, name+" ") {
|
|
| 106 |
+ return nil |
|
| 107 |
+ } |
|
| 108 |
+ } |
|
| 109 |
+} |
| 0 | 110 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,50 @@ |
| 0 |
+// +build linux |
|
| 1 |
+ |
|
| 2 |
+package apparmor |
|
| 3 |
+ |
|
| 4 |
+// baseTemplate defines the default apparmor profile for containers. |
|
| 5 |
+const baseTemplate = ` |
|
| 6 |
+{{range $value := .Imports}}
|
|
| 7 |
+{{$value}}
|
|
| 8 |
+{{end}}
|
|
| 9 |
+ |
|
| 10 |
+profile {{.Name}} flags=(attach_disconnected,mediate_deleted) {
|
|
| 11 |
+{{range $value := .InnerImports}}
|
|
| 12 |
+ {{$value}}
|
|
| 13 |
+{{end}}
|
|
| 14 |
+ |
|
| 15 |
+ network, |
|
| 16 |
+ capability, |
|
| 17 |
+ file, |
|
| 18 |
+ umount, |
|
| 19 |
+ |
|
| 20 |
+ deny @{PROC}/* w, # deny write for all files directly in /proc (not in a subdir)
|
|
| 21 |
+ # deny write to files not in /proc/<number>/** or /proc/sys/** |
|
| 22 |
+ deny @{PROC}/{[^1-9],[^1-9][^0-9],[^1-9s][^0-9y][^0-9s],[^1-9][^0-9][^0-9][^0-9]*}/** w,
|
|
| 23 |
+ deny @{PROC}/sys/[^k]** w, # deny /proc/sys except /proc/sys/k* (effectively /proc/sys/kernel)
|
|
| 24 |
+ deny @{PROC}/sys/kernel/{?,??,[^s][^h][^m]**} w, # deny everything except shm* in /proc/sys/kernel/
|
|
| 25 |
+ deny @{PROC}/sysrq-trigger rwklx,
|
|
| 26 |
+ deny @{PROC}/mem rwklx,
|
|
| 27 |
+ deny @{PROC}/kmem rwklx,
|
|
| 28 |
+ deny @{PROC}/kcore rwklx,
|
|
| 29 |
+ |
|
| 30 |
+ deny mount, |
|
| 31 |
+ |
|
| 32 |
+ deny /sys/[^f]*/** wklx, |
|
| 33 |
+ deny /sys/f[^s]*/** wklx, |
|
| 34 |
+ deny /sys/fs/[^c]*/** wklx, |
|
| 35 |
+ deny /sys/fs/c[^g]*/** wklx, |
|
| 36 |
+ deny /sys/fs/cg[^r]*/** wklx, |
|
| 37 |
+ deny /sys/firmware/efi/efivars/** rwklx, |
|
| 38 |
+ deny /sys/kernel/security/** rwklx, |
|
| 39 |
+ |
|
| 40 |
+{{if ge .MajorVersion 2}}{{if ge .MinorVersion 8}}
|
|
| 41 |
+ # suppress ptrace denials when using 'docker ps' or using 'ps' inside a container |
|
| 42 |
+ ptrace (trace,read) peer=docker-default, |
|
| 43 |
+{{end}}{{end}}
|
|
| 44 |
+{{if ge .MajorVersion 2}}{{if ge .MinorVersion 9}}
|
|
| 45 |
+ # docker daemon confinement requires explict allow rule for signal |
|
| 46 |
+ signal (receive) set=(kill,term) peer={{.ExecPath}},
|
|
| 47 |
+{{end}}{{end}}
|
|
| 48 |
+} |
|
| 49 |
+` |