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

Add support for cross tenant authentication azure workload identity TriggerAuthentication #5517

Merged
merged 4 commits into from
Mar 4, 2024
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ Here is an overview of all new **experimental** features:

- **General**: Add command-line flag in Adapter to allow override of gRPC Authority Header ([#5449](https://github.com/kedacore/keda/issues/5449))
- **General**: Add OPENTELEMETRY flag in e2e test YAML ([#5375](https://github.com/kedacore/keda/issues/5375))
- **General**: Add support for cross tenant/cloud authentication when using Azure Workload Identity for TriggerAuthentication ([#5441](https://github.com/kedacore/keda/issues/5441))

### Fixes

Expand Down
27 changes: 26 additions & 1 deletion apis/keda/v1alpha1/triggerauthentication_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -141,11 +141,22 @@ const (
type AuthPodIdentity struct {
// +kubebuilder:validation:Enum=azure;azure-workload;gcp;aws;aws-eks;aws-kiam
Provider PodIdentityProvider `json:"provider"`

// +optional
IdentityID *string `json:"identityId"`

// +optional
// Set identityTenantId to override the default Azure tenant id. If this is set, then the IdentityID must also be set
IdentityTenantID *string `json:"identityTenantId"`

// +optional
// Set identityAuthorityHost to override the default Azure authority host. If this is set, then the IdentityTenantID must also be set
IdentityAuthorityHost *string `json:"identityAuthorityHost"`

// +kubebuilder:validation:Optional
// RoleArn sets the AWS RoleArn to be used. Mutually exclusive with IdentityOwner
RoleArn string `json:"roleArn"`
RoleArn *string `json:"roleArn"`

// +kubebuilder:validation:Enum=keda;workload
// +optional
// IdentityOwner configures which identity has to be used during auto discovery, keda or the scaled workload. Mutually exclusive with roleArn
Expand All @@ -159,6 +170,20 @@ func (a *AuthPodIdentity) GetIdentityID() string {
return *a.IdentityID
}

func (a *AuthPodIdentity) GetIdentityTenantID() string {
if a.IdentityTenantID == nil {
return ""
}
return *a.IdentityTenantID
}

func (a *AuthPodIdentity) GetIdentityAuthorityHost() string {
if a.IdentityAuthorityHost == nil {
return ""
}
return *a.IdentityAuthorityHost
}

func (a *AuthPodIdentity) IsWorkloadIdentityOwner() bool {
if a.IdentityOwner == nil {
return false
Expand Down
12 changes: 10 additions & 2 deletions apis/keda/v1alpha1/triggerauthentication_webhook.go
Original file line number Diff line number Diff line change
Expand Up @@ -116,10 +116,18 @@ func validateSpec(spec *TriggerAuthenticationSpec) (admission.Warnings, error) {
switch spec.PodIdentity.Provider {
case PodIdentityProviderAzure, PodIdentityProviderAzureWorkload:
if spec.PodIdentity.IdentityID != nil && *spec.PodIdentity.IdentityID == "" {
return nil, fmt.Errorf("identityid of PodIdentity should not be empty. If it's set, identityId has to be different than \"\"")
return nil, fmt.Errorf("identityId of PodIdentity should not be empty. If it's set, identityId has to be different than \"\"")
}

if spec.PodIdentity.IdentityAuthorityHost != nil && *spec.PodIdentity.IdentityAuthorityHost != "" {
if spec.PodIdentity.IdentityTenantID == nil || *spec.PodIdentity.IdentityTenantID == "" {
return nil, fmt.Errorf("identityTenantID of PodIdentity should not be nil or empty when identityAuthorityHost of PodIdentity is set")
}
JorTurFer marked this conversation as resolved.
Show resolved Hide resolved
} else if spec.PodIdentity.IdentityTenantID != nil && *spec.PodIdentity.IdentityTenantID == "" {
return nil, fmt.Errorf("identityTenantId of PodIdentity should not be empty. If it's set, identityTenantId has to be different than \"\"")
}
case PodIdentityProviderAws:
if spec.PodIdentity.RoleArn != "" && spec.PodIdentity.IsWorkloadIdentityOwner() {
if spec.PodIdentity.RoleArn != nil && *spec.PodIdentity.RoleArn != "" && spec.PodIdentity.IsWorkloadIdentityOwner() {
return nil, fmt.Errorf("roleArn of PodIdentity can't be set if KEDA isn't identityOwner")
}
default:
Expand Down
127 changes: 106 additions & 21 deletions apis/keda/v1alpha1/triggerauthentication_webhook_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ var _ = It("validate triggerauthentication when IdentityID is nil, roleArn is em
err := k8sClient.Create(context.Background(), namespace)
Expect(err).ToNot(HaveOccurred())

spec := createTriggerAuthenticationSpecWithPodIdentity(PodIdentityProviderAzure, "", nil, nil)
spec := createTriggerAuthenticationSpecWithPodIdentity(PodIdentityProviderAzure, nil, nil, nil, nil, nil)
ta := createTriggerAuthentication("nilidentityidta", namespaceName, "TriggerAuthentication", spec)
Eventually(func() error {
return k8sClient.Create(context.Background(), ta)
Expand All @@ -44,7 +44,7 @@ var _ = It("validate triggerauthentication when IdentityID is empty", func() {
Expect(err).ToNot(HaveOccurred())

identityID := ""
spec := createTriggerAuthenticationSpecWithPodIdentity(PodIdentityProviderAzure, "", &identityID, nil)
spec := createTriggerAuthenticationSpecWithPodIdentity(PodIdentityProviderAzure, nil, &identityID, nil, nil, nil)
ta := createTriggerAuthentication("emptyidentityidta", namespaceName, "TriggerAuthentication", spec)
Eventually(func() error {
return k8sClient.Create(context.Background(), ta)
Expand All @@ -58,20 +58,98 @@ var _ = It("validate triggerauthentication when IdentityID is not empty", func()
Expect(err).ToNot(HaveOccurred())

identityID := "12345"
spec := createTriggerAuthenticationSpecWithPodIdentity(PodIdentityProviderAzure, "", &identityID, nil)
spec := createTriggerAuthenticationSpecWithPodIdentity(PodIdentityProviderAzure, nil, &identityID, nil, nil, nil)
ta := createTriggerAuthentication("identityidta", namespaceName, "TriggerAuthentication", spec)
Eventually(func() error {
return k8sClient.Create(context.Background(), ta)
}).ShouldNot(HaveOccurred())
})

var _ = It("validate triggerauthentication when IdentityTenantID is not nil and not empty", func() {
namespaceName := "identitytenantidta"
namespace := createNamespace(namespaceName)
err := k8sClient.Create(context.Background(), namespace)
Expect(err).ToNot(HaveOccurred())

identityID := "12345"
identityTenantID := "12345"
spec := createTriggerAuthenticationSpecWithPodIdentity(PodIdentityProviderAzureWorkload, nil, &identityID, &identityTenantID, nil, nil)
ta := createTriggerAuthentication("identitytenantidta", namespaceName, "TriggerAuthentication", spec)
Eventually(func() error {
return k8sClient.Create(context.Background(), ta)
}).ShouldNot(HaveOccurred())
})

var _ = It("validate triggerauthentication when IdentityTenantID is not nil but empty", func() {
namespaceName := "emptyidentitytenantidta"
namespace := createNamespace(namespaceName)
err := k8sClient.Create(context.Background(), namespace)
Expect(err).ToNot(HaveOccurred())

identityID := "12345"
identityTenantID := ""
spec := createTriggerAuthenticationSpecWithPodIdentity(PodIdentityProviderAzureWorkload, nil, &identityID, &identityTenantID, nil, nil)
ta := createTriggerAuthentication("emptyidentitytenantidta", namespaceName, "TriggerAuthentication", spec)
Eventually(func() error {
return k8sClient.Create(context.Background(), ta)
}).Should(HaveOccurred())
})

var _ = It("validate triggerauthentication when IdentityAuthorityHost is not nil and not empty and IdentityTenantID is not nil and not empty", func() {
namespaceName := "identityauthorityhostta"
namespace := createNamespace(namespaceName)
err := k8sClient.Create(context.Background(), namespace)
Expect(err).ToNot(HaveOccurred())

identityID := "12345"
identityTenantID := "12345"
identityAuthorityHost := "12345"
spec := createTriggerAuthenticationSpecWithPodIdentity(PodIdentityProviderAzureWorkload, nil, &identityID, &identityTenantID, &identityAuthorityHost, nil)
ta := createTriggerAuthentication("identityauthorityhostta", namespaceName, "TriggerAuthentication", spec)
Eventually(func() error {
return k8sClient.Create(context.Background(), ta)
}).ShouldNot(HaveOccurred())
})

var _ = It("validate triggerauthentication when IdentityAuthorityHost is not nil and not empty and IdentityTenantID is nil", func() {
namespaceName := "niltenantidentityauthorityhostta"
namespace := createNamespace(namespaceName)
err := k8sClient.Create(context.Background(), namespace)
Expect(err).ToNot(HaveOccurred())

identityID := "12345"
identityAuthorityHost := "12345"
spec := createTriggerAuthenticationSpecWithPodIdentity(PodIdentityProviderAzureWorkload, nil, &identityID, nil, &identityAuthorityHost, nil)
ta := createTriggerAuthentication("niltenantidentityauthorityhostta", namespaceName, "TriggerAuthentication", spec)
Eventually(func() error {
return k8sClient.Create(context.Background(), ta)
}).Should(HaveOccurred())
})

var _ = It("validate triggerauthentication when IdentityAuthorityHost is not nil and not empty and IdentityTenantID is not nil but empty", func() {
namespaceName := "emptytenantidentityauthorityhostta"
namespace := createNamespace(namespaceName)
err := k8sClient.Create(context.Background(), namespace)
Expect(err).ToNot(HaveOccurred())

identityID := "12345"
identityTenantID := ""
identityAuthorityHost := "12345"
spec := createTriggerAuthenticationSpecWithPodIdentity(PodIdentityProviderAzureWorkload, nil, &identityID, &identityTenantID, &identityAuthorityHost, nil)
ta := createTriggerAuthentication("emptytenantidentityauthorityhostta", namespaceName, "TriggerAuthentication", spec)
Eventually(func() error {
return k8sClient.Create(context.Background(), ta)
}).Should(HaveOccurred())
})

var _ = It("validate triggerauthentication when RoleArn is not empty and IdentityOwner is nil", func() {
namespaceName := "rolearn"
namespace := createNamespace(namespaceName)
err := k8sClient.Create(context.Background(), namespace)
Expect(err).ToNot(HaveOccurred())

spec := createTriggerAuthenticationSpecWithPodIdentity(PodIdentityProviderAws, "Helo", nil, nil)
roleArn := "Hello"
spec := createTriggerAuthenticationSpecWithPodIdentity(PodIdentityProviderAws, &roleArn, nil, nil, nil, nil)
ta := createTriggerAuthentication("identityidta", namespaceName, "TriggerAuthentication", spec)
Eventually(func() error {
return k8sClient.Create(context.Background(), ta)
Expand All @@ -84,8 +162,9 @@ var _ = It("validate triggerauthentication when RoleArn is not empty and Identit
err := k8sClient.Create(context.Background(), namespace)
Expect(err).ToNot(HaveOccurred())

roleArn := "Hello"
identityOwner := kedaString
spec := createTriggerAuthenticationSpecWithPodIdentity(PodIdentityProviderAws, "Helo", nil, &identityOwner)
spec := createTriggerAuthenticationSpecWithPodIdentity(PodIdentityProviderAws, &roleArn, nil, nil, nil, &identityOwner)
ta := createTriggerAuthentication("identityidta", namespaceName, "TriggerAuthentication", spec)
Eventually(func() error {
return k8sClient.Create(context.Background(), ta)
Expand All @@ -98,8 +177,9 @@ var _ = It("validate triggerauthentication when RoleArn is not empty and Identit
err := k8sClient.Create(context.Background(), namespace)
Expect(err).ToNot(HaveOccurred())

roleArn := "Hello"
identityOwner := workloadString
spec := createTriggerAuthenticationSpecWithPodIdentity(PodIdentityProviderAws, "Helo", nil, &identityOwner)
spec := createTriggerAuthenticationSpecWithPodIdentity(PodIdentityProviderAws, &roleArn, nil, nil, nil, &identityOwner)
ta := createTriggerAuthentication("identityidta", namespaceName, "TriggerAuthentication", spec)
Eventually(func() error {
return k8sClient.Create(context.Background(), ta)
Expand All @@ -113,7 +193,7 @@ var _ = It("validate triggerauthentication when RoleArn is empty and IdentityOwn
Expect(err).ToNot(HaveOccurred())

identityOwner := kedaString
spec := createTriggerAuthenticationSpecWithPodIdentity(PodIdentityProviderAws, "", nil, &identityOwner)
spec := createTriggerAuthenticationSpecWithPodIdentity(PodIdentityProviderAws, nil, nil, nil, nil, &identityOwner)
ta := createTriggerAuthentication("identityidta", namespaceName, "TriggerAuthentication", spec)
Eventually(func() error {
return k8sClient.Create(context.Background(), ta)
Expand All @@ -127,7 +207,7 @@ var _ = It("validate triggerauthentication when RoleArn is not empty and Identit
Expect(err).ToNot(HaveOccurred())

identityOwner := workloadString
spec := createTriggerAuthenticationSpecWithPodIdentity(PodIdentityProviderAws, "", nil, &identityOwner)
spec := createTriggerAuthenticationSpecWithPodIdentity(PodIdentityProviderAws, nil, nil, nil, nil, &identityOwner)
ta := createTriggerAuthentication("identityidta", namespaceName, "TriggerAuthentication", spec)
Eventually(func() error {
return k8sClient.Create(context.Background(), ta)
Expand All @@ -140,7 +220,7 @@ var _ = It("validate clustertriggerauthentication when IdentityID is nil", func(
err := k8sClient.Create(context.Background(), namespace)
Expect(err).ToNot(HaveOccurred())

spec := createTriggerAuthenticationSpecWithPodIdentity(PodIdentityProviderAzure, "", nil, nil)
spec := createTriggerAuthenticationSpecWithPodIdentity(PodIdentityProviderAzure, nil, nil, nil, nil, nil)
ta := createTriggerAuthentication("clusternilidentityidta", namespaceName, "ClusterTriggerAuthentication", spec)
Eventually(func() error {
return k8sClient.Create(context.Background(), ta)
Expand All @@ -154,7 +234,7 @@ var _ = It("validate clustertriggerauthentication when IdentityID is empty", fun
Expect(err).ToNot(HaveOccurred())

identityID := ""
spec := createTriggerAuthenticationSpecWithPodIdentity(PodIdentityProviderAzure, "", &identityID, nil)
spec := createTriggerAuthenticationSpecWithPodIdentity(PodIdentityProviderAzure, nil, &identityID, nil, nil, nil)
ta := createTriggerAuthentication("clusteremptyidentityidta", namespaceName, "ClusterTriggerAuthentication", spec)
Eventually(func() error {
return k8sClient.Create(context.Background(), ta)
Expand All @@ -168,7 +248,7 @@ var _ = It("validate clustertriggerauthentication when IdentityID is not empty",
Expect(err).ToNot(HaveOccurred())

identityID := "12345"
spec := createTriggerAuthenticationSpecWithPodIdentity(PodIdentityProviderAzure, "", &identityID, nil)
spec := createTriggerAuthenticationSpecWithPodIdentity(PodIdentityProviderAzure, nil, &identityID, nil, nil, nil)
ta := createTriggerAuthentication("clusteridentityidta", namespaceName, "ClusterTriggerAuthentication", spec)
Eventually(func() error {
return k8sClient.Create(context.Background(), ta)
Expand All @@ -181,7 +261,8 @@ var _ = It("validate clustertriggerauthentication when RoleArn is not empty and
err := k8sClient.Create(context.Background(), namespace)
Expect(err).ToNot(HaveOccurred())

spec := createTriggerAuthenticationSpecWithPodIdentity(PodIdentityProviderAws, "Helo", nil, nil)
roleArn := "Hello"
spec := createTriggerAuthenticationSpecWithPodIdentity(PodIdentityProviderAws, &roleArn, nil, nil, nil, nil)
ta := createTriggerAuthentication("clusteridentityidta", namespaceName, "ClusterTriggerAuthentication", spec)
Eventually(func() error {
return k8sClient.Create(context.Background(), ta)
Expand All @@ -194,8 +275,9 @@ var _ = It("validate clustertriggerauthentication when RoleArn is not empty and
err := k8sClient.Create(context.Background(), namespace)
Expect(err).ToNot(HaveOccurred())

roleArn := "Hello"
identityOwner := kedaString
spec := createTriggerAuthenticationSpecWithPodIdentity(PodIdentityProviderAws, "Helo", nil, &identityOwner)
spec := createTriggerAuthenticationSpecWithPodIdentity(PodIdentityProviderAws, &roleArn, nil, nil, nil, &identityOwner)
ta := createTriggerAuthentication("clusteridentityidta", namespaceName, "ClusterTriggerAuthentication", spec)
Eventually(func() error {
return k8sClient.Create(context.Background(), ta)
Expand All @@ -208,8 +290,9 @@ var _ = It("validate clustertriggerauthentication when RoleArn is not empty and
err := k8sClient.Create(context.Background(), namespace)
Expect(err).ToNot(HaveOccurred())

roleArn := "Hello"
identityOwner := workloadString
spec := createTriggerAuthenticationSpecWithPodIdentity(PodIdentityProviderAws, "Helo", nil, &identityOwner)
spec := createTriggerAuthenticationSpecWithPodIdentity(PodIdentityProviderAws, &roleArn, nil, nil, nil, &identityOwner)
ta := createTriggerAuthentication("clusteridentityidta", namespaceName, "ClusterTriggerAuthentication", spec)
Eventually(func() error {
return k8sClient.Create(context.Background(), ta)
Expand All @@ -223,7 +306,7 @@ var _ = It("validate clustertriggerauthentication when RoleArn is empty and Iden
Expect(err).ToNot(HaveOccurred())

identityOwner := kedaString
spec := createTriggerAuthenticationSpecWithPodIdentity(PodIdentityProviderAws, "", nil, &identityOwner)
spec := createTriggerAuthenticationSpecWithPodIdentity(PodIdentityProviderAws, nil, nil, nil, nil, &identityOwner)
ta := createTriggerAuthentication("clusteridentityidta", namespaceName, "ClusterTriggerAuthentication", spec)
Eventually(func() error {
return k8sClient.Create(context.Background(), ta)
Expand All @@ -237,20 +320,22 @@ var _ = It("validate clustertriggerauthentication when RoleArn is not empty and
Expect(err).ToNot(HaveOccurred())

identityOwner := workloadString
spec := createTriggerAuthenticationSpecWithPodIdentity(PodIdentityProviderAws, "", nil, &identityOwner)
spec := createTriggerAuthenticationSpecWithPodIdentity(PodIdentityProviderAws, nil, nil, nil, nil, &identityOwner)
ta := createTriggerAuthentication("clusteridentityidta", namespaceName, "TriggerAuthentication", spec)
Eventually(func() error {
return k8sClient.Create(context.Background(), ta)
}).ShouldNot(HaveOccurred())
})

func createTriggerAuthenticationSpecWithPodIdentity(provider PodIdentityProvider, roleArn string, identityID, identityOwner *string) TriggerAuthenticationSpec {
func createTriggerAuthenticationSpecWithPodIdentity(provider PodIdentityProvider, roleArn, identityID, identityTenantID, identityAuthorityHost, identityOwner *string) TriggerAuthenticationSpec {
return TriggerAuthenticationSpec{
PodIdentity: &AuthPodIdentity{
Provider: provider,
IdentityID: identityID,
RoleArn: roleArn,
IdentityOwner: identityOwner,
Provider: provider,
IdentityID: identityID,
IdentityTenantID: identityTenantID,
IdentityAuthorityHost: identityAuthorityHost,
RoleArn: roleArn,
IdentityOwner: identityOwner,
},
}
}
Expand Down
15 changes: 15 additions & 0 deletions apis/keda/v1alpha1/zz_generated.deepcopy.go

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