diff --git a/docs/SECURITY.md b/docs/SECURITY.md new file mode 100644 index 00000000..6f1ae709 --- /dev/null +++ b/docs/SECURITY.md @@ -0,0 +1,87 @@ +## Security ## +This section will cover various security considerations when using the Splunk Enterprise and Universal Forwarder containers. + +### Startup Users ### + +The Splunk Enterprise and Universal Forwarder containers may be started using one of the following three user accounts: + +* `splunk` (most secure): This user has no privileged access and cannot use `sudo` to change to another user account. +It is a member of the `ansible` group, which enables it to run the embedded playbooks at startup. When using the +`splunk` user, all processes will run as this user. Note that you must set the `SPLUNK_HOME_OWNERSHIP_ENFORCEMENT` +environment variable to `false` when starting as this user. ***Recommended for production*** + +* `ansible` (middle ground): This user is a member of the `sudo` group and able to execute `sudo` commands without a +password. It uses privileged access at startup only to perform certain actions which cannot be performed by regular +users (see below). After startup, `sudo` access will automatically be removed from the `ansible` user if the +environment variable `STEPDOWN_ANSIBLE_USER` is set to `true`. ***This is the default user account*** + +* `root` (least secure): This is a privileged user running with UID of `0`. Some customers may want to use this for +forwarder processes that require access to log files which cannot be read by any other user. ***This is not recommended*** + +### After Startup ### + +By default, the primary Splunk processes will always run as the unprivileged user and group `splunk`, +irregardless of which user account the containers are started with. You can override this by changing the following: + +* User: `splunk.user` variable in your `default.yml` template, or the `SPLUNK_USER` environment variable +* Group: `splunk.group` variable in your `default.yml` template, or the `SPLUNK_GROUP` environment variable + +Note that the containers are built with the `splunk` user having UID `41812` and the `splunk` group having GID `41812`. + +You may want to override these settings to ensure that Splunk forwarder processes have access to read your log files. +For example, you can ensure that all processes run as `root` by starting as the `root` user with the environment +variable `SPLUNK_USER` also set to `root` (this is not recommended). + +### Privileged Features ### + +Certain features supported by the Splunk Enterprise and Universal Forwarder containers require that they are started +with privileged access using either the `ansible` or `root` user accounts. + +#### Splunk Home Ownership #### + +By default, at startup the containers will ensure that all files located under the `SPLUNK_HOME` directory +(`/opt/splunk`) are owned by user `splunk` and group `splunk`. This helps to ensure that the Splunk processes are +able to read and write any external volumes mounted for `/opt/splunk/etc` and `/opt/splunk/var`. While all supported +versions of the docker engine will automatically set proper ownership for these volumes, external orchestration systems +typically will require extra steps. + +If you know that this step is unnecessary, you can disable it by setting the `SPLUNK_HOME_OWNERSHIP_ENFORCEMENT` +environment variable to `false`. Note that this must be disabled when starting containers with the `splunk` user +account. + +#### Package Installation #### + +The `JAVA_VERSION` environment variable can be used to automatically install OpenJDK at startup time. This feature +requires starting as a privileged user account. + +### Kubernetes Users ### + +For Kubernetes, we recommend using the `fsGroup` [Security Context](https://kubernetes.io/docs/tasks/configure-pod-container/security-context/) +to ensure that all Pods are able to write to your Persistent Volumes. For example: + +``` +apiVersion: v1 +kind: Pod +metadata: + name: example-splunk-pod +spec: + securityContext: + runAsUser: 41812 + fsGroup: 41812 + containers: + name: example-splunk-container + image: splunk/splunk + env: + - name: SPLUNK_HOME_OWNERSHIP_ENFORCEMENT + value: "false" +... +``` + +This can be used to create a Splunk Enterprise Pod running as the unprivileged `splunk` user which is able to securely +read and write from any Persistent Volumes that are created for it. + +Red Hat OpenShift users can leverage the built-in `nonroot` [Security Context Constraint](https://docs.openshift.com/container-platform/3.9/admin_guide/manage_scc.html) +to run Pods with the above Security Context: +``` +oc adm policy add-scc-to-user nonroot default +``` \ No newline at end of file diff --git a/splunk/common-files/Dockerfile b/splunk/common-files/Dockerfile index 9c70e0ac..667f1ab0 100644 --- a/splunk/common-files/Dockerfile +++ b/splunk/common-files/Dockerfile @@ -101,11 +101,15 @@ RUN sed -i -e 's/%sudo\s\+ALL=(ALL\(:ALL\)\?)\s\+ALL/%sudo ALL=NOPASSWD:ALL/g' / && groupadd -r ${ANSIBLE_GROUP} \ && useradd -r -m -g ${ANSIBLE_GROUP} ${ANSIBLE_USER} \ && usermod -aG sudo ${ANSIBLE_USER} \ + && usermod -aG ${ANSIBLE_GROUP} ${SPLUNK_USER} \ # Container Artifact Directory is a place for all artifacts and logs that are generated by the provisioning process. The directory is owned by the user "ansible". && mkdir ${CONTAINER_ARTIFACT_DIR} \ - && chown -R ${ANSIBLE_USER}:${ANSIBLE_GROUP} $CONTAINER_ARTIFACT_DIR \ + && chown -R ${ANSIBLE_USER}:${ANSIBLE_GROUP} ${CONTAINER_ARTIFACT_DIR} \ + && chmod -R 775 ${CONTAINER_ARTIFACT_DIR} \ && chmod -R 555 ${SPLUNK_ANSIBLE_HOME} \ - && chmod -R 777 ${CONTAINER_ARTIFACT_DIR} \ + && chgrp ${ANSIBLE_GROUP} ${SPLUNK_ANSIBLE_HOME} ${SPLUNK_ANSIBLE_HOME}/ansible.cfg \ + && chmod 775 ${SPLUNK_ANSIBLE_HOME} \ + && chmod 664 ${SPLUNK_ANSIBLE_HOME}/ansible.cfg \ && chmod 755 /sbin/entrypoint.sh /sbin/createdefaults.py /sbin/checkstate.sh USER ${ANSIBLE_USER} diff --git a/splunk/common-files/entrypoint.sh b/splunk/common-files/entrypoint.sh index b4ce7caa..238e8a70 100755 --- a/splunk/common-files/entrypoint.sh +++ b/splunk/common-files/entrypoint.sh @@ -35,6 +35,9 @@ trap teardown SIGINT SIGTERM prep_ansible() { cd ${SPLUNK_ANSIBLE_HOME} + if [ `whoami` == "${SPLUNK_USER}" ]; then + sed -i -e "s,^become\\s*=.*,become = false," ansible.cfg + fi if [[ "$DEBUG" == "true" ]]; then ansible-playbook --version python inventory/environ.py --write-to-file @@ -53,34 +56,37 @@ watch_for_failure(){ echo Ansible playbook complete, will begin streaming var/log/splunk/splunkd_stderr.log echo user_permission_change + if [ `whoami` != "${SPLUNK_USER}" ]; then + RUN_AS_SPLUNK="sudo -u ${SPLUNK_USER}" + fi # Any crashes/errors while Splunk is running should get logged to splunkd_stderr.log and sent to the container's stdout if [ -z "$SPLUNK_TAIL_FILE" ]; then - sudo -u ${SPLUNK_USER} tail -n 0 -f ${SPLUNK_HOME}/var/log/splunk/splunkd_stderr.log & + ${RUN_AS_SPLUNK} tail -n 0 -f ${SPLUNK_HOME}/var/log/splunk/splunkd_stderr.log & else - sudo -u ${SPLUNK_USER} tail -n 0 -f ${SPLUNK_TAIL_FILE} & + ${RUN_AS_SPLUNK} tail -n 0 -f ${SPLUNK_TAIL_FILE} & fi wait } create_defaults() { - createdefaults.py + createdefaults.py } start_and_exit() { - if [ -z "$SPLUNK_PASSWORD" ] - then - echo "WARNING: No password ENV var. Stack may fail to provision if splunk.password is not set in ENV or a default.yml" - fi + if [ -z "$SPLUNK_PASSWORD" ] + then + echo "WARNING: No password ENV var. Stack may fail to provision if splunk.password is not set in ENV or a default.yml" + fi sh -c "echo 'starting' > ${CONTAINER_ARTIFACT_DIR}/splunk-container.state" setup - prep_ansible + prep_ansible ansible-playbook $ANSIBLE_EXTRA_FLAGS -i inventory/environ.py site.yml } start() { - trap teardown EXIT + trap teardown EXIT start_and_exit - watch_for_failure + watch_for_failure } configure_multisite() { @@ -89,17 +95,17 @@ configure_multisite() { } restart(){ - trap teardown EXIT + trap teardown EXIT sh -c "echo 'restarting' > ${CONTAINER_ARTIFACT_DIR}/splunk-container.state" - prep_ansible - ${SPLUNK_HOME}/bin/splunk stop 2>/dev/null || true + prep_ansible + ${SPLUNK_HOME}/bin/splunk stop 2>/dev/null || true ansible-playbook -i inventory/environ.py start.yml watch_for_failure } user_permission_change(){ if [[ "$STEPDOWN_ANSIBLE_USER" == "true" ]]; then - bash -c "sudo deluser -q ansible sudo" + bash -c "sudo deluser -q ansible sudo" fi } @@ -140,7 +146,7 @@ Examples: * docker run -it -e SPLUNK_START_ARGS=--accept-license -e SPLUNK_INDEXER_URL=idx1,idx2 -e SPLUNK_SEARCH_HEAD_URL=sh1,sh2 -e SPLUNK_ROLE=splunk_search_head --hostname sh1 --network splunknet --network-alias sh1 -e SPLUNK_PASSWORD=helloworld -e SPLUNK_LICENSE_URI=http://example.com/splunk.lic splunk/splunk start EOF - exit 1 + exit 1 } case "$1" in @@ -157,12 +163,12 @@ case "$1" in configure_multisite $0 ;; create-defaults) - create_defaults - ;; + create_defaults + ;; restart) - shift - restart $@ - ;; + shift + restart $@ + ;; no-provision) user_permission_change tail -n 0 -f /etc/hosts & diff --git a/uf/common-files/Dockerfile b/uf/common-files/Dockerfile index 9ea27573..5bbab1f5 100644 --- a/uf/common-files/Dockerfile +++ b/uf/common-files/Dockerfile @@ -90,10 +90,15 @@ RUN \ && groupadd -r ${ANSIBLE_GROUP} \ && useradd -r -m -g ${ANSIBLE_GROUP} ${ANSIBLE_USER} \ && usermod -aG sudo ${ANSIBLE_USER} \ + && usermod -aG ${ANSIBLE_GROUP} ${SPLUNK_USER} \ # Container Artifact Directory is a place for all artifacts and logs that are generated by the provisioning process. The directory is owned by the user "ansible". && mkdir ${CONTAINER_ARTIFACT_DIR} \ - && chown -R ${ANSIBLE_USER}:${ANSIBLE_GROUP} $CONTAINER_ARTIFACT_DIR \ + && chown -R ${ANSIBLE_USER}:${ANSIBLE_GROUP} ${CONTAINER_ARTIFACT_DIR} \ + && chmod -R 775 ${CONTAINER_ARTIFACT_DIR} \ && chmod -R 555 ${SPLUNK_ANSIBLE_HOME} \ + && chgrp ${ANSIBLE_GROUP} ${SPLUNK_ANSIBLE_HOME} ${SPLUNK_ANSIBLE_HOME}/ansible.cfg \ + && chmod 775 ${SPLUNK_ANSIBLE_HOME} \ + && chmod 664 ${SPLUNK_ANSIBLE_HOME}/ansible.cfg \ && chmod 755 /sbin/entrypoint.sh /sbin/createdefaults.py /sbin/checkstate.sh USER ${ANSIBLE_USER} diff --git a/uf/common-files/entrypoint.sh b/uf/common-files/entrypoint.sh index 33f581fc..4bbf8ebb 100755 --- a/uf/common-files/entrypoint.sh +++ b/uf/common-files/entrypoint.sh @@ -20,8 +20,8 @@ setup() { # Check if the user accepted the license if [[ "$SPLUNK_START_ARGS" != *"--accept-license"* ]]; then printf "License not accepted, please ensure the environment variable SPLUNK_START_ARGS contains the '--accept-license' flag\n" - printf "For example: docker run -e SPLUNK_START_ARGS=--accept-license splunk/splunk\n\n" - printf "For additional information and examples, see the help: docker run -it splunk/splunk help\n" + printf "For example: docker run -e SPLUNK_START_ARGS=--accept-license splunk/universalforwarder\n\n" + printf "For additional information and examples, see the help: docker run -it splunk/universalforwarder help\n" exit 1 fi } @@ -35,6 +35,9 @@ trap teardown SIGINT SIGTERM prep_ansible() { cd ${SPLUNK_ANSIBLE_HOME} + if [ `whoami` == "${SPLUNK_USER}" ]; then + sed -i -e "s,^become\\s*=.*,become = false," ansible.cfg + fi if [[ "$DEBUG" == "true" ]]; then ansible-playbook --version python inventory/environ.py --write-to-file @@ -52,42 +55,44 @@ watch_for_failure(){ echo Ansible playbook complete, will begin streaming var/log/splunk/splunkd_stderr.log echo user_permission_change - # Any crashes/errors while Splunk is running should get logged to splunkd_stderr.log and sent to the container's stdout + if [ `whoami` != "${SPLUNK_USER}" ]; then + RUN_AS_SPLUNK="sudo -u ${SPLUNK_USER}" + fi # Any crashes/errors while Splunk is running should get logged to splunkd_stderr.log and sent to the container's stdout if [ -z "$SPLUNK_TAIL_FILE" ]; then - sudo -u ${SPLUNK_USER} tail -n 0 -f ${SPLUNK_HOME}/var/log/splunk/splunkd_stderr.log & + ${RUN_AS_SPLUNK} tail -n 0 -f ${SPLUNK_HOME}/var/log/splunk/splunkd_stderr.log & else - sudo -u ${SPLUNK_USER} tail -n 0 -f ${SPLUNK_TAIL_FILE} & + ${RUN_AS_SPLUNK} tail -n 0 -f ${SPLUNK_TAIL_FILE} & fi wait } create_defaults() { - createdefaults.py + createdefaults.py } start_and_exit() { - if [ -z "$SPLUNK_PASSWORD" ] - then - echo "WARNING: No password ENV var. Stack may fail to provision if splunk.password is not set in ENV or a default.yml" - fi + if [ -z "$SPLUNK_PASSWORD" ] + then + echo "WARNING: No password ENV var. Stack may fail to provision if splunk.password is not set in ENV or a default.yml" + fi sh -c "echo 'starting' > ${CONTAINER_ARTIFACT_DIR}/splunk-container.state" setup - prep_ansible + prep_ansible ansible-playbook $ANSIBLE_EXTRA_FLAGS -i inventory/environ.py site.yml } start() { - trap teardown EXIT + trap teardown EXIT start_and_exit - watch_for_failure + watch_for_failure } restart(){ trap teardown EXIT sh -c "echo 'restarting' > ${CONTAINER_ARTIFACT_DIR}/splunk-container.state" - prep_ansible - ${SPLUNK_HOME}/bin/splunk stop 2>/dev/null || true + prep_ansible + ${SPLUNK_HOME}/bin/splunk stop 2>/dev/null || true ansible-playbook -i inventory/environ.py start.yml watch_for_failure } @@ -123,7 +128,7 @@ Environment Variables: EOF - exit 1 + exit 1 } case "$1" in @@ -136,12 +141,12 @@ case "$1" in start_and_exit $@ ;; create-defaults) - create_defaults - ;; + create_defaults + ;; restart) - shift - restart $@ - ;; + shift + restart $@ + ;; no-provision) user_permission_change tail -n 0 -f /etc/hosts &