Files
kopia/cli/command_server_tls.go
Jarek Kowalski 1a826d85c5 cli: added '--insecure' flag to 'kopia server start' (#803)
* cli: added '--insecure' flag to 'kopia server start'

This is a breaking change for development scenarios to prevent people
from unknowingly launching insecure servers.

Attempt to start a server without either TLS or password protection
results in an error now (unless --insecure is also passed).

KopiaUI already launches server with TLS and random password, so it
does not require it.
2021-01-28 09:13:57 -08:00

158 lines
5.0 KiB
Go

package cli
import (
"bytes"
"context"
"crypto/rsa"
"crypto/sha256"
"crypto/tls"
"crypto/x509"
"encoding/base64"
"encoding/hex"
"encoding/pem"
"fmt"
"net"
"net/http"
"os"
"time"
"github.com/pkg/errors"
"github.com/kopia/kopia/internal/tlsutil"
)
const oneDay = 24 * time.Hour
var (
serverStartTLSGenerateCert = serverStartCommand.Flag("tls-generate-cert", "Generate TLS certificate").Hidden().Bool()
serverStartTLSCertFile = serverStartCommand.Flag("tls-cert-file", "TLS certificate PEM").String()
serverStartTLSKeyFile = serverStartCommand.Flag("tls-key-file", "TLS key PEM file").String()
serverStartTLSGenerateRSAKeySize = serverStartCommand.Flag("tls-generate-rsa-key-size", "TLS RSA Key size (bits)").Hidden().Default("4096").Int()
serverStartTLSGenerateCertValidDays = serverStartCommand.Flag("tls-generate-cert-valid-days", "How long should the TLS certificate be valid").Default("3650").Hidden().Int()
serverStartTLSGenerateCertNames = serverStartCommand.Flag("tls-generate-cert-name", "Host names/IP addresses to generate TLS certificate for").Default("127.0.0.1").Hidden().Strings()
serverStartTLSPrintFullServerCert = serverStartCommand.Flag("tls-print-server-cert", "Print server certificate").Hidden().Bool()
)
func generateServerCertificate(ctx context.Context) (*x509.Certificate, *rsa.PrivateKey, error) {
return tlsutil.GenerateServerCertificate(
ctx,
*serverStartTLSGenerateRSAKeySize,
time.Duration(*serverStartTLSGenerateCertValidDays)*oneDay,
*serverStartTLSGenerateCertNames)
}
func startServerWithOptionalTLS(ctx context.Context, httpServer *http.Server) error {
l, err := net.Listen("tcp", httpServer.Addr)
if err != nil {
return errors.Wrap(err, "listen error")
}
defer l.Close() //nolint:errcheck
httpServer.Addr = l.Addr().String()
return startServerWithOptionalTLSAndListener(ctx, httpServer, l)
}
func maybeGenerateTLS(ctx context.Context) error {
if !*serverStartTLSGenerateCert || *serverStartTLSCertFile == "" || *serverStartTLSKeyFile == "" {
return nil
}
if _, err := os.Stat(*serverStartTLSCertFile); err == nil {
return errors.Errorf("TLS cert file already exists: %q", *serverStartTLSCertFile)
}
if _, err := os.Stat(*serverStartTLSKeyFile); err == nil {
return errors.Errorf("TLS key file already exists: %q", *serverStartTLSKeyFile)
}
cert, key, err := generateServerCertificate(ctx)
if err != nil {
return errors.Wrap(err, "unable to generate server cert")
}
fingerprint := sha256.Sum256(cert.Raw)
fmt.Fprintf(os.Stderr, "SERVER CERT SHA256: %v\n", hex.EncodeToString(fingerprint[:]))
log(ctx).Infof("writing TLS certificate to %v", *serverStartTLSCertFile)
if err := tlsutil.WriteCertificateToFile(*serverStartTLSCertFile, cert); err != nil {
return errors.Wrap(err, "unable to write private key")
}
log(ctx).Infof("writing TLS private key to %v", *serverStartTLSKeyFile)
if err := tlsutil.WritePrivateKeyToFile(*serverStartTLSKeyFile, key); err != nil {
return errors.Wrap(err, "unable to write private key")
}
return nil
}
func startServerWithOptionalTLSAndListener(ctx context.Context, httpServer *http.Server, listener net.Listener) error {
if err := maybeGenerateTLS(ctx); err != nil {
return err
}
switch {
case *serverStartTLSCertFile != "" && *serverStartTLSKeyFile != "":
// PEM files provided
fmt.Fprintf(os.Stderr, "SERVER ADDRESS: https://%v\n", httpServer.Addr)
showServerUIPrompt(ctx)
return httpServer.ServeTLS(listener, *serverStartTLSCertFile, *serverStartTLSKeyFile)
case *serverStartTLSGenerateCert:
// PEM files not provided, generate in-memory TLS cert/key but don't persit.
cert, key, err := generateServerCertificate(ctx)
if err != nil {
return errors.Wrap(err, "unable to generate server cert")
}
httpServer.TLSConfig = &tls.Config{
MinVersion: tls.VersionTLS13,
Certificates: []tls.Certificate{
{
Certificate: [][]byte{cert.Raw},
PrivateKey: key,
},
},
}
fingerprint := sha256.Sum256(cert.Raw)
fmt.Fprintf(os.Stderr, "SERVER CERT SHA256: %v\n", hex.EncodeToString(fingerprint[:]))
if *serverStartTLSPrintFullServerCert {
// dump PEM-encoded server cert, only used by KopiaUI to securely connnect.
var b bytes.Buffer
if err := pem.Encode(&b, &pem.Block{Type: "CERTIFICATE", Bytes: cert.Raw}); err != nil {
return errors.Wrap(err, "Failed to write data")
}
fmt.Fprintf(os.Stderr, "SERVER CERTIFICATE: %v\n", base64.StdEncoding.EncodeToString(b.Bytes()))
}
fmt.Fprintf(os.Stderr, "SERVER ADDRESS: https://%v\n", httpServer.Addr)
showServerUIPrompt(ctx)
return httpServer.ServeTLS(listener, "", "")
default:
if !*serverStartInsecure {
return errors.Errorf("TLS not configured. To start server without encryption pass --insecure.")
}
fmt.Fprintf(os.Stderr, "SERVER ADDRESS: http://%v\n", httpServer.Addr)
showServerUIPrompt(ctx)
return httpServer.Serve(listener)
}
}
func showServerUIPrompt(ctx context.Context) {
if *serverStartUI {
log(ctx).Infof("Open the address above in a web browser to use the UI.")
}
}