שמירת פרטי התחברות וסיסמאות ב Docker Container

05/05/2019

פוסט זה כולל טיפ קצר על Docker. אם אתם רוצים ללמוד יותר לעומק על פיתוח עם Docker, Docker Compose או Kubernetes תשמחו לשמוע שבניתי קורס וידאו מקיף בנושא זה.
למידע נוסף והצטרפות לקורס בקרו בדף קורס Docker כאן באתר.
 

לא מומלץ לשמור פרטי התחברות לבסיסי נתונים או ל API בתוך הקוד שלכם או בקבצים בתוך האימג'. האימג'ים שאתם בונים נגישים ליותר מדי אנשים, וכנראה גם תרצו להשתמש בהם בכמה הקשרים ובכמה מקומות, והקוד שלכם נשמר בבקרת התצורה ובגיבויים וגם הוא נגיש לדי הרבה אנשים. לדוקר יש מנגנון לניהול מידע פרטי באמצעותו אנחנו יכולים להעביר בצורה מאובטחת מידע סודי לקונטיינרים שלנו. בואו נראה איך זה עובד.

1. אימג' שצריך מידע סודי

נתחיל עם אימג' פשוט שמפעיל תוכנית Python כדי להתחבר למערכת Open Weather Map ולשלוף משם נתוני מזג אוויר. התוכנית עצמה תשתמש בחבילה שנקראת pyowm כדי להשיג את המידע ונראית ככה:

import pyowm

owm = pyowm.OWM('49daf8ec16cca2b69d5777cd9f8690d9')

observation = owm.weather_at_place('London,GB')
w = observation.get_weather()
print(w)

אני ממשיך לבנות את האימג' בינתיים עם מפתח ה API בתוך קוד התוכנית, ואחרי שהיא תעבוד נעביר את המפתח ל Docker Secret. ניצור קובץ בשם Dockerfile בתיקיה ובו התוכן הבא:

FROM python:3.7-slim

WORKDIR /app

COPY . /app

RUN pip install pyowm

CMD ["python", "/app/demo.py"]

ועכשיו נפעיל:

$ docker build --tag=apidemo .

כדי לבנות את האימג' ואחרי שזה הסתיים נוכל להריץ את הקונטיינר:

$ docker run --rm apidemo
<pyowm.weatherapi25.weather.Weather - reference time=2019-03-26 10:34:01+00, status=clear, detailed status=clear sky>

2. נעביר את הסודות לדוקר

יש רק שתי בעיות עם האימג' שיצרנו:

  1. כל הסודות נשמרים בתוך האימג' בקוד התוכנית, ולכן כל מי שמקבל את האימג' מקבל גם את ה API Key שלי ויכול לפנות בשמי ל Open Weather Map (לא סוף העולם כשמדברים על מזג אוויר, הרבה פחות נעים כשמדברים על גישה ל AWS או ל Github שלכם).

  2. אי אפשר להחליף בקלות את ה API Key - למשל אם נרצה להפעיל את האימג' הזה עם API Key מסוים בסביבת הפיתוח ו API Key אחר לסביבת הבדיקות.

הפיתרון לשתי הבעיות יהיה להעביר את הסוד לדוקר, או יותר מדויק ל Docker Swarm. בפוסט קודם בסידרה ראינו איך לבנות Swarm עם שרת מנהל ושרתים שמפעילים קונטיינרים באמצעות קובץ docker-compose.yml. כל המידע שנשמר בשרת המנהל נשמר בפרוטוקול שנקרא Raft וכולו מוצפן, ולכן אנחנו יכולים לבקש מהשרת המנהל לשמור את הסוד שלנו בצורה מוצפנת. בעבודה בסביבת הפיתוח או הבדיקות נוכל להזין סוד אחר ומבחינת היישום השינוי יהיה שקוף.

תחילה ניצור קובץ docker-compose.yml שיגדיר את הקונטיינר שלנו בתור סרביס, כלומר חלק מ Docker Swarm. צרו את הקובץ docker-compose.yml עם התוכן הבא:

version: '3.7'
services:
  demoapi:
    image: apidemo:latest
    tty: true
    deploy:
      restart_policy:
        condition: none
    secrets:
      - apikey
secrets:
  apikey:
    external: true

שימו לב שאני עדיין לא מגדיר כאן את תוכן ה apikey רק את העובדה שהקונטיינר הזה יצטרך להשתמש בסוד שנקרא apikey, ואת העובדה שהסוד הזה כבר שמור ב Swarm. כמו כן בגלל שהסרביס שכתבנו אמור להריץ פקודה חד-פעמית ואז להיסגר אני מגדיר לו restart_policy כך שהסווארם לא יפעיל אותו שוב ושוב באופן אוטומטי.

עכשיו נפעיל את ה swarm:

$ docker swarm init

Swarm initialized: current node (2krtwr9et1e6i5dkz40riki90) is now a manager.

To add a worker to this swarm, run the following command:

    docker swarm join --token SWMTKN-1-4j22m3zk8zurvf6sdqgmfsfxb0zxpju0lbci4xxx26v5afzm60-0lbc5wry44sxmd4g09ul9uhag 192.168.65.3:2377

To add a manager to this swarm, run 'docker swarm join-token manager' and follow the instructions.

ונשמור את הסוד שלנו ב swarm שיצרנו:

$ echo -n 49daf8ec16cca2b69d5777cd9f8690d9 | docker secret create apikey -

נוודא שהסוד נשמר:

$ docker secret ls
ID                          NAME                DRIVER              CREATED             UPDATED
dwi43638ywdkddpr850qy6aci   apikey                                  11 seconds ago      11 seconds ago

המידע עצמו שמור ב Raft Log המוצפן של ה Swarm. הוא יינתן רק לסרביסים שצריכים אותו לפי קובץ ה docker-compose שיצרנו.

3. עדכון התוכנית כדי שתוכל לקרוא את המידע מדוקר

תוכנית ה Python שלנו עדיין כוללת את הסוד בתוך הקוד, ולכן התחנה הבאה שלנו היא חזרה לקוד כדי לקרוא את ה API Key מדוקר. כשדוקר ייתן לנו את הסוד הוא ישים אותו בתוך קובץ בתיקיה /run/secrets. שם הקובץ הוא שם הסוד - במקרה שלנו זה יהיה apikey.

נכתוב ספרית פייתון קצרה שמקבלת שם של סוד וקוראת אותו מתוך דוקר. שמרו את התוכן הבא בקובץ secrets.py:

def get(name):
    with open(f'/run/secrets/{name}') as f:
        return f.read()

ועכשיו נעדכן את התוכנית הראשית כך שתיקח את התוכן מקובץ הסודות. עדכנו את הקובץ demo.py כך שיכיל את התוכן הבא:

import pyowm
import secrets

apikey = secrets.get('apikey')
owm = pyowm.OWM(apikey)

observation = owm.weather_at_place('London,GB')
w = observation.get_weather()
print(w)

עכשיו בשום מקום בקוד אין יותר את ה API Key שלנו, והמקום היחיד בו הוא שמור זה במערכת הקבצים המוצפנת של Docker Swarm.

נבנה מחדש את האימג' ונריץ כדי לראות שהכל עדיין עובד:

$ docker build --tag apidemo .
$ docker stack deploy -c docker-compose.yml mystack
Creating network mystack_default
Creating service mystack_demoapi

ומה קרה לפלט אתם שואלים? בהרצה של סרביס הפלט נשמר לתוך הלוגים של הקונטיינר. אל תדאגו אפשר למצוא אותו בקלות:

$ docker service logs mystack_demoapi --raw
<pyowm.weatherapi25.weather.Weather - reference time=2019-03-26 11:08:08+00, status=clear, detailed status=clear sky>

4. מה הרווחנו

העבודה עם Secrets בצורה מסודרת מציעה יתרונות משמעותיים בהשוואה לשמירת הסודות בקוד:

  1. היא הרבה יותר מאובטחת, שכן הסודות נשמרים בצורה מוצפנת.

  2. היא הרבה יותר נוחה כי כתבנו את הסוד פעם אחת ב Swarm ויכולים לשכוח מזה.

  3. היא מאפשרת להגדיר סודות שונים ל Swarms שונים לפי הסביבה.

ברוב המערכות שלכם תרצו להחזיק קובץ סודות עבור המערכת שיישמר במקום מאובטח, כשתתקינו את ה Swarm תאתחלו את כל הסודות מתוך קובץ הסודות שלכם ומפה והלאה תוכלו לפרוס Stacks על ה Swarm הזה בלי לדאוג לשמירה מאובטחת של API Keys וסודות אחרים.