@@ -24,6 +24,7 @@ import (
2424 "io/ioutil"
2525 "os"
2626 "path/filepath"
27+ "strconv"
2728
2829 "github.com/go-logr/logr"
2930 apiequality "k8s.io/apimachinery/pkg/api/equality"
@@ -47,6 +48,18 @@ type Filesystem struct {
4748
4849 // used by the 'read only' methods
4950 fs fs.FS
51+
52+ // FixedFSGroup is an optional field which will set the gid ownership of all
53+ // volume's data directories to this value.
54+ // If this value is set, FSGroupVolumeAttributeKey has no effect.
55+ FixedFSGroup * int64
56+
57+ // FSGroupVolumeAttributeKey is an optional well-known key in the volume
58+ // attributes. If this attribute is present in the context when writing
59+ // files, gid ownership of the volume's data directory will be changed to
60+ // the value. Attribute value must be a valid int64 value.
61+ // If FixedFSGroup is defined, this field has no effect.
62+ FSGroupVolumeAttributeKey string
5063}
5164
5265// Ensure the Filesystem implementation is fully featured
@@ -176,18 +189,53 @@ func (f *Filesystem) RegisterMetadata(meta metadata.Metadata) (bool, error) {
176189 return false , nil
177190}
178191
179- func (f * Filesystem ) WriteFiles (volumeID string , files map [string ][]byte ) error {
180- if err := os .MkdirAll (f .dataPathForVolumeID (volumeID ), 0644 ); err != nil {
192+ // WriteFiles writes the given data to filesystem files within the volume's
193+ // data directory. Filesystem supports changing ownership of the data directory
194+ // to a custom gid.
195+ func (f * Filesystem ) WriteFiles (meta metadata.Metadata , files map [string ][]byte ) error {
196+ // Data directory should be read and execute only to the fs user and group.
197+ if err := os .MkdirAll (f .dataPathForVolumeID (meta .VolumeID ), 0550 ); err != nil {
181198 return err
182199 }
183200
184- writer , err := util .NewAtomicWriter (f .dataPathForVolumeID (volumeID ), fmt .Sprintf ("volumeID %v" , volumeID ))
201+ fsGroup , err := f .fsGroupForMetadata (meta )
202+ if err != nil {
203+ return err
204+ }
205+
206+ // If a fsGroup is defined, Chown the directory to that group.
207+ if fsGroup != nil {
208+ if err := os .Chown (f .dataPathForVolumeID (meta .VolumeID ), - 1 , int (* fsGroup )); err != nil {
209+ return fmt .Errorf ("failed to chown data dir to gid %v: %w" , * fsGroup , err )
210+ }
211+ }
212+
213+ writer , err := util .NewAtomicWriter (f .dataPathForVolumeID (meta .VolumeID ), fmt .Sprintf ("volumeID %v" , meta .VolumeID ))
185214 if err != nil {
186215 return err
187216 }
188217
189218 payload := makePayload (files )
190- return writer .Write (payload )
219+ if err := writer .Write (payload ); err != nil {
220+ return err
221+ }
222+
223+ // If a fsGroup is defined, Chown all files just written.
224+ if fsGroup != nil {
225+ // "..data" is the well-known location where the atomic writer links to the
226+ // latest directory containing the files just written. Chown these real
227+ // files.
228+ dirName := filepath .Join (f .dataPathForVolumeID (meta .VolumeID ), "..data" )
229+
230+ for filename := range files {
231+ // Set the uid to -1 which means don't change ownership in Go.
232+ if err := os .Chown (filepath .Join (dirName , filename ), - 1 , int (* fsGroup )); err != nil {
233+ return err
234+ }
235+ }
236+ }
237+
238+ return nil
191239}
192240
193241// ReadFile reads the named file within the volume's data directory.
@@ -228,9 +276,44 @@ func makePayload(in map[string][]byte) map[string]util.FileProjection {
228276 for name , data := range in {
229277 out [name ] = util.FileProjection {
230278 Data : data ,
231- // read-only for user + group (TODO: set fsUser/fsGroup)
232279 Mode : readOnlyUserAndGroupFileMode ,
233280 }
234281 }
235282 return out
236283}
284+
285+ // fsGroupForMetadata returns the gid that ownership of the volume data
286+ // directory should be changed to. Returns nil if ownership should not be
287+ // changed.
288+ func (f * Filesystem ) fsGroupForMetadata (meta metadata.Metadata ) (* int64 , error ) {
289+ // FixedFSGroup takes precedence over attribute key.
290+ if f .FixedFSGroup != nil {
291+ return f .FixedFSGroup , nil
292+ }
293+
294+ // If the FSGroupVolumeAttributeKey is not defined, no ownership can change.
295+ if len (f .FSGroupVolumeAttributeKey ) == 0 {
296+ return nil , nil
297+ }
298+
299+ fsGroupStr , ok := meta .VolumeContext [f .FSGroupVolumeAttributeKey ]
300+ if ! ok {
301+ // If the attribute has not been set, return no ownership change.
302+ return nil , nil
303+ }
304+
305+ fsGroup , err := strconv .ParseInt (fsGroupStr , 10 , 64 )
306+ if err != nil {
307+ return nil , fmt .Errorf ("failed to parse %q, value must be a valid integer: %w" , f .FSGroupVolumeAttributeKey , err )
308+ }
309+
310+ // fsGroup has to be between 1 and 4294967295 inclusive. 4294967295 is the
311+ // largest gid number on most modern operating systems. If the actual maximum
312+ // is smaller on the running machine, then we will simply error later during
313+ // the Chown.
314+ if fsGroup <= 0 || fsGroup > 4294967295 {
315+ return nil , fmt .Errorf ("%q: gid value must be greater than 0 and less than 4294967295: %d" , f .FSGroupVolumeAttributeKey , fsGroup )
316+ }
317+
318+ return & fsGroup , nil
319+ }
0 commit comments