зеркало из https://github.com/Azure/aks-canipull.git
165 строки
5.4 KiB
Go
165 строки
5.4 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"net"
|
|
"os"
|
|
"strings"
|
|
|
|
"github.com/Azure/aks-canipull/pkg/authorizer"
|
|
"github.com/Azure/aks-canipull/pkg/utils"
|
|
|
|
"github.com/Azure/aks-canipull/pkg/exitcode"
|
|
"github.com/Azure/aks-canipull/pkg/log"
|
|
az "github.com/Azure/go-autorest/autorest/azure"
|
|
"k8s.io/legacy-cloud-providers/azure"
|
|
|
|
flag "github.com/spf13/pflag"
|
|
)
|
|
|
|
const (
|
|
DefaultAzureCfgPath string = "/etc/kubernetes/azure.json"
|
|
)
|
|
|
|
var (
|
|
logLevel *uint = flag.UintP("verbose", "v", 2, "output verbosity level.")
|
|
configPath *string = flag.String("config", "", "the azure.json config file path.")
|
|
)
|
|
|
|
func main() {
|
|
flag.Parse()
|
|
|
|
if len(flag.Args()) != 1 {
|
|
fmt.Println("No ACR input. Expect `canipull myacr.azurecr.io`.")
|
|
return
|
|
}
|
|
|
|
ctx := log.WithLogLevel(context.Background(), *logLevel)
|
|
acr := flag.Args()[0]
|
|
logger := log.FromContext(ctx)
|
|
|
|
if _, err := net.LookupHost(acr); err != nil {
|
|
logger.V(2).Info("Checking host name resolution (%s): FAILED", acr)
|
|
logger.V(2).Info("Failed to resolve specified fqdn %s: %s", acr, err)
|
|
os.Exit(exitcode.DNSResolutionFailure)
|
|
}
|
|
logger.V(2).Info("Checking host name resolution (%s): SUCCEEDED", acr)
|
|
|
|
cname, err := net.LookupCNAME(acr)
|
|
if err != nil {
|
|
logger.V(2).Info("Checking CNAME (%s): FAILED", acr)
|
|
logger.V(2).Info("Failed to get CNAME of the ACR: %s", err)
|
|
os.Exit(exitcode.DNSResolutionFailure)
|
|
}
|
|
|
|
logger.V(2).Info("Canonical name for ACR (%s): %s", acr, cname)
|
|
|
|
acrLocation := strings.Split(cname, ".")[1]
|
|
logger.V(2).Info("ACR location: %s", acrLocation)
|
|
|
|
azConfigPath := *configPath
|
|
if *configPath == "" {
|
|
azConfigPath = DefaultAzureCfgPath
|
|
}
|
|
|
|
logger.V(6).Info("Loading azure.json file from %s", azConfigPath)
|
|
if _, err := os.Stat(azConfigPath); err != nil {
|
|
logger.V(2).Info("Failed to load azure.json. Are you running inside Kubernetes on Azure? \n")
|
|
os.Exit(exitcode.AzureConfigNotFound)
|
|
}
|
|
|
|
var cfg azure.Config
|
|
configBytes, err := ioutil.ReadFile(azConfigPath)
|
|
if err != nil {
|
|
logger.V(2).Info("Failed to read azure.json file: %s \n", err)
|
|
os.Exit(exitcode.AzureConfigReadFailure)
|
|
}
|
|
|
|
if err := json.Unmarshal(configBytes, &cfg); err != nil {
|
|
logger.V(2).Info("Failed to read azure.json file: %s", err)
|
|
os.Exit(exitcode.AzureConfigUnmarshalFailure)
|
|
}
|
|
|
|
if !utils.LocationEquals(acrLocation, cfg.Location) {
|
|
logger.V(2).Info("Checking ACR location matches cluster location: FAILED")
|
|
logger.V(2).Info("ACR location '%s' does not match your cluster location '%s'. This may result in slow image pulls and extra cost.", acrLocation, cfg.Location)
|
|
}
|
|
|
|
if cfg.AADClientID == "msi" && cfg.AADClientSecret == "msi" {
|
|
logger.V(2).Info("Checking managed identity...")
|
|
os.Exit(validateMsiAuth(ctx, acr, cfg, logger))
|
|
return
|
|
}
|
|
|
|
logger.V(4).Info("The cluster uses service principal.")
|
|
os.Exit(validateServicePrincipalAuth(ctx, acr, cfg, logger))
|
|
}
|
|
|
|
func validateMsiAuth(ctx context.Context, acr string, cfg azure.Config, logger *log.Logger) int {
|
|
logger.V(6).Info("Cluster cloud name: %s", cfg.Cloud)
|
|
|
|
env, err := az.EnvironmentFromName(cfg.Cloud)
|
|
if err != nil {
|
|
logger.V(2).Info("Unknown Azure cloud name: %s", cfg.Cloud)
|
|
return exitcode.AzureCloudUnknown
|
|
}
|
|
|
|
logger.V(2).Info("Kubelet managed identity client ID: %s", cfg.UserAssignedIdentityID)
|
|
tr := authorizer.NewTokenRetriever(env.ActiveDirectoryEndpoint)
|
|
token, err := tr.AcquireARMTokenMSI(ctx, cfg.UserAssignedIdentityID)
|
|
if err != nil {
|
|
logger.V(2).Info("Validating managed identity existance: FAILED")
|
|
logger.V(2).Info("Getting managed identity token failed with: %s", err)
|
|
return exitcode.ServicePrincipalCredentialInvalid
|
|
}
|
|
logger.V(2).Info("Validating managed identity existance: SUCCEEDED")
|
|
logger.V(9).Info("ARM access token: %s", token)
|
|
|
|
te := authorizer.NewTokenExchanger()
|
|
acrToken, err := te.ExchangeACRAccessToken(token, acr)
|
|
if err != nil {
|
|
logger.V(2).Info("Validating image pull permission: FAILED")
|
|
logger.V(2).Info("ACR %s rejected token exchange: %s", acr, err)
|
|
return exitcode.MissingImagePullPermision
|
|
}
|
|
|
|
logger.V(2).Info("Validating image pull permission: SUCCEEDED")
|
|
logger.V(9).Info("ACR access token: %s", acrToken)
|
|
logger.V(2).Info("\nYour cluster can pull images from %s!", acr)
|
|
return 0
|
|
}
|
|
|
|
func validateServicePrincipalAuth(ctx context.Context, acr string, cfg azure.Config, logger *log.Logger) int {
|
|
env, err := az.EnvironmentFromName(cfg.Cloud)
|
|
if err != nil {
|
|
logger.V(2).Info("Unknown Azure cloud name: %s", cfg.Cloud)
|
|
return exitcode.AzureCloudUnknown
|
|
}
|
|
|
|
tr := authorizer.NewTokenRetriever(env.ActiveDirectoryEndpoint)
|
|
token, err := tr.AcquireARMTokenSP(ctx, cfg.AADClientID, cfg.AADClientSecret, cfg.TenantID)
|
|
if err != nil {
|
|
logger.V(2).Info("Validating service principal credential: FAILED")
|
|
logger.V(2).Info("Sign in to AAD failed with: %s", err)
|
|
return exitcode.ServicePrincipalCredentialInvalid
|
|
}
|
|
logger.V(2).Info("Validating service principal credential: SUCCEEDED")
|
|
logger.V(9).Info("ARM access token: %s", token)
|
|
|
|
te := authorizer.NewTokenExchanger()
|
|
acrToken, err := te.ExchangeACRAccessToken(token, acr)
|
|
if err != nil {
|
|
logger.V(2).Info("Validating image pull permission: FAILED")
|
|
logger.V(2).Info("ACR %s rejected token exchange: %s", acr, err)
|
|
return exitcode.MissingImagePullPermision
|
|
}
|
|
|
|
logger.V(2).Info("Validating image pull permission: SUCCEEDED")
|
|
logger.V(9).Info("ACR access token: %s", acrToken)
|
|
logger.V(2).Info("\nYour cluster can pull images from %s!", acr)
|
|
return 0
|
|
}
|