Skip to content
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
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ require (
github.com/containerd/ttrpc v1.0.2
github.com/containerd/typeurl v1.0.2
github.com/gogo/protobuf v1.3.2
github.com/google/go-cmp v0.5.6
github.com/google/go-containerregistry v0.5.1
github.com/linuxkit/virtsock v0.0.0-20201010232012-f8cee7dfc7a3
github.com/mattn/go-shellwords v1.0.6
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -341,6 +341,8 @@ github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.4 h1:L8R9j+yAqZuZjsqh/z+F1NCffTKKLShY6zXTItVIZ8M=
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ=
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-containerregistry v0.5.1 h1:/+mFTs4AlwsJ/mJe8NDtKb7BxLtbZFpcn8vDsneEkwQ=
github.com/google/go-containerregistry v0.5.1/go.mod h1:Ct15B4yir3PLOP5jsy0GNeYVaIZs/MK/Jz5any1wFW0=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
Expand Down
5 changes: 5 additions & 0 deletions internal/guest/runtime/hcsv2/uvm.go
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,11 @@ func (h *Host) CreateContainer(ctx context.Context, id string, settings *prot.VM
return nil, gcserr.NewHresultError(gcserr.HrVmcomputeSystemAlreadyExists)
}

err = h.securityPolicyEnforcer.EnforceCommandPolicy(id, settings.OCISpecification.Process.Args)
if err != nil {
return nil, errors.Wrapf(err, "container creation denied due to policy")
}

var namespaceID string
criType, isCRI := settings.OCISpecification.Annotations["io.kubernetes.cri.container-type"]
if isCRI {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,7 @@ func (p *MountMonitoringSecurityPolicyEnforcer) EnforceOverlayMountPolicy(contai
p.OverlayMountCalls++
return nil
}

func (p *MountMonitoringSecurityPolicyEnforcer) EnforceCommandPolicy(containerID string, argList []string) (err error) {
return nil
}
6 changes: 3 additions & 3 deletions internal/tools/securitypolicy/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ be downloaded, turned into an ext4, and finally a dm-verity root hash calculated
```toml
[[image]]
name = "rust:1.52.1"
command = "rustc --help"
command = ["rustc", "--help"]
```

### Converted to JSON
Expand All @@ -32,13 +32,13 @@ represented in JSON.
"allow_all": false,
"containers": [
{
"command": "/pause",
"command": ["/pause"],
"layers": [
"16b514057a06ad665f92c02863aca074fd5976c755d26bff16365299169e8415"
]
},
{
"command": "rustc --help",
"command": ["rustc", "--help"],
"layers": [
"fe84c9d5bfddd07a2624d00333cf13c1a9c941f3a261f13ead44fc6a93bc0e7a",
"4dedae42847c704da891a28c25d32201a1ae440bce2aecccfa8e6f03b97a6a6c",
Expand Down
6 changes: 3 additions & 3 deletions internal/tools/securitypolicy/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,8 +79,8 @@ func main() {
}

type Image struct {
Name string `toml:"name"`
Command string `toml:"command"`
Name string `toml:"name"`
Command []string `toml:"command"`
}

type Config struct {
Expand All @@ -102,7 +102,7 @@ func createPolicyFromConfig(config Config) (sp.SecurityPolicy, error) {
// as this is a tool for use by developers currently working
// on security policy implementation code
pausec := sp.SecurityPolicyContainer{
Command: "/pause",
Command: []string{"/pause"},
Layers: []string{"16b514057a06ad665f92c02863aca074fd5976c755d26bff16365299169e8415"},
}
p.Containers = append(p.Containers, pausec)
Expand Down
2 changes: 1 addition & 1 deletion pkg/securitypolicy/securitypolicy.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ type SecurityPolicy struct {
// policy and running the command would be rejected.
type SecurityPolicyContainer struct {
// The command that we will allow the container to execute
Command string `json:"command"`
Command []string `json:"command"`
// An ordered list of dm-verity root hashes for each layer that makes up
// "a container". Containers are constructed as an overlay file system. The
// order that the layers are overlayed is important and needs to be enforced
Expand Down
186 changes: 183 additions & 3 deletions pkg/securitypolicy/securitypolicy_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,16 @@ import (
"testing"
"testing/quick"
"time"

"github.com/google/go-cmp/cmp"
)

const (
maxContainersInGeneratedPolicy = 32
maxLayersInGeneratedContainer = 32
maxGeneratedContainerID = 1000000
maxGeneratedCommandLength = 128
maxGeneratedCommandArgs = 12
maxGeneratedMountTargetLength = 256
rootHashLength = 64
)
Expand Down Expand Up @@ -198,8 +201,9 @@ func Test_EnforceOverlayMountPolicy_Multiple_Instances_Same_Container(t *testing
var containers []SecurityPolicyContainer

for i := 1; i <= int(containersToCreate); i++ {
arg := "command " + strconv.Itoa(i)
c := SecurityPolicyContainer{
Command: "command " + strconv.Itoa(i),
Command: []string{arg},
Layers: []string{"1", "2"},
}

Expand Down Expand Up @@ -278,6 +282,175 @@ func Test_EnforceOverlayMountPolicy_Overlay_Single_Container_Twice_With_Differen
}
}

func Test_EnforceCommandPolicy_Matches(t *testing.T) {
f := func(p *SecurityPolicy) bool {
policy, err := NewStandardSecurityPolicyEnforcer(p)
if err != nil {
return false
}

r := rand.New(rand.NewSource(time.Now().UnixNano()))
containerID := generateContainerId(r)
container := selectContainerFromPolicy(p, r)

layerPaths, err := createValidOverlayForContainer(policy, container, r)
if err != nil {
return false
}

err = policy.EnforceOverlayMountPolicy(containerID, layerPaths)
if err != nil {
return false
}

err = policy.EnforceCommandPolicy(containerID, container.Command)

// getting an error means something is broken
return err == nil
}

if err := quick.Check(f, &quick.Config{MaxCount: 1000}); err != nil {
t.Errorf("Test_EnforceCommandPolicy_Matches: %v", err)
}
}

func Test_EnforceCommandPolicy_NoMatches(t *testing.T) {
f := func(p *SecurityPolicy) bool {
policy, err := NewStandardSecurityPolicyEnforcer(p)
if err != nil {
return false
}

r := rand.New(rand.NewSource(time.Now().UnixNano()))
containerID := generateContainerId(r)
container := selectContainerFromPolicy(p, r)

layerPaths, err := createValidOverlayForContainer(policy, container, r)
if err != nil {
return false
}

err = policy.EnforceOverlayMountPolicy(containerID, layerPaths)
if err != nil {
return false
}

err = policy.EnforceCommandPolicy(containerID, generateCommand(r))

// not getting an error means something is broken
return err != nil
}

if err := quick.Check(f, &quick.Config{MaxCount: 1000}); err != nil {
t.Errorf("Test_EnforceCommandPolicy_NoMatches: %v", err)
}
}

// This is a tricky test.
// The key to understanding it is, that when we have multiple containers
// with the same base aka same mounts and overlay, then we don't know at the
// time of overlay which container from policy is a given container id refers
// to. Instead we have a list of possible container ids for the so far matching
// containers in policy. We can narrow down the list of possible containers
// at the time that we enforce commands.
//
// This test verifies the "narrowing possible container ids that could be
// the container in our policy" functionality works correctly.
func Test_EnforceCommandPolicy_NarrowingMatches(t *testing.T) {
f := func(p *SecurityPolicy) bool {
r := rand.New(rand.NewSource(time.Now().UnixNano()))
// create two additional containers that "share everything"
// except that they have different commands
testContainerOne := generateSecurityPolicyContainer(r, 5)
testContainerTwo := testContainerOne
testContainerTwo.Command = generateCommand(r)
// add new containers to policy before creating enforcer
p.Containers = append(p.Containers, testContainerOne, testContainerTwo)

policy, err := NewStandardSecurityPolicyEnforcer(p)
if err != nil {
return false
}

testContainerOneId := ""
testContainerTwoId := ""
indexForContainerOne := -1
indexForContainerTwo := -1

// mount and overlay all our containers
for index, container := range p.Containers {
containerID := generateContainerId(r)

layerPaths, err := createValidOverlayForContainer(policy, container, r)
if err != nil {
return false
}

err = policy.EnforceOverlayMountPolicy(containerID, layerPaths)
if err != nil {
return false
}

if cmp.Equal(container, testContainerOne) {
testContainerOneId = containerID
indexForContainerOne = index
}
if cmp.Equal(container, testContainerTwo) {
testContainerTwoId = containerID
indexForContainerTwo = index
}
}

// validate our expectations prior to enforcing command policy
containerOneMapping := policy.ContainerIndexToContainerIds[indexForContainerOne]
if len(containerOneMapping) != 2 {
return false
}
for _, id := range containerOneMapping {
if (id != testContainerOneId) && (id != testContainerTwoId) {
return false
}
}

containerTwoMapping := policy.ContainerIndexToContainerIds[indexForContainerTwo]
if len(containerTwoMapping) != 2 {
return false
}
for _, id := range containerTwoMapping {
if (id != testContainerOneId) && (id != testContainerTwoId) {
return false
}
}

// enforce command policy for containerOne
// this will narrow our list of possible ids down
err = policy.EnforceCommandPolicy(testContainerOneId, testContainerOne.Command)
if err != nil {
return false
}

// Ok, we have full setup and we can now verify that when we enforced
// command policy above that it correctly narrowed down containerTwo
updatedMapping := policy.ContainerIndexToContainerIds[indexForContainerTwo]
if len(updatedMapping) != 1 {
return false
}
for _, id := range updatedMapping {
if id != testContainerTwoId {
return false
}
}

return true
}

// This is a more expensive test to run than others, so we run fewer times
// for each run,
if err := quick.Check(f, &quick.Config{MaxCount: 100}); err != nil {
t.Errorf("Test_EnforceCommandPolicy_NarrowingMatches: %v", err)
}
}

//
// Setup and "fixtures" follow...
//
Expand Down Expand Up @@ -313,8 +486,15 @@ func generateRootHash(r *rand.Rand) string {
return randString(r, rootHashLength)
}

func generateCommand(r *rand.Rand) string {
return randVariableString(r, maxGeneratedCommandLength)
func generateCommand(r *rand.Rand) []string {
args := []string{}

numArgs := atLeastOneAtMost(r, maxGeneratedCommandArgs)
for i := 0; i < int(numArgs); i++ {
args = append(args, randVariableString(r, maxGeneratedCommandLength))
}

return args
}

func generateMountTarget(r *rand.Rand) string {
Expand Down
Loading