Skip to content

Commit

Permalink
Update AppArmor support for all ECS launch types.
Browse files Browse the repository at this point in the history
- Adjust ecs-agent-default profile to allow full range of required
  permissions for all agent launch types.
- Support ECS_AGENT_APPARMOR_PROFILE env var for opting into alternate
  profiles, or to 'unconfined' profile.
  • Loading branch information
sparrc committed Dec 20, 2023
1 parent 04a1269 commit 5815336
Show file tree
Hide file tree
Showing 7 changed files with 61 additions and 27 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,7 @@ Additionally, the following environment variable(s) can be used to configure the
| `ECS_ALLOW_OFFHOST_INTROSPECTION_ACCESS` | <true | false> | By default, the ecs-init service adds an iptable rule to block access to ECS Agent's introspection port from off-host (or containers in awsvpc network mode), and removes the rule upon stop. If `ECS_ALLOW_OFFHOST_INTROSPECTION_ACCESS` is set to true, this rule will not be added/removed. | false |
| `ECS_OFFHOST_INTROSPECTION_INTERFACE_NAME` | `eth0` | Primary network interface name to be used for blocking offhost agent introspection port access. By default, this value is `eth0` | `eth0` |
| `ECS_AGENT_LABELS` | `{"test.label.1":"value1","test.label.2":"value2"}` | The labels to add to the ECS Agent container. | |
| `ECS_AGENT_APPARMOR_PROFILE` | `unconfined` | Specifies the name of the AppArmor profile to run the ecs-agent container under. This only applies to AppArmor-enabled systems, such as Ubuntu, Debian, and SUSE. If unset, defaults to the profile written out by ecs-init (ecs-agent-default). | `ecs-agent-default` |



Expand Down
48 changes: 29 additions & 19 deletions ecs-init/apparmor/apparmor.go
Original file line number Diff line number Diff line change
@@ -1,41 +1,53 @@
// Copyright 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 (
"fmt"
"os"
"path/filepath"

"github.com/aws/amazon-ecs-agent/ecs-init/config"
"github.com/docker/docker/pkg/aaparser"
aaprofile "github.com/docker/docker/profiles/apparmor"
)

const (
ECSDefaultProfileName = "ecs-default"
appArmorProfileDir = "/etc/apparmor.d"
ECSAgentDefaultProfileName = config.ECSAgentAppArmorDefaultProfileName
appArmorProfileDir = "/etc/apparmor.d"
)

const ecsDefaultProfile = `
const ecsAgentDefaultProfile = `
#include <tunables/global>
profile ecs-default flags=(attach_disconnected,mediate_deleted) {
profile ecs-agent-default flags=(attach_disconnected,mediate_deleted) {
#include <abstractions/base>
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
network inet,
network inet6,
network netlink,
network unix,
capability,
file,
umount,
# Host (privileged) processes may send signals to container processes.
signal (receive) peer=unconfined,
# Container processes may send signals amongst themselves.
signal (send,receive) peer=ecs-default,
signal (send,receive) peer=ecs-agent-default,
# ECS agent requires DBUS send
dbus (send) bus=system,
dbus (send,receive) bus=system,
deny @{PROC}/* w, # deny write for all files directly in /proc (not in a subdir)
# deny write to files not in /proc/<number>/** or /proc/sys/**
Expand All @@ -56,7 +68,8 @@ profile ecs-default flags=(attach_disconnected,mediate_deleted) {
deny /sys/kernel/security/** rwklx,
# suppress ptrace denials when using 'docker ps' or using 'ps' inside a container
ptrace (trace,read,tracedby,readby) peer=ecs-default,
ptrace (trace,read,tracedby,readby) peer=ecs-agent-default,
ptrace (trace,read,tracedby,readby) peer=docker-default,
}
`

Expand All @@ -69,10 +82,7 @@ var (
// 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 := isProfileLoaded(profileName)
if yes {
return nil
}
_, err := isProfileLoaded(profileName)
if err != nil {
return err
}
Expand All @@ -82,7 +92,7 @@ func LoadDefaultProfile(profileName string) error {
return err
}
defer f.Close()
_, err = f.WriteString(ecsDefaultProfile)
_, err = f.WriteString(ecsAgentDefaultProfile)
if err != nil {
return err
}
Expand Down
4 changes: 2 additions & 2 deletions ecs-init/apparmor/apparmor_test.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
// Copyright 2015-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
// Copyright 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/
// 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
Expand Down
16 changes: 15 additions & 1 deletion ecs-init/config/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,11 @@ const (
// defaultCredentialsFetcherSocketPath is set to /var/credentials-fetcher/socket/credentials_fetcher.sock
// in case path is not passed in the env variable
DefaultCredentialsFetcherSocketPath = "/var/credentials-fetcher/socket/credentials_fetcher.sock"

// ECSAgentAppArmorProfileNameEnvVar specifies the AppArmor profile name to use. Only applies
// on AppArmor-enabled platforms (such as Ubuntu and Debian).
ECSAgentAppArmorProfileNameEnvVar = "ECS_AGENT_APPARMOR_PROFILE"
ECSAgentAppArmorDefaultProfileName = "ecs-agent-default"
)

// partitionBucketRegion provides the "partitional" bucket region
Expand Down Expand Up @@ -249,7 +254,7 @@ func CgroupMountpoint() string {

// MountDirectoryEBS returns the location on disk where EBS volumes will be mounted
func MountDirectoryEBS() string {
return directoryPrefix + "/mnt/ecs/ebs"
return directoryPrefix + "/mnt/ecs/ebs"
}

// HostCertsDirPath() returns the CA store path on the host
Expand Down Expand Up @@ -336,6 +341,15 @@ func RunningInExternal() bool {
return envVar == "true"
}

// ECSAgentApparmorProfileName returns the name of the AppArmor profile to use.
func ECSAgentAppArmorProfileName() string {
envVar := os.Getenv(ECSAgentAppArmorProfileNameEnvVar)
if len(strings.TrimSpace(envVar)) == 0 {
return ECSAgentAppArmorDefaultProfileName
}
return envVar
}

func agentArtifactName(version string, arch string) (string, error) {
var interpose string
switch arch {
Expand Down
10 changes: 10 additions & 0 deletions ecs-init/config/common_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,16 @@ func TestDockerUnixSocketWithDockerHost(t *testing.T) {
}
}

func TestECSAgentAppArmorProfileName(t *testing.T) {
profile := ECSAgentAppArmorProfileName()
assert.Equal(t, profile, "ecs-agent-default")

os.Setenv(ECSAgentAppArmorProfileNameEnvVar, "docker-default")
defer os.Unsetenv(ECSAgentAppArmorProfileNameEnvVar)
profile = ECSAgentAppArmorProfileName()
assert.Equal(t, profile, "docker-default")
}

func TestGetAgentPartitionBucketRegion(t *testing.T) {
testCases := []struct {
region string
Expand Down
3 changes: 1 addition & 2 deletions ecs-init/docker/docker_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ package docker
import (
"fmt"

"github.com/aws/amazon-ecs-agent/ecs-init/apparmor"
"github.com/aws/amazon-ecs-agent/ecs-init/config"
ctrdapparmor "github.com/containerd/containerd/pkg/apparmor"
godocker "github.com/fsouza/go-dockerclient"
Expand Down Expand Up @@ -67,7 +66,7 @@ func createHostConfig(binds []string) *godocker.HostConfig {
}

if ctrdapparmor.HostSupports() {
hostConfig.SecurityOpt = []string{fmt.Sprintf("apparmor:%s", apparmor.ECSDefaultProfileName)}
hostConfig.SecurityOpt = []string{fmt.Sprintf("apparmor:%s", config.ECSAgentAppArmorProfileName())}
}

if config.RunPrivileged() {
Expand Down
6 changes: 3 additions & 3 deletions ecs-init/engine/engine.go
Original file line number Diff line number Diff line change
Expand Up @@ -206,12 +206,12 @@ func (e *Engine) PreStartGPU() error {
return nil
}

// PreStartAppArmor sets up the ecs-default AppArmor profile if we're running
// PreStartAppArmor sets up the ecs-agent-default AppArmor profile if we're running
// on an AppArmor-enabled system.
func (e *Engine) PreStartAppArmor() error {
if hostSupports() {
log.Infof("pre-start: setting up %s AppArmor profile", apparmor.ECSDefaultProfileName)
return loadDefaultProfile(apparmor.ECSDefaultProfileName)
log.Infof("pre-start: setting up %s AppArmor profile", apparmor.ECSAgentDefaultProfileName)
return loadDefaultProfile(apparmor.ECSAgentDefaultProfileName)
}
return nil
}
Expand Down

0 comments on commit 5815336

Please sign in to comment.