Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

adding multi tenancy for controller using aad-pod-identity #977

Merged
merged 3 commits into from
Dec 18, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions api/v1alpha2/azurecluster_conversion.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,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.IdentityRef = restored.Spec.IdentityRef

for _, restoredSubnet := range restored.Spec.NetworkSpec.Subnets {
if restoredSubnet != nil {
Expand Down
1 change: 1 addition & 0 deletions api/v1alpha2/zz_generated.conversion.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 8 additions & 0 deletions api/v1alpha3/azurecluster_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ limitations under the License.
package v1alpha3

import (
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
clusterv1 "sigs.k8s.io/cluster-api/api/v1alpha3"
)
Expand All @@ -25,6 +26,9 @@ const (
// ClusterFinalizer allows ReconcileAzureCluster to clean up Azure resources associated with AzureCluster before
// removing it from the apiserver.
ClusterFinalizer = "azurecluster.infrastructure.cluster.x-k8s.io"

// ClusterLabelNamespace indicates the namespace of the cluster
ClusterLabelNamespace = "azurecluster.infrastructure.cluster.x-k8s.io/cluster-namespace"
)

// AzureClusterSpec defines the desired state of AzureCluster
Expand All @@ -48,6 +52,10 @@ type AzureClusterSpec struct {
// ones added by default.
// +optional
AdditionalTags Tags `json:"additionalTags,omitempty"`

// IdentityRef is a reference to a AzureIdentity to be used when reconciling this cluster
// +optional
IdentityRef *corev1.ObjectReference `json:"identityRef,omitempty"`
}

// AzureClusterStatus defines the observed state of AzureCluster
Expand Down
105 changes: 105 additions & 0 deletions api/v1alpha3/azureclusteridentity_types.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
/*
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"
clusterv1 "sigs.k8s.io/cluster-api/api/v1alpha3"
)

// AzureClusterIdentitySpec defines the parameters that are used to create an AzureIdentity
type AzureClusterIdentitySpec struct {
// UserAssignedMSI or Service Principal
Type IdentityType `json:"type"`
CecileRobertMichon marked this conversation as resolved.
Show resolved Hide resolved
// User assigned MSI resource id.
// +optional
ResourceID string `json:"resourceID,omitempty"`
// Both User Assigned MSI and SP can use this field.
ClientID string `json:"clientID"`
// ClientSecret is a secret reference which should contain either a Service Principal password or certificate secret.
// +optional
ClientSecret corev1.SecretReference `json:"clientSecret,omitempty"`
// Service principal primary tenant id.
TenantID string `json:"tenantID"`
// AllowedNamespaces is an array of namespaces that AzureClusters can
// use this Identity from.
//
// An empty list (default) indicates that AzureClusters can use this
// Identity from any namespace. This field is intentionally not a
// pointer because the nil behavior (no namespaces) is undesirable here.
// +optional
AllowedNamespaces []string `json:"allowedNamespaces"`
}

// AzureClusterIdentityStatus defines the observed state of AzureClusterIdentity
type AzureClusterIdentityStatus struct {
// Conditions defines current service state of the AzureClusterIdentity.
// +optional
Conditions clusterv1.Conditions `json:"conditions,omitempty"`
}

// +kubebuilder:object:root=true
// +kubebuilder:resource:path=azureclusteridentities,scope=Namespaced,categories=cluster-api
// +kubebuilder:storageversion
// +kubebuilder:subresource:status

// AzureClusterIdentity is the Schema for the azureclustersidentities API
type AzureClusterIdentity struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`

Spec AzureClusterIdentitySpec `json:"spec,omitempty"`
Status AzureClusterIdentityStatus `json:"status,omitempty"`
}

// +kubebuilder:object:root=true

// AzureClusterIdentityList contains a list of AzureClusterIdentity
type AzureClusterIdentityList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata,omitempty"`
Items []AzureClusterIdentity `json:"items"`
}

// GetConditions returns the list of conditions for an AzureClusterIdentity API object.
func (c *AzureClusterIdentity) GetConditions() clusterv1.Conditions {
return c.Status.Conditions
}

// SetConditions will set the given conditions on an AzureClusterIdentity object
func (c *AzureClusterIdentity) SetConditions(conditions clusterv1.Conditions) {
c.Status.Conditions = conditions
}

// ClusterNamespaceAllowed indicates if the cluster namespace is allowed
func (c *AzureClusterIdentity) ClusterNamespaceAllowed(namespace string) bool {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let's add a unit test for this

if len(c.Spec.AllowedNamespaces) == 0 {
return true
}
for _, v := range c.Spec.AllowedNamespaces {
if v == namespace {
return true
}
}

return false
}

func init() {
SchemeBuilder.Register(&AzureClusterIdentity{}, &AzureClusterIdentityList{})
}
71 changes: 71 additions & 0 deletions api/v1alpha3/azureclusteridentity_types_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
/*
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 (
"testing"

. "github.com/onsi/gomega"
)

func TestAllowedNamespaces(t *testing.T) {
g := NewWithT(t)

tests := []struct {
name string
identity *AzureClusterIdentity
clusterNamespace string
expected bool
}{
{
name: "allow any cluster namespace when empty",
identity: &AzureClusterIdentity{
Spec: AzureClusterIdentitySpec{
AllowedNamespaces: []string{},
},
},
clusterNamespace: "default",
expected: true,
},
{
name: "allow cluster with namespace in list",
identity: &AzureClusterIdentity{
Spec: AzureClusterIdentitySpec{
AllowedNamespaces: []string{"namespace24", "namespace32"},
},
},
clusterNamespace: "namespace24",
expected: true,
},
{
name: "don't allow cluster with namespace not in list",
identity: &AzureClusterIdentity{
Spec: AzureClusterIdentitySpec{
AllowedNamespaces: []string{"namespace24", "namespace32"},
},
},
clusterNamespace: "namespace8",
expected: false,
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
actual := tc.identity.ClusterNamespaceAllowed(tc.clusterNamespace)
g.Expect(actual).To(Equal(tc.expected))
})
}
}
2 changes: 2 additions & 0 deletions api/v1alpha3/conditions_consts.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ const (
LoadBalancerProvisioningReason = "LoadBalancerProvisioning"
// LoadBalancerProvisioningFailedReason used for failure during provisioning of loadbalancer.
LoadBalancerProvisioningFailedReason = "LoadBalancerProvisioningFailed"
// NamespaceNotAllowedByIdentity used to indicate cluster in a namespace not allowed by identity
NamespaceNotAllowedByIdentity = "NamespaceNotAllowedByIdentity"
)

// AzureMachine Conditions and Reasons
Expand Down
21 changes: 21 additions & 0 deletions api/v1alpha3/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ import (
)

const (
// ControllerNamespace is the namespace where controller manager will run
ControllerNamespace = "capz-system"
// ControlPlane machine label
ControlPlane string = "control-plane"
// Node machine label
Expand Down Expand Up @@ -321,6 +323,25 @@ type UserAssignedIdentity struct {
ProviderID string `json:"providerID"`
}

const (
// AzureIdentityBindingSelector is the label used to match with the AzureIdentityBinding
// For the controller to match an identity binding, it needs a [label] with the key `aadpodidbinding`
// whose value is that of the `selector:` field in the `AzureIdentityBinding`.
AzureIdentityBindingSelector = "capz-controller-aadpodidentity-selector"
)

// IdentityType represents different types of identities.
// +kubebuilder:validation:Enum=ServicePrincipal;UserAssignedMSI
type IdentityType string

const (
// UserAssignedMSI represents a user-assigned identity.
UserAssignedMSI IdentityType = "UserAssignedMSI"

// ServicePrincipal represents a service principal.
ServicePrincipal IdentityType = "ServicePrincipal"
)

// OSDisk defines the operating system disk for a VM.
type OSDisk struct {
OSType string `json:"osType"`
Expand Down
107 changes: 107 additions & 0 deletions api/v1alpha3/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading