Add support for HTTP Strict Transport Security
Changes needed in order to use new HSTS support in Octavia. Partial-Bug: #2017972 Change-Id: I61882c844424a768d70b758e22d2aac979e3e3c6
This commit is contained in:
parent
98beca9d67
commit
15a8d74124
|
@ -82,7 +82,11 @@ LISTENER_ROWS = (
|
||||||
'tls_ciphers',
|
'tls_ciphers',
|
||||||
'tls_versions',
|
'tls_versions',
|
||||||
'alpn_protocols',
|
'alpn_protocols',
|
||||||
'tags')
|
'tags',
|
||||||
|
'hsts_max_age',
|
||||||
|
'hsts_include_subdomains',
|
||||||
|
'hsts_preload',
|
||||||
|
)
|
||||||
|
|
||||||
LISTENER_COLUMNS = (
|
LISTENER_COLUMNS = (
|
||||||
'id',
|
'id',
|
||||||
|
|
|
@ -13,7 +13,7 @@
|
||||||
#
|
#
|
||||||
|
|
||||||
"""Listener action implementation"""
|
"""Listener action implementation"""
|
||||||
|
import argparse
|
||||||
|
|
||||||
from cliff import lister
|
from cliff import lister
|
||||||
from osc_lib.command import command
|
from osc_lib.command import command
|
||||||
|
@ -201,6 +201,32 @@ class CreateListener(command.ShowOne):
|
||||||
help="Set the ALPN protocol to be used "
|
help="Set the ALPN protocol to be used "
|
||||||
"by the listener (can be set multiple times)."
|
"by the listener (can be set multiple times)."
|
||||||
)
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
'--hsts-max-age',
|
||||||
|
dest='hsts_max_age',
|
||||||
|
metavar='<hsts_max_age>',
|
||||||
|
type=int,
|
||||||
|
help="The value of the max_age directive for the "
|
||||||
|
"Strict-Transport-Security HTTP response header. "
|
||||||
|
"Setting this enables HTTP Strict Transport "
|
||||||
|
"Security (HSTS) for the TLS-terminated listener."
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
'--hsts-include-subdomains',
|
||||||
|
action='store_true',
|
||||||
|
dest='hsts_include_subdomains',
|
||||||
|
help="Define whether the includeSubDomains directive should be "
|
||||||
|
"added to the Strict-Transport-Security HTTP response "
|
||||||
|
"header."
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
'--hsts-preload',
|
||||||
|
action='store_true',
|
||||||
|
dest='hsts_preload',
|
||||||
|
help="Define whether the preload directive should be "
|
||||||
|
"added to the Strict-Transport-Security HTTP response "
|
||||||
|
"header."
|
||||||
|
)
|
||||||
|
|
||||||
_tag.add_tag_option_to_parser_for_create(
|
_tag.add_tag_option_to_parser_for_create(
|
||||||
parser, 'listener')
|
parser, 'listener')
|
||||||
|
@ -528,6 +554,35 @@ class SetListener(command.Command):
|
||||||
help="Set the ALPN protocol to be used "
|
help="Set the ALPN protocol to be used "
|
||||||
"by the listener (can be set multiple times)."
|
"by the listener (can be set multiple times)."
|
||||||
)
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
'--hsts-max-age',
|
||||||
|
dest='hsts_max_age',
|
||||||
|
metavar='<hsts_max_age>',
|
||||||
|
type=int,
|
||||||
|
default=argparse.SUPPRESS,
|
||||||
|
help="The value of the max_age directive for the "
|
||||||
|
"Strict-Transport-Security HTTP response header. "
|
||||||
|
"Setting this enables HTTP Strict Transport "
|
||||||
|
"Security (HSTS) for the TLS-terminated listener."
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
'--hsts-include-subdomains',
|
||||||
|
action='store_true',
|
||||||
|
default=argparse.SUPPRESS,
|
||||||
|
dest='hsts_include_subdomains',
|
||||||
|
help="Defines whether the includeSubDomains directive should be "
|
||||||
|
"added to the Strict-Transport-Security HTTP response "
|
||||||
|
"header."
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
'--hsts-preload',
|
||||||
|
action='store_true',
|
||||||
|
default=argparse.SUPPRESS,
|
||||||
|
dest='hsts_preload',
|
||||||
|
help="Defines whether the preload directive should be "
|
||||||
|
"added to the Strict-Transport-Security HTTP response "
|
||||||
|
"header."
|
||||||
|
)
|
||||||
|
|
||||||
_tag.add_tag_option_to_parser_for_set(parser, 'listener')
|
_tag.add_tag_option_to_parser_for_set(parser, 'listener')
|
||||||
|
|
||||||
|
@ -664,7 +719,27 @@ class UnsetListener(command.Command):
|
||||||
action='store_true',
|
action='store_true',
|
||||||
help="Clear all ALPN protocols from the listener."
|
help="Clear all ALPN protocols from the listener."
|
||||||
)
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
'--hsts-max-age',
|
||||||
|
dest='hsts_max_age',
|
||||||
|
action='store_true',
|
||||||
|
help="Disables HTTP Strict Transport "
|
||||||
|
"Security (HSTS) for the TLS-terminated listener."
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
'--hsts-include-subdomains',
|
||||||
|
action='store_true',
|
||||||
|
dest='hsts_include_subdomains',
|
||||||
|
help="Removes the includeSubDomains directive from the "
|
||||||
|
"Strict-Transport-Security HTTP response header."
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
'--hsts-preload',
|
||||||
|
action='store_true',
|
||||||
|
dest='hsts_preload',
|
||||||
|
help="Removes the preload directive from the "
|
||||||
|
"Strict-Transport-Security HTTP response header."
|
||||||
|
)
|
||||||
_tag.add_tag_option_to_parser_for_unset(parser, 'listener')
|
_tag.add_tag_option_to_parser_for_unset(parser, 'listener')
|
||||||
|
|
||||||
return parser
|
return parser
|
||||||
|
|
|
@ -322,6 +322,9 @@ def get_listener_attrs(client_manager, parsed_args):
|
||||||
'tls_ciphers': ('tls_ciphers', str),
|
'tls_ciphers': ('tls_ciphers', str),
|
||||||
'tls_versions': ('tls_versions', list),
|
'tls_versions': ('tls_versions', list),
|
||||||
'alpn_protocols': ('alpn_protocols', list),
|
'alpn_protocols': ('alpn_protocols', list),
|
||||||
|
'hsts_max_age': ('hsts_max_age', int),
|
||||||
|
'hsts_include_subdomains': ('hsts_include_subdomains', bool),
|
||||||
|
'hsts_preload': ('hsts_preload', bool),
|
||||||
}
|
}
|
||||||
add_tags_attr_map(attr_map)
|
add_tags_attr_map(attr_map)
|
||||||
|
|
||||||
|
|
|
@ -82,7 +82,10 @@ LISTENER_ATTRS = {
|
||||||
'tls_ciphers': "ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256",
|
'tls_ciphers': "ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256",
|
||||||
'tls_versions': ['TLSv1.1', 'TLSv1.2'],
|
'tls_versions': ['TLSv1.1', 'TLSv1.2'],
|
||||||
'alpn_protocols': ['h2', 'http/1.1'],
|
'alpn_protocols': ['h2', 'http/1.1'],
|
||||||
"tags": ["foo", "bar"]
|
"tags": ["foo", "bar"],
|
||||||
|
'hsts_max_age': 15_000_000,
|
||||||
|
'hsts_include_subdomains': True,
|
||||||
|
'hsts_preload': True,
|
||||||
}
|
}
|
||||||
|
|
||||||
LOADBALANCER_ATTRS = {
|
LOADBALANCER_ATTRS = {
|
||||||
|
|
|
@ -255,7 +255,12 @@ class TestListenerCreate(TestListener):
|
||||||
'--alpn-protocol',
|
'--alpn-protocol',
|
||||||
self._listener.alpn_protocols[0],
|
self._listener.alpn_protocols[0],
|
||||||
'--alpn-protocol',
|
'--alpn-protocol',
|
||||||
self._listener.alpn_protocols[1]]
|
self._listener.alpn_protocols[1],
|
||||||
|
'--hsts-max-age',
|
||||||
|
'12000000',
|
||||||
|
'--hsts-include-subdomains',
|
||||||
|
'--hsts-preload',
|
||||||
|
]
|
||||||
|
|
||||||
verifylist = [
|
verifylist = [
|
||||||
('loadbalancer', 'mock_lb_id'),
|
('loadbalancer', 'mock_lb_id'),
|
||||||
|
@ -276,6 +281,9 @@ class TestListenerCreate(TestListener):
|
||||||
self._listener.tls_versions),
|
self._listener.tls_versions),
|
||||||
('alpn_protocols',
|
('alpn_protocols',
|
||||||
self._listener.alpn_protocols),
|
self._listener.alpn_protocols),
|
||||||
|
('hsts_max_age', 12_000_000),
|
||||||
|
('hsts_include_subdomains', True),
|
||||||
|
('hsts_preload', True),
|
||||||
]
|
]
|
||||||
|
|
||||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||||
|
@ -386,7 +394,12 @@ class TestListenerSet(TestListener):
|
||||||
'--alpn-protocol',
|
'--alpn-protocol',
|
||||||
self._listener.alpn_protocols[0],
|
self._listener.alpn_protocols[0],
|
||||||
'--alpn-protocol',
|
'--alpn-protocol',
|
||||||
self._listener.alpn_protocols[1]]
|
self._listener.alpn_protocols[1],
|
||||||
|
'--hsts-max-age',
|
||||||
|
'15000000',
|
||||||
|
'--hsts-include-subdomains',
|
||||||
|
'--hsts-preload',
|
||||||
|
]
|
||||||
verifylist = [
|
verifylist = [
|
||||||
('listener', self._listener.id),
|
('listener', self._listener.id),
|
||||||
('name', 'new_name'),
|
('name', 'new_name'),
|
||||||
|
@ -402,7 +415,10 @@ class TestListenerSet(TestListener):
|
||||||
('allowed_cidrs', self._listener.allowed_cidrs),
|
('allowed_cidrs', self._listener.allowed_cidrs),
|
||||||
('tls_ciphers', self._listener.tls_ciphers),
|
('tls_ciphers', self._listener.tls_ciphers),
|
||||||
('tls_versions', self._listener.tls_versions),
|
('tls_versions', self._listener.tls_versions),
|
||||||
('alpn_protocols', self._listener.alpn_protocols)
|
('alpn_protocols', self._listener.alpn_protocols),
|
||||||
|
('hsts_max_age', 15_000_000),
|
||||||
|
('hsts_include_subdomains', True),
|
||||||
|
('hsts_preload', True),
|
||||||
]
|
]
|
||||||
|
|
||||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||||
|
@ -424,7 +440,30 @@ class TestListenerSet(TestListener):
|
||||||
'tls_ciphers': self._listener.tls_ciphers,
|
'tls_ciphers': self._listener.tls_ciphers,
|
||||||
'tls_versions': self._listener.tls_versions,
|
'tls_versions': self._listener.tls_versions,
|
||||||
'alpn_protocols': self._listener.alpn_protocols,
|
'alpn_protocols': self._listener.alpn_protocols,
|
||||||
}})
|
'hsts_max_age': 15_000_000,
|
||||||
|
'hsts_include_subdomains': True,
|
||||||
|
'hsts_preload': True,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_listener_set_suppressed(self):
|
||||||
|
arglist = [self._listener.id, '--name', 'foo']
|
||||||
|
verifylist = [('name', 'foo')]
|
||||||
|
|
||||||
|
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||||
|
self.cmd.take_action(parsed_args)
|
||||||
|
# Suppressed arguments should not be included
|
||||||
|
self.assertNotIn('hsts_preload', parsed_args)
|
||||||
|
self.assertNotIn('hsts_include_subdomain', parsed_args)
|
||||||
|
self.assertNotIn('hsts_max_age', parsed_args)
|
||||||
|
self.api_mock.listener_set.assert_called_with(
|
||||||
|
self._listener.id, json={
|
||||||
|
'listener': {
|
||||||
|
'name': 'foo',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
@mock.patch('osc_lib.utils.wait_for_status')
|
@mock.patch('osc_lib.utils.wait_for_status')
|
||||||
def test_listener_set_wait(self, mock_wait):
|
def test_listener_set_wait(self, mock_wait):
|
||||||
|
@ -438,7 +477,10 @@ class TestListenerSet(TestListener):
|
||||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||||
self.cmd.take_action(parsed_args)
|
self.cmd.take_action(parsed_args)
|
||||||
self.api_mock.listener_set.assert_called_with(
|
self.api_mock.listener_set.assert_called_with(
|
||||||
self._listener.id, json={'listener': {'name': 'new_name'}})
|
self._listener.id, json={
|
||||||
|
'listener': {'name': 'new_name'}
|
||||||
|
}
|
||||||
|
)
|
||||||
mock_wait.assert_called_once_with(
|
mock_wait.assert_called_once_with(
|
||||||
status_f=mock.ANY,
|
status_f=mock.ANY,
|
||||||
res_id=self._listener.id,
|
res_id=self._listener.id,
|
||||||
|
@ -574,6 +616,15 @@ class TestListenerUnset(TestListener):
|
||||||
def test_listener_unset_tls_ciphers(self):
|
def test_listener_unset_tls_ciphers(self):
|
||||||
self._test_listener_unset_param('tls_ciphers')
|
self._test_listener_unset_param('tls_ciphers')
|
||||||
|
|
||||||
|
def test_listener_unset_hsts_max_age(self):
|
||||||
|
self._test_listener_unset_param('hsts_max_age')
|
||||||
|
|
||||||
|
def test_listener_unset_hsts_include_subdomains(self):
|
||||||
|
self._test_listener_unset_param('hsts_include_subdomains')
|
||||||
|
|
||||||
|
def test_listener_unset_hsts_preload(self):
|
||||||
|
self._test_listener_unset_param('hsts_preload')
|
||||||
|
|
||||||
def _test_listener_unset_param(self, param):
|
def _test_listener_unset_param(self, param):
|
||||||
self.api_mock.listener_set.reset_mock()
|
self.api_mock.listener_set.reset_mock()
|
||||||
arg_param = param.replace('_', '-') if '_' in param else param
|
arg_param = param.replace('_', '-') if '_' in param else param
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
---
|
||||||
|
features:
|
||||||
|
- |
|
||||||
|
Added support for HTTP Strict Transport Security (HSTS). This new feature
|
||||||
|
can be enabled and configured during listener creation and update
|
||||||
|
using the new ``--hsts-max-age`` option and the optional
|
||||||
|
``--hsts-include-subdomains`` and ``--hsts-prefetch`` options.
|
Loading…
Reference in New Issue