Google은 흑인 공동체를 위한 인종 간 평등을 진전시키기 위해 노력하고 있습니다. Google에서 어떤 노력을 하고 있는지 확인하세요.

동적 시스템 업데이트

동적 시스템 업데이트(DSU)를 사용하면 사용자가 현재 시스템 이미지의 손상 위험 없이 인터넷에서 다운로드하여 사용해볼 수 있는 Android 시스템 이미지를 생성할 수 있습니다. 이 문서에서는 DSU를 지원하는 방법에 관해 설명합니다.

커널 요구사항

커널 요구사항은 동적 파티션 구현을 참고하세요.

또한 DSU는 Android 시스템 이미지 확인을 위해 device-mapper-verity(dm-verity) 커널 기능에 의존합니다. 따라서 다음과 같은 커널 구성을 사용 설정해야 합니다.

  • CONFIG_DM_VERITY=y
  • CONFIG_DM_VERITY_FEC=y

파티션 요구사항

Android 11부터 DSU에서 F2FS 또는 ext4 파일 시스템을 사용하려면 /data 파티션이 필요합니다. F2FS가 더 나은 성능을 제공하여 권장되지만 차이는 크지 않습니다.

다음은 Pixel 기기의 동적 시스템 업데이트에 걸리는 시간의 예입니다.

  • F2FS 사용:
    • 109초, 8G 사용자, 867M 시스템, 파일 시스템 유형: F2FS: encryption=aes-256-xts:aes-256-cts
    • 104초, 8G 사용자, 867M 시스템, 파일 시스템 유형: F2FS: encryption=ice
  • ext4: 사용:
    • 135초, 8G 사용자, 867M 시스템, 파일 시스템 유형: ext4: encryption=aes-256-xts:aes-256-cts

사용 중인 플랫폼에서 시간이 훨씬 더 오래 걸리는 경우 마운트 플래그에 '동기화' 쓰기를 실행하는 플래그가 포함되어 있는지 확인하거나 성능 향상을 위해 명시적으로 '비동기' 플래그를 지정할 수 있습니다.

설치된 이미지와 관련된 데이터를 저장하기 위해서는 metadata 파티션(16MB 이상)이 필요합니다. 이는 1단계 마운트 도중에 마운트되어야 합니다.

userdata 파티션은 F2FS 또는 ext4 파일 시스템을 사용해야 합니다. F2FS를 사용할 때는 Android 일반 커널에서 제공하는 모든 F2FS 관련 패치를 포함하세요.

DSU는 커널/일반 4.9로 개발 및 테스트됩니다. 이 기능에는 커널 4.9 이상을 사용하는 것이 좋습니다.

공급업체 HAL 동작

위버 HAL

위버 HAL은 사용자 키 보관을 위한 고정된 수의 슬롯을 제공합니다. DSU는 2개의 추가 키 슬롯을 사용합니다. 위버 HAL을 보유한 OEM은 일반 시스템 이미지(GSI)와 호스트 이미지를 위한 충분한 공간을 보유해야 합니다.

게이트키퍼 HAL

게이트키퍼 HAL은 큰 USER_ID 값을 지원해야 합니다. GSI가 HAL의 UID를 +1000000만큼 상쇄하기 때문입니다.

부팅 확인

자체 검사 부팅을 사용 중지하지 않고 잠금 상태에서 개발자 GSI 이미지 부팅을 지원하려면 device/<device_name>/device.mk 파일에 다음 행을 추가하여 개발자 GSI 키를 포함하세요.

$(call inherit-product, $(SRC_TARGET_DIR)/product/developer_gsi_keys.mk)

롤백 보호

DSU를 사용할 때는 다운로드한 Android 시스템 이미지가 기기의 현재 시스템 이미지보다 최신이어야 합니다. 이를 위해서는 두 시스템 이미지의 Android 자체 검사 부팅(AVB) AVB 속성 설명자에서 보안 패치 수준을 비교해야 합니다(Prop: com.android.build.system.security_patch -> '2019-04-05').

AVB를 사용하지 않는 기기의 경우 현재 시스템 이미지의 보안 패치 수준을 부트로더 커널 cmdline인 androidboot.system.security_patch=2019-04-05에 삽입합니다.

하드웨어 요구사항

DSU 인스턴스를 실행하면 2개의 임시 파일이 할당됩니다.

  • GSI.img(1~1.5G) 저장을 위한 논리 파티션
  • GSI 실행을 위한 샌드박스로서의 8GB의 빈 /data 파티션

DSU 인스턴스를 실행하기 전에 최소 10GB의 여유 공간을 남겨 두는 것이 좋습니다. DSU는 SD 카드에서의 할당도 지원합니다. SD 카드가 있으면 할당과 관련된 가장 높은 우선순위를 지닙니다. SD 카드 지원은 내부 저장소가 부족할 수 있는 저전력 기기에 매우 중요합니다. SD 카드가 있는 경우 채택되지 않았는지 확인해야 합니다. DSU는 채택된 SD 카드를 지원하지 않습니다.

사용 가능한 프런트엔드

adb, OEM 앱 또는 원클릭 DSU 로더(Android 11 이상)를 사용하여 DSU를 실행할 수 있습니다.

adb를 사용하여 DSU를 실행

adb를 사용하여 DSU를 실행하려면 다음 명령어를 입력합니다.

$ simg2img out/target/product/.../system.img system.raw
$ gzip -c system.raw > system.raw.gz
$ adb push system.raw.gz /storage/emulated/0/Download
$ adb shell am start-activity \
-n com.android.dynsystem/com.android.dynsystem.VerificationActivity  \
-a android.os.image.action.START_INSTALL    \
-d file:///storage/emulated/0/Download/system.raw.gz  \
--el KEY_SYSTEM_SIZE $(du -b system.raw|cut -f1)  \
--el KEY_USERDATA_SIZE 8589934592

앱을 사용하여 DSU를 실행

DSU의 기본 진입점은 android.os.image.DynamicSystemClient.java API입니다.

public class DynamicSystemClient {

...
...

     /**
     * Start installing DynamicSystem from URL with default userdata size.
     *
     * @param systemUrl A network URL or a file URL to system image.
     * @param systemSize size of system image.
     */
    public void start(String systemUrl, long systemSize) {
        start(systemUrl, systemSize, DEFAULT_USERDATA_SIZE);
    }

이 앱은 기기에 번들화/사전 설치해야 합니다. DynamicSystemClient는 시스템 API이므로 일반 SDK API로 앱을 빌드할 수 없으며 Google Play에 게시할 수도 없습니다. 이 앱의 목적은 다음과 같습니다.

  1. 공급업체에서 정의한 구성표로 이미지 목록과 상응하는 URL을 가져옵니다.
  2. 목록의 이미지를 기기에 매칭하고 사용자가 선택할 수 있는 호환되는 이미지를 표시합니다.
  3. 아래와 같이 DynamicSystemClient.start를 호출합니다.

    DynamicSystemClient aot = new DynamicSystemClient(...)
       aot.start(
            ...URL of the selected image...,
            ...uncompressed size of the selected image...);
    
    

URL은 다음 명령어로 생성 가능한 gzipped, 비희소성, 시스템 이미지 파일을 가리킵니다.

$ simg2img ${OUT}/system.img ${OUT}/system.raw
$ gzip ${OUT}/system.raw
$ ls ${OUT}/system.raw.gz

파일 이름은 다음 형식을 따라야 합니다.

<android version>.<lunch name>.<user defined title>.raw.gz

예를 들면 다음과 같습니다.

  • o.aosp_taimen-userdebug.2018dev.raw.gz
  • p.aosp_taimen-userdebug.2018dev.raw.gz

원클릭 DSU 로더

Android 11에는 원클릭 DSU 로더가 도입되었으며 이 DSU 로더는 개발자 설정의 프런트엔드입니다.

DSU 로더 실행

그림 1. DSU 로더 실행

개발자가 DSU 로더 버튼을 클릭하면 DSU 로더가 웹에서 사전 구성된 DSU JSON 설명자를 가져오고 모든 관련 이미지를 플로팅 메뉴에 표시합니다. DSU 설치를 시작하려면 이미지를 선택하세요. 알림바에 진행률이 표시됩니다.

DSU 이미지 설치 진행률

그림 2. DSU 이미지 설치 진행률

기본적으로 DSU 로더는 GSI 이미지가 포함된 JSON 설명자를 로드합니다. 다음 섹션에서는 OEM 서명 DSU 패키지를 만들어 DSU 로더에서 로드하는 방법을 보여줍니다.

기능 플래그

DSU 기능은 settings_dynamic_android 기능 플래그 아래에 있습니다. DSU를 사용하기 전에 상응하는 기능 플래그가 사용 설정되어 있는지 확인하세요.

기능 플래그 사용 설정

그림 3. 기능 플래그 사용 설정

기능 플래그 UI는 사용자 빌드를 실행 중인 기기에 없을 수도 있습니다. 이러한 경우에는 adb 명령어를 대신 사용하세요.

$ adb shell setprop persist.sys.fflag.override.settings_dynamic_system 1

GCE의 공급업체 호스트 시스템 이미지(선택사항)

시스템 이미지의 잠재적인 보관 위치 중 하나는 Google Compute Engine(GCE) 버킷입니다. 출시 관리자는 GCP 저장소 콘솔을 사용하여 출시된 시스템 이미지를 추가/삭제/변경합니다.

이미지는 아래에 보이는 것처럼 공개 액세스여야 합니다.

GCE의 공개 액세스

그림 4. GCE의 공개 액세스

항목을 공개로 전환하는 절차는 Google Cloud 문서에서 확인할 수 있습니다.

ZIP 파일의 다중 파티션 DSU

Android 11부터는 DSU에 파티션을 2개 이상 포함할 수 있습니다. 예를 들어 system.img 외에 product.img를 포함할 수 있습니다. 기기가 부팅되면 설치된 DSU가 사용 설정된 경우 1단계 init에서 설치된 DSU 파티션을 감지하고 기기의 파티션을 일시적으로 교체합니다. DSU 패키지에 기기에 상응하는 파티션이 없는 파티션이 포함될 수도 있습니다.

여러 파티션을 사용하는 DSU 프로세스

그림 5. 여러 파티션을 사용하는 DSU 프로세스

OEM 서명 DSU

기기에서 실행되는 모든 이미지에 기기 제조업체의 승인을 받으려면 DSU 패키지 내의 모든 이미지에 서명해야 합니다. 예를 들어 아래와 같은 파티션 이미지 두 개가 포함된 DSU 패키지가 있다고 가정합니다.

dsu.zip {
    - system.img
    - product.img
}

system.imgproduct.img 모두 ZIP 파일에 추가하기 전에 OEM 키로 서명해야 합니다. 일반적인 방법은 비대칭 알고리즘(예: RSA)을 사용하는 것입니다. RSA에서는 보안 비밀 키가 패키지에 서명하는 데 사용되고 공개 키가 패키지를 확인하는 데 사용됩니다. 1단계 램디스크에는 페어링 공개 키(예: /avb/*.avbpubkey)가 포함되어야 합니다. 기기에서 이미 AVB를 채택한 경우 기존 서명 절차로 충분합니다. 다음 섹션에서는 서명 프로세스를 설명하고 DSU 패키지에서 이미지를 확인하는 데 사용되는 AVB pubkey의 배치를 강조표시합니다.

DSU JSON 설명자

DSU JSON 설명자는 DSU 패키지를 설명하며 두 가지 프리미티브를 지원합니다. 첫째, include 프리미티브는 추가 JSON 설명자를 포함하거나 DSU 로더를 새 위치로 리디렉션합니다. 예:

{
    "include": ["https://.../gsi-release/gsi-src.json"]
}

둘째, image 프리미티브는 출시된 DSU 패키지를 설명하는 데 사용됩니다. 이미지 프리미티브 내에는 여러 가지 속성이 있습니다.

  • namedetails 속성은 대화상자에 표시되는 문자열로 사용자가 선택할 수 있습니다.

  • cpu_api, vndkos_version 속성은 다음 섹션에 설명된 호환성 검사에 사용됩니다.

  • pubkey 속성(선택사항)은 DSU 패키지에 서명하는 데 사용되는 보안 비밀 키와 페어링되는 공개 키를 설명합니다. 이 속성이 지정되면 DSU 서비스에서 기기에 DSU 패키지를 확인하는 데 사용되는 키가 있는지 확인할 수 있습니다. 이렇게 하면 인식할 수 없는 DSU 패키지가 설치되지 않습니다. 예를 들어 OEM-A에서 서명한 DSU가 OEM-B가 만든 기기에 설치되지 않습니다.

  • tos 속성(선택사항)은 상응하는 DSU 패키지의 서비스 약관을 설명하는 텍스트 파일을 가리킵니다. 개발자가 서비스 속성이 지정된 DSU 패키지를 선택하면 그림 6에 표시된 대화상자가 열리고 개발자에게 DSU 패키지를 설치하기 전에 서비스 약관에 동의할 것을 요청합니다.

    서비스 약관 대화상자

    그림 6. 서비스 약관 대화상자

참고사항: 다음은 GSI의 DSU JSON 설명자입니다.

{
   "images":[
      {
         "name":"GSI+GMS x86",
         "os_version":"10",
         "cpu_abi": "x86",
         "details":"exp-QP1A.190711.020.C4-5928301",
         "vndk":[
            27,
            28,
            29
         ],
         "pubkey":"",
         "tos": "https://dl.google.com/developers/android/gsi/gsi-tos.txt",
         "uri":"https://.../gsi/gsi_gms_x86-exp-QP1A.190711.020.C4-5928301.zip"
      },
      {
         "name":"GSI+GMS ARM64",
         "os_version":"10",
         "cpu_abi": "arm64-v8a",
         "details":"exp-QP1A.190711.020.C4-5928301",
         "vndk":[
            27,
            28,
            29
         ],
         "pubkey":"",
         "tos": "https://dl.google.com/developers/android/gsi/gsi-tos.txt",
         "uri":"https://.../gsi/gsi_gms_arm64-exp-QP1A.190711.020.C4-5928301.zip"
      },
      {
         "name":"GSI ARM64",
         "os_version":"10",
         "cpu_abi": "arm64-v8a",
         "details":"exp-QP1A.190711.020.C4-5928301",
         "vndk":[
            27,
            28,
            29
         ],
         "pubkey":"",
         "uri":"https://.../gsi/aosp_arm64-exp-QP1A.190711.020.C4-5928301.zip"
      },
      {
         "name":"GSI x86_64",
         "os_version":"10",
         "cpu_abi": "x86_64",
         "details":"exp-QP1A.190711.020.C4-5928301",
         "vndk":[
            27,
            28,
            29
         ],
         "pubkey":"",
         "uri":"https://.../gsi/aosp_x86_64-exp-QP1A.190711.020.C4-5928301.zip"
      }
   ]
}

호환성 관리

여러 가지 속성이 DSU 패키지와 로컬 기기 간의 호환성을 지정하는 데 사용됩니다.

  • cpu_api는 기기 아키텍처를 설명하는 문자열입니다. 이 속성은 필수이며 ro.product.cpu.abi 시스템 속성과 비교됩니다. 두 속성의 값은 정확히 일치해야 합니다.

  • os_version(선택사항)은 Android 버전을 지정하는 정수입니다. 예를 들어 Android 10의 경우 os_version10이고 Android 11의 경우 os_version11입니다. 지정된 경우 이 속성은 ro.system.build.version.release 시스템 속성과 같거나 그보다 커야 합니다. 이 검사는 현재 지원되지 않는 Android 11 공급업체 기기에서 Android 10 GSI 이미지를 부팅하지 못하도록 하는 데 사용됩니다. Android 10 기기에서 Android 11 GSI 이미지를 부팅할 수 있습니다.

  • vndk(선택사항)는 DSU 패키지에 포함된 모든 VNDK를 지정하는 배열입니다. 이 속성이 지정된 경우 DSU 로더가 ro.vndk.version 시스템 속성에서 추출한 숫자가 포함되어 있는지 확인합니다.

보안을 위해 DSU 키 취소

매우 드물지만 DSU 이미지에 서명하는 데 사용되는 RSA 키 쌍이 손상된 경우 손상된 키를 삭제하려면 램디스크를 최대한 빨리 업데이트해야 합니다. 부팅 파티션을 업데이트하는 것 외에 HTTPS URL에서 DSU 키 취소 목록(키 블랙리스트)을 사용하여 손상된 키를 차단할 수 있습니다.

DSU 키 취소 목록에는 취소된 AVB 공개 키 목록이 포함됩니다. DSU 설치 중에 취소 목록을 사용하여 DSU 이미지 내 공개 키의 유효성을 검사합니다. 이미지에 취소된 공개 키가 포함된 것으로 확인되는 경우 DSU 설치 프로세스가 중지됩니다.

키 취소 목록 URL은 보안을 강화하기 위해 HTTPS URL이어야 하며 리소스 문자열로 지정됩니다.

frameworks/base/packages/DynamicSystemInstallationService/res/values/strings.xml@key_revocation_list_url

문자열의 값은 https://dl.google.com/developers/android/gsi/gsi-keyblacklist.json이며 이 값은 Google에서 제공하는 GSI 키의 취소 목록입니다. 이 리소스 문자열은 오버레이하고 맞춤설정할 수 있으므로 DSU 기능을 채택한 OEM은 자체 키 블랙리스트를 제공하고 유지관리할 수 있습니다. 이를 통해 OEM에서 기기의 램디스크 이미지를 업데이트하지 않고 특정 공개 키를 차단할 수 있습니다.

취소 목록의 형식은 다음과 같습니다.

{
   "entries":[
      {
         "public_key":"bf14e439d1acf231095c4109f94f00fc473148e6",
         "status":"REVOKED",
         "reason":"Key revocation test key"
      },
      {
         "public_key":"d199b2f29f3dc224cca778a7544ea89470cbef46",
         "status":"REVOKED",
         "reason":"Key revocation test key"
      }
   ]
}
  • public_keyAVB pubkey 생성 섹션에 설명된 형식의 취소된 키의 SHA-1 다이제스트입니다.
  • status는 키의 취소 상태를 나타냅니다. 현재 유일하게 지원되는 값은 REVOKED입니다.
  • reason(선택사항)은 취소 이유를 설명하는 문자열입니다.

DSU 절차

이 섹션에서는 여러 DSU 구성 절차를 실행하는 방법을 설명합니다.

새 키 쌍 생성

openssl 명령어를 사용하여 RSA 비공개/공개 키 쌍을 .pem 형식으로 생성합니다(예: 2,048비트 크기로).

$ openssl genrsa -out oem_cert_pri.pem 2048
$ openssl rsa -in oem_cert_pri.pem -pubout -out oem_cert_pub.pem

비공개 키는 액세스하지 못할 수도 있으며 하드웨어 보안 모듈(HSM)에만 보관됩니다. 이 경우 키 생성 후 x509 공개키 인증서를 이용할 수도 있습니다. x509 인증서에서 AVB 공개 키를 생성하는 방법은 램디스크에 페어링 pubkey 추가 섹션을 참고하세요.

x509 인증서를 PEM 형식으로 변환하려면 다음 단계를 따르세요.

$ openssl x509 -pubkey -noout -in oem_cert_pub.x509.pem > oem_cert_pub.pem

인증서가 이미 PEM 파일인 경우 이 단계를 건너뛰세요.

램디스크에 페어링 pubkey 추가

서명된 DSU 패키지를 확인하려면 oem_cert.avbpubkey/avb/*.avbpubkey 아래에 넣어야 합니다. 먼저 PEM 형식의 공개 키를 AVB 공개 키 형식으로 변환합니다.

$ avbtool extract_public_key --key oem_cert_pub.pem --output oem_cert.avbpubkey

그런 후 다음 단계에 따라 1단계 램디스크의 공개 키를 포함합니다.

  1. 사전 빌드된 모듈을 추가하여 avbpubkey를 복사합니다. 예를 들어 다음과 같은 콘텐츠로 device/<company>/<board>/oem_cert.avbpubkeydevice/<company>/<board>/avb/Android.mk를 추가합니다.

    include $(CLEAR_VARS)
    
    LOCAL_MODULE := oem_cert.avbpubkey
    LOCAL_MODULE_CLASS := ETC
    LOCAL_SRC_FILES := $(LOCAL_MODULE)
    ifeq ($(BOARD_USES_RECOVERY_AS_BOOT),true)
    LOCAL_MODULE_PATH := $(TARGET_RECOVERY_ROOT_OUT)/first_stage_ramdisk/avb
    else
    LOCAL_MODULE_PATH := $(TARGET_RAMDISK_OUT)/avb
    endif
    
    include $(BUILD_PREBUILT)
    
  2. droidcore 타겟이 추가된 oem_cert.avbpubkey에 종속되는지 확인합니다.

    droidcore: oem_cert.avbpubkey
    

JSON 설명자에 AVB pubkey 속성 생성

oem_cert.avbpubkey는 AVB 공개 키 바이너리 형식입니다. SHA-1을 사용하여 이 속성을 JSON 설명자에 추가하기 전에 읽을 수 있도록 만듭니다.

$ sha1sum oem_cert.avbpubkey | cut -f1 -d ' '
3e62f2be9d9d813ef5........866ac72a51fd20

이 값이 JSON 설명자의 pubkey 속성 콘텐츠가 됩니다.

   "images":[
      {
         ...
         "pubkey":"3e62f2be9d9d813ef5........866ac72a51fd20",
         ...
      },

DSU 패키지 서명

다음 방법 중 하나를 사용하여 DSU 패키지에 서명합니다.

  • 방법 1: 원래 AVB 서명 프로세스에서 만든 아티팩트를 재사용하여 DSU 패키지를 만듭니다. 또 다른 방법은 릴리스 패키지에서 이미 서명된 이미지를 추출한 후 추출된 이미지를 사용하여 ZIP 파일을 직접 만드는 것입니다.

  • 방법 2: 비공개 키를 사용할 수 있는 경우 다음 명령어를 사용하여 DSU 파티션에 서명합니다. DSU 패키지(ZIP 파일) 내의 각 img는 별도로 서명됩니다.

    $ key_len=$(openssl rsa -in oem_cert_pri.pem -text | grep Private-Key | sed -e 's/.*(\(.*\) bit.*/\1/')
    $ for partition in system product; do
        avbtool add_hashtree_footer \
            --image ${OUT}/${partition}.img \
            --partition_name ${partition} \
            --algorithm SHA256_RSA${key_len} \
            --key oem_cert_pri.pem
    done
    

avbtool을 사용하여 add_hashtree_footer를 추가하는 방법에 관한 자세한 내용은 avbtool 사용을 참고하세요.

로컬에서 DSU 패키지 확인

모든 로컬 이미지를 다음 명령어를 사용하여 페어링 공개 키와 비교하고 확인하는 것이 좋습니다.


for partition in system product; do
    avbtool verify_image --image ${OUT}/${partition}.img  --key oem_cert_pub.pem
done

예상되는 출력은 다음과 같습니다.

Verifying image dsu/system.img using key at oem_cert_pub.pem
vbmeta: Successfully verified footer and SHA256_RSA2048 vbmeta struct in dsu/system.img
: Successfully verified sha1 hashtree of dsu/system.img for image of 898494464 bytes

Verifying image dsu/product.img using key at oem_cert_pub.pem
vbmeta: Successfully verified footer and SHA256_RSA2048 vbmeta struct in dsu/product.img
: Successfully verified sha1 hashtree of dsu/product.img for image of 905830400 bytes

DSU 패키지 만들기

다음 예에서는 system.imgproduct.img가 포함된 DSU 패키지를 만듭니다.

dsu.zip {
    - system.img
    - product.img
}

두 이미지가 모두 서명된 후 다음 명령어를 사용하여 ZIP 파일을 만듭니다.

$ mkdir -p dsu
$ cp ${OUT}/system.img dsu
$ cp ${OUT}/product.img dsu
$ cd dsu && zip ../dsu.zip *.img && cd -

원클릭 DSU 맞춤설정

기본적으로 DSU 로더는 https://...google.com/.../gsi-src.json인 GSI 이미지의 메타데이터를 가리킵니다.

OEM은 자체 JSON 설명자를 가리키는 persist.sys.fflag.override.settings_dynamic_system.list 속성을 정의하여 목록을 덮어쓸 수 있습니다. 예를 들어 OEM은 다음과 같이 GSI 및 OEM 전용 이미지가 포함된 JSON 메타데이터를 제공할 수 있습니다.

{
    "include": ["https://dl.google.com/.../gsi-src.JSON"]
    "images":[
      {
         "name":"OEM image",
         "os_version":"10",
         "cpu_abi": "arm64-v8a",
         "details":"...",
         "vndk":[
            27,
            28,
            29
         ],
         "spl":"...",
         "pubkey":"",
         "uri":"https://.../....zip"
      },

}

OEM은 그림 7과 같이 게시된 DSU 메타데이터를 연결할 수 있습니다.

게시된 DSU 메타데이터 연결

그림 7. 게시된 DSU 메타데이터 연결