Skip to content

Commit

Permalink
Merge pull request #174 from gabriel-samfira/add-ca-cert-bundle-metadata
Browse files Browse the repository at this point in the history
Add root CA bundle metadata URL
  • Loading branch information
gabriel-samfira authored Aug 28, 2023
2 parents f463a41 + 2caf25f commit 0da5f10
Show file tree
Hide file tree
Showing 9 changed files with 183 additions and 17 deletions.
16 changes: 0 additions & 16 deletions apiserver/controllers/instances.go
Original file line number Diff line number Diff line change
Expand Up @@ -316,19 +316,3 @@ 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)
if _, err := w.Write([]byte(token)); err != nil {
log.Printf("failed to encode response: %q", err)
}
}
52 changes: 52 additions & 0 deletions apiserver/controllers/metadata.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// Copyright 2023 Cloudbase Solutions SRL
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.

package controllers

import (
"encoding/json"
"log"
"net/http"
)

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)
if _, err := w.Write([]byte(token)); err != nil {
log.Printf("failed to encode response: %q", err)
}
}

func (a *APIController) RootCertificateBundleHandler(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()

bundle, err := a.r.GetRootCertificateBundle(ctx)
if err != nil {
handleError(w, err)
return
}

w.Header().Set("Content-Type", "application/json")
if err := json.NewEncoder(w).Encode(bundle); err != nil {
log.Printf("failed to encode response: %q", err)
}
}
10 changes: 9 additions & 1 deletion apiserver/routers/routers.go
Original file line number Diff line number Diff line change
Expand Up @@ -108,10 +108,18 @@ func NewAPIRouter(han *controllers.APIController, logWriter io.Writer, authMiddl
callbackRouter.Handle("/status", http.HandlerFunc(han.InstanceStatusMessageHandler)).Methods("POST", "OPTIONS")
callbackRouter.Use(instanceMiddleware.Middleware)

///////////////////
// Metadata URLs //
///////////////////
metadataRouter := apiSubRouter.PathPrefix("/metadata").Subrouter()
metadataRouter.Use(instanceMiddleware.Middleware)

// Registration token
metadataRouter.Handle("/runner-registration-token/", http.HandlerFunc(han.InstanceGithubRegistrationTokenHandler)).Methods("GET", "OPTIONS")
metadataRouter.Handle("/runner-registration-token", http.HandlerFunc(han.InstanceGithubRegistrationTokenHandler)).Methods("GET", "OPTIONS")
metadataRouter.Use(instanceMiddleware.Middleware)
metadataRouter.Handle("/system/cert-bundle/", http.HandlerFunc(han.RootCertificateBundleHandler)).Methods("GET", "OPTIONS")
metadataRouter.Handle("/system/cert-bundle", http.HandlerFunc(han.RootCertificateBundleHandler)).Methods("GET", "OPTIONS")

// Login
authRouter := apiSubRouter.PathPrefix("/auth").Subrouter()
authRouter.Handle("/{login:login\\/?}", http.HandlerFunc(han.LoginHandler)).Methods("POST", "OPTIONS")
Expand Down
20 changes: 20 additions & 0 deletions auth/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ package auth
import (
"context"

runnerErrors "github.com/cloudbase/garm-provider-common/errors"
"github.com/cloudbase/garm/params"
)

Expand All @@ -38,6 +39,7 @@ const (
instanceEntityKey contextFlags = "entity"
instanceRunnerStatus contextFlags = "status"
instanceTokenFetched contextFlags = "tokenFetched"
instanceParams contextFlags = "instanceParams"
)

func SetInstanceID(ctx context.Context, id string) context.Context {
Expand All @@ -64,6 +66,23 @@ func InstanceTokenFetched(ctx context.Context) bool {
return elem.(bool)
}

func SetInstanceParams(ctx context.Context, instance params.Instance) context.Context {
return context.WithValue(ctx, instanceParams, instance)
}

func InstanceParams(ctx context.Context) (params.Instance, error) {
elem := ctx.Value(instanceParams)
if elem == nil {
return params.Instance{}, runnerErrors.ErrNotFound
}

instanceParams, ok := elem.(params.Instance)
if !ok {
return params.Instance{}, runnerErrors.ErrNotFound
}
return instanceParams, nil
}

func SetInstanceRunnerStatus(ctx context.Context, val params.RunnerStatus) context.Context {
return context.WithValue(ctx, instanceRunnerStatus, val)
}
Expand Down Expand Up @@ -130,6 +149,7 @@ func PopulateInstanceContext(ctx context.Context, instance params.Instance) cont
ctx = SetInstancePoolID(ctx, instance.PoolID)
ctx = SetInstanceRunnerStatus(ctx, instance.RunnerStatus)
ctx = SetInstanceTokenFetched(ctx, instance.TokenFetched)
ctx = SetInstanceParams(ctx, instance)
return ctx
}

Expand Down
38 changes: 38 additions & 0 deletions params/params.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,11 @@
package params

import (
"bytes"
"crypto/x509"
"encoding/json"
"encoding/pem"
"fmt"
"time"

commonParams "github.com/cloudbase/garm-provider-common/params"
Expand Down Expand Up @@ -410,6 +414,36 @@ type GithubCredentials struct {
CABundle []byte `json:"ca_bundle,omitempty"`
}

func (g GithubCredentials) RootCertificateBundle() (CertificateBundle, error) {
if len(g.CABundle) == 0 {
return CertificateBundle{}, nil
}

ret := map[string][]byte{}

var block *pem.Block
var rest []byte = g.CABundle
for {
block, rest = pem.Decode(rest)
if block == nil {
break
}
pub, err := x509.ParseCertificate(block.Bytes)
if err != nil {
return CertificateBundle{}, err
}
out := &bytes.Buffer{}
if err := pem.Encode(out, &pem.Block{Type: "CERTIFICATE", Bytes: block.Bytes}); err != nil {
return CertificateBundle{}, err
}
ret[fmt.Sprintf("%d", pub.SerialNumber)] = out.Bytes()
}

return CertificateBundle{
RootCertificates: ret,
}, nil
}

// used by swagger client generated code
type Credentials []GithubCredentials

Expand Down Expand Up @@ -513,3 +547,7 @@ type HookInfo struct {
Active bool `json:"active"`
InsecureSSL bool `json:"insecure_ssl"`
}

type CertificateBundle struct {
RootCertificates map[string][]byte `json:"root_certificates"`
}
24 changes: 24 additions & 0 deletions runner/common/mocks/PoolManager.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions runner/common/pool.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ type PoolManager interface {
GetWebhookInfo(ctx context.Context) (params.HookInfo, error)
UninstallWebhook(ctx context.Context) error

RootCABundle() (params.CertificateBundle, error)

// PoolManager lifecycle functions. Start/stop pool.
Start() error
Stop() error
Expand Down
34 changes: 34 additions & 0 deletions runner/metadata.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package runner

import (
"context"
"log"

runnerErrors "github.com/cloudbase/garm-provider-common/errors"
"github.com/cloudbase/garm/auth"
"github.com/cloudbase/garm/params"
"github.com/pkg/errors"
)

func (r *Runner) GetRootCertificateBundle(ctx context.Context) (params.CertificateBundle, error) {
instance, err := auth.InstanceParams(ctx)
if err != nil {
log.Printf("failed to get instance params: %s", err)
return params.CertificateBundle{}, runnerErrors.ErrUnauthorized
}

poolMgr, err := r.getPoolManagerFromInstance(ctx, instance)
if err != nil {
return params.CertificateBundle{}, errors.Wrap(err, "fetching pool manager for instance")
}

bundle, err := poolMgr.RootCABundle()
if err != nil {
log.Printf("failed to get root CA bundle: %s", err)
// The root CA bundle is invalid. Return an empty bundle to the runner and log the event.
return params.CertificateBundle{
RootCertificates: make(map[string][]byte),
}, nil
}
return bundle, nil
}
4 changes: 4 additions & 0 deletions runner/pool/pool.go
Original file line number Diff line number Diff line change
Expand Up @@ -1643,3 +1643,7 @@ func (r *basePoolManager) UninstallWebhook(ctx context.Context) error {
func (r *basePoolManager) GetWebhookInfo(ctx context.Context) (params.HookInfo, error) {
return r.helper.GetHookInfo(ctx)
}

func (r *basePoolManager) RootCABundle() (params.CertificateBundle, error) {
return r.credsDetails.RootCertificateBundle()
}

0 comments on commit 0da5f10

Please sign in to comment.