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

[#36] SSHKit.user errors because sudo is missing #51

Merged
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
29 changes: 12 additions & 17 deletions lib/sshkit/context.ex
Original file line number Diff line number Diff line change
Expand Up @@ -22,32 +22,27 @@ defmodule SSHKit.Context do
```
"""
def build(context, command) do
command
|> cmd
|> group(context.group)
|> user(context.user)
|> env(context.env)
"/usr/bin/env #{command}"
|> sudo(context.user, context.group)
|> export(context.env)
|> umask(context.umask)
|> path(context.path)
|> cd(context.path)
end

defp cmd(command), do: "/usr/bin/env #{command}"
defp sudo(command, nil, nil), do: command
defp sudo(command, username, nil), do: "sudo -n -u #{username} -- sh -c #{shellquote(command)}"
defp sudo(command, nil, groupname), do: "sudo -n -g #{groupname} -- sh -c #{shellquote(command)}"
defp sudo(command, username, groupname), do: "sudo -n -u #{username} -g #{groupname} -- sh -c #{shellquote(command)}"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

only this match of sudo/3 is ever used, right?

Copy link
Contributor Author

@pmeinhardt pmeinhardt May 11, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Errr, nope. Unless I missed something, the context may hold any combination of :user and :group being present or not present.

Example:

context =
  hosts
  |> SSHKit.context()
  |> SSHKit.user("tessi")
  |> SSHKit.path("/var/log")

SSHKit.Context.build(context, "ls")

Here, context has a user, but no group set.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah, you're right. i just misread the code yesterday evening. let's act as if i never wrote this comment ;)


defp group(command, nil), do: command
defp group(command, _name), do: command

defp user(command, nil), do: command
defp user(command, name), do: "sudo -u #{name} -- sh -c #{shellquote(command)}"

defp env(command, nil), do: command
defp env(command, env) do
defp export(command, nil), do: command
defp export(command, env) do
exports = Enum.map_join(env, " ", fn {name, value} -> "#{name}=\"#{value}\"" end)
"(export #{exports} && #{command})"
end

defp umask(command, nil), do: command
defp umask(command, mask), do: "umask #{mask} && #{command}"

defp path(command, nil), do: command
defp path(command, path), do: "cd #{path} && #{command}"
defp cd(command, nil), do: command
defp cd(command, path), do: "cd #{path} && #{command}"
end
4 changes: 2 additions & 2 deletions lib/sshkit/utils.ex
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@ defmodule SSHKit.Utils do

def shellescape(value), do: value

def shellquote(value), do: value
def shellquote(value), do: "'#{value}'"

def charlistify(value) when is_list(value) do
Enum.map(value, &charlistify/1)
end
def charlistify(value) when is_tuple(value) do
value
|> Tuple.to_list
|> Tuple.to_list()
|> charlistify()
|> List.to_tuple()
end
Expand Down
2 changes: 1 addition & 1 deletion test/sshkit/ssh_functional_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ defmodule SSHKit.SSHFunctionalTest do
test "opens a connection with username and password", %{hosts: [host]} do
options = [port: host.port, user: host.user, password: host.password]
{:ok, conn} = SSH.connect(host.ip, Keyword.merge(@defaults, options))
{:ok, data, status} = SSH.run(conn, "whoami")
{:ok, data, status} = SSH.run(conn, "id -un")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍


assert [stdout: "#{host.user}\n"] == data
assert 0 = status
Expand Down
52 changes: 24 additions & 28 deletions test/sshkit_functional_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -5,21 +5,15 @@ defmodule SSHKitFunctionalTest do

use SSHKit.FunctionalCase, async: true

@defaults [silently_accept_hosts: true]
@defaults [silently_accept_hosts: true, timeout: 5000]

def options(overrides) do
Keyword.merge(@defaults, overrides)
end

def build_context(host) do
SSHKit.context({
host.ip,
options(port: host.port,
user: host.user,
password: host.password,
timeout: 5000
)
})
overrides = [port: host.port, user: host.user, password: host.password]
SSHKit.context({host.ip, Keyword.merge(@defaults, overrides)})
end

defp stdio(output, type) do
Expand All @@ -33,42 +27,39 @@ defmodule SSHKitFunctionalTest do

@tag boot: 1
test "connects", %{hosts: [host]} do
[{:ok, output, 0}] = SSHKit.run(build_context(host), "whoami")

[{:ok, output, 0}] = SSHKit.run(build_context(host), "id -un")
name = String.trim(stdout(output))
assert name == host.user
end

@tag boot: 1
test "run", %{hosts: [host]} do
test "runs commands", %{hosts: [host]} do
context = build_context(host)

[{:ok, output, status}] = SSHKit.run(context, "pwd")
assert status == 0
output = stdout(output)
assert output == "/home/me\n"
assert stdout(output) == "/home/me\n"

[{:ok, output, status}] = SSHKit.run(context, "ls non-existing")
assert status == 1
output = stderr(output)
assert output =~ "ls: non-existing: No such file or directory"
assert stderr(output) =~ "ls: non-existing: No such file or directory"

[{:ok, output, status}] = SSHKit.run(context, "does-not-exist")
assert status == 127
output = stderr(output)
assert output =~ "'does-not-exist': No such file or directory"
assert stderr(output) =~ "'does-not-exist': No such file or directory"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

end

@tag boot: 1
test "env", %{hosts: [host]} do
[{:ok, output, status}] =
host
|> build_context
|> build_context()
|> SSHKit.env(%{"PATH" => "$HOME/.rbenv/shims:$PATH", "NODE_ENV" => "production"})
|> SSHKit.run("env")

assert status == 0
output = stdout(output)

assert status == 0
assert output =~ "NODE_ENV=production"
assert output =~ ~r/PATH=.*\/\.rbenv\/shims:/
end
Expand All @@ -77,14 +68,17 @@ defmodule SSHKitFunctionalTest do
test "umask", %{hosts: [host]} do
context =
host
|> build_context
|> build_context()
|> SSHKit.umask("077")
SSHKit.run(context, "mkdir my_dir")
SSHKit.run(context, "touch my_file")

[{:ok, _, 0}] = SSHKit.run(context, "mkdir my_dir")
[{:ok, _, 0}] = SSHKit.run(context, "touch my_file")

[{:ok, output, status}] = SSHKit.run(context, "ls -la")

assert status == 0
output = stdout(output)

assert status == 0
assert output =~ ~r/drwx--S---\s+2\s+me\s+me\s+4096.+my_dir/
assert output =~ ~r/-rw-------\s+1\s+me\s+me\s+0.+my_file/
end
Expand All @@ -97,23 +91,25 @@ defmodule SSHKitFunctionalTest do
|> SSHKit.path("/var/log")

[{:ok, output, status}] = SSHKit.run(context, "pwd")
assert status == 0
output = stdout(output)

assert status == 0
assert output == "/var/log\n"
end

@tag skip: true # it produces an error: "sudo not found" on stderr
@tag boot: 1
test "user", %{hosts: [host]} do
adduser(host, "despicable_me")
add_user_to_group(host, host.user, "passwordless-sudoers")

context =
host
|> build_context
|> build_context()
|> SSHKit.user("despicable_me")

[{:ok, output, status}] = SSHKit.run(context, "whoami")
[{:ok, output, status}] = SSHKit.run(context, "id -un")
output = stdout(output)

assert output == "despicable_me\n"
assert status == 0
end
Expand Down
11 changes: 10 additions & 1 deletion test/support/docker/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ FROM alpine:3.5
# Set up an Alpine Linux machine running an SSH server.
# Autogenerate missing host keys.

RUN apk add --no-cache openssh
RUN apk add --no-cache openssh sudo
RUN ssh-keygen -A

# Create the skeleton directory, used for creating new users.
Expand All @@ -14,6 +14,15 @@ RUN chmod 700 /etc/skel/.ssh
RUN touch /etc/skel/.ssh/authorized_keys
RUN chmod 600 /etc/skel/.ssh/authorized_keys

# Allow members of group "wheel" to execute any command.

RUN echo "%wheel ALL=(ALL) ALL" >> /etc/sudoers.d/wheel

# Allow passwordless sudo for users in the "danger" group.

RUN addgroup -S passwordless-sudoers
RUN echo "%passwordless-sudoers ALL=(ALL) NOPASSWD: ALL" >> /etc/sudoers.d/yolo

# Run SSH daemon and expose the standard SSH port.

EXPOSE 22
Expand Down