ผลต่างระหว่างรุ่นของ "01204223/secrets"

จาก Theory Wiki
ไปยังการนำทาง ไปยังการค้นหา
 
(ไม่แสดง 10 รุ่นระหว่างกลางโดยผู้ใช้คนเดียวกัน)
แถว 1: แถว 1:
 +
: ''หน้านี้เป็นส่วนของวิชา [[01204223]]''
 +
 
เราจะฝึกจัดการกับข้อมูลที่เป็นความลับในการพัฒนาเว็บแอพ
 
เราจะฝึกจัดการกับข้อมูลที่เป็นความลับในการพัฒนาเว็บแอพ
  
ก่อนอื่นอ่านภาพรวมในการจัดการก่อน
+
ก่อนอื่นอ่านภาพรวมในการจัดการก่อน อ่านที่นี่: [[01204223/secrets overview|แนวทางทั่วไปในการจัดการกับความลับ]]
  
== แนวปฏิบัติทั่วไปในการจัดการ ''secrets'' (รหัสผ่าน / API keys) สำหรับเว็บแอป Flask + React ==
+
เราได้ส่งบัญชีและรหัสผ่านผู้ใช้ mysql บนเครื่อง p1.secondtrain.org แล้ว  เราจะเริ่มโดยย้ายฐานข้อมูลของ Todo app ของเราไปใช้ mysql บนเซิร์ฟเวอร์  จากนั้นเราจะจัดการกับความลับนี้
  
: ''เขียนโดย chatgpt''
+
=== เปลี่ยนฐานข้อมูล ===
  
=== หลักคิดพื้นฐาน ===
+
เราจะย้ายไปใช้ฐานข้อมูล mysql บนเซิร์ฟเวอร์ p1.secondtrain.org ให้ไปตรวจสอบบัญชีที่แจกให้ใน google sheet
  
* '''ห้ามฝัง secret ลงในโค้ด''' (รวมถึง commit เข้า git, paste ลง issue, หรือส่งในแชต)
+
{{กล่องเทา|
* '''ให้สิทธิ์เท่าที่จำเป็น (least privilege)''' เช่น key ที่ใช้เรียก API แค่ read ก็อย่าให้ write
+
ถ้ามีโปรแกรม client mysql ในเครื่อง ให้ทดลอง access database ที่กำหนดให้ดูก่อน ถ้าสามารถตรวจสอบได้ จะทำให้ debug โค้ดที่เราจะทดลองต่อไปได้ง่ายขึ้น (อาจจะง่ายขึ้นกว่าใช้ sqlite)
* '''แยกสภาพแวดล้อม (dev / staging / prod)''' และใช้ secret คนละชุดเสมอ
+
}}
* '''หมุนคีย์ได้ (rotation)''' ออกแบบให้เปลี่ยน key/รหัสผ่านแล้วระบบรันต่อได้ ไม่ต้อง deploy แบบเสี่ยง ๆ
 
* '''ทุกอย่างที่อยู่ฝั่ง client ถือว่า “เปิดเผยได้”''' เพราะ React build แล้วผู้ใช้สามารถดูไฟล์ JS ได้
 
  
=== ทำความเข้าใจ “อะไรเป็น secret” ในสถาปัตยกรรม Flask + React ===
+
เราจะปรับให้ Flask ใช้ฐานข้อมูลที่กำหนดให้ ก่อนอื่นต้องติดตั้งไลบรารีสำหรับเชื่อมต่อ mysql บน python เสียก่อน
==== อะไรที่ควรอยู่ฝั่ง Backend (Flask) เท่านั้น ====
 
  
* รหัสผ่าน DB, connection string
+
เริ่มโดยการ activate virtual environment ก่อนจะสั่ง
* API key ของบริการภายนอกที่มีสิทธิ์ “ทำอะไรได้จริง” (เช่น จ่ายเงิน, ส่งอีเมล, อ่านข้อมูลส่วนตัว)
 
* JWT signing key / session secret / Flask SECRET_KEY
 
* Private keys (เช่น OAuth client secret, service account key)
 
  
==== อะไรที่ “ไม่ใช่ secret” และอยู่ฝั่ง Frontend ได้ ====
+
pip install mysqlclient
  
* ค่า config ที่เป็น public เช่น base URL, feature flag บางชนิด, public analytics key (ที่ผู้ให้บริการออกแบบให้เปิดเผยได้)
+
เมื่อสั่งเรียบร้อย ให้อัพเดท requirements.txt โดยสั่ง
* OAuth '''client id''' มักไม่ถือเป็น secret (แต่ '''client secret''' เป็น secret)
 
  
=== Git และการป้องกันการหลุดเข้าระบบควบคุมเวอร์ชัน ===
+
pip freeze > requirements.txt
  
ใส่ไฟล์ secret ใน ''.gitignore'' เช่น
+
เราจะเปลี่ยน SQLALCHEMY_DATABASE_URI ให้เป็นฐานข้อมูลใหม่ โดยแก้บรรทัดใน main.py ให้เป็น
  
* ''.env''
+
app.config['SQLALCHEMY_DATABASE_URI'] = 'mysql://USERNAME:PASSWORD@p1.secondtrain.org/DATABASENAME'
* ''config.local.json''
 
* ''.pem'', ''.key''
 
  
ใช้ ''pre-commit hook'' หรือเครื่องมือสแกน secret เพื่อกันพลาด (เช่นตรวจพบ pattern ของ key ก่อน commit)
+
โดยแทนค่า USERNAME, PASSWORD, และ DATABASENAME ให้เป็นไปตามที่กำหนดให้
  
ถ้าเผลอ commit ไปแล้ว:
+
จากนั้น ให้ migrate ฐานข้อมูล โดยสั่ง
  
* ถือว่า secret “รั่ว” แล้ว (แม้จะ revert ก็ยังอยู่ใน history)
+
flask db upgrade
* รีบ '''revoke/rotate''' key
 
* พิจารณา rewrite history (แต่ต้องเข้าใจผลกระทบกับทีม)
 
  
=== การเก็บ secret ระหว่าง Development ===
+
ถ้ากำหนดค่าเรียบร้อยน่าจะเห็นผลลัพธ์ดังนี้
==== ใช้ Environment Variables ====
 
แนวทางที่พบบ่อยสุดคือเก็บ secret ใน environment variables แล้วให้ Flask อ่านจาก env
 
  
ข้อดี: ไม่ต้องใส่ลงโค้ด, แยก env ได้ง่าย
+
INFO  [alembic.runtime.migration] Context impl MySQLImpl.
 +
INFO  [alembic.runtime.migration] Will assume non-transactional DDL.
 +
INFO  [alembic.runtime.migration] Running upgrade  -> 45b68ed6f7cf, Initial migration
 +
INFO  [alembic.runtime.migration] Running upgrade 45b68ed6f7cf -> dbe42139c979, add comment
 +
INFO  [alembic.runtime.migration] Running upgrade dbe42139c979 -> 2da8e1a15425, Add User
  
ข้อควรระวัง: อย่า log ค่า env ออกมา, และอย่าให้ dump env ใน error page
+
{{กล่องเทา|
 +
ถ้าสามารถดูฐานข้อมูลประกอบไปด้วย อาจจะทำให้เห็นภาพการทำงานชัดเจนขึ้น}}
  
==== ใช้ไฟล์ .env ในเครื่องตัวเอง (dev only) ====
+
ให้สั่งสร้างผู้ใช้
  
ใช้ ''.env'' สำหรับ dev และใส่ลง ''.gitignore''
+
flask create-user admin "Admin Fullname" helloworld
  
ทำไฟล์ตัวอย่าง ''(.env.example)'' ที่ไม่มี secret จริง เช่น
+
จากนั้นให้ทดลองใช้ todo app ว่าทำงานได้ปกติ เพิ่ม todo item ได้ เพิ่ม comment ได้
  
''DATABASE_URL=postgresql://USER:PASSWORD@HOST/DB'' (ใส่ placeholder)
+
=== จัดการกับรหัสผ่าน ===
''STRIPE_SECRET_KEY=...'' (placeholder)
 
  
=== การเก็บ secret ใน Production ===
+
ถ้าเรายังเก็บ config ของฐานข้อมูลไว้ใน main.py เมื่อเรา push ขึ้นไปที่ github รหัสผ่านทั้งหมดของเราก็อาจจะรั่วไหลไปจนทราบกันทั้งโลกได้  ดังนั้นเราจะต้องนำความลับดังกล่าวออกจากไฟล์ที่จะนำขึ้น version control system
==== ใช้ Secret Manager ของแพลตฟอร์ม ====
 
แนวปฏิบัติทั่วไป:
 
  
* Docker/Kubernetes: ใช้ ''Kubernetes Secrets'' + mount เป็น env หรือไฟล์
+
เมื่อนำออกไปแล้ว มีสองวิธีคร่าว ๆ ที่จะทำให้โปรแกรมของเราทราบความลับดังกล่าวได้
* Cloud: ใช้ secret manager (เช่น AWS/GCP/Azure) แล้วดึงตอนรัน
+
* อ่านจากไฟล์อื่น ๆ (ที่ต้องไม่ถูกนำเข้า version control ซึ่งโดยมากจะต้องระบุให้ชัดเจนใน .gitignore '''สำคัญมาก''')
* PaaS: ตั้งค่า secret ผ่าน dashboard/CLI (เช่น environment config ของ platform)
+
* อ่านจาก environment variable (ยกตัวอย่างเช่น FLASK_APP ที่เราต้องตั้งก่อนเรียก flask run --debug ทุกครั้ง)
  
หลักสำคัญ:
+
การอ่าน config จากไฟล์จะสะดวกในการใช้ระหว่าง development แต่การอ่านค่าจาก environment จะสะดวกกับการ deploy มากกว่า
  
* จำกัดสิทธิ์การเข้าถึง secret manager ให้เฉพาะ service account ของแอป
+
อย่างไรก็ตาม เราสามารถเลือกใช้ไลบรารี [https://pypi.org/project/python-dotenv/ python-dotenv] เพื่อทำให้เราได้ความสะดวกจากทั้งสองแนวทาง โดยเราจะใส่ config ต่าง ๆ ในไฟล์ ชื่อ <tt>.env</tt> และใช้ไลบรารีโหลดค่าดังกล่าวมาไว้ใน environment variable ซึ่งจะถูกโหลดเข้า Flask อีกที (โดยอัตโนมัติ)
* audit log ได้ว่าใคร/อะไรเข้าถึง secret
 
  
==== หลีกเลี่ยงการวาง secret ไว้ใน image/build artifact ====
+
ขั้นแรก เราจะต้องติดตั้ง python-dotenv
  
อย่า bake secret เข้า Docker image
+
pip install python-dotenv
  
อย่าให้ขั้นตอน build ของ React รับ secret จริง (เพราะจะถูก bundle ลงไฟล์ JS)
+
อย่าลืม pip freeze ไปใส่ requirements.txt ด้วย
  
=== Flask: จุดที่เกี่ยวกับ secret โดยตรง ===
+
จากนั้นเราจะสร้างไฟล์ชื่อ .env (ในไดเร็กทอรี backend) โดยนำ config สองค่าไปใส่ดังนี้  (แทนค่าใน USERNAME, PASSWORD, DATABASENAME ให้เรียบร้อย
==== Flask SECRET_KEY และ session security ====
 
  
ตั้งค่า ''SECRET_KEY'' จาก env
+
SQLALCHEMY_DATABASE_URI=mysql://USERNAME:PASSWORD@p1.secondtrain.org/DATABASENAME
 +
JWT_SECRET_KEY=fdsjkfjioi2rjshr2345hrsh043j5oij5545
  
ห้ามใช้ค่าเดิมระหว่างหลาย environment
+
แล้วแก้โค้ดใน main.py ให้อ่านค่าจาก config ทั้งสองจาก environment variable แทน
  
หมุนคีย์แล้วกระทบ session (ผู้ใช้อาจถูก logout) — ถือเป็นเรื่องปกติ
+
ก่อนอื่นเพิ่ม import os ที่ตอนต้นโค้ด
  
==== Database credentials / connection string ====
+
<syntaxhighlight lang="python">
 +
import os
 +
</syntaxhighlight>
  
อ่านจาก env เช่น ''DATABASE_URL''
+
แล้วแก้บรรทัด config ให้เป็น (''หมายเหตุ:'' เราใส่ค่า default ไปด้วย เพื่อให้ github action สามารถทำงานได้ -- ไม่เช่นนั้นตอนทำงานจะมี error)
  
ให้ DB user มีสิทธิ์เท่าที่ต้องใช้ (แยก read/write ถ้าทำได้)
+
<syntaxhighlight lang="python">
 +
app.config['SQLALCHEMY_DATABASE_URI'] = os.getenv('SQLALCHEMY_DATABASE_URI','sqlite:///todos.db')  
 +
app.config['JWT_SECRET_KEY'] = os.getenv('JWT_SECRET_KEY','fdslkfjsdlkufewhjroiewurewrew')
 +
</syntaxhighlight>
  
==== JWT / OAuth / API signing keys ====
+
จากนั้นถ้าเรียก flask run --debug ทุกอย่างน่าจะใช้งานได้ไม่ต่างจากเดิม
  
เก็บ private/signing key ฝั่ง backend เท่านั้น
+
'''ก่อนจะทำต่อไป สิ่งที่เราควรจะทำทันทีคือ เพิ่ม .env ลงใน .gitignore''' เพื่อป้องกันไม่ให้เรา add .env เข้าไปใน git repository
  
แยก key ต่อ environment
+
ให้เพิ่มบรรทัดด้านล่างลงใน .gitignore ที่อยู่ใน backend
  
พิจารณาใช้ key id (kid) เพื่อรองรับ rotation หลายคีย์พร้อมกัน
+
.env
  
=== React: สิ่งที่ต้องระวังเป็นพิเศษ ===
+
จากนั้นลองสั่ง git status  ควรจะไม่เห็นไฟล์ .env ในรายการ
==== ทุกอย่างที่อยู่ใน React build “ไม่ลับ” ====
 
  
ค่าใน ''process.env'' ที่ถูก inject ตอน build จะไปอยู่ใน bundle
+
==== ทดสอบส่งค่าให้ทาง environment ====
  
ต่อให้ obfuscate ก็ยังดึงได้
+
เราจะทดสอบว่า Flask อ่าน config จาก environment หรือไม่
  
==== ถ้าต้องเรียกบริการภายนอก ให้ทำผ่าน Backend เป็น proxy ====
+
ในขั้นแรกให้ลองแก้ไฟล์ .env และใส่ comment บรรทัด SQLALCHEMY_DATABASE_URI
ตัวอย่างสถานการณ์:
 
  
React ต้องเรียก API ที่ต้องใช้ secret → ให้ React เรียก Flask ก่อน แล้ว Flask ใส่ secret เรียกต่อ
+
# SQLALCHEMY_DATABASE_URI=mysql://USERNAME:PASSWORD@p1.secondtrain.org/DATABASENAME
 +
 +
แล้วลองเรียก
  
จะได้ควบคุม:
+
flask db check
* rate limit
 
* validation
 
* logging/audit
 
* permission ตาม user
 
  
==== การตั้งค่าฝั่ง frontend แบบปลอดภัย ====
+
จะได้รับ error เป็นผลลัพธ์
  
เก็บเฉพาะ public config เช่น ''REACT_APP_API_BASE_URL''
+
จากนั้นให้ลองสั่ง (ใน mac/ubuntu)
  
ถ้าต้องการ runtime config ให้เสิร์ฟไฟล์ config จาก backend (ที่ไม่มี secret) แทนการ bake ตอน build
+
export SQLALCHEMY_DATABASE_URI=mysql://USERNAME:PASSWORD@p1.secondtrain.org/DATABASENAME
  
=== การส่ง secret ระหว่างบริการ (backend ↔ external services) ===
+
หรือ (ใน windows)
  
* ใช้ HTTPS เสมอ
+
set SQLALCHEMY_DATABASE_URI=mysql://USERNAME:PASSWORD@p1.secondtrain.org/DATABASENAME
* ตั้ง timeout และ retry policy (ลดโอกาสหลุด log แปลก ๆ)
 
* อย่าใส่ secret ใน query string (URL) เพราะอาจหลุดใน:
 
** server access logs
 
** browser history
 
** referrer headers
 
  
=== Logging / Monitoring / Error handling ===
+
จากนั้นลองเรียก
  
* '''อย่า log secret''' (รวมถึง request headers เช่น ''Authorization'')
+
flask db check
  
* ทำ ''redaction'' ใน logger:
+
แล้วทดลองใช้อีกครั้ง น่าจะกลับมาทำงานได้แบบเดิม
** mask token เหลือท้าย 4 ตัว
 
** remove fields เช่น ''password'', ''api_key'', ''secret''
 
  
* ระวัง error page / stack trace ใน production:
+
ก่อนจะทำงานต่อให้ไปแก้ .env กลับให้เป็นแบบเดิม (เอา comment ออก)  แล้วทดลองว่าแอพทำงานได้ตามปกติ
** ปิด debug mode
 
** จัดการ exception แล้วตอบ error แบบไม่เผยข้อมูลภายใน
 
  
=== การจัดการสิทธิ์และการหมุนคีย์ (Rotation) ===
+
{{กล่องฟ้า|
 
+
'''🄶''' เมื่อทดลองใช้เรียบร้อย ให้เก็บการแก้ไข และ commit (ดูอีกทีว่าไฟล์ .env ไม่ถูกเพิ่มเข้าไป) จากนั้น push งานไปที่ github เพื่อดูผลการเทสทั้งหมดอีกครั้ง
* มีนโยบาย rotation เช่น ทุก 60–90 วัน (แล้วแต่ความเสี่ยง)
+
}}
* รองรับ rollout แบบไม่ downtime:
 
** backend ยอมรับทั้ง “คีย์เก่าและคีย์ใหม่” ช่วงหนึ่ง (dual keys)
 
** แล้วค่อย revoke คีย์เก่า
 
 
 
เก็บประวัติการออกคีย์ / revoke / ผู้รับผิดชอบ
 
 
 
=== การทดสอบและ CI/CD ===
 
 
 
* ใน CI ให้ใช้ secret store ของระบบ CI (เช่น GitHub Actions Secrets / GitLab CI Variables)
 
* แยก secret สำหรับ test/staging โดยเฉพาะ
 
* ใน unit test หลีกเลี่ยงการใช้ secret จริง:
 
** mock external API
 
** ใช้ dummy key ที่ไม่มีสิทธิ์
 
 
 
=== Checklist แบบสั้น ===
 
 
 
* ไม่มี secret ใน repo (รวม history)
 
* ใช้ env vars / secret manager แทน hardcode
 
* React ไม่มี secret ใด ๆ ใน bundle
 
* Flask ปิด debug ใน production
 
* logger มีการ redaction (Authorization/password/api_key)
 
* แยก secret ต่อ environment + least privilege
 
* มีแผน rotation/revoke และทำได้จริง
 
 
 
=== ตัวอย่างโครงสร้างไฟล์ที่พบบ่อย ===
 
 
 
* ''backend/''
 
** ''.env'' (ignored)
 
** ''.env.example'' (commit ได้)
 
** ''app/config.py'' (อ่านค่าจาก env)
 
 
 
* ''frontend/''
 
** ''.env'' (มักไม่ใส่ secret; ถ้ามีให้เป็นแค่ public config)
 
** ''.env.production'' (ระวัง: ยังถือว่า public หลัง build)
 
** ''src/config.ts'' (อ่าน public config)
 
 
 
ถ้าคุณอยากได้ “ตัวอย่าง template” สำหรับ Flask config + วิธีเสิร์ฟ runtime config ให้ React (แบบไม่ bake ตอน build) บอกสแต็กที่ใช้ deploy (เช่น Docker/K8s/Render/Fly/EC2) เดี๋ยวผมเขียนให้เป็นแพตเทิร์นครบชุดได้เลย.
 
 
 
== ทดลองกับบัญชีผู้ใช้ฐานข้อมูล mysql ==
 
 
 
เราได้ส่งบัญชีและรหัสผ่านผู้ใช้ mysql บนเครื่อง p1.secondtrain.org แล้ว  เราจะเริ่มโดยย้ายฐานข้อมูลของ Todo app ของเราไปใช้ mysql บนเซิร์ฟเวอร์  จากนั้นเราจะจัดการกับความลับนี้
 
 
 
=== เปลี่ยนฐานข้อมูล ===
 
 
 
=== จัดการกับรหัสผ่าน ===
 

รุ่นแก้ไขปัจจุบันเมื่อ 08:20, 27 กุมภาพันธ์ 2569

หน้านี้เป็นส่วนของวิชา 01204223

เราจะฝึกจัดการกับข้อมูลที่เป็นความลับในการพัฒนาเว็บแอพ

ก่อนอื่นอ่านภาพรวมในการจัดการก่อน อ่านที่นี่: แนวทางทั่วไปในการจัดการกับความลับ

เราได้ส่งบัญชีและรหัสผ่านผู้ใช้ mysql บนเครื่อง p1.secondtrain.org แล้ว เราจะเริ่มโดยย้ายฐานข้อมูลของ Todo app ของเราไปใช้ mysql บนเซิร์ฟเวอร์ จากนั้นเราจะจัดการกับความลับนี้

เปลี่ยนฐานข้อมูล

เราจะย้ายไปใช้ฐานข้อมูล mysql บนเซิร์ฟเวอร์ p1.secondtrain.org ให้ไปตรวจสอบบัญชีที่แจกให้ใน google sheet

ถ้ามีโปรแกรม client mysql ในเครื่อง ให้ทดลอง access database ที่กำหนดให้ดูก่อน ถ้าสามารถตรวจสอบได้ จะทำให้ debug โค้ดที่เราจะทดลองต่อไปได้ง่ายขึ้น (อาจจะง่ายขึ้นกว่าใช้ sqlite)

เราจะปรับให้ Flask ใช้ฐานข้อมูลที่กำหนดให้ ก่อนอื่นต้องติดตั้งไลบรารีสำหรับเชื่อมต่อ mysql บน python เสียก่อน

เริ่มโดยการ activate virtual environment ก่อนจะสั่ง

pip install mysqlclient

เมื่อสั่งเรียบร้อย ให้อัพเดท requirements.txt โดยสั่ง

pip freeze > requirements.txt

เราจะเปลี่ยน SQLALCHEMY_DATABASE_URI ให้เป็นฐานข้อมูลใหม่ โดยแก้บรรทัดใน main.py ให้เป็น

app.config['SQLALCHEMY_DATABASE_URI'] = 'mysql://USERNAME:PASSWORD@p1.secondtrain.org/DATABASENAME'

โดยแทนค่า USERNAME, PASSWORD, และ DATABASENAME ให้เป็นไปตามที่กำหนดให้

จากนั้น ให้ migrate ฐานข้อมูล โดยสั่ง

flask db upgrade

ถ้ากำหนดค่าเรียบร้อยน่าจะเห็นผลลัพธ์ดังนี้

INFO  [alembic.runtime.migration] Context impl MySQLImpl.
INFO  [alembic.runtime.migration] Will assume non-transactional DDL.
INFO  [alembic.runtime.migration] Running upgrade  -> 45b68ed6f7cf, Initial migration
INFO  [alembic.runtime.migration] Running upgrade 45b68ed6f7cf -> dbe42139c979, add comment
INFO  [alembic.runtime.migration] Running upgrade dbe42139c979 -> 2da8e1a15425, Add User

ถ้าสามารถดูฐานข้อมูลประกอบไปด้วย อาจจะทำให้เห็นภาพการทำงานชัดเจนขึ้น

ให้สั่งสร้างผู้ใช้

flask create-user admin "Admin Fullname" helloworld

จากนั้นให้ทดลองใช้ todo app ว่าทำงานได้ปกติ เพิ่ม todo item ได้ เพิ่ม comment ได้

จัดการกับรหัสผ่าน

ถ้าเรายังเก็บ config ของฐานข้อมูลไว้ใน main.py เมื่อเรา push ขึ้นไปที่ github รหัสผ่านทั้งหมดของเราก็อาจจะรั่วไหลไปจนทราบกันทั้งโลกได้ ดังนั้นเราจะต้องนำความลับดังกล่าวออกจากไฟล์ที่จะนำขึ้น version control system

เมื่อนำออกไปแล้ว มีสองวิธีคร่าว ๆ ที่จะทำให้โปรแกรมของเราทราบความลับดังกล่าวได้

  • อ่านจากไฟล์อื่น ๆ (ที่ต้องไม่ถูกนำเข้า version control ซึ่งโดยมากจะต้องระบุให้ชัดเจนใน .gitignore สำคัญมาก)
  • อ่านจาก environment variable (ยกตัวอย่างเช่น FLASK_APP ที่เราต้องตั้งก่อนเรียก flask run --debug ทุกครั้ง)

การอ่าน config จากไฟล์จะสะดวกในการใช้ระหว่าง development แต่การอ่านค่าจาก environment จะสะดวกกับการ deploy มากกว่า

อย่างไรก็ตาม เราสามารถเลือกใช้ไลบรารี python-dotenv เพื่อทำให้เราได้ความสะดวกจากทั้งสองแนวทาง โดยเราจะใส่ config ต่าง ๆ ในไฟล์ ชื่อ .env และใช้ไลบรารีโหลดค่าดังกล่าวมาไว้ใน environment variable ซึ่งจะถูกโหลดเข้า Flask อีกที (โดยอัตโนมัติ)

ขั้นแรก เราจะต้องติดตั้ง python-dotenv

pip install python-dotenv

อย่าลืม pip freeze ไปใส่ requirements.txt ด้วย

จากนั้นเราจะสร้างไฟล์ชื่อ .env (ในไดเร็กทอรี backend) โดยนำ config สองค่าไปใส่ดังนี้ (แทนค่าใน USERNAME, PASSWORD, DATABASENAME ให้เรียบร้อย

SQLALCHEMY_DATABASE_URI=mysql://USERNAME:PASSWORD@p1.secondtrain.org/DATABASENAME
JWT_SECRET_KEY=fdsjkfjioi2rjshr2345hrsh043j5oij5545

แล้วแก้โค้ดใน main.py ให้อ่านค่าจาก config ทั้งสองจาก environment variable แทน

ก่อนอื่นเพิ่ม import os ที่ตอนต้นโค้ด

import os

แล้วแก้บรรทัด config ให้เป็น (หมายเหตุ: เราใส่ค่า default ไปด้วย เพื่อให้ github action สามารถทำงานได้ -- ไม่เช่นนั้นตอนทำงานจะมี error)

app.config['SQLALCHEMY_DATABASE_URI'] = os.getenv('SQLALCHEMY_DATABASE_URI','sqlite:///todos.db') 
app.config['JWT_SECRET_KEY'] = os.getenv('JWT_SECRET_KEY','fdslkfjsdlkufewhjroiewurewrew')

จากนั้นถ้าเรียก flask run --debug ทุกอย่างน่าจะใช้งานได้ไม่ต่างจากเดิม

ก่อนจะทำต่อไป สิ่งที่เราควรจะทำทันทีคือ เพิ่ม .env ลงใน .gitignore เพื่อป้องกันไม่ให้เรา add .env เข้าไปใน git repository

ให้เพิ่มบรรทัดด้านล่างลงใน .gitignore ที่อยู่ใน backend

.env

จากนั้นลองสั่ง git status ควรจะไม่เห็นไฟล์ .env ในรายการ

ทดสอบส่งค่าให้ทาง environment

เราจะทดสอบว่า Flask อ่าน config จาก environment หรือไม่

ในขั้นแรกให้ลองแก้ไฟล์ .env และใส่ comment บรรทัด SQLALCHEMY_DATABASE_URI

# SQLALCHEMY_DATABASE_URI=mysql://USERNAME:PASSWORD@p1.secondtrain.org/DATABASENAME

แล้วลองเรียก

flask db check

จะได้รับ error เป็นผลลัพธ์

จากนั้นให้ลองสั่ง (ใน mac/ubuntu)

export SQLALCHEMY_DATABASE_URI=mysql://USERNAME:PASSWORD@p1.secondtrain.org/DATABASENAME

หรือ (ใน windows)

set SQLALCHEMY_DATABASE_URI=mysql://USERNAME:PASSWORD@p1.secondtrain.org/DATABASENAME

จากนั้นลองเรียก

flask db check

แล้วทดลองใช้อีกครั้ง น่าจะกลับมาทำงานได้แบบเดิม

ก่อนจะทำงานต่อให้ไปแก้ .env กลับให้เป็นแบบเดิม (เอา comment ออก) แล้วทดลองว่าแอพทำงานได้ตามปกติ

🄶 เมื่อทดลองใช้เรียบร้อย ให้เก็บการแก้ไข และ commit (ดูอีกทีว่าไฟล์ .env ไม่ถูกเพิ่มเข้าไป) จากนั้น push งานไปที่ github เพื่อดูผลการเทสทั้งหมดอีกครั้ง