Merge pull request #24 from cdalvaro/issue-23/add-salt-api-support
Add salt-api support
This commit is contained in:
23
.shellcheckrc
Normal file
23
.shellcheckrc
Normal file
@@ -0,0 +1,23 @@
|
||||
# Check shellcheck wiki at:
|
||||
# https://github.com/koalaman/shellcheck/wiki/SCXXXX
|
||||
|
||||
# Redirections are performed by the current shell before sudo is started.
|
||||
disable=SC2024
|
||||
|
||||
# foo appears unused. Verify it or export it.
|
||||
disable=SC2034
|
||||
|
||||
# Double quote array expansions to avoid re-splitting elements.
|
||||
disable=SC2068
|
||||
|
||||
# Argument mixes string and array.
|
||||
disable=SC2145
|
||||
|
||||
# Declare and assign separately to avoid masking return values.
|
||||
disable=SC2155
|
||||
|
||||
# This redirection doesn't have a command.
|
||||
disable=SC2188
|
||||
|
||||
# This is a file redirection.
|
||||
disable=SC2210
|
||||
@@ -3,6 +3,13 @@
|
||||
This file only reflects the changes that are made in this image.
|
||||
Please refer to the SaltStack [Release Notes](https://docs.saltstack.com/en/latest/topics/releases/3000.3.html) for the list of changes in SaltStack.
|
||||
|
||||
**3000.3_1**
|
||||
|
||||
- Add support for `salt-api` service
|
||||
- Add entrypoint support to restart services
|
||||
- Use previous image as Docker cache
|
||||
- Add `build-arg` to Makefile
|
||||
|
||||
**3000.3**
|
||||
|
||||
- Upgrade SaltStack Master to `3000.3`
|
||||
|
||||
@@ -57,7 +57,7 @@ COPY entrypoint.sh /sbin/entrypoint.sh
|
||||
RUN chmod +x /sbin/entrypoint.sh
|
||||
|
||||
# Shared resources
|
||||
EXPOSE 4505/tcp 4506/tcp
|
||||
EXPOSE 4505 4506 8000
|
||||
RUN mkdir -p ${SALT_DATA_DIR} ${SALT_BASE_DIR} ${SALT_KEYS_DIR} ${SALT_CONFS_DIR} ${SALT_LOGS_DIR}
|
||||
VOLUME [ "${SALT_BASE_DIR}" "${SALT_KEYS_DIR}" "${SALT_CONFS_DIR}" "${SALT_LOGS_DIR}" ]
|
||||
|
||||
|
||||
8
Makefile
8
Makefile
@@ -11,10 +11,14 @@ help:
|
||||
@echo " 5. make logs - view logs"
|
||||
|
||||
build:
|
||||
@docker build --tag=cdalvaro/saltstack-master .
|
||||
@docker build --tag=cdalvaro/saltstack-master . \
|
||||
--build-arg=BUILD_DATE="$(shell date +"%Y-%m-%d %H:%M:%S%:z")" \
|
||||
--build-arg=VCS_REF="$(shell git rev-parse --short HEAD)" \
|
||||
|
||||
release: build
|
||||
@docker build --tag=cdalvaro/saltstack-master:$(shell cat VERSION) .
|
||||
@docker build --tag=cdalvaro/saltstack-master:$(shell cat VERSION) . \
|
||||
--build-arg=BUILD_DATE="$(shell date +"%Y-%m-%d %H:%M:%S%:z")" \
|
||||
--build-arg=VCS_REF="$(shell git rev-parse --short HEAD)" \
|
||||
|
||||
quickstart:
|
||||
@echo "Starting saltstack-master container..."
|
||||
|
||||
157
README.md
157
README.md
@@ -1,4 +1,9 @@
|
||||
# SaltStack Master v3000.3
|
||||
[![SaltStack][saltstack_badge]][saltstack_release_notes]
|
||||
[![Ubuntu Image][ubuntu_badge]][ubuntu_hub_docker]
|
||||
[![Docker Build Status][docker_build_badge]][docker_hub]
|
||||
[![CodeFactor][codefactor_badge]][codefactor_score]
|
||||
|
||||
# SaltStack Master v3000.3_1
|
||||
|
||||
Dockerfile to build a [SaltStack](https://www.saltstack.com) Master image for the Docker opensource container platform.
|
||||
|
||||
@@ -15,6 +20,8 @@ For other methods to install SaltStack please refer to the [Official SaltStack I
|
||||
- [Custom Recipes](#custom-recipes)
|
||||
- [Minion Keys](#minion-keys)
|
||||
- [Master Signed Keys](#master-signed-keys)
|
||||
- [Salt API](#salt-api)
|
||||
- [Salt Pepper](#salt-pepper)
|
||||
- [Host Mapping](#host-mapping)
|
||||
- [Git Fileserver](#git-fileserver)
|
||||
- [GitPython](#gitpython)
|
||||
@@ -23,6 +30,7 @@ For other methods to install SaltStack please refer to the [Official SaltStack I
|
||||
- [Available Configuration Parameters](#available-configuration-parameters)
|
||||
- [Usage](#usage)
|
||||
- [Shell Access](#shell-access)
|
||||
- [Restart Services](#restart-services)
|
||||
- [References](#references)
|
||||
|
||||
## Installation
|
||||
@@ -30,7 +38,7 @@ For other methods to install SaltStack please refer to the [Official SaltStack I
|
||||
Automated builds of the image are available on [Dockerhub](https://hub.docker.com/r/cdalvaro/saltstack-master/) and is the recommended method of installation.
|
||||
|
||||
```sh
|
||||
docker pull cdalvaro/saltstack-master:3000.3
|
||||
docker pull cdalvaro/saltstack-master:3000.3_1
|
||||
```
|
||||
|
||||
You can also pull the latest tag which is built from the repository `HEAD`
|
||||
@@ -69,63 +77,149 @@ Alternatively, you can manually launch the `saltstack-master` container:
|
||||
|
||||
```sh
|
||||
docker run --name salt_master --detach \
|
||||
--publish 4505:4505/tcp --publish 4506:4506/tcp \
|
||||
--publish 4505:4505 --publish 4506:4506 \
|
||||
--env 'SALT_LOG_LEVEL=info' \
|
||||
--volume $(pwd)/roots/:/home/salt/data/srv/ \
|
||||
--volume $(pwd)/keys/:/home/salt/data/keys/ \
|
||||
cdalvaro/saltstack-master:3000.3
|
||||
cdalvaro/saltstack-master:3000.3_1
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
### Custom Recipes
|
||||
|
||||
In order to provide salt with your custom recipes you must mount the volume `/home/salt/data/srv/` with your `roots` directory.
|
||||
In order to provide salt with your custom recipes you must mount the volume `/home/salt/data/srv/`
|
||||
with your `roots` directory.
|
||||
|
||||
### Minion Keys
|
||||
|
||||
Minion keys can be added automatically on startup to SaltStack master by mounting the volume `/home/salt/data/keys` and copying the minion keys inside `keys/minions/` directory.
|
||||
Minion keys can be added automatically on startup to SaltStack master by mounting the volume
|
||||
`/home/salt/data/keys` and copying the minion keys inside `keys/minions/` directory.
|
||||
|
||||
It is also important to know that, in order to keep your keys after removing the container, the keys directory must be mounted.
|
||||
It is also important to know that, in order to keep your keys after removing the container,
|
||||
the keys directory must be mounted.
|
||||
|
||||
```sh
|
||||
mkdir -p keys/minions
|
||||
rsync root@minion1:/etc/salt/pki/minion/minion.pub keys/minions/minion1
|
||||
|
||||
docker run --name salt_master -d \
|
||||
--publish 4505:4505/tcp --publish 4506:4506/tcp \
|
||||
--publish 4505:4505 --publish 4506:4506 \
|
||||
--env 'SALT_LOG_LEVEL=info' \
|
||||
--volume $(pwd)/roots/:/home/salt/data/srv/ \
|
||||
--volume $(pwd)/keys/:/home/salt/data/keys/ \
|
||||
cdalvaro/saltstack-master:3000.3
|
||||
cdalvaro/saltstack-master:3000.3_1
|
||||
```
|
||||
|
||||
### Master Signed Keys
|
||||
|
||||
It is possible to use signed master keys by establishing the environment variable `SALT_MASTER_SIGN_PUBKEY` to `True`.
|
||||
It is possible to use signed master keys by establishing the environment variable
|
||||
`SALT_MASTER_SIGN_PUBKEY` to `True`.
|
||||
|
||||
```sh
|
||||
docker run --name salt_stack --detach \
|
||||
--publish 4505:4505/tcp --publish 4506:4506/tcp \
|
||||
--publish 4505:4505 --publish 4506:4506 \
|
||||
--env 'SALT_LOG_LEVEL=info' \
|
||||
--env 'SALT_MASTER_SIGN_PUBKEY=True'
|
||||
--volume $(pwd)/roots/:/home/salt/data/srv/ \
|
||||
--volume $(pwd)/keys/:/home/salt/data/keys/ \
|
||||
cdalvaro/saltstack-master:3000.3
|
||||
cdalvaro/saltstack-master:3000.3_1
|
||||
```
|
||||
|
||||
The container will create the `master_sign` key and its signature. More information about how to configure the minion service can be found [here](https://docs.saltstack.com/en/latest/topics/tutorials/multimaster_pki.html#prepping-the-minion-to-verify-received-public-keys).
|
||||
The container will create the `master_sign` key and its signature.
|
||||
More information about how to configure the minion service can be found
|
||||
[here](https://docs.saltstack.com/en/latest/topics/tutorials/multimaster_pki.html#prepping-the-minion-to-verify-received-public-keys).
|
||||
|
||||
Additionally, you can generate new keys by executing the following command:
|
||||
|
||||
```sh
|
||||
docker run --name salt_stack -it --rm \
|
||||
--volume $(pwd)/keys/:/home/salt/data/keys/ \
|
||||
cdalvaro/saltstack-master:3000.3 app:gen-signed-keys other_master_sign
|
||||
cdalvaro/saltstack-master:3000.3_1 app:gen-signed-keys other_master_sign
|
||||
```
|
||||
|
||||
The newly created keys will appear inside `keys/generated/other_master_sign` directory.
|
||||
|
||||
### Salt API
|
||||
|
||||
You can enable `salt-api` service by setting env variable `SALT_API_SERVICE_ENABLED` to `true`.
|
||||
|
||||
A self-signed SSL certificate will be automatically generated and the following configuration
|
||||
will be added to the master configuration file:
|
||||
|
||||
```yml
|
||||
rest_cherrypy:
|
||||
port: 8000
|
||||
ssl_crt: /etc/pki/tls/certs/docker-salt-master.crt
|
||||
ssl_key: /etc/pki/tls/certs/docker-salt-master.key
|
||||
```
|
||||
|
||||
The container exposes port `8000` by default, although you can map this port to whatever port you like in
|
||||
your `docker run` command or in your `docker-compose.yml` file.
|
||||
|
||||
```sh
|
||||
docker run --name salt_stack --detach \
|
||||
--publish 4505:4505 --publish 4506:4506 --publish 8000:8000 \
|
||||
--env 'SALT_API_SERVICE_ENABLED=true' \
|
||||
--env 'SALT_API_USER_PASS=SuperCool/Password10'
|
||||
--volume $(pwd)/roots/:/home/salt/data/srv/ \
|
||||
--volume $(pwd)/keys/:/home/salt/data/keys/ \
|
||||
cdalvaro/saltstack-master:3000.3_1
|
||||
```
|
||||
|
||||
By default, user `salt_api` is created and you can set its password by setting the environment variable
|
||||
`SALT_API_USER_PASS`.
|
||||
|
||||
You can also change the salt-api _username_ by setting `SALT_API_USER`.
|
||||
It is possible to disable this user by explicitly setting this variable to an empty string: `SALT_API_USER=''` if you are going to use an `LDAP` server.
|
||||
|
||||
As a security measure, if `SALT_API_USER_PASS` is set to `true` and you don't disable `SALT_API_USER`,
|
||||
you'll be required to set `SALT_API_USER_PASS`. Otherwise initialization will fail and your Docker image won't work.
|
||||
|
||||
With all that set, you'll be able to provide your _salt-api_ custom configuration by creating the `salt-api.conf`
|
||||
file inside your `conf` directory:
|
||||
|
||||
```yml
|
||||
external_auth:
|
||||
pam:
|
||||
salt_api:
|
||||
- .*
|
||||
```
|
||||
|
||||
More information is available in the following link: [External Authentication System (eAuth)](https://docs.saltstack.com/en/latest/topics/eauth/index.html#acl-eauth).
|
||||
|
||||
Now you have your saltstack-master docker image ready to accept external authentications and to connect external tools such as [`saltstack/pepper`](https://github.com/saltstack/pepper).
|
||||
|
||||
#### Salt Pepper
|
||||
|
||||
The pepper CLI script allows users to execute Salt commands from computers that are external to computers running the salt-master or salt-minion daemons as though they were running Salt locally
|
||||
|
||||
##### Installation:
|
||||
|
||||
```sh
|
||||
pip3 install salt-pepper
|
||||
```
|
||||
|
||||
##### Configuration
|
||||
|
||||
Then configure pepper by filling your `~/.pepperrc` file with your salt-api credentials:
|
||||
|
||||
```conf
|
||||
[main]
|
||||
SALTAPI_URL=https://your.salt-master.hostname:8000/
|
||||
SALTAPI_USER=salt_api
|
||||
SALTAPI_PASS=SuperCool/Password10
|
||||
SALTAPI_EAUTH=pam
|
||||
```
|
||||
|
||||
##### Usage
|
||||
|
||||
Beging executing salt recipes with `pepper`:
|
||||
|
||||
```sh
|
||||
pepper '*' test.ping
|
||||
```
|
||||
|
||||
### Host Mapping
|
||||
|
||||
Per default the container is configured to run `salt-master` as user and group `salt` with `uid` and `gid` `1000`. From the host it appears as if the mounted data volumes are owned by the host's user/group `1000` and maybe leading to unfavorable effects.
|
||||
@@ -137,7 +231,7 @@ docker run --name salt_stack -it --rm \
|
||||
--env "USERMAP_UID=$(id -u)" --env "USERMAP_GID=$(id -g)" \
|
||||
--volume $(pwd)/roots/:/home/salt/data/srv/ \
|
||||
--volume $(pwd)/keys/:/home/salt/data/keys/ \
|
||||
cdalvaro/saltstack-master:3000.3
|
||||
cdalvaro/saltstack-master:3000.3_1
|
||||
```
|
||||
|
||||
### Git Fileserver
|
||||
@@ -188,12 +282,12 @@ Inside that directory you could find `supervisor/` logs and `salt/` logs:
|
||||
|
||||
```sh
|
||||
docker run --name salt_master --detach \
|
||||
--publish 4505:4505/tcp --publish 4506:4506/tcp \
|
||||
--publish 4505:4505 --publish 4506:4506 \
|
||||
--env 'SALT_LOG_LEVEL=info' \
|
||||
--volume $(pwd)/roots/:/home/salt/data/srv/ \
|
||||
--volume $(pwd)/keys/:/home/salt/data/keys/ \
|
||||
--volume $(pwd)/logs/:/home/salt/data/logs/ \
|
||||
cdalvaro/saltstack-master:3000.3
|
||||
cdalvaro/saltstack-master:3000.3_1
|
||||
```
|
||||
|
||||
Check [Available Configuration Parameters](#available-configuration-parameters) section for configuring logrotate.
|
||||
@@ -212,6 +306,9 @@ Below is the list of available options that can be used to customize your SaltSt
|
||||
| `SALT_LOG_ROTATE_FREQUENCY` | Logrotate frequency for salt logs. Available options are 'daily', 'weekly', 'monthly', and 'yearly'. Default: `weekly` |
|
||||
| `SALT_LOG_ROTATE_RETENTION` | Keep x files before deleting old log files. Defaults: `52` |
|
||||
| `SALT_LEVEL_LOGFILE` | The level of messages to send to the log file. One of 'garbage', 'trace', 'debug', info', 'warning', 'error', 'critical'. Default: `warning` |
|
||||
| `SALT_API_SERVICE_ENABLED` | Enable `salt-api` service. Default: `false` |
|
||||
| `SALT_API_USER` | Set username for `salt-api` service. Default: `salt_api` |
|
||||
| `SALT_API_USER_PASS` | `SALT_API_USER` password. Required if `SALT_API_SERVICE_ENBALED` is `true` and `SALT_API_USER` is not empty. _Unset_ by default |
|
||||
| `SALT_MASTER_SIGN_PUBKEY` | Sign the master auth-replies with a cryptographic signature of the master's public key. Possible values: 'True' or 'False'. Default: `False` |
|
||||
| `SALT_MASTER_USE_PUBKEY_SIGNATURE` | Instead of computing the signature for each auth-reply, use a pre-calculated signature. This option requires `SALT_MASTER_SIGN_PUBKEY` set to 'True'. Possible values: 'True' or 'False'. Default: `True` |
|
||||
| `SALT_MASTER_SIGN_KEY_NAME` | The customizable name of the signing-key-pair without suffix. Default: `master_sign` |
|
||||
@@ -234,12 +331,12 @@ ret_port: 3506
|
||||
EOF
|
||||
|
||||
docker run --name salt_master -d \
|
||||
--publish 3505:3505/tcp --publish 3506:3506/tcp \
|
||||
--publish 3505:3505 --publish 3506:3506 \
|
||||
--env 'SALT_LOG_LEVEL=info' \
|
||||
--volume $(pwd)/roots/:/home/salt/data/srv/ \
|
||||
--volume $(pwd)/keys/:/home/salt/data/keys/ \
|
||||
--volume $(pwd)/config/:/home/salt/data/config/ \
|
||||
cdalvaro/saltstack-master:3000.3
|
||||
cdalvaro/saltstack-master:3000.3_1
|
||||
```
|
||||
|
||||
## Usage
|
||||
@@ -264,8 +361,30 @@ For debugging and maintenance purposes you may want access the container shell.
|
||||
docker exec -it salt_master bash
|
||||
```
|
||||
|
||||
## Restart Services
|
||||
|
||||
You can restart containers services by running the following command:
|
||||
|
||||
```sh
|
||||
docker exec -it salt_master entrypoint.sh app:restart [salt-service]
|
||||
```
|
||||
|
||||
Where `salt-service` is one of: `salt-master` os `salt-api` (if `SALT_API_SERVICE_ENABLED` is set to `true`)
|
||||
|
||||
## References
|
||||
|
||||
- https://docs.saltstack.com/en/latest/topics/installation/index.html
|
||||
- https://docs.saltstack.com/en/latest/topics/tutorials/salt_bootstrap.html
|
||||
- https://github.com/saltstack/salt/releases
|
||||
|
||||
[saltstack_badge]: https://img.shields.io/badge/SaltStack-v3000.3-lightgrey.svg?style=flat-square&logo=Saltstack
|
||||
[saltstack_release_notes]: https://docs.saltstack.com/en/latest/topics/releases/3000.3.html "SaltStack Release Notes"
|
||||
|
||||
[ubuntu_badge]: https://img.shields.io/badge/ubuntu-bionic--20200403-E95420.svg?style=flat-square&logo=Ubuntu
|
||||
[ubuntu_hub_docker]: https://hub.docker.com/_/ubuntu/ "Ubuntu Image"
|
||||
|
||||
[docker_build_badge]: https://img.shields.io/docker/build/cdalvaro/saltstack-master?logo=docker&style=flat-square
|
||||
[docker_hub]: https://hub.docker.com/r/cdalvaro/saltstack-master/builds
|
||||
|
||||
[codefactor_badge]: https://www.codefactor.io/repository/github/cdalvaro/saltstack-master/badge?style=flat-square
|
||||
[codefactor_score]: https://www.codefactor.io/repository/github/cdalvaro/saltstack-master
|
||||
@@ -5,9 +5,9 @@ set -e
|
||||
# Execute a command as SALT_USER
|
||||
function exec_as_salt()
|
||||
{
|
||||
if [[ $(whoami) == ${SALT_USER} ]]; then
|
||||
if [[ $(whoami) == "${SALT_USER}" ]]; then
|
||||
$@
|
||||
else
|
||||
sudo -HEu ${SALT_USER} "$@"
|
||||
sudo -HEu "${SALT_USER}" "$@"
|
||||
fi
|
||||
}
|
||||
|
||||
@@ -2,39 +2,41 @@
|
||||
|
||||
set -e
|
||||
|
||||
source ${SALT_BUILD_DIR}/functions.sh
|
||||
source "${SALT_BUILD_DIR}/functions.sh"
|
||||
|
||||
# Install build dependencies
|
||||
echo "Installing dependencies ..."
|
||||
BUILD_DEPENDENCIES="cmake gcc g++ make \
|
||||
libhttp-parser-dev libssl-dev zlib1g-dev \
|
||||
libcurl4-openssl-dev libffi-dev swig"
|
||||
BUILD_DEPENDENCIES=(
|
||||
cmake gcc g++ make \
|
||||
libhttp-parser-dev libssl-dev zlib1g-dev \
|
||||
libcurl4-openssl-dev libffi-dev swig \
|
||||
)
|
||||
|
||||
apt-get update
|
||||
DEBIAN_FRONTEND=noninteractive apt-get install --yes --quiet --no-install-recommends ${BUILD_DEPENDENCIES}
|
||||
DEBIAN_FRONTEND=noninteractive apt-get install --yes --quiet --no-install-recommends "${BUILD_DEPENDENCIES[@]}"
|
||||
|
||||
# Create salt user
|
||||
echo "Creating ${SALT_USER} user ..."
|
||||
useradd -d ${SALT_HOME} -ms /bin/bash -U -G root,sudo ${SALT_USER}
|
||||
useradd -d "${SALT_HOME}" -ms /bin/bash -U -G root,sudo,shadow "${SALT_USER}"
|
||||
|
||||
# Set PATH
|
||||
exec_as_salt cat >> ${SALT_HOME}/.profile <<EOF
|
||||
exec_as_salt cat >> "${SALT_HOME}/.profile" <<EOF
|
||||
PATH=/usr/local/sbin:/usr/local/bin:\$PATH
|
||||
EOF
|
||||
|
||||
# Compile libssh2
|
||||
echo "Building libssh2 v${LIBSSH2_VERSION} ..."
|
||||
wget https://github.com/libssh2/libssh2/archive/libssh2-${LIBSSH2_VERSION}.tar.gz
|
||||
tar xzf libssh2-${LIBSSH2_VERSION}.tar.gz
|
||||
cd libssh2-libssh2-${LIBSSH2_VERSION}/
|
||||
wget "https://github.com/libssh2/libssh2/archive/libssh2-${LIBSSH2_VERSION}.tar.gz"
|
||||
tar xzf "libssh2-${LIBSSH2_VERSION}.tar.gz"
|
||||
cd "libssh2-libssh2-${LIBSSH2_VERSION}/"
|
||||
cmake -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=ON -DENABLE_ZLIB_COMPRESSION=ON .
|
||||
cmake --build . --target install
|
||||
|
||||
# Compile libgit2
|
||||
echo "Building libgit2 v${LIBGIT2_VERSION} ..."
|
||||
wget https://github.com/libgit2/libgit2/archive/v${LIBGIT2_VERSION}.tar.gz
|
||||
tar xzf v${LIBGIT2_VERSION}.tar.gz
|
||||
cd libgit2-${LIBGIT2_VERSION}/
|
||||
wget "https://github.com/libgit2/libgit2/archive/v${LIBGIT2_VERSION}.tar.gz"
|
||||
tar xzf "v${LIBGIT2_VERSION}.tar.gz"
|
||||
cd "libgit2-${LIBGIT2_VERSION}/"
|
||||
cmake -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=ON -DBUILD_CLAR=OFF -DTHREADSAFE=ON .
|
||||
cmake --build . --target install
|
||||
|
||||
@@ -42,7 +44,7 @@ cmake --build . --target install
|
||||
echo "Installing python3 packages ..."
|
||||
DEBIAN_FRONTEND=noninteractive apt-get install --yes --quiet --no-install-recommends \
|
||||
python3-mako python3-pycryptodome python3-cherrypy3 python3-git python3-u-msgpack \
|
||||
python3-redis python3-gnupg python3-mysqldb python3-dateutil python3-libnacl
|
||||
python3-redis python3-gnupg python3-mysqldb python3-dateutil python3-libnacl python3-openssl
|
||||
|
||||
# Install pip3 python packages
|
||||
echo "Installing pip3 python packages ..."
|
||||
@@ -54,21 +56,26 @@ pip3 install "pygit2==v${PYGIT2_VERSION}" \
|
||||
## -M: install Salt Master by default
|
||||
## -N: Do not install salt-minion
|
||||
## -X: Do not start daemons after installation
|
||||
## -d: Disables checking if Salt services are enabled to start on system boot
|
||||
## -P: Allow pip based installations
|
||||
## -p: Extra-package to install
|
||||
## -x: Changes the python version used to install a git version of salt
|
||||
SALT_BOOTSTRAP_OPTS="-M -N -X -P -x python${PYTHON_VERSION}"
|
||||
SALT_BOOTSTRAP_OPTS=( -M -N -X -d -P -p salt-api -x "python${PYTHON_VERSION}" )
|
||||
|
||||
echo "Installing saltstack ..."
|
||||
echo "Option: ${SALT_BOOTSTRAP_OPTS[@]}"
|
||||
wget -O bootstrap-salt.sh https://bootstrap.saltstack.com
|
||||
sh bootstrap-salt.sh ${SALT_BOOTSTRAP_OPTS} git v${SALT_VERSION}
|
||||
chown -R ${SALT_USER}: ${SALT_ROOT_DIR}
|
||||
sh bootstrap-salt.sh ${SALT_BOOTSTRAP_OPTS[@]} git "v${SALT_VERSION}"
|
||||
chown -R "${SALT_USER}": "${SALT_ROOT_DIR}"
|
||||
|
||||
# Configure ssh
|
||||
echo "Configuring ssh ..."
|
||||
sed -i -e "s|^[# ]*StrictHostKeyChecking.*$| StrictHostKeyChecking no|" /etc/ssh/ssh_config
|
||||
echo " UserKnownHostsFile /dev/null" >> /etc/ssh/ssh_config
|
||||
echo " LogLevel ERROR" >> /etc/ssh/ssh_config
|
||||
echo "# IdentityFile salt_ssh_key" >> /etc/ssh/ssh_config
|
||||
{
|
||||
echo " UserKnownHostsFile /dev/null"
|
||||
echo " LogLevel ERROR"
|
||||
echo "# IdentityFile salt_ssh_key"
|
||||
} >> /etc/ssh/ssh_config
|
||||
|
||||
# Configure logrotate
|
||||
echo "Configuring logrotate ..."
|
||||
@@ -89,7 +96,7 @@ priority=5
|
||||
directory=${SALT_HOME}
|
||||
environment=HOME=${SALT_HOME}
|
||||
command=/usr/local/bin/salt-master
|
||||
user=${SALT_USER}
|
||||
user=root
|
||||
autostart=true
|
||||
autorestart=true
|
||||
stopsignal=QUIT
|
||||
|
||||
@@ -3,6 +3,9 @@
|
||||
DEBUG=${DEBUG:-false}
|
||||
TIMEZONE=${TIMEZONE:-UTC}
|
||||
|
||||
SALT_API_SERVICE_ENABLED=${SALT_API_SERVICE_ENABLED:-false}
|
||||
SALT_API_USER=${SALT_API_USER:-salt_api}
|
||||
|
||||
SALT_LOG_ROTATE_FREQUENCY=${SALT_LOG_ROTATE_FREQUENCY:-weekly}
|
||||
SALT_LOG_ROTATE_RETENTION=${SALT_LOG_ROTATE_RETENTION:-52}
|
||||
|
||||
|
||||
@@ -1,30 +1,36 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -e
|
||||
source ${SALT_RUNTIME_DIR}/env-defaults.sh
|
||||
source "${SALT_RUNTIME_DIR}/env-defaults.sh"
|
||||
|
||||
# Execute a command as SALT_USER
|
||||
function exec_as_salt()
|
||||
{
|
||||
if [[ $(whoami) == ${SALT_USER} ]]; then
|
||||
if [[ $(whoami) == "${SALT_USER}" ]]; then
|
||||
$@
|
||||
else
|
||||
sudo -HEu ${SALT_USER} "$@"
|
||||
sudo -HEu "${SALT_USER}" "$@"
|
||||
fi
|
||||
}
|
||||
|
||||
# Log error
|
||||
function log_error()
|
||||
{
|
||||
(>2& echo "ERROR: $*")
|
||||
}
|
||||
|
||||
# Map salt user with host user
|
||||
function map_uidgid()
|
||||
{
|
||||
USERMAP_ORIG_UID=$(id -u ${SALT_USER})
|
||||
USERMAP_ORIG_GID=$(id -g ${SALT_USER})
|
||||
USERMAP_ORIG_UID=$(id -u "${SALT_USER}")
|
||||
USERMAP_ORIG_GID=$(id -g "${SALT_USER}")
|
||||
USERMAP_GID=${USERMAP_GID:-${USERMAP_UID:-$USERMAP_ORIG_GID}}
|
||||
USERMAP_UID=${USERMAP_UID:-$USERMAP_ORIG_UID}
|
||||
if [[ ${USERMAP_UID} != ${USERMAP_ORIG_UID} ]] || [[ ${USERMAP_GID} != ${USERMAP_ORIG_GID} ]]; then
|
||||
if [[ "${USERMAP_UID}" != "${USERMAP_ORIG_UID}" ]] || [[ "${USERMAP_GID}" != "${USERMAP_ORIG_GID}" ]]; then
|
||||
echo "Mapping UID and GID for ${SALT_USER}:${SALT_USER} to ${USERMAP_UID}:${USERMAP_GID} ..."
|
||||
groupmod -o -g ${USERMAP_GID} ${SALT_USER}
|
||||
groupmod -o -g "${USERMAP_GID}" "${SALT_USER}"
|
||||
sed -i -e "s|:${USERMAP_ORIG_UID}:${USERMAP_GID}:|:${USERMAP_UID}:${USERMAP_GID}:|" /etc/passwd
|
||||
find ${SALT_HOME} -path ${SALT_DATA_DIR}/\* \( ! -uid ${USERMAP_ORIG_UID} -o ! -gid ${USERMAP_ORIG_GID} \) -print0 | xargs -0 chown -h ${SALT_USER}: ${SALT_HOME}
|
||||
find "${SALT_HOME}" -path "${SALT_DATA_DIR}/*" \( ! -uid "${USERMAP_ORIG_UID}" -o ! -gid "${USERMAP_ORIG_GID}" \) -print0 | xargs -0 chown -h "${SALT_USER}": "${SALT_HOME}"
|
||||
fi
|
||||
}
|
||||
|
||||
@@ -36,25 +42,25 @@ function update_template()
|
||||
local FILE=${1?missing argument}
|
||||
shift
|
||||
|
||||
[[ ! -f ${FILE} ]] && return 1
|
||||
[[ ! -f "${FILE}" ]] && return 1
|
||||
|
||||
local VARIABLES=($@)
|
||||
local USR=$(stat -c %U ${FILE})
|
||||
local VARIABLES=( "$@" )
|
||||
local USR=$(stat -c %U "${FILE}")
|
||||
local tmp_file=$(mktemp)
|
||||
cp -a "${FILE}" ${tmp_file}
|
||||
cp -a "${FILE}" "${tmp_file}"
|
||||
|
||||
local variables
|
||||
for variable in ${VARIABLES[@]}; do
|
||||
sed -ri "s|[{}]{2}$variable[}]{2}|\${$variable}|g" ${tmp_file}
|
||||
for variable in "${VARIABLES[@]}"; do
|
||||
sed -ri "s|[{}]{2}${variable}[}]{2}|\${${variable}}|g" "${tmp_file}"
|
||||
done
|
||||
|
||||
# Replace placeholders
|
||||
(
|
||||
export ${VARIABLES[@]}
|
||||
local IFS=":"; sudo -HEu ${USR} envsubst "${VARIABLES[*]/#/$}" < ${tmp_file} > ${FILE}
|
||||
export "${VARIABLES[@]}"
|
||||
local IFS=":"; sudo -HEu "${USR}" envsubst "${VARIABLES[*]/#/$}" < "${tmp_file}" > "${FILE}"
|
||||
)
|
||||
|
||||
rm -f ${tmp_file}
|
||||
rm -f "${tmp_file}"
|
||||
}
|
||||
|
||||
# This function configures containers timezone
|
||||
@@ -63,14 +69,14 @@ function configure_timezone()
|
||||
echo "Configuring container timezone ..."
|
||||
|
||||
# Perform sanity check of provided timezone value
|
||||
if [ -e /usr/share/zoneinfo/${TIMEZONE} ]; then
|
||||
if [ -e "/usr/share/zoneinfo/${TIMEZONE}" ]; then
|
||||
echo "Setting TimeZone -> ${TIMEZONE} ..."
|
||||
|
||||
# Set localtime
|
||||
ln -snf /usr/share/zoneinfo/${TIMEZONE} /etc/localtime
|
||||
ln -snf "/usr/share/zoneinfo/${TIMEZONE}" /etc/localtime
|
||||
|
||||
# Set timezone
|
||||
echo ${TIMEZONE} > /etc/timezone
|
||||
echo "${TIMEZONE}" > /etc/timezone
|
||||
else
|
||||
echo "Timezone: '${TIMEZONE}' is not valid. Check available timezones at: https://en.wikipedia.org/wiki/List_of_tz_database_time_zones"
|
||||
return 1
|
||||
@@ -82,39 +88,40 @@ function gen_signed_keys()
|
||||
{
|
||||
local key_name=${1:-master}
|
||||
|
||||
mkdir -p ${SALT_KEYS_DIR}/generated/
|
||||
GENERATED_KEYS_DIR=$(mktemp -d -p ${SALT_KEYS_DIR}/generated/ -t ${key_name}.XXXXX)
|
||||
mkdir -p "${SALT_KEYS_DIR}/generated/"
|
||||
GENERATED_KEYS_DIR=$(mktemp -d -p "${SALT_KEYS_DIR}/generated/" -t "${key_name}.XXXXX")
|
||||
|
||||
salt-key --gen-keys ${key_name} --gen-keys-dir ${GENERATED_KEYS_DIR} > /dev/null 2>&1
|
||||
salt-key --gen-signature --auto-create --pub ${GENERATED_KEYS_DIR}/${key_name}.pub --signature-path ${GENERATED_KEYS_DIR} > /dev/null 2>&1
|
||||
salt-key --gen-keys "${key_name}" --gen-keys-dir "${GENERATED_KEYS_DIR}" > /dev/null 2>&1
|
||||
salt-key --gen-signature --auto-create --pub "${GENERATED_KEYS_DIR}/${key_name}.pub" --signature-path "${GENERATED_KEYS_DIR}" > /dev/null 2>&1
|
||||
|
||||
echo -n ${GENERATED_KEYS_DIR}
|
||||
echo -n "${GENERATED_KEYS_DIR}"
|
||||
}
|
||||
|
||||
# This function repairs keys permissions and creates keys if neaded
|
||||
function setup_salt_keys()
|
||||
{
|
||||
echo "Setting up salt keys ..."
|
||||
if [ ! -f ${SALT_KEYS_DIR}/master.pem ]; then
|
||||
if [ ! -f "${SALT_KEYS_DIR}/master.pem" ]; then
|
||||
echo "Generating keys ..."
|
||||
salt-key --gen-keys master --gen-keys-dir ${SALT_KEYS_DIR}
|
||||
salt-key --gen-keys master --gen-keys-dir "${SALT_KEYS_DIR}"
|
||||
fi
|
||||
|
||||
if [ ! -f "${SALT_KEYS_DIR}/${SALT_MASTER_SIGN_KEY_NAME}.pem" ] && [ ${SALT_MASTER_SIGN_PUBKEY} == True ]; then
|
||||
if [ ! -f "${SALT_KEYS_DIR}/${SALT_MASTER_SIGN_KEY_NAME}.pem" ] && [ "${SALT_MASTER_SIGN_PUBKEY}" == True ]; then
|
||||
echo "Generating signed keys ..."
|
||||
salt-key --gen-signature --auto-create --pub ${SALT_KEYS_DIR}/master.pub --signature-path ${SALT_KEYS_DIR}
|
||||
salt-key --gen-signature --auto-create --pub "${SALT_KEYS_DIR}/master.pub" --signature-path "${SALT_KEYS_DIR}"
|
||||
fi
|
||||
|
||||
for pub_key in $(find ${SALT_KEYS_DIR} -maxdepth 1 -type f); do
|
||||
if [[ ${pub_key} =~ .*\.pem$ ]]; then
|
||||
chmod 400 ${pub_key}
|
||||
while IFS= read -r -d '' pub_key
|
||||
do
|
||||
if [[ "${pub_key}" =~ .*\.pem$ ]]; then
|
||||
chmod 400 "${pub_key}"
|
||||
else
|
||||
chmod 644 ${pub_key}
|
||||
chmod 644 "${pub_key}"
|
||||
fi
|
||||
done
|
||||
done < <(find "${SALT_KEYS_DIR}" -maxdepth 1 -type f -print0)
|
||||
|
||||
find ${SALT_KEYS_DIR}/minions* -maxdepth 1 -type f -exec chmod 644 {} \;
|
||||
find ${SALT_HOME} -path ${SALT_KEYS_DIR}/\* -prune -o -print0 | xargs -0 chown -h ${SALT_USER}:
|
||||
find "${SALT_KEYS_DIR}/minions"* -maxdepth 1 -type f -exec chmod 644 {} \;
|
||||
find "${SALT_HOME}" -path "${SALT_KEYS_DIR}/*" -prune -o -print0 | xargs -0 chown -h "${SALT_USER}":
|
||||
}
|
||||
|
||||
# This function configures ssh keys
|
||||
@@ -135,16 +142,16 @@ function setup_ssh_keys()
|
||||
fi
|
||||
}
|
||||
|
||||
# This functions cofigures master service
|
||||
# This function cofigures master service
|
||||
function configure_salt_master()
|
||||
{
|
||||
echo "Configuring salt-master ..."
|
||||
echo "Configuring salt-master service ..."
|
||||
# https://docs.saltstack.com/en/latest/ref/configuration/master.html
|
||||
|
||||
exec_as_salt cp -p ${SALT_RUNTIME_DIR}/config/master.yml ${SALT_ROOT_DIR}/master
|
||||
exec_as_salt cp -p "${SALT_RUNTIME_DIR}/config/master.yml" "${SALT_ROOT_DIR}/master"
|
||||
|
||||
# Update main configuration
|
||||
update_template ${SALT_ROOT_DIR}/master \
|
||||
update_template "${SALT_ROOT_DIR}/master" \
|
||||
SALT_USER \
|
||||
SALT_LOG_LEVEL \
|
||||
SALT_LEVEL_LOGFILE \
|
||||
@@ -155,13 +162,73 @@ function configure_salt_master()
|
||||
SALT_KEYS_DIR
|
||||
|
||||
# Update keys configuration
|
||||
update_template ${SALT_ROOT_DIR}/master \
|
||||
update_template "${SALT_ROOT_DIR}/master" \
|
||||
SALT_MASTER_SIGN_PUBKEY \
|
||||
SALT_MASTER_SIGN_KEY_NAME \
|
||||
SALT_MASTER_PUBKEY_SIGNATURE \
|
||||
SALT_MASTER_USE_PUBKEY_SIGNATURE
|
||||
}
|
||||
|
||||
# This function configures salt-api if service is set to be enabled
|
||||
function configure_salt_api()
|
||||
{
|
||||
[[ ${SALT_API_SERVICE_ENABLED} == true ]] || return 0
|
||||
|
||||
if [[ -n "${SALT_API_USER}" ]]; then
|
||||
|
||||
if [[ ${SALT_API_USER} == "${SALT_USER}" ]]; then
|
||||
log_error "SALT_API_USER cannot be the same as '${SALT_USER}'"
|
||||
return 1
|
||||
fi
|
||||
|
||||
if [[ -z "${SALT_API_USER_PASS}" ]]; then
|
||||
log_error "SALT_API_USER_PASS env variable must be set to create '${SALT_API_USER}' user"
|
||||
return 2
|
||||
fi
|
||||
|
||||
echo "Creating '${SALT_API_USER}' user for salt-api ..."
|
||||
adduser --quiet --disabled-password --gecos "Salt API" "${SALT_API_USER}"
|
||||
echo "${SALT_API_USER}:${SALT_API_USER_PASS}" | chpasswd
|
||||
unset SALT_API_USER_PASS
|
||||
fi
|
||||
|
||||
echo "Configuring salt-api service ..."
|
||||
|
||||
CERTS_PATH=/etc/pki
|
||||
rm -rf "${CERTS_PATH}/tls/certs/*"
|
||||
salt-call --local tls.create_self_signed_cert cacert_path="${CERTS_PATH}" CN=docker-salt-master
|
||||
|
||||
cat >> "${SALT_ROOT_DIR}/master" <<EOF
|
||||
|
||||
|
||||
##### salt-api settings #####
|
||||
##########################################
|
||||
# Basic configuration for salt-api
|
||||
api_logfile: ${SALT_LOGS_DIR}/salt/api
|
||||
|
||||
rest_cherrypy:
|
||||
port: 8000
|
||||
ssl_crt: /etc/pki/tls/certs/docker-salt-master.crt
|
||||
ssl_key: /etc/pki/tls/certs/docker-salt-master.key
|
||||
EOF
|
||||
|
||||
# configure supervisord to start salt-api
|
||||
cat > /etc/supervisor/conf.d/salt-api.conf <<EOF
|
||||
[program:salt-api]
|
||||
priority=5
|
||||
directory=${SALT_HOME}
|
||||
environment=HOME=${SALT_HOME}
|
||||
command=/usr/local/bin/salt-api
|
||||
user=root
|
||||
autostart=true
|
||||
autorestart=true
|
||||
stopsignal=QUIT
|
||||
stdout_logfile=${SALT_LOGS_DIR}/supervisor/%(program_name)s.log
|
||||
stderr_logfile=${SALT_LOGS_DIR}/supervisor/%(program_name)s.log
|
||||
EOF
|
||||
|
||||
}
|
||||
|
||||
# Initializes main directories
|
||||
function initialize_datadir()
|
||||
{
|
||||
@@ -169,32 +236,32 @@ function initialize_datadir()
|
||||
|
||||
# This symlink simplifies paths for loading sls files
|
||||
[[ -d /srv ]] && [[ ! -L /srv ]] && rm -rf /srv
|
||||
ln -sfnv ${SALT_BASE_DIR} /srv
|
||||
ln -sfnv "${SALT_BASE_DIR}" /srv
|
||||
|
||||
# Set Salt root permissions
|
||||
chown -R ${SALT_USER}: ${SALT_ROOT_DIR}
|
||||
chown -R "${SALT_USER}": "${SALT_ROOT_DIR}"
|
||||
|
||||
# Set Salt run permissions
|
||||
mkdir -p /var/run/salt
|
||||
chown -R ${SALT_USER}: /var/run/salt
|
||||
chown -R "${SALT_USER}": /var/run/salt
|
||||
|
||||
# Set cache permissions
|
||||
mkdir -p /var/cache/salt/master
|
||||
chown -R ${SALT_USER}: /var/cache/salt
|
||||
chown -R "${SALT_USER}": /var/cache/salt
|
||||
|
||||
# Keys directories
|
||||
mkdir -p ${SALT_KEYS_DIR}/minions
|
||||
chown -R ${SALT_USER}: ${SALT_KEYS_DIR}
|
||||
mkdir -p "${SALT_KEYS_DIR}/minions"
|
||||
chown -R "${SALT_USER}": "${SALT_KEYS_DIR}"
|
||||
|
||||
# Logs directory
|
||||
mkdir -p ${SALT_LOGS_DIR}/salt ${SALT_LOGS_DIR}/supervisor
|
||||
chmod -R 0755 ${SALT_LOGS_DIR}/supervisor
|
||||
chown -R root: ${SALT_LOGS_DIR}/supervisor
|
||||
mkdir -p "${SALT_LOGS_DIR}/salt" "${SALT_LOGS_DIR}/supervisor"
|
||||
chmod -R 0755 "${SALT_LOGS_DIR}/supervisor"
|
||||
chown -R root: "${SALT_LOGS_DIR}/supervisor"
|
||||
|
||||
[[ -d /var/log/salt ]] && [[ ! -L /var/log/salt ]] && rm -rf /var/log/salt
|
||||
mkdir -p ${SALT_LOGS_DIR}/salt /var/log
|
||||
ln -sfnv ${SALT_LOGS_DIR}/salt /var/log/salt
|
||||
chown -R ${SALT_USER}: ${SALT_LOGS_DIR}/salt
|
||||
mkdir -p "${SALT_LOGS_DIR}/salt" /var/log
|
||||
ln -sfnv "${SALT_LOGS_DIR}/salt" /var/log/salt
|
||||
chown -R "${SALT_USER}": "${SALT_LOGS_DIR}/salt"
|
||||
}
|
||||
|
||||
# Configures logrotate
|
||||
@@ -243,6 +310,21 @@ ${SALT_LOGS_DIR}/salt/key {
|
||||
notifempty
|
||||
}
|
||||
EOF
|
||||
|
||||
if [[ "${SALT_API_SERVICE_ENABLED}" == true ]]; then
|
||||
# configure salt-api log rotation
|
||||
cat >> /etc/logrotate.d/salt <<EOF
|
||||
|
||||
${SALT_LOGS_DIR}/salt/api {
|
||||
${SALT_LOG_ROTATE_FREQUENCY}
|
||||
missingok
|
||||
rotate ${SALT_LOG_ROTATE_RETENTION}
|
||||
compress
|
||||
notifempty
|
||||
}
|
||||
EOF
|
||||
fi
|
||||
|
||||
}
|
||||
|
||||
# Initializes the system
|
||||
@@ -253,6 +335,7 @@ function initialize_system()
|
||||
configure_logrotate
|
||||
configure_timezone
|
||||
configure_salt_master
|
||||
configure_salt_api
|
||||
setup_salt_keys
|
||||
setup_ssh_keys
|
||||
rm -rf /var/run/supervisor.sock
|
||||
|
||||
@@ -3,7 +3,7 @@ version: '3'
|
||||
services:
|
||||
master:
|
||||
container_name: salt_master
|
||||
image: cdalvaro/saltstack-master:3000.3
|
||||
image: cdalvaro/saltstack-master:3000.3_1
|
||||
restart: always
|
||||
volumes:
|
||||
- "roots/:/home/salt/data/srv"
|
||||
|
||||
@@ -1,29 +1,45 @@
|
||||
#!/usr/bin/env bash
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
set -o pipefail
|
||||
|
||||
source "${SALT_RUNTIME_DIR}/functions.sh"
|
||||
|
||||
[[ ${DEBUG} == true ]] && set -x
|
||||
[[ "${DEBUG}" == true ]] && set -x
|
||||
|
||||
case ${1} in
|
||||
app:start|app:init|app:gen-signed-keys)
|
||||
case "${1}" in
|
||||
app:start|app:gen-signed-keys)
|
||||
|
||||
initialize_system
|
||||
|
||||
case ${1} in
|
||||
case "${1}" in
|
||||
app:start)
|
||||
echo "Starting salt-master..."
|
||||
echo "Starting supervisord ..."
|
||||
exec /usr/bin/supervisord -nc /etc/supervisor/supervisord.conf
|
||||
;;
|
||||
app:gen-signed-keys)
|
||||
shift 1
|
||||
gen_signed_keys ${1}
|
||||
gen_signed_keys "${1}"
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
app:restart)
|
||||
shift 1
|
||||
case "${1}" in
|
||||
salt-master|salt-api)
|
||||
echo "Restarting ${1} service ..."
|
||||
exec pkill "${1}"
|
||||
;;
|
||||
*)
|
||||
log_error "Unable to restart ${1} serice. Service is unknown"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
app:help)
|
||||
echo "Available options:"
|
||||
echo " app:start - Start salt-master service. (default)"
|
||||
echo " app:start - Start configured services. (default)"
|
||||
echo " app:restart - Restart the specified service on a running container. Choices: salt-master, salt-api"
|
||||
echo " app:gen-signed-keys <key_name> - Create a master_sign key pair and its signature inside ${SALT_KEYS_DIR}/generated/"
|
||||
echo " app:help - Displays this help."
|
||||
echo " [command] - Execute the specified command, eg. bash."
|
||||
|
||||
@@ -3,7 +3,10 @@
|
||||
# Docker Daemon Build Hook
|
||||
# $IMAGE_NAME var is injected into the build so the tag is correct.
|
||||
|
||||
docker pull ${DOCKER_REPO}:latest
|
||||
|
||||
docker build \
|
||||
--cache-from=${DOCKER_REPO}:latest \
|
||||
--build-arg=BUILD_DATE="$(date +"%Y-%m-%d %H:%M:%S%:z")" \
|
||||
--build-arg=VCS_REF="$(git rev-parse --short HEAD)" \
|
||||
-t ${IMAGE_NAME} .
|
||||
|
||||
Reference in New Issue
Block a user