Add ALPN support for TLS-enabled pools
ALPN is a TLS extension for application-layer protocol negotiation within the TLS handshake [1]. This patch extends the Pool API to include a new 'alpn_protocols' parameter. With this parameter, users can set an ALPN preference list (descending order of preference) to be advertised by load balancer to members. This patch also adds HTTP/2 over TLS support to TLS-enabled pools to the Amphora provider driver, although default the pool ALPN protocol list configuration setting has HTTP/2 disabled similarly to the default listener ALPN protocol list value added in Victoria release. [1] https://tools.ietf.org/html/rfc7301 Change-Id: I91924486bab22601c15c538c8a5282ad8bc54700
This commit is contained in:
parent
fbbc5f9024
commit
d2d5fc80f8
|
@ -183,7 +183,7 @@ allowed_cidrs-optional:
|
|||
min_version: 2.12
|
||||
required: false
|
||||
type: array
|
||||
alpn_protocols:
|
||||
alpn_protocols-listener:
|
||||
description: |
|
||||
A list of ALPN protocols.
|
||||
Available protocols: http/1.0, http/1.1, h2
|
||||
|
@ -191,7 +191,7 @@ alpn_protocols:
|
|||
min_version: 2.20
|
||||
required: true
|
||||
type: array
|
||||
alpn_protocols-optional:
|
||||
alpn_protocols-listener-optional:
|
||||
description: |
|
||||
A list of ALPN protocols.
|
||||
Available protocols: http/1.0, http/1.1, h2
|
||||
|
@ -199,6 +199,22 @@ alpn_protocols-optional:
|
|||
min_version: 2.20
|
||||
required: false
|
||||
type: array
|
||||
alpn_protocols-pool:
|
||||
description: |
|
||||
A list of ALPN protocols.
|
||||
Available protocols: http/1.0, http/1.1, h2
|
||||
in: body
|
||||
min_version: 2.24
|
||||
required: true
|
||||
type: array
|
||||
alpn_protocols-pool-optional:
|
||||
description: |
|
||||
A list of ALPN protocols.
|
||||
Available protocols: http/1.0, http/1.1, h2
|
||||
in: body
|
||||
min_version: 2.24
|
||||
required: false
|
||||
type: array
|
||||
amphora-id:
|
||||
description: |
|
||||
The associated amphora ID.
|
||||
|
|
|
@ -1 +1 @@
|
|||
curl -X POST -H "Content-Type: application/json" -H "X-Auth-Token: <token>" -d '{"pool":{"lb_algorithm":"ROUND_ROBIN","protocol":"HTTP","description":"Super Round Robin Pool","admin_state_up":true,"session_persistence":{"cookie_name":"ChocolateChip","type":"APP_COOKIE"},"listener_id":"023f2e34-7806-443b-bfae-16c324569a3d","name":"super-pool","tags":["test_tag"],"tls_container_ref":"http://198.51.100.10:9311/v1/containers/4073846f-1d5e-42e1-a4cf-a7046419d0e6","ca_tls_container_ref":"http://198.51.100.10:9311/v1/containers/5f0d5540-fae6-4646-85d6-8a84883807fb","crl_container_ref":"http://198.51.100.10:9311/v1/containers/6faf0a01-6892-454c-aaac-650282820c0b","tls_enabled":true,"tls_ciphers":"ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256", "tls_versions": ["TLSv1.2", "TLSv1.3"]}}' http://198.51.100.10:9876/v2/lbaas/pools
|
||||
curl -X POST -H "Content-Type: application/json" -H "X-Auth-Token: <token>" -d '{"pool":{"lb_algorithm":"ROUND_ROBIN","protocol":"HTTP","description":"Super Round Robin Pool","admin_state_up":true,"session_persistence":{"cookie_name":"ChocolateChip","type":"APP_COOKIE"},"listener_id":"023f2e34-7806-443b-bfae-16c324569a3d","name":"super-pool","tags":["test_tag"],"tls_container_ref":"http://198.51.100.10:9311/v1/containers/4073846f-1d5e-42e1-a4cf-a7046419d0e6","ca_tls_container_ref":"http://198.51.100.10:9311/v1/containers/5f0d5540-fae6-4646-85d6-8a84883807fb","crl_container_ref":"http://198.51.100.10:9311/v1/containers/6faf0a01-6892-454c-aaac-650282820c0b","tls_enabled":true,"tls_ciphers":"ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256", "tls_versions": ["TLSv1.2", "TLSv1.3"], "alpn_protocols": ["http/1.1", "http/1.0"]}}' http://198.51.100.10:9876/v2/lbaas/pools
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
"crl_container_ref": "http://198.51.100.10:9311/v1/containers/6faf0a01-6892-454c-aaac-650282820c0b",
|
||||
"tls_enabled": true,
|
||||
"tls_ciphers": "ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256",
|
||||
"tls_versions": ["TLSv1.2", "TLSv1.3"]
|
||||
"tls_versions": ["TLSv1.2", "TLSv1.3"],
|
||||
"alpn_protocols": ["http/1.1", "http/1.0"]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -33,6 +33,7 @@
|
|||
"crl_container_ref": "http://198.51.100.10:9311/v1/containers/6faf0a01-6892-454c-aaac-650282820c0b",
|
||||
"tls_enabled": true,
|
||||
"tls_ciphers": "ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256",
|
||||
"tls_versions": ["TLSv1.2", "TLSv1.3"]
|
||||
"tls_versions": ["TLSv1.2", "TLSv1.3"],
|
||||
"alpn_protocols": ["http/1.1", "http/1.0"]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -33,6 +33,7 @@
|
|||
"crl_container_ref": "http://198.51.100.10:9311/v1/containers/6faf0a01-6892-454c-aaac-650282820c0b",
|
||||
"tls_enabled": false,
|
||||
"tls_ciphers": "ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256",
|
||||
"tls_versions": ["TLSv1.2", "TLSv1.3"]
|
||||
"tls_versions": ["TLSv1.2", "TLSv1.3"],
|
||||
"alpn_protocols": ["http/1.1", "http/1.0"]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1 +1 @@
|
|||
curl -X PUT -H "Content-Type: application/json" -H "X-Auth-Token: <token>" -d '{"pool":{"lb_algorithm":"LEAST_CONNECTIONS","session_persistence":{"type":"SOURCE_IP"},"description":"second description","name":"second_name","tags":["updated_tag"],"tls_container_ref":"http://198.51.100.10:9311/v1/containers/c1cd501d-3cf9-4873-a11b-a74bebcde929","ca_tls_container_ref":null,"crl_container_ref":null,"tls_enabled":false,"tls_ciphers":"ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256", "tls_versions": ["TLSv1.2", "TLSv1.3"]}}' http://198.51.100.10:9876/v2/lbaas/pools/4029d267-3983-4224-a3d0-afb3fe16a2cd
|
||||
curl -X PUT -H "Content-Type: application/json" -H "X-Auth-Token: <token>" -d '{"pool":{"lb_algorithm":"LEAST_CONNECTIONS","session_persistence":{"type":"SOURCE_IP"},"description":"second description","name":"second_name","tags":["updated_tag"],"tls_container_ref":"http://198.51.100.10:9311/v1/containers/c1cd501d-3cf9-4873-a11b-a74bebcde929","ca_tls_container_ref":null,"crl_container_ref":null,"tls_enabled":false,"tls_ciphers":"ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256", "tls_versions": ["TLSv1.2", "TLSv1.3"], "alpn_protocols": ["http/1.1", "http/1.0"]}}' http://198.51.100.10:9876/v2/lbaas/pools/4029d267-3983-4224-a3d0-afb3fe16a2cd
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
"crl_container_ref": null,
|
||||
"tls_enabled": false,
|
||||
"tls_ciphers": "ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256",
|
||||
"tls_versions": ["TLSv1.2", "TLSv1.3"]
|
||||
"tls_versions": ["TLSv1.2", "TLSv1.3"],
|
||||
"alpn_protocols": ["http/1.1", "http/1.0"]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -33,6 +33,7 @@
|
|||
"crl_container_ref": null,
|
||||
"tls_enabled": false,
|
||||
"tls_ciphers": "ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256",
|
||||
"tls_versions": ["TLSv1.2", "TLSv1.3"]
|
||||
"tls_versions": ["TLSv1.2", "TLSv1.3"],
|
||||
"alpn_protocols": ["http/1.1", "http/1.0"]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -39,7 +39,8 @@
|
|||
"crl_container_ref": "http://198.51.100.10:9311/v1/containers/6faf0a01-6892-454c-aaac-650282820c0b",
|
||||
"tls_enabled": true,
|
||||
"tls_ciphers": "ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256",
|
||||
"tls_versions": ["TLSv1.2", "TLSv1.3"]
|
||||
"tls_versions": ["TLSv1.2", "TLSv1.3"],
|
||||
"alpn_protocols": ["http/1.1", "http/1.0"]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -47,7 +47,7 @@ Response Parameters
|
|||
|
||||
- admin_state_up: admin_state_up
|
||||
- allowed_cidrs: allowed_cidrs
|
||||
- alpn_protocols: alpn_protocols
|
||||
- alpn_protocols: alpn_protocols-listener
|
||||
- client_authentication: client_authentication
|
||||
- client_ca_tls_container_ref: client_ca_tls_container_ref
|
||||
- client_crl_container_ref: client_crl_container_ref
|
||||
|
@ -144,7 +144,7 @@ Request
|
|||
|
||||
- admin_state_up: admin_state_up-default-optional
|
||||
- allowed_cidrs: allowed_cidrs-optional
|
||||
- alpn_protocols: alpn_protocols-optional
|
||||
- alpn_protocols: alpn_protocols-listener-optional
|
||||
- client_authentication: client_authentication-optional
|
||||
- client_ca_tls_container_ref: client_ca_tls_container_ref-optional
|
||||
- client_crl_container_ref: client_crl_container_ref-optional
|
||||
|
@ -268,7 +268,7 @@ Response Parameters
|
|||
|
||||
- admin_state_up: admin_state_up
|
||||
- allowed_cidrs: allowed_cidrs
|
||||
- alpn_protocols: alpn_protocols
|
||||
- alpn_protocols: alpn_protocols-listener
|
||||
- client_authentication: client_authentication
|
||||
- client_ca_tls_container_ref: client_ca_tls_container_ref
|
||||
- client_crl_container_ref: client_crl_container_ref
|
||||
|
@ -349,7 +349,7 @@ Response Parameters
|
|||
|
||||
- admin_state_up: admin_state_up
|
||||
- allowed_cidrs: allowed_cidrs
|
||||
- alpn_protocols: alpn_protocols
|
||||
- alpn_protocols: alpn_protocols-listener
|
||||
- client_authentication: client_authentication
|
||||
- client_ca_tls_container_ref: client_ca_tls_container_ref
|
||||
- client_crl_container_ref: client_crl_container_ref
|
||||
|
@ -420,7 +420,7 @@ Request
|
|||
|
||||
- admin_state_up: admin_state_up-default-optional
|
||||
- allowed_cidrs: allowed_cidrs-optional
|
||||
- alpn_protocols: alpn_protocols-optional
|
||||
- alpn_protocols: alpn_protocols-listener-optional
|
||||
- client_authentication: client_authentication-optional
|
||||
- client_ca_tls_container_ref: client_ca_tls_container_ref-optional
|
||||
- client_crl_container_ref: client_crl_container_ref-optional
|
||||
|
@ -459,7 +459,7 @@ Response Parameters
|
|||
|
||||
- admin_state_up: admin_state_up
|
||||
- allowed_cidrs: allowed_cidrs
|
||||
- alpn_protocols: alpn_protocols
|
||||
- alpn_protocols: alpn_protocols-listener
|
||||
- client_authentication: client_authentication
|
||||
- client_ca_tls_container_ref: client_ca_tls_container_ref
|
||||
- client_crl_container_ref: client_crl_container_ref
|
||||
|
|
|
@ -46,6 +46,7 @@ Response Parameters
|
|||
.. rest_parameters:: ../parameters.yaml
|
||||
|
||||
- admin_state_up: admin_state_up
|
||||
- alpn_protocols: alpn_protocols-pool
|
||||
- ca_tls_container_ref: ca_tls_container_ref
|
||||
- created_at: created_at
|
||||
- crl_container_ref: crl_container_ref
|
||||
|
@ -170,6 +171,7 @@ Request
|
|||
.. rest_parameters:: ../parameters.yaml
|
||||
|
||||
- admin_state_up: admin_state_up-default-optional
|
||||
- alpn_protocols: alpn_protocols-pool-optional
|
||||
- ca_tls_container_ref: ca_tls_container_ref-optional
|
||||
- crl_container_ref: crl_container_ref-optional
|
||||
- description: description-optional
|
||||
|
@ -247,6 +249,7 @@ Response Parameters
|
|||
.. rest_parameters:: ../parameters.yaml
|
||||
|
||||
- admin_state_up: admin_state_up
|
||||
- alpn_protocols: alpn_protocols-pool
|
||||
- ca_tls_container_ref: ca_tls_container_ref
|
||||
- created_at: created_at
|
||||
- crl_container_ref: crl_container_ref
|
||||
|
@ -320,6 +323,7 @@ Response Parameters
|
|||
.. rest_parameters:: ../parameters.yaml
|
||||
|
||||
- admin_state_up: admin_state_up
|
||||
- alpn_protocols: alpn_protocols-pool
|
||||
- ca_tls_container_ref: ca_tls_container_ref
|
||||
- created_at: created_at
|
||||
- crl_container_ref: crl_container_ref
|
||||
|
@ -383,6 +387,7 @@ Request
|
|||
.. rest_parameters:: ../parameters.yaml
|
||||
|
||||
- admin_state_up: admin_state_up-default-optional
|
||||
- alpn_protocols: alpn_protocols-pool-optional
|
||||
- ca_tls_container_ref: ca_tls_container_ref-optional
|
||||
- crl_container_ref: crl_container_ref-optional
|
||||
- description: description-optional
|
||||
|
@ -414,6 +419,7 @@ Response Parameters
|
|||
.. rest_parameters:: ../parameters.yaml
|
||||
|
||||
- admin_state_up: admin_state_up
|
||||
- alpn_protocols: alpn_protocols-pool
|
||||
- ca_tls_container_ref: ca_tls_container_ref
|
||||
- created_at: created_at
|
||||
- crl_container_ref: crl_container_ref
|
||||
|
|
|
@ -30,6 +30,14 @@ cli=openstack loadbalancer pool create [--enable | --disable] --listener <listen
|
|||
driver.amphora=complete
|
||||
driver.ovn=complete
|
||||
|
||||
[operation.alpn_protocol]
|
||||
title=alpn_protocol
|
||||
status=optional
|
||||
notes=List of accepted ALPN protocols (can be set multiple times).
|
||||
cli=openstack loadbalancer pool create [--alpn-protocol <protocol>] --listener <listener>
|
||||
driver.amphora=complete
|
||||
driver.ovn=missing
|
||||
|
||||
[operation.ca_tls_container_ref]
|
||||
title=ca_tls_container_ref
|
||||
status=optional
|
||||
|
|
|
@ -88,6 +88,10 @@
|
|||
# listeners. Available protocols: http/1.0, http/1.1, h2
|
||||
# default_listener_alpn_protocols = http/1.1, http/1.0
|
||||
|
||||
# List of default ALPN protocols to be used on new TLS-terminated
|
||||
# pools. Available protocols: http/1.0, http/1.1, h2
|
||||
# default_pool_alpn_protocols = http/1.1, http/1.0
|
||||
|
||||
|
||||
[database]
|
||||
# This line MUST be changed to actually run the plugin.
|
||||
|
|
|
@ -72,7 +72,7 @@ munch==2.2.0
|
|||
netaddr==0.7.19
|
||||
netifaces==0.10.4
|
||||
networkx==2.1.0
|
||||
octavia-lib==2.2.0
|
||||
octavia-lib==2.3.0
|
||||
openstacksdk==0.12.0
|
||||
os-client-config==1.29.0
|
||||
os-service-types==1.2.0
|
||||
|
|
|
@ -83,11 +83,11 @@ class AmphoraProviderDriver(driver_base.ProviderDriver):
|
|||
user_fault_string=msg,
|
||||
operator_fault_string=msg)
|
||||
|
||||
def _validate_alpn_protocols(self, listener):
|
||||
if not listener.alpn_protocols:
|
||||
def _validate_alpn_protocols(self, obj):
|
||||
if not obj.alpn_protocols:
|
||||
return
|
||||
supported = consts.AMPHORA_SUPPORTED_ALPN_PROTOCOLS
|
||||
not_supported = set(listener.alpn_protocols) - set(supported)
|
||||
not_supported = set(obj.alpn_protocols) - set(supported)
|
||||
if not_supported:
|
||||
msg = ('Amphora provider does not support %s ALPN protocol(s). '
|
||||
'Supported: %s'
|
||||
|
@ -189,6 +189,7 @@ class AmphoraProviderDriver(driver_base.ProviderDriver):
|
|||
# Pool
|
||||
def pool_create(self, pool):
|
||||
self._validate_pool_algorithm(pool)
|
||||
self._validate_alpn_protocols(pool)
|
||||
payload = {consts.POOL_ID: pool.pool_id}
|
||||
self.client.cast({}, 'create_pool', **payload)
|
||||
|
||||
|
@ -198,6 +199,7 @@ class AmphoraProviderDriver(driver_base.ProviderDriver):
|
|||
self.client.cast({}, 'delete_pool', **payload)
|
||||
|
||||
def pool_update(self, old_pool, new_pool):
|
||||
self._validate_alpn_protocols(new_pool)
|
||||
if new_pool.lb_algorithm:
|
||||
self._validate_pool_algorithm(new_pool)
|
||||
pool_dict = new_pool.to_dict()
|
||||
|
|
|
@ -85,11 +85,11 @@ class AmphoraProviderDriver(driver_base.ProviderDriver):
|
|||
user_fault_string=msg,
|
||||
operator_fault_string=msg)
|
||||
|
||||
def _validate_alpn_protocols(self, listener):
|
||||
if not listener.alpn_protocols:
|
||||
def _validate_alpn_protocols(self, obj):
|
||||
if not obj.alpn_protocols:
|
||||
return
|
||||
supported = consts.AMPHORA_SUPPORTED_ALPN_PROTOCOLS
|
||||
not_supported = set(listener.alpn_protocols) - set(supported)
|
||||
not_supported = set(obj.alpn_protocols) - set(supported)
|
||||
if not_supported:
|
||||
msg = ('Amphora provider does not support %s ALPN protocol(s). '
|
||||
'Supported: %s'
|
||||
|
@ -229,6 +229,7 @@ class AmphoraProviderDriver(driver_base.ProviderDriver):
|
|||
|
||||
def pool_create(self, pool):
|
||||
self._validate_pool_algorithm(pool)
|
||||
self._validate_alpn_protocols(pool)
|
||||
payload = {consts.POOL: self._pool_convert_to_dict(pool)}
|
||||
self.client.cast({}, 'create_pool', **payload)
|
||||
|
||||
|
@ -237,6 +238,7 @@ class AmphoraProviderDriver(driver_base.ProviderDriver):
|
|||
self.client.cast({}, 'delete_pool', **payload)
|
||||
|
||||
def pool_update(self, old_pool, new_pool):
|
||||
self._validate_alpn_protocols(new_pool)
|
||||
if new_pool.lb_algorithm:
|
||||
self._validate_pool_algorithm(new_pool)
|
||||
pool_dict = self._pool_convert_to_dict(new_pool)
|
||||
|
|
|
@ -124,7 +124,7 @@ class RootController(object):
|
|||
# Add quota support to octavia's l7policy and l7rule
|
||||
self._add_a_version(versions, 'v2.19', 'v2', 'SUPPORTED',
|
||||
'2020-05-12T00:00:00Z', host_url)
|
||||
# ALPN protocols
|
||||
# ALPN protocols (listener)
|
||||
self._add_a_version(versions, 'v2.20', 'v2', 'SUPPORTED',
|
||||
'2020-08-02T00:00:00Z', host_url)
|
||||
# Amphora delete
|
||||
|
@ -134,6 +134,9 @@ class RootController(object):
|
|||
self._add_a_version(versions, 'v2.22', 'v2', 'SUPPORTED',
|
||||
'2020-09-04T00:00:00Z', host_url)
|
||||
# SCTP protocol
|
||||
self._add_a_version(versions, 'v2.23', 'v2', 'CURRENT',
|
||||
self._add_a_version(versions, 'v2.23', 'v2', 'SUPPORTED',
|
||||
'2020-09-07T00:00:00Z', host_url)
|
||||
# ALPN protocols (pool)
|
||||
self._add_a_version(versions, 'v2.24', 'v2', 'CURRENT',
|
||||
'2020-10-15T00:00:00Z', host_url)
|
||||
return {'versions': versions}
|
||||
|
|
|
@ -137,6 +137,8 @@ class PoolsController(base.BaseController):
|
|||
validate.check_tls_version_list(pool_dict['tls_versions'])
|
||||
# Validate TLS versions against minimum
|
||||
validate.check_tls_version_min(pool_dict['tls_versions'])
|
||||
# Validate ALPN protocol list
|
||||
validate.check_alpn_protocols(pool_dict['alpn_protocols'])
|
||||
|
||||
try:
|
||||
return self.repositories.create_pool_on_load_balancer(
|
||||
|
@ -420,6 +422,10 @@ class PoolsController(base.BaseController):
|
|||
# Validate TLS version against minimum
|
||||
validate.check_tls_version_min(pool.tls_versions)
|
||||
|
||||
if pool.alpn_protocols is not wtypes.Unset:
|
||||
# Validate ALPN protocol list
|
||||
validate.check_alpn_protocols(pool.alpn_protocols)
|
||||
|
||||
@wsme_pecan.wsexpose(pool_types.PoolRootResponse, wtypes.text,
|
||||
body=pool_types.PoolRootPut, status_code=200)
|
||||
def put(self, id, pool_):
|
||||
|
|
|
@ -86,6 +86,7 @@ class PoolResponse(BasePoolType):
|
|||
tls_enabled = wtypes.wsattr(bool)
|
||||
tls_ciphers = wtypes.wsattr(wtypes.StringType())
|
||||
tls_versions = wtypes.wsattr(wtypes.ArrayType(wtypes.StringType()))
|
||||
alpn_protocols = wtypes.wsattr(wtypes.ArrayType(types.AlpnProtocolType()))
|
||||
|
||||
@classmethod
|
||||
def from_data_model(cls, data_model, children=False):
|
||||
|
@ -118,6 +119,7 @@ class PoolResponse(BasePoolType):
|
|||
member_model.from_data_model(i) for i in data_model.members]
|
||||
|
||||
pool.tls_versions = data_model.tls_versions
|
||||
pool.alpn_protocols = data_model.alpn_protocols
|
||||
|
||||
return pool
|
||||
|
||||
|
@ -167,6 +169,7 @@ class PoolPOST(BasePoolType):
|
|||
tls_ciphers = wtypes.wsattr(wtypes.StringType(max_length=2048))
|
||||
tls_versions = wtypes.wsattr(wtypes.ArrayType(wtypes.StringType(
|
||||
max_length=32)))
|
||||
alpn_protocols = wtypes.wsattr(wtypes.ArrayType(types.AlpnProtocolType()))
|
||||
|
||||
|
||||
class PoolRootPOST(types.BaseType):
|
||||
|
@ -189,6 +192,7 @@ class PoolPUT(BasePoolType):
|
|||
tls_ciphers = wtypes.wsattr(wtypes.StringType(max_length=2048))
|
||||
tls_versions = wtypes.wsattr(wtypes.ArrayType(wtypes.StringType(
|
||||
max_length=32)))
|
||||
alpn_protocols = wtypes.wsattr(wtypes.ArrayType(types.AlpnProtocolType()))
|
||||
|
||||
|
||||
class PoolRootPut(types.BaseType):
|
||||
|
@ -215,6 +219,7 @@ class PoolSingleCreate(BasePoolType):
|
|||
tls_ciphers = wtypes.wsattr(wtypes.StringType(max_length=2048))
|
||||
tls_versions = wtypes.wsattr(wtypes.ArrayType(wtypes.StringType(
|
||||
max_length=32)))
|
||||
alpn_protocols = wtypes.wsattr(wtypes.ArrayType(types.AlpnProtocolType()))
|
||||
|
||||
|
||||
class PoolStatusResponse(BasePoolType):
|
||||
|
|
|
@ -134,6 +134,11 @@ api_opts = [
|
|||
lib_consts.ALPN_PROTOCOL_HTTP_1_0],
|
||||
help=_('List of ALPN protocols to use for new TLS-enabled '
|
||||
'listeners.')),
|
||||
cfg.ListOpt('default_pool_alpn_protocols',
|
||||
default=[lib_consts.ALPN_PROTOCOL_HTTP_1_1,
|
||||
lib_consts.ALPN_PROTOCOL_HTTP_1_0],
|
||||
help=_('List of ALPN protocols to use for new TLS-enabled '
|
||||
'pools.')),
|
||||
]
|
||||
|
||||
# Options only used by the amphora agent
|
||||
|
|
|
@ -293,7 +293,8 @@ class Pool(BaseDataModel):
|
|||
created_at=None, updated_at=None, provisioning_status=None,
|
||||
tags=None, tls_certificate_id=None,
|
||||
ca_tls_certificate_id=None, crl_container_id=None,
|
||||
tls_enabled=None, tls_ciphers=None, tls_versions=None):
|
||||
tls_enabled=None, tls_ciphers=None, tls_versions=None,
|
||||
alpn_protocols=None):
|
||||
self.id = id
|
||||
self.project_id = project_id
|
||||
self.name = name
|
||||
|
@ -319,6 +320,7 @@ class Pool(BaseDataModel):
|
|||
self.tls_enabled = tls_enabled
|
||||
self.tls_ciphers = tls_ciphers
|
||||
self.tls_versions = tls_versions
|
||||
self.alpn_protocols = alpn_protocols
|
||||
|
||||
def update(self, update_dict):
|
||||
for key, value in update_dict.items():
|
||||
|
|
|
@ -369,6 +369,8 @@ class JinjaTemplater(object):
|
|||
ret_value['tls_ciphers'] = pool.tls_ciphers
|
||||
if pool.tls_versions is not None:
|
||||
ret_value['tls_versions'] = pool.tls_versions
|
||||
if pool.alpn_protocols is not None:
|
||||
ret_value['alpn_protocols'] = ",".join(pool.alpn_protocols)
|
||||
if (pool.ca_tls_certificate_id and pool_tls_certs and
|
||||
pool_tls_certs.get('ca_cert')):
|
||||
ret_value['ca_cert'] = pool_tls_certs.get('ca_cert')
|
||||
|
|
|
@ -284,11 +284,17 @@ frontend {{ listener.id }}
|
|||
{% set tls_versions_opt = tls_versions_opt + " no-tlsv13" %}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{{ "server %s %s:%d weight %s%s%s%s%s%s%s%s%s%s%s%s%s%s"|e|format(
|
||||
{% if pool.alpn_protocols is defined %}
|
||||
{% set alpn_opt = " alpn %s"|format(pool.alpn_protocols) %}
|
||||
{% else %}
|
||||
{% set alpn_opt = "" %}
|
||||
{% endif %}
|
||||
{{ "server %s %s:%d weight %s%s%s%s%s%s%s%s%s%s%s%s%s%s%s"|e|format(
|
||||
member.id, member.address, member.protocol_port, member.weight,
|
||||
hm_opt, persistence_opt, proxy_protocol_opt, member_backup_opt,
|
||||
member_enabled_opt, def_opt_prefix, def_crt_opt, ca_opt, crl_opt,
|
||||
def_verify_opt, def_sni_opt, ciphers_opt, tls_versions_opt)|trim() }}
|
||||
def_verify_opt, def_sni_opt, ciphers_opt, tls_versions_opt,
|
||||
alpn_opt)|trim() }}
|
||||
{% endmacro %}
|
||||
|
||||
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
# Copyright 2020 Red Hat, Inc. All rights reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
"""add pool alpn protocols column
|
||||
|
||||
Revision ID: be9fdc039b51
|
||||
Revises: 8b47b2546312
|
||||
Create Date: 2020-09-15 09:30:00.521760
|
||||
|
||||
"""
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = 'be9fdc039b51'
|
||||
down_revision = '8b47b2546312'
|
||||
|
||||
|
||||
def upgrade():
|
||||
op.add_column(
|
||||
'pool',
|
||||
sa.Column('alpn_protocols', sa.String(512), nullable=True)
|
||||
)
|
|
@ -353,6 +353,7 @@ class Pool(base_models.BASE, base_models.IdMixin, base_models.ProjectMixin,
|
|||
tls_enabled = sa.Column(sa.Boolean, default=False, nullable=False)
|
||||
tls_ciphers = sa.Column(sa.String(2048), nullable=True)
|
||||
tls_versions = sa.Column(ScalarListType(), nullable=True)
|
||||
alpn_protocols = sa.Column(ScalarListType(), nullable=True)
|
||||
|
||||
# This property should be a unique list of any listeners that reference
|
||||
# this pool as its default_pool and any listeners referenced by enabled
|
||||
|
|
|
@ -184,6 +184,9 @@ def create_pool(pool_dict, lb_id=None):
|
|||
if pool_dict['tls_versions'] is None:
|
||||
pool_dict['tls_versions'] = (
|
||||
CONF.api_settings.default_pool_tls_versions)
|
||||
if pool_dict['alpn_protocols'] is None:
|
||||
pool_dict['alpn_protocols'] = (
|
||||
CONF.api_settings.default_pool_alpn_protocols)
|
||||
pool_dict[constants.PROVISIONING_STATUS] = constants.PENDING_CREATE
|
||||
pool_dict[constants.OPERATING_STATUS] = constants.OFFLINE
|
||||
return pool_dict
|
||||
|
|
|
@ -249,7 +249,9 @@ class SampleDriverDataModels(object):
|
|||
constants.CRL_CONTAINER_ID: self.pool_crl_container_ref,
|
||||
lib_consts.TLS_ENABLED: True,
|
||||
lib_consts.TLS_CIPHERS: None,
|
||||
lib_consts.TLS_VERSIONS: None
|
||||
lib_consts.TLS_VERSIONS: None,
|
||||
lib_consts.ALPN_PROTOCOLS:
|
||||
constants.AMPHORA_SUPPORTED_ALPN_PROTOCOLS
|
||||
}
|
||||
|
||||
self.test_pool1_dict.update(self._common_test_dict)
|
||||
|
@ -300,6 +302,8 @@ class SampleDriverDataModels(object):
|
|||
lib_consts.TLS_ENABLED: True,
|
||||
lib_consts.TLS_CIPHERS: None,
|
||||
lib_consts.TLS_VERSIONS: None,
|
||||
lib_consts.ALPN_PROTOCOLS:
|
||||
constants.AMPHORA_SUPPORTED_ALPN_PROTOCOLS
|
||||
}
|
||||
|
||||
self.provider_pool2_dict = copy.deepcopy(self.provider_pool1_dict)
|
||||
|
|
|
@ -45,7 +45,7 @@ class TestRootController(base_db_test.OctaviaDBTestBase):
|
|||
def test_api_versions(self):
|
||||
versions = self._get_versions_with_config()
|
||||
version_ids = tuple(v.get('id') for v in versions)
|
||||
self.assertEqual(24, len(version_ids))
|
||||
self.assertEqual(25, len(version_ids))
|
||||
self.assertIn('v2.0', version_ids)
|
||||
self.assertIn('v2.1', version_ids)
|
||||
self.assertIn('v2.2', version_ids)
|
||||
|
@ -70,6 +70,7 @@ class TestRootController(base_db_test.OctaviaDBTestBase):
|
|||
self.assertIn('v2.21', version_ids)
|
||||
self.assertIn('v2.22', version_ids)
|
||||
self.assertIn('v2.23', version_ids)
|
||||
self.assertIn('v2.24', version_ids)
|
||||
|
||||
# Each version should have a 'self' 'href' to the API version URL
|
||||
# [{u'rel': u'self', u'href': u'http://localhost/v2'}]
|
||||
|
|
|
@ -2623,3 +2623,73 @@ class TestPool(base.BaseAPITest):
|
|||
self.assertEqual(expect_error_msg,
|
||||
res.json['faultstring'])
|
||||
self.assert_correct_status(lb_id=self.lb_id)
|
||||
|
||||
def test_create_with_alpn(self):
|
||||
alpn_protocols = [lib_constants.ALPN_PROTOCOL_HTTP_2,
|
||||
lib_constants.ALPN_PROTOCOL_HTTP_1_1]
|
||||
api_pool = self.create_pool(
|
||||
self.lb_id,
|
||||
constants.PROTOCOL_HTTP,
|
||||
constants.LB_ALGORITHM_ROUND_ROBIN,
|
||||
listener_id=self.listener_id,
|
||||
tls_enabled=True,
|
||||
alpn_protocols=alpn_protocols).get(self.root_tag)
|
||||
self.assertEqual(alpn_protocols, api_pool['alpn_protocols'])
|
||||
|
||||
def test_create_with_alpn_negative(self):
|
||||
req_dict = {'protocol': constants.PROTOCOL_HTTP,
|
||||
'lb_algorithm': constants.LB_ALGORITHM_ROUND_ROBIN,
|
||||
'listener_id': self.listener_id,
|
||||
'tls_enabled': True,
|
||||
'alpn_protocols': [lib_constants.ALPN_PROTOCOL_HTTP_1_1,
|
||||
'invalid-proto']}
|
||||
res = self.post(self.POOLS_PATH, self._build_body(req_dict),
|
||||
status=400)
|
||||
fault = res.json['faultstring']
|
||||
self.assertIn(
|
||||
'Invalid input for field/attribute alpn_protocols', fault)
|
||||
self.assertIn('Value should be a valid ALPN protocol ID', fault)
|
||||
self.assert_correct_status(lb_id=self.lb_id)
|
||||
|
||||
def test_update_with_alpn(self):
|
||||
alpn_protocols_orig = [lib_constants.ALPN_PROTOCOL_HTTP_1_0]
|
||||
alpn_protocols = [lib_constants.ALPN_PROTOCOL_HTTP_2,
|
||||
lib_constants.ALPN_PROTOCOL_HTTP_1_1]
|
||||
pool = self.create_pool(
|
||||
self.lb_id,
|
||||
constants.PROTOCOL_HTTP,
|
||||
constants.LB_ALGORITHM_ROUND_ROBIN,
|
||||
listener_id=self.listener_id,
|
||||
tls_enabled=True,
|
||||
alpn_protocols=alpn_protocols_orig)
|
||||
self.set_lb_status(self.lb_id)
|
||||
pool_path = self.POOL_PATH.format(pool_id=pool['pool']['id'])
|
||||
get_pool = self.get(pool_path).json['pool']
|
||||
self.assertEqual(alpn_protocols_orig, get_pool.get('alpn_protocols'))
|
||||
self.put(pool_path,
|
||||
self._build_body({'alpn_protocols': alpn_protocols}))
|
||||
get_pool = self.get(pool_path).json['pool']
|
||||
self.assertEqual(alpn_protocols, get_pool.get('alpn_protocols'))
|
||||
|
||||
def test_update_with_alpn_negative(self):
|
||||
alpn_protocols_orig = [lib_constants.ALPN_PROTOCOL_HTTP_1_0]
|
||||
pool = self.create_pool(
|
||||
self.lb_id,
|
||||
constants.PROTOCOL_HTTP,
|
||||
constants.LB_ALGORITHM_ROUND_ROBIN,
|
||||
listener_id=self.listener_id,
|
||||
tls_enabled=True,
|
||||
alpn_protocols=alpn_protocols_orig)
|
||||
self.set_lb_status(self.lb_id)
|
||||
pool_path = self.POOL_PATH.format(pool_id=pool['pool']['id'])
|
||||
get_pool = self.get(pool_path).json['pool']
|
||||
self.assertEqual(alpn_protocols_orig, get_pool.get('alpn_protocols'))
|
||||
|
||||
req_dict = {'alpn_protocols': [
|
||||
lib_constants.ALPN_PROTOCOL_HTTP_1_1, 'invalid-proto']}
|
||||
res = self.put(self.POOLS_PATH, self._build_body(req_dict), status=400)
|
||||
fault = res.json['faultstring']
|
||||
self.assertIn(
|
||||
'Invalid input for field/attribute alpn_protocols', fault)
|
||||
self.assertIn('Value should be a valid ALPN protocol ID', fault)
|
||||
self.assert_correct_status(lb_id=self.lb_id)
|
||||
|
|
|
@ -186,7 +186,8 @@ class AllRepositoriesTest(base.OctaviaDBTestBase):
|
|||
'tags': ['test_tag'],
|
||||
'tls_certificate_id': uuidutils.generate_uuid(),
|
||||
'tls_enabled': False, 'tls_ciphers': None,
|
||||
'tls_versions': None}
|
||||
'tls_versions': None,
|
||||
'alpn_protocols': None}
|
||||
pool_dm = self.repos.create_pool_on_load_balancer(
|
||||
self.session, pool, listener_id=self.listener.id)
|
||||
pool_dm_dict = pool_dm.to_dict()
|
||||
|
@ -220,7 +221,8 @@ class AllRepositoriesTest(base.OctaviaDBTestBase):
|
|||
'tls_certificate_id': uuidutils.generate_uuid(),
|
||||
'tls_enabled': False,
|
||||
'tls_ciphers': None,
|
||||
'tls_versions': None}
|
||||
'tls_versions': None,
|
||||
'alpn_protocols': None}
|
||||
sp = {'type': constants.SESSION_PERSISTENCE_HTTP_COOKIE,
|
||||
'cookie_name': 'cookie_monster',
|
||||
'pool_id': pool['id'],
|
||||
|
@ -265,7 +267,8 @@ class AllRepositoriesTest(base.OctaviaDBTestBase):
|
|||
'provisioning_status': constants.ACTIVE,
|
||||
'tags': ['test_tag'], 'tls_enabled': False,
|
||||
'tls_ciphers': None,
|
||||
'tls_versions': None}
|
||||
'tls_versions': None,
|
||||
'alpn_protocols': None}
|
||||
pool_dm = self.repos.create_pool_on_load_balancer(
|
||||
self.session, pool, listener_id=self.listener.id)
|
||||
update_pool = {'protocol': constants.PROTOCOL_TCP, 'name': 'up_pool'}
|
||||
|
@ -301,7 +304,8 @@ class AllRepositoriesTest(base.OctaviaDBTestBase):
|
|||
'tags': ['test_tag'],
|
||||
'tls_certificate_id': uuidutils.generate_uuid(),
|
||||
'tls_enabled': False, 'tls_ciphers': None,
|
||||
'tls_versions': None}
|
||||
'tls_versions': None,
|
||||
'alpn_protocols': None}
|
||||
sp = {'type': constants.SESSION_PERSISTENCE_HTTP_COOKIE,
|
||||
'cookie_name': 'cookie_monster',
|
||||
'pool_id': pool['id'],
|
||||
|
@ -406,7 +410,8 @@ class AllRepositoriesTest(base.OctaviaDBTestBase):
|
|||
'id': uuidutils.generate_uuid(),
|
||||
'provisioning_status': constants.ACTIVE,
|
||||
'tls_enabled': False, 'tls_ciphers': None,
|
||||
'tls_versions': None}
|
||||
'tls_versions': None,
|
||||
'alpn_protocols': None}
|
||||
pool_dm = self.repos.create_pool_on_load_balancer(
|
||||
self.session, pool, listener_id=self.listener.id)
|
||||
update_pool = {'tls_certificate_id': uuidutils.generate_uuid()}
|
||||
|
|
|
@ -204,7 +204,8 @@ class TestAmphoraDriver(base.TestRpc):
|
|||
def test_pool_create(self, mock_cast):
|
||||
provider_pool = driver_dm.Pool(
|
||||
pool_id=self.sample_data.pool1_id,
|
||||
lb_algorithm=consts.LB_ALGORITHM_ROUND_ROBIN)
|
||||
lb_algorithm=consts.LB_ALGORITHM_ROUND_ROBIN,
|
||||
alpn_protocols=consts.AMPHORA_SUPPORTED_ALPN_PROTOCOLS)
|
||||
self.amp_driver.pool_create(provider_pool)
|
||||
payload = {consts.POOL_ID: self.sample_data.pool1_id}
|
||||
mock_cast.assert_called_with({}, 'create_pool', **payload)
|
||||
|
@ -220,6 +221,16 @@ class TestAmphoraDriver(base.TestRpc):
|
|||
provider_pool)
|
||||
mock_cast.assert_not_called()
|
||||
|
||||
@mock.patch('oslo_messaging.RPCClient.cast')
|
||||
def test_pool_create_unsupported_alpn(self, mock_cast):
|
||||
provider_pool = driver_dm.Pool(pool_id=self.sample_data.pool1_id)
|
||||
provider_pool.alpn_protocols = ['http/1.1', 'eureka']
|
||||
self.assertRaises(
|
||||
exceptions.UnsupportedOptionError,
|
||||
self.amp_driver.pool_create,
|
||||
provider_pool)
|
||||
mock_cast.assert_not_called()
|
||||
|
||||
@mock.patch('oslo_messaging.RPCClient.cast')
|
||||
def test_pool_delete(self, mock_cast):
|
||||
provider_pool = driver_dm.Pool(
|
||||
|
@ -269,6 +280,18 @@ class TestAmphoraDriver(base.TestRpc):
|
|||
provider_pool)
|
||||
mock_cast.assert_not_called()
|
||||
|
||||
@mock.patch('oslo_messaging.RPCClient.cast')
|
||||
def test_pool_update_unsupported_alpn(self, mock_cast):
|
||||
old_provider_pool = driver_dm.Pool(pool_id=self.sample_data.pool1_id)
|
||||
provider_pool = driver_dm.Pool(
|
||||
listener_id=self.sample_data.pool1_id,
|
||||
alpn_protocols=['http/1.1', 'eureka'])
|
||||
self.assertRaises(
|
||||
exceptions.UnsupportedOptionError,
|
||||
self.amp_driver.pool_update,
|
||||
old_provider_pool,
|
||||
provider_pool)
|
||||
|
||||
# Member
|
||||
@mock.patch('octavia.db.api.get_session')
|
||||
@mock.patch('octavia.db.repositories.PoolRepository.get')
|
||||
|
|
|
@ -206,9 +206,10 @@ class TestAmphoraDriver(base.TestRpc):
|
|||
def test_pool_create(self, mock_cast):
|
||||
provider_pool = driver_dm.Pool(
|
||||
pool_id=self.sample_data.pool1_id,
|
||||
lb_algorithm=consts.LB_ALGORITHM_ROUND_ROBIN)
|
||||
lb_algorithm=consts.LB_ALGORITHM_ROUND_ROBIN,
|
||||
alpn_protocols=consts.AMPHORA_SUPPORTED_ALPN_PROTOCOLS)
|
||||
self.amp_driver.pool_create(provider_pool)
|
||||
payload = {consts.POOL: provider_pool.to_dict()}
|
||||
payload = {consts.POOL: provider_pool.to_dict(recurse=True)}
|
||||
mock_cast.assert_called_with({}, 'create_pool', **payload)
|
||||
|
||||
@mock.patch('oslo_messaging.RPCClient.cast')
|
||||
|
@ -222,6 +223,16 @@ class TestAmphoraDriver(base.TestRpc):
|
|||
provider_pool)
|
||||
mock_cast.assert_not_called()
|
||||
|
||||
@mock.patch('oslo_messaging.RPCClient.cast')
|
||||
def test_pool_create_unsupported_alpn(self, mock_cast):
|
||||
provider_pool = driver_dm.Pool(pool_id=self.sample_data.pool1_id)
|
||||
provider_pool.alpn_protocols = ['http/1.1', 'eureka']
|
||||
self.assertRaises(
|
||||
exceptions.UnsupportedOptionError,
|
||||
self.amp_driver.pool_create,
|
||||
provider_pool)
|
||||
mock_cast.assert_not_called()
|
||||
|
||||
@mock.patch('oslo_messaging.RPCClient.cast')
|
||||
def test_pool_delete(self, mock_cast):
|
||||
provider_pool = driver_dm.Pool(
|
||||
|
@ -271,6 +282,18 @@ class TestAmphoraDriver(base.TestRpc):
|
|||
provider_pool)
|
||||
mock_cast.assert_not_called()
|
||||
|
||||
@mock.patch('oslo_messaging.RPCClient.cast')
|
||||
def test_pool_update_unsupported_alpn(self, mock_cast):
|
||||
old_provider_pool = driver_dm.Pool(pool_id=self.sample_data.pool1_id)
|
||||
provider_pool = driver_dm.Pool(
|
||||
listener_id=self.sample_data.pool1_id,
|
||||
alpn_protocols=['http/1.1', 'eureka'])
|
||||
self.assertRaises(
|
||||
exceptions.UnsupportedOptionError,
|
||||
self.amp_driver.pool_update,
|
||||
old_provider_pool,
|
||||
provider_pool)
|
||||
|
||||
# Member
|
||||
@mock.patch('octavia.db.api.get_session')
|
||||
@mock.patch('octavia.db.repositories.PoolRepository.get')
|
||||
|
|
|
@ -145,6 +145,13 @@ class TestPoolPOST(base.BaseTypesTest):
|
|||
self.assertRaises(exc.InvalidInput, wsme_json.fromjson, self._type,
|
||||
body)
|
||||
|
||||
def test_invalid_alpn_protocols(self):
|
||||
body = {"protocol": constants.PROTOCOL_HTTP,
|
||||
"loadbalancer_id": uuidutils.generate_uuid(),
|
||||
"alpn_protocols": ["bad", "boy"]}
|
||||
self.assertRaises(exc.InvalidInput, wsme_json.fromjson, self._type,
|
||||
body)
|
||||
|
||||
|
||||
class TestPoolPUT(base.BaseTypesTest):
|
||||
|
||||
|
@ -188,6 +195,11 @@ class TestPoolPUT(base.BaseTypesTest):
|
|||
self.assertRaises(exc.InvalidInput, wsme_json.fromjson, self._type,
|
||||
body)
|
||||
|
||||
def test_invalid_alpn_protocols(self):
|
||||
body = {"alpn_protocols": ["bad", "boy"]}
|
||||
self.assertRaises(exc.InvalidInput, wsme_json.fromjson, self._type,
|
||||
body)
|
||||
|
||||
|
||||
class TestSessionPersistencePOST(base.BaseTypesTest, TestSessionPersistence):
|
||||
|
||||
|
|
|
@ -1100,14 +1100,15 @@ class TestHaproxyCfg(base.TestCase):
|
|||
" timeout server 50000\n"
|
||||
" server sample_member_id_1 10.0.0.99:82 weight 13 "
|
||||
"check inter 30s fall 3 rise 2 cookie sample_member_id_1 "
|
||||
"{opts}\n"
|
||||
"{opts} alpn {alpn}\n"
|
||||
" server sample_member_id_2 10.0.0.98:82 weight 13 "
|
||||
"check inter 30s fall 3 rise 2 cookie sample_member_id_2 "
|
||||
"{opts}\n\n").format(
|
||||
"{opts} alpn {alpn}\n\n").format(
|
||||
maxconn=constants.HAPROXY_DEFAULT_MAXCONN,
|
||||
opts="ssl crt %s verify none sni ssl_fc_sni" % cert_file_path +
|
||||
" ciphers " + constants.CIPHERS_OWASP_SUITE_B +
|
||||
" no-sslv3 no-tlsv10 no-tlsv11")
|
||||
" no-sslv3 no-tlsv10 no-tlsv11",
|
||||
alpn=",".join(constants.AMPHORA_SUPPORTED_ALPN_PROTOCOLS))
|
||||
rendered_obj = self.jinja_cfg.render_loadbalancer_obj(
|
||||
sample_configs_combined.sample_amphora_tuple(),
|
||||
[sample_configs_combined.sample_listener_tuple(
|
||||
|
@ -1137,13 +1138,14 @@ class TestHaproxyCfg(base.TestCase):
|
|||
" timeout server 50000\n"
|
||||
" server sample_member_id_1 10.0.0.99:82 weight 13 "
|
||||
"check inter 30s fall 3 rise 2 cookie sample_member_id_1 "
|
||||
"{opts}\n"
|
||||
"{opts} alpn {alpn}\n"
|
||||
" server sample_member_id_2 10.0.0.98:82 weight 13 "
|
||||
"check inter 30s fall 3 rise 2 cookie sample_member_id_2 "
|
||||
"{opts}\n\n").format(
|
||||
"{opts} alpn {alpn}\n\n").format(
|
||||
maxconn=constants.HAPROXY_DEFAULT_MAXCONN,
|
||||
opts="ssl crt %s verify none sni ssl_fc_sni" % cert_file_path +
|
||||
" ciphers " + constants.CIPHERS_OWASP_SUITE_B)
|
||||
" ciphers " + constants.CIPHERS_OWASP_SUITE_B,
|
||||
alpn=",".join(constants.AMPHORA_SUPPORTED_ALPN_PROTOCOLS))
|
||||
rendered_obj = self.jinja_cfg.render_loadbalancer_obj(
|
||||
sample_configs_combined.sample_amphora_tuple(),
|
||||
[sample_configs_combined.sample_listener_tuple(
|
||||
|
@ -1174,13 +1176,14 @@ class TestHaproxyCfg(base.TestCase):
|
|||
" timeout server 50000\n"
|
||||
" server sample_member_id_1 10.0.0.99:82 weight 13 "
|
||||
"check inter 30s fall 3 rise 2 cookie sample_member_id_1 "
|
||||
"{opts}\n"
|
||||
"{opts} alpn {alpn}\n"
|
||||
" server sample_member_id_2 10.0.0.98:82 weight 13 "
|
||||
"check inter 30s fall 3 rise 2 cookie sample_member_id_2 "
|
||||
"{opts}\n\n").format(
|
||||
"{opts} alpn {alpn}\n\n").format(
|
||||
maxconn=constants.HAPROXY_DEFAULT_MAXCONN,
|
||||
opts="ssl crt %s verify none sni ssl_fc_sni" % cert_file_path +
|
||||
" no-sslv3 no-tlsv10 no-tlsv11")
|
||||
" no-sslv3 no-tlsv10 no-tlsv11",
|
||||
alpn=",".join(constants.AMPHORA_SUPPORTED_ALPN_PROTOCOLS))
|
||||
rendered_obj = self.jinja_cfg.render_loadbalancer_obj(
|
||||
sample_configs_combined.sample_amphora_tuple(),
|
||||
[sample_configs_combined.sample_listener_tuple(
|
||||
|
@ -1193,7 +1196,7 @@ class TestHaproxyCfg(base.TestCase):
|
|||
sample_configs_combined.sample_base_expected_config(backend=be),
|
||||
rendered_obj)
|
||||
|
||||
def test_render_template_pool_cert_no_ciphers_or_versions(self):
|
||||
def test_render_template_pool_cert_no_ciphers_or_versions_or_alpn(self):
|
||||
cert_file_path = os.path.join(self.jinja_cfg.base_crt_dir,
|
||||
'sample_listener_id_1', 'fake path')
|
||||
be = ("backend sample_pool_id_1:sample_listener_id_1\n"
|
||||
|
@ -1218,7 +1221,8 @@ class TestHaproxyCfg(base.TestCase):
|
|||
rendered_obj = self.jinja_cfg.render_loadbalancer_obj(
|
||||
sample_configs_combined.sample_amphora_tuple(),
|
||||
[sample_configs_combined.sample_listener_tuple(
|
||||
pool_cert=True, tls_enabled=True, backend_tls_versions=None)],
|
||||
pool_cert=True, tls_enabled=True, backend_tls_versions=None,
|
||||
backend_alpn_protocols=None)],
|
||||
tls_certs={
|
||||
'sample_pool_id_1':
|
||||
{'client_cert': cert_file_path,
|
||||
|
@ -1227,6 +1231,32 @@ class TestHaproxyCfg(base.TestCase):
|
|||
sample_configs_combined.sample_base_expected_config(backend=be),
|
||||
rendered_obj)
|
||||
|
||||
def test_render_template_pool_no_alpn(self):
|
||||
be = ("backend sample_pool_id_1:sample_listener_id_1\n"
|
||||
" mode http\n"
|
||||
" balance roundrobin\n"
|
||||
" cookie SRV insert indirect nocache\n"
|
||||
" timeout check 31s\n"
|
||||
" option httpchk GET /index.html HTTP/1.0\\r\\n\n"
|
||||
" http-check expect rstatus 418\n"
|
||||
" fullconn {maxconn}\n"
|
||||
" option allbackups\n"
|
||||
" timeout connect 5000\n"
|
||||
" timeout server 50000\n"
|
||||
" server sample_member_id_1 10.0.0.99:82 weight 13 "
|
||||
"check inter 30s fall 3 rise 2 cookie sample_member_id_1\n"
|
||||
" server sample_member_id_2 10.0.0.98:82 weight 13 "
|
||||
"check inter 30s fall 3 rise 2 cookie sample_member_id_2"
|
||||
"\n\n").format(
|
||||
maxconn=constants.HAPROXY_DEFAULT_MAXCONN)
|
||||
rendered_obj = self.jinja_cfg.render_loadbalancer_obj(
|
||||
sample_configs_combined.sample_amphora_tuple(),
|
||||
[sample_configs_combined.sample_listener_tuple(
|
||||
backend_alpn_protocols=None)])
|
||||
self.assertEqual(
|
||||
sample_configs_combined.sample_base_expected_config(backend=be),
|
||||
rendered_obj)
|
||||
|
||||
def test_render_template_with_full_pool_cert(self):
|
||||
pool_client_cert = '/foo/cert.pem'
|
||||
pool_ca_cert = '/foo/ca.pem'
|
||||
|
@ -1244,16 +1274,17 @@ class TestHaproxyCfg(base.TestCase):
|
|||
" timeout server 50000\n"
|
||||
" server sample_member_id_1 10.0.0.99:82 weight 13 "
|
||||
"check inter 30s fall 3 rise 2 cookie sample_member_id_1 "
|
||||
"{opts}\n"
|
||||
"{opts} alpn {alpn}\n"
|
||||
" server sample_member_id_2 10.0.0.98:82 weight 13 "
|
||||
"check inter 30s fall 3 rise 2 cookie sample_member_id_2 "
|
||||
"{opts}\n\n").format(
|
||||
"{opts} alpn {alpn}\n\n").format(
|
||||
maxconn=constants.HAPROXY_DEFAULT_MAXCONN,
|
||||
opts="%s %s %s %s %s %s" % (
|
||||
"ssl", "crt", pool_client_cert,
|
||||
"ca-file %s" % pool_ca_cert,
|
||||
"crl-file %s" % pool_crl,
|
||||
"verify required sni ssl_fc_sni no-sslv3 no-tlsv10 no-tlsv11"))
|
||||
"verify required sni ssl_fc_sni no-sslv3 no-tlsv10 no-tlsv11"),
|
||||
alpn=",".join(constants.AMPHORA_SUPPORTED_ALPN_PROTOCOLS))
|
||||
rendered_obj = self.jinja_cfg.render_loadbalancer_obj(
|
||||
sample_configs_combined.sample_amphora_tuple(),
|
||||
[sample_configs_combined.sample_listener_tuple(
|
||||
|
|
|
@ -607,7 +607,9 @@ def sample_listener_tuple(proto=None, monitor=True, alloc_default_pool=True,
|
|||
alpn_protocols=constants.
|
||||
AMPHORA_SUPPORTED_ALPN_PROTOCOLS,
|
||||
sample_default_pool=1,
|
||||
pool_enabled=True):
|
||||
pool_enabled=True,
|
||||
backend_alpn_protocols=constants.
|
||||
AMPHORA_SUPPORTED_ALPN_PROTOCOLS):
|
||||
proto = 'HTTP' if proto is None else proto
|
||||
if be_proto is None:
|
||||
be_proto = 'HTTP' if proto == 'TERMINATED_HTTPS' else proto
|
||||
|
@ -645,7 +647,8 @@ def sample_listener_tuple(proto=None, monitor=True, alloc_default_pool=True,
|
|||
listener_id='sample_listener_id_1',
|
||||
tls_ciphers=backend_tls_ciphers,
|
||||
tls_versions=backend_tls_versions,
|
||||
enabled=pool_enabled),
|
||||
enabled=pool_enabled,
|
||||
alpn_protocols=backend_alpn_protocols),
|
||||
sample_pool_tuple(
|
||||
proto=be_proto, monitor=monitor, persistence=persistence,
|
||||
persistence_type=persistence_type,
|
||||
|
@ -657,7 +660,8 @@ def sample_listener_tuple(proto=None, monitor=True, alloc_default_pool=True,
|
|||
listener_id='sample_listener_id_1',
|
||||
tls_ciphers=backend_tls_ciphers,
|
||||
tls_versions=None,
|
||||
enabled=pool_enabled)]
|
||||
enabled=pool_enabled,
|
||||
alpn_protocols=backend_alpn_protocols)]
|
||||
l7policies = [
|
||||
sample_l7policy_tuple('sample_l7policy_id_1', sample_policy=1),
|
||||
sample_l7policy_tuple('sample_l7policy_id_2', sample_policy=2),
|
||||
|
@ -683,7 +687,8 @@ def sample_listener_tuple(proto=None, monitor=True, alloc_default_pool=True,
|
|||
listener_id='sample_listener_id_1',
|
||||
tls_ciphers=backend_tls_ciphers,
|
||||
tls_versions=backend_tls_versions,
|
||||
enabled=pool_enabled)]
|
||||
enabled=pool_enabled,
|
||||
alpn_protocols=backend_alpn_protocols)]
|
||||
l7policies = []
|
||||
listener = in_listener(
|
||||
id=id,
|
||||
|
@ -799,18 +804,21 @@ def sample_pool_tuple(listener_id=None, proto=None, monitor=True,
|
|||
tls_ciphers=constants.CIPHERS_OWASP_SUITE_B,
|
||||
tls_versions=constants.TLS_VERSIONS_OWASP_SUITE_B,
|
||||
lb_algorithm=constants.LB_ALGORITHM_ROUND_ROBIN,
|
||||
enabled=True):
|
||||
enabled=True,
|
||||
alpn_protocols=constants.
|
||||
AMPHORA_SUPPORTED_ALPN_PROTOCOLS):
|
||||
proto = 'HTTP' if proto is None else proto
|
||||
if not tls_enabled:
|
||||
tls_ciphers = None
|
||||
tls_versions = None
|
||||
alpn_protocols = None
|
||||
monitor_proto = proto if monitor_proto is None else monitor_proto
|
||||
in_pool = collections.namedtuple(
|
||||
'pool', 'id, protocol, lb_algorithm, members, health_monitor, '
|
||||
'session_persistence, enabled, operating_status, '
|
||||
'tls_certificate_id, ca_tls_certificate_id, '
|
||||
'crl_container_id, tls_enabled, tls_ciphers, '
|
||||
'tls_versions, provisioning_status, ' +
|
||||
'tls_versions, provisioning_status, alpn_protocols, ' +
|
||||
constants.HTTP_REUSE)
|
||||
if (proto == constants.PROTOCOL_UDP and
|
||||
persistence_type == constants.SESSION_PERSISTENCE_SOURCE_IP):
|
||||
|
@ -867,7 +875,8 @@ def sample_pool_tuple(listener_id=None, proto=None, monitor=True,
|
|||
tls_enabled=tls_enabled,
|
||||
tls_ciphers=tls_ciphers,
|
||||
tls_versions=tls_versions,
|
||||
provisioning_status=provisioning_status)
|
||||
provisioning_status=provisioning_status,
|
||||
alpn_protocols=alpn_protocols)
|
||||
|
||||
|
||||
def sample_member_tuple(id, ip, enabled=True, operating_status='ACTIVE',
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
---
|
||||
features:
|
||||
- |
|
||||
Added support for TLS extension Application Layer Protocol Negotiation
|
||||
(ALPN) to TLS-enabled pools. A new parameter ``alpn_protocols`` was added
|
||||
to the Pool API.
|
||||
- |
|
||||
Octavia provider drivers can now be extended to support HTTP/2 between
|
||||
TLS-enabled pools and members.
|
||||
- |
|
||||
Added HTTP/2 over TLS support via ALPN protocol negotiation to the
|
||||
amphora provider driver for TLS-enabled pools.
|
||||
- |
|
||||
The Octavia amphora driver now supports gRPC protocol when HTTP/2 is
|
||||
enabled for TERMINATED_HTTPS listeners and TLS-enabled pools, and the
|
||||
amphora image is using HAProxy 2.0 or newer.
|
|
@ -41,7 +41,7 @@ castellan>=0.16.0 # Apache-2.0
|
|||
tenacity>=5.0.4 # Apache-2.0
|
||||
distro>=1.2.0 # Apache-2.0
|
||||
jsonschema>=3.2.0 # MIT
|
||||
octavia-lib>=2.2.0 # Apache-2.0
|
||||
octavia-lib>=2.3.0 # Apache-2.0
|
||||
netaddr>=0.7.19 # BSD
|
||||
simplejson>=3.13.2 # MIT
|
||||
setproctitle>=1.1.10 # BSD
|
||||
|
|
Loading…
Reference in New Issue