1
0
mirror of https://github.com/smallstep/cli.git synced 2025-08-10 14:22:59 +03:00
Files
step-ca-cli/command/fileserver/fileserver.go
max furman 85fa03947f Fix or ignore gosec issues
* fix a few other linting issues
2022-08-18 18:55:38 -07:00

152 lines
3.6 KiB
Go

package fileserver
import (
"crypto/tls"
"crypto/x509"
"fmt"
"net"
"net/http"
"os"
"time"
"github.com/pkg/errors"
"github.com/smallstep/cli/utils"
"go.step.sm/cli-utils/errs"
"github.com/urfave/cli"
"go.step.sm/cli-utils/command"
)
func init() {
cmd := cli.Command{
Name: "fileserver",
Hidden: true,
Action: command.ActionFunc(fileServerAction),
Usage: "start an HTTP(S) server serving the contents of a path",
UsageText: `step fileserver <dir>
[**--address**=<address>] [**--cert**=<file>] [**--key**=<file>] [**--roots**=<file>]`,
Description: `**step fileserver** command starts an HTTP(S) server serving the contents of a file
system.
This command is experimental and only intended for test purposes.
## POSITIONAL ARGUMENTS
<dir>
: The directory used as root for the HTTP file server.
## EXAMPLES
Start an HTTP file server on port 8080.
'''
$ step fileserver --address :8080 /path/to/web-root
'''
Start an HTTPS file server on 127.0.0.1:8443.
'''
$ step ca certificate 127.0.0.1 localhost.crt localhost.key
...
$ step fileserver --address 127.0.0.1:8443 \
--cert localhost.crt --key localhost.key /path/to/web-root
'''
Start an HTTPS file server on a random port and require client certificates.
'''
$ step fileserver --cert localhost.crt --key localhost.key \
--roots $(step path)/certs/root_ca.crt /path/to/web-root
'''`,
Flags: []cli.Flag{
cli.StringFlag{
Name: "address",
Usage: "The TCP <address> to listen on (e.g. \":8443\").",
Value: ":0",
},
cli.StringFlag{
Name: "cert",
Usage: `The <file> containing the TLS certificate to use.`,
},
cli.StringFlag{
Name: "key",
Usage: `The <file> containing the key corresponding to the certificate.`,
},
cli.StringFlag{
Name: "roots",
Usage: "The <file> containing the root certificate(s) that will be used to verify the client certificates.",
},
},
}
command.Register(cmd)
}
func fileServerAction(ctx *cli.Context) error {
if err := errs.NumberOfArguments(ctx, 1); err != nil {
return err
}
root := ctx.Args().First()
f, err := os.Stat(root)
if err != nil {
return errs.FileError(err, root)
}
if !f.Mode().IsDir() {
return errors.New("positional argument <dir> must be a directory")
}
address := ctx.String("address")
cert := ctx.String("cert")
key := ctx.String("key")
roots := ctx.String("roots")
switch {
case address == "":
return errs.RequiredFlag(ctx, "address")
case roots != "" && cert == "":
return errs.RequiredWithFlag(ctx, "roots", "cert")
case roots != "" && key == "":
return errs.RequiredWithFlag(ctx, "roots", "key")
case cert != "" && key == "":
return errs.RequiredWithFlag(ctx, "cert", "key")
case key != "" && cert == "":
return errs.RequiredWithFlag(ctx, "key", "cert")
}
var tlsConfig *tls.Config
if roots != "" {
b, err := utils.ReadFile(roots)
if err != nil {
return err
}
pool := x509.NewCertPool()
pool.AppendCertsFromPEM(b)
tlsConfig = &tls.Config{
ClientCAs: pool,
ClientAuth: tls.RequireAndVerifyClientCert,
MinVersion: tls.VersionTLS13,
}
}
l, err := net.Listen("tcp", address)
if err != nil {
return errors.Wrapf(err, "failed to listen on at %s", address)
}
srv := &http.Server{
Handler: http.FileServer(http.Dir(root)),
TLSConfig: tlsConfig,
ReadHeaderTimeout: 15 * time.Second,
}
if cert != "" && key != "" {
fmt.Printf("Serving HTTPS at %s ...\n", l.Addr().String())
err = srv.ServeTLS(l, cert, key)
} else {
fmt.Printf("Serving HTTP at %s...\n", l.Addr().String())
err = srv.Serve(l)
}
if err != nil && err != http.ErrServerClosed {
return errors.Wrap(err, "file server failed")
}
return nil
}