Skip to content
Open
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
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
### Fixed

- Do not use immutable Secret objects for internal secrets. Migrate existing secrets to mutable versions ([#770]).
- Allow OPA integration with arbitrary user role when using Airflow 3. Previously, this only worked for users with Admin role ([#800])

[#750]: https://github.com/stackabletech/airflow-operator/pull/750
[#765]: https://github.com/stackabletech/airflow-operator/pull/765
Expand All @@ -39,6 +40,7 @@
[#784]: https://github.com/stackabletech/airflow-operator/pull/784
[#786]: https://github.com/stackabletech/airflow-operator/pull/786
[#795]: https://github.com/stackabletech/airflow-operator/pull/795
[#800]: https://github.com/stackabletech/airflow-operator/pull/800

## [26.3.0] - 2026-03-16

Expand Down
9 changes: 9 additions & 0 deletions tests/templates/kuttl/opa-interop/10-patch-ns.yaml.j2
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{% if test_scenario['values']['openshift'] == 'true' %}
# see https://github.com/stackabletech/issues/issues/566
---
apiVersion: kuttl.dev/v1beta1
kind: TestStep
commands:
- script: kubectl patch namespace $NAMESPACE -p '{"metadata":{"labels":{"pod-security.kubernetes.io/enforce":"privileged"}}}'
timeout: 120
{% endif %}
14 changes: 14 additions & 0 deletions tests/templates/kuttl/opa-interop/11-assert.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
---
apiVersion: kuttl.dev/v1beta1
kind: TestAssert
metadata:
name: test-airflow-postgresql
timeout: 480
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: airflow-postgresql
status:
readyReplicas: 1
replicas: 1
12 changes: 12 additions & 0 deletions tests/templates/kuttl/opa-interop/11-install-postgresql.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
apiVersion: kuttl.dev/v1beta1
kind: TestStep
commands:
- script: >-
helm install airflow-postgresql
--namespace $NAMESPACE
--version 12.5.6
--values 11_helm-bitnami-postgresql-values.yaml
--repo https://charts.bitnami.com/bitnami postgresql
--wait
timeout: 600
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
---
global:
security:
allowInsecureImages: true

image:
repository: bitnamilegacy/postgresql

volumePermissions:
enabled: false
image:
repository: bitnamilegacy/os-shell
securityContext:
runAsUser: auto

metrics:
image:
repository: bitnamilegacy/postgres-exporter

primary:
podSecurityContext:
{% if test_scenario['values']['openshift'] == 'true' %}
enabled: false
{% else %}
enabled: true
{% endif %}
containerSecurityContext:
enabled: false
resources:
requests:
memory: "128Mi"
cpu: "512m"
limits:
memory: "128Mi"
cpu: "1"

shmVolume:
chmod:
enabled: false

auth:
username: airflow
password: airflow
database: airflow
6 changes: 6 additions & 0 deletions tests/templates/kuttl/opa-interop/20-assert.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
apiVersion: kuttl.dev/v1beta1
kind: TestAssert
timeout: 300
commands:
- script: kubectl -n $NAMESPACE wait --for=condition=available --timeout=10m opacluster/test-opa
34 changes: 34 additions & 0 deletions tests/templates/kuttl/opa-interop/20-install-opa.yaml.j2
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
---
apiVersion: kuttl.dev/v1beta1
kind: TestStep
metadata:
name: install-opa
---
apiVersion: opa.stackable.tech/v1alpha1
kind: OpaCluster
metadata:
name: test-opa
spec:
image:
{% if test_scenario['values']['opa-latest'].find(",") > 0 %}
custom: "{{ test_scenario['values']['opa-latest'].split(',')[1] }}"
productVersion: "{{ test_scenario['values']['opa-latest'].split(',')[0] }}"
{% else %}
productVersion: "{{ test_scenario['values']['opa-latest'] }}"
{% endif %}
pullPolicy: IfNotPresent
clusterConfig:
{% if lookup('env', 'VECTOR_AGGREGATOR') %}
vectorAggregatorConfigMapName: vector-aggregator-discovery
{% endif %}
servers:
config:
logging:
enableVectorAgent: {{ lookup('env', 'VECTOR_AGGREGATOR') | length > 0 }}
containers:
opa:
loggers:
decision:
level: INFO
roleGroups:
default: {}
28 changes: 28 additions & 0 deletions tests/templates/kuttl/opa-interop/30-assert.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
---
apiVersion: kuttl.dev/v1beta1
kind: TestAssert
metadata:
name: install-airflow
timeout: 1200
commands:
- script: >
kubectl --namespace $NAMESPACE
wait --for=condition=available=true
airflowclusters.airflow.stackable.tech/airflow
--timeout 301s
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: airflow-webserver-default
status:
readyReplicas: 1
replicas: 1
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: airflow-scheduler-default
status:
readyReplicas: 1
replicas: 1
64 changes: 64 additions & 0 deletions tests/templates/kuttl/opa-interop/30-install-airflow.yaml.j2
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
---
apiVersion: kuttl.dev/v1beta1
kind: TestStep
metadata:
name: install-airflow
---
apiVersion: v1
kind: Secret
metadata:
name: airflow-admin-credentials
type: Opaque
stringData:
adminUser.username: airflow
adminUser.firstname: Airflow
adminUser.lastname: Admin
adminUser.email: airflow@airflow.com
adminUser.password: airflow
---
apiVersion: v1
kind: Secret
metadata:
name: airflow-postgresql-credentials
stringData:
username: airflow
password: airflow
---
apiVersion: airflow.stackable.tech/v1alpha1
kind: AirflowCluster
metadata:
name: airflow
spec:
image:
{% if test_scenario['values']['airflow-latest'].find(",") > 0 %}
custom: "{{ test_scenario['values']['airflow-latest'].split(',')[1] }}"
productVersion: "{{ test_scenario['values']['airflow-latest'].split(',')[0] }}"
{% else %}
productVersion: "{{ test_scenario['values']['airflow-latest'] }}"
{% endif %}
pullPolicy: IfNotPresent
clusterConfig:
authorization:
opa:
configMapName: test-opa
package: airflow
credentialsSecretName: airflow-admin-credentials
metadataDatabase:
postgresql:
host: airflow-postgresql
database: airflow
credentialsSecretName: airflow-postgresql-credentials
loadExamples: true
webservers:
roleConfig:
listenerClass: external-unstable
envOverrides:
AIRFLOW__CORE__AUTH_OPA_CACHE_MAXSIZE: "0"
roleGroups:
default:
replicas: 1
kubernetesExecutors: {}
schedulers:
roleGroups:
default:
replicas: 1
56 changes: 56 additions & 0 deletions tests/templates/kuttl/opa-interop/31-opa-rules.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
---
apiVersion: v1
kind: ConfigMap
metadata:
name: airflow-rules
labels:
opa.stackable.tech/bundle: "true"
data:
airflow.rego: |
package airflow

# Default deny everything. Only the rules below should permit anything.
default is_authorized_configuration := false
default is_authorized_connection := false
default is_authorized_dag := false
default is_authorized_pool := false
default is_authorized_variable := false
default is_authorized_view := false
default is_authorized_custom_view := false
default is_authorized_backfill := false
default is_authorized_asset := false
default is_authorized_asset_alias := false

# Allow-all for the "airflow" admin so the operator can bootstrap the
# cluster (creating the admin user, syncing example DAGs, etc).
is_authorized_configuration if input.user.name == "airflow"
is_authorized_connection if input.user.name == "airflow"
is_authorized_dag if input.user.name == "airflow"
is_authorized_pool if input.user.name == "airflow"
is_authorized_variable if input.user.name == "airflow"
is_authorized_view if input.user.name == "airflow"
is_authorized_custom_view if input.user.name == "airflow"
is_authorized_backfill if input.user.name == "airflow"
is_authorized_asset if input.user.name == "airflow"
is_authorized_asset_alias if input.user.name == "airflow"

# jane.doe is OPA-allow-all - same shape as the airflow admin block above.
# Intentionally broad: the goal of this test is to isolate the FAB
# filter/listing layer. Any 403 we see for jane.doe must therefore come
# from FAB (the layer with the bug), not from OPA denying some auxiliary
# check (is_authorized_view, is_authorized_configuration, ...) that the
# listing endpoint also performs.
#
# With this rule + Admin role: both layers say yes -> test passes.
# With this rule + Public role: OPA says yes everywhere, FAB's
# unoverridden get_authorized_dag_ids says no -> 403 -> test fails (bug).
is_authorized_configuration if input.user.name == "jane.doe"
is_authorized_connection if input.user.name == "jane.doe"
is_authorized_dag if input.user.name == "jane.doe"
is_authorized_pool if input.user.name == "jane.doe"
is_authorized_variable if input.user.name == "jane.doe"
is_authorized_view if input.user.name == "jane.doe"
is_authorized_custom_view if input.user.name == "jane.doe"
is_authorized_backfill if input.user.name == "jane.doe"
is_authorized_asset if input.user.name == "jane.doe"
is_authorized_asset_alias if input.user.name == "jane.doe"
22 changes: 22 additions & 0 deletions tests/templates/kuttl/opa-interop/32-create-users.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
---
apiVersion: kuttl.dev/v1beta1
kind: TestStep
metadata:
name: create-users
timeout: 300
commands:
- script: |
kubectl exec -n $NAMESPACE airflow-webserver-default-0 -- airflow users create \
--username "jane.doe" \
--firstname "Jane" \
--lastname "Doe" \
--email "jane.doe@stackable.tech" \
--password "T8mn72D9" \
--role "Public"
kubectl exec -n $NAMESPACE airflow-webserver-default-0 -- airflow users create \
--username "john.doe" \
--firstname "John" \
--lastname "Doe" \
--email "john.doe@stackable.tech" \
--password "T8mn72D9" \
--role "Admin"
6 changes: 6 additions & 0 deletions tests/templates/kuttl/opa-interop/40-assert.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
apiVersion: kuttl.dev/v1beta1
kind: TestAssert
timeout: 120
commands:
- script: kubectl exec -n $NAMESPACE -i airflow-webserver-default-0 -- python3 < 40_check-listing_3.py
Loading
Loading