Skip to content

Commit

Permalink
Merge pull request microsoft#1094 from SeanTAllen/minimalist-policy
Browse files Browse the repository at this point in the history
Add basis for allowing the creation of configuration enforcement in gcs
  • Loading branch information
anmaxvl authored Aug 4, 2021
2 parents 9c1d08f + 5957464 commit a9618d7
Show file tree
Hide file tree
Showing 20 changed files with 610 additions and 42 deletions.
10 changes: 10 additions & 0 deletions guest/prot/protocol.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"strconv"

"github.com/Microsoft/hcsshim/internal/guest/commonutils"
"github.com/Microsoft/hcsshim/pkg/securitypolicy"
v1 "github.com/containerd/cgroups/stats/v1"
oci "github.com/opencontainers/runtime-spec/specs-go"
"github.com/pkg/errors"
Expand Down Expand Up @@ -519,6 +520,8 @@ const (
MrtVPCIDevice = ModifyResourceType("VPCIDevice")
// MrtContainerConstraints is the modify resource type for updating container constraints
MrtContainerConstraints = ModifyResourceType("ContainerConstraints")
// MrtSecurityPolicy is the modify resource type for updating the security policy
MrtSecurityPolicy = ModifyResourceType("SecurityPolicy")
)

// ModifyRequestType is the type of operation to perform on a given modify
Expand Down Expand Up @@ -618,6 +621,12 @@ func UnmarshalContainerModifySettings(b []byte) (*ContainerModifySettings, error
return &request, errors.Wrap(err, "failed to unmarshal settings as ContainerConstraintsV2")
}
msr.Settings = cc
case MrtSecurityPolicy:
policy := &securitypolicy.EncodedSecurityPolicy{}
if err := commonutils.UnmarshalJSONWithHresult(msrRawSettings, policy); err != nil {
return &request, errors.Wrap(err, "failed to unmarshal settings as EncodedSecurityPolicy")
}
msr.Settings = policy
default:
return &request, errors.Errorf("invalid ResourceType '%s'", msr.ResourceType)
}
Expand Down Expand Up @@ -713,6 +722,7 @@ type CombinedLayersV2 struct {
Layers []Layer `json:",omitempty"`
ScratchPath string `json:",omitempty"`
ContainerRootPath string
ContainerId string `json:",omitempty"`
}

// NetworkAdapter represents a network interface and its associated
Expand Down
76 changes: 65 additions & 11 deletions guest/runtime/hcsv2/uvm.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ package hcsv2
import (
"bufio"
"context"
"encoding/base64"
"encoding/json"
"fmt"
"os"
Expand All @@ -26,6 +27,7 @@ import (
"github.com/Microsoft/hcsshim/internal/guest/storage/pmem"
"github.com/Microsoft/hcsshim/internal/guest/storage/scsi"
"github.com/Microsoft/hcsshim/internal/guest/transport"
"github.com/Microsoft/hcsshim/pkg/securitypolicy"
shellwords "github.com/mattn/go-shellwords"
"github.com/pkg/errors"
)
Expand All @@ -46,15 +48,60 @@ type Host struct {
// Rtime is the Runtime interface used by the GCS core.
rtime runtime.Runtime
vsock transport.Transport

// state required for the security policy enforcement
policyMutex sync.Mutex
securityPolicyEnforcer securitypolicy.SecurityPolicyEnforcer
securityPolicyEnforcerSet bool
}

func NewHost(rtime runtime.Runtime, vsock transport.Transport) *Host {
return &Host{
containers: make(map[string]*Container),
externalProcesses: make(map[int]*externalProcess),
rtime: rtime,
vsock: vsock,
containers: make(map[string]*Container),
externalProcesses: make(map[int]*externalProcess),
rtime: rtime,
vsock: vsock,
securityPolicyEnforcerSet: false,
securityPolicyEnforcer: &securitypolicy.OpenDoorSecurityPolicyEnforcer{},
}
}

// SetSecurityPolicy takes a base64 encoded security policy
// and sets up our internal data structures we use to store
// said policy.
// The security policy is transmitted as json in an annotation,
// so we first have to remove the base64 encoding that allows
// the JSON based policy to be passed as a string. From there,
// we decode the JSON and setup our security policy state
func (h *Host) SetSecurityPolicy(base64_policy string) error {
h.policyMutex.Lock()
defer h.policyMutex.Unlock()
if h.securityPolicyEnforcerSet {
return errors.New("security policy has already been set")
}

// base64 decode the incoming policy string
// its base64 encoded because it is coming from an annotation
// annotations are a map of string to string
// we want to store a complex json object so.... base64 it is
jsonPolicy, err := base64.StdEncoding.DecodeString(base64_policy)
if err != nil {
return errors.Wrap(err, "Unable to decode policy from Base64 format")
}

// json unmarshall the decoded to a SecurityPolicy
securityPolicy := &securitypolicy.SecurityPolicy{}
json.Unmarshal(jsonPolicy, securityPolicy)

p, err := securitypolicy.NewSecurityPolicyEnforcer(securityPolicy)
if err != nil {
return err
}

h.securityPolicyEnforcer = p
h.securityPolicyEnforcerSet = true

return nil
}

func (h *Host) RemoveContainer(id string) {
Expand Down Expand Up @@ -200,9 +247,9 @@ func (h *Host) modifyHostSettings(ctx context.Context, containerID string, setti
case prot.MrtMappedDirectory:
return modifyMappedDirectory(ctx, h.vsock, settings.RequestType, settings.Settings.(*prot.MappedDirectoryV2))
case prot.MrtVPMemDevice:
return modifyMappedVPMemDevice(ctx, settings.RequestType, settings.Settings.(*prot.MappedVPMemDeviceV2))
return modifyMappedVPMemDevice(ctx, settings.RequestType, settings.Settings.(*prot.MappedVPMemDeviceV2), h.securityPolicyEnforcer)
case prot.MrtCombinedLayers:
return modifyCombinedLayers(ctx, settings.RequestType, settings.Settings.(*prot.CombinedLayersV2))
return modifyCombinedLayers(ctx, settings.RequestType, settings.Settings.(*prot.CombinedLayersV2), h.securityPolicyEnforcer)
case prot.MrtNetwork:
return modifyNetwork(ctx, settings.RequestType, settings.Settings.(*prot.NetworkAdapterV2))
case prot.MrtVPCIDevice:
Expand All @@ -213,6 +260,13 @@ func (h *Host) modifyHostSettings(ctx context.Context, containerID string, setti
return err
}
return c.modifyContainerConstraints(ctx, settings.RequestType, settings.Settings.(*prot.ContainerConstraintsV2))
case prot.MrtSecurityPolicy:
policy, ok := settings.Settings.(*securitypolicy.EncodedSecurityPolicy)
if !ok {
return errors.New("the request's settings are not of type EncodedSecurityPolicy")
}

return h.SetSecurityPolicy(policy.SecurityPolicy)
default:
return errors.Errorf("the ResourceType \"%s\" is not supported for UVM", settings.ResourceType)
}
Expand Down Expand Up @@ -381,12 +435,12 @@ func modifyMappedDirectory(ctx context.Context, vsock transport.Transport, rt pr
}
}

func modifyMappedVPMemDevice(ctx context.Context, rt prot.ModifyRequestType, vpd *prot.MappedVPMemDeviceV2) (err error) {
func modifyMappedVPMemDevice(ctx context.Context, rt prot.ModifyRequestType, vpd *prot.MappedVPMemDeviceV2, securityPolicy securitypolicy.SecurityPolicyEnforcer) (err error) {
switch rt {
case prot.MreqtAdd:
return pmem.Mount(ctx, vpd.DeviceNumber, vpd.MountPath, vpd.MappingInfo, vpd.VerityInfo)
return pmem.Mount(ctx, vpd.DeviceNumber, vpd.MountPath, vpd.MappingInfo, vpd.VerityInfo, securityPolicy)
case prot.MreqtRemove:
return pmem.Unmount(ctx, vpd.DeviceNumber, vpd.MountPath, vpd.MappingInfo, vpd.VerityInfo)
return pmem.Unmount(ctx, vpd.DeviceNumber, vpd.MountPath, vpd.MappingInfo, vpd.VerityInfo, securityPolicy)
default:
return newInvalidRequestTypeError(rt)
}
Expand All @@ -401,7 +455,7 @@ func modifyMappedVPCIDevice(ctx context.Context, rt prot.ModifyRequestType, vpci
}
}

func modifyCombinedLayers(ctx context.Context, rt prot.ModifyRequestType, cl *prot.CombinedLayersV2) (err error) {
func modifyCombinedLayers(ctx context.Context, rt prot.ModifyRequestType, cl *prot.CombinedLayersV2, securityPolicy securitypolicy.SecurityPolicyEnforcer) (err error) {
switch rt {
case prot.MreqtAdd:
layerPaths := make([]string, len(cl.Layers))
Expand All @@ -420,7 +474,7 @@ func modifyCombinedLayers(ctx context.Context, rt prot.ModifyRequestType, cl *pr
workdirPath = filepath.Join(cl.ScratchPath, "work")
}

return overlay.Mount(ctx, layerPaths, upperdirPath, workdirPath, cl.ContainerRootPath, readonly)
return overlay.Mount(ctx, layerPaths, upperdirPath, workdirPath, cl.ContainerRootPath, readonly, cl.ContainerId, securityPolicy)
case prot.MreqtRemove:
return storage.UnmountPath(ctx, cl.ContainerRootPath, true)
default:
Expand Down
7 changes: 6 additions & 1 deletion guest/storage/overlay/overlay.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"strings"

"github.com/Microsoft/hcsshim/internal/oc"
"github.com/Microsoft/hcsshim/pkg/securitypolicy"
"github.com/pkg/errors"
"go.opencensus.io/trace"
"golang.org/x/sys/unix"
Expand All @@ -30,11 +31,15 @@ var (
//
// Always creates `rootfsPath`. On mount failure the created `rootfsPath` will
// be automatically cleaned up.
func Mount(ctx context.Context, layerPaths []string, upperdirPath, workdirPath, rootfsPath string, readonly bool) (err error) {
func Mount(ctx context.Context, layerPaths []string, upperdirPath, workdirPath, rootfsPath string, readonly bool, containerId string, securityPolicy securitypolicy.SecurityPolicyEnforcer) (err error) {
_, span := trace.StartSpan(ctx, "overlay::Mount")
defer span.End()
defer func() { oc.SetSpanStatus(span, err) }()

if err := securityPolicy.EnforceOverlayMountPolicy(containerId, layerPaths); err != nil {
return err
}

lowerdir := strings.Join(layerPaths, ":")
span.AddAttributes(
trace.StringAttribute("layerPaths", lowerdir),
Expand Down
80 changes: 78 additions & 2 deletions guest/storage/overlay/overlay_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,13 @@ import (
"errors"
"os"
"testing"

"github.com/Microsoft/hcsshim/internal/guest/storage/test/policy"
"github.com/Microsoft/hcsshim/pkg/securitypolicy"
)

const (
fakeContainerId = "1"
)

type undo struct {
Expand Down Expand Up @@ -76,7 +83,7 @@ func Test_Mount_Success(t *testing.T) {
return nil
}

err := Mount(context.Background(), []string{"/layer1", "/layer2"}, "/upper", "/work", "/root", false)
err := Mount(context.Background(), []string{"/layer1", "/layer2"}, "/upper", "/work", "/root", false, fakeContainerId, openDoorSecurityPolicyEnforcer())
if err != nil {
t.Fatalf("expected no error got: %v", err)
}
Expand Down Expand Up @@ -120,11 +127,80 @@ func Test_Mount_Readonly_Success(t *testing.T) {
return nil
}

err := Mount(context.Background(), []string{"/layer1", "/layer2"}, "", "", "/root", false)
err := Mount(context.Background(), []string{"/layer1", "/layer2"}, "", "", "/root", false, fakeContainerId, openDoorSecurityPolicyEnforcer())
if err != nil {
t.Fatalf("expected no error got: %v", err)
}
if !rootCreated {
t.Fatal("expected root to be created")
}
}

func Test_Security_Policy_Enforcement(t *testing.T) {
undo := captureTestMethods()
defer undo.Close()

var upperCreated, workCreated, rootCreated bool
osMkdirAll = func(path string, perm os.FileMode) error {
if perm != 0755 {
t.Errorf("os.MkdirAll at: %s, perm: %v expected perm: 0755", path, perm)
}
switch path {
case "/upper":
upperCreated = true
return nil
case "/work":
workCreated = true
return nil
case "/root":
rootCreated = true
return nil
}
return errors.New("unexpected os.MkdirAll path")
}
unixMount = func(source string, target string, fstype string, flags uintptr, data string) error {
if source != "overlay" {
t.Errorf("expected source: 'overlay' got: %v", source)
}
if target != "/root" {
t.Errorf("expected target: '/root' got: %v", target)
}
if fstype != "overlay" {
t.Errorf("expected fstype: 'overlay' got: %v", fstype)
}
if flags != 0 {
t.Errorf("expected flags: '0' got: %v", flags)
}
if data != "lowerdir=/layer1:/layer2,upperdir=/upper,workdir=/work" {
t.Errorf("expected data: 'lowerdir=/layer1:/layer2,upperdir=/upper,workdir=/work' got: %v", data)
}
return nil
}

enforcer := mountMonitoringSecurityPolicyEnforcer()
err := Mount(context.Background(), []string{"/layer1", "/layer2"}, "/upper", "/work", "/root", false, fakeContainerId, enforcer)
if err != nil {
t.Fatalf("expected no error got: %v", err)
}
if !upperCreated || !workCreated || !rootCreated {
t.Fatalf("expected all upper: %v, work: %v, root: %v to be created", upperCreated, workCreated, rootCreated)
}

expectedPmem := 0
if enforcer.PmemMountCalls != expectedPmem {
t.Errorf("expected %d attempt at pmem mount enforcement, got %d", expectedPmem, enforcer.PmemMountCalls)
}

expectedOverlay := 1
if enforcer.OverlayMountCalls != expectedOverlay {
t.Fatalf("expected %d attempts at overlay mount enforcement, got %d", expectedOverlay, enforcer.OverlayMountCalls)
}
}

func openDoorSecurityPolicyEnforcer() securitypolicy.SecurityPolicyEnforcer {
return &securitypolicy.OpenDoorSecurityPolicyEnforcer{}
}

func mountMonitoringSecurityPolicyEnforcer() *policy.MountMonitoringSecurityPolicyEnforcer {
return &policy.MountMonitoringSecurityPolicyEnforcer{}
}
18 changes: 15 additions & 3 deletions guest/storage/pmem/pmem.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@ package pmem
import (
"context"
"fmt"
"os"

"github.com/Microsoft/hcsshim/internal/guest/prot"
"github.com/Microsoft/hcsshim/internal/log"
"os"
"github.com/Microsoft/hcsshim/pkg/securitypolicy"

"github.com/Microsoft/hcsshim/internal/guest/storage"
dm "github.com/Microsoft/hcsshim/internal/guest/storage/devicemapper"
Expand Down Expand Up @@ -63,7 +65,7 @@ func mountInternal(ctx context.Context, source, target string) (err error) {
//
// Note: both mappingInfo and verityInfo can be non-nil at the same time, in that case
// linear target is created first and it becomes the data/hash device for verity target.
func Mount(ctx context.Context, device uint32, target string, mappingInfo *prot.DeviceMappingInfo, verityInfo *prot.DeviceVerityInfo) (err error) {
func Mount(ctx context.Context, device uint32, target string, mappingInfo *prot.DeviceMappingInfo, verityInfo *prot.DeviceVerityInfo, securityPolicy securitypolicy.SecurityPolicyEnforcer) (err error) {
mCtx, span := trace.StartSpan(ctx, "pmem::Mount")
defer span.End()
defer func() { oc.SetSpanStatus(span, err) }()
Expand All @@ -73,6 +75,16 @@ func Mount(ctx context.Context, device uint32, target string, mappingInfo *prot.
trace.StringAttribute("target", target))

devicePath := fmt.Sprintf(pMemFmt, device)

var deviceHash string
if verityInfo != nil {
deviceHash = verityInfo.RootDigest
}
err = securityPolicy.EnforcePmemMountPolicy(target, deviceHash)
if err != nil {
return errors.Wrapf(err, "won't mount pmem device %d onto %s", device, target)
}

// dm linear target has to be created first. when verity info is also present, the linear target becomes the data
// device instead of the original VPMem.
if mappingInfo != nil {
Expand Down Expand Up @@ -178,7 +190,7 @@ func createDMVerityTarget(ctx context.Context, devPath, devName, target string,
}

// Unmount unmounts `target` and removes corresponding linear and verity targets when needed
func Unmount(ctx context.Context, devNumber uint32, target string, mappingInfo *prot.DeviceMappingInfo, verityInfo *prot.DeviceVerityInfo) (err error) {
func Unmount(ctx context.Context, devNumber uint32, target string, mappingInfo *prot.DeviceMappingInfo, verityInfo *prot.DeviceVerityInfo, securityPolicy securitypolicy.SecurityPolicyEnforcer) (err error) {
_, span := trace.StartSpan(ctx, "pmem::Unmount")
defer span.End()
defer func() { oc.SetSpanStatus(span, err) }()
Expand Down
Loading

0 comments on commit a9618d7

Please sign in to comment.