mirror of
https://github.com/kubernetes-sigs/external-dns.git
synced 2025-08-06 09:36:58 +02:00
163 lines
6.5 KiB
Go
163 lines
6.5 KiB
Go
/*
|
|
Copyright 2017 The Kubernetes Authors.
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
you may not use this file except in compliance with the License.
|
|
You may obtain a copy of the License at
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
See the License for the specific language governing permissions and
|
|
limitations under the License.
|
|
*/
|
|
|
|
package azure
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"strings"
|
|
|
|
"github.com/Azure/azure-sdk-for-go/sdk/azcore"
|
|
"github.com/Azure/azure-sdk-for-go/sdk/azcore/arm"
|
|
"github.com/Azure/azure-sdk-for-go/sdk/azcore/cloud"
|
|
"github.com/Azure/azure-sdk-for-go/sdk/azidentity"
|
|
log "github.com/sirupsen/logrus"
|
|
"gopkg.in/yaml.v2"
|
|
)
|
|
|
|
// config represents common config items for Azure DNS and Azure Private DNS
|
|
type config struct {
|
|
Cloud string `json:"cloud" yaml:"cloud"`
|
|
TenantID string `json:"tenantId" yaml:"tenantId"`
|
|
SubscriptionID string `json:"subscriptionId" yaml:"subscriptionId"`
|
|
ResourceGroup string `json:"resourceGroup" yaml:"resourceGroup"`
|
|
Location string `json:"location" yaml:"location"`
|
|
ClientID string `json:"aadClientId" yaml:"aadClientId"`
|
|
ClientSecret string `json:"aadClientSecret" yaml:"aadClientSecret"`
|
|
UseManagedIdentityExtension bool `json:"useManagedIdentityExtension" yaml:"useManagedIdentityExtension"`
|
|
UseWorkloadIdentityExtension bool `json:"useWorkloadIdentityExtension" yaml:"useWorkloadIdentityExtension"`
|
|
UserAssignedIdentityID string `json:"userAssignedIdentityID" yaml:"userAssignedIdentityID"`
|
|
ActiveDirectoryAuthorityHost string `json:"activeDirectoryAuthorityHost" yaml:"activeDirectoryAuthorityHost"`
|
|
}
|
|
|
|
func getConfig(configFile, subscriptionID, resourceGroup, userAssignedIdentityClientID, activeDirectoryAuthorityHost string) (*config, error) {
|
|
contents, err := os.ReadFile(configFile)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to read Azure config file '%s': %v", configFile, err)
|
|
}
|
|
cfg := &config{}
|
|
err = yaml.Unmarshal(contents, &cfg)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to read Azure config file '%s': %v", configFile, err)
|
|
}
|
|
// If a subscription ID was given, override what was present in the config file
|
|
if subscriptionID != "" {
|
|
cfg.SubscriptionID = subscriptionID
|
|
}
|
|
// If a resource group was given, override what was present in the config file
|
|
if resourceGroup != "" {
|
|
cfg.ResourceGroup = resourceGroup
|
|
}
|
|
// If userAssignedIdentityClientID is provided explicitly, override existing one in config file
|
|
if userAssignedIdentityClientID != "" {
|
|
cfg.UserAssignedIdentityID = userAssignedIdentityClientID
|
|
}
|
|
// If activeDirectoryAuthorityHost is provided explicitly, override existing one in config file
|
|
if activeDirectoryAuthorityHost != "" {
|
|
cfg.ActiveDirectoryAuthorityHost = activeDirectoryAuthorityHost
|
|
}
|
|
return cfg, nil
|
|
}
|
|
|
|
// getCredentials retrieves Azure API credentials.
|
|
func getCredentials(cfg config) (azcore.TokenCredential, *arm.ClientOptions, error) {
|
|
cloudCfg, err := getCloudConfiguration(cfg.Cloud)
|
|
if err != nil {
|
|
return nil, nil, fmt.Errorf("failed to get cloud configuration: %w", err)
|
|
}
|
|
clientOpts := azcore.ClientOptions{
|
|
Cloud: cloudCfg,
|
|
}
|
|
armClientOpts := &arm.ClientOptions{
|
|
ClientOptions: clientOpts,
|
|
}
|
|
|
|
// Try to retrieve token with service principal credentials.
|
|
// Try to use service principal first, some AKS clusters are in an intermediate state that `UseManagedIdentityExtension` is `true`
|
|
// and service principal exists. In this case, we still want to use service principal to authenticate.
|
|
if len(cfg.ClientID) > 0 &&
|
|
len(cfg.ClientSecret) > 0 &&
|
|
// due to some historical reason, for pure MSI cluster,
|
|
// they will use "msi" as placeholder in azure.json.
|
|
// In this case, we shouldn't try to use SPN to authenticate.
|
|
!strings.EqualFold(cfg.ClientID, "msi") &&
|
|
!strings.EqualFold(cfg.ClientSecret, "msi") {
|
|
log.Info("Using client_id+client_secret to retrieve access token for Azure API.")
|
|
opts := &azidentity.ClientSecretCredentialOptions{
|
|
ClientOptions: clientOpts,
|
|
}
|
|
cred, err := azidentity.NewClientSecretCredential(cfg.TenantID, cfg.ClientID, cfg.ClientSecret, opts)
|
|
if err != nil {
|
|
return nil, nil, fmt.Errorf("failed to create service principal token: %w", err)
|
|
}
|
|
return cred, armClientOpts, nil
|
|
}
|
|
|
|
// Try to retrieve token with Workload Identity.
|
|
if cfg.UseWorkloadIdentityExtension {
|
|
log.Info("Using workload identity extension to retrieve access token for Azure API.")
|
|
|
|
wiOpt := azidentity.WorkloadIdentityCredentialOptions{
|
|
ClientOptions: clientOpts,
|
|
// In a standard scenario, Client ID and Tenant ID are expected to be read from environment variables.
|
|
// Though, in certain cases, it might be important to have an option to override those (e.g. when AZURE_TENANT_ID is not set
|
|
// through a webhook or azure.workload.identity/client-id service account annotation is absent). When any of those values are
|
|
// empty in our config, they will automatically be read from environment variables by azidentity
|
|
TenantID: cfg.TenantID,
|
|
ClientID: cfg.ClientID,
|
|
}
|
|
|
|
cred, err := azidentity.NewWorkloadIdentityCredential(&wiOpt)
|
|
if err != nil {
|
|
return nil, nil, fmt.Errorf("failed to create a workload identity token: %w", err)
|
|
}
|
|
|
|
return cred, armClientOpts, nil
|
|
}
|
|
|
|
// Try to retrieve token with MSI.
|
|
if cfg.UseManagedIdentityExtension {
|
|
log.Info("Using managed identity extension to retrieve access token for Azure API.")
|
|
msiOpt := azidentity.ManagedIdentityCredentialOptions{
|
|
ClientOptions: clientOpts,
|
|
}
|
|
if cfg.UserAssignedIdentityID != "" {
|
|
msiOpt.ID = azidentity.ClientID(cfg.UserAssignedIdentityID)
|
|
}
|
|
cred, err := azidentity.NewManagedIdentityCredential(&msiOpt)
|
|
if err != nil {
|
|
return nil, nil, fmt.Errorf("failed to create the managed service identity token: %w", err)
|
|
}
|
|
return cred, armClientOpts, nil
|
|
}
|
|
|
|
return nil, nil, fmt.Errorf("no credentials provided for Azure API")
|
|
}
|
|
|
|
func getCloudConfiguration(name string) (cloud.Configuration, error) {
|
|
name = strings.ToUpper(name)
|
|
switch name {
|
|
case "AZURECLOUD", "AZUREPUBLICCLOUD", "":
|
|
return cloud.AzurePublic, nil
|
|
case "AZUREUSGOVERNMENT", "AZUREUSGOVERNMENTCLOUD":
|
|
return cloud.AzureGovernment, nil
|
|
case "AZURECHINACLOUD":
|
|
return cloud.AzureChina, nil
|
|
}
|
|
return cloud.Configuration{}, fmt.Errorf("unknown cloud name: %s", name)
|
|
}
|