-
Notifications
You must be signed in to change notification settings - Fork 214
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Azure Developer Auth Server (azd auth server) #3979
base: main
Are you sure you want to change the base?
Conversation
azd auth serve
: Local Managed Identity Endpoint server for development purposes
Very exciting, do you think this will work with MI-as-FIC with MSAL SDK? (What I talked about in the Build talk) |
I don't see why not, but I'm not sure what endpoints that looks for. Do you have more info? |
Supporting MI-FIC is way more complicated. It requires an
When working with MI w/o FIC, azd takes care of 1 and 2. Instead of a trusted issuer, you use the browser to authenticate azd and get a refreshToken. |
IIRC, there are a few flavors of MSI that allow passing a bearer token (also provided in an environment variable) that the personal calling the endpoint has to pass the token. This prevents other processes on the machine from fetching a token unless they have this shared secret. Do we think we'd want to do that here as well? I'm wondering if the extra handshake would be useful from a security perspective. |
Victor just said lots of great things that I only barely understand, but in case it's helpful, here's the PR that adds support for MI-as-FIC to MSAL SDKs: AzureAD/microsoft-authentication-library-for-python#480 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Direction looks good to me (but I'll admit I've been on the "we should be leveraging MSI in some way like this" plan for a long time).
It's possible we'll want to use of one of the MSI flavors that allows passing a bearer token to prevent any process on the machine from fetching a token as the current user, but I get that we are in a bit of a hard place here because we are trying to emulate one of the existing MSI protocols instead of getting to design our own so we have limited degrees of flexibility.
} | ||
} | ||
|
||
func (serve *serveAction) start(port string) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
NIT: It would be better if this was an int
not a string
perhaps? If we pass in a non int I assume ListenAndServe()
is going to return an error.
cli/azd/cmd/auth_serve.go
Outdated
resource = "https://management.azure.com/" | ||
} | ||
|
||
fmt.Printf("Received request for resource: %s\n", resource) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's use log
here instead of writing directly to the console.
TenantID: "", | ||
}) | ||
if err != nil { | ||
fmt.Printf("credentialProvider: %v", err) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For these error cases, we likely still want to call something like http.Error
instead of just returning here which I think would just close the connection without an HTTP response at all. Using log
would also be preferable vs pushing to the console.
In addition - if the user is never able to provide a tenant ID, perhaps we call the credentialProvider
when we construct the serve action and save it on the object, so we don't have to do it per request?
|
||
// tokenHandler handles token requests. | ||
func (serve *serveAction) tokenHandler(w http.ResponseWriter, r *http.Request) { | ||
resource := r.URL.Query().Get("resource") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we need to ensure anything about the verb that was used (I thought that MSI uses POST
, not GET
, for some reason?)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should ensure that the Metadata
header is present, as outlined on the linked page and reject other verbs, then.
} | ||
|
||
func (serve *serveAction) Run(ctx context.Context) (*actions.ActionResult, error) { | ||
port := os.Getenv("AZD_AUTH_SERVER_PORT") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why not add a --port
to the command instead of controlling this via an env-var?
this is awesome. Great work! |
Hey @ellismg @wbreza @vhvb1989 I wanted to get your feedback on an alternate approach to enhance Reasoning Behind This Approach:Avoiding Confusion with ManagedIdentityCredential:
Proposed Changes:
Security Considerations:
References:Would appreciate your thoughts on this approach and any potential implications or improvements you foresee. Thanks for your input! Jon |
@jongio What you've outlined above is very close to the external auth protocol that we designed for delegating auth to both VS and VS Code: https://github.com/Azure/azure-dev/blob/main/cli/azd/docs/external-authentication.md. This was designed around the TokenRequestContext type from the Track 2 Azure SDKs instead of building off MSAL's wire format (and hence we talk in terms of scopes and not resources) I feel like the original approach of creating a local MSI endpoint would give us larger reach (since I suspect in practice most credential chains include ManagedIdentityCredential while not all will include AzureDeveloperCLICredential, perhaps because they are on "track 1" or an older track 2 DAC that didn't contain this). For example, if we landed something like the MSI thing, we could use it to have terraform auth'd correctly when being run by I do believe there are flavors of MSI (perhaps the one that functions speaks) that allows a shared secret so that would give us some security improvements while still speaking MSI like stuff. I don't have an opinion on if "faking" MSI in this way is good or bad. My general belief is that it is "fine", especially in cases like the above where the user takes explicit action to launch the server - because they are aware of what is going on and if their app behaves strangely, they will have an understanding that it's running in a non standard environment and can diagnose. I do think that some form of common HTTP based protocol that allows us to separate the system requesting the token from the system providing the token is a good design - it's much better than the existing strategy of a credential per tool type. If we do end up creating our own wire protocol, we may also want to include the ability for it to run over HTTPS with a self signed certificate (which can be presented along with the bearer token for the HTTPS protocol via an env-var to the client). We added this level of security based on feedback from an internal design review with the security team for our HTTP JSON-RPC based integration point with VS. |
azd auth serve
: Local Managed Identity Endpoint server for development purposes
I have updated the PR description above to reflect this and other discussions. We'll leave this as an optional enhancement. |
W.r.t. security, I had done a similar hackathon project a while back that had a couple of security mechanisms. One of possible interest is to see which process the TCP connection came from: https://github.com/johnterickson/devproxy/blob/main/Proxy/ProxyAuthPlugins/ProcessTreeProxyAuthPlugin.cs Then one can peform an AuthZ on that: e.g. is the process from a trusted user? is the process parented up to a trusted "root" process? |
Azure Dev CLI Install InstructionsInstall scriptsMacOS/Linux
bash:
pwsh:
WindowsPowerShell install
MSI install
Standalone Binary
MSI
Documentationlearn.microsoft.com documentationtitle: Azure Developer CLI reference
|
Overview and Problem Statement
This PR introduces the Azure Developer Auth Server (
azd auth server start
andazd auth server stop
), a local identity server designed to facilitate authentication for development scenarios. This server uses the identity authenticated withazd
to generate tokens, allowing developers to:Local Development Authentication: Use tokens from their authenticated
azd
identity to authenticate in their application code, avoiding the need for other developer credentials. This server provides local emulation to support the credential types in Azure Identity libraries (e.g.,ManagedIdentityCredential
,DefaultAzureCredential
).Containerized Development: Enable seamless authentication within Docker containers. Authentication using
DefaultAzureCredential
within containers became challenging after the transition to MSAL. The Azure Developer Auth Server helps resolve this by providing tokens for containerized applications. More details on the issue are available here.Terraform Integration: Authenticate Terraform using tokens from the Azure Developer Auth Server without relying on the Azure CLI. This simplifies the authentication process for local and container-based development. Refer to the Terraform MSI Guide for more information.
Solution
Azure Developer Auth Server: This command starts a local identity server that provides tokens based on the current
azd
authenticated identity. The server offers a local emulation for the credential types in Azure Identity libraries, suitable for various development scenarios:Server Initialization:
azd auth server start
to start a server on127.0.0.1
(localhost) orhost.docker.internal
for Docker environments.azd auth server stop
to stop the server.Endpoint Configuration:
MSI_ENDPOINT
tohttp://localhost:53028/MSI/token
for local development orhttp://host.docker.internal:53028/MSI/token
for container development to facilitate this interaction.Application Integration:
ManagedIdentityCredential
,DefaultAzureCredential
) will obtain tokens from theMSI_ENDPOINT
provided byazd auth server start
. The server provides these tokens based on theazd
authenticated identity, allowing the application to authenticate as if using actual Azure services.Additional Enhancements:
AZD_AUTH_ENDPOINT
, an environment variable that can be used as an alternative authentication endpoint forAzureDeveloperCLICredential
.127.0.0.1
to ensure it is only accessible locally.Open Issues
Security:
127.0.0.1
orhost.docker.internal
.Credential Handling:
resourceId
andTokenCredentialOptions
.User-Assigned Managed Identities:
clientId
for user-assigned managed identities while usingazd auth server start
.Next Steps
Prioritized List of Security Considerations for
azd auth serve
Executive Summary
This document provides a security analysis of the
azd auth serve
implementation, identifying potential threats and recommending mitigation strategies to ensure robust security.Detailed Analysis
HTTPS for Secure Communication
Token Scope, Audience, and Security Contexts
User Consent and Trust Boundaries
Avoiding Custom Authentication Mechanisms
Implementing Short-Lived Tokens
IP and DNS Spoofing
Rate Limiting and Abuse Prevention
Cross-Site Request Forgery (CSRF) Attacks
Secure Storage of Tokens
Logging and Monitoring
Kernel Exploits and Privilege Escalation
Shared Kernel Vulnerabilities
Host Security
Container Image Vulnerabilities
Access to Docker Socket
Intra-Container Communication Vulnerabilities
Risk Assessment