Skip to content

Commit 62a981e

Browse files
committed
Add apparmor support
1 parent 517072f commit 62a981e

File tree

10 files changed

+476
-84
lines changed

10 files changed

+476
-84
lines changed

ecs-init/apparmor/apparmor.go

+23-9
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@ import (
44
"fmt"
55
"os"
66
"path/filepath"
7+
8+
"github.com/docker/docker/pkg/aaparser"
9+
aaprofile "github.com/docker/docker/profiles/apparmor"
710
)
811

912
const (
@@ -17,8 +20,13 @@ const ecsDefaultProfile = `
1720
profile ecs-default flags=(attach_disconnected,mediate_deleted) {
1821
#include <abstractions/base>
1922
20-
network,
21-
capability,
23+
network inet, # Allow IPv4 traffic
24+
network inet6, # Allow IPv6 traffic
25+
26+
capability net_admin, # Allow network configuration
27+
capability sys_admin, # Allow ECS Agent to invoke the setns system call
28+
capability dac_override, # Allow ECS Agent to file read, write, and execute permission
29+
2230
file,
2331
umount,
2432
# Host (privileged) processes may send signals to container processes.
@@ -52,18 +60,24 @@ profile ecs-default flags=(attach_disconnected,mediate_deleted) {
5260
}
5361
`
5462

63+
var (
64+
isProfileLoaded = aaprofile.IsLoaded
65+
loadPath = aaparser.LoadProfile
66+
createFile = os.Create
67+
)
68+
5569
// LoadDefaultProfile ensures the default profile to be loaded with the given name.
5670
// Returns nil error if the profile is already loaded.
5771
func LoadDefaultProfile(profileName string) error {
58-
yes, err := isLoaded(profileName)
59-
if err != nil {
60-
return err
61-
}
72+
yes, err := isProfileLoaded(profileName)
6273
if yes {
6374
return nil
6475
}
76+
if err != nil {
77+
return err
78+
}
6579

66-
f, err := os.Create(filepath.Join(appArmorProfileDir, profileName))
80+
f, err := createFile(filepath.Join(appArmorProfileDir, profileName))
6781
if err != nil {
6882
return err
6983
}
@@ -74,8 +88,8 @@ func LoadDefaultProfile(profileName string) error {
7488
}
7589
path := f.Name()
7690

77-
if err := load(path); err != nil {
78-
return fmt.Errorf("load apparmor profile %s: %w", path, err)
91+
if err := loadPath(path); err != nil {
92+
return fmt.Errorf("error loading apparmor profile %s: %w", path, err)
7993
}
8094
return nil
8195
}

ecs-init/apparmor/apparmor_test.go

+103
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
// Copyright 2015-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License"). You may
4+
// not use this file except in compliance with the License. A copy of the
5+
// License is located at
6+
//
7+
// http://aws.amazon.com/apache2.0/
8+
//
9+
// or in the "license" file accompanying this file. This file is distributed
10+
// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
11+
// express or implied. See the License for the specific language governing
12+
// permissions and limitations under the License.
13+
14+
package apparmor
15+
16+
import (
17+
"errors"
18+
"os"
19+
"path/filepath"
20+
"testing"
21+
22+
"github.com/containerd/containerd/pkg/apparmor"
23+
"github.com/docker/docker/pkg/aaparser"
24+
aaprofile "github.com/docker/docker/profiles/apparmor"
25+
"github.com/stretchr/testify/assert"
26+
"github.com/stretchr/testify/require"
27+
)
28+
29+
func TestLoadDefaultProfile(t *testing.T) {
30+
testCases := []struct {
31+
name string
32+
profileName string
33+
isLoadedResponse bool
34+
isLoadedError error
35+
loadError error
36+
expectedError error
37+
}{
38+
{
39+
name: "ProfileIsAlreadyLoaded",
40+
profileName: "testProfile.txt",
41+
isLoadedResponse: true,
42+
isLoadedError: nil,
43+
loadError: nil,
44+
expectedError: nil,
45+
},
46+
{
47+
name: "ProfileNotLoaded",
48+
profileName: "testProfile.txt",
49+
isLoadedResponse: false,
50+
isLoadedError: nil,
51+
loadError: nil,
52+
expectedError: nil,
53+
},
54+
{
55+
name: "IsLoadedError",
56+
profileName: "testProfile.txt",
57+
isLoadedResponse: false,
58+
isLoadedError: errors.New("mock isLoaded error"),
59+
loadError: nil,
60+
expectedError: errors.New("mock isLoaded error"),
61+
},
62+
{
63+
name: "LoadProfileError",
64+
profileName: "testProfile.txt",
65+
isLoadedResponse: false,
66+
isLoadedError: nil,
67+
loadError: errors.New("mock load error"),
68+
expectedError: errors.New("mock load error"),
69+
},
70+
}
71+
defer func() {
72+
isProfileLoaded = aaprofile.IsLoaded
73+
loadPath = aaparser.LoadProfile
74+
createFile = os.Create
75+
}()
76+
for _, tc := range testCases {
77+
t.Run(tc.name, func(t *testing.T) {
78+
if !apparmor.HostSupports() {
79+
t.Skip()
80+
}
81+
tmpdir := os.TempDir()
82+
filePath, err := os.MkdirTemp(tmpdir, "test")
83+
require.NoError(t, err)
84+
createFile = func(profileName string) (*os.File, error) {
85+
f, err := os.Create(filepath.Join(filePath, tc.profileName))
86+
return f, err
87+
}
88+
defer os.RemoveAll(filePath)
89+
isProfileLoaded = func(profileName string) (bool, error) {
90+
return tc.isLoadedResponse, tc.isLoadedError
91+
}
92+
loadPath = func(profile string) error {
93+
return tc.loadError
94+
}
95+
err = LoadDefaultProfile(tc.profileName)
96+
if tc.loadError == nil {
97+
assert.Equal(t, tc.expectedError, err)
98+
} else {
99+
assert.Error(t, err)
100+
}
101+
})
102+
}
103+
}

ecs-init/apparmor/apparmor_utils.go

-68
This file was deleted.

ecs-init/engine/engine.go

+9-5
Original file line numberDiff line numberDiff line change
@@ -51,9 +51,13 @@ const (
5151
)
5252

5353
// Injection point for testing purposes
54-
var getDockerClient = func() (dockerClient, error) {
55-
return docker.Client()
56-
}
54+
var (
55+
getDockerClient = func() (dockerClient, error) {
56+
return docker.Client()
57+
}
58+
hostSupports = ctrdapparmor.HostSupports
59+
loadDefaultProfile = apparmor.LoadDefaultProfile
60+
)
5761

5862
func dockerError(err error) error {
5963
return engineError("could not create docker client", err)
@@ -205,9 +209,9 @@ func (e *Engine) PreStartGPU() error {
205209
// PreStartAppArmor sets up the ecs-default AppArmor profile if we're running
206210
// on an AppArmor-enabled system.
207211
func (e *Engine) PreStartAppArmor() error {
208-
if ctrdapparmor.HostSupports() {
212+
if hostSupports() {
209213
log.Infof("pre-start: setting up %s AppArmor profile", apparmor.ECSDefaultProfileName)
210-
return apparmor.LoadDefaultProfile(apparmor.ECSDefaultProfileName)
214+
return loadDefaultProfile(apparmor.ECSDefaultProfileName)
211215
}
212216
return nil
213217
}

ecs-init/engine/engine_test.go

+49
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,13 @@ import (
2424
"os"
2525
"testing"
2626

27+
"github.com/aws/amazon-ecs-agent/ecs-init/apparmor"
2728
"github.com/aws/amazon-ecs-agent/ecs-init/cache"
2829
"github.com/aws/amazon-ecs-agent/ecs-init/gpu"
2930
"github.com/golang/mock/gomock"
31+
"github.com/stretchr/testify/assert"
32+
33+
ctrdapparmor "github.com/containerd/containerd/pkg/apparmor"
3034
)
3135

3236
// getDockerClientMock backs up getDockerClient package-level function and replaces it with the mock passed as
@@ -584,3 +588,48 @@ func TestPostStopCredentialsProxyRouteRemoveError(t *testing.T) {
584588
t.Errorf("engine post-stop error: %v", err)
585589
}
586590
}
591+
592+
func TestPreStartAppArmorSetup(t *testing.T) {
593+
testCases := []struct {
594+
name string
595+
hostSupports bool
596+
loadProfileError error
597+
expectedError error
598+
}{
599+
{
600+
name: "HostNotSupported",
601+
hostSupports: false,
602+
loadProfileError: nil,
603+
expectedError: nil,
604+
},
605+
{
606+
name: "HostSupportedNoError",
607+
hostSupports: true,
608+
loadProfileError: nil,
609+
expectedError: nil,
610+
},
611+
{
612+
name: "HostSupportedWithError",
613+
hostSupports: true,
614+
loadProfileError: errors.New("error loading apparmor profile"),
615+
expectedError: errors.New("error loading apparmor profile"),
616+
},
617+
}
618+
defer func() {
619+
hostSupports = ctrdapparmor.HostSupports
620+
loadDefaultProfile = apparmor.LoadDefaultProfile
621+
}()
622+
for _, tc := range testCases {
623+
t.Run(tc.name, func(t *testing.T) {
624+
hostSupports = func() bool {
625+
return tc.hostSupports
626+
}
627+
loadDefaultProfile = func(profile string) error {
628+
return tc.loadProfileError
629+
}
630+
engine := &Engine{}
631+
err := engine.PreStartAppArmor()
632+
assert.Equal(t, tc.expectedError, err)
633+
})
634+
}
635+
}

ecs-init/go.mod

+2-2
Original file line numberDiff line numberDiff line change
@@ -7,20 +7,19 @@ require (
77
github.com/aws/aws-sdk-go v1.36.0
88
github.com/cihub/seelog v0.0.0-20170130134532-f561c5e57575
99
github.com/containerd/containerd v1.6.18
10+
github.com/docker/docker v23.0.3+incompatible
1011
github.com/docker/go-plugins-helpers v0.0.0-20181025120712-1e6269c305b8
1112
github.com/fsouza/go-dockerclient v0.0.0-20170830181106-98edf3edfae6
1213
github.com/golang/mock v1.6.0
1314
github.com/pkg/errors v0.9.1
1415
github.com/stretchr/testify v1.7.0
15-
golang.org/x/sys v0.6.0
1616
)
1717

1818
require (
1919
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect
2020
github.com/Microsoft/go-winio v0.5.2 // indirect
2121
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e // indirect
2222
github.com/davecgh/go-spew v1.1.1 // indirect
23-
github.com/docker/docker v23.0.3+incompatible // indirect
2423
github.com/docker/go-connections v0.4.0 // indirect
2524
github.com/docker/go-units v0.4.0 // indirect
2625
github.com/gogo/protobuf v1.3.2 // indirect
@@ -39,6 +38,7 @@ require (
3938
github.com/pmezard/go-difflib v1.0.0 // indirect
4039
github.com/sirupsen/logrus v1.8.1 // indirect
4140
golang.org/x/net v0.8.0 // indirect
41+
golang.org/x/sys v0.6.0 // indirect
4242
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect
4343
gopkg.in/yaml.v3 v3.0.1 // indirect
4444
gotest.tools/v3 v3.3.0 // indirect

0 commit comments

Comments
 (0)