Avoid envs and extend CRDs with vault config; remove controller dependency completely

This commit is contained in:
Nikola Jokic 2025-06-06 17:42:06 +02:00
parent 69dfa4e375
commit c3ce54cabc
No known key found for this signature in database
GPG Key ID: E4104494F9B8DDF6
30 changed files with 723 additions and 225 deletions

View File

@ -59,7 +59,10 @@ type AutoscalingListenerSpec struct {
Proxy *ProxyConfig `json:"proxy,omitempty"` Proxy *ProxyConfig `json:"proxy,omitempty"`
// +optional // +optional
GitHubServerTLS *GitHubServerTLSConfig `json:"githubServerTLS,omitempty"` GitHubServerTLS *TLSConfig `json:"githubServerTLS,omitempty"`
// +optional
VaultConfig *VaultConfig `json:"vaultConfig,omitempty"`
// +optional // +optional
Metrics *MetricsConfig `json:"metrics,omitempty"` Metrics *MetricsConfig `json:"metrics,omitempty"`
@ -98,7 +101,7 @@ func (l *AutoscalingListener) Proxy() *ProxyConfig {
return l.Spec.Proxy return l.Spec.Proxy
} }
func (l *AutoscalingListener) GitHubServerTLS() *GitHubServerTLSConfig { func (l *AutoscalingListener) GitHubServerTLS() *TLSConfig {
return l.Spec.GitHubServerTLS return l.Spec.GitHubServerTLS
} }

View File

@ -24,6 +24,7 @@ import (
"strings" "strings"
"github.com/actions/actions-runner-controller/hash" "github.com/actions/actions-runner-controller/hash"
"github.com/actions/actions-runner-controller/vault"
"golang.org/x/net/http/httpproxy" "golang.org/x/net/http/httpproxy"
corev1 "k8s.io/api/core/v1" corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@ -69,7 +70,13 @@ type AutoscalingRunnerSetSpec struct {
Proxy *ProxyConfig `json:"proxy,omitempty"` Proxy *ProxyConfig `json:"proxy,omitempty"`
// +optional // +optional
GitHubServerTLS *GitHubServerTLSConfig `json:"githubServerTLS,omitempty"` GitHubServerTLS *TLSConfig `json:"githubServerTLS,omitempty"`
// +optional
VaultServerTLS *TLSConfig `json:"vaultServerTLS,omitempty"`
// +optional
VaultConfig *VaultConfig `json:"vaultConfig,omitempty"`
// Required // Required
Template corev1.PodTemplateSpec `json:"template,omitempty"` Template corev1.PodTemplateSpec `json:"template,omitempty"`
@ -89,12 +96,12 @@ type AutoscalingRunnerSetSpec struct {
MinRunners *int `json:"minRunners,omitempty"` MinRunners *int `json:"minRunners,omitempty"`
} }
type GitHubServerTLSConfig struct { type TLSConfig struct {
// Required // Required
CertificateFrom *TLSCertificateSource `json:"certificateFrom,omitempty"` CertificateFrom *TLSCertificateSource `json:"certificateFrom,omitempty"`
} }
func (c *GitHubServerTLSConfig) ToCertPool(keyFetcher func(name, key string) ([]byte, error)) (*x509.CertPool, error) { func (c *TLSConfig) ToCertPool(keyFetcher func(name, key string) ([]byte, error)) (*x509.CertPool, error) {
if c.CertificateFrom == nil { if c.CertificateFrom == nil {
return nil, fmt.Errorf("certificateFrom not specified") return nil, fmt.Errorf("certificateFrom not specified")
} }
@ -235,6 +242,26 @@ type ProxyServerConfig struct {
CredentialSecretRef string `json:"credentialSecretRef,omitempty"` CredentialSecretRef string `json:"credentialSecretRef,omitempty"`
} }
type VaultConfig struct {
// +optional
Type vault.VaultType `json:"type,omitempty"`
// +optional
AzureKeyVault *AzureKeyVaultConfig `json:"azureKeyVault,omitempty"`
// +optional
Proxy *ProxyConfig `json:"proxy,omitempty"`
}
type AzureKeyVaultConfig struct {
// +required
URL string `json:"url,omitempty"`
// +required
TenantID string `json:"tenantId,omitempty"`
// +required
ClientID string `json:"clientId,omitempty"`
// +required
CertificatePath string `json:"certificatePath,omitempty"`
}
// MetricsConfig holds configuration parameters for each metric type // MetricsConfig holds configuration parameters for each metric type
type MetricsConfig struct { type MetricsConfig struct {
// +optional // +optional
@ -293,11 +320,15 @@ func (ars *AutoscalingRunnerSet) GitHubConfigUrl() string {
return ars.Spec.GitHubConfigUrl return ars.Spec.GitHubConfigUrl
} }
func (ars *AutoscalingRunnerSet) VaultConfig() *VaultConfig {
return ars.Spec.VaultConfig
}
func (ars *AutoscalingRunnerSet) Proxy() *ProxyConfig { func (ars *AutoscalingRunnerSet) Proxy() *ProxyConfig {
return ars.Spec.Proxy return ars.Spec.Proxy
} }
func (ars *AutoscalingRunnerSet) GitHubServerTLS() *GitHubServerTLSConfig { func (ars *AutoscalingRunnerSet) GitHubServerTLS() *TLSConfig {
return ars.Spec.GitHubServerTLS return ars.Spec.GitHubServerTLS
} }
@ -308,7 +339,7 @@ func (ars *AutoscalingRunnerSet) RunnerSetSpecHash() string {
RunnerGroup string RunnerGroup string
RunnerScaleSetName string RunnerScaleSetName string
Proxy *ProxyConfig Proxy *ProxyConfig
GitHubServerTLS *GitHubServerTLSConfig GitHubServerTLS *TLSConfig
Template corev1.PodTemplateSpec Template corev1.PodTemplateSpec
} }
spec := &runnerSetSpec{ spec := &runnerSetSpec{

View File

@ -79,10 +79,14 @@ func (er *EphemeralRunner) Proxy() *ProxyConfig {
return er.Spec.Proxy return er.Spec.Proxy
} }
func (er *EphemeralRunner) GitHubServerTLS() *GitHubServerTLSConfig { func (er *EphemeralRunner) GitHubServerTLS() *TLSConfig {
return er.Spec.GitHubServerTLS return er.Spec.GitHubServerTLS
} }
func (ars *EphemeralRunner) VaultConfig() *VaultConfig {
return ars.Spec.VaultConfig
}
// EphemeralRunnerSpec defines the desired state of EphemeralRunner // EphemeralRunnerSpec defines the desired state of EphemeralRunner
type EphemeralRunnerSpec struct { type EphemeralRunnerSpec struct {
// +required // +required
@ -101,7 +105,10 @@ type EphemeralRunnerSpec struct {
ProxySecretRef string `json:"proxySecretRef,omitempty"` ProxySecretRef string `json:"proxySecretRef,omitempty"`
// +optional // +optional
GitHubServerTLS *GitHubServerTLSConfig `json:"githubServerTLS,omitempty"` GitHubServerTLS *TLSConfig `json:"githubServerTLS,omitempty"`
// +optional
VaultConfig *VaultConfig `json:"vaultConfig,omitempty"`
corev1.PodTemplateSpec `json:",inline"` corev1.PodTemplateSpec `json:",inline"`
} }

View File

@ -72,10 +72,14 @@ func (ers *EphemeralRunnerSet) Proxy() *ProxyConfig {
return ers.Spec.EphemeralRunnerSpec.Proxy return ers.Spec.EphemeralRunnerSpec.Proxy
} }
func (ers *EphemeralRunnerSet) GitHubServerTLS() *GitHubServerTLSConfig { func (ers *EphemeralRunnerSet) GitHubServerTLS() *TLSConfig {
return ers.Spec.EphemeralRunnerSpec.GitHubServerTLS return ers.Spec.EphemeralRunnerSpec.GitHubServerTLS
} }
func (ars *EphemeralRunnerSet) VaultConfig() *VaultConfig {
return ars.Spec.EphemeralRunnerSpec.VaultConfig
}
// EphemeralRunnerSetList contains a list of EphemeralRunnerSet // EphemeralRunnerSetList contains a list of EphemeralRunnerSet
// +kubebuilder:object:root=true // +kubebuilder:object:root=true
type EphemeralRunnerSetList struct { type EphemeralRunnerSetList struct {

View File

@ -17,7 +17,7 @@ import (
func TestGitHubServerTLSConfig_ToCertPool(t *testing.T) { func TestGitHubServerTLSConfig_ToCertPool(t *testing.T) {
t.Run("returns an error if CertificateFrom not specified", func(t *testing.T) { t.Run("returns an error if CertificateFrom not specified", func(t *testing.T) {
c := &v1alpha1.GitHubServerTLSConfig{ c := &v1alpha1.TLSConfig{
CertificateFrom: nil, CertificateFrom: nil,
} }
@ -29,7 +29,7 @@ func TestGitHubServerTLSConfig_ToCertPool(t *testing.T) {
}) })
t.Run("returns an error if CertificateFrom.ConfigMapKeyRef not specified", func(t *testing.T) { t.Run("returns an error if CertificateFrom.ConfigMapKeyRef not specified", func(t *testing.T) {
c := &v1alpha1.GitHubServerTLSConfig{ c := &v1alpha1.TLSConfig{
CertificateFrom: &v1alpha1.TLSCertificateSource{}, CertificateFrom: &v1alpha1.TLSCertificateSource{},
} }
@ -41,7 +41,7 @@ func TestGitHubServerTLSConfig_ToCertPool(t *testing.T) {
}) })
t.Run("returns a valid cert pool with correct configuration", func(t *testing.T) { t.Run("returns a valid cert pool with correct configuration", func(t *testing.T) {
c := &v1alpha1.GitHubServerTLSConfig{ c := &v1alpha1.TLSConfig{
CertificateFrom: &v1alpha1.TLSCertificateSource{ CertificateFrom: &v1alpha1.TLSCertificateSource{
ConfigMapKeyRef: &v1.ConfigMapKeySelector{ ConfigMapKeyRef: &v1.ConfigMapKeySelector{
LocalObjectReference: v1.LocalObjectReference{ LocalObjectReference: v1.LocalObjectReference{

View File

@ -100,7 +100,12 @@ func (in *AutoscalingListenerSpec) DeepCopyInto(out *AutoscalingListenerSpec) {
} }
if in.GitHubServerTLS != nil { if in.GitHubServerTLS != nil {
in, out := &in.GitHubServerTLS, &out.GitHubServerTLS in, out := &in.GitHubServerTLS, &out.GitHubServerTLS
*out = new(GitHubServerTLSConfig) *out = new(TLSConfig)
(*in).DeepCopyInto(*out)
}
if in.VaultConfig != nil {
in, out := &in.VaultConfig, &out.VaultConfig
*out = new(VaultConfig)
(*in).DeepCopyInto(*out) (*in).DeepCopyInto(*out)
} }
if in.Metrics != nil { if in.Metrics != nil {
@ -209,7 +214,17 @@ func (in *AutoscalingRunnerSetSpec) DeepCopyInto(out *AutoscalingRunnerSetSpec)
} }
if in.GitHubServerTLS != nil { if in.GitHubServerTLS != nil {
in, out := &in.GitHubServerTLS, &out.GitHubServerTLS in, out := &in.GitHubServerTLS, &out.GitHubServerTLS
*out = new(GitHubServerTLSConfig) *out = new(TLSConfig)
(*in).DeepCopyInto(*out)
}
if in.VaultServerTLS != nil {
in, out := &in.VaultServerTLS, &out.VaultServerTLS
*out = new(TLSConfig)
(*in).DeepCopyInto(*out)
}
if in.VaultConfig != nil {
in, out := &in.VaultConfig, &out.VaultConfig
*out = new(VaultConfig)
(*in).DeepCopyInto(*out) (*in).DeepCopyInto(*out)
} }
in.Template.DeepCopyInto(&out.Template) in.Template.DeepCopyInto(&out.Template)
@ -260,6 +275,21 @@ func (in *AutoscalingRunnerSetStatus) DeepCopy() *AutoscalingRunnerSetStatus {
return out return out
} }
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *AzureKeyVaultConfig) DeepCopyInto(out *AzureKeyVaultConfig) {
*out = *in
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AzureKeyVaultConfig.
func (in *AzureKeyVaultConfig) DeepCopy() *AzureKeyVaultConfig {
if in == nil {
return nil
}
out := new(AzureKeyVaultConfig)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *CounterMetric) DeepCopyInto(out *CounterMetric) { func (in *CounterMetric) DeepCopyInto(out *CounterMetric) {
*out = *in *out = *in
@ -439,7 +469,12 @@ func (in *EphemeralRunnerSpec) DeepCopyInto(out *EphemeralRunnerSpec) {
} }
if in.GitHubServerTLS != nil { if in.GitHubServerTLS != nil {
in, out := &in.GitHubServerTLS, &out.GitHubServerTLS in, out := &in.GitHubServerTLS, &out.GitHubServerTLS
*out = new(GitHubServerTLSConfig) *out = new(TLSConfig)
(*in).DeepCopyInto(*out)
}
if in.VaultConfig != nil {
in, out := &in.VaultConfig, &out.VaultConfig
*out = new(VaultConfig)
(*in).DeepCopyInto(*out) (*in).DeepCopyInto(*out)
} }
in.PodTemplateSpec.DeepCopyInto(&out.PodTemplateSpec) in.PodTemplateSpec.DeepCopyInto(&out.PodTemplateSpec)
@ -497,26 +532,6 @@ func (in *GaugeMetric) DeepCopy() *GaugeMetric {
return out return out
} }
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *GitHubServerTLSConfig) DeepCopyInto(out *GitHubServerTLSConfig) {
*out = *in
if in.CertificateFrom != nil {
in, out := &in.CertificateFrom, &out.CertificateFrom
*out = new(TLSCertificateSource)
(*in).DeepCopyInto(*out)
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GitHubServerTLSConfig.
func (in *GitHubServerTLSConfig) DeepCopy() *GitHubServerTLSConfig {
if in == nil {
return nil
}
out := new(GitHubServerTLSConfig)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *HistogramMetric) DeepCopyInto(out *HistogramMetric) { func (in *HistogramMetric) DeepCopyInto(out *HistogramMetric) {
*out = *in *out = *in
@ -669,3 +684,48 @@ func (in *TLSCertificateSource) DeepCopy() *TLSCertificateSource {
in.DeepCopyInto(out) in.DeepCopyInto(out)
return out return out
} }
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *TLSConfig) DeepCopyInto(out *TLSConfig) {
*out = *in
if in.CertificateFrom != nil {
in, out := &in.CertificateFrom, &out.CertificateFrom
*out = new(TLSCertificateSource)
(*in).DeepCopyInto(*out)
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TLSConfig.
func (in *TLSConfig) DeepCopy() *TLSConfig {
if in == nil {
return nil
}
out := new(TLSConfig)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *VaultConfig) DeepCopyInto(out *VaultConfig) {
*out = *in
if in.AzureKeyVault != nil {
in, out := &in.AzureKeyVault, &out.AzureKeyVault
*out = new(AzureKeyVaultConfig)
**out = **in
}
if in.Proxy != nil {
in, out := &in.Proxy, &out.Proxy
*out = new(ProxyConfig)
(*in).DeepCopyInto(*out)
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VaultConfig.
func (in *VaultConfig) DeepCopy() *VaultConfig {
if in == nil {
return nil
}
out := new(VaultConfig)
in.DeepCopyInto(out)
return out
}

View File

@ -7863,6 +7863,53 @@ spec:
- containers - containers
type: object type: object
type: object type: object
vaultConfig:
properties:
azureKeyVault:
properties:
certificatePath:
type: string
clientId:
type: string
tenantId:
type: string
url:
type: string
required:
- certificatePath
- clientId
- tenantId
- url
type: object
proxy:
properties:
http:
properties:
credentialSecretRef:
type: string
url:
description: Required
type: string
type: object
https:
properties:
credentialSecretRef:
type: string
url:
description: Required
type: string
type: object
noProxy:
items:
type: string
type: array
type: object
type:
description: |-
VaultType represents the type of vault that can be used in the application.
It is used to identify which vault integration should be used to resolve secrets.
type: string
type: object
type: object type: object
status: status:
description: AutoscalingListenerStatus defines the observed state of AutoscalingListener description: AutoscalingListenerStatus defines the observed state of AutoscalingListener

View File

@ -15504,6 +15504,82 @@ spec:
- containers - containers
type: object type: object
type: object type: object
vaultConfig:
properties:
azureKeyVault:
properties:
certificatePath:
type: string
clientId:
type: string
tenantId:
type: string
url:
type: string
required:
- certificatePath
- clientId
- tenantId
- url
type: object
proxy:
properties:
http:
properties:
credentialSecretRef:
type: string
url:
description: Required
type: string
type: object
https:
properties:
credentialSecretRef:
type: string
url:
description: Required
type: string
type: object
noProxy:
items:
type: string
type: array
type: object
type:
description: |-
VaultType represents the type of vault that can be used in the application.
It is used to identify which vault integration should be used to resolve secrets.
type: string
type: object
vaultServerTLS:
properties:
certificateFrom:
description: Required
properties:
configMapKeyRef:
description: Required
properties:
key:
description: The key to select.
type: string
name:
default: ""
description: |-
Name of the referent.
This field is effectively required, but due to backwards compatibility is
allowed to be empty. Instances of this type with an empty value here are
almost certainly wrong.
More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names
type: string
optional:
description: Specify whether the ConfigMap or its key must be defined
type: boolean
required:
- key
type: object
x-kubernetes-map-type: atomic
type: object
type: object
type: object type: object
status: status:
description: AutoscalingRunnerSetStatus defines the observed state of AutoscalingRunnerSet description: AutoscalingRunnerSetStatus defines the observed state of AutoscalingRunnerSet

View File

@ -7784,6 +7784,53 @@ spec:
required: required:
- containers - containers
type: object type: object
vaultConfig:
properties:
azureKeyVault:
properties:
certificatePath:
type: string
clientId:
type: string
tenantId:
type: string
url:
type: string
required:
- certificatePath
- clientId
- tenantId
- url
type: object
proxy:
properties:
http:
properties:
credentialSecretRef:
type: string
url:
description: Required
type: string
type: object
https:
properties:
credentialSecretRef:
type: string
url:
description: Required
type: string
type: object
noProxy:
items:
type: string
type: array
type: object
type:
description: |-
VaultType represents the type of vault that can be used in the application.
It is used to identify which vault integration should be used to resolve secrets.
type: string
type: object
required: required:
- githubConfigSecret - githubConfigSecret
- githubConfigUrl - githubConfigUrl

View File

@ -7778,6 +7778,53 @@ spec:
required: required:
- containers - containers
type: object type: object
vaultConfig:
properties:
azureKeyVault:
properties:
certificatePath:
type: string
clientId:
type: string
tenantId:
type: string
url:
type: string
required:
- certificatePath
- clientId
- tenantId
- url
type: object
proxy:
properties:
http:
properties:
credentialSecretRef:
type: string
url:
description: Required
type: string
type: object
https:
properties:
credentialSecretRef:
type: string
url:
description: Required
type: string
type: object
noProxy:
items:
type: string
type: array
type: object
type:
description: |-
VaultType represents the type of vault that can be used in the application.
It is used to identify which vault integration should be used to resolve secrets.
type: string
type: object
required: required:
- githubConfigSecret - githubConfigSecret
- githubConfigUrl - githubConfigUrl

View File

@ -66,6 +66,24 @@ spec:
{{- end }} {{- end }}
{{- end }} {{- end }}
{{- if and .Values.keyVault .Values.keyVault.type }}
vaultConfig:
type: {{ .Values.keyVault.type }}
{{- if eq .Values.keyVault.type "azure_key_vault" }}
azureKeyVault:
url: {{ .Values.keyVault.azureKeyVault.url }}
tenantId: {{ .Values.keyVault.azureKeyVault.tenantId }}
clientId: {{ .Values.keyVault.azureKeyVault.clientId }}
certificatePath: {{ .Values.keyVault.azureKeyVault.certificatePath }}
secretKey: {{ .Values.keyVault.azureKeyVault.secretKey }}
{{- if .Values.keyVault.azureKeyVault.proxy }}
proxy: {{- toYaml .Values.keyVault.azureKeyVault.proxy | nindent 6 }}
{{- end }}
{{- else }}
{{- fail "Unsupported vaultConfig type: " .Values.vaultConfig.type }}
{{- end }}
{{- end }}
{{- if .Values.proxy }} {{- if .Values.proxy }}
proxy: proxy:
{{- if .Values.proxy.http }} {{- if .Values.proxy.http }}

View File

@ -1158,7 +1158,7 @@ func TestTemplateRenderedWithTLS(t *testing.T) {
ars := render(t, options) ars := render(t, options)
require.NotNil(t, ars.Spec.GitHubServerTLS) require.NotNil(t, ars.Spec.GitHubServerTLS)
expected := &v1alpha1.GitHubServerTLSConfig{ expected := &v1alpha1.TLSConfig{
CertificateFrom: &v1alpha1.TLSCertificateSource{ CertificateFrom: &v1alpha1.TLSCertificateSource{
ConfigMapKeyRef: &corev1.ConfigMapKeySelector{ ConfigMapKeyRef: &corev1.ConfigMapKeySelector{
LocalObjectReference: corev1.LocalObjectReference{ LocalObjectReference: corev1.LocalObjectReference{
@ -1218,7 +1218,7 @@ func TestTemplateRenderedWithTLS(t *testing.T) {
ars := render(t, options) ars := render(t, options)
require.NotNil(t, ars.Spec.GitHubServerTLS) require.NotNil(t, ars.Spec.GitHubServerTLS)
expected := &v1alpha1.GitHubServerTLSConfig{ expected := &v1alpha1.TLSConfig{
CertificateFrom: &v1alpha1.TLSCertificateSource{ CertificateFrom: &v1alpha1.TLSCertificateSource{
ConfigMapKeyRef: &corev1.ConfigMapKeySelector{ ConfigMapKeyRef: &corev1.ConfigMapKeySelector{
LocalObjectReference: corev1.LocalObjectReference{ LocalObjectReference: corev1.LocalObjectReference{
@ -1278,7 +1278,7 @@ func TestTemplateRenderedWithTLS(t *testing.T) {
ars := render(t, options) ars := render(t, options)
require.NotNil(t, ars.Spec.GitHubServerTLS) require.NotNil(t, ars.Spec.GitHubServerTLS)
expected := &v1alpha1.GitHubServerTLSConfig{ expected := &v1alpha1.TLSConfig{
CertificateFrom: &v1alpha1.TLSCertificateSource{ CertificateFrom: &v1alpha1.TLSCertificateSource{
ConfigMapKeyRef: &corev1.ConfigMapKeySelector{ ConfigMapKeyRef: &corev1.ConfigMapKeySelector{
LocalObjectReference: corev1.LocalObjectReference{ LocalObjectReference: corev1.LocalObjectReference{
@ -1338,7 +1338,7 @@ func TestTemplateRenderedWithTLS(t *testing.T) {
ars := render(t, options) ars := render(t, options)
require.NotNil(t, ars.Spec.GitHubServerTLS) require.NotNil(t, ars.Spec.GitHubServerTLS)
expected := &v1alpha1.GitHubServerTLSConfig{ expected := &v1alpha1.TLSConfig{
CertificateFrom: &v1alpha1.TLSCertificateSource{ CertificateFrom: &v1alpha1.TLSCertificateSource{
ConfigMapKeyRef: &corev1.ConfigMapKeySelector{ ConfigMapKeyRef: &corev1.ConfigMapKeySelector{
LocalObjectReference: corev1.LocalObjectReference{ LocalObjectReference: corev1.LocalObjectReference{
@ -1394,7 +1394,7 @@ func TestTemplateRenderedWithTLS(t *testing.T) {
ars := render(t, options) ars := render(t, options)
require.NotNil(t, ars.Spec.GitHubServerTLS) require.NotNil(t, ars.Spec.GitHubServerTLS)
expected := &v1alpha1.GitHubServerTLSConfig{ expected := &v1alpha1.TLSConfig{
CertificateFrom: &v1alpha1.TLSCertificateSource{ CertificateFrom: &v1alpha1.TLSCertificateSource{
ConfigMapKeyRef: &corev1.ConfigMapKeySelector{ ConfigMapKeyRef: &corev1.ConfigMapKeySelector{
LocalObjectReference: corev1.LocalObjectReference{ LocalObjectReference: corev1.LocalObjectReference{
@ -1450,7 +1450,7 @@ func TestTemplateRenderedWithTLS(t *testing.T) {
ars := render(t, options) ars := render(t, options)
require.NotNil(t, ars.Spec.GitHubServerTLS) require.NotNil(t, ars.Spec.GitHubServerTLS)
expected := &v1alpha1.GitHubServerTLSConfig{ expected := &v1alpha1.TLSConfig{
CertificateFrom: &v1alpha1.TLSCertificateSource{ CertificateFrom: &v1alpha1.TLSCertificateSource{
ConfigMapKeyRef: &corev1.ConfigMapKeySelector{ ConfigMapKeyRef: &corev1.ConfigMapKeySelector{
LocalObjectReference: corev1.LocalObjectReference{ LocalObjectReference: corev1.LocalObjectReference{

View File

@ -88,6 +88,27 @@ githubConfigSecret:
# key: ca.crt # key: ca.crt
# runnerMountPath: /usr/local/share/ca-certificates/ # runnerMountPath: /usr/local/share/ca-certificates/
# keyVault:
# Available values: "azure_key_vault"
# type: ""
# Configuration related to azure key vault
# azure_key_vault:
# url: ""
# client_id: ""
# tenant_id: ""
# certificate_path: ""
# proxy:
# http:
# url: http://proxy.com:1234
# credentialSecretRef: proxy-auth # a secret with `username` and `password` keys
# https:
# url: http://proxy.com:1234
# credentialSecretRef: proxy-auth # a secret with `username` and `password` keys
# noProxy:
# - example.com
# - example.org
## Container mode is an object that provides out-of-box configuration ## Container mode is an object that provides out-of-box configuration
## for dind and kubernetes mode. Template will be modified as documented under the ## for dind and kubernetes mode. Template will be modified as documented under the
## template object. ## template object.

View File

@ -15,6 +15,7 @@ import (
"github.com/actions/actions-runner-controller/github/actions" "github.com/actions/actions-runner-controller/github/actions"
"github.com/actions/actions-runner-controller/logging" "github.com/actions/actions-runner-controller/logging"
"github.com/actions/actions-runner-controller/vault" "github.com/actions/actions-runner-controller/vault"
"github.com/actions/actions-runner-controller/vault/azurekeyvault"
"github.com/go-logr/logr" "github.com/go-logr/logr"
"golang.org/x/net/http/httpproxy" "golang.org/x/net/http/httpproxy"
) )
@ -23,6 +24,8 @@ type Config struct {
ConfigureUrl string `json:"configure_url"` ConfigureUrl string `json:"configure_url"`
VaultType vault.VaultType `json:"vault_type"` VaultType vault.VaultType `json:"vault_type"`
VaultLookupKey string `json:"vault_lookup_key"` VaultLookupKey string `json:"vault_lookup_key"`
// If the VaultType is set to "azure_key_vault", this field must be populated.
AzureKeyVaultConfig *azurekeyvault.Config `json:"azure_key_vault,omitempty"`
// AppConfig contains the GitHub App configuration. // AppConfig contains the GitHub App configuration.
// It is initially set to nil if VaultType is set. // It is initially set to nil if VaultType is set.
// Otherwise, it is populated with the GitHub App credentials from the GitHub secret. // Otherwise, it is populated with the GitHub App credentials from the GitHub secret.
@ -53,31 +56,28 @@ func Read(ctx context.Context, configPath string) (*Config, error) {
return nil, fmt.Errorf("failed to decode config: %w", err) return nil, fmt.Errorf("failed to decode config: %w", err)
} }
if config.VaultType == "" { var vault vault.Vault
switch config.VaultType {
case "":
if err := config.Validate(); err != nil { if err := config.Validate(); err != nil {
return nil, fmt.Errorf("failed to validate configuration: %v", err) return nil, fmt.Errorf("failed to validate configuration: %v", err)
} }
return &config, nil return &config, nil
} case "azure_key_vault":
akv, err := azurekeyvault.New(*config.AzureKeyVaultConfig)
if err != nil {
return nil, fmt.Errorf("failed to create Azure Key Vault client: %w", err)
}
if config.VaultLookupKey == "" { vault = akv
panic(fmt.Errorf("vault type set to %q, but lookup key is empty", config.VaultType)) default:
} return nil, fmt.Errorf("unsupported vault type: %s", config.VaultType)
vaults, err := vault.InitAll("LISTENER_")
if err != nil {
return nil, fmt.Errorf("failed to initialize vaults: %v", err)
}
vault, ok := vaults[config.VaultType]
if !ok {
return nil, fmt.Errorf("vault %q is not initialized", config.VaultType)
} }
appConfigRaw, err := vault.GetSecret(ctx, config.VaultLookupKey) appConfigRaw, err := vault.GetSecret(ctx, config.VaultLookupKey)
if err != nil { if err != nil {
return nil, err return nil, fmt.Errorf("failed to get app config from vault: %w", err)
} }
appConfig, err := appconfig.FromString(appConfigRaw) appConfig, err := appconfig.FromString(appConfigRaw)

View File

@ -7863,6 +7863,53 @@ spec:
- containers - containers
type: object type: object
type: object type: object
vaultConfig:
properties:
azureKeyVault:
properties:
certificatePath:
type: string
clientId:
type: string
tenantId:
type: string
url:
type: string
required:
- certificatePath
- clientId
- tenantId
- url
type: object
proxy:
properties:
http:
properties:
credentialSecretRef:
type: string
url:
description: Required
type: string
type: object
https:
properties:
credentialSecretRef:
type: string
url:
description: Required
type: string
type: object
noProxy:
items:
type: string
type: array
type: object
type:
description: |-
VaultType represents the type of vault that can be used in the application.
It is used to identify which vault integration should be used to resolve secrets.
type: string
type: object
type: object type: object
status: status:
description: AutoscalingListenerStatus defines the observed state of AutoscalingListener description: AutoscalingListenerStatus defines the observed state of AutoscalingListener

View File

@ -15504,6 +15504,82 @@ spec:
- containers - containers
type: object type: object
type: object type: object
vaultConfig:
properties:
azureKeyVault:
properties:
certificatePath:
type: string
clientId:
type: string
tenantId:
type: string
url:
type: string
required:
- certificatePath
- clientId
- tenantId
- url
type: object
proxy:
properties:
http:
properties:
credentialSecretRef:
type: string
url:
description: Required
type: string
type: object
https:
properties:
credentialSecretRef:
type: string
url:
description: Required
type: string
type: object
noProxy:
items:
type: string
type: array
type: object
type:
description: |-
VaultType represents the type of vault that can be used in the application.
It is used to identify which vault integration should be used to resolve secrets.
type: string
type: object
vaultServerTLS:
properties:
certificateFrom:
description: Required
properties:
configMapKeyRef:
description: Required
properties:
key:
description: The key to select.
type: string
name:
default: ""
description: |-
Name of the referent.
This field is effectively required, but due to backwards compatibility is
allowed to be empty. Instances of this type with an empty value here are
almost certainly wrong.
More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names
type: string
optional:
description: Specify whether the ConfigMap or its key must be defined
type: boolean
required:
- key
type: object
x-kubernetes-map-type: atomic
type: object
type: object
type: object type: object
status: status:
description: AutoscalingRunnerSetStatus defines the observed state of AutoscalingRunnerSet description: AutoscalingRunnerSetStatus defines the observed state of AutoscalingRunnerSet

View File

@ -7784,6 +7784,53 @@ spec:
required: required:
- containers - containers
type: object type: object
vaultConfig:
properties:
azureKeyVault:
properties:
certificatePath:
type: string
clientId:
type: string
tenantId:
type: string
url:
type: string
required:
- certificatePath
- clientId
- tenantId
- url
type: object
proxy:
properties:
http:
properties:
credentialSecretRef:
type: string
url:
description: Required
type: string
type: object
https:
properties:
credentialSecretRef:
type: string
url:
description: Required
type: string
type: object
noProxy:
items:
type: string
type: array
type: object
type:
description: |-
VaultType represents the type of vault that can be used in the application.
It is used to identify which vault integration should be used to resolve secrets.
type: string
type: object
required: required:
- githubConfigSecret - githubConfigSecret
- githubConfigUrl - githubConfigUrl

View File

@ -7778,6 +7778,53 @@ spec:
required: required:
- containers - containers
type: object type: object
vaultConfig:
properties:
azureKeyVault:
properties:
certificatePath:
type: string
clientId:
type: string
tenantId:
type: string
url:
type: string
required:
- certificatePath
- clientId
- tenantId
- url
type: object
proxy:
properties:
http:
properties:
credentialSecretRef:
type: string
url:
description: Required
type: string
type: object
https:
properties:
credentialSecretRef:
type: string
url:
description: Required
type: string
type: object
noProxy:
items:
type: string
type: array
type: object
type:
description: |-
VaultType represents the type of vault that can be used in the application.
It is used to identify which vault integration should be used to resolve secrets.
type: string
type: object
required: required:
- githubConfigSecret - githubConfigSecret
- githubConfigUrl - githubConfigUrl

View File

@ -1065,7 +1065,7 @@ var _ = Describe("Test GitHub Server TLS configuration", func() {
Spec: v1alpha1.AutoscalingRunnerSetSpec{ Spec: v1alpha1.AutoscalingRunnerSetSpec{
GitHubConfigUrl: "https://github.com/owner/repo", GitHubConfigUrl: "https://github.com/owner/repo",
GitHubConfigSecret: configSecret.Name, GitHubConfigSecret: configSecret.Name,
GitHubServerTLS: &v1alpha1.GitHubServerTLSConfig{ GitHubServerTLS: &v1alpha1.TLSConfig{
CertificateFrom: &v1alpha1.TLSCertificateSource{ CertificateFrom: &v1alpha1.TLSCertificateSource{
ConfigMapKeyRef: &corev1.ConfigMapKeySelector{ ConfigMapKeyRef: &corev1.ConfigMapKeySelector{
LocalObjectReference: corev1.LocalObjectReference{ LocalObjectReference: corev1.LocalObjectReference{
@ -1101,7 +1101,7 @@ var _ = Describe("Test GitHub Server TLS configuration", func() {
Spec: v1alpha1.AutoscalingListenerSpec{ Spec: v1alpha1.AutoscalingListenerSpec{
GitHubConfigUrl: "https://github.com/owner/repo", GitHubConfigUrl: "https://github.com/owner/repo",
GitHubConfigSecret: configSecret.Name, GitHubConfigSecret: configSecret.Name,
GitHubServerTLS: &v1alpha1.GitHubServerTLSConfig{ GitHubServerTLS: &v1alpha1.TLSConfig{
CertificateFrom: &v1alpha1.TLSCertificateSource{ CertificateFrom: &v1alpha1.TLSCertificateSource{
ConfigMapKeyRef: &corev1.ConfigMapKeySelector{ ConfigMapKeyRef: &corev1.ConfigMapKeySelector{
LocalObjectReference: corev1.LocalObjectReference{ LocalObjectReference: corev1.LocalObjectReference{

View File

@ -1201,7 +1201,7 @@ var _ = Describe("Test client optional configuration", Ordered, func() {
Spec: v1alpha1.AutoscalingRunnerSetSpec{ Spec: v1alpha1.AutoscalingRunnerSetSpec{
GitHubConfigUrl: server.ConfigURLForOrg("my-org"), GitHubConfigUrl: server.ConfigURLForOrg("my-org"),
GitHubConfigSecret: configSecret.Name, GitHubConfigSecret: configSecret.Name,
GitHubServerTLS: &v1alpha1.GitHubServerTLSConfig{ GitHubServerTLS: &v1alpha1.TLSConfig{
CertificateFrom: &v1alpha1.TLSCertificateSource{ CertificateFrom: &v1alpha1.TLSCertificateSource{
ConfigMapKeyRef: &corev1.ConfigMapKeySelector{ ConfigMapKeyRef: &corev1.ConfigMapKeySelector{
LocalObjectReference: corev1.LocalObjectReference{ LocalObjectReference: corev1.LocalObjectReference{
@ -1254,7 +1254,7 @@ var _ = Describe("Test client optional configuration", Ordered, func() {
Spec: v1alpha1.AutoscalingRunnerSetSpec{ Spec: v1alpha1.AutoscalingRunnerSetSpec{
GitHubConfigUrl: "https://github.com/owner/repo", GitHubConfigUrl: "https://github.com/owner/repo",
GitHubConfigSecret: configSecret.Name, GitHubConfigSecret: configSecret.Name,
GitHubServerTLS: &v1alpha1.GitHubServerTLSConfig{ GitHubServerTLS: &v1alpha1.TLSConfig{
CertificateFrom: &v1alpha1.TLSCertificateSource{ CertificateFrom: &v1alpha1.TLSCertificateSource{
ConfigMapKeyRef: &corev1.ConfigMapKeySelector{ ConfigMapKeyRef: &corev1.ConfigMapKeySelector{
LocalObjectReference: corev1.LocalObjectReference{ LocalObjectReference: corev1.LocalObjectReference{
@ -1318,7 +1318,7 @@ var _ = Describe("Test client optional configuration", Ordered, func() {
Spec: v1alpha1.AutoscalingRunnerSetSpec{ Spec: v1alpha1.AutoscalingRunnerSetSpec{
GitHubConfigUrl: "https://github.com/owner/repo", GitHubConfigUrl: "https://github.com/owner/repo",
GitHubConfigSecret: configSecret.Name, GitHubConfigSecret: configSecret.Name,
GitHubServerTLS: &v1alpha1.GitHubServerTLSConfig{ GitHubServerTLS: &v1alpha1.TLSConfig{
CertificateFrom: &v1alpha1.TLSCertificateSource{ CertificateFrom: &v1alpha1.TLSCertificateSource{
ConfigMapKeyRef: &corev1.ConfigMapKeySelector{ ConfigMapKeyRef: &corev1.ConfigMapKeySelector{
LocalObjectReference: corev1.LocalObjectReference{ LocalObjectReference: corev1.LocalObjectReference{

View File

@ -43,8 +43,6 @@ const (
AnnotationKeyGitHubRunnerGroupName = "actions.github.com/runner-group-name" AnnotationKeyGitHubRunnerGroupName = "actions.github.com/runner-group-name"
AnnotationKeyGitHubRunnerScaleSetName = "actions.github.com/runner-scale-set-name" AnnotationKeyGitHubRunnerScaleSetName = "actions.github.com/runner-scale-set-name"
AnnotationKeyPatchID = "actions.github.com/patch-id" AnnotationKeyPatchID = "actions.github.com/patch-id"
AnnotationKeyGitHubVaultType = "actions.github.com/vault"
) )
// Labels applied to listener roles // Labels applied to listener roles

View File

@ -1093,7 +1093,7 @@ var _ = Describe("EphemeralRunner", func() {
ephemeralRunner := newExampleRunner("test-runner", autoScalingNS.Name, configSecret.Name) ephemeralRunner := newExampleRunner("test-runner", autoScalingNS.Name, configSecret.Name)
ephemeralRunner.Spec.GitHubConfigUrl = server.ConfigURLForOrg("my-org") ephemeralRunner.Spec.GitHubConfigUrl = server.ConfigURLForOrg("my-org")
ephemeralRunner.Spec.GitHubServerTLS = &v1alpha1.GitHubServerTLSConfig{ ephemeralRunner.Spec.GitHubServerTLS = &v1alpha1.TLSConfig{
CertificateFrom: &v1alpha1.TLSCertificateSource{ CertificateFrom: &v1alpha1.TLSCertificateSource{
ConfigMapKeyRef: &corev1.ConfigMapKeySelector{ ConfigMapKeyRef: &corev1.ConfigMapKeySelector{
LocalObjectReference: corev1.LocalObjectReference{ LocalObjectReference: corev1.LocalObjectReference{

View File

@ -1460,7 +1460,7 @@ var _ = Describe("Test EphemeralRunnerSet controller with custom root CA", func(
EphemeralRunnerSpec: v1alpha1.EphemeralRunnerSpec{ EphemeralRunnerSpec: v1alpha1.EphemeralRunnerSpec{
GitHubConfigUrl: server.ConfigURLForOrg("my-org"), GitHubConfigUrl: server.ConfigURLForOrg("my-org"),
GitHubConfigSecret: configSecret.Name, GitHubConfigSecret: configSecret.Name,
GitHubServerTLS: &v1alpha1.GitHubServerTLSConfig{ GitHubServerTLS: &v1alpha1.TLSConfig{
CertificateFrom: &v1alpha1.TLSCertificateSource{ CertificateFrom: &v1alpha1.TLSCertificateSource{
ConfigMapKeyRef: &corev1.ConfigMapKeySelector{ ConfigMapKeyRef: &corev1.ConfigMapKeySelector{
LocalObjectReference: corev1.LocalObjectReference{ LocalObjectReference: corev1.LocalObjectReference{

View File

@ -18,7 +18,7 @@ import (
"github.com/actions/actions-runner-controller/github/actions" "github.com/actions/actions-runner-controller/github/actions"
"github.com/actions/actions-runner-controller/hash" "github.com/actions/actions-runner-controller/hash"
"github.com/actions/actions-runner-controller/logging" "github.com/actions/actions-runner-controller/logging"
"github.com/actions/actions-runner-controller/vault" "github.com/actions/actions-runner-controller/vault/azurekeyvault"
corev1 "k8s.io/api/core/v1" corev1 "k8s.io/api/core/v1"
rbacv1 "k8s.io/api/rbac/v1" rbacv1 "k8s.io/api/rbac/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@ -110,10 +110,6 @@ func (b *ResourceBuilder) newAutoScalingListener(autoscalingRunnerSet *v1alpha1.
annotationKeyValuesHash: autoscalingRunnerSet.Annotations[annotationKeyValuesHash], annotationKeyValuesHash: autoscalingRunnerSet.Annotations[annotationKeyValuesHash],
} }
if v, ok := autoscalingRunnerSet.Annotations[AnnotationKeyGitHubVaultType]; ok {
annotations[AnnotationKeyGitHubVaultType] = v
}
if err := applyGitHubURLLabels(autoscalingRunnerSet.Spec.GitHubConfigUrl, labels); err != nil { if err := applyGitHubURLLabels(autoscalingRunnerSet.Spec.GitHubConfigUrl, labels); err != nil {
return nil, fmt.Errorf("failed to apply GitHub URL labels: %v", err) return nil, fmt.Errorf("failed to apply GitHub URL labels: %v", err)
} }
@ -128,6 +124,7 @@ func (b *ResourceBuilder) newAutoScalingListener(autoscalingRunnerSet *v1alpha1.
Spec: v1alpha1.AutoscalingListenerSpec{ Spec: v1alpha1.AutoscalingListenerSpec{
GitHubConfigUrl: autoscalingRunnerSet.Spec.GitHubConfigUrl, GitHubConfigUrl: autoscalingRunnerSet.Spec.GitHubConfigUrl,
GitHubConfigSecret: autoscalingRunnerSet.Spec.GitHubConfigSecret, GitHubConfigSecret: autoscalingRunnerSet.Spec.GitHubConfigSecret,
VaultConfig: autoscalingRunnerSet.VaultConfig(),
RunnerScaleSetId: runnerScaleSetId, RunnerScaleSetId: runnerScaleSetId,
AutoscalingRunnerSetNamespace: autoscalingRunnerSet.Namespace, AutoscalingRunnerSetNamespace: autoscalingRunnerSet.Namespace,
AutoscalingRunnerSetName: autoscalingRunnerSet.Name, AutoscalingRunnerSetName: autoscalingRunnerSet.Name,
@ -193,15 +190,18 @@ func (b *ResourceBuilder) newScaleSetListenerConfig(autoscalingListener *v1alpha
Metrics: autoscalingListener.Spec.Metrics, Metrics: autoscalingListener.Spec.Metrics,
} }
if ty, ok := autoscalingListener.Annotations[AnnotationKeyGitHubVaultType]; !ok { vault := autoscalingListener.Spec.VaultConfig
if vault == nil {
config.AppConfig = appConfig config.AppConfig = appConfig
} else { } else {
vaultType := vault.VaultType(ty) config.VaultType = vault.Type
if err := vaultType.Validate(); err != nil {
return nil, fmt.Errorf("vault type validation error: %w", err)
}
config.VaultType = vaultType
config.VaultLookupKey = autoscalingListener.Spec.GitHubConfigSecret config.VaultLookupKey = autoscalingListener.Spec.GitHubConfigSecret
config.AzureKeyVaultConfig = &azurekeyvault.Config{
TenantID: vault.AzureKeyVault.TenantID,
ClientID: vault.AzureKeyVault.ClientID,
URL: vault.AzureKeyVault.URL,
CertificatePath: vault.AzureKeyVault.CertificatePath,
}
} }
if err := config.Validate(); err != nil { if err := config.Validate(); err != nil {
@ -520,10 +520,6 @@ func (b *ResourceBuilder) newEphemeralRunnerSet(autoscalingRunnerSet *v1alpha1.A
annotationKeyRunnerSpecHash: runnerSpecHash, annotationKeyRunnerSpecHash: runnerSpecHash,
} }
if v, ok := autoscalingRunnerSet.Annotations[AnnotationKeyGitHubVaultType]; ok {
newAnnotations[AnnotationKeyGitHubVaultType] = v
}
newEphemeralRunnerSet := &v1alpha1.EphemeralRunnerSet{ newEphemeralRunnerSet := &v1alpha1.EphemeralRunnerSet{
TypeMeta: metav1.TypeMeta{}, TypeMeta: metav1.TypeMeta{},
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
@ -551,6 +547,7 @@ func (b *ResourceBuilder) newEphemeralRunnerSet(autoscalingRunnerSet *v1alpha1.A
Proxy: autoscalingRunnerSet.Spec.Proxy, Proxy: autoscalingRunnerSet.Spec.Proxy,
GitHubServerTLS: autoscalingRunnerSet.Spec.GitHubServerTLS, GitHubServerTLS: autoscalingRunnerSet.Spec.GitHubServerTLS,
PodTemplateSpec: autoscalingRunnerSet.Spec.Template, PodTemplateSpec: autoscalingRunnerSet.Spec.Template,
VaultConfig: autoscalingRunnerSet.VaultConfig(),
}, },
}, },
} }

View File

@ -12,6 +12,7 @@ import (
"github.com/actions/actions-runner-controller/apis/actions.github.com/v1alpha1/appconfig" "github.com/actions/actions-runner-controller/apis/actions.github.com/v1alpha1/appconfig"
"github.com/actions/actions-runner-controller/github/actions" "github.com/actions/actions-runner-controller/github/actions"
"github.com/actions/actions-runner-controller/vault" "github.com/actions/actions-runner-controller/vault"
"github.com/actions/actions-runner-controller/vault/azurekeyvault"
"golang.org/x/net/http/httpproxy" "golang.org/x/net/http/httpproxy"
corev1 "k8s.io/api/core/v1" corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/types"
@ -19,28 +20,20 @@ import (
) )
type SecretResolver struct { type SecretResolver struct {
k8sClient client.Client k8sClient client.Client
vaultResolvers map[vault.VaultType]resolver multiClient actions.MultiClient
multiClient actions.MultiClient
} }
type SecretResolverOption func(*SecretResolver) type SecretResolverOption func(*SecretResolver)
func WithVault(ty vault.VaultType, vault vault.Vault) SecretResolverOption {
return func(pool *SecretResolver) {
pool.vaultResolvers[ty] = &vaultResolver{vault}
}
}
func NewSecretResolver(k8sClient client.Client, multiClient actions.MultiClient, opts ...SecretResolverOption) *SecretResolver { func NewSecretResolver(k8sClient client.Client, multiClient actions.MultiClient, opts ...SecretResolverOption) *SecretResolver {
if k8sClient == nil { if k8sClient == nil {
panic("k8sClient must not be nil") panic("k8sClient must not be nil")
} }
secretResolver := &SecretResolver{ secretResolver := &SecretResolver{
k8sClient: k8sClient, k8sClient: k8sClient,
multiClient: multiClient, multiClient: multiClient,
vaultResolvers: make(map[vault.VaultType]resolver),
} }
for _, opt := range opts { for _, opt := range opts {
@ -55,7 +48,8 @@ type ActionsGitHubObject interface {
GitHubConfigUrl() string GitHubConfigUrl() string
GitHubConfigSecret() string GitHubConfigSecret() string
Proxy() *v1alpha1.ProxyConfig Proxy() *v1alpha1.ProxyConfig
GitHubServerTLS() *v1alpha1.GitHubServerTLSConfig GitHubServerTLS() *v1alpha1.TLSConfig
VaultConfig() *v1alpha1.VaultConfig
} }
func (sr *SecretResolver) GetAppConfig(ctx context.Context, obj ActionsGitHubObject) (*appconfig.AppConfig, error) { func (sr *SecretResolver) GetAppConfig(ctx context.Context, obj ActionsGitHubObject) (*appconfig.AppConfig, error) {
@ -163,25 +157,32 @@ func (sr *SecretResolver) GetActionsService(ctx context.Context, obj ActionsGitH
} }
func (sr *SecretResolver) resolverForObject(obj ActionsGitHubObject) (resolver, error) { func (sr *SecretResolver) resolverForObject(obj ActionsGitHubObject) (resolver, error) {
ty, ok := obj.GetAnnotations()[AnnotationKeyGitHubVaultType] vaultConfig := obj.VaultConfig()
if !ok { if vaultConfig == nil || vaultConfig.Type == "" {
return &k8sResolver{ return &k8sResolver{
namespace: obj.GetNamespace(), namespace: obj.GetNamespace(),
client: sr.k8sClient, client: sr.k8sClient,
}, nil }, nil
} }
vaultType := vault.VaultType(ty) switch vaultConfig.Type {
if err := vaultType.Validate(); err != nil { case vault.VaultTypeAzureKeyVault:
return nil, fmt.Errorf("invalid vault type %q: %v", ty, err) akv, err := azurekeyvault.New(azurekeyvault.Config{
} TenantID: vaultConfig.AzureKeyVault.TenantID,
ClientID: vaultConfig.AzureKeyVault.ClientID,
URL: vaultConfig.AzureKeyVault.URL,
CertificatePath: vaultConfig.AzureKeyVault.CertificatePath,
})
if err != nil {
return nil, fmt.Errorf("failed to create Azure Key Vault client: %v", err)
}
return &vaultResolver{
vault: akv,
}, nil
vault, ok := sr.vaultResolvers[vaultType] default:
if !ok { return nil, fmt.Errorf("unknown vault type %q", vaultConfig.Type)
return nil, fmt.Errorf("unknown vault resolver %q", ty)
} }
return vault, nil
} }
type resolver interface { type resolver interface {

13
main.go
View File

@ -32,7 +32,6 @@ import (
"github.com/actions/actions-runner-controller/github" "github.com/actions/actions-runner-controller/github"
"github.com/actions/actions-runner-controller/github/actions" "github.com/actions/actions-runner-controller/github/actions"
"github.com/actions/actions-runner-controller/logging" "github.com/actions/actions-runner-controller/logging"
"github.com/actions/actions-runner-controller/vault"
"github.com/kelseyhightower/envconfig" "github.com/kelseyhightower/envconfig"
corev1 "k8s.io/api/core/v1" corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
@ -275,21 +274,9 @@ func main() {
log.WithName("actions-clients"), log.WithName("actions-clients"),
) )
vaults, err := vault.InitAll("CONTROLLER_MANAGER_")
if err != nil {
log.Error(err, "unable to read vaults")
os.Exit(1)
}
var secretResolverOptions []actionsgithubcom.SecretResolverOption
for name, vault := range vaults {
secretResolverOptions = append(secretResolverOptions, actionsgithubcom.WithVault(name, vault))
}
secretResolver := actionsgithubcom.NewSecretResolver( secretResolver := actionsgithubcom.NewSecretResolver(
mgr.GetClient(), mgr.GetClient(),
actionsMultiClient, actionsMultiClient,
secretResolverOptions...,
) )
rb := actionsgithubcom.ResourceBuilder{ rb := actionsgithubcom.ResourceBuilder{

View File

@ -3,10 +3,8 @@ package azurekeyvault
import ( import (
"context" "context"
"fmt" "fmt"
"os"
"github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azsecrets" "github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azsecrets"
"github.com/actions/actions-runner-controller/proxyconfig"
) )
// AzureKeyVault is a struct that holds the Azure Key Vault client. // AzureKeyVault is a struct that holds the Azure Key Vault client.
@ -27,31 +25,6 @@ func New(cfg Config) (*AzureKeyVault, error) {
return &AzureKeyVault{client: client}, nil return &AzureKeyVault{client: client}, nil
} }
// FromEnv creates a new AzureKeyVault instance from environment variables.
// The environment variables should be prefixed with the provided prefix.
// For example, if the prefix is "AZURE_KEY_VAULT_", the environment variables should be:
// AZURE_KEY_VAULT_TENANT_ID, AZURE_KEY_VAULT_CLIENT_ID, AZURE_KEY_VAULT_URL,
// AZURE_KEY_VAULT_CERT_PATH, AZURE_KEY_VAULT_CERT_PASSWORD.
// The proxy configuration can be set using the environment variables prefixed with "PROXY_".
// For example, AZURE_KEY_VAULT_PROXY_HTTP_URL, AZURE_KEY_VAULT_PROXY_HTTP_USERNAME, etc.
func FromEnv(prefix string) (*AzureKeyVault, error) {
cfg := Config{
TenantID: os.Getenv(prefix + "TENANT_ID"),
ClientID: os.Getenv(prefix + "CLIENT_ID"),
URL: os.Getenv(prefix + "URL"),
CertPath: os.Getenv(prefix + "CERT_PATH"),
CertPassword: os.Getenv(prefix + "CERT_PASSWORD"),
}
proxyConfig, err := proxyconfig.ReadFromEnv(prefix + "PROXY_")
if err != nil {
return nil, fmt.Errorf("failed to read proxy config: %v", err)
}
cfg.Proxy = proxyConfig
return New(cfg)
}
// GetSecret retrieves a secret from Azure Key Vault. // GetSecret retrieves a secret from Azure Key Vault.
func (v *AzureKeyVault) GetSecret(ctx context.Context, name string) (string, error) { func (v *AzureKeyVault) GetSecret(ctx context.Context, name string) (string, error) {
secret, err := v.client.GetSecret(ctx, name, "", nil) secret, err := v.client.GetSecret(ctx, name, "", nil)

View File

@ -17,12 +17,11 @@ import (
// AzureKeyVault is a struct that holds the Azure Key Vault client. // AzureKeyVault is a struct that holds the Azure Key Vault client.
type Config struct { type Config struct {
TenantID string `json:"tenant_id"` TenantID string `json:"tenant_id"`
ClientID string `json:"client_id"` ClientID string `json:"client_id"`
URL string `json:"url"` URL string `json:"url"`
CertPath string `json:"cert_path"` CertificatePath string `json:"certificate_path"`
CertPassword string `json:"cert_password"` // optional Proxy *proxyconfig.ProxyConfig `json:"proxy,omitempty"`
Proxy *proxyconfig.ProxyConfig `json:"proxy,omitempty"`
} }
func (c *Config) Validate() error { func (c *Config) Validate() error {
@ -36,12 +35,12 @@ func (c *Config) Validate() error {
return fmt.Errorf("failed to parse url: %v", err) return fmt.Errorf("failed to parse url: %v", err)
} }
if c.CertPath == "" { if c.CertificatePath == "" {
return errors.New("cert path must be provided") return errors.New("cert path must be provided")
} }
if _, err := os.Stat(c.CertPath); err != nil { if _, err := os.Stat(c.CertificatePath); err != nil {
return fmt.Errorf("cert path %q does not exist: %v", c.CertPath, err) return fmt.Errorf("cert path %q does not exist: %v", c.CertificatePath, err)
} }
if err := c.Proxy.Validate(); err != nil { if err := c.Proxy.Validate(); err != nil {
@ -57,12 +56,12 @@ func (c *Config) Client() (*azsecrets.Client, error) {
} }
func (c *Config) certClient() (*azsecrets.Client, error) { func (c *Config) certClient() (*azsecrets.Client, error) {
data, err := os.ReadFile(c.CertPath) data, err := os.ReadFile(c.CertificatePath)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to read cert file from path %q: %v", c.CertPath, err) return nil, fmt.Errorf("failed to read cert file from path %q: %v", c.CertificatePath, err)
} }
certs, key, err := azidentity.ParseCertificates(data, []byte(c.CertPassword)) certs, key, err := azidentity.ParseCertificates(data, nil)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to parse certificates: %w", err) return nil, fmt.Errorf("failed to parse certificates: %w", err)
} }

View File

@ -43,43 +43,38 @@ func TestConfigValidate_invalid(t *testing.T) {
tt := map[string]*Config{ tt := map[string]*Config{
"empty": {}, "empty": {},
"no tenant id": { "no tenant id": {
TenantID: "", TenantID: "",
ClientID: clientID, ClientID: clientID,
URL: url, URL: url,
CertPath: certPath, CertificatePath: certPath,
CertPassword: "", Proxy: proxy,
Proxy: proxy,
}, },
"no client id": { "no client id": {
TenantID: tenantID, TenantID: tenantID,
ClientID: "", ClientID: "",
URL: url, URL: url,
CertPath: certPath, CertificatePath: certPath,
CertPassword: "", Proxy: proxy,
Proxy: proxy,
}, },
"no url": { "no url": {
TenantID: tenantID, TenantID: tenantID,
ClientID: clientID, ClientID: clientID,
URL: "", URL: "",
CertPath: certPath, CertificatePath: certPath,
CertPassword: "", Proxy: proxy,
Proxy: proxy,
}, },
"no jwt and no cert path": { "no jwt and no cert path": {
TenantID: tenantID, TenantID: tenantID,
ClientID: clientID, ClientID: clientID,
URL: url, URL: url,
CertPath: "", CertificatePath: "",
CertPassword: "", Proxy: proxy,
Proxy: proxy,
}, },
"invalid proxy": { "invalid proxy": {
TenantID: tenantID, TenantID: tenantID,
ClientID: clientID, ClientID: clientID,
URL: url, URL: url,
CertPath: certPath, CertificatePath: certPath,
CertPassword: "",
Proxy: &proxyconfig.ProxyConfig{ Proxy: &proxyconfig.ProxyConfig{
HTTP: &proxyconfig.ProxyServerConfig{}, HTTP: &proxyconfig.ProxyServerConfig{},
}, },
@ -120,19 +115,17 @@ func TestValidate_valid(t *testing.T) {
tt := map[string]*Config{ tt := map[string]*Config{
"with cert": { "with cert": {
TenantID: tenantID, TenantID: tenantID,
ClientID: clientID, ClientID: clientID,
URL: url, URL: url,
CertPath: certPath, CertificatePath: certPath,
CertPassword: "", Proxy: proxy,
Proxy: proxy,
}, },
"without proxy": { "without proxy": {
TenantID: tenantID, TenantID: tenantID,
ClientID: clientID, ClientID: clientID,
URL: url, URL: url,
CertPath: certPath, CertificatePath: certPath,
CertPassword: "",
}, },
} }

View File

@ -3,8 +3,6 @@ package vault
import ( import (
"context" "context"
"fmt" "fmt"
"os"
"strings"
"github.com/actions/actions-runner-controller/vault/azurekeyvault" "github.com/actions/actions-runner-controller/vault/azurekeyvault"
) )
@ -38,29 +36,3 @@ func (t VaultType) Validate() error {
// Compile-time checks // Compile-time checks
var _ Vault = (*azurekeyvault.AzureKeyVault)(nil) var _ Vault = (*azurekeyvault.AzureKeyVault)(nil)
// InitAll initializes all vaults based on the environment variables
// that start with the given prefix. It returns a map of vault types to their
// corresponding vault instances.
//
// Prefix is the namespace prefix used to filter environment variables.
// For example, the listener environment variable are prefixed with "LISTENER_", followed by the vault type, followed by the value.
//
// For example, listener has prefix "LISTENER_", has "AZURE_KEY_VAULT_" configured,
// and should read the vault URL. The environment variable will be "LISTENER_AZURE_KEY_VAULT_URL".
func InitAll(prefix string) (map[VaultType]Vault, error) {
envs := os.Environ()
result := make(map[VaultType]Vault)
for _, env := range envs {
if strings.HasPrefix(env, prefix+"AZURE_KEY_VAULT_") {
akv, err := azurekeyvault.FromEnv(prefix + "AZURE_KEY_VAULT_")
if err != nil {
return nil, fmt.Errorf("failed to instantiate azure key vault from env: %v", err)
}
result[VaultTypeAzureKeyVault] = akv
}
}
return result, nil
}