Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Common functions to support gMSA linux #3448

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions agent/api/container/container.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,10 @@ package container

import (
"encoding/json"
"errors"
"fmt"
"strconv"
"strings"
"sync"
"time"

Expand Down Expand Up @@ -1325,6 +1327,44 @@ func (c *Container) UpdateManagedAgentSentStatus(agentName string, status apicon
return false
}

// RequiresCredentialSpec checks if container needs a credentialspec resource
func (c *Container) RequiresCredentialSpec() bool {
credSpec, err := c.getCredentialSpec()
if err != nil || credSpec == "" {
return false
}

return true
}

// GetCredentialSpec is used to retrieve the current credentialspec resource
func (c *Container) GetCredentialSpec() (string, error) {
return c.getCredentialSpec()
}

func (c *Container) getCredentialSpec() (string, error) {
c.lock.RLock()
defer c.lock.RUnlock()

if c.DockerConfig.HostConfig == nil {
return "", errors.New("empty container hostConfig")
}

hostConfig := &dockercontainer.HostConfig{}
err := json.Unmarshal([]byte(*c.DockerConfig.HostConfig), hostConfig)
if err != nil || len(hostConfig.SecurityOpt) == 0 {
return "", errors.New("unable to obtain security options from container hostConfig")
}

for _, opt := range hostConfig.SecurityOpt {
if strings.HasPrefix(opt, "credentialspec") {
return opt, nil
}
}

return "", errors.New("unable to obtain credentialspec")
}

func (c *Container) GetManagedAgentStatus(agentName string) apicontainerstatus.ManagedAgentStatus {
c.lock.RLock()
defer c.lock.RUnlock()
Expand Down
110 changes: 110 additions & 0 deletions agent/api/container/container_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -970,3 +970,113 @@ func TestUpdateManagedAgentSentStatus(t *testing.T) {
})
}
}

func TestRequiresCredentialSpec(t *testing.T) {
testCases := []struct {
name string
container *Container
expectedOutput bool
}{
{
name: "hostconfig_nil",
container: &Container{},
expectedOutput: false,
},
{
name: "invalid_case",
container: getContainer("invalid"),
expectedOutput: false,
},
{
name: "empty_sec_opt",
container: getContainer("{\"NetworkMode\":\"bridge\"}"),
expectedOutput: false,
},
{
name: "missing_credentialspec",
container: getContainer("{\"SecurityOpt\": [\"invalid-sec-opt\"]}"),
expectedOutput: false,
},
{
name: "valid_credentialspec_file",
container: getContainer("{\"SecurityOpt\": [\"credentialspec:file://gmsa_gmsa-acct.json\"]}"),
expectedOutput: true,
},
{
name: "valid_credentialspec_s3",
container: getContainer("{\"SecurityOpt\": [\"credentialspec:arn:aws:s3:::${BucketName}/${ObjectName}\"]}"),
expectedOutput: true,
},
{
name: "valid_credentialspec_ssm",
container: getContainer("{\"SecurityOpt\": [\"credentialspec:arn:aws:ssm:region:aws_account_id:parameter/parameter_name\"]}"),
expectedOutput: true,
},
}

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
assert.Equal(t, tc.expectedOutput, tc.container.RequiresCredentialSpec())
})
}
}

func TestGetCredentialSpecErr(t *testing.T) {
testCases := []struct {
name string
container *Container
expectedOutputString string
expectedErrorString string
}{
{
name: "hostconfig_nil",
container: &Container{},
expectedOutputString: "",
expectedErrorString: "empty container hostConfig",
},
{
name: "invalid_case",
container: getContainer("invalid"),
expectedOutputString: "",
expectedErrorString: "unable to obtain security options from container hostConfig",
},
{
name: "empty_sec_opt",
container: getContainer("{\"NetworkMode\":\"bridge\"}"),
expectedOutputString: "",
expectedErrorString: "unable to obtain security options from container hostConfig",
},
{
name: "missing_credentialspec",
container: getContainer("{\"SecurityOpt\": [\"invalid-sec-opt\"]}"),
expectedOutputString: "",
expectedErrorString: "unable to obtain credentialspec",
},
}

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
expectedOutputStr, err := tc.container.GetCredentialSpec()
assert.Equal(t, tc.expectedOutputString, expectedOutputStr)
assert.EqualError(t, err, tc.expectedErrorString)
})
}
}

func TestGetCredentialSpecHappyPath(t *testing.T) {
c := getContainer("{\"SecurityOpt\": [\"credentialspec:file://gmsa_gmsa-acct.json\"]}")

expectedCredentialSpec := "credentialspec:file://gmsa_gmsa-acct.json"

credentialspec, err := c.GetCredentialSpec()
assert.NoError(t, err)
assert.EqualValues(t, expectedCredentialSpec, credentialspec)
}

func getContainer(hostConfig string) *Container {
c := &Container{
Name: "c",
}
c.DockerConfig.HostConfig = &hostConfig
return c
}
14 changes: 0 additions & 14 deletions agent/api/container/container_unix.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,22 +16,8 @@

package container

import (
"github.com/pkg/errors"
)

const (
// DockerContainerMinimumMemoryInBytes is the minimum amount of
// memory to be allocated to a docker container
DockerContainerMinimumMemoryInBytes = 4 * 1024 * 1024 // 4MB
)

// RequiresCredentialSpec checks if container needs a credentialspec resource
func (c *Container) RequiresCredentialSpec() bool {
return false
}

// GetCredentialSpec is used to retrieve the current credentialspec resource
func (c *Container) GetCredentialSpec() (string, error) {
return "", errors.New("unsupported platform")
}
46 changes: 0 additions & 46 deletions agent/api/container/container_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,54 +16,8 @@

package container

import (
"encoding/json"
"strings"

dockercontainer "github.com/docker/docker/api/types/container"
"github.com/pkg/errors"
)

const (
// DockerContainerMinimumMemoryInBytes is the minimum amount of
// memory to be allocated to a docker container
DockerContainerMinimumMemoryInBytes = 256 * 1024 * 1024 // 256MB
)

// RequiresCredentialSpec checks if container needs a credentialspec resource
func (c *Container) RequiresCredentialSpec() bool {
credSpec, err := c.getCredentialSpec()
if err != nil || credSpec == "" {
return false
}

return true
}

// GetCredentialSpec is used to retrieve the current credentialspec resource
func (c *Container) GetCredentialSpec() (string, error) {
return c.getCredentialSpec()
}

func (c *Container) getCredentialSpec() (string, error) {
c.lock.RLock()
defer c.lock.RUnlock()

if c.DockerConfig.HostConfig == nil {
return "", errors.New("empty container hostConfig")
}

hostConfig := &dockercontainer.HostConfig{}
err := json.Unmarshal([]byte(*c.DockerConfig.HostConfig), hostConfig)
if err != nil || len(hostConfig.SecurityOpt) == 0 {
return "", errors.New("unable to obtain security options from container hostConfig")
}

for _, opt := range hostConfig.SecurityOpt {
if strings.HasPrefix(opt, "credentialspec") {
return opt, nil
}
}

return "", errors.New("unable to obtain credentialspec")
}
133 changes: 0 additions & 133 deletions agent/api/container/container_windows_test.go

This file was deleted.

Loading