Change to camel-case for insecureAcceptExternalUnencryptedHttpRequests

- Use camel-case in the static configmap
- Parse the value into a boolean in the go struct instead of a string
- Add test for when unsupported value is used in the configmap
- Run the config_test.go tests in parallel
- Update some paragraphs in configure-supervisor.md for clarity
This commit is contained in:
Ryan Richard 2022-03-31 16:23:45 -07:00
parent ae7aac020a
commit 51c527a965
6 changed files with 77 additions and 43 deletions

View File

@ -52,7 +52,7 @@ _: #@ template.replace(data.values.custom_labels)
#@ "defaultTLSCertificateSecret": defaultResourceNameWithSuffix("default-tls-certificate"), #@ "defaultTLSCertificateSecret": defaultResourceNameWithSuffix("default-tls-certificate"),
#@ }, #@ },
#@ "labels": labels(), #@ "labels": labels(),
#@ "insecure_accept_external_unencrypted_http_requests": data.values.deprecated_insecure_accept_external_unencrypted_http_requests #@ "insecureAcceptExternalUnencryptedHttpRequests": data.values.deprecated_insecure_accept_external_unencrypted_http_requests
#@ } #@ }
#@ if data.values.log_level: #@ if data.values.log_level:
#@ config["logLevel"] = getAndValidateLogLevel() #@ config["logLevel"] = getAndValidateLogLevel()

View File

@ -122,7 +122,7 @@ endpoints:
#! interface, including interfaces that are listening for traffic from outside the pod. This value is being introduced #! interface, including interfaces that are listening for traffic from outside the pod. This value is being introduced
#! to ease the transition to the new loopback interface validation for the HTTP port for any users who need more time #! to ease the transition to the new loopback interface validation for the HTTP port for any users who need more time
#! to change their ingress strategy to avoid using plain HTTP into the Supervisor pods. #! to change their ingress strategy to avoid using plain HTTP into the Supervisor pods.
#! This value is being introduced and immediately deprecated. It will be removed in some future release, at which time #! This value is immediately deprecated upon its introduction. It will be removed in some future release, at which time
#! traffic from outside the pod will need to be sent to the HTTPS listener instead, with no simple workaround available. #! traffic from outside the pod will need to be sent to the HTTPS listener instead, with no simple workaround available.
#! Allowed values are true (boolean), "true" (string), false (boolean), and "false" (string). The default is false. #! Allowed values are true (boolean), "true" (string), false (boolean), and "false" (string). The default is false.
#! Optional. #! Optional.

View File

@ -131,9 +131,9 @@ func validateEndpoint(endpoint Endpoint) error {
} }
} }
func validateAdditionalHTTPEndpointRequirements(endpoint Endpoint, allowExternalHTTP string) error { func validateAdditionalHTTPEndpointRequirements(endpoint Endpoint, allowExternalHTTP stringOrBoolAsBool) error {
if endpoint.Network == NetworkTCP && !addrIsOnlyOnLoopback(endpoint.Address) { if endpoint.Network == NetworkTCP && !addrIsOnlyOnLoopback(endpoint.Address) {
if allowExternalHTTP == "true" { if allowExternalHTTP {
// Log that the validation should have been triggered. // Log that the validation should have been triggered.
plog.Warning("Listening on non-loopback interfaces for the HTTP port is deprecated and will be removed " + plog.Warning("Listening on non-loopback interfaces for the HTTP port is deprecated and will be removed " +
"in a future release. Your current configuration would not be allowed in that future release. " + "in a future release. Your current configuration would not be allowed in that future release. " +

View File

@ -39,7 +39,7 @@ func TestFromPath(t *testing.T) {
http: http:
network: tcp network: tcp
address: 127.0.0.1:1234 address: 127.0.0.1:1234
insecure_accept_external_unencrypted_http_requests: false insecureAcceptExternalUnencryptedHttpRequests: false
`), `),
wantConfig: &Config{ wantConfig: &Config{
APIGroupSuffix: pointer.StringPtr("some.suffix.com"), APIGroupSuffix: pointer.StringPtr("some.suffix.com"),
@ -60,7 +60,7 @@ func TestFromPath(t *testing.T) {
Address: "127.0.0.1:1234", Address: "127.0.0.1:1234",
}, },
}, },
AllowExternalHTTP: "false", AllowExternalHTTP: false,
}, },
}, },
{ {
@ -85,7 +85,7 @@ func TestFromPath(t *testing.T) {
Network: "disabled", Network: "disabled",
}, },
}, },
AllowExternalHTTP: "", AllowExternalHTTP: false,
}, },
}, },
{ {
@ -131,7 +131,7 @@ func TestFromPath(t *testing.T) {
wantError: `validate http endpoint: unknown network "bar"`, wantError: `validate http endpoint: unknown network "bar"`,
}, },
{ {
name: "http endpoint uses tcp but binds to more than only loopback interfaces with insecure_accept_external_unencrypted_http_requests missing", name: "http endpoint uses tcp but binds to more than only loopback interfaces with insecureAcceptExternalUnencryptedHttpRequests missing",
yaml: here.Doc(` yaml: here.Doc(`
--- ---
names: names:
@ -146,7 +146,7 @@ func TestFromPath(t *testing.T) {
wantError: `validate http endpoint: http listener address ":8080" for "tcp" network may only bind to loopback interfaces`, wantError: `validate http endpoint: http listener address ":8080" for "tcp" network may only bind to loopback interfaces`,
}, },
{ {
name: "http endpoint uses tcp but binds to more than only loopback interfaces with insecure_accept_external_unencrypted_http_requests set to boolean false", name: "http endpoint uses tcp but binds to more than only loopback interfaces with insecureAcceptExternalUnencryptedHttpRequests set to boolean false",
yaml: here.Doc(` yaml: here.Doc(`
--- ---
names: names:
@ -157,12 +157,22 @@ func TestFromPath(t *testing.T) {
http: http:
network: tcp network: tcp
address: :8080 address: :8080
insecure_accept_external_unencrypted_http_requests: false insecureAcceptExternalUnencryptedHttpRequests: false
`), `),
wantError: `validate http endpoint: http listener address ":8080" for "tcp" network may only bind to loopback interfaces`, wantError: `validate http endpoint: http listener address ":8080" for "tcp" network may only bind to loopback interfaces`,
}, },
{ {
name: "http endpoint uses tcp but binds to more than only loopback interfaces with insecure_accept_external_unencrypted_http_requests set to string false", name: "http endpoint uses tcp but binds to more than only loopback interfaces with insecureAcceptExternalUnencryptedHttpRequests set to unsupported value",
yaml: here.Doc(`
---
names:
defaultTLSCertificateSecret: my-secret-name
insecureAcceptExternalUnencryptedHttpRequests: "garbage" # this will be treated as the default, which is false
`),
wantError: `decode yaml: error unmarshaling JSON: while decoding JSON: invalid value for boolean`,
},
{
name: "http endpoint uses tcp but binds to more than only loopback interfaces with insecureAcceptExternalUnencryptedHttpRequests set to string false",
yaml: here.Doc(` yaml: here.Doc(`
--- ---
names: names:
@ -173,12 +183,12 @@ func TestFromPath(t *testing.T) {
http: http:
network: tcp network: tcp
address: :8080 address: :8080
insecure_accept_external_unencrypted_http_requests: "false" insecureAcceptExternalUnencryptedHttpRequests: "false"
`), `),
wantError: `validate http endpoint: http listener address ":8080" for "tcp" network may only bind to loopback interfaces`, wantError: `validate http endpoint: http listener address ":8080" for "tcp" network may only bind to loopback interfaces`,
}, },
{ {
name: "http endpoint uses tcp but binds to more than only loopback interfaces with insecure_accept_external_unencrypted_http_requests set to boolean true", name: "http endpoint uses tcp but binds to more than only loopback interfaces with insecureAcceptExternalUnencryptedHttpRequests set to boolean true",
yaml: here.Doc(` yaml: here.Doc(`
--- ---
names: names:
@ -187,7 +197,7 @@ func TestFromPath(t *testing.T) {
http: http:
network: tcp network: tcp
address: :1234 address: :1234
insecure_accept_external_unencrypted_http_requests: true insecureAcceptExternalUnencryptedHttpRequests: true
`), `),
wantConfig: &Config{ wantConfig: &Config{
APIGroupSuffix: pointer.StringPtr("pinniped.dev"), APIGroupSuffix: pointer.StringPtr("pinniped.dev"),
@ -205,11 +215,11 @@ func TestFromPath(t *testing.T) {
Address: ":1234", Address: ":1234",
}, },
}, },
AllowExternalHTTP: "true", AllowExternalHTTP: true,
}, },
}, },
{ {
name: "http endpoint uses tcp but binds to more than only loopback interfaces with insecure_accept_external_unencrypted_http_requests set to string true", name: "http endpoint uses tcp but binds to more than only loopback interfaces with insecureAcceptExternalUnencryptedHttpRequests set to string true",
yaml: here.Doc(` yaml: here.Doc(`
--- ---
names: names:
@ -218,7 +228,7 @@ func TestFromPath(t *testing.T) {
http: http:
network: tcp network: tcp
address: :1234 address: :1234
insecure_accept_external_unencrypted_http_requests: "true" insecureAcceptExternalUnencryptedHttpRequests: "true"
`), `),
wantConfig: &Config{ wantConfig: &Config{
APIGroupSuffix: pointer.StringPtr("pinniped.dev"), APIGroupSuffix: pointer.StringPtr("pinniped.dev"),
@ -236,7 +246,7 @@ func TestFromPath(t *testing.T) {
Address: ":1234", Address: ":1234",
}, },
}, },
AllowExternalHTTP: "true", AllowExternalHTTP: true,
}, },
}, },
{ {
@ -297,6 +307,8 @@ func TestFromPath(t *testing.T) {
for _, test := range tests { for _, test := range tests {
test := test test := test
t.Run(test.name, func(t *testing.T) { t.Run(test.name, func(t *testing.T) {
t.Parallel()
// Write yaml to temp file // Write yaml to temp file
f, err := ioutil.TempFile("", "pinniped-test-config-yaml-*") f, err := ioutil.TempFile("", "pinniped-test-config-yaml-*")
require.NoError(t, err) require.NoError(t, err)
@ -388,6 +400,8 @@ func TestAddrIsOnlyOnLoopback(t *testing.T) {
for _, test := range tests { for _, test := range tests {
tt := test tt := test
t.Run(fmt.Sprintf("address %s should be %t", tt.addr, tt.want), func(t *testing.T) { t.Run(fmt.Sprintf("address %s should be %t", tt.addr, tt.want), func(t *testing.T) {
t.Parallel()
require.Equal(t, tt.want, addrIsOnlyOnLoopback(tt.addr)) require.Equal(t, tt.want, addrIsOnlyOnLoopback(tt.addr))
}) })
} }

View File

@ -3,16 +3,20 @@
package supervisor package supervisor
import "go.pinniped.dev/internal/plog" import (
"errors"
"go.pinniped.dev/internal/plog"
)
// Config contains knobs to setup an instance of the Pinniped Supervisor. // Config contains knobs to setup an instance of the Pinniped Supervisor.
type Config struct { type Config struct {
APIGroupSuffix *string `json:"apiGroupSuffix,omitempty"` APIGroupSuffix *string `json:"apiGroupSuffix,omitempty"`
Labels map[string]string `json:"labels"` Labels map[string]string `json:"labels"`
NamesConfig NamesConfigSpec `json:"names"` NamesConfig NamesConfigSpec `json:"names"`
LogLevel plog.LogLevel `json:"logLevel"` LogLevel plog.LogLevel `json:"logLevel"`
Endpoints *Endpoints `json:"endpoints"` Endpoints *Endpoints `json:"endpoints"`
AllowExternalHTTP string `json:"insecure_accept_external_unencrypted_http_requests"` AllowExternalHTTP stringOrBoolAsBool `json:"insecureAcceptExternalUnencryptedHttpRequests"`
} }
// NamesConfigSpec configures the names of some Kubernetes resources for the Supervisor. // NamesConfigSpec configures the names of some Kubernetes resources for the Supervisor.
@ -29,3 +33,17 @@ type Endpoint struct {
Network string `json:"network"` Network string `json:"network"`
Address string `json:"address"` Address string `json:"address"`
} }
type stringOrBoolAsBool bool
func (sb *stringOrBoolAsBool) UnmarshalJSON(b []byte) error {
switch string(b) {
case "true", `"true"`:
*sb = true
case "false", `"false"`:
*sb = false
default:
return errors.New("invalid value for boolean")
}
return nil
}

View File

@ -29,13 +29,17 @@ It is recommended that the traffic to these endpoints should be encrypted via TL
Supervisor pods, even when crossing boundaries that are entirely inside the Kubernetes cluster. Supervisor pods, even when crossing boundaries that are entirely inside the Kubernetes cluster.
The credentials and tokens that are handled by these endpoints are too sensitive to transmit without encryption. The credentials and tokens that are handled by these endpoints are too sensitive to transmit without encryption.
In all versions of the Supervisor app so far, there are both HTTP and HTTPS ports available for use by default. In previous versions of the Supervisor app, there were both HTTP and HTTPS ports available for use by default.
These ports each host all the Supervisor's endpoints. Unfortunately, this has caused some confusion in the community These ports each host all the Supervisor's endpoints. Unfortunately, this has caused some confusion in the community
and some blog posts have been written which demonstrate using the HTTP port in such a way that a portion of the traffic's and some blog posts have been written which demonstrate using the HTTP port in such a way that a portion of the traffic's
path is unencrypted. **Anything which exposes the non-TLS HTTP port outside the Pod should be considered deprecated**. path is unencrypted. Newer versions of the Supervisor disable the HTTP port by default to make it more clear that
A future version of the Supervisor app may include a breaking change to adjust the default behavior of the HTTP port the Supervisor app is not intended to receive non-TLS HTTP traffic from outside the Pod. Furthermore, in these newer versions,
to only listen on 127.0.0.1 (or perhaps even to be disabled) to make it more clear that the Supervisor app is not intended when the HTTP listener is configured to be enabled it may only listen on loopback interfaces for traffic from within its own pod.
to receive non-TLS HTTP traffic from outside the Pod. To aid in transition for impacted users, the old behavior of allowing the HTTP listener to receive traffic from
outside the pod may be re-enabled using the
`deprecated_insecure_accept_external_unencrypted_http_requests` value in
[values.yaml](https://github.com/vmware-tanzu/pinniped/blob/main/deploy/supervisor/values.yaml),
until that setting is removed in a future release.
Because there are many ways to expose TLS services from a Kubernetes cluster, the Supervisor app leaves this up to the user. Because there are many ways to expose TLS services from a Kubernetes cluster, the Supervisor app leaves this up to the user.
The most common ways are: The most common ways are:
@ -54,12 +58,9 @@ The most common ways are:
traffic using TLS on the backend (upstream) into the Supervisor's Pods. It would not be secure for the OIDC protocol traffic using TLS on the backend (upstream) into the Supervisor's Pods. It would not be secure for the OIDC protocol
to use HTTP, because the user's secret OIDC tokens would be transmitted across the network without encryption. to use HTTP, because the user's secret OIDC tokens would be transmitted across the network without encryption.
If your Ingress controller does not support this feature, then consider using one of the other configurations If your Ingress controller does not support this feature, then consider using one of the other configurations
described here instead of using an Ingress. The backend of the Ingress would typically point to a NodePort or described here instead of using an Ingress. (Please refer to the paragraph above regarding the deprecation of the HTTP listener for more
LoadBalancer Service which exposes the HTTPS port 8443 of the Supervisor pods. (Using plain HTTP from the Ingress information.) The backend of the Ingress would typically point to a NodePort or LoadBalancer Service which exposes
to the Supervisor pods is deprecated and will be removed in a future release. To aid in transition, it may be enabled the HTTPS port 8443 of the Supervisor pods.
using the `insecure_accept_external_unencrypted_http_requests` value in
[values.yaml](https://github.com/vmware-tanzu/pinniped/blob/main/deploy/supervisor/values.yaml),
until that setting is removed in a future release.)
The required configuration of the Ingress is specific to your cluster's Ingress Controller, so please refer to the The required configuration of the Ingress is specific to your cluster's Ingress Controller, so please refer to the
documentation from your Kubernetes provider. If you are using a cluster from a cloud provider, then you'll probably documentation from your Kubernetes provider. If you are using a cluster from a cloud provider, then you'll probably
@ -81,24 +82,25 @@ The most common ways are:
If your service mesh is capable of transparently encrypting traffic all the way into the If your service mesh is capable of transparently encrypting traffic all the way into the
Supervisor Pods, then you should use that capability. In this case, it may make sense to configure the Supervisor's Supervisor Pods, then you should use that capability. In this case, it may make sense to configure the Supervisor's
HTTP port to only listen on a Unix domain socket, such as when the service mesh injects a sidecar container that can HTTP port to listen on a Unix domain socket, such as when the service mesh injects a sidecar container that can
securely access the socket from within the same Pod. securely access the socket from within the same Pod. Alternatively, the HTTP port can be configured as a TCP listener
on loopback interfaces to receive traffic from sidecar containers.
See the `endpoints` option in [deploy/supervisor/values.yml](https://github.com/vmware-tanzu/pinniped/blob/main/deploy/supervisor/values.yaml) See the `endpoints` option in [deploy/supervisor/values.yml](https://github.com/vmware-tanzu/pinniped/blob/main/deploy/supervisor/values.yaml)
for more information. for more information.
This would prevent any unencrypted traffic from accidentally being transmitted from outside the Pod into the Using either a Unix domain socket or a loopback interface listener would prevent any unencrypted traffic from
Supervisor app's HTTP port. accidentally being transmitted from outside the Pod into the Supervisor app's HTTP port.
For example, the following high level steps cover configuring Istio for use with the Supervisor: For example, the following high level steps cover configuring Istio for use with the Supervisor:
- Update the http listener to use a Unix domain socket - Update the HTTP listener to use a Unix domain socket
i.e. `--data-value-yaml 'endpoints={"http":{"network":"unix","address":"/pinniped_socket/socketfile.sock"}}'` i.e. `--data-value-yaml 'endpoints={"http":{"network":"unix","address":"/pinniped_socket/socketfile.sock"}}'`
- Arrange for the Istio sidecar to be injected into the Supervisor app with an appropriate `IstioIngressListener` - Arrange for the Istio sidecar to be injected into the Supervisor app with an appropriate `IstioIngressListener`
i.e `defaultEndpoint: unix:///pinniped_socket/socketfile.sock` i.e `defaultEndpoint: unix:///pinniped_socket/socketfile.sock`
- Mount the socket volume into the Istio sidecar container by including the appropriate annotation on the Supervisor pods - Mount the socket volume into the Istio sidecar container by including the appropriate annotation on the Supervisor pods
i.e. `sidecar.istio.io/userVolumeMount: '{"socket":{"mountPath":"/pinniped_socket"}}'` i.e. `sidecar.istio.io/userVolumeMount: '{"socket":{"mountPath":"/pinniped_socket"}}'`
- Disable the https listener and update the deployment health checks as desired - Disable the HTTPS listener and update the deployment health checks as desired
For service meshes that do not support Unix domain sockets, the http listener should be configured to listen on 127.0.0.1. For service meshes that do not support Unix domain sockets, the HTTP listener should be configured as a TCP listener on a loopback interface.
## Creating a Service to expose the Supervisor app's endpoints within the cluster ## Creating a Service to expose the Supervisor app's endpoints within the cluster