Skip to content

Commit 57a463d

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 57a463d

File tree

4 files changed

+79
-122
lines changed

4 files changed

+79
-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: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@ type SecurityPolicyEnforcer interface {
124124
EnforceGetPropertiesPolicy(ctx context.Context) error
125125
EnforceDumpStacksPolicy(ctx context.Context) error
126126
EnforceRuntimeLoggingPolicy(ctx context.Context) (err error)
127-
LoadFragment(ctx context.Context, issuer string, feed string, code string) error
127+
LoadFragment(ctx context.Context, issuer string, feed string, rego string) error
128128
EnforceScratchMountPolicy(ctx context.Context, scratchPath string, encrypted bool) (err error)
129129
EnforceScratchUnmountPolicy(ctx context.Context, scratchPath string) (err error)
130130
GetUserInfo(spec *oci.Process, rootPath string) (IDName, []IDName, string, error)
@@ -649,7 +649,7 @@ func (*StandardSecurityPolicyEnforcer) EnforceRuntimeLoggingPolicy(context.Conte
649649

650650
// Stub. We are deprecating the standard enforcer. Newly added enforcement
651651
// points are simply allowed.
652-
func (*StandardSecurityPolicyEnforcer) LoadFragment(context.Context, string, string, string) error {
652+
func (*StandardSecurityPolicyEnforcer) LoadFragment(ctx context.Context, issuer string, feed string, rego string) error {
653653
return nil
654654
}
655655

@@ -1039,7 +1039,7 @@ func (OpenDoorSecurityPolicyEnforcer) EnforceDumpStacksPolicy(context.Context) e
10391039
return nil
10401040
}
10411041

1042-
func (OpenDoorSecurityPolicyEnforcer) LoadFragment(context.Context, string, string, string) error {
1042+
func (OpenDoorSecurityPolicyEnforcer) LoadFragment(ctx context.Context, issuer string, feed string, rego string) error {
10431043
return nil
10441044
}
10451045

@@ -1158,7 +1158,7 @@ func (ClosedDoorSecurityPolicyEnforcer) EnforceDumpStacksPolicy(context.Context)
11581158
return errors.New("getting stack dumps is denied by policy")
11591159
}
11601160

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

pkg/securitypolicy/securitypolicyenforcer_rego.go

Lines changed: 71 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,19 +5,27 @@ package securitypolicy
55

66
import (
77
"context"
8+
"crypto/sha256"
89
_ "embed"
910
"encoding/base64"
1011
"encoding/json"
1112
"fmt"
13+
"os"
14+
"path/filepath"
1215
"strconv"
1316
"strings"
1417
"syscall"
18+
"time"
1519

20+
"github.com/Microsoft/cosesign1go/pkg/cosesign1"
21+
didx509resolver "github.com/Microsoft/didx509go/pkg/did-x509-resolver"
1622
"github.com/Microsoft/hcsshim/internal/guestpath"
1723
"github.com/Microsoft/hcsshim/internal/log"
24+
"github.com/Microsoft/hcsshim/internal/protocol/guestresource"
1825
rpi "github.com/Microsoft/hcsshim/internal/regopolicyinterpreter"
1926
oci "github.com/opencontainers/runtime-spec/specs-go"
2027
"github.com/pkg/errors"
28+
"github.com/sirupsen/logrus"
2129
)
2230

2331
const regoEnforcerName = "rego"
@@ -60,17 +68,6 @@ type regoEnforcer struct {
6068

6169
var _ SecurityPolicyEnforcer = (*regoEnforcer)(nil)
6270

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-
7471
func toStringSet(items []string) stringSet {
7572
s := make(stringSet)
7673
for _, item := range items {
@@ -1094,6 +1091,69 @@ func parseNamespace(rego string) (string, error) {
10941091
return strings.TrimSpace(parts[1]), nil
10951092
}
10961093

1094+
// Fragment extends current security policy with additional constraints
1095+
// from the incoming fragment. Note that it is base64 encoded over the bridge/
1096+
//
1097+
// There are three checking steps:
1098+
// 1 - Unpack the cose document and check it was actually signed with the cert
1099+
// chain inside its header
1100+
// 2 - Check that the issuer field did:x509 identifier is for that cert chain
1101+
// (ie fingerprint of a non leaf cert and the subject matches the leaf cert)
1102+
// 3 - Check that this issuer/feed match the requirement of the user provided
1103+
// security policy (done in the regoby LoadFragment)
1104+
func ExtractAndVerifyFragment(ctx context.Context, fragment *guestresource.LCOWSecurityPolicyFragment) (issuer string, feed string, payloadString string, err error) {
1105+
log.G(ctx).WithField("fragment", fmt.Sprintf("%+v", fragment)).Debug("VerifyAndExtractFragment")
1106+
1107+
raw, err := base64.StdEncoding.DecodeString(fragment.Fragment)
1108+
if err != nil {
1109+
return "", "", "", fmt.Errorf("failed to decode fragment: %w", err)
1110+
}
1111+
blob := []byte(fragment.Fragment)
1112+
// keep a copy of the fragment, so we can manually figure out what went wrong
1113+
// will be removed eventually. Give it a unique name to avoid any potential
1114+
// race conditions.
1115+
sha := sha256.New()
1116+
sha.Write(blob)
1117+
timestamp := time.Now()
1118+
fragmentPath := fmt.Sprintf("fragment-%x-%d.blob", sha.Sum(nil), timestamp.UnixMilli())
1119+
_ = os.WriteFile(filepath.Join(os.TempDir(), fragmentPath), blob, 0644)
1120+
1121+
unpacked, err := cosesign1.UnpackAndValidateCOSE1CertChain(raw)
1122+
if err != nil {
1123+
return "", "", "", fmt.Errorf("InjectFragment failed COSE validation: %w", err)
1124+
}
1125+
1126+
payloadString = string(unpacked.Payload[:])
1127+
issuer = unpacked.Issuer
1128+
feed = unpacked.Feed
1129+
chainPem := unpacked.ChainPem
1130+
1131+
log.G(ctx).WithFields(logrus.Fields{
1132+
"issuer": issuer, // eg the DID:x509:blah....
1133+
"feed": feed,
1134+
"cty": unpacked.ContentType,
1135+
"chainPem": chainPem,
1136+
}).Debugf("unpacked COSE1 cert chain")
1137+
1138+
log.G(ctx).WithFields(logrus.Fields{
1139+
"payload": payloadString,
1140+
}).Tracef("unpacked COSE1 payload")
1141+
1142+
if len(issuer) == 0 || len(feed) == 0 { // must both be present
1143+
return "", "", "", fmt.Errorf("either issuer and feed must both be provided in the COSE_Sign1 protected header")
1144+
}
1145+
1146+
// Resolve returns a did doc that we don't need
1147+
// we only care if there was an error or not
1148+
_, err = didx509resolver.Resolve(unpacked.ChainPem, issuer, true)
1149+
if err != nil {
1150+
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())
1151+
return "", "", "", err
1152+
}
1153+
1154+
return issuer, feed, payloadString, nil
1155+
}
1156+
10971157
func (policy *regoEnforcer) LoadFragment(ctx context.Context, issuer string, feed string, rego string) error {
10981158
namespace, err := parseNamespace(rego)
10991159
if err != nil {

0 commit comments

Comments
 (0)