Skip to content

Commit 93ec4eb

Browse files
committed
C-WCOW: Move InjectFragment to securitypolicy pkg
Signed-off-by: Mahati Chamarthy <[email protected]>
1 parent e090d17 commit 93ec4eb

File tree

5 files changed

+79
-173
lines changed

5 files changed

+79
-173
lines changed

internal/gcs-sidecar/handlers.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -618,7 +618,7 @@ func (b *Bridge) modifySettings(req *request) (err error) {
618618
if !ok {
619619
return errors.New("the request settings are not of type SecurityPolicyFragment")
620620
}
621-
return b.hostState.InjectFragment(ctx, r)
621+
return b.hostState.securityOptions.InjectFragment(ctx, r)
622622
case guestresource.ResourceTypeWCOWBlockCims:
623623
// This is request to mount the merged cim at given volumeGUID
624624
if modifyGuestSettingsRequest.RequestType == guestrequest.RequestTypeRemove {

internal/gcs-sidecar/host.go

Lines changed: 0 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ package bridge
55

66
import (
77
"context"
8-
"fmt"
98
"io"
109
"os"
1110
"path/filepath"
@@ -15,10 +14,8 @@ import (
1514
hcsschema "github.com/Microsoft/hcsshim/internal/hcs/schema2"
1615
"github.com/Microsoft/hcsshim/internal/log"
1716
"github.com/Microsoft/hcsshim/internal/logfields"
18-
oci "github.com/Microsoft/hcsshim/internal/oci"
1917
"github.com/Microsoft/hcsshim/internal/protocol/guestresource"
2018
"github.com/Microsoft/hcsshim/internal/pspdriver"
21-
"github.com/Microsoft/hcsshim/pkg/annotations"
2219
"github.com/Microsoft/hcsshim/pkg/securitypolicy"
2320
specs "github.com/opencontainers/runtime-spec/specs-go"
2421
"github.com/pkg/errors"
@@ -62,77 +59,6 @@ func NewHost(initialEnforcer securitypolicy.SecurityPolicyEnforcer, logWriter io
6259
}
6360
}
6461

65-
// Write security policy, signed UVM reference and host AMD certificate to
66-
// container's rootfs, so that application and sidecar containers can have
67-
// access to it. The security policy is required by containers which need to
68-
// extract init-time claims found in the security policy. The directory path
69-
// containing the files is exposed via UVM_SECURITY_CONTEXT_DIR env var.
70-
// It may be an error to have a security policy but not expose it to the
71-
// container as in that case it can never be checked as correct by a verifier.
72-
func (h *Host) SetupSecurityContextDir(ctx context.Context, spec *specs.Spec) error {
73-
if oci.ParseAnnotationsBool(ctx, spec.Annotations, annotations.WCOWSecurityPolicyEnv, true) {
74-
encodedPolicy := h.securityPolicyEnforcer.EncodedSecurityPolicy()
75-
hostAMDCert := spec.Annotations[annotations.WCOWHostAMDCertificate]
76-
if len(encodedPolicy) > 0 || len(hostAMDCert) > 0 || len(h.uvmReferenceInfo) > 0 {
77-
// Use os.MkdirTemp to make sure that the directory is unique.
78-
securityContextDir, err := os.MkdirTemp(spec.Root.Path, securitypolicy.SecurityContextDirTemplate)
79-
if err != nil {
80-
return fmt.Errorf("failed to create security context directory: %w", err)
81-
}
82-
// Make sure that files inside directory are readable
83-
if err := os.Chmod(securityContextDir, 0755); err != nil {
84-
return fmt.Errorf("failed to chmod security context directory: %w", err)
85-
}
86-
87-
if len(encodedPolicy) > 0 {
88-
if err := writeFileInDir(securityContextDir, securitypolicy.PolicyFilename, []byte(encodedPolicy), 0777); err != nil {
89-
return fmt.Errorf("failed to write security policy: %w", err)
90-
}
91-
}
92-
if len(h.uvmReferenceInfo) > 0 {
93-
if err := writeFileInDir(securityContextDir, securitypolicy.ReferenceInfoFilename, []byte(h.uvmReferenceInfo), 0777); err != nil {
94-
return fmt.Errorf("failed to write UVM reference info: %w", err)
95-
}
96-
}
97-
98-
if len(hostAMDCert) > 0 {
99-
if err := writeFileInDir(securityContextDir, securitypolicy.HostAMDCertFilename, []byte(hostAMDCert), 0777); err != nil {
100-
return fmt.Errorf("failed to write host AMD certificate: %w", err)
101-
}
102-
}
103-
104-
containerCtxDir := fmt.Sprintf("/%s", filepath.Base(securityContextDir))
105-
secCtxEnv := fmt.Sprintf("UVM_SECURITY_CONTEXT_DIR=%s", containerCtxDir)
106-
spec.Process.Env = append(spec.Process.Env, secCtxEnv)
107-
}
108-
}
109-
return nil
110-
}
111-
112-
// InjectFragment extends current security policy with additional constraints
113-
// from the incoming fragment. Note that it is base64 encoded over the bridge/
114-
//
115-
// There are three checking steps:
116-
// 1 - Unpack the cose document and check it was actually signed with the cert
117-
// chain inside its header
118-
// 2 - Check that the issuer field did:x509 identifier is for that cert chain
119-
// (ie fingerprint of a non leaf cert and the subject matches the leaf cert)
120-
// 3 - Check that this issuer/feed match the requirement of the user provided
121-
// security policy (done in the regoby LoadFragment)
122-
func (h *Host) InjectFragment(ctx context.Context, fragment *guestresource.SecurityPolicyFragment) (err error) {
123-
log.G(ctx).WithField("fragment", fmt.Sprintf("%+v", fragment)).Debug("GCS Host.InjectFragment")
124-
issuer, feed, payloadString, err := securitypolicy.ExtractAndVerifyFragment(ctx, fragment)
125-
if err != nil {
126-
return err
127-
}
128-
// now offer the payload fragment to the policy
129-
err = h.securityOptions.PolicyEnforcer.LoadFragment(ctx, issuer, feed, payloadString)
130-
if err != nil {
131-
return fmt.Errorf("error loading security policy fragment: %w", err)
132-
}
133-
return nil
134-
}
135-
13662
func (h *Host) SetWCOWConfidentialUVMOptions(ctx context.Context, securityPolicyRequest *guestresource.ConfidentialOptions) error {
13763
if err := pspdriver.GetPspDriverError(); err != nil {
13864
// For this case gcs-sidecar will keep initial deny policy.

internal/guest/runtime/hcsv2/uvm.go

Lines changed: 1 addition & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -139,30 +139,6 @@ func (h *Host) SetConfidentialUVMOptions(ctx context.Context, r *guestresource.C
139139
return nil
140140
}
141141

142-
// InjectFragment extends current security policy with additional constraints
143-
// from the incoming fragment. Note that it is base64 encoded over the bridge/
144-
//
145-
// There are three checking steps:
146-
// 1 - Unpack the cose document and check it was actually signed with the cert
147-
// chain inside its header
148-
// 2 - Check that the issuer field did:x509 identifier is for that cert chain
149-
// (ie fingerprint of a non leaf cert and the subject matches the leaf cert)
150-
// 3 - Check that this issuer/feed match the requirement of the user provided
151-
// security policy (done in the regoby LoadFragment)
152-
func (h *Host) InjectFragment(ctx context.Context, fragment *guestresource.SecurityPolicyFragment) (err error) {
153-
log.G(ctx).WithField("fragment", fmt.Sprintf("%+v", fragment)).Debug("GCS Host.InjectFragment")
154-
issuer, feed, payloadString, err := securitypolicy.ExtractAndVerifyFragment(ctx, fragment)
155-
if err != nil {
156-
return err
157-
}
158-
// now offer the payload fragment to the policy
159-
err = h.securityOptions.PolicyEnforcer.LoadFragment(ctx, issuer, feed, payloadString)
160-
if err != nil {
161-
return fmt.Errorf("error loading security policy fragment: %w", err)
162-
}
163-
return nil
164-
}
165-
166142
func (h *Host) SecurityPolicyEnforcer() securitypolicy.SecurityPolicyEnforcer {
167143
return h.securityOptions.PolicyEnforcer
168144
}
@@ -764,7 +740,7 @@ func (h *Host) modifyHostSettings(ctx context.Context, containerID string, req *
764740
if !ok {
765741
return errors.New("the request settings are not of type SecurityPolicyFragment")
766742
}
767-
return h.InjectFragment(ctx, r)
743+
return h.securityOptions.InjectFragment(ctx, r)
768744
default:
769745
return errors.Errorf("the ResourceType %q is not supported for UVM", req.ResourceType)
770746
}

pkg/securitypolicy/securitypolicy_options.go

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,19 @@ package securitypolicy
22

33
import (
44
"context"
5+
"crypto/sha256"
6+
"encoding/base64"
57
"fmt"
68
"io"
9+
"os"
10+
"path/filepath"
711
"sync"
12+
"time"
813

14+
"github.com/Microsoft/cosesign1go/pkg/cosesign1"
15+
didx509resolver "github.com/Microsoft/didx509go/pkg/did-x509-resolver"
16+
"github.com/Microsoft/hcsshim/internal/log"
17+
"github.com/Microsoft/hcsshim/internal/protocol/guestresource"
918
"github.com/pkg/errors"
1019
"github.com/sirupsen/logrus"
1120
)
@@ -70,3 +79,71 @@ func (s *SecurityOptions) SetConfidentialOptions(ctx context.Context, enforcerTy
7079

7180
return nil
7281
}
82+
83+
// Fragment extends current security policy with additional constraints
84+
// from the incoming fragment. Note that it is base64 encoded over the bridge/
85+
//
86+
// There are three checking steps:
87+
// 1 - Unpack the cose document and check it was actually signed with the cert
88+
// chain inside its header
89+
// 2 - Check that the issuer field did:x509 identifier is for that cert chain
90+
// (ie fingerprint of a non leaf cert and the subject matches the leaf cert)
91+
// 3 - Check that this issuer/feed match the requirement of the user provided
92+
// security policy (done in the regoby LoadFragment)
93+
func (s *SecurityOptions) InjectFragment(ctx context.Context, fragment *guestresource.SecurityPolicyFragment) (err error) {
94+
log.G(ctx).WithField("fragment", fmt.Sprintf("%+v", fragment)).Debug("VerifyAndExtractFragment")
95+
96+
raw, err := base64.StdEncoding.DecodeString(fragment.Fragment)
97+
if err != nil {
98+
return fmt.Errorf("failed to decode fragment: %w", err)
99+
}
100+
blob := []byte(fragment.Fragment)
101+
// keep a copy of the fragment, so we can manually figure out what went wrong
102+
// will be removed eventually. Give it a unique name to avoid any potential
103+
// race conditions.
104+
sha := sha256.New()
105+
sha.Write(blob)
106+
timestamp := time.Now()
107+
fragmentPath := fmt.Sprintf("fragment-%x-%d.blob", sha.Sum(nil), timestamp.UnixMilli())
108+
_ = os.WriteFile(filepath.Join(os.TempDir(), fragmentPath), blob, 0644)
109+
110+
unpacked, err := cosesign1.UnpackAndValidateCOSE1CertChain(raw)
111+
if err != nil {
112+
return fmt.Errorf("InjectFragment failed COSE validation: %w", err)
113+
}
114+
115+
payloadString := string(unpacked.Payload[:])
116+
issuer := unpacked.Issuer
117+
feed := unpacked.Feed
118+
chainPem := unpacked.ChainPem
119+
120+
log.G(ctx).WithFields(logrus.Fields{
121+
"issuer": issuer, // eg the DID:x509:blah....
122+
"feed": feed,
123+
"cty": unpacked.ContentType,
124+
"chainPem": chainPem,
125+
}).Debugf("unpacked COSE1 cert chain")
126+
127+
log.G(ctx).WithFields(logrus.Fields{
128+
"payload": payloadString,
129+
}).Tracef("unpacked COSE1 payload")
130+
131+
if len(issuer) == 0 || len(feed) == 0 { // must both be present
132+
return fmt.Errorf("either issuer and feed must both be provided in the COSE_Sign1 protected header")
133+
}
134+
135+
// Resolve returns a did doc that we don't need
136+
// we only care if there was an error or not
137+
_, err = didx509resolver.Resolve(unpacked.ChainPem, issuer, true)
138+
if err != nil {
139+
log.G(ctx).Printf("Badly formed fragment - did resolver failed to match fragment did:x509 from chain with purported issuer %s, feed %s - err %s", issuer, feed, err.Error())
140+
return fmt.Errorf("failed to resolve DID: %w", err)
141+
}
142+
143+
// now offer the payload fragment to the policy
144+
err = s.PolicyEnforcer.LoadFragment(ctx, issuer, feed, payloadString)
145+
if err != nil {
146+
return fmt.Errorf("error loading security policy fragment: %w", err)
147+
}
148+
return nil
149+
}

pkg/securitypolicy/securitypolicyenforcer.go

Lines changed: 0 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -2,22 +2,12 @@ package securitypolicy
22

33
import (
44
"context"
5-
"crypto/sha256"
6-
"encoding/base64"
75
"fmt"
8-
"os"
9-
"path/filepath"
106
"syscall"
11-
"time"
127

13-
"github.com/Microsoft/cosesign1go/pkg/cosesign1"
14-
didx509resolver "github.com/Microsoft/didx509go/pkg/did-x509-resolver"
15-
"github.com/Microsoft/hcsshim/internal/log"
168
"github.com/Microsoft/hcsshim/internal/protocol/guestrequest"
17-
"github.com/Microsoft/hcsshim/internal/protocol/guestresource"
189
oci "github.com/opencontainers/runtime-spec/specs-go"
1910
"github.com/pkg/errors"
20-
"github.com/sirupsen/logrus"
2111
)
2212

2313
type createEnforcerFunc func(base64EncodedPolicy string, criMounts, criPrivilegedMounts []oci.Mount, maxErrorMessageLength int) (SecurityPolicyEnforcer, error)
@@ -151,69 +141,6 @@ func (s stringSet) contains(item string) bool {
151141
return contains
152142
}
153143

154-
// Fragment extends current security policy with additional constraints
155-
// from the incoming fragment. Note that it is base64 encoded over the bridge/
156-
//
157-
// There are three checking steps:
158-
// 1 - Unpack the cose document and check it was actually signed with the cert
159-
// chain inside its header
160-
// 2 - Check that the issuer field did:x509 identifier is for that cert chain
161-
// (ie fingerprint of a non leaf cert and the subject matches the leaf cert)
162-
// 3 - Check that this issuer/feed match the requirement of the user provided
163-
// security policy (done in the regoby LoadFragment)
164-
func ExtractAndVerifyFragment(ctx context.Context, fragment *guestresource.LCOWSecurityPolicyFragment) (issuer string, feed string, payloadString string, err error) {
165-
log.G(ctx).WithField("fragment", fmt.Sprintf("%+v", fragment)).Debug("VerifyAndExtractFragment")
166-
167-
raw, err := base64.StdEncoding.DecodeString(fragment.Fragment)
168-
if err != nil {
169-
return "", "", "", fmt.Errorf("failed to decode fragment: %w", err)
170-
}
171-
blob := []byte(fragment.Fragment)
172-
// keep a copy of the fragment, so we can manually figure out what went wrong
173-
// will be removed eventually. Give it a unique name to avoid any potential
174-
// race conditions.
175-
sha := sha256.New()
176-
sha.Write(blob)
177-
timestamp := time.Now()
178-
fragmentPath := fmt.Sprintf("fragment-%x-%d.blob", sha.Sum(nil), timestamp.UnixMilli())
179-
_ = os.WriteFile(filepath.Join(os.TempDir(), fragmentPath), blob, 0644)
180-
181-
unpacked, err := cosesign1.UnpackAndValidateCOSE1CertChain(raw)
182-
if err != nil {
183-
return "", "", "", fmt.Errorf("InjectFragment failed COSE validation: %w", err)
184-
}
185-
186-
payloadString = string(unpacked.Payload[:])
187-
issuer = unpacked.Issuer
188-
feed = unpacked.Feed
189-
chainPem := unpacked.ChainPem
190-
191-
log.G(ctx).WithFields(logrus.Fields{
192-
"issuer": issuer, // eg the DID:x509:blah....
193-
"feed": feed,
194-
"cty": unpacked.ContentType,
195-
"chainPem": chainPem,
196-
}).Debugf("unpacked COSE1 cert chain")
197-
198-
log.G(ctx).WithFields(logrus.Fields{
199-
"payload": payloadString,
200-
}).Tracef("unpacked COSE1 payload")
201-
202-
if len(issuer) == 0 || len(feed) == 0 { // must both be present
203-
return "", "", "", fmt.Errorf("either issuer and feed must both be provided in the COSE_Sign1 protected header")
204-
}
205-
206-
// Resolve returns a did doc that we don't need
207-
// we only care if there was an error or not
208-
_, err = didx509resolver.Resolve(unpacked.ChainPem, issuer, true)
209-
if err != nil {
210-
log.G(ctx).Printf("Badly formed fragment - did resolver failed to match fragment did:x509 from chain with purported issuer %s, feed %s - err %s", issuer, feed, err.Error())
211-
return "", "", "", err
212-
}
213-
214-
return issuer, feed, payloadString, nil
215-
}
216-
217144
// CreateSecurityPolicyEnforcer returns an appropriate enforcer for input
218145
// parameters. Returns an error if the requested `enforcer` implementation
219146
// isn't registered.

0 commit comments

Comments
 (0)