Skip to content

Commit 107cab6

Browse files
[release-19.0] Replace uses of os.Create with os2.Create within backup/restore workflows (#17648) (#17664)
Signed-off-by: Matt Lord <mattalord@gmail.com> Co-authored-by: vitess-bot[bot] <108069721+vitess-bot[bot]@users.noreply.github.com> Co-authored-by: Matt Lord <mattalord@gmail.com>
1 parent 74fc34c commit 107cab6

File tree

7 files changed

+141
-11
lines changed

7 files changed

+141
-11
lines changed

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
@@ -408,6 +408,18 @@ func TestBackup(t *testing.T, setupType int, streamMode string, stripes int, cDe
408408
return vterrors.Errorf(vtrpc.Code_UNKNOWN, "test failure: %s", test.name)
409409
}
410410
}
411+
412+
t.Run("check for files created with global permissions", func(t *testing.T) {
413+
t.Logf("Confirming that none of the MySQL data directories that we've created have files with global permissions")
414+
for _, ks := range localCluster.Keyspaces {
415+
for _, shard := range ks.Shards {
416+
for _, tablet := range shard.Vttablets {
417+
tablet.VttabletProcess.ConfirmDataDirHasNoGlobalPerms(t)
418+
}
419+
}
420+
}
421+
})
422+
411423
return nil
412424
}
413425

go/test/endtoend/cluster/vttablet_process.go

Lines changed: 64 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"
@@ -677,6 +680,67 @@ func (vttablet *VttabletProcess) IsShutdown() bool {
677680
return vttablet.proc == nil
678681
}
679682

683+
// ConfirmDataDirHasNoGlobalPerms confirms that no files in the tablet's data directory
684+
// have any global/world/other permissions enabled.
685+
func (vttablet *VttabletProcess) ConfirmDataDirHasNoGlobalPerms(t *testing.T) {
686+
datadir := vttablet.Directory
687+
if _, err := os.Stat(datadir); errors.Is(err, os.ErrNotExist) {
688+
t.Logf("Data directory %s no longer exists, skipping permissions check", datadir)
689+
return
690+
}
691+
692+
var allowedFiles = []string{
693+
// These are intentionally created with the world/other read bit set by mysqld itself
694+
// during the --initialize[-insecure] step.
695+
// See: https://dev.mysql.com/doc/mysql-security-excerpt/en/creating-ssl-rsa-files-using-mysql.html
696+
// "On Unix and Unix-like systems, the file access mode is 644 for certificate files
697+
// (that is, world readable) and 600 for key files (that is, accessible only by the
698+
// account that runs the server)."
699+
path.Join("data", "ca.pem"),
700+
path.Join("data", "client-cert.pem"),
701+
path.Join("data", "public_key.pem"),
702+
path.Join("data", "server-cert.pem"),
703+
// The domain socket must have global perms for anyone to use it.
704+
"mysql.sock",
705+
// These files are created by xtrabackup.
706+
path.Join("tmp", "xtrabackup_checkpoints"),
707+
path.Join("tmp", "xtrabackup_info"),
708+
// These are 5.7 specific xtrabackup files.
709+
path.Join("data", "xtrabackup_binlog_pos_innodb"),
710+
path.Join("data", "xtrabackup_master_key_id"),
711+
path.Join("data", "mysql_upgrade_info"),
712+
}
713+
714+
var matches []string
715+
fsys := os.DirFS(datadir)
716+
err := fs.WalkDir(fsys, ".", func(p string, d fs.DirEntry, _ error) error {
717+
// first check if the file should be skipped
718+
for _, name := range allowedFiles {
719+
if strings.HasSuffix(p, name) {
720+
return nil
721+
}
722+
}
723+
724+
info, err := d.Info()
725+
if err != nil {
726+
return err
727+
}
728+
729+
// check if any global bit is on the filemode
730+
if info.Mode()&0007 != 0 {
731+
matches = append(matches, fmt.Sprintf(
732+
"%s (%s)",
733+
path.Join(datadir, p),
734+
info.Mode(),
735+
))
736+
}
737+
return nil
738+
})
739+
740+
require.NoError(t, err, "Error walking directory")
741+
require.Empty(t, matches, "Found files with global permissions: %s\n", strings.Join(matches, "\n"))
742+
}
743+
680744
// VttabletProcessInstance returns a VttabletProcess handle for vttablet process
681745
// configured with the given Config.
682746
// 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
@@ -30,6 +30,7 @@ import (
3030

3131
"vitess.io/vitess/go/mysql"
3232
"vitess.io/vitess/go/mysql/replication"
33+
"vitess.io/vitess/go/os2"
3334
"vitess.io/vitess/go/vt/logutil"
3435
"vitess.io/vitess/go/vt/mysqlctl/backupstats"
3536
"vitess.io/vitess/go/vt/mysqlctl/backupstorage"
@@ -654,7 +655,7 @@ func createStateFile(cnf *Mycnf) error {
654655
// rename func to openStateFile
655656
// change to return a *File
656657
fname := filepath.Join(cnf.TabletDir(), RestoreState)
657-
fd, err := os.Create(fname)
658+
fd, err := os2.Create(fname)
658659
if err != nil {
659660
return fmt.Errorf("unable to create file: %v", err)
660661
}

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/concurrency"
4445
"vitess.io/vitess/go/vt/log"
@@ -199,10 +200,10 @@ func (fe *FileEntry) open(cnf *Mycnf, readOnly bool) (*os.File, error) {
199200
}
200201
} else {
201202
dir := path.Dir(name)
202-
if err := os.MkdirAll(dir, os.ModePerm); err != nil {
203+
if err := os2.MkdirAll(dir); err != nil {
203204
return nil, vterrors.Wrapf(err, "cannot create destination directory %v", dir)
204205
}
205-
if fd, err = os.Create(name); err != nil {
206+
if fd, err = os2.Create(name); err != nil {
206207
return nil, vterrors.Wrapf(err, "cannot create destination file %v", name)
207208
}
208209
}

go/vt/mysqlctl/filebackupstorage/file.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import (
2828
"github.com/spf13/pflag"
2929

3030
"vitess.io/vitess/go/ioutil"
31+
"vitess.io/vitess/go/os2"
3132
"vitess.io/vitess/go/vt/concurrency"
3233
stats "vitess.io/vitess/go/vt/mysqlctl/backupstats"
3334
"vitess.io/vitess/go/vt/mysqlctl/backupstorage"
@@ -110,7 +111,7 @@ func (fbh *FileBackupHandle) AddFile(ctx context.Context, filename string, files
110111
return nil, fmt.Errorf("AddFile cannot be called on read-only backup")
111112
}
112113
p := path.Join(FileBackupStorageRoot, fbh.dir, fbh.name, filename)
113-
f, err := os.Create(p)
114+
f, err := os2.Create(p)
114115
if err != nil {
115116
return nil, err
116117
}
@@ -186,13 +187,13 @@ func (fbs *FileBackupStorage) ListBackups(ctx context.Context, dir string) ([]ba
186187
func (fbs *FileBackupStorage) StartBackup(ctx context.Context, dir, name string) (backupstorage.BackupHandle, error) {
187188
// Make sure the directory exists.
188189
p := path.Join(FileBackupStorageRoot, dir)
189-
if err := os.MkdirAll(p, os.ModePerm); err != nil {
190+
if err := os2.MkdirAll(p); err != nil {
190191
return nil, err
191192
}
192193

193194
// Create the subdirectory for this named backup.
194195
p = path.Join(p, name)
195-
if err := os.Mkdir(p, os.ModePerm); err != nil {
196+
if err := os2.Mkdir(p); err != nil {
196197
return nil, err
197198
}
198199

go/vt/mysqlctl/mysqld.go

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ import (
4646
"vitess.io/vitess/config"
4747
"vitess.io/vitess/go/mysql"
4848
"vitess.io/vitess/go/mysql/sqlerror"
49+
"vitess.io/vitess/go/os2"
4950
"vitess.io/vitess/go/protoutil"
5051
"vitess.io/vitess/go/sqltypes"
5152
"vitess.io/vitess/go/vt/dbconfigs"
@@ -838,7 +839,7 @@ func (mysqld *Mysqld) initConfig(cnf *Mycnf, outFile string) error {
838839
return err
839840
}
840841

841-
return os.WriteFile(outFile, []byte(configData), 0o664)
842+
return os2.WriteFile(outFile, []byte(configData))
842843
}
843844

844845
func (mysqld *Mysqld) getMycnfTemplate() string {
@@ -982,7 +983,7 @@ func (mysqld *Mysqld) ReinitConfig(ctx context.Context, cnf *Mycnf) error {
982983
func (mysqld *Mysqld) createDirs(cnf *Mycnf) error {
983984
tabletDir := cnf.TabletDir()
984985
log.Infof("creating directory %s", tabletDir)
985-
if err := os.MkdirAll(tabletDir, os.ModePerm); err != nil {
986+
if err := os2.MkdirAll(tabletDir); err != nil {
986987
return err
987988
}
988989
for _, dir := range TopLevelDirs() {
@@ -992,7 +993,7 @@ func (mysqld *Mysqld) createDirs(cnf *Mycnf) error {
992993
}
993994
for _, dir := range cnf.directoryList() {
994995
log.Infof("creating directory %s", dir)
995-
if err := os.MkdirAll(dir, os.ModePerm); err != nil {
996+
if err := os2.MkdirAll(dir); err != nil {
996997
return err
997998
}
998999
// FIXME(msolomon) validate permissions?
@@ -1016,14 +1017,14 @@ func (mysqld *Mysqld) createTopDir(cnf *Mycnf, dir string) error {
10161017
if os.IsNotExist(err) {
10171018
topdir := path.Join(tabletDir, dir)
10181019
log.Infof("creating directory %s", topdir)
1019-
return os.MkdirAll(topdir, os.ModePerm)
1020+
return os2.MkdirAll(topdir)
10201021
}
10211022
return err
10221023
}
10231024
linkto := path.Join(target, vtname)
10241025
source := path.Join(tabletDir, dir)
10251026
log.Infof("creating directory %s", linkto)
1026-
err = os.MkdirAll(linkto, os.ModePerm)
1027+
err = os2.MkdirAll(linkto)
10271028
if err != nil {
10281029
return err
10291030
}

0 commit comments

Comments
 (0)