Skip to content

Commit 2d7645f

Browse files
authored
Merge pull request #508 from AkihiroSuda/dev-apparmor
rootless: allow loading an existing AppArmor profile
2 parents 3b86328 + e8ac71b commit 2d7645f

21 files changed

+591
-25
lines changed

Dockerfile

+1
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,7 @@ FROM ubuntu:${UBUNTU_VERSION} AS base
189189
# fuse3 is required by stargz snapshotter
190190
RUN apt-get update && \
191191
apt-get install -qq -y --no-install-recommends \
192+
apparmor \
192193
ca-certificates curl \
193194
iproute2 iptables \
194195
dbus systemd systemd-sysv \

Dockerfile.d/test-integration-rootless.sh

+6
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,12 @@
1616

1717
set -eux -o pipefail
1818
if [[ "$(id -u)" = "0" ]]; then
19+
if [ -e /sys/kernel/security/apparmor/profiles ]; then
20+
# Load the "nerdctl-default" profile for TestRunApparmor
21+
nerdctl apparmor load
22+
fi
23+
24+
# Switch to the rootless user via SSH
1925
systemctl start sshd
2026
exec ssh -o StrictHostKeyChecking=no rootless@localhost "$0" "$@"
2127
else

README.md

+32
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,8 @@ Minor:
135135
- Connecting a container to multiple networks at once: `nerdctl run --net foo --net bar`
136136
- Running [FreeBSD jails](./docs/freebsd.md).
137137
- Better multi-platform support, e.g., `nerdctl pull --all-platforms IMAGE`
138+
- Applying an (existing) AppArmor profile to rootless containers: `nerdctl run --security-opt apparmor=<PROFILE>`.
139+
Use `sudo nerdctl apparmor load` to load the `nerdctl-default` profile.
138140

139141
Trivial:
140142
- Inspecting raw OCI config: `nerdctl container inspect --mode=native` .
@@ -253,6 +255,11 @@ It does not necessarily mean that the corresponding features are missing in cont
253255
- [:whale: nerdctl volume rm](#whale-nerdctl-volume-rm)
254256
- [Namespace management](#namespace-management)
255257
- [:nerd_face: :blue_square: nerdctl namespace ls](#nerd_face-blue_square-nerdctl-namespace-ls)
258+
- [AppArmor profile management](#apparmor-profile-management)
259+
- [:nerd_face: nerdctl apparmor inspect](#nerd_face-nerdctl-apparmor-inspect)
260+
- [:nerd_face: nerdctl apparmor load](#nerd_face-nerdctl-apparmor-load)
261+
- [:nerd_face: nerdctl apparmor ls](#nerd_face-nerdctl-apparmor-ls)
262+
- [:nerd_face: nerdctl apparmor unload](#nerd_face-nerdctl-apparmor-unload)
256263
- [System](#system)
257264
- [:whale: nerdctl events](#whale-nerdctl-events)
258265
- [:whale: nerdctl info](#whale-nerdctl-info)
@@ -922,6 +929,31 @@ Usage: `nerdctl namespace ls [OPTIONS]`
922929
Flags:
923930
- `-q, --quiet`: Only display namespace names
924931

932+
## AppArmor profile management
933+
### :nerd_face: nerdctl apparmor inspect
934+
Display the default AppArmor profile "nerdctl-default". Other profiles cannot be displayed with this command.
935+
936+
Usage: `nerdctl apparmor inspect`
937+
938+
### :nerd_face: nerdctl apparmor load
939+
Load the default AppArmor profile "nerdctl-default". Requires root.
940+
941+
Usage: `nerdctl apparmor load`
942+
943+
### :nerd_face: nerdctl apparmor ls
944+
List the loaded AppArmor profile
945+
946+
Usage: `nerdctl apparmor ls [OPTIONS]`
947+
948+
Flags:
949+
- `-q, --quiet`: Only display volume names
950+
- `--format`: Format the output using the given Go template, e.g, `{{json .}}`
951+
952+
### :nerd_face: nerdctl apparmor unload
953+
Unload an AppArmor profile. The target profile name defaults to "nerdctl-default". Requires root.
954+
955+
Usage: `nerdctl apparmor unload [PROFILE]`
956+
925957
## System
926958
### :whale: nerdctl events
927959
Get real time events from the server.

cmd/nerdctl/apparmor_inspect_linux.go

+46
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
/*
2+
Copyright The containerd Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package main
18+
19+
import (
20+
"fmt"
21+
22+
"github.com/containerd/containerd/contrib/apparmor"
23+
"github.com/containerd/nerdctl/pkg/defaults"
24+
"github.com/spf13/cobra"
25+
)
26+
27+
func newApparmorInspectCommand() *cobra.Command {
28+
cmd := &cobra.Command{
29+
Use: "inspect",
30+
Short: fmt.Sprintf("Display the default AppArmor profile %q. Other profiles cannot be displayed with this command.", defaults.AppArmorProfileName),
31+
Args: cobra.NoArgs,
32+
RunE: apparmorInspectAction,
33+
SilenceUsage: true,
34+
SilenceErrors: true,
35+
}
36+
return cmd
37+
}
38+
39+
func apparmorInspectAction(cmd *cobra.Command, args []string) error {
40+
b, err := apparmor.DumpDefaultProfile(defaults.AppArmorProfileName)
41+
if err != nil {
42+
return err
43+
}
44+
_, err = fmt.Fprintf(cmd.OutOrStdout(), b)
45+
return err
46+
}

cmd/nerdctl/apparmor_linux.go

+39
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
/*
2+
Copyright The containerd Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package main
18+
19+
import (
20+
"github.com/spf13/cobra"
21+
)
22+
23+
func newApparmorCommand() *cobra.Command {
24+
cmd := &cobra.Command{
25+
Category: CategoryManagement,
26+
Use: "apparmor",
27+
Short: "Manage AppArmor profiles",
28+
RunE: unknownSubcommandAction,
29+
SilenceUsage: true,
30+
SilenceErrors: true,
31+
}
32+
cmd.AddCommand(
33+
newApparmorLsCommand(),
34+
newApparmorInspectCommand(),
35+
newApparmorLoadCommand(),
36+
newApparmorUnloadCommand(),
37+
)
38+
return cmd
39+
}

cmd/nerdctl/apparmor_load_linux.go

+43
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
/*
2+
Copyright The containerd Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package main
18+
19+
import (
20+
"fmt"
21+
22+
"github.com/containerd/containerd/contrib/apparmor"
23+
"github.com/containerd/nerdctl/pkg/defaults"
24+
"github.com/sirupsen/logrus"
25+
"github.com/spf13/cobra"
26+
)
27+
28+
func newApparmorLoadCommand() *cobra.Command {
29+
cmd := &cobra.Command{
30+
Use: "load",
31+
Short: fmt.Sprintf("Load the default AppArmor profile %q. Requires root.", defaults.AppArmorProfileName),
32+
Args: cobra.NoArgs,
33+
RunE: apparmorLoadAction,
34+
SilenceUsage: true,
35+
SilenceErrors: true,
36+
}
37+
return cmd
38+
}
39+
40+
func apparmorLoadAction(cmd *cobra.Command, args []string) error {
41+
logrus.Infof("Loading profile %q", defaults.AppArmorProfileName)
42+
return apparmor.LoadDefaultProfile(defaults.AppArmorProfileName)
43+
}

cmd/nerdctl/apparmor_ls_linux.go

+103
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
/*
2+
Copyright The containerd Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package main
18+
19+
import (
20+
"bytes"
21+
"errors"
22+
"fmt"
23+
"text/tabwriter"
24+
"text/template"
25+
26+
"github.com/containerd/nerdctl/pkg/apparmorutil"
27+
"github.com/spf13/cobra"
28+
)
29+
30+
func newApparmorLsCommand() *cobra.Command {
31+
cmd := &cobra.Command{
32+
Use: "ls",
33+
Aliases: []string{"list"},
34+
Short: "List the loaded AppArmor profiles",
35+
Args: cobra.NoArgs,
36+
RunE: apparmorLsAction,
37+
SilenceUsage: true,
38+
SilenceErrors: true,
39+
}
40+
cmd.Flags().BoolP("quiet", "q", false, "Only display profile names")
41+
// Alias "-f" is reserved for "--filter"
42+
cmd.Flags().String("format", "", "Format the output using the given go template")
43+
cmd.RegisterFlagCompletionFunc("format", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
44+
return []string{"json"}, cobra.ShellCompDirectiveNoFileComp
45+
})
46+
return cmd
47+
}
48+
49+
func apparmorLsAction(cmd *cobra.Command, args []string) error {
50+
quiet, err := cmd.Flags().GetBool("quiet")
51+
if err != nil {
52+
return err
53+
}
54+
w := cmd.OutOrStdout()
55+
var tmpl *template.Template
56+
format, err := cmd.Flags().GetString("format")
57+
if err != nil {
58+
return err
59+
}
60+
switch format {
61+
case "", "table":
62+
w = tabwriter.NewWriter(cmd.OutOrStdout(), 4, 8, 4, ' ', 0)
63+
if !quiet {
64+
fmt.Fprintln(w, "NAME\tMODE")
65+
}
66+
case "raw":
67+
return errors.New("unsupported format: \"raw\"")
68+
default:
69+
if quiet {
70+
return errors.New("format and quiet must not be specified together")
71+
}
72+
var err error
73+
tmpl, err = parseTemplate(format)
74+
if err != nil {
75+
return err
76+
}
77+
}
78+
79+
profiles, err := apparmorutil.Profiles()
80+
if err != nil {
81+
return err
82+
}
83+
84+
for _, f := range profiles {
85+
if tmpl != nil {
86+
var b bytes.Buffer
87+
if err := tmpl.Execute(&b, f); err != nil {
88+
return err
89+
}
90+
if _, err = fmt.Fprintf(w, b.String()+"\n"); err != nil {
91+
return err
92+
}
93+
} else if quiet {
94+
fmt.Fprintln(w, f.Name)
95+
} else {
96+
fmt.Fprintf(w, "%s\t%s\n", f.Name, f.Mode)
97+
}
98+
}
99+
if f, ok := w.(Flusher); ok {
100+
return f.Flush()
101+
}
102+
return nil
103+
}

cmd/nerdctl/apparmor_unload_linux.go

+52
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
/*
2+
Copyright The containerd Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package main
18+
19+
import (
20+
"fmt"
21+
22+
"github.com/containerd/nerdctl/pkg/apparmorutil"
23+
"github.com/containerd/nerdctl/pkg/defaults"
24+
"github.com/sirupsen/logrus"
25+
"github.com/spf13/cobra"
26+
)
27+
28+
func newApparmorUnloadCommand() *cobra.Command {
29+
cmd := &cobra.Command{
30+
Use: "unload [PROFILE]",
31+
Short: fmt.Sprintf("Unload an AppArmor profile. The target profile name defaults to %q. Requires root.", defaults.AppArmorProfileName),
32+
Args: cobra.MaximumNArgs(1),
33+
RunE: apparmorUnloadAction,
34+
ValidArgsFunction: apparmorUnloadShellComplete,
35+
SilenceUsage: true,
36+
SilenceErrors: true,
37+
}
38+
return cmd
39+
}
40+
41+
func apparmorUnloadAction(cmd *cobra.Command, args []string) error {
42+
target := defaults.AppArmorProfileName
43+
if len(args) > 0 {
44+
target = args[0]
45+
}
46+
logrus.Infof("Unloading profile %q", target)
47+
return apparmorutil.Unload(target)
48+
}
49+
50+
func apparmorUnloadShellComplete(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
51+
return shellCompleteApparmorProfiles(cmd)
52+
}

cmd/nerdctl/completion_linux.go

+34
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/*
2+
Copyright The containerd Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package main
18+
19+
import (
20+
"github.com/containerd/nerdctl/pkg/apparmorutil"
21+
"github.com/spf13/cobra"
22+
)
23+
24+
func shellCompleteApparmorProfiles(cmd *cobra.Command) ([]string, cobra.ShellCompDirective) {
25+
profiles, err := apparmorutil.Profiles()
26+
if err != nil {
27+
return nil, cobra.ShellCompDirectiveError
28+
}
29+
var names []string // nolint: prealloc
30+
for _, f := range profiles {
31+
names = append(names, f.Name)
32+
}
33+
return names, cobra.ShellCompDirectiveNoFileComp
34+
}

0 commit comments

Comments
 (0)