Open9

systemd-homedソースコードを読む

kc5mkc5m

コンポーネントは3つ

  • homectl(1): フロントエンド実装
  • homed-homed(8): バックエンド実装
  • homework: 非同期処理実装
kc5mkc5m

とりあえずユーザー新規作成ロジックを見る(ローカル&LUKS使う感じで)

kc5mkc5m

homectl(https://github.com/systemd/systemd/blob/v248/src/home/homectl.c)を読む

  1. (L3329)エントリーポイント
  2. (L3337)argをparseしてcreate_home呼び出し
  3. (L1048)create_home()
    1. (L1053)systemd bus(D-Bus)取得
      r = acquire_bus(&bus);
      
    2. (L1057)polkit開始
      (void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password);
      
    3. (L1078)ユーザー名をjsonに書き込み(json.c)
      r = json_variant_set_field_string(&arg_identity_extra, "userName", argv[1]);
      
    4. (L110)password作成(パスワード入れるように促すダイアログもここ)
      r = acquire_new_password(hr->user_name, hr, /* suggest = */ true, &new_password);
      
    5. (L1134)D-Bus経由でhomedCreateHomeメッセージを渡す
      r = bus_message_new_method_call(bus, &m, bus_mgr, "CreateHome");
      
    6. (残りはD-Busのタイムアウト等なので省略)

homectlの仕事ははここまで。

kc5mkc5m

homed(https://github.com/systemd/systemd/blob/v248/src/home/homed.c)を読む

  1. homedはdaemonとして存在しているためエントリーポイントは今回は見ない。homectlから送信されたCreateHomehttps://github.com/systemd/systemd/blob/v248/src/home/homed-manager-bus.c にて紐付けを行う。
        SD_BUS_METHOD_WITH_NAMES("CreateHome",
                                 "s",
                                 SD_BUS_PARAM(user_record),
                                 NULL,,
                                 method_create_home,
                                 SD_BUS_VTABLE_UNPRIVILEGED|SD_BUS_VTABLE_SENSITIVE),
    
  2. 紐付けられたmethod_create_home()
    1. polkitに権限問い合わせ
      r = bus_verify_polkit_async(
                      message,
                      CAP_SYS_ADMIN,
                      "org.freedesktop.home1.create-home",
                      NULL,
                      true,
                      UID_INVALID,
                      &m->polkit_registry,
                      error);
      
      • CAP_SYS_ADMINの指定により管理者権限が必要であることのチェックを行っている(-> Capability)
      • polkitの定義(src/home/org.freedesktop.home1.policy)
        <action id="org.freedesktop.home1.create-home">
                <description gettext-domain="systemd">Create a home area</description>
                <message gettext-domain="systemd">Authentication is required to create a user's home area.</message>
                <defaults>
                        <allow_any>auth_admin_keep</allow_any>
                        <allow_inactive>auth_admin_keep</allow_inactive>
                        <allow_active>auth_admin_keep</allow_active>
                </defaults>
        </action>
        
        このうち
        • id: D-Busコマンド
        • message: ユーザーに通知されるメッセージ(下記参照)
          $ homectl create test1
          🔐 Please enter new password for user test1: *****
          🔐 Please enter new password for user test1 (repeat): *****
          ==== AUTHENTICATING FOR org.freedesktop.home1.create-home ====
          Authentication is required to create a user's home area.  <= これ
          Authenticating as: kc5m
          Password:
          polkit-agent-helper-1: pam_authenticate failed: Permission denied
          ==== AUTHENTICATION FAILED ====
          Operation on home test1 failed: Access denied
          
        • allow_any: 全般的な設定
        • allow_inactive: not tty(pty等)なときに適用される設定
        • allow_active: ttyな時に適用される設定
        • auth_admin_keep: 管理者ユーザーとしての認証が必要 & sudoのように認証は数分間しか効力を持たない
    2. 対象ユーザーの作成
      r = validate_and_allocate_home(m, hr, &h, error);
      
    3. home_create()を実行
      r = home_create(h, hr, error);
      
kc5mkc5m

home_createは別ファイル(src/home/homed-home.c)

  1. home_create()でhomeworkの開始
    r = home_start_work(h, "create", h->record, secret);
    
  2. home_start_work() -> fork & exec
    r = safe_fork_full("(sd-homework)",
                       (int[]) { stdin_fd, stdout_fd }, 2,
                       FORK_RESET_SIGNALS|FORK_CLOSE_ALL_FDS|FORK_DEATHSIG|FORK_LOG|FORK_REOPEN_LOG, &pid);
    // ...
    execl(homework, homework, verb, NULL);
    
kc5mkc5m

Homework(src/home/homework.c)を読む

エントリーポイントは同ファイルのrun()

  1. run()

    // ...
    else if (streq(argv[1], "create"))
            r = home_create(home, &new_home);
    // ...
    
  2. home_create()

    1. 暗号化用にパスワード生成
      r = user_record_compile_effective_passwords(h, &cache, &effective_passwords);
      
    2. 各種userhomeの種類に基づく処理にルーティング
      switch (user_record_storage(h)) {
      
      case USER_LUKS:
              r = home_create_luks(h, &cache, effective_passwords, &new_home);
              break;
      case USER_DIRECTORY:
      case USER_SUBVOLUME:
              r = home_create_directory_or_subvolume(h, &new_home);
              break;
      
      case USER_FSCRYPT:
              r = home_create_fscrypt(h, effective_passwords, &new_home);
              break;
      
      case USER_CIFS:
              r = home_create_cifs(h, &new_home);
              break;
      
    3. 今回はLUKSの処理を置いたいのでhome_create_luks()を追う
kc5mkc5m

LUKS用の処理は/src/home/homework-luks.cにて実装されている

  1. home_create_luks()
    1. 場所を特定(ip=image path)
      assert_se(ip = user_record_image_path(h));
      
    2. filesystemを特定(btrfs, fallback=ext4)
      fstype = user_record_file_system_type(h);
      
    3. filesystemの利用可否を特定
      r = mkfs_exists(fstype);
      
    4. partition/luks/fsのuuidの生成
      r = sd_id128_randomize(&partition_uuid);
      r = sd_id128_randomize(&luks_uuid);
      r = sd_id128_randomize(&fs_uuid);
      
    5. dm(device mapper)の名前の生成(例: /dev/mapper/home-test1)
      r = make_dm_names(h->user_name, &dm_name, &dm_node);
      
    6. dmに対してテストアクセス
      r = access(dm_node, F_OK);
      
    7. ipが
      • 実block device
        1. block deviceをopen(2)
          image_fd = open(ip, O_RDWR | O_CLOEXEC | O_NOCTTY | O_NONBLOCK);
          
        2. access(2)を使用してsysfsに対象mappingが存在しないことを確認
          if (asprintf(&sysfs, "/sys/dev/block/%u:%u/partition", major(st.st_rdev), minor(st.st_rdev)) <
              0)
                  return log_oom();
          if (access(sysfs, F_OK) < 0) {
                  if (errno != ENOENT)
                          return log_error_errno(errno, "Failed to check whether %s exists: %m", sysfs);
          
        3. ioctl(2)を使用してdiscard属性をonにする(TRIM/シンプロビジョニング対応)
          if (ioctl(image_fd, BLKDISCARD, (uint64_t[]){ 0, block_device_size }) < 0)
          
      • ファイルパス
        1. 親pathを取得
          parent = dirname_malloc(ip);
          
        2. 親pathを作成
          r = mkdir_p(parent, 0755);
          
        3. ファイルパスが存在するfilesystemのサイズを計算
          r = calculate_disk_size(h, parent, &host_size);
          
        4. 親path下に一時ファイル名を生成
          r = tempfn_random(ip, "homework", &temporary_image_path);
          
        5. 一時ファイルをopen(2)
          image_fd = open(
                  temporary_image_path,
                  O_RDWR | O_CREAT | O_EXCL | O_CLOEXEC | O_NOCTTY | O_NOFOLLOW,
                  0600);
          
        6. chattr(2)でcow(Copy-On-Write)をoffにする(btrfs用)
          r = chattr_fd(image_fd, FS_NOCOW_FL, FS_NOCOW_FL, NULL);
          
        7. fileをtruncate(fallocate(2) & ftruncate(2))
          r = home_truncate(h, image_fd, temporary_image_path, host_size);
          
        8. discardをoffにする
  2. fdに対してpartition作成
    r = make_partition_table(
            image_fd,
            user_record_user_name_and_realm(h),
            partition_uuid,
            &partition_offset,
            &partition_size,
            &disk_uuid);
    
  3. fdに対してloop device作成(=> src/shared/loop-util.c)
    r = loop_device_make(image_fd, O_RDWR, partition_offset, partition_size, 0, &loop);
    
    • もしfdが
      • loopback device
        1. (基本的に)その他へ
        /* Oh! This is a loopback device? That's interesting! */
        
      • block device
        1. block deviceをloopback deviceと見做し、受け取ったfdをポインタへ
      • その他(file path)
        1. losetup -f
          control = open("/dev/loop-control", O_RDWR|O_CLOEXEC|O_NOCTTY|O_NONBLOCK);
          nr = ioctl(control, LOOP_CTL_GET_FREE);
          
        2. loop deviceのfdを取得してポインタへ
          loop = open(loopdev, O_CLOEXEC|O_NONBLOCK|O_NOCTTY|open_flags);
          loop_with_fd = TAKE_FD(loop)
          
  4. loopをflock(2)
    r = loop_device_flock(loop, LOCK_EX);
    
  5. loopをLUKSフォーマット
    r = luks_format(
            loop->node,
            dm_name,
            luks_uuid,
            user_record_user_name_and_realm(h),
            cache,
            effective_passwords,
            user_record_luks_discard(h) || user_record_luks_offline_discard(h),
            h,
            &cd);
    
  6. fsを作成
    r = make_filesystem(
                    dm_node, fstype, user_record_user_name_and_realm(h), fs_uuid, user_record_luks_discard(h));
    
  7. 作成したimage fileをunshare(2)(=> namespace)でmount(2)
    r = home_unshare_and_mount(dm_node, fstype, user_record_luks_discard(h), user_record_mount_flags(h));
    // src/home/homework-mount.c へ
    
  8. (可能なら)btrfsのsub volume機能を使う
    r = btrfs_subvol_make_fallback(subdir, 0700);
    
  9. mountしたファイルをopen(2)
    root_fd = open(subdir, O_RDONLY | O_CLOEXEC | O_DIRECTORY | O_NOFOLLOW);
    
  10. home directoryの初期化
    r = home_populate(h, root_fd);
    
    // in home_populate()
    // スケルトンのコピー
    r = copy_skel(dir_fd, user_record_skeleton_directory(h));
    // .identityの作成
    r = home_store_embedded_identity(h, dir_fd, h->uid, NULL);
    // chown
    r = chown_recursive_directory(dir_fd, h->uid);
    // chmod
    r = change_access_mode(dir_fd, user_record_access_mode(h));
    
  11. sync
    r = home_sync_and_statfs(root_fd, &sfs);
    
  12. (必要なら)trim
    r = run_fitrim(root_fd);
    
  13. mountしたファイルをclose(2) & umount(2)
    root_fd = safe_close(root_fd);
    r = umount_verbose(LOG_ERR, "/run/systemd/user-home-mount", UMOUNT_NOFOLLOW);
    
  14. deviceをdeactivate(暗号化)
    r = crypt_deactivate(cd, dm_name);
    
  15. 再読込
        if (disk_uuid_path)
            // block deviceの場合(`BLKRRPART` = re-read partition table)
            (void) ioctl(image_fd, BLKRRPART, 0);
        else {
            // file systemの場合
            r = fsync_directory_of_file(image_fd);
        }
    
  16. image fileをclose(2)
    image_fd = safe_close(image_fd);
    
  17. 一時ファイル名から正規のファイル名にrename(2)
    if (rename(temporary_image_path, ip) < 0) {
    
  18. おわり
    log_info("Everything completed.");
    print_size_summary(host_size, encrypted_size, &sfs);
    
kc5mkc5m

ログインしてからの処理のメモ

  1. src/home/pam_systemd_home.c
    1. pam_sm_open_session()
    2. acquire_home()
  2. src/shared/bus-locator.c
    1. bus_message_new_method_call()
  3. src/home/homed-manager-bus.c
    1. sd_bus_vtable()
    2. bus_message_new_method_call()
    3. method_acquire_home()
    4. (generic_home_method())
  4. src/home/homed-home-bus.c
    1. bus_home_method_acquire()
  5. src/home/home-d-home.c
    1. home_schedule_operation()
    2. home_schedule_operation()
  6. src/sd-event.c
    1. (sd_event_source_set_enabled())
  7. src/home/homed-home.c
    1. on_pending()
    2. home_dispatch_acquire()
    3. home_activate_internal()
    4. home_start_work()
    5. execl()
  8. src/home/homework
    1. run()
    2. home_activate()
  9. src/home/homework-luks.c
    1. home_activate_luks() ここで復号化処理を行う(ツリーへ)
      1. home_prepare_luks()
      2. luks_setup() ここで cryptsetupのAPI(crypt_activate_by_volume_key())を呼び出して復号化
  10. src/home/homework-mount.c
    1. home_move_mount()
  11. src/shared/mount-util.h
    1. mount_nofollow_verbose()
  12. src/shared/mount-util.c
    1. mount_verbose_full()
    2. mount()