diff --git a/airflow-core/src/airflow/cli/commands/local_commands/standalone_command.py b/airflow-core/src/airflow/cli/commands/local_commands/standalone_command.py index 7188411969c00..3e89b8f14fec7 100644 --- a/airflow-core/src/airflow/cli/commands/local_commands/standalone_command.py +++ b/airflow-core/src/airflow/cli/commands/local_commands/standalone_command.py @@ -27,6 +27,7 @@ from termcolor import colored +from airflow.api_fastapi.app import create_auth_manager from airflow.configuration import conf from airflow.executors import executor_constants from airflow.executors.executor_loader import ExecutorLoader @@ -58,7 +59,6 @@ def entrypoint(cls, args): def __init__(self): self.subcommands = {} self.output_queue = deque() - self.user_info = {} self.ready_time = None self.ready_delay = 3 @@ -69,6 +69,7 @@ def run(self): logging.getLogger("").setLevel(logging.WARNING) # Startup checks and prep env = self.calculate_env() + self.find_user_info() self.initialize_database() # Set up commands to run self.subcommands["scheduler"] = SubCommand( @@ -179,8 +180,42 @@ def calculate_env(self): else: self.print_output("standalone", "Forcing executor to LocalExecutor") env["AIRFLOW__CORE__EXECUTOR"] = executor_constants.LOCAL_EXECUTOR + + # Make sure we're using SimpleAuthManager + simple_auth_manager_classpath = ( + "airflow.api_fastapi.auth.managers.simple.simple_auth_manager.SimpleAuthManager" + ) + if conf.get("core", "auth_manager") != simple_auth_manager_classpath: + self.print_output("standalone", "Forcing auth manager to SimpleAuthManager") + env["AIRFLOW__CORE__AUTH_MANAGER"] = simple_auth_manager_classpath + os.environ["AIRFLOW__CORE__AUTH_MANAGER"] = simple_auth_manager_classpath # also in this process! + return env + def find_user_info(self): + if conf.get("core", "simple_auth_manager_all_admins").lower() == "true": + # If we have no auth anyways, no need to print or do anything + return + if conf.get("core", "simple_auth_manager_users") != "admin:admin": + self.print_output( + "standalone", + "Not outputting user passwords - `[core] simple_auth_manager_users` is already set.", + ) + return + + am = create_auth_manager() + + password_file = am.get_generated_password_file() + if os.path.exists(password_file): + self.print_output( + "standalone", + f"Password for the admin user has been previously generated in {password_file}. Not echoing it here.", + ) + return + + # this generates the password and prints it + am.init() + def initialize_database(self): """Make sure all the tables are created.""" # Set up DB tables @@ -188,15 +223,6 @@ def initialize_database(self): db.initdb() self.print_output("standalone", "Database ready") - # Then create a "default" admin user if necessary - from airflow.providers.fab.auth_manager.cli_commands.utils import get_application_builder - - with get_application_builder() as appbuilder: - if hasattr(appbuilder.sm, "create_admin_standalone"): - user_name, password = appbuilder.sm.create_admin_standalone() - # Store what we know about the user for printing later in startup - self.user_info = {"username": user_name, "password": password} - def is_ready(self): """ Detect when all Airflow components are ready to serve. @@ -244,11 +270,6 @@ def print_ready(self): """ self.print_output("standalone", "") self.print_output("standalone", "Airflow is ready") - if self.user_info["password"]: - self.print_output( - "standalone", - f"Login with username: {self.user_info['username']} password: {self.user_info['password']}", - ) self.print_output( "standalone", "Airflow Standalone is for development purposes only. Do not use this in production!", diff --git a/providers/fab/src/airflow/providers/fab/auth_manager/security_manager/override.py b/providers/fab/src/airflow/providers/fab/auth_manager/security_manager/override.py index 7a7f59e732356..48ea2d00614fd 100644 --- a/providers/fab/src/airflow/providers/fab/auth_manager/security_manager/override.py +++ b/providers/fab/src/airflow/providers/fab/auth_manager/security_manager/override.py @@ -21,8 +21,6 @@ import datetime import itertools import logging -import os -import random import uuid from collections.abc import Collection, Iterable, Mapping from typing import TYPE_CHECKING, Any @@ -740,43 +738,6 @@ def builtin_roles(self): """Get the builtin roles.""" return self._builtin_roles - def create_admin_standalone(self) -> tuple[str | None, str | None]: - """Create an Admin user with a random password so that users can access airflow.""" - from airflow.configuration import AIRFLOW_HOME, make_group_other_inaccessible - - user_name = "admin" - - # We want a streamlined first-run experience, but we do not want to - # use a preset password as people will inevitably run this on a public - # server. Thus, we make a random password and store it in AIRFLOW_HOME, - # with the reasoning that if you can read that directory, you can see - # the database credentials anyway. - password_path = os.path.join(AIRFLOW_HOME, "standalone_admin_password.txt") - - user_exists = self.find_user(user_name) is not None - we_know_password = os.path.isfile(password_path) - - # If the user does not exist, make a random password and make it - if not user_exists: - print(f"FlaskAppBuilder Authentication Manager: Creating {user_name} user") - if (role := self.find_role("Admin")) is None: - raise AirflowException("Unable to find role 'Admin'") - # password does not contain visually similar characters: ijlIJL1oO0 - password = "".join(random.choices("abcdefghkmnpqrstuvwxyzABCDEFGHKMNPQRSTUVWXYZ23456789", k=16)) - with open(password_path, "w") as file: - file.write(password) - make_group_other_inaccessible(password_path) - self.add_user(user_name, "Admin", "User", "admin@example.com", role, password) - print(f"FlaskAppBuilder Authentication Manager: Created {user_name} user") - # If the user does exist, and we know its password, read the password - elif user_exists and we_know_password: - with open(password_path) as file: - password = file.read().strip() - # Otherwise we don't know the password - else: - password = None - return user_name, password - def _init_config(self): """ Initialize config.