Skip to content

Azure Key Vault integration to resolve secrets #4090

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 13 commits into
base: master
Choose a base branch
from
Draft
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
89 changes: 89 additions & 0 deletions apis/actions.github.com/v1alpha1/appconfig/appconfig.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
package appconfig

import (
"bytes"
"encoding/json"
"fmt"
"strconv"

corev1 "k8s.io/api/core/v1"
)

type AppConfig struct {
AppID string `json:"github_app_id"`
AppInstallationID int64 `json:"github_app_installation_id"`
AppPrivateKey string `json:"github_app_private_key"`

Token string `json:"github_token"`
}

func (c *AppConfig) tidy() *AppConfig {
if len(c.Token) > 0 {
return &AppConfig{
Token: c.Token,
}
}

return &AppConfig{
AppID: c.AppID,
AppInstallationID: c.AppInstallationID,
AppPrivateKey: c.AppPrivateKey,
}
}

func (c *AppConfig) Validate() error {
if c == nil {
return fmt.Errorf("missing app config")
}
hasToken := len(c.Token) > 0
hasGitHubAppAuth := c.hasGitHubAppAuth()
if hasToken && hasGitHubAppAuth {
return fmt.Errorf("both PAT and GitHub App credentials provided. should only provide one")
}
if !hasToken && !hasGitHubAppAuth {
return fmt.Errorf("no credentials provided: either a PAT or GitHub App credentials should be provided")
}

return nil
}

func (c *AppConfig) hasGitHubAppAuth() bool {
return len(c.AppID) > 0 && c.AppInstallationID > 0 && len(c.AppPrivateKey) > 0
}

func FromSecret(secret *corev1.Secret) (*AppConfig, error) {
var appInstallationID int64
if v := string(secret.Data["github_app_installation_id"]); v != "" {
val, err := strconv.ParseInt(v, 10, 64)
if err != nil {
return nil, err
}
appInstallationID = val
}

cfg := &AppConfig{
Token: string(secret.Data["github_token"]),
AppID: string(secret.Data["github_app_id"]),
AppInstallationID: appInstallationID,
AppPrivateKey: string(secret.Data["github_app_private_key"]),
}

if err := cfg.Validate(); err != nil {
return nil, fmt.Errorf("failed to validate config: %v", err)
}

return cfg.tidy(), nil
}

func FromString(v string) (*AppConfig, error) {
var appConfig AppConfig
if err := json.NewDecoder(bytes.NewBufferString(v)).Decode(&appConfig); err != nil {
return nil, err
}

if err := appConfig.Validate(); err != nil {
return nil, fmt.Errorf("failed to validate app config decoded from string: %w", err)
}

return appConfig.tidy(), nil
}
152 changes: 152 additions & 0 deletions apis/actions.github.com/v1alpha1/appconfig/appconfig_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
package appconfig

import (
"encoding/json"
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
corev1 "k8s.io/api/core/v1"
)

func TestAppConfigValidate_invalid(t *testing.T) {
tt := map[string]*AppConfig{
"empty": {},
"token and app config": {
AppID: "1",
AppInstallationID: 2,
AppPrivateKey: "private key",
Token: "token",
},
"app id not set": {
AppInstallationID: 2,
AppPrivateKey: "private key",
},
"app installation id not set": {
AppID: "2",
AppPrivateKey: "private key",
},
"private key empty": {
AppID: "2",
AppInstallationID: 1,
AppPrivateKey: "",
},
}

for name, cfg := range tt {
t.Run(name, func(t *testing.T) {
err := cfg.Validate()
require.Error(t, err)
})
}
}

func TestAppConfigValidate_valid(t *testing.T) {
tt := map[string]*AppConfig{
"token": {
Token: "token",
},
"app ID": {
AppID: "1",
AppInstallationID: 2,
AppPrivateKey: "private key",
},
}

for name, cfg := range tt {
t.Run(name, func(t *testing.T) {
err := cfg.Validate()
require.NoError(t, err)
})
}
}

func TestAppConfigFromSecret_invalid(t *testing.T) {
tt := map[string]map[string]string{
"empty": {},
"token and app provided": {
"github_token": "token",
"github_app_id": "2",
"githu_app_installation_id": "3",
"github_app_private_key": "private key",
},
"invalid app id": {
"github_app_id": "abc",
"githu_app_installation_id": "3",
"github_app_private_key": "private key",
},
"invalid app installation_id": {
"github_app_id": "1",
"githu_app_installation_id": "abc",
"github_app_private_key": "private key",
},
"empty private key": {
"github_app_id": "1",
"githu_app_installation_id": "2",
"github_app_private_key": "",
},
}

for name, data := range tt {
t.Run(name, func(t *testing.T) {
secret := &corev1.Secret{
StringData: data,
}

appConfig, err := FromSecret(secret)
assert.Error(t, err)
assert.Nil(t, appConfig)
})
}
}

func TestAppConfigFromSecret_valid(t *testing.T) {
tt := map[string]map[string]string{
"with token": {
"github_token": "token",
},
"app config": {
"github_app_id": "2",
"githu_app_installation_id": "3",
"github_app_private_key": "private key",
},
}

for name, data := range tt {
t.Run(name, func(t *testing.T) {
secret := &corev1.Secret{
StringData: data,
}

appConfig, err := FromSecret(secret)
assert.Error(t, err)
assert.Nil(t, appConfig)
})
}
}

func TestAppConfigFromString_valid(t *testing.T) {
tt := map[string]*AppConfig{
"token": {
Token: "token",
},
"app ID": {
AppID: "1",
AppInstallationID: 2,
AppPrivateKey: "private key",
},
}

for name, cfg := range tt {
t.Run(name, func(t *testing.T) {
bytes, err := json.Marshal(cfg)
require.NoError(t, err)

got, err := FromString(string(bytes))
require.NoError(t, err)

want := cfg.tidy()
assert.Equal(t, want, got)
})
}
}
22 changes: 20 additions & 2 deletions apis/actions.github.com/v1alpha1/autoscalinglistener_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,10 @@ type AutoscalingListenerSpec struct {
Proxy *ProxyConfig `json:"proxy,omitempty"`

// +optional
GitHubServerTLS *GitHubServerTLSConfig `json:"githubServerTLS,omitempty"`
GitHubServerTLS *TLSConfig `json:"githubServerTLS,omitempty"`

// +optional
VaultConfig *VaultConfig `json:"vaultConfig,omitempty"`

// +optional
Metrics *MetricsConfig `json:"metrics,omitempty"`
Expand All @@ -86,8 +89,23 @@ type AutoscalingListener struct {
Status AutoscalingListenerStatus `json:"status,omitempty"`
}

// +kubebuilder:object:root=true
func (l *AutoscalingListener) GitHubConfigSecret() string {
return l.Spec.GitHubConfigSecret
}

func (l *AutoscalingListener) GitHubConfigUrl() string {
return l.Spec.GitHubConfigUrl
}

func (l *AutoscalingListener) Proxy() *ProxyConfig {
return l.Spec.Proxy
}

func (l *AutoscalingListener) GitHubServerTLS() *TLSConfig {
return l.Spec.GitHubServerTLS
}

// +kubebuilder:object:root=true
// AutoscalingListenerList contains a list of AutoscalingListener
type AutoscalingListenerList struct {
metav1.TypeMeta `json:",inline"`
Expand Down
55 changes: 51 additions & 4 deletions apis/actions.github.com/v1alpha1/autoscalingrunnerset_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import (
"strings"

"github.com/actions/actions-runner-controller/hash"
"github.com/actions/actions-runner-controller/vault"
"golang.org/x/net/http/httpproxy"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
Expand Down Expand Up @@ -69,7 +70,13 @@ type AutoscalingRunnerSetSpec struct {
Proxy *ProxyConfig `json:"proxy,omitempty"`

// +optional
GitHubServerTLS *GitHubServerTLSConfig `json:"githubServerTLS,omitempty"`
GitHubServerTLS *TLSConfig `json:"githubServerTLS,omitempty"`

// +optional
VaultServerTLS *TLSConfig `json:"vaultServerTLS,omitempty"`

// +optional
VaultConfig *VaultConfig `json:"vaultConfig,omitempty"`

// Required
Template corev1.PodTemplateSpec `json:"template,omitempty"`
Expand All @@ -89,12 +96,12 @@ type AutoscalingRunnerSetSpec struct {
MinRunners *int `json:"minRunners,omitempty"`
}

type GitHubServerTLSConfig struct {
type TLSConfig struct {
// Required
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 {
return nil, fmt.Errorf("certificateFrom not specified")
}
Expand Down Expand Up @@ -235,6 +242,26 @@ type ProxyServerConfig struct {
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
type MetricsConfig struct {
// +optional
Expand Down Expand Up @@ -285,14 +312,34 @@ func (ars *AutoscalingRunnerSet) ListenerSpecHash() string {
return hash.ComputeTemplateHash(&spec)
}

func (ars *AutoscalingRunnerSet) GitHubConfigSecret() string {
return ars.Spec.GitHubConfigSecret
}

func (ars *AutoscalingRunnerSet) GitHubConfigUrl() string {
return ars.Spec.GitHubConfigUrl
}

func (ars *AutoscalingRunnerSet) VaultConfig() *VaultConfig {
return ars.Spec.VaultConfig
}

func (ars *AutoscalingRunnerSet) Proxy() *ProxyConfig {
return ars.Spec.Proxy
}

func (ars *AutoscalingRunnerSet) GitHubServerTLS() *TLSConfig {
return ars.Spec.GitHubServerTLS
}

func (ars *AutoscalingRunnerSet) RunnerSetSpecHash() string {
type runnerSetSpec struct {
GitHubConfigUrl string
GitHubConfigSecret string
RunnerGroup string
RunnerScaleSetName string
Proxy *ProxyConfig
GitHubServerTLS *GitHubServerTLSConfig
GitHubServerTLS *TLSConfig
Template corev1.PodTemplateSpec
}
spec := &runnerSetSpec{
Expand Down
Loading
Loading