2022-04-13 16:47:42 +00:00
|
|
|
package runner
|
|
|
|
|
|
|
|
|
|
import (
|
2022-04-15 15:22:47 +00:00
|
|
|
"context"
|
2022-04-23 13:05:40 +00:00
|
|
|
"crypto/hmac"
|
|
|
|
|
"crypto/sha1"
|
|
|
|
|
"crypto/sha256"
|
|
|
|
|
"encoding/hex"
|
|
|
|
|
"encoding/json"
|
|
|
|
|
"hash"
|
2022-04-18 17:26:13 +00:00
|
|
|
"io/ioutil"
|
2022-04-26 20:29:58 +00:00
|
|
|
"log"
|
2022-04-18 17:26:13 +00:00
|
|
|
"os"
|
|
|
|
|
"path/filepath"
|
2022-04-26 20:29:58 +00:00
|
|
|
"strings"
|
|
|
|
|
"sync"
|
|
|
|
|
|
2022-04-29 14:18:22 +00:00
|
|
|
"runner-manager/auth"
|
2022-04-13 16:47:42 +00:00
|
|
|
"runner-manager/config"
|
2022-04-26 20:29:58 +00:00
|
|
|
"runner-manager/database"
|
|
|
|
|
dbCommon "runner-manager/database/common"
|
2022-04-29 14:18:22 +00:00
|
|
|
runnerErrors "runner-manager/errors"
|
2022-04-23 13:05:40 +00:00
|
|
|
"runner-manager/params"
|
2022-04-13 16:47:42 +00:00
|
|
|
"runner-manager/runner/common"
|
2022-04-28 16:13:20 +00:00
|
|
|
"runner-manager/runner/pool"
|
2022-04-22 14:46:27 +00:00
|
|
|
"runner-manager/runner/providers"
|
2022-04-18 17:26:13 +00:00
|
|
|
"runner-manager/util"
|
2022-04-13 16:47:42 +00:00
|
|
|
|
2022-04-18 17:26:13 +00:00
|
|
|
"github.com/pkg/errors"
|
|
|
|
|
"golang.org/x/crypto/ssh"
|
2022-04-13 16:47:42 +00:00
|
|
|
)
|
|
|
|
|
|
2022-04-22 14:46:27 +00:00
|
|
|
func NewRunner(ctx context.Context, cfg config.Config) (*Runner, error) {
|
2022-04-27 16:56:28 +00:00
|
|
|
// ghc, err := util.GithubClient(ctx, cfg.Github.OAuth2Token)
|
|
|
|
|
// if err != nil {
|
|
|
|
|
// return nil, errors.Wrap(err, "getting github client")
|
|
|
|
|
// }
|
2022-04-22 14:46:27 +00:00
|
|
|
|
|
|
|
|
providers, err := providers.LoadProvidersFromConfig(ctx, cfg, "")
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, errors.Wrap(err, "loading providers")
|
|
|
|
|
}
|
2022-04-26 20:29:58 +00:00
|
|
|
db, err := database.NewDatabase(ctx, cfg.Database)
|
|
|
|
|
if err != nil {
|
|
|
|
|
log.Fatal(err)
|
|
|
|
|
}
|
2022-04-22 14:46:27 +00:00
|
|
|
|
2022-04-29 14:18:22 +00:00
|
|
|
creds := map[string]config.Github{}
|
|
|
|
|
|
|
|
|
|
for _, ghcreds := range cfg.Github {
|
|
|
|
|
creds[ghcreds.Name] = ghcreds
|
|
|
|
|
}
|
2022-04-18 17:26:13 +00:00
|
|
|
runner := &Runner{
|
2022-04-27 16:56:28 +00:00
|
|
|
ctx: ctx,
|
|
|
|
|
config: cfg,
|
2022-04-28 16:13:20 +00:00
|
|
|
store: db,
|
2022-04-27 16:56:28 +00:00
|
|
|
// ghc: ghc,
|
2022-04-28 16:13:20 +00:00
|
|
|
repositories: map[string]common.PoolManager{},
|
|
|
|
|
organizations: map[string]common.PoolManager{},
|
|
|
|
|
providers: providers,
|
2022-04-29 14:18:22 +00:00
|
|
|
credentials: creds,
|
2022-04-18 17:26:13 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if err := runner.ensureSSHKeys(); err != nil {
|
|
|
|
|
return nil, errors.Wrap(err, "ensuring SSH keys")
|
|
|
|
|
}
|
|
|
|
|
|
2022-04-28 16:13:20 +00:00
|
|
|
if err := runner.loadReposAndOrgs(); err != nil {
|
|
|
|
|
return nil, errors.Wrap(err, "loading pool managers")
|
|
|
|
|
}
|
|
|
|
|
|
2022-04-18 17:26:13 +00:00
|
|
|
return runner, nil
|
2022-04-15 15:22:47 +00:00
|
|
|
}
|
|
|
|
|
|
2022-04-13 16:47:42 +00:00
|
|
|
type Runner struct {
|
2022-04-18 17:26:13 +00:00
|
|
|
mux sync.Mutex
|
|
|
|
|
|
2022-04-15 15:22:47 +00:00
|
|
|
ctx context.Context
|
2022-04-27 16:56:28 +00:00
|
|
|
// ghc *github.Client
|
2022-04-28 16:13:20 +00:00
|
|
|
store dbCommon.Store
|
2022-04-13 16:47:42 +00:00
|
|
|
|
2022-04-22 14:46:27 +00:00
|
|
|
controllerID string
|
|
|
|
|
|
|
|
|
|
config config.Config
|
|
|
|
|
repositories map[string]common.PoolManager
|
|
|
|
|
organizations map[string]common.PoolManager
|
|
|
|
|
providers map[string]common.Provider
|
2022-04-29 14:18:22 +00:00
|
|
|
credentials map[string]config.Github
|
2022-04-22 14:46:27 +00:00
|
|
|
}
|
|
|
|
|
|
2022-04-29 14:18:22 +00:00
|
|
|
func (r *Runner) CreateRepository(ctx context.Context, param params.CreateRepoParams) (repo params.Repository, err error) {
|
|
|
|
|
if !auth.IsAdmin(ctx) {
|
|
|
|
|
return repo, runnerErrors.ErrUnauthorized
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if err := param.Validate(); err != nil {
|
|
|
|
|
return params.Repository{}, errors.Wrap(err, "validating params")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
creds, ok := r.credentials[param.CredentialsName]
|
|
|
|
|
if !ok {
|
|
|
|
|
return params.Repository{}, runnerErrors.NewBadRequestError("credentials %s not defined", param.CredentialsName)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_, err = r.store.GetRepository(ctx, param.Owner, param.Name)
|
|
|
|
|
if err != nil {
|
|
|
|
|
if !errors.Is(err, runnerErrors.ErrNotFound) {
|
|
|
|
|
return params.Repository{}, errors.Wrap(err, "fetching repo")
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
return params.Repository{}, runnerErrors.NewConflictError("repository %s/%s already exists", param.Owner, param.Name)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
repo, err = r.store.CreateRepository(ctx, param.Owner, param.Name, creds.Name, param.WebhookSecret)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return params.Repository{}, errors.Wrap(err, "creating repository")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
defer func() {
|
|
|
|
|
if err != nil {
|
|
|
|
|
r.store.DeleteRepository(ctx, repo.ID, true)
|
|
|
|
|
}
|
|
|
|
|
}()
|
|
|
|
|
|
|
|
|
|
poolMgr, err := r.loadRepoPoolManager(repo)
|
|
|
|
|
if err := poolMgr.Start(); err != nil {
|
|
|
|
|
return params.Repository{}, errors.Wrap(err, "starting pool manager")
|
|
|
|
|
}
|
|
|
|
|
r.repositories[repo.ID] = poolMgr
|
|
|
|
|
return repo, nil
|
2022-04-28 16:13:20 +00:00
|
|
|
}
|
|
|
|
|
|
2022-04-29 14:18:22 +00:00
|
|
|
func (r *Runner) ListRepositories(ctx context.Context) ([]params.Repository, error) {
|
|
|
|
|
if !auth.IsAdmin(ctx) {
|
|
|
|
|
return nil, runnerErrors.ErrUnauthorized
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
repos, err := r.store.ListRepositories(ctx)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, errors.Wrap(err, "listing repositories")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return repos, nil
|
2022-04-28 16:13:20 +00:00
|
|
|
}
|
|
|
|
|
|
2022-04-29 14:18:22 +00:00
|
|
|
func (r *Runner) GetRepositoryByID(ctx context.Context, repoID string) (params.Repository, error) {
|
|
|
|
|
if !auth.IsAdmin(ctx) {
|
|
|
|
|
return params.Repository{}, runnerErrors.ErrUnauthorized
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
repo, err := r.store.GetRepositoryByID(ctx, repoID)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return params.Repository{}, errors.Wrap(err, "fetching repository")
|
|
|
|
|
}
|
|
|
|
|
return repo, nil
|
2022-04-28 16:13:20 +00:00
|
|
|
}
|
|
|
|
|
|
2022-04-29 14:18:22 +00:00
|
|
|
func (r *Runner) DeleteRepository(ctx context.Context, repoID string) error {
|
|
|
|
|
if !auth.IsAdmin(ctx) {
|
|
|
|
|
return runnerErrors.ErrUnauthorized
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
repo, err := r.store.GetRepositoryByID(ctx, repoID)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return errors.Wrap(err, "fetching repo")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
poolMgr, ok := r.repositories[repo.ID]
|
|
|
|
|
if ok {
|
|
|
|
|
if err := poolMgr.Stop(); err != nil {
|
|
|
|
|
log.Printf("failed to stop pool for repo %s", repo.ID)
|
|
|
|
|
}
|
|
|
|
|
delete(r.repositories, repoID)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pools, err := r.store.ListRepoPools(ctx, repoID)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return errors.Wrap(err, "fetching repo pools")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if len(pools) > 0 {
|
|
|
|
|
poolIds := []string{}
|
|
|
|
|
for _, pool := range pools {
|
|
|
|
|
poolIds = append(poolIds, pool.ID)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return runnerErrors.NewBadRequestError("repo has pools defined (%s)", strings.Join(poolIds, ", "))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if err := r.store.DeleteRepository(ctx, repoID, true); err != nil {
|
|
|
|
|
return errors.Wrap(err, "removing repository")
|
|
|
|
|
}
|
2022-04-28 16:13:20 +00:00
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
2022-04-29 14:18:22 +00:00
|
|
|
func (r *Runner) UpdateRepository(ctx context.Context, repoID string, param params.UpdateRepositoryParams) (params.Repository, error) {
|
|
|
|
|
if !auth.IsAdmin(ctx) {
|
|
|
|
|
return params.Repository{}, runnerErrors.ErrUnauthorized
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
r.mux.Lock()
|
|
|
|
|
defer r.mux.Unlock()
|
|
|
|
|
|
|
|
|
|
repo, err := r.store.GetRepositoryByID(ctx, repoID)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return params.Repository{}, errors.Wrap(err, "fetching repo")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if param.CredentialsName != "" {
|
|
|
|
|
// Check that credentials are set before saving to db
|
|
|
|
|
if _, ok := r.credentials[param.CredentialsName]; !ok {
|
|
|
|
|
return params.Repository{}, runnerErrors.NewBadRequestError("invalid credentials (%s) for repo %s/%s", param.CredentialsName, repo.Owner, repo.Name)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
repo, err = r.store.UpdateRepository(ctx, repoID, param)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return params.Repository{}, errors.Wrap(err, "updating repo")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
log.Printf("post-update webhook secret: %s", repo.WebhookSecret)
|
|
|
|
|
poolMgr, ok := r.repositories[repo.ID]
|
|
|
|
|
if ok {
|
|
|
|
|
internalCfg, err := r.getInternalConfig(repo)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return params.Repository{}, errors.Wrap(err, "fetching internal config")
|
|
|
|
|
}
|
|
|
|
|
repo.Internal = internalCfg
|
|
|
|
|
// stop the pool mgr
|
|
|
|
|
if err := poolMgr.RefreshState(repo); err != nil {
|
|
|
|
|
return params.Repository{}, errors.Wrap(err, "updating pool manager")
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
poolMgr, err := r.loadRepoPoolManager(repo)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return params.Repository{}, errors.Wrap(err, "loading pool manager")
|
|
|
|
|
}
|
|
|
|
|
r.repositories[repo.ID] = poolMgr
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return repo, nil
|
2022-04-28 16:13:20 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (r *Runner) CreateRepoPool(ctx context.Context) error {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (r *Runner) DeleteRepoPool(ctx context.Context) error {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (r *Runner) ListRepoPools(ctx context.Context) error {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (r *Runner) UpdateRepoPool(ctx context.Context) error {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (r *Runner) ListPoolInstances(ctx context.Context) error {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (r *Runner) ListCredentials(ctx context.Context) ([]params.GithubCredentials, error) {
|
|
|
|
|
ret := []params.GithubCredentials{}
|
|
|
|
|
|
|
|
|
|
for _, val := range r.config.Github {
|
|
|
|
|
ret = append(ret, params.GithubCredentials{
|
|
|
|
|
Name: val.Name,
|
|
|
|
|
Description: val.Description,
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
return ret, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (r *Runner) ListProviders(ctx context.Context) ([]params.Provider, error) {
|
|
|
|
|
ret := []params.Provider{}
|
|
|
|
|
|
|
|
|
|
for _, val := range r.providers {
|
|
|
|
|
ret = append(ret, val.AsParams())
|
|
|
|
|
}
|
|
|
|
|
return ret, nil
|
|
|
|
|
}
|
|
|
|
|
|
2022-04-29 14:18:22 +00:00
|
|
|
func (r *Runner) getInternalConfig(repo params.Repository) (params.Internal, error) {
|
|
|
|
|
creds, ok := r.credentials[repo.CredentialsName]
|
|
|
|
|
if !ok {
|
|
|
|
|
return params.Internal{}, runnerErrors.NewBadRequestError("invalid credentials (%s) for repo %s/%s", repo.CredentialsName, repo.Owner, repo.Name)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return params.Internal{
|
|
|
|
|
OAuth2Token: creds.OAuth2Token,
|
|
|
|
|
ControllerID: r.controllerID,
|
|
|
|
|
InstanceCallbackURL: r.config.Default.CallbackURL,
|
|
|
|
|
JWTSecret: r.config.JWTAuth.Secret,
|
|
|
|
|
}, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (r *Runner) loadRepoPoolManager(repo params.Repository) (common.PoolManager, error) {
|
|
|
|
|
cfg, err := r.getInternalConfig(repo)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, errors.Wrap(err, "fetching internal config")
|
|
|
|
|
}
|
|
|
|
|
repo.Internal = cfg
|
|
|
|
|
poolManager, err := pool.NewRepositoryPoolManager(r.ctx, repo, r.providers, r.store)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, errors.Wrap(err, "creating pool manager")
|
|
|
|
|
}
|
|
|
|
|
return poolManager, nil
|
|
|
|
|
}
|
|
|
|
|
|
2022-04-28 16:13:20 +00:00
|
|
|
func (r *Runner) loadReposAndOrgs() error {
|
2022-04-26 20:29:58 +00:00
|
|
|
r.mux.Lock()
|
|
|
|
|
defer r.mux.Unlock()
|
|
|
|
|
|
2022-04-28 16:13:20 +00:00
|
|
|
repos, err := r.store.ListRepositories(r.ctx)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return errors.Wrap(err, "fetching repositories")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for _, repo := range repos {
|
|
|
|
|
log.Printf("creating pool manager for %s/%s", repo.Owner, repo.Name)
|
2022-04-29 14:18:22 +00:00
|
|
|
poolManager, err := r.loadRepoPoolManager(repo)
|
2022-04-28 16:13:20 +00:00
|
|
|
if err != nil {
|
2022-04-29 14:18:22 +00:00
|
|
|
return errors.Wrap(err, "loading repo pool manager")
|
2022-04-28 16:13:20 +00:00
|
|
|
}
|
|
|
|
|
r.repositories[repo.ID] = poolManager
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (r *Runner) Start() error {
|
2022-04-29 14:18:22 +00:00
|
|
|
r.mux.Lock()
|
|
|
|
|
defer r.mux.Unlock()
|
|
|
|
|
|
2022-04-28 16:13:20 +00:00
|
|
|
for _, repo := range r.repositories {
|
|
|
|
|
if err := repo.Start(); err != nil {
|
|
|
|
|
return errors.Wrap(err, "starting repo pool manager")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for _, org := range r.organizations {
|
|
|
|
|
if err := org.Start(); err != nil {
|
|
|
|
|
return errors.Wrap(err, "starting org pool manager")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (r *Runner) Stop() error {
|
2022-04-29 14:18:22 +00:00
|
|
|
r.mux.Lock()
|
|
|
|
|
defer r.mux.Unlock()
|
|
|
|
|
|
2022-04-28 16:13:20 +00:00
|
|
|
for _, repo := range r.repositories {
|
|
|
|
|
if err := repo.Stop(); err != nil {
|
|
|
|
|
return errors.Wrap(err, "starting repo pool manager")
|
|
|
|
|
}
|
|
|
|
|
}
|
2022-04-26 20:29:58 +00:00
|
|
|
|
2022-04-28 16:13:20 +00:00
|
|
|
for _, org := range r.organizations {
|
|
|
|
|
if err := org.Stop(); err != nil {
|
|
|
|
|
return errors.Wrap(err, "starting org pool manager")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (r *Runner) Wait() error {
|
2022-04-29 14:18:22 +00:00
|
|
|
r.mux.Lock()
|
|
|
|
|
defer r.mux.Unlock()
|
|
|
|
|
|
2022-04-28 16:13:20 +00:00
|
|
|
var wg sync.WaitGroup
|
|
|
|
|
|
|
|
|
|
for poolId, repo := range r.repositories {
|
|
|
|
|
wg.Add(1)
|
|
|
|
|
go func(id string, poolMgr common.PoolManager) {
|
|
|
|
|
defer wg.Done()
|
|
|
|
|
if err := poolMgr.Wait(); err != nil {
|
|
|
|
|
log.Printf("timed out waiting for pool manager %s to exit", id)
|
|
|
|
|
}
|
|
|
|
|
}(poolId, repo)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for poolId, org := range r.organizations {
|
|
|
|
|
wg.Add(1)
|
|
|
|
|
go func(id string, poolMgr common.PoolManager) {
|
|
|
|
|
defer wg.Done()
|
|
|
|
|
if err := poolMgr.Wait(); err != nil {
|
|
|
|
|
log.Printf("timed out waiting for pool manager %s to exit", id)
|
|
|
|
|
}
|
|
|
|
|
}(poolId, org)
|
|
|
|
|
}
|
|
|
|
|
wg.Wait()
|
2022-04-26 20:29:58 +00:00
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
2022-04-28 16:13:20 +00:00
|
|
|
func (r *Runner) findRepoPoolManager(owner, name string) (common.PoolManager, error) {
|
2022-04-26 20:29:58 +00:00
|
|
|
r.mux.Lock()
|
|
|
|
|
defer r.mux.Unlock()
|
|
|
|
|
|
2022-04-28 16:13:20 +00:00
|
|
|
repo, err := r.store.GetRepository(r.ctx, owner, name)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, errors.Wrap(err, "fetching repo")
|
|
|
|
|
}
|
2022-04-26 20:29:58 +00:00
|
|
|
|
2022-04-28 16:13:20 +00:00
|
|
|
if repo, ok := r.repositories[repo.ID]; ok {
|
|
|
|
|
return repo, nil
|
|
|
|
|
}
|
2022-04-29 14:18:22 +00:00
|
|
|
return nil, errors.Wrapf(runnerErrors.ErrNotFound, "repository %s/%s not configured", owner, name)
|
2022-04-23 13:05:40 +00:00
|
|
|
}
|
|
|
|
|
|
2022-04-28 16:13:20 +00:00
|
|
|
func (r *Runner) findOrgPoolManager(name string) (common.PoolManager, error) {
|
|
|
|
|
r.mux.Lock()
|
|
|
|
|
defer r.mux.Unlock()
|
|
|
|
|
|
|
|
|
|
org, err := r.store.GetOrganization(r.ctx, name)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, errors.Wrap(err, "fetching repo")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if orgPoolMgr, ok := r.organizations[org.ID]; ok {
|
|
|
|
|
return orgPoolMgr, nil
|
2022-04-23 13:05:40 +00:00
|
|
|
}
|
2022-04-29 14:18:22 +00:00
|
|
|
return nil, errors.Wrapf(runnerErrors.ErrNotFound, "organization %s not configured", name)
|
2022-04-22 14:46:27 +00:00
|
|
|
}
|
|
|
|
|
|
2022-04-23 13:05:40 +00:00
|
|
|
func (r *Runner) validateHookBody(signature, secret string, body []byte) error {
|
|
|
|
|
if secret == "" {
|
|
|
|
|
// A secret was not set. Skip validation of body.
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if signature == "" {
|
|
|
|
|
// A secret was set in our config, but a signature was not received
|
|
|
|
|
// from Github. Authentication of the body cannot be done.
|
2022-04-29 14:18:22 +00:00
|
|
|
return runnerErrors.NewUnauthorizedError("missing github signature")
|
2022-04-23 13:05:40 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
sigParts := strings.SplitN(signature, "=", 2)
|
|
|
|
|
if len(sigParts) != 2 {
|
|
|
|
|
// We expect the signature from github to be of the format:
|
|
|
|
|
// hashType=hashValue
|
|
|
|
|
// ie: sha256=1fc917c7ad66487470e466c0ad40ddd45b9f7730a4b43e1b2542627f0596bbdc
|
2022-04-29 14:18:22 +00:00
|
|
|
return runnerErrors.NewBadRequestError("invalid signature format")
|
2022-04-23 13:05:40 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var hashFunc func() hash.Hash
|
|
|
|
|
switch sigParts[0] {
|
|
|
|
|
case "sha256":
|
|
|
|
|
hashFunc = sha256.New
|
|
|
|
|
case "sha1":
|
|
|
|
|
hashFunc = sha1.New
|
|
|
|
|
default:
|
2022-04-29 14:18:22 +00:00
|
|
|
return runnerErrors.NewBadRequestError("unknown signature type")
|
2022-04-23 13:05:40 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
mac := hmac.New(hashFunc, []byte(secret))
|
|
|
|
|
_, err := mac.Write(body)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return errors.Wrap(err, "failed to compute sha256")
|
|
|
|
|
}
|
|
|
|
|
expectedMAC := hex.EncodeToString(mac.Sum(nil))
|
|
|
|
|
|
|
|
|
|
if !hmac.Equal([]byte(sigParts[1]), []byte(expectedMAC)) {
|
2022-04-29 14:18:22 +00:00
|
|
|
return runnerErrors.NewUnauthorizedError("signature missmatch")
|
2022-04-23 13:05:40 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return nil
|
2022-04-22 14:46:27 +00:00
|
|
|
}
|
|
|
|
|
|
2022-04-23 13:05:40 +00:00
|
|
|
func (r *Runner) DispatchWorkflowJob(hookTargetType, signature string, jobData []byte) error {
|
|
|
|
|
if jobData == nil || len(jobData) == 0 {
|
2022-04-29 14:18:22 +00:00
|
|
|
return runnerErrors.NewBadRequestError("missing job data")
|
2022-04-23 13:05:40 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var job params.WorkflowJob
|
|
|
|
|
if err := json.Unmarshal(jobData, &job); err != nil {
|
2022-04-29 14:18:22 +00:00
|
|
|
return errors.Wrapf(runnerErrors.ErrBadRequest, "invalid job data: %s", err)
|
2022-04-23 13:05:40 +00:00
|
|
|
}
|
|
|
|
|
|
2022-04-26 20:29:58 +00:00
|
|
|
var poolManager common.PoolManager
|
2022-04-22 14:46:27 +00:00
|
|
|
var err error
|
2022-04-23 13:05:40 +00:00
|
|
|
|
|
|
|
|
switch HookTargetType(hookTargetType) {
|
|
|
|
|
case RepoHook:
|
2022-04-28 16:13:20 +00:00
|
|
|
poolManager, err = r.findRepoPoolManager(job.Repository.Owner.Login, job.Repository.Name)
|
2022-04-23 13:05:40 +00:00
|
|
|
case OrganizationHook:
|
2022-04-28 16:13:20 +00:00
|
|
|
poolManager, err = r.findOrgPoolManager(job.Organization.Login)
|
2022-04-22 14:46:27 +00:00
|
|
|
default:
|
2022-04-29 14:18:22 +00:00
|
|
|
return runnerErrors.NewBadRequestError("cannot handle hook target type %s", hookTargetType)
|
2022-04-23 13:05:40 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
// We don't have a repository or organization configured that
|
|
|
|
|
// can handle this workflow job.
|
2022-04-26 20:29:58 +00:00
|
|
|
return errors.Wrap(err, "fetching poolManager")
|
2022-04-22 14:46:27 +00:00
|
|
|
}
|
2022-04-23 13:05:40 +00:00
|
|
|
|
|
|
|
|
// We found a pool. Validate the webhook job. If a secret is configured,
|
|
|
|
|
// we make sure that the source of this workflow job is valid.
|
2022-04-26 20:29:58 +00:00
|
|
|
secret := poolManager.WebhookSecret()
|
2022-04-23 13:05:40 +00:00
|
|
|
if err := r.validateHookBody(signature, secret, jobData); err != nil {
|
|
|
|
|
return errors.Wrap(err, "validating webhook data")
|
|
|
|
|
}
|
|
|
|
|
|
2022-04-28 16:13:20 +00:00
|
|
|
if err := poolManager.HandleWorkflowJob(job); err != nil {
|
|
|
|
|
return errors.Wrap(err, "handling workflow job")
|
|
|
|
|
}
|
|
|
|
|
|
2022-04-22 14:46:27 +00:00
|
|
|
return nil
|
2022-04-13 16:47:42 +00:00
|
|
|
}
|
2022-04-18 17:26:13 +00:00
|
|
|
|
|
|
|
|
func (r *Runner) sshDir() string {
|
2022-04-27 16:56:28 +00:00
|
|
|
return filepath.Join(r.config.Default.ConfigDir, "ssh")
|
2022-04-18 17:26:13 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (r *Runner) sshKeyPath() string {
|
|
|
|
|
keyPath := filepath.Join(r.sshDir(), "runner_rsa_key")
|
|
|
|
|
return keyPath
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (r *Runner) sshPubKeyPath() string {
|
|
|
|
|
keyPath := filepath.Join(r.sshDir(), "runner_rsa_key.pub")
|
|
|
|
|
return keyPath
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (r *Runner) parseSSHKey() (ssh.Signer, error) {
|
|
|
|
|
r.mux.Lock()
|
|
|
|
|
defer r.mux.Unlock()
|
|
|
|
|
|
|
|
|
|
key, err := ioutil.ReadFile(r.sshKeyPath())
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, errors.Wrapf(err, "reading private key %s", r.sshKeyPath())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
signer, err := ssh.ParsePrivateKey(key)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, errors.Wrapf(err, "parsing private key %s", r.sshKeyPath())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return signer, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (r *Runner) sshPubKey() ([]byte, error) {
|
|
|
|
|
key, err := ioutil.ReadFile(r.sshPubKeyPath())
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, errors.Wrapf(err, "reading public key %s", r.sshPubKeyPath())
|
|
|
|
|
}
|
|
|
|
|
return key, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (r *Runner) ensureSSHKeys() error {
|
|
|
|
|
sshDir := r.sshDir()
|
|
|
|
|
|
|
|
|
|
if _, err := os.Stat(sshDir); err != nil {
|
|
|
|
|
if !errors.Is(err, os.ErrNotExist) {
|
|
|
|
|
return errors.Wrapf(err, "checking SSH dir %s", sshDir)
|
|
|
|
|
}
|
|
|
|
|
if err := os.MkdirAll(sshDir, 0o700); err != nil {
|
|
|
|
|
return errors.Wrapf(err, "creating ssh dir %s", sshDir)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
privKeyFile := r.sshKeyPath()
|
|
|
|
|
pubKeyFile := r.sshPubKeyPath()
|
|
|
|
|
|
|
|
|
|
if _, err := os.Stat(privKeyFile); err == nil {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pubKey, privKey, err := util.GenerateSSHKeyPair()
|
|
|
|
|
if err != nil {
|
|
|
|
|
errors.Wrap(err, "generating keypair")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if err := ioutil.WriteFile(privKeyFile, privKey, 0o600); err != nil {
|
|
|
|
|
return errors.Wrap(err, "writing private key")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if err := ioutil.WriteFile(pubKeyFile, pubKey, 0o600); err != nil {
|
|
|
|
|
return errors.Wrap(err, "writing public key")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
}
|