לדלג לתוכן

סודות

שמות נכנסים. ערכים לעולם אינם יוצאים חזרה.

אף נקודת קצה של ה-API לא מחזירה ערך של סוד שמור, ואף ממשק לא מציע כפתור "הצג". מי שאיבד ערך מחליף אותו, באותה קריאה ובאותו טופס שיצרו אותו. זו לא החלטת מדיניות: נתיב הקריאה פשוט לא קיים בקוד.


תחביר ההפניה

שלוש צורות הפניה תקפות בכל מקום שבו Provisa מפענחת אישורי גישה:

צורה מפוענחת מתוך מי יכול להשתמש בה
${env:VAR_NAME} סביבת תהליך השרת תצורת פריסה בלבד
${secret:NAME} כספת הארגון — משותפת לכל החברים כל שדה המקבל הפניה לאישור גישה
${user:NAME} הכספת האישית של המבצע כל שדה המקבל הפניה לאישור גישה

בפריסה מרובת דיירים, שמרו את פרטי ההתחברות של הפלטפורמה בשירות סודות ולא במשתני סביבה. מנהל דייר יכול לכתוב ${env:...} בשדה פרטי התחברות משלו, כך שכל ערך בסביבת התהליך נמצא בהישג ידו של כל דייר. השאירו בסביבה רק את פרטי ההתחברות של ה-backend של הסודות עצמו.

הפענוח הוא fail-closed לכל אורכו. שם ספק לא מוכר, שם שלא הוגדר, וקצה עורפי בלתי נגיש — כולם מעלים שגיאה. הפניה שלא ניתן היה לפענח לעולם אינה מוחלפת בשקט במחרוזת ריקה.

פורמט השם

שמות סודות חייבים להתאים ל-[A-Za-z_][A-Za-z0-9_]*: אותיות, ספרות וקווים תחתונים, שמתחילים באות או בקו תחתון. הסיבה מעשית. ${secret:NAME} מנותח על ידי דקדוק ההפניות, שקורא עד ה-} הסוגר, ולכן שם עם סוגר מסולסל, רווח או נקודתיים היה יוצר הפניה שמנותחת כמשהו אחר.


שתי כספות, שירות אחד

לכל ארגון יש שתי כספות. שתיהן חיות בתוך אותו שירות סודות.

כספת הארגון. פרטי התחברות שמנהל ארגון שומר כאן משותפים, כך שכל חבר שמפנה אל ${secret:DATABASE_TOKEN} מקבל את אותו ערך. השתמשו בה לפרטי התחברות שהארגון הוא הבעלים שלהם, כמו סיסמת מסד נתונים משותפת, מפתח חשבון שירות או אסימון פריסה. קריאה מכספת הארגון או כתיבה אליה דורשות את היכולת org_settings.

כספת אישית. פרטי התחברות ששמורים כאן שייכים לאדם אחד בדיוק. כששני אנשים מחזיקים כל אחד GIT_TOKEN, ${user:GIT_TOKEN} נפתר לזה של מי שפועל כרגע, כך שאותו טקסט הפניה נותן לכל אדם את פרטי ההתחברות שלו. מי שלא שמר כלום מקבל שגיאה, ולעולם לא את הערך של מישהו אחר. שום יכולת לא שומרת על הכספת האישית, כי החזקת פרטי ההתחברות שלכם אינה הרשאה שמנהל מעניק, ואין תחביר בקשה שמציין כספת של אדם אחר.

התחום הוא חלק מההפניה עצמה, ושום הרשאה לא עוטפת אותה. ${secret:NAME} ו-${user:NAME} לעולם לא עונים זה במקום זה.


בחירת שירות סודות

Admin ← Security ← Secrets service. כל מי שמחזיק ביכולת platform_settings רואה את הלוח. הוא מציג כל backend שהבנייה מכירה, בין אם ה-SDK שלו מותקן ובין אם לא, ושורה אפורה מציינת את חבילת ה-Python החסרה במקום להסתיר את האפשרות.

חמישה קצוות עורפיים נשלחים:

מפתח תווית דורש
provisa Provisa (built-in, encrypted) דבר; זוהי ברירת המחדל
hashicorp_vault HashiCorp Vault (KV v2) hvac
aws_secrets_manager AWS Secrets Manager boto3
gcp_secret_manager Google Secret Manager google-cloud-secret-manager
azure_key_vault Azure Key Vault (secrets) azure-keyvault-secrets

הבחירה נכשלת באופן סגור: backend לא מוכר או לא זמין עוצר את ההפעלה עם שגיאה, ו-Provisa לעולם לא עובר בשקט ל-backend אחר.

אישור הגישה של הקצה העורפי עצמו

פרטי ההתחברות של backend מרכזי הם תצורת תהליך, והם מגיעים מ-${env:...} בלבד. ${secret:...} לא יכול לספק אותם, כי שירות סודות שפרטי ההתחברות שלו שמורים בתוכו לא היה נפתח לעולם. שרשרת האמון מסתיימת בסביבת המארח, במכוון. Provisa פותר כל ערך תצורה של backend מהסביבה לפני שהוא בונה את ה-backend.

לדוגמה, תצורת Vault ב-provisa.yaml:

secrets:
  provider: hashicorp_vault
  hashicorp_vault:
    url: https://vault.internal:8200
    token: ${env:VAULT_TOKEN}   # process env only — never ${secret:...}
    mount: secret

שירות מרכזי מול המובנה

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

כשה-backend המובנה provisa פעיל, דף הסודות ניתן לכתיבה מלאה. אתם יוצרים, מחליפים ומוחקים סודות מהממשק או דרך ה-API.


הסודות של ארגון בשירות מרכזי

פריסה שמחזיקה ארגונים רבים וקוראת שירות מרכזי קוראת אותו עבור כל ארגון בתוך הקידומת של אותו ארגון. הפניה נשארת השם הקצר (${secret:db#password}); הקידומת מתווספת בקריאה.

שירות היכן נשמר הסוד db של הארגון acme
HashiCorp Vault (KV v2) orgs/acme/db
AWS Secrets Manager orgs/acme/db
GCP Secret Manager org-acme--db
Azure Key Vault o4-acme-db (o, אורך מזהה הארגון, המזהה עם _ שנכתב כ--, ואז השם)

הפניה נדחית, בשמה, כאשר:

  • שמה היה יוצא מהקידומת: הוא מתחיל ב-/, מכיל מקטע .. או קו נטוי הפוך, או מכיל / ב-GCP או ב-Azure, שבהם השמות שטוחים;
  • שמה ארוך מדי עבור השירות לאחר הוספת הקידומת (GCP 255 תווים, Azure 127); הדבר נדחה בעת שמירת ההפניה, עם ציון המגבלה;
  • אף ארגון אינו קשור לפעולה שקוראת אותה.

פריסה של ארגון אחד קוראת הפניה בדיוק כפי שנכתבה, ללא קידומת: השירות שאליו היא מחוברת הוא של אותו ארגון, ואין צורך להעביר דבר.

מעבר לקידומת. בפריסה של ארגונים רבים, כל סוד שארגון מפנה אליו חייב לשבת תחת הקידומת של אותו ארגון. העתיקו כל אחד לשם בכלים של השירות עצמו, ואז קצרו את ההפניה לשם שבתוך הקידומת (${secret:teams/data/db} שנשמר ב-orgs/acme/db הופך ל-${secret:db}). הפניה שהסוד שלה לא הועבר נכשלת כלא נמצאה; היא אינה נקראת מהמקום שבו היה.

המאגר המובנה של Provisa

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

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

צורת האחסון:

עמודה סוג מטרה
org_id Text הארגון שהוא הבעלים של סוד זה
owner_id Text "*" לכספת הארגון; מזהה משתמש לכספת אישית
name Text שם ההפניה
value LargeBinary blob של מעטפה מוצפנת
description Text למה הסוד משמש — לעולם אינו נגזר מהערך
updated_by Text מי קבע אותו לאחרונה

ארגון שיצר מפתח הצפנה משלו מקבל את סודותיו עטופים תחת אותו מפתח; אחרת הם עטופים תחת מפתח הפריסה. ראו הצפנה במנוחה.

העמודה value אינה נבחרת בשום שאילתת רשימה.


נקודות קצה של ה-API

כל הנתיבים נמצאים תחת /admin/orgs/{org_id}. כספת הארגון דורשת org_settings באותו ארגון. הכספת האישית לא דורשת שום יכולת, כי הבעלים נקרא מהזהות המאומתת, ואף פרמטר בקשה לא מציין כספת של מישהו אחר.

שיטה נתיב מה היא עושה
GET /secrets רשימת שמות והפניות בכספת הארגון
PUT /secrets/{name} יצירה או החלפה של סוד ארגוני אחד
DELETE /secrets/{name} מחיקת סוד ארגוני אחד
GET /my-secrets רשימת השמות וההפניות האישיים של הקורא
PUT /my-secrets/{name} יצירה או החלפה של אחד מסודות הקורא
DELETE /my-secrets/{name} מחיקת אחד מסודות הקורא

כל תגובה מחזירה מטא-נתונים ולעולם לא את הערך: השם, התיאור, updated_at, updated_by ומחרוזת reference להדבקה. גוף ה-PUT נושא value (חובה) ו-description (רשות). החלפה היא אותה קריאה כמו יצירה, כי השם הוא הזהות ואין מזהה נפרד.

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


היכן ${secret:NAME} מתפענח

הפתרון קורה בתוך פעולה מאוגדת. שום דבר לא נפתר בזמן ייבוא או בהפעלה. המאגר קורא ומפענח את סודות הארגון פעם אחת, כשהפעולה מתחילה, ומחזיק את המיפוי כל עוד היא רצה. מחוץ לפעולה מאוגדת, ${secret:NAME} מעלה שגיאה.

שני אתרי קריאה מכוננים את הכבילה:

פעולות Git מרוחקות. כתובת ה-remote של מאגר הקוד של ארגון יכולה להכיל הפניה ${secret:...} או ${user:...}, כמו אסימון push שמוטמע בכתובת. נתב הסביבות קושר אז את כספת הארגון ואת הכספת האישית של המשתמש הפועל סביב קריאת ה-git. בצורה ${user:GIT_TOKEN}, commit נרשם תחת פרטי ההתחברות של מי שדחף אותו. שום חשבון שירות משותף לא מעורב.

קריאת מפתחות API של ספקי AI. כש-Provisa קורא את מפתח ספק ה-LLM של ארגון והמפתח שמור כהפניה ${secret:NAME}, הוא קושר את כספת הארגון לאותה בקשה. ההפניה נפתרת ביציאה, וטקסט ההפניה עצמו לעולם לא מגיע לספק.


מפתחות ספק AI ארגוניים כהפניות לסודות

מפתח ספק ה-AI של ארגון (Anthropic, ‏OpenAI ואחרים) יכול להישמר כהפניית ${secret:NAME} במקום כמפתח מילולי.

אחסנו תחילה את המפתח בכספת הארגון:

PUT /admin/orgs/{org_id}/secrets/OPENAI_KEY
{ "value": "sk-...", "description": "OpenAI production key" }

לאחר מכן קבעו את תצורת ה-AI של הארגון כך שתפנה אליו:

vendor key field → ${secret:OPENAI_KEY}

ההפניה נשמרת מוצפנת ב-org_secrets. בזמן השאילתה Provisa פותר את ${secret:OPENAI_KEY} מול כספת הארגון ומעביר את המפתח עצמו ל-SDK של הספק. החלפת הרשומה בכספת נכנסת לתוקף מיד, בלי שינוי תצורה בהגדרות הארגון.


אישורי הגישה של המאגרים של הפריסה עצמה

סיסמאות המאגרים ש-Provisa מפעילה (PostgreSQL, Redis, MinIO, חשבון המנהל של Grafana והסוד המשותף של אשכול Trino) אינן סודות באף אחת מהכספות. הן נוצרות לכל פריסה ונשמרות בידי הפריסה: ב-~/.provisa/store-credentials.env (מצב 600), ב-state של Terraform, או ב-Secret של Kubernetes ש-credentials.existingSecret של ה-Helm chart מציין. ראו הקשחת פריסה.


גישת מנהל פלטפורמה

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


ראו גם