Skip to content

Commit 471f0d3

Browse files
committed
NE-310 Implement route admission support for HSTS header
1 parent ae79113 commit 471f0d3

File tree

5 files changed

+263
-3
lines changed

5 files changed

+263
-3
lines changed

config/v1/0000_10_config-operator_01_ingress.crd.yaml

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,73 @@ spec:
7777
domain:
7878
description: "domain is used to generate a default host name for a route when the route's host name is empty. The generated host name will follow this pattern: \"<route-name>.<route-namespace>.<domain>\". \n It is also used as the default wildcard domain suffix for ingress. The default ingresscontroller domain will follow this pattern: \"*.<domain>\". \n Once set, changing domain is not currently supported."
7979
type: string
80+
requiredHSTSPolicies:
81+
description: "requiredHSTSPolicies specifies HSTS policies that are required to be set on newly created or updated routes matching the domainPattern/s and namespaceSelector/s that are specified in the policy. Each requiredHSTSPolicy must have at least a domainPattern and a maxAge to validate a route HSTS Policy route annotation, and affect route admission. \n A candidate route is checked for HSTS Policies if it has the HSTS Policy route annotation: \"haproxy.router.openshift.io/hsts_header\" E.g. haproxy.router.openshift.io/hsts_header: max-age=31536000;preload;includeSubDomains \n - For each candidate route, if it matches a requiredHSTSPolicy domainPattern and optional namespaceSelector, then the maxAge, preloadPolicy, and includeSubdomainsPolicy must be valid to be admitted. Otherwise, the route is rejected. - The first match, by domainPattern and optional namespaceSelector, in the ordering of the RequiredHSTSPolicies determines the route's admission status. - If the candidate route doesn't match any requiredHSTSPolicy domainPattern and optional namespaceSelector, then it may use any HSTS Policy annotation. \n The HSTSRequiredPolicies may be changed over time and can therefore reject a previously admitted route if it has any invalidating changes after the HSTSRequiredPolicies are changed. \n Note that if there are no RequiredHSTSPolicies, any HSTS Policy annotation on the route is valid."
82+
type: array
83+
items:
84+
type: object
85+
properties:
86+
domainPatterns:
87+
description: "domainPatterns is a list of domains for which the desired HSTS annotations are required. If domainPatterns is specified and a route is created with a spec.host matching one of the domains, the route must specify the HSTS Policy components described in the matching RequiredHSTSPolicy. \n The use of wildcards is allowed like this: *.foo.com matches everything under foo.com. foo.com only matches foo.com, so to cover foo.com and everything under it, you must specify *both*. kubebuilder:validation:MinLength=1"
88+
type: array
89+
items:
90+
type: string
91+
includeSubDomainsPolicy:
92+
description: 'includeSubDomainsPolicy means the HSTS Policy should apply to any subdomains of the host''s domain name. Thus, for the host bar.foo.com, if includeSubDomainsPolicy was set to RequireIncludeSubDomains: - the host app.bar.foo.com would inherit the HSTS Policy of bar.foo.com - the host bar.foo.com would inherit the HSTS Policy of bar.foo.com - the host foo.com would NOT inherit the HSTS Policy of bar.foo.com - the host def.foo.com would NOT inherit the HSTS Policy of bar.foo.com'
93+
type: string
94+
enum:
95+
- RequireIncludeSubDomains
96+
- RequireNotIncludeSubDomains
97+
- NoOpinion
98+
maxAge:
99+
description: maxAge is the delta time range in seconds during which hosts are regarded as HSTS hosts. If set to 0, it negates the effect, and hosts are removed as HSTS hosts. If set to 0 and includeSubdomains is specified, all subdomains of the host are also removed as HSTS hosts. maxAge is a time-to-live value, and if this policy is not refreshed on a client, the HSTS policy will eventually expire on that client.
100+
type: object
101+
properties:
102+
largestMaxAge:
103+
description: The largest allowed value (in seconds) of the RequiredHSTSPolicy max-age This value can be left unspecified, in which case no upper limit is enforced. kubebuilder:validation:minimum=0:maximum=2147483647
104+
type: integer
105+
format: int32
106+
smallestMaxAge:
107+
description: The smallest allowed value (in seconds) of the RequiredHSTSPolicy max-age Setting max-age=0 allows the deletion of an existing HSTS header from a host. This is a necessary tool for administrators to quickly correct mistakes. This value can be left unspecified, in which case no lower limit is enforced. kubebuilder:validation:minimum=0:maximum=2147483647
108+
type: integer
109+
format: int32
110+
namespaceSelector:
111+
description: namespaceSelector specifies a label selector such that the policy applies only to those routes that are in namespaces with labels that match the selector, and are in one of the DomainPatterns. Defaults to the empty LabelSelector, which matches everything.
112+
type: object
113+
properties:
114+
matchExpressions:
115+
description: matchExpressions is a list of label selector requirements. The requirements are ANDed.
116+
type: array
117+
items:
118+
description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values.
119+
type: object
120+
required:
121+
- key
122+
- operator
123+
properties:
124+
key:
125+
description: key is the label key that the selector applies to.
126+
type: string
127+
operator:
128+
description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist.
129+
type: string
130+
values:
131+
description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch.
132+
type: array
133+
items:
134+
type: string
135+
matchLabels:
136+
description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed.
137+
type: object
138+
additionalProperties:
139+
type: string
140+
preloadPolicy:
141+
description: preloadPolicy directs the client to include hosts in its host preload list so that it never needs to do an initial load to get the HSTS header (note that this is not defined in RFC 6797 and is therefore client implementation-dependent).
142+
type: string
143+
enum:
144+
- RequirePreload
145+
- RequireNotPreload
146+
- NoOpinion
80147
status:
81148
description: status holds observed values from the cluster. They may not be overridden.
82149
type: object

config/v1/types.go

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -310,3 +310,89 @@ type DelegatedAuthorization struct {
310310
// disabled indicates that authorization should be disabled. By default it will use delegated authorization.
311311
Disabled bool `json:"disabled,omitempty"`
312312
}
313+
type RequiredHSTSPolicy struct {
314+
// namespaceSelector specifies a label selector such that the policy applies only to those routes that
315+
// are in namespaces with labels that match the selector, and are in one of the DomainPatterns.
316+
// Defaults to the empty LabelSelector, which matches everything.
317+
// +optional
318+
NamespaceSelector *metav1.LabelSelector `json:"namespaceSelector,omitempty"`
319+
320+
// domainPatterns is a list of domains for which the desired HSTS annotations are required.
321+
// If domainPatterns is specified and a route is created with a spec.host matching one of the domains,
322+
// the route must specify the HSTS Policy components described in the matching RequiredHSTSPolicy.
323+
//
324+
// The use of wildcards is allowed like this: *.foo.com matches everything under foo.com.
325+
// foo.com only matches foo.com, so to cover foo.com and everything under it, you must specify *both*.
326+
// kubebuilder:validation:MinLength=1
327+
// +required
328+
DomainPatterns []string `json:"domainPatterns"`
329+
330+
// maxAge is the delta time range in seconds during which hosts are regarded as HSTS hosts.
331+
// If set to 0, it negates the effect, and hosts are removed as HSTS hosts.
332+
// If set to 0 and includeSubdomains is specified, all subdomains of the host are also removed as HSTS hosts.
333+
// maxAge is a time-to-live value, and if this policy is not refreshed on a client, the HSTS
334+
// policy will eventually expire on that client.
335+
// +required
336+
MaxAge MaxAgePolicy `json:"maxAge"`
337+
338+
// preloadPolicy directs the client to include hosts in its host preload list so that
339+
// it never needs to do an initial load to get the HSTS header (note that this is not defined
340+
// in RFC 6797 and is therefore client implementation-dependent).
341+
// +optional
342+
PreloadPolicy PreloadPolicy `json:"preloadPolicy,omitempty"`
343+
344+
// includeSubDomainsPolicy means the HSTS Policy should apply to any subdomains of the host's
345+
// domain name. Thus, for the host bar.foo.com, if includeSubDomainsPolicy was set to RequireIncludeSubDomains:
346+
// - the host app.bar.foo.com would inherit the HSTS Policy of bar.foo.com
347+
// - the host bar.foo.com would inherit the HSTS Policy of bar.foo.com
348+
// - the host foo.com would NOT inherit the HSTS Policy of bar.foo.com
349+
// - the host def.foo.com would NOT inherit the HSTS Policy of bar.foo.com
350+
// +optional
351+
IncludeSubDomainsPolicy IncludeSubDomainsPolicy `json:"includeSubDomainsPolicy,omitempty"`
352+
}
353+
354+
// MaxAgePolicy contains a numeric range for specifying a compliant HSTS max-age for the enclosing RequiredHSTSPolicy
355+
type MaxAgePolicy struct {
356+
// The largest allowed value (in seconds) of the RequiredHSTSPolicy max-age
357+
// This value can be left unspecified, in which case no upper limit is enforced.
358+
// kubebuilder:validation:minimum=0:maximum=2147483647
359+
LargestMaxAge *int32 `json:"largestMaxAge,omitempty"`
360+
361+
// The smallest allowed value (in seconds) of the RequiredHSTSPolicy max-age
362+
// Setting max-age=0 allows the deletion of an existing HSTS header from a host. This is a necessary
363+
// tool for administrators to quickly correct mistakes.
364+
// This value can be left unspecified, in which case no lower limit is enforced.
365+
// kubebuilder:validation:minimum=0:maximum=2147483647
366+
SmallestMaxAge *int32 `json:"smallestMaxAge,omitempty"`
367+
}
368+
369+
// PreloadPolicy contains a value for specifying a compliant HSTS preload policy for the enclosing RequiredHSTSPolicy
370+
// +kubebuilder:validation:Enum=RequirePreload;RequireNotPreload;NoOpinion
371+
type PreloadPolicy string
372+
373+
const (
374+
// RequirePreloadPolicy means HSTS "preload" is required by the RequiredHSTSPolicy
375+
RequirePreloadPolicy PreloadPolicy = "RequirePreload"
376+
377+
// RequireNoPreloadPolicy means HSTS "preload" is forbidden by the RequiredHSTSPolicy
378+
RequireNoPreloadPolicy PreloadPolicy = "RequireNoPreload"
379+
380+
// NoOpinionPreloadPolicy means HSTS "preload" doesn't matter to the RequiredHSTSPolicy
381+
NoOpinionPreloadPolicy PreloadPolicy = "NoOpinion"
382+
)
383+
384+
// IncludeSubDomainsPolicy contains a value for specifying a compliant HSTS includeSubdomains policy
385+
// for the enclosing RequiredHSTSPolicy
386+
// +kubebuilder:validation:Enum=RequireIncludeSubDomains;RequireNotIncludeSubDomains;NoOpinion
387+
type IncludeSubDomainsPolicy string
388+
389+
const (
390+
// RequireIncludeSubDomains means HSTS "includeSubDomains" is required by the RequiredHSTSPolicy
391+
RequireIncludeSubDomains IncludeSubDomainsPolicy = "RequireIncludeSubDomains"
392+
393+
// RequireNoIncludeSubDomains means HSTS "includeSubDomains" is forbidden by the RequiredHSTSPolicy
394+
RequireNoIncludeSubDomains IncludeSubDomainsPolicy = "RequireNoIncludeSubDomains"
395+
396+
// NoOpinionIncludeSubDomains means HSTS "includeSubDomains" doesn't matter to the RequiredHSTSPolicy
397+
NoOpinionIncludeSubDomains IncludeSubDomainsPolicy = "NoOpinion"
398+
)

config/v1/types_ingress.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,30 @@ type IngressSpec struct {
5454
// configurable routes.
5555
// +optional
5656
ComponentRoutes []ComponentRouteSpec `json:"componentRoutes,omitempty"`
57+
58+
// requiredHSTSPolicies specifies HSTS policies that are required to be set on newly created or updated routes
59+
// matching the domainPattern/s and namespaceSelector/s that are specified in the policy.
60+
// Each requiredHSTSPolicy must have at least a domainPattern and a maxAge to validate a route HSTS Policy route
61+
// annotation, and affect route admission.
62+
//
63+
// A candidate route is checked for HSTS Policies if it has the HSTS Policy route annotation:
64+
// "haproxy.router.openshift.io/hsts_header"
65+
// E.g. haproxy.router.openshift.io/hsts_header: max-age=31536000;preload;includeSubDomains
66+
//
67+
// - For each candidate route, if it matches a requiredHSTSPolicy domainPattern and optional namespaceSelector,
68+
// then the maxAge, preloadPolicy, and includeSubdomainsPolicy must be valid to be admitted. Otherwise, the route
69+
// is rejected.
70+
// - The first match, by domainPattern and optional namespaceSelector, in the ordering of the RequiredHSTSPolicies
71+
// determines the route's admission status.
72+
// - If the candidate route doesn't match any requiredHSTSPolicy domainPattern and optional namespaceSelector,
73+
// then it may use any HSTS Policy annotation.
74+
//
75+
// The HSTSRequiredPolicies may be changed over time and can therefore reject a previously admitted route if it
76+
// has any invalidating changes after the HSTSRequiredPolicies are changed.
77+
//
78+
// Note that if there are no RequiredHSTSPolicies, any HSTS Policy annotation on the route is valid.
79+
// +optional
80+
RequiredHSTSPolicies []RequiredHSTSPolicy `json:"requiredHSTSPolicies,omitempty"`
5781
}
5882

5983
// ConsumingUser is an alias for string which we add validation to. Currently only service accounts are supported.

config/v1/zz_generated.deepcopy.go

Lines changed: 60 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)