Define a metadata subrouter

Define a metadata subrouter and move the token endpoint there. We may
end up needing multiple endpoints for various purposes in the future.

Signed-off-by: Gabriel Adrian Samfira <gsamfira@cloudbasesolutions.com>
This commit is contained in:
Gabriel Adrian Samfira 2022-12-02 19:48:32 +00:00
parent a078645ab2
commit 0869073906
19 changed files with 63 additions and 30 deletions

View file

@ -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")

View file

@ -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

View file

@ -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")
}
}

View file

@ -1,6 +1,6 @@
#!/bin/bash
set -ex
set -e
set -o pipefail
CALLBACK_URL="GARM_CALLBACK_URL"

View file

@ -1,6 +1,6 @@
#!/bin/bash
set -ex
set -e
set -o pipefail
CALLBACK_URL="GARM_CALLBACK_URL"

View file

@ -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}"
}

View file

@ -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)

View file

@ -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"`

View file

@ -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,

View file

@ -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.

View file

@ -113,6 +113,7 @@ type CreateInstanceParams struct {
Status common.InstanceStatus
RunnerStatus common.RunnerStatus
CallbackURL string
MetadataURL string
CreateAttempt int `json:"-"`
GithubRegistrationToken []byte `json:"-"`
}

View file

@ -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 {

View file

@ -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

View file

@ -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 {

View file

@ -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,

View file

@ -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 {

View file

@ -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,

11
testdata/config.toml vendored
View file

@ -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"

View file

@ -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")