Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
zero: managed mode controller (#4459)
- Loading branch information
Showing
11 changed files
with
417 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,83 @@ | ||
// Package cmd implements the pomerium zero command. | ||
package cmd | ||
|
||
import ( | ||
"context" | ||
"errors" | ||
"fmt" | ||
"os" | ||
"os/signal" | ||
"syscall" | ||
|
||
"github.com/mattn/go-isatty" | ||
"github.com/rs/zerolog" | ||
"github.com/rs/zerolog/log" | ||
|
||
"github.com/pomerium/pomerium/internal/zero/controller" | ||
) | ||
|
||
// Run runs the pomerium zero command. | ||
func Run(ctx context.Context) error { | ||
err := setupLogger() | ||
if err != nil { | ||
return fmt.Errorf("error setting up logger: %w", err) | ||
} | ||
|
||
token := getToken() | ||
if token == "" { | ||
return errors.New("no token provided") | ||
} | ||
|
||
return controller.Run( | ||
withInterrupt(ctx), | ||
controller.WithAPIToken(token), | ||
controller.WithClusterAPIEndpoint(getClusterAPIEndpoint()), | ||
controller.WithConnectAPIEndpoint(getConnectAPIEndpoint()), | ||
) | ||
} | ||
|
||
// IsManagedMode returns true if Pomerium should start in managed mode using this command. | ||
func IsManagedMode() bool { | ||
return getToken() != "" | ||
} | ||
|
||
func withInterrupt(ctx context.Context) context.Context { | ||
ctx, cancel := context.WithCancel(ctx) | ||
go func(ctx context.Context) { | ||
ch := make(chan os.Signal, 2) | ||
defer signal.Stop(ch) | ||
|
||
signal.Notify(ch, os.Interrupt) | ||
signal.Notify(ch, syscall.SIGTERM) | ||
|
||
select { | ||
case sig := <-ch: | ||
log.Ctx(ctx).Info().Str("signal", sig.String()).Msg("quitting...") | ||
case <-ctx.Done(): | ||
} | ||
cancel() | ||
}(ctx) | ||
return ctx | ||
} | ||
|
||
func setupLogger() error { | ||
if isatty.IsTerminal(os.Stdin.Fd()) { | ||
log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr}) | ||
} else { | ||
log.Logger = zerolog.New(os.Stderr) | ||
} | ||
|
||
if rawLvl, ok := os.LookupEnv("LOG_LEVEL"); ok { | ||
lvl, err := zerolog.ParseLevel(rawLvl) | ||
if err != nil { | ||
return err | ||
} | ||
log.Logger = log.Logger.Level(lvl) | ||
} else { | ||
log.Logger = log.Logger.Level(zerolog.InfoLevel) | ||
} | ||
|
||
// set the default context logger | ||
zerolog.DefaultContextLogger = &log.Logger | ||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
package cmd | ||
|
||
import "os" | ||
|
||
const ( | ||
// PomeriumZeroTokenEnv is the environment variable name for the API token. | ||
//nolint: gosec | ||
PomeriumZeroTokenEnv = "POMERIUM_ZERO_TOKEN" | ||
) | ||
|
||
func getToken() string { | ||
return os.Getenv(PomeriumZeroTokenEnv) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
//go:build !release | ||
|
||
package cmd | ||
|
||
import "os" | ||
|
||
func getConnectAPIEndpoint() string { | ||
connectServerEndpoint := os.Getenv("CONNECT_SERVER_ENDPOINT") | ||
if connectServerEndpoint == "" { | ||
connectServerEndpoint = "http://localhost:8721" | ||
} | ||
return connectServerEndpoint | ||
} | ||
|
||
func getClusterAPIEndpoint() string { | ||
clusterAPIEndpoint := os.Getenv("CLUSTER_API_ENDPOINT") | ||
if clusterAPIEndpoint == "" { | ||
clusterAPIEndpoint = "http://localhost:8720/cluster/v1" | ||
} | ||
return clusterAPIEndpoint | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
//go:build release | ||
|
||
package cmd | ||
|
||
func getConnectAPIEndpoint() string { | ||
return "https://connect.pomerium.com" | ||
} | ||
|
||
func getClusterAPIEndpoint() string { | ||
return "https://console.pomerium.com/cluster/v1" | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,86 @@ | ||
package controller | ||
|
||
import "time" | ||
|
||
// Option configures a controller. | ||
type Option func(*controllerConfig) | ||
|
||
type controllerConfig struct { | ||
apiToken string | ||
clusterAPIEndpoint string | ||
connectAPIEndpoint string | ||
|
||
tmpDir string | ||
bootstrapConfigFileName string | ||
|
||
reconcilerLeaseDuration time.Duration | ||
databrokerRequestTimeout time.Duration | ||
} | ||
|
||
// WithTmpDir sets the temporary directory to use. | ||
func WithTmpDir(dir string) Option { | ||
return func(c *controllerConfig) { | ||
c.tmpDir = dir | ||
} | ||
} | ||
|
||
// WithClusterAPIEndpoint sets the endpoint to use for the cluster API | ||
func WithClusterAPIEndpoint(endpoint string) Option { | ||
return func(c *controllerConfig) { | ||
c.clusterAPIEndpoint = endpoint | ||
} | ||
} | ||
|
||
// WithConnectAPIEndpoint sets the endpoint to use for the connect API | ||
func WithConnectAPIEndpoint(endpoint string) Option { | ||
return func(c *controllerConfig) { | ||
c.connectAPIEndpoint = endpoint | ||
} | ||
} | ||
|
||
// WithAPIToken sets the API token to use for authentication. | ||
func WithAPIToken(token string) Option { | ||
return func(c *controllerConfig) { | ||
c.apiToken = token | ||
} | ||
} | ||
|
||
// WithBootstrapConfigFileName sets the name of the file to store the bootstrap config in. | ||
func WithBootstrapConfigFileName(name string) Option { | ||
return func(c *controllerConfig) { | ||
c.bootstrapConfigFileName = name | ||
} | ||
} | ||
|
||
// WithDatabrokerLeaseDuration sets the lease duration for the | ||
func WithDatabrokerLeaseDuration(duration time.Duration) Option { | ||
return func(c *controllerConfig) { | ||
c.reconcilerLeaseDuration = duration | ||
} | ||
} | ||
|
||
// WithDatabrokerRequestTimeout sets the timeout for databroker requests. | ||
func WithDatabrokerRequestTimeout(timeout time.Duration) Option { | ||
return func(c *controllerConfig) { | ||
c.databrokerRequestTimeout = timeout | ||
} | ||
} | ||
|
||
func newControllerConfig(opts ...Option) *controllerConfig { | ||
c := new(controllerConfig) | ||
|
||
for _, opt := range []Option{ | ||
WithClusterAPIEndpoint("https://console.pomerium.com/cluster/v1"), | ||
WithConnectAPIEndpoint("https://connect.pomerium.com"), | ||
WithBootstrapConfigFileName("/var/cache/pomerium-bootstrap.dat"), | ||
WithDatabrokerLeaseDuration(time.Second * 30), | ||
WithDatabrokerRequestTimeout(time.Second * 30), | ||
} { | ||
opt(c) | ||
} | ||
|
||
for _, opt := range opts { | ||
opt(c) | ||
} | ||
return c | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,99 @@ | ||
// Package controller implements Pomerium managed mode | ||
package controller | ||
|
||
import ( | ||
"context" | ||
"errors" | ||
"fmt" | ||
|
||
"golang.org/x/sync/errgroup" | ||
|
||
"github.com/pomerium/pomerium/internal/log" | ||
"github.com/pomerium/pomerium/internal/zero/bootstrap" | ||
"github.com/pomerium/pomerium/pkg/cmd/pomerium" | ||
"github.com/pomerium/pomerium/pkg/grpc/databroker" | ||
sdk "github.com/pomerium/zero-sdk" | ||
) | ||
|
||
// Run runs Pomerium is managed mode using the provided token. | ||
func Run(ctx context.Context, opts ...Option) error { | ||
c := controller{cfg: newControllerConfig(opts...)} | ||
eg, ctx := errgroup.WithContext(ctx) | ||
|
||
err := c.initAPI(ctx) | ||
if err != nil { | ||
return fmt.Errorf("init api: %w", err) | ||
} | ||
|
||
src, err := bootstrap.New([]byte(c.cfg.apiToken)) | ||
if err != nil { | ||
return fmt.Errorf("error creating bootstrap config: %w", err) | ||
} | ||
c.bootstrapConfig = src | ||
|
||
err = c.InitDatabrokerClient(ctx, src.GetConfig()) | ||
if err != nil { | ||
return fmt.Errorf("init databroker client: %w", err) | ||
} | ||
|
||
eg.Go(func() error { return run(ctx, "connect", c.runConnect, nil) }) | ||
eg.Go(func() error { return run(ctx, "zero-bootstrap", c.runBootstrap, nil) }) | ||
eg.Go(func() error { return run(ctx, "pomerium-core", c.runPomeriumCore, src.WaitReady) }) | ||
eg.Go(func() error { return run(ctx, "zero-reconciler", c.RunReconciler, src.WaitReady) }) | ||
eg.Go(func() error { return run(ctx, "connect-log", c.RunConnectLog, nil) }) | ||
return eg.Wait() | ||
} | ||
|
||
type controller struct { | ||
cfg *controllerConfig | ||
|
||
api *sdk.API | ||
|
||
bootstrapConfig *bootstrap.Source | ||
|
||
databrokerClient databroker.DataBrokerServiceClient | ||
} | ||
|
||
func (c *controller) initAPI(ctx context.Context) error { | ||
api, err := sdk.NewAPI(ctx, | ||
sdk.WithClusterAPIEndpoint(c.cfg.clusterAPIEndpoint), | ||
sdk.WithAPIToken(c.cfg.apiToken), | ||
sdk.WithConnectAPIEndpoint(c.cfg.connectAPIEndpoint), | ||
) | ||
if err != nil { | ||
return fmt.Errorf("error initializing cloud api: %w", err) | ||
} | ||
|
||
c.api = api | ||
|
||
return nil | ||
} | ||
|
||
func run(ctx context.Context, name string, runFn func(context.Context) error, waitFn func(context.Context) error) error { | ||
if waitFn != nil { | ||
log.Ctx(ctx).Info().Str("name", name).Msg("waiting for initial configuration") | ||
err := waitFn(ctx) | ||
if err != nil { | ||
return fmt.Errorf("%s: error waiting for initial configuration: %w", name, err) | ||
} | ||
} | ||
|
||
log.Ctx(ctx).Info().Str("name", name).Msg("starting") | ||
err := runFn(ctx) | ||
if err != nil && !errors.Is(err, context.Canceled) { | ||
return fmt.Errorf("%s: %w", name, err) | ||
} | ||
return nil | ||
} | ||
|
||
func (c *controller) runBootstrap(ctx context.Context) error { | ||
return c.bootstrapConfig.Run(ctx, c.api, c.cfg.bootstrapConfigFileName) | ||
} | ||
|
||
func (c *controller) runPomeriumCore(ctx context.Context) error { | ||
return pomerium.Run(ctx, c.bootstrapConfig) | ||
} | ||
|
||
func (c *controller) runConnect(ctx context.Context) error { | ||
return c.api.Connect(ctx) | ||
} |
Oops, something went wrong.