Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion deployment/chainloop/Chart.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ description: Chainloop is an open source software supply chain control plane, a

type: application
# Bump the patch (not minor, not major) version on each change in the Chart Source code
version: 1.451.0
version: 1.451.1
# Do not update appVersion, this is handled automatically by the release process
appVersion: v1.113.0

Expand Down
28 changes: 27 additions & 1 deletion deployment/chainloop/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -388,6 +388,31 @@ secretsBackend:

```

To authenticate without a stored secret, select [AKS Workload Identity](https://learn.microsoft.com/en-us/azure/aks/workload-identity-overview) explicitly and leave `clientSecret` unset. The pods need the `azure.workload.identity/use: "true"` label (set through `commonLabels`), and the controlplane and CAS service accounts need the `azure.workload.identity/client-id: [CLIENT_ID]` annotation

```yaml
secretsBackend:
backend: azureKeyVault
azureKeyVault:
authType: AUTH_TYPE_WORKLOAD_IDENTITY
tenantID: [TENANT_ID]
clientID: [CLIENT_ID]
vaultURI: [VAULT URI]

commonLabels:
azure.workload.identity/use: "true"

controlplane:
serviceAccount:
annotations:
azure.workload.identity/client-id: [CLIENT_ID]

cas:
serviceAccount:
annotations:
azure.workload.identity/client-id: [CLIENT_ID]
```

### Deploy in keyless mode with file-based CA

You can enable keyless signing mode by providing a custom Certificate Authority.
Expand Down Expand Up @@ -547,7 +572,8 @@ Once done, you can access with [two predefined users](https://github.com/chainlo
| `secretsBackend.gcpSecretManager.serviceAccountKey` | GCP Auth Key | |
| `secretsBackend.azureKeyVault.tenantID` | Active Directory Tenant ID | |
| `secretsBackend.azureKeyVault.clientID` | Registered application / service principal client ID | |
| `secretsBackend.azureKeyVault.clientSecret` | Service principal client secret | |
| `secretsBackend.azureKeyVault.authType` | AUTH_TYPE_CREDENTIALS (default, client secret) or AUTH_TYPE_WORKLOAD_IDENTITY | |
| `secretsBackend.azureKeyVault.clientSecret` | Service principal client secret (AUTH_TYPE_CREDENTIALS only) | |
| `secretsBackend.azureKeyVault.vaultURI` | Azure Key Vault URL | |

### Authentication
Expand Down
10 changes: 10 additions & 0 deletions deployment/chainloop/templates/_helpers.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -95,10 +95,20 @@ gcpSecretManager:
{{- fail ".Values.secretsBackend.gcpSecretManager.serviceAccountKey not set" }}
{{- end }}
{{- else if eq .backend "azureKeyVault" }}
{{- $authType := .azureKeyVault.authType | default "AUTH_TYPE_CREDENTIALS" }}
azure_key_vault:
tenant_id: {{ required "AD tenantID required" .azureKeyVault.tenantID | quote }}
client_id: {{ required "Service principal ID required" .azureKeyVault.clientID | quote }}
auth_type: {{ $authType | quote }}
{{- if eq $authType "AUTH_TYPE_CREDENTIALS" }}
client_secret: {{ required "Service principal secret required" .azureKeyVault.clientSecret | quote }}
{{- else if eq $authType "AUTH_TYPE_WORKLOAD_IDENTITY" }}
{{- if .azureKeyVault.clientSecret }}
{{- fail "secretsBackend.azureKeyVault: clientSecret must not be set with authType AUTH_TYPE_WORKLOAD_IDENTITY" }}
{{- end }}
{{- else }}
{{- fail (printf "secretsBackend.azureKeyVault.authType %q is not one of AUTH_TYPE_CREDENTIALS, AUTH_TYPE_WORKLOAD_IDENTITY" $authType) }}
{{- end }}
vault_uri: {{ required "Azure Vault URL required" .azureKeyVault.vaultURI | quote }}
{{- end }}
{{- end }}
Expand Down
6 changes: 5 additions & 1 deletion deployment/chainloop/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -88,10 +88,14 @@ secretsBackend:

## @extra secretsBackend.azureKeyVault.tenantID Active Directory Tenant ID
## @extra secretsBackend.azureKeyVault.clientID Registered application / service principal client ID
## @extra secretsBackend.azureKeyVault.clientSecret Service principal client secret
## @extra secretsBackend.azureKeyVault.authType AUTH_TYPE_CREDENTIALS (default, client secret) or AUTH_TYPE_WORKLOAD_IDENTITY
## @extra secretsBackend.azureKeyVault.clientSecret Service principal client secret (AUTH_TYPE_CREDENTIALS only)
## (AUTH_TYPE_WORKLOAD_IDENTITY: set commonLabels azure.workload.identity/use: "true", and annotate the controlplane and
## cas service accounts azure.workload.identity/client-id: <clientID>)
## @extra secretsBackend.azureKeyVault.vaultURI Azure Key Vault URL
##
# azureKeyVault:
# authType: AUTH_TYPE_CREDENTIALS
# tenantID: ""
# clientID: ""
# clientSecret: ""
Expand Down
118 changes: 94 additions & 24 deletions pkg/credentials/api/credentials/v1/config.pb.go

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

16 changes: 14 additions & 2 deletions pkg/credentials/api/credentials/v1/config.proto
Original file line number Diff line number Diff line change
Expand Up @@ -67,9 +67,21 @@ message Credentials {
string tenant_id = 1 [(buf.validate.field).string.min_len = 1];
// Registered application / service principal client ID
string client_id = 2 [(buf.validate.field).string.min_len = 1];
// Registered application / service principal client secret
string client_secret = 3 [(buf.validate.field).string.min_len = 1];
// Registered application / service principal client secret.
// Required for AUTH_TYPE_CREDENTIALS, rejected for AUTH_TYPE_WORKLOAD_IDENTITY.
string client_secret = 3;
// Azure Key Vault URL
string vault_uri = 4 [(buf.validate.field).string.uri_ref = true];

enum AuthType {
AUTH_TYPE_UNSPECIFIED = 0;
// Use the service principal client secret.
AUTH_TYPE_CREDENTIALS = 1;
// Use AKS Workload Identity for client_id: the federated token the workload identity webhook projects into the pod.
AUTH_TYPE_WORKLOAD_IDENTITY = 2;
}

// How to authenticate. AUTH_TYPE_UNSPECIFIED is treated as AUTH_TYPE_CREDENTIALS.
AuthType auth_type = 5 [(buf.validate.field).enum.defined_only = true];
}
}
60 changes: 53 additions & 7 deletions pkg/credentials/azurekv/keyvault.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,13 +50,26 @@ type SecretsRW interface {
DeleteSecret(ctx context.Context, secretName string, options *azsecrets.DeleteSecretOptions) (azsecrets.DeleteSecretResponse, error)
}

// AuthType selects how the manager authenticates to Azure. The zero value is AuthTypeCredentials, so a configuration
// that does not choose keeps using the client secret.
type AuthType int

const (
// AuthTypeCredentials uses the service principal ClientSecret.
AuthTypeCredentials AuthType = iota
// AuthTypeWorkloadIdentity uses AKS Workload Identity for ClientID, so no secret is stored.
AuthTypeWorkloadIdentity
)

type NewManagerOpts struct {
// Active Directory Tenant ID
TenantID string
// Registered application / service principal client ID
ClientID string
// Registered application / service principal client secret
// Registered application / service principal client secret. Required for AuthTypeCredentials, rejected for
// AuthTypeWorkloadIdentity.
ClientSecret string
AuthType AuthType
// Vault URL
VaultURI string
// Optional secret prefix
Expand All @@ -76,17 +89,50 @@ func (o *NewManagerOpts) Validate() error {
return fmt.Errorf("%w: missing client ID", ErrValidation)
}

if o.ClientSecret == "" {
return fmt.Errorf("%w: missing client secret", ErrValidation)
}

if o.VaultURI == "" {
return fmt.Errorf("%w: missing VAULT URI", ErrValidation)
}

// The operator chooses workload identity explicitly, so a forgotten secret fails here instead of silently
// switching the authentication method.
switch o.AuthType {
case AuthTypeCredentials:
if o.ClientSecret == "" {
return fmt.Errorf("%w: missing client secret", ErrValidation)
}
case AuthTypeWorkloadIdentity:
if o.ClientSecret != "" {
return fmt.Errorf("%w: client secret must not be set for the workload identity auth type", ErrValidation)
}
default:
return fmt.Errorf("%w: unknown auth type %d", ErrValidation, o.AuthType)
}

return nil
}

// newCredential uses the service principal secret for AuthTypeCredentials, and AKS Workload Identity for the same
// tenant and client ID for AuthTypeWorkloadIdentity: the federated token file the workload identity webhook projects
// into the pod (AZURE_FEDERATED_TOKEN_FILE).
func newCredential(opts *NewManagerOpts) (azcore.TokenCredential, error) {
if opts.AuthType == AuthTypeCredentials {
credential, err := azidentity.NewClientSecretCredential(opts.TenantID, opts.ClientID, opts.ClientSecret, nil)
if err != nil {
return nil, fmt.Errorf("failed to create Azure Service principal Credential: %w", err)
}
return credential, nil
}

credential, err := azidentity.NewWorkloadIdentityCredential(&azidentity.WorkloadIdentityCredentialOptions{
TenantID: opts.TenantID,
ClientID: opts.ClientID,
})
if err != nil {
return nil, fmt.Errorf("failed to create Azure Workload Identity Credential: %w", err)
}
return credential, nil
}

func NewManager(opts *NewManagerOpts) (*Manager, error) {
l := opts.Logger
if l == nil {
Expand All @@ -100,9 +146,9 @@ func NewManager(opts *NewManagerOpts) (*Manager, error) {
return nil, fmt.Errorf("invalid credentials: %w", err)
}

credential, err := azidentity.NewClientSecretCredential(opts.TenantID, opts.ClientID, opts.ClientSecret, nil)
credential, err := newCredential(opts)
if err != nil {
return nil, fmt.Errorf("failed to create Azure Service principal Credential: %w", err)
return nil, err
}

// Establish a connection to the Key Vault client
Expand Down
Loading