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

Tier: Free, Premium, Ultimate Offering: 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 인증서 저장소에 추가해야 할 수 있습니다.
  • 서명 시간이
    인증서 유효성의 기간 내에 있어야 하며,
    이는 일반적으로 최대 3년까지입니다.
  • 서명 시간은 커밋 시간보다 같거나 늦어야 합니다.

커밋의 상태가 이미 결정되어 데이터베이스에 저장된 경우,
Rake 작업 상태를 재확인하기 위해 사용하세요.
문제 해결 섹션을 참조하세요.
GitLab은 매일 백그라운드 작업자를 사용하여 인증서 폐기 목록을 확인합니다.

알려진 문제

  • authorityKeyIdentifier,
    subjectKeyIdentifiercrlDistributionPoints가 없는 인증서는 검증되지 않음으로 표시됩니다.
    RFC 5280와 일치하는 PKI의 인증서를 사용하는 것이 좋습니다.
  • 검증됨 배지는 GitLab.com 오퍼링에서 표시되지 않으며,
    사용자 정의 인증 기관(CA)를 업로드하는 것은
    자가 관리 인스턴스에서만 가능합니다.
  • 인증서의 확장 키 사용(EKU) 섹션에 요구되는 키 사용(KU)인 Digital Signature 외에 값을 설정하면
    귀하의 커밋이 검증되지 않음으로 표시될 가능성이 높습니다.
    이를 해결하려면 EKU 목록에 emailProtection을 추가하세요.
    RFC 5280은 이 제한을 명시하고 있습니다.

    이를 진단하기 위해 OpenSSL을 사용한 S/MIME 검증을 따르세요.
    이 변경으로 문제가 해결되지 않으면,
    문제 440189에 피드백을 제공해 주세요.

  • GitLab 16.2 이하에서는 서명 인증서의 대체 이름 목록에 이메일이 여러 개 있는 경우,
    첫 번째 이메일만 사용하여 커밋을 검증합니다.
  • GitLab 15.1 이하에서는 발급자 인증서와 서명 인증서의
    X509v3 Subject Key Identifier(SKI)가 40자 길이어야 합니다.
    SKI가 더 짧으면 커밋이 GitLab에서 검증된 것으로 표시되지 않으며,
    짧은 주제 키 식별자는 프로젝트에 접근할 때 오류를 유발할 수 있습니다,
    예: ‘커밋 서명 로드 중 오류 발생’ 및
    HTTP 422 Unprocessable Entity 오류.

서명된 커밋 구성하기

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

  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을 구성할 수 있습니다.

Linux

서명을 위해 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 "내 서명된 태그"
    
  2. GitLab에 푸시하고, 이 명령으로 태그가 서명되었는지 확인합니다:

    git tag --verify v1.1.1
    
  3. 매번 태그할 때 -s 플래그를 입력하고 싶지 않은 경우, Git이 매번 태그에 서명하도록 하기 위해 이 명령을 실행합니다:

    git config --global tag.gpgsign true
    

관련된 주제

문제 해결

관리자 액세스 권한이 없는 커미터의 경우, 서명된 커밋에 대한 검증 문제 목록을 검토하여 가능한 수정을 찾아보세요. 이 페이지의 나머지 문제 해결 제안은 관리자 액세스 권한이 필요합니다.

커밋 재검증

GitLab은 데이터베이스에 체크된 커밋의 상태를 저장합니다. Rake 작업을 사용하여 이전에 체크된 커밋의 상태 확인을 할 수 있습니다.

변경 사항을 적용한 후 다음 명령을 실행하세요:

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를 확인합니다. 이 정보를 사용하여 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)
    

    체크를 실행하여 확인된 문제를 해결하기 위해 수정 사항을 적용한 경우, 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로 표시됩니다.

S/MIME 검증과 OpenSSL

서명에 문제가 있거나 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
    

    출력에 signer’s certificate를 포함한 최소한 하나의 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
    

    이 단계가 실패하면, 서명이 signer’s certificate가 누락되었을 수 있습니다.

    • 이 문제를 Git 클라이언트에서 해결하십시오.
    • 다음 단계는 실패하겠지만, signer’s certificate를 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에서 수행하는 확인과 동일하지 않습니다. 이유는 다음과 같습니다:

    • signer’s certificate를 확인하지 않습니다 (-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:
      • 인증서는 signer’s certificate의 X509v3 Key Usage 섹션에 Digital Signature를 지정해야 합니다.
      • X509v3 Extended Key Usage (EKU) 섹션이 지정된 경우, emailProtection을 포함해야 합니다. RFC 5280에 대한 자세한 내용은 다음을 참조하십시오:

        두 (Key Usage) 확장에서 일관된 목적이 없으면 인증서는 어떤 목적으로도 사용되어서는 안 됩니다.

        EKU 목록에 이 추가가 문제를 해결하지 않으면, 문제 440189에 피드백을 제공하십시오.

    • signer certificate not found, 둘 중 하나:
      • -nointern 인수를 추가했지만 -certfile을 제공하지 않았습니다.
      • 서명이 signer’s certificate가 누락되었습니다.