Skip to content

Commit

Permalink
Enable Azure OIDC for Azure DevOps Respository
Browse files Browse the repository at this point in the history
- Add a new provider field to GitRepository API spec which can be set to azure to enable passwordless authentication to Azure DevOps repositories.

- API docs for new provider field and guidance to setup Azure environment with workload identity.

- Controller changes to set the provider options in git authoptions to fetch credential while cloning the repository.

- Add unit tests for testing provider

Signed-off-by: Dipti Pai <diptipai89@outlook.com>
  • Loading branch information
dipti-pai committed Sep 12, 2024
1 parent dd144ac commit 772131a
Show file tree
Hide file tree
Showing 8 changed files with 210 additions and 4 deletions.
15 changes: 15 additions & 0 deletions api/v1/gitrepository_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,14 @@ import (
const (
// GitRepositoryKind is the string representation of a GitRepository.
GitRepositoryKind = "GitRepository"

// GitProviderGeneric provides support for authentication using
// credentials specified in secretRef.
GitProviderGeneric string = "generic"

// GitProviderAzure provides support for authentication to azure
// repositories using Managed Identity.
GitProviderAzure string = "azure"
)

const (
Expand Down Expand Up @@ -80,6 +88,13 @@ type GitRepositorySpec struct {
// +optional
SecretRef *meta.LocalObjectReference `json:"secretRef,omitempty"`

// Provider used for authentication, can be 'azure', 'generic'.
// When not specified, defaults to 'generic'.
// +kubebuilder:validation:Enum=generic;azure
// +kubebuilder:default:=generic
// +optional
Provider string `json:"provider,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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,15 @@ spec:
efficient use of resources.
pattern: ^([0-9]+(\.[0-9]+)?(ms|s|m|h))+$
type: string
provider:
default: generic
description: |-
Provider used for authentication, can be 'azure', 'generic'.
When not specified, defaults to 'generic'.
enum:
- generic
- azure
type: string
proxySecretRef:
description: |-
ProxySecretRef specifies the Secret containing the proxy configuration
Expand Down
26 changes: 26 additions & 0 deletions docs/api/v1/source.md
Original file line number Diff line number Diff line change
Expand Up @@ -383,6 +383,19 @@ and &lsquo;known_hosts&rsquo; fields.</p>
</tr>
<tr>
<td>
<code>provider</code><br>
<em>
string
</em>
</td>
<td>
<em>(Optional)</em>
<p>Provider used for authentication, can be &lsquo;azure&rsquo;, &lsquo;generic&rsquo;.
When not specified, defaults to &lsquo;generic&rsquo;.</p>
</td>
</tr>
<tr>
<td>
<code>interval</code><br>
<em>
<a href="https://pkg.go.dev/k8s.io/apimachinery/pkg/apis/meta/v1#Duration">
Expand Down Expand Up @@ -1710,6 +1723,19 @@ and &lsquo;known_hosts&rsquo; fields.</p>
</tr>
<tr>
<td>
<code>provider</code><br>
<em>
string
</em>
</td>
<td>
<em>(Optional)</em>
<p>Provider used for authentication, can be &lsquo;azure&rsquo;, &lsquo;generic&rsquo;.
When not specified, defaults to &lsquo;generic&rsquo;.</p>
</td>
</tr>
<tr>
<td>
<code>interval</code><br>
<em>
<a href="https://pkg.go.dev/k8s.io/apimachinery/pkg/apis/meta/v1#Duration">
Expand Down
85 changes: 85 additions & 0 deletions docs/spec/v1/gitrepositories.md
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,91 @@ For password-protected SSH private keys, the password must be provided
via an additional `password` field in the secret. Flux CLI also supports
this via the `--password` flag.

### Provider

`.spec.provider` is an optional field that allows specifying an OIDC provider
used for authentication purposes.

Supported options are:

- `generic`
- `azure`

When provider is not specified, it defaults to `generic` indicating that
mechanisms using `spec.secretRef` are used for authentication.

#### Azure

The `azure` provider can be used to authenticate to Azure DevOps repositories
automatically using Workload Identity.

##### Pre-requisites

- Ensure that your Azure DevOps Organization is
[connected](https://learn.microsoft.com/en-us/azure/devops/organizations/accounts/connect-organization-to-azure-ad?view=azure-devops)
to Microsoft Entra.
- Ensure Workload Identity is properly [set up on your
cluster](https://learn.microsoft.com/en-us/azure/aks/workload-identity-deploy-cluster#create-an-aks-cluster).

##### Configure Flux controller

- Create a managed identity to access Azure DevOps. Establish a federated
identity credential between the managed identity and the source-controller
service account. In the default installation, the source-controller service
account is located in the `flux-system` namespace with name
`source-controller`. Ensure the federated credential uses the correct
namespace and name of the source-controller service account. For more details,
please refer to this
[guide](https://azure.github.io/azure-workload-identity/docs/quick-start.html#6-establish-federated-identity-credential-between-the-identity-and-the-service-account-issuer--subject).

- Add the managed identity to the Azure DevOps organization as a user. Ensure
that the managed identity has the necessary permissions to access the Azure
DevOps repository as described
[here](https://learn.microsoft.com/en-us/azure/devops/integrate/get-started/authentication/service-principal-managed-identity?view=azure-devops#2-add-and-manage-service-principals-in-an-azure-devops-organization).

- Add the following patch to your bootstrap repository in
`flux-system/kustomization.yaml` file:


```yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- gotk-components.yaml
- gotk-sync.yaml
patches:
- patch: |-
apiVersion: v1
kind: ServiceAccount
metadata:
name: source-controller
namespace: flux-system
annotations:
azure.workload.identity/client-id: <AZURE_CLIENT_ID>
labels:
azure.workload.identity/use: "true"
- patch: |-
apiVersion: apps/v1
kind: Deployment
metadata:
name: source-controller
namespace: flux-system
labels:
azure.workload.identity/use: "true"
spec:
template:
metadata:
labels:
azure.workload.identity/use: "true"
```

**Note:** When azure `provider` is used with `GitRepository`, the `.spec.url`
must follow this format:

```
https://dev.azure.com/{your-organization}/{your-project}/_git/{your-repository}
```

### Interval

`.spec.interval` is a required field that specifies the interval at which the
Expand Down
7 changes: 7 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ require (
github.com/fluxcd/cli-utils v0.36.0-flux.9
github.com/fluxcd/pkg/apis/event v0.10.0
github.com/fluxcd/pkg/apis/meta v1.6.0
github.com/fluxcd/pkg/auth v0.0.0-00010101000000-000000000000
github.com/fluxcd/pkg/git v0.20.0
github.com/fluxcd/pkg/git/gogit v0.20.0
github.com/fluxcd/pkg/gittestserver v0.13.0
Expand Down Expand Up @@ -406,3 +407,9 @@ require (
)

retract v0.32.0 // Refers to incorrect ./api version.

replace github.com/fluxcd/pkg/auth => github.com/dipti-pai/pkg/auth v0.0.0-20240910203859-abee735aa028

replace github.com/fluxcd/pkg/git/gogit => github.com/dipti-pai/pkg/git/gogit v0.0.0-20240910203859-abee735aa028

replace github.com/fluxcd/pkg/git => github.com/dipti-pai/pkg/git v0.0.0-20240910203859-abee735aa028
10 changes: 6 additions & 4 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -291,6 +291,12 @@ github.com/digitorus/timestamp v0.0.0-20231217203849-220c5c2851b7 h1:lxmTCgmHE1G
github.com/digitorus/timestamp v0.0.0-20231217203849-220c5c2851b7/go.mod h1:GvWntX9qiTlOud0WkQ6ewFm0LPy5JUR1Xo0Ngbd1w6Y=
github.com/dimchansky/utfbom v1.1.1 h1:vV6w1AhK4VMnhBno/TPVCoK9U/LP0PkLCS9tbxHdi/U=
github.com/dimchansky/utfbom v1.1.1/go.mod h1:SxdoEBH5qIqFocHMyGOXVAybYJdr71b1Q/j0mACtrfE=
github.com/dipti-pai/pkg/auth v0.0.0-20240910203859-abee735aa028 h1:Ohfv7mzT0aecvS4DJqDgBCGiKccsH8YcfOJ1fwdWi5g=
github.com/dipti-pai/pkg/auth v0.0.0-20240910203859-abee735aa028/go.mod h1:0VS8EHPXNoB9q84OJg+t2LlkdIvWzttUPXhSxMKavGk=
github.com/dipti-pai/pkg/git v0.0.0-20240910203859-abee735aa028 h1:H9PpGshNFcO5yenhJDJOHXF6x5jgof64YI5l+AYkpEQ=
github.com/dipti-pai/pkg/git v0.0.0-20240910203859-abee735aa028/go.mod h1:XTZfxHFy96sbGzbhN68u8+L6IKjqAxLax/dCq9gaUk4=
github.com/dipti-pai/pkg/git/gogit v0.0.0-20240910203859-abee735aa028 h1:Ekj1aPhfud5phbZq9rwZeN43YE/IL1RzFghoRDJKC6I=
github.com/dipti-pai/pkg/git/gogit v0.0.0-20240910203859-abee735aa028/go.mod h1:pX0wDKVhNINddJ3vtUS6ripizHTqjc+kk93CLO0UDmM=
github.com/distribution/distribution/v3 v3.0.0-beta.1 h1:X+ELTxPuZ1Xe5MsD3kp2wfGUhc8I+MPfRis8dZ818Ic=
github.com/distribution/distribution/v3 v3.0.0-beta.1/go.mod h1:O9O8uamhHzWWQVTjuQpyYUVm/ShPHPUDgvQMpHGVBDs=
github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=
Expand Down Expand Up @@ -351,10 +357,6 @@ github.com/fluxcd/pkg/apis/meta v1.6.0 h1:93TcRpiph0OCoQh+cI+PM7E35kBW9dScuas9tW
github.com/fluxcd/pkg/apis/meta v1.6.0/go.mod h1:ZOeHcvyVdZDC5ZOGV7YuwplIvAx6LvmpeyhfTcNZCnc=
github.com/fluxcd/pkg/cache v0.0.3 h1:VK5joG/p+amh5Ob+r1OFOx0cCYiswEf8mX1/J1BG7Mw=
github.com/fluxcd/pkg/cache v0.0.3/go.mod h1:UU6oFhV+mG0A5/RwIlvXhyuKlJwQEkk92jVB3vKMLtk=
github.com/fluxcd/pkg/git v0.20.0 h1:byUbxLLZ9AyVYmK16mvxY/iA/ZhNwA30GHKPKNh7pik=
github.com/fluxcd/pkg/git v0.20.0/go.mod h1:YnBOFhX7zzyVjg/u1Et1xBqXs30kb2sWWesIl3/glhw=
github.com/fluxcd/pkg/git/gogit v0.20.0 h1:ZlWq//I465lv9aEEWaJhjJaTiTtnjcH+Td0fg1rPXWU=
github.com/fluxcd/pkg/git/gogit v0.20.0/go.mod h1:ZA4WsKr28cj1yuplxOw9vHgCL4OCNJJLib1cJ77Tp9o=
github.com/fluxcd/pkg/gittestserver v0.13.0 h1:6rvD9Z7+4zBcNT+LK0z4H0z6mDaw1Zd8ZaLh/dw8dzI=
github.com/fluxcd/pkg/gittestserver v0.13.0/go.mod h1:LDw32Wo9mTmKNmJq4g7LRVBqPXlpMIWFBDOrRRh/+As=
github.com/fluxcd/pkg/helmtestserver v0.19.0 h1:DbidD46we8iLp/Sxn2TO8twtlP5gxFQaP3XTNJC0bl8=
Expand Down
14 changes: 14 additions & 0 deletions internal/controller/gitrepository_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import (
"time"

securejoin "github.com/cyphar/filepath-securejoin"
"github.com/fluxcd/pkg/auth/azure"
"github.com/fluxcd/pkg/runtime/logger"
"github.com/go-git/go-git/v5/plumbing/transport"
corev1 "k8s.io/api/core/v1"
Expand Down Expand Up @@ -647,6 +648,19 @@ func (r *GitRepositoryReconciler) getAuthOpts(ctx context.Context, obj *sourcev1
if err != nil {
return nil, err
}

// Configure provider authentication if specified in spec
if obj.Spec.Provider != "" && obj.Spec.Provider != sourcev1.GitProviderGeneric {
if obj.Spec.Provider == sourcev1.GitProviderAzure {
authOpts.ProviderOpts = &git.ProviderOptions{
Name: obj.Spec.Provider,
AzureOpts: []azure.OptFunc{
azure.WithAzureDevOpsScope(),
},
}
}
}

return authOpts, nil
}

Expand Down
48 changes: 48 additions & 0 deletions internal/controller/gitrepository_controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -683,6 +683,54 @@ func TestGitRepositoryReconciler_reconcileSource_authStrategy(t *testing.T) {
}
}

func TestGitRepositoryReconciler_getAuthOpts_provider(t *testing.T) {
tests := []struct {
name string
beforeFunc func(obj *sourcev1.GitRepository)
wantProviderOptsName string
}{
{
name: "azure provider",
beforeFunc: func(obj *sourcev1.GitRepository) {
obj.Spec.Provider = sourcev1.GitProviderAzure
},
wantProviderOptsName: sourcev1.GitProviderAzure,
},
{
name: "generic provider",
beforeFunc: func(obj *sourcev1.GitRepository) {
obj.Spec.Provider = sourcev1.GitProviderGeneric
},
},
{
name: "no provider",
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
g := NewWithT(t)
obj := &sourcev1.GitRepository{}
r := &GitRepositoryReconciler{}
url, _ := url.Parse("https://dev.azure.com/foo/bar/_git/baz")

if tt.beforeFunc != nil {
tt.beforeFunc(obj)
}
opts, err := r.getAuthOpts(context.TODO(), obj, *url)

g.Expect(err).ToNot(HaveOccurred())
g.Expect(opts).ToNot(BeNil())
if tt.wantProviderOptsName != "" {
g.Expect(opts.ProviderOpts).ToNot(BeNil())
g.Expect(opts.ProviderOpts.Name).To(Equal(tt.wantProviderOptsName))
} else {
g.Expect(opts.ProviderOpts).To(BeNil())
}
})
}
}

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

Expand Down

0 comments on commit 772131a

Please sign in to comment.