diff --git a/.github/workflows/test-agent.yml b/.github/workflows/test-agent.yml index d40384e13..dae0044ca 100644 --- a/.github/workflows/test-agent.yml +++ b/.github/workflows/test-agent.yml @@ -158,6 +158,12 @@ jobs: -v "${GITHUB_WORKSPACE}/php-agent":"/usr/local/src/newrelic-php-agent" -e ENABLE_COVERAGE=${{matrix.codecov}} $IMAGE_NAME:$IMAGE_TAG-${{matrix.php}}-${{matrix.platform}}-$IMAGE_VERSION make agent-${{ steps.get-check-variant.outputs.AGENT_CHECK_VARIANT }} + - name: Run newrelic-install tests + run: > + docker run --rm --platform linux/${{matrix.arch}} + -v "${GITHUB_WORKSPACE}/php-agent":"/usr/local/src/newrelic-php-agent" + -e ENABLE_COVERAGE=${{matrix.codecov}} + $IMAGE_NAME:$IMAGE_TAG-${{matrix.php}}-${{matrix.platform}}-$IMAGE_VERSION make verify-inject-script - name: Save newrelic.so for integration tests uses: actions/upload-artifact@v4 with: diff --git a/Makefile b/Makefile index 0e95237d5..b68b8c871 100644 --- a/Makefile +++ b/Makefile @@ -205,12 +205,20 @@ daemon-clean: # missing from the $PATH. # -installer: bin/newrelic-install bin/newrelic-iutil | bin/ +installer: bin/newrelic-install bin/newrelic-install-inject-envvars.php bin/newrelic-install-php-cfg-mappings.php bin/newrelic-iutil | bin/ bin/newrelic-install: agent/newrelic-install.sh Makefile VERSION | bin/ sed -e "/nrversion:=/s,UNSET,$(AGENT_VERSION)," $< > $@ chmod 755 $@ +bin/newrelic-install-inject-envvars.php: agent/newrelic-install-inject-envvars.php Makefile | bin/ + cp $< $@ + chmod 755 $@ + +bin/newrelic-install-php-cfg-mappings.php: agent/newrelic-install-php-cfg-mappings.php Makefile | bin/ + cp $< $@ + chmod 755 $@ + bin/newrelic-iutil: agent/install-util.c Makefile VERSION | bin/ $(CC) -DNR_VERSION="\"$(AGENT_VERSION)\"" $(CPPFLAGS) $(CFLAGS) -o $@ $< $(LDFLAGS) @@ -362,6 +370,19 @@ integration-events-limits: bin/integration_runner echo "# PHP=$${PHP}"; \ done; +# +# Verify env var injection script mapping has all INI values +# Requires test APIs to be enabled in agent +# +.PHONY: verify-inject-script +verify-inject-script: agent + (cd tests/newrelic-installer; \ + php -d extension=../../agent/.libs/newrelic.so \ + verify_inject_envvars_script_mapping.php) + (cd tests/newrelic-installer; \ + php -d extension=../../agent/.libs/newrelic.so \ + verify_inject_envvars_script_injection.php) + # # Code profiling # diff --git a/agent/newrelic-install-inject-envvars.php b/agent/newrelic-install-inject-envvars.php new file mode 100644 index 000000000..3c5efa756 --- /dev/null +++ b/agent/newrelic-install-inject-envvars.php @@ -0,0 +1,118 @@ += 7.0\n"); + exit(1); +} + +/* Mapping from INI name to environment variable name + * Expected form: + * define('INI_ENVVAR_MAP', array(....)); + */ +require "newrelic-install-php-cfg-mappings.php"; + +/* Verify that ini/envvar mapping was injected and is defined */ +if (!defined('INI_ENVVAR_MAP')) { + failure("INI/ENVVAR mapping was not detected - cannot proceed!"); + exit(1); +} + +/* Verify input INI file exists */ +if (3 != $argc) { + failure(" missing required arguments\n" . + "Usage: newrelic-install-inject-envvars.php \n" . + " Existing INI file\n" . + " Output INI file with injected env var values\n\n"); + exit(1); +} + +$ini_filename = $argv[1]; +if (!file_exists($ini_filename)) { + failure("Input INI file \"$ini_filename\" does not exist!"); + exit(1); +} + +$out_ini_filename = $argv[2]; +if (file_exists($out_ini_filename)) { + failure("Output INI file \"$out_ini_filename\" exists - will not overwrite!"); + exit(1); +} + +$data = file_get_contents(($ini_filename)); +if (!$data) { + failure("Could not read INI file \"$ini_filename\"!"); + exit(1); +} + +$pattern = array(); +$replace = array(); +$modified = array(); + +/* Go through all INI keys and see if the environment variable is defined */ +foreach (INI_ENVVAR_MAP as $ini_name => $env_name) { + $env_value = getenv($env_name); + if (false != $env_value) { + array_push($pattern, generate_regex($ini_name)); + array_push($replace, "$ini_name=$env_value"); + array_push($modified, $env_name); + } +} + +if (0 < count($pattern)) { + /* replace all values which exist in existing INI file */ + $data = preg_replace($pattern, $replace, $data); + + /* append values which will did not already exist in file */ + $missing = ""; + foreach(INI_ENVVAR_MAP as $ini_name => $env_name) { + $env_value = getenv($env_name); + if (false != $env_value) { + if (!preg_match(generate_regex($ini_name), $data)) { + $missing .= "/* Value injected from env var $env_name*/\n"; + $missing .= "$ini_name=$env_value\n"; + } + } + } + $data .= $missing; + + echo "opening $out_ini_filename\n"; + $fh = fopen($out_ini_filename, "w"); + if (!$fh) { + failure("Unable to write out modified INI file $out_ini_filename"); + exit(1); + } + fwrite($fh, $data); + fclose($fh); + + echo "$out_ini_filename created with values from the following environment variables:\n"; + foreach ($modified as $value) { + echo " $value\n"; + } +} else { + echo "$out_ini_filename contains no modified values as no relevant environment variables detected"; +} diff --git a/agent/newrelic-install-php-cfg-mappings.php b/agent/newrelic-install-php-cfg-mappings.php new file mode 100644 index 000000000..cbd25bba1 --- /dev/null +++ b/agent/newrelic-install-php-cfg-mappings.php @@ -0,0 +1,138 @@ +"NEW_RELIC_LOGFILE", +"newrelic.loglevel"=>"NEW_RELIC_LOGLEVEL", +"newrelic.high_security"=>"NEW_RELIC_HIGH_SECURITY", +"newrelic.feature_flag"=>"NEW_RELIC_FEATURE_FLAG", +"newrelic.preload_framework_library_detection"=>"NEW_RELIC_PRELOAD_FRAMEWORK_LIBRARY_DETECTION", +"newrelic.daemon.auditlog"=>"NEW_RELIC_DAEMON_AUDITLOG", +"newrelic.daemon.logfile"=>"NEW_RELIC_DAEMON_LOGFILE", +"newrelic.daemon.loglevel"=>"NEW_RELIC_DAEMON_LOGLEVEL", +"newrelic.daemon.port"=>"NEW_RELIC_DAEMON_PORT", +"newrelic.daemon.address"=>"NEW_RELIC_DAEMON_ADDRESS", +"newrelic.daemon.ssl_ca_bundle"=>"NEW_RELIC_DAEMON_SSL_CA_BUNDLE", +"newrelic.daemon.ssl_ca_path"=>"NEW_RELIC_DAEMON_SSL_CA_PATH", +"newrelic.daemon.collector_host"=>"NEW_RELIC_DAEMON_COLLECTOR_HOST", +"newrelic.daemon.proxy"=>"NEW_RELIC_DAEMON_PROXY", +"newrelic.daemon.location"=>"NEW_RELIC_DAEMON_LOCATION", +"newrelic.daemon.pidfile"=>"NEW_RELIC_DAEMON_PIDFILE", +"newrelic.daemon.dont_launch"=>"NEW_RELIC_DAEMON_DONT_LAUNCH", +"newrelic.daemon.app_timeout"=>"NEW_RELIC_DAEMON_APP_TIMEOUT", +"newrelic.daemon.app_connect_timeout"=>"NEW_RELIC_DAEMON_APP_CONNECT_TIMEOUT", +"newrelic.daemon.start_timeout"=>"NEW_RELIC_DAEMON_START_TIMEOUT", +"newrelic.daemon.utilization.detect_aws"=>"NEW_RELIC_DAEMON_UTILIZATION_DETECT_AWS", +"newrelic.daemon.utilization.detect_azure"=>"NEW_RELIC_DAEMON_UTILIZATION_DETECT_AZURE", +"newrelic.daemon.utilization.detect_gcp"=>"NEW_RELIC_DAEMON_UTILIZATION_DETECT_GCP", +"newrelic.daemon.utilization.detect_pcf"=>"NEW_RELIC_DAEMON_UTILIZATION_DETECT_PCF", +"newrelic.daemon.utilization.detect_docker"=>"NEW_RELIC_DAEMON_UTILIZATION_DETECT_DOCKER", +"newrelic.daemon.utilization.detect_kubernetes"=>"NEW_RELIC_DAEMON_UTILIZATION_DETECT_KUBERNETES", +"newrelic.daemon.special.integration"=>"NEW_RELIC_DAEMON_SPECIAL_INTEGRATION", +"newrelic.special"=>"NEW_RELIC_SPECIAL", +"newrelic.special.appinfo_timeout"=>"NEW_RELIC_SPECIAL_APPINFO_TIMEOUT", +"newrelic.special.disable_instrumentation"=>"NEW_RELIC_SPECIAL_DISABLE_INSTRUMENTATION", +"newrelic.special.expensive_node_min"=>"NEW_RELIC_SPECIAL_EXPENSIVE_NODE_MIN", +"newrelic.special.enable_extension_instrumentation"=>"NEW_RELIC_SPECIAL_ENABLE_EXTENSION_INSTRUMENTATION", +"newrelic.daemon.special.curl_verbose"=>"NEW_RELIC_DAEMON_SPECIAL_CURL_VERBOSE", +"newrelic.enabled"=>"NEW_RELIC_ENABLED", +"newrelic.license"=>"NEW_RELIC_LICENSE", +"newrelic.appname"=>"NEW_RELIC_APPNAME", +"newrelic.webtransaction.name.remove_trailing_path"=>"NEW_RELIC_WEBTRANSACTION_NAME_REMOVE_TRAILING_PATH", +"newrelic.framework.drupal.modules"=>"NEW_RELIC_FRAMEWORK_DRUPAL_MODULES", +"newrelic.framework.wordpress.hooks"=>"NEW_RELIC_FRAMEWORK_WORDPRESS_HOOKS", +"newrelic.framework.wordpress.hooks.options"=>"NEW_RELIC_FRAMEWORK_WORDPRESS_HOOKS_OPTIONS", +"newrelic.framework.wordpress.hooks.threshold"=>"NEW_RELIC_FRAMEWORK_WORDPRESS_HOOKS_THRESHOLD", +"newrelic.framework.wordpress.hooks_skip_filename"=>"NEW_RELIC_FRAMEWORK_WORDPRESS_HOOKS_SKIP_FILENAME", +"newrelic.framework"=>"NEW_RELIC_FRAMEWORK", +"newrelic.cross_application_tracer.enabled"=>"NEW_RELIC_CROSS_APPLICATION_TRACER_ENABLED", +"newrelic.special.max_nesting_level"=>"NEW_RELIC_SPECIAL_MAX_NESTING_LEVEL", +"newrelic.labels"=>"NEW_RELIC_LABELS", +"newrelic.process_host.display_name"=>"NEW_RELIC_PROCESS_HOST_DISPLAY_NAME", +"newrelic.webtransaction.name.files"=>"NEW_RELIC_WEBTRANSACTION_NAME_FILES", +"newrelic.guzzle.enabled"=>"NEW_RELIC_GUZZLE_ENABLED", +"newrelic.attributes.enabled"=>"NEW_RELIC_ATTRIBUTES_ENABLED", +"newrelic.attributes.include"=>"NEW_RELIC_ATTRIBUTES_INCLUDE", +"newrelic.attributes.exclude"=>"NEW_RELIC_ATTRIBUTES_EXCLUDE", +"newrelic.capture_params"=>"NEW_RELIC_CAPTURE_PARAMS", +"newrelic.ignored_params"=>"NEW_RELIC_IGNORED_PARAMS", +"newrelic.transaction_tracer.capture_attributes"=>"NEW_RELIC_TRANSACTION_TRACER_CAPTURE_ATTRIBUTES", +"newrelic.transaction_tracer.attributes.enabled"=>"NEW_RELIC_TRANSACTION_TRACER_ATTRIBUTES_ENABLED", +"newrelic.transaction_tracer.attributes.include"=>"NEW_RELIC_TRANSACTION_TRACER_ATTRIBUTES_INCLUDE", +"newrelic.transaction_tracer.attributes.exclude"=>"NEW_RELIC_TRANSACTION_TRACER_ATTRIBUTES_EXCLUDE", +"newrelic.transaction_tracer.enabled"=>"NEW_RELIC_TRANSACTION_TRACER_ENABLED", +"newrelic.transaction_tracer.explain_enabled"=>"NEW_RELIC_TRANSACTION_TRACER_EXPLAIN_ENABLED", +"newrelic.transaction_tracer.detail"=>"NEW_RELIC_TRANSACTION_TRACER_DETAIL", +"newrelic.transaction_tracer.max_segments_cli"=>"NEW_RELIC_TRANSACTION_TRACER_MAX_SEGMENTS_CLI", +"newrelic.transaction_tracer.max_segments_web"=>"NEW_RELIC_TRANSACTION_TRACER_MAX_SEGMENTS_WEB", +"newrelic.transaction_tracer.slow_sql"=>"NEW_RELIC_TRANSACTION_TRACER_SLOW_SQL", +"newrelic.transaction_tracer.threshold"=>"NEW_RELIC_TRANSACTION_TRACER_THRESHOLD", +"newrelic.transaction_tracer.explain_threshold"=>"NEW_RELIC_TRANSACTION_TRACER_EXPLAIN_THRESHOLD", +"newrelic.transaction_tracer.stack_trace_threshold"=>"NEW_RELIC_TRANSACTION_TRACER_STACK_TRACE_THRESHOLD", +"newrelic.transaction_tracer.record_sql"=>"NEW_RELIC_TRANSACTION_TRACER_RECORD_SQL", +"newrelic.transaction_tracer.gather_input_queries"=>"NEW_RELIC_TRANSACTION_TRACER_GATHER_INPUT_QUERIES", +"newrelic.transaction_tracer.internal_functions_enabled"=>"NEW_RELIC_TRANSACTION_TRACER_INTERNAL_FUNCTIONS_ENABLED", +"newrelic.error_collector.enabled"=>"NEW_RELIC_ERROR_COLLECTOR_ENABLED", +"newrelic.error_collector.ignore_user_exception_handler"=>"NEW_RELIC_ERROR_COLLECTOR_IGNORE_USER_EXCEPTION_HANDLER", +"newrelic.error_collector.ignore_errors"=>"NEW_RELIC_ERROR_COLLECTOR_IGNORE_ERRORS", +"newrelic.error_collector.ignore_exceptions"=>"NEW_RELIC_ERROR_COLLECTOR_IGNORE_EXCEPTIONS", +"newrelic.error_collector.record_database_errors"=>"NEW_RELIC_ERROR_COLLECTOR_RECORD_DATABASE_ERRORS", +"newrelic.error_collector.prioritize_api_errors"=>"NEW_RELIC_ERROR_COLLECTOR_PRIORITIZE_API_ERRORS", +"newrelic.error_collector.capture_attributes"=>"NEW_RELIC_ERROR_COLLECTOR_CAPTURE_ATTRIBUTES", +"newrelic.error_collector.attributes.enabled"=>"NEW_RELIC_ERROR_COLLECTOR_ATTRIBUTES_ENABLED", +"newrelic.error_collector.attributes.include"=>"NEW_RELIC_ERROR_COLLECTOR_ATTRIBUTES_INCLUDE", +"newrelic.error_collector.attributes.exclude"=>"NEW_RELIC_ERROR_COLLECTOR_ATTRIBUTES_EXCLUDE", +"newrelic.analytics_events.enabled"=>"NEW_RELIC_ANALYTICS_EVENTS_ENABLED", +"newrelic.analytics_events.capture_attributes"=>"NEW_RELIC_ANALYTICS_EVENTS_CAPTURE_ATTRIBUTES", +"newrelic.transaction_events.enabled"=>"NEW_RELIC_TRANSACTION_EVENTS_ENABLED", +"newrelic.error_collector.capture_events"=>"NEW_RELIC_ERROR_COLLECTOR_CAPTURE_EVENTS", +"newrelic.transaction_events.attributes.enabled"=>"NEW_RELIC_TRANSACTION_EVENTS_ATTRIBUTES_ENABLED", +"newrelic.transaction_events.attributes.include"=>"NEW_RELIC_TRANSACTION_EVENTS_ATTRIBUTES_INCLUDE", +"newrelic.transaction_events.attributes.exclude"=>"NEW_RELIC_TRANSACTION_EVENTS_ATTRIBUTES_EXCLUDE", +"newrelic.custom_insights_events.enabled"=>"NEW_RELIC_CUSTOM_INSIGHTS_EVENTS_ENABLED", +"newrelic.custom_events.max_samples_stored"=>"NEW_RELIC_CUSTOM_EVENTS_MAX_SAMPLES_STORED", +"newrelic.synthetics.enabled"=>"NEW_RELIC_SYNTHETICS_ENABLED", +"newrelic.datastore_tracer.instance_reporting.enabled"=>"NEW_RELIC_DATASTORE_TRACER_INSTANCE_REPORTING_ENABLED", +"newrelic.datastore_tracer.database_name_reporting.enabled"=>"NEW_RELIC_DATASTORE_TRACER_DATABASE_NAME_REPORTING_ENABLED", +"newrelic.phpunit_events.enabled"=>"NEW_RELIC_PHPUNIT_EVENTS_ENABLED", +"newrelic.browser_monitoring.auto_instrument"=>"NEW_RELIC_BROWSER_MONITORING_AUTO_INSTRUMENT", +"newrelic.browser_monitoring.debug"=>"NEW_RELIC_BROWSER_MONITORING_DEBUG", +"newrelic.browser_monitoring.loader"=>"NEW_RELIC_BROWSER_MONITORING_LOADER", +"newrelic.browser_monitoring.capture_attributes"=>"NEW_RELIC_BROWSER_MONITORING_CAPTURE_ATTRIBUTES", +"newrelic.browser_monitoring.attributes.enabled"=>"NEW_RELIC_BROWSER_MONITORING_ATTRIBUTES_ENABLED", +"newrelic.browser_monitoring.attributes.include"=>"NEW_RELIC_BROWSER_MONITORING_ATTRIBUTES_INCLUDE", +"newrelic.browser_monitoring.attributes.exclude"=>"NEW_RELIC_BROWSER_MONITORING_ATTRIBUTES_EXCLUDE", +"newrelic.webtransaction.name.functions"=>"NEW_RELIC_WEBTRANSACTION_NAME_FUNCTIONS", +"newrelic.transaction_tracer.custom"=>"NEW_RELIC_TRANSACTION_TRACER_CUSTOM", +"newrelic.security_policies_token"=>"NEW_RELIC_SECURITY_POLICIES_TOKEN", +"newrelic.allow_raw_exception_messages"=>"NEW_RELIC_ALLOW_RAW_EXCEPTION_MESSAGES", +"newrelic.custom_parameters_enabled"=>"NEW_RELIC_CUSTOM_PARAMETERS_ENABLED", +"newrelic.distributed_tracing_enabled"=>"NEW_RELIC_DISTRIBUTED_TRACING_ENABLED", +"newrelic.distributed_tracing_exclude_newrelic_header"=>"NEW_RELIC_DISTRIBUTED_TRACING_EXCLUDE_NEWRELIC_HEADER", +"newrelic.span_events_enabled"=>"NEW_RELIC_SPAN_EVENTS_ENABLED", +"newrelic.span_events.max_samples_stored"=>"NEW_RELIC_SPAN_EVENTS_MAX_SAMPLES_STORED", +"newrelic.span_events.attributes.enabled"=>"NEW_RELIC_SPAN_EVENTS_ATTRIBUTES_ENABLED", +"newrelic.span_events.attributes.include"=>"NEW_RELIC_SPAN_EVENTS_ATTRIBUTES_INCLUDE", +"newrelic.span_events.attributes.exclude"=>"NEW_RELIC_SPAN_EVENTS_ATTRIBUTES_EXCLUDE", +"newrelic.infinite_tracing.trace_observer.host"=>"NEW_RELIC_INFINITE_TRACING_TRACE_OBSERVER_HOST", +"newrelic.infinite_tracing.trace_observer.port"=>"NEW_RELIC_INFINITE_TRACING_TRACE_OBSERVER_PORT", +"newrelic.infinite_tracing.span_events.queue_size"=>"NEW_RELIC_INFINITE_TRACING_SPAN_EVENTS_QUEUE_SIZE", +"newrelic.infinite_tracing.span_events.agent_queue.size"=>"NEW_RELIC_INFINITE_TRACING_SPAN_EVENTS_AGENT_QUEUE_SIZE", +"newrelic.infinite_tracing.span_events.agent_queue.timeout"=>"NEW_RELIC_INFINITE_TRACING_SPAN_EVENTS_AGENT_QUEUE_TIMEOUT", +"newrelic.code_level_metrics.enabled"=>"NEW_RELIC_CODE_LEVEL_METRICS_ENABLED", +"newrelic.application_logging.enabled"=>"NEW_RELIC_APPLICATION_LOGGING_ENABLED", +"newrelic.application_logging.local_decorating.enabled"=>"NEW_RELIC_APPLICATION_LOGGING_LOCAL_DECORATING_ENABLED", +"newrelic.application_logging.forwarding.enabled"=>"NEW_RELIC_APPLICATION_LOGGING_FORWARDING_ENABLED", +"newrelic.application_logging.forwarding.max_samples_stored"=>"NEW_RELIC_APPLICATION_LOGGING_FORWARDING_MAX_SAMPLES_STORED", +"newrelic.application_logging.forwarding.log_level"=>"NEW_RELIC_APPLICATION_LOGGING_FORWARDING_LOG_LEVEL", +"newrelic.application_logging.metrics.enabled"=>"NEW_RELIC_APPLICATION_LOGGING_METRICS_ENABLED", +"newrelic.application_logging.forwarding.context_data.enabled"=>"NEW_RELIC_APPLICATION_LOGGING_FORWARDING_CONTEXT_DATA_ENABLED", +"newrelic.application_logging.forwarding.context_data.include"=>"NEW_RELIC_APPLICATION_LOGGING_FORWARDING_CONTEXT_DATA_INCLUDE", +"newrelic.application_logging.forwarding.context_data.exclude"=>"NEW_RELIC_APPLICATION_LOGGING_FORWARDING_CONTEXT_DATA_EXCLUDE", +"newrelic.vulnerability_management.package_detection.enabled"=>"NEW_RELIC_VULNERABILITY_MANAGEMENT_PACKAGE_DETECTION_ENABLED", +)); \ No newline at end of file diff --git a/agent/newrelic-install.sh b/agent/newrelic-install.sh index 68c2d1c45..9408a8150 100755 --- a/agent/newrelic-install.sh +++ b/agent/newrelic-install.sh @@ -323,6 +323,8 @@ if [ -z "${ispkg}" ]; then check_file "${ilibdir}/scripts/newrelic-daemon.logrotate" fi check_file "${ilibdir}/scripts/newrelic.ini.template" +check_file "${ilibdir}/newrelic-install-inject-envvars.php" +check_file "${ilibdir}/newrelic-install-php-cfg-mappings.php" # Check that exxtension artifacts exist for all supported PHP versions # MAKE SURE TO UPDATE THIS LIST WHENEVER SUPPORT IS ADDED OR REMOVED # FOR A PHP VERSION @@ -420,6 +422,9 @@ fi # If this is set, it must be set to a valid New Relic license key, and this # key will be set in the daemon config file. # +# NOTE: If NR_CONFIG_WITH_ENVIRON is defined and NEW_RELIC_LICENSE is defined +# then the value of NEW_RELIC_LICENSE will be used.s +# # NR_INSTALL_INITSCRIPT # If set this must be the name under which the init script will be installed. # This is usually something like /etc/init.d/newrelic-daemon. If no value is @@ -433,7 +438,17 @@ fi # NR_INSTALL_USE_CP_NOT_LN # If set to any non-empty value, copy the agent and don't create a symlink. # - +# NR_CONFIG_WITH_ENVIRON +# If set to any non-empty value, the installer will run a script which will +# use environment variables to configure the fresh newrelic.ini created +# on installs ONLY. This option will have no effect if a newrelic.ini +# already exists. +# +# The environment variable name for a given INI value is created by changing +# all "." to "_" and removing consecutive "_". +# +# If this option is selected and NEW_RELIC_LICENSE is defined then it will +# override the value from NR_INSTALL_KEY # # This script will put the log file and the sysinfo file into a tar file so # it is ready to send to New Relic support if anything goes amiss. @@ -1066,7 +1081,7 @@ for this copy of PHP. We apologize for the inconvenience. 8.3.*) pi_php8="yes" - ;; + ;; *) error "unsupported version '${pi_ver}' of PHP found at: @@ -1471,25 +1486,49 @@ install_agent_here() { # file exists in the target directory. # if [ -n "${pi_inidir_cli}" -a ! -f "${pi_inidir_cli}/newrelic.ini" ]; then - if sed -e "s/REPLACE_WITH_REAL_KEY/${nrkey}/" "${ilibdir}/scripts/newrelic.ini.template" > "${pi_inidir_cli}/newrelic.ini"; then + # check if doing old style injection of just "newrelic.license" or if injecting values from env vars + if [ -n "${NR_CONFIG_WITH_ENVIRON}" ]; then + if php "${ilibdir}/newrelic-install-inject-envvars.php" "${ilibdir}/scripts/newrelic.ini.template" "${pi_inidir_cli}/newrelic.ini"; then + if [ "0" != "$?" ]; then + istat="failed" + fi + fi + if [ -z "${istat}" -a -z "${NR_INSTALL_SILENT}" ]; then + echo " Install Status : ${pi_inidir_cli}/newrelic.ini created and environment variable configuration used" + fi + elif sed -e "s/REPLACE_WITH_REAL_KEY/${nrkey}/" "${ilibdir}/scripts/newrelic.ini.template" > "${pi_inidir_cli}/newrelic.ini"; then logcmd chmod 644 "${pi_inidir_cli}/newrelic.ini" if [ -z "${NR_INSTALL_SILENT}" ]; then echo " Install Status : ${pi_inidir_cli}/newrelic.ini created" fi else istat="failed" + fi + istat="failed" + if [ -n "${istat}" ]; then log "${pdir}: copy ini template to ${pi_inidir_cli}/newrelic.ini failed" fi fi if [ -z "${istat}" -a -n "${pi_inidir_dso}" -a ! -f "${pi_inidir_dso}/newrelic.ini" ]; then - if sed -e "s/REPLACE_WITH_REAL_KEY/${nrkey}/" "${ilibdir}/scripts/newrelic.ini.template" > "${pi_inidir_dso}/newrelic.ini"; then + if [ -n "${NR_CONFIG_WITH_ENVIRON}" ]; then + if php "${ilibdir}/newrelic-install-inject-envvars.php" "${ilibdir}/scripts/newrelic.ini.template" "${pi_inidir_dso}/newrelic.ini"; then + if [ "0" != "$?" ]; then + istat="failed" + fi + fi + if [ -z "${istat}" -a -z "${NR_INSTALL_SILENT}" ]; then + echo " Install Status : ${pi_inidir_cli}/newrelic.ini created and environment variable configuration used" + fi + elif sed -e "s/REPLACE_WITH_REAL_KEY/${nrkey}/" "${ilibdir}/scripts/newrelic.ini.template" > "${pi_inidir_dso}/newrelic.ini"; then logcmd chmod 644 "${pi_inidir_dso}/newrelic.ini" if [ -z "${NR_INSTALL_SILENT}" ]; then echo " Install Status : ${pi_inidir_dso}/newrelic.ini created" fi else istat="failed" + fi + if [ -n "${istat}" ]; then log "${pdir}: copy ini template to ${pi_inidir_dso}/newrelic.ini failed" fi fi @@ -1582,8 +1621,21 @@ do_install() { nrkey=`head -1 /etc/newrelic/upgrade_please.key 2>/dev/null` fi - if [ -n "${NR_INSTALL_KEY}" ]; then - nrkey="${NR_INSTALL_KEY}" + # attempt to determine license key from environment variables + # if configuration from environment variables is enabled then + # look at NEW_RELIC_LICENSE first, then NR_INSTALL_KEY + # + # otherwise use NR_INSTALL_EKY + if [ -n "${NR_CONFIG_WITH_ENVIRON}" ]; then + if [ -z "${NEW_RELIC_LICENSE}" ]; then + nrkey="${NEW_RELIC_LICENSE}"; + else + nrkey="${NR_INSTALL_KEY}" + fi + else + if [ -n "${NR_INSTALL_KEY}" ]; then + nrkey="${NR_INSTALL_KEY}" + fi fi if [ -z "${NR_INSTALL_SILENT}" -a -z "${nrkey}" ]; then @@ -1832,7 +1884,7 @@ EOF The New Relic Proxy Daemon is installed, but the agent is not. Please point your favorite web browser at -${bold}https://docs.newrelic.com/docs/apm/agents/php-agent/installation/php-agent-installation-overview/${rmso} +${bold}https://docs.newrelic.com/docs/apm/agents/php-agent/installation/php-agent-installation-overview/${rmso} for how to install the agent by hand. EOF diff --git a/agent/php_api_internal.c b/agent/php_api_internal.c index 05c6d4836..fd398e8a1 100644 --- a/agent/php_api_internal.c +++ b/agent/php_api_internal.c @@ -7,6 +7,7 @@ #include "php_api_internal.h" #include "php_call.h" #include "php_hash.h" +#include "php_nrini.h" #include "nr_commands_private.h" #include "nr_datastore_instance.h" #include "nr_header.h" @@ -473,4 +474,16 @@ PHP_FUNCTION(newrelic_is_recording) { RETURN_FALSE; } +PHP_FUNCTION(newrelic_get_all_ini_envvar_names) { + zval* name_array = NULL; + NR_UNUSED_RETURN_VALUE; + NR_UNUSED_RETURN_VALUE_PTR; + NR_UNUSED_RETURN_VALUE_USED; + NR_UNUSED_THIS_PTR; + + name_array = nr_php_get_all_ini_envvar_names(); + + RETURN_ZVAL(name_array, 1, 0); +} + #endif /* ENABLE_TESTING_API */ diff --git a/agent/php_api_internal.h b/agent/php_api_internal.h index 61db36648..96db6baf3 100644 --- a/agent/php_api_internal.h +++ b/agent/php_api_internal.h @@ -16,6 +16,13 @@ */ extern PHP_FUNCTION(newrelic_get_request_metadata); +/* + * Proto : zval* newrelic_get_all_ini_envvar_names () + * + * Returns : Array of all ini and env var config names. + */ +extern PHP_FUNCTION(newrelic_get_all_ini_envvar_names); + #ifdef ENABLE_TESTING_API /* diff --git a/agent/php_newrelic.c b/agent/php_newrelic.c index 210c04428..cb2e7d598 100644 --- a/agent/php_newrelic.c +++ b/agent/php_newrelic.c @@ -362,6 +362,7 @@ static zend_function_entry newrelic_functions[] = { PHP_FE(newrelic_get_error_json, newrelic_arginfo_void) PHP_FE(newrelic_get_transaction_guid, newrelic_arginfo_void) PHP_FE(newrelic_is_recording, newrelic_arginfo_void) + PHP_FE(newrelic_get_all_ini_envvar_names, newrelic_arginfo_void) #else PHP_FE(newrelic_get_hostname, 0) PHP_FE(newrelic_get_slowsqls, 0) @@ -369,6 +370,7 @@ static zend_function_entry newrelic_functions[] = { PHP_FE(newrelic_get_error_json, 0) PHP_FE(newrelic_get_transaction_guid, 0) PHP_FE(newrelic_is_recording, 0) + PHP_FE(newrelic_get_all_ini_envvar_names, 0) #endif /* PHP 8 */ #endif /* ENABLE_TESTING_API */ diff --git a/agent/php_nrini.c b/agent/php_nrini.c index 90c88a531..3c6e8f4b3 100644 --- a/agent/php_nrini.c +++ b/agent/php_nrini.c @@ -7,6 +7,7 @@ #include "php_globals.h" #include "php_hash.h" #include "php_internal_instrument.h" +#include "php_nrini.h" #include "php_user_instrument.h" #include "php_nrini.h" @@ -3494,3 +3495,56 @@ int nr_php_ini_setting_is_set_by_user(const char* name) { } #endif /* PHP7 */ } + +static int newrelic_collect_ini_names(zend_ini_entry* ini_entry, + zval* name_array, + zend_hash_key* key NRUNUSED) { + char* ini_name = NULL; + char* env_name = NULL; + + if (ini_entry->module_number != NR_PHP_PROCESS_GLOBALS(our_module_number)) { + return ZEND_HASH_APPLY_KEEP; + } + + if (NULL == name_array || IS_ARRAY != Z_TYPE_P(name_array)) { + nrl_warning(NRL_INIT, "%s: name_array issue", __func__); + return ZEND_HASH_APPLY_KEEP; + } + + ini_name = PHP_INI_ENTRY_NAME(ini_entry); + if (NULL == ini_name) { + nrl_verbosedebug(NRL_INIT, "%s: ini name is NULL!", __func__); + return ZEND_HASH_APPLY_KEEP; + } + + env_name = nr_ini_to_env(ini_name); + if (NULL == env_name) { + nrl_verbosedebug(NRL_INIT, + "%s: Unable to convert ini name %s to env var name!", + __func__, ini_name); + return ZEND_HASH_APPLY_KEEP; + } + + nr_php_add_assoc_string(name_array, PHP_INI_ENTRY_NAME(ini_entry), env_name); + + nr_free(env_name); + + return ZEND_HASH_APPLY_KEEP; +} + +/* returns a PHP array keyed by ini value name and value is the equivalent env + * var name */ +zval* nr_php_get_all_ini_envvar_names() { + zval* name_array = NULL; + + name_array = nr_php_zval_alloc(); + array_init(name_array); + + if (0 != EG(ini_directives)) { + nr_php_zend_hash_ptr_apply(EG(ini_directives), + (nr_php_ptr_apply_t)newrelic_collect_ini_names, + name_array); + } + + return name_array; +} \ No newline at end of file diff --git a/agent/php_nrini.h b/agent/php_nrini.h index a7c4eb13c..e6749b422 100644 --- a/agent/php_nrini.h +++ b/agent/php_nrini.h @@ -6,5 +6,20 @@ */ #ifndef PHP_NRINI_HDR #define PHP_NRINI_HDR +/* + * Purpose : Convert INI name to matching environment variable name. + * + * Params : 1. INI name as a string + * + * Returns : String containing environment name - caller owns allocation + */ extern char* nr_ini_to_env(const char* ini_name); + +/* + * Purpose : Returns a PHP array for all INI values, + * which is keyed by INI name with a value of the equivalent + * env var name. + */ +extern zval* nr_php_get_all_ini_envvar_names(void); + #endif /* PHP_NRINI_HDR */ diff --git a/make/release.mk b/make/release.mk index 6bb9eba61..37a62e370 100644 --- a/make/release.mk +++ b/make/release.mk @@ -67,8 +67,10 @@ release-daemon: Makefile daemon | releases/$(RELEASE_OS)/daemon/ .PHONY: release-installer release-installer: Makefile release-installer-script release-installer-iutil -release-installer-script: bin/newrelic-install | releases/$(RELEASE_OS)/ +release-installer-script: bin/newrelic-install bin/newrelic-install-php-cfg-mappings.php bin/newrelic-install-inject-envvars.php | releases/$(RELEASE_OS)/ cp bin/newrelic-install releases/$(RELEASE_OS) + cp bin/newrelic-install-inject-envvars.php releases/$(RELEASE_OS) + cp bin/newrelic-install-php-cfg-mappings.php releases/$(RELEASE_OS) release-installer-iutil: bin/newrelic-iutil | releases/$(RELEASE_OS)/scripts/ cp bin/newrelic-iutil releases/$(RELEASE_OS)/scripts/newrelic-iutil.$(RELEASE_ARCH) diff --git a/tests/newrelic-installer/verify_inject_envvars_script_injection.php b/tests/newrelic-installer/verify_inject_envvars_script_injection.php new file mode 100644 index 000000000..eeab85727 --- /dev/null +++ b/tests/newrelic-installer/verify_inject_envvars_script_injection.php @@ -0,0 +1,84 @@ + ENVVAR names could not be captured!"); + exit(1); +} + +$template = "../../agent/scripts/newrelic.ini.template"; +$out_ini_file = "newrelic.ini-for-test"; +if (file_exists($out_ini_file)) { + unlink($out_ini_file); +} + +// if (!copy($template, $dup_template)) { +// failure("Could not create copy of INI template $template to $dup_template!"); +// } + +/* Create shell script to test env var injection */ +$test_script = "test_envvar_inject.sh"; +if (file_exists($test_script)) { + unlink($test_script); +} + +$script_fp = fopen($test_script, "w"); +if (!$script_fp) { + unlink($out_ini_file); + failure("Could not create test script $test_script!"); + exit(1); +} + +fwrite($script_fp, "#!/bin/sh\n"); +foreach ($names as $k => $v) { + fwrite($script_fp, "export $v=$v\n"); +} + +fwrite($script_fp, "php ../../agent/newrelic-install-inject-envvars.php $template $out_ini_file\n"); +fclose($script_fp); + +$output = shell_exec("sh " . $test_script); +unlink($test_script); +if (!$output) { + failure("Error running test script $test_script:\n" . $output); + exit(1); +} + +/* if any ini directives ever need to be ignored use this array */ +$ignore_list = array(); + +/* check that all expceted agent INI values were injected */ +$ini_values = parse_ini_file($out_ini_file); +unlink($out_ini_file); +foreach ($names as $k => $v) { + /* skip directives which are not part of newrelic.ini */ + if (in_array($k, $ignore_list)) { + continue; + } + + /* each ini value should be the env var name */ + if (!in_array($k, array_keys($ini_values))) { + failure("Missing expected injected value for $k"); + exit(1); + } + + if ($ini_values[$k] != $names[$k]) { + failure("Injected value for $k wrong: expected \"$names[$k]\", actual \"$ini_values[$k]\""); + exit(1); + } +} + +echo "Injection script test PASS\n"; +exit(0); diff --git a/tests/newrelic-installer/verify_inject_envvars_script_mapping.php b/tests/newrelic-installer/verify_inject_envvars_script_mapping.php new file mode 100644 index 000000000..10ede2ba9 --- /dev/null +++ b/tests/newrelic-installer/verify_inject_envvars_script_mapping.php @@ -0,0 +1,61 @@ + ENVVAR names could not be captured!"); + exit(1); +} + +$mapping_filename = "../../agent/newrelic-install-php-cfg-mappings.php"; +include $mapping_filename; +if (!defined("INI_ENVVAR_MAP")) { + failure(("Mapping file $mapping_filename could not be opened!")); + exit(1); +} +$keys1 = array_keys(INI_ENVVAR_MAP); +$keys2 = array_keys($names); + +if (null == $keys1 || null == $keys2 || 0 == count($keys1) || 0 == count($keys2)) { + failure("Not all keys could be captured!"); + exit(1); +} + +$diff1 = array_diff($keys1, $keys2); +$diff2 = array_diff($keys2, $keys1); + +$matches = true; +if (0 != count($diff1)) { + echo "Keys in current mapping not in agent:\n"; + foreach ($diff1 as $n) { + echo " $n\n"; + } + $matches = false; +} +if (0 != count($diff2)) { + echo "Keys in agent not in current mapping:\n"; + foreach ($diff2 as $n) { + echo " $n\n"; + } + $matches = false; +} + +if ($matches) { + echo "Agent and mapping check PASS!\n"; + exit(0); +} else { + echo "Agent and mapping check FAIL!\n"; + exit(1); +} + +?>