diff --git a/config/components.yml b/config/components.yml index 76f681c5b8..dd4e9cb643 100644 --- a/config/components.yml +++ b/config/components.yml @@ -52,7 +52,8 @@ frameworks: - "JavaBuildpack::Framework::NewRelicAgent" - "JavaBuildpack::Framework::PlayFrameworkAutoReconfiguration" - "JavaBuildpack::Framework::PlayFrameworkJPAPlugin" - - "JavaBuildpack::Framework::PostgresqlJDBC" + - "JavaBuildpack::Framework::PostgresqlJDBC + - "JavaBuildpack::Framework::ProtectAppSecurityProvider" - "JavaBuildpack::Framework::SpringAutoReconfiguration" - "JavaBuildpack::Framework::SpringInsight" - "JavaBuildpack::Framework::YourKitProfiler" diff --git a/config/protect_app_security_provider.yml b/config/protect_app_security_provider.yml new file mode 100644 index 0000000000..1a4246313d --- /dev/null +++ b/config/protect_app_security_provider.yml @@ -0,0 +1,19 @@ +# Cloud Foundry Java Buildpack +# Copyright 2013-2016 the original author or authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Configuration for the ProtectApp Security Provider framework +--- +version: 8.4.+ +repository_root: http://files.cf-hsm.io/protectapp-installer diff --git a/docs/framework-protect_app_security_provider.md b/docs/framework-protect_app_security_provider.md new file mode 100644 index 0000000000..f8e9ace0de --- /dev/null +++ b/docs/framework-protect_app_security_provider.md @@ -0,0 +1,100 @@ +# ProtectApp Security Provider Framework +The ProtectApp Security Provider Framework causes an application to be automatically configured to work with a bound [ProtectApp Security Service][]. + + + + + + + + + + +
Detection CriterionExistence of a single bound ProtectApp Security Provider service. The existence of an ProtectApp Security service defined by the VCAP_SERVICES payload containing a service name, label or tag with protectapp as a substring. +
Tagsprotect-app-security-provider=<version>
+Tags are printed to standard output by the buildpack detect script + +## User-Provided Service +When binding to the ProtectApp Security Provider using a user-provided service, it must have name or tag with `protectapp` in it. The credential payload can contain the following entries: + +| Name | Description +| ---- | ----------- +| `client` | The client configuration +| `trustedcerts` | An array of certs containing trust information +| `NAE_IP.1` | A list of KeySecure server ips or hostnames to be used +| `***` | (Optional) Any additional entries will be applied as a system property appended to `-Dcom.ingrian.security.nae.` to allow full configuration of the library. + + +#### Client Configuration +| Name | Description +| ---- | ----------- +| `certificate` | A PEM encoded client certificate +| `private-key` | A PEM encoded client private key + +#### Trusted Certs Configuration +One or more PEM encoded certificate + + +### Example Credentials Payload +``` +{ + "client": { + "certificate": "-----BEGIN CERTIFICATE-----\n...\n-----END CERTIFICATE-----", + "private-key": "-----BEGIN RSA PRIVATE KEY-----\n...\n-----END RSA PRIVATE KEY-----", + }, + "trustedcerts": [ + "-----BEGIN CERTIFICATE-----\n...\n-----END CERTIFICATE-----" + , + "-----BEGIN CERTIFICATE-----\n...\n-----END CERTIFICATE-----" + ], + "NAE_IP.1": "192.168.1.25:192.168.1.26" + +} +``` + +### Creating Credential Payload +In order to create the credentials payload, you should collapse the JSON payload to a single line and set it like the following + +``` +$ cf create-user-provided-service protectapp -p '{"client":{"certificate":"-----BEGIN CERTIFICATE-----\n....\n-----END CERTIFICATE-----","private-key":"-----BEGIN RSA PRIVATE KEY-----\n...\n-----END RSA PRIVATE KEY-----\n"},"trustedcerts":["-----BEGIN CERTIFICATE-----\n...\n-----END CERTIFICATE-----"],NAE_IP.1":"172.17.34.100"} +``` + + +You may want to use a file for this + +Note the client portion is very exacting and needs line breaks in the body every 64 characters. + + 1. The file must contain: + -----BEGIN CERTIFICATE----- + on a separate line (i.e. it must be terminated with a newline). + 2. Each line of "gibberish" must be 64 characters wide. + 3. The file must end with: + -----END CERTIFICATE----- + and also be terminated with a newline. + 4. Don't save the cert text with Word. It must be in ASCII. + 5. Don't mix DOS and UNIX style line terminations. + +So, here are a few steps you can take to normalize your certificate: + 1. Run it through dos2unix + dos2unix cert.pem + 2. Run it through fold + fold -w 64 cert.pem + +## Configuration +For general information on configuring the buildpack, including how to specify configuration values through environment variables, refer to [Configuration and Extension][]. + +The framework can be configured by modifying the [`config/protect_app_security_provider.yml`][] file in the buildpack. The framework uses the [`Repository` utility support][repositories] and so it supports the [version syntax][] defined there. + +| Name | Description +| ---- | ----------- +| `repository_root` | The URL of the ProtectApp Security Provider repository index ([details][repositories]). +| `version` | Version of the ProtectApp Security Provider to use. + +### Additional Resources +The framework can also be configured by overlaying a set of resources on the default distribution. To do this, add files to the `resources/pprotect_app_security_provider` directory in the buildpack fork. + +[`config/protect_app_security_provider.yml`]: ../config/protect_app_security_provider.yml +[ProtectApp Security Service]: https://safenet.gemalto.com/data-encryption/protectapp-application-protection/ +[Configuration and Extension]: ../README.md#configuration-and-extension +[repositories]: extending-repositories.md +[version syntax]: extending-repositories.md#version-syntax-and-ordering diff --git a/lib/java_buildpack/framework/protect_app_security_provider.rb b/lib/java_buildpack/framework/protect_app_security_provider.rb new file mode 100644 index 0000000000..0edb4563c4 --- /dev/null +++ b/lib/java_buildpack/framework/protect_app_security_provider.rb @@ -0,0 +1,198 @@ +# Encoding: utf-8 +# Cloud Foundry Java Buildpack +# Copyright 2013-2016 the original author or authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +require 'fileutils' +require 'shellwords' +require 'tempfile' +require 'java_buildpack/component/versioned_dependency_component' +require 'java_buildpack/framework' +require 'java_buildpack/util/qualify_path' + +module JavaBuildpack + module Framework + + # Encapsulates the functionality for enabling zero-touch Safenet ProtectApp Java Security Provider support. + class ProtectAppSecurityProvider < JavaBuildpack::Component::VersionedDependencyComponent + include JavaBuildpack::Util + + # (see JavaBuildpack::Component::BaseComponent#compile) + def compile + download_zip + + # copy default properties file + @droplet.copy_resources + + credentials = @application.services.find_service(FILTER)['credentials'] + + write_client credentials['client'] + write_trusted_certs credentials['trustedcerts'] + + certificates.each_with_index { |certificate, index| add_certificate certificate, index } + + # setup java keystore with provided values + merge_clientcert + import_clientcert + + end + + # (see JavaBuildpack::Component::BaseComponent#release) + def release + credentials = @application.services.find_service(FILTER)['credentials'] + java_opts = @droplet.java_opts + configuration = {} + + filter_known_input(credentials, configuration) + + write_java_opts(java_opts, configuration) + @droplet.java_opts + .add_system_property('java.ext.dirs', ext_dirs) + .add_system_property('com.ingrian.security.nae.IngrianNAE_Properties_Conf_Filename', @droplet.sandbox + 'IngrianNAE.properties') + .add_system_property('com.ingrian.security.nae.Key_Store_Location', key_store) + .add_system_property('com.ingrian.security.nae.Key_Store_Password', password) + end + + protected + + # (see JavaBuildpack::Component::VersionedDependencyComponent#supports?) + def supports? + @application.services.one_service? FILTER, 'client', 'trustedcerts' + end + + private + + FILTER = /protectapp/.freeze + + private_constant :FILTER + + def merge_clientcert + + shell "openssl pkcs12 -export -in #{client_certificate} -inkey #{client_private_key} -name #{myclientcert} -out #{myp12} -passout pass:#{password}" + end + + def import_clientcert + + shell "#{keytool} -importkeystore -noprompt -destkeystore #{key_store} -deststorepass #{password} " \ + "-srckeystore #{myp12} -srcstorepass #{password} -srcstoretype pkcs12" \ + " -alias #{myclientcert}" + end + + def add_certificate(certificate, index) + + file = write_certificate certificate + shell "#{keytool} -importcert -noprompt -keystore #{key_store} -storepass #{password} " \ + "-file #{file.to_path} -alias certificate-#{index}" + end + + def certificates + certificates = [] + + certificate = nil + File.open(trusted_certificates).each_line do |line| + if line =~ /BEGIN CERTIFICATE/ + certificate = line + elsif line =~ /END CERTIFICATE/ + certificate += line + certificates << certificate + certificate = nil + elsif !certificate.nil? + certificate += line + end + end + + certificates + end + + def keytool + @droplet.java_home.root + 'bin/keytool' + end + + def password + 'nae-jks-password' + end + + def key_store + @droplet.sandbox + 'keystore.jks' + end + + def write_certificate(certificate) + file = Tempfile.new('certificate-') + file.write(certificate) + file.fsync + file + end + + def ext_dir + @droplet.sandbox + 'ext' + end + + def ext_dirs + "#{qualify_path(@droplet.java_home.root + 'lib/ext', @droplet.root)}:" \ + "#{qualify_path(ext_dir, @droplet.root)}" + end + + def client_certificate + File.join(Dir.tmpdir,'/client-certificate.pem') + end + + def client_private_key + File.join(Dir.tmpdir,'/client-private-key.pem') + end + + def trusted_certificates + File.join(Dir.tmpdir, 'trusted_certificates.pem') + end + + def myclientcert + 'myclientcert' + end + + def myp12 + File.join(Dir.tmpdir,'/clientwrap.p12') + end + + def write_client(client) + File.open(client_certificate, File::CREAT | File::WRONLY) do |f| + f.write "#{client['certificate']}\n" + end + + File.open(client_private_key, File::CREAT | File::WRONLY) do |f| + f.write "#{client['private-key']}\n" + end + end + + def write_trusted_certs(trusted_certs) + File.open(trusted_certificates,File::CREAT | File::WRONLY) do |f| + trusted_certs.each { |cert| f.write "#{cert}\n" } + end + end + + def filter_known_input(credentials, configuration) + credentials.each do |key, value| + if key != "client" and key != "trustedcerts" + configuration[key] = value + end + end + end + + def write_java_opts(java_opts, configuration2) + configuration2.each do |key, value| + java_opts.add_system_property("com.ingrian.security.nae.#{key}", value ) + end + end + + end + end +end diff --git a/resources/protect_app_security_provider/IngrianNAE.properties b/resources/protect_app_security_provider/IngrianNAE.properties new file mode 100644 index 0000000000..be9e361c04 --- /dev/null +++ b/resources/protect_app_security_provider/IngrianNAE.properties @@ -0,0 +1,43 @@ +Version=2.4 +NAE_IP.1= +NAE_Port=9000 +KMIP_Port=5696 +Protocol=ssl +Verify_SSL_Certificate=no +SSL_Handshake_Timeout= +Use_Persistent_Connections=yes +Size_of_Connection_Pool=300 +Load_Balancing_Algorithm=round-robin +Connection_Idle_Timeout=600000 +Unreachable_Server_Retry_Period=60000 +Maximum_Server_Retry_Period=0 +Connection_Timeout=30000 +Connection_Read_Timeout=7000 +Connection_Retry_Interval=600000 +Client_Cert_Alias= +Client_Cert_Passphrase= +Key_Store_Location= +Key_Store_Password= +Cluster_Synchronization_Delay=100 +Symmetric_Key_Cache_Enabled=no +Asymmetric_Key_Cache_Enabled=no +Symmetric_Key_Cache_Expiry=43200 +Local_Cipher_Cache_Expiry=-1 +Local_Crypto_Provider= +Persistent_Cache_Enabled=no +Persistent_Cache_Expiry_Keys=43200 +Persistent_Cache_Directory= +Persistent_Cache_Max_Size=100 +FIPS_Mode=off +Credentials_Encrypted=no +Passphrase_Encrypted=no +Log_Level=NONE +Log_File= +Log_Rotation=Daily +Log_GMT=no +Log_Size_Limit=100k +SysLog_IP= +SysLog_Port= +Log_Config_Advanced= +Key_non_exportable_policy=no +Log_MaxBackupIndex=-1 diff --git a/spec/fixtures/stub-protect-app-security-provider.zip b/spec/fixtures/stub-protect-app-security-provider.zip new file mode 100644 index 0000000000..b116d22593 Binary files /dev/null and b/spec/fixtures/stub-protect-app-security-provider.zip differ diff --git a/spec/java_buildpack/framework/protect_app_security_provider_spec.rb b/spec/java_buildpack/framework/protect_app_security_provider_spec.rb new file mode 100644 index 0000000000..f978d2344c --- /dev/null +++ b/spec/java_buildpack/framework/protect_app_security_provider_spec.rb @@ -0,0 +1,84 @@ +# Encoding: utf-8 +# Cloud Foundry Java Buildpack +# Copyright 2013-2016 the original author or authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +require 'spec_helper' +require 'component_helper' +require 'java_buildpack/framework/pa_security_provider' + +describe JavaBuildpack::Framework::ProtectAppSecurityProvider do + include_context 'component_helper' + + it 'does not detect without protectapp-n/a service' do + expect(component.detect).to be_nil + end + + context do + + before do + allow(services).to receive(:one_service?).with(/protectapp/, 'client', 'trustedcerts').and_return(true) + + end + + it 'detects with protectapp-n/a service' do + expect(component.detect).to eq("protectapp-security-provider=#{version}") + end + + it 'copies resources', + cache_fixture: 'stub-protectapp-security-provider.zip' do + + component.compile + + expect(sandbox + 'IngrianNAE.properties').to exist + end + + it 'unpacks the protectapp zip', + cache_fixture: 'stub-protectapp-security-provider.zip' do + + component.compile + + expect(sandbox + 'IngrianNAE-#{version}.jar').to exist + expect(sandbox + 'Ingrianlog4j-core-2.1.jar').to exist + expect(sandbox + 'Ingrianlog4j-api-2.1.jar').to exist + end + + it 'write certificate files', + cache_fixture: 'stub-protectapp-security-provider.zip' do + + component.compile + + expect(sandbox + 'client-certificate.pem').to exist + expect(sandbox + 'client-private-key.pem').to exist + expect(sandbox + 'trusted_certificates.pem').to exist + expect(sandbox + 'clientwrap.p12').to exist + + # transfer to keystore + expect(sandbox + 'keystore.jks').to exist + + end + + + it 'updates JAVA_OPTS with additional options' do + allow(services).to receive(:find_service).and_return('credentials' => { '#{NAE_IP.1}' => 'server_ip', + '#{foo}' => 'bar' }) + + component.release + + expect(java_opts).to include('-Dcom.ingrian.security.nae.NAE_IP.1=server_ip') + expect(java_opts).to include('-Dcom.ingrian.security.nae.foo=bar') + end + + end +end