2 changes: 1 addition & 1 deletion Modulefile
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
name 'puppetlabs-java_ks'
version '0.0.6'
version '1.0.0'
source 'https://github.com/puppetlabs/puppetlabs-java_ks.git'
author 'puppetlabs'
license 'ASL 2.0'
Expand Down
168 changes: 141 additions & 27 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,27 +1,141 @@
This modules ships a type called java_ks and a single provider named keytool. The purpose is to be able to import arbitrary, already generated and signed certificates into a java keystore for use by various applications. It has a concept of absent, present, and latest. Absent and present are self explanatory but latest will actually verify md5 certificate fingerprints for the stored certificate and the source file. Support for multiple certificates with the same alias but different keystores has been implemented using Puppet's composite namevar functionality. The mapping of title to namevars is $alias:$target (alias of certificate, colon, on disk path to the keystore). If you create dependencies on these resources you need to remember to use the same title syntax outlined for generating the composite namevars. To have a java application server use a specific certificate for incoming connections you will need to import the private key accompanying signed certificate you want to use at the same time, this is a limitation of keytool. As long as you provide the path to the key and the certificate the provider will do the conversion for you.

Note about composite namevars. The way they currently work you must have the colon in the title. YES even if you define name and target parameters. The title can be 'foo:bar' but the name and target parameters be 'broker.example.com' and '/etc/activemq/broker.ks' and it will do as you expect and correctly create an entry in the broker.ks keystore with the alias of broker.example.com...I think you could consider this a bug.

Example Usage:
```puppet
java_ks { 'puppetca:truststore':
ensure => latest,
certificate => '/etc/puppet/ssl/certs/ca.pem',
target => '/etc/activemq/broker.ts',
password => 'puppet',
trustcacerts => true,
}
java_ks { 'puppetca:keystore':
ensure => latest,
certificate => '/etc/puppet/ssl/certs/ca.pem',
target => '/etc/activemq/broker.ks',
password => 'puppet',
trustcacerts => true,
}
java_ks { 'broker.example.com:/etc/activemq/broker.ks':
ensure => latest,
certificate => '/etc/puppet/ssl/certs/broker.example.com.pe-internal-broker.pem',
private_key => '/etc/puppet/ssl/private_keys/broker.example.com.pe-internal-broker.pem',
password => 'puppet',
}
```
java_ks
=======

####Table of Contents

1. [Overview - What is the java_ks module?](#overview)
2. [Module Description - What does the module do?](#module-description)
3. [Setup - The basics of getting started with java_ks](#setup)
4. [Usage - The parameters available for configuration](#usage)
5. [Implementation - An under-the-hood peek at what the module is doing](#implementation)
6. [Limitations - OS compatibility, etc.](#limitations)
7. [Development - Guide for contributing to the module](#development)
8. [Release Notes - Notes on the most recent updates to the module](#release-notes)

Overview
--------

The java_ks module uses a combination of keytool and openssl to manage entries in a Java keystore.

Module Description
------------------

The java_ks module contains a type called 'java_ks' and a single provider named 'keytool'. Their purpose is to enable importation of arbitrary, already generated and signed certificates into a java keystore for use by various applications.

Setup
-----

**What java_ks affects:**

* keystore repositories

### Beginning with java_ks

To use the java_ks module's functionality, declare each java_ks resource you need

java_ks { 'puppetca:truststore':
ensure => latest,
certificate => '/etc/puppet/ssl/certs/ca.pem',
target => '/etc/activemq/broker.ts',
password => 'puppet',
trustcacerts => true,
}

java_ks { 'puppetca:keystore':
ensure => latest,
certificate => '/etc/puppet/ssl/certs/ca.pem',
target => '/etc/activemq/broker.ks',
password => 'puppet',
trustcacerts => true,
}

java_ks { 'broker.example.com:/etc/activemq/broker.ks':
ensure => latest,
certificate => '/etc/puppet/ssl/certs/broker.example.com.pe-internal-broker.pem',
private_key => '/etc/puppet/ssl/private_keys/broker.example.com.pe-internal-broker.pem',
password => 'puppet',
}

Usage
-----

### java_ks

This resource manages the entries in a java keystore, and uses composite namevars to accomplish the same alias spread across multiple target keystores.

**Parameters within java_ks**

#### `certificate`

An already-signed certificate to place in the keystore.

To have a java application server use a specific certificate for incoming connections, you will need to simultaneously import the private key accompanying the signed certificate you want to use. As long as you provide the path to the key and the certificate, the provider will do the conversion for you.

#### `chain`

Some java applications do not properly send intermediary certificate authorities. In these cases, you can bundle them with the server certificate using this chain parameter.

java_ks { 'broker.example.com:/etc/activemq/broker.jks':
ensure => latest,
certificate => '/etc/ssl/certs/broker.example.com.pem',
private_key => '/etc/ssl/private/broker.example.com.key',
chain => '/etc/ssl/certs/GlobalSign_Intermediate_CA.pem
password => 'puppet',
}

#### `ensure`

The `ensure` parameter accepts three attributes: absent, present, and latest. Latest verifies md5 certificate fingerprints for the stored certificate and the source file.

#### `password`

The password used to protect the keystore. If private keys are also protected, this password will be used to attempt to unlock them.

#### `private_key`

If you want an application to be a server and encrypt traffic, you will need a private key. Private key entries in a keystore must be accompanied by a signed certificate for the keytool provider.

#### `target`

Destination file for the keystore. We autorequire the parent directory for convenience.

#### `trustcacerts`

Certificate authorities input into a keystore aren’t trusted by default, so if you are adding a CA you need to set this parameter to true.

### Namevars

Java_ks supports multiple certificates with different keystores but the same alias by implementing Puppet's composite namevar functionality. Titles map to namevars via `$alias:$target` (alias of certificate, colon, on-disk path to the keystore). If you create dependencies on these resources you need to remember to use the same title syntax outlined for generating the composite namevars.

*Note about composite namevars:*
The way composite namevars currently work, you must have the colon in the title. This is true *even if you define name and target parameters.* The title can be `foo:bar`, but the name and target parameters must be `broker.example.com` and `/etc/activemq/broker.ks`. If you follow convention, it will do as you expect and correctly create an entry in the broker.ks keystore with the alias of broker.example.com.

Implementation
--------------

### keytool

Keytool is a provider that uses a combination of the binaries openssl and keytool to manage Java keystores

Limitations
------------

The java_ks module uses the `keytool` and `openssl` commands. It should work on all systems with these commands.

At the moment, Java 7 isn't fully supported, and `ensure => latest` will fail.

Development
-----------

Puppet Labs modules on the Puppet Forge are open projects, and community contributions are essential for keeping them great. We can’t access the huge number of platforms and myriad of hardware, software, and deployment configurations that Puppet is intended to serve.

We want to keep it as easy as possible to contribute changes so that our modules work in your environment. There are a few guidelines that we need contributors to follow so that we can have a chance of keeping on top of things.

You can read the complete module contribution guide [on the Puppet Labs wiki.](http://projects.puppetlabs.com/projects/module-site/wiki/Module_contributing)

Release Notes
-------------

**0.0.6**

Fixes an issue with ibm java handling input from stdin on SLES
97 changes: 54 additions & 43 deletions lib/puppet/provider/java_ks/keytool.rb
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,21 @@ def to_pkcs12
tmpfile = Tempfile.new("#{@resource[:name]}.")
tmpfile.write(@resource[:password])
tmpfile.flush
output = Puppet::Util.execute(
cmd,
:stdinfile => tmpfile.path,
:failonfail => true,
:combine => true
)

# To maintain backwards compatibility with Puppet 2.7.x, resort to ugly
# code to make sure RANDFILE is passed as an environment variable to the
# openssl command but not retained in the Puppet process environment.
randfile = Tempfile.new("#{@resource[:name]}.")
if Puppet::Util::Execution.respond_to?(:withenv)
withenv = Puppet::Util::Execution.method(:withenv)
else
withenv = Puppet::Util.method(:withenv)
end
output = withenv.call('RANDFILE' => randfile.path) do
run_command(cmd, false, tmpfile)
end
tmpfile.close!
randfile.close!
return output
end

Expand All @@ -50,7 +58,7 @@ def import_ks
tmpfile.write("#{@resource[:password]}\n#{@resource[:password]}\n#{@resource[:password]}")
end
tmpfile.flush
run_keystore_command(cmd, @resource[:target], tmpfile)
run_command(cmd, @resource[:target], tmpfile)
tmppk12.close!
tmpfile.close!
end
Expand All @@ -66,12 +74,7 @@ def exists?
tmpfile = Tempfile.new("#{@resource[:name]}.")
tmpfile.write(@resource[:password])
tmpfile.flush
Puppet::Util.execute(
cmd,
:stdinfile => tmpfile.path,
:failonfail => true,
:combine => true
)
run_command(cmd, false, tmpfile)
tmpfile.close!
return true
rescue
Expand All @@ -86,7 +89,7 @@ def latest
'x509', '-fingerprint', '-md5', '-noout',
'-in', @resource[:certificate]
]
output = Puppet::Util.execute(cmd)
output = run_command(cmd)
latest = output.scan(/MD5 Fingerprint=(.*)/)[0][0]
return latest
end
Expand All @@ -96,21 +99,16 @@ def current
output = ''
cmd = [
command(:keytool),
'-list',
'-list', '-v',
'-keystore', @resource[:target],
'-alias', @resource[:name]
]
tmpfile = Tempfile.new("#{@resource[:name]}.")
tmpfile.write(@resource[:password])
tmpfile.flush
output = Puppet::Util.execute(
cmd,
:stdinfile => tmpfile.path,
:failonfail => true,
:combine => true
)
output = run_command(cmd, false, tmpfile)
tmpfile.close!
current = output.scan(/Certificate fingerprint \(MD5\): (.*)/)[0][0]
current = output.scan(/Certificate fingerprints:\n\s+MD5: (.*)/)[0][0]
return current
end

Expand All @@ -137,7 +135,7 @@ def create
tmpfile.write("#{@resource[:password]}\n#{@resource[:password]}")
end
tmpfile.flush
run_keystore_command(cmd, @resource[:target], tmpfile)
run_command(cmd, @resource[:target], tmpfile)
tmpfile.close!
end
end
Expand All @@ -152,12 +150,7 @@ def destroy
tmpfile = Tempfile.new("#{@resource[:name]}.")
tmpfile.write(@resource[:password])
tmpfile.flush
Puppet::Util.execute(
cmd,
:stdinfile => tmpfile.path,
:failonfail => true,
:combine => true
)
run_command(cmd, false, tmpfile)
tmpfile.close!
end

Expand All @@ -167,38 +160,56 @@ def update
create
end

def run_keystore_command(cmd, target, stdinfile)
def run_command(cmd, target=false, stdinfile=false)

# The Puppet::Util::Execution.execute method is deparcated in Puppet 3.x
# but we need this to work on 2.7.x too.
if Puppet::Util::Execution.respond_to?(:execute)
exec_method = Puppet::Util::Execution.method(:execute)
else
exec_method = Puppet::Util.method(:execute)
end

# the java keytool will not correctly deal with an empty target keystore
# file. If we encounter an empty keystore target file, preserve the mode,
# owner and group, and delete the empty file.
if File.exists?(target) and File.zero?(target)
if target and (File.exists?(target) and File.zero?(target))
stat = File.stat(target)
File.delete(target)
end

# There's a problem in IBM java wherein stdin cannot be used (trivially)
# pass in the keystore passwords. This makes the provider work on SLES
# with minimal effort.
# There's a problem in IBM java keytool wherein stdin cannot be used
# (trivially) to pass in the keystore passwords. The below hack makes the
# provider work on SLES with minimal effort at the cost of letting the
# passphrase to the keystore show up in the process list as an argument.
# From a best practice standpoint the keystore should be protected by file
# permissions and not just the passphrase so "making it work on SLES"
# trumps.
if Facter.value('osfamily') == 'Suse' and @resource[:password]
cmd << '-srcstorepass' << @resource[:password]
cmd << '-deststorepass' << @resource[:password]
cmd_to_run = cmd.is_a?(String) ? cmd.split(/\s/).first : cmd.first
if cmd_to_run == command(:keytool)
cmd << '-srcstorepass' << @resource[:password]
cmd << '-deststorepass' << @resource[:password]
end
end

# Now run the command
Puppet::Util.execute(
cmd,
:stdinfile => stdinfile.path,
:failonfail => true,
:combine => true
)
options = { :failonfail => true, :combine => true }
output = if stdinfile
exec_method.call(cmd, options.merge(:stdinfile => stdinfile.path))
else
exec_method.call(cmd, options)
end

# for previously empty files, restore the mode, owner and group. The funky
# double-take check is because on Suse defined? doesn't seem to behave
# quite the same as on Debian, RedHat
if defined? stat and stat
if target and (defined? stat and stat)
File.chmod(stat.mode, target)
File.chown(stat.uid, stat.gid, target)
end

return output
end

end
19 changes: 1 addition & 18 deletions spec/spec_helper.rb
Original file line number Diff line number Diff line change
@@ -1,18 +1 @@
require 'pathname'
dir = Pathname.new(__FILE__).parent
$LOAD_PATH.unshift(dir, dir + 'lib', dir + '../lib')

require 'mocha'
require 'puppet'
gem 'rspec', '>=2.0.0'
require 'rspec/expectations'

RSpec.configure do |config|
config.mock_with :mocha
end

# We need this because the RAL uses 'should' as a method. This
# allows us the same behaviour but with a different method name.
class Object
alias :must :should
end
require 'puppetlabs_spec_helper/module_spec_helper'
Loading