feat: Support Azure Service Principal authentication for Azure DevOps repositories (#25324)

Signed-off-by: Allan Yung <allan.yung@bbdsoftware.com>
Co-authored-by: Dan Garfield <dan.garfield@octopus.com>
This commit is contained in:
Allan Yung 2026-04-16 16:16:47 +01:00 committed by GitHub
parent 6bf97ec1fd
commit 9a19735918
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
27 changed files with 2414 additions and 1107 deletions

80
assets/swagger.json generated
View file

@ -4039,6 +4039,30 @@
"description": "Whether https should be disabled for an OCI repo.",
"name": "insecureOciForceHttp",
"in": "query"
},
{
"type": "string",
"description": "Azure Service Principal Client ID.",
"name": "azureServicePrincipalClientId",
"in": "query"
},
{
"type": "string",
"description": "Azure Service Principal Client Secret.",
"name": "azureServicePrincipalClientSecret",
"in": "query"
},
{
"type": "string",
"description": "Azure Service Principal Tenant ID.",
"name": "azureServicePrincipalTenantId",
"in": "query"
},
{
"type": "string",
"description": "Azure Active Directory Endpoint.",
"name": "azureActiveDirectoryEndpoint",
"in": "query"
}
],
"responses": {
@ -4946,6 +4970,30 @@
"description": "Whether https should be disabled for an OCI repo.",
"name": "insecureOciForceHttp",
"in": "query"
},
{
"type": "string",
"description": "Azure Service Principal Client ID.",
"name": "azureServicePrincipalClientId",
"in": "query"
},
{
"type": "string",
"description": "Azure Service Principal Client Secret.",
"name": "azureServicePrincipalClientSecret",
"in": "query"
},
{
"type": "string",
"description": "Azure Service Principal Tenant ID.",
"name": "azureServicePrincipalTenantId",
"in": "query"
},
{
"type": "string",
"description": "Azure Active Directory Endpoint.",
"name": "azureActiveDirectoryEndpoint",
"in": "query"
}
],
"responses": {
@ -9519,6 +9567,22 @@
"type": "object",
"title": "RepoCreds holds the definition for repository credentials",
"properties": {
"azureActiveDirectoryEndpoint": {
"type": "string",
"title": "AzureActiveDirectoryEndpoint specifies the Azure Active Directory endpoint used for Service Principal authentication. If empty will default to https://login.microsoftonline.com"
},
"azureServicePrincipalClientId": {
"type": "string",
"title": "AzureServicePrincipalClientId specifies the client ID of the Azure Service Principal used to access the repo"
},
"azureServicePrincipalClientSecret": {
"type": "string",
"title": "AzureServicePrincipalClientSecret specifies the client secret of the Azure Service Principal used to access the repo"
},
"azureServicePrincipalTenantId": {
"type": "string",
"title": "AzureServicePrincipalTenantId specifies the tenant ID of the Azure Service Principal used to access the repo"
},
"bearerToken": {
"type": "string",
"title": "BearerToken contains the bearer token used for Git BitBucket Data Center auth at the repo server"
@ -9618,6 +9682,22 @@
"type": "object",
"title": "Repository is a repository holding application configurations",
"properties": {
"azureActiveDirectoryEndpoint": {
"type": "string",
"title": "AzureActiveDirectoryEndpoint specifies the Azure Active Directory endpoint used for Service Principal authentication. If empty will default to https://login.microsoftonline.com"
},
"azureServicePrincipalClientId": {
"type": "string",
"title": "AzureServicePrincipalClientId specifies the client ID of the Azure Service Principal used to access the repo"
},
"azureServicePrincipalClientSecret": {
"type": "string",
"title": "AzureServicePrincipalClientSecret specifies the client secret of the Azure Service Principal used to access the repo"
},
"azureServicePrincipalTenantId": {
"type": "string",
"title": "AzureServicePrincipalTenantId specifies the tenant ID of the Azure Service Principal used to access the repo"
},
"bearerToken": {
"type": "string",
"title": "BearerToken contains the bearer token used for Git BitBucket Data Center auth at the repo server"

View file

@ -102,6 +102,12 @@ func NewRepoAddCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
# Add a private Git repository on Google Cloud Sources via GCP service account credentials
argocd repo add https://source.developers.google.com/p/my-google-cloud-project/r/my-repo --gcp-service-account-key-path service-account-key.json
# Add a private Git repository on Azure Devops via Azure Service Principal credentials
argocd repo add https://dev.azure.com/my-devops-organization/my-devops-project/_git/my-devops-repo --azure-service-principal-client-id 12345678-1234-1234-1234-123456789012 --azure-service-principal-client-secret test --azure-service-principal-tenant-id 12345678-1234-1234-1234-123456789012
# Add a private Git repository on Azure Devops via Azure Service Principal credentials when not using default Azure public cloud
argocd repo add https://dev.azure.com/my-devops-organization/my-devops-project/_git/my-devops-repo --azure-service-principal-client-id 12345678-1234-1234-1234-123456789012 --azure-service-principal-client-secret test --azure-service-principal-tenant-id 12345678-1234-1234-1234-123456789012 --azure-active-directory-endpoint https://login.microsoftonline.de
`
command := &cobra.Command{
@ -191,6 +197,10 @@ func NewRepoAddCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
repoOpts.Repo.NoProxy = repoOpts.NoProxy
repoOpts.Repo.ForceHttpBasicAuth = repoOpts.ForceHttpBasicAuth
repoOpts.Repo.UseAzureWorkloadIdentity = repoOpts.UseAzureWorkloadIdentity
repoOpts.Repo.AzureServicePrincipalTenantId = repoOpts.AzureServicePrincipalTenantId
repoOpts.Repo.AzureServicePrincipalClientId = repoOpts.AzureServicePrincipalClientId
repoOpts.Repo.AzureServicePrincipalClientSecret = repoOpts.AzureServicePrincipalClientSecret
repoOpts.Repo.AzureActiveDirectoryEndpoint = repoOpts.AzureActiveDirectoryEndpoint
repoOpts.Repo.Depth = repoOpts.Depth
repoOpts.Repo.WebhookManifestCacheWarmDisabled = repoOpts.WebhookManifestCacheWarmDisabled
@ -247,6 +257,10 @@ func NewRepoAddCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
ForceHttpBasicAuth: repoOpts.Repo.ForceHttpBasicAuth,
UseAzureWorkloadIdentity: repoOpts.Repo.UseAzureWorkloadIdentity,
InsecureOciForceHttp: repoOpts.Repo.InsecureOCIForceHttp,
AzureServicePrincipalTenantId: repoOpts.Repo.AzureServicePrincipalTenantId,
AzureServicePrincipalClientId: repoOpts.Repo.AzureServicePrincipalClientId,
AzureServicePrincipalClientSecret: repoOpts.Repo.AzureServicePrincipalClientSecret,
AzureActiveDirectoryEndpoint: repoOpts.Repo.AzureActiveDirectoryEndpoint,
}
_, err = repoIf.ValidateAccess(ctx, &repoAccessReq)
errors.CheckError(err)

View file

@ -83,6 +83,12 @@ func NewRepoCredsAddCommand(clientOpts *argocdclient.ClientOptions) *cobra.Comma
# Add credentials with GCP credentials for all repositories under https://source.developers.google.com/p/my-google-cloud-project/r/
argocd repocreds add https://source.developers.google.com/p/my-google-cloud-project/r/ --gcp-service-account-key-path service-account-key.json
# Add credentials with Azure Service Principal to use for all repositories under https://dev.azure.com/my-devops-organization
argocd repocreds add https://dev.azure.com/my-devops-organization --azure-service-principal-client-id 12345678-1234-1234-1234-123456789012 --azure-service-principal-client-secret test --azure-service-principal-tenant-id 12345678-1234-1234-1234-123456789012
# Add credentials with Azure Service Principal to use for all repositories under https://dev.azure.com/my-devops-organization when not using default Azure public cloud
argocd repocreds add https://dev.azure.com/my-devops-organization --azure-service-principal-client-id 12345678-1234-1234-1234-123456789012 --azure-service-principal-client-secret test --azure-service-principal-tenant-id 12345678-1234-1234-1234-123456789012 --azure-active-directory-endpoint https://login.microsoftonline.de
`
command := &cobra.Command{
@ -201,6 +207,10 @@ func NewRepoCredsAddCommand(clientOpts *argocdclient.ClientOptions) *cobra.Comma
command.Flags().BoolVar(&repo.ForceHttpBasicAuth, "force-http-basic-auth", false, "whether to force basic auth when connecting via HTTP")
command.Flags().BoolVar(&repo.UseAzureWorkloadIdentity, "use-azure-workload-identity", false, "whether to use azure workload identity for authentication")
command.Flags().StringVar(&repo.Proxy, "proxy-url", "", "If provided, this URL will be used to connect via proxy")
command.Flags().StringVar(&repo.AzureServicePrincipalClientId, "azure-service-principal-client-id", "", "client id of the Azure Service Principal")
command.Flags().StringVar(&repo.AzureServicePrincipalClientSecret, "azure-service-principal-client-secret", "", "client secret of the Azure Service Principal")
command.Flags().StringVar(&repo.AzureServicePrincipalTenantId, "azure-service-principal-tenant-id", "", "tenant id of the Azure Service Principal")
command.Flags().StringVar(&repo.AzureActiveDirectoryEndpoint, "azure-active-directory-endpoint", "", "Active Directory endpoint when not using default Azure public cloud (e.g. https://login.microsoftonline.de)")
return command
}

View file

@ -29,6 +29,10 @@ type RepoOptions struct {
UseAzureWorkloadIdentity bool
Depth int64
WebhookManifestCacheWarmDisabled bool
AzureServicePrincipalTenantId string
AzureServicePrincipalClientId string
AzureServicePrincipalClientSecret string
AzureActiveDirectoryEndpoint string
}
func AddRepoFlags(command *cobra.Command, opts *RepoOptions) {
@ -57,4 +61,8 @@ func AddRepoFlags(command *cobra.Command, opts *RepoOptions) {
command.Flags().BoolVar(&opts.InsecureOCIForceHTTP, "insecure-oci-force-http", false, "Use http when accessing an OCI repository")
command.Flags().Int64Var(&opts.Depth, "depth", 0, "Specify a custom depth for git clone operations. Unless specified, a full clone is performed using the depth of 0")
command.Flags().BoolVar(&opts.WebhookManifestCacheWarmDisabled, "webhook-manifest-cache-warm-disabled", false, "disable manifest cache warming during webhook processing for this repository (recommended for large monorepos with plain YAML manifests)")
command.Flags().StringVar(&opts.AzureServicePrincipalTenantId, "azure-service-principal-tenant-id", "", "tenant id of the Azure Service Principal")
command.Flags().StringVar(&opts.AzureServicePrincipalClientId, "azure-service-principal-client-id", "", "client id of the Azure Service Principal")
command.Flags().StringVar(&opts.AzureServicePrincipalClientSecret, "azure-service-principal-client-secret", "", "client secret of the Azure Service Principal")
command.Flags().StringVar(&opts.AzureActiveDirectoryEndpoint, "azure-active-directory-endpoint", "", "Active Directory endpoint when not using default Azure public cloud (e.g. https://login.microsoftonline.de)")
}

View file

@ -137,6 +137,9 @@ const (
ChangePasswordSSOTokenMaxAge = time.Minute * 5
// GithubAppCredsExpirationDuration is the default time used to cache the GitHub app credentials
GithubAppCredsExpirationDuration = time.Minute * 60
// AzureServicePrincipalCredsExpirationDuration is the default time used to cache the Azure service principal credentials
// SP tokens are valid for 60 minutes, so cache for 59 minutes to avoid issues with token expiration when taking the cleanup interval of 1 minute into account
AzureServicePrincipalCredsExpirationDuration = time.Minute * 59
// PasswordPatten is the default password patten
PasswordPatten = `^.{8,32}$`
@ -297,6 +300,8 @@ const (
EnvEnableGRPCTimeHistogramEnv = "ARGOCD_ENABLE_GRPC_TIME_HISTOGRAM"
// EnvGithubAppCredsExpirationDuration controls the caching of Github app credentials. This value is in minutes (default: 60)
EnvGithubAppCredsExpirationDuration = "ARGOCD_GITHUB_APP_CREDS_EXPIRATION_DURATION"
// EnvAzureServicePrincipalCredsExpirationDuration controls the caching of Azure service principal credentials. This value is in minutes (default: 59). Any value greater than 59 will be set to 59 minutes
EnvAzureServicePrincipalCredsExpirationDuration = "ARGOCD_AZURE_SERVICE_PRINCIPAL_CREDS_EXPIRATION_DURATION"
// EnvHelmIndexCacheDuration controls how the helm repository index file is cached for (default: 0)
EnvHelmIndexCacheDuration = "ARGOCD_HELM_INDEX_CACHE_DURATION"
// EnvAppConfigPath allows to override the configuration path for repo server

Binary file not shown.

After

Width:  |  Height:  |  Size: 75 KiB

View file

@ -180,7 +180,7 @@ spec:
Repository details are stored in secrets. To configure a repo, create a secret which contains repository details.
Consider using [bitnami-labs/sealed-secrets](https://github.com/bitnami-labs/sealed-secrets) to store an encrypted secret definition as a Kubernetes manifest.
Each repository must have a `url` field and, depending on whether you connect using HTTPS, SSH, or GitHub App, `username` and `password` (for HTTPS), `sshPrivateKey` (for SSH), or `githubAppPrivateKey` (for GitHub App).
Each repository must have a `url` field and, depending on whether you connect using HTTPS, SSH, GitHub App or Azure Service Principal, `username` and `password` (for HTTPS), `sshPrivateKey` (for SSH), `githubAppPrivateKey` (for GitHub App) or `azureServicePrincipalClientSecret` (for Azure Service Principal).
Credentials can be scoped to a project using the optional `project` field. When omitted, the credential will be used as the default for all projects without a scoped credential.
> [!WARNING]
@ -296,6 +296,39 @@ Example for Azure Container Registry/ Azure Devops repositories using Azure work
Refer to [Azure Container Registry/Azure Repos using Azure Workload Identity](../user-guide/private-repositories.md#azure-container-registryazure-repos-using-azure-workload-identity)
Example for Azure Service Principal:
```yaml
apiVersion: v1
kind: Secret
metadata:
name: service-principal-for-azure-public-cloud
namespace: argocd
labels:
argocd.argoproj.io/secret-type: repository
stringData:
type: git
url: https://dev.azure.com/my-devops-organization/my-devops-project/_git/my-devops-repo
azureServicePrincipalClientId: 12345678-1234-1234-1234-123456789012
azureServicePrincipalTenantId: 12345678-1234-1234-1234-123456789012
azureServicePrincipalClientSecret: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
---
apiVersion: v1
kind: Secret
metadata:
name: service-principal-for-azure-other-cloud
namespace: argocd
labels:
argocd.argoproj.io/secret-type: repository
stringData:
type: git
url: https://dev.azure.com/my-devops-organization/my-devops-project/_git/my-devops-repo
azureActiveDirectoryEndpoint: https://login.microsoftonline.de
azureServicePrincipalClientId: 12345678-1234-1234-1234-123456789012
azureServicePrincipalTenantId: 12345678-1234-1234-1234-123456789012
azureServicePrincipalClientSecret: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
```
### Repository Credentials
If you want to use the same credentials for multiple repositories, you can configure credential templates. Credential templates can carry the same credentials information as repositories.

View file

@ -59,6 +59,10 @@ argocd admin repo generate-spec REPOURL [flags]
### Options
```
--azure-active-directory-endpoint string Active Directory endpoint when not using default Azure public cloud (e.g. https://login.microsoftonline.de)
--azure-service-principal-client-id string client id of the Azure Service Principal
--azure-service-principal-client-secret string client secret of the Azure Service Principal
--azure-service-principal-tenant-id string tenant id of the Azure Service Principal
--bearer-token string bearer token to the Git BitBucket Data Center repository
--depth int Specify a custom depth for git clone operations. Unless specified, a full clone is performed using the depth of 0
--enable-lfs enable git-lfs (Large File Support) on this repository

View file

@ -56,11 +56,21 @@ argocd repo add REPOURL [flags]
# Add a private Git repository on Google Cloud Sources via GCP service account credentials
argocd repo add https://source.developers.google.com/p/my-google-cloud-project/r/my-repo --gcp-service-account-key-path service-account-key.json
# Add a private Git repository on Azure Devops via Azure Service Principal credentials
argocd repo add https://dev.azure.com/my-devops-organization/my-devops-project/_git/my-devops-repo --azure-service-principal-client-id 12345678-1234-1234-1234-123456789012 --azure-service-principal-client-secret test --azure-service-principal-tenant-id 12345678-1234-1234-1234-123456789012
# Add a private Git repository on Azure Devops via Azure Service Principal credentials when not using default Azure public cloud
argocd repo add https://dev.azure.com/my-devops-organization/my-devops-project/_git/my-devops-repo --azure-service-principal-client-id 12345678-1234-1234-1234-123456789012 --azure-service-principal-client-secret test --azure-service-principal-tenant-id 12345678-1234-1234-1234-123456789012 --azure-active-directory-endpoint https://login.microsoftonline.de
```
### Options
```
--azure-active-directory-endpoint string Active Directory endpoint when not using default Azure public cloud (e.g. https://login.microsoftonline.de)
--azure-service-principal-client-id string client id of the Azure Service Principal
--azure-service-principal-client-secret string client secret of the Azure Service Principal
--azure-service-principal-tenant-id string tenant id of the Azure Service Principal
--bearer-token string bearer token to the Git BitBucket Data Center repository
--depth int Specify a custom depth for git clone operations. Unless specified, a full clone is performed using the depth of 0
--enable-lfs enable git-lfs (Large File Support) on this repository

View file

@ -32,11 +32,21 @@ argocd repocreds add REPOURL [flags]
# Add credentials with GCP credentials for all repositories under https://source.developers.google.com/p/my-google-cloud-project/r/
argocd repocreds add https://source.developers.google.com/p/my-google-cloud-project/r/ --gcp-service-account-key-path service-account-key.json
# Add credentials with Azure Service Principal to use for all repositories under https://dev.azure.com/my-devops-organization
argocd repocreds add https://dev.azure.com/my-devops-organization --azure-service-principal-client-id 12345678-1234-1234-1234-123456789012 --azure-service-principal-client-secret test --azure-service-principal-tenant-id 12345678-1234-1234-1234-123456789012
# Add credentials with Azure Service Principal to use for all repositories under https://dev.azure.com/my-devops-organization when not using default Azure public cloud
argocd repocreds add https://dev.azure.com/my-devops-organization --azure-service-principal-client-id 12345678-1234-1234-1234-123456789012 --azure-service-principal-client-secret test --azure-service-principal-tenant-id 12345678-1234-1234-1234-123456789012 --azure-active-directory-endpoint https://login.microsoftonline.de
```
### Options
```
--azure-active-directory-endpoint string Active Directory endpoint when not using default Azure public cloud (e.g. https://login.microsoftonline.de)
--azure-service-principal-client-id string client id of the Azure Service Principal
--azure-service-principal-client-secret string client secret of the Azure Service Principal
--azure-service-principal-tenant-id string tenant id of the Azure Service Principal
--bearer-token string bearer token to the Git repository
--enable-oci Specifies whether helm-oci support should be enabled for this repo
--force-http-basic-auth whether to force basic auth when connecting via HTTP

View file

@ -242,6 +242,36 @@ stringData:
useAzureWorkloadIdentity: "true"
```
### Azure Devops using Service Principal
Azure DevOps repositories can be accessed using credentials from a Service Principal. Refer to steps 1,2 and 3 from the [Use service principals and managed identities in Azure DevOps](https://learn.microsoft.com/en-us/azure/devops/integrate/get-started/authentication/service-principal-managed-identity?view=azure-devops) documentation on how to create a service principal and configure access to Azure DevOps.
> [!NOTE]
> Ensure your service principal has at least `Project Readers` permissions to the `Project` that contains the repository. This is the minimum requirement.
You can configure access to your Git repository hosted on Azure DevOps with Service Principal by either using the CLI or the UI.
Using the CLI:
```
argocd repo add https://dev.azure.com/my-devops-organization/my-devops-project/_git/my-devops-repo --azure-service-principal-tenant-id 12345678-1234-1234-1234-123456789012 --azure-service-principal-client-id 12345678-1234-1234-1234-123456789012 --azure-service-principal-client-secret test
```
> [!NOTE]
> To use an Azure cloud other than the default public cloud using the CLI add `--azure-active-directory-endpoint https://login.microsoftonline.de` flag.
Using the UI:
1. Navigate to `Settings/Repositories`
![connect repo overview](../assets/repo-add-overview.png)
2. Click `Connect Repo` button, choose connection method: `Via Azure Service Principal`, enter the URL, Tenant Id, Client Id, Client Secret, and Active Directory Endpoint if not using default Azure public cloud.
![connect repo](../assets/repo-add-azure-service-principal.png)
3. Click `Connect` to test the connection and have the repository added
## Credential templates
You can also set up credentials to serve as templates for connecting repositories, without having to repeat credential configuration. For example, if you setup credential templates for the URL prefix `https://github.com/argoproj`, these credentials will be used for all repositories with this URL as prefix (e.g. `https://github.com/argoproj/argocd-example-apps`) that do not have their own credentials configured.

View file

@ -402,6 +402,14 @@ type RepoAccessQuery struct {
BearerToken string `protobuf:"bytes,21,opt,name=bearerToken,proto3" json:"bearerToken,omitempty"`
// Whether https should be disabled for an OCI repo
InsecureOciForceHttp bool `protobuf:"varint,22,opt,name=insecureOciForceHttp,proto3" json:"insecureOciForceHttp,omitempty"`
// Azure Service Principal Client ID
AzureServicePrincipalClientId string `protobuf:"bytes,23,opt,name=azureServicePrincipalClientId,proto3" json:"azureServicePrincipalClientId,omitempty"`
// Azure Service Principal Client Secret
AzureServicePrincipalClientSecret string `protobuf:"bytes,24,opt,name=azureServicePrincipalClientSecret,proto3" json:"azureServicePrincipalClientSecret,omitempty"`
// Azure Service Principal Tenant ID
AzureServicePrincipalTenantId string `protobuf:"bytes,25,opt,name=azureServicePrincipalTenantId,proto3" json:"azureServicePrincipalTenantId,omitempty"`
// Azure Active Directory Endpoint
AzureActiveDirectoryEndpoint string `protobuf:"bytes,26,opt,name=azureActiveDirectoryEndpoint,proto3" json:"azureActiveDirectoryEndpoint,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
@ -587,6 +595,34 @@ func (m *RepoAccessQuery) GetInsecureOciForceHttp() bool {
return false
}
func (m *RepoAccessQuery) GetAzureServicePrincipalClientId() string {
if m != nil {
return m.AzureServicePrincipalClientId
}
return ""
}
func (m *RepoAccessQuery) GetAzureServicePrincipalClientSecret() string {
if m != nil {
return m.AzureServicePrincipalClientSecret
}
return ""
}
func (m *RepoAccessQuery) GetAzureServicePrincipalTenantId() string {
if m != nil {
return m.AzureServicePrincipalTenantId
}
return ""
}
func (m *RepoAccessQuery) GetAzureActiveDirectoryEndpoint() string {
if m != nil {
return m.AzureActiveDirectoryEndpoint
}
return ""
}
type RepoResponse struct {
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
@ -757,94 +793,99 @@ func init() {
}
var fileDescriptor_8d38260443475705 = []byte{
// 1392 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xbc, 0x58, 0xdf, 0x6e, 0x1b, 0xc5,
0x17, 0xd6, 0x26, 0x8d, 0x9b, 0x4c, 0x9a, 0xd6, 0x9d, 0x24, 0xed, 0xfe, 0xdc, 0x34, 0xcd, 0x6f,
0x5b, 0xa2, 0x34, 0x6a, 0xd7, 0x4d, 0x0a, 0xa2, 0x2a, 0x02, 0xc9, 0x4d, 0x4a, 0x6b, 0x11, 0x91,
0xb2, 0x6d, 0xa9, 0x84, 0x40, 0x68, 0xb2, 0x3e, 0xb1, 0xb7, 0xd9, 0xec, 0x4e, 0x67, 0xc6, 0x6e,
0x4d, 0xd5, 0x1b, 0x84, 0x10, 0x12, 0xdc, 0x20, 0x04, 0xe2, 0x0e, 0x2e, 0x90, 0x90, 0xe0, 0x12,
0x89, 0x67, 0xe0, 0x12, 0x89, 0x17, 0x40, 0x15, 0x0f, 0xc1, 0x25, 0x9a, 0x33, 0xeb, 0xf5, 0x3a,
0xf1, 0x9f, 0x44, 0x4d, 0x73, 0x37, 0x73, 0xce, 0xec, 0xf9, 0xbe, 0xf3, 0xcd, 0x99, 0x33, 0x63,
0x13, 0x47, 0x82, 0x68, 0x80, 0x28, 0x0a, 0xe0, 0xb1, 0x0c, 0x54, 0x2c, 0x9a, 0x99, 0xa1, 0xcb,
0x45, 0xac, 0x62, 0x4a, 0xda, 0x96, 0xc2, 0x4c, 0x35, 0x8e, 0xab, 0x21, 0x14, 0x19, 0x0f, 0x8a,
0x2c, 0x8a, 0x62, 0xc5, 0x54, 0x10, 0x47, 0xd2, 0xac, 0x2c, 0xac, 0x55, 0x03, 0x55, 0xab, 0x6f,
0xb8, 0x7e, 0xbc, 0x5d, 0x64, 0xa2, 0x1a, 0x73, 0x11, 0x3f, 0xc4, 0xc1, 0x65, 0xbf, 0x52, 0x6c,
0x5c, 0x2d, 0xf2, 0xad, 0xaa, 0xfe, 0x52, 0x16, 0x19, 0xe7, 0x61, 0xe0, 0xe3, 0xb7, 0xc5, 0xc6,
0x12, 0x0b, 0x79, 0x8d, 0x2d, 0x15, 0xab, 0x10, 0x81, 0x60, 0x0a, 0x2a, 0x49, 0xb4, 0x9b, 0x03,
0xa2, 0x21, 0xad, 0x81, 0xf4, 0x9d, 0x26, 0x99, 0xf0, 0x80, 0xc7, 0x25, 0xce, 0xe5, 0x7b, 0x75,
0x10, 0x4d, 0x4a, 0xc9, 0x11, 0xbd, 0xc8, 0xb6, 0xe6, 0xac, 0x85, 0x31, 0x0f, 0xc7, 0xb4, 0x40,
0x46, 0x05, 0x34, 0x02, 0x19, 0xc4, 0x91, 0x3d, 0x84, 0xf6, 0x74, 0x4e, 0x6d, 0x72, 0x94, 0x71,
0xfe, 0x2e, 0xdb, 0x06, 0x7b, 0x18, 0x5d, 0xad, 0x29, 0x9d, 0x25, 0x84, 0x71, 0x7e, 0x47, 0xc4,
0x0f, 0xc1, 0x57, 0xf6, 0x11, 0x74, 0x66, 0x2c, 0xce, 0x12, 0x39, 0x5a, 0xe2, 0xbc, 0x1c, 0x6d,
0xc6, 0x1a, 0x54, 0x35, 0x39, 0xb4, 0x40, 0xf5, 0x58, 0xdb, 0x38, 0x53, 0xb5, 0x04, 0x10, 0xc7,
0xce, 0xbf, 0x16, 0x99, 0x4c, 0xe8, 0xae, 0x82, 0x62, 0x41, 0x98, 0x90, 0xae, 0x92, 0x9c, 0x8c,
0xeb, 0xc2, 0x37, 0x11, 0xc6, 0x97, 0xd7, 0xdd, 0xb6, 0x3a, 0x6e, 0x4b, 0x1d, 0x1c, 0x7c, 0xec,
0x57, 0xdc, 0xc6, 0x55, 0x97, 0x6f, 0x55, 0x5d, 0xad, 0xb5, 0x9b, 0xd1, 0xda, 0x6d, 0x69, 0xed,
0x96, 0xda, 0xc6, 0xbb, 0x18, 0xd6, 0x4b, 0xc2, 0x67, 0xb3, 0x1d, 0xea, 0x97, 0xed, 0xf0, 0xce,
0x6c, 0xe9, 0x1c, 0x19, 0x37, 0x31, 0xca, 0x51, 0x05, 0x9e, 0xa0, 0x1c, 0x23, 0x5e, 0xd6, 0x44,
0x67, 0xc8, 0x58, 0x03, 0x84, 0x16, 0xb5, 0x5c, 0xb1, 0x47, 0xd0, 0xdf, 0x36, 0x38, 0x6f, 0x92,
0x7c, 0x6b, 0xa3, 0x3c, 0x90, 0x3c, 0x8e, 0x24, 0xd0, 0x8b, 0x64, 0x24, 0x50, 0xb0, 0x2d, 0x6d,
0x6b, 0x6e, 0x78, 0x61, 0x7c, 0x79, 0xd2, 0xcd, 0x6c, 0x6f, 0x22, 0xad, 0x67, 0x56, 0x38, 0x3e,
0x19, 0xd3, 0x9f, 0xf7, 0xde, 0x63, 0x87, 0x1c, 0xdb, 0x8c, 0x75, 0xaa, 0xb0, 0x29, 0x40, 0x1a,
0xd9, 0x47, 0xbd, 0x0e, 0xdb, 0xa0, 0x1c, 0x9d, 0xdf, 0x72, 0xe4, 0x04, 0x92, 0xf4, 0x7d, 0x90,
0xfd, 0xeb, 0xa9, 0x2e, 0x41, 0x44, 0x6d, 0x19, 0xd3, 0xb9, 0xf6, 0x71, 0x26, 0xe5, 0xe3, 0x58,
0x54, 0x12, 0x84, 0x74, 0x4e, 0x2f, 0x90, 0x09, 0x29, 0x6b, 0x77, 0x44, 0xd0, 0x60, 0x0a, 0xde,
0x81, 0x66, 0x52, 0x54, 0x9d, 0x46, 0x1d, 0x21, 0x88, 0x24, 0xf8, 0x75, 0x01, 0x28, 0xe3, 0xa8,
0x97, 0xce, 0xe9, 0x25, 0x72, 0x52, 0x85, 0x72, 0x25, 0x0c, 0x20, 0x52, 0x2b, 0x20, 0xd4, 0x2a,
0x53, 0xcc, 0xce, 0x61, 0x94, 0xdd, 0x0e, 0xba, 0x48, 0xf2, 0x1d, 0x46, 0x0d, 0x79, 0x14, 0x17,
0xef, 0xb2, 0xa7, 0x25, 0x3c, 0xd6, 0x59, 0xc2, 0x98, 0x23, 0x31, 0x36, 0xcc, 0x6f, 0x86, 0x8c,
0x41, 0xc4, 0x36, 0x42, 0x58, 0xf7, 0x03, 0x7b, 0x1c, 0xe9, 0xb5, 0x0d, 0xf4, 0x0a, 0x99, 0x34,
0x95, 0x5b, 0xd2, 0xaa, 0xa6, 0x79, 0x1e, 0xc3, 0x00, 0xdd, 0x5c, 0xba, 0xae, 0x52, 0x73, 0x79,
0xd5, 0x9e, 0x98, 0xb3, 0x16, 0x86, 0xbd, 0xac, 0x89, 0x5e, 0x23, 0xa7, 0xdb, 0xd3, 0x48, 0x2a,
0x16, 0x86, 0x58, 0xda, 0xe5, 0x55, 0xfb, 0x38, 0xae, 0xee, 0xe5, 0xa6, 0x6f, 0x91, 0x42, 0xea,
0xba, 0x19, 0x29, 0x10, 0x5c, 0x04, 0x12, 0x6e, 0x30, 0x09, 0xf7, 0x45, 0x68, 0x9f, 0x40, 0x52,
0x7d, 0x56, 0xd0, 0x29, 0x32, 0xc2, 0x45, 0xfc, 0xa4, 0x69, 0xe7, 0x71, 0xa9, 0x99, 0xe8, 0x33,
0xc4, 0x93, 0x12, 0x3a, 0x69, 0xce, 0x50, 0x32, 0xa5, 0xcb, 0x64, 0xaa, 0xea, 0xf3, 0xbb, 0x20,
0x1a, 0x81, 0x0f, 0x25, 0xdf, 0x8f, 0xeb, 0x11, 0x6a, 0x4e, 0x71, 0x59, 0x57, 0x1f, 0x75, 0x09,
0xc5, 0x1a, 0xbd, 0xad, 0x14, 0xbf, 0xc1, 0x64, 0xe0, 0x97, 0xea, 0xaa, 0x66, 0x4f, 0xa2, 0xb0,
0x5d, 0x3c, 0xf4, 0x3a, 0xb1, 0xeb, 0x12, 0x4a, 0x9f, 0xd4, 0x05, 0x3c, 0x88, 0xc5, 0x56, 0x18,
0xb3, 0x4a, 0xb9, 0x02, 0x91, 0x0a, 0x54, 0xd3, 0x9e, 0xc2, 0xaf, 0x7a, 0xfa, 0xb5, 0xd6, 0x1b,
0xc0, 0x04, 0x88, 0x7b, 0xf1, 0x16, 0x44, 0xf6, 0x34, 0xd2, 0xca, 0x9a, 0x74, 0x06, 0xad, 0x5a,
0x5b, 0xf7, 0x83, 0xb7, 0x5b, 0xf0, 0xf6, 0x29, 0x8c, 0xdc, 0xd5, 0xe7, 0x1c, 0x27, 0xc7, 0xf4,
0xa1, 0x69, 0x9d, 0x6a, 0xe7, 0x67, 0x8b, 0x9c, 0xd4, 0x86, 0x15, 0x01, 0x4c, 0x81, 0x07, 0x8f,
0xea, 0x20, 0x15, 0xfd, 0x30, 0x73, 0x8e, 0xc6, 0x97, 0x6f, 0xbf, 0x58, 0x83, 0xf3, 0xd2, 0x3e,
0x91, 0x9c, 0xc8, 0x53, 0x24, 0x57, 0xe7, 0x12, 0x84, 0x4a, 0xce, 0x7d, 0x32, 0xd3, 0xd5, 0xea,
0x0b, 0xa8, 0xc8, 0xf5, 0x28, 0x6c, 0xe2, 0x71, 0x1c, 0xf5, 0xda, 0x06, 0xe7, 0x91, 0x21, 0x7a,
0x9f, 0x57, 0x0e, 0x8b, 0xe8, 0xf2, 0x67, 0xa7, 0x0d, 0xa6, 0x31, 0x26, 0xe5, 0x40, 0xbf, 0xb2,
0xc8, 0x91, 0xb5, 0x40, 0x2a, 0x3a, 0x9d, 0x6d, 0x81, 0x69, 0xc3, 0x2b, 0xac, 0x1d, 0x14, 0x0b,
0x0d, 0xe2, 0x9c, 0xfb, 0xf4, 0xaf, 0x7f, 0xbe, 0x19, 0x3a, 0x45, 0xa7, 0xf0, 0xa2, 0x6f, 0x2c,
0xb5, 0x6f, 0xd5, 0x00, 0xe4, 0x17, 0x43, 0x16, 0xfd, 0xd2, 0x22, 0xc3, 0xb7, 0xa0, 0x27, 0x9b,
0x03, 0xd3, 0xc4, 0x39, 0x8f, 0x4c, 0xce, 0xd2, 0x33, 0xdd, 0x98, 0x14, 0x9f, 0xea, 0xd9, 0x33,
0xfa, 0x9d, 0x45, 0x46, 0x6f, 0x81, 0x7a, 0x20, 0x02, 0x05, 0x2f, 0x9f, 0xd2, 0x45, 0xa4, 0x74,
0x9e, 0xfe, 0xbf, 0x45, 0xe9, 0xb1, 0xc6, 0xbd, 0xdc, 0x8d, 0xd8, 0xb7, 0x16, 0xc9, 0x6b, 0x41,
0xbd, 0x8c, 0xef, 0x70, 0x76, 0x70, 0xa6, 0xdf, 0x0e, 0xd2, 0x1f, 0x2d, 0x32, 0xad, 0x97, 0xa1,
0x62, 0x87, 0x4f, 0xce, 0x41, 0x72, 0x33, 0xb4, 0xd0, 0x5b, 0x41, 0xfa, 0x11, 0x19, 0x35, 0xca,
0x6d, 0xf6, 0x24, 0x95, 0xef, 0x34, 0x6f, 0x4a, 0x67, 0x01, 0x03, 0x3b, 0x74, 0xae, 0x4f, 0xb5,
0x14, 0x85, 0x0e, 0x59, 0x21, 0xe3, 0x3a, 0xfc, 0xfa, 0x4a, 0xf9, 0x1e, 0xab, 0xee, 0x03, 0xe1,
0x12, 0x22, 0xcc, 0xd3, 0x0b, 0xfd, 0x10, 0x62, 0x3f, 0xb8, 0xac, 0x74, 0xd8, 0x6d, 0x93, 0x84,
0x7e, 0xd2, 0xd0, 0xff, 0xed, 0x84, 0x48, 0x5f, 0xa4, 0x85, 0x99, 0x6e, 0xae, 0xb4, 0x5b, 0xee,
0x29, 0x29, 0xa6, 0x21, 0xbe, 0xb6, 0xc8, 0xc4, 0x2d, 0x50, 0xed, 0xb7, 0x23, 0x3d, 0xd7, 0x25,
0x72, 0xf6, 0x5d, 0x59, 0x70, 0x7a, 0x2f, 0x48, 0x09, 0xbc, 0x81, 0x04, 0x5e, 0x73, 0xae, 0x74,
0x27, 0x60, 0x5e, 0x78, 0x18, 0xe7, 0xbe, 0xb7, 0x86, 0x54, 0x2a, 0x26, 0xc2, 0x75, 0x6b, 0x91,
0x36, 0x90, 0xd2, 0x6d, 0x08, 0xb7, 0x57, 0x6a, 0x4c, 0xa8, 0x9e, 0x52, 0xcf, 0x66, 0xcd, 0xed,
0xe5, 0x29, 0x09, 0x17, 0x49, 0x2c, 0xd0, 0xf9, 0x7e, 0x2a, 0xd4, 0x20, 0xdc, 0xf6, 0x0d, 0xcc,
0xf7, 0x16, 0xc9, 0x99, 0xfb, 0x85, 0x9e, 0xdd, 0x89, 0xd8, 0x71, 0xef, 0x1c, 0x60, 0x67, 0x78,
0xc5, 0xd4, 0xb5, 0xd3, 0xf5, 0xd0, 0x5d, 0xc7, 0xf6, 0xae, 0x9b, 0xe7, 0x0f, 0x16, 0xc9, 0xb7,
0x28, 0xb4, 0xbe, 0x3d, 0x3c, 0x92, 0xce, 0x60, 0x92, 0xf4, 0x17, 0x8b, 0x4c, 0x1b, 0xfc, 0xce,
0x0e, 0x71, 0x88, 0x34, 0x93, 0xaa, 0x77, 0xfa, 0xf4, 0x88, 0x84, 0xec, 0x4f, 0x16, 0xc9, 0x99,
0x0b, 0x7a, 0x37, 0xbb, 0x8e, 0x8b, 0xfb, 0x00, 0xd9, 0x2d, 0x99, 0x6a, 0x2c, 0xf4, 0x39, 0x93,
0x48, 0xe5, 0x59, 0x7b, 0xd7, 0x7f, 0xb5, 0x48, 0xbe, 0x45, 0xa7, 0xb7, 0x9c, 0x2f, 0x8b, 0xb0,
0xbb, 0x3f, 0xc2, 0xf4, 0x77, 0x8b, 0x4c, 0x1b, 0x2e, 0x03, 0x2b, 0xe0, 0x65, 0x51, 0x7e, 0x15,
0x29, 0xbb, 0x85, 0xf9, 0x41, 0xf7, 0x6c, 0x07, 0x71, 0x46, 0x72, 0xab, 0x10, 0x42, 0xef, 0x87,
0x80, 0xbd, 0xd3, 0x9c, 0xb6, 0x98, 0x79, 0xf3, 0xd6, 0x58, 0xec, 0xf7, 0xd6, 0xd0, 0x3b, 0x59,
0x23, 0x79, 0x03, 0x91, 0x51, 0x65, 0xdf, 0x60, 0xe7, 0xf7, 0x00, 0x46, 0x25, 0x99, 0x36, 0x48,
0x3b, 0x37, 0x61, 0xdf, 0x70, 0xc9, 0xa3, 0x65, 0x71, 0x0f, 0x8f, 0x96, 0xa7, 0xe4, 0xf8, 0xfb,
0x2c, 0x0c, 0xf4, 0xa6, 0x9a, 0x9f, 0xb9, 0xf4, 0xcc, 0xae, 0x4b, 0xa2, 0xfd, 0xf3, 0xb7, 0x0f,
0xe6, 0x32, 0x62, 0x5e, 0x72, 0xfa, 0xde, 0x95, 0x8d, 0x04, 0x2a, 0xd9, 0xbe, 0xcf, 0x2d, 0x32,
0xd9, 0x42, 0xc7, 0xa4, 0x5f, 0x8c, 0xc2, 0x35, 0xa4, 0xb0, 0xec, 0x2c, 0x0e, 0x4c, 0x7b, 0x07,
0x91, 0x1b, 0x37, 0xff, 0x78, 0x3e, 0x6b, 0xfd, 0xf9, 0x7c, 0xd6, 0xfa, 0xfb, 0xf9, 0xac, 0xf5,
0xc1, 0xeb, 0x7b, 0xfb, 0x67, 0xcb, 0xc7, 0x1f, 0xcc, 0x99, 0xff, 0xa0, 0x36, 0x72, 0xf8, 0x27,
0xd4, 0xd5, 0xff, 0x02, 0x00, 0x00, 0xff, 0xff, 0xbc, 0x0b, 0xd2, 0x3f, 0x69, 0x13, 0x00, 0x00,
// 1471 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xbc, 0x58, 0x5f, 0x6f, 0x13, 0xc7,
0x16, 0xd7, 0x26, 0x24, 0x24, 0x13, 0x02, 0x66, 0x92, 0xc0, 0x62, 0x42, 0x08, 0x0b, 0x37, 0x0a,
0x11, 0xac, 0x49, 0xb8, 0x57, 0x17, 0x71, 0x75, 0x2b, 0x39, 0x7f, 0x0a, 0x56, 0xa3, 0x86, 0x1a,
0x28, 0x52, 0xd5, 0xaa, 0x9a, 0xac, 0x4f, 0xec, 0x25, 0x9b, 0xdd, 0x61, 0x66, 0x6c, 0x70, 0x11,
0x2f, 0x55, 0x55, 0x55, 0x6a, 0x5f, 0xaa, 0xaa, 0x55, 0xdf, 0xda, 0x87, 0x4a, 0x95, 0xda, 0xf7,
0x7e, 0x86, 0x3e, 0xb6, 0xea, 0x17, 0xa8, 0x50, 0x3f, 0x44, 0x1f, 0xab, 0x39, 0xb3, 0x5e, 0xaf,
0x13, 0x7b, 0x9d, 0x88, 0x90, 0xb7, 0x9d, 0x73, 0x8e, 0xcf, 0xef, 0x77, 0x7e, 0x73, 0xf6, 0xcc,
0x78, 0x89, 0x23, 0x41, 0x34, 0x40, 0x14, 0x04, 0xf0, 0x48, 0xfa, 0x2a, 0x12, 0xcd, 0xd4, 0xa3,
0xcb, 0x45, 0xa4, 0x22, 0x4a, 0xda, 0x96, 0xfc, 0x74, 0x35, 0x8a, 0xaa, 0x01, 0x14, 0x18, 0xf7,
0x0b, 0x2c, 0x0c, 0x23, 0xc5, 0x94, 0x1f, 0x85, 0xd2, 0x44, 0xe6, 0xd7, 0xab, 0xbe, 0xaa, 0xd5,
0x37, 0x5d, 0x2f, 0xda, 0x29, 0x30, 0x51, 0x8d, 0xb8, 0x88, 0x1e, 0xe3, 0xc3, 0x75, 0xaf, 0x52,
0x68, 0xdc, 0x2c, 0xf0, 0xed, 0xaa, 0xfe, 0xa5, 0x2c, 0x30, 0xce, 0x03, 0xdf, 0xc3, 0xdf, 0x16,
0x1a, 0x8b, 0x2c, 0xe0, 0x35, 0xb6, 0x58, 0xa8, 0x42, 0x08, 0x82, 0x29, 0xa8, 0xc4, 0xd9, 0xd6,
0xfa, 0x64, 0x43, 0x5a, 0x7d, 0xe9, 0x3b, 0x4d, 0x32, 0x5e, 0x06, 0x1e, 0x15, 0x39, 0x97, 0xef,
0xd4, 0x41, 0x34, 0x29, 0x25, 0xc7, 0x74, 0x90, 0x6d, 0xcd, 0x5a, 0xf3, 0xa3, 0x65, 0x7c, 0xa6,
0x79, 0x32, 0x22, 0xa0, 0xe1, 0x4b, 0x3f, 0x0a, 0xed, 0x01, 0xb4, 0x27, 0x6b, 0x6a, 0x93, 0xe3,
0x8c, 0xf3, 0xb7, 0xd9, 0x0e, 0xd8, 0x83, 0xe8, 0x6a, 0x2d, 0xe9, 0x0c, 0x21, 0x8c, 0xf3, 0x7b,
0x22, 0x7a, 0x0c, 0x9e, 0xb2, 0x8f, 0xa1, 0x33, 0x65, 0x71, 0x16, 0xc9, 0xf1, 0x22, 0xe7, 0xa5,
0x70, 0x2b, 0xd2, 0xa0, 0xaa, 0xc9, 0xa1, 0x05, 0xaa, 0x9f, 0xb5, 0x8d, 0x33, 0x55, 0x8b, 0x01,
0xf1, 0xd9, 0xf9, 0xdb, 0x22, 0x13, 0x31, 0xdd, 0x55, 0x50, 0xcc, 0x0f, 0x62, 0xd2, 0x55, 0x32,
0x2c, 0xa3, 0xba, 0xf0, 0x4c, 0x86, 0xb1, 0xa5, 0x0d, 0xb7, 0xad, 0x8e, 0xdb, 0x52, 0x07, 0x1f,
0x3e, 0xf4, 0x2a, 0x6e, 0xe3, 0xa6, 0xcb, 0xb7, 0xab, 0xae, 0xd6, 0xda, 0x4d, 0x69, 0xed, 0xb6,
0xb4, 0x76, 0x8b, 0x6d, 0xe3, 0x7d, 0x4c, 0x5b, 0x8e, 0xd3, 0xa7, 0xab, 0x1d, 0xc8, 0xaa, 0x76,
0x70, 0x77, 0xb5, 0x74, 0x96, 0x8c, 0x99, 0x1c, 0xa5, 0xb0, 0x02, 0xcf, 0x50, 0x8e, 0xa1, 0x72,
0xda, 0x44, 0xa7, 0xc9, 0x68, 0x03, 0x84, 0x16, 0xb5, 0x54, 0xb1, 0x87, 0xd0, 0xdf, 0x36, 0x38,
0xff, 0x27, 0xb9, 0xd6, 0x46, 0x95, 0x41, 0xf2, 0x28, 0x94, 0x40, 0xaf, 0x92, 0x21, 0x5f, 0xc1,
0x8e, 0xb4, 0xad, 0xd9, 0xc1, 0xf9, 0xb1, 0xa5, 0x09, 0x37, 0xb5, 0xbd, 0xb1, 0xb4, 0x65, 0x13,
0xe1, 0x78, 0x64, 0x54, 0xff, 0xbc, 0xf7, 0x1e, 0x3b, 0xe4, 0xc4, 0x56, 0xa4, 0x4b, 0x85, 0x2d,
0x01, 0xd2, 0xc8, 0x3e, 0x52, 0xee, 0xb0, 0xf5, 0xab, 0xd1, 0xf9, 0x7d, 0x84, 0x9c, 0x42, 0x92,
0x9e, 0x07, 0x32, 0xbb, 0x9f, 0xea, 0x12, 0x44, 0xd8, 0x96, 0x31, 0x59, 0x6b, 0x1f, 0x67, 0x52,
0x3e, 0x8d, 0x44, 0x25, 0x46, 0x48, 0xd6, 0xf4, 0x0a, 0x19, 0x97, 0xb2, 0x76, 0x4f, 0xf8, 0x0d,
0xa6, 0xe0, 0x2d, 0x68, 0xc6, 0x4d, 0xd5, 0x69, 0xd4, 0x19, 0xfc, 0x50, 0x82, 0x57, 0x17, 0x80,
0x32, 0x8e, 0x94, 0x93, 0x35, 0xbd, 0x46, 0x4e, 0xab, 0x40, 0xae, 0x04, 0x3e, 0x84, 0x6a, 0x05,
0x84, 0x5a, 0x65, 0x8a, 0xd9, 0xc3, 0x98, 0x65, 0xaf, 0x83, 0x2e, 0x90, 0x5c, 0x87, 0x51, 0x43,
0x1e, 0xc7, 0xe0, 0x3d, 0xf6, 0xa4, 0x85, 0x47, 0x3b, 0x5b, 0x18, 0x6b, 0x24, 0xc6, 0x86, 0xf5,
0x4d, 0x93, 0x51, 0x08, 0xd9, 0x66, 0x00, 0x1b, 0x9e, 0x6f, 0x8f, 0x21, 0xbd, 0xb6, 0x81, 0xde,
0x20, 0x13, 0xa6, 0x73, 0x8b, 0x5a, 0xd5, 0xa4, 0xce, 0x13, 0x98, 0xa0, 0x9b, 0x4b, 0xf7, 0x55,
0x62, 0x2e, 0xad, 0xda, 0xe3, 0xb3, 0xd6, 0xfc, 0x60, 0x39, 0x6d, 0xa2, 0xb7, 0xc8, 0xd9, 0xf6,
0x32, 0x94, 0x8a, 0x05, 0x01, 0xb6, 0x76, 0x69, 0xd5, 0x3e, 0x89, 0xd1, 0xbd, 0xdc, 0xf4, 0x0d,
0x92, 0x4f, 0x5c, 0x6b, 0xa1, 0x02, 0xc1, 0x85, 0x2f, 0x61, 0x99, 0x49, 0x78, 0x28, 0x02, 0xfb,
0x14, 0x92, 0xca, 0x88, 0xa0, 0x93, 0x64, 0x88, 0x8b, 0xe8, 0x59, 0xd3, 0xce, 0x61, 0xa8, 0x59,
0xe8, 0x77, 0x88, 0xc7, 0x2d, 0x74, 0xda, 0xbc, 0x43, 0xf1, 0x92, 0x2e, 0x91, 0xc9, 0xaa, 0xc7,
0xef, 0x83, 0x68, 0xf8, 0x1e, 0x14, 0x3d, 0x2f, 0xaa, 0x87, 0xa8, 0x39, 0xc5, 0xb0, 0xae, 0x3e,
0xea, 0x12, 0x8a, 0x3d, 0x7a, 0x57, 0x29, 0xbe, 0xcc, 0xa4, 0xef, 0x15, 0xeb, 0xaa, 0x66, 0x4f,
0xa0, 0xb0, 0x5d, 0x3c, 0xf4, 0x36, 0xb1, 0xeb, 0x12, 0x8a, 0x1f, 0xd5, 0x05, 0x3c, 0x8a, 0xc4,
0x76, 0x10, 0xb1, 0x4a, 0xa9, 0x02, 0xa1, 0xf2, 0x55, 0xd3, 0x9e, 0xc4, 0x5f, 0xf5, 0xf4, 0x6b,
0xad, 0x37, 0x81, 0x09, 0x10, 0x0f, 0xa2, 0x6d, 0x08, 0xed, 0x29, 0xa4, 0x95, 0x36, 0xe9, 0x0a,
0x5a, 0xbd, 0xb6, 0xe1, 0xf9, 0x6f, 0xb6, 0xe0, 0xed, 0x33, 0x98, 0xb9, 0xab, 0x8f, 0xae, 0x92,
0x0b, 0x4c, 0xc3, 0xc5, 0xb5, 0xdd, 0x13, 0x7e, 0xe8, 0xf9, 0x9c, 0x05, 0xa6, 0xbf, 0x4a, 0x15,
0xfb, 0x2c, 0xe2, 0x64, 0x07, 0xd1, 0x75, 0x72, 0x29, 0x23, 0xe0, 0x3e, 0x78, 0x02, 0x94, 0x6d,
0x63, 0xa6, 0xfe, 0x81, 0x3d, 0x39, 0x3d, 0x80, 0x90, 0x21, 0xa7, 0x73, 0x19, 0x9c, 0x5a, 0x41,
0x74, 0x99, 0x4c, 0x63, 0x40, 0xd1, 0x53, 0x7e, 0x03, 0x56, 0x7d, 0x01, 0x9e, 0x9e, 0x4d, 0x6b,
0x61, 0x85, 0x47, 0x7e, 0xa8, 0xec, 0x3c, 0x26, 0xc9, 0x8c, 0x71, 0x4e, 0x92, 0x13, 0x7a, 0xa4,
0xb4, 0x66, 0x9e, 0xf3, 0xa3, 0x45, 0x4e, 0x6b, 0xc3, 0x8a, 0x00, 0xa6, 0xa0, 0x0c, 0x4f, 0xea,
0x20, 0x15, 0x7d, 0x3f, 0x35, 0x65, 0xc6, 0x96, 0xee, 0xbe, 0xda, 0xf8, 0x2f, 0x27, 0x53, 0x34,
0x9e, 0x57, 0x67, 0xc8, 0x70, 0x9d, 0x4b, 0x10, 0x2a, 0x9e, 0x8a, 0xf1, 0x4a, 0xbf, 0xcb, 0x9e,
0x80, 0x8a, 0xdc, 0x08, 0x83, 0x26, 0x0e, 0xab, 0x91, 0x72, 0xdb, 0xe0, 0x3c, 0x31, 0x44, 0x1f,
0xf2, 0xca, 0x51, 0x11, 0x5d, 0xfa, 0xe4, 0xac, 0xc1, 0x34, 0xc6, 0x78, 0x5f, 0xe8, 0x17, 0x16,
0x39, 0xb6, 0xee, 0x4b, 0x45, 0xa7, 0xd2, 0x07, 0x44, 0x72, 0x1c, 0xe4, 0xd7, 0x0f, 0x8b, 0x85,
0x06, 0x71, 0x2e, 0x7e, 0xfc, 0xc7, 0x5f, 0x5f, 0x0d, 0x9c, 0xa1, 0x93, 0x78, 0x0d, 0x6a, 0x2c,
0xb6, 0xef, 0x1c, 0x3e, 0xc8, 0xcf, 0x06, 0x2c, 0xfa, 0xb9, 0x45, 0x06, 0xef, 0x40, 0x4f, 0x36,
0x87, 0xa6, 0x89, 0x73, 0x19, 0x99, 0x5c, 0xa0, 0xe7, 0xbb, 0x31, 0x29, 0x3c, 0xd7, 0xab, 0x17,
0xf4, 0x1b, 0x8b, 0x8c, 0xdc, 0x01, 0xf5, 0x48, 0xf8, 0x0a, 0x5e, 0x3f, 0xa5, 0xab, 0x48, 0xe9,
0x32, 0xbd, 0xd4, 0xa2, 0xf4, 0x54, 0xe3, 0x5e, 0xef, 0x46, 0xec, 0x6b, 0x8b, 0xe4, 0xb4, 0xa0,
0xe5, 0x94, 0xef, 0x68, 0x76, 0x70, 0x3a, 0x6b, 0x07, 0xe9, 0xf7, 0x16, 0x99, 0xd2, 0x61, 0xa8,
0xd8, 0xd1, 0x93, 0x73, 0x90, 0xdc, 0x34, 0xcd, 0xf7, 0x56, 0x90, 0x7e, 0x40, 0x46, 0x8c, 0x72,
0x5b, 0x3d, 0x49, 0xe5, 0x3a, 0xcd, 0x5b, 0xd2, 0x99, 0xc7, 0xc4, 0x0e, 0x9d, 0xcd, 0xe8, 0x96,
0x82, 0xd0, 0x29, 0x2b, 0x64, 0x4c, 0xa7, 0xdf, 0x58, 0x29, 0x3d, 0x60, 0xd5, 0x03, 0x20, 0x5c,
0x43, 0x84, 0x39, 0x7a, 0x25, 0x0b, 0x21, 0xf2, 0xfc, 0xeb, 0x4a, 0xa7, 0xdd, 0x31, 0x45, 0xe8,
0x0b, 0x1f, 0x3d, 0xb7, 0x1b, 0x22, 0xb9, 0xaf, 0xe7, 0xa7, 0xbb, 0xb9, 0x92, 0x69, 0xb9, 0xaf,
0xa2, 0x98, 0x86, 0xf8, 0xd2, 0x22, 0xe3, 0x77, 0x40, 0xb5, 0x6f, 0xd6, 0xf4, 0x62, 0x97, 0xcc,
0xe9, 0x5b, 0x77, 0xde, 0xe9, 0x1d, 0x90, 0x10, 0xf8, 0x1f, 0x12, 0xf8, 0x8f, 0x73, 0xa3, 0x3b,
0x01, 0x73, 0xff, 0xc5, 0x3c, 0x0f, 0xcb, 0xeb, 0x48, 0xa5, 0x62, 0x32, 0xdc, 0xb6, 0x16, 0x68,
0x03, 0x29, 0xdd, 0x85, 0x60, 0x67, 0xa5, 0xc6, 0x84, 0xea, 0x29, 0xf5, 0x4c, 0xda, 0xdc, 0x0e,
0x4f, 0x48, 0xb8, 0x48, 0x62, 0x9e, 0xce, 0x65, 0xa9, 0x50, 0x83, 0x60, 0xc7, 0x33, 0x30, 0xdf,
0x5a, 0x64, 0xd8, 0x9c, 0x2f, 0xf4, 0xc2, 0x6e, 0xc4, 0x8e, 0x73, 0xe7, 0x10, 0x27, 0xc3, 0xbf,
0x4c, 0x5f, 0x3b, 0x5d, 0x5f, 0xba, 0xdb, 0x38, 0xde, 0xf5, 0xf0, 0xfc, 0xce, 0x22, 0xb9, 0x16,
0x85, 0xd6, 0x6f, 0x8f, 0x8e, 0xa4, 0xd3, 0x9f, 0x24, 0xfd, 0xc9, 0x22, 0x53, 0x06, 0xbf, 0x73,
0x42, 0x1c, 0x21, 0xcd, 0xb8, 0xeb, 0x9d, 0x8c, 0x19, 0x11, 0x93, 0xfd, 0xc1, 0x22, 0xc3, 0xe6,
0x80, 0xde, 0xcb, 0xae, 0xe3, 0xe0, 0x3e, 0x44, 0x76, 0x8b, 0xa6, 0x1b, 0xf3, 0x19, 0xef, 0x24,
0x52, 0x79, 0xd1, 0xde, 0xf5, 0x9f, 0x2d, 0x92, 0x6b, 0xd1, 0xe9, 0x2d, 0xe7, 0xeb, 0x22, 0xec,
0x1e, 0x8c, 0x30, 0xfd, 0xc5, 0x22, 0x53, 0x86, 0x4b, 0xdf, 0x0e, 0x78, 0x5d, 0x94, 0xff, 0x8d,
0x94, 0xdd, 0xfc, 0x5c, 0xbf, 0x73, 0xb6, 0x83, 0x38, 0x23, 0xc3, 0xab, 0x10, 0x40, 0xef, 0x8b,
0x80, 0xbd, 0xdb, 0x9c, 0x8c, 0x98, 0x39, 0x73, 0xd7, 0x58, 0xc8, 0xba, 0x6b, 0xe8, 0x9d, 0xac,
0x91, 0x9c, 0x81, 0x48, 0xa9, 0x72, 0x60, 0xb0, 0xcb, 0xfb, 0x00, 0xa3, 0x92, 0x4c, 0x19, 0xa4,
0xdd, 0x9b, 0x70, 0x60, 0xb8, 0xf8, 0xd2, 0xb2, 0xb0, 0x8f, 0x4b, 0xcb, 0x73, 0x72, 0xf2, 0x5d,
0x16, 0xf8, 0x7a, 0x53, 0xcd, 0x47, 0x00, 0x7a, 0x7e, 0xcf, 0x21, 0xd1, 0xfe, 0x38, 0x90, 0x81,
0xb9, 0x84, 0x98, 0xd7, 0x9c, 0xcc, 0xb3, 0xb2, 0x11, 0x43, 0xc5, 0xdb, 0xf7, 0xa9, 0x45, 0x26,
0x5a, 0xe8, 0x58, 0xf4, 0xab, 0x51, 0xb8, 0x85, 0x14, 0x96, 0x9c, 0x85, 0xbe, 0x65, 0xef, 0x22,
0xb2, 0xbc, 0xf6, 0xeb, 0xcb, 0x19, 0xeb, 0xb7, 0x97, 0x33, 0xd6, 0x9f, 0x2f, 0x67, 0xac, 0xf7,
0xfe, 0xbb, 0xbf, 0xef, 0x7e, 0x1e, 0xfe, 0xff, 0x4a, 0x7d, 0xa1, 0xdb, 0x1c, 0xc6, 0x4f, 0x74,
0x37, 0xff, 0x09, 0x00, 0x00, 0xff, 0xff, 0xff, 0x54, 0xba, 0x9b, 0x87, 0x14, 0x00, 0x00,
}
// Reference imports to suppress errors if they are not otherwise used.
@ -1964,6 +2005,42 @@ func (m *RepoAccessQuery) MarshalToSizedBuffer(dAtA []byte) (int, error) {
i -= len(m.XXX_unrecognized)
copy(dAtA[i:], m.XXX_unrecognized)
}
if len(m.AzureActiveDirectoryEndpoint) > 0 {
i -= len(m.AzureActiveDirectoryEndpoint)
copy(dAtA[i:], m.AzureActiveDirectoryEndpoint)
i = encodeVarintRepository(dAtA, i, uint64(len(m.AzureActiveDirectoryEndpoint)))
i--
dAtA[i] = 0x1
i--
dAtA[i] = 0xd2
}
if len(m.AzureServicePrincipalTenantId) > 0 {
i -= len(m.AzureServicePrincipalTenantId)
copy(dAtA[i:], m.AzureServicePrincipalTenantId)
i = encodeVarintRepository(dAtA, i, uint64(len(m.AzureServicePrincipalTenantId)))
i--
dAtA[i] = 0x1
i--
dAtA[i] = 0xca
}
if len(m.AzureServicePrincipalClientSecret) > 0 {
i -= len(m.AzureServicePrincipalClientSecret)
copy(dAtA[i:], m.AzureServicePrincipalClientSecret)
i = encodeVarintRepository(dAtA, i, uint64(len(m.AzureServicePrincipalClientSecret)))
i--
dAtA[i] = 0x1
i--
dAtA[i] = 0xc2
}
if len(m.AzureServicePrincipalClientId) > 0 {
i -= len(m.AzureServicePrincipalClientId)
copy(dAtA[i:], m.AzureServicePrincipalClientId)
i = encodeVarintRepository(dAtA, i, uint64(len(m.AzureServicePrincipalClientId)))
i--
dAtA[i] = 0x1
i--
dAtA[i] = 0xba
}
if m.InsecureOciForceHttp {
i--
if m.InsecureOciForceHttp {
@ -2477,6 +2554,22 @@ func (m *RepoAccessQuery) Size() (n int) {
if m.InsecureOciForceHttp {
n += 3
}
l = len(m.AzureServicePrincipalClientId)
if l > 0 {
n += 2 + l + sovRepository(uint64(l))
}
l = len(m.AzureServicePrincipalClientSecret)
if l > 0 {
n += 2 + l + sovRepository(uint64(l))
}
l = len(m.AzureServicePrincipalTenantId)
if l > 0 {
n += 2 + l + sovRepository(uint64(l))
}
l = len(m.AzureActiveDirectoryEndpoint)
if l > 0 {
n += 2 + l + sovRepository(uint64(l))
}
if m.XXX_unrecognized != nil {
n += len(m.XXX_unrecognized)
}
@ -3857,6 +3950,134 @@ func (m *RepoAccessQuery) Unmarshal(dAtA []byte) error {
}
}
m.InsecureOciForceHttp = bool(v != 0)
case 23:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field AzureServicePrincipalClientId", wireType)
}
var stringLen uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowRepository
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
stringLen |= uint64(b&0x7F) << shift
if b < 0x80 {
break
}
}
intStringLen := int(stringLen)
if intStringLen < 0 {
return ErrInvalidLengthRepository
}
postIndex := iNdEx + intStringLen
if postIndex < 0 {
return ErrInvalidLengthRepository
}
if postIndex > l {
return io.ErrUnexpectedEOF
}
m.AzureServicePrincipalClientId = string(dAtA[iNdEx:postIndex])
iNdEx = postIndex
case 24:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field AzureServicePrincipalClientSecret", wireType)
}
var stringLen uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowRepository
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
stringLen |= uint64(b&0x7F) << shift
if b < 0x80 {
break
}
}
intStringLen := int(stringLen)
if intStringLen < 0 {
return ErrInvalidLengthRepository
}
postIndex := iNdEx + intStringLen
if postIndex < 0 {
return ErrInvalidLengthRepository
}
if postIndex > l {
return io.ErrUnexpectedEOF
}
m.AzureServicePrincipalClientSecret = string(dAtA[iNdEx:postIndex])
iNdEx = postIndex
case 25:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field AzureServicePrincipalTenantId", wireType)
}
var stringLen uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowRepository
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
stringLen |= uint64(b&0x7F) << shift
if b < 0x80 {
break
}
}
intStringLen := int(stringLen)
if intStringLen < 0 {
return ErrInvalidLengthRepository
}
postIndex := iNdEx + intStringLen
if postIndex < 0 {
return ErrInvalidLengthRepository
}
if postIndex > l {
return io.ErrUnexpectedEOF
}
m.AzureServicePrincipalTenantId = string(dAtA[iNdEx:postIndex])
iNdEx = postIndex
case 26:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field AzureActiveDirectoryEndpoint", wireType)
}
var stringLen uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowRepository
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
stringLen |= uint64(b&0x7F) << shift
if b < 0x80 {
break
}
}
intStringLen := int(stringLen)
if intStringLen < 0 {
return ErrInvalidLengthRepository
}
postIndex := iNdEx + intStringLen
if postIndex < 0 {
return ErrInvalidLengthRepository
}
if postIndex > l {
return io.ErrUnexpectedEOF
}
m.AzureActiveDirectoryEndpoint = string(dAtA[iNdEx:postIndex])
iNdEx = postIndex
default:
iNdEx = preIndex
skippy, err := skipRepository(dAtA[iNdEx:])

File diff suppressed because it is too large Load diff

View file

@ -1876,6 +1876,18 @@ message RepoCreds {
// InsecureOCIForceHttp specifies whether the connection to the repository uses TLS at _all_. If true, no TLS. This flag is applicable for OCI repos only.
optional bool insecureOCIForceHttp = 26;
// AzureServicePrincipalClientId specifies the client ID of the Azure Service Principal used to access the repo
optional string azureServicePrincipalClientId = 29;
// AzureServicePrincipalClientSecret specifies the client secret of the Azure Service Principal used to access the repo
optional string azureServicePrincipalClientSecret = 30;
// AzureServicePrincipalTenantId specifies the tenant ID of the Azure Service Principal used to access the repo
optional string azureServicePrincipalTenantId = 31;
// AzureActiveDirectoryEndpoint specifies the Azure Active Directory endpoint used for Service Principal authentication. If empty will default to https://login.microsoftonline.com
optional string azureActiveDirectoryEndpoint = 32;
}
// RepositoryList is a collection of Repositories.
@ -1973,6 +1985,18 @@ message Repository {
// When set, webhook handlers will only trigger reconciliation for affected applications and skip Redis cache
// operations for unaffected ones. Recommended for large monorepos with plain YAML manifests.
optional bool webhookManifestCacheWarmDisabled = 28;
// AzureServicePrincipalClientId specifies the client ID of the Azure Service Principal used to access the repo
optional string azureServicePrincipalClientId = 29;
// AzureServicePrincipalClientSecret specifies the client secret of the Azure Service Principal used to access the repo
optional string azureServicePrincipalClientSecret = 30;
// AzureServicePrincipalTenantId specifies the tenant ID of the Azure Service Principal used to access the repo
optional string azureServicePrincipalTenantId = 31;
// AzureActiveDirectoryEndpoint specifies the Azure Active Directory endpoint used for Service Principal authentication. If empty will default to https://login.microsoftonline.com
optional string azureActiveDirectoryEndpoint = 32;
}
// A RepositoryCertificate is either SSH known hosts entry or TLS certificate

View file

@ -57,6 +57,14 @@ type RepoCreds struct {
BearerToken string `json:"bearerToken,omitempty" protobuf:"bytes,25,opt,name=bearerToken"`
// InsecureOCIForceHttp specifies whether the connection to the repository uses TLS at _all_. If true, no TLS. This flag is applicable for OCI repos only.
InsecureOCIForceHttp bool `json:"insecureOCIForceHttp,omitempty" protobuf:"bytes,26,opt,name=insecureOCIForceHttp"` //nolint:revive //FIXME(var-naming)
// AzureServicePrincipalClientId specifies the client ID of the Azure Service Principal used to access the repo
AzureServicePrincipalClientId string `json:"azureServicePrincipalClientId,omitempty" protobuf:"bytes,29,opt,name=azureServicePrincipalClientId"`
// AzureServicePrincipalClientSecret specifies the client secret of the Azure Service Principal used to access the repo
AzureServicePrincipalClientSecret string `json:"azureServicePrincipalClientSecret,omitempty" protobuf:"bytes,30,opt,name=azureServicePrincipalClientSecret"`
// AzureServicePrincipalTenantId specifies the tenant ID of the Azure Service Principal used to access the repo
AzureServicePrincipalTenantId string `json:"azureServicePrincipalTenantId,omitempty" protobuf:"bytes,31,opt,name=azureServicePrincipalTenantId"`
// AzureActiveDirectoryEndpoint specifies the Azure Active Directory endpoint used for Service Principal authentication. If empty will default to https://login.microsoftonline.com
AzureActiveDirectoryEndpoint string `json:"azureActiveDirectoryEndpoint,omitempty" protobuf:"bytes,32,opt,name=azureActiveDirectoryEndpoint"`
}
// Repository is a repository holding application configurations
@ -120,6 +128,14 @@ type Repository struct {
// When set, webhook handlers will only trigger reconciliation for affected applications and skip Redis cache
// operations for unaffected ones. Recommended for large monorepos with plain YAML manifests.
WebhookManifestCacheWarmDisabled bool `json:"webhookManifestCacheWarmDisabled,omitempty" protobuf:"varint,28,opt,name=webhookManifestCacheWarmDisabled"`
// AzureServicePrincipalClientId specifies the client ID of the Azure Service Principal used to access the repo
AzureServicePrincipalClientId string `json:"azureServicePrincipalClientId,omitempty" protobuf:"bytes,29,opt,name=azureServicePrincipalClientId"`
// AzureServicePrincipalClientSecret specifies the client secret of the Azure Service Principal used to access the repo
AzureServicePrincipalClientSecret string `json:"azureServicePrincipalClientSecret,omitempty" protobuf:"bytes,30,opt,name=azureServicePrincipalClientSecret"`
// AzureServicePrincipalTenantId specifies the tenant ID of the Azure Service Principal used to access the repo
AzureServicePrincipalTenantId string `json:"azureServicePrincipalTenantId,omitempty" protobuf:"bytes,31,opt,name=azureServicePrincipalTenantId"`
// AzureActiveDirectoryEndpoint specifies the Azure Active Directory endpoint used for Service Principal authentication. If empty will default to https://login.microsoftonline.com
AzureActiveDirectoryEndpoint string `json:"azureActiveDirectoryEndpoint,omitempty" protobuf:"bytes,32,opt,name=azureActiveDirectoryEndpoint"`
}
// IsInsecure returns true if the repository has been configured to skip server verification or set to HTTP only
@ -134,7 +150,7 @@ func (repo *Repository) IsLFSEnabled() bool {
// HasCredentials returns true when the repository has been configured with any credentials
func (repo *Repository) HasCredentials() bool {
return repo.Username != "" || repo.Password != "" || repo.BearerToken != "" || repo.SSHPrivateKey != "" || repo.TLSClientCertData != "" || repo.GithubAppPrivateKey != "" || repo.UseAzureWorkloadIdentity
return repo.Username != "" || repo.Password != "" || repo.BearerToken != "" || repo.SSHPrivateKey != "" || repo.TLSClientCertData != "" || repo.GithubAppPrivateKey != "" || repo.UseAzureWorkloadIdentity || repo.AzureServicePrincipalClientSecret != ""
}
// CopyCredentialsFromRepo copies all credential information from source repository to receiving repository
@ -173,6 +189,18 @@ func (repo *Repository) CopyCredentialsFromRepo(source *Repository) {
if repo.GCPServiceAccountKey == "" {
repo.GCPServiceAccountKey = source.GCPServiceAccountKey
}
if repo.AzureServicePrincipalClientId == "" {
repo.AzureServicePrincipalClientId = source.AzureServicePrincipalClientId
}
if repo.AzureServicePrincipalClientSecret == "" {
repo.AzureServicePrincipalClientSecret = source.AzureServicePrincipalClientSecret
}
if repo.AzureServicePrincipalTenantId == "" {
repo.AzureServicePrincipalTenantId = source.AzureServicePrincipalTenantId
}
if repo.AzureActiveDirectoryEndpoint == "" {
repo.AzureActiveDirectoryEndpoint = source.AzureActiveDirectoryEndpoint
}
repo.InsecureOCIForceHttp = source.InsecureOCIForceHttp
repo.ForceHttpBasicAuth = source.ForceHttpBasicAuth
repo.UseAzureWorkloadIdentity = source.UseAzureWorkloadIdentity
@ -215,6 +243,18 @@ func (repo *Repository) CopyCredentialsFrom(source *RepoCreds) {
if repo.GCPServiceAccountKey == "" {
repo.GCPServiceAccountKey = source.GCPServiceAccountKey
}
if repo.AzureServicePrincipalClientId == "" {
repo.AzureServicePrincipalClientId = source.AzureServicePrincipalClientId
}
if repo.AzureServicePrincipalClientSecret == "" {
repo.AzureServicePrincipalClientSecret = source.AzureServicePrincipalClientSecret
}
if repo.AzureServicePrincipalTenantId == "" {
repo.AzureServicePrincipalTenantId = source.AzureServicePrincipalTenantId
}
if repo.AzureActiveDirectoryEndpoint == "" {
repo.AzureActiveDirectoryEndpoint = source.AzureActiveDirectoryEndpoint
}
if repo.Proxy == "" {
repo.Proxy = source.Proxy
}
@ -252,6 +292,14 @@ func (repo *Repository) GetGitCreds(store git.CredsStore) git.Creds {
if repo.UseAzureWorkloadIdentity {
return git.NewAzureWorkloadIdentityCreds(store, workloadidentity.NewWorkloadIdentityTokenProvider())
}
if repo.AzureServicePrincipalClientId != "" && repo.AzureServicePrincipalClientSecret != "" && repo.AzureServicePrincipalTenantId != "" {
creds := git.NewAzureServicePrincipalCreds(repo.AzureServicePrincipalTenantId, repo.AzureServicePrincipalClientId, repo.AzureServicePrincipalClientSecret, store).
WithActiveDirectoryEndpoint(repo.AzureActiveDirectoryEndpoint).
WithClientCert(repo.TLSClientCertData, repo.TLSClientCertKey).
WithProxy(repo.Proxy).
WithNoProxy(repo.NoProxy)
return creds
}
return git.NopCreds{}
}
@ -366,6 +414,9 @@ func (repo *Repository) Sanitized() *Repository {
GithubAppInstallationId: repo.GithubAppInstallationId,
GitHubAppEnterpriseBaseURL: repo.GitHubAppEnterpriseBaseURL,
UseAzureWorkloadIdentity: repo.UseAzureWorkloadIdentity,
AzureActiveDirectoryEndpoint: repo.AzureActiveDirectoryEndpoint,
AzureServicePrincipalClientId: repo.AzureServicePrincipalClientId,
AzureServicePrincipalTenantId: repo.AzureServicePrincipalTenantId,
Depth: repo.Depth,
}
}

View file

@ -87,6 +87,15 @@ func TestGetGitCreds(t *testing.T) {
},
expected: git.NewGoogleCloudCreds("gcp-key", nil),
},
{
name: "Azure Service Principal credentials",
repo: &Repository{
AzureServicePrincipalClientId: "client-id",
AzureServicePrincipalClientSecret: "client-secret",
AzureServicePrincipalTenantId: "tenant-id",
},
expected: git.NewAzureServicePrincipalCreds("tenant-id", "client-id", "client-secret", nil),
},
{
name: "No credentials",
repo: &Repository{},
@ -153,6 +162,9 @@ func TestSanitizedRepository(t *testing.T) {
GithubAppInstallationId: 67890,
GitHubAppEnterpriseBaseURL: "https://ghe.example.com/api/v3",
UseAzureWorkloadIdentity: true,
AzureServicePrincipalClientId: "client-id",
AzureServicePrincipalClientSecret: "client-secret",
AzureServicePrincipalTenantId: "tenant-id",
Depth: 1,
}
@ -174,6 +186,8 @@ func TestSanitizedRepository(t *testing.T) {
assert.Equal(t, repo.GithubAppInstallationId, sanitized.GithubAppInstallationId)
assert.Equal(t, repo.GitHubAppEnterpriseBaseURL, sanitized.GitHubAppEnterpriseBaseURL)
assert.Equal(t, repo.UseAzureWorkloadIdentity, sanitized.UseAzureWorkloadIdentity)
assert.Equal(t, repo.AzureServicePrincipalClientId, sanitized.AzureServicePrincipalClientId)
assert.Equal(t, repo.AzureServicePrincipalTenantId, sanitized.AzureServicePrincipalTenantId)
assert.Equal(t, repo.Depth, sanitized.Depth)
// Sensitive fields must be stripped
@ -185,6 +199,7 @@ func TestSanitizedRepository(t *testing.T) {
assert.Empty(t, sanitized.TLSClientCertKey)
assert.Empty(t, sanitized.GCPServiceAccountKey)
assert.Empty(t, sanitized.GithubAppPrivateKey)
assert.Empty(t, sanitized.AzureServicePrincipalClientSecret)
}
func TestSanitizedRepositoryPreservesDepthZero(t *testing.T) {

View file

@ -705,6 +705,10 @@ func (s *Server) ValidateAccess(ctx context.Context, q *repositorypkg.RepoAccess
GCPServiceAccountKey: q.GcpServiceAccountKey,
InsecureOCIForceHttp: q.InsecureOciForceHttp,
UseAzureWorkloadIdentity: q.UseAzureWorkloadIdentity,
AzureServicePrincipalClientId: q.AzureServicePrincipalClientId,
AzureServicePrincipalClientSecret: q.AzureServicePrincipalClientSecret,
AzureServicePrincipalTenantId: q.AzureServicePrincipalTenantId,
AzureActiveDirectoryEndpoint: q.AzureActiveDirectoryEndpoint,
}
// If repo does not have credentials, check if there are credentials stored
@ -755,6 +759,10 @@ func (s *Server) ValidateWriteAccess(ctx context.Context, q *repositorypkg.RepoA
Proxy: q.Proxy,
GCPServiceAccountKey: q.GcpServiceAccountKey,
UseAzureWorkloadIdentity: q.UseAzureWorkloadIdentity,
AzureServicePrincipalClientId: q.AzureServicePrincipalClientId,
AzureServicePrincipalClientSecret: q.AzureServicePrincipalClientSecret,
AzureServicePrincipalTenantId: q.AzureServicePrincipalTenantId,
AzureActiveDirectoryEndpoint: q.AzureActiveDirectoryEndpoint,
}
err := s.testRepo(ctx, repo)

View file

@ -95,6 +95,14 @@ message RepoAccessQuery {
string bearerToken = 21;
// Whether https should be disabled for an OCI repo
bool insecureOciForceHttp = 22;
// Azure Service Principal Client ID
string azureServicePrincipalClientId = 23;
// Azure Service Principal Client Secret
string azureServicePrincipalClientSecret = 24;
// Azure Service Principal Tenant ID
string azureServicePrincipalTenantId = 25;
// Azure Active Directory Endpoint
string azureActiveDirectoryEndpoint = 26;
}
message RepoResponse {}

View file

@ -284,6 +284,19 @@ func TestRepositoryServer(t *testing.T) {
require.NoError(t, err)
})
t.Run("Test_validateWriteAccess", func(t *testing.T) {
repoServerClient := &mocks.RepoServerServiceClient{}
repoServerClient.EXPECT().TestRepository(mock.Anything, mock.Anything).Return(&apiclient.TestRepositoryResponse{}, nil)
repoServerClientset := mocks.Clientset{RepoServerServiceClient: repoServerClient}
s := NewServer(&repoServerClientset, argoDB, enforcer, nil, appLister, projInformer, testNamespace, settingsMgr, true)
url := "https://test"
_, err := s.ValidateWriteAccess(t.Context(), &repository.RepoAccessQuery{
Repo: url,
})
require.NoError(t, err)
})
t.Run("Test_Get", func(t *testing.T) {
repoServerClient := &mocks.RepoServerServiceClient{}
repoServerClient.EXPECT().TestRepository(mock.Anything, mock.Anything).Return(&apiclient.TestRepositoryResponse{}, nil)

View file

@ -82,6 +82,21 @@ interface NewGoogleCloudSourceRepoParams {
write: boolean;
}
interface NewAzureServicePrincipalRepoParams {
type: string;
name: string;
url: string;
azureServicePrincipalClientId: string;
azureServicePrincipalClientSecret: string;
azureServicePrincipalTenantId: string;
azureActiveDirectoryEndpoint: string;
proxy: string;
noProxy: string;
project?: string;
// write should be true if saving as a write credential.
write: boolean;
}
interface NewSSHRepoCredsParams {
url: string;
sshPrivateKey: string;
@ -128,11 +143,25 @@ interface NewGoogleCloudSourceRepoCredsParams {
write: boolean;
}
interface NewAzureServicePrincipalRepoCredsParams {
url: string;
azureServicePrincipalClientId: string;
azureServicePrincipalClientSecret: string;
azureServicePrincipalTenantId: string;
azureActiveDirectoryEndpoint: string;
proxy: string;
noProxy: string;
project?: string;
// write should be true if saving as a write credential.
write: boolean;
}
export enum ConnectionMethod {
SSH = 'via SSH',
HTTPS = 'via HTTP/HTTPS',
GITHUBAPP = 'via GitHub App',
GOOGLECLOUD = 'via Google Cloud'
GOOGLECLOUD = 'via Google Cloud',
AZURESERVICEPRINCIPAL = 'via Azure Service Principal'
}
export const ReposList = ({match, location}: RouteComponentProps) => {
@ -172,8 +201,15 @@ export const ReposList = ({match, location}: RouteComponentProps) => {
{method.toUpperCase()} <i className='fa fa-caret-down' />
</p>
)}
items={[ConnectionMethod.SSH, ConnectionMethod.HTTPS, ConnectionMethod.GITHUBAPP, ConnectionMethod.GOOGLECLOUD].map(
(connectMethod: ConnectionMethod.SSH | ConnectionMethod.HTTPS | ConnectionMethod.GITHUBAPP | ConnectionMethod.GOOGLECLOUD) => ({
items={[ConnectionMethod.SSH, ConnectionMethod.HTTPS, ConnectionMethod.GITHUBAPP, ConnectionMethod.GOOGLECLOUD, ConnectionMethod.AZURESERVICEPRINCIPAL].map(
(
connectMethod:
| ConnectionMethod.SSH
| ConnectionMethod.HTTPS
| ConnectionMethod.GITHUBAPP
| ConnectionMethod.GOOGLECLOUD
| ConnectionMethod.AZURESERVICEPRINCIPAL
) => ({
title: connectMethod.toUpperCase(),
action: () => {
onSelection(connectMethod);
@ -191,7 +227,7 @@ export const ReposList = ({match, location}: RouteComponentProps) => {
};
const onChooseDefaultValues = (): FormValues => {
return {type: 'git', ghType: 'GitHub', write: false};
return {type: 'git', ghType: 'GitHub', azureType: 'Azure Public Cloud', write: false};
};
const onValidateErrors = (params: FormValues): FormErrors => {
@ -233,6 +269,16 @@ export const ReposList = ({match, location}: RouteComponentProps) => {
gcpServiceAccountKey: !googleCloudValues.gcpServiceAccountKey && 'GCP service account key is required',
depth: googleCloudValues.depth != undefined && googleCloudValues.depth < 0 && 'Depth must be a non-negative number'
};
case ConnectionMethod.AZURESERVICEPRINCIPAL:
const azureServicePrincipalValues = params as NewAzureServicePrincipalRepoParams;
return {
url:
(!azureServicePrincipalValues.url && 'Repository URL is required') ||
(credsTemplate && !isHTTPOrHTTPSUrl(azureServicePrincipalValues.url) && 'Not a valid HTTP/HTTPS URL'),
azureServicePrincipalClientId: !azureServicePrincipalValues.azureServicePrincipalClientId && 'Azure Service Principal Client ID is required',
azureServicePrincipalClientSecret: !azureServicePrincipalValues.azureServicePrincipalClientSecret && 'Azure Service Principal Client Secret is required',
azureServicePrincipalTenantId: !azureServicePrincipalValues.azureServicePrincipalTenantId && 'Azure Service Principal Tenant ID is required'
};
}
};
@ -283,6 +329,8 @@ export const ReposList = ({match, location}: RouteComponentProps) => {
return connectGitHubAppRepo(params as NewGitHubAppRepoParams);
case ConnectionMethod.GOOGLECLOUD:
return connectGoogleCloudSourceRepo(params as NewGoogleCloudSourceRepoParams);
case ConnectionMethod.AZURESERVICEPRINCIPAL:
return connectAzureServicePrincipalRepo(params as NewAzureServicePrincipalRepoParams);
}
};
@ -313,9 +361,9 @@ export const ReposList = ({match, location}: RouteComponentProps) => {
return url.replace('https://', '').replace('oci://', '');
};
// only connections of git type which is not via GitHub App are updatable
// only connections of git type which are not via GitHub App or Azure Service Principal are updatable
const isRepoUpdatable = (repo: models.Repository) => {
return isHTTPOrHTTPSUrl(repo.repo) && repo.type === 'git' && !repo.githubAppID;
return isHTTPOrHTTPSUrl(repo.repo) && repo.type === 'git' && !repo.githubAppID && !repo.azureServicePrincipalClientId;
};
// Forces a reload of configured repositories, circumventing the cache
@ -466,7 +514,7 @@ export const ReposList = ({match, location}: RouteComponentProps) => {
}
};
// Connect a new repository or create a repository credentials for GitHub App repositories
// Connect a new repository or create a repository credentials for Google Cloud Source repositories
const connectGoogleCloudSourceRepo = async (params: NewGoogleCloudSourceRepoParams) => {
if (credsTemplate.current) {
createGoogleCloudSourceCreds({
@ -495,6 +543,40 @@ export const ReposList = ({match, location}: RouteComponentProps) => {
}
};
// Connect a new repository or create a repository credentials for Azure Service Principal repositories
const connectAzureServicePrincipalRepo = async (params: NewAzureServicePrincipalRepoParams) => {
if (credsTemplate.current) {
createAzureServicePrincipalCreds({
url: params.url,
azureServicePrincipalClientId: params.azureServicePrincipalClientId,
azureServicePrincipalClientSecret: params.azureServicePrincipalClientSecret,
azureServicePrincipalTenantId: params.azureServicePrincipalTenantId,
azureActiveDirectoryEndpoint: params.azureActiveDirectoryEndpoint,
proxy: params.proxy,
noProxy: params.noProxy,
write: params.write
});
} else {
setConnecting(true);
try {
if (params.write) {
await services.repos.createAzureServicePrincipalWrite(params);
} else {
await services.repos.createAzureServicePrincipal(params);
}
repoLoader.current.reload();
setConnectRepo(false);
} catch (e) {
ctx.notifications.show({
content: <ErrorNotification title='Unable to connect Azure Service Principal repository' e={e} />,
type: NotificationType.Error
});
} finally {
setConnecting(false);
}
}
};
const createHTTPSCreds = async (params: NewHTTPSRepoCredsParams) => {
try {
if (params.write) {
@ -563,6 +645,23 @@ export const ReposList = ({match, location}: RouteComponentProps) => {
}
};
const createAzureServicePrincipalCreds = async (params: NewAzureServicePrincipalRepoCredsParams) => {
try {
if (params.write) {
await services.repocreds.createAzureServicePrincipalWrite(params);
} else {
await services.repocreds.createAzureServicePrincipal(params);
}
credsLoader.current.reload();
setConnectRepo(false);
} catch (e) {
ctx.notifications.show({
content: <ErrorNotification title='Unable to create Azure Service Principal credentials' e={e} />,
type: NotificationType.Error
});
}
};
// Remove a repository from the configuration
const disconnectRepo = async (repo: string, project: string, write: boolean) => {
const confirmed = await ctx.popup.confirm('Disconnect repository', `Are you sure you want to disconnect '${repo}'?`);
@ -1322,6 +1421,59 @@ export const ReposList = ({match, location}: RouteComponentProps) => {
</div>
</div>
)}
{method === ConnectionMethod.AZURESERVICEPRINCIPAL && (
<div className='white-box'>
<p>CONNECT REPO USING AZURE SERVICE PRINCIPAL</p>
<div className='argo-form-row'>
<FormField
formApi={formApi}
label='Type'
field='azureType'
component={FormSelect}
componentProps={{options: ['Azure Public Cloud', 'Azure Other Cloud']}}
/>
</div>
{formApi.getFormState().values.azureType === 'Azure Other Cloud' && (
<div className='argo-form-row'>
<FormField
formApi={formApi}
label='Azure Active Directory Endpoint (e.g. https://login.microsoftonline.de)'
field='azureActiveDirectoryEndpoint'
component={Text}
/>
</div>
)}
<div className='argo-form-row'>
<FormField formApi={formApi} label='Project' field='project' component={AutocompleteField} componentProps={{items: projects}} />
</div>
<div className='argo-form-row'>
<FormField formApi={formApi} label='Repository URL' field='url' component={Text} />
</div>
<div className='argo-form-row'>
<FormField formApi={formApi} label='AzureTenant ID' field='azureServicePrincipalTenantId' component={Text} />
</div>
<div className='argo-form-row'>
<FormField formApi={formApi} label='Azure Client ID' field='azureServicePrincipalClientId' component={Text} />
</div>
<div className='argo-form-row'>
<FormField formApi={formApi} label='Azure Client Secret' field='azureServicePrincipalClientSecret' component={Text} />
</div>
<div className='argo-form-row'>
<FormField formApi={formApi} label='Skip server verification' field='insecure' component={CheckboxField} />
<HelpIcon title='This setting is ignored when creating as credential template.' />
</div>
<div className='argo-form-row'>
<FormField formApi={formApi} label='Enable LFS support (Git only)' field='enableLfs' component={CheckboxField} />
<HelpIcon title='This setting is ignored when creating as credential template.' />
</div>
<div className='argo-form-row'>
<FormField formApi={formApi} label='Proxy (optional)' field='proxy' component={Text} />
</div>
<div className='argo-form-row'>
<FormField formApi={formApi} label='NoProxy (optional)' field='noProxy' component={Text} />
</div>
</div>
)}
</form>
)}
</Form>

View file

@ -677,6 +677,10 @@ export interface Repository {
enableOCI: boolean;
useAzureWorkloadIdentity: boolean;
depth?: number;
azureServicePrincipalClientId?: string;
azureServicePrincipalClientSecret?: string;
azureServicePrincipalTenantId?: string;
azureActiveDirectoryEndpoint?: string;
}
export interface RepositoryList extends ItemsList<Repository> {}

View file

@ -64,6 +64,19 @@ export interface GoogleCloudSourceQuery {
depth?: number;
}
export interface AzureServicePrincipalQuery {
type: string;
name: string;
url: string;
azureActiveDirectoryEndpoint: string;
azureServicePrincipalClientId: string;
azureServicePrincipalClientSecret: string;
azureServicePrincipalTenantId: string;
proxy: string;
noProxy: string;
project?: string;
}
export class RepositoriesService {
public list(): Promise<models.Repository[]> {
return requests
@ -311,6 +324,42 @@ export class RepositoriesService {
.then(res => res.body as models.Repository);
}
public createAzureServicePrincipal(q: AzureServicePrincipalQuery): Promise<models.Repository> {
return requests
.post('/repositories')
.send({
type: q.type,
name: q.name,
repo: q.url,
azureServicePrincipalClientId: q.azureServicePrincipalClientId,
azureServicePrincipalClientSecret: q.azureServicePrincipalClientSecret,
azureServicePrincipalTenantId: q.azureServicePrincipalTenantId,
azureActiveDirectoryEndpoint: q.azureActiveDirectoryEndpoint,
proxy: q.proxy,
noProxy: q.noProxy,
project: q.project
})
.then(res => res.body as models.Repository);
}
public createAzureServicePrincipalWrite(q: AzureServicePrincipalQuery): Promise<models.Repository> {
return requests
.post('/write-repositories')
.send({
type: q.type,
name: q.name,
repo: q.url,
azureServicePrincipalClientId: q.azureServicePrincipalClientId,
azureServicePrincipalClientSecret: q.azureServicePrincipalClientSecret,
azureServicePrincipalTenantId: q.azureServicePrincipalTenantId,
azureActiveDirectoryEndpoint: q.azureActiveDirectoryEndpoint,
proxy: q.proxy,
noProxy: q.noProxy,
project: q.project
})
.then(res => res.body as models.Repository);
}
public delete(url: string, project: string): Promise<models.Repository> {
return requests
.delete(`/repositories/${encodeURIComponent(url)}?appProject=${project}`)

View file

@ -37,6 +37,16 @@ export interface GoogleCloudSourceCreds {
gcpServiceAccountKey: string;
}
export interface AzureServicePrincipalCreds {
url: string;
azureActiveDirectoryEndpoint: string;
azureServicePrincipalClientId: string;
azureServicePrincipalClientSecret: string;
azureServicePrincipalTenantId: string;
proxy: string;
noProxy: string;
}
export class RepoCredsService {
public list(): Promise<models.RepoCreds[]> {
return requests
@ -108,6 +118,20 @@ export class RepoCredsService {
.then(res => res.body as models.RepoCreds);
}
public createAzureServicePrincipal(creds: AzureServicePrincipalCreds): Promise<models.RepoCreds> {
return requests
.post('/repocreds')
.send(creds)
.then(res => res.body as models.RepoCreds);
}
public createAzureServicePrincipalWrite(creds: AzureServicePrincipalCreds): Promise<models.RepoCreds> {
return requests
.post('/write-repocreds')
.send(creds)
.then(res => res.body as models.RepoCreds);
}
public delete(url: string): Promise<models.RepoCreds> {
return requests
.delete(`/repocreds/${encodeURIComponent(url)}`)

View file

@ -345,6 +345,10 @@ func secretToRepository(secret *corev1.Secret) (*appsv1.Repository, error) {
NoProxy: string(secretCopy.Data["noProxy"]),
Project: string(secretCopy.Data["project"]),
GCPServiceAccountKey: string(secretCopy.Data["gcpServiceAccountKey"]),
AzureServicePrincipalClientId: string(secretCopy.Data["azureServicePrincipalClientId"]),
AzureServicePrincipalClientSecret: string(secretCopy.Data["azureServicePrincipalClientSecret"]),
AzureServicePrincipalTenantId: string(secretCopy.Data["azureServicePrincipalTenantId"]),
AzureActiveDirectoryEndpoint: string(secretCopy.Data["azureActiveDirectoryEndpoint"]),
}
insecureIgnoreHostKey, err := boolOrFalse(secretCopy, "insecureIgnoreHostKey")
@ -451,6 +455,10 @@ func (s *secretsRepositoryBackend) repositoryToSecret(repository *appsv1.Reposit
updateSecretBool(secretCopy, "useAzureWorkloadIdentity", repository.UseAzureWorkloadIdentity)
updateSecretInt(secretCopy, "depth", repository.Depth)
updateSecretBool(secretCopy, "webhookManifestCacheWarmDisabled", repository.WebhookManifestCacheWarmDisabled)
updateSecretString(secretCopy, "azureServicePrincipalClientId", repository.AzureServicePrincipalClientId)
updateSecretString(secretCopy, "azureServicePrincipalClientSecret", repository.AzureServicePrincipalClientSecret)
updateSecretString(secretCopy, "azureServicePrincipalTenantId", repository.AzureServicePrincipalTenantId)
updateSecretString(secretCopy, "azureActiveDirectoryEndpoint", repository.AzureActiveDirectoryEndpoint)
addSecretMetadata(secretCopy, s.getSecretType())
return secretCopy
@ -473,6 +481,10 @@ func (s *secretsRepositoryBackend) secretToRepoCred(secret *corev1.Secret) (*app
GCPServiceAccountKey: string(secretCopy.Data["gcpServiceAccountKey"]),
Proxy: string(secretCopy.Data["proxy"]),
NoProxy: string(secretCopy.Data["noProxy"]),
AzureServicePrincipalClientId: string(secretCopy.Data["azureServicePrincipalClientID"]),
AzureServicePrincipalClientSecret: string(secretCopy.Data["azureServicePrincipalClientSecret"]),
AzureServicePrincipalTenantId: string(secretCopy.Data["azureServicePrincipalTenantID"]),
AzureActiveDirectoryEndpoint: string(secretCopy.Data["azureActiveDirectoryEndpoint"]),
}
enableOCI, err := boolOrFalse(secretCopy, "enableOCI")
@ -540,6 +552,10 @@ func (s *secretsRepositoryBackend) repoCredsToSecret(repoCreds *appsv1.RepoCreds
updateSecretString(secretCopy, "noProxy", repoCreds.NoProxy)
updateSecretBool(secretCopy, "forceHttpBasicAuth", repoCreds.ForceHttpBasicAuth)
updateSecretBool(secretCopy, "useAzureWorkloadIdentity", repoCreds.UseAzureWorkloadIdentity)
updateSecretString(secretCopy, "azureServicePrincipalClientID", repoCreds.AzureServicePrincipalClientId)
updateSecretString(secretCopy, "azureServicePrincipalClientSecret", repoCreds.AzureServicePrincipalClientSecret)
updateSecretString(secretCopy, "azureServicePrincipalTenantID", repoCreds.AzureServicePrincipalTenantId)
updateSecretString(secretCopy, "azureActiveDirectoryEndpoint", repoCreds.AzureActiveDirectoryEndpoint)
addSecretMetadata(secretCopy, s.getRepoCredSecretType())
return secretCopy

View file

@ -412,6 +412,13 @@ func newAuth(repoURL string, creds Creds) (transport.AuthMethod, error) {
return nil, fmt.Errorf("failed to get access token from creds: %w", err)
}
auth := githttp.TokenAuth{Token: token}
return &auth, nil
case AzureServicePrincipalCreds:
token, err := creds.getAccessToken()
if err != nil {
return nil, fmt.Errorf("failed to get access token from creds: %w", err)
}
auth := githttp.TokenAuth{Token: token}
return &auth, nil
}

View file

@ -18,6 +18,10 @@ import (
"sync"
"time"
"github.com/Azure/azure-sdk-for-go/sdk/azcore"
"github.com/Azure/azure-sdk-for-go/sdk/azcore/cloud"
"github.com/Azure/azure-sdk-for-go/sdk/azcore/policy"
"github.com/Azure/azure-sdk-for-go/sdk/azidentity"
giturls "github.com/chainguard-dev/git-urls"
"github.com/google/go-github/v69/github"
@ -50,6 +54,8 @@ var (
// installationIdCache caches installation IDs for organizations to avoid redundant API calls.
githubInstallationIdCache *gocache.Cache
githubInstallationIdCacheMutex sync.RWMutex // For bulk API call coordination
// In memory cache for storing Azure Service Principal tokens
azureServicePrincipalTokenCache *gocache.Cache
)
const (
@ -64,16 +70,30 @@ const (
func init() {
githubAppCredsExp := common.GithubAppCredsExpirationDuration
if exp := os.Getenv(common.EnvGithubAppCredsExpirationDuration); exp != "" {
if qps, err := strconv.Atoi(exp); err != nil {
if qps, err := strconv.Atoi(exp); err == nil {
githubAppCredsExp = time.Duration(qps) * time.Minute
}
}
azureServicePrincipalCredsExp := common.AzureServicePrincipalCredsExpirationDuration
if exp := os.Getenv(common.EnvAzureServicePrincipalCredsExpirationDuration); exp != "" {
if qps, err := strconv.Atoi(exp); err == nil {
// Azure service principal tokens are valid for 60 minutes
// the cache has a cleanup interval of 1 minute
// cap the expiration duration to 59 minutes to avoid issues with token expiration
if qps > 59 {
log.Warnf("Value in %s is %d, which is greater than maximum 59 minutes allowed. Setting to 59 minutes", common.EnvAzureServicePrincipalCredsExpirationDuration, qps)
qps = 59
}
azureServicePrincipalCredsExp = time.Duration(qps) * time.Minute
}
}
githubAppTokenCache = gocache.New(githubAppCredsExp, 1*time.Minute)
// oauth2.TokenSource handles fetching new Tokens once they are expired. The oauth2.TokenSource itself does not expire.
googleCloudTokenSource = gocache.New(gocache.NoExpiration, 0)
azureTokenCache = gocache.New(gocache.NoExpiration, 0)
githubInstallationIdCache = gocache.New(60*time.Minute, 60*time.Minute)
azureServicePrincipalTokenCache = gocache.New(azureServicePrincipalCredsExp, 1*time.Minute)
}
type NoopCredsStore struct{}
@ -974,3 +994,133 @@ func (creds AzureWorkloadIdentityCreds) GetAzureDevOpsAccessToken() (string, err
accessToken, err := creds.getAccessToken(azureDevopsEntraResourceId) // wellknown resourceid of Azure DevOps
return accessToken, err
}
var _ Creds = AzureServicePrincipalCreds{}
// AzureServicePrincipalCreds to authenticate to Azure DevOps using a Service Principal
type AzureServicePrincipalCreds struct {
tenantID string
clientID string
clientSecret string
activeDirectoryEndpoint string
clientCertData string
clientCertKey string
proxy string
noProxy string
store CredsStore
}
// NewAzureServicePrincipalCreds creates new Azure Service Principal credentials
func NewAzureServicePrincipalCreds(tenantID string, clientID string, clientSecret string, store CredsStore) AzureServicePrincipalCreds {
return AzureServicePrincipalCreds{tenantID: tenantID, clientID: clientID, clientSecret: clientSecret, store: store}
}
// WithActiveDirectoryEndpoint sets a custom Active Directory endpoint. When not set, the default Azure public cloud is used.
func (a AzureServicePrincipalCreds) WithActiveDirectoryEndpoint(activeDirectoryEndpoint string) AzureServicePrincipalCreds {
if activeDirectoryEndpoint != "" {
a.activeDirectoryEndpoint = activeDirectoryEndpoint
}
return a
}
// WithClientCert sets the client certificate data and key
func (a AzureServicePrincipalCreds) WithClientCert(data string, key string) AzureServicePrincipalCreds {
if data != "" && key != "" {
a.clientCertData = data
a.clientCertKey = key
}
return a
}
// WithProxy sets the HTTP/HTTPS proxy used to access the repo
func (a AzureServicePrincipalCreds) WithProxy(proxy string) AzureServicePrincipalCreds {
if proxy != "" {
a.proxy = proxy
}
return a
}
// WithNoProxy sets a comma separated list of IPs/hostnames that should not use the proxy
func (a AzureServicePrincipalCreds) WithNoProxy(noProxy string) AzureServicePrincipalCreds {
if noProxy != "" {
a.noProxy = noProxy
}
return a
}
// GetUserInfo doesn't return any user info as they are not present for Azure Service Principals.
func (a AzureServicePrincipalCreds) GetUserInfo(_ context.Context) (string, string, error) {
return workloadidentity.EmptyGuid, "", nil
}
func (a AzureServicePrincipalCreds) Environ() (io.Closer, []string, error) {
token, err := a.getAccessToken()
if err != nil {
return NopCloser{}, nil, err
}
nonce := a.store.Add("", token)
env := a.store.Environ(nonce)
env = append(env, fmt.Sprintf("%s=Authorization: Bearer %s", bearerAuthHeaderEnv, token))
return utilio.NewCloser(func() error {
a.store.Remove(nonce)
return nil
}), env, nil
}
func (a AzureServicePrincipalCreds) getAccessToken() (string, error) {
// Override the default active directory endpoint if present
activeDirectoryEndpoint := "https://login.microsoftonline.com"
disableInstanceDiscovery := false
if a.activeDirectoryEndpoint != "" {
activeDirectoryEndpoint = a.activeDirectoryEndpoint
disableInstanceDiscovery = true
}
// Generate cache key for creds
key, err := argoutils.GenerateCacheKey("%s %s %s %s", a.tenantID, a.clientID, a.clientSecret, activeDirectoryEndpoint)
if err != nil {
return "", fmt.Errorf("failed to get get SHA256 hash for Azure Service Principal credentials: %w", err)
}
t, found := azureServicePrincipalTokenCache.Get(key)
if found {
return t.(string), nil
}
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
defer cancel()
opts := azcore.ClientOptions{}
opts.Cloud = cloud.Configuration{
ActiveDirectoryAuthorityHost: activeDirectoryEndpoint,
}
// Configure HTTP client with proxy if proxy is set
if a.proxy != "" {
opts.Transport = GetRepoHTTPClient(activeDirectoryEndpoint, false, a, a.proxy, a.noProxy)
}
cred, err := azidentity.NewClientSecretCredential(a.tenantID, a.clientID, a.clientSecret, &azidentity.ClientSecretCredentialOptions{ClientOptions: opts, DisableInstanceDiscovery: disableInstanceDiscovery})
if err != nil {
return "", fmt.Errorf("failed to create Azure client secret credential: %w", err)
}
token, err := cred.GetToken(ctx, policy.TokenRequestOptions{
Scopes: []string{azureDevopsEntraResourceId},
})
if err != nil {
return "", fmt.Errorf("failed to get Azure access token: %w", err)
}
azureServicePrincipalTokenCache.Set(key, token.Token, 0)
return token.Token, nil
}
func (a AzureServicePrincipalCreds) HasClientCert() bool {
return a.clientCertData != "" && a.clientCertKey != ""
}
func (a AzureServicePrincipalCreds) GetClientCertData() string {
return a.clientCertData
}
func (a AzureServicePrincipalCreds) GetClientCertKey() string {
return a.clientCertKey
}

View file

@ -326,6 +326,14 @@ type Repository struct {
ForceHttpBasicAuth bool `json:"forceHttpBasicAuth,omitempty"` //nolint:revive //FIXME(var-naming)
// UseAzureWorkloadIdentity specifies whether to use Azure Workload Identity for authentication
UseAzureWorkloadIdentity bool `json:"useAzureWorkloadIdentity,omitempty"`
// AzureActiveDirectoryEndpoint specifies the Azure Active Directory endpoint used for Service Principal authentication. If empty will default to https://login.microsoftonline.com
AzureActiveDirectoryEndpoint string `json:"azureActiveDirectoryEndpoint,omitempty"`
// AzureServicePrincipalClientId specifies the client ID of the Azure Service Principal used to access the repo
AzureServicePrincipalClientId string `json:"azureServicePrincipalClientId,omitempty"`
// AzureServicePrincipalClientSecret specifies the client secret of the Azure Service Principal used to access the repo
AzureServicePrincipalClientSecret string `json:"azureServicePrincipalClientSecret,omitempty"`
// AzureServicePrincipalTenantId specifies the tenant ID of the Azure Service Principal used to access the repo
AzureServicePrincipalTenantId string `json:"azureServicePrincipalTenantId,omitempty"`
}
// Credential template for accessing repositories
@ -360,6 +368,14 @@ type RepositoryCredentials struct {
ForceHttpBasicAuth bool `json:"forceHttpBasicAuth,omitempty"` //nolint:revive //FIXME(var-naming)
// UseAzureWorkloadIdentity specifies whether to use Azure Workload Identity for authentication
UseAzureWorkloadIdentity bool `json:"useAzureWorkloadIdentity,omitempty"`
// AzureActiveDirectoryEndpoint specifies the Azure Active Directory endpoint used for Service Principal authentication. If empty will default to https://login.microsoftonline.com
AzureActiveDirectoryEndpoint string `json:"azureActiveDirectoryEndpoint,omitempty"`
// AzureServicePrincipalClientId specifies the client ID of the Azure Service Principal used to access the repo
AzureServicePrincipalClientId string `json:"azureServicePrincipalClientId,omitempty"`
// AzureServicePrincipalClientSecret specifies the client secret of the Azure Service Principal used to access the repo
AzureServicePrincipalClientSecret string `json:"azureServicePrincipalClientSecret,omitempty"`
// AzureServicePrincipalTenantId specifies the tenant ID of the Azure Service Principal used to access the repo
AzureServicePrincipalTenantId string `json:"azureServicePrincipalTenantId,omitempty"`
}
// DeepLink structure