Background / The Problem
Because of security configurations that I absolutely CANNOT change, I must ssh onto all of my boxes a federated user, myuser
.
EVERYTHING on my box that I need to deploy to is owned by deployuser
and I absolutely CANNOT change this.
myuser
can assume user deployuser
and has all sudo privileges.
Ultimately, I want my Capistrano deployments to function as if I ssh'd in as deployuser
from the get-go, so every single command is run as deployuser
and from the /home/deployuser
.
I can use ANY version of Capistrano I want, but right now I'm trying to do it with Capistrano 3.
The test setup
I created a simple Capistrano task to test whether I can perform an action on a file I know is owned by deployuser
.
desc "Check that we can access everything"
task :check_permissions do
on roles(:all) do |host|
execute("whoami")
execute("cp /tmp/file-owned-by-deploy-user /tmp/test-file")
end
end
EDIT: The test above actually doesn't work as one might expect. I believe it's because when you manually declare something like execute('command')
, it overrides any configuration that says "do this thing as X user." A better test is to just run your cap environment deploy
task and see what fails.
What I've tried
sshkit-backends-netssh_global as per their docs
The first thing I tried was sshkit-backends-netssh_global, which in their documentation says they handle exactly this use case.
I added this to my Capfile, then tried adding it to deploy.rb
and then even above my task definition just in case:
require 'sshkit/backends/netssh_global'
SSHKit::Backend::NetsshGlobal.configure do |config|
config.owner = 'deployuser'
config.directory = '/home/deployuser'
end
This has 0 effect whatsoever. No new error messages, just the same Permission Denied, and whoami returns myuser.
Setting :sshkit_backend in Capistrano 3
Second, I tried to set :ssh_backend
from Capistrano, which I saw in this issue for sshkit-backends-netssh_global:
require 'sshkit/backends/netssh_global'
set :sshkit_backend, -> {
SSHKit::Backend::NetsshGlobal.tap do |backend|
backend.configure do |config|
config.owner = 'deployuser'
config.directory = '/home/deployuser'
end
end
}
This reveals that sshkit-backends-netssh_global
is no longer compatible with the latest version of Capistrano. It's using a deprecated method checkout
. So damn:
NoMethodError: undefined method `checkout' for #<SSHKit::Backend::ConnectionPool:0x007fae8340e3c0>
I tried downgrading to a lower version of Capistrano, and while that fixed the deprecation issue, it did not magically make these configuration settings take effect. I didn't dig in too deep though.
Using as 'deployuser' from SSHKit to test
I kinda feel like I'm going crazy here though because, when I try to wrap my task in as 'user' as per the SSHKit documentation, whoami
gets run as deployuser
while the cp command still returns Permission Denied.
So maybe my test script isn't doing what I think it's doing. Still, this command can be copy/pasted onto the server and run by appending sudo -u deployuser
.
In my edit above, I say it's because I think doing an execute('command')
is a little different and doesn't take your config into account - it just executes the thing. I think. So this may still work for other types of tasks (though not internal Capistrano tasks of course).
desc "Check that we can access everything"
task :check_permissions do
on roles(:all) do
as 'deployuser' do
# this returns deployuser
execute("whoami")
# this throws permission denied
execute("cp /tmp/file-owned-by-deploy-user /tmp/test-file")
end
end
end
Using SSHKit.config.command_map
Another tactic I tried, which results in the same as the above example is:
SSHKit.config.command_map = Hash.new do |hash, command|
hash[command] = "sudo -u deployuser #{command}"
end
whoami
returns deployuser
, but the cp
command still returns Permission Denied. Again, this might be because execute
is not actually what I need to be testing here.
Related but unsolved Stackoverflow questions
- This one gives a solution for Capistrano 2 I tried downgrading and using it, but that didn't work for all commands, and there was no answer for Capistrano 3
- This one is generally unanswered in the way I need
- Same here
Some related issues
Has anyone ever gotten this to work? Any ideas?
EDIT: Things that are kind of working (ish)
I've been working on this all day, and here's what I've got so far...and it's gross, but I got a basic deploy working.
First, while you for the most part want to append sudo -u deployuser
to commands, that's not true for ALL commands. I had a couple others in here originally, but I got it down to just chmod
that can't be run as my deployuser
SSHKit.config.command_map = Hash.new do |hash, command|
skipped_commands = [:chmod]
if skipped_commands.include?(command)
hash[command] = command
else
hash[command] = "sudo -u deployuser #{command}"
end
end
Ok so THEN I realized that this doesn't work for all Capistrano cases, because of these >>
being inserted in a couple of rake tasks on the Capistrano side.
So I wrote a monkey patch for the two tasks:
namespace :deploy do
# MONKEY PATCH OH NO
# =======================
# you actually have to clear the old rake task
# for yours to overwrite Capistrano's
Rake::Task['deploy:set_current_revision'].clear
desc "IT'S MINE"
task :set_current_revision do
on release_roles(:all) do
within release_path do
execute :echo, "\"$(sudo -u deployuser git rev-parse HEAD)\" | sudo -u deployuser tee REVISION"
end
end
end
Rake::Task['deploy:log_revision'].clear
desc "Log details of the depl
task :log_revision do
on release_roles(:all) do
within releases_path do
execute :echo, %Q{"#{revision_log_message}" | sudo -u deployuser tee #{revision_log}}
end
end
end
end
Investigation is ongoing (I still have to implement the rest of the deploy), but a simple cap test_environment deploy
is finishing now after these changes.
Also, here's my bundle for those who find this in the future:
Gems included by the bundle:
* activesupport (4.2.10)
* airbrussh (1.3.0)
* aws-partitions (1.56.0)
* aws-sdk-core (3.14.0)
* aws-sdk-ec2 (1.25.0)
* aws-sigv4 (1.0.2)
* bundler (1.16.0)
* capistrano (3.10.1)
* capistrano-bundler (1.3.0)
* capistrano-rails (1.3.1)
* coderay (1.1.2)
* concurrent-ruby (1.0.5)
* dogapi (1.28.0)
* faraday (0.14.0)
* i18n (0.9.3)
* jmespath (1.3.1)
* json (1.8.6)
* method_source (0.9.0)
* minitest (5.11.1)
* multi_json (1.13.1)
* multipart-post (2.0.0)
* net-scp (1.2.1)
* net-ssh (4.2.0)
* pry (0.11.3)
* rake (12.3.0)
* slackbot (0.0.2)
* sshkit (1.15.1)
* sshkit-backends-netssh_global (0.1.1)
* thread_safe (0.3.6)
* tzinfo (1.2.4)
Edit Again!
The author of the original plugin posted this patch yesterday which works for me in Capistrano 3.10 without any monkey patches.