HTTPS

황서희·2024년 2월 16일
0
post-thumbnail

HTTPS

HTTPS는 HTTP의 요청과 응답을 암호화하여 전달하기 위해 사용한다. HTTP는 암호화 되어 있지 않으므로, 데이터가 중간에 가로채지거나 변조될 수 있는 취약점을 해결하기 위해 도입되었다. 또한 사용자간의 인증을 위해서도 사용한다. HTTPS는 공개 키 기반 구조(PKI)를 사용하여 신원을 확인하고 데이터를 암호화한다.

웹 사이트에 접속했을 때, 웹 사이트가 개인 키를 가지고 있으며, 브라우저는 해당 웹 사이트의 공개 키가 포함된 인증서를 검증한다. 인증서는 신뢰할 수 있는 인증 기관(CA)에 의해 발급된다. 브라우저는 인증서를 사용하여 웹 서버의 신원을 확인한다.

브라우저에 "등록"된 것은 실제로 인증서의 유효성을 확인하기 위한 루트 인증서 목록이다. 웹 서버의 인증서가 이 루트 인증서들 중 하나에 의해 서명되었다면, 브라우저는 그 인증서를 신뢰한다.

HTTPS는 HTTP 프로토콜 상위에 TLS 암호화를 구현한 것이라고 할 수 있다. TLS는 응용 계층과 전송 계층 사이에서 작동하여 안전한 보안 채널을 형성해 주는 역할을 한다.

HTTPS는 다음과 같이 동작한다:

  1. 핸드셰이크: 클라이언트가 서버에 접속하면, 서버는 자신의 공개 키를 포함한 인증서를 클라이언트에게 제공한다.
  2. 인증서 검증: 클라이언트는 인증서가 신뢰할 수 있는 CA(Certificate Authority)에 의해 발급되었는지 확인한다.
  3. 대칭 키 생성: 클라이언트는 대칭 키를 생성하고, 서버의 공개 키를 사용하여 이를 암호화하여 서버에게 보낸다.
  4. 암호화된 세션 시작: 서버는 개인 키를 사용해 대칭 키를 복호화하고, 이후의 통신은 이 대칭 키를 사용하여 암호화된다.

HTTPS의 장점은 다음과 같다.

  • 데이터 보안: 중간자 공격으로부터 사용자 데이터를 보호한다.
  • 데이터 무결성: 전송 중인 데이터가 변경되거나 손상되지 않았음을 보장한다.
  • 인증: 사용자가 실제로 의도한 서버와 통신하고 있음을 보증한다.

HTTP의 성능 향상을 목적으로 설계된 새로운 프로토콜인 HTTP/2는 대부분의 상황에서 HTTPS 연결을 전제로 한다. 이를 통해 웹 페이지 속도를 빠르게 하고, 다수의 리소스를 효율적으로 전송할 수 있도록 해 준다.

SSL / TLS

TLS는 넷스케이프의 SSL을 기반으로 한 보안 프로토콜이다. SSL과 TLS는 사실상 같은 것을 의미한다고 보면 된다. 주로 HTTP를 암호화할 때 사용한다. 인증서를 통해 상호가 적당한 사용자인지 검증하고, 암호화 알고리즘을 통해 암호화된 키를 교환한 후 암호화 통신을 진행한다.

TLS는 세 가지 역할을 한다.

  • 암호화: 제 3자로부터 전송되는 데이터를 숨긴다.
  • 인증: 정보를 교환하는 당사자가 요청된 당사자임을 보장한다.
  • 무결성: 데이터가 위조되거나 변조되지 않았는지 확인한다.

TLS는 공개키를 사용하여 암호화를 진행한다. (공개키 암호화) 공개 키는 서버의 인증서를 통해 클라이언트 장치와 공유된다. 인증서는 CA(인증 기관) 에 의해 암호화되어 서명되며, 브라우저에는 신뢰하는 CA의 목록이 있다. 이 CA의 목록에 저장되지 않은 곳에서 인증서가 발급되었다면, https가 신뢰되지 않는다고 뜬다. (그 빨간색 그거)

서버와 클라이언트는 교환된 공개 키와 개인 키를 통해 세션 키라는 새로운 키에 동의하여 통신을 암호화한다. 세션 키는 이 하나의 통신 세션만을 위해 존재하는 일회용 대칭 키이다.

TLS 프로토콜은 여러 버전(예: TLS 1.0, 1.1, 1.2, 1.3 등)이 있으며, 각각의 버전은 보안 강도와 호환성 측면에서 차이가 있다.

  • TLS 1.0
    • SSL의 직접적인 후속 버전이다. SSL의 보안 취약점을 많이 해결했지만, 현대의 기준으로는 여전히 취약점이 많다고 여겨진다.
  • TLS 1.1
    • TLS 1.0에 비해 몇 가지 보안 개선 사항을 도입했지만, 초기 버전의 취약점을 완전히 해결하지는 못했다.
  • TLS 1.2
    • SHA-256과 같은 강력한 해시 알고리즘을 지원하며, 안전하지 않은 암호화 알고리즘(예: RC4)의 사용을 피함. 현재까지도 널리 사용되고 있는 버전이다.
  • TLS 1.3
    • TLS 프로토콜의 최신 버전. TLS 1.2보다 더 빠르고, 더 안전하다. TLS 핸드셰이크의 작동 방식을 업데이트하여, 두 번이 아니라 한 번의 왕복만 하면 되도록 했다. 또한, 클라이언트가 이전의 웹 사이트에 접속한 적이 있는 경우 왕복 횟수를 0으로 해 HTTPS의 연결 속도가 빨라지도록 했다. 이를 통해 대기 시간이 대폭 단축되고 전반적인 사용자 경험이 개선되도록 했다.

TLS Handshake

TLS를 사용하는 통신 프로세스를 TLS Handshake 라고 한다. 서버와 클라이언트가 서로를 인식하고, 검증하고, 안전하게 통신할 수 있도록 하는 과정이다.

  1. ClientHello

    Handshake 프로세스는 클라이언트가 서버에게 Hello 메시지를 보내면서 시작된다. 이 때 메시지에는 클라이언트가 지원하는 TLS 버전, 클라이언트가 지원하는 암호화 방식, 세션을 재개하기 위한 ID(이전 연결에서 사용됨), 그리고 무작위 난수가 포함되어 있다.

  2. ServerHello

    서버는 ClientHello에 대해 ServerHello 메시지로 응답한다. 이 때 메시지에는 선택된 TLS 버전, 서버에서 선택한 암호화 방식, 세션 ID, 그리고 서버에서 생성한 무작위 난수가 포함되어 있다.

  3. Certificate

    서버는 자신의 인증서를 클라이언트에게 보내 클라이언트가 서버의 신뢰성을 검증할 수 있도록 한다. 인증서에는 서버의 공개 키와 인증서 발급 기관(CA)의 서명이 포함되어 있다. 클라이언트는 자신의 웹 브라우저에 등록된 “신뢰할 수 있는 인증서” 목록에 있는 공개 키로 인증서 해시 값을 비교하여 서버를 신뢰할 수 있는지 검증한다.

  4. ServerHelloDone

    서버가 인증서와 필요한 모든 메시지를 보낸 후, 서버는 ServerHelloDone 메시지를 클라이언트에게 전송하여 서버가 클라이언트에게 보낼 메시지를 모두 보냈다고 한다.

  5. ClientKeyExchange

    인증서의 무결성이 검증되었다면, 클라이언트는 클라이언트의 난수와 서버의 난수를 조합하여 대칭 키(세션 키)를 생성한다. 그리고 대칭 키를 서버의 공개 키로 암호화하고, 암호화된 키를 서버에 전송한다. 이를 통해 양측은 통신에 사용할 공유된 비밀을 가지게 된다.

    자세히 설명하자면, 클라이언트는 키 교환에 필요한 정보를 서버에 제공한다. 이 정보를 사전 마스터 시크릿(pre-master secret) 이라고 하며, 절대로 노출이 되어서는 안 된다. 이 값을 클라이언트가 서버의 공개 키로 암호화 하여 전송한다. 그러면 서버는 개인 키로 복호화하고, 이렇게 되면 서로가 사전 마스터 시크릿을 공유한다. 이 값을 사용해서 세션에 사용할 키를 만드는 것이다.

  6. ChangeCipherSpec

    양측은 이후의 메시지가 협상된 암호화 설정을 사용하여 전송될 것임을 알린다.

  7. Finished

    핸드셰이크 과정 동안 교환된 모든 메시지의 무결성을 검증하며, 이 시점부터 양측은 안전한 통신을 시작한다.

TLS Termination

클라이언트-인터넷망-서비스-파드로 연결된 구조에서, 클라이언트부터 파드까지 end-to-end로 https 암호화가 걸린다. 문제는 서버 입장에서 end-to-end 암호화는 전혀 안전하지 않다. https는 클라이언트가 서버를 인증하는 과정인데, 서버 입장에서는 클라이언트를 확인하지 못한다(인증을 할 수 없다). 따라서 서버 입장에서는 클라이언트의 요청을 수행할 수 밖에 없는데, 악성 요청도 그대로 수행하게 된다.

이를 방지하기 위해서 쓰이는 것이 WAF지만, 결국 암호화 된 데이터를 중간에서 풀어볼 수 없어 공격인지 아닌지 확인할 수 없다. 이를 위해서 사용하는 것이 TLS Termination Proxy이다. 클라이언트와 프록시 서버 간에 HTTPS 연결을 종료하고, 프록시 서버와 내부 서비스 간에는 HTTP 또는 새로운 HTTPS 연결을 사용하는 방식이다. 프록시 서버에서 암호화를 해제하여 요청을 검사할 수 있으며, 내부 네트워크에서는 HTTPS가 필요하지 않거나, 필요한 경우 새로운 HTTPS 세션을 시작할 수 있다.

클라이언트와 서버 사이에 프록시 서버가 있다. 프라이빗 네트워크 안에서는 사실 접근 제어만 잘 된다는 가정 하에 https로 통신할 필요가 없다. 프록시 서버에서 tls 종료 프록시를 통해 https를 종료시키고, 내부에서는 http를 종료한 상태에서 통신한다. 내부적으로 https 통신을 한다고 해도, tls 통신 프록시를 통해 한 번 종료시키고 다시 https로 통신한다.

쿠버네티스 환경에서는 ingress를 사용하여 쉽게 TLS Termination Proxy를 구현할 수 있다.

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: myapp-ing-tls-term
spec:
  tls:
  # - hosts:
    # - myapp.example.com #실습할 때는 없애는 것이 편함
    secretName: myapp-tls-secret #이곳의 인증서와 키를 가져온다.
  rules:
  # - host: myapp.example.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend: 
          service:
            name: myapp-svc-np
            port: 
              number: 80

예시는 클라이언트와 Ingress 간의 HTTPS 연결을 종료하고, Ingress와 내부 서비스 간에는 HTTP를 사용하도록 설정하는 방법을 보여준다. 이 설정을 통해 Ingress에서 TLS 인증서를 관리하며, 내부 파드는 HTTPS 관련 설정이나 키를 관리할 필요가 없게 된다. 암호화 통신은 클라이언트와 인그레스 사이에서만 하기 때문에 파드의 nginx는 암호를 위한 설정과 인증키가 필요 없기 때문이다. 따라서 사용자가 훨씬 유연하게 사용할 수 있다.

PKI

PKI는 공개 키 기반 구조(Public Key Infrastructure)를 의미한다. PKI는 디지털 인증서와 공개 키 암호화를 활용하여 인터넷 상에서 사용자, 서버 및 조직의 신원을 안전하게 인증하고, 데이터의 무결성 및 기밀성을 보장한다.

기존의 암호화는 대칭 키(암호화 키와 복호화 키가 같음) 알고리즘을 사용하여 정보를 교환했다. 대칭 키 암호화는 구현이 빠르고 속도가 빠르다는 장점이 있지만, 키를 스니핑(패킷을 엿보는 행위)나 스푸핑(속여서 공격) 등의 해킹 기법에 당하기 쉽다는 단점이 있었다. 이를 해결하기 위해 암호화 키와 복호화 키를 따로 가져가는 기술이 등장했고, 이에 사용하는 것이 PKI 이다.

PKI를 이용한 정보 교환은 두 가지 상황을 가정할 수 있다:

  • 공개 키를 사용해 암호화
    • 공개 키를 사용하여 암호화하면 공개 키에 매칭되는 개인 키를 가지고 있는 상대방만 개인 키를 사용해 복호화 할 수 있다.
  • 개인 키를 사용해 암호화
    • 개인 키를 사용해 암호화하면 공개 키를 이용해 누구나 복호화할 수 있다. 이는 자신의 자격을 증명하는 데에 주로 사용된다.

공개 키의 경우에는 공개되어 있기 때문에, 해당 키가 신뢰할 수 있는 키인지 확인하기 어렵다. 이 때 사용되는 것이 바로 인증 기관(CA) 이다. CA는 인증서를 발행하고 관리하는 공개 키에 대한 공신력 있는 기관이다. 인증 정책을 수립하고 사용자의 공개 키와 신원 정보를 결합하여 인증서를 발급하며, 이를 통해 사용자의 신원을 보증한다.

  • RA(등록 기관) 이라는 것도 있다. 등록 기관은 CA의 일 중 공개 키의 등록과 본인에 대한 인증을 대행한다.
  • 인증서를 보존해 두고, PKI의 이용자가 인증서를 입수할 수 있도록 한 데이터베이스를 저장소라고 한다. 전화번호부와 같은 역할을 한다고 볼 수 있다.

CA는 개인 키와 공개 키 쌍을 만들고, 개인 키에 대한 인증서를 발급한다. 이러한 인증서들의 표준 규칙이 X.509 형식이다. 이 때 인증서의 최상위에 존재하는, 일반적으로 웹 브라우저에 내장되어 있는 ‘누구나 신용할 만한’ 인증서 발급 기관에서 발급한 인증서들을 Root CA라고 한다.

Chain of Trust

인증서는 트리 구조를 가진다. 예를 들어, 네이버의 인증서 구조를 보자.

네이버는 Root CA인 DigiCert로 부터 인증받은 하위 인증서인 DigiCert TLS~CA1 으로부터 인증을 받았다. 보통은 이처럼 3계층 구조로 이루어진다. DigiCert가 Root CA이고, DigiCert TLS~CA1 역시 인증기관이다. 이것을 중간 인증 기관(Intermidiate CA) 라고 부른다. 이 ICA에 대한 신뢰를 입증하기 위해 다음과 같은 구성을 한다.

  1. 회사가 Root CA에 인증서 발급 요청을 하면, 인증서의 해시 값을 Root CA의 비밀 키로 암호화한다.
  2. 이러면 회사의 인증서 값은 Root CA의 공개 키로 복호화 할 수 있다. 복호화한 값이 Root CA 인증서의 값과 다르다면 인증서의 내용이 변조되었음을 의미한다.
  3. 클라이언트는 제공된 최종 인증서부터 시작하여, 각 중간 인증서를 거슬러 올라가면서 루트 인증서에 도달할 때까지 각 인증서의 유효성을 검증한다. 각 인증서는 바로 상위 인증서에 의해 서명되어 있어야 하며, 상위 인증서의 공개 키로 하위 인증서의 서명을 검증할 수 있다.
  4. 마지막 단계에서는 루트 인증서가 클라이언트 소프트웨어에 사전 설치된 신뢰할 수 있는 루트 인증서 목록 중 하나와 일치하는지 확인한다.
  5. 상위기관의 공개키로 하위기관의 인증서 해시값을 복호화함으로써 쉽게 변조 유무를 확인할 수 있으므로, 하위 기관을 신뢰할 수 있다고 간주한다.

이처럼, X.509 인증서는 신뢰 체인을 구성할 수 있다. 최상위의 루트 CA가 중간 CA의 인증서에 서명하고, 이 중간 CA는 다시 사용자 또는 서버의 인증서에 서명한다. 이 체인을 통해 최종 사용자의 인증서가 루트 CA에 의해 간접적으로 신뢰된다. 이것을 Chain of Trust라고 부른다.

X.509

X.509는 ITU-T에서 책정한 표준 형식이다. X.509는 매우 엄격한 수직 구조를 띄고 있어, 하나의 인증 기관(Root CA)를 정점으로 하는 트리 구조를 띄고 있다.

X.509 규격의 인증서는 공개되어 있고, OpenSSL 등을 통해 누구나 만들 수 있다. 이러면 인증서에 대한 신뢰의 문제가 발생한다. 이래서 등장한 것이 바로 CA라고 볼 수 있다.

X.509 인증서는 다음과 같은 주요 구성 요소를 포함한다:

  • Version 인증서의 버전. v3 가 가장 널리 사용되는 버전이다
  • Serial Number 인증서를 식별하기 위한 고유 번호
  • Algorithm Identifier 알고리즘 식별자
  • Signature 서명
  • Issuer 인증서를 발급한 CA의 이름
  • Validity 인증서의 유효 기간
    • Not Before 유효 기간이 시작하는 날짜
    • Not After 유효 기간이 끝나는 날짜
  • Subject 인증서를 소유하고 있는 개인, 조직, 장치 등의 이름
  • Subject Public Key Info 소유자 공개 키 정보. 공개 키 알고리즘과 공개 키를 포함한다
  • Issuer Unique Identifier (Optional) 증서의 발급자를 고유하게 식별하는 데 사용된다
  • Subject Unique Identifier (Optional) 인증서의 주체를 고유하게 식별하는 데 사용된다
  • Extensions (Optional) 키 사용법, 인증서 정책, 대체 이름 등 추가적인 정보를 제공한다.

OpenSSL로 인증서 설정해 보기

HAProxy가 설치되어 있다는 것을 전제로 한다.

인증서의 경로는 /etc/haproxy/tls 에 있다.

openssl genrsa -out test.com.key 2048
openssl req -new -key test.com.key -out test.com.csr
openssl x509 -req -days 365 -in test.com.csr -signkey test.com.key -out test.com.crt

를 통해 crt 파일을 생성한다.

openssl rsa -in test.com.key -text > key.pem
openssl x509 -inform PEM -in test.com.crt > crt.pem
openssl pkcs12 -export -in test.com.crt -inkey test.com.key -out cert.p12
openssl pkcs12 -in cert.p12 -nodes -out cert.pem

.pem 파일로 만든다.

vi /etc/haproxy/haporxy.cfg

global
ssl-default-bind-options no-sslv3
ssl-default-bind-ciphers ECDH+AESGCM:DH+AESGCM:ECDH+AES256:DH+AES256:ECDH+AES128:DH+AES:ECDH+3DES:DH+3DES:RSA+AESGCM:RSA+AES:RSA+3DES:!aNULL:!MD5:!DSS
ssl-default-server-options no-sslv3
ssl-default-server-ciphers ECDH+AESGCM:DH+AESGCM:ECDH+AES256:DH+AES256:ECDH+AES128:DH+AES:ECDH+3DES:DH+3DES:RSA+AESGCM:RSA+AES:RSA+3DES:!aNULL:!MD5:!DSS
tune.ssl.cachesize 100000
tune.ssl.lifetime 600
tune.ssl.default-dh-param 2048

frontend https
bind *:443 ssl crt /etc/haproxy/tls/cert.pem
default_backend https_test
option forwardfor

backend https_test
balance roundrobin
server app1 192.168.0.26:80 check
server app2 192.168.0.36:80 check

정상적으로 설정되었는지 확인한다.

haproxy -f /etc/haproxy/haproxy.cfg -c

configure valid ok가 나오면 완료.
profile
다 아는 건 아니어도 바라는 대로

0개의 댓글