Skip to content

Commit 24412ed

Browse files
authored
Merge pull request #1871 from dipti-pai/azure-obj-level-gitrepo
[RFC-0010] Add multi-tenant workload identity support for Azure GitRepository
2 parents 5f9702b + 4fe3434 commit 24412ed

File tree

6 files changed

+91
-0
lines changed

6 files changed

+91
-0
lines changed

api/v1/gitrepository_types.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ const (
7777

7878
// GitRepositorySpec specifies the required configuration to produce an
7979
// Artifact for a Git repository.
80+
// +kubebuilder:validation:XValidation:rule="!has(self.serviceAccountName) || (has(self.provider) && self.provider == 'azure')",message="serviceAccountName can only be set when provider is 'azure'"
8081
type GitRepositorySpec struct {
8182
// URL specifies the Git repository URL, it can be an HTTP/S or SSH address.
8283
// +kubebuilder:validation:Pattern="^(http|https|ssh)://.*$"
@@ -98,6 +99,11 @@ type GitRepositorySpec struct {
9899
// +optional
99100
Provider string `json:"provider,omitempty"`
100101

102+
// ServiceAccountName is the name of the Kubernetes ServiceAccount used to
103+
// authenticate to the GitRepository. This field is only supported for 'azure' provider.
104+
// +optional
105+
ServiceAccountName string `json:"serviceAccountName,omitempty"`
106+
101107
// Interval at which the GitRepository URL is checked for updates.
102108
// This interval is approximate and may be subject to jitter to ensure
103109
// efficient use of resources.

config/crd/bases/source.toolkit.fluxcd.io_gitrepositories.yaml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,11 @@ spec:
174174
required:
175175
- name
176176
type: object
177+
serviceAccountName:
178+
description: |-
179+
ServiceAccountName is the name of the Kubernetes ServiceAccount used to
180+
authenticate to the GitRepository. This field is only supported for 'azure' provider.
181+
type: string
177182
sparseCheckout:
178183
description: |-
179184
SparseCheckout specifies a list of directories to checkout when cloning
@@ -235,6 +240,10 @@ spec:
235240
- interval
236241
- url
237242
type: object
243+
x-kubernetes-validations:
244+
- message: serviceAccountName can only be set when provider is 'azure'
245+
rule: '!has(self.serviceAccountName) || (has(self.provider) && self.provider
246+
== ''azure'')'
238247
status:
239248
default:
240249
observedGeneration: -1

docs/api/v1/source.md

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -413,6 +413,19 @@ When not specified, defaults to &lsquo;generic&rsquo;.</p>
413413
</tr>
414414
<tr>
415415
<td>
416+
<code>serviceAccountName</code><br>
417+
<em>
418+
string
419+
</em>
420+
</td>
421+
<td>
422+
<em>(Optional)</em>
423+
<p>ServiceAccountName is the name of the Kubernetes ServiceAccount used to
424+
authenticate to the GitRepository. This field is only supported for &lsquo;azure&rsquo; provider.</p>
425+
</td>
426+
</tr>
427+
<tr>
428+
<td>
416429
<code>interval</code><br>
417430
<em>
418431
<a href="https://pkg.go.dev/k8s.io/apimachinery/pkg/apis/meta/v1#Duration">
@@ -2067,6 +2080,19 @@ When not specified, defaults to &lsquo;generic&rsquo;.</p>
20672080
</tr>
20682081
<tr>
20692082
<td>
2083+
<code>serviceAccountName</code><br>
2084+
<em>
2085+
string
2086+
</em>
2087+
</td>
2088+
<td>
2089+
<em>(Optional)</em>
2090+
<p>ServiceAccountName is the name of the Kubernetes ServiceAccount used to
2091+
authenticate to the GitRepository. This field is only supported for &lsquo;azure&rsquo; provider.</p>
2092+
</td>
2093+
</tr>
2094+
<tr>
2095+
<td>
20702096
<code>interval</code><br>
20712097
<em>
20722098
<a href="https://pkg.go.dev/k8s.io/apimachinery/pkg/apis/meta/v1#Duration">

docs/spec/v1/gitrepositories.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -393,6 +393,24 @@ flux create secret githubapp ghapp-secret \
393393
--app-private-key=~/private-key.pem
394394
```
395395

396+
### Service Account reference
397+
398+
`.spec.serviceAccountName` is an optional field to specify a Service Account
399+
in the same namespace as GitRepository with purpose depending on the value of
400+
the `.spec.provider` field:
401+
402+
- When `.spec.provider` is set to `azure`, the Service Account
403+
will be used for Workload Identity authentication. In this case, the controller
404+
feature gate `ObjectLevelWorkloadIdentity` must be enabled, otherwise the
405+
controller will error out. For Azure DevOps specific setup, see the
406+
[Azure DevOps integration guide](https://fluxcd.io/flux/integrations/azure/#for-azure-devops).
407+
408+
**Note:** that for a publicly accessible git repository, you don't need to
409+
provide a `secretRef` nor `serviceAccountName`.
410+
411+
For a complete guide on how to set up authentication for cloud providers,
412+
see the integration [docs](/flux/integrations/).
413+
396414
### Interval
397415

398416
`.spec.interval` is a required field that specifies the interval at which the

internal/controller/gitrepository_controller.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -663,6 +663,22 @@ func (r *GitRepositoryReconciler) getAuthOpts(ctx context.Context, obj *sourcev1
663663
getCreds = func() (*authutils.GitCredentials, error) {
664664
var opts []auth.Option
665665

666+
if obj.Spec.ServiceAccountName != "" {
667+
// Check object-level workload identity feature gate.
668+
if !auth.IsObjectLevelWorkloadIdentityEnabled() {
669+
const gate = auth.FeatureGateObjectLevelWorkloadIdentity
670+
const msgFmt = "to use spec.serviceAccountName for provider authentication please enable the %s feature gate in the controller"
671+
err := serror.NewStalling(fmt.Errorf(msgFmt, gate), meta.FeatureGateDisabledReason)
672+
conditions.MarkTrue(obj, sourcev1.FetchFailedCondition, meta.FeatureGateDisabledReason, "%s", err)
673+
return nil, err
674+
}
675+
serviceAccount := client.ObjectKey{
676+
Name: obj.Spec.ServiceAccountName,
677+
Namespace: obj.GetNamespace(),
678+
}
679+
opts = append(opts, auth.WithServiceAccount(serviceAccount, r.Client))
680+
}
681+
666682
if r.TokenCache != nil {
667683
involvedObject := cache.InvolvedObject{
668684
Kind: sourcev1.GitRepositoryKind,
@@ -742,6 +758,12 @@ func (r *GitRepositoryReconciler) getAuthOpts(ctx context.Context, obj *sourcev1
742758
if getCreds != nil {
743759
creds, err := getCreds()
744760
if err != nil {
761+
// Check if it's already a structured error and preserve it
762+
switch err.(type) {
763+
case *serror.Stalling, *serror.Generic:
764+
return nil, err
765+
}
766+
745767
e := serror.NewGeneric(
746768
fmt.Errorf("failed to configure authentication options: %w", err),
747769
sourcev1.AuthenticationFailedReason,

internal/controller/gitrepository_controller_test.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ import (
4848

4949
kstatus "github.com/fluxcd/cli-utils/pkg/kstatus/status"
5050
"github.com/fluxcd/pkg/apis/meta"
51+
"github.com/fluxcd/pkg/auth"
5152
"github.com/fluxcd/pkg/git"
5253
"github.com/fluxcd/pkg/git/github"
5354
"github.com/fluxcd/pkg/gittestserver"
@@ -919,6 +920,15 @@ func TestGitRepositoryReconciler_getAuthOpts_provider(t *testing.T) {
919920
},
920921
wantErr: "ManagedIdentityCredential",
921922
},
923+
{
924+
name: "azure provider with service account and feature gate for object-level identity disabled",
925+
url: "https://dev.azure.com/foo/bar/_git/baz",
926+
beforeFunc: func(obj *sourcev1.GitRepository) {
927+
obj.Spec.Provider = sourcev1.GitProviderAzure
928+
obj.Spec.ServiceAccountName = "azure-sa"
929+
},
930+
wantErr: auth.FeatureGateObjectLevelWorkloadIdentity,
931+
},
922932
{
923933
name: "github provider with no secret ref",
924934
url: "https://github.com/org/repo.git",

0 commit comments

Comments
 (0)