From d55c592b1ce277fcf33b5295019fc6e24a1aeccd Mon Sep 17 00:00:00 2001 From: Nader Ziada Date: Fri, 9 Oct 2020 14:28:17 -0400 Subject: [PATCH] user azure identity instead of principal --- Makefile | 5 +- api/v1alpha2/azurecluster_conversion.go | 2 +- api/v1alpha2/zz_generated.conversion.go | 2 +- api/v1alpha3/azurecluster_types.go | 15 +- api/v1alpha3/azureserviceprincipal_types.go | 72 ------- api/v1alpha3/zz_generated.deepcopy.go | 96 +--------- cloud/scope/clients.go | 5 +- cloud/scope/cluster.go | 4 +- cloud/scope/identity.go | 179 ++++++++++++++++++ cloud/scope/principal.go | 167 ---------------- .../aad-pod-identity-deployment.yaml | 35 ++-- config/aadpodidentity/kustomization.yaml | 2 + ...uster.x-k8s.io_azureclusterprincipals.yaml | 122 ------------ ...ucture.cluster.x-k8s.io_azureclusters.yaml | 25 +-- ...ture.cluster.x-k8s.io_azureprincipals.yaml | 77 -------- config/kustomization.yaml | 1 + config/manager/manager.yaml | 2 + config/rbac/role.yaml | 12 -- controllers/azurecluster_controller.go | 1 - go.sum | 1 + 20 files changed, 212 insertions(+), 613 deletions(-) delete mode 100644 api/v1alpha3/azureserviceprincipal_types.go create mode 100644 cloud/scope/identity.go delete mode 100644 cloud/scope/principal.go rename {templates/addons => config/aadpodidentity}/aad-pod-identity-deployment.yaml (92%) create mode 100644 config/aadpodidentity/kustomization.yaml delete mode 100644 config/crd/bases/infrastructure.cluster.x-k8s.io_azureclusterprincipals.yaml delete mode 100644 config/crd/bases/infrastructure.cluster.x-k8s.io_azureprincipals.yaml diff --git a/Makefile b/Makefile index 7d7d653ee3f..a202e38f108 100644 --- a/Makefile +++ b/Makefile @@ -426,7 +426,7 @@ create-management-cluster: $(KUSTOMIZE) $(ENVSUBST) kubectl wait --for=condition=Available --timeout=5m -n capi-kubeadm-bootstrap-system deployment -l cluster.x-k8s.io/provider=bootstrap-kubeadm kubectl wait --for=condition=Available --timeout=5m -n capi-kubeadm-control-plane-system deployment -l cluster.x-k8s.io/provider=control-plane-kubeadm - sleep 5 + sleep 10 # apply CNI ClusterResourceSets kubectl create configmap calico-addon --from-file=templates/addons/calico.yaml kubectl create configmap calico-ipv6-addon --from-file=templates/addons/calico-ipv6.yaml @@ -439,9 +439,6 @@ create-management-cluster: $(KUSTOMIZE) $(ENVSUBST) sleep 10 @echo 'Set kubectl context to the kind management cluster by running "kubectl config set-context kind-capz"' - # aad-pod-identity deploylment for handling identities - $(ENVSUBST) < $(TEMPLATES_DIR)/addons/aad-pod-identity-deployment.yaml | kubectl apply -f - - .PHONY: create-workload-cluster create-workload-cluster: $(ENVSUBST) # Create workload Cluster. diff --git a/api/v1alpha2/azurecluster_conversion.go b/api/v1alpha2/azurecluster_conversion.go index cf8627a2f22..a71c696e69f 100644 --- a/api/v1alpha2/azurecluster_conversion.go +++ b/api/v1alpha2/azurecluster_conversion.go @@ -60,7 +60,7 @@ func (src *AzureCluster) ConvertTo(dstRaw conversion.Hub) error { // nolint dst.Status.FailureDomains = restored.Status.FailureDomains dst.Spec.NetworkSpec.Vnet.CIDRBlocks = restored.Spec.NetworkSpec.Vnet.CIDRBlocks - dst.Spec.PrincipalRef = restored.Spec.PrincipalRef + dst.Spec.IdentityName = restored.Spec.IdentityName for _, restoredSubnet := range restored.Spec.NetworkSpec.Subnets { if restoredSubnet != nil { diff --git a/api/v1alpha2/zz_generated.conversion.go b/api/v1alpha2/zz_generated.conversion.go index 37dcd6b7862..6530d6519cb 100644 --- a/api/v1alpha2/zz_generated.conversion.go +++ b/api/v1alpha2/zz_generated.conversion.go @@ -434,7 +434,7 @@ func autoConvert_v1alpha3_AzureClusterSpec_To_v1alpha2_AzureClusterSpec(in *v1al out.Location = in.Location // WARNING: in.ControlPlaneEndpoint requires manual conversion: does not exist in peer-type out.AdditionalTags = *(*Tags)(unsafe.Pointer(&in.AdditionalTags)) - // WARNING: in.PrincipalRef requires manual conversion: does not exist in peer-type + // WARNING: in.IdentityName requires manual conversion: does not exist in peer-type return nil } diff --git a/api/v1alpha3/azurecluster_types.go b/api/v1alpha3/azurecluster_types.go index c9d3d9793e9..7ce92ecd769 100644 --- a/api/v1alpha3/azurecluster_types.go +++ b/api/v1alpha3/azurecluster_types.go @@ -27,17 +27,6 @@ const ( ClusterFinalizer = "azurecluster.infrastructure.cluster.x-k8s.io" ) -// AzurePrincipalKind defines allowed Azure cluster principal types -// +kubebuilder:validation:Enum=AzureSystemAssigned;AzureUserAssigned;AzureServicePrincipal -type AzurePrincipalKind string - -// AzurePrincipalRef is a reference to a principal -type AzurePrincipalRef struct { - Kind AzurePrincipalKind `json:"kind"` - Name string `json:"name"` - Namespace string `json:"namespace"` -} - // AzureClusterSpec defines the desired state of AzureCluster type AzureClusterSpec struct { // NetworkSpec encapsulates all things related to Azure network. @@ -60,9 +49,9 @@ type AzureClusterSpec struct { // +optional AdditionalTags Tags `json:"additionalTags,omitempty"` - // PrincipalRef is a reference to a principal to be used when reconciling this cluster + // IdentityName is a reference to a AzureIdentity to be used when reconciling this cluster // +optional - PrincipalRef *AzurePrincipalRef `json:"principalRef,omitempty"` + IdentityName *string `json:"identityName,omitempty"` } // AzureClusterStatus defines the observed state of AzureCluster diff --git a/api/v1alpha3/azureserviceprincipal_types.go b/api/v1alpha3/azureserviceprincipal_types.go deleted file mode 100644 index f5b13cfaf07..00000000000 --- a/api/v1alpha3/azureserviceprincipal_types.go +++ /dev/null @@ -1,72 +0,0 @@ -/* -Copyright 2020 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 v1alpha3 - -import ( - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) - -// +kubebuilder:object:root=true -// +kubebuilder:resource:path=azureclusterprincipals,scope=Namespaced,categories=cluster-api -// +kubebuilder:storageversion - -// AzureClusterPrincipal represents a reference to an Azure access key ID and -// secret access key, stored in a secret. -type AzureClusterPrincipal struct { - metav1.TypeMeta `json:",inline"` - metav1.ObjectMeta `json:"metadata,omitempty"` - - // Spec for this AzureClusterPrincipalSpec. - Spec AzureClusterPrincipalSpec `json:"spec,omitempty"` -} - -// +kubebuilder:object:root=true - -// AzureClusterPrincipalList contains a list of AzureClusterPrincipal -type AzureClusterPrincipalList struct { - metav1.TypeMeta `json:",inline"` - metav1.ListMeta `json:"metadata,omitempty"` - Items []AzureClusterPrincipal `json:"items"` -} - -// AzureClusterPrincipalSpec defines the fileds of the Azure cluster principal -type AzureClusterPrincipalSpec struct { - Name string `json:"name"` - // Reference to a secret containing the credentials. The secret should - // contain the following data keys: - // tenantID - // clientID - // clientSecret - SecretRef corev1.SecretReference `json:"secretRef"` - // AllowedNamespaces is a selector of namespaces that AzureClusters can - // use this ClusterPrincipal from. This is a standard Kubernetes LabelSelector, - // a label query over a set of resources. The result of matchLabels and - // matchExpressions are ANDed. Controllers must not support AzureClusters in - // namespaces outside this selector. - // - // An empty selector (default) indicates that AzureClusters can use this - // AzureClusterPrincipal from any namespace. This field is intentionally not a - // pointer because the nil behavior (no namespaces) is undesirable here. - // - // +optional - AllowedNamespaces metav1.LabelSelector `json:"allowedNamespaces"` -} - -func init() { - SchemeBuilder.Register(&AzureClusterPrincipal{}, &AzureClusterPrincipalList{}) -} diff --git a/api/v1alpha3/zz_generated.deepcopy.go b/api/v1alpha3/zz_generated.deepcopy.go index 493941720b6..a10e1697ec9 100644 --- a/api/v1alpha3/zz_generated.deepcopy.go +++ b/api/v1alpha3/zz_generated.deepcopy.go @@ -111,81 +111,6 @@ func (in *AzureClusterList) DeepCopyObject() runtime.Object { return nil } -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *AzureClusterPrincipal) DeepCopyInto(out *AzureClusterPrincipal) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) - in.Spec.DeepCopyInto(&out.Spec) -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AzureClusterPrincipal. -func (in *AzureClusterPrincipal) DeepCopy() *AzureClusterPrincipal { - if in == nil { - return nil - } - out := new(AzureClusterPrincipal) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *AzureClusterPrincipal) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } - return nil -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *AzureClusterPrincipalList) DeepCopyInto(out *AzureClusterPrincipalList) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ListMeta.DeepCopyInto(&out.ListMeta) - if in.Items != nil { - in, out := &in.Items, &out.Items - *out = make([]AzureClusterPrincipal, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AzureClusterPrincipalList. -func (in *AzureClusterPrincipalList) DeepCopy() *AzureClusterPrincipalList { - if in == nil { - return nil - } - out := new(AzureClusterPrincipalList) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *AzureClusterPrincipalList) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } - return nil -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *AzureClusterPrincipalSpec) DeepCopyInto(out *AzureClusterPrincipalSpec) { - *out = *in - out.SecretRef = in.SecretRef - in.AllowedNamespaces.DeepCopyInto(&out.AllowedNamespaces) -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AzureClusterPrincipalSpec. -func (in *AzureClusterPrincipalSpec) DeepCopy() *AzureClusterPrincipalSpec { - if in == nil { - return nil - } - out := new(AzureClusterPrincipalSpec) - in.DeepCopyInto(out) - return out -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *AzureClusterSpec) DeepCopyInto(out *AzureClusterSpec) { *out = *in @@ -198,9 +123,9 @@ func (in *AzureClusterSpec) DeepCopyInto(out *AzureClusterSpec) { (*out)[key] = val } } - if in.PrincipalRef != nil { - in, out := &in.PrincipalRef, &out.PrincipalRef - *out = new(AzurePrincipalRef) + if in.IdentityName != nil { + in, out := &in.IdentityName, &out.IdentityName + *out = new(string) **out = **in } } @@ -512,21 +437,6 @@ func (in *AzureMarketplaceImage) DeepCopy() *AzureMarketplaceImage { return out } -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *AzurePrincipalRef) DeepCopyInto(out *AzurePrincipalRef) { - *out = *in -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AzurePrincipalRef. -func (in *AzurePrincipalRef) DeepCopy() *AzurePrincipalRef { - if in == nil { - return nil - } - out := new(AzurePrincipalRef) - in.DeepCopyInto(out) - return out -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *AzureSharedGalleryImage) DeepCopyInto(out *AzureSharedGalleryImage) { *out = *in diff --git a/cloud/scope/clients.go b/cloud/scope/clients.go index 659b4d878f5..85cb6a3934e 100644 --- a/cloud/scope/clients.go +++ b/cloud/scope/clients.go @@ -104,11 +104,8 @@ func (c *AzureClients) setCredentialsWithProvider(ctx context.Context, subscript c.EnvironmentSettings = settings c.ResourceManagerEndpoint = settings.Environment.ResourceManagerEndpoint c.ResourceManagerVMDNSSuffix = settings.Environment.ResourceManagerVMDNSSuffix - c.Values[auth.ClientID] = strings.TrimSuffix(c.Values[auth.ClientID], "\n") - c.Values[auth.ClientSecret] = strings.TrimSuffix(c.Values[auth.ClientSecret], "\n") c.Values[auth.SubscriptionID] = strings.TrimSuffix(subscriptionID, "\n") - c.Values[auth.TenantID] = strings.TrimSuffix(c.Values[auth.TenantID], "\n") - c.Authorizer, err = credentialsProvider.GetAuthorizer(ctx) + c.Authorizer, err = credentialsProvider.GetAuthorizer(ctx, c.ResourceManagerEndpoint) return err } diff --git a/cloud/scope/cluster.go b/cloud/scope/cluster.go index 3a377ee2a9d..4d8490a287b 100644 --- a/cloud/scope/cluster.go +++ b/cloud/scope/cluster.go @@ -59,13 +59,13 @@ func NewClusterScope(ctx context.Context, params ClusterScopeParams) (*ClusterSc params.Logger = klogr.New() } - if params.AzureCluster.Spec.PrincipalRef == nil { + if params.AzureCluster.Spec.IdentityName == nil { err := params.AzureClients.setCredentials(params.AzureCluster.Spec.SubscriptionID) if err != nil { return nil, errors.Wrap(err, "failed to configure azure settings and credentials from environment") } } else { - credentailsProvider, err := NewAzureCredentialsProvider(ctx, params.Client, params.ResourceManagerEndpoint, params.AzureCluster.Spec.PrincipalRef) + credentailsProvider, err := NewAzureCredentialsProvider(ctx, params.AzureCluster.Namespace, to.String(params.AzureCluster.Spec.IdentityName)) if err != nil { return nil, errors.Wrap(err, "failed to init credentials provider") } diff --git a/cloud/scope/identity.go b/cloud/scope/identity.go new file mode 100644 index 00000000000..780f599944f --- /dev/null +++ b/cloud/scope/identity.go @@ -0,0 +1,179 @@ +/* +Copyright 2020 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 scope + +import ( + "context" + + "github.com/Azure/go-autorest/autorest" + "github.com/Azure/go-autorest/autorest/adal" + "github.com/pkg/errors" + + aadpodv1 "github.com/Azure/aad-pod-identity/pkg/apis/aadpodidentity/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/runtime/serializer" + clientgoscheme "k8s.io/client-go/kubernetes/scheme" + "k8s.io/client-go/rest" +) + +// AzureCredentialsProvider provides +type AzureCredentialsProvider struct { + CrdClient *rest.RESTClient + Identity aadpodv1.AzureIdentity +} + +// NewAzureCredentialsProvider creates a new AzureCredentialsProvider from the supplied inputs. +func NewAzureCredentialsProvider(ctx context.Context, namespace, identityName string) (*AzureCredentialsProvider, error) { + if identityName == "" { + return nil, errors.New("failed to generate new AzureCredentialsProvider from empty identityName") + } + + config, err := rest.InClusterConfig() + if err != nil { + return nil, errors.Wrap(err, "failed to get AzureIdentity") + } + crdClient, err := newRestClient(config) + if err != nil { + return nil, errors.Wrap(err, "failed to get AzureIdentity") + } + azureIdentity := aadpodv1.AzureIdentity{} + err = crdClient.Get().Namespace(namespace).Resource(aadpodv1.AzureIDResource).Name(identityName).Do().Into(&azureIdentity) + if err != nil { + return nil, errors.Wrap(err, "failed to get AzureIdentity") + } + + return &AzureCredentialsProvider{ + CrdClient: crdClient, + Identity: azureIdentity, + }, nil +} + +// GetAuthorizer returnes Azure authorizer based on the provided azure identity +func (p *AzureCredentialsProvider) GetAuthorizer(ctx context.Context, resourceManagerEndpoint string) (autorest.Authorizer, error) { + copiedIdentity := aadpodv1.AzureIdentity{ + TypeMeta: metav1.TypeMeta{ + Kind: "AzureIdentity", + APIVersion: "aadpodidentity.k8s.io/v1", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: p.Identity.Name, + Namespace: "capz-system", + Annotations: map[string]string{ + aadpodv1.BehaviorKey: "namespaced", + }, + }, + Spec: p.Identity.Spec, + } + + newAzureIdentity := aadpodv1.AzureIdentity{} + err := p.CrdClient.Post().Namespace(copiedIdentity.Namespace).Resource(aadpodv1.AzureIDResource).Body(&copiedIdentity).Do().Into(&newAzureIdentity) + if err != nil && !apierrors.IsAlreadyExists(err) { + return nil, errors.Wrapf(err, "failed to create copied AzureIdentity %s in capz-system", copiedIdentity.Name) + } + + azureIdentityBinding := aadpodv1.AzureIdentityBinding{ + TypeMeta: metav1.TypeMeta{ + Kind: "AzureIdentityBinding", + APIVersion: "aadpodidentity.k8s.io/v1", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: copiedIdentity.Name, + Namespace: copiedIdentity.Namespace, + }, + Spec: aadpodv1.AzureIdentityBindingSpec{ + AzureIdentity: copiedIdentity.Name, + Selector: "capz-controller-aadpodidentity-selector", //should be same as selector added on controller + }, + } + newAzureIdentityBinding := aadpodv1.AzureIdentityBinding{} + err = p.CrdClient.Post().Namespace(azureIdentityBinding.Namespace).Resource(aadpodv1.AzureIDBindingResource).Body(&azureIdentityBinding).Do().Into(&newAzureIdentityBinding) + if err != nil && !apierrors.IsAlreadyExists(err) { + return nil, errors.Wrapf(err, "failed to create AzureIdentityBinding %s in capz-system", copiedIdentity.Name) + } + + // oauthConfig, err := adal.NewOAuthConfig(settings.Environment.ActiveDirectoryEndpoint, p.Identity.Spec.TenantID) + // if err != nil { + // return nil, errors.Wrap(err, "failed to create OAuth config") + // } + + var spt *adal.ServicePrincipalToken + msiEndpoint, err := adal.GetMSIVMEndpoint() + if err != nil { + return nil, errors.Wrap(err, "failed to get MSI endpoint") + } + if p.Identity.Spec.Type == aadpodv1.ServicePrincipal { + spt, err = adal.NewServicePrincipalTokenFromMSI(msiEndpoint, p.Identity.Spec.ClientID) + if err != nil { + return nil, errors.Wrap(err, "failed to get token from service principal identity") + } + if err := spt.Refresh(); err != nil { + return nil, errors.Wrapf(err, "failed to refresh ServicePrincipalTokenFromMSI using the MSI endpoint %s", msiEndpoint) + } + token := spt.Token() + if token.IsZero() { + return nil, errors.New("zero token found using the MSI endpoint") + } + } else if p.Identity.Spec.Type == aadpodv1.UserAssignedMSI { + spt, err = adal.NewServicePrincipalTokenFromMSIWithUserAssignedID(msiEndpoint, resourceManagerEndpoint, p.Identity.Spec.ClientID) + if err != nil { + return nil, errors.Wrap(err, "failed to get token from user-assigned identity") + } + if err := spt.Refresh(); err != nil { + return nil, errors.Wrapf(err, "failed to refresh UserAssignedMSI using the MSI endpoint %s", msiEndpoint) + } + token := spt.Token() + if token.IsZero() { + return nil, errors.New("zero token found using the MSI endpoint") + } + } + + return autorest.NewBearerAuthorizer(spt), nil +} + +func newRestClient(config *rest.Config) (r *rest.RESTClient, err error) { + crdconfig := *config + crdconfig.GroupVersion = &schema.GroupVersion{Group: aadpodv1.CRDGroup, Version: aadpodv1.CRDVersion} + crdconfig.APIPath = "/apis" + crdconfig.ContentType = runtime.ContentTypeJSON + scheme := runtime.NewScheme() + + scheme.AddKnownTypes(*crdconfig.GroupVersion, + &aadpodv1.AzureIdentity{}, + &aadpodv1.AzureIdentityList{}, + &aadpodv1.AzureIdentityBinding{}, + &aadpodv1.AzureIdentityBindingList{}, + &aadpodv1.AzureAssignedIdentity{}, + &aadpodv1.AzureAssignedIdentityList{}, + &aadpodv1.AzurePodIdentityException{}, + &aadpodv1.AzurePodIdentityExceptionList{}, + ) + + if err := clientgoscheme.AddToScheme(scheme); err != nil { + return nil, err + } + + crdconfig.NegotiatedSerializer = serializer.WithoutConversionCodecFactory{CodecFactory: serializer.NewCodecFactory(scheme)} + + restClient, err := rest.RESTClientFor(&crdconfig) + if err != nil { + return nil, err + } + return restClient, nil +} diff --git a/cloud/scope/principal.go b/cloud/scope/principal.go deleted file mode 100644 index 5f22aabdf00..00000000000 --- a/cloud/scope/principal.go +++ /dev/null @@ -1,167 +0,0 @@ -/* -Copyright 2020 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 scope - -import ( - "context" - "encoding/base64" - "fmt" - - "github.com/Azure/go-autorest/autorest" - "github.com/Azure/go-autorest/autorest/adal" - "github.com/Azure/go-autorest/autorest/azure/auth" - "github.com/pkg/errors" - - aadpodid "github.com/Azure/aad-pod-identity/pkg/apis/aadpodidentity" - - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/types" - - infrav1 "sigs.k8s.io/cluster-api-provider-azure/api/v1alpha3" - client "sigs.k8s.io/controller-runtime/pkg/client" -) - -// type AzureIdentityProvider interface { -// Authorizer - -// // Hash returns a unique hash of the data forming the credentials -// // for this principal -// Hash() (string, error) -// } - -// AzureCredentialsProvider provides -type AzureCredentialsProvider struct { - Client client.Client - Principal *infrav1.AzureClusterPrincipal - PrincipalRef *infrav1.AzurePrincipalRef - ResourceManagerEndpoint string - TenantID string - ClientID string - ClientSecret string -} - -// NewAzureCredentialsProvider creates a new AzureCredentialsProvider from the supplied inputs. -func NewAzureCredentialsProvider(ctx context.Context, kubeclient client.Client, resourceManagerEndpoint string, ref *infrav1.AzurePrincipalRef) (*AzureCredentialsProvider, error) { - principal := infrav1.AzureClusterPrincipal{} - if ref == nil { - return nil, errors.New("failed to generate new AzureCredentialsProvider from nil PrincipalRef") - } - err := kubeclient.Get(ctx, client.ObjectKey{Name: ref.Name, Namespace: ref.Namespace}, &principal) - if err != nil { - return nil, errors.Wrap(err, "failed to get AzureClusterPrincipal") - } - - secret := &corev1.Secret{} - key := types.NamespacedName{Name: principal.Spec.SecretRef.Name, Namespace: principal.Spec.SecretRef.Namespace} - if err := kubeclient.Get(ctx, key, secret); err != nil { - return nil, errors.Wrapf(err, "failed to retrieve bootstrap data secret for AzureMachine %s", key) - } - tenantID, err := getValueFromSecret(secret, "tenantID") - if err != nil { - return nil, err - } - clientID, _ := getValueFromSecret(secret, "clientID") - clientSecret, _ := getValueFromSecret(secret, "clientSecret") - - return &AzureCredentialsProvider{ - Client: kubeclient, - Principal: &principal, - PrincipalRef: ref, - ResourceManagerEndpoint: resourceManagerEndpoint, - TenantID: tenantID, - ClientID: clientID, - ClientSecret: clientSecret, - }, nil -} - -// GetAuthorizer returnes Azure authorizer based on the provided principal -func (p *AzureCredentialsProvider) GetAuthorizer(ctx context.Context) (autorest.Authorizer, error) { - settings, err := auth.GetSettingsFromEnvironment() - if err != nil { - return nil, err - } - if p.PrincipalRef == nil { - return settings.GetAuthorizer() - } - oauthConfig, err := adal.NewOAuthConfig(settings.Environment.ActiveDirectoryEndpoint, p.TenantID) - if err != nil { - return nil, errors.Wrap(err, "failed to create OAuth config") - } - - var spt *adal.ServicePrincipalToken - - if p.PrincipalRef.Kind == "AzureServicePrincipal" { - azureIdentityBinding := &aadpodid.AzureIdentityBinding{ - ObjectMeta: metav1.ObjectMeta{ - Name: p.PrincipalRef.Name, - Namespace: p.PrincipalRef.Namespace, - }, - Spec: aadpodid.AzureIdentityBindingSpec{ - AzureIdentity: p.PrincipalRef.Name, - Selector: fmt.Sprintf("%s-selector", p.PrincipalRef.Name), - }, - } - - err = p.Client.Create(ctx, azureIdentityBinding) - if err != nil { - return nil, err - } - - spt, err = adal.NewServicePrincipalToken( - *oauthConfig, - p.ClientID, - p.ClientSecret, - p.ResourceManagerEndpoint, - ) - if err != nil { - return nil, errors.Wrap(err, "failed to get service principal token") - } - } else { - msiEndpoint, err := adal.GetMSIVMEndpoint() - if err != nil { - return nil, errors.Wrap(err, "failed to get MSI endpoint") - } - if p.PrincipalRef.Kind == "AzureSystemAssigned" { - - // create AzureIdentity - // create AzureIdentityBinding - - spt, err = adal.NewServicePrincipalTokenFromMSI(msiEndpoint, p.ResourceManagerEndpoint) - if err != nil { - return nil, errors.Wrap(err, "failed to get token from system-assigned identity") - } - } else if p.PrincipalRef.Kind == "AzureUserAssigned" { - - // create AzureIdentityBinding - - spt, err = adal.NewServicePrincipalTokenFromMSIWithUserAssignedID(msiEndpoint, p.ResourceManagerEndpoint, p.PrincipalRef.Name) - if err != nil { - return nil, errors.Wrap(err, "failed to get token from user-assigned identity") - } - } - } - return autorest.NewBearerAuthorizer(spt), nil -} - -func getValueFromSecret(secret *corev1.Secret, key string) (string, error) { - value, ok := secret.Data[key] - if !ok { - return "", errors.New(fmt.Sprintf("error retrieving %s data: secret value key is missing", key)) - } - return base64.StdEncoding.EncodeToString(value), nil -} diff --git a/templates/addons/aad-pod-identity-deployment.yaml b/config/aadpodidentity/aad-pod-identity-deployment.yaml similarity index 92% rename from templates/addons/aad-pod-identity-deployment.yaml rename to config/aadpodidentity/aad-pod-identity-deployment.yaml index 007bf2d567d..2c4c53b6f37 100644 --- a/templates/addons/aad-pod-identity-deployment.yaml +++ b/config/aadpodidentity/aad-pod-identity-deployment.yaml @@ -1,9 +1,3 @@ -apiVersion: v1 -kind: ServiceAccount -metadata: - name: aad-pod-id-nmi-service-account - namespace: default ---- apiVersion: apiextensions.k8s.io/v1beta1 kind: CustomResourceDefinition metadata: @@ -80,11 +74,11 @@ kind: ClusterRoleBinding metadata: name: aad-pod-id-nmi-binding labels: - k8s-app: aad-pod-id-nmi-binding + k8s-app: capz-aad-pod-id-nmi-binding subjects: - kind: ServiceAccount - name: aad-pod-id-nmi-service-account - namespace: default + name: default + namespace: capz-system roleRef: kind: ClusterRole name: aad-pod-id-nmi-role @@ -98,7 +92,7 @@ metadata: tier: node k8s-app: aad-pod-id name: nmi - namespace: default + namespace: capz-system spec: updateStrategy: type: RollingUpdate @@ -112,7 +106,7 @@ spec: component: nmi tier: node spec: - serviceAccountName: aad-pod-id-nmi-service-account + serviceAccountName: default hostNetwork: true dnsPolicy: ClusterFirstWithHostNet volumes: @@ -171,13 +165,7 @@ data: kind: Secret metadata: name: aadpodidentity-admin-secret - namespace: default ---- -apiVersion: v1 -kind: ServiceAccount -metadata: - name: aad-pod-id-mic-service-account - namespace: default + namespace: capz-system --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole @@ -201,7 +189,7 @@ rules: verbs: ["create", "get","update"] - apiGroups: ["aadpodidentity.k8s.io"] resources: ["azureidentitybindings", "azureidentities"] - verbs: ["get", "list", "watch", "post", "update"] + verbs: ["*"] - apiGroups: ["aadpodidentity.k8s.io"] resources: ["azurepodidentityexceptions"] verbs: ["list", "update"] @@ -217,8 +205,8 @@ metadata: k8s-app: aad-pod-id-mic-binding subjects: - kind: ServiceAccount - name: aad-pod-id-mic-service-account - namespace: default + name: default + namespace: capz-system roleRef: kind: ClusterRole name: aad-pod-id-mic-role @@ -231,7 +219,7 @@ metadata: component: mic k8s-app: aad-pod-id name: mic - namespace: default + namespace: capz-system spec: replicas: 2 selector: @@ -244,13 +232,14 @@ spec: component: mic app: mic spec: - serviceAccountName: aad-pod-id-mic-service-account + serviceAccountName: default containers: - name: mic image: "mcr.microsoft.com/oss/azure/aad-pod-identity/mic:v1.6.3" imagePullPolicy: Always args: - "--logtostderr" + - "--forceNamespaced" env: - name: MIC_POD_NAMESPACE valueFrom: diff --git a/config/aadpodidentity/kustomization.yaml b/config/aadpodidentity/kustomization.yaml new file mode 100644 index 00000000000..1f7f3a7f049 --- /dev/null +++ b/config/aadpodidentity/kustomization.yaml @@ -0,0 +1,2 @@ +resources: + - aad-pod-identity-deployment.yaml diff --git a/config/crd/bases/infrastructure.cluster.x-k8s.io_azureclusterprincipals.yaml b/config/crd/bases/infrastructure.cluster.x-k8s.io_azureclusterprincipals.yaml deleted file mode 100644 index 29278b4701c..00000000000 --- a/config/crd/bases/infrastructure.cluster.x-k8s.io_azureclusterprincipals.yaml +++ /dev/null @@ -1,122 +0,0 @@ - ---- -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - controller-gen.kubebuilder.io/version: v0.3.0 - creationTimestamp: null - name: azureclusterprincipals.infrastructure.cluster.x-k8s.io -spec: - group: infrastructure.cluster.x-k8s.io - names: - categories: - - cluster-api - kind: AzureClusterPrincipal - listKind: AzureClusterPrincipalList - plural: azureclusterprincipals - singular: azureclusterprincipal - scope: Namespaced - versions: - - name: v1alpha3 - schema: - openAPIV3Schema: - description: AzureClusterPrincipal represents a reference to an Azure access - key ID and secret access key, stored in a secret. - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - description: Spec for this AzureClusterPrincipalSpec. - properties: - allowedNamespaces: - description: "AllowedNamespaces is a selector of namespaces that AzureClusters - can use this ClusterPrincipal from. This is a standard Kubernetes - LabelSelector, a label query over a set of resources. The result - of matchLabels and matchExpressions are ANDed. Controllers must - not support AzureClusters in namespaces outside this selector. \n - An empty selector (default) indicates that AzureClusters can use - this AzureClusterPrincipal from any namespace. This field is intentionally - not a pointer because the nil behavior (no namespaces) is undesirable - here." - properties: - matchExpressions: - description: matchExpressions is a list of label selector requirements. - The requirements are ANDed. - items: - description: A label selector requirement is a selector that - contains values, a key, and an operator that relates the key - and values. - properties: - key: - description: key is the label key that the selector applies - to. - type: string - operator: - description: operator represents a key's relationship to - a set of values. Valid operators are In, NotIn, Exists - and DoesNotExist. - type: string - values: - description: values is an array of string values. If the - operator is In or NotIn, the values array must be non-empty. - If the operator is Exists or DoesNotExist, the values - array must be empty. This array is replaced during a strategic - merge patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: - type: string - description: matchLabels is a map of {key,value} pairs. A single - {key,value} in the matchLabels map is equivalent to an element - of matchExpressions, whose key field is "key", the operator - is "In", and the values array contains only "value". The requirements - are ANDed. - type: object - type: object - name: - type: string - secretRef: - description: 'Reference to a secret containing the credentials. The - secret should contain the following data keys: tenantID clientID - clientSecret' - properties: - name: - description: Name is unique within a namespace to reference a - secret resource. - type: string - namespace: - description: Namespace defines the space within which the secret - name must be unique. - type: string - type: object - required: - - name - - secretRef - type: object - type: object - served: true - storage: true -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] diff --git a/config/crd/bases/infrastructure.cluster.x-k8s.io_azureclusters.yaml b/config/crd/bases/infrastructure.cluster.x-k8s.io_azureclusters.yaml index adcd8e26e4f..816cd0efeb9 100644 --- a/config/crd/bases/infrastructure.cluster.x-k8s.io_azureclusters.yaml +++ b/config/crd/bases/infrastructure.cluster.x-k8s.io_azureclusters.yaml @@ -459,6 +459,10 @@ spec: - host - port type: object + identityName: + description: IdentityName is a reference to a AzureIdentity to be + used when reconciling this cluster + type: string location: type: string networkSpec: @@ -615,27 +619,6 @@ spec: - name type: object type: object - principalRef: - description: PrincipalRef is a reference to a principal to be used - when reconciling this cluster - properties: - kind: - description: AzurePrincipalKind defines allowed Azure cluster - principal types - enum: - - AzureSystemAssigned - - AzureUserAssigned - - AzureServicePrincipal - type: string - name: - type: string - namespace: - type: string - required: - - kind - - name - - namespace - type: object resourceGroup: type: string subscriptionID: diff --git a/config/crd/bases/infrastructure.cluster.x-k8s.io_azureprincipals.yaml b/config/crd/bases/infrastructure.cluster.x-k8s.io_azureprincipals.yaml deleted file mode 100644 index 584796852cb..00000000000 --- a/config/crd/bases/infrastructure.cluster.x-k8s.io_azureprincipals.yaml +++ /dev/null @@ -1,77 +0,0 @@ - ---- -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - controller-gen.kubebuilder.io/version: v0.3.0 - creationTimestamp: null - name: azureprincipals.infrastructure.cluster.x-k8s.io -spec: - group: infrastructure.cluster.x-k8s.io - names: - categories: - - cluster-api - kind: AzurePrincipal - listKind: AzurePrincipalList - plural: azureprincipals - singular: azureprincipal - scope: Namespaced - versions: - - name: v1alpha3 - schema: - openAPIV3Schema: - description: AzurePrincipal represents a reference to an Azure access key - ID and secret access key, stored in a secret. - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - description: Spec for this AzurePrincipalSpec. - properties: - kind: - description: AzurePrincipalKind defines allowed Azure principal types - enum: - - AzureSystemAssigned - - AzureUserAssigned - - AzureServicePrincipal - type: string - name: - type: string - secretRef: - description: 'Reference to a secret containing the credentials. The - secret should contain the following data keys: tenantID clientID clientSecret' - properties: - name: - description: Name is unique within a namespace to reference a - secret resource. - type: string - namespace: - description: Namespace defines the space within which the secret - name must be unique. - type: string - type: object - required: - - kind - - name - - secretRef - type: object - type: object - served: true - storage: true -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] diff --git a/config/kustomization.yaml b/config/kustomization.yaml index 1b7dbb4a4c4..f09583a5169 100644 --- a/config/kustomization.yaml +++ b/config/kustomization.yaml @@ -7,6 +7,7 @@ bases: - crd - webhook - default + - aadpodidentity patchesJson6902: - target: diff --git a/config/manager/manager.yaml b/config/manager/manager.yaml index 13a1cfb83b1..9e81b994776 100644 --- a/config/manager/manager.yaml +++ b/config/manager/manager.yaml @@ -5,6 +5,7 @@ metadata: namespace: system labels: control-plane: capz-controller-manager + aadpodidbinding: capz-controller-aadpodidentity-selector spec: selector: matchLabels: @@ -14,6 +15,7 @@ spec: metadata: labels: control-plane: capz-controller-manager + aadpodidbinding: capz-controller-aadpodidentity-selector spec: containers: - args: diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml index 03f2463c698..329b7e27c7b 100644 --- a/config/rbac/role.yaml +++ b/config/rbac/role.yaml @@ -137,18 +137,6 @@ rules: - get - patch - update -- apiGroups: - - infrastructure.cluster.x-k8s.io - resources: - - azureclusterprincipals - verbs: - - create - - delete - - get - - list - - patch - - update - - watch - apiGroups: - infrastructure.cluster.x-k8s.io resources: diff --git a/controllers/azurecluster_controller.go b/controllers/azurecluster_controller.go index 377342697b9..5b0f95723b3 100644 --- a/controllers/azurecluster_controller.go +++ b/controllers/azurecluster_controller.go @@ -81,7 +81,6 @@ func (r *AzureClusterReconciler) SetupWithManager(mgr ctrl.Manager, options cont // +kubebuilder:rbac:groups=infrastructure.cluster.x-k8s.io,resources=azureclusters/status,verbs=get;update;patch // +kubebuilder:rbac:groups=cluster.x-k8s.io,resources=clusters;clusters/status,verbs=get;list;watch // +kubebuilder:rbac:groups=infrastructure.cluster.x-k8s.io,resources=azuremachinetemplates;azuremachinetemplates/status,verbs=get;list;watch -// +kubebuilder:rbac:groups=infrastructure.cluster.x-k8s.io,resources=azureclusterprincipals,verbs=get;list;watch;create;update;patch;delete func (r *AzureClusterReconciler) Reconcile(req ctrl.Request) (_ ctrl.Result, reterr error) { ctx, cancel := context.WithTimeout(context.Background(), reconciler.DefaultedLoopTimeout(r.ReconcileTimeout)) diff --git a/go.sum b/go.sum index 53a5b1c2815..65719adf21f 100644 --- a/go.sum +++ b/go.sum @@ -376,6 +376,7 @@ github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQL github.com/kisielk/gotool v1.0.0 h1:AV2c/EiW3KqPNT9ZKl07ehoAGi4C5/01Cfbblndcapg= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= +github.com/knative/pkg v0.0.0-20201009175121-3c4df8c27293 h1:EPP3k5RVp5+4Spq0cnemB0tnRR/WIOlwwICtey9hagQ= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=