2022-03-22 19:17:50 +00:00
package cli
import (
"context"
2022-03-24 19:21:05 +00:00
"crypto/tls"
"crypto/x509"
2022-03-24 15:07:33 +00:00
"database/sql"
2022-03-24 19:21:05 +00:00
"encoding/pem"
2022-03-22 19:17:50 +00:00
"fmt"
"io/ioutil"
"net"
"net/http"
"net/url"
"os"
2022-03-24 15:07:33 +00:00
"os/signal"
2022-03-28 19:57:19 +00:00
"path/filepath"
2022-03-22 19:17:50 +00:00
"time"
2022-03-29 00:19:28 +00:00
"github.com/briandowns/spinner"
"github.com/coreos/go-systemd/daemon"
"github.com/spf13/cobra"
"golang.org/x/xerrors"
"google.golang.org/api/idtoken"
"google.golang.org/api/option"
"gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer"
2022-03-22 19:17:50 +00:00
"cdr.dev/slog"
"cdr.dev/slog/sloggers/sloghuman"
2022-03-28 19:26:41 +00:00
"github.com/coder/coder/cli/cliflag"
2022-03-22 19:17:50 +00:00
"github.com/coder/coder/cli/cliui"
2022-03-24 15:07:33 +00:00
"github.com/coder/coder/cli/config"
2022-03-22 19:17:50 +00:00
"github.com/coder/coder/coderd"
2022-03-25 21:07:45 +00:00
"github.com/coder/coder/coderd/database"
"github.com/coder/coder/coderd/database/databasefake"
2022-03-22 19:17:50 +00:00
"github.com/coder/coder/coderd/tunnel"
"github.com/coder/coder/codersdk"
"github.com/coder/coder/provisioner/terraform"
"github.com/coder/coder/provisionerd"
"github.com/coder/coder/provisionersdk"
"github.com/coder/coder/provisionersdk/proto"
)
func start ( ) * cobra . Command {
var (
2022-03-28 19:26:41 +00:00
accessURL string
address string
2022-03-28 19:57:19 +00:00
cacheDir string
2022-03-28 19:26:41 +00:00
dev bool
postgresURL string
// provisionerDaemonCount is a uint8 to ensure a number > 0.
2022-03-24 15:07:33 +00:00
provisionerDaemonCount uint8
2022-03-24 19:21:05 +00:00
tlsCertFile string
tlsClientCAFile string
tlsClientAuth string
tlsEnable bool
tlsKeyFile string
tlsMinVersion string
2022-03-24 15:07:33 +00:00
useTunnel bool
2022-03-28 22:11:52 +00:00
traceDatadog bool
2022-03-22 19:17:50 +00:00
)
root := & cobra . Command {
Use : "start" ,
RunE : func ( cmd * cobra . Command , args [ ] string ) error {
2022-03-28 22:11:52 +00:00
if traceDatadog {
tracer . Start ( )
defer tracer . Stop ( )
}
2022-03-24 15:07:33 +00:00
2022-03-28 22:11:52 +00:00
printLogo ( cmd )
2022-03-22 19:17:50 +00:00
listener , err := net . Listen ( "tcp" , address )
if err != nil {
return xerrors . Errorf ( "listen %q: %w" , address , err )
}
defer listener . Close ( )
2022-03-24 19:21:05 +00:00
if tlsEnable {
2022-03-25 00:20:13 +00:00
listener , err = configureTLS ( listener , tlsMinVersion , tlsClientAuth , tlsCertFile , tlsKeyFile , tlsClientCAFile )
2022-03-24 19:21:05 +00:00
if err != nil {
return xerrors . Errorf ( "configure tls: %w" , err )
}
}
2022-03-22 19:17:50 +00:00
tcpAddr , valid := listener . Addr ( ) . ( * net . TCPAddr )
if ! valid {
return xerrors . New ( "must be listening on tcp" )
}
2022-03-24 15:07:33 +00:00
// If just a port is specified, assume localhost.
2022-03-22 19:17:50 +00:00
if tcpAddr . IP . IsUnspecified ( ) {
tcpAddr . IP = net . IPv4 ( 127 , 0 , 0 , 1 )
}
localURL := & url . URL {
Scheme : "http" ,
Host : tcpAddr . String ( ) ,
}
2022-03-24 19:21:05 +00:00
if tlsEnable {
localURL . Scheme = "https"
}
if accessURL == "" {
accessURL = localURL . String ( )
}
2022-03-22 19:17:50 +00:00
var tunnelErr <- chan error
2022-03-24 15:07:33 +00:00
// If we're attempting to tunnel in dev-mode, the access URL
// needs to be changed to use the tunnel.
if dev && useTunnel {
_ , _ = fmt . Fprintln ( cmd . OutOrStdout ( ) , cliui . Styles . Paragraph . Render ( "Coder requires a network endpoint that can be accessed by provisioned workspaces. In dev mode, a free tunnel can be created for you. This will expose your Coder deployment to the internet." ) + "\n" )
_ , err = cliui . Prompt ( cmd , cliui . PromptOptions {
Text : "Would you like Coder to start a tunnel for simple setup?" ,
IsConfirm : true ,
} )
if err == nil {
2022-03-24 19:21:05 +00:00
accessURL , tunnelErr , err = tunnel . New ( cmd . Context ( ) , localURL . String ( ) )
2022-03-22 19:17:50 +00:00
if err != nil {
return xerrors . Errorf ( "create tunnel: %w" , err )
}
2022-03-28 18:43:22 +00:00
_ , _ = fmt . Fprintf ( cmd . OutOrStdout ( ) , cliui . Styles . Paragraph . Render ( cliui . Styles . Wrap . Render ( cliui . Styles . Prompt . String ( ) + ` Tunnel started. Your deployment is accessible at: ` ) ) + "\n " + cliui . Styles . Field . Render ( accessURL ) + "\n" )
2022-03-24 15:07:33 +00:00
}
2022-03-22 19:17:50 +00:00
}
validator , err := idtoken . NewValidator ( cmd . Context ( ) , option . WithoutAuthentication ( ) )
if err != nil {
return err
}
2022-03-24 15:07:33 +00:00
2022-03-24 19:21:05 +00:00
accessURLParsed , err := url . Parse ( accessURL )
if err != nil {
return xerrors . Errorf ( "parse access url %q: %w" , accessURL , err )
}
2022-03-24 15:07:33 +00:00
logger := slog . Make ( sloghuman . Sink ( os . Stderr ) )
options := & coderd . Options {
2022-03-24 19:21:05 +00:00
AccessURL : accessURLParsed ,
2022-03-24 15:07:33 +00:00
Logger : logger . Named ( "coderd" ) ,
2022-03-22 19:17:50 +00:00
Database : databasefake . New ( ) ,
Pubsub : database . NewPubsubInMemory ( ) ,
GoogleTokenValidator : validator ,
2022-03-24 15:07:33 +00:00
}
if ! dev {
sqlDB , err := sql . Open ( "postgres" , postgresURL )
if err != nil {
return xerrors . Errorf ( "dial postgres: %w" , err )
}
err = sqlDB . Ping ( )
if err != nil {
return xerrors . Errorf ( "ping postgres: %w" , err )
}
err = database . MigrateUp ( sqlDB )
if err != nil {
return xerrors . Errorf ( "migrate up: %w" , err )
}
options . Database = database . New ( sqlDB )
options . Pubsub , err = database . NewPubsub ( cmd . Context ( ) , sqlDB , postgresURL )
if err != nil {
return xerrors . Errorf ( "create pubsub: %w" , err )
}
}
handler , closeCoderd := coderd . New ( options )
2022-03-22 19:17:50 +00:00
client := codersdk . New ( localURL )
2022-03-24 19:21:05 +00:00
if tlsEnable {
2022-03-25 00:20:13 +00:00
// Secure transport isn't needed for locally communicating!
2022-03-24 19:21:05 +00:00
client . HTTPClient . Transport = & http . Transport {
2022-03-25 00:20:13 +00:00
TLSClientConfig : & tls . Config {
//nolint:gosec
InsecureSkipVerify : true ,
} ,
2022-03-24 19:21:05 +00:00
}
}
2022-03-22 19:17:50 +00:00
2022-03-24 15:07:33 +00:00
provisionerDaemons := make ( [ ] * provisionerd . Server , 0 )
2022-03-28 19:26:41 +00:00
for i := 0 ; uint8 ( i ) < provisionerDaemonCount ; i ++ {
2022-03-28 19:57:19 +00:00
daemonClose , err := newProvisionerDaemon ( cmd . Context ( ) , client , logger , cacheDir )
2022-03-24 15:07:33 +00:00
if err != nil {
return xerrors . Errorf ( "create provisioner daemon: %w" , err )
}
provisionerDaemons = append ( provisionerDaemons , daemonClose )
2022-03-22 19:17:50 +00:00
}
2022-03-24 15:07:33 +00:00
defer func ( ) {
for _ , provisionerDaemon := range provisionerDaemons {
_ = provisionerDaemon . Close ( )
}
} ( )
2022-03-22 19:17:50 +00:00
2022-03-24 19:21:05 +00:00
errCh := make ( chan error , 1 )
shutdownConnsCtx , shutdownConns := context . WithCancel ( cmd . Context ( ) )
defer shutdownConns ( )
2022-03-22 19:17:50 +00:00
go func ( ) {
defer close ( errCh )
2022-03-24 19:21:05 +00:00
server := http . Server {
Handler : handler ,
BaseContext : func ( _ net . Listener ) context . Context {
return shutdownConnsCtx
} ,
}
errCh <- server . Serve ( listener )
2022-03-22 19:17:50 +00:00
} ( )
2022-03-24 15:07:33 +00:00
config := createConfig ( cmd )
2022-03-22 19:17:50 +00:00
if dev {
2022-03-24 15:07:33 +00:00
err = createFirstUser ( cmd , client , config )
2022-03-22 19:17:50 +00:00
if err != nil {
2022-03-24 15:07:33 +00:00
return xerrors . Errorf ( "create first user: %w" , err )
2022-03-22 19:17:50 +00:00
}
2022-03-24 15:07:33 +00:00
_ , _ = fmt . Fprintf ( cmd . OutOrStdout ( ) , cliui . Styles . Paragraph . Render ( cliui . Styles . Wrap . Render ( cliui . Styles . Prompt . String ( ) + ` Started in ` +
cliui . Styles . Field . Render ( "dev" ) + ` mode. All data is in-memory! Do not use in production. Press ` + cliui . Styles . Field . Render ( "ctrl+c" ) + ` to clean up provisioned infrastructure. ` ) ) +
`
` +
cliui . Styles . Paragraph . Render ( cliui . Styles . Wrap . Render ( cliui . Styles . Prompt . String ( ) + ` Run ` + cliui . Styles . Code . Render ( "coder projects init" ) + " in a new terminal to get started.\n" ) ) + `
` )
} else {
// This is helpful for tests, but can be silently ignored.
// Coder may be ran as users that don't have permission to write in the homedir,
// such as via the systemd service.
_ = config . URL ( ) . Write ( client . URL . String ( ) )
_ , _ = fmt . Fprintf ( cmd . OutOrStdout ( ) , cliui . Styles . Paragraph . Render ( cliui . Styles . Wrap . Render ( cliui . Styles . Prompt . String ( ) + ` Started in ` +
cliui . Styles . Field . Render ( "production" ) + ` mode. All data is stored in the PostgreSQL provided! Press ` + cliui . Styles . Field . Render ( "ctrl+c" ) + ` to gracefully shutdown. ` ) ) + "\n" )
2022-03-25 00:20:13 +00:00
hasFirstUser , err := client . HasFirstUser ( cmd . Context ( ) )
if ! hasFirstUser && err == nil {
// This could fail for a variety of TLS-related reasons.
// This is a helpful starter message, and not critical for user interaction.
2022-03-24 15:07:33 +00:00
_ , _ = fmt . Fprint ( cmd . OutOrStdout ( ) , cliui . Styles . Paragraph . Render ( cliui . Styles . Wrap . Render ( cliui . Styles . FocusedPrompt . String ( ) + ` Run ` + cliui . Styles . Code . Render ( "coder login " + client . URL . String ( ) ) + " in a new terminal to get started.\n" ) ) )
2022-03-22 19:17:50 +00:00
}
}
2022-03-24 15:07:33 +00:00
// Updates the systemd status from activating to activated.
_ , err = daemon . SdNotify ( false , daemon . SdNotifyReady )
if err != nil {
return xerrors . Errorf ( "notify systemd: %w" , err )
}
stopChan := make ( chan os . Signal , 1 )
defer signal . Stop ( stopChan )
signal . Notify ( stopChan , os . Interrupt )
2022-03-22 19:17:50 +00:00
select {
case <- cmd . Context ( ) . Done ( ) :
2022-03-24 15:07:33 +00:00
closeCoderd ( )
2022-03-22 19:17:50 +00:00
return cmd . Context ( ) . Err ( )
case err := <- tunnelErr :
return err
case err := <- errCh :
2022-03-24 15:07:33 +00:00
closeCoderd ( )
2022-03-22 19:17:50 +00:00
return err
2022-03-24 15:07:33 +00:00
case <- stopChan :
}
signal . Stop ( stopChan )
_ , err = daemon . SdNotify ( false , daemon . SdNotifyStopping )
if err != nil {
return xerrors . Errorf ( "notify systemd: %w" , err )
}
_ , _ = fmt . Fprintln ( cmd . OutOrStdout ( ) , "\n\n" + cliui . Styles . Bold . Render ( "Interrupt caught. Gracefully exiting..." ) )
if dev {
workspaces , err := client . WorkspacesByUser ( cmd . Context ( ) , "" )
if err != nil {
return xerrors . Errorf ( "get workspaces: %w" , err )
}
for _ , workspace := range workspaces {
before := time . Now ( )
build , err := client . CreateWorkspaceBuild ( cmd . Context ( ) , workspace . ID , codersdk . CreateWorkspaceBuildRequest {
Transition : database . WorkspaceTransitionDelete ,
} )
if err != nil {
return xerrors . Errorf ( "delete workspace: %w" , err )
}
2022-03-28 18:43:22 +00:00
err = cliui . ProvisionerJob ( cmd , cliui . ProvisionerJobOptions {
2022-03-24 15:07:33 +00:00
Fetch : func ( ) ( codersdk . ProvisionerJob , error ) {
build , err := client . WorkspaceBuild ( cmd . Context ( ) , build . ID )
return build . Job , err
} ,
Cancel : func ( ) error {
return client . CancelWorkspaceBuild ( cmd . Context ( ) , build . ID )
} ,
Logs : func ( ) ( <- chan codersdk . ProvisionerJobLog , error ) {
return client . WorkspaceBuildLogsAfter ( cmd . Context ( ) , build . ID , before )
} ,
} )
if err != nil {
return xerrors . Errorf ( "delete workspace %s: %w" , workspace . Name , err )
}
}
}
for _ , provisionerDaemon := range provisionerDaemons {
spin := spinner . New ( spinner . CharSets [ 5 ] , 100 * time . Millisecond )
spin . Writer = cmd . OutOrStdout ( )
spin . Suffix = cliui . Styles . Keyword . Render ( " Shutting down provisioner daemon..." )
spin . Start ( )
err = provisionerDaemon . Shutdown ( cmd . Context ( ) )
if err != nil {
spin . FinalMSG = cliui . Styles . Prompt . String ( ) + "Failed to shutdown provisioner daemon: " + err . Error ( )
spin . Stop ( )
}
err = provisionerDaemon . Close ( )
if err != nil {
spin . Stop ( )
return xerrors . Errorf ( "close provisioner daemon: %w" , err )
}
spin . FinalMSG = cliui . Styles . Prompt . String ( ) + "Gracefully shut down provisioner daemon!\n"
spin . Stop ( )
2022-03-22 19:17:50 +00:00
}
2022-03-24 15:07:33 +00:00
_ , _ = fmt . Fprintf ( cmd . OutOrStdout ( ) , cliui . Styles . Prompt . String ( ) + "Waiting for WebSocket connections to close...\n" )
2022-03-24 19:21:05 +00:00
shutdownConns ( )
2022-03-24 15:07:33 +00:00
closeCoderd ( )
return nil
2022-03-22 19:17:50 +00:00
} ,
}
2022-03-28 19:26:41 +00:00
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" )
2022-03-28 19:57:19 +00:00
// 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." )
2022-03-28 19:26:41 +00:00
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 . Uint8VarP ( root . Flags ( ) , & provisionerDaemonCount , "provisioner-daemons" , "" , "CODER_PROVISIONER_DAEMONS" , 1 , "The amount of provisioner daemons to create on start." )
cliflag . BoolVarP ( root . Flags ( ) , & tlsEnable , "tls-enable" , "" , "CODER_TLS_ENABLE" , false , "Specifies if TLS will be enabled" )
cliflag . StringVarP ( root . Flags ( ) , & tlsCertFile , "tls-cert-file" , "" , "CODER_TLS_CERT_FILE" , "" ,
2022-03-24 19:21:05 +00:00
"Specifies the path to the certificate for TLS. It requires a PEM-encoded file. " +
"To configure the listener to use a CA certificate, concatenate the primary certificate " +
2022-03-28 19:26:41 +00:00
"and the CA certificate together. The primary certificate should appear first in the combined file" )
cliflag . StringVarP ( root . Flags ( ) , & tlsClientCAFile , "tls-client-ca-file" , "" , "CODER_TLS_CLIENT_CA_FILE" , "" ,
"PEM-encoded Certificate Authority file used for checking the authenticity of client" )
cliflag . StringVarP ( root . Flags ( ) , & tlsClientAuth , "tls-client-auth" , "" , "CODER_TLS_CLIENT_AUTH" , "request" ,
2022-03-24 19:21:05 +00:00
` Specifies the policy the server will follow for TLS Client Authentication. ` +
2022-03-28 19:26:41 +00:00
` Accepted values are "none", "request", "require-any", "verify-if-given", or "require-and-verify" ` )
cliflag . StringVarP ( root . Flags ( ) , & tlsKeyFile , "tls-key-file" , "" , "CODER_TLS_KEY_FILE" , "" ,
"Specifies the path to the private key for the certificate. It requires a PEM-encoded file" )
cliflag . StringVarP ( root . Flags ( ) , & tlsMinVersion , "tls-min-version" , "" , "CODER_TLS_MIN_VERSION" , "tls12" ,
` Specifies the minimum supported version of TLS. Accepted values are "tls10", "tls11", "tls12" or "tls13" ` )
2022-03-28 22:16:13 +00:00
cliflag . BoolVarP ( root . Flags ( ) , & useTunnel , "tunnel" , "" , "CODER_DEV_TUNNEL" , true , "Serve dev mode through a Cloudflare Tunnel for easy setup" )
2022-03-24 15:07:33 +00:00
_ = root . Flags ( ) . MarkHidden ( "tunnel" )
2022-03-28 22:11:52 +00:00
cliflag . BoolVarP ( root . Flags ( ) , & traceDatadog , "trace-datadog" , "" , "CODER_TRACE_DATADOG" , false , "Send tracing data to a datadog agent" )
2022-03-22 19:17:50 +00:00
return root
}
2022-03-24 15:07:33 +00:00
func createFirstUser ( cmd * cobra . Command , client * codersdk . Client , cfg config . Root ) error {
_ , err := client . CreateFirstUser ( cmd . Context ( ) , codersdk . CreateFirstUserRequest {
Email : "admin@coder.com" ,
Username : "developer" ,
Password : "password" ,
Organization : "acme-corp" ,
} )
if err != nil {
return xerrors . Errorf ( "create first user: %w" , err )
}
token , err := client . LoginWithPassword ( cmd . Context ( ) , codersdk . LoginWithPasswordRequest {
Email : "admin@coder.com" ,
Password : "password" ,
} )
if err != nil {
return xerrors . Errorf ( "login with first user: %w" , err )
}
client . SessionToken = token . SessionToken
err = cfg . URL ( ) . Write ( client . URL . String ( ) )
if err != nil {
return xerrors . Errorf ( "write local url: %w" , err )
}
err = cfg . Session ( ) . Write ( token . SessionToken )
if err != nil {
return xerrors . Errorf ( "write session token: %w" , err )
}
return nil
}
2022-03-28 19:57:19 +00:00
func newProvisionerDaemon ( ctx context . Context , client * codersdk . Client , logger slog . Logger , cacheDir string ) ( * provisionerd . Server , error ) {
2022-03-22 19:17:50 +00:00
terraformClient , terraformServer := provisionersdk . TransportPipe ( )
go func ( ) {
err := terraform . Serve ( ctx , & terraform . ServeOptions {
ServeOptions : & provisionersdk . ServeOptions {
Listener : terraformServer ,
} ,
2022-03-28 19:57:19 +00:00
CachePath : cacheDir ,
Logger : logger ,
2022-03-22 19:17:50 +00:00
} )
if err != nil {
panic ( err )
}
} ( )
tempDir , err := ioutil . TempDir ( "" , "provisionerd" )
if err != nil {
return nil , err
}
return provisionerd . New ( client . ListenProvisionerDaemon , & provisionerd . Options {
Logger : logger ,
PollInterval : 50 * time . Millisecond ,
UpdateInterval : 50 * time . Millisecond ,
Provisioners : provisionerd . Provisioners {
string ( database . ProvisionerTypeTerraform ) : proto . NewDRPCProvisionerClient ( provisionersdk . Conn ( terraformClient ) ) ,
} ,
WorkDirectory : tempDir ,
} ) , nil
}
2022-03-24 19:21:05 +00:00
func printLogo ( cmd * cobra . Command ) {
_ , _ = fmt . Fprintf ( cmd . OutOrStdout ( ) , ` ▄ █ ▀ ▀ █ ▄
▄ ▄ ▀ ▀ ▀ █ ▌ █ █ ▀ ▀ █ ▄ ▐ █
▄ ▄ █ █ ▀ ▀ █ ▄ ▄ ▄ █ █ █ █ █ ▀ ▀ █ ▐ █ ▀ ▀ █ █ ▄ █ ▀ ▀ █ █ ▀ ▀
█ ▌ ▄ ▌ ▐ █ █ ▌ ▀ █ ▄ ▄ ▄ █ ▌ █ █ ▐ █ █ █ █ █ ▀ ▀ █
█ █ █ █ █ █ ▀ ▄ █ ▀ ▀ ▀ ▀ ▀ ▀ ▀ ▀ ▀ ▀ ▀ ▀ ▀ ▀ ▀ ▀ ▀ ▀
` )
}
2022-03-25 00:20:13 +00:00
func configureTLS ( listener net . Listener , tlsMinVersion , tlsClientAuth , tlsCertFile , tlsKeyFile , tlsClientCAFile string ) ( net . Listener , error ) {
tlsConfig := & tls . Config {
MinVersion : tls . VersionTLS12 ,
}
2022-03-24 19:21:05 +00:00
switch tlsMinVersion {
case "tls10" :
tlsConfig . MinVersion = tls . VersionTLS10
case "tls11" :
tlsConfig . MinVersion = tls . VersionTLS11
case "tls12" :
tlsConfig . MinVersion = tls . VersionTLS12
case "tls13" :
tlsConfig . MinVersion = tls . VersionTLS13
default :
return nil , xerrors . Errorf ( "unrecognized tls version: %q" , tlsMinVersion )
}
switch tlsClientAuth {
case "none" :
tlsConfig . ClientAuth = tls . NoClientCert
case "request" :
tlsConfig . ClientAuth = tls . RequestClientCert
case "require-any" :
tlsConfig . ClientAuth = tls . RequireAnyClientCert
case "verify-if-given" :
tlsConfig . ClientAuth = tls . VerifyClientCertIfGiven
case "require-and-verify" :
tlsConfig . ClientAuth = tls . RequireAndVerifyClientCert
default :
return nil , xerrors . Errorf ( "unrecognized tls client auth: %q" , tlsClientAuth )
}
if tlsCertFile == "" {
return nil , xerrors . New ( "tls-cert-file is required when tls is enabled" )
}
if tlsKeyFile == "" {
return nil , xerrors . New ( "tls-key-file is required when tls is enabled" )
}
certPEMBlock , err := os . ReadFile ( tlsCertFile )
if err != nil {
return nil , xerrors . Errorf ( "read file %q: %w" , tlsCertFile , err )
}
keyPEMBlock , err := os . ReadFile ( tlsKeyFile )
if err != nil {
return nil , xerrors . Errorf ( "read file %q: %w" , tlsKeyFile , err )
}
keyBlock , _ := pem . Decode ( keyPEMBlock )
if keyBlock == nil {
return nil , xerrors . New ( "decoded pem is blank" )
}
cert , err := tls . X509KeyPair ( certPEMBlock , keyPEMBlock )
if err != nil {
return nil , xerrors . Errorf ( "create key pair: %w" , err )
}
tlsConfig . GetCertificate = func ( chi * tls . ClientHelloInfo ) ( * tls . Certificate , error ) {
return & cert , nil
}
certPool := x509 . NewCertPool ( )
certPool . AppendCertsFromPEM ( certPEMBlock )
tlsConfig . RootCAs = certPool
if tlsClientCAFile != "" {
caPool := x509 . NewCertPool ( )
data , err := ioutil . ReadFile ( tlsClientCAFile )
if err != nil {
return nil , xerrors . Errorf ( "read %q: %w" , tlsClientCAFile , err )
}
if ! caPool . AppendCertsFromPEM ( data ) {
return nil , xerrors . Errorf ( "failed to parse CA certificate in tls-client-ca-file" )
}
tlsConfig . ClientCAs = caPool
}
return tls . NewListener ( listener , tlsConfig ) , nil
}