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

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

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

  • X.509의 경우 루트 인증 기관이 GitLab 신뢰 저장소에 추가됩니다. (신뢰 저장소는 신뢰할 수 있는 보안 인증서의 저장소입니다.) 개발자 인증서가 신뢰된 루트 인증서로 연결되도록 필요한 중간 인증서와 결합되면 GitLab은 이를 신뢰할 수 있습니다.
  • GPG의 경우 개발자가 계정에 GPG 키를 추가합니다.

GitLab은 고유의 인증서 저장소를 사용하며 따라서 신뢰 체인을 정의합니다. GitLab에서 검증되기 위해 커밋 또는 태그에 대한 다음 조건이 충족되어야 합니다: - 서명 인증서 이메일은 GitLab의 확인된 이메일 주소와 일치해야 합니다. - GitLab 인스턴스는 서명에 사용된 인증서에서 시작하여 GitLab 인증서 저장소의 신뢰할 수 있는 인증서에 이르는 완전한 신뢰 체인을 설정해야 합니다. 이 신뢰 체인에는 서명에서 제공된 중간 인증서를 포함할 수 있습니다. 인증서 관리자 루트 인증서와 같은 인증서를 GitLab 인증서 저장소에 추가해야 할 수 있습니다. - 서명 시간은 일반적으로 최대 세 년까지인 인증서 유효 기간 내에 있어야 합니다. - 서명 시간은 커밋 시간과 동일하거나 그 이후여야 합니다.

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

제한 사항

  • authorityKeyIdentifier, subjectKeyIdentifier, crlDistributionPoints가 없는 인증서는 Unverified로 표시됩니다. RFC 5280과 일치하는 PKI의 인증서를 사용하는 것을 권장합니다.
  • GitLab 16.2 및 이전 버전에서 서명 인증서의 Subject Alternative Name 목록에 이메일이 하나 이상 있는 경우, 첫 번째 이메일만 사용되어 커밋이 검증됩니다.
  • 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 버전은 git --version 명령으로 확인할 수 있습니다.

적절한 버전이 있는 경우, 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
    

커밋 서명과 확인

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

  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
    

태그 서명과 확인

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

  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 태스크를 사용할 수 있습니다.

수정 사항을 적용한 후에 다음 명령어를 실행하세요:

sudo gitlab-rake gitlab:x509:update_signatures

주요 확인 사항

코드는 다음의 주요 확인 사항들을 수행합니다 (https://gitlab.com/gitlab-org/gitlab/-/blob/v14.1.0-ee/lib/gitlab/x509/signature.rb#L33):

  • 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
    
  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가 존재해야합니다. 이전 main verification checkssignature 단계를 참조하세요.

  1. 확인을 위해 서명을 확인하세요(issuer 확인 없이):

    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. 추가한 후, (만약 이러한 문제 해결 단계를 통과한다면) 커밋 재확인 작업을 실행하세요.

    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. 이전 main verification checks의 초기 두 단계는 필요하므로 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
    

이 데이터는 이제 OpenSSL을 사용하여 Linux 명령줄에서 조사할 수 있습니다:

  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 신뢰 저장소를 사용합니다. 신뢰 저장소에서 루트 인증서가 누락되었거나 서명에 중간 인증서가 누락되어 신뢰할 수 있는 루트로의 체인을 구축할 수 없습니다.
        • 서명에 포함되지 않으므로 중간 인증서를 신뢰 저장소에 넣을 수 있습니다.
        • 인증서 추가 절차/etc/gitlab/trusted-certs를 사용하여 패키지화된 GitLab을 위해 사용하세요.
      • OpenSSL을 사용하여 추가 신뢰할 수 있는 인증서를 테스트하세요: -CAfile /path/to/rootcertificate.pem
    • unsupported certificate purpose:
      • 인증서는 반드시 Digital Signature를 지정해야합니다.
      • 이것은 보통 서명자의 인증서의 X509v3 Key Usage 섹션에 있습니다.
      • 또한 X509v3 Extended Key Usage 섹션이 있습니다: 여기에 지정된 경우 반드시 Digital Signature를 포함해야합니다. 더 자세한 내용은 RFC 5280을 참조하세요:

        두 (Key Usage) 확장명 모두와 일치하는 용도가 없으면 인증서를 어떤 목적으로도 사용할 수 없습니다.

    • signer certificate not found, 아래 중 하나:
      • -nointern 인자를 추가했지만 -certfile을 제공하지 않았습니다.
      • 서명에 서명자의 인증서가 누락되었습니다.