InfoGrab DocsInfoGrab Docs

X.509 인증서로 커밋 및 태그 서명

요약

X.509는 공공 또는 사설 공개 키 인프라(PKI)에서 발급하는 공개 키 인증서의 표준 형식입니다. GitLab은 자체 인증서 저장소를 사용하므로 신뢰 체인을 직접 정의합니다. 신뢰 체인의 루트 CA 또는 중간 인증서가 만료되어 갱신되면, 커밋을 재확인할 때까지 커밋이 일시적으로 "unverified"로 표시될 수 있습니다.

X.509는 공공 또는 사설 공개 키 인프라(PKI)에서 발급하는 공개 키 인증서의 표준 형식입니다. 개인 X.509 인증서는 S/MIME(Secure/Multipurpose Internet Mail Extensions)과 같은 인증 또는 서명 목적으로 사용됩니다. 그러나 Git은 X.509 인증서로 커밋과 태그에 서명하는 것도 GPG (GnuPG, or GNU Privacy Guard)와 유사한 방식으로 지원합니다. 주요 차이는 GitLab이 개발자의 서명을 신뢰할 수 있는지 판단하는 방식입니다:

  • X.509의 경우 루트 인증기관이 GitLab 신뢰 저장소에 추가됩니다. (신뢰 저장소는 신뢰할 수 있는 보안 인증서를 모아 둔 저장소입니다.) 서명에 포함된 필요한 중간 인증서와 결합하면 개발자의 인증서를 신뢰할 수 있는 루트 인증서까지 체인으로 연결할 수 있습니다.
  • GPG의 경우 개발자가 자신의 계정에 GPG 키를 추가합니다.

GitLab은 자체 인증서 저장소를 사용하므로 신뢰 체인을 직접 정의합니다. GitLab이 커밋 또는 태그를 확인하려면 다음 조건을 충족해야 합니다:

  • 서명 인증서의 이메일이 GitLab에서 확인된 이메일 주소와 일치해야 합니다.
  • GitLab 인스턴스에는 서명의 인증서에서 GitLab 인증서 저장소의 신뢰할 수 있는 인증서까지 이어지는 전체 신뢰 체인이 필요합니다. 이 체인에는 서명에 제공된 중간 인증서가 포함될 수 있습니다. 인증기관 루트 인증서와 같은 인증서를 GitLab 인증서 저장소에 추가해야 할 수 있습니다.
  • 서명 시간은 보통 최대 3년인 인증서 유효 기간 안에 있어야 합니다.
  • 서명 시간은 커밋 시간과 같거나 그 이후입니다.
Note

신뢰 체인의 루트 CA 또는 중간 인증서가 만료되어 갱신되면, 커밋을 재확인할 때까지 커밋이 일시적으로 "unverified"로 표시될 수 있습니다.

커밋 상태가 이미 결정되어 데이터베이스에 저장된 경우에는 Rake 태스크로 상태를 다시 점검합니다. GitLab은 백그라운드 워커로 매일 인증서 해지 목록을 점검합니다.

알려진 문제#

  • authorityKeyIdentifier, subjectKeyIdentifier, crlDistributionPoints가 없는 인증서는 Unverified로 표시됩니다. RFC 5280에 부합하는 PKI에서 발급한 인증서를 사용하는 것을 권장합니다.

  • Verified 배지는 GitLab.com 제품에서는 표시되지 않습니다. 사용자 지정 인증기관(CA) 업로드가 GitLab Self-Managed에서만 가능하기 때문입니다.

  • 필수 키 사용(KU)인 Digital Signature 외에 인증서의 확장 키 사용(EKU) 섹션에 값을 설정하면 커밋이 Unverified로 표시될 가능성이 높습니다. 이를 해결하려면 EKU 목록에 emailProtection을 추가합니다. RFC 5280이 이 제한 사항을 명시합니다.

    진단하려면 OpenSSL을 사용한 S/MIME 확인을 따릅니다.

  • GitLab 16.2 이하에서 서명 인증서의 Subject Alternative Name 목록에 이메일이 두 개 이상 있으면, 첫 번째 이메일만 커밋 확인에 사용됩니다.

서명된 커밋 구성#

커밋, 태그 또는 둘 다에 서명하려면 다음을 수행해야 합니다:

  1. X.509 키 쌍 획득.
  2. X.509 인증서를 Git과 연결.
  3. 커밋 서명 및 확인.
  4. 태그 서명 및 확인.

X.509 키 쌍 획득#

조직에 공개 키 인프라(PKI)가 있으면 해당 PKI가 S/MIME 키를 제공합니다. PKI에서 받은 S/MIME 키 쌍이 없으면 자체 서명 키 쌍을 직접 만들거나 구매합니다.

X.509 인증서를 Git과 연결#

X.509 서명을 활용하려면 Git 2.19.0 이상이 필요합니다. git --version 명령으로 Git 버전을 확인할 수 있습니다.

올바른 버전이면 Git 구성을 진행할 수 있습니다.

키를 사용하여 서명하도록 Git을 구성합니다:

signingkey=$( gpgsm --list-secret-keys | egrep '(key usage|ID)' | grep -B 1 digitalSignature | awk '/ID/ {print $2}' )
git config --global user.signingkey $signingkey
git config --global gpg.format x509

Windows 또는 macOS를 구성하려면:

  1. 다음 중 하나로 S/MIME Sign을 설치합니다:

    • 설치 프로그램 다운로드.
    • macOS에서 brew install smimesign 실행.
  2. smimesign --list-keys를 실행하여 인증서 ID를 확인합니다.

  3. git config --global user.signingkey 를 실행하여 서명 키를 설정합니다. 는 인증서 ID로 바꿉니다.

  4. 다음 명령으로 X.509를 구성합니다:

    git config --global gpg.x509.program smimesign
    git config --global gpg.format x509
    

커밋 서명 및 확인#

X.509 인증서를 Git과 연결한 후에는 커밋에 서명할 수 있습니다:

  1. Git 커밋을 만들 때 -S 플래그를 추가합니다:

    git commit -S -m "feat: x509 signed commits"
    
  2. GitLab에 푸시하고 --show-signature 플래그로 커밋이 확인되었는지 점검합니다:

    git log --show-signature
    
  3. 커밋할 때마다 -S 플래그를 입력하지 않으려면, 다음 명령을 실행하여 Git이 매번 커밋에 서명하도록 합니다:

    git config --global commit.gpgsign true
    

태그 서명 및 확인#

X.509 인증서를 Git과 연결한 후에는 태그 서명을 시작할 수 있습니다:

  1. Git 태그를 만들 때 -s 플래그를 추가합니다:

    git tag -s v1.1.1 -m "My signed tag"
    
  2. GitLab에 푸시하고 다음 명령으로 태그가 서명되었는지 확인합니다:

    git tag --verify v1.1.1
    
  3. 태그를 만들 때마다 -s 플래그를 입력하지 않으려면, 다음 명령을 실행하여 Git이 매번 태그에 서명하도록 합니다:

    git config --global tag.gpgsign true
    

관련 주제#

문제 해결#

관리자 액세스 권한이 없는 커미터는 가능한 해결 방법을 확인하기 위해 서명된 커밋의 확인 문제 수정 목록을 검토합니다. 이 페이지의 다른 문제 해결 방안에는 관리자 액세스 권한이 필요합니다.

커밋 재확인#

GitLab은 점검한 커밋의 상태를 데이터베이스에 저장합니다. 다음 작업을 한 후에 커밋을 재확인할 수 있습니다:

  • 루트 CA 또는 중간 인증서 갱신.
  • 인증서 저장소 변경.

커밋을 재확인하려면:

  1. 루트 CA와 모든 중간 인증서가 GitLab 인증서 저장소에 있는지 확인합니다.
  2. update_signatures Rake 태스크를 실행하여 이전에 확인한 커밋의 상태를 점검하고 업데이트합니다.

주요 확인 검사#

코드는 이러한 주요 검사를 수행하며, 모두 verified를 반환해야 합니다:

  • x509_certificate.nil?는 false여야 합니다.
  • x509_certificate.revoked?는 false여야 합니다.
  • verified_signature는 true여야 합니다.
  • user.nil?는 false여야 합니다.
  • user.verified_emails.include?(@email)는 true여야 합니다.
  • certificate_email == @email은 true여야 합니다.

커밋이 Unverified로 표시되는 이유를 조사하려면:

  1. Rails 콘솔을 시작합니다:

    sudo gitlab-rails console
    
  2. 조사 대상 프로젝트(경로 또는 ID)와 전체 커밋 SHA를 식별합니다. 이 정보로 다른 검사를 실행할 signature를 만듭니다:

    project = Project.find_by_full_path('group/subgroup/project')
    project = Project.find_by_id('121')
    commit = project.repository.commit_by(oid: '87fdbd0f9382781442053b0b76da729344e37653')
    signedcommit=Gitlab::X509::Commit.new(commit)
    signature=Gitlab::X509::Signature.new(signedcommit.signature_text, signedcommit.signed_text, commit.committer_email, commit.created_at, commit.project)
    

    검사를 진행하면서 발견한 문제를 해결하기 위해 변경한 경우에는 Rails 콘솔을 다시 시작하고 처음부터 검사를 다시 실행합니다.

  3. 커밋의 인증서를 점검합니다:

    signature.x509_certificate.nil?
    signature.x509_certificate.revoked?
    

    두 검사 모두 false를 반환해야 합니다:

    > signature.x509_certificate.nil?
    => false
    > signature.x509_certificate.revoked?
    => false
    
  4. 서명에 암호화 검사를 실행합니다. 코드는 true를 반환해야 합니다:

    signature.verified_signature
    

    false를 반환하면 이 검사를 더 자세히 조사합니다.

  5. 커밋과 서명의 이메일 주소가 일치하는지 확인합니다:

    • Rails 콘솔이 비교 대상 이메일 주소를 표시합니다.
    • 마지막 명령은 true를 반환해야 합니다:
    sigemail=signature.__send__:certificate_email
    commitemail=commit.committer_email
    sigemail == commitemail
    

    GitLab 16.2 이하에서는 Subject Alternative Name 목록의 첫 번째 이메일만 비교됩니다. Subject Alternative Name 목록을 표시하려면 다음을 실행합니다:

    signature.__send__ :get_certificate_extension,'subjectAltName'
    

    개발자의 이메일 주소가 목록의 첫 번째가 아니면 이 검사가 실패하고 커밋은 unverified로 표시됩니다.

  6. 커밋의 이메일 주소는 GitLab의 계정과 연결되어 있어야 합니다. 이 검사는 false를 반환해야 합니다:

    signature.user.nil?
    
  7. 이메일 주소가 GitLab의 사용자와 연결되어 있는지 점검합니다. 이 검사는 #과 같은 사용자를 반환해야 합니다:

    User.find_by_any_email(commit.committer_email)
    

    nil을 반환하면 이메일 주소가 사용자와 연결되지 않은 것이고, 검사는 실패합니다.

  8. 개발자의 이메일 주소가 확인되었는지 확인합니다. 이 검사는 true를 반환해야 합니다:

    signature.user.verified_emails.include?(commit.committer_email)
    

    이전 검사가 nil을 반환했으면 이 명령은 오류를 표시합니다:

    NoMethodError (undefined method `verified_emails' for nil:NilClass)
    
  9. 확인 상태는 데이터베이스에 저장됩니다. 데이터베이스 레코드를 표시하려면:

    pp CommitSignatures::X509CommitSignature.by_commit_sha(commit.sha);nil
    

    이전 검사가 모두 올바른 값을 반환한 경우:

    • verification_status: "unverified"는 데이터베이스 레코드를 업데이트해야 함을 나타냅니다. Rake 태스크를 사용합니다.

    • []는 데이터베이스에 아직 레코드가 없음을 나타냅니다. GitLab에서 커밋을 찾아 서명을 확인하고 결과를 저장합니다.

암호화 확인 검사#

GitLab이 verified_signature가 false라고 판단하면 Rails 콘솔에서 그 이유를 조사합니다. 이 검사에는 signature가 있어야 합니다. 앞의 주요 확인 검사에 있는 signature 단계를 참고합니다.

  1. 발급자를 점검하지 않고 서명을 검사하면 true가 반환되어야 합니다:

    signature.__send__ :valid_signature?
    
  2. 서명 시간과 날짜를 점검합니다. 이 검사는 true를 반환해야 합니다:

    signature.__send__ :valid_signing_time?
    
    • 코드는 코드 서명 인증서의 만료를 허용합니다.

    • 커밋은 인증서의 유효 기간 중에 서명되어야 하며, 커밋의 날짜 기록과 같거나 그 이후여야 합니다. 커밋 시간과 not_before, not_after를 포함한 인증서 세부 정보를 다음으로 표시합니다:

      commit.created_at
      pp signature.__send__ :cert; nil
      
  3. TLS 신뢰를 설정할 수 있는지까지 포함하여 서명을 확인합니다. 이 검사는 true를 반환해야 합니다:

    signature.__send__(:p7).verify([], signature.__send__(:cert_store), signature.__send__(:signed_text))
    
    1. 이 검사가 실패하면 신뢰를 설정하는 데 필요한 누락된 인증서를 GitLab 인증서 저장소에 추가합니다.

    2. 인증서를 더 추가한 다음(이 문제 해결 단계가 통과하면) Rake 태스크를 실행하여 커밋을 재확인합니다.

    3. Rails 콘솔에서 인증서를 동적으로 추가하여 문제가 해결되는지 점검할 수 있습니다.

      1. 수정할 수 있는 신뢰 저장소 cert_store로 서명을 다시 테스트합니다. 여전히 false로 실패해야 합니다:

        cert_store = signature.__send__ :cert_store
        signature.__send__(:p7).verify([], cert_store, signature.__send__(:signed_text))
        
      2. 인증서를 하나 더 추가하고 다시 테스트합니다:

        cert_store.add_file("/etc/ssl/certs/my_new_root_ca.pem")
        signature.__send__(:p7).verify([], cert_store, signature.__send__(:signed_text))
        
    4. 서명에 포함된 인증서를 표시합니다:

      pp signature.__send__(:p7).certificates ; nil
      
    5. 명령줄에서 OpenSSL로 추가 조사를 수행할 수 있습니다.

추가 중간 인증서와 루트 인증서가 인증서 저장소에 추가되어 있는지 확인합니다. 웹 서버에서 인증서 체인이 구성되는 방식과 일관성을 유지하려면 다음을 따릅니다:

  • 커밋에 서명하는 Git 클라이언트는 인증서와 모든 중간 인증서를 서명에 포함해야 합니다.
  • GitLab 인증서 저장소에는 루트만 있어야 합니다.

루트 인증서가 만료되었을 때처럼 GitLab 신뢰 저장소에서 루트 인증서를 제거하면, 해당 루트로 체인이 연결되는 커밋 서명은 unverified로 표시됩니다.

OpenSSL을 사용한 S/MIME 확인#

서명에 문제가 있거나 TLS 신뢰가 실패하면 명령줄에서 OpenSSL로 추가 디버깅을 수행할 수 있습니다.

Rails 콘솔에서 서명과 서명된 텍스트를 내보냅니다:

  1. 주요 확인 검사의 처음 두 단계가 필요하며, 이를 거치면 signature가 설정됩니다.

  2. OpenSSL은 PKCS7 PEM 형식 데이터가 BEGIN PKCS7과 END PKCS7로 둘러싸여 있어야 하므로, 보통 이를 수정해야 합니다:

    pkcs7_text = signature.signature_text.sub('-----BEGIN SIGNED MESSAGE-----', '-----BEGIN PKCS7-----')
    pkcs7_text = pkcs7_text.sub('-----END SIGNED MESSAGE-----', '-----END PKCS7-----')
    
  3. 서명과 서명된 텍스트를 파일로 씁니다:

    f1=File.new('/tmp/signature_text.pk7.pem','w')
    f1 << pkcs7_text
    f1.close
    
    f2=File.new('/tmp/signed_text.txt','w')
    f2 << signature.signed_text
    f2.close
    

이제 이 데이터를 Linux 명령줄에서 OpenSSL로 조사할 수 있습니다:

  1. 서명이 들어 있는 PKCS #7 파일을 조회할 수 있습니다:

    /opt/gitlab/embedded/bin/openssl pkcs7 -inform pem -print_certs \
        -in /tmp/signature_text.pk7.pem -print -noout
    

    출력에는 서명자의 인증서인 cert 섹션이 최소 하나 포함되어야 합니다.

    출력에는 저수준 세부 정보가 많습니다. 다음은 포함되어야 하는 구조와 제목의 일부 예입니다:

    PKCS7:
      d.sign:
        cert:
            cert_info:
              issuer:
              validity:
                notBefore:
                notAfter:
              subject:
    

    개발자의 코드 서명 인증서가 중간 인증기관에서 발급된 것이라면 인증서 세부 정보가 더 있어야 합니다:

    PKCS7:
      d.sign:
        cert:
            cert_info:
        cert:
            cert_info:
    
  2. 서명에서 인증서를 추출합니다:

    /opt/gitlab/embedded/bin/openssl pkcs7 -inform pem -print_certs \
        -in /tmp/signature_text.pk7.pem -out /tmp/signature_cert.pem
    

    이 단계가 실패하면 서명에 서명자의 인증서가 없을 수 있습니다.

    • Git 클라이언트에서 이 문제를 수정합니다.
    • 다음 단계는 실패하지만, 서명자의 인증서를 GitLab 서버로 복사하면 -nointern -certfile signerscertificate.pem을 사용하여 일부 테스트를 수행할 수 있습니다.
  3. 추출한 인증서로 커밋을 부분적으로 확인합니다:

    /opt/gitlab/embedded/bin/openssl smime -verify -binary -inform pem \
        -in /tmp/signature_text.pk7.pem -content /tmp/signed_text.txt \
        -noverify -certfile /tmp/signature_cert.pem -nointern
    

    출력에는 보통 다음이 포함됩니다:

    • 부모 커밋
    • 커밋의 이름, 이메일, 타임스탬프
    • 커밋 텍스트
    • Verification successful(또는 유사한 내용)

    이 검사는 GitLab이 수행하는 검사와 동일하지 않습니다. 이유는 다음과 같습니다:

    • 서명자의 인증서를 확인하지 않습니다(-noverify)
    • 메시지에 들어 있는 인증서가 아니라 제공된 -certfile로 확인합니다(-nointern)
  4. 메시지에 들어 있는 인증서로 커밋을 부분적으로 확인합니다:

    /opt/gitlab/embedded/bin/openssl smime -verify -binary -inform pem \
        -in /tmp/signature_text.pk7.pem -content /tmp/signed_text.txt \
        -noverify
    

    추출한 인증서를 사용한 이전 단계와 같은 결과가 나와야 합니다.

    메시지에 인증서가 없으면 오류에 signer certificate not found가 포함됩니다.

  5. 커밋을 완전히 확인합니다:

    /opt/gitlab/embedded/bin/openssl smime -verify -binary -inform pem \
        -in /tmp/signature_text.pk7.pem -content /tmp/signed_text.txt
    

    이 단계가 실패하면 GitLab에서도 확인이 실패합니다.

    오류를 해결합니다. 예를 들면 다음과 같습니다:

    • certificate verify error .. unable to get local issuer certificate:

      • 신뢰 체인을 설정할 수 없었습니다.
      • 이 OpenSSL 바이너리는 GitLab 신뢰 저장소를 사용합니다. 신뢰 저장소에 루트 인증서가 없거나, 서명에 중간 인증서가 없어 신뢰할 수 있는 루트까지의 체인을 구성할 수 없습니다.
        • 서명에 포함할 수 없는 경우 중간 인증서를 신뢰 저장소에 넣을 수 있습니다.
        • 패키지 설치 GitLab의 신뢰 저장소에 인증서를 추가하는 절차 는 /etc/gitlab/trusted-certs를 사용합니다.
      • OpenSSL에서 -CAfile /path/to/rootcertificate.pem으로 추가 신뢰 인증서를 테스트합니다
    • unsupported certificate purpose:

      • 인증서는 서명자 인증서의 X509v3 Key Usage 섹션에 Digital Signature를 지정해야 합니다.

      • X509v3 Extended Key Usage(EKU) 섹션을 지정한 경우에는 emailProtection이 포함되어야 합니다. 자세한 내용은 RFC 5280을 참고합니다:

        (키 사용) 두 확장 모두와 일치하는 목적이 없으면 인증서를 어떤 목적으로도 사용해서는 안 됩니다.

    • signer certificate not found, 다음 중 하나입니다:

      • -nointern 인수를 추가했지만 -certfile을 제공하지 않았습니다.
      • 서명에 서명자의 인증서가 없습니다.

X.509 인증서로 커밋 및 태그 서명

GitLab v19.5
Tier: Free, Premium, Ultimate
Offering: GitLab Self-Managed, GitLab Dedicated
원문 보기

요약

X.509는 공공 또는 사설 공개 키 인프라(PKI)에서 발급하는 공개 키 인증서의 표준 형식입니다. GitLab은 자체 인증서 저장소를 사용하므로 신뢰 체인을 직접 정의합니다. 신뢰 체인의 루트 CA 또는 중간 인증서가 만료되어 갱신되면, 커밋을 재확인할 때까지 커밋이 일시적으로 "unverified"로 표시될 수 있습니다.

X.509는 공공 또는 사설 공개 키 인프라(PKI)에서 발급하는 공개 키 인증서의 표준 형식입니다. 개인 X.509 인증서는 S/MIME(Secure/Multipurpose Internet Mail Extensions)과 같은 인증 또는 서명 목적으로 사용됩니다. 그러나 Git은 X.509 인증서로 커밋과 태그에 서명하는 것도 GPG (GnuPG, or GNU Privacy Guard)와 유사한 방식으로 지원합니다. 주요 차이는 GitLab이 개발자의 서명을 신뢰할 수 있는지 판단하는 방식입니다:

  • X.509의 경우 루트 인증기관이 GitLab 신뢰 저장소에 추가됩니다. (신뢰 저장소는 신뢰할 수 있는 보안 인증서를 모아 둔 저장소입니다.) 서명에 포함된 필요한 중간 인증서와 결합하면 개발자의 인증서를 신뢰할 수 있는 루트 인증서까지 체인으로 연결할 수 있습니다.
  • GPG의 경우 개발자가 자신의 계정에 GPG 키를 추가합니다.

GitLab은 자체 인증서 저장소를 사용하므로 신뢰 체인을 직접 정의합니다. GitLab이 커밋 또는 태그를 확인하려면 다음 조건을 충족해야 합니다:

  • 서명 인증서의 이메일이 GitLab에서 확인된 이메일 주소와 일치해야 합니다.
  • GitLab 인스턴스에는 서명의 인증서에서 GitLab 인증서 저장소의 신뢰할 수 있는 인증서까지 이어지는 전체 신뢰 체인이 필요합니다. 이 체인에는 서명에 제공된 중간 인증서가 포함될 수 있습니다. 인증기관 루트 인증서와 같은 인증서를 GitLab 인증서 저장소에 추가해야 할 수 있습니다.
  • 서명 시간은 보통 최대 3년인 인증서 유효 기간 안에 있어야 합니다.
  • 서명 시간은 커밋 시간과 같거나 그 이후입니다.
Note

신뢰 체인의 루트 CA 또는 중간 인증서가 만료되어 갱신되면, 커밋을 재확인할 때까지 커밋이 일시적으로 "unverified"로 표시될 수 있습니다.

커밋 상태가 이미 결정되어 데이터베이스에 저장된 경우에는 Rake 태스크로 상태를 다시 점검합니다. GitLab은 백그라운드 워커로 매일 인증서 해지 목록을 점검합니다.

알려진 문제#

  • authorityKeyIdentifier, subjectKeyIdentifier, crlDistributionPoints가 없는 인증서는 Unverified로 표시됩니다. RFC 5280에 부합하는 PKI에서 발급한 인증서를 사용하는 것을 권장합니다.

  • Verified 배지는 GitLab.com 제품에서는 표시되지 않습니다. 사용자 지정 인증기관(CA) 업로드가 GitLab Self-Managed에서만 가능하기 때문입니다.

  • 필수 키 사용(KU)인 Digital Signature 외에 인증서의 확장 키 사용(EKU) 섹션에 값을 설정하면 커밋이 Unverified로 표시될 가능성이 높습니다. 이를 해결하려면 EKU 목록에 emailProtection을 추가합니다. RFC 5280이 이 제한 사항을 명시합니다.

    진단하려면 OpenSSL을 사용한 S/MIME 확인을 따릅니다.

  • GitLab 16.2 이하에서 서명 인증서의 Subject Alternative Name 목록에 이메일이 두 개 이상 있으면, 첫 번째 이메일만 커밋 확인에 사용됩니다.

서명된 커밋 구성#

커밋, 태그 또는 둘 다에 서명하려면 다음을 수행해야 합니다:

  1. X.509 키 쌍 획득.
  2. X.509 인증서를 Git과 연결.
  3. 커밋 서명 및 확인.
  4. 태그 서명 및 확인.

X.509 키 쌍 획득#

조직에 공개 키 인프라(PKI)가 있으면 해당 PKI가 S/MIME 키를 제공합니다. PKI에서 받은 S/MIME 키 쌍이 없으면 자체 서명 키 쌍을 직접 만들거나 구매합니다.

X.509 인증서를 Git과 연결#

X.509 서명을 활용하려면 Git 2.19.0 이상이 필요합니다. git --version 명령으로 Git 버전을 확인할 수 있습니다.

올바른 버전이면 Git 구성을 진행할 수 있습니다.

키를 사용하여 서명하도록 Git을 구성합니다:

signingkey=$( gpgsm --list-secret-keys | egrep '(key usage|ID)' | grep -B 1 digitalSignature | awk '/ID/ {print $2}' )
git config --global user.signingkey $signingkey
git config --global gpg.format x509

Windows 또는 macOS를 구성하려면:

  1. 다음 중 하나로 S/MIME Sign을 설치합니다:

    • 설치 프로그램 다운로드.
    • macOS에서 brew install smimesign 실행.
  2. smimesign --list-keys를 실행하여 인증서 ID를 확인합니다.

  3. git config --global user.signingkey 를 실행하여 서명 키를 설정합니다. 는 인증서 ID로 바꿉니다.

  4. 다음 명령으로 X.509를 구성합니다:

    git config --global gpg.x509.program smimesign
    git config --global gpg.format x509
    

커밋 서명 및 확인#

X.509 인증서를 Git과 연결한 후에는 커밋에 서명할 수 있습니다:

  1. Git 커밋을 만들 때 -S 플래그를 추가합니다:

    git commit -S -m "feat: x509 signed commits"
    
  2. GitLab에 푸시하고 --show-signature 플래그로 커밋이 확인되었는지 점검합니다:

    git log --show-signature
    
  3. 커밋할 때마다 -S 플래그를 입력하지 않으려면, 다음 명령을 실행하여 Git이 매번 커밋에 서명하도록 합니다:

    git config --global commit.gpgsign true
    

태그 서명 및 확인#

X.509 인증서를 Git과 연결한 후에는 태그 서명을 시작할 수 있습니다:

  1. Git 태그를 만들 때 -s 플래그를 추가합니다:

    git tag -s v1.1.1 -m "My signed tag"
    
  2. GitLab에 푸시하고 다음 명령으로 태그가 서명되었는지 확인합니다:

    git tag --verify v1.1.1
    
  3. 태그를 만들 때마다 -s 플래그를 입력하지 않으려면, 다음 명령을 실행하여 Git이 매번 태그에 서명하도록 합니다:

    git config --global tag.gpgsign true
    

관련 주제#

문제 해결#

관리자 액세스 권한이 없는 커미터는 가능한 해결 방법을 확인하기 위해 서명된 커밋의 확인 문제 수정 목록을 검토합니다. 이 페이지의 다른 문제 해결 방안에는 관리자 액세스 권한이 필요합니다.

커밋 재확인#

GitLab은 점검한 커밋의 상태를 데이터베이스에 저장합니다. 다음 작업을 한 후에 커밋을 재확인할 수 있습니다:

  • 루트 CA 또는 중간 인증서 갱신.
  • 인증서 저장소 변경.

커밋을 재확인하려면:

  1. 루트 CA와 모든 중간 인증서가 GitLab 인증서 저장소에 있는지 확인합니다.
  2. update_signatures Rake 태스크를 실행하여 이전에 확인한 커밋의 상태를 점검하고 업데이트합니다.

주요 확인 검사#

코드는 이러한 주요 검사를 수행하며, 모두 verified를 반환해야 합니다:

  • x509_certificate.nil?는 false여야 합니다.
  • x509_certificate.revoked?는 false여야 합니다.
  • verified_signature는 true여야 합니다.
  • user.nil?는 false여야 합니다.
  • user.verified_emails.include?(@email)는 true여야 합니다.
  • certificate_email == @email은 true여야 합니다.

커밋이 Unverified로 표시되는 이유를 조사하려면:

  1. Rails 콘솔을 시작합니다:

    sudo gitlab-rails console
    
  2. 조사 대상 프로젝트(경로 또는 ID)와 전체 커밋 SHA를 식별합니다. 이 정보로 다른 검사를 실행할 signature를 만듭니다:

    project = Project.find_by_full_path('group/subgroup/project')
    project = Project.find_by_id('121')
    commit = project.repository.commit_by(oid: '87fdbd0f9382781442053b0b76da729344e37653')
    signedcommit=Gitlab::X509::Commit.new(commit)
    signature=Gitlab::X509::Signature.new(signedcommit.signature_text, signedcommit.signed_text, commit.committer_email, commit.created_at, commit.project)
    

    검사를 진행하면서 발견한 문제를 해결하기 위해 변경한 경우에는 Rails 콘솔을 다시 시작하고 처음부터 검사를 다시 실행합니다.

  3. 커밋의 인증서를 점검합니다:

    signature.x509_certificate.nil?
    signature.x509_certificate.revoked?
    

    두 검사 모두 false를 반환해야 합니다:

    > signature.x509_certificate.nil?
    => false
    > signature.x509_certificate.revoked?
    => false
    
  4. 서명에 암호화 검사를 실행합니다. 코드는 true를 반환해야 합니다:

    signature.verified_signature
    

    false를 반환하면 이 검사를 더 자세히 조사합니다.

  5. 커밋과 서명의 이메일 주소가 일치하는지 확인합니다:

    • Rails 콘솔이 비교 대상 이메일 주소를 표시합니다.
    • 마지막 명령은 true를 반환해야 합니다:
    sigemail=signature.__send__:certificate_email
    commitemail=commit.committer_email
    sigemail == commitemail
    

    GitLab 16.2 이하에서는 Subject Alternative Name 목록의 첫 번째 이메일만 비교됩니다. Subject Alternative Name 목록을 표시하려면 다음을 실행합니다:

    signature.__send__ :get_certificate_extension,'subjectAltName'
    

    개발자의 이메일 주소가 목록의 첫 번째가 아니면 이 검사가 실패하고 커밋은 unverified로 표시됩니다.

  6. 커밋의 이메일 주소는 GitLab의 계정과 연결되어 있어야 합니다. 이 검사는 false를 반환해야 합니다:

    signature.user.nil?
    
  7. 이메일 주소가 GitLab의 사용자와 연결되어 있는지 점검합니다. 이 검사는 #과 같은 사용자를 반환해야 합니다:

    User.find_by_any_email(commit.committer_email)
    

    nil을 반환하면 이메일 주소가 사용자와 연결되지 않은 것이고, 검사는 실패합니다.

  8. 개발자의 이메일 주소가 확인되었는지 확인합니다. 이 검사는 true를 반환해야 합니다:

    signature.user.verified_emails.include?(commit.committer_email)
    

    이전 검사가 nil을 반환했으면 이 명령은 오류를 표시합니다:

    NoMethodError (undefined method `verified_emails' for nil:NilClass)
    
  9. 확인 상태는 데이터베이스에 저장됩니다. 데이터베이스 레코드를 표시하려면:

    pp CommitSignatures::X509CommitSignature.by_commit_sha(commit.sha);nil
    

    이전 검사가 모두 올바른 값을 반환한 경우:

    • verification_status: "unverified"는 데이터베이스 레코드를 업데이트해야 함을 나타냅니다. Rake 태스크를 사용합니다.

    • []는 데이터베이스에 아직 레코드가 없음을 나타냅니다. GitLab에서 커밋을 찾아 서명을 확인하고 결과를 저장합니다.

암호화 확인 검사#

GitLab이 verified_signature가 false라고 판단하면 Rails 콘솔에서 그 이유를 조사합니다. 이 검사에는 signature가 있어야 합니다. 앞의 주요 확인 검사에 있는 signature 단계를 참고합니다.

  1. 발급자를 점검하지 않고 서명을 검사하면 true가 반환되어야 합니다:

    signature.__send__ :valid_signature?
    
  2. 서명 시간과 날짜를 점검합니다. 이 검사는 true를 반환해야 합니다:

    signature.__send__ :valid_signing_time?
    
    • 코드는 코드 서명 인증서의 만료를 허용합니다.

    • 커밋은 인증서의 유효 기간 중에 서명되어야 하며, 커밋의 날짜 기록과 같거나 그 이후여야 합니다. 커밋 시간과 not_before, not_after를 포함한 인증서 세부 정보를 다음으로 표시합니다:

      commit.created_at
      pp signature.__send__ :cert; nil
      
  3. TLS 신뢰를 설정할 수 있는지까지 포함하여 서명을 확인합니다. 이 검사는 true를 반환해야 합니다:

    signature.__send__(:p7).verify([], signature.__send__(:cert_store), signature.__send__(:signed_text))
    
    1. 이 검사가 실패하면 신뢰를 설정하는 데 필요한 누락된 인증서를 GitLab 인증서 저장소에 추가합니다.

    2. 인증서를 더 추가한 다음(이 문제 해결 단계가 통과하면) Rake 태스크를 실행하여 커밋을 재확인합니다.

    3. Rails 콘솔에서 인증서를 동적으로 추가하여 문제가 해결되는지 점검할 수 있습니다.

      1. 수정할 수 있는 신뢰 저장소 cert_store로 서명을 다시 테스트합니다. 여전히 false로 실패해야 합니다:

        cert_store = signature.__send__ :cert_store
        signature.__send__(:p7).verify([], cert_store, signature.__send__(:signed_text))
        
      2. 인증서를 하나 더 추가하고 다시 테스트합니다:

        cert_store.add_file("/etc/ssl/certs/my_new_root_ca.pem")
        signature.__send__(:p7).verify([], cert_store, signature.__send__(:signed_text))
        
    4. 서명에 포함된 인증서를 표시합니다:

      pp signature.__send__(:p7).certificates ; nil
      
    5. 명령줄에서 OpenSSL로 추가 조사를 수행할 수 있습니다.

추가 중간 인증서와 루트 인증서가 인증서 저장소에 추가되어 있는지 확인합니다. 웹 서버에서 인증서 체인이 구성되는 방식과 일관성을 유지하려면 다음을 따릅니다:

  • 커밋에 서명하는 Git 클라이언트는 인증서와 모든 중간 인증서를 서명에 포함해야 합니다.
  • GitLab 인증서 저장소에는 루트만 있어야 합니다.

루트 인증서가 만료되었을 때처럼 GitLab 신뢰 저장소에서 루트 인증서를 제거하면, 해당 루트로 체인이 연결되는 커밋 서명은 unverified로 표시됩니다.

OpenSSL을 사용한 S/MIME 확인#

서명에 문제가 있거나 TLS 신뢰가 실패하면 명령줄에서 OpenSSL로 추가 디버깅을 수행할 수 있습니다.

Rails 콘솔에서 서명과 서명된 텍스트를 내보냅니다:

  1. 주요 확인 검사의 처음 두 단계가 필요하며, 이를 거치면 signature가 설정됩니다.

  2. OpenSSL은 PKCS7 PEM 형식 데이터가 BEGIN PKCS7과 END PKCS7로 둘러싸여 있어야 하므로, 보통 이를 수정해야 합니다:

    pkcs7_text = signature.signature_text.sub('-----BEGIN SIGNED MESSAGE-----', '-----BEGIN PKCS7-----')
    pkcs7_text = pkcs7_text.sub('-----END SIGNED MESSAGE-----', '-----END PKCS7-----')
    
  3. 서명과 서명된 텍스트를 파일로 씁니다:

    f1=File.new('/tmp/signature_text.pk7.pem','w')
    f1 << pkcs7_text
    f1.close
    
    f2=File.new('/tmp/signed_text.txt','w')
    f2 << signature.signed_text
    f2.close
    

이제 이 데이터를 Linux 명령줄에서 OpenSSL로 조사할 수 있습니다:

  1. 서명이 들어 있는 PKCS #7 파일을 조회할 수 있습니다:

    /opt/gitlab/embedded/bin/openssl pkcs7 -inform pem -print_certs \
        -in /tmp/signature_text.pk7.pem -print -noout
    

    출력에는 서명자의 인증서인 cert 섹션이 최소 하나 포함되어야 합니다.

    출력에는 저수준 세부 정보가 많습니다. 다음은 포함되어야 하는 구조와 제목의 일부 예입니다:

    PKCS7:
      d.sign:
        cert:
            cert_info:
              issuer:
              validity:
                notBefore:
                notAfter:
              subject:
    

    개발자의 코드 서명 인증서가 중간 인증기관에서 발급된 것이라면 인증서 세부 정보가 더 있어야 합니다:

    PKCS7:
      d.sign:
        cert:
            cert_info:
        cert:
            cert_info:
    
  2. 서명에서 인증서를 추출합니다:

    /opt/gitlab/embedded/bin/openssl pkcs7 -inform pem -print_certs \
        -in /tmp/signature_text.pk7.pem -out /tmp/signature_cert.pem
    

    이 단계가 실패하면 서명에 서명자의 인증서가 없을 수 있습니다.

    • Git 클라이언트에서 이 문제를 수정합니다.
    • 다음 단계는 실패하지만, 서명자의 인증서를 GitLab 서버로 복사하면 -nointern -certfile signerscertificate.pem을 사용하여 일부 테스트를 수행할 수 있습니다.
  3. 추출한 인증서로 커밋을 부분적으로 확인합니다:

    /opt/gitlab/embedded/bin/openssl smime -verify -binary -inform pem \
        -in /tmp/signature_text.pk7.pem -content /tmp/signed_text.txt \
        -noverify -certfile /tmp/signature_cert.pem -nointern
    

    출력에는 보통 다음이 포함됩니다:

    • 부모 커밋
    • 커밋의 이름, 이메일, 타임스탬프
    • 커밋 텍스트
    • Verification successful(또는 유사한 내용)

    이 검사는 GitLab이 수행하는 검사와 동일하지 않습니다. 이유는 다음과 같습니다:

    • 서명자의 인증서를 확인하지 않습니다(-noverify)
    • 메시지에 들어 있는 인증서가 아니라 제공된 -certfile로 확인합니다(-nointern)
  4. 메시지에 들어 있는 인증서로 커밋을 부분적으로 확인합니다:

    /opt/gitlab/embedded/bin/openssl smime -verify -binary -inform pem \
        -in /tmp/signature_text.pk7.pem -content /tmp/signed_text.txt \
        -noverify
    

    추출한 인증서를 사용한 이전 단계와 같은 결과가 나와야 합니다.

    메시지에 인증서가 없으면 오류에 signer certificate not found가 포함됩니다.

  5. 커밋을 완전히 확인합니다:

    /opt/gitlab/embedded/bin/openssl smime -verify -binary -inform pem \
        -in /tmp/signature_text.pk7.pem -content /tmp/signed_text.txt
    

    이 단계가 실패하면 GitLab에서도 확인이 실패합니다.

    오류를 해결합니다. 예를 들면 다음과 같습니다:

    • certificate verify error .. unable to get local issuer certificate:

      • 신뢰 체인을 설정할 수 없었습니다.
      • 이 OpenSSL 바이너리는 GitLab 신뢰 저장소를 사용합니다. 신뢰 저장소에 루트 인증서가 없거나, 서명에 중간 인증서가 없어 신뢰할 수 있는 루트까지의 체인을 구성할 수 없습니다.
        • 서명에 포함할 수 없는 경우 중간 인증서를 신뢰 저장소에 넣을 수 있습니다.
        • 패키지 설치 GitLab의 신뢰 저장소에 인증서를 추가하는 절차 는 /etc/gitlab/trusted-certs를 사용합니다.
      • OpenSSL에서 -CAfile /path/to/rootcertificate.pem으로 추가 신뢰 인증서를 테스트합니다
    • unsupported certificate purpose:

      • 인증서는 서명자 인증서의 X509v3 Key Usage 섹션에 Digital Signature를 지정해야 합니다.

      • X509v3 Extended Key Usage(EKU) 섹션을 지정한 경우에는 emailProtection이 포함되어야 합니다. 자세한 내용은 RFC 5280을 참고합니다:

        (키 사용) 두 확장 모두와 일치하는 목적이 없으면 인증서를 어떤 목적으로도 사용해서는 안 됩니다.

    • signer certificate not found, 다음 중 하나입니다:

      • -nointern 인수를 추가했지만 -certfile을 제공하지 않았습니다.
      • 서명에 서명자의 인증서가 없습니다.