Commit bcc73fad authored by Alex Tayts's avatar Alex Tayts
Browse files

Initial commit

parents
Wallet Module
=============
The module installs `wallet-client` package and provides a `wallet` resource to download any type of wallet object and keep a local copy up-to-date.
## Wallet installation
To install wallet client using the module just add
include wallet
to the manifest. The class installs the necessary prerequisites like wallet-client package and kerberos, if it has not been installed already.
## Dependencies
The resource depends on the presence of the following binaries:
- `wallet` from a package `wallet-client`
- `k5start` from a package `kstart`
- MIT or Heimdal kerberos utilities from `krb5-user` or `heimdal-clients` respectively.
## Wallet resource
Wallet resource is implemented as a custom type and provider accepting the following arguments:
#### ensure
Can be `present` or `absent`, downloading an object from wallet or removing its local copy. Defaults to _present_.
#### name
A name of an object in wallet. _Required_.
#### path
The name of a file for a downloaded wallet object. _Required_.
#### type
A type of a wallet object like `file`, `keytab` or `pam-duo`. Defaults to _file_.
#### auth_principal
A kerberos principal used for authentication to wallet, by default a server's host principal. Defaults to a first entry in a keytab.
#### auth_keytab
A keytab file where `auth_principal` keys are stored. Must be an absolute path. Defaults to _/etc/krb5.keytab_.
#### owner
A desired owner of a file created out of a wallet object. Can be given as a numeric _uid_ (like _1001_), string representation of a numeric _uid_ (like _"1001"_) or a user name (like _jdoe_). Defaults to not settingan owner. Since typically puppet runs as root, that would be a default owner of a file.
#### group
A desired group of a file created out of a wallet object. Can be given as a numeric _gid_ (like _1001_), string representation of a numeric _gid_ (like _"1001"_) or a group name (like _operator_). Defaults to not setting a group. Since typically puppet runs as root, that would be a default group of a file.
#### mode
A desired mode of a file created out of a wallet object. Can be given as a numeric _uid_ (like _1001_) or a string representation of a numeric _uid_ (like _"1001"_). Defaults to not setting a mode. Wallet client automatically sets mode to 600, which would be a natural default.
#### verify
A boolean enabling or disabling verification of a local copy of a wallet object. If verification fails for any reason (file is missing, modified, stale keytab, etc.), it is downloaded from wallet again. Defaults to _false_.
## Examples
### Download and maintain a keytab
A keytab wallet object `service/myapplication` is downloaded and stored in a file. Host principal in a host keytab is used to authenticate to wallet. Ownership is set to allow access by _myapp_ account. Keytab is verified on every puppet run to contain the keys of a `service/myapplication` principal. If keys are updated in wallet, the local keytab would also be updated with them.
```
wallet { 'service/myapplication':
type => 'keytab',
ensure => 'present',
verify => true,
mode => '600',
owner => 'myapp',
group => 'webconfig',
path => '/etc/myapplication/myapp.keytab',
}
```
The same, except a particular key from a host keytab is used to authenticate to wallet and keytab is not maintained. It is only checked for a presence of a key without worrying about its validity. If keytab is misssing, it would be recreated from a wallet item. If a key for a `service/myapplication` principal is missing from a keytab, it would be added.
```
wallet { 'service/myapplication':
type => 'keytab',
ensure => 'present',
verify => false,
auth_principal => 'host/server2.stanford.edu',
path => '/etc/myapplication/myapp.keytab',
}
```
### Download and maintain a file
A file with a shibboleth key stored in wallet is downloaded. Host principal found in a host keytab is used to authenticate to wallet. The content of a shibboleth key is maintained. If it gets updated in wallet, a local copy would get updated as well.
```
wallet { 'ssl-keypair/server.stanford.edu/shibboleth':
type => 'file',
ensure => 'present',
verify => true,
path => '/etc/shibboleth/sp-key.pem',
}
```
### Get Duo configuration for a server
A duo configuration is downloaded in a local file. Host principal found in a host keytab is used to authenticate to wallet. The content of a duo configuration is not maintained. If a configuration file gets missing it would be downloaded again, but local modifictaions of its content won't be overwritten.
```
wallet { 'server.stanford.edu':
type => 'pam-duo',
ensure => 'present',
verify => false,
path => '/etc/security/${hostname}.conf',
}
```
### Delete a local copy of a wallet object
```
wallet { 'ssl-key/server.stanford.edu':
ensure => 'absent',
}
```
## References
- [Wallet application](https://www.eyrie.org/~eagle/software/wallet/)
- [Wallet object naming](https://www.eyrie.org/~eagle/software/wallet/naming.html)
- [Wallet client commands](https://www.eyrie.org/~eagle/software/wallet/wallet.html)
require "digest/md5"
require "etc"
Puppet::Type.type(:wallet).provide(:wallet) do
desc "Wallet support"
confine :osfamily => [:redhat, :debian]
commands :wallet => "/usr/bin/wallet",
:kdestroy => "/usr/bin/kdestroy",
:kstart => "/usr/bin/k5start",
:klist => "/usr/bin/klist",
:ktutil => "/usr/bin/ktutil"
#### does resource exist?
##############################
def exists?
if File.file?(@resource[:path])
# do not go further than that if a file
# is destined to deletion
return true if @resource[:ensure] == :absent
if @resource[:verify]
if @resource[:type].to_s == "keytab"
# try to get TGT with the keytab
kstart("-Uqf", @resource[:path])
exists = ($?.exitstatus == 0)
# cleanup the ticket after we got it
kdestroy() if exists
else
# checksum the wallet object and compare to a
# local file
begin
if @resource[:auth_principal].nil?
wallet_obj = kstart("-U", "-q", "-f", @resource[:auth_keytab], "--", "/usr/bin/wallet", "get", @resource[:type], @resource[:name])
else
wallet_obj = kstart("-q", "-f", @resource[:auth_keytab], @resource[:auth_principal], "--", "/usr/bin/wallet", "get", @resource[:type], @resource[:name])
end
rescue Puppet::ExecutionFailure => detail
raise Puppet::Error, "Failed to acquire wallet object. #{@resource.class.name} #{@resource.name}: #{detail}", detail.backtrace
end
local_md5 = Digest::MD5.file(@resource[:path])
object_md5 = Digest::MD5.hexdigest(wallet_obj)
exists = (object_md5.to_s == local_md5.to_s)
end
else
# if a file is a keytab, make sure it has a key for
# the principal we need. For other types of objects existence
# of a file is enough.
if @resource[:type].to_s == "keytab"
# Determine whether MIT Kerberos is intalled or Heimdal
# Check for one of the files which comes with heimdal-clients package
if File.file?("/usr/bin/heimtools")
# heimdal Kerberos is installed
princs = ktutil("-k", @resource[:path], "list").split("/n")
exists = (princs.any? { |s| s.include?(@resource[:name])})
else
# MIT Kerberos is installed
princs = klist("-k", @resource[:path]).split("/n")
exists = (princs.any? { |s| s.include?(@resource[:name])})
end
else
exists = true
end
end
else
# file doesn't exist
exists = false
end
return exists
end
#### create resource
##############################
def create
begin
if @resource[:auth_principal].nil?
kstart("-U", "-q", "-f", @resource[:auth_keytab], "--", "/usr/bin/wallet", "-f", @resource[:path], "get", @resource[:type], @resource[:name])
else
kstart("-q", "-f", @resource[:auth_keytab], @resource[:auth_principal], "--", "/usr/bin/wallet", "-f", @resource[:path], "get", @resource[:type], @resource[:name])
end
rescue Puppet::ExecutionFailure => detail
raise Puppet::Error, "Failed to acquire wallet object. #{@resource.class.name} #{@resource.name}: #{detail}", detail.backtrace
end
# set initial permissions and ownership as requested
File.chmod(Integer("0" + @resource[:mode].to_s), @resource[:path]) unless @resource[:mode].nil?
File.chown(arg_to_uid(@resource[:owner]), arg_to_gid(@resource[:group]), @resource[:path]) unless (@resource[:owner].nil? or @resource[:group].nil?)
end
#### destroy resource
##############################
def destroy
File.unlink(resource[:path])
end
#### manage properties
##############################
def mode
"%o" % (File.stat(@resource[:path]).mode & 007777)
end
def mode=(value)
File.chmod(Integer("0" + value.to_s), @resource[:path])
end
def owner
File.stat(@resource[:path]).uid
end
def owner=(value)
File.chown(arg_to_uid(value), nil, @resource[:path])
end
def group
File.stat(@resource[:path]).gid
end
def group=(value)
File.chown(nil, arg_to_gid(value), @resource[:path])
end
#### helper functions
##############################
def arg_to_gid(value)
case value
when String
if value =~ /^[-0-9]+$/
Integer(value)
else
Etc.getgrnam(value).gid
end
else
value
end
end
def arg_to_uid(value)
case value
when String
if value =~ /^[-0-9]+$/
Integer(value)
else
Etc.getpwnam(value).uid
end
else
value
end
end
end
require 'puppet/parameter/boolean'
require 'etc'
Puppet::Type.newtype(:wallet) do
@doc = "Get a file from wallet"
ensurable do
desc "Get a file from wallet or remove it"
defaultvalues
defaultto(:present)
end
newparam(:name) do
desc "Wallet object to download"
isrequired
end
newparam(:path) do
desc "The local file to save wallet object to"
validate do |value|
unless Puppet::Util.absolute_path?(value)
fail Puppet::Error, "File paths must be fully qualified, not '#{value}'"
end
end
end
newparam(:type) do
desc "Type of wallet object"
defaultto('file')
newvalues('file', 'keytab', 'duo-pam', 'duo-radius' 'duo-rdp')
end
newparam(:auth_keytab) do
desc "Keytab used to authenticate to wallet"
defaultto('/etc/krb5.keytab')
validate do |value|
unless Puppet::Util.absolute_path?(value)
fail Puppet::Error, "File paths must be fully qualified, not '#{value}'"
end
end
end
newparam(:auth_principal) do
desc "Principal in auth_keytab used to authenticate to wallet"
validate do |value|
unless /^(host|service|webauth|smtp|pop|postgres|nfs|lpr|ldap|imap|ftp|cifs|afpserver|HTTP)\/[0-9a-zA-Z\.\-]+$/.match(value)
raise Puppet::Error, "Principal name #{value} is invalid."
end
end
end
newparam(:verify, :boolean => true, :parent => Puppet::Parameter::Boolean) do
desc "Enable/disable wallet object validation"
defaultto(true)
end
newproperty(:owner) do
desc "Owner of the local file"
# make sure that no matter how owner is specified
# (integer, string of numbers, user name), we always
# compare an integer uid to a value given by the provider.
munge do |value|
case value
when String
if value =~ /^[-0-9]+$/
value = Integer(value)
else
value = Etc.getpwnam(value).uid
end
end
return value
end
end
newproperty(:group) do
desc "Group permission on the local file"
# make sure that no matter how group is specified
# (integer, string of numbers, group name), we always
# compare an integer gid to a value given by the provider.
munge do |value|
case value
when String
if value =~ /^[-0-9]+$/
value = Integer(value)
else
value = Etc.getgrnam(value).gid
end
end
return value
end
end
newproperty(:mode) do
desc "Manage the file's mode."
# make sure we always compare modes as integers
munge do |value|
case value
when String
if value =~ /^[-0-9]+$/
value = Integer(value)
end
end
return value
end
end
# require any parent directory be created first
autorequire :file do
[ File.dirname(self[:path]) ]
end
end
# Install packages necessary for the "wallet" resource to work
#
# It depends on
# wallet-client
# kerberos, specifically klist/ktutil
# k5start
class wallet::client {
include kerberos
package { 'wallet-client': ensure => 'present' }
}
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment