mirror of https://github.com/k3s-io/k3s
Merge pull request #48854 from colemickens/msi
Automatic merge from submit-queue (batch tested with PRs 47066, 48892, 48933, 48854, 48894) azure: msi: add managed identity field, logic **What this PR does / why we need it**: Enables managed service identity support for the Azure cloudprovider. "Managed Service Identity" allows us to ask the Azure Compute infra to provision an identity for the VM. Users can then retrieve the identity and assign it RBAC permissions to talk to Azure ARM APIs for the purpose of the cloudprovider needs. Per the commit text: ``` The azure cloudprovider will now use the Managed Service Identity to retrieve access tokens for the Azure ARM APIs, rather than requiring hard-coded, user-specified credentials. ``` **Which issue this PR fixes** *(optional, in `fixes #<issue number>(, fixes #<issue_number>, ...)` format, will close that issue when PR gets merged)*: n/a **Special notes for your reviewer**: none **Release note**: ```release-note azure: support retrieving access tokens via managed identity extension ``` cc: @brendandburns @jdumars @anhowepull/6/head
commit
df47592d5a
|
@ -205,6 +205,7 @@ pkg/controller/volume/attachdetach/util
|
|||
pkg/conversion
|
||||
pkg/conversion/queryparams
|
||||
pkg/credentialprovider/aws
|
||||
pkg/credentialprovider/azure
|
||||
pkg/fieldpath
|
||||
pkg/fields
|
||||
pkg/hyperkube
|
||||
|
|
|
@ -108,6 +108,9 @@ type Config struct {
|
|||
|
||||
// Use instance metadata service where possible
|
||||
UseInstanceMetadata bool `json:"useInstanceMetadata" yaml:"useInstanceMetadata"`
|
||||
|
||||
// Use managed service identity for the virtual machine to access Azure ARM APIs
|
||||
UseManagedIdentityExtension bool `json:"useManagedIdentityExtension"`
|
||||
}
|
||||
|
||||
// Cloud holds the config and clients
|
||||
|
@ -151,62 +154,62 @@ func decodePkcs12(pkcs []byte, password string) (*x509.Certificate, *rsa.Private
|
|||
return certificate, rsaPrivateKey, nil
|
||||
}
|
||||
|
||||
// newServicePrincipalToken creates a new service principal token based on the configuration
|
||||
func newServicePrincipalToken(az *Cloud) (*adal.ServicePrincipalToken, error) {
|
||||
oauthConfig, err := adal.NewOAuthConfig(az.Environment.ActiveDirectoryEndpoint, az.TenantID)
|
||||
// GetServicePrincipalToken creates a new service principal token based on the configuration
|
||||
func GetServicePrincipalToken(config *Config, env *azure.Environment) (*adal.ServicePrincipalToken, error) {
|
||||
oauthConfig, err := adal.NewOAuthConfig(env.ActiveDirectoryEndpoint, config.TenantID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("creating the OAuth config: %v", err)
|
||||
}
|
||||
|
||||
if len(az.AADClientSecret) > 0 {
|
||||
if config.UseManagedIdentityExtension {
|
||||
glog.V(2).Infoln("azure: using managed identity extension to retrieve access token")
|
||||
return adal.NewServicePrincipalTokenFromMSI(
|
||||
*oauthConfig,
|
||||
env.ServiceManagementEndpoint)
|
||||
}
|
||||
|
||||
if len(config.AADClientSecret) > 0 {
|
||||
glog.V(2).Infoln("azure: using client_id+client_secret to retrieve access token")
|
||||
return adal.NewServicePrincipalToken(
|
||||
*oauthConfig,
|
||||
az.AADClientID,
|
||||
az.AADClientSecret,
|
||||
az.Environment.ServiceManagementEndpoint)
|
||||
} else if len(az.AADClientCertPath) > 0 && len(az.AADClientCertPassword) > 0 {
|
||||
certData, err := ioutil.ReadFile(az.AADClientCertPath)
|
||||
config.AADClientID,
|
||||
config.AADClientSecret,
|
||||
env.ServiceManagementEndpoint)
|
||||
}
|
||||
|
||||
if len(config.AADClientCertPath) > 0 && len(config.AADClientCertPassword) > 0 {
|
||||
glog.V(2).Infoln("azure: using jwt client_assertion (client_cert+client_private_key) to retrieve access token")
|
||||
certData, err := ioutil.ReadFile(config.AADClientCertPath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("reading the client certificate from file %s: %v", az.AADClientCertPath, err)
|
||||
return nil, fmt.Errorf("reading the client certificate from file %s: %v", config.AADClientCertPath, err)
|
||||
}
|
||||
certificate, privateKey, err := decodePkcs12(certData, az.AADClientCertPassword)
|
||||
certificate, privateKey, err := decodePkcs12(certData, config.AADClientCertPassword)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("decoding the client certificate: %v", err)
|
||||
}
|
||||
return adal.NewServicePrincipalTokenFromCertificate(
|
||||
*oauthConfig,
|
||||
az.AADClientID,
|
||||
config.AADClientID,
|
||||
certificate,
|
||||
privateKey,
|
||||
az.Environment.ServiceManagementEndpoint)
|
||||
} else {
|
||||
return nil, fmt.Errorf("No credentials provided for AAD application %s", az.AADClientID)
|
||||
env.ServiceManagementEndpoint)
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("No credentials provided for AAD application %s", config.AADClientID)
|
||||
}
|
||||
|
||||
// NewCloud returns a Cloud with initialized clients
|
||||
func NewCloud(configReader io.Reader) (cloudprovider.Interface, error) {
|
||||
var az Cloud
|
||||
|
||||
configContents, err := ioutil.ReadAll(configReader)
|
||||
config, env, err := ParseConfig(configReader)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = yaml.Unmarshal(configContents, &az)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
az := Cloud{
|
||||
Config: *config,
|
||||
Environment: *env,
|
||||
}
|
||||
|
||||
if az.Cloud == "" {
|
||||
az.Environment = azure.PublicCloud
|
||||
} else {
|
||||
az.Environment, err = azure.EnvironmentFromName(az.Cloud)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
servicePrincipalToken, err := newServicePrincipalToken(&az)
|
||||
servicePrincipalToken, err := GetServicePrincipalToken(config, env)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -321,6 +324,31 @@ func NewCloud(configReader io.Reader) (cloudprovider.Interface, error) {
|
|||
return &az, nil
|
||||
}
|
||||
|
||||
// ParseConfig returns a parsed configuration and azure.Environment for an Azure cloudprovider config file
|
||||
func ParseConfig(configReader io.Reader) (*Config, *azure.Environment, error) {
|
||||
var config Config
|
||||
|
||||
configContents, err := ioutil.ReadAll(configReader)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
err = yaml.Unmarshal(configContents, &config)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
var env azure.Environment
|
||||
if config.Cloud == "" {
|
||||
env = azure.PublicCloud
|
||||
} else {
|
||||
env, err = azure.EnvironmentFromName(config.Cloud)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
}
|
||||
return &config, &env, nil
|
||||
}
|
||||
|
||||
// Initialize passes a Kubernetes clientBuilder interface to the cloud provider
|
||||
func (az *Cloud) Initialize(clientBuilder controller.ControllerClientBuilder) {}
|
||||
|
||||
|
|
|
@ -17,11 +17,9 @@ go_library(
|
|||
"//pkg/credentialprovider:go_default_library",
|
||||
"//vendor/github.com/Azure/azure-sdk-for-go/arm/containerregistry:go_default_library",
|
||||
"//vendor/github.com/Azure/go-autorest/autorest:go_default_library",
|
||||
"//vendor/github.com/Azure/go-autorest/autorest/adal:go_default_library",
|
||||
"//vendor/github.com/Azure/go-autorest/autorest/azure:go_default_library",
|
||||
"//vendor/github.com/golang/glog:go_default_library",
|
||||
"//vendor/github.com/spf13/pflag:go_default_library",
|
||||
"//vendor/gopkg.in/yaml.v2:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
|
|
|
@ -17,14 +17,12 @@ limitations under the License.
|
|||
package azure
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"io"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
yaml "gopkg.in/yaml.v2"
|
||||
|
||||
"github.com/Azure/azure-sdk-for-go/arm/containerregistry"
|
||||
"github.com/Azure/go-autorest/autorest"
|
||||
"github.com/Azure/go-autorest/autorest/adal"
|
||||
azureapi "github.com/Azure/go-autorest/autorest/azure"
|
||||
"github.com/golang/glog"
|
||||
"github.com/spf13/pflag"
|
||||
|
@ -47,10 +45,12 @@ func init() {
|
|||
})
|
||||
}
|
||||
|
||||
// RegistriesClient is a testable interface for the ACR client List operation.
|
||||
type RegistriesClient interface {
|
||||
List() (containerregistry.RegistryListResult, error)
|
||||
}
|
||||
|
||||
// NewACRProvider parses the specified configFile and returns a DockerConfigProvider
|
||||
func NewACRProvider(configFile *string) credentialprovider.DockerConfigProvider {
|
||||
return &acrProvider{
|
||||
file: configFile,
|
||||
|
@ -59,24 +59,16 @@ func NewACRProvider(configFile *string) credentialprovider.DockerConfigProvider
|
|||
|
||||
type acrProvider struct {
|
||||
file *string
|
||||
config azure.Config
|
||||
environment azureapi.Environment
|
||||
config *azure.Config
|
||||
environment *azureapi.Environment
|
||||
registryClient RegistriesClient
|
||||
}
|
||||
|
||||
func (a *acrProvider) loadConfig(contents []byte) error {
|
||||
err := yaml.Unmarshal(contents, &a.config)
|
||||
func (a *acrProvider) loadConfig(rdr io.Reader) error {
|
||||
var err error
|
||||
a.config, a.environment, err = azure.ParseConfig(rdr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if a.config.Cloud == "" {
|
||||
a.environment = azureapi.PublicCloud
|
||||
} else {
|
||||
a.environment, err = azureapi.EnvironmentFromName(a.config.Cloud)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
glog.Errorf("Failed to load azure credential file: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -86,27 +78,21 @@ func (a *acrProvider) Enabled() bool {
|
|||
glog.V(5).Infof("Azure config unspecified, disabling")
|
||||
return false
|
||||
}
|
||||
contents, err := ioutil.ReadFile(*a.file)
|
||||
|
||||
f, err := os.Open(*a.file)
|
||||
if err != nil {
|
||||
glog.Errorf("Failed to load azure credential file: %v", err)
|
||||
glog.Errorf("Failed to load config from file: %s", *a.file)
|
||||
return false
|
||||
}
|
||||
if err := a.loadConfig(contents); err != nil {
|
||||
glog.Errorf("Failed to parse azure credential file: %v", err)
|
||||
defer f.Close()
|
||||
|
||||
err = a.loadConfig(f)
|
||||
if err != nil {
|
||||
glog.Errorf("Failed to load config from file: %s", *a.file)
|
||||
return false
|
||||
}
|
||||
|
||||
oauthConfig, err := adal.NewOAuthConfig(a.environment.ActiveDirectoryEndpoint, a.config.TenantID)
|
||||
if err != nil {
|
||||
glog.Errorf("Failed to get oauth config: %v", err)
|
||||
return false
|
||||
}
|
||||
|
||||
servicePrincipalToken, err := adal.NewServicePrincipalToken(
|
||||
*oauthConfig,
|
||||
a.config.AADClientID,
|
||||
a.config.AADClientSecret,
|
||||
a.environment.ServiceManagementEndpoint)
|
||||
servicePrincipalToken, err := azure.GetServicePrincipalToken(a.config, a.environment)
|
||||
if err != nil {
|
||||
glog.Errorf("Failed to create service principal token: %v", err)
|
||||
return false
|
||||
|
|
|
@ -17,6 +17,7 @@ limitations under the License.
|
|||
package azure
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
|
||||
"github.com/Azure/azure-sdk-for-go/arm/containerregistry"
|
||||
|
@ -66,7 +67,7 @@ func Test(t *testing.T) {
|
|||
provider := &acrProvider{
|
||||
registryClient: fakeClient,
|
||||
}
|
||||
provider.loadConfig([]byte(configStr))
|
||||
provider.loadConfig(bytes.NewBufferString(configStr))
|
||||
|
||||
creds := provider.Provide()
|
||||
|
||||
|
|
Loading…
Reference in New Issue