Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
108 changes: 67 additions & 41 deletions lib/java_buildpack/framework/contrast_security_agent.rb
Original file line number Diff line number Diff line change
Expand Up @@ -28,23 +28,29 @@ module Framework
class ContrastSecurityAgent < JavaBuildpack::Component::VersionedDependencyComponent
include JavaBuildpack::Util

def initialize(context)
super(context)
@logger = JavaBuildpack::Logging::LoggerFactory.instance.get_logger ContrastSecurityAgent
end

# (see JavaBuildpack::Component::BaseComponent#compile)
def compile
download_jar
@droplet.copy_resources

write_configuration @application.services.find_service(FILTER, API_KEY, SERVICE_KEY, TEAMSERVER_URL,
USERNAME)['credentials']
end

# (see JavaBuildpack::Component::BaseComponent#release)
def release
@droplet.java_opts.add_system_property('contrast.override.appname', application_name) unless appname_exist?
# Fetch the credentials and settings
credentials = @application.services.find_service(FILTER, API_KEY, SERVICE_KEY, TEAMSERVER_URL,
USERNAME)['credentials']

# Add the Contrast config via env vars
add_config_to_env credentials

# Add the -javaagent option to cause the agent to start with the JVM
@droplet.java_opts
.add_system_property('contrast.dir', '$TMPDIR')
.add_preformatted_options("-javaagent:#{qualify_path(@droplet.sandbox + jar_name, @droplet.root)}=" \
"#{qualify_path(contrast_config, @droplet.root)}")
.add_preformatted_options("-javaagent:#{qualify_path(@droplet.sandbox + jar_name, @droplet.root)}")
end

protected
Expand Down Expand Up @@ -78,40 +84,14 @@ def supports?
private_constant :API_KEY, :FILTER, :INFLECTION_VERSION, :PLUGIN_PACKAGE, :SERVICE_KEY, :TEAMSERVER_URL,
:USERNAME

def add_contrast(doc, credentials)
contrast = doc.add_element('contrast')
(contrast.add_element 'id').add_text('default')
(contrast.add_element 'global-key').add_text(credentials[API_KEY])
(contrast.add_element 'url').add_text("#{credentials[TEAMSERVER_URL]}/Contrast/s/")
(contrast.add_element 'results-mode').add_text('never')

add_user contrast, credentials
add_plugins contrast
end

def add_plugins(contrast)
plugin_group = contrast.add_element('plugins')

(plugin_group.add_element 'plugin').add_text("#{PLUGIN_PACKAGE}.security.SecurityPlugin")
(plugin_group.add_element 'plugin').add_text("#{PLUGIN_PACKAGE}.architecture.ArchitecturePlugin")
(plugin_group.add_element 'plugin').add_text("#{PLUGIN_PACKAGE}.appupdater.ApplicationUpdatePlugin")
(plugin_group.add_element 'plugin').add_text("#{PLUGIN_PACKAGE}.sitemap.SitemapPlugin")
(plugin_group.add_element 'plugin').add_text("#{PLUGIN_PACKAGE}.frameworks.FrameworkSupportPlugin")
(plugin_group.add_element 'plugin').add_text("#{PLUGIN_PACKAGE}.http.HttpPlugin")
end

def add_user(contrast, credentials)
user = contrast.add_element('user')
(user.add_element 'id').add_text(credentials[USERNAME])
(user.add_element 'key').add_text(credentials[SERVICE_KEY])
end

def application_name
@application.details['application_name'] || 'ROOT'
end

def appname_exist?
@droplet.java_opts.any? { |java_opt| java_opt =~ /contrast.override.appname/ }
@droplet.java_opts.any? do |java_opt|
java_opt =~ /contrast\.override\.appname/ || java_opt =~ /contrast\.application\.name/
end
end

def contrast_config
Expand All @@ -122,16 +102,62 @@ def short_version
"#{@version[0]}.#{@version[1]}.#{@version[2]}"
end

def write_configuration(credentials)
doc = REXML::Document.new
# Add Contrast config to the env variables of the droplet.
def add_config_to_env(credentials)
env_vars = @droplet.environment_variables

# Add any extra environment variables that start with CONTRAST__
process_extra_env_vars credentials, env_vars

# Add the config in the backwards compatible old format setting name
add_env_var env_vars, 'CONTRAST__API__API_KEY', credentials[API_KEY]
add_env_var env_vars, 'CONTRAST__API__SERVICE_KEY', credentials[SERVICE_KEY]
add_env_var env_vars, 'CONTRAST__API__URL', "#{credentials[TEAMSERVER_URL]}/Contrast"
add_env_var env_vars, 'CONTRAST__API__USER_NAME', credentials[USERNAME]

add_contrast doc, credentials
add_env_var env_vars, 'CONTRAST__AGENT__CONTRAST_WORKING_DIR', '$TMPDIR'

contrast_config.open(File::CREAT | File::WRONLY) { |f| f.write(doc) }
app_name = application_name
add_env_var env_vars, 'CONTRAST__APPLICATION__NAME', app_name unless appname_exist?

# Add the config for the proxy, if it exists
add_proxy_config credentials, env_vars
end

# Add any generic new config from the broker, for any entry that starts with CONTRAST__ add to the env
# The intention is to allow the broker to add any new config that it wants to, without needing to modify the
# buildpack
def process_extra_env_vars(credentials, env_vars)
credentials.each do |key, value|
# Add any that start with CONTRAST__ AND non-empty values
matched = key.match?(/^CONTRAST__/) && !value.to_s.empty?
add_env_var env_vars, key, value if matched
end
end

def add_env_var(env_vars, key, value)
env_vars.add_environment_variable key, value
end

def add_proxy_config(credentials, env_vars)
host_set = credentials_value_set?(credentials, 'proxy_host')
add_env_var env_vars, 'CONTRAST__API__PROXY__HOST', credentials['proxy_host'] if host_set

port_set = credentials_value_set?(credentials, 'proxy_port')
add_env_var env_vars, 'CONTRAST__API__PROXY__PORT', credentials['proxy_port'] if port_set

pass_set = credentials_value_set?(credentials, 'proxy_pass')
add_env_var env_vars, 'CONTRAST__API__PROXY__PASS', credentials['proxy_pass'] if pass_set

user_set = credentials_value_set?(credentials, 'proxy_user')
add_env_var env_vars, 'CONTRAST__API__PROXY__USER', credentials['proxy_user'] if user_set
end

def credentials_value_set?(credentials, key)
!credentials[key].to_s.empty?
end

end

end

end
137 changes: 121 additions & 16 deletions spec/java_buildpack/framework/contrast_security_agent_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,19 @@
before do
allow(services).to receive(:one_service?).with(/contrast-security/, 'api_key', 'service_key', 'teamserver_url',
'username').and_return(true)
allow(services).to receive(:find_service).and_return('credentials' => { 'teamserver_url' => 'a_url',
allow(services).to receive(:find_service).and_return('credentials' => { 'teamserver_url' => 'https://host.com',
'username' => 'contrast_user',
'api_key' => 'api_test',
'service_key' => 'service_test' })
'api_key' => 'api_key_test',
'service_key' => 'service_key_test',
'proxy_host' => 'proxy_host_test',
'proxy_port' => 8080,
'proxy_user' => 'proxy_user_test',
'proxy_pass' => 'proxy_password_test',
'CONTRAST__INVENTORY__LIBRARY_DIRS' =>
'/lib/dir',
'CONTRAST__API__TIMEOUT_MS' => '30000',
'CONTRAST__API__URL' =>
'invalid_override_url' })
end

it 'detects with contrastsecurity service' do
Expand Down Expand Up @@ -70,20 +79,10 @@
expect(java_opts.to_s).to include('java-agent-3.4.3.jar')
end

it 'updates JAVA_OPTS' do
it 'updates JAVA_OPTS to enable the agent' do
component.release

expect(java_opts).to include('-javaagent:$PWD/.java-buildpack/contrast_security_agent/contrast-engine-0.0.0.jar' \
'=$PWD/.java-buildpack/contrast_security_agent/contrast.config')
expect(java_opts).to include('-Dcontrast.dir=$TMPDIR')
expect(java_opts).to include('-Dcontrast.override.appname=test-application-name')
end

it 'created contrast.config',
cache_fixture: 'stub-contrast-security-agent.jar' do

component.compile
expect(sandbox + 'contrast.config').to exist
expect(java_opts).to include('-javaagent:$PWD/.java-buildpack/contrast_security_agent/contrast-engine-0.0.0.jar')
end

it 'does not override app name if there is an existing appname' do
Expand All @@ -92,7 +91,113 @@
component.release

expect(java_opts).to include('-Dcontrast.override.appname=NAME_ALREADY_OVERRIDDEN')
expect(java_opts).not_to include('-Dcontrast.override.appname=test-application-name')
expect(environment_variables).not_to include('CONTRAST__APPLICATION__NAME=test-application-name')
end

it 'does not override app name if there is an existing name' do
java_opts.add_system_property('contrast.application.name', 'NAME_ALREADY_OVERRIDDEN')

component.release

expect(java_opts).to include('-Dcontrast.application.name=NAME_ALREADY_OVERRIDDEN')
expect(environment_variables).not_to include('CONTRAST__APPLICATION__NAME=test-application-name')
end

it 'sets in env vars the credentials for connecting to Contrast UI' do
component.release

expect(environment_variables).to include('CONTRAST__API__URL=https://host.com/Contrast')
expect(environment_variables).to include('CONTRAST__API__API_KEY=api_key_test')
expect(environment_variables).to include('CONTRAST__API__SERVICE_KEY=service_key_test')
expect(environment_variables).to include('CONTRAST__API__USER_NAME=contrast_user')
end

it 'sets in env vars the working directory for Contrast' do
component.release

expect(environment_variables).to include('CONTRAST__AGENT__CONTRAST_WORKING_DIR=$TMPDIR')
end

it 'sets in env vars the proxy settings when using a proxy' do
component.release

expect(environment_variables).to include('CONTRAST__API__PROXY__HOST=proxy_host_test')
expect(environment_variables).to include('CONTRAST__API__PROXY__PORT=8080')
expect(environment_variables).to include('CONTRAST__API__PROXY__USER=proxy_user_test')
expect(environment_variables).to include('CONTRAST__API__PROXY__PASS=proxy_password_test')
end

it 'sets in env vars any other CONTRAST_ settings that exist' do
component.release

# Sets them without knowing what they are ahead of time
expect(environment_variables).to include('CONTRAST__INVENTORY__LIBRARY_DIRS=/lib/dir')
expect(environment_variables).to include('CONTRAST__API__TIMEOUT_MS=30000')
end

it 'specifically named settings override any generic CONTRAST__ settings' do
component.release

# The standard property `teamserver_url` was set along with CONTRAST__API__URL, so the former must be used
expect(environment_variables).to include('CONTRAST__API__URL=https://host.com/Contrast')
end

end

# Test with different settings from the service broker
context do

before do
allow(services).to receive(:one_service?).with(/contrast-security/, 'api_key', 'service_key', 'teamserver_url',
'username').and_return(true)
allow(services).to receive(:find_service).and_return('credentials' => { 'teamserver_url' => 'https://host.com',
'username' => 'contrast_user',
'api_key' => 'api_key_test',
'service_key' => 'service_key_test' })
end

it 'proxy settings not applied to env vars when not set by broker' do
component.release

# convert to string to search for the env var by name
env_var_str = environment_variables.to_s

expect(env_var_str).not_to include('CONTRAST__API__PROXY__HOST')
expect(env_var_str).not_to include('CONTRAST__API__PROXY__PORT')
expect(env_var_str).not_to include('CONTRAST__API__PROXY__USER')
expect(env_var_str).not_to include('CONTRAST__API__PROXY__PASS')
end

end

# Test with null and empty values for the proxy settings
context do

before do
allow(services).to receive(:one_service?).with(/contrast-security/, 'api_key', 'service_key', 'teamserver_url',
'username').and_return(true)
allow(services).to receive(:find_service).and_return('credentials' => { 'teamserver_url' => 'https://host.com',
'username' => 'contrast_user',
'api_key' => 'api_key_test',
'service_key' => 'service_key_test',
# Test nil and empty values
'proxy_host' => nil,
'CONTRAST__IGNORE_01' => '',
'CONTRAST__IGNORE_02' => nil })
end

it 'proxy settings handle nil and empty' do
component.release

# convert to string to search for the env var by name
env_var_str = environment_variables.to_s

expect(env_var_str).not_to include('CONTRAST__API__PROXY__HOST')
expect(env_var_str).not_to include('CONTRAST__API__PROXY__PORT')
expect(env_var_str).not_to include('CONTRAST__API__PROXY__USER')
expect(env_var_str).not_to include('CONTRAST__API__PROXY__PASS')
expect(env_var_str).not_to include('CONTRAST__IGNORE_01')
expect(env_var_str).not_to include('CONTRAST__IGNORE_02')
end

end
Expand Down