Skip to content

Commit dd8ae5d

Browse files
authored
Merge pull request #12 from JoshVanL/storage-write-files-fsUser
Allow storage implementations to optionally specifiy an fsGroup GID when writing files.
2 parents e867e03 + dc34277 commit dd8ae5d

File tree

7 files changed

+217
-11
lines changed

7 files changed

+217
-11
lines changed

deploy/example/example-app.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,9 @@ spec:
5151
labels:
5252
app: my-csi-app
5353
spec:
54+
securityContext:
55+
runAsGroup: 1000
56+
runAsUser: 2000
5457
containers:
5558
- name: my-frontend
5659
image: busybox
@@ -67,3 +70,4 @@ spec:
6770
csi.cert-manager.io/issuer-name: ca-issuer
6871
csi.cert-manager.io/dns-names: my-service.sandbox.svc.cluster.local
6972
csi.cert-manager.io/uri-sans: spiffe://my-service.sandbox.cluster.local
73+
csi.cert-manager.io/fs-group: "1000"

example/main.go

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,10 @@ const (
6464

6565
RenewBeforeKey string = "csi.cert-manager.io/renew-before"
6666
ReusePrivateKey string = "csi.cert-manager.io/reuse-private-key"
67+
68+
// fs-group is used to optionally set the GID ownership of the volume's
69+
// files. Useful when running containers with a specified user and group.
70+
FsGroupKey string = "csi.cert-manager.io/fs-group"
6771
)
6872

6973
var (
@@ -97,6 +101,8 @@ func main() {
97101
panic("failed to setup filesystem: " + err.Error())
98102
}
99103

104+
store.FSGroupVolumeAttributeKey = FsGroupKey
105+
100106
d, err := driver.New(*endpoint, log, driver.Options{
101107
DriverName: "csi.cert-manager.io",
102108
DriverVersion: "v0.0.1",
@@ -250,7 +256,7 @@ func (w *writer) writeKeypair(meta metadata.Metadata, key crypto.PrivateKey, cha
250256
return fmt.Errorf("calculating next issuance time: %w", err)
251257
}
252258

253-
if err := w.store.WriteFiles(meta.VolumeID, map[string][]byte{
259+
if err := w.store.WriteFiles(meta, map[string][]byte{
254260
pkFile: keyPEM,
255261
crtFile: chain,
256262
caFile: ca,

storage/filesystem.go

Lines changed: 88 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -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+
}

storage/filesystem_test.go

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,3 +117,115 @@ func TestFilesystem_ListVolumes(t *testing.T) {
117117
t.Errorf("expected only entry to be 'fake-volume' but got: %s", vols[0])
118118
}
119119
}
120+
121+
func Test_fsGroupForMetadata(t *testing.T) {
122+
intPtr := func(i int64) *int64 {
123+
return &i
124+
}
125+
126+
tests := map[string]struct {
127+
fixedFSGroup *int64
128+
fsGroupVolumeAttributeKey string
129+
volumeContext map[string]string
130+
131+
expGID *int64
132+
expErr bool
133+
}{
134+
"FixedFSGroup=nil FSGroupVolumeAttributeKey='', should return nil gid": {
135+
fixedFSGroup: nil,
136+
fsGroupVolumeAttributeKey: "",
137+
volumeContext: map[string]string{},
138+
expGID: nil,
139+
expErr: false,
140+
},
141+
"FixedFSGroup=10 FSGroupVolumeAttributeKey='', should return 10": {
142+
fixedFSGroup: intPtr(10),
143+
fsGroupVolumeAttributeKey: "",
144+
volumeContext: map[string]string{},
145+
expGID: intPtr(10),
146+
expErr: false,
147+
},
148+
"FixedFSGroup=nil FSGroupVolumeAttributeKey=defined but not present in context, should return nil": {
149+
fixedFSGroup: nil,
150+
fsGroupVolumeAttributeKey: "fs-gid",
151+
volumeContext: map[string]string{},
152+
expGID: nil,
153+
expErr: false,
154+
},
155+
"FixedFSGroup=nil FSGroupVolumeAttributeKey=defined and present in context, should return 20": {
156+
fixedFSGroup: nil,
157+
fsGroupVolumeAttributeKey: "fs-gid",
158+
volumeContext: map[string]string{
159+
"fs-gid": "20",
160+
},
161+
expGID: intPtr(20),
162+
expErr: false,
163+
},
164+
"FixedFSGroup=nil FSGroupVolumeAttributeKey=defined and present in context but value of 0, should error": {
165+
fixedFSGroup: nil,
166+
fsGroupVolumeAttributeKey: "fs-gid",
167+
volumeContext: map[string]string{
168+
"fs-gid": "0",
169+
},
170+
expGID: nil,
171+
expErr: true,
172+
},
173+
"FixedFSGroup=nil FSGroupVolumeAttributeKey=defined and present in context but value of -1, should error": {
174+
fixedFSGroup: nil,
175+
fsGroupVolumeAttributeKey: "fs-gid",
176+
volumeContext: map[string]string{
177+
"fs-gid": "-1",
178+
},
179+
expGID: nil,
180+
expErr: true,
181+
},
182+
"FixedFSGroup=nil FSGroupVolumeAttributeKey=defined and present in context but value greater than the max gid, should error": {
183+
fixedFSGroup: nil,
184+
fsGroupVolumeAttributeKey: "fs-gid",
185+
volumeContext: map[string]string{
186+
"fs-gid": "4294967296",
187+
},
188+
expGID: nil,
189+
expErr: true,
190+
},
191+
"FixedFSGroup=nil FSGroupVolumeAttributeKey=defined and present in context but with bad value, should return error": {
192+
fixedFSGroup: nil,
193+
fsGroupVolumeAttributeKey: "fs-gid",
194+
volumeContext: map[string]string{
195+
"fs-gid": "bad-value",
196+
},
197+
expGID: nil,
198+
expErr: true,
199+
},
200+
"FixedFSGroup=10 FSGroupVolumeAttributeKey=defined and present in context, should return superseding FixedFSGroup (10)": {
201+
fixedFSGroup: intPtr(10),
202+
fsGroupVolumeAttributeKey: "fs-gid",
203+
volumeContext: map[string]string{
204+
"fs-gid": "20",
205+
},
206+
expGID: intPtr(10),
207+
expErr: false,
208+
},
209+
}
210+
211+
for name, test := range tests {
212+
t.Run(name, func(t *testing.T) {
213+
f := Filesystem{
214+
FixedFSGroup: test.fixedFSGroup,
215+
FSGroupVolumeAttributeKey: test.fsGroupVolumeAttributeKey,
216+
}
217+
218+
gid, err := f.fsGroupForMetadata(metadata.Metadata{
219+
VolumeContext: test.volumeContext,
220+
})
221+
222+
if (err != nil) != test.expErr {
223+
t.Errorf("unexpected error, exp=%t got=%v", test.expErr, err)
224+
}
225+
226+
if !reflect.DeepEqual(gid, test.expGID) {
227+
t.Errorf("unexpected gid, exp=%v got=%v", test.expGID, gid)
228+
}
229+
})
230+
}
231+
}

storage/interfaces.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,5 +71,5 @@ type MetadataWriter interface {
7171
// DataWriter is used to write data (e.g. certificate and private keys) to the
7272
// storage backend.
7373
type DataWriter interface {
74-
WriteFiles(volumeID string, files map[string][]byte) error
74+
WriteFiles(meta metadata.Metadata, files map[string][]byte) error
7575
}

storage/memory.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -126,10 +126,10 @@ func (m *MemoryFS) RegisterMetadata(meta metadata.Metadata) (bool, error) {
126126
return true, nil
127127
}
128128

129-
func (m *MemoryFS) WriteFiles(volumeID string, files map[string][]byte) error {
129+
func (m *MemoryFS) WriteFiles(meta metadata.Metadata, files map[string][]byte) error {
130130
m.lock.Lock()
131131
defer m.lock.Unlock()
132-
vol, ok := m.files[volumeID]
132+
vol, ok := m.files[meta.VolumeID]
133133
if !ok {
134134
return ErrNotFound
135135
}

test/integration/issuance_test.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ func TestIssuesCertificate(t *testing.T) {
5757
return []byte{}, nil
5858
},
5959
WriteKeypair: func(meta metadata.Metadata, key crypto.PrivateKey, chain []byte, ca []byte) error {
60-
store.WriteFiles(meta.VolumeID, map[string][]byte{
60+
store.WriteFiles(meta, map[string][]byte{
6161
"ca": ca,
6262
"cert": chain,
6363
})
@@ -106,6 +106,7 @@ func TestIssuesCertificate(t *testing.T) {
106106
func TestManager_CleansUpOldRequests(t *testing.T) {
107107
store := storage.NewMemoryFS()
108108
clock := fakeclock.NewFakeClock(time.Now())
109+
109110
opts, cl, stop := testutil.RunTestDriver(t, testutil.DriverOptions{
110111
Store: store,
111112
Clock: clock,
@@ -117,7 +118,7 @@ func TestManager_CleansUpOldRequests(t *testing.T) {
117118
}, nil
118119
},
119120
WriteKeypair: func(meta metadata.Metadata, key crypto.PrivateKey, chain []byte, ca []byte) error {
120-
store.WriteFiles(meta.VolumeID, map[string][]byte{
121+
store.WriteFiles(meta, map[string][]byte{
121122
"ca": ca,
122123
"cert": chain,
123124
})

0 commit comments

Comments
 (0)