From c9227dfc019ba9f6b933949f88b96be92061ece8 Mon Sep 17 00:00:00 2001 From: Sebastian Bernauer Date: Wed, 1 Oct 2025 13:06:25 +0200 Subject: [PATCH 1/2] fix: Set FERNET_KEY to prevent connections from crashing webservers --- CHANGELOG.md | 3 +++ .../operator-binary/src/airflow_controller.rs | 27 +++++++++++++++---- .../src/crd/internal_secret.rs | 7 +++-- rust/operator-binary/src/crd/mod.rs | 8 ++++-- rust/operator-binary/src/env_vars.rs | 25 ++++++++++++----- 5 files changed, 54 insertions(+), 16 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4edb4121..ecbc606e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,8 @@ Previously, the operator would always use the same name for the executor Pod template ConfigMap. Thus when deploying multiple Airflow instances in the same namespace, there would be a conflict over the contents of that ConfigMap ([#678]). - For versions >= 3 custom logging initializes the RemoteLogIO handler to fix remote logging ([#683]). +- Prevent Airflow connections from breaking in combination with Airflow 3. + This was achieved by setting the `AIRFLOW__CORE__FERNET_KEY` env var ([#XXX]). ### Removed @@ -42,6 +44,7 @@ [#690]: https://github.com/stackabletech/airflow-operator/pull/690 [#691]: https://github.com/stackabletech/airflow-operator/pull/691 [#692]: https://github.com/stackabletech/airflow-operator/pull/692 +[#XXX]: https://github.com/stackabletech/airflow-operator/pull/XXX ## [25.7.0] - 2025-07-23 diff --git a/rust/operator-binary/src/airflow_controller.rs b/rust/operator-binary/src/airflow_controller.rs index 219d3d97..e9e17d34 100644 --- a/rust/operator-binary/src/airflow_controller.rs +++ b/rust/operator-binary/src/airflow_controller.rs @@ -93,7 +93,10 @@ use crate::{ }, authorization::AirflowAuthorizationResolved, build_recommended_labels, - internal_secret::{ENV_INTERNAL_SECRET, ENV_JWT_SECRET, create_random_secret}, + internal_secret::{ + FERNET_KEY_SECRET_KEY, INTERNAL_SECRET_SECRET_KEY, JWT_SECRET_SECRET_KEY, + create_random_secret, + }, v1alpha1, }, env_vars::{self, build_airflow_template_envs}, @@ -476,8 +479,8 @@ pub async fn reconcile_airflow( } create_random_secret( - airflow.shared_internal_secret_name().as_ref(), - ENV_INTERNAL_SECRET, + &airflow.shared_internal_secret_secret_name(), + INTERNAL_SECRET_SECRET_KEY, 256, airflow, client, @@ -486,8 +489,8 @@ pub async fn reconcile_airflow( .context(InvalidInternalSecretSnafu)?; create_random_secret( - airflow.shared_jwt_secret_name().as_ref(), - ENV_JWT_SECRET, + &airflow.shared_jwt_secret_secret_name(), + JWT_SECRET_SECRET_KEY, 256, airflow, client, @@ -495,6 +498,20 @@ pub async fn reconcile_airflow( .await .context(InvalidInternalSecretSnafu)?; + create_random_secret( + &airflow.shared_fernet_key_secret_name(), + FERNET_KEY_SECRET_KEY, + // https://airflow.apache.org/docs/apache-airflow/stable/security/secrets/fernet.html#security-fernet + // does not document how long the fernet key should be, but recommends using + // python -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())" + // which returns `jUm21LuA76YZmrIa9u4eXRg0h0P24MDC9IDOmDvJbfw=`, which has 44 characters, which makes 32 bytes. + 32, + airflow, + client, + ) + .await + .context(InvalidInternalSecretSnafu)?; + for (role_name, role_config) in validated_role_config.iter() { let airflow_role = AirflowRole::from_str(role_name).context(UnidentifiedAirflowRoleSnafu { diff --git a/rust/operator-binary/src/crd/internal_secret.rs b/rust/operator-binary/src/crd/internal_secret.rs index f0b7d4d6..bf2eeb0a 100644 --- a/rust/operator-binary/src/crd/internal_secret.rs +++ b/rust/operator-binary/src/crd/internal_secret.rs @@ -16,12 +16,15 @@ use crate::{airflow_controller::AIRFLOW_CONTROLLER_NAME, crd::v1alpha1}; // Secret key used to run the api server. It should be as random as possible. // It should be consistent across instances of the webserver. The webserver key // is also used to authorize requests to Celery workers when logs are retrieved. -pub const ENV_INTERNAL_SECRET: &str = "INTERNAL_SECRET"; +pub const INTERNAL_SECRET_SECRET_KEY: &str = "INTERNAL_SECRET"; // Used for env-var: AIRFLOW__API_AUTH__JWT_SECRET // Secret key used to encode and decode JWTs to authenticate to public and // private APIs. It should be as random as possible, but consistent across // instances of API services. -pub const ENV_JWT_SECRET: &str = "JWT_SECRET"; +pub const JWT_SECRET_SECRET_KEY: &str = "JWT_SECRET"; +// Used for env-var: AIRFLOW__CORE__FERNET_KEY +// See https://airflow.apache.org/docs/apache-airflow/stable/security/secrets/fernet.html#security-fernet +pub const FERNET_KEY_SECRET_KEY: &str = "FERNET_KEY"; type Result = std::result::Result; diff --git a/rust/operator-binary/src/crd/mod.rs b/rust/operator-binary/src/crd/mod.rs index e710bc06..aa1a234f 100644 --- a/rust/operator-binary/src/crd/mod.rs +++ b/rust/operator-binary/src/crd/mod.rs @@ -454,13 +454,17 @@ impl v1alpha1::AirflowCluster { fragment::validate(conf_executor).context(FragmentValidationFailureSnafu) } - pub fn shared_internal_secret_name(&self) -> String { + pub fn shared_internal_secret_secret_name(&self) -> String { format!("{}-internal-secret", &self.name_any()) } - pub fn shared_jwt_secret_name(&self) -> String { + pub fn shared_jwt_secret_secret_name(&self) -> String { format!("{}-jwt-secret", &self.name_any()) } + + pub fn shared_fernet_key_secret_name(&self) -> String { + format!("{}-fernet-key", &self.name_any()) + } } fn extract_role_from_webserver_config( diff --git a/rust/operator-binary/src/env_vars.rs b/rust/operator-binary/src/env_vars.rs index 109cb055..b04144bc 100644 --- a/rust/operator-binary/src/env_vars.rs +++ b/rust/operator-binary/src/env_vars.rs @@ -21,7 +21,9 @@ use crate::{ AirflowAuthenticationClassResolved, AirflowClientAuthenticationDetailsResolved, }, authorization::AirflowAuthorizationResolved, - internal_secret::{ENV_INTERNAL_SECRET, ENV_JWT_SECRET}, + internal_secret::{ + FERNET_KEY_SECRET_KEY, INTERNAL_SECRET_SECRET_KEY, JWT_SECRET_SECRET_KEY, + }, v1alpha1, }, util::{env_var_from_secret, role_service_name}, @@ -83,7 +85,7 @@ pub fn build_airflow_statefulset_envs( ) -> Result, Error> { let mut env: BTreeMap = BTreeMap::new(); let secret = airflow.spec.cluster_config.credentials_secret.as_str(); - let internal_secret_name = airflow.shared_internal_secret_name(); + let internal_secret_name = airflow.shared_internal_secret_secret_name(); env.extend(static_envs(git_sync_resources)); @@ -100,7 +102,7 @@ pub fn build_airflow_statefulset_envs( env_var_from_secret( AIRFLOW_WEBSERVER_SECRET_KEY, &internal_secret_name, - ENV_INTERNAL_SECRET, + INTERNAL_SECRET_SECRET_KEY, ), ); // Replaces AIRFLOW__WEBSERVER__SECRET_KEY >= 3.0.2. @@ -109,9 +111,19 @@ pub fn build_airflow_statefulset_envs( env_var_from_secret( "AIRFLOW__API__SECRET_KEY", &internal_secret_name, - ENV_INTERNAL_SECRET, + INTERNAL_SECRET_SECRET_KEY, ), ); + + env.insert( + "AIRFLOW__CORE__FERNET_KEY".into(), + env_var_from_secret( + "AIRFLOW__CORE__FERNET_KEY", + &airflow.shared_fernet_key_secret_name(), + FERNET_KEY_SECRET_KEY, + ), + ); + env.insert( AIRFLOW_DATABASE_SQL_ALCHEMY_CONN.into(), env_var_from_secret( @@ -485,13 +497,12 @@ fn add_version_specific_env_vars( // cluster, but should also be cluster-specific. // It is accessed from a secret to avoid cluster restarts // being triggered by an operator restart. - let jwt_secret_name = airflow.shared_jwt_secret_name(); env.insert( "AIRFLOW__API_AUTH__JWT_SECRET".into(), env_var_from_secret( "AIRFLOW__API_AUTH__JWT_SECRET", - &jwt_secret_name, - ENV_JWT_SECRET, + &airflow.shared_jwt_secret_secret_name(), + JWT_SECRET_SECRET_KEY, ), ); if airflow_role == &AirflowRole::Webserver { From 53d7a95f093e4f7574f0d063807a0dd66ab698d9 Mon Sep 17 00:00:00 2001 From: Sebastian Bernauer Date: Wed, 1 Oct 2025 13:08:12 +0200 Subject: [PATCH 2/2] changelog --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ecbc606e..e12bf98f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,7 +26,7 @@ Thus when deploying multiple Airflow instances in the same namespace, there would be a conflict over the contents of that ConfigMap ([#678]). - For versions >= 3 custom logging initializes the RemoteLogIO handler to fix remote logging ([#683]). - Prevent Airflow connections from breaking in combination with Airflow 3. - This was achieved by setting the `AIRFLOW__CORE__FERNET_KEY` env var ([#XXX]). + This was achieved by setting the `AIRFLOW__CORE__FERNET_KEY` env var ([#695]). ### Removed @@ -44,7 +44,7 @@ [#690]: https://github.com/stackabletech/airflow-operator/pull/690 [#691]: https://github.com/stackabletech/airflow-operator/pull/691 [#692]: https://github.com/stackabletech/airflow-operator/pull/692 -[#XXX]: https://github.com/stackabletech/airflow-operator/pull/XXX +[#695]: https://github.com/stackabletech/airflow-operator/pull/695 ## [25.7.0] - 2025-07-23