@@ -5,19 +5,27 @@ package securitypolicy
55
66import (
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
2331const regoEnforcerName = "rego"
@@ -60,17 +68,6 @@ type regoEnforcer struct {
6068
6169var _ 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-
7471func toStringSet (items []string ) stringSet {
7572 s := make (stringSet )
7673 for _ , item := range items {
@@ -1094,7 +1091,76 @@ func parseNamespace(rego string) (string, error) {
10941091 return strings .TrimSpace (parts [1 ]), nil
10951092}
10961093
1097- func (policy * regoEnforcer ) LoadFragment (ctx context.Context , issuer string , feed string , rego string ) error {
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+
1157+ func (policy * regoEnforcer ) LoadFragment (ctx context.Context , policyfragment * guestresource.LCOWSecurityPolicyFragment ) error {
1158+
1159+ issuer , feed , rego , err := extractAndVerifyFragment (ctx , policyfragment )
1160+ if err != nil {
1161+ return fmt .Errorf ("unable to extract fragment: %w" , err )
1162+ }
1163+
10981164 namespace , err := parseNamespace (rego )
10991165 if err != nil {
11001166 return fmt .Errorf ("unable to load fragment: %w" , err )
0 commit comments