Custom UID and GID for subprocesses and files written (#116) Fix #116

- Environment variables `UID` and `GID`, both defaulting to `1000`
- All subprocesses (openvpn, tinyproxy, etc.) run using the UID and GID given
- All files are written with an ownership for the UID and GID given
- Port forwarded file has also ownership for UID, GID and read permission only
This commit is contained in:
Quentin McGaw
2020-03-29 19:52:49 -04:00
committed by GitHub
parent 76cea56864
commit e5adccd9c5
12 changed files with 193 additions and 30 deletions

28
internal/alpine/alpine.go Normal file
View File

@@ -0,0 +1,28 @@
package alpine
import (
"os/user"
"github.com/qdm12/golibs/files"
"github.com/qdm12/golibs/logging"
)
const logPrefix = "alpine configurator"
type Configurator interface {
CreateUser(username string, uid int) error
}
type configurator struct {
fileManager files.FileManager
lookupUID func(uid string) (*user.User, error)
lookupUser func(username string) (*user.User, error)
}
func NewConfigurator(logger logging.Logger, fileManager files.FileManager) Configurator {
return &configurator{
fileManager: fileManager,
lookupUID: user.LookupId,
lookupUser: user.Lookup,
}
}

38
internal/alpine/users.go Normal file
View File

@@ -0,0 +1,38 @@
package alpine
import (
"fmt"
"os/user"
)
// CreateUser creates a user in Alpine with the given UID
func (c *configurator) CreateUser(username string, uid int) error {
UIDStr := fmt.Sprintf("%d", uid)
u, err := c.lookupUID(UIDStr)
_, unknownUID := err.(user.UnknownUserIdError)
if err != nil && !unknownUID {
return fmt.Errorf("cannot create user: %w", err)
} else if u != nil {
if u.Username == username {
return nil
}
return fmt.Errorf("user with ID %d exists with username %q instead of %q", uid, u.Username, username)
}
u, err = c.lookupUser(username)
_, unknownUsername := err.(user.UnknownUserError)
if err != nil && !unknownUsername {
return fmt.Errorf("cannot create user: %w", err)
} else if u != nil {
return fmt.Errorf("cannot create user: user with name %s already exists for ID %s instead of %d", username, u.Uid, uid)
}
passwd, err := c.fileManager.ReadFile("/etc/passwd")
if err != nil {
return fmt.Errorf("cannot create user: %w", err)
}
passwd = append(passwd, []byte(fmt.Sprintf("%s:x:%d:::/dev/null:/sbin/nologin\n", username, uid))...)
if err := c.fileManager.WriteToFile("/etc/passwd", passwd); err != nil {
return fmt.Errorf("cannot create user: %w", err)
}
return nil
}

15
internal/params/ids.go Normal file
View File

@@ -0,0 +1,15 @@
package params
import (
libparams "github.com/qdm12/golibs/params"
)
// GetUID obtains the user ID to use from the environment variable UID
func (p *paramsReader) GetUID() (uid int, err error) {
return p.envParams.GetEnvIntRange("UID", 0, 65535, libparams.Default("1000"))
}
// GetGID obtains the group ID to use from the environment variable GID
func (p *paramsReader) GetGID() (gid int, err error) {
return p.envParams.GetEnvIntRange("GID", 0, 65535, libparams.Default("1000"))
}

View File

@@ -28,6 +28,10 @@ type ParamsReader interface {
GetDNSOverTLSPrivateAddresses() (privateAddresses []string)
GetDNSOverTLSIPv6() (ipv6 bool, err error)
// IDs
GetUID() (uid int, err error)
GetGID() (gid int, err error)
// Firewall getters
GetExtraSubnets() (extraSubnets []net.IPNet, err error)

View File

@@ -20,7 +20,7 @@ type Configurator interface {
encryption models.PIAEncryption, targetIP net.IP) (connections []models.OpenVPNConnection, err error)
BuildConf(connections []models.OpenVPNConnection, encryption models.PIAEncryption, verbosity, uid, gid int, root bool, cipher, auth string) (err error)
GetPortForward() (port uint16, err error)
WritePortForward(filepath models.Filepath, port uint16) (err error)
WritePortForward(filepath models.Filepath, port uint16, uid, gid int) (err error)
AllowPortForwardFirewall(device models.VPNDevice, port uint16) (err error)
}

View File

@@ -5,6 +5,7 @@ import (
"encoding/json"
"fmt"
"github.com/qdm12/golibs/files"
"github.com/qdm12/private-internet-access-docker/internal/constants"
"github.com/qdm12/private-internet-access-docker/internal/models"
)
@@ -35,9 +36,13 @@ func (c *configurator) GetPortForward() (port uint16, err error) {
return body.Port, nil
}
func (c *configurator) WritePortForward(filepath models.Filepath, port uint16) (err error) {
func (c *configurator) WritePortForward(filepath models.Filepath, port uint16, uid, gid int) (err error) {
c.logger.Info("%s: Writing forwarded port to %s", logPrefix, filepath)
return c.fileManager.WriteLinesToFile(string(filepath), []string{fmt.Sprintf("%d", port)})
return c.fileManager.WriteLinesToFile(
string(filepath),
[]string{fmt.Sprintf("%d", port)},
files.Ownership(uid, gid),
files.Permissions(400))
}
func (c *configurator) AllowPortForwardFirewall(device models.VPNDevice, port uint16) (err error) {

View File

@@ -18,6 +18,8 @@ type Settings struct {
Firewall Firewall
TinyProxy TinyProxy
ShadowSocks ShadowSocks
UID int
GID int
}
func (s *Settings) String() string {
@@ -32,6 +34,7 @@ func (s *Settings) String() string {
}
return strings.Join([]string{
"Settings summary below:",
fmt.Sprintf("|-- Using UID %d and GID %d", s.UID, s.GID),
s.OpenVPN.String(),
vpnServiceProvider,
s.DNS.String(),
@@ -115,5 +118,13 @@ func GetAllSettings(params params.ParamsReader) (settings Settings, err error) {
if err != nil {
return settings, err
}
settings.UID, err = params.GetUID()
if err != nil {
return settings, err
}
settings.GID, err = params.GetGID()
if err != nil {
return settings, err
}
return settings, nil
}