Add token endpoint

This change adds a github registration endpoint that instances can use
to fetch a github registration token.

This change also invalidates disables access to an instance to the token
and status updates endpoints once the instance transitions from
"pending" or "installing" to any other state.
This commit is contained in:
Gabriel Adrian Samfira 2022-12-01 18:00:22 +02:00
parent eba42b0481
commit a078645ab2
No known key found for this signature in database
GPG key ID: 7D073DCC2C074CB5
18 changed files with 252 additions and 77 deletions

View file

@ -202,3 +202,17 @@ func (a *APIController) InstanceStatusMessageHandler(w http.ResponseWriter, r *h
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
}
func (a *APIController) InstanceGithubRegistrationTokenHandler(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
token, err := a.r.GetInstanceGithubRegistrationToken(ctx)
if err != nil {
handleError(w, err)
return
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
w.Write([]byte(token))
}

View file

@ -45,6 +45,8 @@ func NewAPIRouter(han *controllers.APIController, logWriter io.Writer, authMiddl
callbackRouter := apiSubRouter.PathPrefix("/callbacks").Subrouter()
callbackRouter.Handle("/status/", log(logWriter, http.HandlerFunc(han.InstanceStatusMessageHandler))).Methods("POST", "OPTIONS")
callbackRouter.Handle("/status", log(logWriter, http.HandlerFunc(han.InstanceStatusMessageHandler))).Methods("POST", "OPTIONS")
callbackRouter.Handle("/token/", log(logWriter, http.HandlerFunc(han.InstanceGithubRegistrationTokenHandler))).Methods("GET", "OPTIONS")
callbackRouter.Handle("/token", log(logWriter, http.HandlerFunc(han.InstanceGithubRegistrationTokenHandler))).Methods("GET", "OPTIONS")
callbackRouter.Use(instanceMiddleware.Middleware)
// Login
authRouter := apiSubRouter.PathPrefix("/auth").Subrouter()

View file

@ -18,6 +18,7 @@ import (
"context"
"garm/params"
"garm/runner/providers/common"
)
type contextFlags string
@ -45,11 +46,13 @@ const (
isEnabledFlag contextFlags = "is_enabled"
jwtTokenFlag contextFlags = "jwt_token"
instanceIDKey contextFlags = "id"
instanceNameKey contextFlags = "name"
instancePoolIDKey contextFlags = "pool_id"
instancePoolTypeKey contextFlags = "scope"
instanceEntityKey contextFlags = "entity"
instanceIDKey contextFlags = "id"
instanceNameKey contextFlags = "name"
instancePoolIDKey contextFlags = "pool_id"
instancePoolTypeKey contextFlags = "scope"
instanceEntityKey contextFlags = "entity"
instanceRunnerStatus contextFlags = "status"
instanceGithubToken contextFlags = "github_token"
)
func SetInstanceID(ctx context.Context, id string) context.Context {
@ -64,6 +67,30 @@ func InstanceID(ctx context.Context) string {
return elem.(string)
}
func SetInstanceRunnerStatus(ctx context.Context, val common.RunnerStatus) context.Context {
return context.WithValue(ctx, instanceRunnerStatus, val)
}
func InstanceRunnerStatus(ctx context.Context) common.RunnerStatus {
elem := ctx.Value(instanceRunnerStatus)
if elem == nil {
return common.RunnerPending
}
return elem.(common.RunnerStatus)
}
func SetInstanceGithubToken(ctx context.Context, val string) context.Context {
return context.WithValue(ctx, instanceGithubToken, val)
}
func InstanceGithubToken(ctx context.Context) string {
elem := ctx.Value(instanceGithubToken)
if elem == nil {
return ""
}
return elem.(string)
}
func SetInstanceName(ctx context.Context, val string) context.Context {
return context.WithValue(ctx, instanceNameKey, val)
}
@ -116,6 +143,8 @@ func PopulateInstanceContext(ctx context.Context, instance params.Instance) cont
ctx = SetInstanceID(ctx, instance.ID)
ctx = SetInstanceName(ctx, instance.Name)
ctx = SetInstancePoolID(ctx, instance.PoolID)
ctx = SetInstanceRunnerStatus(ctx, instance.RunnerStatus)
ctx = SetInstanceGithubToken(ctx, string(instance.GithubRegistrationToken))
return ctx
}

View file

@ -26,6 +26,7 @@ import (
runnerErrors "garm/errors"
"garm/params"
"garm/runner/common"
providerCommon "garm/runner/providers/common"
"github.com/golang-jwt/jwt"
"github.com/pkg/errors"
@ -145,6 +146,14 @@ func (amw *instanceMiddleware) Middleware(next http.Handler) http.Handler {
if InstanceID(ctx) == "" {
invalidAuthResponse(w)
return
}
runnerStatus := InstanceRunnerStatus(ctx)
if runnerStatus != providerCommon.RunnerInstalling && runnerStatus != providerCommon.RunnerPending {
// Instances that have finished installing can no longer authenticate to the API
invalidAuthResponse(w)
return
}
// ctx = SetJWTClaim(ctx, *claims)

View file

@ -77,8 +77,7 @@ func invalidAuthResponse(w http.ResponseWriter) {
w.Header().Add("Content-Type", "application/json")
json.NewEncoder(w).Encode(
apiParams.APIErrorResponse{
Error: "Authentication failed",
Details: "Invalid authentication token",
Error: "Authentication failed",
})
}

View file

@ -27,7 +27,17 @@ set -ex
set -o pipefail
CALLBACK_URL="{{ .CallbackURL }}"
TOKEN_URL="{{ .TokenURL }}"
BEARER_TOKEN="{{ .CallbackToken }}"
GITHUB_TOKEN="{{ .GithubToken }}"
if [ -z "$GITHUB_TOKEN" ];then
if [ -z "$TOKEN_URL" ];then
echo "no token is available and TOKEN_URL is not set"
exit 1
fi
GITHUB_TOKEN=$(curl -s -X GET -H 'Accept: application/json' -H "Authorization: Bearer ${BEARER_TOKEN}" "${TOKEN_URL}")
fi
function call() {
PAYLOAD="$1"
@ -55,13 +65,11 @@ sendStatus "downloading tools from {{ .DownloadURL }}"
TEMP_TOKEN=""
if [ ! -z "{{ .TempDownloadToken }}" ]; then
TEMP_TOKEN="Authorization: Bearer {{ .TempDownloadToken }}"
fi
curl -L -H "${TEMP_TOKEN}" -o "/home/runner/{{ .FileName }}" "{{ .DownloadURL }}" || fail "failed to download tools"
curl -L -H "${TEMP_TOKEN}" -o "/home/{{ .RunnerUsername }}/{{ .FileName }}" "{{ .DownloadURL }}" || fail "failed to download tools"
mkdir -p /home/runner/actions-runner || fail "failed to create actions-runner folder"
@ -74,7 +82,7 @@ cd /home/{{ .RunnerUsername }}/actions-runner
sudo ./bin/installdependencies.sh || fail "failed to install dependencies"
sendStatus "configuring runner"
sudo -u {{ .RunnerUsername }} -- ./config.sh --unattended --url "{{ .RepoURL }}" --token "{{ .GithubToken }}" --name "{{ .RunnerName }}" --labels "{{ .RunnerLabels }}" --ephemeral || fail "failed to configure runner"
sudo -u {{ .RunnerUsername }} -- ./config.sh --unattended --url "{{ .RepoURL }}" --token "$GITHUB_TOKEN" --name "{{ .RunnerName }}" --labels "{{ .RunnerLabels }}" --ephemeral || fail "failed to configure runner"
sendStatus "installing runner service"
./svc.sh install {{ .RunnerUsername }} || fail "failed to install service"
@ -99,6 +107,7 @@ type InstallRunnerParams struct {
RunnerGroup string
RepoURL string
GithubToken string
TokenURL string
RunnerName string
RunnerLabels string
CallbackURL string

View file

@ -20,6 +20,7 @@ import (
"fmt"
"log"
"net"
"net/url"
"os"
"path/filepath"
"time"
@ -169,8 +170,11 @@ type Default struct {
// ConfigDir is the folder where the runner may save any aditional files
// or configurations it may need. Things like auto-generated SSH keys that
// may be used to access the runner instances.
ConfigDir string `toml:"config_dir,omitempty" json:"config-dir,omitempty"`
ConfigDir string `toml:"config_dir,omitempty" json:"config-dir,omitempty"`
// CallbackURL is the URL where the instances can send back status reports.
CallbackURL string `toml:"callback_url" json:"callback-url"`
// TokenURL is the URL where instances can fetch a github runner registration token.
TokenURL string `toml:"token_url" json:"token-url"`
// LogFile is the location of the log file.
LogFile string `toml:"log_file,omitempty" json:"log-file"`
EnableLogStreamer bool `toml:"enable_log_streamer"`
@ -181,6 +185,17 @@ func (d *Default) Validate() error {
return fmt.Errorf("missing callback_url")
}
_, err := url.Parse(d.CallbackURL)
if err != nil {
return errors.Wrap(err, "validating callback_url")
}
if d.TokenURL != "" {
if _, err := url.Parse(d.TokenURL); err != nil {
return errors.Wrap(err, "validating token_url")
}
}
if d.ConfigDir == "" {
return fmt.Errorf("config_dir cannot be empty")
}

View file

@ -6,11 +6,18 @@ set -o pipefail
CALLBACK_URL="GARM_CALLBACK_URL"
BEARER_TOKEN="GARM_CALLBACK_TOKEN"
DOWNLOAD_URL="GH_DOWNLOAD_URL"
DOWNLOAD_TOKEN="GH_TEMP_DOWNLOAD_TOKEN"
FILENAME="GH_FILENAME"
TARGET_URL="GH_TARGET_URL"
RUNNER_TOKEN="GH_RUNNER_TOKEN"
RUNNER_NAME="GH_RUNNER_NAME"
RUNNER_LABELS="GH_RUNNER_LABELS"
TEMP_TOKEN=""
if [ ! -z "$DOWNLOAD_TOKEN" ]; then
TEMP_TOKEN="Authorization: Bearer $DOWNLOAD_TOKEN"
fi
function call() {
PAYLOAD="$1"
@ -37,7 +44,7 @@ function fail() {
sendStatus "downloading tools from ${DOWNLOAD_URL}"
curl -L -o "/home/runner/${FILENAME}" "${DOWNLOAD_URL}" || fail "failed to download tools"
curl -L -H "${TEMP_TOKEN}" -o "/home/runner/${FILENAME}" "${DOWNLOAD_URL}" || fail "failed to download tools"
mkdir -p /home/runner/actions-runner || fail "failed to create actions-runner folder"

View file

@ -145,6 +145,20 @@ function downloadURL() {
echo "${URL}"
}
function tempDownloadToken() {
# temp_download_token
[ -z "$1" -o -z "$2" ] && return 1
GH_ARCH="${GARM_TO_GH_ARCH_MAP[$2]}"
TOKEN=$(echo "$INPUT" | jq -c -r --arg OS "$1" --arg ARCH "$GH_ARCH" '(.tools[] | select( .os == $OS and .architecture == $ARCH)).temp_download_token')
echo "${TOKEN}"
}
function runnerTokenURL() {
TOKEN_URL=$(echo "$INPUT" | jq -c -r '."token-url"')
checkValNotNull "${TOKEN_URL}" "token-url" || return $?
echo "${TOKEN_URL}"
}
function downloadFilename() {
[ -z "$1" -o -z "$2" ] && return 1
GH_ARCH="${GARM_TO_GH_ARCH_MAP[$2]}"
@ -177,8 +191,19 @@ function repoURL() {
echo "${REPO}"
}
function getRegistrationTokenFromAPI() {
TOKEN_URL=$(runnerTokenURL)
BEARER_TOKEN=$(callbackToken)
TOKEN=$(curl -s -X GET -H 'Accept: application/json' -H "Authorization: Bearer ${BEARER_TOKEN}" "${TOKEN_URL}")
checkValNotNull "${TOKEN}" "repo_url" || return $?
echo "${TOKEN}"
}
function ghAccessToken() {
TOKEN=$(echo "$INPUT" | jq -c -r '.github_runner_access_token')
if [ -z "$TOKEN" ];then
TOKEN=$(getRegistrationTokenFromAPI)
fi
checkValNotNull "${TOKEN}" "github_runner_access_token" || return $?
echo "${TOKEN}"
}
@ -215,6 +240,7 @@ function getCloudConfig() {
ARCH=$(requestedArch)
DW_URL=$(downloadURL "${OS_TYPE}" "${ARCH}")
DW_TOKEN=$(tempDownloadToken "${OS_TYPE}" "${ARCH}")
DW_FILENAME=$(downloadFilename "${OS_TYPE}" "${ARCH}")
LABELS=$(labels)
@ -230,6 +256,7 @@ function getCloudConfig() {
-e "s|GH_TARGET_URL|$(repoURL)|g" \
-e "s|GH_RUNNER_TOKEN|$(ghAccessToken)|g" \
-e "s|GH_RUNNER_NAME|$(instanceName)|g" \
-e "s|GH_TEMP_DOWNLOAD_TOKEN|${DW_TOKEN}|g" \
-e "s|GH_RUNNER_LABELS|${LABELS}|g" > ${TMP_SCRIPT}
AS_B64=$(base64 -w0 ${TMP_SCRIPT})
@ -306,7 +333,7 @@ function CreateInstance() {
if [ $? -ne 0 ];then
CODE=$?
# cleanup
rm -f "${CC_FILE}" || true
rm -f "${CC_FILE}" || true
openstack server delete "${INSTANCE_NAME}" || true
openstack volume delete "${INSTANCE_NAME}" || true
set -e

View file

@ -16,8 +16,10 @@ package sql
import (
"context"
"fmt"
runnerErrors "garm/errors"
"garm/params"
"garm/util"
"github.com/pkg/errors"
uuid "github.com/satori/go.uuid"
@ -30,14 +32,22 @@ func (s *sqlDatabase) CreateInstance(ctx context.Context, poolID string, param p
if err != nil {
return params.Instance{}, errors.Wrap(err, "fetching pool")
}
var ghToken []byte
if param.GithubRegistrationToken != nil {
ghToken, err = util.Aes256EncodeString(string(param.GithubRegistrationToken), s.cfg.Passphrase)
if err != nil {
return params.Instance{}, fmt.Errorf("failed to encrypt gh token")
}
}
newInstance := Instance{
Pool: pool,
Name: param.Name,
Status: param.Status,
RunnerStatus: param.RunnerStatus,
OSType: param.OSType,
OSArch: param.OSArch,
CallbackURL: param.CallbackURL,
Pool: pool,
Name: param.Name,
Status: param.Status,
RunnerStatus: param.RunnerStatus,
OSType: param.OSType,
OSArch: param.OSArch,
CallbackURL: param.CallbackURL,
GithubRegistrationToken: ghToken,
}
q := s.conn.Create(&newInstance)
if q.Error != nil {
@ -112,6 +122,15 @@ func (s *sqlDatabase) GetPoolInstanceByName(ctx context.Context, poolID string,
if err != nil {
return params.Instance{}, errors.Wrap(err, "fetching instance")
}
if instance.GithubRegistrationToken != nil {
token, err := util.Aes256DecodeString(instance.GithubRegistrationToken, s.cfg.Passphrase)
if err != nil {
return params.Instance{}, errors.Wrap(err, "decoing token")
}
instance.GithubRegistrationToken = []byte(token)
}
return s.sqlToParamsInstance(instance), nil
}
@ -120,6 +139,14 @@ func (s *sqlDatabase) GetInstanceByName(ctx context.Context, instanceName string
if err != nil {
return params.Instance{}, errors.Wrap(err, "fetching instance")
}
if instance.GithubRegistrationToken != nil {
token, err := util.Aes256DecodeString(instance.GithubRegistrationToken, s.cfg.Passphrase)
if err != nil {
return params.Instance{}, errors.Wrap(err, "decoing token")
}
instance.GithubRegistrationToken = []byte(token)
}
return s.sqlToParamsInstance(instance), nil
}

View file

@ -127,19 +127,20 @@ type InstanceStatusUpdate struct {
type Instance struct {
Base
ProviderID *string `gorm:"uniqueIndex"`
Name string `gorm:"uniqueIndex"`
AgentID int64
OSType config.OSType
OSArch config.OSArch
OSName string
OSVersion string
Addresses []Address `gorm:"foreignKey:InstanceID"`
Status common.InstanceStatus
RunnerStatus common.RunnerStatus
CallbackURL string
ProviderFault []byte `gorm:"type:longblob"`
CreateAttempt int
ProviderID *string `gorm:"uniqueIndex"`
Name string `gorm:"uniqueIndex"`
AgentID int64
OSType config.OSType
OSArch config.OSArch
OSName string
OSVersion string
Addresses []Address `gorm:"foreignKey:InstanceID"`
Status common.InstanceStatus
RunnerStatus common.RunnerStatus
CallbackURL string
ProviderFault []byte `gorm:"type:longblob"`
CreateAttempt int
GithubRegistrationToken []byte `gorm:"type:longblob"`
PoolID uuid.UUID
Pool Pool `gorm:"foreignKey:PoolID"`

View file

@ -29,21 +29,22 @@ func (s *sqlDatabase) sqlToParamsInstance(instance Instance) params.Instance {
id = *instance.ProviderID
}
ret := params.Instance{
ID: instance.ID.String(),
ProviderID: id,
AgentID: instance.AgentID,
Name: instance.Name,
OSType: instance.OSType,
OSName: instance.OSName,
OSVersion: instance.OSVersion,
OSArch: instance.OSArch,
Status: instance.Status,
RunnerStatus: instance.RunnerStatus,
PoolID: instance.PoolID.String(),
CallbackURL: instance.CallbackURL,
StatusMessages: []params.StatusMessage{},
CreateAttempt: instance.CreateAttempt,
UpdatedAt: instance.UpdatedAt,
ID: instance.ID.String(),
ProviderID: id,
AgentID: instance.AgentID,
Name: instance.Name,
OSType: instance.OSType,
OSName: instance.OSName,
OSVersion: instance.OSVersion,
OSArch: instance.OSArch,
Status: instance.Status,
RunnerStatus: instance.RunnerStatus,
PoolID: instance.PoolID.String(),
CallbackURL: instance.CallbackURL,
StatusMessages: []params.StatusMessage{},
CreateAttempt: instance.CreateAttempt,
UpdatedAt: instance.UpdatedAt,
GithubRegistrationToken: instance.GithubRegistrationToken,
}
if len(instance.ProviderFault) > 0 {

View file

@ -73,11 +73,12 @@ type Instance struct {
ProviderFault []byte `json:"provider_fault,omitempty"`
StatusMessages []StatusMessage `json:"status_messages,omitempty"`
UpdatedAt time.Time `json:"updated_at"`
// Do not serialize sensitive info.
CallbackURL string `json:"-"`
CreateAttempt int `json:"-"`
UpdatedAt time.Time `json:"updated_at"`
CallbackURL string `json:"-"`
CreateAttempt int `json:"-"`
GithubRegistrationToken []byte `json:"-"`
}
type BootstrapInstance struct {
@ -91,6 +92,8 @@ type BootstrapInstance struct {
// CallbackUrl is the URL where the instance can send a post, signaling
// progress or status.
CallbackURL string `json:"callback-url"`
// TokenURL is the URL where instances can fetch github runner registratin tokens.
TokenURL string `json:"token-url"`
// InstanceToken is the token that needs to be set by the instance in the headers
// in order to send updated back to the garm via CallbackURL.
InstanceToken string `json:"instance-token"`

View file

@ -107,13 +107,14 @@ type UpdatePoolParams struct {
}
type CreateInstanceParams struct {
Name string
OSType config.OSType
OSArch config.OSArch
Status common.InstanceStatus
RunnerStatus common.RunnerStatus
CallbackURL string
CreateAttempt int `json:"-"`
Name string
OSType config.OSType
OSArch config.OSArch
Status common.InstanceStatus
RunnerStatus common.RunnerStatus
CallbackURL string
CreateAttempt int `json:"-"`
GithubRegistrationToken []byte `json:"-"`
}
type CreatePoolParams struct {

View file

@ -345,15 +345,24 @@ func (r *basePoolManager) AddRunner(ctx context.Context, poolID string) error {
}
name := fmt.Sprintf("garm-%s", uuid.New())
tk, err := r.helper.GetGithubRegistrationToken()
if err != nil {
if errors.Is(err, runnerErrors.ErrUnauthorized) {
failureReason := fmt.Sprintf("failed to fetch registration token: %q", err)
r.setPoolRunningState(false, failureReason)
log.Print(failureReason)
}
return errors.Wrap(err, "fetching registration token")
}
createParams := params.CreateInstanceParams{
Name: name,
Status: providerCommon.InstancePendingCreate,
RunnerStatus: providerCommon.RunnerPending,
OSArch: pool.OSArch,
OSType: pool.OSType,
CallbackURL: r.helper.GetCallbackURL(),
CreateAttempt: 1,
Name: name,
Status: providerCommon.InstancePendingCreate,
RunnerStatus: providerCommon.RunnerPending,
OSArch: pool.OSArch,
OSType: pool.OSType,
GithubRegistrationToken: []byte(tk),
CallbackURL: r.helper.GetCallbackURL(),
CreateAttempt: 1,
}
_, err = r.store.CreateInstance(r.ctx, poolID, createParams)
@ -512,14 +521,17 @@ func (r *basePoolManager) addInstanceToProvider(instance params.Instance) error
labels = append(labels, r.controllerLabel())
labels = append(labels, r.poolLabel(pool.ID))
tk, err := r.helper.GetGithubRegistrationToken()
if err != nil {
if errors.Is(err, runnerErrors.ErrUnauthorized) {
failureReason := fmt.Sprintf("failed to fetch registration token: %q", err)
r.setPoolRunningState(false, failureReason)
log.Print(failureReason)
if instance.GithubRegistrationToken == nil {
tk, err := r.helper.GetGithubRegistrationToken()
if err != nil {
if errors.Is(err, runnerErrors.ErrUnauthorized) {
failureReason := fmt.Sprintf("failed to fetch registration token: %q", err)
r.setPoolRunningState(false, failureReason)
log.Print(failureReason)
}
return errors.Wrap(err, "fetching registration token")
}
return errors.Wrap(err, "fetching registration token")
instance.GithubRegistrationToken = []byte(tk)
}
jwtValidity := pool.RunnerTimeout()
@ -538,7 +550,7 @@ func (r *basePoolManager) addInstanceToProvider(instance params.Instance) error
Name: instance.Name,
Tools: r.tools,
RepoURL: r.helper.GithubURL(),
GithubRunnerAccessToken: tk,
GithubRunnerAccessToken: string(instance.GithubRegistrationToken),
CallbackURL: instance.CallbackURL,
InstanceToken: jwtToken,
OSArch: pool.OSArch,

View file

@ -722,6 +722,20 @@ func (r *Runner) AddInstanceStatusMessage(ctx context.Context, param params.Inst
return nil
}
func (r *Runner) GetInstanceGithubRegistrationToken(ctx context.Context) (string, error) {
instanceID := auth.InstanceID(ctx)
if instanceID == "" {
return "", runnerErrors.ErrUnauthorized
}
status := auth.InstanceRunnerStatus(ctx)
if status != providerCommon.RunnerPending && status != providerCommon.RunnerInstalling {
return "", runnerErrors.ErrUnauthorized
}
token := auth.InstanceGithubToken(ctx)
return token, nil
}
func (r *Runner) ForceDeleteRunner(ctx context.Context, instanceName string) error {
if !auth.IsAdmin(ctx) {
return runnerErrors.ErrUnauthorized

View file

@ -4,6 +4,11 @@
# the github actions runner. Status messages can be seen by querying the
# runner status in garm.
callback_url = "https://garm.example.com/api/v1/callbacks/status"
# This URL is used to retrieve a github runner registration token for a particular
# instance. Once the instance transitions to "installed", this endpoint should
# no longer be accessible.
token_url = "https://garm.example.com/api/v1/callbacks/tokens"
# This folder is defined here for future use. Right now, we create a SSH
# public/private key-pair.
config_dir = "/etc/garm"

View file

@ -24,7 +24,6 @@ import (
"encoding/base64"
"fmt"
"io"
"io/ioutil"
"net/http"
"os"
"path"
@ -147,7 +146,7 @@ func GetLoggingWriter(cfg *config.Config) (io.Writer, error) {
}
func ConvertFileToBase64(file string) (string, error) {
bytes, err := ioutil.ReadFile(file)
bytes, err := os.ReadFile(file)
if err != nil {
return "", errors.Wrap(err, "reading file")
}
@ -214,6 +213,7 @@ func GetCloudConfig(bootstrapParams params.BootstrapInstance, tools github.Runne
DownloadURL: *tools.DownloadURL,
TempDownloadToken: tempToken,
GithubToken: bootstrapParams.GithubRunnerAccessToken,
TokenURL: bootstrapParams.TokenURL,
RunnerUsername: config.DefaultUser,
RunnerGroup: config.DefaultUser,
RepoURL: bootstrapParams.RepoURL,