Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions .ci/eda-event-stream-external-database.secret.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
---
apiVersion: v1
kind: Secret
metadata:
name: 'eda-demo-event-stream-external-database'
stringData:
host: 'eda-postgresql'
port: '5555'
database: 'eda'
username: 'eda_event_stream'
password: 'eda_event_stream'
sslmode: 'prefer'
type: 'unmanaged'
type: Opaque
2 changes: 2 additions & 0 deletions .ci/eda_v1alpha1_eda.externaldb.ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,5 @@ spec:
automation_server_url: http://foo.bar
database:
database_secret: eda-demo-external-database
event_stream:
database_secret: eda-demo-event-stream-external-database
13 changes: 12 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -54,10 +54,21 @@ jobs:
- name: Set context to eda namespace
run: kubectl config set-context --current --namespace=eda

- name: Create postgresl secret for external database
- name: Create postgresql secret for external database
run: kubectl apply -f .ci/eda-external-database.secret.yaml
if: ${{ matrix.SCENARIO == 'externaldb' }}

- name: Create event stream database user in external postgresql
run: |
eval $(minikube -p minikube docker-env)
docker exec postgresql psql -U postgres -d eda -c "CREATE USER eda_event_stream WITH PASSWORD 'eda_event_stream';"
docker exec postgresql psql -U postgres -d eda -c "GRANT CONNECT ON DATABASE eda TO eda_event_stream;"
if: ${{ matrix.SCENARIO == 'externaldb' }}

- name: Create event stream postgresql secret for external database
run: kubectl apply -f .ci/eda-event-stream-external-database.secret.yaml
if: ${{ matrix.SCENARIO == 'externaldb' }}

- name: Create the EDA demo CR
run: |
kubectl apply -f .ci/eda_v1alpha1_eda.${{ matrix.SCENARIO }}.ci.yaml
Expand Down
13 changes: 12 additions & 1 deletion .github/workflows/pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -52,10 +52,21 @@ jobs:
- name: Set context to eda namespace
run: kubectl config set-context --current --namespace=eda

- name: Create postgresl secret for external database
- name: Create postgresql secret for external database
run: kubectl apply -f .ci/eda-external-database.secret.yaml
if: ${{ matrix.SCENARIO == 'externaldb' }}

- name: Create event stream database user in external postgresql
run: |
eval $(minikube -p minikube docker-env)
docker exec postgresql psql -U postgres -d eda -c "CREATE USER eda_event_stream WITH PASSWORD 'eda_event_stream';"
docker exec postgresql psql -U postgres -d eda -c "GRANT CONNECT ON DATABASE eda TO eda_event_stream;"
if: ${{ matrix.SCENARIO == 'externaldb' }}

- name: Create event stream postgresql secret for external database
run: kubectl apply -f .ci/eda-event-stream-external-database.secret.yaml
if: ${{ matrix.SCENARIO == 'externaldb' }}

- name: Create the EDA demo CR
run: |
kubectl apply -f .ci/eda_v1alpha1_eda.${{ matrix.SCENARIO }}.ci.yaml
Expand Down
9 changes: 9 additions & 0 deletions config/crd/bases/eda.ansible.com_edas.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ spec:
x-kubernetes-validations:
- rule: "!has(self.event_stream) || !has(self.event_stream.prefix) || !has(self.event_stream.mtls_prefix) || self.event_stream.prefix != self.event_stream.mtls_prefix"
message: "Event stream prefix and mtls_prefix cannot be the same."
- rule: "!has(self.event_stream) || !has(self.event_stream.database_secret) || !has(self.database) || !has(self.database.database_secret) || self.event_stream.database_secret != self.database.database_secret"
message: "Event stream database_secret must be different from the main database.database_secret."
- rule: "has(self.image) && has(self.image_version) || !has(self.image) && !has(self.image_version)"
message: "Both image and image_version must be set when required"
- rule: "has(self.image_web) && has(self.image_web_version) || !has(self.image_web) && !has(self.image_web_version)"
Expand Down Expand Up @@ -442,6 +444,13 @@ spec:
event_stream:
description: Defines desired state of Event Stream resources
properties:
database_secret:
description: |
Name of a pre-existing secret containing event stream database credentials.
If not provided, credentials will be auto-generated for managed databases.
Secret must contain keys: username, password, database, host, port, sslmode, type.
For external databases, the user must be pre-created with CONNECT privilege on the EDA database.
type: string
mtls:
description: Enable mTLS for event-stream.
type: boolean
Expand Down
59 changes: 59 additions & 0 deletions docs/user-guide/database-configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -119,3 +119,62 @@ spec:
chown 26:0 /var/lib/pgsql/data
chmod 700 /var/lib/pgsql/data
```

#### Event Stream Database User

The EDA operator automatically creates a dedicated PostgreSQL user (`eda_event_stream`) for event stream operations. This user has minimal privileges (CONNECT only) to reduce the security impact if credentials are exposed in Decision Environments.

> **Important**: The event stream database user must reside on the same PostgreSQL instance as the main EDA database. The `host`, `port`, and `database` fields in the event stream secret should match those of the main EDA database secret — they are used for operator-internal bookkeeping and do not configure a separate database connection.

##### Managed Database

For managed databases (when no `database.database_secret` is specified), the operator automatically:

1. Generates credentials and stores them in a secret named `<instance-name>-event-stream-postgres-configuration`
2. Creates the `eda_event_stream` user with CONNECT privilege on the EDA database
3. Injects the credentials into the event-stream deployment

No additional configuration is required for managed databases.

##### External Database

For external databases, you must manually create the event stream database user before deploying EDA:

1. Create the `eda_event_stream` user with CONNECT privilege:

```sql
CREATE USER eda_event_stream WITH PASSWORD 'your-secure-password';
GRANT CONNECT ON DATABASE eda TO eda_event_stream;
```

2. Create a Kubernetes secret with the event stream database credentials:

```yaml
---
apiVersion: v1
kind: Secret
metadata:
name: <resourcename>-event-stream-postgres-configuration
namespace: <target namespace>
stringData:
username: eda_event_stream
password: <password>
database: eda
port: "5432"
host: <external postgres host>
sslmode: prefer
type: unmanaged
type: Opaque
```

3. Reference the secret in your EDA CR:

```yaml
---
spec:
...
event_stream:
database_secret: <resourcename>-event-stream-postgres-configuration
```

**Note**: The variable `sslmode` is valid for `external` databases only. The allowed values are: `prefer`, `disable`, `allow`, `require`, `verify-ca`, `verify-full`.
6 changes: 6 additions & 0 deletions roles/backup/tasks/secrets.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@
- adminPasswordSecret
- databaseConfigurationSecret

- name: Dump event stream database secret if present
include_tasks: dump_generated_secret.yml
with_items:
- eventStreamDatabaseConfigurationSecret
when: this_eda['resources'][0]['status']['eventStreamDatabaseConfigurationSecret'] | default('') | length > 0

- name: Dump secret names from eda spec and data into file
include_tasks: dump_secret.yml
loop:
Expand Down
4 changes: 4 additions & 0 deletions roles/eda/defaults/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,10 @@ event_stream_mtls: "{{ event_stream.mtls | default(true) }}"
event_stream_mtls_prefix_path: "mtls/{{ event_stream_prefix_path.strip('/') }}"
event_stream_prefix_path: "{{ event_stream.prefix | default('/eda-event-streams') }}"

# Event stream database configuration
event_stream_database_username: eda_event_stream
event_stream_pg_sslmode: "prefer"

# Disable UI container's nginx ipv6 listener
ipv6_disabled: false

Expand Down
10 changes: 10 additions & 0 deletions roles/eda/tasks/update_status.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,16 @@
dbFieldsEncryptionSecret: "{{ db_fields_encryption_secret_name }}"
image: "{{ _image }}"

- name: Update event stream database secret status
operator_sdk.util.k8s_status:
api_version: '{{ api_version }}'
kind: "{{ kind }}"
name: "{{ ansible_operator_meta.name }}"
namespace: "{{ ansible_operator_meta.namespace }}"
status:
eventStreamDatabaseConfigurationSecret: "{{ __event_stream_database_secret }}"
when: __event_stream_database_secret is defined

- block:
- name: Retrieve instance version
k8s_exec:
Expand Down
18 changes: 18 additions & 0 deletions roles/eda/templates/eda-api.deployment.yaml.j2
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,9 @@ spec:
] %}
checksum-secret-{{ secret }}: "{{ lookup('ansible.builtin.vars', secret, default='')["resources"][0]["data"] | default('') | sha1 }}"
{% endfor %}
{% if event_stream_pg_config is defined %}
checksum-secret-event-stream-db: "{{ event_stream_pg_config['resources'][0]['data'] | default('') | sha1 }}"
{% endif %}
spec:
serviceAccountName: '{{ ansible_operator_meta.name }}'
{% if image_pull_secrets | length > 0 %}
Expand Down Expand Up @@ -164,6 +167,21 @@ spec:
secretKeyRef:
name: '{{ db_fields_encryption_secret_name }}'
key: secret_key
{% if __event_stream_database_secret is defined %}
# Event stream specific database credentials
- name: EDA_EVENT_STREAM_DB_USER
valueFrom:
secretKeyRef:
name: '{{ __event_stream_database_secret }}'
key: username
- name: EDA_EVENT_STREAM_DB_PASSWORD
valueFrom:
secretKeyRef:
name: '{{ __event_stream_database_secret }}'
key: password
- name: EDA_EVENT_STREAM_DB_SSLMODE
value: '{{ event_stream_postgres_sslmode }}'
{% endif %}
{% if combined_api.resource_requirements is defined %}
resources: {{ combined_api.resource_requirements }}
{% endif %}
Expand Down
56 changes: 56 additions & 0 deletions roles/postgres/tasks/create_event_stream_user.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
---
# Create event stream database user in PostgreSQL
#
# For EDA managed databases, postgres_pod is already set by create_managed_postgres.yml

- name: Check if event stream database user exists
kubernetes.core.k8s_exec:
namespace: "{{ ansible_operator_meta.namespace }}"
pod: "{{ postgres_pod['resources'][0]['metadata']['name'] }}"
container: postgres
command: >-
psql -tAc "SELECT 1 FROM pg_roles WHERE rolname = '{{ event_stream_postgres_user }}'"
register: event_stream_user_exists
no_log: "{{ no_log }}"
when:
- postgres_pod['resources'] | length > 0

- name: Create event stream database user
kubernetes.core.k8s_exec:
namespace: "{{ ansible_operator_meta.namespace }}"
pod: "{{ postgres_pod['resources'][0]['metadata']['name'] }}"
container: postgres
command: >-
psql -c "CREATE USER \"{{ event_stream_postgres_user }}\" WITH PASSWORD '{{ event_stream_postgres_pass }}';"
register: create_event_stream_user_result
failed_when: create_event_stream_user_result.rc != 0
no_log: "{{ no_log }}"
when:
- postgres_pod['resources'] | length > 0
- event_stream_user_exists.stdout | default('') | trim != '1'

- name: Update event stream database user password
kubernetes.core.k8s_exec:
namespace: "{{ ansible_operator_meta.namespace }}"
pod: "{{ postgres_pod['resources'][0]['metadata']['name'] }}"
container: postgres
command: >-
psql -c "ALTER USER \"{{ event_stream_postgres_user }}\" WITH PASSWORD '{{ event_stream_postgres_pass }}';"
register: update_event_stream_user_result
failed_when: update_event_stream_user_result.rc != 0
no_log: "{{ no_log }}"
when:
- postgres_pod['resources'] | length > 0
- event_stream_user_exists.stdout | default('') | trim == '1'

- name: Grant CONNECT privilege to event stream user
kubernetes.core.k8s_exec:
namespace: "{{ ansible_operator_meta.namespace }}"
pod: "{{ postgres_pod['resources'][0]['metadata']['name'] }}"
container: postgres
command: >-
psql -c "GRANT CONNECT ON DATABASE \"{{ eda_postgres_database }}\" TO \"{{ event_stream_postgres_user }}\";"
register: grant_connect_result
failed_when: grant_connect_result.rc != 0
when:
- postgres_pod['resources'] | length > 0
23 changes: 23 additions & 0 deletions roles/postgres/tasks/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,26 @@
- name: Create managed Postgres StatefulSet if no external db is defined
import_tasks: create_managed_postgres.yml
when: managed_database

# Event stream database user configuration
- name: Determine and set event stream postgres configuration secret
import_tasks: set_event_stream_secret.yml
when: managed_database | bool or (event_stream.database_secret is defined and event_stream.database_secret | length > 0)

- name: Validate event stream configuration for external database
ansible.builtin.fail:
msg: >-
When using an external database, you must provide event stream database credentials.
Specify 'event_stream.database_secret' in your EDA CR with a pre-created secret
containing the event stream user credentials. The secret must contain: username,
password, host, port, and database keys. The event stream database user
must be pre-created on your external database with CONNECT privilege.
when:
- not managed_database | bool
- event_stream is defined
- event_stream.keys() | length > 0
- event_stream.database_secret is not defined or not event_stream.database_secret

- name: Create event stream database user in PostgreSQL
ansible.builtin.import_tasks: create_event_stream_user.yml
when: managed_database | bool
61 changes: 61 additions & 0 deletions roles/postgres/tasks/set_event_stream_secret.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
---
# Determine and set event stream postgres configuration secret

- name: Check for specified event stream PostgreSQL configuration
kubernetes.core.k8s_info:
kind: Secret
namespace: '{{ ansible_operator_meta.namespace }}'
name: '{{ event_stream.database_secret }}'
register: _custom_event_stream_pg_config
when:
- event_stream is defined
- event_stream.database_secret is defined
- event_stream.database_secret | length
no_log: "{{ no_log }}"

- name: Check for default event stream PostgreSQL configuration
kubernetes.core.k8s_info:
kind: Secret
namespace: '{{ ansible_operator_meta.namespace }}'
name: '{{ ansible_operator_meta.name }}-event-stream-postgres-configuration'
register: _default_event_stream_pg_config
no_log: "{{ no_log }}"

- name: Set event stream PostgreSQL configuration based on if user secret exists
ansible.builtin.set_fact:
_event_stream_pg_config: '{{ _custom_event_stream_pg_config["resources"] | default([]) | length | ternary(_custom_event_stream_pg_config, _default_event_stream_pg_config) }}'
no_log: "{{ no_log }}"

- name: Create event stream database configuration if no secret exists
when: not _event_stream_pg_config['resources'] | default([]) | length
block:
- name: Create event stream database configuration
kubernetes.core.k8s:
apply: true
definition: "{{ lookup('template', 'event-stream-postgres.secret.yaml.j2') }}"
no_log: "{{ no_log }}"

- name: Read event stream database configuration
kubernetes.core.k8s_info:
kind: Secret
namespace: '{{ ansible_operator_meta.namespace }}'
name: '{{ ansible_operator_meta.name }}-event-stream-postgres-configuration'
register: _generated_event_stream_pg_config
no_log: "{{ no_log }}"

- name: Set event stream PostgreSQL configuration fact
ansible.builtin.set_fact:
event_stream_pg_config: '{{ _generated_event_stream_pg_config["resources"] | default([]) | length | ternary(_generated_event_stream_pg_config, _event_stream_pg_config) }}'
no_log: "{{ no_log }}"

- name: Set actual event stream postgres secret name
ansible.builtin.set_fact:
__event_stream_database_secret: "{{ event_stream_pg_config['resources'][0]['metadata']['name'] }}"
no_log: "{{ no_log }}"

- name: Store event stream database configuration vars
ansible.builtin.set_fact:
event_stream_postgres_user: "{{ event_stream_pg_config['resources'][0]['data']['username'] | b64decode }}"
event_stream_postgres_pass: "{{ event_stream_pg_config['resources'][0]['data']['password'] | b64decode }}"
event_stream_postgres_sslmode: "{{ event_stream_pg_config['resources'][0]['data']['sslmode'] | default('prefer' | b64encode) | b64decode }}"
no_log: "{{ no_log }}"
19 changes: 19 additions & 0 deletions roles/postgres/templates/event-stream-postgres.secret.yaml.j2
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Event Stream Postgres Secret.
---
apiVersion: v1
kind: Secret
metadata:
name: '{{ ansible_operator_meta.name }}-event-stream-postgres-configuration'
namespace: '{{ ansible_operator_meta.namespace }}'
labels:
app.kubernetes.io/name: '{{ ansible_operator_meta.name }}'
app.kubernetes.io/managed-by: '{{ deployment_type }}-operator'
app.kubernetes.io/component: 'event-stream-database'
stringData:
username: '{{ event_stream_database_username }}'
password: '{{ lookup("password", "/dev/null length=32 chars=ascii_letters,digits") }}'
database: '{{ database_name }}'
port: '{{ eda_postgres_port }}'
host: '{{ eda_postgres_host }}'
sslmode: '{{ event_stream_pg_sslmode }}'
type: '{{ "managed" if managed_database else "unmanaged" }}'
Loading
Loading