mirror of
https://github.com/kopia/kopia.git
synced 2026-01-03 12:07:55 -05:00
* 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.
158 lines
5.0 KiB
Go
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.")
|
|
}
|
|
}
|