Skip to content

Commit

Permalink
Need the ssh plugin
Browse files Browse the repository at this point in the history
  • Loading branch information
edavis10 committed Mar 20, 2010
1 parent 30c81c7 commit 0a6b576
Show file tree
Hide file tree
Showing 7 changed files with 386 additions and 0 deletions.
2 changes: 2 additions & 0 deletions app/manifests/application_manifest.rb
Expand Up @@ -203,6 +203,8 @@ def sqlite_stack

plugin :iptables
recipe :iptables
recipe :ssh


# Add your application's custom requirements here
def application_packages
Expand Down
88 changes: 88 additions & 0 deletions vendor/plugins/moonshine_ssh/README.rdoc
@@ -0,0 +1,88 @@
= Moonshine_SSH

== A plugin for Moonshine[http://github.com/railsmachine/moonshine]

This plugin provides a few security improvements for your default SSH
configuration. It also gives you the ability to easily customize your
settings. Browse through the sshd_config in the <tt>templates/</tt> directory
to see the available settings.

The new configuration file is tested before it's used, so there's less
chance that you'll accidentally lock yourself out.

=== Instructions

* <tt>script/plugin install git://github.com/railsmachine/moonshine_ssh.git</tt>
* Edit moonshine.yml to customize plugin settings if desired:
:ssh
:port: 9022
:allow_users:
- rob
- rails
* Include the plugin and recipe you in your manifest:
plugin :ssh
recipe :ssh

=== SFTP-only users

OpenSSH supports chrooting users natively. You can create users who will be chrooted
and only allowed to use SFTP (no console access) by adding the following to your
moonshine.yml:

:ssh:
:sftponly: true

Then add this to your manifest:

plugin :ssh
recipe :ssh

This creates a user called sftponly with a randomized password. To allow access, you can manag authorized_keys through your manifest:

file '/home/sftponly/home/sftponly/.ssh/authorized_keys',
:ensure => :present,
:content => YOUR_SSH_PUBKEYS

Once connected via sftp, the user will be chrooted to /home/sftponly where they will only see a
'home' directory. The user can upload files to /home/sftponly. For a normal user, the uploaded
files will be located at /home/sftponly/home/sftponly.

==== Advanced

For a more complicated example, we'll consider a user called 'rob' who needs to upload
files into a directory under the Rails application's shared/ directory. Since he will
be chrooted, he can't have direct access to the directory. Also, the directory is owned
by the rails group, so he'll need to be a member of that. Finally, we don't want to worry
about public keys, so he'll need a password.

In your moonshine.yml:

:ssh:
:sftponly:
:users:
:rob:
:groups: rails
:password: sooper_sekrit

In your manifest:

plugin :ssh
recipe :ssh
def mount_assets
file '/home/rob/home/rob/assets',
:ensure => :directory,
:owner => 'rob',
:require => file('/home/rob/home/rob')

mount '/home/rob/home/rob/assets',
:ensure => :mounted,
:device => "#{configuration[:deploy_to]}/shared/assets/",
:options => 'bind',
:fstype => :none,
:atboot => true,
:remounts => true,
:require => file('/home/rob/home/rob/assets')
end
recipe :mount_assets

Then deploy, and you're done! The user's /home/rob/assets directory is now actually the shared assets directory. Anything uploaded there will be available to the application automatically.
89 changes: 89 additions & 0 deletions vendor/plugins/moonshine_ssh/lib/ssh.rb
@@ -0,0 +1,89 @@
module SSH

# Ensures the SSH server is installed, running, and configured
# per specifications. Use <tt>configure</tt> to change defaults.
# The available options can be gathered by perusing the sshd_config
# template.
#
# configure(:ssh => {:permit_root_login => 'yes', :port => 9022})
#
def ssh(options = {})

package 'ssh', :ensure => :installed
service 'ssh', :enable => true, :ensure => :running

if options[:sftponly]
options[:subsystem] = {:sftp => 'internal-sftp'}
sftponly(options[:sftponly])
end

file '/etc/ssh/sshd_config.new',
:mode => '644',
:content => template(File.join(File.dirname(__FILE__), '..', 'templates', 'sshd_config'), binding),
:require => package('ssh'),
:notify => exec('update_sshd_config')

exec 'cp /etc/ssh/sshd_config.new /etc/ssh/sshd_config',
:alias => 'update_sshd_config',
:onlyif => '/usr/sbin/sshd -t -f /etc/ssh/sshd_config.new',
:refreshonly => true,
:require => file('/etc/ssh/sshd_config.new'),
:notify => service('ssh')

end

private

# Sets up users and directories for chrooted, sftp-only access
# By default, the chroot is /home/USERNAME and the user's home
# will be inside that, at /home/USERNAME/home/USERNAME
def sftponly(options)

group 'sftponly', :ensure => :present

exec "fake shell",
:command => "echo /bin/false >> /etc/shells",
:onlyif => "test -z `grep /bin/false /etc/shells`"

(options[:users]||'sftponly').to_a.each do |user,hash|
user = user.to_s

chroot = options[:chroot_directory] || "/home/#{user}"
homedir = chroot + ( hash[:home] || "/home/#{user}" )

parent_directories homedir, :owner => 'root', :mode => '755'
file homedir,
:ensure => :directory,
:owner => user,
:group => user,
:require => user(user)

user user,
:ensure => :present,
:home => "/home/#{user}/home/#{user}",
:shell => "/bin/false",
:groups => (['sftponly'] + hash[:groups].to_a).uniq,
:require => [group('sftponly'),exec('fake shell')],
:notify => exec("#{user} password")

password = hash[:password] || rand_pass(6)
exec "#{user} password",
:command => "echo #{user}:#{password} | chpasswd",
:refreshonly => true

end
end

def parent_directories(path,options)
options.merge!({:ensure => :directory})
while path != "/"
path = File.split(path)[0]
file path, options
end
end

def rand_pass(len)
Array.new(len/2) { rand(256) }.pack('C*').unpack('H*').first
end

end
3 changes: 3 additions & 0 deletions vendor/plugins/moonshine_ssh/moonshine/init.rb
@@ -0,0 +1,3 @@
require "#{File.dirname(__FILE__)}/../lib/ssh.rb"

include SSH
8 changes: 8 additions & 0 deletions vendor/plugins/moonshine_ssh/spec/spec_helper.rb
@@ -0,0 +1,8 @@
require 'rubygems'
ENV['RAILS_ENV'] = 'test'
ENV['RAILS_ROOT'] ||= File.dirname(__FILE__) + '/../../../..'

require File.join(File.dirname(__FILE__), '..', '..', 'moonshine', 'lib', 'moonshine.rb')
require File.join(File.dirname(__FILE__), '..', 'lib', 'ssh.rb')

require 'shadow_puppet/test'
86 changes: 86 additions & 0 deletions vendor/plugins/moonshine_ssh/spec/ssh_spec.rb
@@ -0,0 +1,86 @@
require File.join(File.dirname(__FILE__), 'spec_helper.rb')

class SSHManifest < Moonshine::Manifest
plugin :ssh
end

describe SSH do

before do
@manifest = SSHManifest.new
end


it "should be executable" do
@manifest.should be_executable
end

it "should load the template file" do
File.should_receive(:read).with(File.expand_path(File.join(File.dirname(__FILE__), '..', 'templates', 'sshd_config'))).and_return "SSH CONFIG"
@manifest.ssh
end

it "should set default values" do
@manifest.ssh
@manifest.files.should include("/etc/ssh/sshd_config.new")
sshd_config = @manifest.files["/etc/ssh/sshd_config.new"].content
sshd_config.should match /Port 22/
sshd_config.should match /PermitRootLogin no/
end

it "should check the configuration file before updating" do
@manifest.ssh
@manifest.execs.should include("cp /etc/ssh/sshd_config.new /etc/ssh/sshd_config")
@manifest.execs["cp /etc/ssh/sshd_config.new /etc/ssh/sshd_config"].onlyif.should match /sshd -t/
end

it "should allow customization" do
@manifest.ssh( :port => 9022 )
@manifest.files["/etc/ssh/sshd_config.new"].content.should match /Port 9022/
end

describe "configured for sftponly" do

before do
@manifest.ssh(:sftponly => {
:users => {
:rob => {
:password => 'sekrit',
:groups => 'rails'
}
}
})
end

it "should create the sftponly group" do
@manifest.groups.should include('sftponly')
end

it "should add the user" do
@manifest.users.should include('rob')
@manifest.execs['rob password'].command.should == "echo rob:sekrit | chpasswd"
end

it "should add user to sftponly and extra groups if requested" do
@manifest.users['rob'].groups.should == ['sftponly','rails']
end

it "should create the home directory" do
@manifest.files.should include('/home/rob/home/rob')
@manifest.files['/home/rob/home/rob'].owner.should == 'rob'
@manifest.files['/home/rob/home'].owner.should == 'root'
@manifest.files['/home/rob'].owner.should == 'root'
end

it "should set the sftp subsystem" do
@manifest.files['/etc/ssh/sshd_config.new'].content.should match /Subsystem sftp internal-sftp/
end

it "should add a group matcher to the sshd config" do
@manifest.files['/etc/ssh/sshd_config.new'].content.should match /Match Group sftponly/
@manifest.files['/etc/ssh/sshd_config.new'].content.should match /ChrootDirectory \/home\/%u/
end

end

end

0 comments on commit 0a6b576

Please sign in to comment.