diff --git a/api/v1/gitrepository_types.go b/api/v1/gitrepository_types.go
index 590f1a38..01efec29 100644
--- a/api/v1/gitrepository_types.go
+++ b/api/v1/gitrepository_types.go
@@ -77,6 +77,7 @@ const (
// GitRepositorySpec specifies the required configuration to produce an
// Artifact for a Git repository.
+// +kubebuilder:validation:XValidation:rule="!has(self.serviceAccountName) || (has(self.provider) && self.provider == 'azure')",message="serviceAccountName can only be set when provider is 'azure'"
type GitRepositorySpec struct {
// URL specifies the Git repository URL, it can be an HTTP/S or SSH address.
// +kubebuilder:validation:Pattern="^(http|https|ssh)://.*$"
@@ -98,6 +99,11 @@ type GitRepositorySpec struct {
// +optional
Provider string `json:"provider,omitempty"`
+ // ServiceAccountName is the name of the Kubernetes ServiceAccount used to
+ // authenticate to the GitRepository. This field is only supported for 'azure' provider.
+ // +optional
+ ServiceAccountName string `json:"serviceAccountName,omitempty"`
+
// Interval at which the GitRepository URL is checked for updates.
// This interval is approximate and may be subject to jitter to ensure
// efficient use of resources.
diff --git a/config/crd/bases/source.toolkit.fluxcd.io_gitrepositories.yaml b/config/crd/bases/source.toolkit.fluxcd.io_gitrepositories.yaml
index cffdf747..7ba4c96e 100644
--- a/config/crd/bases/source.toolkit.fluxcd.io_gitrepositories.yaml
+++ b/config/crd/bases/source.toolkit.fluxcd.io_gitrepositories.yaml
@@ -174,6 +174,11 @@ spec:
required:
- name
type: object
+ serviceAccountName:
+ description: |-
+ ServiceAccountName is the name of the Kubernetes ServiceAccount used to
+ authenticate to the GitRepository. This field is only supported for 'azure' provider.
+ type: string
sparseCheckout:
description: |-
SparseCheckout specifies a list of directories to checkout when cloning
@@ -235,6 +240,10 @@ spec:
- interval
- url
type: object
+ x-kubernetes-validations:
+ - message: serviceAccountName can only be set when provider is 'azure'
+ rule: '!has(self.serviceAccountName) || (has(self.provider) && self.provider
+ == ''azure'')'
status:
default:
observedGeneration: -1
diff --git a/docs/api/v1/source.md b/docs/api/v1/source.md
index 772fb100..fc0de302 100644
--- a/docs/api/v1/source.md
+++ b/docs/api/v1/source.md
@@ -413,6 +413,19 @@ When not specified, defaults to ‘generic’.
+serviceAccountName
+
+string
+
+ |
+
+(Optional)
+ ServiceAccountName is the name of the Kubernetes ServiceAccount used to
+authenticate to the GitRepository. This field is only supported for ‘azure’ provider.
+ |
+
+
+
interval
@@ -2067,6 +2080,19 @@ When not specified, defaults to ‘generic’.
|
+serviceAccountName
+
+string
+
+ |
+
+(Optional)
+ ServiceAccountName is the name of the Kubernetes ServiceAccount used to
+authenticate to the GitRepository. This field is only supported for ‘azure’ provider.
+ |
+
+
+
interval
diff --git a/docs/spec/v1/gitrepositories.md b/docs/spec/v1/gitrepositories.md
index be17a1b4..d39ee73d 100644
--- a/docs/spec/v1/gitrepositories.md
+++ b/docs/spec/v1/gitrepositories.md
@@ -393,6 +393,24 @@ flux create secret githubapp ghapp-secret \
--app-private-key=~/private-key.pem
```
+### Service Account reference
+
+`.spec.serviceAccountName` is an optional field to specify a Service Account
+in the same namespace as GitRepository with purpose depending on the value of
+the `.spec.provider` field:
+
+- When `.spec.provider` is set to `azure`, the Service Account
+ will be used for Workload Identity authentication. In this case, the controller
+ feature gate `ObjectLevelWorkloadIdentity` must be enabled, otherwise the
+ controller will error out. For Azure DevOps specific setup, see the
+ [Azure DevOps integration guide](https://fluxcd.io/flux/integrations/azure/#for-azure-devops).
+
+**Note:** that for a publicly accessible git repository, you don't need to
+provide a `secretRef` nor `serviceAccountName`.
+
+For a complete guide on how to set up authentication for cloud providers,
+see the integration [docs](/flux/integrations/).
+
### Interval
`.spec.interval` is a required field that specifies the interval at which the
diff --git a/internal/controller/gitrepository_controller.go b/internal/controller/gitrepository_controller.go
index 7f32abc0..35ce52e4 100644
--- a/internal/controller/gitrepository_controller.go
+++ b/internal/controller/gitrepository_controller.go
@@ -663,6 +663,22 @@ func (r *GitRepositoryReconciler) getAuthOpts(ctx context.Context, obj *sourcev1
getCreds = func() (*authutils.GitCredentials, error) {
var opts []auth.Option
+ if obj.Spec.ServiceAccountName != "" {
+ // Check object-level workload identity feature gate.
+ if !auth.IsObjectLevelWorkloadIdentityEnabled() {
+ const gate = auth.FeatureGateObjectLevelWorkloadIdentity
+ const msgFmt = "to use spec.serviceAccountName for provider authentication please enable the %s feature gate in the controller"
+ err := serror.NewStalling(fmt.Errorf(msgFmt, gate), meta.FeatureGateDisabledReason)
+ conditions.MarkTrue(obj, sourcev1.FetchFailedCondition, meta.FeatureGateDisabledReason, "%s", err)
+ return nil, err
+ }
+ serviceAccount := client.ObjectKey{
+ Name: obj.Spec.ServiceAccountName,
+ Namespace: obj.GetNamespace(),
+ }
+ opts = append(opts, auth.WithServiceAccount(serviceAccount, r.Client))
+ }
+
if r.TokenCache != nil {
involvedObject := cache.InvolvedObject{
Kind: sourcev1.GitRepositoryKind,
@@ -742,6 +758,12 @@ func (r *GitRepositoryReconciler) getAuthOpts(ctx context.Context, obj *sourcev1
if getCreds != nil {
creds, err := getCreds()
if err != nil {
+ // Check if it's already a structured error and preserve it
+ switch err.(type) {
+ case *serror.Stalling, *serror.Generic:
+ return nil, err
+ }
+
e := serror.NewGeneric(
fmt.Errorf("failed to configure authentication options: %w", err),
sourcev1.AuthenticationFailedReason,
diff --git a/internal/controller/gitrepository_controller_test.go b/internal/controller/gitrepository_controller_test.go
index 13693499..23ee8084 100644
--- a/internal/controller/gitrepository_controller_test.go
+++ b/internal/controller/gitrepository_controller_test.go
@@ -48,6 +48,7 @@ import (
kstatus "github.com/fluxcd/cli-utils/pkg/kstatus/status"
"github.com/fluxcd/pkg/apis/meta"
+ "github.com/fluxcd/pkg/auth"
"github.com/fluxcd/pkg/git"
"github.com/fluxcd/pkg/git/github"
"github.com/fluxcd/pkg/gittestserver"
@@ -919,6 +920,15 @@ func TestGitRepositoryReconciler_getAuthOpts_provider(t *testing.T) {
},
wantErr: "ManagedIdentityCredential",
},
+ {
+ name: "azure provider with service account and feature gate for object-level identity disabled",
+ url: "https://dev.azure.com/foo/bar/_git/baz",
+ beforeFunc: func(obj *sourcev1.GitRepository) {
+ obj.Spec.Provider = sourcev1.GitProviderAzure
+ obj.Spec.ServiceAccountName = "azure-sa"
+ },
+ wantErr: auth.FeatureGateObjectLevelWorkloadIdentity,
+ },
{
name: "github provider with no secret ref",
url: "https://github.com/org/repo.git",
|