Skip to content
Open
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
50 changes: 50 additions & 0 deletions internal/gcs-sidecar/handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import (
"path/filepath"
"time"

"regexp"

"github.com/Microsoft/hcsshim/hcn"
"github.com/Microsoft/hcsshim/internal/bridgeutils/commonutils"
"github.com/Microsoft/hcsshim/internal/fsformatter"
Expand All @@ -35,6 +37,8 @@ const (
UVMContainerID = "00000000-0000-0000-0000-000000000000"
)

var volumeGUIDRe = regexp.MustCompile(`^\\\\\?\\Volume\{([0-9A-Fa-f\-]+)\}\\Files$`)

// - Handler functions handle the incoming message requests. It
// also enforces security policy for confidential cwcow containers.
// - These handler functions may do some additional processing before
Expand Down Expand Up @@ -489,6 +493,14 @@ func (b *Bridge) lifecycleNotification(req *request) (err error) {
return nil
}

func volumeGUIDFromLayerPath(p string) (string, bool) {
m := volumeGUIDRe.FindStringSubmatch(p)
if len(m) != 2 {
return "", false
}
return m[1], true
}

func (b *Bridge) modifySettings(req *request) (err error) {
ctx, span := oc.StartSpan(req.ctx, "sidecar::modifySettings")
defer span.End()
Expand Down Expand Up @@ -622,6 +634,20 @@ func (b *Bridge) modifySettings(req *request) (err error) {
return errors.Wrap(err, "CIM mount is denied by policy")
}

// Volume GUID from request
volGUID := wcowBlockCimMounts.VolumeGUID.String()

// Cache hashes along with volGUID
b.hostState.blockCIMVolumeHashes[volGUID] = hashesToVerify

// Store the containerID (associated with volGUID) to mark that hashes are verified for this container
if _, ok := b.hostState.blockCIMVolumeContainers[volGUID]; !ok {
b.hostState.blockCIMVolumeContainers[volGUID] = make(map[string]struct{})
}
b.hostState.blockCIMVolumeContainers[volGUID][containerID] = struct{}{}

log.G(ctx).Tracef("Cached %d verified CIM layer hashes for volume %s (container %s)", len(hashesToVerify), volGUID, containerID)

if len(layerCIMs) > 1 {
_, err = cimfs.MountMergedVerifiedBlockCIMs(layerCIMs[0], layerCIMs[1:], wcowBlockCimMounts.MountFlags, wcowBlockCimMounts.VolumeGUID, layerDigests[0])
if err != nil {
Expand Down Expand Up @@ -653,6 +679,30 @@ func (b *Bridge) modifySettings(req *request) (err error) {
log.G(ctx).Tracef("CWCOWCombinedLayers:: ContainerID: %v, ContainerRootPath: %v, Layers: %v, ScratchPath: %v",
containerID, settings.CombinedLayers.ContainerRootPath, settings.CombinedLayers.Layers, settings.CombinedLayers.ScratchPath)

// The layers size is only one, this is just defensive checking
if len(settings.CombinedLayers.Layers) == 1 {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

feels like we should do something if it's not 1? The request is coming from the host, so this can be set to anything?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

also, consider reducing the nested ifs, by returning error early.

layerPath := settings.CombinedLayers.Layers[0].Path
if guidStr, ok := volumeGUIDFromLayerPath(layerPath); ok {
hashes, haveHashes := b.hostState.blockCIMVolumeHashes[guidStr]
if haveHashes {
// Only do this if it wasn't already enforced by the ResourceTypeWCOWBlockCims request
containers := b.hostState.blockCIMVolumeContainers[guidStr]
if _, seen := containers[containerID]; !seen {
// This is a container with CIMs already mounted (container with similar layers as an existing container). Call EnforceVerifiedCIMsPolicy on this new container
log.G(ctx).Tracef("Verified CIM hashes for reused mount volume %s (container %s)", guidStr, containerID)
if err := b.hostState.securityOptions.PolicyEnforcer.EnforceVerifiedCIMsPolicy(ctx, containerID, hashes); err != nil {
return fmt.Errorf("CIM mount is denied by policy for this container: %w", err)
}
containers[containerID] = struct{}{}
}
} else {
log.G(ctx).Debugf("No cached CIM hashes found for volume %s", guidStr)
}
} else {
return fmt.Errorf("no cim hashes found for container ID %s", containerID)
}
}

//Since unencrypted scratch is not an option, always pass true
if err := b.hostState.securityOptions.PolicyEnforcer.EnforceScratchMountPolicy(ctx, settings.CombinedLayers.ContainerRootPath, true); err != nil {
return fmt.Errorf("scratch mounting denied by policy: %w", err)
Expand Down
11 changes: 9 additions & 2 deletions internal/gcs-sidecar/host.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,11 @@ type Host struct {
securityOptions *securitypolicy.SecurityOptions
containersMutex sync.Mutex
containers map[string]*Container

// mapping of volumeGUID to container layer hashes
blockCIMVolumeHashes map[string][]string
// mapping of volumeGUID to container IDs
blockCIMVolumeContainers map[string]map[string]struct{}
}

type Container struct {
Expand Down Expand Up @@ -49,8 +54,10 @@ func NewHost(initialEnforcer securitypolicy.SecurityPolicyEnforcer, logWriter io
logWriter,
)
return &Host{
containers: make(map[string]*Container),
securityOptions: securityPolicyOptions,
containers: make(map[string]*Container),
blockCIMVolumeHashes: make(map[string][]string),
blockCIMVolumeContainers: make(map[string]map[string]struct{}),
securityOptions: securityPolicyOptions,
}
}

Expand Down
20 changes: 20 additions & 0 deletions internal/regopolicyinterpreter/regopolicyinterpreter.go
Original file line number Diff line number Diff line change
Expand Up @@ -585,6 +585,26 @@ func (r *RegoPolicyInterpreter) RawQuery(rule string, input map[string]interface
return resultSet, nil
}

// MetadataJSON returns the entire metadata object as a JSON string.
// The returned JSON is a snapshot (deep-copied via marshal/unmarshal).
func (r *RegoPolicyInterpreter) MetadataJSON() (string, error) {
r.dataAndModulesMutex.Lock()
defer r.dataAndModulesMutex.Unlock()

root, ok := r.data["metadata"].(regoMetadata)
if !ok {
return "", errors.New("incorrect interpreter state: invalid metadata object type")
}

// Deep copy to avoid callers modifying internal maps.
b, err := json.Marshal(root)
if err != nil {
return "", fmt.Errorf("unable to marshal metadata: %w", err)
}

return string(b), nil
}

// Query queries the policy with the given rule and input data and returns the result.
func (r *RegoPolicyInterpreter) Query(rule string, input map[string]interface{}) (RegoQueryResult, error) {
// this mutex ensures no other threads modify the data and compiledModules fields during query execution
Expand Down
1 change: 1 addition & 0 deletions pkg/securitypolicy/framework.rego
Original file line number Diff line number Diff line change
Expand Up @@ -1360,6 +1360,7 @@ env_rule_matches(rule) {
}

errors["missing required environment variable"] {
is_linux
input.rule == "create_container"

not container_started
Expand Down
40 changes: 40 additions & 0 deletions pkg/securitypolicy/regopolicy_windows_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
_ "embed"
"fmt"
"math/rand"
"strconv"
"strings"
"testing"
"testing/quick"
Expand Down Expand Up @@ -315,6 +316,45 @@ func Test_Rego_EnforceCreateContainer_Same_Container_Twice_Windows(t *testing.T)
}
}

func Test_Rego_EnforceVerifiedCIMSPolicy_Multiple_Instances_Same_Container(t *testing.T) {
for containersToCreate := 5; containersToCreate <= maxContainersInGeneratedConstraints; containersToCreate++ {
constraints := new(generatedWindowsConstraints)
constraints.ctx = context.Background()
constraints.externalProcesses = generateExternalProcesses(testRand)

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

constraints.containers = append(constraints.containers, c)
}

securityPolicy := constraints.toPolicy()
policy, err := newRegoPolicy(securityPolicy.marshalWindowsRego(), []oci.Mount{}, []oci.Mount{}, testOSType)

if err != nil {
t.Fatalf("failed create enforcer")
}

for _, container := range constraints.containers {
// Reverse container.Layers to satisfy layerHashes_ok ordering
layerHashes := make([]string, len(container.Layers))
for i, layer := range container.Layers {
layerHashes[len(container.Layers)-1-i] = layer
}

id := testDataGenerator.uniqueContainerID()
err = policy.EnforceVerifiedCIMsPolicy(constraints.ctx, id, layerHashes)
if err != nil {
t.Fatalf("failed with %d containers", containersToCreate)
}
}
}
}

// -- Capabilities/Mount/Rego version tests are removed -- Add back Rego versions test//
func Test_Rego_ExecInContainerPolicy_Windows(t *testing.T) {
f := func(p *generatedWindowsConstraints) bool {
Expand Down
19 changes: 7 additions & 12 deletions pkg/securitypolicy/securitypolicyenforcer_rego.go
Original file line number Diff line number Diff line change
Expand Up @@ -718,6 +718,13 @@ func (policy *regoEnforcer) EnforceCreateContainerPolicyV2(
"seccompProfileSHA256": opts.SeccompProfileSHA256,
}
case "windows":
// Dump full interpreter metadata for debugging diagnostics.
if mdJSON, err := policy.rego.MetadataJSON(); err == nil {
log.G(ctx).Debugf("Current policy metadata: %s", mdJSON)
} else {
log.G(ctx).WithError(err).Warn("failed to obtain policy metadata snapshot")
}

input = inputData{
"containerID": containerID,
"argList": argList,
Expand Down Expand Up @@ -781,18 +788,6 @@ func appendMountData(mountData []interface{}, mounts []oci.Mount) []interface{}
return mountData
}

func appendMountDataWindows(mountData []interface{}, mounts []oci.Mount) []interface{} {
for _, mount := range mounts {
mountData = append(mountData, inputData{
"destination": mount.Destination,
"source": mount.Source,
"options": mount.Options,
})
}

return mountData
}

func (policy *regoEnforcer) ExtendDefaultMounts(mounts []oci.Mount) error {
policy.defaultMounts = append(policy.defaultMounts, mounts...)
defaultMounts := appendMountData([]interface{}{}, policy.defaultMounts)
Expand Down