diff --git a/apiserver/routers/routers.go b/apiserver/routers/routers.go index b59119da..60675e99 100644 --- a/apiserver/routers/routers.go +++ b/apiserver/routers/routers.go @@ -41,13 +41,16 @@ func NewAPIRouter(han *controllers.APIController, logWriter io.Writer, authMiddl firstRunRouter := apiSubRouter.PathPrefix("/first-run").Subrouter() firstRunRouter.Handle("/", log(logWriter, http.HandlerFunc(han.FirstRunHandler))).Methods("POST", "OPTIONS") - // Instance callback + // Instance URLs 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) + + metadataRouter := apiSubRouter.PathPrefix("/metadata").Subrouter() + metadataRouter.Handle("/token/", log(logWriter, http.HandlerFunc(han.InstanceGithubRegistrationTokenHandler))).Methods("GET", "OPTIONS") + metadataRouter.Handle("/token", log(logWriter, http.HandlerFunc(han.InstanceGithubRegistrationTokenHandler))).Methods("GET", "OPTIONS") + metadataRouter.Use(instanceMiddleware.Middleware) // Login authRouter := apiSubRouter.PathPrefix("/auth").Subrouter() authRouter.Handle("/{login:login\\/?}", log(logWriter, http.HandlerFunc(han.LoginHandler))).Methods("POST", "OPTIONS") diff --git a/cloudconfig/templates.go b/cloudconfig/templates.go index c3c0f7d5..d9dd3a61 100644 --- a/cloudconfig/templates.go +++ b/cloudconfig/templates.go @@ -23,25 +23,25 @@ import ( var CloudConfigTemplate = `#!/bin/bash -set -ex +set -e set -o pipefail CALLBACK_URL="{{ .CallbackURL }}" -TOKEN_URL="{{ .TokenURL }}" +METADATA_URL="{{ .MetadataURL }}" 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" + if [ -z "$METADATA_URL" ];then + echo "no token is available and METADATA_URL is not set" exit 1 fi - GITHUB_TOKEN=$(curl -s -X GET -H 'Accept: application/json' -H "Authorization: Bearer ${BEARER_TOKEN}" "${TOKEN_URL}") + GITHUB_TOKEN=$(curl --fail -s -X GET -H 'Accept: application/json' -H "Authorization: Bearer ${BEARER_TOKEN}" "${METADATA_URL}/token") fi function call() { PAYLOAD="$1" - curl -s -X POST -d "${PAYLOAD}" -H 'Accept: application/json' -H "Authorization: Bearer ${BEARER_TOKEN}" "${CALLBACK_URL}" || echo "failed to call home: exit code ($?)" + curl --fail -s -X POST -d "${PAYLOAD}" -H 'Accept: application/json' -H "Authorization: Bearer ${BEARER_TOKEN}" "${CALLBACK_URL}" || echo "failed to call home: exit code ($?)" } function sendStatus() { @@ -107,7 +107,7 @@ type InstallRunnerParams struct { RunnerGroup string RepoURL string GithubToken string - TokenURL string + MetadataURL string RunnerName string RunnerLabels string CallbackURL string diff --git a/config/config.go b/config/config.go index 8f004580..edec3436 100644 --- a/config/config.go +++ b/config/config.go @@ -173,8 +173,9 @@ type Default struct { 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"` + // MetadataURL is the URL where instances can fetch information they may need + // to set themselves up. + MetadataURL string `toml:"metadata_url" json:"metadata-url"` // LogFile is the location of the log file. LogFile string `toml:"log_file,omitempty" json:"log-file"` EnableLogStreamer bool `toml:"enable_log_streamer"` @@ -190,9 +191,9 @@ func (d *Default) Validate() error { 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.MetadataURL != "" { + if _, err := url.Parse(d.MetadataURL); err != nil { + return errors.Wrap(err, "validating metadata_url") } } diff --git a/contrib/providers.d/azure/cloudconfig/install_runner.tpl b/contrib/providers.d/azure/cloudconfig/install_runner.tpl index a13f78c3..1a0127a4 100644 --- a/contrib/providers.d/azure/cloudconfig/install_runner.tpl +++ b/contrib/providers.d/azure/cloudconfig/install_runner.tpl @@ -1,6 +1,6 @@ #!/bin/bash -set -ex +set -e set -o pipefail CALLBACK_URL="GARM_CALLBACK_URL" diff --git a/contrib/providers.d/openstack/cloudconfig/install_runner.tpl b/contrib/providers.d/openstack/cloudconfig/install_runner.tpl index 130344e1..3becb040 100644 --- a/contrib/providers.d/openstack/cloudconfig/install_runner.tpl +++ b/contrib/providers.d/openstack/cloudconfig/install_runner.tpl @@ -1,6 +1,6 @@ #!/bin/bash -set -ex +set -e set -o pipefail CALLBACK_URL="GARM_CALLBACK_URL" diff --git a/contrib/providers.d/openstack/garm-external-provider b/contrib/providers.d/openstack/garm-external-provider index c56c84c8..8bc59284 100755 --- a/contrib/providers.d/openstack/garm-external-provider +++ b/contrib/providers.d/openstack/garm-external-provider @@ -154,9 +154,9 @@ function tempDownloadToken() { } function runnerTokenURL() { - TOKEN_URL=$(echo "$INPUT" | jq -c -r '."token-url"') - checkValNotNull "${TOKEN_URL}" "token-url" || return $? - echo "${TOKEN_URL}" + METADATA_URL=$(echo "$INPUT" | jq -c -r '."metadata-url"') + checkValNotNull "${METADATA_URL}" "metadata-url" || return $? + echo "${METADATA_URL}/token/" } function downloadFilename() { @@ -194,7 +194,7 @@ function repoURL() { function getRegistrationTokenFromAPI() { TOKEN_URL=$(runnerTokenURL) BEARER_TOKEN=$(callbackToken) - TOKEN=$(curl -s -X GET -H 'Accept: application/json' -H "Authorization: Bearer ${BEARER_TOKEN}" "${TOKEN_URL}") + TOKEN=$(curl --fail -s -X GET -H 'Accept: application/json' -H "Authorization: Bearer ${BEARER_TOKEN}" "${TOKEN_URL}") checkValNotNull "${TOKEN}" "repo_url" || return $? echo "${TOKEN}" } diff --git a/database/sql/instances.go b/database/sql/instances.go index ca75e01f..eb1ee67b 100644 --- a/database/sql/instances.go +++ b/database/sql/instances.go @@ -47,6 +47,7 @@ func (s *sqlDatabase) CreateInstance(ctx context.Context, poolID string, param p OSType: param.OSType, OSArch: param.OSArch, CallbackURL: param.CallbackURL, + MetadataURL: param.MetadataURL, GithubRegistrationToken: ghToken, } q := s.conn.Create(&newInstance) diff --git a/database/sql/models.go b/database/sql/models.go index 305e17ca..9009eb09 100644 --- a/database/sql/models.go +++ b/database/sql/models.go @@ -138,6 +138,7 @@ type Instance struct { Status common.InstanceStatus RunnerStatus common.RunnerStatus CallbackURL string + MetadataURL string ProviderFault []byte `gorm:"type:longblob"` CreateAttempt int GithubRegistrationToken []byte `gorm:"type:longblob"` diff --git a/database/sql/util.go b/database/sql/util.go index 9a573ea7..430ca7ec 100644 --- a/database/sql/util.go +++ b/database/sql/util.go @@ -41,6 +41,7 @@ func (s *sqlDatabase) sqlToParamsInstance(instance Instance) params.Instance { RunnerStatus: instance.RunnerStatus, PoolID: instance.PoolID.String(), CallbackURL: instance.CallbackURL, + MetadataURL: instance.MetadataURL, StatusMessages: []params.StatusMessage{}, CreateAttempt: instance.CreateAttempt, UpdatedAt: instance.UpdatedAt, diff --git a/params/params.go b/params/params.go index e1abcdfe..6791681b 100644 --- a/params/params.go +++ b/params/params.go @@ -77,6 +77,7 @@ type Instance struct { // Do not serialize sensitive info. CallbackURL string `json:"-"` + MetadataURL string `json:"-"` CreateAttempt int `json:"-"` GithubRegistrationToken []byte `json:"-"` } @@ -92,8 +93,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"` + // MetadataURL is the URL where instances can fetch information needed to set themselves up. + MetadataURL string `json:"metadata-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"` @@ -147,6 +148,7 @@ type Internal struct { OAuth2Token string `json:"oauth2"` ControllerID string `json:"controller_id"` InstanceCallbackURL string `json:"instance_callback_url"` + InstanceMetadataURL string `json:"instance_metadata_url"` JWTSecret string `json:"jwt_secret"` // GithubCredentialsDetails contains all info about the credentials, except the // token, which is added above. diff --git a/params/requests.go b/params/requests.go index ba5e3a35..d79c4b7b 100644 --- a/params/requests.go +++ b/params/requests.go @@ -113,6 +113,7 @@ type CreateInstanceParams struct { Status common.InstanceStatus RunnerStatus common.RunnerStatus CallbackURL string + MetadataURL string CreateAttempt int `json:"-"` GithubRegistrationToken []byte `json:"-"` } diff --git a/runner/pool/enterprise.go b/runner/pool/enterprise.go index 0cc99d97..fa0b25b4 100644 --- a/runner/pool/enterprise.go +++ b/runner/pool/enterprise.go @@ -18,7 +18,7 @@ import ( ) // test that we implement PoolManager -var _ poolHelper = &organization{} +var _ poolHelper = &enterprise{} func NewEnterprisePoolManager(ctx context.Context, cfg params.Enterprise, cfgInternal params.Internal, providers map[string]common.Provider, store dbCommon.Store) (common.PoolManager, error) { ghc, ghEnterpriseClient, err := util.GithubClient(ctx, cfgInternal.OAuth2Token, cfgInternal.GithubCredentialsDetails) @@ -179,6 +179,10 @@ func (r *enterprise) GetCallbackURL() string { return r.cfgInternal.InstanceCallbackURL } +func (r *enterprise) GetMetadataURL() string { + return r.cfgInternal.InstanceMetadataURL +} + func (r *enterprise) FindPoolByTags(labels []string) (params.Pool, error) { pool, err := r.store.FindEnterprisePoolByTags(r.ctx, r.id, labels) if err != nil { diff --git a/runner/pool/interfaces.go b/runner/pool/interfaces.go index 9f1f7b85..1e07ab52 100644 --- a/runner/pool/interfaces.go +++ b/runner/pool/interfaces.go @@ -34,6 +34,7 @@ type poolHelper interface { JwtToken() string String() string GetCallbackURL() string + GetMetadataURL() string FindPoolByTags(labels []string) (params.Pool, error) GetPoolByID(poolID string) (params.Pool, error) ValidateOwner(job params.WorkflowJob) error diff --git a/runner/pool/organization.go b/runner/pool/organization.go index b8efba72..a5985f8d 100644 --- a/runner/pool/organization.go +++ b/runner/pool/organization.go @@ -192,6 +192,10 @@ func (r *organization) GetCallbackURL() string { return r.cfgInternal.InstanceCallbackURL } +func (r *organization) GetMetadataURL() string { + return r.cfgInternal.InstanceMetadataURL +} + func (r *organization) FindPoolByTags(labels []string) (params.Pool, error) { pool, err := r.store.FindOrganizationPoolByTags(r.ctx, r.id, labels) if err != nil { diff --git a/runner/pool/pool.go b/runner/pool/pool.go index d17b7224..aef90357 100644 --- a/runner/pool/pool.go +++ b/runner/pool/pool.go @@ -362,6 +362,7 @@ func (r *basePoolManager) AddRunner(ctx context.Context, poolID string) error { OSType: pool.OSType, GithubRegistrationToken: []byte(tk), CallbackURL: r.helper.GetCallbackURL(), + MetadataURL: r.helper.GetMetadataURL(), CreateAttempt: 1, } @@ -551,6 +552,7 @@ func (r *basePoolManager) addInstanceToProvider(instance params.Instance) error Tools: r.tools, RepoURL: r.helper.GithubURL(), GithubRunnerAccessToken: string(instance.GithubRegistrationToken), + MetadataURL: instance.MetadataURL, CallbackURL: instance.CallbackURL, InstanceToken: jwtToken, OSArch: pool.OSArch, diff --git a/runner/pool/repository.go b/runner/pool/repository.go index 598ac939..f5415d7a 100644 --- a/runner/pool/repository.go +++ b/runner/pool/repository.go @@ -193,6 +193,10 @@ func (r *repository) GetCallbackURL() string { return r.cfgInternal.InstanceCallbackURL } +func (r *repository) GetMetadataURL() string { + return r.cfgInternal.InstanceMetadataURL +} + func (r *repository) FindPoolByTags(labels []string) (params.Pool, error) { pool, err := r.store.FindRepositoryPoolByTags(r.ctx, r.id, labels) if err != nil { diff --git a/runner/runner.go b/runner/runner.go index 96398a13..1e289bd9 100644 --- a/runner/runner.go +++ b/runner/runner.go @@ -239,6 +239,7 @@ func (p *poolManagerCtrl) getInternalConfig(credsName string) (params.Internal, OAuth2Token: creds.OAuth2Token, ControllerID: p.controllerID, InstanceCallbackURL: p.config.Default.CallbackURL, + InstanceMetadataURL: p.config.Default.MetadataURL, JWTSecret: p.config.JWTAuth.Secret, GithubCredentialsDetails: params.GithubCredentials{ Name: creds.Name, diff --git a/testdata/config.toml b/testdata/config.toml index 30867186..7aff4207 100644 --- a/testdata/config.toml +++ b/testdata/config.toml @@ -5,13 +5,16 @@ # 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 URL is used by instances to retrieve information they need to set themselves +# up. Access to this URL is granted using the same JWT token used to send back +# status updates. Once the instance transitions to "installed" or "failed" state, +# access to both the status and metadata endpoints is disabled. +metadata_url = "https://garm.example.com/api/v1/metadata" + # This folder is defined here for future use. Right now, we create a SSH # public/private key-pair. config_dir = "/etc/garm" + # Uncomment this line if you'd like to log to a file instead of standard output. # log_file = "/tmp/runner-manager.log" diff --git a/util/util.go b/util/util.go index 5e6a85d2..e3b9e9ba 100644 --- a/util/util.go +++ b/util/util.go @@ -212,8 +212,7 @@ func GetCloudConfig(bootstrapParams params.BootstrapInstance, tools github.Runne FileName: *tools.Filename, DownloadURL: *tools.DownloadURL, TempDownloadToken: tempToken, - GithubToken: bootstrapParams.GithubRunnerAccessToken, - TokenURL: bootstrapParams.TokenURL, + MetadataURL: bootstrapParams.MetadataURL, RunnerUsername: config.DefaultUser, RunnerGroup: config.DefaultUser, RepoURL: bootstrapParams.RepoURL, @@ -223,6 +222,11 @@ func GetCloudConfig(bootstrapParams params.BootstrapInstance, tools github.Runne CallbackToken: bootstrapParams.InstanceToken, } + if bootstrapParams.MetadataURL == "" { + // Token URL is not set. Add the GH runner registration token to userdata. + installRunnerParams.GithubToken = bootstrapParams.GithubRunnerAccessToken + } + installScript, err := cloudconfig.InstallRunnerScript(installRunnerParams) if err != nil { return "", errors.Wrap(err, "generating script")