summaryrefslogtreecommitdiff
path: root/lib/puppet/type/sudoers.rb
blob: d62ae0d0e9281285f83240c03699e99da69cd25b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
Puppet::Type.newtype(:sudoers) do
  @doc = "Manage the contents of /etc/sudoers

Author:: Dan Bode (dan@reductivelabs.com)
Copyright:: BLAH!!
License:: GPL3

= Summary

The sudoers type supports managing individual lines from the sudoers file.

Supports present/absent.

supports purging.

= Record Types

There are 3 types of records that are supported:

== Aliases:
 
Manages an alias line of a sudoers file.

Example:
 
sudoers{'ALIAS_NAME':
  ensure => present,
  sudo_alias => 'Cmnd',
  items => ['/bin/true', '/usr/bin/su - bob'],
}

creates the following line:

Cmnd_Alias ALIAS_NAME=/bin/true,/usr/bin/su - bob

== User Specification

sudoers line that specifies how users can run commands.

This there is no way to clearly determine uniqueness, a comment line is added above user spec lines that contains the namevar.

Example:

sudoers{'NAME':
  ensure => present,
  users => ['dan1', 'dan2'],
  hosts => 'ALL',
  commands => [
    '(root) /usr/bin/su - easapp',
    '(easapp)/usr/local/eas-ts/bin/appctl',
  ],
}

creates the following line:  

#Puppet NAMEVAR NAME
dan1,dan2 ALL=(root) /usr/bin/su - easapp,(easapp)/usr/local/eas-ts/bin/appctl

Defaults:

the default name is used to determine uniqueness.

sudoers{'Defaults@host':
  parameters => ['x=y', 'one=1', 'two=2'],
}

Defaults@host x=y,one=1,two=2

== Notes:

- parsing of multi-line sudoers records is not currently supported.
- ordering only applies when records are created.

            "
  # support absent and present (also purge -> true)
  ensurable

  newparam(:name) do
    desc "Either the name of the alias, default, or arbitrary unique string for user specifications"
    isnamevar
    munge do |value|
      #puts "params \n#{resource.original_parameters.to_yaml}\n"
      value
    end
    validate do |name| 
      if name =~ /^fake_namevar_\d+/
        raise Puppet::Error, "cannot use reserved namevar #{name}"
      end
    end
  end


  #
  # I changed this to be required. this will allow me to 
  # do more param checking based on type.
  #
  newproperty(:type) do
    desc "optional parameter used to determine what the record type is"
    # why isnt this working?
    validate do |my_type|
      unless my_type =~ /(default|alias|user_spec)/
        raise Puppet::Error, "unexpected sudoers type #{my_type}" 
      end
    end
    isrequired
  end

  newproperty(:sudo_alias) do
    desc "Type of alias. Options are Cmnd, Host, User, and Runas"
    newvalue(/^(Cmnd|Host|User|Runas)(_Alias)?$/)
    # add _Alias if it was ommitted
    munge do |value|
      if(value =~ /^(Cmnd|Host|User|Runas)$/) 
        value << '_Alias'
      end
      value
    end
    # this is now an alias type
  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

# single user is namevar
  newproperty(:users, :array_matching => :all) do
    desc "list of users for user spec"
    validate do |value|
      if value =~ /^\s*Defaults/
        raise Puppet::Error, 'Cannot specify user named Defaults in sudoers'
      end
    end
  end

  newproperty(:hosts, :array_matching => :all) do
    desc "list of hosts for user spec"
  end

  newproperty(:commands, :array_matching => :all) do
    desc "commands to run"
  end

  newproperty(:parameters, :array_matching => :all) do
    desc "default parameters"
  end

  # I should check that this is not /PUPPET NAMEVAR/ 
  newproperty(:comment) do
    defaultto ''
  end



  # make sure that we only have attributes for either default, alias, or user_spec
  # I need to think about this... This prevents users from being able 
  # to set resource defaults...
  #
  SUDOERS_DEFAULT = [:parameters]
  SUDOERS_ALIAS = [:sudo_alias, :items]
  SUDOERS_SPEC = [:users, :hosts, :commands]
#
# this does not work both ways for some reason
#
#
  validate do
    # this if ensure if a little hackish - 
    # balically, when initialize is called from self.instances
    # none of the attributes are actually set (including type)
    # the best way to tell if I was called by self.instances
    # is to check if ensure has a value?
    if self[:ensure]
      if self.value(:type) == 'default'
        checkprops(SUDOERS_DEFAULT)      
      elsif self.value(:type) == 'alias'
        checkprops(SUDOERS_ALIAS)      
      elsif self.value(:type) == 'user_spec'
        checkprops(SUDOERS_SPEC)      
      elsif ! self[:type]
        # this is only during purging (self.instances)
        raise Puppet::Error, 'attribute type must be set for sudoers type'
      end
    else
      # this occurs with self.instances
      # is there a better way?
    end
  end

  private

    def checkprops(props)
      props.each do |prop|
        unless self[prop.to_s]
          raise Puppet::Error, "missing attribute #{prop} for type #{self[:type]}"
        end
      end
    end
#    if self[:sudo_alias] 
#      self[:type] = 'alias'
#      checkprops(SUDOERS_DEFAULT, SUDOERS_SPEC)
#    elsif self[:parameters]
#      self[:type] = 'default'
#      checkprops(SUDOERS_ALIAS, SUDOERS_SPEC)
#    elsif self[:users]
#      self[:type] = 'user_spec'
#      checkprops(SUDOERS_ALIAS, SUDOERS_DEFAULT)
#    else
#      # these are parsed records, do nothing 
#    end
    #puts self.should('sudo_alias')
    #puts self.to_yaml  
    #puts self.eachproperty do |x| puts x end
#  end

#  private

  # check that we dont have any conflicting attributes
#  def checkprops(array_one, array_two)
#    combined = Array.new.concat(array_one).concat(array_two)
#    combined.each do |item|
#      if self[item.to_sym]
#        raise Puppet::Error, "Unexpected attribute #{item} for sudo record type #{self[:type]}"
#      end
#    end 
#  end
end