פודי Kubernetes כזהויות ענן: EKS IRSA מול AKS Workload Identity
כיצד ServiceAccount של Kubernetes הופך לזהות ענן אמיתית - שרשרת האמון של פדרציית OIDC מאחורי AWS IRSA ו-Azure AKS Workload Identity, במיפוי אחד לאחד, עם הקשיים שמפרים את הזיכרון השרירי שלכם.
גם AWS וגם Azure פותרות את אותה בעיה באותה דרך: פוד מאמת את עצמו מול ממשקי API של הענן כשלעצמו, עם אסימון קצר-חיים וללא סוד מאוחסן, על ידי הפיכת ServiceAccount של Kubernetes לזהות ענן מאוחדת (federated). ב-AWS התכונה היא IRSA (IAM Roles for Service Accounts); ב-Azure זוהי Microsoft Entra Workload Identity (היורשת של aad-pod-identity שהוצאה משימוש). מתחת למכסה המנוע, שניהם הם אותו טריק של פדרציית OIDC, והרכיבים מתיישרים כמעט אחד לאחד.
הגרסה הקצרה, אם אתם קוראים רק פסקה אחת: האשכול הופך למנפיק OpenID Connect שחותם על אסימון עבור ServiceAccount של כל פוד; ספק הזהויות של הענן בוטח במנפיק זה ומחליף את האסימון באישורי ענן אמיתיים המוגדרים לזהות אחת. AWS מכנה את הזהות IAM role ומגבילה אותה באמצעות trust policy; Azure מכנה אותה user-assigned managed identity ומגבילה אותה באמצעות federated identity credential. מחרוזת ה-subject system:serviceaccount:<namespace>:<name> היא אבן הראשה בשניהם. שלושה דברים שונים מספיק כדי להפתיע אתכם: Azure מפצלת את "OIDC provider" לשני מתגי אשכול נפרדים ששניהם כבויים כברירת מחדל, Azure דורשת בנוסף תווית על ה-פוד עצמו, ומשיכת תמונת הקונטיינר שלכם היא זהות שונה מזהות העומס שלכם בשני העננים.
הבעיה ששניהם פותרים
לפני פדרציה, מתן הרשאות ענן לפוד כלל אפשרויות גרועות: הטמעת מפתח גישה סטטי בתמונה או ב-Secret (דליפות, כאבי רוטציה), או הענקת ההרשאה לצומת כולו כך שכל פוד עליו יירש אותה (ללא בידוד, היקף רחב מדי). פתרונות הביניים בצד AWS היו מיירטי IMDS של צמתים כמו kube2iam ו-kiam; בצד Azure זה היה aad-pod-identity. כולם פעלו כפרוקסי לזהות הצומת והיו מסורבלים ומועדים לבעיות תזמון (race-prone).
פדרציה מחליפה זאת באמון קריפטוגרפי. ה-kubelet מקרין אסימון OIDC חתום וקצר-חיים לתוך הפוד, בהיקף של ServiceAccount של הפוד. ה-IdP של הענן מאמת את חתימת האסימון מול המפתחות הציבוריים שפורסמו על ידי האשכול, ואם ה-issuer, subject וה-audience של האסימון תואמים ל-trust rule שהגדרתם, הוא מחזיר אישורים לזהות ענן אחת בדיוק. שום סוד אינו מאוחסן בשום מקום; האסימון המוקרן מסתובב אוטומטית (זמן חיים ברירת מחדל כשעה) וחסר תועלת מחוץ לאשכול.
המנגנון המשותף: פדרציית OIDC
כל הגדרת workload-identity, בכל ענן, היא אותו משולש אמון:
- האשכול הוא מנפיק OIDC. הוא חושף מסמך גילוי בכתובת
<issuer-url>/.well-known/openid-configurationונקודת קצה של JWKS עם המפתחות הציבוריים שבהם הוא חותם על אסימוני ServiceAccount. כתובת ה-URL של המנפיק היא עוגן האמון. - ה-IdP של הענן בוטח במנפיק זה עבור זוג
(subject, audience)ספציפי. ה-subject מזהה איזה ServiceAccount, וה-audience מזהה למי מיועד האסימון. - ה-workload מחליף את האסימון המוקרן באישורי ענן. ה-SDK קורא את קובץ האסימון שה-kubelet הקרין, מציג אותו לנקודת הקצה של האסימונים של הענן, ומקבל אישורים קצרי-חיים עבור זהות ענן אחת.
כל השאר זה עניין של שמות. הנה המפה, נסקור אותה שורה אחר שורה.
המיפוי אחד לאחד
- צד Kubernetes. ב-AWS אתם מוסיפים הערה (annotate) ל-ServiceAccount עם
eks.amazonaws.com/role-arn: <role arn>. ב-Azure אתם מוסיפים הערה עםazure.workload.identity/client-id: <managed identity client id>וגם מתייגים את הפוד עםazure.workload.identity/use: "true". שניהם מפנים את הפוד לזהות ענן; Azure זקוקה לתווית הפוד הנוספת (עוד על כך בהמשך). - "ספק ה-OIDC". ב-AWS זהו מנפיק ה-OIDC של אשכול EKS הרשום פעם אחת ב-IAM כ-OIDC identity provider. ב-Azure אלו שתי הגדרות אשכול: מנפיק ה-OIDC (
oidcIssuerProfile.enabled) שמפרסם את כתובת ה-URL של המנפיק ואת מפתחות החתימה, ותוסף ה-workload-identity (securityProfile.workloadIdentity.enabled), שהוא mutating admission webhook. שניהם חייבים להיות מופעלים. - הזהות. AWS: IAM role. Azure: user-assigned managed identity (UAMI). זהו האובייקט שהפוד הופך אליו.
- כלל האמון. AWS: ה-trust policy של ה-IAM role מאחד (federates) את ה-OIDC provider ומצמיד
sub = system:serviceaccount:<ns>:<sa>ו-aud = sts.amazonaws.com. Azure: federated identity credential על ה-UAMI עםissuer = <cluster OIDC issuer url>,subject = system:serviceaccount:<ns>:<sa>, ו-audience = api://AzureADTokenExchange. אותם שלושה שדות, בבתים שונים. - ההרשאות. AWS: IAM policy המצורף ל-role (קריאה מ-S3 bucket זה, משיכה מ-ECR repo זה). Azure: RBAC role assignments על משאבי היעד (Key Vault Secrets User, Storage Blob Data Reader, AcrPull, ...). זהו הפיצול הפילוסופי העמוק יותר - AWS מצמידה policy לזהות; Azure מעניקה role בהיקף המשאב.
- ההחלפה. AWS: ה-SDK קורא ל-
sts:AssumeRoleWithWebIdentityעם האסימון המוקרן. Azure: ה-SDK מבצע החלפת אסימוני Entra באמצעותDefaultAzureCredential/WorkloadIdentityCredential, ומציג את האסימון המוקרן כ-client assertion. שניהם מניבים אישורים קצרי-חיים, ושניהם אינם זקוקים לחומר סודי כלשהו בקוד שלכם.
שאר הפוסט הזה סוקר כל ענן מקצה לקצה, ולאחר מכן מתעכב על ההבדלים שבפועל מבלבלים אנשים.
סקירה: AWS EKS IRSA, מקצה לקצה
החלקים, בסדר זרימת האמון:
1. מנפיק ה-OIDC של האשכול. לכל אשכול EKS יש אחד, בכתובת URL כמו https://oidc.eks.<region>.amazonaws.com/id/<hash>. אתם רושמים אותו פעם אחת ב-IAM כ-OIDC identity provider כך ש-IAM יבטח באסימונים שהוא חותם עליהם.
2. ה-IAM role ו-trust policy שלו. ה-trust policy הוא מה שהופך את ה-role לניתן להנחה על ידי ServiceAccount ספציפי ולא שום דבר אחר:
{
"Effect": "Allow",
"Principal": { "Federated": "arn:aws:iam::<account>:oidc-provider/oidc.eks.<region>.amazonaws.com/id/<hash>" },
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
"StringEquals": {
"oidc.eks.<region>.amazonaws.com/id/<hash>:sub": "system:serviceaccount:apps:checkout",
"oidc.eks.<region>.amazonaws.com/id/<hash>:aud": "sts.amazonaws.com"
}
}
}
הצמידו גם את :sub וגם את :aud. הצמדת המנפיק בלבד תאפשר לכל ServiceAccount באשכול להניח את ה-role - באג קלאסי של אמון יתר.
3. ה-IAM policy. policy רגיל מבוסס זהות המצורף ל-role מעניק את מה שהאפליקציה באמת צריכה (s3:GetObject על bucket אחד, ecr:GetDownloadUrlForLayer, וכן הלאה).
4. ה-ServiceAccount. ServiceAccount רגיל עם הערה אחת:
apiVersion: v1
kind: ServiceAccount
metadata:
name: checkout
namespace: apps
annotations:
eks.amazonaws.com/role-arn: arn:aws:iam::<account>:role/checkout
5. ההקרנה. EKS מספקת את Pod Identity Webhook המובנה באשכול. כאשר פוד משתמש ב-ServiceAccount עם הערה, ה-webhook משנה אותו: הוא מזריק משתני סביבה AWS_ROLE_ARN ו-AWS_WEB_IDENTITY_TOKEN_FILE וטוען נפח אסימון מוקרן (audience sts.amazonaws.com, מסתובב אוטומטית). אינכם מתייגים את הפוד; הערת ה-SA מספיקה.
6. ההחלפה. שרשרת האישורים המוגדרת כברירת מחדל של AWS SDK מבחינה ב-AWS_WEB_IDENTITY_TOKEN_FILE, קוראת ל-sts:AssumeRoleWithWebIdentity עם האסימון, ושומרת במטמון את האישורים קצרי-החיים שהוחזרו. הקוד שלכם הוא פשוט boto3.client("s3") - ללא טיפול באישורים כלל.
אלטרנטיבה חדשה יותר: EKS Pod Identity (2023) עושה את אותה עבודה באמצעות סוכן בתוך האשכול ו-API של אסוציאציה במקום IAM OIDC provider לכל אשכול ו-trust policy לכל role. קל יותר לתפעל אותו בקנה מידה גדול אך הוא פתרון AWS-ספציפי; מודל פדרציית ה-OIDC שלעיל הוא זה שממופה בצורה נקייה ל-Azure, ולכן הוא זה שכדאי לזכור להשוואה בין עננים.
סקירה: Azure AKS Workload Identity, מקצה לקצה
אותה זרימת אמון, מונחים של Azure:
1. שני מתגי אשכול. הפעילו את מנפיק ה-OIDC (oidcIssuerProfile.enabled = true), שמפרסם כתובת URL של מנפיק כמו https://<region>.oic.prod-aks.azure.com/<tenant>/<guid>/, ואת תוסף ה-workload-identity (securityProfile.workloadIdentity.enabled = true), שמתקין את ה-mutating webhook. שניהם כבויים כברירת מחדל באשכול חדש, ו-oidcIssuerUrl של האשכול הוא null עד שהראשון מופעל. הפעלתם היא עדכון במקום, לא יצירה מחדש.
2. הזהות המנוהלת. צרו user-assigned managed identity. (Federated credentials מתחברים רק לזהויות שהוקצו למשתמש, לא לאלו שהוקצו למערכת.) ה-client id שלה הוא מה שה-ServiceAccount יפנה אליו.
3. ה-federated identity credential. זהו כלל האמון, אובייקט ילד על ה-UAMI:
issuer: https://<region>.oic.prod-aks.azure.com/<tenant>/<guid>/
subject: system:serviceaccount:apps:checkout
audience: api://AzureADTokenExchange
שימו לב לאותה דקדוק subject כמו ב-AWS ול-audience קבוע שונה.
4. ה-RBAC. העניקו ל-principal של ה-UAMI את ה-roles שהוא זקוק להם על משאבי היעד: Key Vault Secrets User על vault, Storage Blob Data Reader על storage account, AcrPull על registry אם האפליקציה קוראת ל-data plane של ה-registry עצמו. אין מסמך policy על הזהות; הגישה היא באמצעות role assignments בהיקף של כל משאב.
5. ה-ServiceAccount ותווית הפוד. ה-ServiceAccount נושא את הערת ה-client-id, ו - זה החלק שאנשי AWS שוכחים - הפוד חייב להיות מתויג גם כן:
apiVersion: v1
kind: ServiceAccount
metadata:
name: checkout
namespace: apps
annotations:
azure.workload.identity/client-id: <uami-client-id>
---
# in the Deployment's pod template:
metadata:
labels:
azure.workload.identity/use: "true" # required on the POD, not just the SA
spec:
serviceAccountName: checkout
6. ההקרנה. ה-webhook של התוסף משנה רק פודים הנושאים את התווית azure.workload.identity/use: "true". כאשר הוא מופעל, הוא מזריק את AZURE_CLIENT_ID, AZURE_TENANT_ID, AZURE_FEDERATED_TOKEN_FILE, ו-AZURE_AUTHORITY_HOST, ומקרין אסימון עם audience api://AzureADTokenExchange.
7. ההחלפה. DefaultAzureCredential() (או ה-WorkloadIdentityCredential המפורש) קורא את משתני הסביבה הללו, מציג את האסימון המוקרן ל-Entra ID כ-client assertion, ומקבל בחזרה access token עבור משאב היעד. קריאת Key Vault היא azure.identity.DefaultAzureCredential() בתוספת SecretClient ב-Python, או new DefaultAzureCredential() בתוספת @azure/keyvault-secrets ב-TypeScript - ושוב, אין סוד בשום מקום בקוד.
מחרוזת ה-subject היא אבן הראשה (בשני העננים)
הערך היחיד שחייב להתאים בדיוק, בשני העננים, הוא ה-subject של האסימון:
system:serviceaccount:<namespace>:<serviceaccount-name>
ה-kubelet מטביע זאת באסימון בהתבסס על ה-namespace וה-ServiceAccount בפועל של הפוד. כלל האמון (IAM trust policy ב-AWS, federated credential ב-Azure) מצמיד את המחרוזת שהוא יקבל. אם האפליקציה רצה ב-namespace apps תחת ServiceAccount checkout, כלל האמון חייב לומר system:serviceaccount:apps:checkout - תו אחר תו. הכישלון הנפוץ ביותר של "הוא מאמת את עצמו כלא-אף-אחד" הוא אי-התאמה כאן: ה-chart נפרס ל-namespace אחר, ה-ServiceAccount קיבל את שם ה-release במקום את שם האפליקציה, או שמישהו הניח את ה-default ServiceAccount. כאשר פדרציה נכשלת בשקט, בדקו קודם את ה-subject.
ה-audience הוא השדה המצומד השני, והוא קבוע לכל ענן: sts.amazonaws.com ב-AWS, api://AzureADTokenExchange ב-Azure. לעיתים רחוקות אתם משנים אותו, אך כלל האמון והאסימון המוקרן חייבים להסכים עליו.
ארבעה הבדלים שבפועל יבלבלו אתכם
1. Azure מפצלת את "OIDC provider" לשני מתגים, ושניהם כבויים כברירת מחדל. ב-EKS ה-pod-identity webhook מובנה באשכול והשלב החד-פעמי היחיד הוא רישום ה-OIDC provider ב-IAM. ב-AKS עליכם להפעיל בנפרד את מנפיק ה-OIDC ואת תוסף ה-workload-identity; אם תשכחו את התוסף, המנפיק עדיין יפרסם אסימונים אך שום דבר לא יקרין אותם לפודים, כך שהאפליקציה לא תראה משתני סביבה AZURE_* ו-DefaultAzureCredential פשוט יעבור למקור האישורים הבא. הפעילו את שניהם, וזכרו שלא ניתן ליצור את ה-federated credential עד שכתובת ה-URL של המנפיק קיימת.
2. Azure זקוקה לתווית על הפוד, לא רק להערה על ה-ServiceAccount. ה-webhook של AKS פועל על בסיס התווית azure.workload.identity/use: "true" על הפוד (הערת ה-ServiceAccount לבדה אינה מספיקה). ה-webhook של IRSA פועל על בסיס הערת ה-ServiceAccount ואינו זקוק לתווית פוד. זוהי המלכודת הנפוצה ביותר בצד Azure: ה-ServiceAccount נראה מושלם, אך תבנית הפוד שכחה את התווית, ולכן לא מוקרן אסימון.
3. משיכת התמונה היא זהות שונה מזהות העומס - בשני העננים. משיכת תמונה היא עבודת הזהות של הצומת/kubelet: ב-AKS זהות ה-kubelet מחזיקה ב-AcrPull; ב-EKS ה-instance role של קבוצת הצמתים מחזיק ב-AmazonEC2ContainerRegistryReadOnly (או שאתם משתמשים ב-pull secrets). Workload identity נחוצה רק כאשר קוד האפליקציה קורא ל-API של ענן (קריאת סוד, רישום bucket, קריאה ל-data plane של ה-registry). שירות HTTP טהור שלעולם אינו מדבר עם SDK של ענן אינו זקוק ל-workload identity כלל - ולהיפך, הענקת AcrPull ל-workload identity אינה עושה דבר עבור משיכת תמונה, מכיוון שהמשיכה מתרחשת לפני שהאפליקציה (והאסימון המאוחד שלה) בכלל רצה.
4. הרשאות מתחברות באופן שונה. AWS מצמידה IAM policy ל-role - ההרשאה עוברת עם הזהות. Azure מעניקה RBAC role assignments בהיקף משאב היעד - ההרשאה נמצאת על הדבר הנגיש, לא על הזהות. אותה תוצאה סופית (פוד זה יכול לקרוא את ה-vault ההוא), אך אתם בודקים במקומות שונים כדי לבקר זאת: ב-AWS, קראו את ה-policies המצורפים ל-role; ב-Azure, רשמו את ה-role assignments על המשאב (או את ה-assignments של הזהות על פני היקפים שונים). זה משקף את ההבדל הרחב יותר בין IAM ל-RBAC בין שני העננים.
איך נראית סביבת ה-environment המוזרקת
שני ה-webhooks מעבירים ל-SDK את כל מה שהוא צריך באמצעות משתני סביבה וקובץ מוקרן, כך שקוד האפליקציה אינו מפנה לשום סוד ולשום לוגיקת אישורים ספציפית לענן:
- AWS:
AWS_ROLE_ARN,AWS_WEB_IDENTITY_TOKEN_FILE,AWS_REGION. שרשרת האישורים המוגדרת כברירת מחדל קוראת ל-AssumeRoleWithWebIdentityעבורכם. - Azure:
AZURE_CLIENT_ID,AZURE_TENANT_ID,AZURE_FEDERATED_TOKEN_FILE,AZURE_AUTHORITY_HOST.DefaultAzureCredentialמבצע את החלפת ה-Entra עבורכם.
בשני המקרים קובץ האסימון המוקרן הוא קצר-חיים ומסתובב אוטומטית על ידי ה-kubelet; ה-SDK קורא אותו מחדש ומרענן את אישורי הענן באופן שקוף. אין מה לסובב, אין מה לאחסן, ואין מה לדלוף.
רשימת בדיקה לניפוי באגים שעובדת בשני העננים
כאשר פוד "אינו יכול לאמת את עצמו", עברו על שרשרת האמון לפי הסדר:
- מנפיק האשכול מופעל? ודאו שכתובת ה-URL של מנפיק ה-OIDC קיימת (AKS: שני המתגים מופעלים; EKS: ה-IAM OIDC provider רשום). אין מנפיק, אין עוגן אמון.
- הקרנה מתרחשת? בצעו exec לתוך הפוד ובדקו אם קובץ האסימון ומשתני הסביבה קיימים (
AWS_WEB_IDENTITY_TOKEN_FILE/AZURE_FEDERATED_TOKEN_FILE). חוסר ב-Azure בדרך כלל אומר שתווית הפוד חסרה או שהתוסף כבוי. - התאמת subject? השוו את ה-subject של כלל האמון ל-
system:serviceaccount:<actual-namespace>:<actual-sa>. זוהי הכישלון הנפוץ ביותר. - התאמת audience?
sts.amazonaws.com/api://AzureADTokenExchange, בהסכמה משני הצדדים. - הרשאות? רק לאחר שהזהות נפתרת: ה-IAM policy על ה-role, או ה-RBAC role assignment על משאב היעד. תוצאה ריקה כאן היא כשל אישור (authorization failure), לא כשל אימות (authentication failure) - הבחנה שימושית, מכיוון שהיא אומרת לכם שהפדרציה עצמה פועלת.
אילו הסמכות מתעמקות בכך
Workload identity נמצאת בדיוק בנקודת המפגש של Kubernetes, IAM בענן, ופדרציית OIDC, ולכן היא מופיעה בשלושה מסלולי בחינה. אם אתם כבר מחזיקים באישורים של ענן אחד, אותו קונספט בענן השני הוא קפיצה קצרה.
בצד AWS:
- AWS Certified Solutions Architect - Associate (SAA-C03) - IAM roles, EKS, וכיצד זהויות מקבלות הרשאות ענן.
- AWS Certified Security - Specialty (SCS-C03) - IAM trust policies, פדרציית OIDC, והגדרת היקף הרשאות מינימלי (least-privilege scoping) (בדיוק שרשרת האמון של IRSA).
בצד Azure:
- Microsoft Azure Administrator Associate (AZ-104) - AKS, managed identities, ו-RBAC role assignments.
- Microsoft Azure Security Engineer Associate (AZ-500) - Entra ID, managed identities, federated credentials, ועומק RBAC.
בצד Kubernetes:
- CNCF Certified Kubernetes Administrator (CKA) - ServiceAccounts, projected tokens, ו-pod specs.
- CNCF Certified Kubernetes Security Specialist (CKS) - אבטחת אסימוני ServiceAccount, הרשאה מינימלית (least privilege), והפחתת חשיפת סודות סטטיים.
השורה התחתונה
IRSA ו-AKS Workload Identity הם אותו רעיון בלבוש מילולי שונה: האשכול חותם על אסימון OIDC קצר-חיים עבור ServiceAccount, ה-IdP של הענן בוטח במנפיק זה עבור (subject, audience) אחד, והפוד מחליף את האסימון באישורים המוגדרים לזהות ענן אחת - IAM role ב-AWS, managed identity ב-Azure. למדו את משולש האמון פעם אחת והתרגום הוא מכני: role הופך ל-managed identity, trust policy הופך ל-federated credential, attached policy הופך ל-RBAC assignment, AssumeRoleWithWebIdentity הופך להחלפת אסימוני Entra. שמרו שלושה דברים בראש - שני מתגי האשכול של Azure, תווית הפוד הנדרשת של Azure, והעובדה שמשיכת תמונה היא זהות שונה לחלוטין - והסירובים שנראו בעבר אקראיים יתחילו להיקרא כעיצוב קוהרנטי ומכוון.