diff --git a/Makefile b/Makefile index 4d9ae281..df6c6e59 100644 --- a/Makefile +++ b/Makefile @@ -7,8 +7,8 @@ SPLUNK_ANSIBLE_BRANCH ?= develop SPLUNK_COMPOSE ?= cluster_absolute_unit.yaml # Set Splunk version/build parameters here to define downstream URLs and file names SPLUNK_PRODUCT := splunk -SPLUNK_VERSION := 8.0.2 -SPLUNK_BUILD := a7f645ddaf91 +SPLUNK_VERSION := 8.0.2.1 +SPLUNK_BUILD := f002026bad55 ifeq ($(shell arch), s390x) SPLUNK_ARCH = s390x else diff --git a/README.md b/README.md index 97e3de18..5e7dec28 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ [![Build Status](https://circleci.com/gh/splunk/docker-splunk/tree/develop.svg?style=svg)](https://circleci.com/gh/splunk/docker-splunk/tree/develop) -Welcome to Splunk's official repository containing Dockerfiles for building Splunk Enterprise and Universal Forwarder images using containerization technology. This repository supports all Splunk roles and deployment topologies, and currently works on any Linux-based platform. +Welcome to Splunk's official repository containing Dockerfiles for building Splunk Enterprise and Universal Forwarder images using containerization technology. The provisioning of these disjoint containers is handled by the [splunk-ansible](https://github.com/splunk/splunk-ansible) project. Please refer to [Ansible documentation](http://docs.ansible.com/) for more details about Ansible concepts and how it works. @@ -26,9 +26,7 @@ Splunk Enterprise is a platform for operational intelligence. Our software lets Please refer to [Splunk products](https://www.splunk.com/en_us/software.html) for more knowledge about the features and capabilities of Splunk, and how you can bring it into your organization. ##### What is docker-splunk? -This is the official source code repository for building Docker images of Splunk Enterprise and Splunk Universal Forwarder. By introducing containerization, we can marry the ideals of infrastructure-as-code and declarative directives to manage and run Splunk and its other product offerings. - -This repository should be used by people interested in running Splunk in their container orchestration environments. With this Docker image, we support running a standalone development Splunk instance as easily as running a full-fledged distributed production cluster, all while maintaining the best practices and recommended standards of operating Splunk at scale. +This is the official source code repository for building Docker images of Splunk Enterprise and Splunk Universal Forwarder. By introducing containerization, we can marry the ideals of infrastructure-as-code and declarative directives to manage and run Splunk Enterprise. ## Quickstart Use the following command to start a single standalone instance of Splunk Enterprise: diff --git a/base/redhat-8/Dockerfile b/base/redhat-8/Dockerfile index 21c0d708..5c9cc528 100644 --- a/base/redhat-8/Dockerfile +++ b/base/redhat-8/Dockerfile @@ -16,7 +16,7 @@ # the container catalog moved from registry.access.redhat.com to registry.redhat.io # So at some point before they deprecate the old registry we have to make sure that # we have access to the new registry and change where we pull the ubi image from. -FROM registry.access.redhat.com/ubi8/ubi-minimal:8.1-328 +FROM registry.access.redhat.com/ubi8/ubi-minimal:8.1 LABEL name="splunk" \ maintainer="support@splunk.com" \ vendor="splunk" \ diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index b1b3f8d4..344e87e0 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -2,9 +2,11 @@ ## Navigation +* [8.0.2.1](#8021) * [8.0.2](#802) * [8.0.1](#801) * [8.0.0](#800) +* [7.3.4.2](#7342) * [7.3.4](#734) * [7.3.3](#733) * [7.3.2](#732) @@ -24,6 +26,23 @@ --- +## 8.0.2.1 + +#### What's New? +* Releasing new images to support Splunk Enterprise maintenance patch. + +#### docker-splunk changes: +* Bumping Splunk version. For details, see [Fixed issues](https://docs.splunk.com/Documentation/Splunk/8.0.2/ReleaseNotes/Fixedissues) in 8.0.2.1. +* Bugfixes and additional tests for new features + +#### splunk-ansible changes: +* Added support for reading `SPLUNK_PASSWORD` from a file +* License master and cluster master URLs are now also configurable in the `default.yml` config, as well as with the `LICENSE_MASTER_URL` and `CLUSTER_MASTER_URL` environment variables +* Added support for auto-detecting the `service_name` for SplunkForwarder and allowing manual configuration with `splunk.service_name` +* All HEC related variables were revised to follow a nested dict format in `default.yml`, i.e. `splunk.hec_enableSSL` is now `splunk.hec.ssl`. See the [Provision HEC](https://github.com/splunk/splunk-ansible/blob/develop/docs/EXAMPLES.md#provision-hec) example in the docs. + +--- + ## 8.0.2 #### What's New? @@ -34,7 +53,7 @@ * Bugfixes and increasing test coverage for new features #### splunk-ansible changes: -* * Revised Splunk forwarding/receiving plays to optionally support SSL (see documentation on [securing data from forwarders](https://docs.splunk.com/Documentation/Splunk/latest/Security/Aboutsecuringdatafromforwarders)) +* Revised Splunk forwarding/receiving plays to optionally support SSL (see documentation on [securing data from forwarders](https://docs.splunk.com/Documentation/Splunk/latest/Security/Aboutsecuringdatafromforwarders)) * Initial support for forwarder management using [Splunk Monitoring Console](https://docs.splunk.com/Documentation/Splunk/latest/DMC/DMCoverview) * New environment variables exposed to control replication/search factor for clusters, key/value pairs written to `splunk-launch.conf`, and replacing default security key (pass4SymmKey) @@ -78,17 +97,32 @@ --- +## 7.3.4.2 + +#### What's New? +* Releasing new images to support Splunk Enterprise maintenance patch. +* Bundling in changes to be consistent with the release of [8.0.2.1](#8021). + +#### docker-splunk changes: +* Bumping Splunk version. For details, see [Fixed issues](https://docs.splunk.com/Documentation/Splunk/7.3.4/ReleaseNotes/Fixedissues) in 7.3.4.2. +* See [8.0.2.1](#8021) changes. + +#### splunk-ansible changes: +* See [8.0.2.1](#8021) changes. + +--- + ## 7.3.4 #### What's New? * New Splunk Enterprise release of 7.3.4 #### docker-splunk changes: -* Bumping Splunk version. For details, see: https://docs.splunk.com/Documentation/Splunk/7.3.4/ReleaseNotes/Fixedissues -* See [8.0.1](#801) changes +* Bumping Splunk version. For details, see [Fixed issues](https://docs.splunk.com/Documentation/Splunk/7.3.4/ReleaseNotes/Fixedissues). +* See [8.0.1](#801) changes. #### splunk-ansible changes: -* See [8.0.1](#801) changes +* See [8.0.1](#801) changes. --- diff --git a/docs/TROUBLESHOOTING.md b/docs/TROUBLESHOOTING.md index f760e8c7..f139932e 100644 --- a/docs/TROUBLESHOOTING.md +++ b/docs/TROUBLESHOOTING.md @@ -52,9 +52,9 @@ $ docker logs -f If your container is still running but in a bad state, you can try to debug by putting yourself within the context of that process. -To gain interactive shell access to the container's runtime, you can run: +To gain interactive shell access to the container's runtime as the splunk user, you can run: ``` -$ docker exec -it /bin/bash +$ docker exec -it -u splunk /bin/bash ``` #### Debug variables @@ -142,17 +142,17 @@ Generating a diag is only an option if: To create this diag, run the following command: ``` -$ docker exec -it ${SPLUNK_HOME}/bin/splunk diag +$ docker exec -it -u splunk "${SPLUNK_HOME}/bin/splunk diag" ``` Additionally, if your Docker container/hosts have access to https://www.splunk.com you can now send the file directly to Splunk Support by using the following command: ``` -$ docker exec -it ${SPLUNK_HOME}/bin/splunk diag --upload --case-number= --upload-user= --upload-password= --upload-description="Monday diag, as requested" +$ docker exec -it -u splunk "${SPLUNK_HOME}/bin/splunk diag --upload --case-number= --upload-user= --upload-password= --upload-description='Monday diag, as requested'" ``` However, if you don't have direct access, you can manually copy the diag back to your host via `docker cp`: ``` -$ docker cp :/opt/splunk/var/run/diags/ +$ docker cp :/opt/splunk/ ``` ## Contact diff --git a/test_scenarios/1sh1cm.yaml b/test_scenarios/1sh1cm.yaml new file mode 100644 index 00000000..eecf5654 --- /dev/null +++ b/test_scenarios/1sh1cm.yaml @@ -0,0 +1,42 @@ +version: "3.6" + +networks: + splunknet: + driver: bridge + attachable: true + +services: + sh1: + networks: + splunknet: + aliases: + - sh1 + image: ${SPLUNK_IMAGE:-splunk/splunk:latest} + hostname: sh1 + container_name: sh1 + environment: + - SPLUNK_START_ARGS=--accept-license + - SPLUNK_CLUSTER_MASTER_URL=cm1 + - SPLUNK_ROLE=splunk_search_head + - SPLUNK_PASSWORD + - DEBUG=true + ports: + - 8000 + - 8089 + + cm1: + networks: + splunknet: + aliases: + - cm1 + image: ${SPLUNK_IMAGE:-splunk/splunk:latest} + hostname: cm1 + container_name: cm1 + environment: + - SPLUNK_START_ARGS=--accept-license + - SPLUNK_ROLE=splunk_cluster_master + - SPLUNK_PASSWORD + - DEBUG=true + ports: + - 8000 + - 8089 diff --git a/test_scenarios/1so1cm_connected.yaml b/test_scenarios/1so1cm_connected.yaml new file mode 100644 index 00000000..8cca9788 --- /dev/null +++ b/test_scenarios/1so1cm_connected.yaml @@ -0,0 +1,41 @@ +version: "3.6" + +networks: + splunknet: + driver: bridge + attachable: true + +services: + cm1: + networks: + splunknet: + aliases: + - cm1 + image: ${SPLUNK_IMAGE:-splunk/splunk:latest} + hostname: cm1 + container_name: cm1 + environment: + - SPLUNK_START_ARGS=--accept-license + - SPLUNK_ROLE=splunk_cluster_master + - DEBUG=true + - SPLUNK_PASSWORD + ports: + - 8000 + - 8089 + + so1: + networks: + splunknet: + aliases: + - so1 + image: ${SPLUNK_IMAGE:-splunk/splunk:latest} + hostname: so1 + container_name: so1 + environment: + - SPLUNK_START_ARGS=--accept-license + - SPLUNK_CLUSTER_MASTER_URL=cm1 + - DEBUG=true + - SPLUNK_PASSWORD + ports: + - 8000 + - 8089 diff --git a/test_scenarios/1so1cm_unconnected.yaml b/test_scenarios/1so1cm_unconnected.yaml new file mode 100644 index 00000000..eae1f96c --- /dev/null +++ b/test_scenarios/1so1cm_unconnected.yaml @@ -0,0 +1,40 @@ +version: "3.6" + +networks: + splunknet: + driver: bridge + attachable: true + +services: + cm1: + networks: + splunknet: + aliases: + - cm1 + image: ${SPLUNK_IMAGE:-splunk/splunk:latest} + hostname: cm1 + container_name: cm1 + environment: + - SPLUNK_START_ARGS=--accept-license + - SPLUNK_ROLE=splunk_cluster_master + - DEBUG=true + - SPLUNK_PASSWORD + ports: + - 8000 + - 8089 + + so1: + networks: + splunknet: + aliases: + - so1 + image: ${SPLUNK_IMAGE:-splunk/splunk:latest} + hostname: so1 + container_name: so1 + environment: + - SPLUNK_START_ARGS=--accept-license + - DEBUG=true + - SPLUNK_PASSWORD + ports: + - 8000 + - 8089 diff --git a/tests/fixtures/pwfile b/tests/fixtures/pwfile new file mode 100644 index 00000000..17329a5a --- /dev/null +++ b/tests/fixtures/pwfile @@ -0,0 +1 @@ +changeme123 diff --git a/tests/test_docker_splunk.py b/tests/test_docker_splunk.py index 7e657647..1a49c932 100644 --- a/tests/test_docker_splunk.py +++ b/tests/test_docker_splunk.py @@ -42,7 +42,7 @@ global platform platform = "debian-9" -OLD_SPLUNK_VERSION = "7.3.2" +OLD_SPLUNK_VERSION = "7.3.4" def generate_random_string(): @@ -396,6 +396,28 @@ def test_splunk_entrypoint_no_provision(self): if cid: self.client.remove_container(cid, v=True, force=True) + def test_splunk_uid_gid(self): + cid = None + try: + # Run container + cid = self.client.create_container(self.SPLUNK_IMAGE_NAME, tty=True, command="no-provision") + cid = cid.get("Id") + self.client.start(cid) + # Wait a bit + time.sleep(5) + # If the container is still running, we should be able to exec inside + # Check that the git SHA exists in /opt/ansible + exec_command = self.client.exec_create(cid, "id", user="splunk") + std_out = self.client.exec_start(exec_command) + assert "uid=41812" in std_out + assert "gid=41812" in std_out + except Exception as e: + self.logger.error(e) + raise e + finally: + if cid: + self.client.remove_container(cid, v=True, force=True) + def test_uf_entrypoint_help(self): # Run container cid = self.client.create_container(self.UF_IMAGE_NAME, tty=True, command="help") @@ -452,6 +474,28 @@ def test_uf_entrypoint_no_provision(self): if cid: self.client.remove_container(cid, v=True, force=True) + def test_uf_uid_gid(self): + cid = None + try: + # Run container + cid = self.client.create_container(self.UF_IMAGE_NAME, tty=True, command="no-provision") + cid = cid.get("Id") + self.client.start(cid) + # Wait a bit + time.sleep(5) + # If the container is still running, we should be able to exec inside + # Check that the git SHA exists in /opt/ansible + exec_command = self.client.exec_create(cid, "id", user="splunk") + std_out = self.client.exec_start(exec_command) + assert "uid=41812" in std_out + assert "gid=41812" in std_out + except Exception as e: + self.logger.error(e) + raise e + finally: + if cid: + self.client.remove_container(cid, v=True, force=True) + def test_adhoc_1so_using_default_yml(self): # Generate default.yml cid = self.client.create_container(self.SPLUNK_IMAGE_NAME, tty=True, command="create-defaults") @@ -811,6 +855,112 @@ def test_adhoc_1uf_change_tailed_files(self): if cid: self.client.remove_container(cid, v=True, force=True) + def test_adhoc_1so_password_from_file(self): + # Create a splunk container + cid = None + # From fixtures/pwfile + filePW = "changeme123" + try: + splunk_container_name = generate_random_string() + cid = self.client.create_container(self.SPLUNK_IMAGE_NAME, tty=True, ports=[8089], + volumes=["/var/secrets/pwfile"], name=splunk_container_name, + environment={ + "DEBUG": "true", + "SPLUNK_START_ARGS": "--accept-license", + "SPLUNK_PASSWORD": "/var/secrets/pwfile" + }, + host_config=self.client.create_host_config(binds=[FIXTURES_DIR + "/pwfile:/var/secrets/pwfile"], + port_bindings={8089: ("0.0.0.0",)}) + ) + cid = cid.get("Id") + self.client.start(cid) + # Poll for the container to be ready + assert self.wait_for_containers(1, name=splunk_container_name) + # Check splunkd + splunkd_port = self.client.port(cid, 8089)[0]["HostPort"] + url = "https://localhost:{}/services/server/info".format(splunkd_port) + kwargs = {"auth": ("admin", filePW), "verify": False} + status, content = self.handle_request_retry("GET", url, kwargs) + assert status == 200 + except Exception as e: + self.logger.error(e) + raise e + finally: + if cid: + self.client.remove_container(cid, v=True, force=True) + + def test_adhoc_1uf_password_from_file(self): + # Create a splunk container + cid = None + # From fixtures/pwfile + filePW = "changeme123" + try: + splunk_container_name = generate_random_string() + cid = self.client.create_container(self.UF_IMAGE_NAME, tty=True, ports=[8089], + volumes=["/var/secrets/pwfile"], name=splunk_container_name, + environment={ + "DEBUG": "true", + "SPLUNK_START_ARGS": "--accept-license", + "SPLUNK_PASSWORD": "/var/secrets/pwfile" + }, + host_config=self.client.create_host_config(binds=[FIXTURES_DIR + "/pwfile:/var/secrets/pwfile"], + port_bindings={8089: ("0.0.0.0",)}) + ) + cid = cid.get("Id") + self.client.start(cid) + # Poll for the container to be ready + assert self.wait_for_containers(1, name=splunk_container_name) + # Check splunkd + splunkd_port = self.client.port(cid, 8089)[0]["HostPort"] + url = "https://localhost:{}/services/server/info".format(splunkd_port) + kwargs = {"auth": ("admin", filePW), "verify": False} + status, content = self.handle_request_retry("GET", url, kwargs) + assert status == 200 + except Exception as e: + self.logger.error(e) + raise e + finally: + if cid: + self.client.remove_container(cid, v=True, force=True) + + def test_adhoc_1so_splunk_pass4symmkey(self): + # Create a splunk container + cid = None + try: + splunk_container_name = generate_random_string() + cid = self.client.create_container(self.SPLUNK_IMAGE_NAME, tty=True, ports=[8089], name=splunk_container_name, + environment={ + "DEBUG": "true", + "SPLUNK_START_ARGS": "--accept-license", + "SPLUNK_PASSWORD": self.password, + "SPLUNK_PASS4SYMMKEY": "wubbalubbadubdub" + }, + host_config=self.client.create_host_config(port_bindings={8089: ("0.0.0.0",)}) + ) + cid = cid.get("Id") + self.client.start(cid) + # Poll for the container to be ready + assert self.wait_for_containers(1, name=splunk_container_name) + # Check splunkd + splunkd_port = self.client.port(cid, 8089)[0]["HostPort"] + url = "https://localhost:{}/services/server/info".format(splunkd_port) + kwargs = {"auth": ("admin", self.password), "verify": False} + status, content = self.handle_request_retry("GET", url, kwargs) + assert status == 200 + # Check the decrypted pass4SymmKey + exec_command = self.client.exec_create(cid, "cat /opt/splunk/etc/system/local/server.conf", user="splunk") + std_out = self.client.exec_start(exec_command) + pass4SymmKey = re.search(r'\[general\].*?pass4SymmKey = (.*?)\n', std_out, flags=re.MULTILINE|re.DOTALL).group(1).strip() + exec_command = self.client.exec_create(cid, "/opt/splunk/bin/splunk show-decrypted --value '{}'".format(pass4SymmKey), user="splunk") + std_out = self.client.exec_start(exec_command) + assert "wubbalubbadubdub" in std_out + except Exception as e: + self.logger.error(e) + raise e + finally: + if cid: + self.client.remove_container(cid, v=True, force=True) + def test_adhoc_1so_splunk_secret_env(self): # Create a splunk container cid = None @@ -845,6 +995,44 @@ def test_adhoc_1so_splunk_secret_env(self): finally: if cid: self.client.remove_container(cid, v=True, force=True) + + def test_adhoc_1uf_splunk_pass4symmkey(self): + # Create a splunk container + cid = None + try: + splunk_container_name = generate_random_string() + cid = self.client.create_container(self.UF_IMAGE_NAME, tty=True, ports=[8089], name=splunk_container_name, + environment={ + "DEBUG": "true", + "SPLUNK_START_ARGS": "--accept-license", + "SPLUNK_PASSWORD": self.password, + "SPLUNK_PASS4SYMMKEY": "wubbalubbadubdub" + }, + host_config=self.client.create_host_config(port_bindings={8089: ("0.0.0.0",)}) + ) + cid = cid.get("Id") + self.client.start(cid) + # Poll for the container to be ready + assert self.wait_for_containers(1, name=splunk_container_name) + # Check splunkd + splunkd_port = self.client.port(cid, 8089)[0]["HostPort"] + url = "https://localhost:{}/services/server/info".format(splunkd_port) + kwargs = {"auth": ("admin", self.password), "verify": False} + status, content = self.handle_request_retry("GET", url, kwargs) + assert status == 200 + # Check the decrypted pass4SymmKey + exec_command = self.client.exec_create(cid, "cat /opt/splunkforwarder/etc/system/local/server.conf", user="splunk") + std_out = self.client.exec_start(exec_command) + pass4SymmKey = re.search(r'\[general\].*?pass4SymmKey = (.*?)\n', std_out, flags=re.MULTILINE|re.DOTALL).group(1).strip() + exec_command = self.client.exec_create(cid, "/opt/splunkforwarder/bin/splunk show-decrypted --value '{}'".format(pass4SymmKey), user="splunk") + std_out = self.client.exec_start(exec_command) + assert "wubbalubbadubdub" in std_out + except Exception as e: + self.logger.error(e) + raise e + finally: + if cid: + self.client.remove_container(cid, v=True, force=True) def test_adhoc_1uf_splunk_secret_env(self): # Create a uf container @@ -1005,7 +1193,7 @@ def test_adhoc_1so_postplaybook_with_sudo(self): self.client.remove_container(cid, v=True, force=True) def test_adhoc_1so_apps_location_in_default_yml(self): - with tarfile.open(EXAMPLE_APP_TGZ, "w:gz") as tar: + with tarfile.open(EXAMPLE_APP_TGZ, "w:gz") as tar: tar.add(EXAMPLE_APP, arcname=os.path.basename(EXAMPLE_APP)) # Generate default.yml cid = self.client.create_container(self.SPLUNK_IMAGE_NAME, tty=True, command="create-defaults") @@ -1057,7 +1245,7 @@ def test_adhoc_1so_apps_location_in_default_yml(self): if cid: self.client.remove_container(cid, v=True, force=True) try: - os.remove(EXAMPLE_APP_TGZ) + os.remove(EXAMPLE_APP_TGZ) os.remove(os.path.join(FIXTURES_DIR, "default.yml")) except OSError: pass @@ -1145,11 +1333,7 @@ def test_adhoc_1uf_bind_mount_apps(self): # Poll for the container to be ready assert self.wait_for_containers(1, name=splunk_container_name) # Check splunkd - splunkd_port = self.client.port(cid, 8089)[0]["HostPort"] - url = "https://localhost:{}/services/server/info".format(splunkd_port) - kwargs = {"auth": ("admin", password), "verify": False} - status, content = self.handle_request_retry("GET", url, kwargs) - assert status == 200 + assert self.check_splunkd("admin", password) # Check the app endpoint splunkd_port = self.client.port(cid, 8089)[0]["HostPort"] url = "https://localhost:{}/servicesNS/nobody/splunk_app_example/configs/conf-app/launcher?output_mode=json".format(splunkd_port) @@ -1170,43 +1354,183 @@ def test_adhoc_1uf_bind_mount_apps(self): except OSError: pass + def test_adhoc_1so_hec_idempotence(self): + """ + This test is intended to check how the container gets provisioned with changing splunk.hec.* parameters + """ + # Create a splunk container + cid = None + try: + splunk_container_name = generate_random_string() + cid = self.client.create_container(self.SPLUNK_IMAGE_NAME, tty=True, ports=[8089, 8088, 9999], + volumes=["/playbooks/play.yml"], name=splunk_container_name, + environment={ + "DEBUG": "true", + "SPLUNK_START_ARGS": "--accept-license", + "SPLUNK_PASSWORD": self.password + }, + host_config=self.client.create_host_config(port_bindings={8089: ("0.0.0.0",), 8088: ("0.0.0.0",), 9999: ("0.0.0.0",)}) + ) + cid = cid.get("Id") + self.client.start(cid) + # Poll for the container to be ready + assert self.wait_for_containers(1, name=splunk_container_name) + # Check splunkd + assert self.check_splunkd("admin", self.password) + # Check that HEC endpoint is up - by default, the image will enable HEC + exec_command = self.client.exec_create(cid, "cat /opt/splunk/etc/apps/splunk_httpinput/local/inputs.conf", user="splunk") + std_out = self.client.exec_start(exec_command) + assert std_out == '''[http] +disabled = 0 +''' + exec_command = self.client.exec_create(cid, "netstat -tuln", user="splunk") + std_out = self.client.exec_start(exec_command) + assert "tcp 0 0 0.0.0.0:8088 0.0.0.0:* LISTEN" in std_out + # Create a new /tmp/defaults/default.yml to change desired HEC settings + exec_command = self.client.exec_create(cid, "mkdir -p /tmp/defaults", user="splunk") + self.client.exec_start(exec_command) + exec_command = self.client.exec_create(cid, '''bash -c 'cat > /tmp/defaults/default.yml << EOL +splunk: + hec: + port: 9999 + token: hihihi + ssl: False +EOL' +''', user="splunk") + self.client.exec_start(exec_command) + # Restart the container - it should pick up the new HEC settings in /tmp/defaults/default.yml + self.client.restart(splunk_container_name) + assert self.wait_for_containers(1, name=splunk_container_name) + assert self.check_splunkd("admin", self.password) + # Check the new HEC settings + exec_command = self.client.exec_create(cid, "cat /opt/splunk/etc/apps/splunk_httpinput/local/inputs.conf", user="splunk") + std_out = self.client.exec_start(exec_command) + assert '''[http] +disabled = 0 +enableSSL = 0 +port = 9999''' in std_out + assert '''[http://splunk_hec_token] +disabled = 0 +token = hihihi''' in std_out + exec_command = self.client.exec_create(cid, "netstat -tuln", user="splunk") + std_out = self.client.exec_start(exec_command) + assert "tcp 0 0 0.0.0.0:9999 0.0.0.0:* LISTEN" in std_out + # Check HEC + hec_port = self.client.port(cid, 9999)[0]["HostPort"] + url = "http://localhost:{}/services/collector/event".format(hec_port) + kwargs = {"json": {"event": "hello world"}, "headers": {"Authorization": "Splunk hihihi"}} + status, content = self.handle_request_retry("POST", url, kwargs) + assert status == 200 + # Modify the HEC configuration + exec_command = self.client.exec_create(cid, '''bash -c 'cat > /tmp/defaults/default.yml << EOL +splunk: + hec: + port: 8088 + token: byebyebye + ssl: True +EOL' +''', user="splunk") + self.client.exec_start(exec_command) + # Restart the container - it should pick up the new HEC settings in /tmp/defaults/default.yml + self.client.restart(splunk_container_name) + assert self.wait_for_containers(1, name=splunk_container_name) + assert self.check_splunkd("admin", self.password) + # Check the new HEC settings + exec_command = self.client.exec_create(cid, "cat /opt/splunk/etc/apps/splunk_httpinput/local/inputs.conf", user="splunk") + std_out = self.client.exec_start(exec_command) + assert '''[http] +disabled = 0 +enableSSL = 1 +port = 8088''' in std_out + assert '''[http://splunk_hec_token] +disabled = 0 +token = byebyebye''' in std_out + exec_command = self.client.exec_create(cid, "netstat -tuln", user="splunk") + std_out = self.client.exec_start(exec_command) + assert "tcp 0 0 0.0.0.0:8088 0.0.0.0:* LISTEN" in std_out + # Check HEC + hec_port = self.client.port(cid, 8088)[0]["HostPort"] + url = "https://localhost:{}/services/collector/event".format(hec_port) + kwargs = {"json": {"event": "hello world"}, "headers": {"Authorization": "Splunk byebyebye"}, "verify": False} + status, content = self.handle_request_retry("POST", url, kwargs) + assert status == 200 + # Remove the token + exec_command = self.client.exec_create(cid, '''bash -c 'cat > /tmp/defaults/default.yml << EOL +splunk: + hec: + token: +EOL' +''', user="splunk") + self.client.exec_start(exec_command) + # Restart the container - it should pick up the new HEC settings in /tmp/defaults/default.yml + self.client.restart(splunk_container_name) + assert self.wait_for_containers(1, name=splunk_container_name) + assert self.check_splunkd("admin", self.password) + # Check the new HEC settings + exec_command = self.client.exec_create(cid, "cat /opt/splunk/etc/apps/splunk_httpinput/local/inputs.conf", user="splunk") + std_out = self.client.exec_start(exec_command) + # NOTE: The previous configuration still applies - we just deleted the former token + assert '''[http] +disabled = 0 +enableSSL = 1 +port = 8088''' in std_out + assert "[http://splunk_hec_token]" not in std_out + exec_command = self.client.exec_create(cid, "netstat -tuln", user="splunk") + std_out = self.client.exec_start(exec_command) + assert "tcp 0 0 0.0.0.0:8088 0.0.0.0:* LISTEN" in std_out + # Disable HEC entirely + exec_command = self.client.exec_create(cid, '''bash -c 'cat > /tmp/defaults/default.yml << EOL +splunk: + hec: + enable: False +EOL' +''', user="splunk") + self.client.exec_start(exec_command) + # Restart the container - it should pick up the new HEC settings in /tmp/defaults/default.yml + self.client.restart(splunk_container_name) + assert self.wait_for_containers(1, name=splunk_container_name) + assert self.check_splunkd("admin", self.password) + # Check the new HEC settings + exec_command = self.client.exec_create(cid, "cat /opt/splunk/etc/apps/splunk_httpinput/local/inputs.conf", user="splunk") + std_out = self.client.exec_start(exec_command) + assert '''[http] +disabled = 1''' in std_out + exec_command = self.client.exec_create(cid, "netstat -tuln", user="splunk") + std_out = self.client.exec_start(exec_command) + assert "tcp 0 0 0.0.0.0:8088 0.0.0.0:* LISTEN" not in std_out + except Exception as e: + self.logger.error(e) + raise e + finally: + if cid: + self.client.remove_container(cid, v=True, force=True) + def test_adhoc_1so_hec_ssl_disabled(self): - # Generate default.yml - cid = self.client.create_container(self.SPLUNK_IMAGE_NAME, tty=True, command="create-defaults") - self.client.start(cid.get("Id")) - output = self.get_container_logs(cid.get("Id")) - self.client.remove_container(cid.get("Id"), v=True, force=True) - # Get the password - password = re.search(" password: (.*)", output).group(1).strip() - assert password - # Get the HEC token - hec_token = re.search(" hec_token: (.*)", output).group(1).strip() - assert hec_token - # Make sure hec_enableSSL is disabled - output = re.sub(r' hec_enableSSL: 1', r' hec_enableSSL: 0', output) - # Write the default.yml to a file - with open(os.path.join(FIXTURES_DIR, "default.yml"), "w") as f: - f.write(output) - # Create the container and mount the default.yml + # Create the container cid = None try: splunk_container_name = generate_random_string() cid = self.client.create_container(self.SPLUNK_IMAGE_NAME, tty=True, ports=[8089, 8088], volumes=["/tmp/defaults/"], name=splunk_container_name, - environment={"DEBUG": "true", "SPLUNK_START_ARGS": "--accept-license"}, - host_config=self.client.create_host_config(binds=[FIXTURES_DIR + ":/tmp/defaults/"], - port_bindings={8089: ("0.0.0.0",), 8088: ("0.0.0.0",)}) + environment={ + "DEBUG": "true", + "SPLUNK_START_ARGS": "--accept-license", + "SPLUNK_HEC_TOKEN": "get-schwifty", + "SPLUNK_HEC_SSL": "False", + "SPLUNK_PASSWORD": self.password + }, + host_config=self.client.create_host_config(port_bindings={8089: ("0.0.0.0",), 8088: ("0.0.0.0",)}) ) cid = cid.get("Id") self.client.start(cid) # Poll for the container to be ready assert self.wait_for_containers(1, name=splunk_container_name) # Check splunkd - assert self.check_splunkd("admin", password) + assert self.check_splunkd("admin", self.password) # Check HEC hec_port = self.client.port(cid, 8088)[0]["HostPort"] url = "http://localhost:{}/services/collector/event".format(hec_port) - kwargs = {"json": {"event": "hello world"}, "headers": {"Authorization": "Splunk {}".format(hec_token)}} + kwargs = {"json": {"event": "hello world"}, "headers": {"Authorization": "Splunk get-schwifty"}} status, content = self.handle_request_retry("POST", url, kwargs) assert status == 200 except Exception as e: @@ -1221,42 +1545,31 @@ def test_adhoc_1so_hec_ssl_disabled(self): pass def test_adhoc_1uf_hec_ssl_disabled(self): - # Generate default.yml - cid = self.client.create_container(self.UF_IMAGE_NAME, tty=True, command="create-defaults") - self.client.start(cid.get("Id")) - output = self.get_container_logs(cid.get("Id")) - self.client.remove_container(cid.get("Id"), v=True, force=True) - # Get the password - password = re.search(" password: (.*)", output).group(1).strip() - assert password - # Get the HEC token - hec_token = re.search(" hec_token: (.*)", output).group(1).strip() - assert hec_token - # Make sure hec_enableSSL is disabled - output = re.sub(r' hec_enableSSL: 1', r' hec_enableSSL: 0', output) - # Write the default.yml to a file - with open(os.path.join(FIXTURES_DIR, "default.yml"), "w") as f: - f.write(output) - # Create the container and mount the default.yml + # Create the container cid = None try: splunk_container_name = generate_random_string() cid = self.client.create_container(self.UF_IMAGE_NAME, tty=True, ports=[8089, 8088], volumes=["/tmp/defaults/"], name=splunk_container_name, - environment={"DEBUG": "true", "SPLUNK_START_ARGS": "--accept-license"}, - host_config=self.client.create_host_config(binds=[FIXTURES_DIR + ":/tmp/defaults/"], - port_bindings={8089: ("0.0.0.0",), 8088: ("0.0.0.0",)}) + environment={ + "DEBUG": "true", + "SPLUNK_START_ARGS": "--accept-license", + "SPLUNK_HEC_TOKEN": "get-schwifty", + "SPLUNK_HEC_SSL": "false", + "SPLUNK_PASSWORD": self.password + }, + host_config=self.client.create_host_config(port_bindings={8089: ("0.0.0.0",), 8088: ("0.0.0.0",)}) ) cid = cid.get("Id") self.client.start(cid) # Poll for the container to be ready assert self.wait_for_containers(1, name=splunk_container_name) # Check splunkd - assert self.check_splunkd("admin", password) + assert self.check_splunkd("admin", self.password) # Check HEC hec_port = self.client.port(cid, 8088)[0]["HostPort"] url = "http://localhost:{}/services/collector/event".format(hec_port) - kwargs = {"json": {"event": "hello world"}, "headers": {"Authorization": "Splunk {}".format(hec_token)}} + kwargs = {"json": {"event": "hello world"}, "headers": {"Authorization": "Splunk get-schwifty"}} status, content = self.handle_request_retry("POST", url, kwargs) assert status == 200 except Exception as e: @@ -1611,7 +1924,7 @@ def test_compose_1deployment1so(self): container_name = container["Names"][0].strip("/") splunkd_port = self.client.port(container["Id"], 8089)[0]["HostPort"] if container_name == "depserver1": - # Check the app and version + # Check the app and version url = "https://localhost:{}/servicesNS/nobody/splunk_app_example/configs/conf-app/launcher?output_mode=json".format(splunkd_port) resp = requests.get(url, auth=("admin", self.password), verify=False) # Deployment server should *not* install the app @@ -1674,7 +1987,7 @@ def test_compose_1deployment1uf(self): container_name = container["Names"][0].strip("/") splunkd_port = self.client.port(container["Id"], 8089)[0]["HostPort"] if container_name == "depserver1": - # Check the app and version + # Check the app and version url = "https://localhost:{}/servicesNS/nobody/splunk_app_example/configs/conf-app/launcher?output_mode=json".format(splunkd_port) resp = requests.get(url, auth=("admin", self.password), verify=False) # Deployment server should *not* install the app @@ -2011,7 +2324,7 @@ def test_compose_1so_hec(self): self.check_common_keys(log_json, "so") try: # token "abcd1234" is hard-coded within the 1so_hec.yaml compose - assert log_json["all"]["vars"]["splunk"]["hec_token"] == "abcd1234" + assert log_json["all"]["vars"]["splunk"]["hec"]["token"] == "abcd1234" except KeyError as e: self.logger.error(e) raise e @@ -2043,7 +2356,7 @@ def test_compose_1uf_hec(self): self.check_common_keys(log_json, "uf") try: # token "abcd1234" is hard-coded within the 1so_hec.yaml compose - assert log_json["all"]["vars"]["splunk"]["hec_token"] == "abcd1234" + assert log_json["all"]["vars"]["splunk"]["hec"]["token"] == "abcd1234" except KeyError as e: self.logger.error(e) raise e @@ -2374,6 +2687,109 @@ def test_compose_3idx1cm_custom_repl_factor(self): except OSError as e: pass + def test_compose_1so1cm_connected(self): + # Standup deployment + self.compose_file_name = "1so1cm_connected.yaml" + self.project_name = generate_random_string() + container_count, rc = self.compose_up() + assert rc == 0 + # Wait for containers to come up + assert self.wait_for_containers(container_count, label="com.docker.compose.project={}".format(self.project_name)) + # Get container logs + container_mapping = {"so1": "so", "cm1": "cm"} + for container in container_mapping: + # Check ansible version & configs + ansible_logs = self.get_container_logs(container) + self.check_ansible(ansible_logs) + # Check values in log output + inventory_json = self.extract_json(container) + self.check_common_keys(inventory_json, container_mapping[container]) + # Check Splunkd on all the containers + assert self.check_splunkd("admin", self.password) + # Check connections + containers = self.client.containers(filters={"label": "com.docker.compose.service={}".format("cm1")}) + splunkd_port = self.client.port(containers[0]["Id"], 8089)[0]["HostPort"] + status, content = self.handle_request_retry("GET", "https://localhost:{}/services/cluster/master/searchheads?output_mode=json".format(splunkd_port), + {"auth": ("admin", self.password), "verify": False}) + assert status == 200 + output = json.loads(content) + assert len(output["entry"]) == 2 + for sh in output["entry"]: + assert sh["content"]["label"] in ["cm1", "so1"] + assert sh["content"]["status"] == "Connected" + + def test_compose_1so1cm_unconnected(self): + # Standup deployment + self.compose_file_name = "1so1cm_unconnected.yaml" + self.project_name = generate_random_string() + container_count, rc = self.compose_up() + assert rc == 0 + # Wait for containers to come up + assert self.wait_for_containers(container_count, label="com.docker.compose.project={}".format(self.project_name)) + # Get container logs + container_mapping = {"so1": "so", "cm1": "cm"} + for container in container_mapping: + # Check ansible version & configs + ansible_logs = self.get_container_logs(container) + self.check_ansible(ansible_logs) + # Check values in log output + inventory_json = self.extract_json(container) + self.check_common_keys(inventory_json, container_mapping[container]) + # Check Splunkd on all the containers + assert self.check_splunkd("admin", self.password) + # Check connections + containers = self.client.containers(filters={"label": "com.docker.compose.service={}".format("cm1")}) + splunkd_port = self.client.port(containers[0]["Id"], 8089)[0]["HostPort"] + status, content = self.handle_request_retry("GET", "https://localhost:{}/services/cluster/master/searchheads?output_mode=json".format(splunkd_port), + {"auth": ("admin", self.password), "verify": False}) + assert status == 200 + output = json.loads(content) + assert len(output["entry"]) == 1 + assert output["entry"][0]["content"]["label"] == "cm1" + assert output["entry"][0]["content"]["status"] == "Connected" + + def test_adhoc_1cm_idxc_pass4symmkey(self): + # Create the container + cid = None + try: + splunk_container_name = generate_random_string() + cid = self.client.create_container(self.SPLUNK_IMAGE_NAME, tty=True, ports=[8089], name=splunk_container_name, + environment={ + "DEBUG": "true", + "SPLUNK_START_ARGS": "--accept-license", + "SPLUNK_PASSWORD": self.password, + "SPLUNK_ROLE": "splunk_cluster_master", + "SPLUNK_INDEXER_URL": "idx1", + "SPLUNK_IDXC_PASS4SYMMKEY": "keepsummerbeingliketotallystokedaboutlikethegeneralvibeandstuff", + "SPLUNK_IDXC_LABEL": "keepsummersafe", + }, + host_config=self.client.create_host_config(port_bindings={8089: ("0.0.0.0",)}) + ) + cid = cid.get("Id") + self.client.start(cid) + # Poll for the container to be ready + assert self.wait_for_containers(1, name=splunk_container_name) + # Check splunkd + splunkd_port = self.client.port(cid, 8089)[0]["HostPort"] + url = "https://localhost:{}/services/server/info".format(splunkd_port) + kwargs = {"auth": ("admin", self.password), "verify": False} + status, content = self.handle_request_retry("GET", url, kwargs) + assert status == 200 + # Check if the cluster label and pass4SymmKey line up + exec_command = self.client.exec_create(cid, "cat /opt/splunk/etc/system/local/server.conf", user="splunk") + std_out = self.client.exec_start(exec_command) + assert "cluster_label = keepsummersafe" in std_out + pass4SymmKey = re.search(r'\[clustering\].*?pass4SymmKey = (.*?)\n', std_out, flags=re.MULTILINE|re.DOTALL).group(1).strip() + exec_command = self.client.exec_create(cid, "/opt/splunk/bin/splunk show-decrypted --value '{}'".format(pass4SymmKey), user="splunk") + std_out = self.client.exec_start(exec_command) + assert "keepsummerbeingliketotallystokedaboutlikethegeneralvibeandstuff" in std_out + except Exception as e: + self.logger.error(e) + raise e + finally: + if cid: + self.client.remove_container(cid, v=True, force=True) + def test_compose_1cm_smartstore(self): # Generate default.yml cid = self.client.create_container(self.SPLUNK_IMAGE_NAME, tty=True, command="create-defaults") @@ -2445,6 +2861,38 @@ def test_compose_1cm_smartstore(self): except OSError: pass + def test_compose_1sh1cm(self): + # Standup deployment + self.compose_file_name = "1sh1cm.yaml" + self.project_name = generate_random_string() + container_count, rc = self.compose_up() + assert rc == 0 + # Wait for containers to come up + assert self.wait_for_containers(container_count, label="com.docker.compose.project={}".format(self.project_name)) + # Get container logs + container_mapping = {"sh1": "sh", "cm1": "cm"} + for container in container_mapping: + # Check ansible version & configs + ansible_logs = self.get_container_logs(container) + self.check_ansible(ansible_logs) + # Check values in log output + inventory_json = self.extract_json(container) + self.check_common_keys(inventory_json, container_mapping[container]) + # Check Splunkd on all the containers + assert self.check_splunkd("admin", self.password) + # Check connections + containers = self.client.containers(filters={"label": "com.docker.compose.service={}".format("cm1")}) + splunkd_port = self.client.port(containers[0]["Id"], 8089)[0]["HostPort"] + status, content = self.handle_request_retry("GET", "https://localhost:{}/services/cluster/master/searchheads?output_mode=json".format(splunkd_port), + {"auth": ("admin", self.password), "verify": False}) + assert status == 200 + output = json.loads(content) + # There's only 1 "standalone" search head connected and 1 cluster master + assert len(output["entry"]) == 2 + for sh in output["entry"]: + assert sh["content"]["label"] == "sh1" or sh["content"]["label"] == "cm1" + assert sh["content"]["status"] == "Connected" + def test_compose_2idx2sh(self): # Standup deployment self.compose_file_name = "2idx2sh.yaml" @@ -2602,7 +3050,7 @@ def test_compose_2idx2sh1cm(self): inventory_json = self.extract_json(container) self.check_common_keys(inventory_json, container_mapping[container]) try: - assert inventory_json["all"]["vars"]["splunk"]["indexer_cluster"] == True + assert inventory_json["splunk_cluster_master"]["hosts"] == ["cm1"] assert inventory_json["splunk_indexer"]["hosts"] == ["idx1", "idx2"] assert inventory_json["splunk_search_head"]["hosts"] == ["sh1", "sh2"] except KeyError as e: diff --git a/uf/common-files/Dockerfile b/uf/common-files/Dockerfile index 5bbab1f5..3ebadf3a 100644 --- a/uf/common-files/Dockerfile +++ b/uf/common-files/Dockerfile @@ -36,6 +36,12 @@ COPY uf/common-files/apps ${SPLUNK_HOME}-etc/apps/ FROM ${SPLUNK_BASE_IMAGE}:latest as bare LABEL maintainer="support@splunk.com" +# Currently kubernetes only accepts UID and not USER field to +# start a container as a particular user. So we create Splunk +# user with pre-determined UID. +ARG UID=41812 +ARG GID=41812 + ENV SPLUNK_HOME=/opt/splunkforwarder \ SPLUNK_GROUP=splunk \ SPLUNK_USER=splunk @@ -44,8 +50,8 @@ ENV SPLUNK_HOME=/opt/splunkforwarder \ COPY [ "splunk/common-files/updateetc.sh", "/sbin/"] # Setup users and groups -RUN groupadd -r ${SPLUNK_GROUP} \ - && useradd -r -m -g ${SPLUNK_GROUP} ${SPLUNK_USER} \ +RUN groupadd -r -g ${GID} ${SPLUNK_GROUP} \ + && useradd -r -m -u ${UID} -g ${GID} ${SPLUNK_USER} \ && chmod 755 /sbin/updateetc.sh # Copy files from package