Add support for HTTP Strict Transport Security

Change-Id: I61882c844424a768d70b758e22d2aac979e3e3c6
This commit is contained in:
Tom Weininger 2023-04-19 12:22:15 +02:00
parent 98beca9d67
commit cd0bb14594
6 changed files with 66 additions and 7 deletions

View File

@ -82,7 +82,10 @@ LISTENER_ROWS = (
'tls_ciphers', 'tls_ciphers',
'tls_versions', 'tls_versions',
'alpn_protocols', 'alpn_protocols',
'tags') 'tags',
'hsts_enabled',
'hsts_header_opts',
)
LISTENER_COLUMNS = ( LISTENER_COLUMNS = (
'id', 'id',

View File

@ -201,6 +201,22 @@ 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(
'--enable-hsts',
dest='hsts_enabled',
action='store_true',
help="Enables HTTP Strict Transport Security (HSTS) for the "
"TLS-terminated listener."
)
parser.add_argument(
'--hsts-header-opts',
dest='hsts_header_opts',
metavar='<hsts_header_opts>',
nargs='?',
help="Options of the Strict-Transport-Security header. E.g. "
"'max-age=16000000; includeSubDomains; preload;'. Note that "
"max-age is a mandatory field for using the HSTS feature."
)
_tag.add_tag_option_to_parser_for_create( _tag.add_tag_option_to_parser_for_create(
parser, 'listener') parser, 'listener')
@ -528,6 +544,22 @@ 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(
'--enable-hsts',
dest='hsts_enabled',
action='store_true',
help="Enables HTTP Strict Transport Security (HSTS) for the "
"TLS-terminated listener."
)
parser.add_argument(
'--hsts-header-opts',
dest='hsts_header_opts',
metavar='<hsts_header_opts>',
nargs='?',
help="Options of the Strict-Transport-Security header. E.g. "
"'max-age=16000000; includeSubDomains; preload;'. Note that "
"max-age is a mandatory field for using the HSTS feature."
)
_tag.add_tag_option_to_parser_for_set(parser, 'listener') _tag.add_tag_option_to_parser_for_set(parser, 'listener')

View File

@ -322,6 +322,8 @@ 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_enabled': ('hsts_enabled', bool),
'hsts_header_opts': ('hsts_header_opts', str),
} }
add_tags_attr_map(attr_map) add_tags_attr_map(attr_map)

View File

@ -82,7 +82,8 @@ 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_header_opts': "max-age=16000000; includeSubDomains; preload;",
} }
LOADBALANCER_ATTRS = { LOADBALANCER_ATTRS = {

View File

@ -255,7 +255,11 @@ 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],
'--enable-hsts',
'--hsts-header-opts',
self._listener.hsts_header_opts,
]
verifylist = [ verifylist = [
('loadbalancer', 'mock_lb_id'), ('loadbalancer', 'mock_lb_id'),
@ -276,6 +280,8 @@ class TestListenerCreate(TestListener):
self._listener.tls_versions), self._listener.tls_versions),
('alpn_protocols', ('alpn_protocols',
self._listener.alpn_protocols), self._listener.alpn_protocols),
('hsts_enabled', True),
('hsts_header_opts', self._listener.hsts_header_opts),
] ]
parsed_args = self.check_parser(self.cmd, arglist, verifylist) parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@ -386,7 +392,11 @@ 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],
'--enable-hsts',
'--hsts-header-opts',
self._listener.hsts_header_opts,
]
verifylist = [ verifylist = [
('listener', self._listener.id), ('listener', self._listener.id),
('name', 'new_name'), ('name', 'new_name'),
@ -402,7 +412,9 @@ 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_enabled', True),
('hsts_header_opts', self._listener.hsts_header_opts),
] ]
parsed_args = self.check_parser(self.cmd, arglist, verifylist) parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@ -424,6 +436,8 @@ 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_enabled': True,
'hsts_header_opts': self._listener.hsts_header_opts,
}}) }})
@mock.patch('osc_lib.utils.wait_for_status') @mock.patch('osc_lib.utils.wait_for_status')
@ -438,7 +452,8 @@ 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',
'hsts_enabled': False}})
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,
@ -480,7 +495,7 @@ class TestListenerSet(TestListener):
self.api_mock.listener_set.assert_called_once_with( self.api_mock.listener_set.assert_called_once_with(
self._listener.id, self._listener.id,
json={"listener": {"tags": ['bar']}} json={"listener": {"tags": ['bar'], 'hsts_enabled': False}}
) )

View File

@ -0,0 +1,6 @@
---
features:
- |
Added support for HTTP Strict Transport Security (HSTS). This new feature
can be enabled and configured during listener creation using the new
``--enable-hsts`` and ``--hsts-header-opts`` settings.