@@ -2,12 +2,22 @@ package securitypolicy
22
33import (
44 "context"
5+ "crypto/sha256"
6+ "encoding/base64"
57 "fmt"
8+ "os"
9+ "path/filepath"
610 "syscall"
11+ "time"
712
13+ "github.com/Microsoft/cosesign1go/pkg/cosesign1"
14+ didx509resolver "github.com/Microsoft/didx509go/pkg/did-x509-resolver"
15+ "github.com/Microsoft/hcsshim/internal/log"
816 "github.com/Microsoft/hcsshim/internal/protocol/guestrequest"
17+ "github.com/Microsoft/hcsshim/internal/protocol/guestresource"
918 oci "github.com/opencontainers/runtime-spec/specs-go"
1019 "github.com/pkg/errors"
20+ "github.com/sirupsen/logrus"
1121)
1222
1323type createEnforcerFunc func (base64EncodedPolicy string , criMounts , criPrivilegedMounts []oci.Mount , maxErrorMessageLength int ) (SecurityPolicyEnforcer , error )
@@ -121,7 +131,7 @@ type SecurityPolicyEnforcer interface {
121131 EnforceGetPropertiesPolicy (ctx context.Context ) error
122132 EnforceDumpStacksPolicy (ctx context.Context ) error
123133 EnforceRuntimeLoggingPolicy (ctx context.Context ) (err error )
124- LoadFragment (ctx context.Context , issuer string , feed string , code string ) error
134+ LoadFragment (ctx context.Context , issuer string , feed string , rego string ) error
125135 EnforceScratchMountPolicy (ctx context.Context , scratchPath string , encrypted bool ) (err error )
126136 EnforceScratchUnmountPolicy (ctx context.Context , scratchPath string ) (err error )
127137 GetUserInfo (spec * oci.Process , rootPath string ) (IDName , []IDName , string , error )
@@ -142,6 +152,69 @@ func (s stringSet) contains(item string) bool {
142152 return contains
143153}
144154
155+ // Fragment extends current security policy with additional constraints
156+ // from the incoming fragment. Note that it is base64 encoded over the bridge/
157+ //
158+ // There are three checking steps:
159+ // 1 - Unpack the cose document and check it was actually signed with the cert
160+ // chain inside its header
161+ // 2 - Check that the issuer field did:x509 identifier is for that cert chain
162+ // (ie fingerprint of a non leaf cert and the subject matches the leaf cert)
163+ // 3 - Check that this issuer/feed match the requirement of the user provided
164+ // security policy (done in the regoby LoadFragment)
165+ func ExtractAndVerifyFragment (ctx context.Context , fragment * guestresource.LCOWSecurityPolicyFragment ) (issuer string , feed string , payloadString string , err error ) {
166+ log .G (ctx ).WithField ("fragment" , fmt .Sprintf ("%+v" , fragment )).Debug ("VerifyAndExtractFragment" )
167+
168+ raw , err := base64 .StdEncoding .DecodeString (fragment .Fragment )
169+ if err != nil {
170+ return "" , "" , "" , fmt .Errorf ("failed to decode fragment: %w" , err )
171+ }
172+ blob := []byte (fragment .Fragment )
173+ // keep a copy of the fragment, so we can manually figure out what went wrong
174+ // will be removed eventually. Give it a unique name to avoid any potential
175+ // race conditions.
176+ sha := sha256 .New ()
177+ sha .Write (blob )
178+ timestamp := time .Now ()
179+ fragmentPath := fmt .Sprintf ("fragment-%x-%d.blob" , sha .Sum (nil ), timestamp .UnixMilli ())
180+ _ = os .WriteFile (filepath .Join (os .TempDir (), fragmentPath ), blob , 0644 )
181+
182+ unpacked , err := cosesign1 .UnpackAndValidateCOSE1CertChain (raw )
183+ if err != nil {
184+ return "" , "" , "" , fmt .Errorf ("InjectFragment failed COSE validation: %w" , err )
185+ }
186+
187+ payloadString = string (unpacked .Payload [:])
188+ issuer = unpacked .Issuer
189+ feed = unpacked .Feed
190+ chainPem := unpacked .ChainPem
191+
192+ log .G (ctx ).WithFields (logrus.Fields {
193+ "issuer" : issuer , // eg the DID:x509:blah....
194+ "feed" : feed ,
195+ "cty" : unpacked .ContentType ,
196+ "chainPem" : chainPem ,
197+ }).Debugf ("unpacked COSE1 cert chain" )
198+
199+ log .G (ctx ).WithFields (logrus.Fields {
200+ "payload" : payloadString ,
201+ }).Tracef ("unpacked COSE1 payload" )
202+
203+ if len (issuer ) == 0 || len (feed ) == 0 { // must both be present
204+ return "" , "" , "" , fmt .Errorf ("either issuer and feed must both be provided in the COSE_Sign1 protected header" )
205+ }
206+
207+ // Resolve returns a did doc that we don't need
208+ // we only care if there was an error or not
209+ _ , err = didx509resolver .Resolve (unpacked .ChainPem , issuer , true )
210+ if err != nil {
211+ 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 ())
212+ return "" , "" , "" , err
213+ }
214+
215+ return issuer , feed , payloadString , nil
216+ }
217+
145218// CreateSecurityPolicyEnforcer returns an appropriate enforcer for input
146219// parameters. Returns an error if the requested `enforcer` implementation
147220// isn't registered.
0 commit comments