You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

897 lines
42 KiB

#!/usr/bin/env python
# Copyright 2018-2020 Splunk
#
# 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.
from __future__ import absolute_import
from __future__ import print_function
import os
import platform
import json
import argparse
import re
import random
import string
from time import sleep
try:
from urllib.parse import urlparse
except ImportError:
from urlparse import urlparse
import socket
import requests
import urllib3
import yaml
urllib3.disable_warnings()
HERE = os.path.dirname(os.path.normpath(__file__))
_PLATFORM = platform.platform().lower()
PLATFORM = "windows" if ("windows" in _PLATFORM or "cygwin" in _PLATFORM) else "linux"
HOSTNAME = os.uname()[1]
JAVA_VERSION_WHITELIST = frozenset(("oracle:8", "openjdk:8", "openjdk:9", "openjdk:11"))
roleNames = [
'splunk_cluster_master', # (if it exists, set up indexer clustering)
'splunk_deployer',
'splunk_heavy_forwarder',
'splunk_standalone',
'splunk_search_head',
'splunk_indexer',
'splunk_license_master', # (if it exists, run adding license with a license master)
'splunk_search_head_captain', # TODO: remove this as we deprecate this role
'splunk_universal_forwarder',
'splunk_deployment_server',
'splunk_monitor'
]
varPrefix = "SPLUNK_VAR_"
reVarPrefix = r"${varPrefix}(.*)"
envPrefix = "SPLUNK_ROLE_"
reNamePattern = r"${envPrefix}(.*)"
udsSocketFilename = "/opt/splunkforwarder/var/run/splunk/cli.socket"
inventory = {
"_meta": {
"hostvars": {}
},
"all": {
"hosts": [],
"children": ["ungrouped"]
},
"ungrouped": {
"hosts": []
}
}
def getVars(rePattern):
"""
Return a mapping of environment keys::values if they match a given regex
"""
return {re.match(rePattern, k).group(1).lower():os.environ[k] for k in os.environ
if re.match(rePattern, k)}
def getSplunkInventory(inventory, reName=r"(.*)_URL"):
"""
Build an inventory of hosts based on a regex that defines host-groupings
"""
group_information = getVars(reName)
for group_name in group_information:
if group_name.lower() in roleNames:
inventory[group_name] = {}
hosts = [host.strip() for host in group_information[group_name].split(',')]
inventory[group_name] = {
'hosts': list([host.split(':')[0] for host in hosts])
}
inventory["all"]["vars"] = getDefaultVars()
inventory["all"]["vars"]["docker"] = False
if inventory["all"]["vars"]["splunk"]["splunk_http_enabled"] == "true":
if not os.path.exists(udsSocketFilename) or os.stat(udsSocketFilename).st_size == 0:
inventory["all"]["vars"]["splunk"]["splunk_http_enabled"] = "false"
if os.path.isfile("/.dockerenv") or os.path.isfile("/run/.containerenv") or os.path.isdir("/var/run/secrets/kubernetes.io") or os.environ.get("KUBERNETES_SERVICE_HOST"):
inventory["all"]["vars"]["docker"] = True
if "localhost" not in inventory["all"]["children"]:
inventory["all"]["hosts"].append("localhost")
inventory["_meta"]["hostvars"]["localhost"] = inventory["all"]["vars"]
inventory["_meta"]["hostvars"]["localhost"]["ansible_connection"] = "local"
def getDefaultVars():
"""
Load all splunk-ansible defaults and perform overwrites based on
environment variables to return a consolidated inventory object
"""
defaultVars = loadDefaults()
defaultVars["splunk"]["role"] = os.environ.get('SPLUNK_ROLE', defaultVars["splunk"].get("role") or "splunk_standalone")
overrideEnvironmentVars(defaultVars)
getAnsibleContext(defaultVars)
getASan(defaultVars)
getDisablePopups(defaultVars)
getHEC(defaultVars)
getSecrets(defaultVars)
getSplunkPaths(defaultVars)
getIndexerClustering(defaultVars)
getSearchHeadClustering(defaultVars)
# getMultisite() must be called after getIndexerClustering() + getSearchHeadClustering()
# in order to rectify multisite replication and search factors
getMultisite(defaultVars)
getSplunkWebSSL(defaultVars)
getSplunkdSSL(defaultVars)
getDistributedTopology(defaultVars)
getLicenses(defaultVars)
# Determine DMC settings
defaultVars["dmc_forwarder_monitoring"] = os.environ.get('DMC_FORWARDER_MONITORING', False)
defaultVars["dmc_asset_interval"] = os.environ.get('DMC_ASSET_INTERVAL', '3,18,33,48 * * * *')
# Determine SPLUNK_HOME owner
defaultVars["splunk_home_ownership_enforcement"] = True
if os.environ.get("SPLUNK_HOME_OWNERSHIP_ENFORCEMENT", "").lower() == "false":
defaultVars["splunk_home_ownership_enforcement"] = False
# Determine password visibility
if os.environ.get("HIDE_PASSWORD", "").lower() == "true":
defaultVars["hide_password"] = True
# Determine SHC preferred captaincy
defaultVars["splunk"]["preferred_captaincy"] = True
if os.environ.get("SPLUNK_PREFERRED_CAPTAINCY", "").lower() == "false":
defaultVars["splunk"]["preferred_captaincy"] = False
defaultVars["splunk"]["hostname"] = os.environ.get('SPLUNK_HOSTNAME', socket.getfqdn())
getJava(defaultVars)
getSplunkBuild(defaultVars)
getSplunkbaseToken(defaultVars)
getSplunkBuildAuth(defaultVars)
getSplunkApps(defaultVars)
getSplunkAppsLocal(defaultVars)
getLaunchConf(defaultVars)
getDFS(defaultVars)
getUFSplunkVariables(defaultVars)
getESSplunkVariables(defaultVars)
getDSP(defaultVars)
return defaultVars
def getSplunkPaths(vars_scope):
"""
Normalize the paths used by Splunk for downstream plays
"""
# TODO: Handle changes to SPLUNK_HOME that impact other paths (ex splunk.app_paths.*)
splunk_vars = vars_scope["splunk"]
splunk_vars["opt"] = os.environ.get("SPLUNK_OPT", splunk_vars.get("opt"))
splunk_vars["home"] = os.environ.get("SPLUNK_HOME", splunk_vars.get("home"))
# Not sure if we should expose this - exec is fixed relative to SPLUNK_HOME
splunk_vars["exec"] = os.environ.get("SPLUNK_EXEC", splunk_vars.get("exec"))
# Not sure if we should expose this - pid is fixed relative to SPLUNK_HOME
splunk_vars["pid"] = os.environ.get("SPLUNK_PID", splunk_vars.get("pid"))
def getIndexerClustering(vars_scope):
"""
Parse and set parameters to configure indexer clustering
"""
if "idxc" not in vars_scope["splunk"]:
vars_scope["splunk"]["idxc"] = {}
idxc_vars = vars_scope["splunk"]["idxc"]
idxc_vars["label"] = os.environ.get("SPLUNK_IDXC_LABEL", idxc_vars.get("label"))
idxc_vars["secret"] = os.environ.get("SPLUNK_IDXC_SECRET", idxc_vars.get("secret"))
idxc_vars["pass4SymmKey"] = os.environ.get("SPLUNK_IDXC_PASS4SYMMKEY", idxc_vars.get("pass4SymmKey")) # Control flow for issue #316 backwards-compatibility
if idxc_vars["pass4SymmKey"]:
idxc_vars["secret"] = idxc_vars["pass4SymmKey"]
else:
idxc_vars["secret"] = os.environ.get("SPLUNK_IDXC_SECRET", idxc_vars.get("secret"))
idxc_vars["pass4SymmKey"] = idxc_vars["secret"]
# Support separate pass4SymmKey for indexer discovery
idxc_vars["discoveryPass4SymmKey"] = os.environ.get("SPLUNK_IDXC_DISCOVERYPASS4SYMMKEY", idxc_vars.get("discoveryPass4SymmKey"))
if not idxc_vars["discoveryPass4SymmKey"]:
idxc_vars["discoveryPass4SymmKey"] = idxc_vars["pass4SymmKey"]
# Rectify replication factor (https://docs.splunk.com/Documentation/Splunk/latest/Indexer/Thereplicationfactor)
# Make sure default repl/search factor>0 else Splunk doesn't start unless user-defined
if inventory.get("splunk_indexer"):
# If there are indexers, we need to make sure the replication factor is <= number of indexers
indexer_count = len(inventory["splunk_indexer"].get("hosts", []))
else:
# Only occurs during create-defaults generation or topologies without indexers
indexer_count = idxc_vars.get("replication_factor", 1)
replf = os.environ.get("SPLUNK_IDXC_REPLICATION_FACTOR", idxc_vars.get("replication_factor", 1))
idxc_vars["replication_factor"] = min(indexer_count, int(replf))
# Rectify search factor (https://docs.splunk.com/Documentation/Splunk/latest/Indexer/Thesearchfactor)
searchf = os.environ.get("SPLUNK_IDXC_SEARCH_FACTOR", idxc_vars.get("search_factor", 1))
idxc_vars["search_factor"] = min(idxc_vars["replication_factor"], int(searchf))
def getSearchHeadClustering(vars_scope):
"""
Parse and set parameters to configure search head clustering
"""
if "shc" not in vars_scope["splunk"]:
vars_scope["splunk"]["shc"] = {}
shc_vars = vars_scope["splunk"]["shc"]
shc_vars["label"] = os.environ.get("SPLUNK_SHC_LABEL", shc_vars.get("label"))
shc_vars["pass4SymmKey"] = os.environ.get("SPLUNK_SHC_PASS4SYMMKEY", shc_vars.get("pass4SymmKey")) # Control flow for issue #316 backwards-compatibility
if shc_vars["pass4SymmKey"]:
shc_vars["secret"] = shc_vars["pass4SymmKey"]
else:
shc_vars["secret"] = os.environ.get("SPLUNK_SHC_SECRET", shc_vars.get("secret"))
shc_vars["pass4SymmKey"] = shc_vars["secret"]
# Rectify search factor (https://docs.splunk.com/Documentation/Splunk/latest/Indexer/Thesearchfactor)
# Make sure default repl factor>0 else Splunk doesn't start unless user-defined
if inventory.get("splunk_search_head"):
# If there are indexers, we need to make sure the replication factor is <= number of search heads
shcount = len(inventory["splunk_search_head"].get("hosts", []))
else:
# Only occurs during create-defaults generation or topologies without search heads
shcount = shc_vars.get("replication_factor", 1)
replf = os.environ.get("SPLUNK_SHC_REPLICATION_FACTOR", shc_vars.get("replication_factor", 1))
shc_vars["replication_factor"] = min(shcount, int(replf))
def getMultisite(vars_scope):
"""
Parse and set parameters to configure multisite
"""
splunk_vars = vars_scope["splunk"]
if "SPLUNK_SITE" in os.environ or splunk_vars.get("site"):
splunk_vars["site"] = os.environ.get("SPLUNK_SITE", splunk_vars.get("site"))
all_sites = os.environ.get("SPLUNK_ALL_SITES", splunk_vars.get("all_sites"))
if all_sites:
splunk_vars["all_sites"] = all_sites
multisite_master = os.environ.get("SPLUNK_MULTISITE_MASTER", splunk_vars.get("multisite_master"))
if multisite_master:
splunk_vars["multisite_master"] = multisite_master
# TODO: Split this into its own splunk.multisite.* section
splunk_vars["multisite_master_port"] = int(os.environ.get("SPLUNK_MULTISITE_MASTER_PORT", splunk_vars.get("multisite_master_port", 8089)))
splunk_vars["multisite_replication_factor_origin"] = int(os.environ.get("SPLUNK_MULTISITE_REPLICATION_FACTOR_ORIGIN", splunk_vars.get("multisite_replication_factor_origin", 1)))
splunk_vars["multisite_replication_factor_total"] = int(os.environ.get("SPLUNK_MULTISITE_REPLICATION_FACTOR_TOTAL", splunk_vars.get("multisite_replication_factor_total", 1)))
splunk_vars["multisite_replication_factor_total"] = max(splunk_vars["multisite_replication_factor_total"], splunk_vars["idxc"]["replication_factor"])
splunk_vars["multisite_search_factor_origin"] = int(os.environ.get("SPLUNK_MULTISITE_SEARCH_FACTOR_ORIGIN", splunk_vars.get("multisite_search_factor_origin", 1)))
splunk_vars["multisite_search_factor_total"] = int(os.environ.get("SPLUNK_MULTISITE_SEARCH_FACTOR_TOTAL", splunk_vars.get("multisite_search_factor_total", 1)))
splunk_vars["multisite_search_factor_total"] = max(splunk_vars["multisite_search_factor_total"], splunk_vars["idxc"]["search_factor"])
def getSplunkWebSSL(vars_scope):
"""
Parse and set parameters to define Splunk Web accessibility
"""
# TODO: Split this into its own splunk.http.* section
splunk_vars = vars_scope["splunk"]
splunk_vars["http_enableSSL"] = os.environ.get('SPLUNK_HTTP_ENABLESSL', splunk_vars.get("http_enableSSL"))
splunk_vars["http_enableSSL_cert"] = os.environ.get('SPLUNK_HTTP_ENABLESSL_CERT', splunk_vars.get("http_enableSSL_cert"))
splunk_vars["http_enableSSL_privKey"] = os.environ.get('SPLUNK_HTTP_ENABLESSL_PRIVKEY', splunk_vars.get("http_enableSSL_privKey"))
splunk_vars["http_enableSSL_privKey_password"] = os.environ.get('SPLUNK_HTTP_ENABLESSL_PRIVKEY_PASSWORD', splunk_vars.get("http_enableSSL_privKey_password"))
splunk_vars["http_port"] = int(os.environ.get('SPLUNK_HTTP_PORT', splunk_vars.get("http_port")))
def getSplunkdSSL(vars_scope):
"""
Parse and set parameters to define Splunkd
"""
if "ssl" not in vars_scope["splunk"]:
vars_scope["splunk"]["ssl"] = {}
ssl_vars = vars_scope["splunk"]["ssl"]
ssl_vars["cert"] = os.environ.get("SPLUNKD_SSL_CERT", ssl_vars.get("cert"))
ssl_vars["ca"] = os.environ.get("SPLUNKD_SSL_CA", ssl_vars.get("ca"))
ssl_vars["password"] = os.environ.get("SPLUNKD_SSL_PASSWORD", ssl_vars.get("password"))
ssl_vars["enable"] = ssl_vars.get("enable", True)
enable = os.environ.get("SPLUNKD_SSL_ENABLE", "")
if enable.lower() == "false":
ssl_vars["enable"] = False
vars_scope["cert_prefix"] = "http"
def getDistributedTopology(vars_scope):
"""
Parse and set parameters to define topology if this is a distributed environment
"""
license_master_url = os.environ.get("SPLUNK_LICENSE_MASTER_URL", vars_scope["splunk"].get("license_master_url", ""))
vars_scope["splunk"]["license_master_url"] = parseUrl(license_master_url, vars_scope)
vars_scope["splunk"]["deployer_url"] = os.environ.get("SPLUNK_DEPLOYER_URL", vars_scope["splunk"].get("deployer_url", ""))
vars_scope["splunk"]["cluster_master_url"] = os.environ.get("SPLUNK_CLUSTER_MASTER_URL", vars_scope["splunk"].get("cluster_master_url", ""))
vars_scope["splunk"]["search_head_captain_url"] = os.environ.get("SPLUNK_SEARCH_HEAD_CAPTAIN_URL", vars_scope["splunk"].get("search_head_captain_url", ""))
if not vars_scope["splunk"]["search_head_captain_url"] and "search_head_cluster_url" in vars_scope["splunk"]:
vars_scope["splunk"]["search_head_captain_url"] = vars_scope["splunk"]["search_head_cluster_url"]
def getLicenses(vars_scope):
"""
Determine the location of Splunk licenses to install at start-up time
"""
# Need to provide some file value (does not have to exist). The task will automatically skip over if the file is not found. Otherwise, will throw an error if no file is specified.
vars_scope["splunk"]["license_uri"] = os.environ.get("SPLUNK_LICENSE_URI", vars_scope["splunk"].get("license_uri") or "splunk.lic")
vars_scope["splunk"]["wildcard_license"] = False
if vars_scope["splunk"]["license_uri"] and '*' in vars_scope["splunk"]["license_uri"]:
vars_scope["splunk"]["wildcard_license"] = True
vars_scope["splunk"]["ignore_license"] = False
if os.environ.get("SPLUNK_IGNORE_LICENSE", "").lower() == "true":
vars_scope["splunk"]["ignore_license"] = True
vars_scope["splunk"]["license_download_dest"] = os.environ.get("SPLUNK_LICENSE_INSTALL_PATH", vars_scope["splunk"].get("license_download_dest") or "/tmp/splunk.lic")
def getJava(vars_scope):
"""
Parse and set Java installation parameters
"""
vars_scope["java_version"] = vars_scope.get("java_version")
vars_scope["java_download_url"] = vars_scope.get("java_download_url")
vars_scope["java_update_version"] = vars_scope.get("java_update_version")
java_version = os.environ.get("JAVA_VERSION")
if not java_version:
return
java_version = java_version.lower()
if java_version not in JAVA_VERSION_WHITELIST:
raise Exception("Invalid Java version supplied, supported versions are: {}".format(JAVA_VERSION_WHITELIST))
vars_scope["java_version"] = java_version
# TODO: We can probably DRY this up
if java_version == "oracle:8":
vars_scope["java_download_url"] = os.environ.get("JAVA_DOWNLOAD_URL", "https://download.oracle.com/otn-pub/java/jdk/8u141-b15/336fa29ff2bb4ef291e347e091f7f4a7/jdk-8u141-linux-x64.tar.gz")
try:
vars_scope["java_update_version"] = re.search(r"jdk-8u(\d+)-linux-x64.tar.gz", vars_scope["java_download_url"]).group(1)
except:
raise Exception("Invalid Java download URL format")
elif java_version == "openjdk:11":
vars_scope["java_download_url"] = os.environ.get("JAVA_DOWNLOAD_URL", "https://download.java.net/java/GA/jdk11/9/GPL/openjdk-11.0.2_linux-x64_bin.tar.gz")
try:
vars_scope["java_update_version"] = re.search(r"openjdk-(\d+\.\d+\.\d+)_linux-x64_bin.tar.gz", vars_scope["java_download_url"]).group(1)
except:
raise Exception("Invalid Java download URL format")
def getSplunkBuild(vars_scope):
"""
Determine the location of the Splunk build
"""
vars_scope["splunk"]["build_url_bearer_token"] = os.environ.get("SPLUNK_BUILD_URL_BEARER_TOKEN", vars_scope["splunk"].get("build_url_bearer_token"))
vars_scope["splunk"]["build_location"] = os.environ.get("SPLUNK_BUILD_URL", vars_scope["splunk"].get("build_location"))
def getSplunkbaseToken(vars_scope):
"""
Authenticate to SplunkBase and modify the variable scope in-place to utilize temporary session token
"""
vars_scope["splunkbase_token"] = None
vars_scope["splunkbase_username"] = os.environ.get("SPLUNKBASE_USERNAME", vars_scope.get("splunkbase_username"))
vars_scope["splunkbase_password"] = os.environ.get("SPLUNKBASE_PASSWORD", vars_scope.get("splunkbase_password"))
if vars_scope["splunkbase_username"] and vars_scope["splunkbase_password"]:
resp = requests.post("https://splunkbase.splunk.com/api/account:login/",
data={"username": vars_scope["splunkbase_username"], "password": vars_scope["splunkbase_password"]})
if resp.status_code != 200:
raise Exception("Invalid Splunkbase credentials - will not download apps from Splunkbase")
output = resp.content
if isinstance(output, bytes):
output = output.decode("utf-8", "ignore")
splunkbase_token = re.search("<id>(.*)</id>", output, re.IGNORECASE)
vars_scope["splunkbase_token"] = splunkbase_token.group(1) if splunkbase_token else None
def getSplunkBuildAuth(vars_scope):
"""
Load username and password to be used in basic auth when fetching splunk build or apps
"""
vars_scope["splunk"]["artifact_auth_user"] = os.environ.get("ARTIFACTORY_USER", vars_scope["splunk"].get("artifact_auth_user"))
vars_scope["splunk"]["artifact_auth_pass"] = os.environ.get("ARTIFACTORY_TOKEN", vars_scope["splunk"].get("artifact_auth_pass"))
def getSplunkApps(vars_scope):
"""
Determine the set of Splunk apps to install as union of defaults.yml and environment variables
"""
appList = []
if not "apps_location" in vars_scope["splunk"]:
vars_scope["splunk"]["apps_location"] = []
# From default.yml
elif type(vars_scope["splunk"]["apps_location"]) == str:
appList = vars_scope["splunk"]["apps_location"].split(",")
elif type(vars_scope["splunk"]["apps_location"]) == list:
appList = vars_scope["splunk"]["apps_location"]
# From environment variables
apps = os.environ.get("SPLUNK_APPS_URL")
if apps:
apps = apps.split(",")
for app in apps:
if app not in appList:
appList.append(app)
vars_scope["splunk"]["apps_location"] = appList
def getSplunkAppsLocal(vars_scope):
"""
Determine the set of Splunk apps to install locally only as union of defaults.yml and environment variables
"""
# Check if theres a local apps list for CM/Deployer roles
appListLocal = []
if not "apps_location_local" in vars_scope["splunk"]:
vars_scope["splunk"]["apps_location_local"] = []
# From default.yml
elif type(vars_scope["splunk"]["apps_location_local"]) == str:
appListLocal = vars_scope["splunk"]["apps_location_local"].split(",")
elif type(vars_scope["splunk"]["apps_location_local"]) == list:
appListLocal = vars_scope["splunk"]["apps_location_local"]
# From environment variables
apps = os.environ.get("SPLUNK_APPS_URL_LOCAL")
if apps:
apps = apps.split(",")
for app in apps:
if app not in appListLocal:
appListLocal.append(app)
vars_scope["splunk"]["apps_location_local"] = appListLocal
def getSplunkAppPathInstall(vars_scope):
"""
Determine the set of Splunk apps to install at which location as a
union of defaults.yml. The actual path to install to is determined by
the splunk.app_paths.* variable
"""
appPaths = ["shc", "idxc", "default", "deployment"]
if not "app_paths_install" in vars_scope["splunk"]:
vars_scope["splunk"]["app_paths_install"] = {}
for path in appPaths:
vars_scope["splunk"]["app_paths_install"][path] = []
else:
for path in appPaths:
appList = []
if path in vars_scope["splunk"]["app_paths_install"]:
# From default.yml
if type(vars_scope["splunk"]["app_paths_install"][path]) == str:
appList = vars_scope["splunk"]["app_paths_install"][path].split(",")
elif type(vars_scope["splunk"]["app_paths_install"][path]) == list:
appList = vars_scope["splunk"]["app_paths_install"][path]
vars_scope["splunk"]["app_paths_install"][path] = appList
def getSecrets(vars_scope):
"""
Parse sensitive passphrases
"""
vars_scope["splunk"]["password"] = os.environ.get("SPLUNK_PASSWORD", vars_scope["splunk"].get("password"))
if not vars_scope["splunk"]["password"]:
raise Exception("Splunk password must be supplied!")
if os.path.isfile(vars_scope["splunk"]["password"]):
with open(vars_scope["splunk"]["password"], "r") as f:
vars_scope["splunk"]["password"] = f.read().strip()
if not vars_scope["splunk"]["password"]:
raise Exception("Splunk password supplied is empty/null")
dpw = os.environ.get("SPLUNK_DECLARATIVE_ADMIN_PASSWORD", "")
if dpw.lower() == "true":
vars_scope["splunk"]["declarative_admin_password"] = True
else:
vars_scope["splunk"]["declarative_admin_password"] = bool(vars_scope["splunk"].get("declarative_admin_password"))
vars_scope["splunk"]["pass4SymmKey"] = os.environ.get('SPLUNK_PASS4SYMMKEY', vars_scope["splunk"].get("pass4SymmKey"))
vars_scope["splunk"]["secret"] = os.environ.get('SPLUNK_SECRET', vars_scope["splunk"].get("secret"))
def getLaunchConf(vars_scope):
"""
Parse key/value pairs to set in splunk-launch.conf
"""
launch = {}
if not "launch" in vars_scope["splunk"]:
vars_scope["splunk"]["launch"] = {}
# From default.yml
if type(vars_scope["splunk"]["launch"]) == dict:
launch.update(vars_scope["splunk"]["launch"])
# From environment variables
settings = os.environ.get("SPLUNK_LAUNCH_CONF")
if settings:
launch.update({k:v for k,v in [x.split("=", 1) for x in settings.split(",")]})
vars_scope["splunk"]["launch"] = launch
def ensureListValue(value, separator):
if isinstance(value, list):
return value
elif (not value) or (not value.strip()):
return []
else:
return splitAndStrip(value, separator)
def splitAndStrip(value, separator):
if not value:
return []
return [x.strip() for x in value.split(separator)]
def transformEnvironmentVariable(environmentVariableName, transform, default):
if environmentVariableName in os.environ:
return transform(os.environ.get(environmentVariableName))
else:
return default
def getAnsibleContext(vars_scope):
"""
Parse parameters that influence Ansible execution
"""
stringSeparator = ","
vars_scope["ansible_pre_tasks"] = transformEnvironmentVariable("SPLUNK_ANSIBLE_PRE_TASKS", lambda v: splitAndStrip(v, stringSeparator), ensureListValue(vars_scope.get("ansible_pre_tasks"), stringSeparator))
vars_scope["ansible_post_tasks"] = transformEnvironmentVariable("SPLUNK_ANSIBLE_POST_TASKS", lambda v: splitAndStrip(v, stringSeparator), ensureListValue(vars_scope.get("ansible_post_tasks"), stringSeparator))
vars_scope["ansible_environment"] = vars_scope.get("ansible_environment") or {}
env = os.environ.get("SPLUNK_ANSIBLE_ENV")
if env:
vars_scope["ansible_environment"].update({k:v for k,v in [x.split("=", 1) for x in env.split(",")]})
def getASan(vars_scope):
"""
Enable ASan debug builds
"""
vars_scope["splunk"]["asan"] = bool(os.environ.get("SPLUNK_ENABLE_ASAN", vars_scope["splunk"].get("asan")))
if vars_scope["splunk"]["asan"]:
vars_scope["ansible_environment"].update({"ASAN_OPTIONS": "detect_leaks=0"})
def getDisablePopups(vars_scope):
"""
Configure pop-up settings
"""
vars_scope["splunk"]["disable_popups"] = bool(vars_scope["splunk"].get("disable_popups"))
popups_disabled = os.environ.get("SPLUNK_DISABLE_POPUPS", "")
if popups_disabled.lower() == "true":
vars_scope["splunk"]["disable_popups"] = True
elif popups_disabled.lower() == "false":
vars_scope["splunk"]["disable_popups"] = False
def getHEC(vars_scope):
"""
Configure HEC settings
"""
if not "hec" in vars_scope["splunk"]:
vars_scope["splunk"]["hec"] = {}
vars_scope["splunk"]["hec"]["token"] = os.environ.get("SPLUNK_HEC_TOKEN", vars_scope["splunk"]["hec"].get("token"))
vars_scope["splunk"]["hec"]["port"] = int(os.environ.get("SPLUNK_HEC_PORT", vars_scope["splunk"]["hec"].get("port")))
ssl = os.environ.get("SPLUNK_HEC_SSL", "")
if ssl.lower() == "false":
vars_scope["splunk"]["hec"]["ssl"] = False
else:
vars_scope["splunk"]["hec"]["ssl"] = bool(vars_scope["splunk"]["hec"].get("ssl"))
def getDSP(vars_scope):
"""
Configure DSP settings
"""
if not "dsp" in vars_scope["splunk"]:
vars_scope["splunk"]["dsp"] = {}
vars_scope["splunk"]["dsp"]["server"] = os.environ.get("SPLUNK_DSP_SERVER", vars_scope["splunk"]["dsp"].get("server"))
vars_scope["splunk"]["dsp"]["cert"] = os.environ.get("SPLUNK_DSP_CERT", vars_scope["splunk"]["dsp"].get("cert"))
vars_scope["splunk"]["dsp"]["verify"] = bool(vars_scope["splunk"]["dsp"].get("verify"))
verify = os.environ.get("SPLUNK_DSP_VERIFY", "")
if verify.lower() == "true":
vars_scope["splunk"]["dsp"]["verify"] = True
vars_scope["splunk"]["dsp"]["enable"] = bool(vars_scope["splunk"]["dsp"].get("enable"))
enable = os.environ.get("SPLUNK_DSP_ENABLE", "")
if enable.lower() == "true":
vars_scope["splunk"]["dsp"]["enable"] = True
vars_scope["splunk"]["dsp"]["pipeline_name"] = os.environ.get("SPLUNK_DSP_PIPELINE_NAME", vars_scope["splunk"]["dsp"].get("pipeline_name"))
vars_scope["splunk"]["dsp"]["pipeline_desc"] = os.environ.get("SPLUNK_DSP_PIPELINE_DESC", vars_scope["splunk"]["dsp"].get("pipeline_desc"))
vars_scope["splunk"]["dsp"]["pipeline_spec"] = os.environ.get("SPLUNK_DSP_PIPELINE_SPEC", vars_scope["splunk"]["dsp"].get("pipeline_spec"))
def getESSplunkVariables(vars_scope):
"""
Get any special Enterprise Security configuration variables
"""
ssl_enablement_env = os.environ.get("SPLUNK_ES_SSL_ENABLEMENT")
if not ssl_enablement_env and (not "es" in vars_scope["splunk"] or not "ssl_enablement" in vars_scope["splunk"]["es"]):
# This feature is only for specific versions of ES.
# if it is missing, don't pass any value in.
vars_scope["es_ssl_enablement"] = ""
else:
# Use the environment variable unless the ssl enablement value is present
ssl_enablement = ssl_enablement_env or vars_scope["splunk"]["es"]["ssl_enablement"]
# Build the flag in it's entirety here
if ssl_enablement not in ["auto", "strict", "ignore"]:
raise Exception("Invalid ssl_enablement flag {0}".format(ssl_enablement))
vars_scope["es_ssl_enablement"] = "--ssl_enablement {0}".format(ssl_enablement)
def overrideEnvironmentVars(vars_scope):
vars_scope["splunk"]["user"] = os.environ.get("SPLUNK_USER", vars_scope["splunk"]["user"])
vars_scope["splunk"]["group"] = os.environ.get("SPLUNK_GROUP", vars_scope["splunk"]["group"])
vars_scope["cert_prefix"] = os.environ.get("SPLUNK_CERT_PREFIX", vars_scope.get("cert_prefix", "https"))
vars_scope["splunk"]["root_endpoint"] = os.environ.get('SPLUNK_ROOT_ENDPOINT', vars_scope["splunk"]["root_endpoint"])
vars_scope["splunk"]["svc_port"] = os.environ.get('SPLUNK_SVC_PORT', vars_scope["splunk"]["svc_port"])
vars_scope["splunk"]["splunk_http_enabled"] = os.environ.get('ENABLE_TCP_MODE', vars_scope["splunk"]["enable_tcp_mode"])
vars_scope["splunk"]["s2s"]["port"] = int(os.environ.get('SPLUNK_S2S_PORT', vars_scope["splunk"]["s2s"]["port"]))
vars_scope["splunk"]["enable_service"] = os.environ.get('SPLUNK_ENABLE_SERVICE', vars_scope["splunk"]["enable_service"])
vars_scope["splunk"]["service_name"] = os.environ.get('SPLUNK_SERVICE_NAME', vars_scope["splunk"]["service_name"])
vars_scope["splunk"]["allow_upgrade"] = os.environ.get('SPLUNK_ALLOW_UPGRADE', vars_scope["splunk"]["allow_upgrade"])
vars_scope["splunk"]["appserver"]["port"] = os.environ.get('SPLUNK_APPSERVER_PORT', vars_scope["splunk"]["appserver"]["port"])
vars_scope["splunk"]["kvstore"]["port"] = os.environ.get('SPLUNK_KVSTORE_PORT', vars_scope["splunk"]["kvstore"]["port"])
vars_scope["splunk"]["connection_timeout"] = int(os.environ.get('SPLUNK_CONNECTION_TIMEOUT', vars_scope["splunk"]["connection_timeout"]))
if vars_scope["splunk"]["splunk_http_enabled"] == "false" and "forwarder" not in vars_scope["splunk"]["role"].lower():
vars_scope["splunk"]["splunk_http_enabled"] = "true"
# Set set_search_peers to False to disable peering to indexers when creating multisite topology
if os.environ.get("SPLUNK_SET_SEARCH_PEERS", "").lower() == "false":
vars_scope["splunk"]["set_search_peers"] = False
def getDFS(vars_scope):
"""
Parse and set parameters to configure Data Fabric Search
"""
if "dfs" not in vars_scope["splunk"]:
vars_scope["splunk"]["dfs"] = {}
dfs_vars = vars_scope["splunk"]["dfs"]
dfs_vars["enable"] = bool(os.environ.get("SPLUNK_ENABLE_DFS", dfs_vars.get("enable")))
dfs_vars["dfw_num_slots"] = int(os.environ.get("SPLUNK_DFW_NUM_SLOTS", dfs_vars.get("dfw_num_slots", 10)))
dfs_vars["dfc_num_slots"] = int(os.environ.get("SPLUNK_DFC_NUM_SLOTS", dfs_vars.get("dfc_num_slots", 4)))
dfs_vars["dfw_num_slots_enabled"] = bool(os.environ.get('SPLUNK_DFW_NUM_SLOTS_ENABLED', dfs_vars.get("dfw_num_slots_enabled")))
dfs_vars["spark_master_host"] = os.environ.get("SPARK_MASTER_HOST", dfs_vars.get("spark_master_host", "127.0.0.1"))
dfs_vars["spark_master_webui_port"] = int(os.environ.get("SPARK_MASTER_WEBUI_PORT", dfs_vars.get("spark_master_webui_port", 8080)))
def getUFSplunkVariables(vars_scope):
"""
Set or override specific environment variables for universal forwarders
"""
if os.environ.get("SPLUNK_DEPLOYMENT_SERVER"):
vars_scope["splunk"]["deployment_server"] = os.environ.get("SPLUNK_DEPLOYMENT_SERVER")
if os.environ.get("SPLUNK_ADD"):
vars_scope["splunk"]["add"] = os.environ.get("SPLUNK_ADD").split(",")
if os.environ.get("SPLUNK_BEFORE_START_CMD"):
vars_scope["splunk"]["before_start_cmd"] = os.environ.get("SPLUNK_BEFORE_START_CMD").split(",")
if os.environ.get("SPLUNK_CMD"):
vars_scope["splunk"]["cmd"] = os.environ.get("SPLUNK_CMD").split(",")
if os.environ.get("SPLUNK_DEPLOYMENT_CLIENT_NAME"):
vars_scope["splunk"]["deployment_client"] = {
"name": os.environ.get("SPLUNK_DEPLOYMENT_CLIENT_NAME")
}
def getRandomString():
"""
Generate a random string consisting of characters + digits
"""
char_set = string.ascii_uppercase + string.digits
return ''.join(random.sample(char_set * 6, 6))
def parseUrl(url, vars_scope):
"""
Parses role URL to handle non-default schemes, ports, etc.
"""
if not url:
return ""
scheme = vars_scope.get("cert_prefix", "https")
port = vars_scope["splunk"].get("svc_port", 8089)
parsed = urlparse(url)
# If netloc exists, we should consider the url provided well-formatted w/ scheme provided
if parsed.netloc:
# Strip auth info, if it exists
netloc = parsed.netloc.split("@", 1)[-1]
if ":" not in netloc:
return "{}://{}:{}".format(parsed.scheme, netloc, port)
return "{}://{}".format(parsed.scheme, netloc)
# Strip auth info, if it exists
parsed = url.split("@", 1)[-1]
# Strip path, if it exists
parsed = parsed.split("/", 1)[0]
# Extract hostname and port
parsed = parsed.split(":", 1)
hostname = parsed[0]
if len(parsed) == 2:
port = parsed[1]
return "{}://{}:{}".format(scheme, hostname, port)
def merge_dict(dict1, dict2, path=None):
"""
Merge two dictionaries such that all the keys in dict2 overwrite those in dict1
"""
if path is None: path = []
for key in dict2:
if key in dict1:
if isinstance(dict1[key], dict) and isinstance(dict2[key], dict):
merge_dict(dict1[key], dict2[key], path + [str(key)])
elif isinstance(dict1[key], list) and isinstance(dict2[key], list):
dict1[key] += dict2[key]
else:
dict1[key] = dict2[key]
else:
dict1[key] = dict2[key]
return dict1
def mergeDefaults(vars_scope, key, src):
"""
Helper method to fetch defaults from various sources/other methods
"""
if not src or not src.strip():
return vars_scope
src = src.strip().lower()
if src.startswith("file://"):
src = src[7:]
if src.startswith(("http://", "https://")):
headers = None
verify = False
if vars_scope.get("config") and vars_scope["config"].get(key):
headers = vars_scope["config"][key].get("headers")
verify = bool(vars_scope["config"][key].get("verify"))
vars_scope = mergeDefaultsFromURL(vars_scope, src, headers, verify)
else:
vars_scope = mergeDefaultsFromFile(vars_scope, src)
return vars_scope
def mergeDefaultsFromURL(vars_scope, url, headers=None, verify=False):
"""
Fetch defaults from a URL and merge them into a single dict
"""
if not url:
return vars_scope
if not headers:
headers = {}
max_retries = int(os.environ.get("SPLUNK_DEFAULTS_HTTP_MAX_RETRIES", vars_scope["config"].get("max_retries")))
max_delay = int(os.environ.get("SPLUNK_DEFAULTS_HTTP_MAX_DELAY", vars_scope["config"].get("max_delay")))
max_timeout = int(os.environ.get("SPLUNK_DEFAULTS_HTTP_MAX_TIMEOUT", vars_scope["config"].get("max_timeout")))
auth = os.environ.get("SPLUNK_DEFAULTS_HTTP_AUTH_HEADER")
if auth:
headers["Authorization"] = auth
unlimited_retries = (max_retries == -1)
current_retry = 0
while True:
try:
resp = requests.get(url.format(hostname=HOSTNAME, platform=PLATFORM),
headers=headers, timeout=max_timeout, verify=verify)
resp.raise_for_status()
output = resp.content
if isinstance(output, bytes):
output = output.decode("utf-8", "ignore")
vars_scope = merge_dict(vars_scope, yaml.load(output, Loader=yaml.Loader))
break
except Exception as err:
if unlimited_retries or current_retry < max_retries:
current_retry += 1
print('URL request #{0} failed, sleeping {1} seconds and retrying'.format(current_retry, max_delay))
sleep(max_delay)
continue
raise err
return vars_scope
def mergeDefaultsFromFile(vars_scope, file):
"""
Fetch defaults from a file and merge them into a single dict
"""
if not file:
return vars_scope
if os.path.exists(file):
with open(file, "r") as f:
vars_scope = merge_dict(vars_scope, yaml.load(f.read(), Loader=yaml.Loader))
return vars_scope
def loadDefaults():
"""
Generate a consolidated map containing variables used to drive Ansible functionality.
Defaults are loaded in a particular order such that latter overrides former.
"""
# Load base defaults from splunk-ansible repository
base = loadBaseDefaults()
if not base.get("config"):
return base
# Add "baked" files to array
for yml in loadBakedDefaults(base.get("config")):
base = mergeDefaults(base, yml["key"], yml["src"])
# Add "env" files to array
for yml in loadEnvDefaults(base.get("config")):
base = mergeDefaults(base, yml["key"], yml["src"])
# Add "host" files to array
for yml in loadHostDefaults(base.get("config")):
base = mergeDefaults(base, yml["key"], yml["src"])
return base
def loadBaseDefaults():
"""
Load the base defaults shipped in splunk-ansible
"""
yml = {}
filename = "splunk_defaults_{}.yml".format(PLATFORM)
if os.environ.get("SPLUNK_ROLE") == "splunk_universal_forwarder":
filename = "splunkforwarder_defaults_{}.yml".format(PLATFORM)
with open(os.path.join(HERE, filename), "r") as yaml_file:
yml = yaml.load(yaml_file, Loader=yaml.Loader)
return yml
def loadBakedDefaults(config):
"""
Load the defaults in "baked" key of the configuration.
"""
if not config or not config.get("baked"):
return []
files = config["baked"].split(",")
default_dir = config.get("defaults_dir", "")
return [{"key": "baked", "src": os.path.join(default_dir, f.strip())} for f in files]
def loadEnvDefaults(config):
"""
Load the defaults in "env" key of the configuration.
"""
if not config or not config.get("env") or not config["env"].get("var"):
return []
urls = os.environ.get(config["env"]["var"], "")
if not urls:
return []
return [{"key": "env", "src": url} for url in urls.split(",")]
def loadHostDefaults(config):
"""
Load the defaults in "host" key of the configuration.
"""
if not config or not config.get("host") or not config["host"].get("url"):
return []
urls = config["host"]["url"].split(",")
return [{"key": "host", "src": url} for url in urls]
def obfuscate_vars(inventory):
"""
Remove sensitive variables when dumping inventory out to stdout or file
"""
stars = "*"*14
splunkVars = inventory.get("all", {}).get("vars", {}).get("splunk", {})
if splunkVars.get("password"):
splunkVars["password"] = stars
if splunkVars.get("pass4SymmKey"):
splunkVars["pass4SymmKey"] = stars
if splunkVars.get("shc") and splunkVars["shc"].get("secret"):
splunkVars["shc"]["secret"] = stars
if splunkVars.get("shc") and splunkVars["shc"].get("pass4SymmKey"):
splunkVars["shc"]["pass4SymmKey"] = stars
if splunkVars.get("idxc") and splunkVars["idxc"].get("secret"):
splunkVars["idxc"]["secret"] = stars
if splunkVars.get("idxc") and splunkVars["idxc"].get("pass4SymmKey"):
splunkVars["idxc"]["pass4SymmKey"] = stars
if splunkVars.get("smartstore") and splunkVars["smartstore"].get("index"):
splunkIndexes = splunkVars["smartstore"]["index"]
for idx in range(0, len(splunkIndexes)):
if splunkIndexes[idx].get("s3"):
if splunkIndexes[idx]["s3"].get("access_key"):
splunkIndexes[idx]["s3"]["access_key"] = stars
if splunkIndexes[idx]["s3"].get("secret_key"):
splunkIndexes[idx]["s3"]["secret_key"] = stars
return inventory
def create_parser():
"""
Create an argparser to control dynamic inventory execution
"""
parser = argparse.ArgumentParser(description='Return Ansible inventory defined in the environment.')
parser.add_argument('--list', action='store_true', default=True, help='List all hosts (default: True)')
parser.add_argument('--host', action='store', help='Only get information for a specific host.')
parser.add_argument('--write-to-file', action='store_true', default=False, help='Write to file for debugging')
parser.add_argument('--write-to-stdout', action='store_true', default=False, help='create a default.yml file shown on stdout from current vars')
return parser
def prep_for_yaml_out(inventory):
"""
Prune the inventory by removing select keys before printing/writing to file
"""
inventory_to_dump = inventory["all"]["vars"]
keys_to_del = ["ansible_ssh_user",
"apps_location",
"build_location",
"hostname",
"role",
"preferred_captaincy",
"license_uri"]
for key in keys_to_del:
if key in inventory_to_dump:
del inventory_to_dump[key]
if key in inventory_to_dump["splunk"]:
del inventory_to_dump["splunk"][key]
if key in inventory_to_dump["splunk"]["app_paths"]:
del inventory_to_dump["splunk"]["app_paths"][key]
if key in inventory_to_dump["splunk"]["shc"]:
del inventory_to_dump["splunk"]["shc"][key]
if key in inventory_to_dump["splunk"]["idxc"]:
del inventory_to_dump["splunk"]["idxc"][key]
#remove extra stuff when we know it's the forwarder
if inventory_to_dump["splunk"]["home"] == "/opt/splunkforwarder":
del inventory_to_dump["splunk"]["idxc"]
del inventory_to_dump["splunk"]["shc"]
return inventory_to_dump
def main():
"""
Primary entrypoint to dynamic inventory script
"""
parser = create_parser()
args = parser.parse_args()
getSplunkInventory(inventory)
if args.write_to_file:
with open(os.path.join("/opt/container_artifact", "ansible_inventory.json"), "w") as outfile:
json.dump(obfuscate_vars(inventory), outfile, sort_keys=True, indent=4, ensure_ascii=False)
elif args.write_to_stdout:
#remove keys we don't want to print
inventory_to_dump = prep_for_yaml_out(inventory)
print("---")
print(yaml.dump(inventory_to_dump, default_flow_style=False))
else:
print(json.dumps(inventory))
if __name__ == "__main__":
main()

Powered by BW's shoe-string budget.