Skip to content

Commit

Permalink
rootless: allow loading an existing AppArmor profile
Browse files Browse the repository at this point in the history
nerdctl run
---
Rootless nerdctl now applies the `nerdctl-default` profile to containers by default,
if it is already loaded with `sudo nerdctl apparmor load`.

Still defaults to "unconfined" when the profile is not loaded.

nerdctl info
---
Now `nerdctl info` shows "apparmor" when the AppArmor kernel module is enabled,
regardless to whether the `nerdctl-default` profile is loaded or not.
When the profile is not available, `nerdctl info` shows a warning message.

Signed-off-by: Akihiro Suda <akihiro.suda.cz@hco.ntt.co.jp>
  • Loading branch information
AkihiroSuda committed Nov 17, 2021
1 parent 3b86328 commit a88f558
Show file tree
Hide file tree
Showing 20 changed files with 570 additions and 25 deletions.
1 change: 1 addition & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,7 @@ FROM ubuntu:${UBUNTU_VERSION} AS base
# fuse3 is required by stargz snapshotter
RUN apt-get update && \
apt-get install -qq -y --no-install-recommends \
apparmor \
ca-certificates curl \
iproute2 iptables \
dbus systemd systemd-sysv \
Expand Down
6 changes: 6 additions & 0 deletions Dockerfile.d/test-integration-rootless.sh
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,12 @@

set -eux -o pipefail
if [[ "$(id -u)" = "0" ]]; then
if [ -e /sys/kernel/security/apparmor/profiles ]; then
# Load the "nerdctl-default" profile for TestRunApparmor
nerdctl apparmor load
fi

# Switch to the rootless user via SSH
systemctl start sshd
exec ssh -o StrictHostKeyChecking=no rootless@localhost "$0" "$@"
else
Expand Down
32 changes: 32 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,8 @@ Minor:
- Connecting a container to multiple networks at once: `nerdctl run --net foo --net bar`
- Running [FreeBSD jails](./docs/freebsd.md).
- Better multi-platform support, e.g., `nerdctl pull --all-platforms IMAGE`
- Applying an (existing) AppArmor profile to rootless containers: `nerdctl run --security-opt apparmor=<PROFILE>`.
Use `sudo nerdctl apparmor load` to load the `nerdctl-default` profile.

Trivial:
- Inspecting raw OCI config: `nerdctl container inspect --mode=native` .
Expand Down Expand Up @@ -253,6 +255,11 @@ It does not necessarily mean that the corresponding features are missing in cont
- [:whale: nerdctl volume rm](#whale-nerdctl-volume-rm)
- [Namespace management](#namespace-management)
- [:nerd_face: :blue_square: nerdctl namespace ls](#nerd_face-blue_square-nerdctl-namespace-ls)
- [AppArmor profile management](#apparmor-profile-management)
- [:nerd_face: nerdctl apparmor inspect](#nerd_face-nerdctl-apparmor-inspect)
- [:nerd_face: nerdctl apparmor load](#nerd_face-nerdctl-apparmor-load)
- [:nerd_face: nerdctl apparmor ls](#nerd_face-nerdctl-apparmor-ls)
- [:nerd_face: nerdctl apparmor unload](#nerd_face-nerdctl-apparmor-unload)
- [System](#system)
- [:whale: nerdctl events](#whale-nerdctl-events)
- [:whale: nerdctl info](#whale-nerdctl-info)
Expand Down Expand Up @@ -922,6 +929,31 @@ Usage: `nerdctl namespace ls [OPTIONS]`
Flags:
- `-q, --quiet`: Only display namespace names

## AppArmor profile management
### :nerd_face: nerdctl apparmor inspect
Display the default AppArmor profile "nerdctl-default". Other profiles cannot be displayed with this command.

Usage: `nerdctl apparmor inspect`

### :nerd_face: nerdctl apparmor load
Load the default AppArmor profile "nerdctl-default". Requires root.

Usage: `nerdctl apparmor load`

### :nerd_face: nerdctl apparmor ls
List the loaded AppArmor profile

Usage: `nerdctl apparmor ls [OPTIONS]`

Flags:
- `-q, --quiet`: Only display volume names
- `--format`: Format the output using the given Go template, e.g, `{{json .}}`

### :nerd_face: nerdctl apparmor unload
Unload an AppArmor profile. The target profile name defaults to "nerdctl-default". Requires root.

Usage: `nerdctl apparmor unload [PROFILE]`

## System
### :whale: nerdctl events
Get real time events from the server.
Expand Down
46 changes: 46 additions & 0 deletions cmd/nerdctl/apparmor_inspect_linux.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package main

import (
"fmt"

"github.com/containerd/containerd/contrib/apparmor"
"github.com/containerd/nerdctl/pkg/defaults"
"github.com/spf13/cobra"
)

func newApparmorInspectCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "inspect",
Short: fmt.Sprintf("Display the default AppArmor profile %q. Other profiles cannot be displayed with this command.", defaults.AppArmorProfileName),
Args: cobra.NoArgs,
RunE: apparmorInspectAction,
SilenceUsage: true,
SilenceErrors: true,
}
return cmd
}

func apparmorInspectAction(cmd *cobra.Command, args []string) error {
b, err := apparmor.DumpDefaultProfile(defaults.AppArmorProfileName)
if err != nil {
return err
}
_, err = fmt.Fprintf(cmd.OutOrStdout(), b)
return err
}
39 changes: 39 additions & 0 deletions cmd/nerdctl/apparmor_linux.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package main

import (
"github.com/spf13/cobra"
)

func newApparmorCommand() *cobra.Command {
cmd := &cobra.Command{
Category: CategoryManagement,
Use: "apparmor",
Short: "Manage AppArmor profiles",
RunE: unknownSubcommandAction,
SilenceUsage: true,
SilenceErrors: true,
}
cmd.AddCommand(
newApparmorLsCommand(),
newApparmorInspectCommand(),
newApparmorLoadCommand(),
newApparmorUnloadCommand(),
)
return cmd
}
43 changes: 43 additions & 0 deletions cmd/nerdctl/apparmor_load_linux.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package main

import (
"fmt"

"github.com/containerd/containerd/contrib/apparmor"
"github.com/containerd/nerdctl/pkg/defaults"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
)

func newApparmorLoadCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "load",
Short: fmt.Sprintf("Load the default AppArmor profile %q. Requires root.", defaults.AppArmorProfileName),
Args: cobra.NoArgs,
RunE: apparmorLoadAction,
SilenceUsage: true,
SilenceErrors: true,
}
return cmd
}

func apparmorLoadAction(cmd *cobra.Command, args []string) error {
logrus.Infof("Loading profile %q", defaults.AppArmorProfileName)
return apparmor.LoadDefaultProfile(defaults.AppArmorProfileName)
}
103 changes: 103 additions & 0 deletions cmd/nerdctl/apparmor_ls_linux.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package main

import (
"bytes"
"errors"
"fmt"
"text/tabwriter"
"text/template"

"github.com/containerd/nerdctl/pkg/apparmorutil"
"github.com/spf13/cobra"
)

func newApparmorLsCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "ls",
Aliases: []string{"list"},
Short: "List the loaded AppArmor profiles",
Args: cobra.NoArgs,
RunE: apparmorLsAction,
SilenceUsage: true,
SilenceErrors: true,
}
cmd.Flags().BoolP("quiet", "q", false, "Only display profile names")
// Alias "-f" is reserved for "--filter"
cmd.Flags().String("format", "", "Format the output using the given go template")
cmd.RegisterFlagCompletionFunc("format", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
return []string{"json"}, cobra.ShellCompDirectiveNoFileComp
})
return cmd
}

func apparmorLsAction(cmd *cobra.Command, args []string) error {
quiet, err := cmd.Flags().GetBool("quiet")
if err != nil {
return err
}
w := cmd.OutOrStdout()
var tmpl *template.Template
format, err := cmd.Flags().GetString("format")
if err != nil {
return err
}
switch format {
case "", "table":
w = tabwriter.NewWriter(cmd.OutOrStdout(), 4, 8, 4, ' ', 0)
if !quiet {
fmt.Fprintln(w, "NAME\tMODE")
}
case "raw":
return errors.New("unsupported format: \"raw\"")
default:
if quiet {
return errors.New("format and quiet must not be specified together")
}
var err error
tmpl, err = parseTemplate(format)
if err != nil {
return err
}
}

profiles, err := apparmorutil.Profiles()
if err != nil {
return err
}

for _, f := range profiles {
if tmpl != nil {
var b bytes.Buffer
if err := tmpl.Execute(&b, f); err != nil {
return err
}
if _, err = fmt.Fprintf(w, b.String()+"\n"); err != nil {
return err
}
} else if quiet {
fmt.Fprintln(w, f.Name)
} else {
fmt.Fprintf(w, "%s\t%s\n", f.Name, f.Mode)
}
}
if f, ok := w.(Flusher); ok {
return f.Flush()
}
return nil
}
52 changes: 52 additions & 0 deletions cmd/nerdctl/apparmor_unload_linux.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package main

import (
"fmt"

"github.com/containerd/nerdctl/pkg/apparmorutil"
"github.com/containerd/nerdctl/pkg/defaults"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
)

func newApparmorUnloadCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "unload [PROFILE]",
Short: fmt.Sprintf("Unload an AppArmor profile. The target profile name defaults to %q. Requires root.", defaults.AppArmorProfileName),
Args: cobra.MaximumNArgs(1),
RunE: apparmorUnloadAction,
ValidArgsFunction: apparmorUnloadShellComplete,
SilenceUsage: true,
SilenceErrors: true,
}
return cmd
}

func apparmorUnloadAction(cmd *cobra.Command, args []string) error {
target := defaults.AppArmorProfileName
if len(args) > 0 {
target = args[0]
}
logrus.Infof("Unloading profile %q", target)
return apparmorutil.Unload(target)
}

func apparmorUnloadShellComplete(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
return shellCompleteApparmorProfiles(cmd)
}
34 changes: 34 additions & 0 deletions cmd/nerdctl/completion_linux.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package main

import (
"github.com/containerd/nerdctl/pkg/apparmorutil"
"github.com/spf13/cobra"
)

func shellCompleteApparmorProfiles(cmd *cobra.Command) ([]string, cobra.ShellCompDirective) {
profiles, err := apparmorutil.Profiles()
if err != nil {
return nil, cobra.ShellCompDirectiveError
}
var names []string // nolint: prealloc
for _, f := range profiles {
names = append(names, f.Name)
}
return names, cobra.ShellCompDirectiveNoFileComp
}
Loading

0 comments on commit a88f558

Please sign in to comment.