X.509 certificates를 사용하여 커밋과 태그에 서명하기

Tier: Free, Premium, Ultimate
Offering: GitLab.com, Self-Managed, GitLab Dedicated

X.509는 공용 또는 사설 공개 키 인프라(PKI)에서 발급된 공개 키 인증서의 표준 형식입니다. 개인 X.509 인증서는 S/MIME(안전/다목적 인터넷 메일 확장)와 같은 인증 또는 서명 목적으로 사용됩니다. 그러나 Git은 GPG (GnuPG 또는 GNU Privacy Guard)와 유사한 방식으로 X.509 인증서로 커밋과 태그에 서명하는 것을 지원합니다. 가장 큰 차이점은 개발자의 서명이 신뢰할 수 있는지 GitLab에서 어떻게 확인하는지입니다.

  • X.509의 경우 루트 인증 기관이 GitLab 신뢰 리포지터리에 추가됩니다. (신뢰 리포지터리는 신뢰할 수 있는 보안 인증서의 리포지터리입니다.) 서명에 필요한 중간 인증서와 결합하여, 개발자 인증서는 신뢰할 수 있는 루트 인증서까지 연결될 수 있습니다.
  • GPG의 경우 개발자는 GPG 키를 계정에 추가합니다.

GitLab은 자체 인증서 리포지터리를 사용하며, 따라서 신뢰 체인을 정의합니다. GitLab에 의해 커밋 또는 태그가 확인되려면:

  • 서명 인증서 이메일은 GitLab에서 확인된 이메일 주소와 일치해야 합니다.
  • GitLab 인스턴스는 서명의 인증서에서 GitLab 인증서 리포지터리의 신뢰할 수 있는 인증서에 이르는 완전한 신뢰 체인을 설정해야 합니다. 이 체인에는 서명에서 제공된 중간 인증서를 포함할 수 있습니다. 사용자는 GitLab 인증서 리포지터리에 인증서를 추가해야 할 수 있습니다.
  • 서명 시간은 일반적으로 최대 세 년간 유효한 인증서 유효 기간 내에 있어야 합니다.
  • 서명 시간은 커밋 시간과 동일하거나 그 이후여야 합니다.

커밋의 상태가 이미 확인되어 데이터베이스에 저장된 경우 상태를 다시 확인하기 위해 Rake task를 사용합니다. 문제 해결 섹션을 참조하세요. GitLab은 백그라운드 워커를 사용하여 매일 인증서 폐지 디렉터리을 확인합니다.

제한 사항

  • authorityKeyIdentifier, subjectKeyIdentifier, 및 crlDistributionPoints가 없는 인증서는 Unverified로 표시됩니다. RFC 5280 규격에 따른 PKI에서의 인증서 사용을 권장합니다.
  • GitLab 16.2 및 이전 버전에서 서명 인증서의 대체 이름 디렉터리에 둘 이상의 이메일이 포함된 경우, 첫 번째 이메일만을 사용하여 커밋을 확인합니다.
  • GitLab 15.1 및 이전 버전에서는 발급자 인증서의 X509v3 Subject Key Identifier (SKI)와 서명 인증서가 40자여야만 합니다. SKI가 더 짧은 경우 GitLab에서 커밋이 확인되지 않을 뿐만 아니라 짧은 주제 키 식별자는 프로젝트에 액세스할 때 오류를 발생시킬 수도 있습니다, ‘커밋 서명을 로드하는 중 오류가 발생했습니다’ 및 HTTP 422 Unprocessable Entity 오류 등을 일으킬 수 있습니다.

서명된 커밋을 위한 구성

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

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

X.509 키 페어 획득

조직에서 공개 키 인프라(PKI)가 있는 경우 PKI에서 S/MIME 키를 제공합니다. PKI에서 S/MIME 키 페어를 제공받지 못한 경우 자체적으로 자체 서명 페어를 생성하거나 페어를 구입해야 합니다.

Git과 X.509 인증서 연결

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

적절한 버전이 있는 경우 Git을 구성할 수 있습니다.

Linux

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

Windows 또는 macOS를 구성하려면 다음을 수행하세요:

  1. S/MIME Sign을 설치하십시오.
    • 설치 프로그램을 다운로드하거나
    • macOS에서 brew install smimesign을 실행합니다.
  2. smimesign --list-keys를 실행하여 인증서의 ID를 확인합니다.
  3. git config --global user.signingkey <ID> 명령을 사용하여 서명 키를 설정하십시오. <ID>를 인증서 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은 이전에 확인된 커밋의 상태를 데이터베이스에 저장합니다. Rake task를 사용하여 이전에 확인된 커밋의 상태를 다시 확인할 수 있습니다.

변경 사항이 있는 경우 다음 명령을 실행하십시오:

sudo gitlab-rake gitlab:x509:update_signatures

기본 확인 사항

코드는 이러한 주요 확인 사항을 수행하며, 모두 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를 식별합니다. 이 정보를 사용하여 시그니처를 생성하여 다른 확인 사항을 실행합니다:

    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)
    

    확인된 확인 사항을 실행하여 식별된 문제를 해결하기 위해 변경 사항을 가하면, 다시 Rails 콘솔을 시작하고 처음부터 다시 확인 사항을 실행하십시오.

  3. 커밋에 대한 인증서를 확인합니다:

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

    두 확인 사항은 둘 다 false를 반환해야 합니다:

    > signature.x509_certificate.nil?
    => false
    > signature.x509_certificate.revoked?
    => false
    

    알려진 문제로 이러한 확인 사항이 Validation failed: Subject key identifier is invalid로 실패합니다.

  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 id:1234 @user_handle>:

    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_signaturefalse로 결정하는 경우, 이유를 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 PKCS7END 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:
      • 인증서는 Digital Signature를 명시해야 합니다.
      • 이것은 보통 서명자의 인증서의 X509v3 Key Usage 섹션에 있습니다.
      • X509v3 Extended Key Usage 섹션이 있으면 Digital Signature를 반드시 포함해야 합니다. 자세한 내용은 RFC 5280를 참조하십시오.

        하나 이상의 (Key Usage) 확장을