diff --git a/internal/cmd/authenticated/client.go b/internal/cmd/authenticated/client.go index e197e41..f8bf980 100644 --- a/internal/cmd/authenticated/client.go +++ b/internal/cmd/authenticated/client.go @@ -1,12 +1,36 @@ package authenticated import ( + "crypto/tls" + "crypto/x509" + "fmt" + "net/http" + "os" + "github.com/urfave/cli/v2" "github.com/spacelift-io/spacectl/client" "github.com/spacelift-io/spacectl/client/session" ) +const ( + // EnvSpaceliftAPIClientTLSCert represents the path to a client certificate for + // communicating with the Spacelift API endpoint. + EnvSpaceliftAPIClientTLSCert = "SPACELIFT_API_TLS_CERT" + + // EnvSpaceliftAPIClientTLSKey represents the path to a client key for + // communicating with the Spacelift API endpoint. + EnvSpaceliftAPIClientTLSKey = "SPACELIFT_API_TLS_KEY" + + // EnvSpaceliftAPIClientCA represents the path to a CA bundle for + // verifying Spacelift API endpoint. + EnvSpaceliftAPIClientCA = "SPACELIFT_API_TLS_CA" +) + +var ( + errEnvSpaceliftAPIClientCAParse = fmt.Errorf("failed to parse %s", EnvSpaceliftAPIClientCA) +) + // Client is the authenticated client that can be used by all CLI commands. var Client client.Client @@ -15,6 +39,10 @@ var Client client.Client func Ensure(*cli.Context) error { ctx, httpClient := session.Defaults() + if err := configureTLS(httpClient); err != nil { + return err + } + session, err := session.New(ctx, httpClient) if err != nil { return err @@ -24,3 +52,42 @@ func Ensure(*cli.Context) error { return nil } + +// configureTLS configures client TLS from the environment. +func configureTLS(httpClient *http.Client) error { + clientTLS := &tls.Config{ + MinVersion: tls.VersionTLS12, + } + + if caFile, ok := os.LookupEnv(EnvSpaceliftAPIClientCA); ok && caFile != "" { + caCert, err := os.ReadFile("cacert") + if err != nil { + return err + } + + caCertPool := x509.NewCertPool() + if !caCertPool.AppendCertsFromPEM(caCert) { + return errEnvSpaceliftAPIClientCAParse + } + + clientTLS.RootCAs = caCertPool + } + + keyFile, keyOk := os.LookupEnv(EnvSpaceliftAPIClientTLSKey) + certFile, certOk := os.LookupEnv(EnvSpaceliftAPIClientTLSCert) + + if keyOk && keyFile != "" && certOk && certFile != "" { + cert, err := tls.LoadX509KeyPair(certFile, keyFile) + if err != nil { + return err + } + + clientTLS.Certificates = []tls.Certificate{cert} + } + + httpClient.Transport = &http.Transport{ + TLSClientConfig: clientTLS, + } + + return nil +}