Skip to content

Commit

Permalink
feat: Drop root priviledges
Browse files Browse the repository at this point in the history
  • Loading branch information
dadav committed Jul 7, 2024
1 parent 6ad6226 commit 6afba31
Show file tree
Hide file tree
Showing 7 changed files with 125 additions and 24 deletions.
5 changes: 0 additions & 5 deletions .goreleaser.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ builds:
- CGO_ENABLED=0
goos:
- linux
- windows
- darwin

archives:
Expand All @@ -27,10 +26,6 @@ archives:
{{- else if eq .Arch "386" }}i386
{{- else }}{{ .Arch }}{{ end }}
{{- if .Arm }}v{{ .Arm }}{{ end }}
# use zip for windows archives
format_overrides:
- goos: windows
format: zip
nfpms:
- homepage: https://github.com/dadav/gorge
Expand Down
15 changes: 15 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,10 @@ Via file (`$HOME/.config/gorge.yaml` or `./gorge.yaml`):

```yaml
---
# Set uid of process to this users uid
user: ""
# Set gid of process to this groups gid
group: ""
# The forge api version to use. Currently only v3 is supported.
api-version: v3
# The backend type to use. Currently only filesystem is supported.
Expand All @@ -162,6 +166,8 @@ cache-prefixes: /v3/files
cors: "*"
# Enables the dev mode.
dev: false
# Drop privileges if running as root (user & group options must be set)
drop-privileges: false
# List of comma separated upstream forge(s) to use when local requests return 404
fallback-proxy:
# Import proxied modules into local backend.
Expand All @@ -187,13 +193,16 @@ tls-key: ""
Via environment:

```bash
GORGE_USER=""
GORGE_GROUP=""
GORGE_API_VERSION=v3
GORGE_BACKEND=filesystem
GORGE_BIND=127.0.0.1
GORGE_CACHE_MAX_AGE=86400
GORGE_CACHE_PREFIXES=/v3/files
GORGE_CORS="*"
GORGE_DEV=false
GORGE_DROP_PRIVILEGES=false
GORGE_FALLBACK_PROXY=""
GORGE_IMPORT_PROXIED_RELEASES=false
GORGE_MODULESDIR=~/.gorge/modules
Expand All @@ -218,6 +227,12 @@ in the Authorization header like this:

In dev mode these security checks are disabled.

### 💧 Dropping privileges

There is no need to run gorge as root. But if you still want to do it, be sure to
use the `--drop-privileges` options combined with `--user` and `--group`. You could
set these to `www-data`. It will ensure gorge won't keep running as root.
## 🐝 Development
The code template for `v3` was generated with this command:
Expand Down
49 changes: 30 additions & 19 deletions cmd/serve.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import (
config "github.com/dadav/gorge/internal/config"
log "github.com/dadav/gorge/internal/log"
customMiddleware "github.com/dadav/gorge/internal/middleware"
"github.com/dadav/gorge/internal/utils"
v3 "github.com/dadav/gorge/internal/v3/api"
backend "github.com/dadav/gorge/internal/v3/backend"
openapi "github.com/dadav/gorge/pkg/gen/v3/openapi"
Expand All @@ -40,7 +41,6 @@ import (
"github.com/go-chi/cors"
"github.com/go-chi/jwtauth/v5"
"github.com/go-chi/stampede"
homedir "github.com/mitchellh/go-homedir"
"github.com/spf13/cobra"
"golang.org/x/sync/errgroup"
)
Expand All @@ -60,19 +60,19 @@ You can also enable the caching functionality to speed things up.`,

log.Setup(config.Dev)

config.ModulesDir, err = homedir.Expand(config.ModulesDir)
config.ModulesDir, err = utils.ExpandTilde(config.ModulesDir)
if err != nil {
log.Log.Fatal(err)
}
config.TlsCertPath, err = homedir.Expand(config.TlsCertPath)
config.TlsCertPath, err = utils.ExpandTilde(config.TlsCertPath)
if err != nil {
log.Log.Fatal(err)
}
config.TlsKeyPath, err = homedir.Expand(config.TlsKeyPath)
config.TlsKeyPath, err = utils.ExpandTilde(config.TlsKeyPath)
if err != nil {
log.Log.Fatal(err)
}
config.JwtTokenPath, err = homedir.Expand(config.JwtTokenPath)
config.JwtTokenPath, err = utils.ExpandTilde(config.JwtTokenPath)
if err != nil {
log.Log.Fatal(err)
}
Expand Down Expand Up @@ -119,14 +119,12 @@ You can also enable the caching functionality to speed things up.`,
return r.Method != "GET"
}))

if _, err = os.Stat(config.JwtTokenPath); err != nil {
_, tokenString, _ := tokenAuth.Encode(map[string]interface{}{"user": "admin"})
err = os.WriteFile(config.JwtTokenPath, []byte(tokenString), 0400)
if err != nil {
log.Log.Fatal(err)
}
log.Log.Infof("JWT token was written to %s\n", config.JwtTokenPath)
_, tokenString, _ := tokenAuth.Encode(map[string]interface{}{"user": "admin"})
err = os.WriteFile(config.JwtTokenPath, []byte(tokenString), 0400)
if err != nil {
log.Log.Fatal(err)
}
log.Log.Infof("JWT token was written to %s", config.JwtTokenPath)
}

if !config.NoCache {
Expand All @@ -139,7 +137,6 @@ You can also enable the caching functionality to speed things up.`,
}

if config.FallbackProxyUrl != "" {

proxies := strings.Split(config.FallbackProxyUrl, ",")
slices.Reverse(proxies)

Expand Down Expand Up @@ -191,9 +188,6 @@ You can also enable the caching functionality to speed things up.`,
w.Write([]byte(`{"message": "ok"}`))
})

bindPort := fmt.Sprintf("%s:%d", config.Bind, config.Port)
log.Log.Infof("Listen on %s", bindPort)

ctx, restoreDefaultSignalHandling := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)
defer restoreDefaultSignalHandling()
g, gCtx := errgroup.WithContext(ctx)
Expand All @@ -220,15 +214,29 @@ You can also enable the caching functionality to speed things up.`,
}
}

server := http.Server{Addr: bindPort, Handler: r, BaseContext: func(_ net.Listener) context.Context { return ctx }}
bindPort := fmt.Sprintf("%s:%d", config.Bind, config.Port)
listener, err := net.Listen("tcp", bindPort)
if err != nil {
log.Log.Fatal(err)
}
log.Log.Infof("Listen on %s", bindPort)

if config.DropPrivileges && utils.IsRoot() {
log.Log.Infof("Give control to user %s and group %s", config.User, config.Group)
if err = utils.DropPrivileges(config.User, config.Group); err != nil {
log.Log.Fatal(err)
}
}

server := http.Server{Handler: r, BaseContext: func(_ net.Listener) context.Context { return ctx }}

g.Go(func() error {
if config.TlsKeyPath != "" && config.TlsCertPath != "" {
if err := server.ListenAndServeTLS(config.TlsCertPath, config.TlsKeyPath); err != http.ErrServerClosed {
if err := server.ServeTLS(listener, config.TlsCertPath, config.TlsKeyPath); err != http.ErrServerClosed {
return err
}
} else {
if err := server.ListenAndServe(); err != http.ErrServerClosed {
if err := server.Serve(listener); err != http.ErrServerClosed {
return err
}
}
Expand Down Expand Up @@ -257,6 +265,8 @@ You can also enable the caching functionality to speed things up.`,
func init() {
rootCmd.AddCommand(serveCmd)

serveCmd.Flags().StringVar(&config.User, "user", "", "give control to this user (requires root)")
serveCmd.Flags().StringVar(&config.Group, "group", "", "give control to this group (requires root)")
serveCmd.Flags().StringVar(&config.ApiVersion, "api-version", "v3", "the forge api version to use")
serveCmd.Flags().IntVar(&config.Port, "port", 8080, "the port to listen to")
serveCmd.Flags().StringVar(&config.Bind, "bind", "127.0.0.1", "host to listen to")
Expand All @@ -266,6 +276,7 @@ func init() {
serveCmd.Flags().StringVar(&config.CORSOrigins, "cors", "*", "allowed cors origins separated by comma")
serveCmd.Flags().StringVar(&config.FallbackProxyUrl, "fallback-proxy", "", "optional comma separated list of fallback upstream proxy urls")
serveCmd.Flags().BoolVar(&config.Dev, "dev", false, "enables dev mode")
serveCmd.Flags().BoolVar(&config.DropPrivileges, "drop-privileges", false, "drops privileges to the given user/group")
serveCmd.Flags().StringVar(&config.CachePrefixes, "cache-prefixes", "/v3/files", "url prefixes to cache")
serveCmd.Flags().StringVar(&config.JwtSecret, "jwt-secret", "changeme", "jwt secret")
serveCmd.Flags().StringVar(&config.JwtTokenPath, "jwt-token-path", "~/.gorge/token", "jwt token path")
Expand Down
6 changes: 6 additions & 0 deletions defaults.yaml
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
---
# Set uid of process to this users uid
user: ""
# Set gid of process to this groups gid
group: ""
# The forge api version to use. Currently only v3 is supported.
api-version: v3
# The backend type to use. Currently only filesystem is supported.
Expand All @@ -13,6 +17,8 @@ cache-prefixes: /v3/files
cors: "*"
# Enables the dev mode.
dev: false
# Drop privileges if running as root (user & group options must be set)
drop-privileges: false
# List of comma separated upstream forge(s) to use when local requests return 404
fallback-proxy:
# Import proxied modules into local backend.
Expand Down
3 changes: 3 additions & 0 deletions internal/config/config.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
package config

var (
User string
Group string
ApiVersion string
Port int
Bind string
Dev bool
DropPrivileges bool
ModulesDir string
ModulesScanSec int
Backend string
Expand Down
56 changes: 56 additions & 0 deletions internal/utils/privileges.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package utils

import (
"errors"
"os"
"os/user"
"strconv"
"syscall"
)

func IsRoot() bool {
return os.Geteuid() == 0
}

func DropPrivileges(newUid, newGid string) error {
if newUid == "" {
return errors.New("user option is empty, cant drop privileges")
}

if newGid == "" {
return errors.New("group option is unset, cant drop privileges")
}

gid, err := strconv.Atoi(newGid)
if err != nil {
g, err := user.LookupGroup(newGid)
if err != nil {
return err
}
gid, err = strconv.Atoi(g.Gid)
if err != nil {
return err
}
}

if err = syscall.Setgid(gid); err != nil {
return err
}

uid, err := strconv.Atoi(newUid)
if err != nil {
u, err := user.Lookup(newUid)
if err != nil {
return err
}
uid, err = strconv.Atoi(u.Uid)
if err != nil {
return err
}
}
if err = syscall.Setuid(uid); err != nil {
return err
}

return nil
}
15 changes: 15 additions & 0 deletions internal/utils/tilde.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package utils

import (
"os/user"
"strings"
)

// ExpandTilde replaces ~ with the homedir of the current user
func ExpandTilde(path string) (string, error) {
u, err := user.Current()
if err != nil {
return "", err
}
return strings.Replace(path, "~", u.HomeDir, 1), nil
}

0 comments on commit 6afba31

Please sign in to comment.