@@ -59,6 +59,34 @@ const (
5959 // create uniquely-named files, but we don't want to try to use their
6060 // contents until after they've been written to
6161 containerExcludesSubstring = ".tmp"
62+
63+ // Windows-specific PAX record keys
64+ keyFileAttr = "MSWINDOWS.fileattr"
65+ keySDRaw = "MSWINDOWS.rawsd"
66+ keyCreationTime = "LIBARCHIVE.creationtime"
67+
68+ // Windows Security Descriptors (base64-encoded)
69+ // SDDL: O:BAG:SYD:(A;OICI;FA;;;BA)(A;OICI;FA;;;SY)(A;;FA;;;BA)(A;OICIIO;GA;;;CO)(A;OICI;0x1200a9;;;BU)(A;CI;LC;;;BU)(A;CI;DC;;;BU)
70+ // Owner: Built-in Administrators (BA)
71+ // Group: Local System (SY)
72+ // DACL:
73+ // - Allow OBJECT_INHERIT+CONTAINER_INHERIT Full Access to Built-in Administrators (BA)
74+ // - Allow OBJECT_INHERIT+CONTAINER_INHERIT Full Access to Local System (SY)
75+ // - Allow Full Access to Built-in Administrators (BA)
76+ // - Allow OBJECT_INHERIT+CONTAINER_INHERIT+INHERIT_ONLY Generic All to Creator Owner (CO)
77+ // - Allow OBJECT_INHERIT+CONTAINER_INHERIT Read/Execute permissions to Built-in Users (BU)
78+ // - Allow CONTAINER_INHERIT List Contents to Built-in Users (BU)
79+ // - Allow CONTAINER_INHERIT Delete Child to Built-in Users (BU)
80+ winSecurityDescriptorDirectory = "AQAEgBQAAAAkAAAAAAAAADAAAAABAgAAAAAABSAAAAAgAgAAAQEAAAAAAAUSAAAAAgCoAAcAAAAAAxgA/wEfAAECAAAAAAAFIAAAACACAAAAAxQA/wEfAAEBAAAAAAAFEgAAAAAAGAD/AR8AAQIAAAAAAAUgAAAAIAIAAAALFAAAAAAQAQEAAAAAAAMAAAAAAAMYAKkAEgABAgAAAAAABSAAAAAhAgAAAAIYAAQAAAABAgAAAAAABSAAAAAhAgAAAAIYAAIAAAABAgAAAAAABSAAAAAhAgAA"
81+
82+ // SDDL: O:BAG:SYD:(A;;FA;;;BA)(A;;FA;;;SY)(A;;0x1200a9;;;BU)
83+ // Owner: Built-in Administrators (BA)
84+ // Group: Local System (SY)
85+ // DACL:
86+ // - Allow Full Access to Built-in Administrators (BA)
87+ // - Allow Full Access to Local System (SY)
88+ // - Allow Read/Execute permissions to Built-in Users (BU)
89+ winSecurityDescriptorFile = "AQAEgBQAAAAkAAAAAAAAADAAAAABAgAAAAAABSAAAAAgAgAAAQEAAAAAAAUSAAAAAgBMAAMAAAAAABgA/wEfAAECAAAAAAAFIAAAACACAAAAABQA/wEfAAEBAAAAAAAFEgAAAAAAGACpABIAAQIAAAAAAAUgAAAAIQIAAA=="
6290)
6391
6492// ExtractRootfsOptions is consumed by ExtractRootfs() which allows users to
@@ -112,6 +140,7 @@ type containerImageRef struct {
112140 unsetAnnotations []string
113141 setAnnotations []string
114142 createdAnnotation types.OptionalBool
143+ os string
115144}
116145
117146type blobLayerInfo struct {
@@ -1075,7 +1104,7 @@ func (i *containerImageRef) NewImageSource(ctx context.Context, _ *types.SystemC
10751104 return nil , fmt .Errorf ("opening file for %s: %w" , what , err )
10761105 }
10771106
1078- layerFileWriter , err := newLayerWriter (layerFile , i .compression , i .layerModTime , i .layerLatestModTime , layerExclusions )
1107+ layerFileWriter , err := newLayerWriter (layerFile , i .compression , i .layerModTime , i .layerLatestModTime , layerExclusions , i . os == "windows" )
10791108 if err != nil {
10801109 layerFile .Close ()
10811110 rc .Close ()
@@ -1097,7 +1126,9 @@ func (i *containerImageRef) NewImageSource(ctx context.Context, _ *types.SystemC
10971126 return nil , fmt .Errorf ("extracting container rootfs: %w" , err )
10981127 }
10991128 }
1100- if layerFileWriter .SourceDigest () != layerFileWriter .DestDigest () {
1129+ // If the stream was transformed (compression or Windows mutation), use TotalWritten()
1130+ // Otherwise verify that io.Copy size matches TotalWritten()
1131+ if layerFileWriter .SourceDigest () != layerFileWriter .DestDigest () || i .os == "windows" {
11011132 size = layerFileWriter .TotalWritten ()
11021133 } else {
11031134 if size != layerFileWriter .TotalWritten () {
@@ -1143,6 +1174,98 @@ func (i *containerImageRef) NewImageSource(ctx context.Context, _ *types.SystemC
11431174 return src , nil
11441175}
11451176
1177+ func prepareWinHeader (h * tar.Header ) {
1178+ if h .PAXRecords == nil {
1179+ h .PAXRecords = map [string ]string {}
1180+ }
1181+ if h .Typeflag == tar .TypeDir {
1182+ h .Mode |= 1 << 14
1183+ h .PAXRecords [keyFileAttr ] = "16"
1184+ }
1185+
1186+ if h .Typeflag == tar .TypeReg {
1187+ h .Mode |= 1 << 15
1188+ h .PAXRecords [keyFileAttr ] = "32"
1189+ }
1190+
1191+ if ! h .ModTime .IsZero () {
1192+ h .PAXRecords [keyCreationTime ] = fmt .Sprintf ("%d.%d" , h .ModTime .Unix (), h .ModTime .Nanosecond ())
1193+ }
1194+
1195+ h .Format = tar .FormatPAX
1196+ }
1197+
1198+ func addSecurityDescriptor (h * tar.Header ) {
1199+ if h .Typeflag == tar .TypeDir {
1200+ h .PAXRecords [keySDRaw ] = winSecurityDescriptorDirectory
1201+ }
1202+
1203+ if h .Typeflag == tar .TypeReg {
1204+ h .PAXRecords [keySDRaw ] = winSecurityDescriptorFile
1205+ }
1206+ }
1207+
1208+ func windowsMutator (w io.WriteCloser ) io.WriteCloser {
1209+ pr , pw := io .Pipe ()
1210+
1211+ go func () {
1212+ tarReader := tar .NewReader (pr )
1213+ tarWriter := tar .NewWriter (w )
1214+
1215+ err := func () error {
1216+ h := & tar.Header {
1217+ Name : "Hives" ,
1218+ Typeflag : tar .TypeDir ,
1219+ ModTime : time .Now (),
1220+ }
1221+ prepareWinHeader (h )
1222+ if err := tarWriter .WriteHeader (h ); err != nil {
1223+ return err
1224+ }
1225+
1226+ h = & tar.Header {
1227+ Name : "Files" ,
1228+ Typeflag : tar .TypeDir ,
1229+ ModTime : time .Now (),
1230+ }
1231+ prepareWinHeader (h )
1232+ if err := tarWriter .WriteHeader (h ); err != nil {
1233+ return err
1234+ }
1235+
1236+ for {
1237+ h , err := tarReader .Next ()
1238+ if err == io .EOF {
1239+ break
1240+ }
1241+ if err != nil {
1242+ return err
1243+ }
1244+ h .Name = filepath .Join ("Files" , h .Name )
1245+ if h .Linkname != "" {
1246+ h .Linkname = filepath .Join ("Files" , h .Linkname )
1247+ }
1248+ prepareWinHeader (h )
1249+ addSecurityDescriptor (h )
1250+ if err := tarWriter .WriteHeader (h ); err != nil {
1251+ return err
1252+ }
1253+ if h .Size > 0 {
1254+ if _ , err := io .Copy (tarWriter , tarReader ); err != nil {
1255+ return err
1256+ }
1257+ }
1258+ }
1259+ return tarWriter .Close ()
1260+ }()
1261+ if err != nil {
1262+ logrus .Errorf ("makeWindowsLayer %v" , err )
1263+ }
1264+ pw .CloseWithError (err )
1265+ }()
1266+ return pw
1267+ }
1268+
11461269// layerWriter represents a pipeline of writers
11471270type layerWriter struct {
11481271 outputCounter * int64
@@ -1189,15 +1312,15 @@ func (l *layerWriter) TotalWritten() int64 {
11891312// newLayerWriter creates a writer pipeline which processes an input stream and ultimately writes to the given destination.
11901313// The write stream pipeline is as follows:
11911314// input -> filter -> compressor -> destination
1192- func newLayerWriter (destination io.Writer , compression archive.Compression , layerModTime , layerLatestModTime * time.Time , layerExclusions []copier.ConditionalRemovePath ) (* layerWriter , error ) {
1315+ func newLayerWriter (destination io.Writer , compression archive.Compression , layerModTime , layerLatestModTime * time.Time , layerExclusions []copier.ConditionalRemovePath , windows bool ) (* layerWriter , error ) {
11931316 layerWriteCounter := ioutils .NewWriteCounter (destination )
11941317 var multiWriter io.Writer
11951318 var destHasher digest.Digester
11961319 srcHasher := digest .Canonical .Digester ()
11971320 var closers []func () error
11981321
11991322 // If the input stream will not be different from the output stream, avoid rehashing
1200- if compression != archive .Uncompressed || layerModTime != nil || layerLatestModTime != nil || len (layerExclusions ) != 0 {
1323+ if windows || compression != archive .Uncompressed || layerModTime != nil || layerLatestModTime != nil || len (layerExclusions ) != 0 {
12011324 destHasher = digest .Canonical .Digester ()
12021325 multiWriter = io .MultiWriter (layerWriteCounter , destHasher .Hash ())
12031326 } else {
@@ -1212,9 +1335,16 @@ func newLayerWriter(destination io.Writer, compression archive.Compression, laye
12121335 closers = append (closers , compressor .Close )
12131336 compressorHasher := io .MultiWriter (compressor , srcHasher .Hash ())
12141337
1338+ // Apply Windows layer mutation if needed, before calculating the uncompressed hash
1339+ preCompressor := ioutils .NopWriteCloser (compressorHasher )
1340+ if windows {
1341+ preCompressor = windowsMutator (preCompressor )
1342+ }
1343+
12151344 // Use specified timestamps in the layer, if we're doing that for history entries.
1216- filter := makeFilteredLayerWriteCloser (ioutils . NopWriteCloser ( compressorHasher ) , layerModTime , layerLatestModTime , layerExclusions )
1345+ filter := makeFilteredLayerWriteCloser (preCompressor , layerModTime , layerLatestModTime , layerExclusions )
12171346 closers = append (closers , filter .Close )
1347+
12181348 return & layerWriter {
12191349 outputCounter : & layerWriteCounter .Count ,
12201350 inputDigester : srcHasher ,
@@ -1720,6 +1850,7 @@ func (b *Builder) makeContainerImageRef(options CommitOptions) (*containerImageR
17201850 layerMountTargets : layerMountTargets ,
17211851 layerPullUps : layerPullUps ,
17221852 createdAnnotation : options .CreatedAnnotation ,
1853+ os : b .OCIv1 .OS ,
17231854 }
17241855 if ref .created != nil {
17251856 for i := range ref .preEmptyLayers {
0 commit comments