don't remove user's ability to configure http port to listen on loopback

This commit is contained in:
Ryan Richard
2024-05-01 12:27:26 -07:00
parent 460fbbacc7
commit ad7df9f7d1
10 changed files with 226 additions and 242 deletions

View File

@@ -98,9 +98,7 @@ data:
(@ end @)
(@ if data.values.log_level: @)
log:
(@ if data.values.log_level: @)
level: (@= getAndValidateLogLevel() @)
(@ end @)
(@ end @)
---
#@ if data.values.image_pull_dockerconfigjson and data.values.image_pull_dockerconfigjson != "":

View File

@@ -156,28 +156,39 @@ https_proxy: ""
no_proxy: "$(KUBERNETES_SERVICE_HOST),169.254.169.254,127.0.0.1,localhost,.svc,.cluster.local"
#@schema/title "Endpoints"
#@ endpoints_desc = "Control the HTTPS listener of the Supervisor. The current defaults are: \
#@ {\"https\":{\"network\":\"tcp\",\"address\":\":8443\"}}. \
#@ These defaults mean: Tor HTTPS listening, bind to all interfaces using TCP on port 8443. \
#@ endpoints_desc = "Control the HTTP and HTTPS listeners of the Supervisor. The current defaults are: \
#@ {\"https\":{\"network\":\"tcp\",\"address\":\":8443\"},\"http\":\"disabled\"}. \
#@ These defaults mean: 1.) for HTTPS listening, bind to all interfaces using TCP on port 8443 and \
#@ 2.) disable HTTP listening by default. \
#@ The schema of this config is as follows: \
#@ {\"https\":{\"network\":\"tcp\",\"address\":\"{host}:{port}\"}} \
#@ The HTTPS listener must be used to accept all traffic from outside the pod. \
#@ Ingresses and load balancers that terminate TLS connections must re-encrypt the data and route traffic \
#@ to the HTTPS listener, or provide TLS passthrough. \
#@ {\"https\":{\"network\":\"tcp | unix | disabled\",\"address\":\"host:port when network=tcp or /pinniped_socket/socketfile.sock when network=unix\"},\"http\":{\"network\":\"tcp | unix | disabled\",\"address\":\"same as https, except that when network=tcp then the address is only allowed to bind to loopback interfaces\"}} \
#@ The HTTP listener can only be bound to loopback interfaces. This allows the listener to accept \
#@ traffic from within the pod, e.g. from a service mesh sidecar. The HTTP listener should not be \
#@ used to accept traffic from outside the pod, since that would mean that the network traffic could be \
#@ transmitted unencrypted. The HTTPS listener should be used instead to accept traffic from outside the pod. \
#@ Ingresses and load balancers that terminate TLS connections should re-encrypt the data and route traffic \
#@ to the HTTPS listener. Unix domain sockets may also be used for integrations with service meshes. \
#@ Changing the HTTPS port number must be accompanied by matching changes to the service and deployment \
#@ manifests. Changes to the HTTPS listener must be coordinated with the deployment health checks."
#@schema/desc endpoints_desc
#@schema/examples ("Example matching default settings", '{"https":{"network":"tcp","address":":8443"}}')
#@schema/examples ("Example matching default settings", '{"https":{"network":"tcp","address":":8443"},"http":"disabled"}')
#@schema/type any=True
#@ def validate_endpoint(endpoint):
#@ if (type(endpoint) != "yamlfragment"):
#@ if(type(endpoint) not in ["yamlfragment", "string"]):
#@ return False
#@ end
#@ if (endpoint["network"] != "tcp"):
#@ return False
#@ if(type(endpoint) in ["string"]):
#@ if (endpoint != "disabled"):
#@ return False
#@ end
#@ end
#@ if (type(endpoint["address"]) != "string"):
#@ return False
#@ if(type(endpoint) in ["yamlfragment"]):
#@ if (endpoint["network"] not in ["tcp", "unix", "disabled"]):
#@ return False
#@ end
#@ if (type(endpoint["address"]) not in ["string"]):
#@ return False
#@ end
#@ end
#@ return True
#@ end
@@ -185,11 +196,10 @@ no_proxy: "$(KUBERNETES_SERVICE_HOST),169.254.169.254,127.0.0.1,localhost,.svc,.
#@ """
#@ Returns True if endpoints fulfill the expected structure
#@ """
#@ if (type(endpoints) != "yamlfragment"):
#@ return False
#@ end
#@ return validate_endpoint(endpoints["https"])
#@ http_val = endpoints["http"]
#@ https_val = endpoints["https"]
#@ return validate_endpoint(http_val) and validate_endpoint(https_val)
#@ end
#@schema/nullable
#@schema/validation ("a map with key 'https', whose values are either the string 'disabled' or a map having keys 'network' and 'address', and the value of 'network' must be one of the allowed values", validate_endpoints)
#@schema/validation ("a map with keys 'http' and 'https', whose values are either the string 'disabled' or a map having keys 'network' and 'address', and the value of 'network' must be one of the allowed values", validate_endpoints)
endpoints: { }

View File

@@ -101,87 +101,7 @@ func TestFromPath(t *testing.T) {
},
},
{
name: "Fully filled out new log struct",
yaml: here.Doc(`
---
discovery:
url: https://some.discovery/url
api:
servingCertificate:
durationSeconds: 3600
renewBeforeSeconds: 2400
apiGroupSuffix: some.suffix.com
aggregatedAPIServerPort: 12345
impersonationProxyServerPort: 4242
names:
servingCertificateSecret: pinniped-concierge-api-tls-serving-certificate
credentialIssuer: pinniped-config
apiService: pinniped-api
kubeCertAgentPrefix: kube-cert-agent-prefix
impersonationLoadBalancerService: impersonationLoadBalancerService-value
impersonationClusterIPService: impersonationClusterIPService-value
impersonationTLSCertificateSecret: impersonationTLSCertificateSecret-value
impersonationCACertificateSecret: impersonationCACertificateSecret-value
impersonationSignerSecret: impersonationSignerSecret-value
impersonationSignerSecret: impersonationSignerSecret-value
agentServiceAccount: agentServiceAccount-value
impersonationProxyServiceAccount: impersonationProxyServiceAccount-value
impersonationProxyLegacySecret: impersonationProxyLegacySecret-value
extraName: extraName-value
labels:
myLabelKey1: myLabelValue1
myLabelKey2: myLabelValue2
kubeCertAgent:
namePrefix: kube-cert-agent-name-prefix-
image: kube-cert-agent-image
imagePullSecrets: [kube-cert-agent-image-pull-secret]
log:
level: all
format: json
`),
wantConfig: &Config{
DiscoveryInfo: DiscoveryInfoSpec{
URL: ptr.To("https://some.discovery/url"),
},
APIConfig: APIConfigSpec{
ServingCertificateConfig: ServingCertificateConfigSpec{
DurationSeconds: ptr.To[int64](3600),
RenewBeforeSeconds: ptr.To[int64](2400),
},
},
APIGroupSuffix: ptr.To("some.suffix.com"),
AggregatedAPIServerPort: ptr.To[int64](12345),
ImpersonationProxyServerPort: ptr.To[int64](4242),
NamesConfig: NamesConfigSpec{
ServingCertificateSecret: "pinniped-concierge-api-tls-serving-certificate",
CredentialIssuer: "pinniped-config",
APIService: "pinniped-api",
ImpersonationLoadBalancerService: "impersonationLoadBalancerService-value",
ImpersonationClusterIPService: "impersonationClusterIPService-value",
ImpersonationTLSCertificateSecret: "impersonationTLSCertificateSecret-value",
ImpersonationCACertificateSecret: "impersonationCACertificateSecret-value",
ImpersonationSignerSecret: "impersonationSignerSecret-value",
AgentServiceAccount: "agentServiceAccount-value",
ImpersonationProxyServiceAccount: "impersonationProxyServiceAccount-value",
ImpersonationProxyLegacySecret: "impersonationProxyLegacySecret-value",
},
Labels: map[string]string{
"myLabelKey1": "myLabelValue1",
"myLabelKey2": "myLabelValue2",
},
KubeCertAgentConfig: KubeCertAgentSpec{
NamePrefix: ptr.To("kube-cert-agent-name-prefix-"),
Image: ptr.To("kube-cert-agent-image"),
ImagePullSecrets: []string{"kube-cert-agent-image-pull-secret"},
},
Log: plog.LogSpec{
Level: plog.LevelAll,
Format: plog.FormatJSON,
},
},
},
{
name: "Fully filled out old log and new log struct",
name: "fully filled out including log format",
yaml: here.Doc(`
---
discovery:
@@ -279,7 +199,28 @@ func TestFromPath(t *testing.T) {
level: all
format: snorlax
`),
wantError: "decode yaml: error unmarshaling JSON: while decoding JSON: invalid log format, valid choices are the empty string, json and text",
wantError: "decode yaml: error unmarshaling JSON: while decoding JSON: invalid log format, valid choices are the empty string or 'json'",
},
{
name: "cli is a bad log format when configured by the user",
yaml: here.Doc(`
---
names:
servingCertificateSecret: pinniped-concierge-api-tls-serving-certificate
credentialIssuer: pinniped-config
apiService: pinniped-api
impersonationLoadBalancerService: impersonationLoadBalancerService-value
impersonationClusterIPService: impersonationClusterIPService-value
impersonationTLSCertificateSecret: impersonationTLSCertificateSecret-value
impersonationCACertificateSecret: impersonationCACertificateSecret-value
impersonationSignerSecret: impersonationSignerSecret-value
agentServiceAccount: agentServiceAccount-value
impersonationProxyServiceAccount: impersonationProxyServiceAccount-value
log:
level: all
format: cli
`),
wantError: "decode yaml: error unmarshaling JSON: while decoding JSON: invalid log format, valid choices are the empty string or 'json'",
},
{
name: "When only the required fields are present, causes other fields to be defaulted",

View File

@@ -79,10 +79,23 @@ func FromPath(ctx context.Context, path string) (*Config, error) {
Network: NetworkTCP,
Address: ":8443",
})
maybeSetEndpointDefault(&config.Endpoints.HTTP, Endpoint{
Network: NetworkDisabled,
})
if err := validateEndpoint(*config.Endpoints.HTTPS); err != nil {
return nil, fmt.Errorf("validate https endpoint: %w", err)
}
if err := validateEndpoint(*config.Endpoints.HTTP); err != nil {
return nil, fmt.Errorf("validate http endpoint: %w", err)
}
if err := validateAdditionalHTTPEndpointRequirements(*config.Endpoints.HTTP); err != nil {
return nil, fmt.Errorf("validate http endpoint: %w", err)
}
if err := validateAtLeastOneEnabledEndpoint(*config.Endpoints.HTTPS, *config.Endpoints.HTTP); err != nil {
return nil, fmt.Errorf("validate endpoints: %w", err)
}
return &config, nil
}
@@ -128,12 +141,34 @@ func validateEndpoint(endpoint Endpoint) error {
}
return nil
case NetworkDisabled:
return fmt.Errorf("must not be disabled")
if len(endpoint.Address) != 0 {
return fmt.Errorf("address set to %q when disabled, should be empty", endpoint.Address)
}
return nil
default:
return fmt.Errorf("unknown network %q", n)
}
}
func validateAdditionalHTTPEndpointRequirements(endpoint Endpoint) error {
if endpoint.Network == NetworkTCP && !addrIsOnlyOnLoopback(endpoint.Address) {
return fmt.Errorf(
"http listener address %q for %q network may only bind to loopback interfaces",
endpoint.Address,
endpoint.Network)
}
return nil
}
func validateAtLeastOneEnabledEndpoint(endpoints ...Endpoint) error {
for _, endpoint := range endpoints {
if endpoint.Network != NetworkDisabled {
return nil
}
}
return constable.Error("all endpoints are disabled")
}
// For tcp networks, the address can be in several formats: host:port, host:, and :port.
// See address description in https://pkg.go.dev/net#Listen and https://pkg.go.dev/net#Dial.
// The host may be a literal IP address, or a host name that can be resolved to IP addresses,

View File

@@ -24,7 +24,7 @@ func TestFromPath(t *testing.T) {
wantError string
}{
{
name: "Happy (with new log field)",
name: "Happy",
yaml: here.Doc(`
---
apiGroupSuffix: some.suffix.com
@@ -37,9 +37,13 @@ func TestFromPath(t *testing.T) {
https:
network: unix
address: :1234
http:
network: tcp
address: 127.0.0.1:1234
insecureAcceptExternalUnencryptedHttpRequests: false
log:
level: info
format: text
format: json
aggregatedAPIServerPort: 12345
`),
wantConfig: &Config{
@@ -56,16 +60,20 @@ func TestFromPath(t *testing.T) {
Network: "unix",
Address: ":1234",
},
HTTP: &Endpoint{
Network: "tcp",
Address: "127.0.0.1:1234",
},
},
Log: plog.LogSpec{
Level: plog.LevelInfo,
Format: plog.FormatText,
Format: plog.FormatJSON,
},
AggregatedAPIServerPort: ptr.To[int64](12345),
},
},
{
name: "bad log format",
name: "cli is a bad log format when configured by the user",
yaml: here.Doc(`
---
names:
@@ -74,7 +82,7 @@ func TestFromPath(t *testing.T) {
level: info
format: cli
`),
wantError: "decode yaml: error unmarshaling JSON: while decoding JSON: invalid log format, valid choices are the empty string, json and text",
wantError: "decode yaml: error unmarshaling JSON: while decoding JSON: invalid log format, valid choices are the empty string or 'json'",
},
{
name: "When only the required fields are present, causes other fields to be defaulted",
@@ -94,6 +102,9 @@ func TestFromPath(t *testing.T) {
Network: "tcp",
Address: ":8443",
},
HTTP: &Endpoint{
Network: "disabled",
},
},
AggregatedAPIServerPort: ptr.To[int64](10250),
},
@@ -107,8 +118,10 @@ func TestFromPath(t *testing.T) {
endpoints:
https:
network: disabled
http:
network: disabled
`),
wantError: "validate https endpoint: must not be disabled",
wantError: "validate endpoints: all endpoints are disabled",
},
{
name: "invalid https endpoint",
@@ -124,6 +137,50 @@ func TestFromPath(t *testing.T) {
`),
wantError: `validate https endpoint: unknown network "foo"`,
},
{
name: "invalid http endpoint",
yaml: here.Doc(`
---
names:
defaultTLSCertificateSecret: my-secret-name
endpoints:
https:
network: disabled
http:
network: bar
`),
wantError: `validate http endpoint: unknown network "bar"`,
},
{
name: "http endpoint uses tcp but binds to more than only loopback interfaces with insecureAcceptExternalUnencryptedHttpRequests missing",
yaml: here.Doc(`
---
names:
defaultTLSCertificateSecret: my-secret-name
endpoints:
https:
network: disabled
http:
network: tcp
address: :8080
`),
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",
yaml: here.Doc(`
---
names:
defaultTLSCertificateSecret: my-secret-name
endpoints:
https:
network: disabled
http:
network: tcp
address: :8080
`),
wantError: `validate http endpoint: http listener address ":8080" for "tcp" network may only bind to loopback interfaces`,
},
{
name: "endpoint disabled with non-empty address",
yaml: here.Doc(`
@@ -135,7 +192,7 @@ func TestFromPath(t *testing.T) {
network: disabled
address: wee
`),
wantError: `validate https endpoint: must not be disabled`,
wantError: `validate https endpoint: address set to "wee" when disabled, should be empty`,
},
{
name: "endpoint tcp with empty address",
@@ -144,10 +201,10 @@ func TestFromPath(t *testing.T) {
names:
defaultTLSCertificateSecret: my-secret-name
endpoints:
https:
http:
network: tcp
`),
wantError: `validate https endpoint: address must be set with "tcp" network`,
wantError: `validate http endpoint: address must be set with "tcp" network`,
},
{
name: "endpoint unix with empty address",

View File

@@ -25,6 +25,7 @@ type NamesConfigSpec struct {
type Endpoints struct {
HTTPS *Endpoint `json:"https,omitempty"`
HTTP *Endpoint `json:"http,omitempty"`
}
type Endpoint struct {

View File

@@ -22,8 +22,6 @@ func (l *LogFormat) UnmarshalJSON(b []byte) error {
switch string(b) {
case `""`, `"json"`:
*l = FormatJSON
case `"text"`:
*l = FormatText
// there is no "cli" case because it is not a supported option via our config
default:
return errInvalidLogFormat
@@ -33,11 +31,10 @@ func (l *LogFormat) UnmarshalJSON(b []byte) error {
const (
FormatJSON LogFormat = "json"
FormatText LogFormat = "text"
FormatCLI LogFormat = "cli" // only used by the pinniped CLI and not the server components
errInvalidLogLevel = constable.Error("invalid log level, valid choices are the empty string, info, debug, trace and all")
errInvalidLogFormat = constable.Error("invalid log format, valid choices are the empty string, json and text")
errInvalidLogFormat = constable.Error("invalid log format, valid choices are the empty string or 'json'")
)
var _ json.Unmarshaler = func() *LogFormat {
@@ -68,8 +65,6 @@ func ValidateAndSetLogLevelAndFormatGlobally(ctx context.Context, spec LogSpec)
encoding = "json"
case FormatCLI:
encoding = "console"
case FormatText:
encoding = "text"
default:
return errInvalidLogFormat
}
@@ -81,12 +76,8 @@ func ValidateAndSetLogLevelAndFormatGlobally(ctx context.Context, spec LogSpec)
setGlobalLoggers(log, flush)
//nolint:exhaustive // the switch above is exhaustive for format already
switch spec.Format {
case FormatCLI:
if spec.Format == FormatCLI {
return nil // do not spawn go routines on the CLI to allow the CLI to call this more than once
case FormatText:
Warning("setting log.format to 'text' is deprecated - this option will be removed in a future release")
}
// do spawn go routines on the server

View File

@@ -101,7 +101,7 @@ func TestFormat(t *testing.T) {
"timestamp": "2022-11-21T23:37:26.953313Z",
"caller": "%s/config_test.go:%d$plog.TestFormat.func1",
"message": "something happened",
"error": "invalid log format, valid choices are the empty string, json and text",
"error": "invalid log format, valid choices are the empty string or 'json'",
"an": "item"
}`, wd, getLineNumberOfCaller()-11), scanner.Text())
@@ -148,7 +148,7 @@ testing.tRunner
DebugErr("something happened", errInvalidLogFormat, "an", "item")
require.True(t, scanner.Scan())
require.NoError(t, scanner.Err())
require.Equal(t, fmt.Sprintf(nowStr+` plog/config_test.go:%d something happened {"error": "invalid log format, valid choices are the empty string, json and text", "an": "item"}`,
require.Equal(t, fmt.Sprintf(nowStr+` plog/config_test.go:%d something happened {"error": "invalid log format, valid choices are the empty string or 'json'", "an": "item"}`,
getLineNumberOfCaller()-4), scanner.Text())
Logr().WithName("burrito").Error(errInvalidLogLevel, "wee", "a", "b", "slightly less than a year", 363*24*time.Hour, "slightly more than 2 years", 2*367*24*time.Hour)
@@ -157,74 +157,6 @@ testing.tRunner
require.Equal(t, fmt.Sprintf(nowStr+` burrito plog/config_test.go:%d wee {"a": "b", "slightly less than a year": "363d", "slightly more than 2 years": "2y4d", "error": "invalid log level, valid choices are the empty string, info, debug, trace and all"}`,
getLineNumberOfCaller()-4), scanner.Text())
old := New().WithName("created before mode change").WithValues("is", "old")
err = ValidateAndSetLogLevelAndFormatGlobally(ctx, LogSpec{Level: LevelDebug, Format: FormatText})
require.NoError(t, err)
pid := os.Getpid()
// check for the deprecation warning
require.True(t, scanner.Scan())
require.NoError(t, scanner.Err())
require.Equal(t, fmt.Sprintf(`I1121 23:37:26.953313%8d config.go:89] "setting log.format to 'text' is deprecated - this option will be removed in a future release" warning=true`,
pid), scanner.Text())
Debug("what is happening", "does klog", "work?")
require.True(t, scanner.Scan())
require.NoError(t, scanner.Err())
require.Equal(t, fmt.Sprintf(`I1121 23:37:26.953313%8d config_test.go:%d] "what is happening" does klog="work?"`,
pid, getLineNumberOfCaller()-4), scanner.Text())
Logr().WithName("panda").V(KlogLevelDebug).Info("are the best", "yes?", "yes.")
require.True(t, scanner.Scan())
require.NoError(t, scanner.Err())
require.Equal(t, fmt.Sprintf(`I1121 23:37:26.953313%8d config_test.go:%d] "are the best" logger="panda" yes?="yes."`,
pid, getLineNumberOfCaller()-4), scanner.Text())
New().WithName("hi").WithName("there").WithValues("a", 1, "b", 2).Always("do it")
require.True(t, scanner.Scan())
require.NoError(t, scanner.Err())
require.Equal(t, fmt.Sprintf(`I1121 23:37:26.953313%8d config_test.go:%d] "do it" logger="hi.there" a=1 b=2`,
pid, getLineNumberOfCaller()-4), scanner.Text())
l := WithValues("x", 33, "z", 22)
l.Debug("what to do")
l.Debug("and why")
require.True(t, scanner.Scan())
require.NoError(t, scanner.Err())
require.Equal(t, fmt.Sprintf(`I1121 23:37:26.953313%8d config_test.go:%d] "what to do" x=33 z=22`,
pid, getLineNumberOfCaller()-5), scanner.Text())
require.True(t, scanner.Scan())
require.NoError(t, scanner.Err())
require.Equal(t, fmt.Sprintf(`I1121 23:37:26.953313%8d config_test.go:%d] "and why" x=33 z=22`,
pid, getLineNumberOfCaller()-8), scanner.Text())
old.Always("should be klog text format", "for", "sure")
require.True(t, scanner.Scan())
require.NoError(t, scanner.Err())
require.Equal(t, fmt.Sprintf(`I1121 23:37:26.953313%8d config_test.go:%d] "should be klog text format" logger="created before mode change" is="old" for="sure"`,
pid, getLineNumberOfCaller()-4), scanner.Text())
// make sure child loggers do not share state
old1 := old.WithValues("i am", "old1")
old2 := old.WithName("old2")
old1.Warning("warn")
old2.Info("info")
require.True(t, scanner.Scan())
require.NoError(t, scanner.Err())
require.Equal(t, fmt.Sprintf(`I1121 23:37:26.953313%8d config_test.go:%d] "warn" logger="created before mode change" is="old" i am="old1" warning=true`,
pid, getLineNumberOfCaller()-5), scanner.Text())
require.True(t, scanner.Scan())
require.NoError(t, scanner.Err())
require.Equal(t, fmt.Sprintf(`I1121 23:37:26.953313%8d config_test.go:%d] "info" logger="created before mode change.old2" is="old"`,
pid, getLineNumberOfCaller()-8), scanner.Text())
Trace("should not be logged", "for", "sure")
require.Empty(t, buf.String())
Logr().V(klogLevelAll).Info("also should not be logged", "open", "close")
require.Empty(t, buf.String())
require.False(t, scanner.Scan())
require.NoError(t, scanner.Err())
require.Empty(t, scanner.Text())

View File

@@ -508,60 +508,79 @@ func runSupervisor(ctx context.Context, podInfo *downward.PodInfo, cfg *supervis
return fmt.Errorf("could not create aggregated API server: %w", err)
}
finishSetupPerms := maybeSetupUnixPerms(cfg.Endpoints.HTTPS, supervisorPod)
if e := cfg.Endpoints.HTTP; e.Network != supervisor.NetworkDisabled {
finishSetupPerms := maybeSetupUnixPerms(e, supervisorPod)
bootstrapCert, err := getBootstrapCert() // generate this in-memory once per process startup
if err != nil {
return fmt.Errorf("https listener bootstrap error: %w", err)
}
c := ptls.Default(nil)
c.GetCertificate = func(info *tls.ClientHelloInfo) (*tls.Certificate, error) {
cert := dynamicTLSCertProvider.GetTLSCert(strings.ToLower(info.ServerName))
foundServerNameCert := cert != nil
defaultCert := dynamicTLSCertProvider.GetDefaultTLSCert()
if !foundServerNameCert {
cert = defaultCert
httpListener, err := net.Listen(e.Network, e.Address)
if err != nil {
return fmt.Errorf("cannot create http listener with network %q and address %q: %w", e.Network, e.Address, err)
}
// If we still don't have a cert for the request at this point, then using the bootstrapping cert,
// but in that case also set the request to fail unless it is a health check request.
usingBootstrapCert := false
if cert == nil {
usingBootstrapCert = true
setIsBootstrapConn(info.Context()) // make this connection only work for bootstrap requests
cert = bootstrapCert
if err := finishSetupPerms(); err != nil {
return fmt.Errorf("cannot setup http listener permissions for network %q and address %q: %w", e.Network, e.Address, err)
}
// Emit logs visible at a higher level of logging than the default. Using Info level so the user
// can safely configure a production Supervisor to show this message if they choose.
plog.Info("choosing TLS cert for incoming request",
"requestSNIServerName", info.ServerName,
"foundCertForSNIServerNameFromFederationDomain", foundServerNameCert,
"foundDefaultCertFromSecret", defaultCert != nil,
"defaultCertSecretName", cfg.NamesConfig.DefaultTLSCertificateSecret,
"servingBootstrapHealthzCert", usingBootstrapCert,
"requestLocalAddr", info.Conn.LocalAddr().String(),
"requestRemoteAddr", info.Conn.RemoteAddr().String(),
)
return cert, nil
defer func() { _ = httpListener.Close() }()
startServer(ctx, shutdown, httpListener, oidProvidersManager)
plog.Debug("supervisor http listener started", "address", httpListener.Addr().String())
}
httpsListener, err := tls.Listen(cfg.Endpoints.HTTPS.Network, cfg.Endpoints.HTTPS.Address, c)
if err != nil {
return fmt.Errorf("cannot create https listener with network %q and address %q: %w", cfg.Endpoints.HTTPS.Network, cfg.Endpoints.HTTPS.Address, err)
}
if e := cfg.Endpoints.HTTPS; e.Network != supervisor.NetworkDisabled { //nolint:nestif
finishSetupPerms := maybeSetupUnixPerms(e, supervisorPod)
if err := finishSetupPerms(); err != nil {
return fmt.Errorf("cannot setup https listener permissions for network %q and address %q: %w", cfg.Endpoints.HTTPS.Network, cfg.Endpoints.HTTPS.Address, err)
}
bootstrapCert, err := getBootstrapCert() // generate this in-memory once per process startup
if err != nil {
return fmt.Errorf("https listener bootstrap error: %w", err)
}
defer func() { _ = httpsListener.Close() }()
startServer(ctx, shutdown, httpsListener, oidProvidersManager)
plog.Debug("supervisor https listener started", "address", httpsListener.Addr().String())
c := ptls.Default(nil)
c.GetCertificate = func(info *tls.ClientHelloInfo) (*tls.Certificate, error) {
cert := dynamicTLSCertProvider.GetTLSCert(strings.ToLower(info.ServerName))
foundServerNameCert := cert != nil
defaultCert := dynamicTLSCertProvider.GetDefaultTLSCert()
if !foundServerNameCert {
cert = defaultCert
}
// If we still don't have a cert for the request at this point, then using the bootstrapping cert,
// but in that case also set the request to fail unless it is a health check request.
usingBootstrapCert := false
if cert == nil {
usingBootstrapCert = true
setIsBootstrapConn(info.Context()) // make this connection only work for bootstrap requests
cert = bootstrapCert
}
// Emit logs visible at a higher level of logging than the default. Using Info level so the user
// can safely configure a production Supervisor to show this message if they choose.
plog.Info("choosing TLS cert for incoming request",
"requestSNIServerName", info.ServerName,
"foundCertForSNIServerNameFromFederationDomain", foundServerNameCert,
"foundDefaultCertFromSecret", defaultCert != nil,
"defaultCertSecretName", cfg.NamesConfig.DefaultTLSCertificateSecret,
"servingBootstrapHealthzCert", usingBootstrapCert,
"requestLocalAddr", info.Conn.LocalAddr().String(),
"requestRemoteAddr", info.Conn.RemoteAddr().String(),
)
return cert, nil
}
httpsListener, err := tls.Listen(e.Network, e.Address, c)
if err != nil {
return fmt.Errorf("cannot create https listener with network %q and address %q: %w", e.Network, e.Address, err)
}
if err := finishSetupPerms(); err != nil {
return fmt.Errorf("cannot setup https listener permissions for network %q and address %q: %w", e.Network, e.Address, err)
}
defer func() { _ = httpsListener.Close() }()
startServer(ctx, shutdown, httpsListener, oidProvidersManager)
plog.Debug("supervisor https listener started", "address", httpsListener.Addr().String())
}
plog.Debug("supervisor started")
defer plog.Debug("supervisor exiting")

View File

@@ -54,15 +54,15 @@ ingress and TLS configuration. In that case, please refer to the documentation f
## Exposing the Supervisor app's endpoints outside the cluster
The Supervisor app's endpoints must be exposed as HTTPS endpoints with proper TLS certificates signed by a
The Supervisor app's endpoints should be exposed as HTTPS endpoints with proper TLS certificates signed by a
certificate authority (CA) which is trusted by your end user's web browsers.
Furthermore, all traffic to Supervisor endpoints must be encrypted via TLS all the way into the
It is recommended that the traffic to these endpoints should be encrypted via TLS all the way into the
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.
Previous versions of the Supervisor app supported both HTTP and HTTPS ports. Starting with Pinniped v0.30.0,
HTTP ports are no longer allowed.
The Supervisor only listens on an HTTPS port by default. Incoming traffic must use TLS. The only exception is for
an advanced configuration style using a service mesh to deliver traffic into the Supervisor (discussed below).
Because there are many ways to expose TLS services from a Kubernetes cluster, the Supervisor app leaves this up to the user.
Some common approaches are: