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 인증서 저장소에 인증서를 추가해야 하는 경우도 있습니다.
  • 서명 시간은 보통 최대 3년까지 활성화되는 인증서 유효 기간 내에 있어야 합니다.
  • 서명 시간은 커밋 시간과 동일하거나 그 이후여야 합니다.

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

알려진 이슈

  • authorityKeyIdentifier, subjectKeyIdentifiercrlDistributionPoints가 없는 인증서는 Unverified로 표시됩니다. 이러한 경우 RFC 5280과 일치하는 PKI에서 인증서를 사용하는 것을 권장합니다.
  • GitLab.com 오퍼링에서는 Verified 배지가 표시되지 않습니다. 왜냐하면 사용자 지정 인증 기관 (CA)를 업로드할 수 있는 기능이 자체 관리형 인스턴스에서만 사용할 수 있기 때문입니다.
  • 필수 Key Usage(KU) 외에 인증서의 확장 키 사용(EKU) 부분에 값을 설정하면 커밋이 Unverified로 표시될 가능성이 있습니다. 이 문제를 해결하려면 EKU 목록에 emailProtection을 추가하세요. 이러한 제한은 RFC 5280에서 지정됩니다. 이를 진단하려면 OpenSSL을 사용한 S/MIME 검증을 따르세요. 이 변경으로 문제를 해결할 수 없는 경우, 이슈 440189에 의견을 제공하세요.
  • GitLab 16.2 이전 버전에서 서명 인증서의 대체 이메일이 Subject Alternative Name 목록에 두 개 이상 있으면, 커밋을 확인할 때 첫 번째 이메일만 사용됩니다.
  • GitLab 15.1 이전 버전에서 발급 기관 인증서의 X509v3 Subject Key Identifier(SKI) 및 서명 인증서의 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 버전을 git --version 명령어로 확인할 수 있습니다.

올바른 버전이 있다면 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. 인증서 ID를 실행하여 가져옵니다 smimesign --list-keys.
  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"
  1. GitLab에 푸시한 뒤, --show-signature 플래그로 커밋이 확인되었는지 확인하세요:
git log --show-signature
  1. 매번 커밋할 때 -S 플래그를 입력하지 않으려면, 다음 명령을 실행하여 Git에서 매번 커밋할 때 서명하도록 설정하세요:
git config --global commit.gpgsign true

태그 서명 및 확인

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

  1. Git 태그를 생성할 때 -s 플래그를 추가하세요:
git tag -s v1.1.1 -m "My signed tag"
  1. GitLab에 푸시한 후, 다음 명령으로 태그가 서명되었는지 확인하세요:
git tag --verify v1.1.1
  1. 매번 태그할 때 -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여야 합니다.

커밋이 확인되지 않음으로 표시되는 이유를 조사하려면:

  1. Rails 콘솔을 시작합니다:
sudo gitlab-rails console
  1. 조사 중인 프로젝트(경로 또는 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 콘솔을 종료하고 처음부터 확인을 다시 진행하세요.

  1. 커밋의 인증서를 확인합니다:
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로 실패하는 경우가 있습니다.

  1. 서명에 대한 암호학적 확인을 실행합니다. 코드는 true을 반환해야 합니다:
signature.verified_signature

false을 반환하면 이 확인을 더 조사하세요.

  1. 커밋과 서명의 이메일 주소가 일치하는지 확인합니다:
  • 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'

개발자의 이메일 주소가 목록에서 처음이 아닌 경우, 이 확인이 실패하여 커밋이 확인되지 않음으로 표시됩니다.

  1. 커밋의 이메일 주소가 GitLab의 계정과 연결되어 있는지 확인합니다. 이 확인은 false을 반환해야 합니다:
signature.user.nil?
  1. GitLab에서 사용자에게 이메일 주소가 연결되어 있는지 확인합니다. 이 확인은 사용자(#<User id:1234 @user_handle>)를 반환해야 합니다:
User.find_by_any_email(commit.committer_email)

nil을 반환하면, 이메일 주소가 사용자에게 연결되지 않고 확인이 실패합니다.

  1. 개발자의 이메일 주소가 확인되어 있는지 확인합니다. 이 확인은 true을 반환해야 합니다:
signature.user.verified_emails.include?(commit.committer_email)

이전 확인이 nil을 반환하면, 다음 명령은 오류를 표시합니다:

NoMethodError (undefined method `verified_emails' for nil:NilClass)
  1. 확인 상태가 데이터베이스에 저장됩니다. 데이터베이스 레코드를 표시하려면:
pp CommitSignatures::X509CommitSignature.by_commit_sha(commit.sha);nil

이전의 모든 확인이 올바른 값을 반환하면:

  • verification_status: "unverified"는 데이터베이스 레코드를 업데이트해야 함을 나타냅니다. Rake 작업을 사용하여 업데이트하십시오.

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

암호 검증 확인

만약 GitLab이 verified_signaturefalse임을 결정한다면, 레일즈 콘솔에서 해당 이유를 조사하십시오. 이러한 확인 작업은 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. 문제 해결을 위해 레일즈 콘솔에서 동적으로 추가적인 인증서를 추가할 수 있습니다.

      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 대신 사용된 -cert을 사용하여 수행됩니다 (-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 신뢰 저장소를 사용합니다. 신뢰 저장소에서 루트 인증서가 누락되었거나, 서명에 중간 인증서가 누락되어 신뢰할 수 있는 루트로의 체인을 만들지 못했습니다.
        • 서명에 포함할 수 없는 경우 중간 인증서를 신뢰 저장소에 추가할 수 있습니다.
        • 신뢰할 수 있는 인증서를 추가하는 절차 - /etc/gitlab/trusted-certs를 사용하여 패키지화된 GitLab에 대한 신뢰 저장소에.
      • OpenSSL로 추가 신뢰할 수 있는 인증서를 테스트합니다: -CAfile /path/to/rootcertificate.pem
    • unsupported certificate purpose:
      • 인증서는 서명자의 인증서의 X509v3 Key Usage 섹션에서 Digital Signature를 지정해야 합니다.
      • X509v3 Extended Key Usage (EKU) 섹션이 지정된 경우, emailProtection을 포함해야 합니다. 자세한 내용은 RFC 5280을 참조하세요.

        두 (Key Usage) 확장과 일치하는 용도가 없다면, 인증서는 어떤 용도에도 사용할 수 없습니다.

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

    • signer certificate not found, 다음 중 하나:
      • -nointern 인수는 추가했지만 -certfile을 제공하지 않은 경우입니다.
      • 서명자의 인증서가 포함되지 않은 경우입니다.