Skip to content

Commit 37ea7f4

Browse files
committed
Securitypolicy: Move fragment extraction
Move inject and load fragment into the securitypolicy pkg Signed-off-by: Mahati Chamarthy <[email protected]>
1 parent 1ce04dd commit 37ea7f4

File tree

4 files changed

+80
-122
lines changed

4 files changed

+80
-122
lines changed

internal/gcs-sidecar/host.go

Lines changed: 2 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,10 @@ package bridge
55

66
import (
77
"context"
8-
"crypto/sha256"
9-
"encoding/base64"
108
"fmt"
119
"io"
12-
"os"
13-
"path/filepath"
1410
"sync"
15-
"time"
1611

17-
"github.com/Microsoft/cosesign1go/pkg/cosesign1"
18-
didx509resolver "github.com/Microsoft/didx509go/pkg/did-x509-resolver"
1912
"github.com/Microsoft/hcsshim/internal/bridgeutils/gcserr"
2013
hcsschema "github.com/Microsoft/hcsshim/internal/hcs/schema2"
2114
"github.com/Microsoft/hcsshim/internal/log"
@@ -75,61 +68,15 @@ func NewHost(initialEnforcer securitypolicy.SecurityPolicyEnforcer) *Host {
7568
// security policy (done in the regoby LoadFragment)
7669
func (h *Host) InjectFragment(ctx context.Context, fragment *guestresource.LCOWSecurityPolicyFragment) (err error) {
7770
log.G(ctx).WithField("fragment", fmt.Sprintf("%+v", fragment)).Debug("GCS Host.InjectFragment")
78-
79-
raw, err := base64.StdEncoding.DecodeString(fragment.Fragment)
80-
if err != nil {
81-
return err
82-
}
83-
blob := []byte(fragment.Fragment)
84-
// keep a copy of the fragment, so we can manually figure out what went wrong
85-
// will be removed eventually. Give it a unique name to avoid any potential
86-
// race conditions.
87-
sha := sha256.New()
88-
sha.Write(blob)
89-
timestamp := time.Now()
90-
fragmentPath := fmt.Sprintf("fragment-%x-%d.blob", sha.Sum(nil), timestamp.UnixMilli())
91-
_ = os.WriteFile(filepath.Join(os.TempDir(), fragmentPath), blob, 0644)
92-
93-
unpacked, err := cosesign1.UnpackAndValidateCOSE1CertChain(raw)
94-
if err != nil {
95-
return fmt.Errorf("InjectFragment failed COSE validation: %w", err)
96-
}
97-
98-
payloadString := string(unpacked.Payload[:])
99-
issuer := unpacked.Issuer
100-
feed := unpacked.Feed
101-
chainPem := unpacked.ChainPem
102-
103-
log.G(ctx).WithFields(logrus.Fields{
104-
"issuer": issuer, // eg the DID:x509:blah....
105-
"feed": feed,
106-
"cty": unpacked.ContentType,
107-
"chainPem": chainPem,
108-
}).Debugf("unpacked COSE1 cert chain")
109-
110-
log.G(ctx).WithFields(logrus.Fields{
111-
"payload": payloadString,
112-
}).Tracef("unpacked COSE1 payload")
113-
114-
if len(issuer) == 0 || len(feed) == 0 { // must both be present
115-
return fmt.Errorf("either issuer and feed must both be provided in the COSE_Sign1 protected header")
116-
}
117-
118-
// Resolve returns a did doc that we don't need
119-
// we only care if there was an error or not
120-
_, err = didx509resolver.Resolve(unpacked.ChainPem, issuer, true)
71+
issuer, feed, payloadString, err := securitypolicy.ExtractAndVerifyFragment(ctx, fragment)
12172
if err != nil {
122-
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())
12373
return err
12474
}
125-
12675
// now offer the payload fragment to the policy
12776
err = h.securityPolicyEnforcer.LoadFragment(ctx, issuer, feed, payloadString)
12877
if err != nil {
129-
return fmt.Errorf("InjectFragment failed policy load: %w", err)
78+
return fmt.Errorf("error loading security policy fragment: %w", err)
13079
}
131-
log.G(ctx).Printf("passed fragment into the enforcer.")
132-
13380
return nil
13481
}
13582

internal/guest/runtime/hcsv2/uvm.go

Lines changed: 2 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,6 @@ package hcsv2
66
import (
77
"bufio"
88
"context"
9-
"crypto/sha256"
10-
"encoding/base64"
119
"encoding/json"
1210
"fmt"
1311
"io"
@@ -20,8 +18,6 @@ import (
2018
"syscall"
2119
"time"
2220

23-
"github.com/Microsoft/cosesign1go/pkg/cosesign1"
24-
didx509resolver "github.com/Microsoft/didx509go/pkg/did-x509-resolver"
2521
"github.com/Microsoft/hcsshim/internal/bridgeutils/gcserr"
2622
"github.com/Microsoft/hcsshim/internal/debug"
2723
"github.com/Microsoft/hcsshim/internal/guest/policy"
@@ -166,61 +162,15 @@ func (h *Host) SetConfidentialUVMOptions(ctx context.Context, r *guestresource.L
166162
// security policy (done in the regoby LoadFragment)
167163
func (h *Host) InjectFragment(ctx context.Context, fragment *guestresource.LCOWSecurityPolicyFragment) (err error) {
168164
log.G(ctx).WithField("fragment", fmt.Sprintf("%+v", fragment)).Debug("GCS Host.InjectFragment")
169-
170-
raw, err := base64.StdEncoding.DecodeString(fragment.Fragment)
171-
if err != nil {
172-
return err
173-
}
174-
blob := []byte(fragment.Fragment)
175-
// keep a copy of the fragment, so we can manually figure out what went wrong
176-
// will be removed eventually. Give it a unique name to avoid any potential
177-
// race conditions.
178-
sha := sha256.New()
179-
sha.Write(blob)
180-
timestamp := time.Now()
181-
fragmentPath := fmt.Sprintf("fragment-%x-%d.blob", sha.Sum(nil), timestamp.UnixMilli())
182-
_ = os.WriteFile(filepath.Join("/tmp", fragmentPath), blob, 0644)
183-
184-
unpacked, err := cosesign1.UnpackAndValidateCOSE1CertChain(raw)
185-
if err != nil {
186-
return fmt.Errorf("InjectFragment failed COSE validation: %w", err)
187-
}
188-
189-
payloadString := string(unpacked.Payload[:])
190-
issuer := unpacked.Issuer
191-
feed := unpacked.Feed
192-
chainPem := unpacked.ChainPem
193-
194-
log.G(ctx).WithFields(logrus.Fields{
195-
"issuer": issuer, // eg the DID:x509:blah....
196-
"feed": feed,
197-
"cty": unpacked.ContentType,
198-
"chainPem": chainPem,
199-
}).Debugf("unpacked COSE1 cert chain")
200-
201-
log.G(ctx).WithFields(logrus.Fields{
202-
"payload": payloadString,
203-
}).Tracef("unpacked COSE1 payload")
204-
205-
if len(issuer) == 0 || len(feed) == 0 { // must both be present
206-
return fmt.Errorf("either issuer and feed must both be provided in the COSE_Sign1 protected header")
207-
}
208-
209-
// Resolve returns a did doc that we don't need
210-
// we only care if there was an error or not
211-
_, err = didx509resolver.Resolve(unpacked.ChainPem, issuer, true)
165+
issuer, feed, payloadString, err := securitypolicy.ExtractAndVerifyFragment(ctx, fragment)
212166
if err != nil {
213-
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())
214167
return err
215168
}
216-
217169
// now offer the payload fragment to the policy
218170
err = h.securityPolicyEnforcer.LoadFragment(ctx, issuer, feed, payloadString)
219171
if err != nil {
220-
return fmt.Errorf("InjectFragment failed policy load: %w", err)
172+
return fmt.Errorf("error loading security policy fragment: %w", err)
221173
}
222-
log.G(ctx).Printf("passed fragment into the enforcer.")
223-
224174
return nil
225175
}
226176

pkg/securitypolicy/securitypolicyenforcer.go

Lines changed: 76 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,26 @@ package securitypolicy
22

33
import (
44
"context"
5+
"crypto/sha256"
56
"encoding/base64"
67
"encoding/json"
78
"fmt"
9+
"os"
10+
"path/filepath"
811
"regexp"
912
"strconv"
1013
"sync"
1114
"syscall"
15+
"time"
1216

17+
"github.com/Microsoft/cosesign1go/pkg/cosesign1"
18+
didx509resolver "github.com/Microsoft/didx509go/pkg/did-x509-resolver"
19+
"github.com/Microsoft/hcsshim/internal/log"
1320
"github.com/Microsoft/hcsshim/internal/protocol/guestrequest"
21+
"github.com/Microsoft/hcsshim/internal/protocol/guestresource"
1422
oci "github.com/opencontainers/runtime-spec/specs-go"
1523
"github.com/pkg/errors"
24+
"github.com/sirupsen/logrus"
1625
)
1726

1827
type createEnforcerFunc func(base64EncodedPolicy string, criMounts, criPrivilegedMounts []oci.Mount, maxErrorMessageLength int) (SecurityPolicyEnforcer, error)
@@ -124,7 +133,7 @@ type SecurityPolicyEnforcer interface {
124133
EnforceGetPropertiesPolicy(ctx context.Context) error
125134
EnforceDumpStacksPolicy(ctx context.Context) error
126135
EnforceRuntimeLoggingPolicy(ctx context.Context) (err error)
127-
LoadFragment(ctx context.Context, issuer string, feed string, code string) error
136+
LoadFragment(ctx context.Context, issuer string, feed string, rego string) error
128137
EnforceScratchMountPolicy(ctx context.Context, scratchPath string, encrypted bool) (err error)
129138
EnforceScratchUnmountPolicy(ctx context.Context, scratchPath string) (err error)
130139
GetUserInfo(spec *oci.Process, rootPath string) (IDName, []IDName, string, error)
@@ -245,6 +254,69 @@ func createStandardEnforcer(
245254
return enforcer, nil
246255
}
247256

257+
// Fragment extends current security policy with additional constraints
258+
// from the incoming fragment. Note that it is base64 encoded over the bridge/
259+
//
260+
// There are three checking steps:
261+
// 1 - Unpack the cose document and check it was actually signed with the cert
262+
// chain inside its header
263+
// 2 - Check that the issuer field did:x509 identifier is for that cert chain
264+
// (ie fingerprint of a non leaf cert and the subject matches the leaf cert)
265+
// 3 - Check that this issuer/feed match the requirement of the user provided
266+
// security policy (done in the regoby LoadFragment)
267+
func ExtractAndVerifyFragment(ctx context.Context, fragment *guestresource.LCOWSecurityPolicyFragment) (issuer string, feed string, payloadString string, err error) {
268+
log.G(ctx).WithField("fragment", fmt.Sprintf("%+v", fragment)).Debug("VerifyAndExtractFragment")
269+
270+
raw, err := base64.StdEncoding.DecodeString(fragment.Fragment)
271+
if err != nil {
272+
return "", "", "", fmt.Errorf("failed to decode fragment: %w", err)
273+
}
274+
blob := []byte(fragment.Fragment)
275+
// keep a copy of the fragment, so we can manually figure out what went wrong
276+
// will be removed eventually. Give it a unique name to avoid any potential
277+
// race conditions.
278+
sha := sha256.New()
279+
sha.Write(blob)
280+
timestamp := time.Now()
281+
fragmentPath := fmt.Sprintf("fragment-%x-%d.blob", sha.Sum(nil), timestamp.UnixMilli())
282+
_ = os.WriteFile(filepath.Join(os.TempDir(), fragmentPath), blob, 0644)
283+
284+
unpacked, err := cosesign1.UnpackAndValidateCOSE1CertChain(raw)
285+
if err != nil {
286+
return "", "", "", fmt.Errorf("InjectFragment failed COSE validation: %w", err)
287+
}
288+
289+
payloadString = string(unpacked.Payload[:])
290+
issuer = unpacked.Issuer
291+
feed = unpacked.Feed
292+
chainPem := unpacked.ChainPem
293+
294+
log.G(ctx).WithFields(logrus.Fields{
295+
"issuer": issuer, // eg the DID:x509:blah....
296+
"feed": feed,
297+
"cty": unpacked.ContentType,
298+
"chainPem": chainPem,
299+
}).Debugf("unpacked COSE1 cert chain")
300+
301+
log.G(ctx).WithFields(logrus.Fields{
302+
"payload": payloadString,
303+
}).Tracef("unpacked COSE1 payload")
304+
305+
if len(issuer) == 0 || len(feed) == 0 { // must both be present
306+
return "", "", "", fmt.Errorf("either issuer and feed must both be provided in the COSE_Sign1 protected header")
307+
}
308+
309+
// Resolve returns a did doc that we don't need
310+
// we only care if there was an error or not
311+
_, err = didx509resolver.Resolve(unpacked.ChainPem, issuer, true)
312+
if err != nil {
313+
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())
314+
return "", "", "", err
315+
}
316+
317+
return issuer, feed, payloadString, nil
318+
}
319+
248320
// CreateSecurityPolicyEnforcer returns an appropriate enforcer for input parameters.
249321
// When `enforcer` isn't return either an AllowAll or default enforcer.
250322
// Returns an error if the requested `enforcer` implementation isn't registered.
@@ -649,7 +721,7 @@ func (*StandardSecurityPolicyEnforcer) EnforceRuntimeLoggingPolicy(context.Conte
649721

650722
// Stub. We are deprecating the standard enforcer. Newly added enforcement
651723
// points are simply allowed.
652-
func (*StandardSecurityPolicyEnforcer) LoadFragment(context.Context, string, string, string) error {
724+
func (*StandardSecurityPolicyEnforcer) LoadFragment(ctx context.Context, issuer string, feed string, rego string) error {
653725
return nil
654726
}
655727

@@ -1039,7 +1111,7 @@ func (OpenDoorSecurityPolicyEnforcer) EnforceDumpStacksPolicy(context.Context) e
10391111
return nil
10401112
}
10411113

1042-
func (OpenDoorSecurityPolicyEnforcer) LoadFragment(context.Context, string, string, string) error {
1114+
func (OpenDoorSecurityPolicyEnforcer) LoadFragment(ctx context.Context, issuer string, feed string, rego string) error {
10431115
return nil
10441116
}
10451117

@@ -1158,7 +1230,7 @@ func (ClosedDoorSecurityPolicyEnforcer) EnforceDumpStacksPolicy(context.Context)
11581230
return errors.New("getting stack dumps is denied by policy")
11591231
}
11601232

1161-
func (ClosedDoorSecurityPolicyEnforcer) LoadFragment(context.Context, string, string, string) error {
1233+
func (ClosedDoorSecurityPolicyEnforcer) LoadFragment(ctx context.Context, issuer string, feed string, rego string) error {
11621234
return errors.New("loading fragments is denied by policy")
11631235
}
11641236

pkg/securitypolicy/securitypolicyenforcer_rego.go

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -60,17 +60,6 @@ type regoEnforcer struct {
6060

6161
var _ SecurityPolicyEnforcer = (*regoEnforcer)(nil)
6262

63-
//nolint:unused
64-
/*func (sp SecurityPolicy) toInternal() (*securityPolicyInternal, error) {
65-
policy := new(securityPolicyInternal)
66-
var err error
67-
if policy.Containers, err = sp.Containers.toInternal(); err != nil {
68-
return nil, err
69-
}
70-
71-
return policy, nil
72-
}*/
73-
7463
func toStringSet(items []string) stringSet {
7564
s := make(stringSet)
7665
for _, item := range items {

0 commit comments

Comments
 (0)