minio icon indicating copy to clipboard operation
minio copied to clipboard

ldap just don't work

Open lmaddox opened this issue 1 month ago • 2 comments

services:
  
  minio:
    container_name: minio
    hostname:       minio.innovanon.com
    image:          quay.io/minio/minio:latest # Recommended image from quay.io
    ports:
      - "9000:9000" # MinIO API port
      #- "9001:9001" # MinIO Console UI port
    environment:
      MINIO_ROOT_USER:             minio_admin # Change this to a strong username
      MINIO_ROOT_PASSWORD:         minio_password # Change this to a strong password
      MINIO_SERVER_URL:            http://minio.innovanon.com:9000 # Important for S3 clients
      SOCKS_PROXY:                 socks5://tor.innovanon.com:9050
      HTTPS_PROXY:                 socks5://tor.innovanon.com:9050
      OTEL_EXPORTER_OTLP_ENDPOINT: http://otel-collector.innovanon.com:4317
      VIRTUAL_HOST:                minio.innovanon.com
      VIRTUAL_PORT:                9001
      MINIO_IDENTITY_LDAP_ENABLE:                 true
      MINIO_IDENTITY_LDAP_SERVER_ADDR:            ldap:389
      MINIO_IDENTITY_LDAP_SERVER_INSECURE:        true
      MINIO_IDENTITY_LDAP_LOOKUP_BIND_DN:         cn=admin,dc=innovanon,dc=com
      MINIO_IDENTITY_LDAP_LOOKUP_BIND_PASSWORD:   abc123
      MINIO_IDENTITY_LDAP_USER_DN_SEARCH_BASE_DN: ou=Users,dc=innovanon,dc=com
      MINIO_IDENTITY_LDAP_USER_DN_SEARCH_FILTER:  "(uid=%s)"
      #MINIO_IDENTITY_LDAP_USER_DN_ATTRIBUTES:     uid
      #MINIO_IDENTITY_LDAP_GROUP_SEARCH_FILTER: # Add if you need group-based policies
      #MINIO_IDENTITY_LDAP_POLICY_NAME:            default-ldap-policy # Example policy mapping
    volumes:
      - minio-data:/data # Persistent storage for MinIO data
    command: server /data --console-address ":9001" # Start server and console on specified ports
    restart:        unless-stopped
    logging:
      driver:       syslog
    depends_on:
      nginx-proxy:
        condition:  service_started # FIXME
      #syslog:
      #  condition: service_healthy
    deploy:
      resources:
        limits:
          cpus:     1.0
          memory:   500M
        reservations:
          cpus:     0.1
          memory:   100M
    healthcheck:
      #test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:9000/minio/health/live"]
      test: ["CMD", "curl", "-f", "--connect-timeout", "5", "http://localhost:9000/minio/health/live"]
      interval: 10s       # Check every 10 seconds
      timeout: 5s         # Wait up to 5 seconds for a response
      retries: 5          # Retry 5 times before marking as unhealthy
      start_period: 30s   # Give MinIO 30 seconds to start up initially

volumes:
  minio-data:
(venv) user@user-XPS-15-9550:~/src/ia$ docker compose down minio && docker compose up -d minio && docker compose logs -f minio
[+] Running 2/2
 ✔ Container minio     Removed                                                                                                                                                                                                           0.3s 
 ! Network ia_default  Resource is still in use                                                                                                                                                                                          0.0s 
WARN[0000] Found orphan containers ([caido ia-python-run-6a51f842b87c pos cafe-pos-system ia-sqlscript-1 inventree-proxy grocy inventree homebox snipeit ia-zap-1 kimai-db ia-python-run-b9926ae74fde flink-sql-gateway ia-flink-sql-gateway-run-7ff3159a4591 ia-python-run-251ccea060f8 airbyte-webapp airbyte-server airbyte-worker ia-python-run-bb2cc22abb06 ia-python-run-3f88571de8bc ia-python-run-9fa63b6560cd ia-python-run-9542d3715a6a]) for this project. If you removed or renamed this service in your compose file, you can run this command with the --remove-orphans flag to clean it up. 
[+] Running 2/2
 ✔ Container nginx-proxy  Running                                                                                                                                                                                                        0.0s 
 ✔ Container minio        Started                                                                                                                                                                                                        0.4s 
minio  | MinIO Object Storage Server
minio  | Copyright: 2015-2025 MinIO, Inc.
minio  | License: GNU AGPLv3 - https://www.gnu.org/licenses/agpl-3.0.html
minio  | Version: RELEASE.2025-09-07T16-13-09Z (go1.24.6 linux/amd64)
minio  | 
minio  | API: http://minio.innovanon.com:9000 
minio  | WebUI: http://172.18.0.22:9001 http://127.0.0.1:9001  
minio  | 
minio  | Docs: https://docs.min.io

ldap logs:


notice the ldap logs are blank. minio does not attempt to contact the ldap server. minio logs are similarly tacit unless ldap is misconfigured:

minio  | API: SYSTEM.iam
minio  | Time: 20:58:40 UTC 11/23/2025
minio  | DeploymentID: e23783af-a595-480f-a5ca-c3a5093f0cb0
minio  | Error: Unable to load LDAP configuration (LDAP configuration will be disabled!): The following environment variables are unknown: MINIO_IDENTITY_LDAP_POLICY_NAME (*fmt.wrapError)
minio  |        6: internal/logger/logger.go:271:logger.LogIf()
minio  |        5: cmd/logging.go:29:cmd.iamLogIf()
minio  |        4: cmd/iam.go:297:cmd.(*IAMSys).Init()
minio  |        3: cmd/server-main.go:1006:cmd.serverMain.func15.1()
minio  |        2: cmd/server-main.go:566:cmd.bootstrapTrace()
minio  |        1: cmd/server-main.go:1005:cmd.serverMain.func15()
minio  | INFO: Waiting for LDAP to be initialized.. (retrying in 1.887043323s)

ldap authentication is actually really easy to implement in python:

LDAP_SERVER_URL   :str    = os.getenv('LDAP_SERVER_URL',    "ldap://ldap.innovanon.com:389")
BIND_DN           :str    = os.getenv('BIND_DN',            "cn=admin,dc=innovanon,dc=com")
BIND_PASSWORD     :str    = os.getenv('BIND_PASSWORD',      "abc123")
SEARCH_BASE       :str    = os.getenv('SEARCH_BASE',        "dc=innovanon,dc=com")
USER_SEARCH_FILTER:str    = os.getenv('USER_SEARCH_FILTER', "(uid={})") # Set to search by 'uid' as requested


@cite('https://github.com/RetributionByRevenue/LDAP3-fastapi-auth-simple/blob/main/main.py')
async def LDAP_AUTH(username:str, password:str)->bool:
    """
    Authenticates a user against the LDAP server using the Search-and-Bind method.
    This version avoids importing constants to bypass environment errors.
    """
    try:
        server = Server(LDAP_SERVER_URL, get_info=ALL)
        
        # 1. Bind with Service Account to Search for the user's full DN
        # Connection() defaults to simple authentication and sync strategy.
        search_conn = Connection(
            server, 
            user=BIND_DN, 
            password=BIND_PASSWORD, 
            auto_bind=True
        )
        
        # Check if service account bind was successful
        if not search_conn.bound:
             #print(f"LDAP: Service account bind failed with result: {search_conn.result}")
             await logger.aerror("LDAP: Service account bind failed with result: %s", search_conn.result)
             return False

        # Construct the search filter for the user
        search_filter = USER_SEARCH_FILTER.format(username)
        
        search_conn.search(
            search_base=SEARCH_BASE,
            search_filter=search_filter,
            attributes=['uid'],
            size_limit=1
        )
        
        if not search_conn.entries:
            #print(f"LDAP: User '{username}' not found in search base.")
            await logger.aerror("LDAP: User '%s' not found in search base.", username)
            return False

        # Get the full DN of the user found
        user_dn = search_conn.entries[0].entry_dn
        
        # 2. Attempt User Authentication (Direct Bind)
        # This connection uses the user's full DN and the password they provided.
        user_conn = Connection(
            server, 
            user=user_dn, 
            password=password, 
            auto_bind=True # Attempts simple authentication by default
        )
        
        # We only need to check if the connection was successfully bound
        if user_conn.bound:
            #print(f"LDAP: Authentication successful for {username}. DN: {user_dn}")
            await logger.ainfo("LDAP: Authentication successful for %s. DN: %s", username, user_dn)
            return True
        else:
            #print(f"LDAP: Authentication failed for {username}. Result: {user_conn.result}")
            await logger.ainfo("LDAP: Authentication failed for %s. Result: %s", username, user_conn.result)
            return False

    except core.exceptions.LDAPInvalidCredentialsResult:
        # Handles explicit invalid credentials error during bind attempt
        #print(f"LDAP: Invalid credentials used for either service account or user.")
        await logger.aerror("LDAP: Invalid credentials used for either service account or user.")
        return False
    except core.exceptions.LDAPSocketOpenError as e:
        # Handles connection issues (e.g., server down or incorrect URL)
        #print(f"LDAP: Connection error (503): {e}")
        await logger.aerror("LDAP: Connection error (503): %s", e)
        # Reraise as HTTP 503 so the client knows the auth service is offline
        raise HTTPException(status_code=503, detail="Authentication service unavailable.")
    except Exception as e:
        # Catch all unexpected errors gracefully
        #print(f"LDAP: Unexpected error: {e}")
        await logger.aerror(f"LDAP: Unexpected error: %s", e)
        return False

# Initialize FastAPI app
app     :FastAPI   = FastAPI()

# Define the HTTPBasic authentication scheme
security:HTTPBasic = HTTPBasic()


# Dependency to check LDAP authentication
async def check_ldap_auth(credentials: HTTPBasicCredentials = Depends(security)):
    """FastAPI dependency that attempts LDAP login."""
    if not await LDAP_AUTH(credentials.username, credentials.password):
        # Triggers the browser's basic auth popup
        raise HTTPException(
            status_code=401, 
            detail="Invalid credentials",
            headers={"WWW-Authenticate": "Basic"},
        )
    return credentials.username

Please consider correctly implementing ldap authentication or kindly remove it from your documentation. Thanks

lmaddox avatar Nov 23 '25 21:11 lmaddox

It's best not to use this software anymore.

nishidashabia avatar Dec 06 '25 14:12 nishidashabia

It is no longer maintained

luhong123 avatar Dec 08 '25 08:12 luhong123