mirror of
https://github.com/vmware-tanzu/pinniped.git
synced 2026-01-05 04:56:11 +00:00
don't remove user's ability to configure http port to listen on loopback
This commit is contained in:
@@ -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 != "":
|
||||
|
||||
@@ -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: { }
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -25,6 +25,7 @@ type NamesConfigSpec struct {
|
||||
|
||||
type Endpoints struct {
|
||||
HTTPS *Endpoint `json:"https,omitempty"`
|
||||
HTTP *Endpoint `json:"http,omitempty"`
|
||||
}
|
||||
|
||||
type Endpoint struct {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user