์ž์ฒด ์„œ๋ช…ํ•œ ์ธ์ฆ์„œ๋กœ FastAPI์— Warning ์—†์ด SSL ์ ์šฉํ•˜๊ธฐ!

6 minute read

๋“ค์–ด๊ฐ€๋ฉฐ

โ€œHello, OpenSSL!โ€ ํฌ์ŠคํŠธ์—์„œ ์ž ๊น ์–ธ๊ธ‰ํ•˜๊ธฐ๋„ ํ–ˆ์ง€๋งŒ, SSL๋กœ ํ†ต์‹ ํ•˜๋Š” ํด๋Ÿฌ์Šคํ„ฐ๋ฅผ ๊ตฌ์„ฑํ•  ๋•Œ ์ž์ฒด ์„œ๋ช… ์ธ์ฆ์„œ๋ฅผ ๋ฃจํŠธ CA๋กœ ์‚ฌ์šฉํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. ์ด ๊ณผ์ •์„ ์ข€๋” ์‚ดํŽด๋ด…์‹œ๋‹ค!

์šฐ๋ฆฌ์˜ ๋ชฉํ‘œ๋Š” ๋กœ์ปฌ์—์„œ ์‹คํ–‰ํ•˜๋Š” FastAPI์— ์ ‘์†ํ–ˆ์„ ๋•Œ, SSL๋กœ ํ†ต์‹ ํ•˜๋„๋ก ์„ธํŒ…ํ•˜๋Š” ๊ฒƒ ์ž…๋‹ˆ๋‹ค. ๋ธŒ๋ผ์šฐ์ €์— ์ ‘์†ํ–ˆ์„ ๋•Œ, ์•„๋ž˜์™€ ๊ฐ™์ด ์•„๋ฌด๋Ÿฐ ๊ฒฝ๊ณ ๊ฐ€ ๋œจ์ง€ ์•Š์•„์•ผ ํ•ฉ๋‹ˆ๋‹ค.

์ผ๋‹จ FastAPI ์•ฑ์„ ์•„๋ž˜์™€ ๊ฐ™์ด ์ž‘์„ฑ ํ•ฉ๋‹ˆ๋‹ค.

from fastapi import FastAPI
from fastapi.responses import PlainTextResponse
import uvicorn

app = FastAPI()

@app.get("/", response_class=PlainTextResponse)
def read_root():
    return "Hello, World!"

if __name__ == "__main__":
    uvicorn.run("main:app", host="0.0.0.0", port=8443)

๊ทธ๋ฆฌ๊ณ  ์‹คํ–‰์„ ์œ„ํ•ด pip install fastapi uvicorn์œผ๋กœ ์„ค์น˜๋„ ํ•ฉ๋‹ˆ๋‹ค.

์ง€๊ธˆ ์ƒํƒœ๋Š” HTTP๋กœ ํ†ต์‹ ํ•˜๋Š” FastAPI ์ž…๋‹ˆ๋‹ค.

์ž์ฒด ์„œ๋ช…ํ•œ ์ธ์ฆ์„œ๋ฅผ ์‚ฌ์šฉํ•˜๊ธฐ

๊ฐ€์žฅ ๋จผ์ € self-signed ์ธ์ฆ์„œ๋ฅผ ์‚ฌ์šฉํ•ด SSL์„ ๊ตฌ์„ฑํ•ด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

# ๊ฐœ์ธํ‚ค ์ƒ์„ฑ
$ openssl genrsa -out server.key 2048

# ์ธ์ฆ์„œ ์„œ๋ช… ์š”์ฒญ ์ƒ์„ฑ
$ openssl req -new -key server.key -out server.csr \
  -subj "/CN=localhost"

# ์ž์ฒด ์„œ๋ช… ์ธ์ฆ์„œ ์ƒ์„ฑ
$ openssl x509 -req -in server.csr -days 365 \
    -signkey server.key \
    -out server.crt

์ด์ œ ์ด ์ธ์ฆ์„œ๋ฅผ ์‚ฌ์šฉํ•˜๋„๋ก FastAPI ์ฝ”๋“œ๋ฅผ ์•ฝ๊ฐ„ ์ˆ˜์ • ํ•ฉ๋‹ˆ๋‹ค.

# ๋ณ€๊ฒฝ ์ „
if __name__ == "__main__":
  uvicorn.run(
    "main:app", host="0.0.0.0", port=8443
  )

# ๋ณ€๊ฒฝ ํ›„
if __name__ == "__main__":
  uvicorn.run(
    "main:app", host="0.0.0.0", port=8443,
    ssl_keyfile="ssl/server.key", ssl_certfile="ssl/server.crt"
  )

python3 main.py๋กœ FastAPI๋ฅผ ์‹คํ–‰ํ•˜๊ณ  ๋ธŒ๋ผ์šฐ์ €์— ์ ‘์†ํ•ด๋ณด๋ฉดโ€ฆ

์ด๋Ÿฐโ€ฆ Warning์ด ๋œจ๊ณ  ๋ง™๋‹ˆ๋‹คโ€ฆ ๐Ÿคฆ ์ด์œ ๋Š” ํฌ๋กฌ ๋ธŒ๋ผ์šฐ์ €๊ฐ€ ์œ ์ €๋“ค์˜ ์•ˆ์ „ํ•œ ์ธํ„ฐ๋„ท ์ด์šฉ์„ ์œ„ํ•ด ์ž์ฒด ์„œ๋ช…ํ•œ ์ธ์ฆ์„œ๋ฅผ ์‹ ๋ขฐํ•˜์ง€ ์•Š๋„๋ก ๋˜์–ด ์žˆ๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค.

โ€œAdvancedโ€ ๋ฒ„ํ„ด์„ ๋ˆ„๋ฅด๊ณ , ๊ณ„์† ์ง„ํ–‰ํ•˜๊ธฐ๋ฅผ ํ•œ๋‹ค๋ฉด HTTPS๋กœ ์ ‘์†์€ ๊ฐ€๋Šฅํ•˜์ง€๋งŒ,

์ด๋ ‡๊ฒŒ ์ฃผ์†Œ์ฐฝ์— Not Secure๋ผ๋Š” ๊ฒฝ๊ณ  ๋ฌธ๊ตฌ๋ฅผ ๊ณ„์† ๋ด์•ผ ํ•ฉ๋‹ˆ๋‹ค ๐Ÿ˜ต

๋ฃจํŠธ CA๊ฐ€ ์„œ๋ช…ํ•œ ์ธ์ฆ์„œ๋ฅผ ์‚ฌ์šฉํ•˜๊ธฐ

๋กœ์ปฌ์—์„œ ์ž์ฒด ์„œ๋ช…ํ•œ ์ธ์ฆ์„œ๋กœ๋„ FastAPI ์•ฑ์„ Not Secure๋กœ ๋„ˆ๋ฌด๋„ˆ๋ฌด ๋„์›Œ๋ณด๊ณ  ์‹ถ์—ˆ์Šต๋‹ˆ๋‹คโ€ฆ! ๊ทธ๋ž˜์„œ ์—ด์‹ฌํžˆ ์ฐพ๊ณ , ๋˜ GPT์˜ ๋„์›€์„ ๋ฐ›์•„์„œ ๊ฒจ์šฐ ๋ฐฉ๋ฒ•์„ ์ฐพ์•„๋ƒˆ์Šต๋‹ˆ๋‹ค ใ…Žใ…Ž

์ผ๋‹จ ํฌ๊ฒŒ 2๊ฐ€์ง€๋ฅผ ์ง„ํ–‰ํ•ด์•ผ ํ•˜๋Š”๋ฐ,

  1. ๋ฃจํŠธ CA์˜ ์ธ์ฆ์„œ๋ฅผ ๋ฐœ๊ธ‰ํ•˜๊ณ , ์ด๊ฒƒ์œผ๋กœ ์„œ๋ฒ„ ์ธ์ฆ์„œ๋ฅผ ์„œ๋ช…ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
    • ์ด๋•Œ, ๋ฃจํŠธ CA์˜ ์ธ์ฆ์„œ๋Š” ์ž์ฒด ์„œ๋ช…์ด๊ณ , ์„œ๋ฒ„ ์ธ์ฆ์„œ๋Š” ์ž์ฒด ์„œ๋ช…์ด ์•„๋‹™๋‹ˆ๋‹ค. (๋ฃจํŠธ CA๊ฐ€ ์„œ๋ช…)
  2. ํฌ๋กฌ ๋ธŒ๋ผ์šฐ์ €์— ๋ฃจํŠธ CA์˜ ์ธ์ฆ์„œ๋ฅผ ๋“ฑ๋ก

๋ฃจํŠธ CA์˜ ์ธ์ฆ์„œ๋ฅผ ๋ฐœ๊ธ‰ํ•˜๊ณ , ์ด๊ฑธ๋กœ ์„œ๋ช…ํ•˜๊ธฐ

์ž์ฒด ์„œ๋ช… ์ธ์ฆ์„œ๋ฅผ ์ง์ ‘ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์€ ๊ถŒ์žฅ ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ์ž์ฒด ์„œ๋ช… ์ธ์ฆ์„œ๋Š” ๋ฃจํŠธ CA๋ฅผ ๊ตฌ์„ฑํ•˜๋Š” ์šฉ๋„๋กœ ์‚ฌ์šฉํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

๋ฃจํŠธ CA๋Š” ๋”์ด์ƒ ์ƒ์œ„ ์ธ์ฆ ๊ธฐ๊ด€์ด ์—†๊ธฐ ๋•Œ๋ฌธ์—, ์–ด์ฉ” ์ˆ˜ ์—†์ด ์ž์ฒด ์„œ๋ช…ํ•œ ์ธ์ฆ์„œ๋ฅผ ๋งŒ๋“ค๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.

# ๋ฃจํŠธ CA์˜ ๊ฐœ์ธํ‚ค
$ openssl genrsa -out rootCA.key 4096

# ๋ฃจํŠธ CA์˜ ์ธ์ฆ์„œ ์ƒ์„ฑ (self-signed)
$ openssl req -x509 -new -key rootCA.key -sha256 -days 3650 -out rootCA.crt \
  -subj "/C=KR/ST=Seoul/L=Seoul/O=MyOrg/OU=Dev/CN=MyLocalRootCA"

์ด์ œ ์„œ๋ฒ„์—์„œ ์‚ฌ์šฉํ•  ์ธ์ฆ์„œ์™€ ์ธ์ฆ์„œ ์„œ๋ช… ์š”์ฒญ์„ ๋งŒ๋“ค์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

# ์„œ๋ฒ„์šฉ ๊ฐœ์ธํ‚ค
$ openssl genrsa -out server.key 2048

# ์„œ๋ฒ„์šฉ ์ธ์ฆ์„œ ์„œ๋ช… ์š”์ฒญ
$ openssl req -new -key server.key -out server.csr \
  -subj "/C=KR/ST=Seoul/L=Seoul/O=MyOrg/OU=Dev/CN=localhost"

๊ทธ๋ฆฌ๊ณ  ์„œ๋ฒ„ ์ธ์ฆ์„œ์— ๋Œ€ํ•œ SAN(Subject Alternative Name)์„ ์„ค์ •ํ•˜๊ธฐ ์œ„ํ•ด server.ext ํŒŒ์ผ์„ ์•„๋ž˜์™€ ๊ฐ™์ด ์ƒ์„ฑ ํ•ฉ๋‹ˆ๋‹ค.

authorityKeyIdentifier=keyid,issuer
basicConstraints=CA:FALSE
keyUsage = digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment
subjectAltName = @alt_names

[alt_names]
DNS.1 = localhost
IP.1 = 127.0.0.1

๋งˆ์ง€๋ง‰์œผ๋กœ ์•„๋ž˜ ๋ช…๋ น์–ด๋กœ ์„œ๋ฒ„์˜ ์ธ์ฆ์„œ๋ฅผ ์ƒ์„ฑํ•ด์ค๋‹ˆ๋‹ค!

openssl x509 -req -in server.csr -CA rootCA.crt -CAkey rootCA.key -CAcreateserial \
-out server.crt -days 365 -sha256 -extfile server.ext

์ €๋Š” ์ด๊ฑธ ์ง„ํ–‰ํ•˜๋ฉด์„œ SAN ์„ค์ •์ด ๊ผญ ํ•„์š”ํ•œ์ง€ ๊ทธ๋ฆฌ๊ณ  server.ext ํŒŒ์ผ์ด ๊ผญ ํ•„์š”ํ•œ์ง€ ์˜๋ฌธ์ด์—ˆ๋Š”๋ฐ์š”. ์œ„์˜ ๋ช…๋ น์–ด์—์„œ -extfile ๋ถ€๋ถ„์„ ๋นผ๊ณ  ๊ฐ™์€ ๊ณผ์ •์„ ์ง„ํ–‰ํ•˜๋ฉด, ํ›„์† ๊ณผ์ •์„ ์ œ๋Œ€๋กœ ์ง„ํ–‰ํ•ด๋„ ์„œ๋ฒ„ ์ธ์ฆ์„œ๋ฅผ ์‚ฌ์šฉํ•  ๋•Œ ์—ฌ์ „ํžˆ ์—๋Ÿฌ๋ฅผ ๊ฒช๊ฒŒ ๋ฉ๋‹ˆ๋‹ค ๐Ÿ˜…

ํฌ๋กฌ ๋ธŒ๋ผ์šฐ์ €์— ๋ฃจํŠธ CA์˜ ์ธ์ฆ์„œ๋ฅผ ๋“ฑ๋ก

์ด์ œ ํฌ๋กฌ ๋ธŒ๋ผ์šฐ์ €๊ฐ€ ์ œ๊ฐ€ ์ž์ฒด ์„œ๋ช…ํ•œ ๋ฃจํŠธ CA ์ธ์ฆ์„œ๋ฅผ ์‹ ๋ขฐํ•˜๋„๋ก ์„ค์ •ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

chrome://certificate-manager (๋งํฌ ๋™์ž‘์ด ์•ˆ ๋˜์–ด์„œ ์ง์ ‘ ์ž…๋ ฅ ํ•˜์‹œ๊ธธ!)

์š” ์ฃผ์†Œ๋กœ ์ ‘์†ํ•˜๋ฉด, ํฌ๋กฌ ๋ธŒ๋ผ์šฐ์ €์—์„œ ํ˜„์žฌ ์‹ ๋ขฐํ•˜๊ณ  ์žˆ๋Š” CA ์ธ์ฆ์„œ ๋ชฉ๋ก์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์ €๋Š” MacOS์˜ Keychain Access์— ๋“ฑ๋ก๋œ ์ธ์ฆ์„œ๊ฐ€ ๋ธŒ๋ผ์šฐ์ €์—๋„ ๋™์ผํ•˜๊ฒŒ ์‹ ๋ขฐ ํ•˜๋„๋ก ์„ค์ • ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค.

๊ทธ๋ฆฌ๊ณ  โ€œCustomโ€ ํ•ญ๋ชฉ์—๋„ ๋ณด์ด๋“ฏ ์ œ๊ฐ€ ๋ฐœ๊ธ‰ํ•œ ๋ฃจํŠธ CA์˜ ์ธ์ฆ์„œ๋ฅผ ์ด๊ณณ์— ๋“ฑ๋ก ํ–ˆ์Šต๋‹ˆ๋‹ค.

์—ฌ๊ธฐ๊นŒ์ง€ ํ•˜๊ณ , FastAPI๋ฅผ ์‹คํ–‰ํ•œ ํ›„ ํฌ๋กฌ ๋ธŒ๋ผ์šฐ์ €์— ์ ‘์†ํ•ด๋ณด๋ฉดโ€ฆ

์งœ์ž”!! ์ด์ œ๋Š” ๋กœ์ปฌ ์ธ์ฆ์„œ๋ฅผ ์‚ฌ์šฉํ•ด๋„ ๋”์ด์ƒ Not Secure ๋ฌธ๊ตฌ๊ฐ€ ๋ณด์ด์ง€ ์•Š์Šต๋‹ˆ๋‹ค!! ๐Ÿฅณ

ํฌ๋กฌ ๋ธŒ๋ผ์šฐ์ €์—์„œ๋งŒ ๋ฉ๋‹ˆ๋‹ค!

๋‹น์—ฐํ•˜๊ฒ ์ง€๋งŒ, ๋กœ์ปฌ์˜ ๋ฃจํŠธ CA ์ธ์ฆ์„œ๋ฅผ ํฌ๋กฌ ๋ธŒ๋ผ์šฐ์ €์— ๋“ฑ๋กํ•œ ๊ฒƒ์ด๊ธฐ ๋•Œ๋ฌธ์— ๋‹ค๋ฅธ ์‹œ์Šคํ…œ์—์„œ๋Š” SSL ์ธ์ฆ์ด ์ œ๋Œ€๋กœ ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

์˜ˆ๋ฅผ ๋“ค์–ด ์•„๋ž˜์™€ ๊ฐ™์ด ํ„ฐ๋ฏธ๋„์—์„œ curl ํ†ตํ•ด์„œ ์ ‘์†์„ ํ•ด๋ด๋„

$ curl -X GET https://localhost:8443/
curl: (60) SSL certificate problem: unable to get local issuer certificate
More details here: https://curl.se/docs/sslcerts.html

curl failed to verify the legitimacy of the server and therefore could not
establish a secure connection to it. To learn more about this situation and
how to fix it, please visit the web page mentioned above.

์ธ์ฆ์„œ๋ฅผ ์‹ ๋ขฐํ•˜์ง€ ๋ชปํ•˜๊ณ  ์—๋Ÿฌ๋ฅผ ๋ฟœ์Šต๋‹ˆ๋‹ค!! ์ด๋ ‡๋“ฏ ํ˜„์žฌ ๊ตฌ์„ฑ์€ ์˜ค์ง ํฌ๋กฌ ๋ธŒ๋ผ์šฐ์ € ํ™˜๊ฒฝ์—์„œ๋งŒ ๋™์ž‘ํ•˜๋Š”, ๋ฐ˜์ชฝ์งœ๋ฆฌ ์†”๋ฃจ์…˜ ์ž…๋‹ˆ๋‹ค.

Keychain Access์— ๋“ฑ๋กํ•˜๊ธฐ

TODO: ์š”์ „ ๋‚ด์ผ ์ด์–ด์„œ ์ž‘์„ฑํ•ด๋ณด๊ฒ ์Šต๋‹ˆ๋‹คโ€ฆ ใ…Žใ…Ž

Categories:

Updated: