Skip to content

Commit

Permalink
gMSA support on Linux - capability advertisement
Browse files Browse the repository at this point in the history
*changes to ecs-init to establish communication of agent with
credentials-fetcher daemon
*changes to agent to advertise gMSA capability on Linux
  • Loading branch information
saikiranakula-amzn committed Oct 25, 2022
1 parent 32fcc33 commit dd9ed75
Show file tree
Hide file tree
Showing 15 changed files with 322 additions and 55 deletions.
8 changes: 8 additions & 0 deletions agent/app/agent_capability.go
Original file line number Diff line number Diff line change
Expand Up @@ -304,6 +304,14 @@ func (agent *ecsAgent) appendDockerDependentCapabilities(capabilities []*ecs.Att
return capabilities
}

func (agent *ecsAgent) appendGMSACapabilities(capabilities []*ecs.Attribute) []*ecs.Attribute {
if agent.cfg.GMSACapable {
return appendNameOnlyAttribute(capabilities, attributePrefix+capabilityGMSA)
}

return capabilities
}

func (agent *ecsAgent) appendLoggingDriverCapabilities(capabilities []*ecs.Attribute) []*ecs.Attribute {
knownVersions := make(map[dockerclient.DockerVersion]struct{})
// Determine known API versions. Known versions are used exclusively for logging-driver enablement, since none of
Expand Down
26 changes: 26 additions & 0 deletions agent/app/agent_capability_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1416,3 +1416,29 @@ func TestAppendAndRemoveAttributes(t *testing.T) {
Name: aws.String("cap-2"),
})
}

func TestAppendGMSACapabilities(t *testing.T) {
var inputCapabilities []*ecs.Attribute
var expectedCapabilities []*ecs.Attribute

expectedCapabilities = append(expectedCapabilities,
[]*ecs.Attribute{
{
Name: aws.String(attributePrefix + capabilityGMSA),
},
}...)

agent := &ecsAgent{
cfg: &config.Config{
GMSACapable: true,
},
}

capabilities := agent.appendGMSACapabilities(inputCapabilities)

assert.Equal(t, len(expectedCapabilities), len(capabilities))
for i, expected := range expectedCapabilities {
assert.Equal(t, aws.StringValue(expected.Name), aws.StringValue(capabilities[i].Name))
assert.Equal(t, aws.StringValue(expected.Value), aws.StringValue(capabilities[i].Value))
}
}
4 changes: 0 additions & 4 deletions agent/app/agent_capability_unix.go
Original file line number Diff line number Diff line change
Expand Up @@ -200,10 +200,6 @@ func (agent *ecsAgent) appendFirelensConfigCapabilities(capabilities []*ecs.Attr
return appendNameOnlyAttribute(capabilities, attributePrefix+capabilityFirelensConfigS3)
}

func (agent *ecsAgent) appendGMSACapabilities(capabilities []*ecs.Attribute) []*ecs.Attribute {
return capabilities
}

func (agent *ecsAgent) appendIPv6Capability(capabilities []*ecs.Attribute) []*ecs.Attribute {
return appendNameOnlyAttribute(capabilities, attributePrefix+taskENIIPv6AttributeSuffix)
}
Expand Down
10 changes: 0 additions & 10 deletions agent/app/agent_capability_unix_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -913,16 +913,6 @@ func TestFirelensConfigCapabilitiesUnix(t *testing.T) {
assert.Contains(t, capabilities, &ecs.Attribute{Name: aws.String(attributePrefix + capabilityFirelensConfigS3)})
}

func TestAppendGMSACapabilities(t *testing.T) {
var inputCapabilities []*ecs.Attribute

agent := &ecsAgent{}

capabilities := agent.appendGMSACapabilities(inputCapabilities)
assert.Equal(t, len(inputCapabilities), len(capabilities))
assert.EqualValues(t, capabilities, inputCapabilities)
}

func TestAppendFSxWindowsFileServerCapabilities(t *testing.T) {
var inputCapabilities []*ecs.Attribute

Expand Down
8 changes: 0 additions & 8 deletions agent/app/agent_capability_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,14 +100,6 @@ func (agent *ecsAgent) appendFirelensConfigCapabilities(capabilities []*ecs.Attr
return capabilities
}

func (agent *ecsAgent) appendGMSACapabilities(capabilities []*ecs.Attribute) []*ecs.Attribute {
if agent.cfg.GMSACapable {
return appendNameOnlyAttribute(capabilities, attributePrefix+capabilityGMSA)
}

return capabilities
}

func (agent *ecsAgent) appendEFSVolumePluginCapabilities(capabilities []*ecs.Attribute, pluginCapability string) []*ecs.Attribute {
return capabilities
}
Expand Down
26 changes: 0 additions & 26 deletions agent/app/agent_capability_windows_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -225,32 +225,6 @@ func TestSupportedCapabilitiesWindows(t *testing.T) {
}
}

func TestAppendGMSACapabilities(t *testing.T) {
var inputCapabilities []*ecs.Attribute
var expectedCapabilities []*ecs.Attribute

expectedCapabilities = append(expectedCapabilities,
[]*ecs.Attribute{
{
Name: aws.String(attributePrefix + capabilityGMSA),
},
}...)

agent := &ecsAgent{
cfg: &config.Config{
GMSACapable: true,
},
}

capabilities := agent.appendGMSACapabilities(inputCapabilities)

assert.Equal(t, len(expectedCapabilities), len(capabilities))
for i, expected := range expectedCapabilities {
assert.Equal(t, aws.StringValue(expected.Name), aws.StringValue(capabilities[i].Name))
assert.Equal(t, aws.StringValue(expected.Value), aws.StringValue(capabilities[i].Value))
}
}

func TestAppendGMSACapabilitiesFalse(t *testing.T) {
var inputCapabilities []*ecs.Attribute
var expectedCapabilities []*ecs.Attribute
Expand Down
6 changes: 5 additions & 1 deletion agent/config/config_unix.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ func DefaultConfig() Config {
PollingMetricsWaitDuration: DefaultPollingMetricsWaitDuration,
NvidiaRuntime: DefaultNvidiaRuntime,
CgroupCPUPeriod: defaultCgroupCPUPeriod,
GMSACapable: false,
GMSACapable: isGMSACapable(),
FSxWindowsFileServerCapable: false,
RuntimeStatsLogFile: defaultRuntimeStatsLogFile,
EnableRuntimeStats: BooleanDefaultFalse{Value: NotSet},
Expand Down Expand Up @@ -135,3 +135,7 @@ func (cfg *Config) platformString() string {
func getConfigFileName() (string, error) {
return utils.DefaultIfBlank(os.Getenv("ECS_AGENT_CONFIG_FILE_PATH"), defaultConfigFileName), nil
}

func isGMSACapable() bool {
return utils.ParseBool(os.Getenv("ECS_GMSA_SUPPORTED"), true)
}
7 changes: 7 additions & 0 deletions agent/config/parse.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,13 @@ import (
cnitypes "github.com/containernetworking/cni/pkg/types"
)

const (
// envSkipDomainJoinCheck is an environment setting that can be used to skip
// domain join check validation. This is useful for integration and
// functional-tests but should not be set for any non-test use-case.
envSkipDomainJoinCheck = "ZZZ_SKIP_DOMAIN_JOIN_CHECK_NOT_SUPPORTED_IN_PRODUCTION"
)

func parseCheckpoint(dataDir string) BooleanDefaultFalse {
checkPoint := parseBooleanDefaultFalseConfig("ECS_CHECKPOINT")
if dataDir != "" {
Expand Down
35 changes: 34 additions & 1 deletion agent/config/parse_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
// not use this file except in compliance with the License. A copy of the
// License is located at
//
// httpaws.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 All @@ -18,10 +18,43 @@ package config

import (
"errors"
"os"
"strings"

"github.com/aws/amazon-ecs-agent/agent/utils"
"github.com/cihub/seelog"
)

func parseGMSACapability() bool {
envStatus := utils.ParseBool(os.Getenv("ECS_GMSA_SUPPORTED"), true)
if envStatus {
// Check if domain join check override is present
skipDomainJoinCheck := utils.ParseBool(os.Getenv(envSkipDomainJoinCheck), false)
if skipDomainJoinCheck {
seelog.Errorf("Skipping domain join validation based on environment override")
return true
}

// check if the credentials fetcher socket is created and exists
// this env variable is set in ecs-init module
if credentialsfetcherHostDir := os.Getenv("CREDENTIALS_FETCHER_HOST_DIR"); credentialsfetcherHostDir != "" {
_, err := os.Stat(credentialsfetcherHostDir)
if err != nil {
if os.IsNotExist(err) {
seelog.Errorf("credentials fetcher socket doesn't exist to support linux gmsa")
return false
}
}
// returns true if the container instance is domain joined
// this env variable is set in ecs-init module
isDomainJoined := utils.ParseBool(os.Getenv("ECS_DOMAIN_JOINED_LINUX_INSTANCE"), false)

if !isDomainJoined {
seelog.Errorf("Unable to determine valid domain join")
}
return isDomainJoined
}
}
return false
}

Expand Down
63 changes: 63 additions & 0 deletions agent/config/parse_linux_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
//go:build linux && unit
// +build linux,unit

// 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 config

import (
"os"
"testing"

"github.com/stretchr/testify/assert"
)

func TestParseGMSACapabilitySupported(t *testing.T) {
os.Setenv("ECS_GMSA_SUPPORTED", "True")
defer os.Unsetenv("ECS_GMSA_SUPPORTED")

os.Setenv("ECS_DOMAIN_JOINED_LINUX_INSTANCE", "True")
defer os.Unsetenv("ECS_DOMAIN_JOINED_LINUX_INSTANCE")

os.Setenv("CREDENTIALS_FETCHER_HOST_DIR", "/var/run")
defer os.Unsetenv("CREDENTIALS_FETCHER_HOST_DIR")

assert.True(t, parseGMSACapability())
}

func TestParseGMSACapabilityNonDomainJoined(t *testing.T) {
os.Setenv("ECS_GMSA_SUPPORTED", "True")
defer os.Unsetenv("ECS_GMSA_SUPPORTED")

os.Setenv("ECS_DOMAIN_JOINED_LINUX_INSTANCE", "False")
defer os.Unsetenv("ECS_DOMAIN_JOINED_LINUX_INSTANCE")

assert.False(t, parseGMSACapability())
}

func TestParseGMSACapabilityUnsupported(t *testing.T) {
os.Setenv("ECS_GMSA_SUPPORTED", "False")
defer os.Unsetenv("ECS_GMSA_SUPPORTED")

assert.False(t, parseGMSACapability())
}

func TestSkipDomainJoinCheckParseGMSACapability(t *testing.T) {
os.Setenv("ECS_GMSA_SUPPORTED", "True")
defer os.Unsetenv("ECS_GMSA_SUPPORTED")
os.Setenv("ZZZ_SKIP_DOMAIN_JOIN_CHECK_NOT_SUPPORTED_IN_PRODUCTION", "True")
defer os.Unsetenv("ZZZ_SKIP_DOMAIN_JOIN_CHECK_NOT_SUPPORTED_IN_PRODUCTION")

assert.True(t, parseGMSACapability())
}
5 changes: 0 additions & 5 deletions agent/config/parse_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,6 @@ import (
)

const (
// envSkipDomainJoinCheck is an environment setting that can be used to skip
// domain join check validation. This is useful for integration and
// functional-tests but should not be set for any non-test use-case.
envSkipDomainJoinCheck = "ZZZ_SKIP_DOMAIN_JOIN_CHECK_NOT_SUPPORTED_IN_PRODUCTION"

// envSkipWindowsServerVersionCheck is an environment setting that can be used
// to skip the windows server version check. This is useful for testing and
// should not be set for any non-test use-case.
Expand Down
23 changes: 23 additions & 0 deletions ecs-init/config/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,12 @@ const (

// DefaultRegionEnvVar is the environment variable for specifying the default AWS region to use.
DefaultRegionEnvVar = "AWS_DEFAULT_REGION"

// ECSGMSASupportEnvVar indicates that the gMSA is supported
ECSGMSASupportEnvVar = "ECS_GMSA_SUPPORTED"

// CredentialsFetcherHostEnvVar is the environment variable that specifies the location of the credentials-fetcher daemon socket.
CredentialsFetcherHostEnvVar = "CREDENTIALS_FETCHER_HOST"
)

// partitionBucketRegion provides the "partitional" bucket region
Expand Down Expand Up @@ -212,6 +218,23 @@ func DockerUnixSocket() (string, bool) {
return "/var/run", false
}

// CredentialsFetcherUnixSocket returns the credentials fetcher daemon socket endpoint and whether it reads from CredentialsFetcherEnvVar
func CredentialsFetcherUnixSocket() (string, bool) {
if credentialsFetcherHost := os.Getenv(CredentialsFetcherHostEnvVar); strings.HasPrefix(credentialsFetcherHost, UnixSocketPrefix) {
return strings.TrimPrefix(credentialsFetcherHost, UnixSocketPrefix), true
}

return "/var/credentials-fetcher/socket/credentials_fetcher.sock", false
}

// HostCredentialsFetcherPath() returns the daemon socket location if it is available
func HostCredentialsFetcherPath() string {
if credentialsFetcherHost, _ := CredentialsFetcherUnixSocket(); len(credentialsFetcherHost) > 0 {
return credentialsFetcherHost
}
return ""
}

// CgroupMountpoint returns the cgroup mountpoint for the system
func CgroupMountpoint() string {
return cgroupMountpoint
Expand Down
27 changes: 27 additions & 0 deletions ecs-init/config/common_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -253,3 +253,30 @@ func TestAgentRunningInExternal(t *testing.T) {
defer os.Unsetenv(ExternalEnvVar)
assert.True(t, RunningInExternal())
}

func TestCredentialsFetcherUnixSocketWithoutCredentialsFetcherHost(t *testing.T) {
// Make sure that the env variable is not set
os.Unsetenv("CREDENTIALS_FETCHER_HOST")

credentialsFetcherUnixSocketSourcePath, fromEnv := CredentialsFetcherUnixSocket()

if credentialsFetcherUnixSocketSourcePath != "/var/credentials-fetcher/socket/credentials_fetcher.sock" {
t.Error("CredentialsFetcherUnixSocket() should be \"/var/credentials-fetcher/socket\"")
}
if fromEnv {
t.Error("CredentialsFetcherUnixSocket() should return the default instead of reading from CREDENTIALS_FETCHER_HOST when CREDENTIALS_FETCHER_HOST isn't set")
}
}

func TestCredentialsFetcherUnixSocketWithCredentialsFetcherHost(t *testing.T) {
os.Setenv("CREDENTIALS_FETCHER_HOST", "unix:///foo/bar")
defer os.Unsetenv("CREDENTIALS_FETCHER_HOST")

credentialsFetcherUnixSocketSourcePath, fromEnv := CredentialsFetcherUnixSocket()
if credentialsFetcherUnixSocketSourcePath != "/foo/bar" {
t.Error("CredentialsFetcherUnixSocket() should be \"/foo/bar\"")
}
if !fromEnv {
t.Error("CredentialsFetcherUnixSocket() should read from environment variable CREDENTIALS_FETCHER_HOST, when CREDENTIALS_FETCHER_HOST is set")
}
}
Loading

0 comments on commit dd9ed75

Please sign in to comment.