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.sum
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20190514135907-3a4b5fb9f71f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3 h1:7TYNF4UdlohbFwpNH04CoPMp1cHUZgO1Ebq5r2hIjfo=
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd h1:xhmwyvizuTgC2qz7ZlMluP20uW+C3Rm0FD/WLDX8884=
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
Expand Down
49 changes: 49 additions & 0 deletions internal/hcs/service.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package hcs

import (
"context"
"encoding/json"

hcsschema "github.com/Microsoft/hcsshim/internal/schema2"
"github.com/Microsoft/hcsshim/internal/vmcompute"
)

// GetServiceProperties returns properties of the host compute service.
func GetServiceProperties(ctx context.Context, q hcsschema.PropertyQuery) (*hcsschema.ServiceProperties, error) {
operation := "hcsshim::GetServiceProperties"

queryb, err := json.Marshal(q)
if err != nil {
return nil, err
}
propertiesJSON, resultJSON, err := vmcompute.HcsGetServiceProperties(ctx, string(queryb))
events := processHcsResult(ctx, resultJSON)
if err != nil {
return nil, &HcsError{Op: operation, Err: err, Events: events}
}

if propertiesJSON == "" {
return nil, ErrUnexpectedValue
}
properties := &hcsschema.ServiceProperties{}
if err := json.Unmarshal([]byte(propertiesJSON), properties); err != nil {
return nil, err
}
return properties, nil
}

// ModifyServiceSettings modifies settings of the host compute service.
func ModifyServiceSettings(ctx context.Context, settings hcsschema.ModificationRequest) error {
operation := "hcsshim::ModifyServiceSettings"

settingsJSON, err := json.Marshal(settings)
if err != nil {
return err
}
resultJSON, err := vmcompute.HcsModifyServiceSettings(ctx, string(settingsJSON))
events := processHcsResult(ctx, resultJSON)
if err != nil {
return &HcsError{Op: operation, Err: err, Events: events}
}
return nil
}
3 changes: 1 addition & 2 deletions internal/hcs/system.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,7 @@ type System struct {
waitBlock chan struct{}
waitError error
exitError error

os, typ string
os, typ string
}

func newSystem(id string) *System {
Expand Down
1 change: 1 addition & 0 deletions internal/hcsoci/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ type createOptionsInternal struct {
actualID string // Identifier for the container
actualOwner string // Owner for the container
actualNetworkNamespace string
ccgState *hcsschema.ContainerCredentialGuardState // Container Credential Guard information to be attached to HCS container document
}

// CreateContainer creates a container. It can cope with a wide variety of
Expand Down
131 changes: 131 additions & 0 deletions internal/hcsoci/credentials.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
// +build windows

package hcsoci

import (
"context"
"encoding/json"
"errors"
"fmt"

"github.com/Microsoft/hcsshim/internal/hcs"
"github.com/Microsoft/hcsshim/internal/log"
hcsschema "github.com/Microsoft/hcsshim/internal/schema2"
)

// This file holds the necessary structs and functions for adding and removing Container
// Credential Guard instances (shortened to CCG normally) for V2 HCS schema
// containers. Container Credential Guard is in HCS's own words "The solution to
// allowing windows containers to have access to domain credentials for the
// applications running in their corresponding guest." It essentially acts as
// a way to temporarily Active Directory join a given container with a Group
// Managed Service Account (GMSA for short) credential specification.
// CCG will launch a process in the host that will act as a middleman for the
// credential passthrough logic. The guest is then configured through registry
// keys to have access to the process in the host.
// A CCG instance needs to be created through various HCS calls and then added to
// the V2 schema container document before being sent to HCS. For V1 HCS schema containers
// setting up instances manually is not needed, the GMSA credential specification
// simply needs to be present in the V1 container document.

// CCGInstance stores the id used when creating a ccg instance. Used when
// closing a container to be able to release the instance.
type CCGInstance struct {
// ID of container that instance belongs to.
id string
}

// Release calls into hcs to remove the ccg instance. These do not get cleaned up automatically
// they MUST be explicitly removed with a call to ModifyServiceSettings. The instances will persist
// unless vmcompute.exe exits or they are removed manually as done here.
func (instance *CCGInstance) Release(ctx context.Context) error {
if err := removeCredentialGuard(ctx, instance.id); err != nil {
return fmt.Errorf("failed to remove container credential guard instance: %s", err)
}
return nil
}

// CreateCredentialGuard creates a container credential guard instance and
// returns the state object to be placed in a v2 container doc.
func CreateCredentialGuard(ctx context.Context, id, credSpec string, hypervisorIsolated bool) (*hcsschema.ContainerCredentialGuardState, *CCGInstance, error) {
log.G(ctx).WithField("containerID", id).Debug("creating container credential guard instance")
// V2 schema ccg setup a little different as its expected to be passed
// through all the way to the gcs. Can no longer be enabled just through
// a single property. The flow is as follows
// ------------------------------------------------------------------------
// 1. Call HcsModifyServiceSettings with a ModificationRequest set with a
// ContainerCredentialGuardAddInstanceRequest. This is where the cred spec
// gets passed in. Transport either "LRPC" (Argon) or "HvSocket" (Xenon).
// 2. Query the instance with a call to HcsGetServiceProperties with the
// PropertyType "ContainerCredentialGuard". This will return all instances
// 3. Parse for the id of our container to find which one correlates to the
// container we're building the doc for, then add to the V2 doc.
// 4. If xenon container the hvsocketconfig will need to be in the UVMs V2
// schema HcsComputeSystem document before being created/sent to HCS. It must
// be in the doc at creation time as we do not support hot adding hvsocket
// service table entries.
// This is currently a blocker for adding support for hyper-v gmsa.
transport := "LRPC"
if hypervisorIsolated {
// TODO(Dcantah) Set transport to HvSocket here when this is supported
return nil, nil, errors.New("hypervisor isolated containers with v2 HCS schema do not support GMSA")
}
req := hcsschema.ModificationRequest{
PropertyType: hcsschema.PTContainerCredentialGuard,
Settings: &hcsschema.ContainerCredentialGuardOperationRequest{
Operation: hcsschema.AddInstance,
OperationDetails: &hcsschema.ContainerCredentialGuardAddInstanceRequest{
Id: id,
CredentialSpec: credSpec,
Transport: transport,
},
},
}
if err := hcs.ModifyServiceSettings(ctx, req); err != nil {
return nil, nil, fmt.Errorf("failed to generate container credential guard instance: %s", err)
}

q := hcsschema.PropertyQuery{
PropertyTypes: []hcsschema.PropertyType{hcsschema.PTContainerCredentialGuard},
}
serviceProps, err := hcs.GetServiceProperties(ctx, q)
if err != nil {
return nil, nil, fmt.Errorf("failed to retrieve container credential guard instances: %s", err)
}
if len(serviceProps.Properties) != 1 {
return nil, nil, errors.New("wrong number of service properties present")
}

ccgSysInfo := &hcsschema.ContainerCredentialGuardSystemInfo{}
if err := json.Unmarshal(serviceProps.Properties[0], ccgSysInfo); err != nil {
return nil, nil, fmt.Errorf("failed to unmarshal container credential guard instances: %s", err)
}
for _, ccgInstance := range ccgSysInfo.Instances {
if ccgInstance.Id == id {
instance := &CCGInstance{
id,
}
return ccgInstance.CredentialGuard, instance, nil
}
}
return nil, nil, fmt.Errorf("failed to find credential guard instance with container ID %s", id)
}

// Removes a ContainerCredentialGuard instance by container ID.
func removeCredentialGuard(ctx context.Context, id string) error {
log.G(ctx).WithField("containerID", id).Debug("removing container credential guard")

req := hcsschema.ModificationRequest{
PropertyType: hcsschema.PTContainerCredentialGuard,
Settings: &hcsschema.ContainerCredentialGuardOperationRequest{
Operation: hcsschema.RemoveInstance,
OperationDetails: &hcsschema.ContainerCredentialGuardRemoveInstanceRequest{
Id: id,
},
},
}
if err := hcs.ModifyServiceSettings(ctx, req); err != nil {
return err
}
return nil
}
7 changes: 6 additions & 1 deletion internal/hcsoci/hcsdoc_wcow.go
Original file line number Diff line number Diff line change
Expand Up @@ -165,9 +165,14 @@ func createWindowsContainerDocument(ctx context.Context, coi *createOptionsInter
v2Container.Networking.NetworkSharedContainerName = v1.NetworkSharedContainerName
}

// // TODO V2 Credentials not in the schema yet.
if cs, ok := coi.Spec.Windows.CredentialSpec.(string); ok {
v1.Credentials = cs
// If this is a HCS v2 schema container, we created the CCG instance
// with the other container resources. Pass the CCG state information
// as part of the container document.
if coi.ccgState != nil {
Comment thread
dcantah marked this conversation as resolved.
v2Container.ContainerCredentialGuard = coi.ccgState
}
}

if coi.Spec.Root == nil {
Expand Down
20 changes: 17 additions & 3 deletions internal/hcsoci/resources.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package hcsoci

import (
"context"
"errors"
"os"

"github.com/Microsoft/hcsshim/internal/log"
Expand Down Expand Up @@ -76,29 +77,42 @@ func ReleaseResources(ctx context.Context, r *Resources, vm *uvm.UtilityVM, all
}
}

releaseErr := false
// Release resources in reverse order so that the most recently
// added are cleaned up first.
// added are cleaned up first. We don't return an error right away
// so that other resources still get cleaned up in the case of one
// or more failing.
for i := len(r.resources) - 1; i >= 0; i-- {
switch r.resources[i].(type) {
case *uvm.NetworkEndpoints:
if r.createdNetNS {
if err := r.resources[i].Release(ctx); err != nil {
return err
log.G(ctx).WithError(err).Error("failed to release container resource")
releaseErr = true
}
r.createdNetNS = false
}
case *CCGInstance:
if err := r.resources[i].Release(ctx); err != nil {
log.G(ctx).WithError(err).Error("failed to release container resource")
releaseErr = true
}
default:
// Don't need to check if vm != nil here anymore as they wouldnt
// have been added in the first place. All resources have embedded
// vm they belong to.
if all {
Comment thread
kevpar marked this conversation as resolved.
if err := r.resources[i].Release(ctx); err != nil {
return err
log.G(ctx).WithError(err).Error("failed to release container resource")
releaseErr = true
}
}
}
}
r.resources = nil
if releaseErr {
return errors.New("failed to release one or more container resources")
}

// cleanup container state
if vm != nil {
Expand Down
13 changes: 13 additions & 0 deletions internal/hcsoci/resources_wcow.go
Original file line number Diff line number Diff line change
Expand Up @@ -138,5 +138,18 @@ func allocateWindowsResources(ctx context.Context, coi *createOptionsInternal, r
}
}

if cs, ok := coi.Spec.Windows.CredentialSpec.(string); ok {
// Only need to create a CCG instance for v2 containers
if schemaversion.IsV21(coi.actualSchemaVersion) {
hypervisorIsolated := coi.HostingSystem != nil
ccgState, ccgInstance, err := CreateCredentialGuard(ctx, coi.actualID, cs, hypervisorIsolated)
if err != nil {
return err
}
coi.ccgState = ccgState
r.resources = append(r.resources, ccgInstance)
//TODO dcantah: If/when dynamic service table entries is supported register the RpcEndpoint with hvsocket here
}
}
return nil
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
/*
* HCS API
*
* No description provided (generated by Swagger Codegen https://github.com/swagger-api/swagger-codegen)
*
* API version: 2.4
* Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git)
*/

package hcsschema

type ContainerCredentialGuardAddInstanceRequest struct {
Id string `json:"Id,omitempty"`
CredentialSpec string `json:"CredentialSpec,omitempty"`
Transport string `json:"Transport,omitempty"`
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/*
* HCS API
*
* No description provided (generated by Swagger Codegen https://github.com/swagger-api/swagger-codegen)
*
* API version: 2.4
* Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git)
*/

package hcsschema

type ContainerCredentialGuardHvSocketServiceConfig struct {
ServiceId string `json:"ServiceId,omitempty"`
ServiceConfig *HvSocketServiceConfig `json:"ServiceConfig,omitempty"`
}
16 changes: 16 additions & 0 deletions internal/schema2/container_credential_guard_instance.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
/*
* HCS API
*
* No description provided (generated by Swagger Codegen https://github.com/swagger-api/swagger-codegen)
*
* API version: 2.4
* Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git)
*/

package hcsschema

type ContainerCredentialGuardInstance struct {
Id string `json:"Id,omitempty"`
CredentialGuard *ContainerCredentialGuardState `json:"CredentialGuard,omitempty"`
HvSocketConfig *ContainerCredentialGuardHvSocketServiceConfig `json:"HvSocketConfig,omitempty"`
}
17 changes: 17 additions & 0 deletions internal/schema2/container_credential_guard_modify_operation.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
/*
* HCS API
*
* No description provided (generated by Swagger Codegen https://github.com/swagger-api/swagger-codegen)
*
* API version: 2.4
* Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git)
*/

package hcsschema

type ContainerCredentialGuardModifyOperation string

const (
AddInstance ContainerCredentialGuardModifyOperation = "AddInstance"
RemoveInstance ContainerCredentialGuardModifyOperation = "RemoveInstance"
)
15 changes: 15 additions & 0 deletions internal/schema2/container_credential_guard_operation_request.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/*
* HCS API
*
* No description provided (generated by Swagger Codegen https://github.com/swagger-api/swagger-codegen)
*
* API version: 2.4
* Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git)
*/

package hcsschema

type ContainerCredentialGuardOperationRequest struct {
Operation ContainerCredentialGuardModifyOperation `json:"Operation,omitempty"`
OperationDetails interface{} `json:"OperationDetails,omitempty"`
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/*
* HCS API
*
* No description provided (generated by Swagger Codegen https://github.com/swagger-api/swagger-codegen)
*
* API version: 2.4
* Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git)
*/

package hcsschema

type ContainerCredentialGuardRemoveInstanceRequest struct {
Id string `json:"Id,omitempty"`
}
Loading