Skip to content

Commit

Permalink
Configure filebeat keystore (#20)
Browse files Browse the repository at this point in the history
  • Loading branch information
arturo-seijas authored Oct 17, 2024
1 parent 04fc435 commit 0c5e4b3
Show file tree
Hide file tree
Showing 8 changed files with 199 additions and 28 deletions.
4 changes: 0 additions & 4 deletions rockcraft.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,3 @@ parts:
umount --recursive $CRAFT_OVERLAY/dev
umount $CRAFT_OVERLAY/proc
umount $CRAFT_OVERLAY/etc/resolv.conf
override-prime: |
echo admin | filebeat keystore add username --stdin --force
echo admin | filebeat keystore add password --stdin --force
craftctl default
10 changes: 8 additions & 2 deletions src-docs/state.py.md
Original file line number Diff line number Diff line change
Expand Up @@ -122,18 +122,22 @@ The Wazuh server charm state.
**Attributes:**

- <b>`indexer_ips`</b>: list of Wazuh indexer IPs.
- <b>`username`</b>: the filebeat username.
- <b>`password`</b>: the filebeat password.
- <b>`certificate`</b>: the TLS certificate.
- <b>`custom_config_repository`</b>: the git repository where the configuration is.
- <b>`custom_config_ssh_key`</b>: the SSH key for the git repository.
- <b>`proxy`</b>: proxy configuration.

<a href="../src/state.py#L72"><img align="right" style="float:right;" src="https://img.shields.io/badge/-source-cccccc?style=flat-square"></a>
<a href="../src/state.py#L76"><img align="right" style="float:right;" src="https://img.shields.io/badge/-source-cccccc?style=flat-square"></a>

### <kbd>function</kbd> `__init__`

```python
__init__(
indexer_ips: list[str],
username: str,
password: str,
certificate: str,
wazuh_config: WazuhConfig,
custom_config_ssh_key: Optional[str]
Expand All @@ -147,6 +151,8 @@ Initialize a new instance of the CharmState class.
**Args:**

- <b>`indexer_ips`</b>: list of Wazuh indexer IPs.
- <b>`username`</b>: the filebeat username.
- <b>`password`</b>: the filebeat password.
- <b>`certificate`</b>: the TLS certificate.
- <b>`wazuh_config`</b>: Wazuh configuration.
- <b>`custom_config_ssh_key`</b>: the SSH key for the git repository.
Expand Down Expand Up @@ -195,7 +201,7 @@ Get charm proxy configuration from juju charm environment.

---

<a href="../src/state.py#L117"><img align="right" style="float:right;" src="https://img.shields.io/badge/-source-cccccc?style=flat-square"></a>
<a href="../src/state.py#L126"><img align="right" style="float:right;" src="https://img.shields.io/badge/-source-cccccc?style=flat-square"></a>

### <kbd>classmethod</kbd> `from_charm`

Expand Down
25 changes: 25 additions & 0 deletions src-docs/wazuh.py.md
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,31 @@ Pull configuration files from the repository.
- <b>`container`</b>: the container to pull the files into.


---

<a href="../src/wazuh.py#L209"><img align="right" style="float:right;" src="https://img.shields.io/badge/-source-cccccc?style=flat-square"></a>

## <kbd>function</kbd> `configure_filebeat_user`

```python
configure_filebeat_user(
container: Container,
username: str,
password: str
) → None
```

Configure the filebeat user.



**Args:**

- <b>`container`</b>: the container to configure the user for.
- <b>`username`</b>: the username.
- <b>`password`</b>: the password.


---

## <kbd>class</kbd> `WazuhInstallationError`
Expand Down
1 change: 1 addition & 0 deletions src/charm.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ def reconcile(self) -> None:
if self.state.custom_config_repository:
wazuh.pull_configuration_files(container)
wazuh.update_configuration(container, self.state.indexer_ips)
wazuh.configure_filebeat_user(container, self.state.username, self.state.password)
container.add_layer("wazuh", self._pebble_layer, combine=True)
container.replan()
self.unit.status = ops.ActiveStatus()
Expand Down
26 changes: 22 additions & 4 deletions src/state.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,20 +58,26 @@ class State(BaseModel): # pylint: disable=too-few-public-methods
Attributes:
indexer_ips: list of Wazuh indexer IPs.
username: the filebeat username.
password: the filebeat password.
certificate: the TLS certificate.
custom_config_repository: the git repository where the configuration is.
custom_config_ssh_key: the SSH key for the git repository.
proxy: proxy configuration.
"""

indexer_ips: typing.Annotated[list[str], Field(min_length=1)]
username: str = Field(..., min_length=1)
password: str = Field(..., min_length=1)
certificate: str = Field(..., min_length=1)
custom_config_repository: typing.Optional[AnyUrl] = None
custom_config_ssh_key: typing.Optional[str] = None

def __init__(
def __init__( # pylint: disable=too-many-arguments, too-many-positional-arguments
self,
indexer_ips: list[str],
username: str,
password: str,
certificate: str,
wazuh_config: WazuhConfig,
custom_config_ssh_key: typing.Optional[str],
Expand All @@ -80,12 +86,16 @@ def __init__(
Args:
indexer_ips: list of Wazuh indexer IPs.
username: the filebeat username.
password: the filebeat password.
certificate: the TLS certificate.
wazuh_config: Wazuh configuration.
custom_config_ssh_key: the SSH key for the git repository.
"""
super().__init__(
indexer_ips=indexer_ips,
username=username,
password=password,
certificate=certificate,
custom_config_repository=wazuh_config.custom_config_repository,
custom_config_ssh_key=custom_config_ssh_key,
Expand Down Expand Up @@ -113,9 +123,8 @@ def proxy(self) -> "ProxyConfig":
except ValidationError as exc:
raise InvalidStateError("Invalid proxy configuration.") from exc

# pylint: disable=unused-argument
@classmethod
def from_charm(
def from_charm( # pylint: disable=unused-argument, too-many-locals
cls,
charm: ops.CharmBase,
indexer_relation_data: dict[str, str],
Expand All @@ -135,6 +144,13 @@ def from_charm(
InvalidStateError: if the state is invalid.
"""
try:
secret_id = indexer_relation_data.get("secret-user")
try:
secret_content = charm.model.get_secret(id=secret_id).get_content()
except ops.SecretNotFoundError as exc:
raise InvalidStateError("Indexer secret not found.") from exc
username = secret_content.get("username", "")
password = secret_content.get("password", "")
endpoint_data = indexer_relation_data.get("endpoints")
endpoints = list(endpoint_data.split(",")) if endpoint_data else []
certificates_json = (
Expand All @@ -153,7 +169,7 @@ def from_charm(
id=valid_config.custom_config_ssh_key
)
except ops.SecretNotFoundError as exc:
raise InvalidStateError("Secret not found.") from exc
raise InvalidStateError("Repository secret not found.") from exc
custom_config_ssh_key_content = custom_config_ssh_key_secret.get_content(
refresh=True
).get("value")
Expand All @@ -162,6 +178,8 @@ def from_charm(
if certificates:
return cls(
indexer_ips=endpoints,
username=username,
password=password,
certificate=certificates[0].get("certificate"),
wazuh_config=valid_config,
custom_config_ssh_key=custom_config_ssh_key_content,
Expand Down
41 changes: 41 additions & 0 deletions src/wazuh.py
Original file line number Diff line number Diff line change
Expand Up @@ -204,3 +204,44 @@ def pull_configuration_files(container: ops.Container) -> None:
process.wait_output()
except ops.pebble.ExecError as ex:
logging.debug(ex)


def configure_filebeat_user(container: ops.Container, username: str, password: str) -> None:
"""Configure the filebeat user.
Args:
container: the container to configure the user for.
username: the username.
password: the password.
"""
try:
process = container.exec(
[
"echo",
username,
"|",
"filebeat",
"keystore",
"add",
"username",
"--stdin",
"--force",
]
)
process.wait_output()
process = container.exec(
[
"echo",
password,
"|",
"filebeat",
"keystore",
"add",
"password",
"--stdin",
"--force",
]
)
process.wait_output()
except ops.pebble.ExecError as ex:
logging.debug(ex)
14 changes: 14 additions & 0 deletions tests/unit/test_charm.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,15 @@ def test_invalid_state_reaches_blocked_status(state_from_charm_mock):
assert harness.model.unit.status.name == ops.BlockedStatus().name


# pylint: disable=too-many-arguments, too-many-positional-arguments
@patch.object(State, "from_charm")
@patch.object(wazuh, "configure_git")
@patch.object(wazuh, "pull_configuration_files")
@patch.object(wazuh, "update_configuration")
@patch.object(wazuh, "install_certificates")
@patch.object(wazuh, "configure_filebeat_user")
def test_reconcile_reaches_active_status_when_repository_configured(
configure_filebeat_user_mock,
wazuh_install_certificates_mock,
wazuh_update_configuration_mock,
pull_configuration_files_mock,
Expand All @@ -51,9 +54,12 @@ def test_reconcile_reaches_active_status_when_repository_configured(
wazuh_config = WazuhConfig(
custom_config_repository=custom_config_repository, custom_config_ssh_key=secret_id
)
password = secrets.token_hex()
state_from_charm_mock.return_value = State(
certificate="somecert",
indexer_ips=["10.0.0.1"],
username="user1",
password=password,
wazuh_config=wazuh_config,
custom_config_ssh_key="somekey",
)
Expand All @@ -71,15 +77,19 @@ def test_reconcile_reaches_active_status_when_repository_configured(
container, str(wazuh_config.custom_config_repository), "somekey"
)
pull_configuration_files_mock.assert_called_with(container)
configure_filebeat_user_mock.assert_called_with(container, "user1", password)
assert harness.model.unit.status.name == ops.ActiveStatus().name


# pylint: disable=too-many-arguments, too-many-positional-arguments
@patch.object(State, "from_charm")
@patch.object(wazuh, "configure_git")
@patch.object(wazuh, "pull_configuration_files")
@patch.object(wazuh, "update_configuration")
@patch.object(wazuh, "install_certificates")
@patch.object(wazuh, "configure_filebeat_user")
def test_reconcile_reaches_active_status_when_repository_not_configured(
configure_filebeat_user_mock,
wazuh_install_certificates_mock,
wazuh_update_configuration_mock,
pull_configuration_files_mock,
Expand All @@ -91,9 +101,12 @@ def test_reconcile_reaches_active_status_when_repository_not_configured(
act: call reconcile.
assert: the charm reaches active status and configs are applied.
"""
password = secrets.token_hex()
state_from_charm_mock.return_value = State(
certificate="somecert",
indexer_ips=["10.0.0.1"],
username="user1",
password=password,
wazuh_config=WazuhConfig(custom_config_repository=None, custom_config_ssh_key=None),
custom_config_ssh_key=None,
)
Expand All @@ -109,6 +122,7 @@ def test_reconcile_reaches_active_status_when_repository_not_configured(
wazuh_update_configuration_mock.assert_called_with(container, ["10.0.0.1"])
configure_git_mock.assert_called_with(container, None, None)
pull_configuration_files_mock.assert_not_called()
configure_filebeat_user_mock.assert_called_with(container, "user1", password)
assert harness.model.unit.status.name == ops.ActiveStatus().name


Expand Down
Loading

0 comments on commit 0c5e4b3

Please sign in to comment.