Skip to content

Commit

Permalink
allow changing user id by setting TSURU_OS_UID env
Browse files Browse the repository at this point in the history
  • Loading branch information
cezarsa committed Aug 7, 2017
1 parent 67cc387 commit c1647a9
Show file tree
Hide file tree
Showing 3 changed files with 224 additions and 1 deletion.
111 changes: 111 additions & 0 deletions internal/user/user.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
// Copyright 2017 deploy-agent authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package user

import (
"bytes"
"fmt"
"os"
"os/user"
"strconv"
"strings"
"text/template"

"github.com/tsuru/tsuru/app/bind"
"github.com/tsuru/tsuru/exec"
)

const (
defaultUserIfRoot = "ubuntu"
oldUserPrefix = "tsuru.old."
tsuruUIDEnv = "TSURU_OS_UID"
)

var sudoFixTemplate *template.Template

func init() {
sudoFixTemplate, _ = template.New("userfix").Parse(`
usermod -u {{.newUID}} {{.username}};
groupmod -g {{.newUID}} {{.username}};
useradd -M -U -u {{.oldUID}} {{.oldUserPrefix}}{{.username}};
echo "{{.oldUserPrefix}}{{.username}} ALL=(#{{.newUID}}) NOPASSWD:ALL" >>/etc/sudoers;
find / \( -name proc -o -name dev -o -name sys \) -prune -o \( -user {{.oldUID}} -exec chown -h {{.newUID}}:{{.newUID}} {} + \);
`)
}

var testProcessCurrentUser func(*user.User) = nil

func ChangeUser(executor exec.Executor, envs []bind.EnvVar) (exec.Executor, error) {
user, err := user.Current()
if err != nil {
return nil, err
}
if testProcessCurrentUser != nil {
testProcessCurrentUser(user)
}
username := user.Username
if username == "" {
username = user.Name
}
if username == "root" {
username = defaultUserIfRoot
}
oldUID := os.Getuid()
newUID, _ := strconv.ParseUint(getEnv(envs, tsuruUIDEnv), 10, 32)
if newUID == 0 ||
oldUID == int(newUID) ||
user.Uid == string(newUID) {
return executor, nil
}
newExecutor := &userExecutor{
baseExecutor: executor,
uid: int(newUID),
}
if strings.HasPrefix(username, oldUserPrefix) {
return newExecutor, nil
}
buf := bytes.NewBuffer(nil)
err = sudoFixTemplate.Execute(buf, map[string]interface{}{
"newUID": newUID,
"oldUID": oldUID,
"oldUserPrefix": oldUserPrefix,
"username": username,
})
if err != nil {
return nil, err
}
return newExecutor, executor.Execute(exec.ExecuteOptions{
Cmd: "sudo",
Args: []string{
"--", "sh", "-c", buf.String(),
},
Dir: "/",
Stdout: os.Stdout,
Stderr: os.Stderr,
})
}

type userExecutor struct {
baseExecutor exec.Executor
uid int
}

func (e *userExecutor) Execute(opts exec.ExecuteOptions) error {
args := []string{
"-u", fmt.Sprintf("#%d", e.uid), "--", opts.Cmd,
}
opts.Args = append(args, opts.Args...)
opts.Cmd = "sudo"
return e.baseExecutor.Execute(opts)
}

func getEnv(envs []bind.EnvVar, key string) string {
for _, e := range envs {
if e.Name == key {
return e.Value
}
}
return ""
}
106 changes: 106 additions & 0 deletions internal/user/user_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
// Copyright 2017 deploy-agent authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package user

import (
"os/user"
"testing"

"github.com/tsuru/tsuru/app/bind"
"github.com/tsuru/tsuru/exec"
"github.com/tsuru/tsuru/exec/exectest"
"gopkg.in/check.v1"
)

func Test(t *testing.T) { check.TestingT(t) }

type S struct {
exec *exectest.FakeExecutor
}

var _ = check.Suite(&S{})

func (s *S) SetUpTest(c *check.C) {
s.exec = &exectest.FakeExecutor{}
}

func (s *S) TestChangeUserWithAnotherUID(c *check.C) {
executor, err := ChangeUser(s.exec, []bind.EnvVar{
{Name: "TSURU_OS_UID", Value: "1234"},
})
c.Assert(err, check.IsNil)
c.Assert(executor, check.DeepEquals, &userExecutor{
baseExecutor: s.exec,
uid: 1234,
})
cmds := s.exec.GetCommands("sudo")
c.Assert(cmds, check.HasLen, 1)
args := cmds[0].GetArgs()
c.Assert(args, check.HasLen, 4)
c.Assert(args[:3], check.DeepEquals, []string{"--", "sh", "-c"})
c.Assert(args[3], check.Matches, `(?s)
usermod -u 1234 .+?;
groupmod -g 1234 .+?;
useradd -M -U -u \d+ tsuru\.old\..+?;
echo "tsuru\.old\..+? ALL=\(#1234\) NOPASSWD:ALL" >>/etc/sudoers;
find / \\\( -name proc -o -name dev -o -name sys \\\) -prune -o \\\( -user \d+ -exec chown -h 1234:1234 \{\} \+ \\\);
`)
}

func (s *S) TestChangeUserNoEnvs(c *check.C) {
executor, err := ChangeUser(s.exec, nil)
c.Assert(err, check.IsNil)
c.Assert(executor, check.Equals, s.exec)
cmds := s.exec.GetCommands("sudo")
c.Assert(cmds, check.HasLen, 0)
}

func (s *S) TestChangeUserSameUser(c *check.C) {
u, err := user.Current()
c.Assert(err, check.IsNil)
executor, err := ChangeUser(s.exec, []bind.EnvVar{
{Name: "TSURU_OS_UID", Value: u.Uid},
})
c.Assert(err, check.IsNil)
c.Assert(executor, check.Equals, s.exec)
cmds := s.exec.GetCommands("sudo")
c.Assert(cmds, check.HasLen, 0)
}

func (s *S) TestChangeUserWithOldUser(c *check.C) {
testProcessCurrentUser = func(u *user.User) {
u.Name = "tsuru.old.myuser"
u.Username = u.Name
}
defer func() { testProcessCurrentUser = nil }()
executor, err := ChangeUser(s.exec, []bind.EnvVar{
{Name: "TSURU_OS_UID", Value: "9998"},
})
c.Assert(err, check.IsNil)
c.Assert(executor, check.DeepEquals, &userExecutor{
baseExecutor: s.exec,
uid: 9998,
})
cmds := s.exec.GetCommands("sudo")
c.Assert(cmds, check.HasLen, 0)
}

func (s *S) TestUserExecutorExecute(c *check.C) {
exe := userExecutor{
baseExecutor: s.exec,
uid: 999,
}
err := exe.Execute(exec.ExecuteOptions{
Cmd: "mycmd",
Args: []string{"arg1", "arg2"},
})
c.Assert(err, check.IsNil)
cmds := s.exec.GetCommands("sudo")
c.Assert(cmds, check.HasLen, 1)
args := cmds[0].GetArgs()
c.Assert(args, check.DeepEquals, []string{
"-u", "#999", "--", "mycmd", "arg1", "arg2",
})
}
8 changes: 7 additions & 1 deletion tasks.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"regexp"
"strings"

"github.com/tsuru/deploy-agent/internal/user"
"github.com/tsuru/tsuru/app/bind"
"github.com/tsuru/tsuru/exec"
"github.com/tsuru/tsuru/fs"
Expand Down Expand Up @@ -41,10 +42,15 @@ func executor() exec.Executor {
}
return osExecutor
}

func execScript(cmds []string, envs []bind.EnvVar, w io.Writer) error {
if w == nil {
w = ioutil.Discard
}
currentExecutor, err := user.ChangeUser(executor(), envs)
if err != nil {
return err
}
workingDir := defaultWorkingDir
if _, err := filesystem().Stat(defaultWorkingDir); err != nil {
if os.IsNotExist(err) {
Expand All @@ -69,7 +75,7 @@ func execScript(cmds []string, envs []bind.EnvVar, w io.Writer) error {
Stderr: os.Stderr,
}
fmt.Fprintf(w, " ---> Running %q\n", cmd)
err := executor().Execute(execOpts)
err := currentExecutor.Execute(execOpts)
if err != nil {
return fmt.Errorf("error running %q: %s\n", cmd, err)
}
Expand Down

0 comments on commit c1647a9

Please sign in to comment.