immich-app / immich

High performance self-hosted photo and video management solution.
https://immich.app
GNU Affero General Public License v3.0
44.58k stars 2.17k forks source link

Video does not play anymore when using TLS client certificate on android #11634

Closed julpoppi closed 1 month ago

julpoppi commented 1 month ago

The bug

I put in place a TLS client certificate using nginx as a reverse proxy thanks to the feature introduced in the v1.110.0. This is a good thing to strengthen the security and be a lot safer while opening the service outside of my network.

However, when I do so, the playing of the video does not work anymore on then Android App. I've got the following error in the log : Screenshot_20240807-162020

The bug is only present if I try to play a video that has not been uploaded from the android device, otherwise the video is correctly played.

I tried to deactivate the TLS client certificate from the nginx reverse proxy (ssl_verify_client off) while keeping the certificate imported in the android app : the video is then correctly played.

The OS that Immich Server is running on

Raspian

Version of Immich Server

v1.111

Version of Immich Mobile App

v1.111

Platform with the issue

Your docker-compose.yml content

#
# WARNING: Make sure to use the docker-compose.yml of the current release:
#
# https://github.com/immich-app/immich/releases/latest/download/docker-compose.yml
#
# The compose file on main may not be compatible with the latest release.
#

name: immich

services:
  immich-server:
    container_name: immich_server
    image: ghcr.io/immich-app/immich-server:${IMMICH_VERSION:-release}
    # extends:
    #   file: hwaccel.transcoding.yml
    #   service: cpu # set to one of [nvenc, quicksync, rkmpp, vaapi, vaapi-wsl] for accelerated transcoding
    volumes:
      - ${UPLOAD_LOCATION}:/usr/src/app/upload
      - /etc/localtime:/etc/localtime:ro
      - /data/photos/archives:/mnt/photos/archives:ro
    env_file:
      - .env
#    ports:
#      - 2283:3001
    depends_on:
      - redis
      - database
    restart: always

  immich-machine-learning:
    container_name: immich_machine_learning
    # For hardware acceleration, add one of -[armnn, cuda, openvino] to the image tag.
    # Example tag: ${IMMICH_VERSION:-release}-cuda
    image: ghcr.io/immich-app/immich-machine-learning:${IMMICH_VERSION:-release}
    # extends: # uncomment this section for hardware acceleration - see https://immich.app/docs/features/ml-hardware-acceleration
    #   file: hwaccel.ml.yml
    #   service: cpu # set to one of [armnn, cuda, openvino, openvino-wsl] for accelerated inference - use the `-wsl` version for WSL2 where applicable
    volumes:
      - model-cache:/cache
    env_file:
      - .env
    restart: always

  redis:
    container_name: immich_redis
    image: docker.io/redis:6.2-alpine@sha256:d6c2911ac51b289db208767581a5d154544f2b2fe4914ea5056443f62dc6e900
    healthcheck:
      test: redis-cli ping || exit 1
    restart: always

  database:
    container_name: immich_postgres
    image: docker.io/tensorchord/pgvecto-rs:pg14-v0.2.0@sha256:90724186f0a3517cf6914295b5ab410db9ce23190a2d9d0b9dd6463e3fa298f0
    environment:
      POSTGRES_PASSWORD: ${DB_PASSWORD}
      POSTGRES_USER: ${DB_USERNAME}
      POSTGRES_DB: ${DB_DATABASE_NAME}
      POSTGRES_INITDB_ARGS: '--data-checksums'
    volumes:
      - ${DB_DATA_LOCATION}:/var/lib/postgresql/data
    healthcheck:
      test: pg_isready --dbname='${DB_DATABASE_NAME}' || exit 1; Chksum="$$(psql --dbname='${DB_DATABASE_NAME}' --username='${DB_USERNAME}' --tuples-only --no-align --command='SELECT COALESCE(SUM(checksum_failures), 0) FROM pg_stat_database')"; echo "checksum failure count is $$Chksum"; [ "$$Chksum" = '0' ] || exit 1
      interval: 5m
      start_interval: 30s
      start_period: 5m
    command: ["postgres", "-c" ,"shared_preload_libraries=vectors.so", "-c", 'search_path="$$user", public, vectors', "-c", "logging_collector=on", "-c", "max_wal_size=2GB", "-c", "shared_buffers=512MB", "-c", "wal_compression=on"]
    restart: always

volumes:
  model-cache:

include:
  - docker-nginx.yml

Your .env content

# You can find documentation for all the supported env variables at https://immich.app/docs/install/environment-variables

# The location where your uploaded files are stored
UPLOAD_LOCATION=/data/photos/upload
# The location where your database files are stored
DB_DATA_LOCATION=/data/immich/postgres

# The Immich version to use. You can pin this to a specific version like "v1.71.0"
IMMICH_VERSION=release

# Connection secret for postgres. You should change it to a random password
DB_PASSWORD=<secret>

# The values below this line do not need to be changed
###################################################################################
DB_USERNAME=postgres
DB_DATABASE_NAME=immich

Reproduction steps

- Put in place a TLS client certificate with a reverse proxy as nginx in front of immich
- Upload a video from another device (from your computer for example).
- Try to play the video from the Android App.
- An error occurs.

Relevant log output

Instance of 'FlutterErrorDetails'
Exception: PlatformException(VideoError, Video player had error y2.r: Source error, null, null)
Library: widgets library
Context: Instance of 'ErrorDescription'

#1      AsyncError.value (package:riverpod/src/common.dart:494:0)
#2      VideoViewerPage.build (package:immich_mobile/pages/common/video_viewer.page.dart:36:0)
#3      _ConsumerState.build (package:flutter_riverpod/src/consumer.dart:476:0)
#4      StatefulElement.build (package:flutter/src/widgets/framework.dart:5599:0)
#5      ConsumerStatefulElement.build (package:flutter_riverpod/src/consumer.dart:539:0)
#6      HookElement.build (package:flutter_hooks/src/framework.dart:438:0)
#7      ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:5487:0)
#8      StatefulElement.performRebuild (package:flutter/src/widgets/framework.dart:5650:0)
#9      Element.rebuild (package:flutter/src/widgets/framework.dart:5203:0)
#10     BuildOwner.buildScope (package:flutter/src/widgets/framework.dart:2905:0)
#11     WidgetsBinding.drawFrame (package:flutter/src/widgets/binding.dart:1136:0)
#12     RendererBinding._handlePersistentFrameCallback (package:flutter/src/rendering/binding.dart:443:0)
#13     SchedulerBinding._invokeFrameCallback (package:flutter/src/scheduler/binding.dart:1392:0)
#14     SchedulerBinding.handleDrawFrame (package:flutter/src/scheduler/binding.dart:1313:0)
#15     SchedulerBinding._handleDrawFrame (package:flutter/src/scheduler/binding.dart:1171:0)
#16     _invoke (dart:ui/hooks.dart:312:0)
#17     PlatformDispatcher._drawFrame (dart:ui/platform_dispatcher.dart:419:0)
#18     _drawFrame (dart:ui/hooks.dart:283:0)

Additional information

Nginx configuration:

server {
  listen 443 ssl;
  server_tokens off;
  server_name localhost website;

  ssl_certificate /etc/nginx/ssl/live/website/fullchain.pem;
  ssl_certificate_key /etc/nginx/ssl/live/website/privkey.pem;

  ssl_client_certificate /etc/nginx/ssl-client/rootCA.crt;
  ssl_verify_client on;

  # allow large file uploads
  client_max_body_size 50000M;

  # Set headers
  proxy_set_header Host              $http_host;
  proxy_set_header X-Real-IP         $remote_addr;
  proxy_set_header X-Forwarded-For   $proxy_add_x_forwarded_for;
  proxy_set_header X-Forwarded-Proto $scheme;

  # enable websockets: http://nginx.org/en/docs/http/websocket.html
  proxy_http_version 1.1;
  proxy_set_header   Upgrade    $http_upgrade;
  proxy_set_header   Connection "upgrade";
  proxy_redirect     off;

  # set timeout
  proxy_read_timeout 600s;
  proxy_send_timeout 600s;
  send_timeout       600s;

  location / {
    proxy_pass http://immich_server:3001;
  }
}
mmomjian commented 1 month ago

Thank you. Duplicate of #5553

julpoppi commented 1 month ago

Ok thanks. It may be related but it is not exactly the same than #5553.

5553 seems related to the self signed TLS certificate of the server, in my case I have a valid let's encrypt certificate.

My bug is related to the certificate for the client app (web or app) that is indeed issued by a self signed Root CA.

julpoppi commented 1 month ago

Ok I've seen the comment on another issue LinkedIn that it is indeed the same root cause.