Skip to content

Commit 75172e7

Browse files
wilybracejkatz
authored andcommitted
Enables the bypassing of authentication for health check endpoint
This allows the health check endpoint to be accessible to large-scale monitoring tools. Prior to this, all URL routes require mutual TLS authentication and all but the /health route requires HTTP Basic authentication. The /health route potentially prompts for HTTP Basic, but does not enforce it. When the `NOAUTH_ROUTES` environment variable is correctly configured, the server's TLS configuration is reduced to verifying presented client certificates if presented vice both requiring and verifying client certificates. For all protected routes, the existence of verified client certificates is confirmed or a "HTTP Forbidden" status is returned. For noauth routes, that verified certificate check is skipped. At this time, only the /health endpoint is allowed to be a noauth route. Enabling future endpoints will require they remove dependencies on the username returned from HTTP Basic authentication. /health already had no dependency and no validation prior to this change.
1 parent 4203302 commit 75172e7

File tree

4 files changed

+136
-4
lines changed

4 files changed

+136
-4
lines changed

apiserver.go

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import (
2222
"net/http"
2323
"os"
2424
"strconv"
25+
"strings"
2526
"time"
2627

2728
"github.com/crunchydata/postgres-operator/apiserver"
@@ -51,9 +52,10 @@ import (
5152
"github.com/crunchydata/postgres-operator/apiserver/userservice"
5253
"github.com/crunchydata/postgres-operator/apiserver/versionservice"
5354
"github.com/crunchydata/postgres-operator/apiserver/workflowservice"
55+
crunchylog "github.com/crunchydata/postgres-operator/logging"
56+
5457
"github.com/gorilla/mux"
5558
log "github.com/sirupsen/logrus"
56-
crunchylog "github.com/crunchydata/postgres-operator/logging"
5759
)
5860

5961
const serverCertPath = "/tmp/server.crt"
@@ -98,6 +100,8 @@ func main() {
98100
}
99101
disableTLS, _ := strconv.ParseBool(tmp)
100102

103+
skipAuthRoutes := strings.TrimSpace(os.Getenv("NOAUTH_ROUTES"))
104+
101105
log.Infoln("postgres-operator apiserver starts")
102106

103107
apiserver.Initialize()
@@ -181,6 +185,18 @@ func main() {
181185
r.HandleFunc("/benchmarkdelete", benchmarkservice.DeleteBenchmarkHandler).Methods("POST")
182186
r.HandleFunc("/benchmarkshow", benchmarkservice.ShowBenchmarkHandler).Methods("POST")
183187

188+
// Optionally lighten mTLS requirement if some paths don't require certs
189+
certsVerify := tls.RequireAndVerifyClientCert
190+
if !disableTLS && len(skipAuthRoutes) > 0 {
191+
certsVerify = tls.VerifyClientCertIfGiven
192+
optCertEnforcer, err := apiserver.NewCertEnforcer(strings.Split(skipAuthRoutes, ","))
193+
if err != nil {
194+
log.Fatalf("NOAUTH_ROUTES configured incorrectly: %s", err)
195+
os.Exit(2)
196+
}
197+
r.Use(optCertEnforcer.Enforce)
198+
}
199+
184200
err := apiserver.GetTLS(serverCertPath, serverKeyPath)
185201
if err != nil {
186202
log.Fatal(err)
@@ -199,9 +215,9 @@ func main() {
199215
caCertPool.AppendCertsFromPEM(caCert)
200216

201217
cfg := &tls.Config{
202-
ClientAuth: tls.RequireAndVerifyClientCert,
203218
//specify pgo-apiserver in the CN....then, add ServerName: "pgo-apiserver",
204219
ServerName: "pgo-apiserver",
220+
ClientAuth: certsVerify,
205221
InsecureSkipVerify: tlsNoVerify,
206222
ClientCAs: caCertPool,
207223
MinVersion: tls.VersionTLS11,

apiserver/middleware.go

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
package apiserver
2+
3+
/*
4+
Copyright 2019 Crunchy Data Solutions, Inc.
5+
Licensed under the Apache License, Version 2.0 (the "License");
6+
you may not use this file except in compliance with the License.
7+
You may obtain a copy of the License at
8+
9+
http://www.apache.org/licenses/LICENSE-2.0
10+
11+
Unless required by applicable law or agreed to in writing, software
12+
distributed under the License is distributed on an "AS IS" BASIS,
13+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
See the License for the specific language governing permissions and
15+
limitations under the License.
16+
*/
17+
18+
import (
19+
"fmt"
20+
"net/http"
21+
"strings"
22+
)
23+
24+
// certEnforcer is a contextual middleware for deferred enforcement of
25+
// client certificates. It assumes any certificates presented where validated
26+
// as part of establishing the TLS connection.
27+
type certEnforcer struct {
28+
skip map[string]struct{}
29+
}
30+
31+
// NewCertEnforcer ensures a certEnforcer is created with skipped routes
32+
// and validates that the configured routes are allowed
33+
func NewCertEnforcer(reqRoutes []string) (*certEnforcer, error) {
34+
allowed := map[string]struct{}{
35+
// List of allowed routes is part of the published documentation
36+
"/health": struct{}{},
37+
}
38+
39+
ce := &certEnforcer{
40+
skip: map[string]struct{}{},
41+
}
42+
43+
for _, route := range reqRoutes {
44+
r := strings.TrimSpace(route)
45+
if _, ok := allowed[r]; !ok {
46+
return nil, fmt.Errorf("Disabling auth unsupported for route [%s]", r)
47+
}
48+
ce.skip[r] = struct{}{}
49+
}
50+
return ce, nil
51+
}
52+
53+
// Enforce is an HTTP middleware for selectively enforcing deferred client
54+
// certificate checks based on the certEnforcer's skip list
55+
func (ce *certEnforcer) Enforce(next http.Handler) http.Handler {
56+
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
57+
path := r.URL.Path
58+
if _, ok := ce.skip[path]; ok {
59+
next.ServeHTTP(w, r)
60+
} else {
61+
clientCerts := len(r.TLS.PeerCertificates) > 0
62+
if !clientCerts {
63+
http.Error(w, "Forbidden: Client Certificate Required", http.StatusForbidden)
64+
} else {
65+
next.ServeHTTP(w, r)
66+
}
67+
}
68+
})
69+
}

apiserver/versionservice/versionservice.go

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,6 @@ func VersionHandler(w http.ResponseWriter, r *http.Request) {
4444

4545
// HealthHandler ...
4646
func HealthHandler(w http.ResponseWriter, r *http.Request) {
47-
48-
w.Header().Set("WWW-Authenticate", `Basic realm="Restricted"`)
4947
w.Header().Set("Content-Type", "application/json")
5048
w.WriteHeader(http.StatusOK)
5149

hugo/content/Configuration/configuration.md

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,55 @@ Files within this directory are used specifically when creating PostgreSQL Clust
5252

5353
As with the other Operator templates, administrators can make custom changes to this set of templates to add custom features or metadata into the Resources created by the Operator.
5454

55+
## Operator API Server
56+
57+
The Operator's API server can be configured to allow access to select URL routes
58+
without requiring TLS authentication from the client and without
59+
the HTTP Basic authentication used for role-based-access.
60+
61+
This configuration is performed by defining the `NOAUTH_ROUTES` environment
62+
variable for the apiserver container within the Operator pod.
63+
64+
Typically, this configuration is made within the `deploy/deployment.json`
65+
file for bash-based installations and
66+
`ansible/roles/pgo-operator/templates/deployment.json.j2` for ansible installations.
67+
68+
For example:
69+
```
70+
...
71+
containers: [
72+
{
73+
"name": "apiserver"
74+
"env": [
75+
{
76+
"name": "NOAUTH_ROUTES",
77+
"value": "/health"
78+
}
79+
]
80+
...
81+
}
82+
...
83+
]
84+
...
85+
```
86+
87+
The `NOAUTH_ROUTES` variable must be set to a comma-separated list of
88+
URL routes. For example: `/health,/version,/example3` would opt to **disable**
89+
authentication for `$APISERVER_URL/health`, `$APISERVER_URL/version`, and
90+
`$APISERVER_URL/example3` respectively.
91+
92+
Currently, only the following routes may have authentication disabled using
93+
this setting:
94+
95+
```
96+
/health
97+
```
98+
99+
If the health route has its authentication disabled, the existing readiness
100+
and liveness probes for the apiserver container could be enhanced by
101+
configuring them with HTTP-based checks against the health route.
102+
103+
55104
## Security
56105

57106
Setting up pgo users and general security configuration is described in the [Security](/security) section of this documentation.

0 commit comments

Comments
 (0)