Azure Key Vault integration to resolve secrets (#4090)

This commit is contained in:
Nikola Jokic
2025-06-11 15:53:33 +02:00
committed by GitHub
parent d4af75d82e
commit e46c929241
48 changed files with 2013 additions and 599 deletions

View File

@@ -3,6 +3,7 @@ package fake
import (
"context"
"github.com/actions/actions-runner-controller/apis/actions.github.com/v1alpha1/appconfig"
"github.com/actions/actions-runner-controller/github/actions"
)
@@ -34,10 +35,6 @@ func NewMultiClient(opts ...MultiClientOption) actions.MultiClient {
return f
}
func (f *fakeMultiClient) GetClientFor(ctx context.Context, githubConfigURL string, creds actions.ActionsAuth, namespace string, options ...actions.ClientOption) (actions.ActionsService, error) {
return f.defaultClient, f.defaultErr
}
func (f *fakeMultiClient) GetClientFromSecret(ctx context.Context, githubConfigURL, namespace string, secretData actions.KubernetesSecretData, options ...actions.ClientOption) (actions.ActionsService, error) {
func (f *fakeMultiClient) GetClientFor(ctx context.Context, githubConfigURL string, appConfig *appconfig.AppConfig, namespace string, options ...actions.ClientOption) (actions.ActionsService, error) {
return f.defaultClient, f.defaultErr
}

View File

@@ -3,15 +3,14 @@ package actions
import (
"context"
"fmt"
"strconv"
"sync"
"github.com/actions/actions-runner-controller/apis/actions.github.com/v1alpha1/appconfig"
"github.com/go-logr/logr"
)
type MultiClient interface {
GetClientFor(ctx context.Context, githubConfigURL string, creds ActionsAuth, namespace string, options ...ClientOption) (ActionsService, error)
GetClientFromSecret(ctx context.Context, githubConfigURL, namespace string, secretData KubernetesSecretData, options ...ClientOption) (ActionsService, error)
GetClientFor(ctx context.Context, githubConfigURL string, appConfig *appconfig.AppConfig, namespace string, options ...ClientOption) (ActionsService, error)
}
type multiClient struct {
@@ -50,15 +49,22 @@ func NewMultiClient(logger logr.Logger) MultiClient {
}
}
func (m *multiClient) GetClientFor(ctx context.Context, githubConfigURL string, creds ActionsAuth, namespace string, options ...ClientOption) (ActionsService, error) {
func (m *multiClient) GetClientFor(ctx context.Context, githubConfigURL string, appConfig *appconfig.AppConfig, namespace string, options ...ClientOption) (ActionsService, error) {
m.logger.Info("retrieve actions client", "githubConfigURL", githubConfigURL, "namespace", namespace)
if creds.Token == "" && creds.AppCreds == nil {
return nil, fmt.Errorf("no credentials provided. either a PAT or GitHub App credentials should be provided")
if err := appConfig.Validate(); err != nil {
return nil, fmt.Errorf("failed to validate app config: %w", err)
}
if creds.Token != "" && creds.AppCreds != nil {
return nil, fmt.Errorf("both PAT and GitHub App credentials provided. should only provide one")
var creds ActionsAuth
if len(appConfig.Token) > 0 {
creds.Token = appConfig.Token
} else {
creds.AppCreds = &GitHubAppAuth{
AppID: appConfig.AppID,
AppInstallationID: appConfig.AppInstallationID,
AppPrivateKey: appConfig.AppPrivateKey,
}
}
client, err := NewClient(
@@ -69,7 +75,7 @@ func (m *multiClient) GetClientFor(ctx context.Context, githubConfigURL string,
}, options...)...,
)
if err != nil {
return nil, err
return nil, fmt.Errorf("failed to instantiate new client: %w", err)
}
m.mu.Lock()
@@ -94,42 +100,3 @@ func (m *multiClient) GetClientFor(ctx context.Context, githubConfigURL string,
return client, nil
}
type KubernetesSecretData map[string][]byte
func (m *multiClient) GetClientFromSecret(ctx context.Context, githubConfigURL, namespace string, secretData KubernetesSecretData, options ...ClientOption) (ActionsService, error) {
if len(secretData) == 0 {
return nil, fmt.Errorf("must provide secret data with either PAT or GitHub App Auth")
}
token := string(secretData["github_token"])
hasToken := len(token) > 0
appID := string(secretData["github_app_id"])
appInstallationID := string(secretData["github_app_installation_id"])
appPrivateKey := string(secretData["github_app_private_key"])
hasGitHubAppAuth := len(appID) > 0 && len(appInstallationID) > 0 && len(appPrivateKey) > 0
if hasToken && hasGitHubAppAuth {
return nil, fmt.Errorf("must provide secret with only PAT or GitHub App Auth to avoid ambiguity in client behavior")
}
if !hasToken && !hasGitHubAppAuth {
return nil, fmt.Errorf("neither PAT nor GitHub App Auth credentials provided in secret")
}
auth := ActionsAuth{}
if hasToken {
auth.Token = token
return m.GetClientFor(ctx, githubConfigURL, auth, namespace, options...)
}
parsedAppInstallationID, err := strconv.ParseInt(appInstallationID, 10, 64)
if err != nil {
return nil, err
}
auth.AppCreds = &GitHubAppAuth{AppID: appID, AppInstallationID: parsedAppInstallationID, AppPrivateKey: appPrivateKey}
return m.GetClientFor(ctx, githubConfigURL, auth, namespace, options...)
}

View File

@@ -5,6 +5,7 @@ import (
"fmt"
"testing"
"github.com/actions/actions-runner-controller/apis/actions.github.com/v1alpha1/appconfig"
"github.com/go-logr/logr"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
@@ -23,10 +24,13 @@ func TestMultiClientCaching(t *testing.T) {
defaultNamespace := "default"
defaultConfigURL := "https://github.com/org/repo"
defaultCreds := &ActionsAuth{
defaultCreds := &appconfig.AppConfig{
Token: "token",
}
client, err := NewClient(defaultConfigURL, defaultCreds)
defaultAuth := ActionsAuth{
Token: defaultCreds.Token,
}
client, err := NewClient(defaultConfigURL, &defaultAuth)
require.NoError(t, err)
multiClient.clients[ActionsClientKey{client.Identifier(), defaultNamespace}] = client
@@ -35,7 +39,7 @@ func TestMultiClientCaching(t *testing.T) {
cachedClient, err := multiClient.GetClientFor(
ctx,
defaultConfigURL,
*defaultCreds,
defaultCreds,
defaultNamespace,
)
require.NoError(t, err)
@@ -47,7 +51,7 @@ func TestMultiClientCaching(t *testing.T) {
newClient, err := multiClient.GetClientFor(
ctx,
defaultConfigURL,
*defaultCreds,
defaultCreds,
otherNamespace,
)
require.NoError(t, err)
@@ -63,7 +67,7 @@ func TestMultiClientOptions(t *testing.T) {
defaultConfigURL := "https://github.com/org/repo"
t.Run("GetClientFor", func(t *testing.T) {
defaultCreds := &ActionsAuth{
defaultCreds := &appconfig.AppConfig{
Token: "token",
}
@@ -71,7 +75,7 @@ func TestMultiClientOptions(t *testing.T) {
service, err := multiClient.GetClientFor(
ctx,
defaultConfigURL,
*defaultCreds,
defaultCreds,
defaultNamespace,
)
service.SetUserAgent(testUserAgent)
@@ -83,27 +87,6 @@ func TestMultiClientOptions(t *testing.T) {
require.NoError(t, err)
assert.Equal(t, testUserAgent.String(), req.Header.Get("User-Agent"))
})
t.Run("GetClientFromSecret", func(t *testing.T) {
secret := map[string][]byte{
"github_token": []byte("token"),
}
multiClient := NewMultiClient(logger)
service, err := multiClient.GetClientFromSecret(
ctx,
defaultConfigURL,
defaultNamespace,
secret,
)
service.SetUserAgent(testUserAgent)
require.NoError(t, err)
client := service.(*Client)
req, err := client.NewGitHubAPIRequest(ctx, "GET", "/test", nil)
require.NoError(t, err)
assert.Equal(t, testUserAgent.String(), req.Header.Get("User-Agent"))
})
}
func TestCreateJWT(t *testing.T) {