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:
parent
eba42b0481
commit
a078645ab2
18 changed files with 252 additions and 77 deletions
|
|
@ -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))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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"`
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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"`
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
5
testdata/config.toml
vendored
5
testdata/config.toml
vendored
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue