use dependency injection for embedded envoy provider

This commit is contained in:
Caleb Doxsey 2021-06-21 16:28:49 -06:00
parent e09217ece5
commit 5c08990501
14 changed files with 136 additions and 123 deletions

View file

@ -21,7 +21,7 @@ func main() {
flag.Parse()
if *versionFlag {
fmt.Println("pomerium:", version.FullVersion())
fmt.Println("envoy:", files.FullVersion())
fmt.Println("envoy:", files.EmbeddedEnvoyProvider.Version()+"+"+files.EmbeddedEnvoyProvider.Checksum())
return
}
@ -33,5 +33,5 @@ func main() {
}
func run(ctx context.Context) error {
return pomerium.Run(ctx, *configFile)
return pomerium.Run(ctx, *configFile, files.EmbeddedEnvoyProvider)
}

View file

@ -22,14 +22,16 @@ type MetricsManager struct {
addr string
basicAuth string
handler http.Handler
envoyVersion string
}
// NewMetricsManager creates a new MetricsManager.
func NewMetricsManager(ctx context.Context, src Source) *MetricsManager {
func NewMetricsManager(ctx context.Context, src Source, envoyVersion string) *MetricsManager {
ctx = log.WithContext(ctx, func(c zerolog.Context) zerolog.Context {
return c.Str("service", "metrics_manager")
})
mgr := &MetricsManager{}
mgr.envoyVersion = envoyVersion
metrics.RegisterInfoMetrics()
src.OnConfigChange(ctx, mgr.OnConfigChange)
mgr.OnConfigChange(ctx, src.GetConfig())
@ -73,7 +75,7 @@ func (mgr *MetricsManager) updateInfo(cfg *Config) {
hostname = "__unknown__"
}
metrics.SetBuildInfo(serviceName, hostname)
metrics.SetBuildInfo(serviceName, hostname, mgr.envoyVersion)
mgr.serviceName = serviceName
}

View file

@ -19,7 +19,7 @@ func TestMetricsManager(t *testing.T) {
MetricsAddr: "ADDRESS",
},
})
mgr := NewMetricsManager(ctx, src)
mgr := NewMetricsManager(ctx, src, "v1.2.3")
srv1 := httptest.NewServer(mgr)
defer srv1.Close()
srv2 := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
@ -44,7 +44,7 @@ func TestMetricsManagerBasicAuth(t *testing.T) {
MetricsBasicAuth: base64.StdEncoding.EncodeToString([]byte("x:y")),
},
})
mgr := NewMetricsManager(context.Background(), src)
mgr := NewMetricsManager(context.Background(), src, "v1.2.3")
srv1 := httptest.NewServer(mgr)
defer srv1.Close()

View file

@ -15,7 +15,6 @@ import (
"github.com/pomerium/pomerium/config"
"github.com/pomerium/pomerium/internal/directory"
"github.com/pomerium/pomerium/internal/envoy/files"
"github.com/pomerium/pomerium/internal/identity"
"github.com/pomerium/pomerium/internal/identity/manager"
"github.com/pomerium/pomerium/internal/log"
@ -44,7 +43,7 @@ type DataBroker struct {
}
// New creates a new databroker service.
func New(cfg *config.Config) (*DataBroker, error) {
func New(cfg *config.Config, envoyVersion string) (*DataBroker, error) {
localListener, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
return nil, err
@ -54,7 +53,7 @@ func New(cfg *config.Config) (*DataBroker, error) {
ui, si := grpcutil.AttachMetadataInterceptors(
metadata.Pairs(
grpcutil.MetadataKeyEnvoyVersion, files.FullVersion(),
grpcutil.MetadataKeyEnvoyVersion, envoyVersion,
grpcutil.MetadataKeyPomeriumVersion, version.FullVersion(),
),
)

View file

@ -29,7 +29,7 @@ func TestNew(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
tt.opts.Provider = "google"
_, err := New(&config.Config{Options: &tt.opts})
_, err := New(&config.Config{Options: &tt.opts}, "v1.2.3")
if (err != nil) != tt.wantErr {
t.Errorf("New() error = %v, wantErr %v", err, tt.wantErr)
return

View file

@ -23,7 +23,6 @@ import (
"github.com/pomerium/pomerium/internal/controlplane"
"github.com/pomerium/pomerium/internal/databroker"
"github.com/pomerium/pomerium/internal/envoy"
"github.com/pomerium/pomerium/internal/envoy/files"
"github.com/pomerium/pomerium/internal/log"
"github.com/pomerium/pomerium/internal/registry"
"github.com/pomerium/pomerium/internal/urlutil"
@ -32,9 +31,11 @@ import (
)
// Run runs the main pomerium application.
func Run(ctx context.Context, configFile string) error {
func Run(ctx context.Context, configFile string, embeddedEnvoyProvider envoy.EmbeddedEnvoyProvider) error {
envoyVersion := embeddedEnvoyProvider.Version() + "+" + embeddedEnvoyProvider.Checksum()
log.Info(ctx).
Str("envoy_version", files.FullVersion()).
Str("envoy_version", envoyVersion).
Str("version", version.FullVersion()).
Msg("cmd/pomerium")
@ -60,13 +61,13 @@ func Run(ctx context.Context, configFile string) error {
// override the default http transport so we can use the custom CA in the TLS client config (#1570)
http.DefaultTransport = config.NewHTTPTransport(src)
metricsMgr := config.NewMetricsManager(ctx, src)
metricsMgr := config.NewMetricsManager(ctx, src, envoyVersion)
defer metricsMgr.Close()
traceMgr := config.NewTraceManager(ctx, src)
defer traceMgr.Close()
// setup the control plane
controlPlane, err := controlplane.NewServer(src.GetConfig().Options.Services, metricsMgr)
controlPlane, err := controlplane.NewServer(src.GetConfig().Options.Services, metricsMgr, envoyVersion)
if err != nil {
return fmt.Errorf("error creating control plane: %w", err)
}
@ -90,7 +91,7 @@ func Run(ctx context.Context, configFile string) error {
log.Info(ctx).Str("port", httpPort).Msg("HTTP server started")
// create envoy server
envoyServer, err := envoy.NewServer(ctx, src, grpcPort, httpPort, controlPlane.Builder)
envoyServer, err := envoy.NewServer(ctx, src, grpcPort, httpPort, controlPlane.Builder, embeddedEnvoyProvider)
if err != nil {
return fmt.Errorf("error creating envoy server: %w", err)
}
@ -109,7 +110,7 @@ func Run(ctx context.Context, configFile string) error {
}
var dataBrokerServer *databroker_service.DataBroker
if config.IsDataBroker(src.GetConfig().Options.Services) {
dataBrokerServer, err = setupDataBroker(ctx, src, controlPlane)
dataBrokerServer, err = setupDataBroker(ctx, src, controlPlane, envoyVersion)
if err != nil {
return fmt.Errorf("setting up databroker: %w", err)
}
@ -200,8 +201,8 @@ func setupAuthorize(ctx context.Context, src config.Source, controlPlane *contro
return svc, nil
}
func setupDataBroker(ctx context.Context, src config.Source, controlPlane *controlplane.Server) (*databroker_service.DataBroker, error) {
svc, err := databroker_service.New(src.GetConfig())
func setupDataBroker(ctx context.Context, src config.Source, controlPlane *controlplane.Server, envoyVersion string) (*databroker_service.DataBroker, error) {
svc, err := databroker_service.New(src.GetConfig(), envoyVersion)
if err != nil {
return nil, fmt.Errorf("error creating databroker service: %w", err)
}

View file

@ -8,6 +8,20 @@ import (
"time"
)
type mockEmbeddedEnvoyProvider struct{}
func (mockEmbeddedEnvoyProvider) Checksum() string {
return "CHECKSUM"
}
func (mockEmbeddedEnvoyProvider) Extract(ctx context.Context) (string, error) {
return "", nil
}
func (mockEmbeddedEnvoyProvider) Version() string {
return "VERSION"
}
func Test_run(t *testing.T) {
os.Clearenv()
tests := []struct {
@ -127,7 +141,7 @@ func Test_run(t *testing.T) {
ctx, clearTimeout := context.WithTimeout(context.Background(), 500*time.Millisecond)
defer clearTimeout()
err = Run(ctx, configFile)
err = Run(ctx, configFile, mockEmbeddedEnvoyProvider{})
if (err != nil) != tt.wantErr {
t.Errorf("run() error = %v, wantErr %v", err, tt.wantErr)
}

View file

@ -20,7 +20,6 @@ import (
"github.com/pomerium/pomerium/config/envoyconfig"
"github.com/pomerium/pomerium/config/envoyconfig/filemgr"
"github.com/pomerium/pomerium/internal/controlplane/xdsmgr"
"github.com/pomerium/pomerium/internal/envoy/files"
"github.com/pomerium/pomerium/internal/httputil/reproxy"
"github.com/pomerium/pomerium/internal/log"
"github.com/pomerium/pomerium/internal/telemetry"
@ -68,7 +67,7 @@ type Server struct {
}
// NewServer creates a new Server. Listener ports are chosen by the OS.
func NewServer(name string, metricsMgr *config.MetricsManager) (*Server, error) {
func NewServer(name string, metricsMgr *config.MetricsManager, envoyVersion string) (*Server, error) {
srv := &Server{
metricsMgr: metricsMgr,
reproxy: reproxy.New(),
@ -87,7 +86,7 @@ func NewServer(name string, metricsMgr *config.MetricsManager) (*Server, error)
}
ui, si := grpcutil.AttachMetadataInterceptors(
metadata.Pairs(
grpcutil.MetadataKeyEnvoyVersion, files.FullVersion(),
grpcutil.MetadataKeyEnvoyVersion, envoyVersion,
grpcutil.MetadataKeyPomeriumVersion, version.FullVersion(),
),
)

View file

@ -1,68 +0,0 @@
package envoy
import (
"bytes"
"context"
"fmt"
"io/fs"
"os"
"path/filepath"
"sync"
"github.com/natefinch/atomic"
"github.com/pomerium/pomerium/internal/envoy/files"
"github.com/pomerium/pomerium/internal/log"
)
const (
embeddedEnvoyPermissions fs.FileMode = 0o700
embeddedDirectoryPermissions fs.FileMode = 0o755
)
var (
embeddedFilesBaseDirectory = filepath.Join(os.TempDir(), "pomerium-embedded-files")
extractEmbeddedEnvoyOnce sync.Once
)
func extractEmbeddedEnvoy(ctx context.Context) (outPath string, err error) {
extractEmbeddedEnvoyOnce.Do(func() {
// clean up our base directory before starting
err = os.RemoveAll(embeddedFilesBaseDirectory)
if err != nil {
err = fmt.Errorf("error cleaning embedded file directory: (directory=%s): %w", embeddedFilesBaseDirectory, err)
return
}
// create known directory base to clean at startup
err = os.MkdirAll(embeddedFilesBaseDirectory, embeddedDirectoryPermissions)
if err != nil {
err = fmt.Errorf("error creating embedded file directory: (directory=%s): %w", embeddedFilesBaseDirectory, err)
return
}
// build a random temp directory inside our base directory to guarantee permissions
var tmpDir string
tmpDir, err = os.MkdirTemp(embeddedFilesBaseDirectory, "envoy-")
if err != nil {
err = fmt.Errorf("error creating embedded file tmp directory: (directory=%s): %w", embeddedFilesBaseDirectory, err)
return
}
outPath = filepath.Join(tmpDir, "envoy")
log.Info(ctx).Str("path", outPath).Msg("extracting envoy binary")
err = atomic.WriteFile(outPath, bytes.NewReader(files.Binary()))
if err != nil {
err = fmt.Errorf("error extracting embedded envoy binary to temporary directory (path=%s): %w", outPath, err)
return
}
err = os.Chmod(outPath, embeddedEnvoyPermissions)
if err != nil {
err = fmt.Errorf("error chmoding embedded envoy binary: %w", err)
return
}
})
return outPath, err
}

View file

@ -30,16 +30,15 @@ import (
"github.com/shirou/gopsutil/v3/process"
"google.golang.org/protobuf/encoding/protojson"
"github.com/pomerium/pomerium/internal/envoy/files"
"github.com/pomerium/pomerium/config"
"github.com/pomerium/pomerium/config/envoyconfig"
"github.com/pomerium/pomerium/internal/log"
)
const (
workingDirectoryName = ".pomerium-envoy"
configFileName = "envoy-config.yaml"
workingDirectoryName = ".pomerium-envoy"
workingDirectoryPermissions = 0o755
configFileName = "envoy-config.yaml"
)
type serverOptions struct {
@ -47,6 +46,13 @@ type serverOptions struct {
logLevel string
}
// An EmbeddedEnvoyProvider provides an implementation of an embedded envoy.
type EmbeddedEnvoyProvider interface {
Checksum() string
Extract(ctx context.Context) (outPath string, err error)
Version() string
}
// A Server is a pomerium proxy implemented via envoy.
type Server struct {
wd string
@ -63,14 +69,19 @@ type Server struct {
}
// NewServer creates a new server with traffic routed by envoy.
func NewServer(ctx context.Context, src config.Source, grpcPort, httpPort string, builder *envoyconfig.Builder) (*Server, error) {
func NewServer(ctx context.Context,
src config.Source,
grpcPort, httpPort string,
builder *envoyconfig.Builder,
embeddedEnvoyProvider EmbeddedEnvoyProvider,
) (*Server, error) {
wd := filepath.Join(os.TempDir(), workingDirectoryName)
err := os.MkdirAll(wd, embeddedEnvoyPermissions)
err := os.MkdirAll(wd, workingDirectoryPermissions)
if err != nil {
return nil, fmt.Errorf("error creating temporary working directory for envoy: %w", err)
}
envoyPath, err := extractEmbeddedEnvoy(ctx)
envoyPath, err := embeddedEnvoyProvider.Extract(ctx)
if err != nil {
log.Warn(ctx).Err(err).Send()
envoyPath = "envoy"
@ -82,7 +93,7 @@ func NewServer(ctx context.Context, src config.Source, grpcPort, httpPort string
}
// Checksum is written at build time, if it's not empty we verify the binary
if files.Checksum() != "" {
if embeddedEnvoyProvider.Checksum() != "" {
bs, err := ioutil.ReadFile(fullEnvoyPath)
if err != nil {
return nil, fmt.Errorf("error reading envoy binary for checksum verification: %w", err)
@ -90,8 +101,8 @@ func NewServer(ctx context.Context, src config.Source, grpcPort, httpPort string
h := sha256.New()
h.Write(bs)
s := hex.EncodeToString(h.Sum(nil))
if files.Checksum() != s {
return nil, fmt.Errorf("invalid envoy binary, expected %s but got %s", files.Checksum(), s)
if embeddedEnvoyProvider.Checksum() != s {
return nil, fmt.Errorf("invalid envoy binary, expected %s but got %s", embeddedEnvoyProvider.Checksum(), s)
}
} else {
log.Info(ctx).Msg("no checksum defined, envoy binary will not be verified!")
@ -113,7 +124,7 @@ func NewServer(ctx context.Context, src config.Source, grpcPort, httpPort string
log.Info(ctx).
Str("path", envoyPath).
Str("checksum", files.Checksum()).
Str("checksum", embeddedEnvoyProvider.Checksum()).
Msg("running envoy")
return srv, nil

View file

@ -2,26 +2,85 @@
package files
import (
_ "embed" // for embedded files
"bytes"
"context"
"fmt"
"io/fs"
"os"
"path/filepath"
"strings"
"sync"
"github.com/natefinch/atomic"
"github.com/pomerium/pomerium/internal/log"
)
// Binary returns the raw envoy binary bytes.
func Binary() []byte {
return rawBinary
const (
embeddedEnvoyPermissions fs.FileMode = 0o700
embeddedDirectoryPermissions fs.FileMode = 0o755
)
var (
embeddedFilesBaseDirectory = filepath.Join(os.TempDir(), "pomerium-embedded-files")
extractOnce sync.Once
extractOutPath string
extractErr error
)
type embeddedEnvoyProvider struct {
}
// Checksum returns the checksum for the embedded envoy binary.
func Checksum() string {
func (embeddedEnvoyProvider) Checksum() string {
return strings.Fields(rawChecksum)[0]
}
// FullVersion returns the full version string for envoy.
func FullVersion() string {
return Version() + "+" + Checksum()
func (embeddedEnvoyProvider) Extract(ctx context.Context) (string, error) {
extractOnce.Do(func() {
// clean up our base directory before starting
extractErr = os.RemoveAll(embeddedFilesBaseDirectory)
if extractErr != nil {
extractErr = fmt.Errorf("error cleaning embedded file directory: (directory=%s): %w", embeddedFilesBaseDirectory, extractErr)
return
}
// create known directory base to clean at startup
extractErr = os.MkdirAll(embeddedFilesBaseDirectory, embeddedDirectoryPermissions)
if extractErr != nil {
extractErr = fmt.Errorf("error creating embedded file directory: (directory=%s): %w", embeddedFilesBaseDirectory, extractErr)
return
}
// build a random temp directory inside our base directory to guarantee permissions
var tmpDir string
tmpDir, extractErr = os.MkdirTemp(embeddedFilesBaseDirectory, "envoy-")
if extractErr != nil {
extractErr = fmt.Errorf("error creating embedded file tmp directory: (directory=%s): %w", embeddedFilesBaseDirectory, extractErr)
return
}
extractOutPath = filepath.Join(tmpDir, "envoy")
log.Info(ctx).Str("path", extractOutPath).Msg("extracting envoy binary")
extractErr = atomic.WriteFile(extractOutPath, bytes.NewReader(rawBinary))
if extractErr != nil {
extractErr = fmt.Errorf("error extracting embedded envoy binary to temporary directory (path=%s): %w", extractOutPath, extractErr)
return
}
extractErr = os.Chmod(extractOutPath, embeddedEnvoyPermissions)
if extractErr != nil {
extractErr = fmt.Errorf("error chmoding embedded envoy binary: %w", extractErr)
return
}
})
return extractOutPath, extractErr
}
// Version returns the envoy version.
func Version() string {
func (embeddedEnvoyProvider) Version() string {
return strings.TrimSpace(rawVersion)
}
// EmbeddedEnvoyProvider provides an embedded envoy binary via go:embed.
var EmbeddedEnvoyProvider = new(embeddedEnvoyProvider)

View file

@ -133,7 +133,6 @@ func SetDBConfigInfo(ctx context.Context, service, configID string, version uint
); err != nil {
log.Error(ctx).Err(err).Msg("telemetry/metrics: failed to record config error count")
}
}
// SetDBConfigRejected records that a certain databroker config version has been rejected
@ -176,8 +175,8 @@ func SetConfigInfo(ctx context.Context, service, configName string, checksum uin
// SetBuildInfo records the pomerium build info. You must call RegisterInfoMetrics to
// have this exported
func SetBuildInfo(service, hostname string) {
registry.setBuildInfo(service, hostname)
func SetBuildInfo(service, hostname, envoyVersion string) {
registry.setBuildInfo(service, hostname, envoyVersion)
}
// RegisterInfoMetrics registers non-view based metrics registry globally for export

View file

@ -6,7 +6,6 @@ import (
"runtime"
"testing"
"github.com/pomerium/pomerium/internal/envoy/files"
"github.com/pomerium/pomerium/internal/version"
"github.com/pomerium/pomerium/pkg/metrics"
@ -74,13 +73,13 @@ func Test_SetBuildInfo(t *testing.T) {
wantLabels := []metricdata.LabelValue{
{Value: "test_service", Present: true},
{Value: version.FullVersion(), Present: true},
{Value: files.FullVersion(), Present: true},
{Value: "v1.2.3", Present: true},
{Value: version.GitCommit, Present: true},
{Value: runtime.Version(), Present: true},
{Value: "test_host", Present: true},
}
SetBuildInfo("test_service", "test_host")
SetBuildInfo("test_service", "test_host", "v1.2.3")
testMetricRetrieval(registry.registry.Read(), t, wantLabels, int64(1), metrics.BuildInfo)
}

View file

@ -8,8 +8,6 @@ import (
"go.opencensus.io/metric"
"go.opencensus.io/metric/metricdata"
"github.com/pomerium/pomerium/internal/envoy/files"
"github.com/pomerium/pomerium/internal/log"
"github.com/pomerium/pomerium/internal/version"
"github.com/pomerium/pomerium/pkg/metrics"
@ -82,14 +80,14 @@ func (r *metricRegistry) init() {
// SetBuildInfo records the pomerium build info. You must call RegisterInfoMetrics to
// have this exported
func (r *metricRegistry) setBuildInfo(service, hostname string) {
func (r *metricRegistry) setBuildInfo(service, hostname, envoyVersion string) {
if registry.buildInfo == nil {
return
}
m, err := registry.buildInfo.GetEntry(
metricdata.NewLabelValue(service),
metricdata.NewLabelValue(version.FullVersion()),
metricdata.NewLabelValue(files.FullVersion()),
metricdata.NewLabelValue(envoyVersion),
metricdata.NewLabelValue(version.GitCommit),
metricdata.NewLabelValue((runtime.Version())),
metricdata.NewLabelValue(hostname),