diff --git a/.gitignore b/.gitignore index 9dd177a..72c916e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,2 @@ -certs/*.pem -certs/*.key \ No newline at end of file +tmp/* +ezshare.toml \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index c7001ce..ec5dc18 100644 --- a/Dockerfile +++ b/Dockerfile @@ -18,4 +18,4 @@ FROM alpine:latest COPY --from=builder-base /app/ezshare /usr/bin/ezshare EXPOSE 50051 EXPOSE 8088 -CMD ["/usr/bin/ezshare", "serve"] \ No newline at end of file +CMD ["/usr/bin/ezshare","--config", "/data/ezshare.toml" "serve"] \ No newline at end of file diff --git a/certs/certs_all.go b/certs/certs_all.go deleted file mode 100644 index fa10e9d..0000000 --- a/certs/certs_all.go +++ /dev/null @@ -1,25 +0,0 @@ -//go:build allcerts - -package certs - -import ( - _ "embed" -) - -//go:embed ca.key -var CAKey []byte - -//go:embed ca.pem -var CACert []byte - -//go:embed srv.key -var SrvKey []byte - -//go:embed srv.pem -var SrvCert []byte - -//go:embed client.key -var ClientKey []byte - -//go:embed client.pem -var ClientCert []byte diff --git a/certs/certs_client.go b/certs/certs_client.go deleted file mode 100644 index 70dd9b2..0000000 --- a/certs/certs_client.go +++ /dev/null @@ -1,23 +0,0 @@ -//go:build clientcerts - -package certs - -import ( - _ "embed" -) - -var CAKey []byte - -//go:embed ca.pem -var CACert []byte - -var SrvKey []byte - -//go:embed srv.pem -var SrvCert []byte - -//go:embed client.key -var ClientKey []byte - -//go:embed client.key -var ClientCert []byte diff --git a/certs/certs_none.go b/certs/certs_none.go deleted file mode 100644 index f952f03..0000000 --- a/certs/certs_none.go +++ /dev/null @@ -1,19 +0,0 @@ -//go:build !allcerts && !clientcerts - -package certs - -import ( - _ "embed" -) - -var CAKey []byte - -var CACert []byte - -var SrvKey []byte - -var SrvCert []byte - -var ClientKey []byte - -var ClientCert []byte diff --git a/certs/generate.go b/certs/generate.go index aa1bf9b..4a0a0c0 100644 --- a/certs/generate.go +++ b/certs/generate.go @@ -11,6 +11,7 @@ import ( "fmt" "math/big" "os" + "path/filepath" "time" ) @@ -88,16 +89,16 @@ func GenCACert() (priv []byte, pub []byte, err error) { return caPrivKeyBytes, caBytes, nil } -func GenCerts() error { +func GenAllCerts(path string) error { // Create CA certs caPriv, caPub, err := GenCACert() if err != nil { return err } - if err := WriteKey(caPriv, "certs/ca.key"); err != nil { + if err := WriteKey(caPriv, filepath.Join(path, "ca.key")); err != nil { return err } - if err := WriteCert(caPub, "certs/ca.pem"); err != nil { + if err := WriteCert(caPub, filepath.Join(path, "ca.pem")); err != nil { return err } @@ -106,10 +107,10 @@ func GenCerts() error { if err != nil { return err } - if err := WriteKey(srvKey, "certs/srv.key"); err != nil { + if err := WriteKey(srvKey, filepath.Join(path, "srv.key")); err != nil { return err } - if err := WriteCert(srvCrt, "certs/srv.pem"); err != nil { + if err := WriteCert(srvCrt, filepath.Join(path, "srv.pem")); err != nil { return err } @@ -117,10 +118,10 @@ func GenCerts() error { if err != nil { return err } - if err := WriteKey(clientKey, "certs/client.key"); err != nil { + if err := WriteKey(clientKey, filepath.Join(path, "client.key")); err != nil { return err } - if err := WriteCert(clientCrt, "certs/client.pem"); err != nil { + if err := WriteCert(clientCrt, filepath.Join(path, "client.pem")); err != nil { return err } diff --git a/client/client.go b/client/client.go deleted file mode 100644 index 44e1f37..0000000 --- a/client/client.go +++ /dev/null @@ -1,17 +0,0 @@ -package client - -import ( - "context" - - "gitea.benny.dog/torjus/ezshare/pb" - "google.golang.org/grpc" -) - -func NewClient(ctx context.Context, addr string) (pb.FileServiceClient, error) { - conn, err := grpc.DialContext(ctx, addr, grpc.WithInsecure()) - if err != nil { - return nil, err - } - client := pb.NewFileServiceClient(conn) - return client, nil -} diff --git a/cmd/ezshare.go b/cmd/ezshare.go index 6a9fd4a..f561e3d 100644 --- a/cmd/ezshare.go +++ b/cmd/ezshare.go @@ -15,6 +15,7 @@ import ( "time" "gitea.benny.dog/torjus/ezshare/certs" + "gitea.benny.dog/torjus/ezshare/config" "gitea.benny.dog/torjus/ezshare/pb" "gitea.benny.dog/torjus/ezshare/server" "gitea.benny.dog/torjus/ezshare/store" @@ -26,6 +27,12 @@ import ( func main() { app := cli.App{ Name: "ezshare", + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "config", + Usage: "Path to config-file.", + }, + }, Commands: []*cli.Command{ { Name: "serve", @@ -79,15 +86,21 @@ func main() { }, }, { - Name: "gencerts", - Usage: "Generate certificates", - Flags: []cli.Flag{ - &cli.StringFlag{ - Name: "out-dir", - Usage: "Directory where certificates will be stored.", + Name: "cert", + Usage: "Certificate commands", + Subcommands: []*cli.Command{ + { + Name: "gen-all", + Usage: "Generate CA, Server and Client certificates", + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "out-dir", + Usage: "Directory where certificates will be stored.", + }, + }, + Action: ActionGencerts, }, }, - Action: ActionGencerts, }, }, } @@ -100,6 +113,25 @@ func main() { } func ActionServe(c *cli.Context) error { + cfg, err := getConfig(c) + if err != nil { + return err + } + + // Read certificates + srvCertBytes, err := cfg.Server.GRPC.Certs.GetCertBytes() + if err != nil { + return err + } + srvKeyBytes, err := cfg.Server.GRPC.Certs.GetKeyBytes() + if err != nil { + return err + } + caCertBytes, err := cfg.Server.GRPC.CACerts.GetCertBytes() + if err != nil { + return err + } + fileStore := store.NewMemoryFileStore() // Setup shutdown-handling rootCtx, rootCancel := signal.NotifyContext(context.Background(), os.Interrupt) @@ -115,7 +147,7 @@ func ActionServe(c *cli.Context) error { // Start grpc server go func() { - grpcAddr := ":50051" + grpcAddr := cfg.Server.GRPC.ListenAddr if c.IsSet("grpc-addr") { grpcAddr = c.String("grpc-addr") } @@ -130,14 +162,13 @@ func ActionServe(c *cli.Context) error { log.Printf("Unable to setup grpc listener: %s\n", err) rootCancel() } - srvCert, err := tls.X509KeyPair(certs.SrvCert, certs.SrvKey) + srvCert, err := tls.X509KeyPair(srvCertBytes, srvKeyBytes) if err != nil { - log.Printf("%d %d", len(certs.SrvCert), len(certs.SrvKey)) log.Printf("Unable load server certs: %s\n", err) rootCancel() } certPool := x509.NewCertPool() - if !certPool.AppendCertsFromPEM(certs.CACert) { + if !certPool.AppendCertsFromPEM(caCertBytes) { log.Println("Unable to load CA cert") rootCancel() } @@ -242,8 +273,17 @@ func ActionClientGet(c *cli.Context) error { } func ActionClientUpload(c *cli.Context) error { - addr := c.String("addr") - clientCreds, err := getClientCreds() + cfg, err := getConfig(c) + if err != nil { + return err + } + + addr := cfg.Client.DefaultServer + if c.IsSet("addr") { + addr = c.String("addr") + } + + clientCreds, err := cfg.Client.Creds() if err != nil { return err } @@ -279,22 +319,22 @@ func ActionClientUpload(c *cli.Context) error { } func ActionGencerts(c *cli.Context) error { - return certs.GenCerts() + outDir := "." + if c.IsSet("out-dir") { + outDir = c.String("out-dir") + } + return certs.GenAllCerts(outDir) } -func getClientCreds() (credentials.TransportCredentials, error) { - certPool := x509.NewCertPool() - if !certPool.AppendCertsFromPEM(certs.CACert) { - return nil, fmt.Errorf("unable to load ca cert") +func getConfig(c *cli.Context) (*config.Config, error) { + if c.IsSet("config") { + cfgPath := c.String("config") + return config.FromFile(cfgPath) } - clientCert, err := tls.X509KeyPair(certs.ClientCert, certs.ClientKey) - if err != nil { - return nil, fmt.Errorf("unable to load client cert: %s", err) + cfg, err := config.FromDefaultLocations() + if err == nil { + fmt.Printf("Config loaded from %s\n", cfg.Location()) + fmt.Printf("Config: %+v\n", cfg) } - config := &tls.Config{ - Certificates: []tls.Certificate{clientCert}, - RootCAs: certPool, - } - return credentials.NewTLS(config), nil - + return cfg, err } diff --git a/config/config.go b/config/config.go new file mode 100644 index 0000000..7ccac2f --- /dev/null +++ b/config/config.go @@ -0,0 +1,175 @@ +package config + +import ( + "crypto/tls" + "crypto/x509" + "fmt" + "io" + "io/ioutil" + "os" + "path/filepath" + + "github.com/pelletier/go-toml" + "google.golang.org/grpc/credentials" +) + +type Config struct { + LogLevel string `toml:"LogLevel"` + Server *ServerConfig `toml:"Server"` + Client *ClientConfig `toml:"Client"` + location string +} +type CertificatePaths struct { + CertificatePath string `toml:"CertificatePath"` + CertificateKeyPath string `toml:"CertificateKeyPath"` +} + +type ServerConfig struct { + GRPC *ServerGRPCConfig `toml:"GRPC"` + HTTP *ServerHTTPConfig `toml:"HTTP"` +} +type ServerStoreConfig struct { + Type string `toml:"Type"` + FSStoreConfig *FSStoreConfig `toml:"Filesystem"` +} +type FSStoreConfig struct { + Dir string `toml:"Dir"` +} +type ServerGRPCConfig struct { + ListenAddr string `toml:"ListenAddr"` + CACerts *CertificatePaths `toml:"CACerts"` + Certs *CertificatePaths `toml:"Certs"` +} +type ServerHTTPConfig struct { + ListenAddr string `toml:"ListenAddr"` +} + +type ClientConfig struct { + DefaultServer string `toml:"DefaultServer"` + ServerCertPath string `toml:"ServerCertPath"` + Certs *CertificatePaths `toml:"Certs"` +} + +func FromDefault() *Config { + cfg := &Config{ + LogLevel: "INFO", + Server: &ServerConfig{ + GRPC: &ServerGRPCConfig{ + ListenAddr: ":50051", + CACerts: &CertificatePaths{}, + Certs: &CertificatePaths{}, + }, + HTTP: &ServerHTTPConfig{ + ListenAddr: ":8089", + }, + }, + Client: &ClientConfig{}, + } + + return cfg +} + +func FromReader(r io.Reader) (*Config, error) { + decoder := toml.NewDecoder(r) + c := FromDefault() + if err := decoder.Decode(c); err != nil { + return nil, fmt.Errorf("unable to read config: %w", err) + } + + return c, nil +} + +func FromFile(path string) (*Config, error) { + f, err := os.Open(path) + if err != nil { + return nil, fmt.Errorf("unable to open config-file: %w", err) + } + defer f.Close() + + cfg, err := FromReader(f) + if err == nil { + cfg.location = path + } + + return cfg, err +} + +func FromDefaultLocations() (*Config, error) { + defaultLocations := []string{ + "ezshare.toml", + } + userConfigDir, err := os.UserConfigDir() + if err != nil { + defaultLocations = append(defaultLocations, filepath.Join(userConfigDir, "ezshare", "ezshare.toml")) + } + + for _, location := range defaultLocations { + if _, err := os.Stat(location); err == nil { + return FromFile(location) + } + } + return nil, fmt.Errorf("config not found") +} + +func (c *Config) Location() string { + return c.location +} + +func (cp *CertificatePaths) GetCertBytes() ([]byte, error) { + f, err := os.Open(cp.CertificatePath) + if err != nil { + return nil, err + } + return ioutil.ReadAll(f) +} + +func (cp *CertificatePaths) GetKeyBytes() ([]byte, error) { + f, err := os.Open(cp.CertificateKeyPath) + if err != nil { + return nil, err + } + return ioutil.ReadAll(f) +} + +func (cc *ClientConfig) ServerCertBytes() ([]byte, error) { + f, err := os.Open(cc.ServerCertPath) + if err != nil { + return nil, fmt.Errorf("unable to open server certificate: %w", err) + } + defer f.Close() + + data, err := ioutil.ReadAll(f) + if err != nil { + return nil, fmt.Errorf("unable to read client server certificate: %w", err) + } + return data, nil +} + +func (cc *ClientConfig) Creds() (credentials.TransportCredentials, error) { + srvCertBytes, err := cc.ServerCertBytes() + if err != nil { + return nil, err + } + clientCertBytes, err := cc.Certs.GetCertBytes() + if err != nil { + return nil, fmt.Errorf("unable to read client cert: %w", err) + } + clientKeyBytes, err := cc.Certs.GetKeyBytes() + if err != nil { + return nil, fmt.Errorf("unable to read client cert: %w", err) + } + certPool := x509.NewCertPool() + if !certPool.AppendCertsFromPEM(srvCertBytes) { + return nil, fmt.Errorf("unable to load ca cert") + } + clientCert, err := tls.X509KeyPair(clientCertBytes, clientKeyBytes) + if err != nil { + return nil, fmt.Errorf("unable to load client cert: %s", err) + } + config := &tls.Config{ + Certificates: []tls.Certificate{clientCert}, + RootCAs: certPool, + } + return credentials.NewTLS(config), nil + +} diff --git a/ezshare.example.toml b/ezshare.example.toml new file mode 100644 index 0000000..5d1a562 --- /dev/null +++ b/ezshare.example.toml @@ -0,0 +1,63 @@ +######################## +# Server configuration # +######################## +[Server] +# Set server log-level +# Must be one of: DEBUG, INFO, WARN, ERROR +# Default: INFO +LogLevel = "INFO" + +# Storage configuration +[Server.Store] +# How server stores file +# Must be one of: filesystem, memory +# Required +Type = "filesystem" + +[Server.Store.Filesystem] +# Where files are stored +# Required if store-type is filesystem +Dir = "/data" + +# GRPC Configuration +[Server.GRPC] +# Address to listen to +# Default: :50051 +ListenAddr = ":50051" + +# GRPC Certificate Configuration +[Server.GRPC.CACerts] +# Path of PEM-encoded certificate file +CertificatePath = "" +# Path of PEM-encoded private key +# Must be of type ecdsa +CertificateKeyPath = "" + +[Server.GRPC.Certs] +# Path of PEM-encoded certificate file +CertificatePath = "" +# Path of PEM-encoded private key +# Must be of type ecdsa +CertificateKeyPath = "" + +[Server.HTTP] +# Address to listen to +# Default: :8089 +ListenAddr = ":8089" + + +######################## +# Client configuration # +######################## +[Client] +# Server used if not specified using command-line +DefaultServer = "localhost:50051" +# Path to PEM-encoder server-certificate +ServerCertPath = "" + +[Client.Certs] +# Path of PEM-encoded certificate file +CertificatePath = "" +# Path of PEM-encoded private key +# Must be of type ecdsa +CertificateKeyPath = "" diff --git a/go.mod b/go.mod index 62c1efc..5e6c6de 100644 --- a/go.mod +++ b/go.mod @@ -5,6 +5,7 @@ go 1.17 require ( github.com/go-chi/chi/v5 v5.0.7 github.com/google/uuid v1.3.0 + github.com/pelletier/go-toml v1.9.4 github.com/urfave/cli/v2 v2.3.0 google.golang.org/grpc v1.42.0 google.golang.org/protobuf v1.27.1 @@ -14,8 +15,8 @@ require ( github.com/cpuguy83/go-md2man/v2 v2.0.1 // indirect github.com/golang/protobuf v1.5.2 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect - golang.org/x/net v0.0.0-20211201190559-0a0e4e1bb54c // indirect + golang.org/x/net v0.0.0-20211203184738-4852103109b8 // indirect golang.org/x/sys v0.0.0-20211124211545-fe61309f8881 // indirect golang.org/x/text v0.3.7 // indirect - google.golang.org/genproto v0.0.0-20211129164237-f09f9a12af12 // indirect + google.golang.org/genproto v0.0.0-20211203200212-54befc351ae9 // indirect ) diff --git a/go.sum b/go.sum index d57878e..36bcde1 100644 --- a/go.sum +++ b/go.sum @@ -55,6 +55,8 @@ github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+ github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= +github.com/pelletier/go-toml v1.9.4 h1:tjENF6MfZAg8e4ZmZTeWaWiT2vXtsoO6+iuOjFhECwM= +github.com/pelletier/go-toml v1.9.4/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= @@ -83,8 +85,8 @@ golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= -golang.org/x/net v0.0.0-20211201190559-0a0e4e1bb54c h1:WtYZ93XtWSO5KlOMgPZu7hXY9WhMZpprvlm5VwvAl8c= -golang.org/x/net v0.0.0-20211201190559-0a0e4e1bb54c/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211203184738-4852103109b8 h1:PFkPt/jI9Del3hmFplBtRp8tDhSRpFu7CyRs7VmEC0M= +golang.org/x/net v0.0.0-20211203184738-4852103109b8/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -122,8 +124,8 @@ google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoA google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/genproto v0.0.0-20211129164237-f09f9a12af12 h1:DN5b3HU13J4sMd/QjDx34U6afpaexKTDdop+26pdjdk= -google.golang.org/genproto v0.0.0-20211129164237-f09f9a12af12/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20211203200212-54befc351ae9 h1:fU3FNfL/oBU2D5DvGqiuyVqqn40DdxvaTFHq7aivA3k= +google.golang.org/genproto v0.0.0-20211203200212-54befc351ae9/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= diff --git a/version.go b/version.go index 1ea8ab6..99d250a 100644 --- a/version.go +++ b/version.go @@ -1,3 +1,3 @@ package ezshare -const Version = "v0.1.0" +const Version = "v0.1.1"