Generate certificates if they don't exist
This commit is contained in:
parent
2f7676b35e
commit
1732c2c389
@ -11,7 +11,8 @@ VOLUME ["/data"]
|
|||||||
CMD ["/app/scripts/cross-compile.sh"]
|
CMD ["/app/scripts/cross-compile.sh"]
|
||||||
|
|
||||||
FROM alpine:latest
|
FROM alpine:latest
|
||||||
|
VOLUME [ "/data" ]
|
||||||
COPY --from=builder-base /app/dist/ezshare /usr/bin/ezshare
|
COPY --from=builder-base /app/dist/ezshare /usr/bin/ezshare
|
||||||
EXPOSE 50051
|
EXPOSE 50051
|
||||||
EXPOSE 8088
|
EXPOSE 8088
|
||||||
CMD ["/usr/bin/ezshare", "--config", "/data/ezshare.toml", "serve"]
|
CMD ["/usr/bin/ezshare", "serve"]
|
24
README.md
24
README.md
@ -8,18 +8,9 @@ Running a server using docker:
|
|||||||
# First, build the image
|
# First, build the image
|
||||||
docker build -t ezshare:latest .
|
docker build -t ezshare:latest .
|
||||||
|
|
||||||
# Then run an interactive container to generate certificates
|
Copy config file to /var/ezshare/ezshare.toml (or see ezshare.minimal.env to see how to set config-values using envvars)
|
||||||
# This will generate CA, server and client certs in /var/ezshare
|
|
||||||
docker run --rm -it -v /var/ezshare:/data ezshare:latest sh
|
|
||||||
# Run this inside the container
|
|
||||||
ezshare cert gen-all --out-dir /data/certs
|
|
||||||
exit
|
|
||||||
|
|
||||||
# Then copy ezshare.example.toml to /var/ezshare/ezshare.toml
|
|
||||||
# Edit the config, making sure to point certificates to /data/server.pem etc
|
|
||||||
|
|
||||||
# Run the server
|
# Run the server
|
||||||
docker run --rm -d -v /var/ezshare:/data ezshare:latest
|
docker run --rm -d -v /var/ezshare:/data -e EZSHARE_CONFIG=/data/ezshare.toml ezshare:latest
|
||||||
```
|
```
|
||||||
|
|
||||||
## Client
|
## Client
|
||||||
@ -29,11 +20,12 @@ Compile the client:
|
|||||||
go build -o ezshare cmd/ezshare.go
|
go build -o ezshare cmd/ezshare.go
|
||||||
```
|
```
|
||||||
|
|
||||||
Copy the resulting binary somewhere into `$PATH`. Then generate an empty client config using `ezshare client init-config`.
|
Generate client config, and fetch needed certificates:
|
||||||
Copy certificates from server (`srv.pem`, `client.pem`, `client.key`) to the same dir as the generated config.
|
```
|
||||||
Edit the config-file, and update the `Client` section with correct default-server and path to certificates.
|
./ezshare client login --overwrite "https://ezshare.example.org"
|
||||||
|
username: admin
|
||||||
Client should then be ready for use.
|
password:
|
||||||
|
```
|
||||||
|
|
||||||
```text
|
```text
|
||||||
NAME:
|
NAME:
|
||||||
|
@ -103,7 +103,7 @@ func ActionClientUpload(c *cli.Context) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
fmt.Printf("%s uploaded with id %s. Available at %s\n", arg, resp.Id, resp.FileUrl)
|
fmt.Printf("%s uploaded with id %s. Available at:\n%s\n", arg, resp.Id, resp.FileUrl)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@ package actions
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"os"
|
||||||
|
|
||||||
"gitea.benny.dog/torjus/ezshare/certs"
|
"gitea.benny.dog/torjus/ezshare/certs"
|
||||||
"gitea.benny.dog/torjus/ezshare/config"
|
"gitea.benny.dog/torjus/ezshare/config"
|
||||||
@ -25,10 +26,20 @@ func getConfig(c *cli.Context) (*config.Config, error) {
|
|||||||
cfgPath := c.String("config")
|
cfgPath := c.String("config")
|
||||||
return config.FromFile(cfgPath)
|
return config.FromFile(cfgPath)
|
||||||
}
|
}
|
||||||
|
if val, ok := os.LookupEnv("EZSHARE_CONFIG"); ok {
|
||||||
|
return config.FromFile(val)
|
||||||
|
}
|
||||||
cfg, err := config.FromDefaultLocations()
|
cfg, err := config.FromDefaultLocations()
|
||||||
if err == nil {
|
if err == nil {
|
||||||
verbosePrint(c, fmt.Sprintf("Config loaded from %s", cfg.Location()))
|
verbosePrint(c, fmt.Sprintf("Config loaded from %s", cfg.Location()))
|
||||||
}
|
}
|
||||||
|
if cfg == nil {
|
||||||
|
cfg = config.FromDefault()
|
||||||
|
}
|
||||||
|
cfg.UpdateFromEnv()
|
||||||
|
if cfg.Client.Valid() == nil || cfg.Server.Valid() == nil {
|
||||||
|
return cfg, nil
|
||||||
|
}
|
||||||
return cfg, err
|
return cfg, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
165
actions/serve.go
165
actions/serve.go
@ -4,11 +4,15 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io/fs"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"gitea.benny.dog/torjus/ezshare/certs"
|
"gitea.benny.dog/torjus/ezshare/certs"
|
||||||
@ -37,21 +41,9 @@ func ActionServe(c *cli.Context) error {
|
|||||||
binsLogger := logger.Named("BINS")
|
binsLogger := logger.Named("BINS")
|
||||||
|
|
||||||
// Read certificates
|
// Read certificates
|
||||||
srvCertBytes, err := cfg.Server.GRPC.Certs.GetCertBytes()
|
certificates, err := getCerts(c, serverLogger)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return cli.Exit(fmt.Sprintf("Error getting certificates: %s", err), 1)
|
||||||
}
|
|
||||||
srvKeyBytes, err := cfg.Server.GRPC.Certs.GetKeyBytes()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
caCertBytes, err := cfg.Server.GRPC.CACerts.GetCertBytes()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
caKeyBytes, err := cfg.Server.GRPC.CACerts.GetKeyBytes()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Setup file store
|
// Setup file store
|
||||||
@ -74,7 +66,7 @@ func ActionServe(c *cli.Context) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Setup cert-service
|
// Setup cert-service
|
||||||
certSvc, err := certs.NewCertService(dataStore, caCertBytes, caKeyBytes)
|
certSvc, err := certs.NewCertService(dataStore, certificates.caCert, certificates.caCertKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("error initializing certificate service: %w", err)
|
return fmt.Errorf("error initializing certificate service: %w", err)
|
||||||
}
|
}
|
||||||
@ -122,13 +114,13 @@ func ActionServe(c *cli.Context) error {
|
|||||||
serverLogger.Errorw("Unable to setup GRPC listener.", "error", err)
|
serverLogger.Errorw("Unable to setup GRPC listener.", "error", err)
|
||||||
rootCancel()
|
rootCancel()
|
||||||
}
|
}
|
||||||
srvCert, err := tls.X509KeyPair(srvCertBytes, srvKeyBytes)
|
srvCert, err := tls.X509KeyPair(certificates.serverCert, certificates.serverKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
serverLogger.Errorw("Unable to load server certs.", "error", err)
|
serverLogger.Errorw("Unable to load server certs.", "error", err)
|
||||||
rootCancel()
|
rootCancel()
|
||||||
}
|
}
|
||||||
certPool := x509.NewCertPool()
|
certPool := x509.NewCertPool()
|
||||||
if !certPool.AppendCertsFromPEM(caCertBytes) {
|
if !certPool.AppendCertsFromPEM(certificates.caCert) {
|
||||||
serverLogger.Errorw("Unable to load CA certs.")
|
serverLogger.Errorw("Unable to load CA certs.")
|
||||||
rootCancel()
|
rootCancel()
|
||||||
}
|
}
|
||||||
@ -176,7 +168,7 @@ func ActionServe(c *cli.Context) error {
|
|||||||
if c.IsSet("http-addr") {
|
if c.IsSet("http-addr") {
|
||||||
httpAddr = c.String("http-addr")
|
httpAddr = c.String("http-addr")
|
||||||
}
|
}
|
||||||
httpServer := server.NewHTTPSever(s, dataStore, srvCertBytes, cfg.Server.GRPCEndpoint)
|
httpServer := server.NewHTTPSever(s, dataStore, certificates.serverCert, cfg.Server.GRPCEndpoint)
|
||||||
httpServer.Logger = httpLogger
|
httpServer.Logger = httpLogger
|
||||||
httpServer.Addr = httpAddr
|
httpServer.Addr = httpAddr
|
||||||
|
|
||||||
@ -235,3 +227,140 @@ func initializeUsers(us store.UserStore, logger *zap.SugaredLogger) error {
|
|||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type certBytes struct {
|
||||||
|
caCert []byte
|
||||||
|
caCertKey []byte
|
||||||
|
serverCert []byte
|
||||||
|
serverKey []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func getCerts(c *cli.Context, logger *zap.SugaredLogger) (*certBytes, error) {
|
||||||
|
cfg, err := getConfig(c)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
cb := &certBytes{}
|
||||||
|
|
||||||
|
caCertBytes, caCertErr := cfg.Server.GRPC.CACerts.GetCertBytes()
|
||||||
|
caKeyBytes, caKeyErr := cfg.Server.GRPC.CACerts.GetKeyBytes()
|
||||||
|
if caCertErr != nil || caKeyErr != nil {
|
||||||
|
if errors.Is(caCertErr, fs.ErrNotExist) && errors.Is(caKeyErr, fs.ErrNotExist) {
|
||||||
|
// Neither cert or key found, generate
|
||||||
|
logger.Warn("Certificates not found. Generating.")
|
||||||
|
priv, pub, err := certs.GenCACert()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
cb.caCert = pub
|
||||||
|
cb.caCertKey = priv
|
||||||
|
|
||||||
|
// Since we remade ca certs, any existing server certs are useless
|
||||||
|
parsedUrl, err := url.Parse(cfg.Server.Hostname)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("unable to parse hostname: %w", err)
|
||||||
|
}
|
||||||
|
host := parsedUrl.Host
|
||||||
|
if strings.Contains(host, ":") {
|
||||||
|
host, _, err = net.SplitHostPort(host)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("unable to parse hostname: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("unable to generate certs due to unknown hostname")
|
||||||
|
}
|
||||||
|
priv, pub, err = certs.GenCert(host, cb.caCert, cb.caCertKey, []string{host})
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error creating server cert: %s", err)
|
||||||
|
}
|
||||||
|
pub, err = certs.ToPEM(pub, "CERTIFICATE")
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error encoding server cert: %s", err)
|
||||||
|
}
|
||||||
|
priv, err = certs.ToPEM(priv, "EC PRIVATE KEY")
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error encoding server cert: %s", err)
|
||||||
|
}
|
||||||
|
cb.serverCert = pub
|
||||||
|
cb.serverKey = priv
|
||||||
|
cb.caCert, err = certs.ToPEM(cb.caCert, "CERTIFICATE")
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error encoding server cert: %s", err)
|
||||||
|
}
|
||||||
|
cb.caCertKey, err = certs.ToPEM(cb.caCertKey, "EC PRIVATE KEY")
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error encoding server cert: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write them to files
|
||||||
|
if cfg.Server.GRPC.CACerts.CertificatePath != "" {
|
||||||
|
f, err := os.Create(cfg.Server.GRPC.CACerts.CertificatePath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error writing certificate: %w", err)
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
if _, err := f.Write(cb.caCert); err != nil {
|
||||||
|
return nil, fmt.Errorf("error writing certificate: %w", err)
|
||||||
|
}
|
||||||
|
logger.Infow("Wrote CACert.", "path", cfg.Server.GRPC.CACerts.CertificatePath)
|
||||||
|
}
|
||||||
|
if cfg.Server.GRPC.CACerts.CertificateKeyPath != "" {
|
||||||
|
f, err := os.Create(cfg.Server.GRPC.CACerts.CertificateKeyPath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error writing certificate: %w", err)
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
if _, err := f.Write(cb.caCertKey); err != nil {
|
||||||
|
return nil, fmt.Errorf("error writing certificate: %w", err)
|
||||||
|
}
|
||||||
|
logger.Infow("Wrote CACert key.", "path", cfg.Server.GRPC.CACerts.CertificateKeyPath)
|
||||||
|
}
|
||||||
|
if cfg.Server.GRPC.Certs.CertificateKeyPath != "" {
|
||||||
|
f, err := os.Create(cfg.Server.GRPC.Certs.CertificateKeyPath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error writing certificate: %w", err)
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
if _, err := f.Write(cb.serverKey); err != nil {
|
||||||
|
return nil, fmt.Errorf("error writing certificate: %w", err)
|
||||||
|
}
|
||||||
|
logger.Infow("Wrote server cert key.", "path", cfg.Server.GRPC.Certs.CertificateKeyPath)
|
||||||
|
}
|
||||||
|
if cfg.Server.GRPC.Certs.CertificatePath != "" {
|
||||||
|
f, err := os.Create(cfg.Server.GRPC.Certs.CertificatePath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error writing certificate: %w", err)
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
if _, err := f.Write(cb.serverCert); err != nil {
|
||||||
|
return nil, fmt.Errorf("error writing certificate: %w", err)
|
||||||
|
}
|
||||||
|
logger.Infow("Wrote server cert key.", "path", cfg.Server.GRPC.Certs.CertificatePath)
|
||||||
|
}
|
||||||
|
|
||||||
|
return cb, nil
|
||||||
|
} else {
|
||||||
|
if caCertErr != nil {
|
||||||
|
return nil, caCertErr
|
||||||
|
}
|
||||||
|
return nil, caKeyErr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
srvCertBytes, err := cfg.Server.GRPC.Certs.GetCertBytes()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
srvKeyBytes, err := cfg.Server.GRPC.Certs.GetKeyBytes()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &certBytes{
|
||||||
|
caCert: caCertBytes,
|
||||||
|
caCertKey: caKeyBytes,
|
||||||
|
serverCert: srvCertBytes,
|
||||||
|
serverKey: srvKeyBytes}, nil
|
||||||
|
}
|
||||||
|
@ -149,7 +149,7 @@ func FromDefaultLocations() (*Config, error) {
|
|||||||
return nil, fmt.Errorf("config not found")
|
return nil, fmt.Errorf("config not found")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Config) UpdateFromEnv() error {
|
func (c *Config) UpdateFromEnv() {
|
||||||
// Server stuff
|
// Server stuff
|
||||||
if val, found := os.LookupEnv("EZSHARE_SERVER_LOGLEVEL"); found {
|
if val, found := os.LookupEnv("EZSHARE_SERVER_LOGLEVEL"); found {
|
||||||
c.Server.LogLevel = val
|
c.Server.LogLevel = val
|
||||||
@ -207,8 +207,6 @@ func (c *Config) UpdateFromEnv() error {
|
|||||||
if val, found := os.LookupEnv("EZSHARE_CLIENT_SERVERCERTPATH"); found {
|
if val, found := os.LookupEnv("EZSHARE_CLIENT_SERVERCERTPATH"); found {
|
||||||
c.Client.ServerCertPath = val
|
c.Client.ServerCertPath = val
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (sc *ServerConfig) Valid() error {
|
func (sc *ServerConfig) Valid() error {
|
||||||
|
@ -219,9 +219,7 @@ func TestConfig(t *testing.T) {
|
|||||||
t.Errorf("Loglevel is WARN before updating from env.")
|
t.Errorf("Loglevel is WARN before updating from env.")
|
||||||
}
|
}
|
||||||
os.Setenv("EZSHARE_SERVER_LOGLEVEL", "WARN")
|
os.Setenv("EZSHARE_SERVER_LOGLEVEL", "WARN")
|
||||||
if err := cfg.UpdateFromEnv(); err != nil {
|
cfg.UpdateFromEnv()
|
||||||
t.Fatalf("Error updating config from environment: %s", err)
|
|
||||||
}
|
|
||||||
if cfg.Server.LogLevel != "WARN" {
|
if cfg.Server.LogLevel != "WARN" {
|
||||||
t.Errorf("Loglevel is not WARN after updating from env.")
|
t.Errorf("Loglevel is not WARN after updating from env.")
|
||||||
}
|
}
|
||||||
@ -229,9 +227,7 @@ func TestConfig(t *testing.T) {
|
|||||||
// Test Server.Hostname
|
// Test Server.Hostname
|
||||||
hostname := "https://share.example.org"
|
hostname := "https://share.example.org"
|
||||||
os.Setenv("EZSHARE_SERVER_HOSTNAME", hostname)
|
os.Setenv("EZSHARE_SERVER_HOSTNAME", hostname)
|
||||||
if err := cfg.UpdateFromEnv(); err != nil {
|
cfg.UpdateFromEnv()
|
||||||
t.Fatalf("Error updating config from environment: %s", err)
|
|
||||||
}
|
|
||||||
if cfg.Server.Hostname != hostname {
|
if cfg.Server.Hostname != hostname {
|
||||||
t.Errorf("Hostname is incorrect after updating from env.")
|
t.Errorf("Hostname is incorrect after updating from env.")
|
||||||
}
|
}
|
||||||
@ -239,9 +235,7 @@ func TestConfig(t *testing.T) {
|
|||||||
// Test Server.Datastore.Bolt.Path
|
// Test Server.Datastore.Bolt.Path
|
||||||
boltPath := "/data/bolt.db"
|
boltPath := "/data/bolt.db"
|
||||||
os.Setenv("EZSHARE_SERVER_DATASTORE_BOLT_PATH", boltPath)
|
os.Setenv("EZSHARE_SERVER_DATASTORE_BOLT_PATH", boltPath)
|
||||||
if err := cfg.UpdateFromEnv(); err != nil {
|
cfg.UpdateFromEnv()
|
||||||
t.Fatalf("Error updating config from environment: %s", err)
|
|
||||||
}
|
|
||||||
if cfg.Server.DataStoreConfig.BoltStoreConfig.Path != boltPath {
|
if cfg.Server.DataStoreConfig.BoltStoreConfig.Path != boltPath {
|
||||||
t.Errorf("Bolt path is incorrect after updating from env.")
|
t.Errorf("Bolt path is incorrect after updating from env.")
|
||||||
}
|
}
|
||||||
@ -249,9 +243,7 @@ func TestConfig(t *testing.T) {
|
|||||||
// Test Server.Datastore.Bolt.Path
|
// Test Server.Datastore.Bolt.Path
|
||||||
caCertPath := "/data/cert.pem"
|
caCertPath := "/data/cert.pem"
|
||||||
os.Setenv("EZSHARE_SERVER_GRPC_CACERTS_CERTIFICATEKEYPATH", caCertPath)
|
os.Setenv("EZSHARE_SERVER_GRPC_CACERTS_CERTIFICATEKEYPATH", caCertPath)
|
||||||
if err := cfg.UpdateFromEnv(); err != nil {
|
cfg.UpdateFromEnv()
|
||||||
t.Fatalf("Error updating config from environment: %s", err)
|
|
||||||
}
|
|
||||||
if cfg.Server.GRPC.CACerts.CertificateKeyPath != caCertPath {
|
if cfg.Server.GRPC.CACerts.CertificateKeyPath != caCertPath {
|
||||||
t.Errorf("GPRC CA Cert path is incorrect after updating from env.")
|
t.Errorf("GPRC CA Cert path is incorrect after updating from env.")
|
||||||
}
|
}
|
||||||
|
10
ezshare.minimal.env
Normal file
10
ezshare.minimal.env
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
EZSHARE_SERVER_HOSTNAME=https://server.example.org
|
||||||
|
EZSHARE_SERVER_GRPCENDPOINT=server.example.org:50051
|
||||||
|
EZSHARE_SERVER_DATASTORE_TYPE=bolt
|
||||||
|
EZSHARE_SERVER_DATASTORE_BOLT_PATH=/data/ds.db
|
||||||
|
EZSHARE_SERVER_FILESTORE_TYPE=bolt
|
||||||
|
EZSHARE_SERVER_FILESTORE_BOLT_PATH=/data/fs.db
|
||||||
|
EZSHARE_SERVER_GRPC_CACERTS_CERTIFICATEPATH=/data/ca.pem
|
||||||
|
EZSHARE_SERVER_GRPC_CACERTS_CERTIFICATEKEYPATH=/data/ca.key
|
||||||
|
EZSHARE_SERVER_GRPC_CERTS_CERTIFICATEPATH=/data/server.pem
|
||||||
|
EZSHARE_SERVER_GRPC_CERTS_CERTIFICATEKEYPATH=/data/server.key
|
Loading…
Reference in New Issue
Block a user