Skip to content

Commit 8713e4f

Browse files
committed
Use xrdhttp-pelican plugin to put the tester file into the selfTest dir when drop privs is enabled
- Modified the functions in self test package impacted by the drop privs mode - Tell the plugin what are the test file transplant destinations by setting the environment variables (hardcoded the destination paths) - The self-test file is named "self-test-*.txt" (* is a random string decided by os.CreateTemp) when it gets created in its birthplace, but it is renamed to "self-test-cache-server.txt" and overwrite the previous file when it is transplanted to the selfTest dir. The original file in the birthplace will be deleted after the transplant.
1 parent eb6f6eb commit 8713e4f

5 files changed

Lines changed: 137 additions & 23 deletions

File tree

launchers/cache_serve.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ func CacheServe(ctx context.Context, engine *gin.Engine, egrp *errgroup.Group, m
114114
return nil, err
115115
}
116116

117-
self_monitor.PeriodicCacheSelfTest(ctx, egrp, false)
117+
self_monitor.PeriodicSelfTest(ctx, egrp, false)
118118
}
119119

120120
// Director and origin also registers this metadata URL; avoid registering twice.

launchers/origin_serve.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ func OriginServe(ctx context.Context, engine *gin.Engine, egrp *errgroup.Group,
110110
}
111111

112112
if param.Origin_SelfTest.GetBool() {
113-
self_monitor.PeriodicCacheSelfTest(ctx, egrp, true)
113+
self_monitor.PeriodicSelfTest(ctx, egrp, true)
114114
}
115115

116116
privileged := param.Origin_Multiuser.GetBool()

self_monitor/self_monitor.go

Lines changed: 121 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ import (
4141
"github.com/pelicanplatform/pelican/server_utils"
4242
"github.com/pelicanplatform/pelican/token"
4343
"github.com/pelicanplatform/pelican/token_scopes"
44+
"github.com/pelicanplatform/pelican/xrootd"
4445
)
4546

4647
const (
@@ -62,22 +63,13 @@ func InitSelfTestDir() error {
6263
}
6364

6465
basePath := param.Cache_NamespaceLocation.GetString()
65-
pelicanMonPath := filepath.Join(basePath, "/pelican")
66-
monitoringPath := filepath.Join(pelicanMonPath, "/monitoring")
67-
selfTestPath := filepath.Join(monitoringPath, "/selfTest")
68-
err = os.MkdirAll(selfTestPath, 0700)
66+
selfTestPath := filepath.Join(basePath, selfTestDir)
67+
err = config.MkdirAll(selfTestPath, 0750, uid, gid)
6968
if err != nil {
7069
return errors.Wrap(err, "Fail to create directory for the self-test")
7170
}
72-
if err = os.Chown(pelicanMonPath, uid, gid); err != nil {
73-
return errors.Wrapf(err, "Unable to change ownership of self-test /pelican directory %v to desired daemon gid %v", monitoringPath, gid)
74-
}
75-
if err = os.Chown(monitoringPath, uid, gid); err != nil {
76-
return errors.Wrapf(err, "Unable to change ownership of self-test /pelican/monitoring directory %v to desired daemon gid %v", monitoringPath, gid)
77-
}
78-
if err = os.Chown(selfTestPath, uid, gid); err != nil {
79-
return errors.Wrapf(err, "Unable to change ownership of self-test /pelican/monitoring directory %v to desired daemon gid %v", monitoringPath, gid)
80-
}
71+
log.Debugf("Created cache self-test directory at %s", selfTestPath)
72+
8173
return nil
8274
}
8375

@@ -169,6 +161,94 @@ func generateTestFile() (string, error) {
169161
return baseUrl.String(), nil
170162
}
171163

164+
// generateTestFileViaPlugin creates a test file and its .cinfo file in a temp location (birthplace),
165+
// then copies them to the selfTestDir using the xrdhttp-pelican plugin. This function is used
166+
// when drop privileges is enabled, as the pelican server is running as an unprivileged user
167+
// and cannot directly create files in the selfTestDir.
168+
func generateTestFileViaPlugin() (string, error) {
169+
// Create a temp directory own by pelican user to bypass privilege restrictions, named "birthplace"
170+
selfTestBirthplace := filepath.Join(param.Monitoring_DataLocation.GetString(), "selfTest")
171+
err := os.MkdirAll(selfTestBirthplace, 0750)
172+
if err != nil {
173+
return "", errors.Wrap(err, "failed to create selftest directory")
174+
}
175+
176+
// Create a test file and its cinfo in the birthplace
177+
testFileBytes := []byte(selfTestBody)
178+
fileSize := len(testFileBytes)
179+
cinfo := cache.Cinfo{
180+
Store: cache.Store{
181+
FileSize: int64(fileSize),
182+
CreationTime: time.Now().Unix(),
183+
Status: 2,
184+
},
185+
}
186+
cinfoBytes, err := cinfo.Serialize()
187+
if err != nil {
188+
return "", err
189+
}
190+
191+
file, err := os.CreateTemp(selfTestBirthplace, selfTestPrefix+"*.txt")
192+
if err != nil {
193+
return "", errors.Wrap(err, "failed to create test file")
194+
}
195+
cinfoFile, err := os.Create(file.Name() + ".cinfo")
196+
if err != nil {
197+
return "", errors.Wrap(err, "failed to create test file cinfo")
198+
}
199+
defer func() {
200+
file.Close()
201+
cinfoFile.Close()
202+
// Delete the test file and its cinfo in the birthplace
203+
if err := os.Remove(file.Name()); err != nil {
204+
log.Warningf("Failed to remove test file %s: %v", file.Name(), err)
205+
}
206+
if err := os.Remove(cinfoFile.Name()); err != nil {
207+
log.Warningf("Failed to remove test file cinfo %s: %v", cinfoFile.Name(), err)
208+
}
209+
}()
210+
211+
// Write test data and cinfo to the files
212+
if _, err := file.Write(testFileBytes); err != nil {
213+
return "", errors.Wrapf(err, "failed to write test content to self-test file %s", file.Name())
214+
}
215+
if _, err := cinfoFile.Write(cinfoBytes); err != nil {
216+
return "", errors.Wrapf(err, "failed to write cinfo content to self-test cinfo %s", cinfoFile.Name())
217+
}
218+
219+
// After writing the test content to the file, the file pointer remains at the end.
220+
// Seek back to the beginning of the file so that the copy operation reads from the start.
221+
if _, err := file.Seek(0, io.SeekStart); err != nil {
222+
return "", errors.Wrap(err, "failed to seek to beginning of test file")
223+
}
224+
if _, err := cinfoFile.Seek(0, io.SeekStart); err != nil {
225+
return "", errors.Wrap(err, "failed to seek to beginning of cinfo file")
226+
}
227+
228+
// Transplant the test file and cinfo from birthplace to the selfTestDir via xrdhttp-pelican plugin
229+
if err = xrootd.SelfTestFileCopy(4, file); err != nil {
230+
return "", errors.Wrap(err, "failed to copy the test file to the self-test directory")
231+
}
232+
if err = xrootd.SelfTestFileCopy(5, cinfoFile); err != nil {
233+
return "", errors.Wrap(err, "failed to copy the test cinfo file to the self-test directory")
234+
}
235+
236+
// Construct and return the URL of the copied test file in the selfTestDir
237+
cachePort := param.Cache_Port.GetInt()
238+
baseUrlStr := fmt.Sprintf("https://%s:%d", param.Server_Hostname.GetString(), cachePort)
239+
baseUrl, err := url.Parse(baseUrlStr)
240+
if err != nil {
241+
return "", errors.Wrap(err, "failed to validate the base url for self-test download")
242+
}
243+
// This is for web URL path, do not use filepath pkg.
244+
// The file name of the test file is always the same in the selfTestDir,
245+
// no matter what's the file name in the birthplace.
246+
extFilePath := path.Join(selfTestDir, selfTestPrefix+"cache-server.txt")
247+
baseUrl.Path = extFilePath
248+
249+
return baseUrl.String(), nil
250+
}
251+
172252
func generateFileTestScitoken() (string, error) {
173253
issuerUrl := param.Server_ExternalWebUrl.GetString()
174254
if issuerUrl == "" { // if both are empty, then error
@@ -227,6 +307,14 @@ func downloadTestFile(ctx context.Context, fileUrl string) error {
227307
}
228308

229309
func deleteTestFile(fileUrlStr string) error {
310+
// If drop privileges is enabled, the test file at selfTestDir will be overwritten by the next self-test,
311+
// because the test file and cinfo at selfTestDir always have the same name.
312+
// Also, the test file and cinfo in their birthplace were deleted right after generateTestFileViaPlugin,
313+
// so this function can be skipped.
314+
if param.Server_DropPrivileges.GetBool() {
315+
return nil
316+
}
317+
230318
basePath := param.Cache_NamespaceLocation.GetString()
231319
fileUrl, err := url.Parse(fileUrlStr)
232320
if err != nil {
@@ -250,18 +338,30 @@ func deleteTestFile(fileUrlStr string) error {
250338
}
251339

252340
func runSelfTest(ctx context.Context) (bool, error) {
253-
fileUrl, err := generateTestFile()
254-
if err != nil {
255-
return false, errors.Wrap(err, "self-test failed when generating the file")
341+
var fileUrl string
342+
var err error
343+
if param.Server_DropPrivileges.GetBool() {
344+
fileUrl, err = generateTestFileViaPlugin()
345+
if err != nil {
346+
return false, errors.Wrap(err, "self-test failed when generating the file")
347+
}
348+
} else {
349+
fileUrl, err = generateTestFile()
350+
if err != nil {
351+
return false, errors.Wrap(err, "self-test failed when generating the file")
352+
}
256353
}
354+
257355
err = downloadTestFile(ctx, fileUrl)
258356
if err != nil {
357+
log.Warningf("Self-test download failed for file %s; err: %v", fileUrl, err)
259358
errDel := deleteTestFile(fileUrl)
260359
if errDel != nil {
261360
return false, errors.Wrap(errDel, "self-test failed during delete")
262361
}
263362
return false, errors.Wrap(err, "self-test failed during download. File is cleaned up at "+fileUrl)
264363
}
364+
265365
err = deleteTestFile(fileUrl)
266366
if err != nil {
267367
return false, errors.Wrap(err, "self-test failed during delete")
@@ -304,22 +404,23 @@ func doSelfMonitorOrigin(ctx context.Context) {
304404
// Start self-test monitoring of the origin/cache. This will upload, download, and delete
305405
// a generated filename every 15 seconds to the local origin. On failure, it will
306406
// set the xrootd component's status to critical.
307-
func PeriodicCacheSelfTest(ctx context.Context, ergp *errgroup.Group, isOrigin bool) {
407+
func PeriodicSelfTest(ctx context.Context, ergp *errgroup.Group, isOrigin bool) {
408+
customInterval := param.Cache_SelfTestInterval.GetDuration()
308409
doSelfMonitor := doSelfMonitorCache
309410
if isOrigin {
310411
doSelfMonitor = doSelfMonitorOrigin
412+
customInterval = param.Origin_SelfTestInterval.GetDuration()
311413
}
312414

313-
customInterval := param.Cache_SelfTestInterval.GetDuration()
314415
if customInterval == 0 {
315416
customInterval = 15 * time.Second
316-
log.Error("Invalid config value: Cache.SelfTestInterval is 0. Fallback to 15s.")
417+
log.Error("Invalid config value: both Origin.SelfTestInterval and Cache.SelfTestInterval are 0. Fallback to 15s.")
317418
}
318419
ticker := time.NewTicker(customInterval)
319-
defer ticker.Stop()
320420
firstRound := time.After(5 * time.Second)
321421

322422
ergp.Go(func() error {
423+
defer ticker.Stop()
323424
for {
324425
select {
325426
case <-firstRound:

xrootd/launch.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,12 @@ func makeUnprivilegedXrootdLauncher(daemonName string, configPath string, isCach
147147
result.ExtraEnv = append(result.ExtraEnv, "XRDHTTP_PELICAN_CA_FILE="+filepath.Join(xrootdRun, "ca-bundle.crt"))
148148
result.ExtraEnv = append(result.ExtraEnv, "XRDHTTP_PELICAN_CERT_FILE="+filepath.Join(xrootdRun, "copied-tls-creds.crt"))
149149
result.ExtraEnv = append(result.ExtraEnv, "XRDHTTP_PELICAN_INFO_FD="+strconv.Itoa(result.fds[1]))
150+
151+
basePath := param.Cache_NamespaceLocation.GetString() + "/pelican/monitoring/selfTest"
152+
testFileLocation := basePath + "/self-test-cache-server.txt"
153+
testFileCinfoLocation := basePath + "/self-test-cache-server.txt.cinfo"
154+
result.ExtraEnv = append(result.ExtraEnv, "XRDHTTP_PELICAN_CACHE_SELF_TEST_FILE="+testFileLocation)
155+
result.ExtraEnv = append(result.ExtraEnv, "XRDHTTP_PELICAN_CACHE_SELF_TEST_FILE_CINFO="+testFileCinfoLocation)
150156
}
151157
return
152158
}

xrootd/xrootd_config.go

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -614,10 +614,17 @@ func copyXrootdCertificates(server server_structs.XRootDServer) error {
614614
return nil
615615
}
616616

617+
func SelfTestFileCopy(cmd int, file *os.File) error {
618+
log.Debug("Transplanting a self-test file")
619+
if err := sendChildFD(false, cmd, file); err != nil {
620+
return errors.Wrap(err, "Failed to copy the self-test file via FD")
621+
}
622+
return nil
623+
}
624+
617625
// After privileges have been dropped, copy the server certificates
618626
// to the xrootd process.
619627
func dropPrivilegeCopy(server server_structs.XRootDServer) error {
620-
621628
certFile := param.Server_TLSCertificateChain.GetString()
622629
certKey := param.Server_TLSKey.GetString()
623630
if _, err := tls.LoadX509KeyPair(certFile, certKey); err != nil {

0 commit comments

Comments
 (0)