Skip to content

Commit 391f242

Browse files
committed
Add apparmor support
1 parent d1f0ec2 commit 391f242

File tree

10 files changed

+475
-84
lines changed

10 files changed

+475
-84
lines changed

ecs-init/apparmor/apparmor.go

+22-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,12 @@ 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
2229
file,
2330
umount,
2431
# Host (privileged) processes may send signals to container processes.
@@ -52,18 +59,24 @@ profile ecs-default flags=(attach_disconnected,mediate_deleted) {
5259
}
5360
`
5461

62+
var (
63+
isProfileLoaded = aaprofile.IsLoaded
64+
loadPath = aaparser.LoadProfile
65+
createFile = os.Create
66+
)
67+
5568
// LoadDefaultProfile ensures the default profile to be loaded with the given name.
5669
// Returns nil error if the profile is already loaded.
5770
func LoadDefaultProfile(profileName string) error {
58-
yes, err := isLoaded(profileName)
59-
if err != nil {
60-
return err
61-
}
71+
yes, err := isProfileLoaded(profileName)
6272
if yes {
6373
return nil
6474
}
75+
if err != nil {
76+
return err
77+
}
6578

66-
f, err := os.Create(filepath.Join(appArmorProfileDir, profileName))
79+
f, err := createFile(filepath.Join(appArmorProfileDir, profileName))
6780
if err != nil {
6881
return err
6982
}
@@ -74,8 +87,8 @@ func LoadDefaultProfile(profileName string) error {
7487
}
7588
path := f.Name()
7689

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

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
@@ -49,9 +49,13 @@ const (
4949
)
5050

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

5660
func dockerError(err error) error {
5761
return engineError("could not create docker client", err)
@@ -198,9 +202,9 @@ func (e *Engine) PreStartGPU() error {
198202
// PreStartAppArmor sets up the ecs-default AppArmor profile if we're running
199203
// on an AppArmor-enabled system.
200204
func (e *Engine) PreStartAppArmor() error {
201-
if ctrdapparmor.HostSupports() {
205+
if hostSupports() {
202206
log.Infof("pre-start: setting up %s AppArmor profile", apparmor.ECSDefaultProfileName)
203-
return apparmor.LoadDefaultProfile(apparmor.ECSDefaultProfileName)
207+
return loadDefaultProfile(apparmor.ECSDefaultProfileName)
204208
}
205209
return nil
206210
}

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)