Skip to content

Commit

Permalink
Add apparmor support
Browse files Browse the repository at this point in the history
  • Loading branch information
SreeeS authored and sparrc committed Dec 20, 2023
1 parent 167d2c3 commit 04a1269
Show file tree
Hide file tree
Showing 10 changed files with 475 additions and 84 deletions.
32 changes: 23 additions & 9 deletions ecs-init/apparmor/apparmor.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ import (
"fmt"
"os"
"path/filepath"

"github.com/docker/docker/pkg/aaparser"
aaprofile "github.com/docker/docker/profiles/apparmor"
)

const (
Expand All @@ -17,8 +20,13 @@ const ecsDefaultProfile = `
profile ecs-default flags=(attach_disconnected,mediate_deleted) {
#include <abstractions/base>
network,
capability,
network inet, # Allow IPv4 traffic
network inet6, # Allow IPv6 traffic
capability net_admin, # Allow network configuration
capability sys_admin, # Allow ECS Agent to invoke the setns system call
capability dac_override, # Allow ECS Agent to file read, write, and execute permission
file,
umount,
# Host (privileged) processes may send signals to container processes.
Expand Down Expand Up @@ -52,18 +60,24 @@ profile ecs-default flags=(attach_disconnected,mediate_deleted) {
}
`

var (
isProfileLoaded = aaprofile.IsLoaded
loadPath = aaparser.LoadProfile
createFile = os.Create
)

// LoadDefaultProfile ensures the default profile to be loaded with the given name.
// Returns nil error if the profile is already loaded.
func LoadDefaultProfile(profileName string) error {
yes, err := isLoaded(profileName)
if err != nil {
return err
}
yes, err := isProfileLoaded(profileName)
if yes {
return nil
}
if err != nil {
return err
}

f, err := os.Create(filepath.Join(appArmorProfileDir, profileName))
f, err := createFile(filepath.Join(appArmorProfileDir, profileName))
if err != nil {
return err
}
Expand All @@ -74,8 +88,8 @@ func LoadDefaultProfile(profileName string) error {
}
path := f.Name()

if err := load(path); err != nil {
return fmt.Errorf("load apparmor profile %s: %w", path, err)
if err := loadPath(path); err != nil {
return fmt.Errorf("error loading apparmor profile %s: %w", path, err)
}
return nil
}
103 changes: 103 additions & 0 deletions ecs-init/apparmor/apparmor_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
// Copyright 2015-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License"). You may
// not use this file except in compliance with the License. A copy of the
// License is located at
//
// http://aws.amazon.com/apache2.0/
//
// or in the "license" file accompanying this file. This file 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 apparmor

import (
"errors"
"os"
"path/filepath"
"testing"

"github.com/containerd/containerd/pkg/apparmor"
"github.com/docker/docker/pkg/aaparser"
aaprofile "github.com/docker/docker/profiles/apparmor"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func TestLoadDefaultProfile(t *testing.T) {
testCases := []struct {
name string
profileName string
isLoadedResponse bool
isLoadedError error
loadError error
expectedError error
}{
{
name: "ProfileIsAlreadyLoaded",
profileName: "testProfile.txt",
isLoadedResponse: true,
isLoadedError: nil,
loadError: nil,
expectedError: nil,
},
{
name: "ProfileNotLoaded",
profileName: "testProfile.txt",
isLoadedResponse: false,
isLoadedError: nil,
loadError: nil,
expectedError: nil,
},
{
name: "IsLoadedError",
profileName: "testProfile.txt",
isLoadedResponse: false,
isLoadedError: errors.New("mock isLoaded error"),
loadError: nil,
expectedError: errors.New("mock isLoaded error"),
},
{
name: "LoadProfileError",
profileName: "testProfile.txt",
isLoadedResponse: false,
isLoadedError: nil,
loadError: errors.New("mock load error"),
expectedError: errors.New("mock load error"),
},
}
defer func() {
isProfileLoaded = aaprofile.IsLoaded
loadPath = aaparser.LoadProfile
createFile = os.Create
}()
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if !apparmor.HostSupports() {
t.Skip()
}
tmpdir := os.TempDir()
filePath, err := os.MkdirTemp(tmpdir, "test")
require.NoError(t, err)
createFile = func(profileName string) (*os.File, error) {
f, err := os.Create(filepath.Join(filePath, tc.profileName))
return f, err
}
defer os.RemoveAll(filePath)
isProfileLoaded = func(profileName string) (bool, error) {
return tc.isLoadedResponse, tc.isLoadedError
}
loadPath = func(profile string) error {
return tc.loadError
}
err = LoadDefaultProfile(tc.profileName)
if tc.loadError == nil {
assert.Equal(t, tc.expectedError, err)
} else {
assert.Error(t, err)
}
})
}
}
68 changes: 0 additions & 68 deletions ecs-init/apparmor/apparmor_utils.go

This file was deleted.

14 changes: 9 additions & 5 deletions ecs-init/engine/engine.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,13 @@ const (
)

// Injection point for testing purposes
var getDockerClient = func() (dockerClient, error) {
return docker.Client()
}
var (
getDockerClient = func() (dockerClient, error) {
return docker.Client()
}
hostSupports = ctrdapparmor.HostSupports
loadDefaultProfile = apparmor.LoadDefaultProfile
)

func dockerError(err error) error {
return engineError("could not create docker client", err)
Expand Down Expand Up @@ -205,9 +209,9 @@ func (e *Engine) PreStartGPU() error {
// PreStartAppArmor sets up the ecs-default AppArmor profile if we're running
// on an AppArmor-enabled system.
func (e *Engine) PreStartAppArmor() error {
if ctrdapparmor.HostSupports() {
if hostSupports() {
log.Infof("pre-start: setting up %s AppArmor profile", apparmor.ECSDefaultProfileName)
return apparmor.LoadDefaultProfile(apparmor.ECSDefaultProfileName)
return loadDefaultProfile(apparmor.ECSDefaultProfileName)
}
return nil
}
Expand Down
49 changes: 49 additions & 0 deletions ecs-init/engine/engine_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,13 @@ import (
"os"
"testing"

"github.com/aws/amazon-ecs-agent/ecs-init/apparmor"
"github.com/aws/amazon-ecs-agent/ecs-init/cache"
"github.com/aws/amazon-ecs-agent/ecs-init/gpu"
"github.com/golang/mock/gomock"
"github.com/stretchr/testify/assert"

ctrdapparmor "github.com/containerd/containerd/pkg/apparmor"
)

// getDockerClientMock backs up getDockerClient package-level function and replaces it with the mock passed as
Expand Down Expand Up @@ -584,3 +588,48 @@ func TestPostStopCredentialsProxyRouteRemoveError(t *testing.T) {
t.Errorf("engine post-stop error: %v", err)
}
}

func TestPreStartAppArmorSetup(t *testing.T) {
testCases := []struct {
name string
hostSupports bool
loadProfileError error
expectedError error
}{
{
name: "HostNotSupported",
hostSupports: false,
loadProfileError: nil,
expectedError: nil,
},
{
name: "HostSupportedNoError",
hostSupports: true,
loadProfileError: nil,
expectedError: nil,
},
{
name: "HostSupportedWithError",
hostSupports: true,
loadProfileError: errors.New("error loading apparmor profile"),
expectedError: errors.New("error loading apparmor profile"),
},
}
defer func() {
hostSupports = ctrdapparmor.HostSupports
loadDefaultProfile = apparmor.LoadDefaultProfile
}()
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
hostSupports = func() bool {
return tc.hostSupports
}
loadDefaultProfile = func(profile string) error {
return tc.loadProfileError
}
engine := &Engine{}
err := engine.PreStartAppArmor()
assert.Equal(t, tc.expectedError, err)
})
}
}
3 changes: 1 addition & 2 deletions ecs-init/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -7,20 +7,19 @@ require (
github.com/aws/aws-sdk-go v1.36.0
github.com/cihub/seelog v0.0.0-20170130134532-f561c5e57575
github.com/containerd/containerd v1.6.18
github.com/docker/docker v23.0.3+incompatible
github.com/docker/go-plugins-helpers v0.0.0-20181025120712-1e6269c305b8
github.com/fsouza/go-dockerclient v0.0.0-20170830181106-98edf3edfae6
github.com/golang/mock v1.6.0
github.com/pkg/errors v0.9.1
github.com/stretchr/testify v1.7.0
golang.org/x/sys v0.6.0
)

require (
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect
github.com/Microsoft/go-winio v0.5.2 // indirect
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/docker/docker v23.0.3+incompatible // indirect
github.com/docker/go-connections v0.4.0 // indirect
github.com/docker/go-units v0.4.0 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
Expand Down
Loading

0 comments on commit 04a1269

Please sign in to comment.