From 2fc3644c508bd50c0f7ce2fe9f2e26bf8d59f3e3 Mon Sep 17 00:00:00 2001 From: Kirtana Ashok Date: Fri, 3 Jan 2025 18:10:26 -0800 Subject: [PATCH] Add calls to policy enforcer helpers Signed-off-by: Kirtana Ashok --- cmd/gcs-sidecar/internal/bridge/bridge.go | 14 +- cmd/gcs-sidecar/internal/bridge/handlers.go | 309 ++++++++++-------- .../internal/bridge/policy_helpers.go | 42 +++ internal/gcs/guestconnection.go | 2 + 4 files changed, 228 insertions(+), 139 deletions(-) create mode 100644 cmd/gcs-sidecar/internal/bridge/policy_helpers.go diff --git a/cmd/gcs-sidecar/internal/bridge/bridge.go b/cmd/gcs-sidecar/internal/bridge/bridge.go index 010a6112d1..81156b336c 100644 --- a/cmd/gcs-sidecar/internal/bridge/bridge.go +++ b/cmd/gcs-sidecar/internal/bridge/bridge.go @@ -184,6 +184,12 @@ func (b *Bridge) AssignHandlers() { b.HandleFunc(rpcLifecycleNotification, b.lifecycleNotification) // TODO: Validate this request as well? } +type messageHeader struct { + Type uint32 + Size uint32 + ID int64 +} + func readMessage(r io.Reader) (request, error) { var h [hdrSize]byte _, err := io.ReadFull(r, h[:]) @@ -245,7 +251,12 @@ func (b *Bridge) ListenAndServeShimRequests() error { recverr = errors.Wrap(err, "bridge read from shim failed:") break } - log.Printf("bridge recv from shim: \n Header: %v \n msg: %v \n", "", string(req.message)) + var header messageHeader + messageTyp := msgType(binary.LittleEndian.Uint32(req.header[hdrOffType:])) + header.Type = binary.LittleEndian.Uint32(req.header[hdrOffType:]) + header.Size = binary.LittleEndian.Uint32(req.header[hdrOffSize:]) + header.ID = int64(binary.LittleEndian.Uint64(req.header[hdrOffID:])) + log.Printf("bridge recv from shim: \n Header {Type: %v Size: %v ID: %v }\n msg: %v \n", messageTyp, header.Size, header.ID, string(req.message)) shimRequestChan <- req } shimRequestErrChan <- recverr @@ -262,6 +273,7 @@ func (b *Bridge) ListenAndServeShimRequests() error { // 2. Code cleanup on error // ? b.close(err) // b.quitCh <- true // give few seconds delay and close connections? + b.close(err) return } diff --git a/cmd/gcs-sidecar/internal/bridge/handlers.go b/cmd/gcs-sidecar/internal/bridge/handlers.go index ebb5732f08..3b4f4e2b87 100644 --- a/cmd/gcs-sidecar/internal/bridge/handlers.go +++ b/cmd/gcs-sidecar/internal/bridge/handlers.go @@ -10,32 +10,13 @@ import ( "log" "strings" - "github.com/Microsoft/hcsshim/cmd/gcs-sidecar/internal/hcs/schema1" hcsschema "github.com/Microsoft/hcsshim/cmd/gcs-sidecar/internal/hcs/schema2" "github.com/Microsoft/hcsshim/cmd/gcs-sidecar/internal/hcs/schema2/resourcepaths" "github.com/Microsoft/hcsshim/cmd/gcs-sidecar/internal/protocol/guestrequest" "github.com/Microsoft/hcsshim/cmd/gcs-sidecar/internal/protocol/guestresource" - "github.com/Microsoft/hnslib/hcn" + "github.com/Microsoft/hcsshim/hcn" ) -/* - b.HandleFunc(rpcCreate, createContainer) - b.HandleFunc(rpcStart, startContainer) - b.HandleFunc(rpcShutdownGraceful, shutdownGraceful) - b.HandleFunc(rpcShutdownForced, shutdownForced) - b.HandleFunc(rpcExecuteProcess, createProcess) - b.HandleFunc(rpcWaitForProcess, waitForProcess) - b.HandleFunc(rpcSignalProcess, signalProcess) - b.HandleFunc(rpcResizeConsole, resizeConsole) - b.HandleFunc(rpcGetProperties, getProperties) - b.HandleFunc(rpcModifySettings, modifySettings) // This will have the max request types to be validated like mounting container layers, data volumes etc - b.HandleFunc(rpcNegotiateProtocol, negotiateProtocol) - b.HandleFunc(rpcDumpStacks, dumpStacks) - b.HandleFunc(rpcDeleteContainerState, deleteContainerState) - b.HandleFunc(rpcUpdateContainer, updateContainer) - b.HandleFunc(rpcLifecycleNotification, lifecycleNotification) -*/ - // Current intent of these handler functions is to call the security policy // enforcement code as needed and return nil if the operation is allowed. // Else error is returned. @@ -47,24 +28,36 @@ import ( // time in sidecar too? Maybe not. func (b *Bridge) createContainer(req *request) error { var r containerCreate + var containerConfig json.RawMessage + r.ContainerConfig.Value = &containerConfig if err := json.Unmarshal(req.message, &r); err != nil { + log.Printf("failed to unmarshal rpcCreate: %v", req) + // TODO: Send valid error response back to the sender before closing bridge return fmt.Errorf("failed to unmarshal rpcCreate: %v", req) } - switch containerConfig := r.ContainerConfig.Value.(type) { - case hcsschema.HostedSystem: - schemaVersion := containerConfig.SchemaVersion - container := containerConfig.Container - log.Printf("rpcCreate: \n ContainerCreate{ requestBase: %v, ContainerConfig: {schemaVersion: %v, container: %v}}", r.requestBase, schemaVersion, container) - case uvmConfig: - systemType := containerConfig.SystemType - timeZoneInformation := containerConfig.TimeZoneInformation + var err error + var uvmConfig uvmConfig + var hostedSystemConfig hcsschema.HostedSystem + if err = json.Unmarshal(containerConfig, &uvmConfig); err == nil { + systemType := uvmConfig.SystemType + timeZoneInformation := uvmConfig.TimeZoneInformation log.Printf("rpcCreate: \n ContainerCreate{ requestBase: %v, uvmConfig: {systemType: %v, timeZoneInformation: %v}}", r.requestBase, systemType, timeZoneInformation) - default: - return fmt.Errorf("createContainer: invalid containerConfig type. Request: %v", r) + // err = call policyEnforcer + err = nil + } else if err = json.Unmarshal(containerConfig, &hostedSystemConfig); err == nil { + schemaVersion := hostedSystemConfig.SchemaVersion + container := hostedSystemConfig.Container + log.Printf("rpcCreate: \n ContainerCreate{ requestBase: %v, ContainerConfig: {schemaVersion: %v, container: %v}}", r.requestBase, schemaVersion, container) + // err = call policyEnforcer + err = nil + } else { + log.Printf("createContainer: invalid containerConfig type. Request: %v", req) + // TODO: Send valid error response back to the sender before closing bridge + err = fmt.Errorf("createContainer: invalid containerConfig type. Request: %v", r) } - return nil + return err } func (b *Bridge) startContainer(req *request) error { @@ -84,6 +77,13 @@ func (b *Bridge) shutdownGraceful(req *request) error { } log.Printf("rpcShutdownGraceful: \n requestBase: %v", r) + /* + containerdID := r.ContainerdID + b.securityPolicyEnforcer.EnforceShutdownContainerPolicy(ctx, containerID) + if err != nil { + return fmt.Errorf("rpcShudownGraceful operation not allowed: %v", err) + } + */ return nil } @@ -94,28 +94,38 @@ func (b *Bridge) shutdownForced(req *request) error { } log.Printf("rpcShutdownForced: \n requestBase: %v", r) + /* + containerdID := r.ContainerdID + b.securityPolicyEnforcer.EnforceShutdownContainerPolicy(ctx, containerID) + if err != nil { + return fmt.Errorf("rpcShudownGraceful operation not allowed: %v", err) + } + */ + return nil } func (b *Bridge) executeProcess(req *request) error { var r containerExecuteProcess + var processParamSettings json.RawMessage + r.Settings.ProcessParameters.Value = &processParamSettings if err := json.Unmarshal(req.message, &r); err != nil { return fmt.Errorf("failed to unmarshal rpcExecuteProcess: %v", req) } + containerID := r.requestBase.ContainerID stdioRelaySettings := r.Settings.StdioRelaySettings vsockStdioRelaySettings := r.Settings.VsockStdioRelaySettings - switch processParams := r.Settings.ProcessParameters.Value.(type) { - case schema1.ProcessConfig: - log.Printf("rpcExecProcess: \n schema1.ProcessConfig{ params: %v, stdioRelaySettings: %v, vsockStdioRelaySettings: %v }", processParams, stdioRelaySettings, vsockStdioRelaySettings) - // internal/cmd/cmd.go:142 - case hcsschema.ProcessParameters: - log.Printf("rpcExecProcess: \n schema1.ProcessParameters{ params: %v, stdioRelaySettings: %v, vsockStdioRelaySettings: %v }", processParams, stdioRelaySettings, vsockStdioRelaySettings) - default: + var err error + var processParams hcsschema.ProcessParameters + if err = json.Unmarshal(processParamSettings, &processParams); err != nil { log.Printf("rpcExecProcess: invalid params type for request %v", r.Settings) + return fmt.Errorf("rpcExecProcess: invalid params type for request %v", r.Settings) } - return nil + log.Printf("rpcExecProcess: \n containerID: %v, schema1.ProcessParameters{ params: %v, stdioRelaySettings: %v, vsockStdioRelaySettings: %v }", containerID, processParams, stdioRelaySettings, vsockStdioRelaySettings) + // err = call policy enforcer + return err } func (b *Bridge) waitForProcess(req *request) error { @@ -125,20 +135,34 @@ func (b *Bridge) waitForProcess(req *request) error { } log.Printf("rpcWaitForProcess: \n containerWaitForProcess{ requestBase: %v, processID: %v, timeoutInMs: %v }", r.requestBase, r.ProcessID, r.TimeoutInMs) + // waitForProcess does not have enforcer in clcow, why? return nil } func (b *Bridge) signalProcess(req *request) error { var r containerSignalProcess + var rawOpts json.RawMessage + r.Options = &rawOpts if err := json.Unmarshal(req.message, &r); err != nil { return fmt.Errorf("failed to unmarshal rpcSignalProcess: %v", req) } - switch opts := r.Options.(type) { - case guestresource.SignalProcessOptionsWCOW: - log.Printf("rpcSignalProcess: \n containerSignalProcess{ requestBase: %v, processID: %v, Options: %v }", r.requestBase, r.ProcessID, opts) - default: + log.Printf("rpcSignalProcess: request %v", r) + + var err error + var wcowOptions guestresource.SignalProcessOptionsWCOW + if rawOpts == nil { + return nil + } else if err = json.Unmarshal(rawOpts, &wcowOptions); err != nil { log.Printf("rpcSignalProcess: invalid Options type for request %v", r) + return fmt.Errorf("rpcSignalProcess: invalid Options type for request %v", r) + } + log.Printf("rpcSignalProcess: \n containerSignalProcess{ requestBase: %v, processID: %v, Options: %v }", r.requestBase, r.ProcessID, wcowOptions) + + // calling policy enforcer + err = signalProcess(r.ContainerID, r.ProcessID, wcowOptions.Signal) + if err != nil { + return fmt.Errorf("waitForProcess not allowed due to policy") } return nil @@ -149,7 +173,12 @@ func (b *Bridge) resizeConsole(req *request) error { if err := json.Unmarshal(req.message, &r); err != nil { return fmt.Errorf("failed to unmarshal rpcSignalProcess: %v", req) } - log.Printf("rpcResizeConsole: \n containerResizeConsole{ requestBase: %v, processID: %v, height: %v, width: %v }", r.requestBase, r.ProcessID, r.Width) + log.Printf("rpcResizeConsole: \n containerResizeConsole{ requestBase: %v, processID: %v, height: %v, width: %v }", r.requestBase, r.ProcessID, r.Height, r.Width) + + err := resizeConsole(r.ContainerID, r.Height, r.Width) + if err != nil { + return fmt.Errorf("waitForProcess not allowed due to policy") + } return nil } @@ -191,107 +220,111 @@ func isSpecialResourcePaths(resourcePath string, settings interface{}) bool { return false } +func unMarshalAndModifySettings(modifySettingsRequest *hcsschema.ModifySettingRequest, requestRawSettings *json.RawMessage) error { + if modifySettingsRequest.ResourcePath != "" { + reqType := modifySettingsRequest.RequestType + resourcePath := modifySettingsRequest.ResourcePath + + log.Printf("rpcModifySettings: ModifySettingRequest { RequestType: %v \n, ResourcePath: %v", reqType, resourcePath) + + switch resourcePath { + case resourcepaths.SiloMappedDirectoryResourcePath: + mappedDirectory := modifySettingsRequest.Settings.(*hcsschema.MappedDirectory) + // TODO: check for Settings to be nil as in some examples + log.Printf(", mappedDirectory: %v \n", mappedDirectory) + case resourcepaths.SiloMemoryResourcePath: + memoryLimit := modifySettingsRequest.Settings.(*uint64) + log.Printf(", memoryLimit: %v \n", memoryLimit) + case resourcepaths.SiloProcessorResourcePath: + processor := modifySettingsRequest.Settings.(*hcsschema.Processor) + log.Printf(", processor: %v \n", processor) + case resourcepaths.CPUGroupResourcePath: + cpuGroup := modifySettingsRequest.Settings.(*hcsschema.CpuGroup) + log.Printf(", cpuGroup: %v \n", cpuGroup) + case resourcepaths.CPULimitsResourcePath: + processorLimits := modifySettingsRequest.Settings.(*hcsschema.ProcessorLimits) + log.Printf(", processorLimits: %v \n", processorLimits) + case resourcepaths.MemoryResourcePath: + actualMemory := modifySettingsRequest.Settings.(*uint64) + log.Printf(", actualMemory: %v \n", actualMemory) + case resourcepaths.VSMBShareResourcePath: + virtualSmbShareSettings := modifySettingsRequest.Settings.(*hcsschema.VirtualSmbShare) + log.Printf(", VirtualSmbShare: %v \n", virtualSmbShareSettings) + // TODO: Plan9 is only for LCOW right? + // case resourcepaths.Plan9ShareResourcePath: + // plat9ShareSettings := modifyRequest.Settings.(*hcsschema.Plan9Share) + // log.Printf(", Plan9Share: %v \n", plat9ShareSettings) + + // TODO: Does following apply for cwcow? + // case resourcepaths.VirtualPCIResourceFormat + // case resourcepaths.VPMemControllerResourceFormat + default: + // Handle cases of HvSocketConfigResourcePrefix, NetworkResourceFormatetc as they have data values in resourcePath string + if !isSpecialResourcePaths(resourcePath, modifySettingsRequest.Settings) { + return fmt.Errorf("invalid rpcModifySettings resourcePath %v", resourcePath) + } + } + } + + if modifySettingsRequest.GuestRequest != nil { + var guestModificationRequest guestrequest.ModificationRequest + if err := json.Unmarshal(*requestRawSettings, &guestModificationRequest); err != nil { + log.Printf("invalid modifySettingsRequest.guestRequest") + return fmt.Errorf("invalid modifySettingsRequest.guestRequest") + } + // modifyRequest.GuestRequest != nil + + guestResourceType := guestModificationRequest.ResourceType + guestRequestType := guestModificationRequest.RequestType + + log.Printf("rpcModifySettings: guestRequest.ModificationRequest { resourceType: %v \n, requestType: %v", guestResourceType, guestRequestType) + + switch guestResourceType { + case guestresource.ResourceTypeCombinedLayers: + settings := guestModificationRequest.Settings.(*guestresource.WCOWCombinedLayers) + log.Printf(", WCOWCombinedLayers {ContainerRootPath: %v, Layers: %v, ScratchPath: %v} \n", settings.ContainerRootPath, settings.Layers, settings.ScratchPath) + case guestresource.ResourceTypeNetworkNamespace: + settings := guestModificationRequest.Settings.(*hcn.HostComputeNamespace) + log.Printf(", HostComputeNamespaces { %v} \n", settings) + case guestresource.ResourceTypeNetwork: + // following valid only for osversion.Build() >= osversion.RS5 + // since Cwcow is available only for latest versions this is ok + settings := guestModificationRequest.Settings.(*guestrequest.NetworkModifyRequest) + log.Printf(", NetworkModifyRequest { %v} \n", settings) + case guestresource.ResourceTypeMappedVirtualDisk: + wcowMappedVirtualDisk := guestModificationRequest.Settings.(*guestresource.WCOWMappedVirtualDisk) + log.Printf(", wcowMappedVirtualDisk { %v} \n", wcowMappedVirtualDisk) + // TODO need a case similar to guestresource.ResourceTypeSecurityPolicy of lcow? + // case guestresource.ResourceTypeSecurityPolicy: + case guestresource.ResourceTypeHvSocket: + hvSocketAddress := guestModificationRequest.Settings.(*hcsschema.HvSocketAddress) + log.Printf(", hvSocketAddress { %v} \n", hvSocketAddress) + default: + isSpecialGuestRequests(string(guestResourceType), guestModificationRequest.Settings) + // invalid + } + } + + // call policy enforcer + return nil +} + func (b *Bridge) modifySettings(req *request) error { var r containerModifySettings + var requestRawSettings json.RawMessage + r.Request = &requestRawSettings if err := json.Unmarshal(req.message, &r); err != nil { return fmt.Errorf("failed to unmarshal rpcModifySettings: %v", req) } - switch modifyRequest := r.Request.(type) { - case hcsschema.ModifySettingRequest: - // TODO: Clean up + more testing - - // TODO: Which of these requests are not valid for c-wcow case? - // modifyRequest.GuestRequest != nil && modifyRequest.Settings != nil. Example resourcepaths.Plan9ShareResourcePath - // Validate all requests coming in? - if modifyRequest.ResourcePath != "" { - reqType := modifyRequest.RequestType - resourcePath := modifyRequest.ResourcePath - - log.Printf("rpcModifySettings: ModifySettingRequest { RequestType: %v \n, ResourcePath: %v", reqType, resourcePath) - - switch resourcePath { - case resourcepaths.SiloMappedDirectoryResourcePath: - mappedDirectory := modifyRequest.Settings.(*hcsschema.MappedDirectory) - // TODO: check for Settings to be nil as in some examples - log.Printf(", mappedDirectory: %v \n", mappedDirectory) - case resourcepaths.SiloMemoryResourcePath: - memoryLimit := modifyRequest.Settings.(*uint64) - log.Printf(", memoryLimit: %v \n", memoryLimit) - case resourcepaths.SiloProcessorResourcePath: - processor := modifyRequest.Settings.(*hcsschema.Processor) - log.Printf(", processor: %v \n", processor) - case resourcepaths.CPUGroupResourcePath: - cpuGroup := modifyRequest.Settings.(*hcsschema.CpuGroup) - log.Printf(", cpuGroup: %v \n", cpuGroup) - case resourcepaths.CPULimitsResourcePath: - processorLimits := modifyRequest.Settings.(*hcsschema.ProcessorLimits) - log.Printf(", processorLimits: %v \n", processorLimits) - case resourcepaths.MemoryResourcePath: - actualMemory := modifyRequest.Settings.(*uint64) - log.Printf(", actualMemory: %v \n", actualMemory) - case resourcepaths.VSMBShareResourcePath: - virtualSmbShareSettings := modifyRequest.Settings.(*hcsschema.VirtualSmbShare) - log.Printf(", VirtualSmbShare: %v \n", virtualSmbShareSettings) - // TODO: Plan9 is only for LCOW right? - // case resourcepaths.Plan9ShareResourcePath: - // plat9ShareSettings := modifyRequest.Settings.(*hcsschema.Plan9Share) - // log.Printf(", Plan9Share: %v \n", plat9ShareSettings) - - // TODO: Does following apply for cwcow? - // case resourcepaths.VirtualPCIResourceFormat - // case resourcepaths.VPMemControllerResourceFormat - default: - // Handle cases of HvSocketConfigResourcePrefix, NetworkResourceFormatetc as they have data values in resourcePath string - if !isSpecialResourcePaths(resourcePath, modifyRequest.Settings) { - return fmt.Errorf("invalid rpcModifySettings resourcePath %v", resourcePath) - } - } - } - - if modifyRequest.GuestRequest != nil { - // modifyRequest.GuestRequest != nil - switch guestRequest := modifyRequest.GuestRequest.(type) { - case guestrequest.ModificationRequest: - guestResourceType := guestRequest.ResourceType - guestRequestType := guestRequest.RequestType - - log.Printf("rpcModifySettings: guestRequest.ModificationRequest { resourceType: %v \n, requestType: %v", guestResourceType, guestRequestType) - - switch guestResourceType { - case guestresource.ResourceTypeCombinedLayers: - settings := guestRequest.Settings.(*guestresource.WCOWCombinedLayers) - log.Printf(", WCOWCombinedLayers {ContainerRootPath: %v, Layers: %v, ScratchPath: %v} \n", settings.ContainerRootPath, settings.Layers, settings.ScratchPath) - case guestresource.ResourceTypeNetworkNamespace: - settings := guestRequest.Settings.(*hcn.HostComputeNamespace) - log.Printf(", HostComputeNamespaces { %v} \n", settings) - case guestresource.ResourceTypeNetwork: - // following valid only for osversion.Build() >= osversion.RS5 - // since Cwcow is available only for latest versions this is ok - settings := guestRequest.Settings.(*guestrequest.NetworkModifyRequest) - log.Printf(", NetworkModifyRequest { %v} \n", settings) - case guestresource.ResourceTypeMappedVirtualDisk: - wcowMappedVirtualDisk := guestRequest.Settings.(*guestresource.WCOWMappedVirtualDisk) - log.Printf(", wcowMappedVirtualDisk { %v} \n", wcowMappedVirtualDisk) - // TODO need a case similar to guestresource.ResourceTypeSecurityPolicy of lcow? - // case guestresource.ResourceTypeSecurityPolicy: - case guestresource.ResourceTypeHvSocket: - hvSocketAddress := guestRequest.Settings.(*hcsschema.HvSocketAddress) - log.Printf(", hvSocketAddress { %v} \n", hvSocketAddress) - default: - isSpecialGuestRequests(string(guestResourceType), guestRequest.Settings) - // invalid - } - - default: - // invalid request - } - } - - default: - return fmt.Errorf("invalid rpcModifySettings request %v", modifyRequest) + var err error + var modifySettingsRequest hcsschema.ModifySettingRequest + if err = json.Unmarshal(requestRawSettings, &modifySettingsRequest); err != nil { + log.Printf("invalid rpcModifySettings request %v", r) + return fmt.Errorf("invalid rpcModifySettings request %v", r) } - return nil + + return unMarshalAndModifySettings(&modifySettingsRequest, &requestRawSettings) } func isSpecialGuestRequests(guestResourceType string, settings interface{}) bool { diff --git a/cmd/gcs-sidecar/internal/bridge/policy_helpers.go b/cmd/gcs-sidecar/internal/bridge/policy_helpers.go new file mode 100644 index 0000000000..c2007fa2dd --- /dev/null +++ b/cmd/gcs-sidecar/internal/bridge/policy_helpers.go @@ -0,0 +1,42 @@ +//go:build windows +// +build windows + +package bridge + +import ( + "context" + + hcsschema "github.com/Microsoft/hcsshim/cmd/gcs-sidecar/internal/hcs/schema2" + "github.com/Microsoft/hcsshim/cmd/gcs-sidecar/internal/protocol/guestrequest" +) + +func ExecProcess(ctx context.Context, containerID string, params hcsschema.ProcessParameters) error { + /* + + err = h.securityPolicyEnforcer.EnforceExecExternalProcessPolicy( + ctx, + params.CommandArgs, + processParamEnvTOOCIEnv(params.Environment), + params.WorkingDirectory, + ) + if err != nil { + return errors.Wrapf(err, "exec is denied due to policy") + } + */ + return nil +} + +func signalProcess(containerID string, processID uint32, signal guestrequest.SignalValueWCOW) error { + /* + err = h.securityPolicyEnforcer.EnforceSignalContainerProcessPolicy(ctx, containerID, signal, signalingInitProcess, startupArgList) + if err != nil { + return err + } + */ + + return nil +} +func resizeConsole(containerID string, height uint16, width uint16) error { + // not validated in clcow + return nil +} diff --git a/internal/gcs/guestconnection.go b/internal/gcs/guestconnection.go index 73282e95d0..b75ebe9703 100644 --- a/internal/gcs/guestconnection.go +++ b/internal/gcs/guestconnection.go @@ -146,6 +146,8 @@ func (gc *GuestConnection) connect(ctx context.Context, isColdStart bool, initGu requestBase: makeRequest(ctx, nullContainerID), ContainerConfig: anyInString{conf}, } + log.G(ctx).Debugf("!! ContainerCreate request %v", createReq) + log.G(ctx).Debugf("!! ContainerCreate.ContainerConfig request %v", createReq.ContainerConfig.Value) var createResp responseBase err = gc.brdg.RPC(ctx, rpcCreate, &createReq, &createResp, true) if err != nil {