feat: Support caching provisioner assets (#574)

* feat: Add AWS instance identity authentication

This allows zero-trust authentication for all AWS instances.

Prior to this, AWS instances could be used by passing `CODER_TOKEN`
as an environment variable to the startup script. AWS explicitly
states that secrets should not be passed in startup scripts because
it's user-readable.

* feat: Support caching provisioner assets

This caches the Terraform binary, and Terraform plugins.
Eventually, it could cache other temporary files.

* chore: fix linter

Co-authored-by: Garrett <garrett@coder.com>
This commit is contained in:
Kyle Carberry
2022-03-28 13:57:19 -06:00
committed by GitHub
parent 9485fd62da
commit 13cef7d07c
4 changed files with 23 additions and 5 deletions

View File

@ -13,6 +13,7 @@ import (
"net/url" "net/url"
"os" "os"
"os/signal" "os/signal"
"path/filepath"
"time" "time"
"github.com/briandowns/spinner" "github.com/briandowns/spinner"
@ -42,6 +43,7 @@ func start() *cobra.Command {
var ( var (
accessURL string accessURL string
address string address string
cacheDir string
dev bool dev bool
postgresURL string postgresURL string
// provisionerDaemonCount is a uint8 to ensure a number > 0. // provisionerDaemonCount is a uint8 to ensure a number > 0.
@ -161,7 +163,7 @@ func start() *cobra.Command {
provisionerDaemons := make([]*provisionerd.Server, 0) provisionerDaemons := make([]*provisionerd.Server, 0)
for i := 0; uint8(i) < provisionerDaemonCount; i++ { for i := 0; uint8(i) < provisionerDaemonCount; i++ {
daemonClose, err := newProvisionerDaemon(cmd.Context(), client, logger) daemonClose, err := newProvisionerDaemon(cmd.Context(), client, logger, cacheDir)
if err != nil { if err != nil {
return xerrors.Errorf("create provisioner daemon: %w", err) return xerrors.Errorf("create provisioner daemon: %w", err)
} }
@ -305,6 +307,8 @@ func start() *cobra.Command {
cliflag.StringVarP(root.Flags(), &accessURL, "access-url", "", "CODER_ACCESS_URL", "", "Specifies the external URL to access Coder") cliflag.StringVarP(root.Flags(), &accessURL, "access-url", "", "CODER_ACCESS_URL", "", "Specifies the external URL to access Coder")
cliflag.StringVarP(root.Flags(), &address, "address", "a", "CODER_ADDRESS", "127.0.0.1:3000", "The address to serve the API and dashboard") cliflag.StringVarP(root.Flags(), &address, "address", "a", "CODER_ADDRESS", "127.0.0.1:3000", "The address to serve the API and dashboard")
// systemd uses the CACHE_DIRECTORY environment variable!
cliflag.StringVarP(root.Flags(), &cacheDir, "cache-dir", "", "CACHE_DIRECTORY", filepath.Join(os.TempDir(), ".coder-cache"), "Specifies a directory to cache binaries for provision operations.")
cliflag.BoolVarP(root.Flags(), &dev, "dev", "", "CODER_DEV_MODE", false, "Serve Coder in dev mode for tinkering") cliflag.BoolVarP(root.Flags(), &dev, "dev", "", "CODER_DEV_MODE", false, "Serve Coder in dev mode for tinkering")
cliflag.StringVarP(root.Flags(), &postgresURL, "postgres-url", "", "CODER_PG_CONNECTION_URL", "", "URL of a PostgreSQL database to connect to") cliflag.StringVarP(root.Flags(), &postgresURL, "postgres-url", "", "CODER_PG_CONNECTION_URL", "", "URL of a PostgreSQL database to connect to")
cliflag.Uint8VarP(root.Flags(), &provisionerDaemonCount, "provisioner-daemons", "", "CODER_PROVISIONER_DAEMONS", 1, "The amount of provisioner daemons to create on start.") cliflag.Uint8VarP(root.Flags(), &provisionerDaemonCount, "provisioner-daemons", "", "CODER_PROVISIONER_DAEMONS", 1, "The amount of provisioner daemons to create on start.")
@ -358,14 +362,15 @@ func createFirstUser(cmd *cobra.Command, client *codersdk.Client, cfg config.Roo
return nil return nil
} }
func newProvisionerDaemon(ctx context.Context, client *codersdk.Client, logger slog.Logger) (*provisionerd.Server, error) { func newProvisionerDaemon(ctx context.Context, client *codersdk.Client, logger slog.Logger, cacheDir string) (*provisionerd.Server, error) {
terraformClient, terraformServer := provisionersdk.TransportPipe() terraformClient, terraformServer := provisionersdk.TransportPipe()
go func() { go func() {
err := terraform.Serve(ctx, &terraform.ServeOptions{ err := terraform.Serve(ctx, &terraform.ServeOptions{
ServeOptions: &provisionersdk.ServeOptions{ ServeOptions: &provisionersdk.ServeOptions{
Listener: terraformServer, Listener: terraformServer,
}, },
Logger: logger, CachePath: cacheDir,
Logger: logger,
}) })
if err != nil { if err != nil {
panic(err) panic(err)

View File

@ -16,6 +16,7 @@ PrivateTmp=yes
PrivateDevices=yes PrivateDevices=yes
SecureBits=keep-caps SecureBits=keep-caps
AmbientCapabilities=CAP_IPC_LOCK AmbientCapabilities=CAP_IPC_LOCK
CacheDirectory=coder
CapabilityBoundingSet=CAP_SYSLOG CAP_IPC_LOCK CAP_NET_BIND_SERVICE CapabilityBoundingSet=CAP_SYSLOG CAP_IPC_LOCK CAP_NET_BIND_SERVICE
NoNewPrivileges=yes NoNewPrivileges=yes
ExecStart=/usr/bin/coder start ExecStart=/usr/bin/coder start

View File

@ -87,6 +87,14 @@ func (t *terraform) Provision(stream proto.DRPCProvisioner_ProvisionStream) erro
}) })
} }
}() }()
if t.cachePath != "" {
err = terraform.SetEnv(map[string]string{
"TF_PLUGIN_CACHE_DIR": t.cachePath,
})
if err != nil {
return xerrors.Errorf("set terraform plugin cache dir: %w", err)
}
}
terraform.SetStdout(writer) terraform.SetStdout(writer)
t.logger.Debug(shutdown, "running initialization") t.logger.Debug(shutdown, "running initialization")
err = terraform.Init(shutdown) err = terraform.Init(shutdown)

View File

@ -34,6 +34,7 @@ type ServeOptions struct {
// BinaryPath specifies the "terraform" binary to use. // BinaryPath specifies the "terraform" binary to use.
// If omitted, the $PATH will attempt to find it. // If omitted, the $PATH will attempt to find it.
BinaryPath string BinaryPath string
CachePath string
Logger slog.Logger Logger slog.Logger
} }
@ -43,8 +44,9 @@ func Serve(ctx context.Context, options *ServeOptions) error {
binaryPath, err := exec.LookPath("terraform") binaryPath, err := exec.LookPath("terraform")
if err != nil { if err != nil {
installer := &releases.ExactVersion{ installer := &releases.ExactVersion{
Product: product.Terraform, InstallDir: options.CachePath,
Version: version.Must(version.NewVersion("1.1.7")), Product: product.Terraform,
Version: version.Must(version.NewVersion("1.1.7")),
} }
execPath, err := installer.Install(ctx) execPath, err := installer.Install(ctx)
@ -58,11 +60,13 @@ func Serve(ctx context.Context, options *ServeOptions) error {
} }
return provisionersdk.Serve(ctx, &terraform{ return provisionersdk.Serve(ctx, &terraform{
binaryPath: options.BinaryPath, binaryPath: options.BinaryPath,
cachePath: options.CachePath,
logger: options.Logger, logger: options.Logger,
}, options.ServeOptions) }, options.ServeOptions)
} }
type terraform struct { type terraform struct {
binaryPath string binaryPath string
cachePath string
logger slog.Logger logger slog.Logger
} }