FastAPI with Self-signed SSL Certificate
๋ค์ด๊ฐ๋ฉฐ
โ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๊ฐ์ง๋ฅผ ์งํํด์ผ ํ๋๋ฐ,
- ๋ฃจํธ CA์ ์ธ์ฆ์๋ฅผ ๋ฐ๊ธํ๊ณ , ์ด๊ฒ์ผ๋ก ์๋ฒ ์ธ์ฆ์๋ฅผ ์๋ช
ํด์ผ ํฉ๋๋ค.
- ์ด๋, ๋ฃจํธ CA์ ์ธ์ฆ์๋ ์์ฒด ์๋ช ์ด๊ณ , ์๋ฒ ์ธ์ฆ์๋ ์์ฒด ์๋ช ์ด ์๋๋๋ค. (๋ฃจํธ CA๊ฐ ์๋ช )
- ํฌ๋กฌ ๋ธ๋ผ์ฐ์ ์ ๋ฃจํธ 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: ์์ ๋ด์ผ ์ด์ด์ ์์ฑํด๋ณด๊ฒ ์ต๋๋คโฆ ใ ใ