diff --git a/apiserver/controllers/instances.go b/apiserver/controllers/instances.go index a8d6fe2f..7ace7fc6 100644 --- a/apiserver/controllers/instances.go +++ b/apiserver/controllers/instances.go @@ -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)) +} diff --git a/apiserver/routers/routers.go b/apiserver/routers/routers.go index 87d7d471..b59119da 100644 --- a/apiserver/routers/routers.go +++ b/apiserver/routers/routers.go @@ -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() diff --git a/auth/context.go b/auth/context.go index 6d86b168..af4cd919 100644 --- a/auth/context.go +++ b/auth/context.go @@ -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 } diff --git a/auth/instance_middleware.go b/auth/instance_middleware.go index 3229927b..c8cdc63e 100644 --- a/auth/instance_middleware.go +++ b/auth/instance_middleware.go @@ -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) diff --git a/auth/jwt.go b/auth/jwt.go index e43571cd..835b5cc7 100644 --- a/auth/jwt.go +++ b/auth/jwt.go @@ -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", }) } diff --git a/cloudconfig/templates.go b/cloudconfig/templates.go index c68c88ef..c3c0f7d5 100644 --- a/cloudconfig/templates.go +++ b/cloudconfig/templates.go @@ -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 diff --git a/config/config.go b/config/config.go index d02daea5..8f004580 100644 --- a/config/config.go +++ b/config/config.go @@ -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") } diff --git a/contrib/providers.d/openstack/cloudconfig/install_runner.tpl b/contrib/providers.d/openstack/cloudconfig/install_runner.tpl index a13f78c3..130344e1 100644 --- a/contrib/providers.d/openstack/cloudconfig/install_runner.tpl +++ b/contrib/providers.d/openstack/cloudconfig/install_runner.tpl @@ -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" diff --git a/contrib/providers.d/openstack/garm-external-provider b/contrib/providers.d/openstack/garm-external-provider index da0876d1..c56c84c8 100755 --- a/contrib/providers.d/openstack/garm-external-provider +++ b/contrib/providers.d/openstack/garm-external-provider @@ -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 diff --git a/database/sql/instances.go b/database/sql/instances.go index 34f4c720..ca75e01f 100644 --- a/database/sql/instances.go +++ b/database/sql/instances.go @@ -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 } diff --git a/database/sql/models.go b/database/sql/models.go index 523c258b..305e17ca 100644 --- a/database/sql/models.go +++ b/database/sql/models.go @@ -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"` diff --git a/database/sql/util.go b/database/sql/util.go index e8ff2f54..9a573ea7 100644 --- a/database/sql/util.go +++ b/database/sql/util.go @@ -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 { diff --git a/params/params.go b/params/params.go index b0a07450..e1abcdfe 100644 --- a/params/params.go +++ b/params/params.go @@ -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"` diff --git a/params/requests.go b/params/requests.go index e2ded969..ba5e3a35 100644 --- a/params/requests.go +++ b/params/requests.go @@ -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 { diff --git a/runner/pool/pool.go b/runner/pool/pool.go index 1eac96c2..d17b7224 100644 --- a/runner/pool/pool.go +++ b/runner/pool/pool.go @@ -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, diff --git a/runner/runner.go b/runner/runner.go index 3c7b00ba..96398a13 100644 --- a/runner/runner.go +++ b/runner/runner.go @@ -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 diff --git a/testdata/config.toml b/testdata/config.toml index 7f1c0d42..30867186 100644 --- a/testdata/config.toml +++ b/testdata/config.toml @@ -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" diff --git a/util/util.go b/util/util.go index 8658f6a5..5e6a85d2 100644 --- a/util/util.go +++ b/util/util.go @@ -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,