Skip to content

Commit 770dcf0

Browse files
Replace uses of os.Create with os2.Create within backup/restore workflows (#17648)
Signed-off-by: Matt Robenolt <matt@ydekproductions.com> Signed-off-by: Matt Lord <mattalord@gmail.com> Co-authored-by: Matt Lord <mattalord@gmail.com>
1 parent be677ef commit 770dcf0

8 files changed

Lines changed: 139 additions & 12 deletions

File tree

go/os2/file.go

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
/*
2+
Copyright 2025 The Vitess Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package os2
18+
19+
import (
20+
"io/fs"
21+
"os"
22+
)
23+
24+
const (
25+
// PermFile is a FileMode for regular files without world permission bits.
26+
PermFile fs.FileMode = 0660
27+
// PermDirectory is a FileMode for directories without world permission bits.
28+
PermDirectory fs.FileMode = 0770
29+
)
30+
31+
// Create is identical to os.Create except uses 0660 permission
32+
// rather than 0666, to exclude world read/write bit.
33+
func Create(name string) (*os.File, error) {
34+
return os.OpenFile(name, os.O_RDWR|os.O_CREATE|os.O_TRUNC, PermFile)
35+
}
36+
37+
// WriteFile is identical to os.WriteFile except permission of 0660 is used.
38+
func WriteFile(name string, data []byte) error {
39+
return os.WriteFile(name, data, PermFile)
40+
}
41+
42+
// Mkdir is identical to os.Mkdir except permission of 0770 is used.
43+
func Mkdir(path string) error {
44+
return os.Mkdir(path, PermDirectory)
45+
}
46+
47+
// MkdirAll is identical to os.MkdirAll except permission of 0770 is used.
48+
func MkdirAll(path string) error {
49+
return os.MkdirAll(path, PermDirectory)
50+
}

go/test/endtoend/backup/vtctlbackup/backup_utils.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -425,6 +425,18 @@ func TestBackup(t *testing.T, setupType int, streamMode string, stripes int, cDe
425425
return vterrors.Errorf(vtrpc.Code_UNKNOWN, "test failure: %s", test.name)
426426
}
427427
}
428+
429+
t.Run("check for files created with global permissions", func(t *testing.T) {
430+
t.Logf("Confirming that none of the MySQL data directories that we've created have files with global permissions")
431+
for _, ks := range localCluster.Keyspaces {
432+
for _, shard := range ks.Shards {
433+
for _, tablet := range shard.Vttablets {
434+
tablet.VttabletProcess.ConfirmDataDirHasNoGlobalPerms(t)
435+
}
436+
}
437+
}
438+
})
439+
428440
return nil
429441
}
430442

go/test/endtoend/cluster/vttablet_process.go

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import (
2424
"errors"
2525
"fmt"
2626
"io"
27+
"io/fs"
2728
"net/http"
2829
"os"
2930
"os/exec"
@@ -35,6 +36,8 @@ import (
3536
"testing"
3637
"time"
3738

39+
"github.com/stretchr/testify/require"
40+
3841
"vitess.io/vitess/go/constants/sidecar"
3942
"vitess.io/vitess/go/mysql"
4043
"vitess.io/vitess/go/sqltypes"
@@ -711,6 +714,63 @@ func (vttablet *VttabletProcess) IsShutdown() bool {
711714
return vttablet.proc == nil
712715
}
713716

717+
// ConfirmDataDirHasNoGlobalPerms confirms that no files in the tablet's data directory
718+
// have any global/world/other permissions enabled.
719+
func (vttablet *VttabletProcess) ConfirmDataDirHasNoGlobalPerms(t *testing.T) {
720+
datadir := vttablet.Directory
721+
if _, err := os.Stat(datadir); errors.Is(err, os.ErrNotExist) {
722+
t.Logf("Data directory %s no longer exists, skipping permissions check", datadir)
723+
return
724+
}
725+
726+
var allowedFiles = []string{
727+
// These are intentionally created with the world/other read bit set by mysqld itself
728+
// during the --initialize[-insecure] step.
729+
// See: https://dev.mysql.com/doc/mysql-security-excerpt/en/creating-ssl-rsa-files-using-mysql.html
730+
// "On Unix and Unix-like systems, the file access mode is 644 for certificate files
731+
// (that is, world readable) and 600 for key files (that is, accessible only by the
732+
// account that runs the server)."
733+
path.Join("data", "ca.pem"),
734+
path.Join("data", "client-cert.pem"),
735+
path.Join("data", "public_key.pem"),
736+
path.Join("data", "server-cert.pem"),
737+
// The domain socket must have global perms for anyone to use it.
738+
"mysql.sock",
739+
// These files are created by xtrabackup.
740+
path.Join("tmp", "xtrabackup_checkpoints"),
741+
path.Join("tmp", "xtrabackup_info"),
742+
}
743+
744+
var matches []string
745+
fsys := os.DirFS(datadir)
746+
err := fs.WalkDir(fsys, ".", func(p string, d fs.DirEntry, _ error) error {
747+
// first check if the file should be skipped
748+
for _, name := range allowedFiles {
749+
if strings.HasSuffix(p, name) {
750+
return nil
751+
}
752+
}
753+
754+
info, err := d.Info()
755+
if err != nil {
756+
return err
757+
}
758+
759+
// check if any global bit is on the filemode
760+
if info.Mode()&0007 != 0 {
761+
matches = append(matches, fmt.Sprintf(
762+
"%s (%s)",
763+
path.Join(datadir, p),
764+
info.Mode(),
765+
))
766+
}
767+
return nil
768+
})
769+
770+
require.NoError(t, err, "Error walking directory")
771+
require.Empty(t, matches, "Found files with global permissions: %s\n", strings.Join(matches, "\n"))
772+
}
773+
714774
// VttabletProcessInstance returns a VttabletProcess handle for vttablet process
715775
// configured with the given Config.
716776
// The process must be manually started by calling setup()

go/vt/mysqlctl/backupengine.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import (
3131

3232
"vitess.io/vitess/go/mysql"
3333
"vitess.io/vitess/go/mysql/replication"
34+
"vitess.io/vitess/go/os2"
3435
"vitess.io/vitess/go/vt/logutil"
3536
"vitess.io/vitess/go/vt/mysqlctl/backupstats"
3637
"vitess.io/vitess/go/vt/mysqlctl/backupstorage"
@@ -723,7 +724,7 @@ func createStateFile(cnf *Mycnf) error {
723724
// rename func to openStateFile
724725
// change to return a *File
725726
fname := filepath.Join(cnf.TabletDir(), RestoreState)
726-
fd, err := os.Create(fname)
727+
fd, err := os2.Create(fname)
727728
if err != nil {
728729
return fmt.Errorf("unable to create file: %v", err)
729730
}

go/vt/mysqlctl/blackbox/utils.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import (
3030

3131
"vitess.io/vitess/go/mysql"
3232
"vitess.io/vitess/go/mysql/capabilities"
33+
"vitess.io/vitess/go/os2"
3334
"vitess.io/vitess/go/vt/logutil"
3435
"vitess.io/vitess/go/vt/mysqlctl"
3536
"vitess.io/vitess/go/vt/mysqlctl/backupstats"
@@ -182,7 +183,7 @@ func createBackupDir(root string, dirs ...string) error {
182183

183184
func createBackupFiles(root string, fileCount int, ext string) error {
184185
for i := 0; i < fileCount; i++ {
185-
f, err := os.Create(path.Join(root, fmt.Sprintf("%d.%s", i, ext)))
186+
f, err := os2.Create(path.Join(root, fmt.Sprintf("%d.%s", i, ext)))
186187
if err != nil {
187188
return err
188189
}

go/vt/mysqlctl/builtinbackupengine.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ import (
3939
"vitess.io/vitess/go/ioutil"
4040
"vitess.io/vitess/go/mysql"
4141
"vitess.io/vitess/go/mysql/replication"
42+
"vitess.io/vitess/go/os2"
4243
"vitess.io/vitess/go/protoutil"
4344
"vitess.io/vitess/go/vt/log"
4445
"vitess.io/vitess/go/vt/logutil"
@@ -206,10 +207,10 @@ func (fe *FileEntry) open(cnf *Mycnf, readOnly bool) (*os.File, error) {
206207
}
207208
} else {
208209
dir := path.Dir(name)
209-
if err := os.MkdirAll(dir, os.ModePerm); err != nil {
210+
if err := os2.MkdirAll(dir); err != nil {
210211
return nil, vterrors.Wrapf(err, "cannot create destination directory %v", dir)
211212
}
212-
if fd, err = os.Create(name); err != nil {
213+
if fd, err = os2.Create(name); err != nil {
213214
return nil, vterrors.Wrapf(err, "cannot create destination file %v", name)
214215
}
215216
}

go/vt/mysqlctl/filebackupstorage/file.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import (
2727

2828
"github.com/spf13/pflag"
2929

30+
"vitess.io/vitess/go/os2"
3031
"vitess.io/vitess/go/vt/mysqlctl/errors"
3132

3233
"vitess.io/vitess/go/ioutil"
@@ -96,7 +97,7 @@ func (fbh *FileBackupHandle) AddFile(ctx context.Context, filename string, files
9697
return nil, fmt.Errorf("AddFile cannot be called on read-only backup")
9798
}
9899
p := path.Join(FileBackupStorageRoot, fbh.dir, fbh.name, filename)
99-
f, err := os.Create(p)
100+
f, err := os2.Create(p)
100101
if err != nil {
101102
return nil, err
102103
}
@@ -172,13 +173,13 @@ func (fbs *FileBackupStorage) ListBackups(ctx context.Context, dir string) ([]ba
172173
func (fbs *FileBackupStorage) StartBackup(ctx context.Context, dir, name string) (backupstorage.BackupHandle, error) {
173174
// Make sure the directory exists.
174175
p := path.Join(FileBackupStorageRoot, dir)
175-
if err := os.MkdirAll(p, os.ModePerm); err != nil {
176+
if err := os2.MkdirAll(p); err != nil {
176177
return nil, err
177178
}
178179

179180
// Create the subdirectory for this named backup.
180181
p = path.Join(p, name)
181-
if err := os.Mkdir(p, os.ModePerm); err != nil {
182+
if err := os2.Mkdir(p); err != nil {
182183
return nil, err
183184
}
184185

go/vt/mysqlctl/mysqld.go

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ import (
4747
"vitess.io/vitess/config"
4848
"vitess.io/vitess/go/mysql"
4949
"vitess.io/vitess/go/mysql/sqlerror"
50+
"vitess.io/vitess/go/os2"
5051
"vitess.io/vitess/go/osutil"
5152
"vitess.io/vitess/go/protoutil"
5253
"vitess.io/vitess/go/sqltypes"
@@ -921,7 +922,7 @@ func (mysqld *Mysqld) initConfig(cnf *Mycnf, outFile string) error {
921922
return err
922923
}
923924

924-
return os.WriteFile(outFile, []byte(configData), 0o664)
925+
return os2.WriteFile(outFile, []byte(configData))
925926
}
926927

927928
func (mysqld *Mysqld) getMycnfTemplate() string {
@@ -1071,7 +1072,7 @@ func (mysqld *Mysqld) ReinitConfig(ctx context.Context, cnf *Mycnf) error {
10711072
func (mysqld *Mysqld) createDirs(cnf *Mycnf) error {
10721073
tabletDir := cnf.TabletDir()
10731074
log.Infof("creating directory %s", tabletDir)
1074-
if err := os.MkdirAll(tabletDir, os.ModePerm); err != nil {
1075+
if err := os2.MkdirAll(tabletDir); err != nil {
10751076
return err
10761077
}
10771078
for _, dir := range TopLevelDirs() {
@@ -1081,7 +1082,7 @@ func (mysqld *Mysqld) createDirs(cnf *Mycnf) error {
10811082
}
10821083
for _, dir := range cnf.directoryList() {
10831084
log.Infof("creating directory %s", dir)
1084-
if err := os.MkdirAll(dir, os.ModePerm); err != nil {
1085+
if err := os2.MkdirAll(dir); err != nil {
10851086
return err
10861087
}
10871088
// FIXME(msolomon) validate permissions?
@@ -1105,14 +1106,14 @@ func (mysqld *Mysqld) createTopDir(cnf *Mycnf, dir string) error {
11051106
if os.IsNotExist(err) {
11061107
topdir := path.Join(tabletDir, dir)
11071108
log.Infof("creating directory %s", topdir)
1108-
return os.MkdirAll(topdir, os.ModePerm)
1109+
return os2.MkdirAll(topdir)
11091110
}
11101111
return err
11111112
}
11121113
linkto := path.Join(target, vtname)
11131114
source := path.Join(tabletDir, dir)
11141115
log.Infof("creating directory %s", linkto)
1115-
err = os.MkdirAll(linkto, os.ModePerm)
1116+
err = os2.MkdirAll(linkto)
11161117
if err != nil {
11171118
return err
11181119
}

0 commit comments

Comments
 (0)