15 changes: 15 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,18 @@
##Supported Release 1.4.0
###Summary
This release contains a new option to provide destkeypass. Also contains
bugfixes and a metadata update to support Puppet Enterprise 2015.3.x.

####Features
- Adds `destkeypass` option to pass in password when importing into the keystore.
- Adds feature support for JCEKS format and extensions.

####Bugfixes
- Fixes composite title patterns in provider to improve support for Windows.

####Test Improvements
- Improves Windows testing.

##2015-07-20 - Supported Release 1.3.1
###Summary
This release updates the metadata for the upcoming release of PE as well as an additional bugfix.
Expand Down
6 changes: 5 additions & 1 deletion Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ group :system_tests do
gem 'beaker-rspec', :require => false
end
gem 'serverspec', :require => false
gem 'beaker-puppet_install_helper', :require => false
gem 'beaker-puppet_install_helper', '~> 0.3', :require => false
end


Expand All @@ -45,4 +45,8 @@ else
gem 'puppet', :require => false
end

if File.exists? "#{__FILE__}.local"
eval(File.read("#{__FILE__}.local"), binding)
end

# vim:ft=ruby
30 changes: 22 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ The java_ks module uses a combination of keytool and openssl to manage entries i

##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.
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

Expand Down Expand Up @@ -64,22 +64,22 @@ To have a Java application server use a specific certificate for incoming connec

### Namevars

The java_ks module 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.
The java_ks module 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
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.

##Reference

###Public Types
* `java_ks`: This resource manages the entries in a Java keystore, and uses composite namevars to allow the same alias across multiple target keystores.
* `java_ks`: This resource manages the entries in a Java keystore, and uses composite namevars to allow the same alias across multiple target keystores.

###Public Providers
* `keytool`: Manages Java keystores by using a combination of the `openssl` and `keytool` commands.

####Parameters
All parameters, except where specified, are optional.
All parameters, except where specified, are optional.

#####`certificate`
*Required.* Places an already-signed certificate in the keystore. This autorequires the specified file and must be present on the node before java_ks{} is run. Valid options: string. Default: undef.
Expand All @@ -97,7 +97,10 @@ Valid options: absent, present, latest. Latest verifies md5 certificate fingerpr
This password is used to protect the keystore. If private keys are also protected, this password will be used to attempt to unlock them. Valid options: String. Must be 6 or more characters. This cannot be used together with `password_file`, but *you must pass at least one of these parameters.* Default: undef.

#####`password_file`
Sets a plaintext file where the password is stored. Used as an alternative to `password`. This cannot be used together with `password`, but *you must pass at least one of these parameters.* Valid options: String to the plaintext file. Default: undef.
Sets a plaintext file where the password is stored. Used as an alternative to `password`. This cannot be used together with `password`, but *you must pass at least one of these parameters.* Valid options: String to the plaintext file. Default: undef.

#####`destkeypass`
The password you want to set to protect the key in the keystore.

#####`path`
Used for command (keytool, openssl) execution. Valid options: array or file path separated list (for example : in linux). Default: undef.
Expand All @@ -111,11 +114,23 @@ Sets a private key that encrypts traffic to a server application. Must be accomp
#####`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'. Valid options: 'true' or 'false'. Default: 'false'.

### `storetype`

The storetype parameter allows you to use 'jceks' format if desired.

java_ks { 'puppetca:/opt/puppet/truststore.jceks':
ensure => latest,
storetype => 'jceks',
certificate => '/etc/puppet/ssl/certs/ca.pem',
password => 'puppet',
trustcacerts => true,
}


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

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

Java 7 is supported as of 1.0.0.

Expand All @@ -127,4 +142,3 @@ 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 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. For more information, see our [module contribution guide.](https://docs.puppetlabs.com/forge/contributing.html)

35 changes: 35 additions & 0 deletions lib/puppet/provider/java_ks/keytool.rb
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,13 @@ def to_pkcs12(path)
File.open(path, "wb") { |f| f.print pkcs12.to_der }
end

# Keytool can only import a jceks keystore if the format is der. Generating and
# importing a keystore is used to add private_key and certifcate pairs.
def to_der(path)
x509_cert = OpenSSL::X509::Certificate.new File.read certificate
File.open(path, "wb") { |f| f.print x509_cert.to_der }
end

def get_password
if @resource[:password_file].nil?
@resource[:password]
Expand Down Expand Up @@ -58,20 +65,41 @@ def import_ks
'-alias', @resource[:name]
]
cmd << '-trustcacerts' if @resource[:trustcacerts] == :true
cmd += [ '-destkeypass', @resource[:destkeypass] ] unless @resource[:destkeypass].nil?

pwfile = password_file
run_command(cmd, @resource[:target], pwfile)
tmppk12.close!
pwfile.close! if pwfile.is_a? Tempfile
end

def import_jceks
tmpder = Tempfile.new("#{@resource[:name]}.")
to_der(tmpder.path)
cmd = [
command_keytool,
'-importcert', '-noprompt',
'-alias', @resource[:name],
'-file', tmpder.path,
'-keystore', @resource[:target],
'-storetype', storetype
]
cmd << '-trustcacerts' if @resource[:trustcacerts] == :true
cmd += [ '-destkeypass', @resource[:destkeypass] ] unless @resource[:destkeypass].nil?

pwfile = password_file
run_command(cmd, @resource[:target], pwfile)
pwfile.close! if pwfile.is_a? Tempfile
end

def exists?
cmd = [
command_keytool,
'-list',
'-keystore', @resource[:target],
'-alias', @resource[:name]
]
cmd += [ '-storetype', storetype ] if storetype == "jceks"
begin
tmpfile = password_file
run_command(cmd, false, tmpfile)
Expand Down Expand Up @@ -111,6 +139,7 @@ def current
'-keystore', @resource[:target],
'-alias', @resource[:name]
]
cmd += [ '-storetype', storetype ] if storetype == "jceks"
tmpfile = password_file
output = run_command(cmd, false, tmpfile)
tmpfile.close!
Expand All @@ -126,6 +155,8 @@ def create
import_ks
elsif certificate.nil? and !private_key.nil?
raise Puppet::Error, 'Keytool is not capable of importing a private key without an accomapaning certificate.'
elsif storetype == "jceks"
import_jceks
else
cmd = [
command_keytool,
Expand Down Expand Up @@ -171,6 +202,10 @@ def chain
@resource[:chain]
end

def storetype
@resource[:storetype]
end

def run_command(cmd, target=false, stdinfile=false, env={})

env[:PATH] = @resource[:path].join(File::PATH_SEPARATOR) if resource[:path]
Expand Down
22 changes: 22 additions & 0 deletions lib/puppet/type/java_ks.rb
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,13 @@ def insync?(is)
isrequired
end

newparam(:storetype) do
desc 'Optional storetype
Valid options: <jceks>'

newvalues(:jceks)
end

newparam(:private_key) do
desc '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
Expand Down Expand Up @@ -97,6 +104,14 @@ def insync?(is)
keystore. This cannot be used together with :password, but you must pass at least one of these parameters.'
end

newparam(:destkeypass) do
desc 'The password used to protect the key in keystore.'

validate do |value|
raise Puppet::Error, "destkeypass is #{value.length} characters long; must be of length 6 or greater" if value.length < 6
end
end

newparam(:trustcacerts) do
desc "Certificate authorities aren't by default trusted so if you are adding a CA you need to set this to true.
Defaults to :false."
Expand Down Expand Up @@ -143,6 +158,13 @@ def self.title_patterns
[ :name, identity ]
]
],
[
/^(.*):([a-z]:(\/|\\).*)$/i,
[
[ :name, identity ],
[ :target, identity ]
]
],
[
/^(.*):(.*)$/,
[
Expand Down
13 changes: 7 additions & 6 deletions metadata.json
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
{
"name": "puppetlabs-java_ks",
"version": "1.3.1",
"version": "1.4.0",
"author": "puppetlabs",
"summary": "Manage arbitrary Java keystore files",
"license": "Apache-2.0",
"source": "https://github.com/puppetlabs/puppetlabs-java_ks.git",
"project_page": "https://github.com/puppetlabs/puppetlabs-java_ks",
"issues_url": "https://tickets.puppetlabs.com/browse/MODULES",
"dependencies": [

],
"data_provider": null,
"operatingsystem_support": [
{
"operatingsystem": "RedHat",
Expand Down Expand Up @@ -93,15 +97,12 @@
"requirements": [
{
"name": "pe",
"version_requirement": ">= 3.0.0 < 2015.3.0"
"version_requirement": ">= 3.0.0 < 2015.4.0"
},
{
"name": "puppet",
"version_requirement": ">= 3.0.0 < 5.0.0"
}
],
"description": "Uses a combination of keytool and Ruby openssl library to manage entries in a Java keystore.",
"dependencies": [

]
"description": "Uses a combination of keytool and Ruby openssl library to manage entries in a Java keystore."
}
21 changes: 10 additions & 11 deletions spec/acceptance/basic_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,16 @@
java_source = ENV['JAVA_DOWNLOAD_SOURCE'] || "http://download.oracle.com/otn-pub/java/jdk/7u67-b01/jdk-7u67-windows-x64.exe"
java_major, java_minor = (ENV['JAVA_VERSION'] || '7u67').split('u')
pp = <<-EOS
if $::osfamily !~ /windows/ {
class { 'java': }
} else {
windows_java::jdk{'JDK #{java_major}u#{java_minor}':
ensure => 'present',
install_name => 'Java SE Development Kit #{java_major} Update #{java_minor} (64-bit)',
source => '#{java_source}',
install_path => 'C:\\Java\\jdk1.#{java_major}.0_#{java_minor}',
jre_install_path => 'C:\\Java\\jre',
}
}
if $::osfamily !~ /windows/ {
class { 'java': }
} else {
windows_java::jdk{'JDK #{java_major}u#{java_minor}':
ensure => 'present',
install_name => 'Java SE Development Kit #{java_major} Update #{java_minor} (64-bit)',
source => '#{java_source}',
install_path => 'C:\\Java\\jdk1.#{java_major}.0_#{java_minor}',
}
}
EOS
apply_manifest(pp, :catch_failures => true)
end
Expand Down
47 changes: 47 additions & 0 deletions spec/acceptance/destkeypass_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
require 'spec_helper_acceptance'

hostname = default.node_name

describe 'password protected java private keys', :unless => UNSUPPORTED_PLATFORMS.include?(fact('operatingsystem')) do
include_context 'common variables'

let(:confdir) { default['puppetpath'] }
let(:modulepath) { default['distmoduledir'] }

case fact('osfamily')
when "windows"
target = 'c:/private_key.ks'
else
target = '/etc/private_key.ks'
end

it 'creates a password protected private key' do
pp = <<-EOS
java_ks { 'broker.example.com:#{target}':
ensure => latest,
certificate => "#{@temp_dir}ca.pem",
private_key => "#{@temp_dir}privkey.pem",
password => 'testpass',
destkeypass => 'testkeypass',
path => #{@resource_path},
}
EOS

apply_manifest(pp, :catch_failures => true)
end

it 'can make a cert req with the right password' do
shell("#{@keytool_path}keytool -certreq -alias broker.example.com -v "\
"-keystore #{target} -storepass testpass -keypass testkeypass") do |r|
expect(r.exit_code).to be_zero
expect(r.stdout).to match(/-BEGIN NEW CERTIFICATE REQUEST-/)
end
end

it 'cannot make a cert req with the wrong password' do
shell("#{@keytool_path}keytool -certreq -alias broker.example.com -v "\
"-keystore #{target} -storepass testpass -keypass qwert",
:acceptable_exit_codes => 1)
end

end
Loading