Skip to content

Commit

Permalink
securitypolicy: add security policy enforcer registration and defaults (
Browse files Browse the repository at this point in the history
#1476)

* Stub out Rego policy enforcer and hide it behind a build tag.

Add enforcer registration logic and support for default enforcer.
The host can request which security policy enforcer to use with
supplied policy, if none supplied, GCS code tries to make a "guess"
as to which enforcer should be used: "allow all" or "default".
Default enforcer is set to `StandardSecurityPolicyEnforcer` unless
GCS is built with "rego" tag present. In that case, the default
enforcer will be set to `RegoEnforcer`.

New annotation has been added that allows callers to pick which
enforcer to use, e.g.
```pod.json
{
    ...
    "annotations": {
        "io.microsoft.virtualmachine.lcow.enforcer": "rego"
    },
    ...
}
```

Signed-off-by: Maksim An <maksiman@microsoft.com>
  • Loading branch information
anmaxvl authored Aug 18, 2022
1 parent dca430e commit 8351158
Show file tree
Hide file tree
Showing 13 changed files with 174 additions and 43 deletions.
4 changes: 4 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ GO_FLAGS_EXTRA:=
ifeq "$(GOMODVENDOR)" "1"
GO_FLAGS_EXTRA += -mod=vendor
endif
GO_BUILD_TAGS:=
ifneq ($(strip $(GO_BUILD_TAGS)),)
GO_FLAGS_EXTRA += -tags="$(GO_BUILD_TAGS)"
endif
GO_BUILD:=CGO_ENABLED=$(CGO_ENABLED) $(GO) build $(GO_FLAGS) $(GO_FLAGS_EXTRA)

SRCROOT=$(dir $(abspath $(firstword $(MAKEFILE_LIST))))
Expand Down
4 changes: 2 additions & 2 deletions cmd/containerd-shim-runhcs-v1/pod.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import (
"github.com/containerd/containerd/errdefs"
"github.com/containerd/containerd/runtime"
"github.com/containerd/containerd/runtime/v2/task"
specs "github.com/opencontainers/runtime-spec/specs-go"
"github.com/opencontainers/runtime-spec/specs-go"
"github.com/pkg/errors"
"golang.org/x/sync/errgroup"
)
Expand Down Expand Up @@ -145,7 +145,7 @@ func createPod(ctx context.Context, events publisher, req *task.CreateTaskReques
}

if lopts != nil {
err := parent.SetSecurityPolicy(ctx, lopts.SecurityPolicy)
err := parent.SetSecurityPolicy(ctx, lopts.SecurityPolicyEnforcer, lopts.SecurityPolicy)
if err != nil {
return nil, errors.Wrap(err, "unable to set security policy")
}
Expand Down
16 changes: 7 additions & 9 deletions internal/guest/runtime/hcsv2/uvm.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ func NewHost(rtime runtime.Runtime, vsock transport.Transport) *Host {
// 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(base64Policy string) error {
func (h *Host) SetSecurityPolicy(enforcerType, base64Policy string) error {
h.policyMutex.Lock()
defer h.policyMutex.Unlock()
if h.securityPolicyEnforcerSet {
Expand All @@ -92,9 +92,11 @@ func (h *Host) SetSecurityPolicy(base64Policy string) error {
return err
}

p, err := securitypolicy.NewSecurityPolicyEnforcer(
p, err := securitypolicy.CreateSecurityPolicyEnforcer(
enforcerType,
*securityPolicyState,
securitypolicy.WithPrivilegedMounts(policy.DefaultCRIPrivilegedMounts()),
policy.DefaultCRIMounts(),
policy.DefaultCRIPrivilegedMounts(),
)
if err != nil {
return err
Expand All @@ -109,10 +111,6 @@ func (h *Host) SetSecurityPolicy(base64Policy string) error {
return err
}

if err := p.ExtendDefaultMounts(policy.DefaultCRIMounts()); err != nil {
return err
}

h.securityPolicyEnforcer = p
h.securityPolicyEnforcerSet = true

Expand Down Expand Up @@ -380,12 +378,12 @@ func (h *Host) modifyHostSettings(ctx context.Context, containerID string, req *
}
return c.modifyContainerConstraints(ctx, req.RequestType, req.Settings.(*guestresource.LCOWContainerConstraints))
case guestresource.ResourceTypeSecurityPolicy:
policy, ok := req.Settings.(*securitypolicy.EncodedSecurityPolicy)
r, ok := req.Settings.(*guestresource.LCOWSecurityPolicyEnforcer)
if !ok {
return errors.New("the request's settings are not of type EncodedSecurityPolicy")
}

return h.SetSecurityPolicy(policy.SecurityPolicy)
return h.SetSecurityPolicy(r.EnforcerType, r.EncodedSecurityPolicy)
default:
return errors.Errorf("the ResourceType \"%s\" is not supported for UVM", req.ResourceType)
}
Expand Down
1 change: 1 addition & 0 deletions internal/oci/uvm.go
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,7 @@ func SpecToUVMCreateOpts(ctx context.Context, s *specs.Spec, id, owner string) (
lopts.BootFilesPath = parseAnnotationsString(s.Annotations, annotations.BootFilesRootPath, lopts.BootFilesPath)
lopts.EnableScratchEncryption = ParseAnnotationsBool(ctx, s.Annotations, annotations.EncryptedScratchDisk, lopts.EnableScratchEncryption)
lopts.SecurityPolicy = parseAnnotationsString(s.Annotations, annotations.SecurityPolicy, lopts.SecurityPolicy)
lopts.SecurityPolicyEnforcer = parseAnnotationsString(s.Annotations, annotations.SecurityPolicyEnforcer, lopts.SecurityPolicyEnforcer)
lopts.KernelBootOptions = parseAnnotationsString(s.Annotations, annotations.KernelBootOptions, lopts.KernelBootOptions)
lopts.DisableTimeSyncService = ParseAnnotationsBool(ctx, s.Annotations, annotations.DisableLCOWTimeSyncService, lopts.DisableTimeSyncService)
handleAnnotationPreferredRootFSType(ctx, s.Annotations, lopts)
Expand Down
5 changes: 5 additions & 0 deletions internal/protocol/guestresource/resources.go
Original file line number Diff line number Diff line change
Expand Up @@ -158,3 +158,8 @@ type SignalProcessOptionsLCOW struct {
type SignalProcessOptionsWCOW struct {
Signal guestrequest.SignalValueWCOW `json:",omitempty"`
}

type LCOWSecurityPolicyEnforcer struct {
EnforcerType string `json:"EnforcerType,omitempty"`
EncodedSecurityPolicy string `json:"EncodedSecurityPolicy,omitempty"`
}
2 changes: 1 addition & 1 deletion internal/tools/uvmboot/lcow.go
Original file line number Diff line number Diff line change
Expand Up @@ -268,7 +268,7 @@ func runLCOW(ctx context.Context, options *uvm.OptionsLCOW, c *cli.Context) erro
return err
}

if err := vm.SetSecurityPolicy(ctx, options.SecurityPolicy); err != nil {
if err := vm.SetSecurityPolicy(ctx, "", options.SecurityPolicy); err != nil {
return fmt.Errorf("could not set UVM security policy: %w", err)
}

Expand Down
2 changes: 2 additions & 0 deletions internal/uvm/create_lcow.go
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ type OptionsLCOW struct {
EnableScratchEncryption bool // Whether the scratch should be encrypted
SecurityPolicy string // Optional security policy
SecurityPolicyEnabled bool // Set when there is a security policy to apply on actual SNP hardware, use this rathen than checking the string length
SecurityPolicyEnforcer string // Set which security policy enforcer to use (open door, standard or rego). This allows for better fallback mechanic.
UseGuestStateFile bool // Use a vmgs file that contains a kernel and initrd, required for SNP
GuestStateFile string // The vmgs file to load
DisableTimeSyncService bool // Disables the time synchronization service
Expand Down Expand Up @@ -152,6 +153,7 @@ func NewDefaultOptionsLCOW(id, owner string) *OptionsLCOW {
VPCIEnabled: false,
EnableScratchEncryption: false,
SecurityPolicyEnabled: false,
SecurityPolicyEnforcer: "",
SecurityPolicy: "",
GuestStateFile: "",
DisableTimeSyncService: false,
Expand Down
10 changes: 4 additions & 6 deletions internal/uvm/security_policy.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import (
//
// This has to happen before we start mounting things or generally changing
// the state of the UVM after is has been measured at startup
func (uvm *UtilityVM) SetSecurityPolicy(ctx context.Context, policy string) error {
func (uvm *UtilityVM) SetSecurityPolicy(ctx context.Context, enforcer, policy string) error {
if uvm.operatingSystem != "linux" {
return errNotSupported
}
Expand All @@ -35,16 +35,14 @@ func (uvm *UtilityVM) SetSecurityPolicy(ctx context.Context, policy string) erro

modification := &hcsschema.ModifySettingRequest{
RequestType: guestrequest.RequestTypeAdd,
Settings: securitypolicy.EncodedSecurityPolicy{
SecurityPolicy: policy,
},
}

modification.GuestRequest = guestrequest.ModificationRequest{
ResourceType: guestresource.ResourceTypeSecurityPolicy,
RequestType: guestrequest.RequestTypeAdd,
Settings: securitypolicy.EncodedSecurityPolicy{
SecurityPolicy: policy,
Settings: guestresource.LCOWSecurityPolicyEnforcer{
EnforcerType: enforcer,
EncodedSecurityPolicy: policy,
},
}

Expand Down
4 changes: 4 additions & 0 deletions pkg/annotations/annotations.go
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,10 @@ const (
// SecurityPolicy is used to specify a security policy for opengcs to enforce
SecurityPolicy = "io.microsoft.virtualmachine.lcow.securitypolicy"

// SecurityPolicyEnforcer is used to specify which enforcer to initialize (open-door, standard or rego).
// This allows for better fallback mechanics.
SecurityPolicyEnforcer = "io.microsoft.virtualmachine.lcow.enforcer"

// ContainerProcessDumpLocation specifies a path inside of containers to save process dumps to. As
// the scratch space for a container is generally cleaned up after exit, this is best set to a volume mount of
// some kind (vhd, bind mount, fileshare mount etc.)
Expand Down
3 changes: 0 additions & 3 deletions pkg/securitypolicy/securitypolicy.go
Original file line number Diff line number Diff line change
Expand Up @@ -327,11 +327,8 @@ func newMountOptions(opts []string) Options {
// newOptionsFromConfig applies the same logic as CRI plugin to generate
// mount options given readonly and propagation config.
// TODO: (anmaxvl) update when support for other mount types is added,
//
// e.g., vhd:// or evd://
//
// TODO: (anmaxvl) Do we need to set/validate Linux rootfs propagation?
//
// In case we do, update securityPolicyContainer and Container structs
// as well as mount enforcement logic.
func newOptionsFromConfig(mCfg *MountConfig) []string {
Expand Down
91 changes: 73 additions & 18 deletions pkg/securitypolicy/securitypolicyenforcer.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,23 @@ import (
"github.com/Microsoft/hcsshim/pkg/annotations"
)

type createEnforcerFunc func(_ SecurityPolicyState, criMounts, criPrivilegedMounts []oci.Mount) (SecurityPolicyEnforcer, error)

const (
allowAllEnforcer = "allow_all"
standardEnforcer = "standard"
)

var (
registeredEnforcers = map[string]createEnforcerFunc{}
defaultEnforcer = standardEnforcer
)

func init() {
registeredEnforcers[allowAllEnforcer] = createAllowAllEnforcer
registeredEnforcers[standardEnforcer] = createStandardEnforcer
}

type SecurityPolicyEnforcer interface {
EnforceDeviceMountPolicy(target string, deviceHash string) (err error)
EnforceDeviceUnmountPolicy(unmountTarget string) (err error)
Expand All @@ -32,26 +49,64 @@ type SecurityPolicyEnforcer interface {
EncodedSecurityPolicy() string
}

func NewSecurityPolicyEnforcer(state SecurityPolicyState, eOpts ...standardEnforcerOpt) (SecurityPolicyEnforcer, error) {
if state.SecurityPolicy.AllowAll {
if state.SecurityPolicy.Containers.Length > 0 || len(state.SecurityPolicy.Containers.Elements) > 0 {
return nil, ErrInvalidAllowAllPolicy
// createAllowAllEnforcer creates and returns OpenDoorSecurityPolicyEnforcer instance.
// Both AllowAll and Containers cannot be set at the same time.
func createAllowAllEnforcer(state SecurityPolicyState, _, _ []oci.Mount) (SecurityPolicyEnforcer, error) {
policyContainers := state.SecurityPolicy.Containers
if !state.AllowAll || policyContainers.Length > 0 || len(policyContainers.Elements) > 0 {
return nil, ErrInvalidAllowAllPolicy
}
return &OpenDoorSecurityPolicyEnforcer{
encodedSecurityPolicy: state.EncodedSecurityPolicy.SecurityPolicy,
}, nil
}

// createStandardEnforcer creates and returns StandardSecurityPolicyEnforcer instance.
// Make sure that the input JSON policy can be converted to internal representation
// and that `criMounts` and `criPrivilegedMounts` can be injected before successful return.
func createStandardEnforcer(
state SecurityPolicyState,
criMounts,
criPrivilegedMounts []oci.Mount,
) (SecurityPolicyEnforcer, error) {
containers, err := state.SecurityPolicy.Containers.toInternal()
if err != nil {
return nil, err
}

enforcer := NewStandardSecurityPolicyEnforcer(containers, state.EncodedSecurityPolicy.SecurityPolicy)

if err := enforcer.ExtendDefaultMounts(criMounts); err != nil {
return nil, err
}

addPrivilegedMountsWrapper := WithPrivilegedMounts(criPrivilegedMounts)
if err := addPrivilegedMountsWrapper(enforcer); err != nil {
return nil, err
}
return enforcer, nil
}

// CreateSecurityPolicyEnforcer returns an appropriate enforcer for input parameters.
// When `enforcer` isn't return either an AllowAll or default enforcer.
// Returns an error if the requested `enforcer` implementation isn't registered.
func CreateSecurityPolicyEnforcer(
enforcer string,
state SecurityPolicyState,
criMounts,
criPrivilegedMounts []oci.Mount,
) (SecurityPolicyEnforcer, error) {
if enforcer == "" {
if state.SecurityPolicy.AllowAll {
enforcer = allowAllEnforcer
} else {
enforcer = defaultEnforcer
}
return &OpenDoorSecurityPolicyEnforcer{
encodedSecurityPolicy: state.EncodedSecurityPolicy.SecurityPolicy,
}, nil
}
if createEnforcer, ok := registeredEnforcers[enforcer]; !ok {
return nil, fmt.Errorf("unknown enforcer: %q", enforcer)
} else {
containers, err := state.SecurityPolicy.Containers.toInternal()
if err != nil {
return nil, err
}
enforcer := NewStandardSecurityPolicyEnforcer(containers, state.EncodedSecurityPolicy.SecurityPolicy)
for _, o := range eOpts {
if err := o(enforcer); err != nil {
return nil, err
}
}
return enforcer, nil
return createEnforcer(state, criMounts, criPrivilegedMounts)
}
}

Expand Down
70 changes: 70 additions & 0 deletions pkg/securitypolicy/securitypolicyenforcer_rego.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
//go:build linux && rego
// +build linux,rego

package securitypolicy

import (
"errors"

oci "github.com/opencontainers/runtime-spec/specs-go"
)

const regoEnforcer = "rego"

func init() {
registeredEnforcers[regoEnforcer] = createRegoEnforcer
// Overriding the value inside init guarantees that this assignment happens
// after the variable has been initialized in securitypolicy.go and there
// are no race conditions. When multiple init functions are defined in a
// single package, the order of their execution is determined by the
// filename.
defaultEnforcer = regoEnforcer
}

// RegoEnforcer is a stub implementation of a security policy, which will be
// based on [Rego] policy language. The detailed implementation will be
// introduced in the subsequent PRs and documentation updated accordingly.
//
// [Rego]: https://www.openpolicyagent.org/docs/latest/policy-language/
type RegoEnforcer struct{}

var (
_ SecurityPolicyEnforcer = (*RegoEnforcer)(nil)
ErrNotImplemented = errors.New("not implemented")
)

func createRegoEnforcer(_ SecurityPolicyState, _, _ []oci.Mount) (SecurityPolicyEnforcer, error) {
return &RegoEnforcer{}, nil
}

func (RegoEnforcer) EnforceDeviceMountPolicy(_, _ string) error {
return ErrNotImplemented
}

func (RegoEnforcer) EnforceDeviceUnmountPolicy(_ string) error {
return ErrNotImplemented
}

func (RegoEnforcer) EnforceOverlayMountPolicy(_ string, _ []string) error {
return ErrNotImplemented
}

func (RegoEnforcer) EnforceCreateContainerPolicy(_ string, _, _ []string, _ string) error {
return ErrNotImplemented
}

func (RegoEnforcer) EnforceWaitMountPointsPolicy(_ string, _ *oci.Spec) error {
return ErrNotImplemented
}

func (RegoEnforcer) EnforceMountPolicy(_, _ string, _ *oci.Spec) error {
return ErrNotImplemented
}

func (RegoEnforcer) ExtendDefaultMounts(_ []oci.Mount) error {
return ErrNotImplemented
}

func (RegoEnforcer) EncodedSecurityPolicy() string {
return ""
}
5 changes: 1 addition & 4 deletions test/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -709,7 +709,6 @@ golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwY
golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.0.0-20210825183410-e898025ed96a h1:bRuuGXV8wwSdGTB+CtJf+FjgO1APK1CoO39T4BN/XBw=
golang.org/x/net v0.0.0-20210825183410-e898025ed96a/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd h1:O7DYs+zxREGLKzKoMQrtrEacpb0ZVXA5rIwylE2Xchk=
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
Expand Down Expand Up @@ -805,7 +804,6 @@ golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
Expand Down Expand Up @@ -923,8 +921,8 @@ gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo=
gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw=
Expand Down Expand Up @@ -956,7 +954,6 @@ k8s.io/component-base v0.20.6/go.mod h1:6f1MPBAeI+mvuts3sIdtpjljHWBQ2cIy38oBIWMY
k8s.io/cri-api v0.17.3/go.mod h1:X1sbHmuXhwaHs9xxYffLqJogVsnI+f6cPRcgPel7ywM=
k8s.io/cri-api v0.20.1/go.mod h1:2JRbKt+BFLTjtrILYVqQK5jqhI+XNdF6UiGMgczeBCI=
k8s.io/cri-api v0.20.4/go.mod h1:2JRbKt+BFLTjtrILYVqQK5jqhI+XNdF6UiGMgczeBCI=
k8s.io/cri-api v0.20.6 h1:iXX0K2pRrbR8yXbZtDK/bSnmg/uSqIFiVJK1x4LUOMc=
k8s.io/cri-api v0.20.6/go.mod h1:ew44AjNXwyn1s0U4xCKGodU7J1HzBeZ1MpGrpa5r8Yc=
k8s.io/cri-api v0.24.1 h1:BNdjWY1zrBUmR5Xg8H9mrM7C+q0n/YPg/TyfA93lDxg=
k8s.io/cri-api v0.24.1/go.mod h1:t3tImFtGeStN+ES69bQUX9sFg67ek38BM9YIJhMmuig=
Expand Down

0 comments on commit 8351158

Please sign in to comment.