summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorlutter <lutter@980ebf18-57e1-0310-9a29-db15c13687c0>2006-08-26 06:29:59 +0000
committerlutter <lutter@980ebf18-57e1-0310-9a29-db15c13687c0>2006-08-26 06:29:59 +0000
commit55d3fb8360bff73711f3d4bf9286fb380a1b2d61 (patch)
tree353f82a430da1a59d39ca74bf8308bf6b6407b92
parent2540cdf2704eb62dfa760c334fcde105645ee007 (diff)
downloadpuppet-55d3fb8360bff73711f3d4bf9286fb380a1b2d61.tar.gz
puppet-55d3fb8360bff73711f3d4bf9286fb380a1b2d61.tar.xz
puppet-55d3fb8360bff73711f3d4bf9286fb380a1b2d61.zip
Support for %h and %H replacement in the path of fileserver modules.
git-svn-id: https://reductivelabs.com/svn/puppet/trunk@1490 980ebf18-57e1-0310-9a29-db15c13687c0
-rw-r--r--documentation/documentation/fsconfigref.page16
-rwxr-xr-xlib/puppet/server/fileserver.rb50
-rwxr-xr-xtest/server/fileserver.rb115
3 files changed, 176 insertions, 5 deletions
diff --git a/documentation/documentation/fsconfigref.page b/documentation/documentation/fsconfigref.page
index c7f962448..598676184 100644
--- a/documentation/documentation/fsconfigref.page
+++ b/documentation/documentation/fsconfigref.page
@@ -45,6 +45,22 @@ While the path is the only required option, the default security configuration
is to deny all access, so if no ``allow`` lines are specified, the module will
be configured but available to no one.
+The path can contain the patterns ``%h`` and ``%H``, which are dynamically
+replaced by the client's short name and its fully qualified domain name,
+both taken from the client's SSL certificate. This is useful in creating
+modules where files for each client are kept completely separately,
+e.g. for private ssh host keys. For example, with the configuration
+
+ [private]
+ path /data/private/%h
+ allow *
+
+the request for file ``/private/file.txt`` from client
+``client1.example.com`` will look for a file
+``/data/private/client1/file.txt``, while the same request from
+``client2.example.com`` will try to retrieve the file
+``/data/private/client1/file.txt`` on the fileserver.
+
# Security
There are two aspects to securing the Puppet file server: Allowing specific
diff --git a/lib/puppet/server/fileserver.rb b/lib/puppet/server/fileserver.rb
index 7e0e49105..983e39271 100755
--- a/lib/puppet/server/fileserver.rb
+++ b/lib/puppet/server/fileserver.rb
@@ -1,6 +1,7 @@
require 'puppet'
require 'webrick/httpstatus'
require 'cgi'
+require 'delegate'
module Puppet
class FileServerError < Puppet::Error; end
@@ -40,7 +41,7 @@ class Server
raise Puppet::FileServerError, "Cannot currently copy links"
end
- mount, path = splitpath(file)
+ mount, path = splitpath(file, client)
authcheck(file, mount, client, clientip)
@@ -133,7 +134,7 @@ class Server
# List a specific directory's contents.
def list(dir, links = :ignore, recurse = false, ignore = false, client = nil, clientip = nil)
readconfig
- mount, path = splitpath(dir)
+ mount, path = splitpath(dir, client)
authcheck(dir, mount, client, clientip)
@@ -297,7 +298,7 @@ class Server
def retrieve(file, links = :ignore, client = nil, clientip = nil)
readconfig
links = links.intern if links.is_a? String
- mount, path = splitpath(file)
+ mount, path = splitpath(file, client)
authcheck(file, mount, client, clientip)
@@ -390,7 +391,7 @@ class Server
end
# Split the path into the separate mount point and path.
- def splitpath(dir)
+ def splitpath(dir, client)
# the dir is based on one of the mounts
# so first retrieve the mount path
mount = nil
@@ -410,6 +411,7 @@ class Server
# And now replace the name with the actual object.
mount = @mounts[mount]
+ mount = SubstMount.new(mount, client) unless client.nil?
else
raise FileServerError, "Fileserver error: Invalid path '%s'" % dir
end
@@ -529,7 +531,13 @@ class Server
# Set the path.
def path=(path)
unless FileTest.exists?(path)
- raise FileServerError, "%s does not exist" % path
+ map = { "h" => "\000", "H" => "\000" }
+ # FIXME: What should we do if there are replacement
+ # patterns in path ? Replace with '*' and glob ?
+ # But that could turn out to be _very_ expensive
+ unless Mount::subst(path, map).index("\000")
+ raise FileServerError, "%s does not exist" % path
+ end
end
@path = path
end
@@ -553,6 +561,38 @@ class Server
raise FileServerError, "No path specified"
end
end
+
+ # Replace occurences of %C in PATH with entries from MAP and
+ # return a new string. Literal percent signs can be included as
+ # '%%', and a replacement is only done when C is a key in MAP
+ def self.subst(path, map)
+ path.gsub(/%./) do |v|
+ if v == "%%"
+ "%"
+ elsif ! map.key?(v[1,1])
+ v
+ else
+ map[v[1,1]]
+ end
+ end
+ end
+
+ end
+
+ # A mount that does substitutions in the path on the fly
+ class SubstMount < DelegateClass(Mount)
+ def initialize(mount, client)
+ @mount = mount
+ super(@mount)
+ @map = {
+ "h" => client.sub(/\..*$/, ""),
+ "H" => client
+ }
+ end
+
+ def path
+ Mount::subst(@mount.path, @map)
+ end
end
end
end
diff --git a/test/server/fileserver.rb b/test/server/fileserver.rb
index c3b9d3bb7..ef191b304 100755
--- a/test/server/fileserver.rb
+++ b/test/server/fileserver.rb
@@ -749,6 +749,121 @@ class TestFileServer < Test::Unit::TestCase
assert(v != "", "%s has no value" % p)
}
end
+
+ # Test that substitution patterns in the path are exapanded
+ # properly
+ def test_host_specific
+ client1 = "client1.example.com"
+ client2 = "client2.example.com"
+ ip = "127.0.0.1"
+
+ # Setup a directory hierarchy for the tests
+ fsdir = File.join(tmpdir(), "host-specific")
+ @@tmpfiles << fsdir
+ hostdir = File.join(fsdir, "host")
+ fqdndir = File.join(fsdir, "fqdn")
+ client1_hostdir = File.join(hostdir, "client1")
+ client2_fqdndir = File.join(fqdndir, client2)
+ [fsdir, hostdir, fqdndir,
+ client1_hostdir, client2_fqdndir].each { |d| Dir.mkdir(d) }
+
+ [client1_hostdir, client2_fqdndir].each do |d|
+ File.open(File.join(d, "file.txt"), "w") { |f| f.puts d }
+ end
+
+ conffile = tempfile()
+ File.open(conffile, "w") do |f|
+ f.print("
+[host]
+path #{hostdir}/%h
+allow *
+[fqdn]
+path #{fqdndir}/%H
+allow *
+")
+ end
+
+ server = nil
+ assert_nothing_raised {
+ server = Puppet::Server::FileServer.new(
+ :Local => true,
+ :Config => conffile
+ )
+ }
+
+ # check that list returns the correct thing for the two clients
+ list = nil
+ sfile = "/host/file.txt"
+ assert_nothing_raised {
+ list = server.list(sfile, :ignore, true, false, client1, ip)
+ }
+ assert_equal("/\tfile", list)
+ assert_nothing_raised {
+ list = server.list(sfile, :ignore, true, false, client2, ip)
+ }
+ assert_equal("", list)
+
+ sfile = "/fqdn/file.txt"
+ assert_nothing_raised {
+ list = server.list(sfile, :ignore, true, false, client1, ip)
+ }
+ assert_equal("", list)
+ assert_nothing_raised {
+ list = server.list(sfile, :ignore, true, false, client2, ip)
+ }
+ assert_equal("/\tfile", list)
+
+ # check describe
+ sfile = "/host/file.txt"
+ assert_nothing_raised {
+ list = server.describe(sfile, :ignore, client1, ip).split("\t")
+ }
+ assert_equal(5, list.size)
+ assert_equal("file", list[1])
+ assert_equal("{md5}95b0dea1b0c692b7563120afb4056e7f", list[4])
+
+ assert_nothing_raised {
+ list = server.describe(sfile, :ignore, client2, ip).split("\t")
+ }
+ assert_equal([], list)
+
+ sfile = "/fqdn/file.txt"
+ assert_nothing_raised {
+ list = server.describe(sfile, :ignore, client1, ip).split("\t")
+ }
+ assert_equal([], list)
+
+ assert_nothing_raised {
+ list = server.describe(sfile, :ignore, client2, ip).split("\t")
+ }
+ assert_equal(5, list.size)
+ assert_equal("file", list[1])
+ assert_equal("{md5}4dcf36004229f400c5821a3faf0f2300", list[4])
+
+ # Check retrieve
+ sfile = "/host/file.txt"
+ assert_nothing_raised {
+ list = server.retrieve(sfile, :ignore, client1, ip).chomp
+ }
+ assert_equal(client1_hostdir, list)
+
+ assert_nothing_raised {
+ list = server.retrieve(sfile, :ignore, client2, ip).chomp
+ }
+ assert_equal("", list)
+
+ sfile = "/fqdn/file.txt"
+ assert_nothing_raised {
+ list = server.retrieve(sfile, :ignore, client1, ip).chomp
+ }
+ assert_equal("", list)
+
+ assert_nothing_raised {
+ list = server.retrieve(sfile, :ignore, client2, ip).chomp
+ }
+ assert_equal(client2_fqdndir, list)
+ end
+
end
# $Id$