1 Star 0 Fork 0

powerpaas/machine

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
克隆/下载
auth.go 9.19 KB
一键复制 编辑 原始数据 按行查看 历史
package azureutil
import (
"fmt"
"os"
"path/filepath"
"github.com/docker/machine/libmachine/log"
"github.com/docker/machine/libmachine/mcnutils"
"github.com/Azure/go-autorest/autorest/azure"
"github.com/Azure/go-autorest/autorest/to"
"github.com/docker/machine/drivers/azure/logutil"
)
// Azure driver allows two authentication methods:
//
// 1. OAuth Device Flow
//
// Azure Active Directory implements OAuth 2.0 Device Flow described here:
// https://tools.ietf.org/html/draft-denniss-oauth-device-flow-00. It is simple
// for users to authenticate through a browser and requires re-authenticating
// every 2 weeks.
//
// Device auth prints a message to the screen telling the user to click on URL
// and approve the app on the browser, meanwhile the client polls the auth API
// for a token. Once we have token, we save it locally to a file with proper
// permissions and when the token expires (in Azure case typically 1 hour) SDK
// will automatically refresh the specified token and will call the refresh
// callback function we implement here. This way we will always be storing a
// token with a refresh_token saved on the machine.
//
// 2. Azure Service Principal Account
//
// This is designed for headless authentication to Azure APIs but requires more
// steps from user to create a Service Principal Account and provide its
// credentials to the machine driver.
var (
// AD app id for docker-machine driver in various Azure realms
appIDs = map[string]string{
azure.PublicCloud.Name: "637ddaba-219b-43b8-bf19-8cea500cf273",
azure.ChinaCloud.Name: "bb5eed6f-120b-4365-8fd9-ab1a3fba5698",
azure.GermanCloud.Name: "aabac5f7-dd47-47ef-824c-e0d57598cada",
}
)
// AuthenticateDeviceFlow fetches a token from the local file cache or initiates a consent
// flow and waits for token to be obtained. Obtained token is stored in a file cache for
// future use and refreshing.
func AuthenticateDeviceFlow(env azure.Environment, subscriptionID string) (*azure.ServicePrincipalToken, error) {
// First we locate the tenant ID of the subscription as we store tokens per
// tenant (which could have multiple subscriptions)
tenantID, err := loadOrFindTenantID(env, subscriptionID)
if err != nil {
return nil, err
}
oauthCfg, err := env.OAuthConfigForTenant(tenantID)
if err != nil {
return nil, fmt.Errorf("Failed to obtain oauth config for azure environment: %v", err)
}
tokenPath := tokenCachePath(tenantID)
saveToken := mkTokenCallback(tokenPath)
saveTokenCallback := func(t azure.Token) error {
log.Debug("Azure token expired. Saving the refreshed token...")
return saveToken(t)
}
f := logutil.Fields{"path": tokenPath}
appID, ok := appIDs[env.Name]
if !ok {
return nil, fmt.Errorf("docker-machine application not set up for Azure environment %q", env.Name)
}
scope := getScope(env)
// Lookup the token cache file for an existing token.
spt, err := tokenFromFile(*oauthCfg, tokenPath, appID, scope, saveTokenCallback)
if err != nil {
return nil, err
}
if spt != nil {
log.Debug("Auth token found in file.", f)
// NOTE(ahmetalpbalkan): The token file we found might be containing an
// expired access_token. In that case, the first call to Azure SDK will
// attempt to refresh the token using refresh_token –which might have
// expired[1], in that case we will get an error and we shall remove the
// token file and initiate token flow again so that the user would not
// need removing the token cache file manually.
//
// [1]: for device flow auth, the expiration date of refresh_token is
// not returned in AAD /token response, we just know it is 14
// days. Therefore user’s token will go stale every 14 days and we
// will delete the token file, re-initiate the device flow. Service
// Principal Account tokens are not subject to this limitation.
log.Debug("Validating the token.")
if err := validateToken(env, spt); err != nil {
log.Debug(fmt.Sprintf("Error: %v", err))
log.Debug(fmt.Sprintf("Deleting %s", tokenPath))
if err := os.RemoveAll(tokenPath); err != nil {
return nil, fmt.Errorf("Error deleting stale token file: %v", err)
}
} else {
log.Debug("Token works.")
return spt, nil
}
}
log.Debug("Obtaining a token.", f)
spt, err = deviceFlowAuth(*oauthCfg, appID, scope)
if err != nil {
return nil, err
}
log.Debug("Obtained a token.")
if err := saveToken(spt.Token); err != nil {
log.Error("Error occurred saving token to cache file.")
return nil, err
}
return spt, nil
}
// AuthenticateServicePrincipal uses given service principal credentials to return a
// service principal token. Generated token is not stored in a cache file or refreshed.
func AuthenticateServicePrincipal(env azure.Environment, subscriptionID, spID, spPassword string) (*azure.ServicePrincipalToken, error) {
tenantID, err := loadOrFindTenantID(env, subscriptionID)
if err != nil {
return nil, err
}
oauthCfg, err := env.OAuthConfigForTenant(tenantID)
if err != nil {
return nil, fmt.Errorf("Failed to obtain oauth config for azure environment: %v", err)
}
spt, err := azure.NewServicePrincipalToken(*oauthCfg, spID, spPassword, getScope(env))
if err != nil {
return nil, fmt.Errorf("Failed to create service principal token: %+v", err)
}
return spt, nil
}
// tokenFromFile returns a token from the specified file if it is found, otherwise
// returns nil. Any error retrieving or creating the token is returned as an error.
func tokenFromFile(oauthCfg azure.OAuthConfig, tokenPath, clientID, resource string,
callback azure.TokenRefreshCallback) (*azure.ServicePrincipalToken, error) {
log.Debug("Loading auth token from file", logutil.Fields{"path": tokenPath})
if _, err := os.Stat(tokenPath); err != nil {
if os.IsNotExist(err) { // file not found
return nil, nil
}
return nil, err
}
token, err := azure.LoadToken(tokenPath)
if err != nil {
return nil, fmt.Errorf("Failed to load token from file: %v", err)
}
spt, err := azure.NewServicePrincipalTokenFromManualToken(oauthCfg, clientID, resource, *token, callback)
if err != nil {
return nil, fmt.Errorf("Error constructing service principal token: %v", err)
}
return spt, nil
}
// deviceFlowAuth prints a message to the screen for user to take action to
// consent application on a browser and in the meanwhile the authentication
// endpoint is polled until user gives consent, denies or the flow times out.
// Returned token must be saved.
func deviceFlowAuth(oauthCfg azure.OAuthConfig, clientID, resource string) (*azure.ServicePrincipalToken, error) {
cl := oauthClient()
deviceCode, err := azure.InitiateDeviceAuth(&cl, oauthCfg, clientID, resource)
if err != nil {
return nil, fmt.Errorf("Failed to start device auth: %v", err)
}
log.Debug("Retrieved device code.", logutil.Fields{
"expires_in": to.Int64(deviceCode.ExpiresIn),
"interval": to.Int64(deviceCode.Interval),
})
// Example message: “To sign in, open https://aka.ms/devicelogin and enter
// the code 0000000 to authenticate.”
log.Infof("Microsoft Azure: %s", to.String(deviceCode.Message))
token, err := azure.WaitForUserCompletion(&cl, deviceCode)
if err != nil {
return nil, fmt.Errorf("Failed to complete device auth: %v", err)
}
spt, err := azure.NewServicePrincipalTokenFromManualToken(oauthCfg, clientID, resource, *token)
if err != nil {
return nil, fmt.Errorf("Error constructing service principal token: %v", err)
}
return spt, nil
}
// azureCredsPath returns the directory the azure credentials are stored in.
func azureCredsPath() string {
return filepath.Join(mcnutils.GetHomeDir(), ".docker", "machine", "credentials", "azure")
}
// tokenCachePath returns the full path the OAuth 2.0 token should be saved at
// for given tenant ID.
func tokenCachePath(tenantID string) string {
return filepath.Join(azureCredsPath(), fmt.Sprintf("%s.json", tenantID))
}
// tenantIDPath returns the full path the tenant ID for the given subscription
// should be saved at.f
func tenantIDPath(subscriptionID string) string {
return filepath.Join(azureCredsPath(), fmt.Sprintf("%s.tenantid", subscriptionID))
}
// mkTokenCallback returns a callback function that can be used to save the
// token initially or register to the Azure SDK to be called when the token is
// refreshed.
func mkTokenCallback(path string) azure.TokenRefreshCallback {
return func(t azure.Token) error {
if err := azure.SaveToken(path, 0600, t); err != nil {
return err
}
log.Debug("Saved token to file.")
return nil
}
}
// validateToken makes a call to Azure SDK with given token, essentially making
// sure if the access_token valid, if not it uses SDK’s functionality to
// automatically refresh the token using refresh_token (which might have
// expired). This check is essentially to make sure refresh_token is good.
func validateToken(env azure.Environment, token *azure.ServicePrincipalToken) error {
c := subscriptionsClient(env.ResourceManagerEndpoint)
c.Authorizer = token
_, err := c.List()
if err != nil {
return fmt.Errorf("Token validity check failed: %v", err)
}
return nil
}
// getScope returns the API scope for authnetication tokens.
func getScope(env azure.Environment) string {
// for AzurePublicCloud (https://management.core.windows.net/), this old
// Service Management scope covers both ASM and ARM.
return env.ServiceManagementEndpoint
}
马建仓 AI 助手
尝试更多
代码解读
代码找茬
代码优化
1
https://gitee.com/powerpaas/machine.git
git@gitee.com:powerpaas/machine.git
powerpaas
machine
machine
v0.9.0-rc2

搜索帮助

344bd9b3 5694891 D2dac590 5694891