feat(api): added tls/ssl support

This commit is contained in:
Jonas Geiler
2024-05-26 08:07:44 +02:00
committed by Julien Neuhart
parent e50de4201d
commit 20b8991fa4
7 changed files with 201 additions and 10 deletions

View File

@@ -28,6 +28,8 @@ func init() {
// middlewares or health checks.
type Api struct {
port int
tlsCertFile string
tlsKeyFile string
startTimeout time.Duration
timeout time.Duration
rootPath string
@@ -159,6 +161,8 @@ func (a *Api) Descriptor() gotenberg.ModuleDescriptor {
fs := flag.NewFlagSet("api", flag.ExitOnError)
fs.Int("api-port", 3000, "Set the port on which the API should listen")
fs.String("api-port-from-env", "", "Set the environment variable with the port on which the API should listen - override the default port")
fs.String("api-tls-cert-file", "", "Path to the TLS/SSL certificate file - for HTTPS support")
fs.String("api-tls-key-file", "", "Path to the TLS/SSL key file - for HTTPS support")
fs.Duration("api-start-timeout", time.Duration(30)*time.Second, "Set the time limit for the API to start")
fs.Duration("api-timeout", time.Duration(30)*time.Second, "Set the time limit for requests")
fs.String("api-root-path", "/", "Set the root path of the API - for service discovery via URL paths")
@@ -175,6 +179,8 @@ func (a *Api) Descriptor() gotenberg.ModuleDescriptor {
func (a *Api) Provision(ctx *gotenberg.Context) error {
flags := ctx.ParsedFlags()
a.port = flags.MustInt("api-port")
a.tlsCertFile = flags.MustString("api-tls-cert-file")
a.tlsKeyFile = flags.MustString("api-tls-key-file")
a.startTimeout = flags.MustDuration("api-start-timeout")
a.timeout = flags.MustDuration("api-timeout")
a.rootPath = flags.MustString("api-root-path")
@@ -301,6 +307,12 @@ func (a *Api) Validate() error {
)
}
if (a.tlsCertFile != "" && a.tlsKeyFile == "") || (a.tlsCertFile == "" && a.tlsKeyFile != "") {
err = multierr.Append(err,
errors.New("tls certificate and key file must both be set"),
)
}
if !strings.HasPrefix(a.rootPath, "/") {
err = multierr.Append(err,
errors.New("root path must start with /"),
@@ -478,10 +490,19 @@ func (a *Api) Start() error {
// As the following code is blocking, run it in a goroutine.
go func() {
server := &http2.Server{}
err := a.srv.StartH2CServer(fmt.Sprintf(":%d", a.port), server)
if !errors.Is(err, http.ErrServerClosed) {
a.logger.Fatal(err.Error())
if a.tlsCertFile != "" && a.tlsKeyFile != "" {
// Start an HTTPS server (supports HTTP/2).
err := a.srv.StartTLS(fmt.Sprintf(":%d", a.port), a.tlsCertFile, a.tlsKeyFile)
if !errors.Is(err, http.ErrServerClosed) {
a.logger.Fatal(err.Error())
}
} else {
// Start an HTTP/2 Cleartext (non-HTTPS) server.
server := &http2.Server{}
err := a.srv.StartH2CServer(fmt.Sprintf(":%d", a.port), server)
if !errors.Is(err, http.ErrServerClosed) {
a.logger.Fatal(err.Error())
}
}
}()

View File

@@ -462,6 +462,8 @@ func TestApi_Validate(t *testing.T) {
for _, tc := range []struct {
scenario string
port int
tlsCertFile string
tlsKeyFile string
rootPath string
traceHeader string
routes []Route
@@ -486,6 +488,26 @@ func TestApi_Validate(t *testing.T) {
middlewares: nil,
expectError: true,
},
{
scenario: "invalid tls files: only cert file provided",
port: 10,
tlsCertFile: "cert.pem",
rootPath: "/foo/",
traceHeader: "foo",
routes: nil,
middlewares: nil,
expectError: true,
},
{
scenario: "invalid tls files: only key file provided",
port: 10,
tlsKeyFile: "key.pem",
rootPath: "/foo/",
traceHeader: "foo",
routes: nil,
middlewares: nil,
expectError: true,
},
{
scenario: "invalid root path: missing / prefix",
port: 10,
@@ -647,10 +669,45 @@ func TestApi_Validate(t *testing.T) {
},
},
},
{
scenario: "success with tls",
port: 10,
tlsCertFile: "cert.pem",
tlsKeyFile: "key.pem",
rootPath: "/foo/",
traceHeader: "foo",
routes: []Route{
{
Method: http.MethodGet,
Path: "/foo",
Handler: func(_ echo.Context) error { return nil },
},
{
Method: http.MethodGet,
Path: "/forms/foo",
Handler: func(_ echo.Context) error { return nil },
IsMultipart: true,
},
},
middlewares: []Middleware{
{
Priority: HighPriority,
Handler: func() echo.MiddlewareFunc {
return func(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
return next(c)
}
}
}(),
},
},
},
} {
t.Run(tc.scenario, func(t *testing.T) {
mod := Api{
port: tc.port,
tlsCertFile: tc.tlsCertFile,
tlsKeyFile: tc.tlsKeyFile,
rootPath: tc.rootPath,
traceHeader: tc.traceHeader,
routes: tc.routes,
@@ -673,6 +730,8 @@ func TestApi_Start(t *testing.T) {
for _, tc := range []struct {
scenario string
readyFn []func() error
tlsCertFile string
tlsKeyFile string
expectError bool
}{
{
@@ -691,10 +750,22 @@ func TestApi_Start(t *testing.T) {
},
expectError: false,
},
{
scenario: "success with tls",
readyFn: []func() error{
func() error { return nil },
func() error { return nil },
},
tlsCertFile: "/tests/test/testdata/api/cert.pem",
tlsKeyFile: "/tests/test/testdata/api/key.pem",
expectError: false,
},
} {
t.Run(tc.scenario, func(t *testing.T) {
mod := new(Api)
mod.port = 3000
mod.tlsCertFile = tc.tlsCertFile
mod.tlsKeyFile = tc.tlsKeyFile
mod.startTimeout = time.Duration(30) * time.Second
mod.rootPath = "/"
mod.basicAuthUsername = "foo"