Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for running as root and 'multiuser' mode #51

Merged
merged 7 commits into from
Aug 29, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ jobs:
name: Set up Go
uses: actions/setup-go@v4
with:
go-version: 1.18
go-version: 1.20
-
name: Run GoReleaser
uses: goreleaser/goreleaser-action@v4
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ jobs:
test:
strategy:
matrix:
go-version: [1.19.x]
go-version: [1.20.x]
os: [ubuntu-latest, macos-latest, windows-latest]
runs-on: ${{ matrix.os }}
steps:
Expand Down
4 changes: 2 additions & 2 deletions cmd/namespace_registry.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import (
"encoding/hex"
"encoding/json"
"fmt"
"io/ioutil"
"io"
"os"
"net/http"
"bytes"
Expand Down Expand Up @@ -55,7 +55,7 @@ func make_request(url string, method string, data map[string]interface{}) ([]byt
}
defer resp.Body.Close()

body, _ := ioutil.ReadAll(resp.Body)
body, _ := io.ReadAll(resp.Body)
return body, nil
}

Expand Down
223 changes: 51 additions & 172 deletions cmd/origin_serve.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,27 +3,23 @@
package main

import (
"bufio"
"crypto/elliptic"
"crypto/rand"
_ "embed"
"encoding/base64"
"encoding/json"
"fmt"
"os"
"os/exec"
"os/signal"
"path"
"path/filepath"
"strings"
"syscall"
"text/template"
"time"

"github.com/pelicanplatform/pelican/metrics"
"github.com/pelicanplatform/pelican/config"
"github.com/pelicanplatform/pelican/origin_ui"
"github.com/pelicanplatform/pelican/web_ui"
"github.com/pelicanplatform/pelican/xrootd"
"github.com/pkg/errors"
log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"
Expand All @@ -39,7 +35,13 @@ var (

)

type XrootdConfig struct {
type (

OriginConfig struct {
Multiuser bool
}

XrootdConfig struct {
Port int
ManagerHost string
ManagerPort string
Expand All @@ -59,10 +61,12 @@ type XrootdConfig struct {
ScitokensConfig string
Mount string
NamespacePrefix string
XrootdMultiuser bool
LocalMonitoringPort int
Origin OriginConfig
}

)

func init() {
err := config.InitServer()
cobra.CheckErr(err)
Expand Down Expand Up @@ -92,7 +96,7 @@ func checkXrootdEnv() error {

// Ensure the runtime directory exists
runtimeDir := viper.GetString("XrootdRun")
err = os.MkdirAll(runtimeDir, 0755)
err = config.MkdirAll(runtimeDir, 0755, uid, gid)
if err != nil {
return errors.Wrapf(err, "Unable to create runtime directory %v", runtimeDir)
}
Expand Down Expand Up @@ -131,7 +135,7 @@ func checkXrootdEnv() error {
volumeMountDst)
}
destPath := path.Clean(filepath.Join(exportPath, volumeMountDst[1:]))
err = os.MkdirAll(filepath.Dir(destPath), 0755)
err = config.MkdirAll(filepath.Dir(destPath), 0755, uid, gid)
if err != nil {
return errors.Wrapf(err, "Unable to create export directory %v",
filepath.Dir(destPath))
Expand Down Expand Up @@ -163,7 +167,7 @@ to export the directory /mnt/foo to the path /bar in the data federation`)
namespacePrefix)
}
destPath := path.Clean(filepath.Join(exportPath, namespacePrefix[1:]))
err = os.MkdirAll(filepath.Dir(destPath), 0755)
err = config.MkdirAll(filepath.Dir(destPath), 0755, uid, gid)
if err != nil {
return errors.Wrapf(err, "Unable to create export directory %v",
filepath.Dir(destPath))
Expand All @@ -181,7 +185,7 @@ to export the directory /mnt/foo to the path /bar in the data federation`)
return err
}
wellKnownPath := filepath.Join(exportPath, ".well-known")
err = os.MkdirAll(wellKnownPath, 0755)
err = config.MkdirAll(wellKnownPath, 0755, -1, gid)
if err != nil {
return err
}
Expand All @@ -205,7 +209,7 @@ to export the directory /mnt/foo to the path /bar in the data federation`)
if _, err := os.Open(robotsTxtFile); err != nil {
if errors.Is(err, os.ErrNotExist) {
newPath := filepath.Join(runtimeDir, "robots.txt")
err = os.MkdirAll(path.Dir(newPath), 0755)
err = config.MkdirAll(path.Dir(newPath), 0755, -1, gid)
if err != nil {
return errors.Wrapf(err, "Unable to create directory %v",
path.Dir(newPath))
Expand All @@ -228,7 +232,7 @@ to export the directory /mnt/foo to the path /bar in the data federation`)
macaroonsSecret := viper.GetString("MacaroonsKeyFile")
if _, err := os.Open(macaroonsSecret); err != nil {
if errors.Is(err, os.ErrNotExist) {
err = os.MkdirAll(path.Dir(macaroonsSecret), 0755)
err = config.MkdirAll(path.Dir(macaroonsSecret), 0755, -1, gid)
if err != nil {
return errors.Wrapf(err, "Unable to create directory %v",
path.Dir(macaroonsSecret))
Expand Down Expand Up @@ -258,7 +262,7 @@ to export the directory /mnt/foo to the path /bar in the data federation`)

// If the authfile or scitokens.cfg does not exist, create one
authfile := viper.GetString("Authfile")
err = os.MkdirAll(path.Dir(authfile), 0755)
err = config.MkdirAll(path.Dir(authfile), 0755, -1, gid)
if err != nil {
return errors.Wrapf(err, "Unable to create directory %v",
path.Dir(authfile))
Expand All @@ -274,7 +278,7 @@ to export the directory /mnt/foo to the path /bar in the data federation`)
}

scitokensCfg := viper.GetString("ScitokensConfig")
err = os.MkdirAll(path.Dir(scitokensCfg), 0755)
err = config.MkdirAll(path.Dir(scitokensCfg), 0755, -1, gid)
if err != nil {
return errors.Wrapf(err, "Unable to create directory %v",
path.Dir(scitokensCfg))
Expand Down Expand Up @@ -340,178 +344,48 @@ func checkDefaults() error {
}

func configXrootd() (string, error) {
var config XrootdConfig
config.LocalMonitoringPort = -1
if err := viper.Unmarshal(&config); err != nil {
gid, err := config.GetDaemonGID()
if err != nil {
return "", err
}
templ := template.Must(template.New("xrootd.cfg").Parse(xrootdCfg))

xrootdRun := viper.GetString("XrootdRun")
configPath := filepath.Join(xrootdRun, "xrootd.cfg")
file, err := os.OpenFile(configPath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
if err != nil {
var xrdConfig XrootdConfig
xrdConfig.LocalMonitoringPort = -1
if err := viper.Unmarshal(&xrdConfig); err != nil {
return "", err
}
defer file.Close()

err = templ.Execute(file, config)
if err != nil {
return "", err
if xrdConfig.Origin.Multiuser {
ok, err := config.HasMultiuserCaps()
if err != nil {
return "", errors.Wrap(err, "Failed to determine if the origin can run in multiuser mode")
}
if !ok {
return "", errors.New("Origin.Multiuser is set to `true` but the command was run without sufficient privilege; was it launched as root?")
}
}

return configPath, nil
}
templ := template.Must(template.New("xrootd.cfg").Parse(xrootdCfg))

func forwardCommandToLogger(daemonName string, cmd *exec.Cmd, isDoneChannel chan error) error {
cmdStdout, err := cmd.StdoutPipe()
xrootdRun := viper.GetString("XrootdRun")
configPath := filepath.Join(xrootdRun, "xrootd.cfg")
file, err := os.OpenFile(configPath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0640)
if err != nil {
return err
return "", err
}
cmdStderr, err := cmd.StderrPipe()
if err != nil {
return err
if err = os.Chown(configPath, -1, gid); err != nil {
return "", errors.Wrapf(err, "Unable to change ownership of configuration file %v"+
" to desired daemon gid %v", configPath, gid)
}
go func() {
cmd_logger := log.WithFields(log.Fields{"daemon": daemonName})
stdout_scanner := bufio.NewScanner(cmdStdout)
stdout_lines := make(chan string, 10)

stderr_scanner := bufio.NewScanner(cmdStderr)
stderr_lines := make(chan string, 10)
go func() {
defer close(stdout_lines)
for stdout_scanner.Scan() {
stdout_lines <- stdout_scanner.Text()
}
}()
go func() {
defer close(stderr_lines)
for stderr_scanner.Scan() {
stderr_lines <- stderr_scanner.Text()
}
}()
for {
select {
case stdout_line, ok := <-stdout_lines:
if ok {
cmd_logger.Info(stdout_line)
} else {
stdout_lines = nil
}
case stderr_line, ok := <-stderr_lines:
if ok {
cmd_logger.Info(stderr_line)
} else {
stderr_lines = nil
}
}
if stdout_lines == nil && stderr_lines == nil {
break
}
}
result := cmd.Wait()
isDoneChannel <- result
close(isDoneChannel)
}()
return nil
}

func launchXrootd() error {
configPath, err := configXrootd()
if err != nil {
return err
}
xrootdCmd := exec.Command("xrootd", "-f", "-c", configPath)
if xrootdCmd.Err != nil {
return xrootdCmd.Err
}
xrootdDoneChannel := make(chan error, 1)
if err := forwardCommandToLogger("xrootd", xrootdCmd, xrootdDoneChannel); err != nil {
return err
}
if err := xrootdCmd.Start(); err != nil {
return err
}
log.Info("Successfully launched xrootd")
if err := metrics.SetComponentHealthStatus("xrootd", "ok", ""); err != nil {
return err
}
defer file.Close()

cmsdCmd := exec.Command("cmsd", "-f", "-c", configPath)
if cmsdCmd.Err != nil {
return cmsdCmd.Err
}
cmsdDoneChannel := make(chan error, 1)
if err := forwardCommandToLogger("cmsd", cmsdCmd, cmsdDoneChannel); err != nil {
return err
}
if err := cmsdCmd.Start(); err != nil {
return err
}
log.Info("Successfully launched cmsd")
if err := metrics.SetComponentHealthStatus("cmsd", "ok", ""); err != nil {
return err
err = templ.Execute(file, xrdConfig)
if err != nil {
return "", err
}

sigs := make(chan os.Signal, 1)
signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT)
var xrootdExpiry time.Time
var cmsdExpiry time.Time
for {
timer := time.NewTimer(time.Second)
select {
case sig := <-sigs:
if sys_sig, ok := sig.(syscall.Signal); ok {
log.Warnf("Forwarding signal %v to xrootd processes\n", sys_sig)
if err = syscall.Kill(xrootdCmd.Process.Pid, sys_sig); err != nil {
return errors.Wrap(err, "Failed to forward signal to xrootd process")
}
if err = syscall.Kill(cmsdCmd.Process.Pid, sys_sig); err != nil {
return errors.Wrap(err, "Failed to forward signal to cmsd process")
}
} else {
panic(errors.New("Unable to convert signal to syscall.Signal"))
}
xrootdExpiry = time.Now().Add(10 * time.Second)
cmsdExpiry = time.Now().Add(10 * time.Second)
case waitResult := <-xrootdDoneChannel:
if waitResult != nil {
if !cmsdExpiry.IsZero() {
return nil
}
if err = metrics.SetComponentHealthStatus("xrootd", "critical",
"xrootd process failed unexpectedly"); err != nil {
return err
}
return errors.Wrap(waitResult, "xrootd process failed unexpectedly")
}
return nil
case waitResult := <-cmsdDoneChannel:
if waitResult != nil {
if !xrootdExpiry.IsZero() {
return nil
}
if err = metrics.SetComponentHealthStatus("cmsd", "critical",
"cmsd process failed unexpectedly"); err != nil {
return nil
}
return errors.Wrap(waitResult, "cmsd process failed unexpectedly")
}
return nil
case <-timer.C:
if !xrootdExpiry.IsZero() && time.Now().After(xrootdExpiry) {
if err = syscall.Kill(xrootdCmd.Process.Pid, syscall.SIGKILL); err != nil {
return errors.Wrap(err, "Failed to SIGKILL the xrootd process")
}
}
if !cmsdExpiry.IsZero() && time.Now().After(cmsdExpiry) {
if err = syscall.Kill(cmsdCmd.Process.Pid, syscall.SIGKILL); err != nil {
return errors.Wrap(err, "Failed to SIGKILL the cmsd process")
}
}
}
}
return configPath, nil
}

func serveOrigin(/*cmd*/ *cobra.Command, /*args*/ []string) error {
Expand Down Expand Up @@ -558,7 +432,12 @@ func serveOrigin(/*cmd*/ *cobra.Command, /*args*/ []string) error {
return err
}

err = launchXrootd()
configPath, err := configXrootd()
if err != nil {
return err
}
privileged := viper.GetBool("Origin.Multiuser")
err = xrootd.LaunchXrootd(privileged, configPath)
if err != nil {
return err
}
Expand Down
2 changes: 1 addition & 1 deletion cmd/resources/xrootd.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ acc.audit deny grant
acc.authdb {{.Authfile}}
ofs.authlib ++ libXrdAccSciTokens.so config={{.ScitokensConfig}}
all.export {{.NamespacePrefix}}
{{if .XrootdMultiuser}}
{{if .Origin.Multiuser}}
ofs.osslib libXrdMultiuser.so default
ofs.ckslib * libXrdMultiuser.so
{{end}}
Expand Down
Loading