First commit for sudoers module.
authorroot <root@collector.localdomain>
Fri, 12 Mar 2010 02:23:27 +0000 (20:23 -0600)
committerroot <root@collector.localdomain>
Fri, 12 Mar 2010 02:23:27 +0000 (20:23 -0600)
This doesnt work... yet.

NOTES [new file with mode: 0644]
README [new file with mode: 0644]
files/sudoers [new file with mode: 0644]
lib/puppet/provider/sudoers/parsed.rb [new file with mode: 0644]
lib/puppet/type/sudoers.rb [new file with mode: 0644]
manifests/init.pp [new file with mode: 0644]
manifests/tests/sudo.pp [new file with mode: 0644]
tests/sudo.pp [new file with mode: 0644]
tests/sudoers-delete.pp [new file with mode: 0644]
tests/sudoers.pp [new file with mode: 0644]

diff --git a/NOTES b/NOTES
new file mode 100644 (file)
index 0000000..77a33c7
--- /dev/null
+++ b/NOTES
@@ -0,0 +1,5 @@
+only properties can be accessed by the parsedfile provider, this sucks.
+
+what is the difference between self.function and function? why must I use self for everything?
+
+I will not support multiline
diff --git a/README b/README
new file mode 100644 (file)
index 0000000..955a020
--- /dev/null
+++ b/README
@@ -0,0 +1,5 @@
+This is a type and a provider for managing sudoers file.
+
+It doesnt work yet, but it will. still trying to learn how parsedfile works and how much I can push its boundaries.
+
+It will update this README when its ready for consumption.
diff --git a/files/sudoers b/files/sudoers
new file mode 100644 (file)
index 0000000..bb03635
--- /dev/null
@@ -0,0 +1,27 @@
+#
+# DO NOT EDIT! This file is managed by Puppet so any changes you make locally will be lost.
+#
+## Command Aliases
+## These are groups of related commands...
+
+## Installation and management of software
+Cmnd_Alias SOFTWARE = /bin/rpm, /usr/bin/up2date, /usr/bin/yum, /sbin/service httpd *, /sbin/service interchange *, /sbin/service puppet *, /sbin/service mvc_cached *
+
+#
+# This flag needs to be off for ControlTier to work:
+#
+# Defaults    requiretty
+Defaults    env_reset
+Defaults    env_keep = "COLORS DISPLAY HOSTNAME HISTSIZE INPUTRC KDEDIR \
+                        LS_COLORS MAIL PS1 PS2 QTDIR USERNAME \
+                        LANG LC_ADDRESS LC_CTYPE LC_COLLATE LC_IDENTIFICATION \
+                        LC_MEASUREMENT LC_MESSAGES LC_MONETARY LC_NAME LC_NUMERIC \
+                        LC_PAPER LC_TELEPHONE LC_TIME LC_ALL LANGUAGE LINGUAS \
+                        _XKB_CHARSET XAUTHORITY"
+#
+# User and group based sudo permissions
+#
+
+root      ALL=(ALL)    ALL
+%wheel ALL=(ALL)       ALL
+%interch       ALL=NOPASSWD:  SOFTWARE         
diff --git a/lib/puppet/provider/sudoers/parsed.rb b/lib/puppet/provider/sudoers/parsed.rb
new file mode 100644 (file)
index 0000000..520eef5
--- /dev/null
@@ -0,0 +1,123 @@
+require 'puppet/provider/parsedfile'
+sudoers = "/etc/sudoers"
+
+#
+# crontab does the same thing, it uses a comment to specify uniqueness
+#
+Puppet::Type.type(:sudoers).provide(
+  :parsed, 
+  :parent => Puppet::Provider::ParsedFile, 
+  :default_target => '/etc/sudoers', 
+  # what the heck does this mean?
+  :filetype => :flat
+) do
+  desc "The sudoers provider that uses the ParsedFile class"
+
+  commands :visudo => 'visudo'
+
+  # this is just copied from hosts
+  text_line :comment, :match => %r{^#}, :post_parse => proc { |record|
+    # we determine the name from the comment above user spec lines
+    if record[:line] =~ /Puppet Name: (.+)\s*$/
+# ok, we set record name, but how is this applied to the next line?
+      record[:name] = $1
+    end
+  } 
+
+  text_line :blank, :match => /^\s*$/;
+
+  # ignore for now, I will support this line later
+  text_line :defaults, :match => /^Defaults/
+
+# I need to parse the , delim list into an array
+# I am pretty sure that I would do this with a block statement
+
+#
+# it seems like I have to put type here for it to be accessible to 
+# to_line
+#
+
+
+# not bothering to specify match or fields, I will determine all of this
+# in post_parse
+
+# parse everything else?
+  record_line :parsed, :fields => %w{line},
+    :match => /(.*)/,
+    :post_parse => proc { |hash|
+      puts "\npost_parse"
+      puts hash[:line]
+      if (hash[:line] =~ /^\s*(User_Alias|Runas_Alias|Host_Alias|Cmnd_Alias)\s+(\S+)\s*=\s*(.+)$/)
+        hash[:sudo_alias] = $1
+        hash[:name] = $2
+        hash[:items] = $3
+        hash[:items]=hash[:items].gsub(/\s/, '').split(',')
+        #puts hash.to_yaml
+      elsif (hash[:line] =~ /^(.*)?=(.*)$/)
+        # should name already be set when get get here?
+        hash = parse_user_spec($1, $2)
+      else 
+        raise ArgumentError, "unexpected line #{hash[:line]}" 
+      end
+    }
+
+  def self.parse_user_spec(lhs, rhs) 
+    lhs_array = lhs.split(',')  
+  end 
+
+  def self.to_line(hash) 
+    puts "\nEntering self.to_line for #{hash[:name]}"
+    puts hash.to_yaml
+    # dynamically call a function based on the value of hash[:type]
+    if(hash[:type] == 'alias')
+      self.alias_to_line(hash) 
+    elsif(hash[:type] == 'spec')
+      spec_to_line(hash)
+    elsif(hash[:type] == 'default')
+      default_to_line(hash)
+    end
+  end
+
+  def self.spec_to_line(hash)
+    "spec"
+  end
+
+  def self.alias_to_line(hash) 
+    # do I need to ensure that the required elements are here?
+    # shouldnt the type do that? check file, its similar
+    # since different attributes make sense based on ensure value (dir/file/symlink)
+    items=hash[:items]
+    items=items.join(',') if items.class == Array
+    "#{hash[:sudo_alias]} #{hash[:name]}=#{items}"
+  end
+
+  def default_to_line(hash)
+    "default"
+  end
+
+  def self.flush(record)
+#  a little pre-flush host visudo action
+#
+    super(record)
+  end
+
+# lets assume that runas is always there and lets not deal with options yet
+#  record_line :spec, :fields => %w{users hosts runas specs type},
+#    :match => %r{(\S+)\s+(\S+)=(\(\S+\))\s+(.+)},
+#    :post_parse => proc { |hash|
+#      puts 'spec'
+#    } 
+
+# I dont know if I can properly support multiple commands
+# because they are composite, one command, one runas, multiple tags
+#  record_line :spec, :fields => %w{user host runas tags commands name},
+#    :match => %r{^\s*(\S+)\s+(\S+)\s*=\s*(\(\S+\))?(.+)$},
+#    :optional => %w{runas tags}
+
+# I need to override flush to validate sudoers
+#
+#
+#
+end
diff --git a/lib/puppet/type/sudoers.rb b/lib/puppet/type/sudoers.rb
new file mode 100644 (file)
index 0000000..6c94269
--- /dev/null
@@ -0,0 +1,94 @@
+Puppet::Type.newtype(:sudoers) do
+  @doc = "Manage the contents of /etc/sudoers
+
+there are two types of things here:
+
+  sudoer{'NAME':
+    ensure => (absent|present)
+    type => (alias|spec) # required??
+    alias => (User_alias|Runas_alias|Host_alias|Cmnd_alias),
+    items => [] # this is only for aliases
+    user_list => []
+    host_list => []
+    operator_list => []
+    # NOPASSWD, PASSWD, NOEXEC, EXEC, SETENV and NOSETENV
+    tag_list => []
+    command_list => []
+  }
+
+  alias NAME - starts with CAP ([A-Z]([A-Z][0-9]_)*)
+
+aliases, user specifications
+   User_alias
+   Runas_alias
+   Host_alias
+   Cmnd_alias
+
+alias spec:
+
+ Alias_Type NAME = item1, item2, item3 : NAME = item4, item5
+
+
+order matters!!
+
+
+            "
+  # we can either remove or add lines
+  # they should also be purgable?(whats the namesvar for specs?)
+  ensurable
+
+  newparam(:name) do
+    desc "Either the name of the alias to create 
+          or for user specification, a random string in a comment that serves as a place holder (kind of ugly, but its true)
+    "
+                              
+    isnamevar
+  end
+
+#
+# this has to be a property to be found by parsedfile, but 
+# its really a parameter
+
+  newproperty(:type) do
+    desc "Either determines which type of sudo configuration line is
+          is being managed. Either user_spec or alias"
+  end
+
+  newproperty(:sudo_alias) do
+    desc "Types of alias."
+  end
+
+  newproperty(:items, :array_matching => :all) do
+    desc "list of items applied to an alias"
+  end
+
+  newproperty(:target) do
+    desc "Location of the shells file"
+
+    defaultto do
+      if
+        @resource.class.defaultprovider.ancestors.include?(Puppet::Provider::ParsedFile)
+        @resource.class.defaultprovider.default_target
+      else
+        nil
+      end
+    end
+  end
+
+  newproperty(:users, :array_matching => :all) do
+    desc "list of users for user spec"
+  end
+
+  newproperty(:hosts, :array_matching => :all) do
+    desc "list of hosts for user spec"
+  end
+
+  newproperty(:runas, :array_matching => :all) do
+    desc "user to run commands as"
+  end
+  newproperty(:commands, :array_matching => :all) do
+    desc "commands to run"
+  end
+
+end
diff --git a/manifests/init.pp b/manifests/init.pp
new file mode 100644 (file)
index 0000000..ba052c2
--- /dev/null
@@ -0,0 +1,10 @@
+# sudo class
+class sudo {
+  package { "sudo": ensure => present }
+  file{"/etc/sudoers": 
+    owner => "root", 
+    group => "root", 
+    mode => "400",
+    source => ["puppet:///modules/site-files/sudoers", "puppet:///modules/sudo/sudoers" ]
+  }
+}
diff --git a/manifests/tests/sudo.pp b/manifests/tests/sudo.pp
new file mode 100644 (file)
index 0000000..a27b70b
--- /dev/null
@@ -0,0 +1 @@
+include sudo
diff --git a/tests/sudo.pp b/tests/sudo.pp
new file mode 100644 (file)
index 0000000..a27b70b
--- /dev/null
@@ -0,0 +1 @@
+include sudo
diff --git a/tests/sudoers-delete.pp b/tests/sudoers-delete.pp
new file mode 100644 (file)
index 0000000..3085a63
--- /dev/null
@@ -0,0 +1,21 @@
+sudoers{'blah1':
+  target => '/tmp/sudoers',
+  ensure => absent,
+  alias => 'Cmnd_Alias',
+  items => ['blah4', 'blah2'],
+  linetype => 'alias',
+}
+#sudoers{'blah2':
+#  target => '/tmp/sudoers',
+##  ensure => present,
+#  alias => 'Host_Alias',
+#  items => ['blah2', 'blah3'],
+#  type => 'alias',
+#}
+##sudoers{'blah3':
+#  target => '/tmp/sudoers',
+##  ensure => present,
+#  users => 'dan',
+#  hosts => 'localhost',
+#  type => 'spec',
+#}
diff --git a/tests/sudoers.pp b/tests/sudoers.pp
new file mode 100644 (file)
index 0000000..ee829d1
--- /dev/null
@@ -0,0 +1,21 @@
+sudoers{'blah1':
+  #target => '/tmp/sudoers',
+  ensure => present,
+  sudo_alias => 'Cmnd_Alias',
+  items => ['blah4', 'blah2'],
+  type => 'alias',
+}
+sudoers{'blah2':
+  #target => '/tmp/sudoers',
+  ensure => present,
+  sudo_alias => 'Host_Alias',
+  items => ['blah2', 'blah3'],
+  type => 'alias',
+}
+#sudoers{'blah3':
+#  target => '/tmp/sudoers',
+#  ensure => present,
+#  users => 'dan',
+#  hosts => 'localhost',
+#  type => 'spec',
+#}