def supported_ssl_ciphers( self, new_config_arguments: Dict[str, str], config_path: str) -> List[str]: """ Finds the line that looks like: ssl_ciphers EECDH+AES256:RSA+AES256:EECDH+AES128:RSA+AES128:EECDH+3DES:RSA+3DES:!MD5; and returns the list of ciphers. Args: new_config_arguments: Arguments which are added to the 'standard' set of arguments before generating configuration files. config_path: A path to configuration file which should be examined for ssl_ciphers configuration. """ arguments = make_arguments(new_arguments=new_config_arguments) generated = gen.generate(arguments=arguments) package = generated.templates['dcos-config.yaml']['package'] [config] = [item for item in package if item['path'] == config_path] [ssl_ciphers_line] = [ line for line in config['content'].split('\n') if # We strip whitespace from the beginning of the line as NGINX # configuration lines can start with whitespace. line.lstrip().startswith('ssl_ciphers ') ] ssl_ciphers_line = ssl_ciphers_line.strip(';') ciphers = ssl_ciphers_line.split()[1:] return ciphers
def test_default(self): """ By default, the configuration specifies certain TLS settings. This test is a sanity check for the configuration template logic rather than a particularly useful feature test. """ config_path = '/etc/adminrouter-tls.conf' arguments = make_arguments(new_arguments={}) generated = gen.generate(arguments=arguments) package = generated.templates['dcos-config.yaml']['package'] [config] = [item for item in package if item['path'] == config_path] expected_configuration = dedent( """\ # Ref: https://github.com/cloudflare/sslconfig/blob/master/conf # Modulo ChaCha20 cipher. ssl_ciphers EECDH+AES128:RSA+AES128:EECDH+AES256:RSA+AES256:EECDH+3DES:RSA+3DES:!MD5; ssl_prefer_server_ciphers on; # To manually test which TLS versions are enabled on a node, use # `openssl` commands. # # See comments on https://jira.mesosphere.com/browse/DCOS-13437 for more # details. ssl_protocols TLSv1.1 TLSv1.2; """ ) assert config['content'] == expected_configuration
def supported_ssl_protocols(self, new_config_arguments) -> List[str]: """ This finds a line which looks like the following: ssl protocols TLSv1, TLSv1.1; in the Admin Router TLS configuration. It then returns the listed protocols. Args: new_config_arguments: Arguments which are added to the 'standard' set of arguments before generating configuration files. Returns: A ``list`` of supported SSL protocols. """ arguments = make_arguments(new_arguments=new_config_arguments) generated = gen.generate(arguments=arguments) package = generated.templates['dcos-config.yaml']['package'] config_path = '/etc/adminrouter-tls.conf' [config] = [item for item in package if item['path'] == config_path] [ssl_protocols_line] = [ line for line in config['content'].split('\n') if # We strip whitespace from the beginning of the line as NGINX # configuration lines can start with whitespace. line.lstrip().startswith('ssl_protocols ') ] ssl_protocols_line = ssl_protocols_line.strip(';') protocols = ssl_protocols_line.split()[1:] return protocols
def supported_ssl_ciphers(self, new_config_arguments: Dict[str, str], config_path: str) -> List[str]: """ Finds the line that looks like: ssl_ciphers EECDH+AES256:RSA+AES256:EECDH+AES128:RSA+AES128:!MD5:!3DES; and returns the list of ciphers. Args: new_config_arguments: Arguments which are added to the 'standard' set of arguments before generating configuration files. config_path: A path to configuration file which should be examined for ssl_ciphers configuration. """ arguments = make_arguments(new_arguments=new_config_arguments) generated = gen.generate(arguments=arguments) package = generated.templates['dcos-config.yaml']['package'] [config] = [item for item in package if item['path'] == config_path] [ssl_ciphers_line] = [ line for line in config['content'].split('\n') if # We strip whitespace from the beginning of the line as NGINX # configuration lines can start with whitespace. line.lstrip().startswith('ssl_ciphers ') ] ssl_ciphers_line = ssl_ciphers_line.strip(';') ciphers = ssl_ciphers_line.split()[1:] return ciphers
def test_master_default(self): """ Test that Master Admin Router config file has the correct default `ssl_ciphers` and `ssl_protocols` values. Defaults are present in `dcos-config.yaml` file and in `calc.py`. """ config_path = '/etc/adminrouter-tls-master.conf' arguments = make_arguments(new_arguments={}) generated = gen.generate(arguments=arguments) package = generated.templates['dcos-config.yaml']['package'] [config] = [item for item in package if item['path'] == config_path] expected_configuration = dedent("""\ # Ref: https://github.com/cloudflare/sslconfig/blob/master/conf # Modulo ChaCha20 cipher and 3DES bulk encryption algorithm. # For 3DES see https://jira.mesosphere.com/browse/DCOS-21958 ssl_ciphers EECDH+AES128:RSA+AES128:EECDH+AES256:RSA+AES256:!MD5:!3DES; ssl_prefer_server_ciphers on; # To manually test which TLS versions are enabled on a node, use # `openssl` commands. # # See comments on https://jira.mesosphere.com/browse/DCOS-13437 for more # details. ssl_protocols TLSv1.2; """) assert config['content'] == expected_configuration
def test_master(self, adminrouter_tls_1_0_enabled, tls_versions): """ Test that Master Admin Router config file has the correct content. """ config_path = '/etc/adminrouter-tls-master.conf' arguments = make_arguments({ 'adminrouter_tls_1_0_enabled': adminrouter_tls_1_0_enabled, }) generated = gen.generate(arguments=arguments) package = generated.templates['dcos-config.yaml']['package'] [config] = [item for item in package if item['path'] == config_path] expected_configuration = dedent( """\ # Ref: https://github.com/cloudflare/sslconfig/blob/master/conf # Modulo ChaCha20 cipher. ssl_ciphers EECDH+AES128:RSA+AES128:EECDH+AES256:RSA+AES256:EECDH+3DES:RSA+3DES:!MD5; ssl_prefer_server_ciphers on; # To manually test which TLS versions are enabled on a node, use # `openssl` commands. # # See comments on https://jira.mesosphere.com/browse/DCOS-13437 for more # details. ssl_protocols {tls_versions}; """.format(tls_versions=tls_versions) ) assert config['content'] == expected_configuration
def supported_tls_protocols_ar_master( self, new_config_arguments: Dict[str, str]) -> List[str]: """ This finds a line which looks like the following: ssl_protocols TLSv1, TLSv1.1; in the Admin Router TLS configuration. It then returns the listed protocols. Args: new_config_arguments: Arguments which are added to the 'standard' set of arguments before generating configuration files. Returns: A list of supported TLS protocols. """ arguments = make_arguments(new_arguments=new_config_arguments) generated = gen.generate(arguments=arguments) package = generated.templates['dcos-config.yaml']['package'] config_path = '/etc/adminrouter-tls-master.conf' [config] = [item for item in package if item['path'] == config_path] [ssl_protocols_line] = [ line for line in config['content'].split('\n') if # We strip whitespace from the beginning of the line as NGINX # configuration lines can start with whitespace. line.lstrip().startswith('ssl_protocols ') ] ssl_protocols_line = ssl_protocols_line.strip(';') protocols = ssl_protocols_line.split()[1:] return protocols
def test_master_default(self): """ Test that Master Admin Router config file has the correct default `ssl_ciphers` and `ssl_protocols` values. Defaults are present in `dcos-config.yaml` file and in `calc.py`. """ config_path = '/etc/adminrouter-tls-master.conf' arguments = make_arguments(new_arguments={}) generated = gen.generate(arguments=arguments) package = generated.templates['dcos-config.yaml']['package'] [config] = [item for item in package if item['path'] == config_path] expected_configuration = dedent( """\ # Ref: https://github.com/cloudflare/sslconfig/blob/master/conf # Modulo ChaCha20 cipher and 3DES bulk encryption algorithm. # For 3DES see https://jira.mesosphere.com/browse/DCOS-21958 ssl_ciphers EECDH+AES128:RSA+AES128:EECDH+AES256:RSA+AES256:!MD5:!3DES; ssl_prefer_server_ciphers on; # To manually test which TLS versions are enabled on a node, use # `openssl` commands. # # See comments on https://jira.mesosphere.com/browse/DCOS-13437 for more # details. ssl_protocols TLSv1.2; """ ) assert config['content'] == expected_configuration
def test_master(self, adminrouter_tls_1_0_enabled, tls_versions): """ Test that Master Admin Router config file has the correct content. """ config_path = '/etc/adminrouter-tls-master.conf' arguments = make_arguments({ 'adminrouter_tls_1_0_enabled': adminrouter_tls_1_0_enabled, }) generated = gen.generate(arguments=arguments) package = generated.templates['dcos-config.yaml']['package'] [config] = [item for item in package if item['path'] == config_path] expected_configuration = dedent("""\ # Ref: https://github.com/cloudflare/sslconfig/blob/master/conf # Modulo ChaCha20 cipher. ssl_ciphers EECDH+AES128:RSA+AES128:EECDH+AES256:RSA+AES256:EECDH+3DES:RSA+3DES:!MD5; ssl_prefer_server_ciphers on; # To manually test which TLS versions are enabled on a node, use # `openssl` commands. # # See comments on https://jira.mesosphere.com/browse/DCOS-13437 for more # details. ssl_protocols {tls_versions}; """.format(tls_versions=tls_versions)) assert config['content'] == expected_configuration
def test_adminrouter_ui_x_frame_options_validation(value): new_arguments = {'adminrouter_x_frame_options': value} expected_error_msg = ( 'X-Frame-Options must be set to one of DENY, SAMEORIGIN, ALLOW-FROM' ) result = gen.validate(arguments=make_arguments(new_arguments)) assert result['status'] == 'errors' assert result['errors']['adminrouter_x_frame_options']['message'] == expected_error_msg
def test_adminrouter_ui_x_frame_options_validation(value): new_arguments = {'adminrouter_x_frame_options': value} expected_error_msg = ( 'X-Frame-Options must be set to one of DENY, SAMEORIGIN, ALLOW-FROM') result = gen.validate(arguments=make_arguments(new_arguments)) assert result['status'] == 'errors' assert result['errors']['adminrouter_x_frame_options'][ 'message'] == expected_error_msg
def test_fault_domain_disabled(): arguments = make_arguments(new_arguments={ 'fault_domain_detect_filename': pkg_resources.resource_filename('gen', 'fault-domain-detect/aws.sh') }) generated = gen.generate(arguments=arguments) assert generated.arguments['fault_domain_enabled'] == 'false' assert 'fault_domain_detect_contents' not in generated.arguments
def validate_error_multikey(new_arguments, keys, message, unset=None): assert gen.validate(arguments=make_arguments(new_arguments)) == { 'status': 'errors', 'errors': {key: { 'message': message } for key in keys}, 'unset': set() if unset is None else unset, }
def test_fault_domain_disabled(): arguments = make_arguments(new_arguments={ 'fault_domain_detect_filename': pkg_resources.resource_filename('gen', 'fault-domain-detect/aws.sh') }) generated = gen.generate(arguments=arguments) assert generated.arguments['fault_domain_enabled'] == 'false' assert 'fault_domain_detect_contents' not in generated.arguments
def test_exhibitor_tls_initialize_prints_errors(capsys): gen.generate(arguments=make_arguments({ 'platform': 'onprem', 'exhibitor_tls_enabled': 'true', })) expected_message = ( '[gen.exhibitor_tls_bootstrap] not bootstrapping ' 'exhibitor CA: Exhibitor security is an enterprise feature') assert expected_message in capsys.readouterr().out
def test_exhibitor_admin_password_obscured(): var_name = 'exhibitor_admin_password' var_value = 'secret' generated = gen.generate(make_arguments(new_arguments={var_name: var_value})) assert var_name not in json.loads(generated.arguments['expanded_config']) assert json.loads(generated.arguments['expanded_config_full'])[var_name] == var_value assert json.loads(generated.arguments['user_arguments'])[var_name] == '**HIDDEN**' assert json.loads(generated.arguments['user_arguments_full'])[var_name] == var_value assert yaml.load(generated.arguments['config_yaml'])[var_name] == '**HIDDEN**' assert yaml.load(generated.arguments['config_yaml_full'])[var_name] == var_value
def test_exhibitor_tls_initialize_fail(): with pytest.raises(ExhibitorTLSBootstrapError) as exc: gen.generate(arguments=make_arguments({ 'platform': 'onprem', 'exhibitor_tls_enabled': 'false', 'exhibitor_tls_required': 'true', })) print(exc.value.errors) assert exc.value.errors == [ 'Exhibitor security is disabled', 'Exhibitor security is an enterprise feature', 'CA init in gen is only supported when using a remote bootstrap node', ] with pytest.raises(ExhibitorTLSBootstrapError) as exc: gen.generate(arguments=make_arguments({ 'platform': 'onprem', 'exhibitor_tls_enabled': 'true', 'exhibitor_tls_required': 'true', })) assert exc.value.errors == [ 'Exhibitor security is an enterprise feature', 'CA init in gen is only supported when using a remote bootstrap node', ] with pytest.raises(ExhibitorTLSBootstrapError) as exc: gen.generate(arguments=make_arguments({ 'platform': 'onprem', 'exhibitor_tls_enabled': 'true', 'exhibitor_tls_required': 'true', 'master_discovery': 'master_http_loadbalancer', 'exhibitor_address': 'http://foobar', 'num_masters': '5', })) assert exc.value.errors == [ 'Only static master discovery is supported', 'Exhibitor security is an enterprise feature', 'CA init in gen is only supported when using a remote bootstrap node', ]
def test_exhibitor_admin_password_obscured(): var_name = 'exhibitor_admin_password' var_value = 'secret' generated = gen.generate(make_arguments(new_arguments={var_name: var_value})) assert var_name not in json.loads(generated.arguments['expanded_config']) assert json.loads(generated.arguments['expanded_config_full'])[var_name] == var_value assert json.loads(generated.arguments['user_arguments'])[var_name] == '**HIDDEN**' assert json.loads(generated.arguments['user_arguments_full'])[var_name] == var_value assert yaml.load(generated.arguments['config_yaml'])[var_name] == '**HIDDEN**' assert yaml.load(generated.arguments['config_yaml_full'])[var_name] == var_value
def test_adminrouter_ui_x_frame_options_default(): """ Test that Master Admin Router config file has the correct default `X-Frame-Options` value. Defaults are present in `calc.py`. """ config_path = '/etc_master/adminrouter-ui-security.conf' arguments = make_arguments(new_arguments={}) generated = gen.generate(arguments=arguments) package = generated.templates['dcos-config.yaml']['package'] [config] = [item for item in package if item['path'] == config_path] expected_configuration = dedent("""\ # Browser security settings for the DC/OS UI add_header X-Frame-Options "DENY"; """) assert config['content'] == expected_configuration
def test_no_tls_version_enabled(self): """ Not setting the `adminrouter_tls_version_override` or any of the TLS version configuration options results in error. """ new_arguments = {'adminrouter_tls_1_0_enabled': 'false', 'adminrouter_tls_1_1_enabled': 'false', 'adminrouter_tls_1_2_enabled': 'false'} expected_error_msg = ( 'At least one of adminrouter_tls_1_0_enabled, ' 'adminrouter_tls_1_1_enabled and adminrouter_tls_1_2_enabled must ' "be set to 'true'." ) result = gen.validate(arguments=make_arguments(new_arguments)) assert result['status'] == 'errors' key = 'adminrouter_tls_1_2_enabled' assert result['errors'][key]['message'] == expected_error_msg
def test_adminrouter_ui_x_frame_options_custom(value): """ Test for all 3 allowed values See: https://tools.ietf.org/html/rfc7034#section-2.1 """ config_path = '/etc_master/adminrouter-ui-security.conf' arguments = make_arguments(new_arguments={ 'adminrouter_x_frame_options': value, }) generated = gen.generate(arguments=arguments) package = generated.templates['dcos-config.yaml']['package'] [config] = [item for item in package if item['path'] == config_path] expected_configuration = dedent("""\ # Browser security settings for the DC/OS UI add_header X-Frame-Options "{value}"; """.format(value=value)) assert config['content'] == expected_configuration
def test_adminrouter_ui_x_frame_options_default(): """ Test that Master Admin Router config file has the correct default `X-Frame-Options` value. Defaults are present in `calc.py`. """ config_path = '/etc_master/adminrouter-ui-security.conf' arguments = make_arguments(new_arguments={}) generated = gen.generate(arguments=arguments) package = generated.templates['dcos-config.yaml']['package'] [config] = [item for item in package if item['path'] == config_path] expected_configuration = dedent( """\ # Browser security settings for the DC/OS UI add_header X-Frame-Options "DENY"; """ ) assert config['content'] == expected_configuration
def test_no_tls_version_enabled(self): """ Not setting the `adminrouter_tls_version_override` or any of the TLS version configuration options results in error. """ new_arguments = { 'adminrouter_tls_1_0_enabled': 'false', 'adminrouter_tls_1_1_enabled': 'false', 'adminrouter_tls_1_2_enabled': 'false' } expected_error_msg = ( 'At least one of adminrouter_tls_1_0_enabled, ' 'adminrouter_tls_1_1_enabled and adminrouter_tls_1_2_enabled must ' "be set to 'true'.") result = gen.validate(arguments=make_arguments(new_arguments)) assert result['status'] == 'errors' key = 'adminrouter_tls_1_2_enabled' assert result['errors'][key]['message'] == expected_error_msg
def test_edited_ip_detect_script_yields_new_packages(): with tempfile.NamedTemporaryFile() as f: arguments = make_arguments(new_arguments={'ip_detect_filename': f.name}) f.write('initial script contents\n'.encode('utf-8')) f.flush() initial_cluster_packages = gen.generate(arguments).cluster_packages # Running genconf with the same config yields the same set of packages. initial_cluster_packages_rerun = gen.generate(arguments).cluster_packages assert initial_cluster_packages == initial_cluster_packages_rerun f.seek(0) f.truncate() f.write('edited script contents\n'.encode('utf-8')) f.flush() edited_cluster_packages = gen.generate(arguments).cluster_packages # Running genconf with an edited IP detect script yields a new set of packages. assert initial_cluster_packages != edited_cluster_packages
def test_agent_default(self): """ Test that Agent Admin Router config file has the correct `ssl_ciphers` and `ssl_protocols` values. It is not possible to override these with any configuration parameters. """ config_path = '/etc/adminrouter-tls-agent.conf' arguments = make_arguments(new_arguments={}) generated = gen.generate(arguments=arguments) package = generated.templates['dcos-config.yaml']['package'] [config] = [item for item in package if item['path'] == config_path] expected_configuration = dedent("""\ # Note that Agent Admin Router only serves cluster-internal clients. Hence, # browser compatibility is not a criterion for the TLS cipher suite selection. ssl_ciphers EECDH+AES128:RSA+AES128:EECDH+AES256:RSA+AES256:!MD5; ssl_prefer_server_ciphers on; ssl_protocols TLSv1.2; """) assert config['content'] == expected_configuration
def test_edited_ip_detect_script_yields_new_packages(): with tempfile.NamedTemporaryFile() as f: arguments = make_arguments(new_arguments={'ip_detect_filename': f.name}) f.write('initial script contents\n'.encode('utf-8')) f.flush() initial_cluster_packages = gen.generate(arguments).cluster_packages # Running genconf with the same config yields the same set of packages. initial_cluster_packages_rerun = gen.generate(arguments).cluster_packages assert initial_cluster_packages == initial_cluster_packages_rerun f.seek(0) f.truncate() f.write('edited script contents\n'.encode('utf-8')) f.flush() edited_cluster_packages = gen.generate(arguments).cluster_packages # Running genconf with an edited IP detect script yields a new set of packages. assert initial_cluster_packages != edited_cluster_packages
def test_adminrouter_ui_x_frame_options_custom(value): """ Test for all 3 allowed values See: https://tools.ietf.org/html/rfc7034#section-2.1 """ config_path = '/etc_master/adminrouter-ui-security.conf' arguments = make_arguments(new_arguments={ 'adminrouter_x_frame_options': value, }) generated = gen.generate(arguments=arguments) package = generated.templates['dcos-config.yaml']['package'] [config] = [item for item in package if item['path'] == config_path] expected_configuration = dedent( """\ # Browser security settings for the DC/OS UI add_header X-Frame-Options "{value}"; """.format(value=value) ) assert config['content'] == expected_configuration
def test_agent(self, adminrouter_tls_1_0_enabled): """ Test that Agent Admin Router config file has the correct content. """ config_path = '/etc/adminrouter-tls-agent.conf' arguments = make_arguments(new_arguments={ 'adminrouter_tls_1_0_enabled': adminrouter_tls_1_0_enabled, }) generated = gen.generate(arguments=arguments) package = generated.templates['dcos-config.yaml']['package'] [config] = [item for item in package if item['path'] == config_path] expected_configuration = dedent( """\ # Note that Agent Admin Router only serves cluster-internal clients. Hence, # browser compatibility is not a criterion for the TLS cipher suite selection. ssl_ciphers EECDH+AES128:RSA+AES128:EECDH+AES256:RSA+AES256:!MD5; ssl_prefer_server_ciphers on; ssl_protocols TLSv1.2; """ ) assert config['content'] == expected_configuration
def test_agent_cannot_be_configured(self, tls_versions, ciphers): """ Agent Admin Router configuration is not affected by changing Master Admin Router TLS version or TLS cipher suites configuration. """ config_path = '/etc/adminrouter-tls-agent.conf' new_arguments = { 'adminrouter_tls_1_0_enabled': tls_versions[0], 'adminrouter_tls_1_1_enabled': tls_versions[1], 'adminrouter_tls_1_2_enabled': tls_versions[2], 'adminrouter_tls_cipher_suite': ciphers } arguments = make_arguments(new_arguments=new_arguments) generated = gen.generate(arguments=arguments) package = generated.templates['dcos-config.yaml']['package'] [config] = [item for item in package if item['path'] == config_path] expected_configuration = dedent("""\ # Note that Agent Admin Router only serves cluster-internal clients. Hence, # browser compatibility is not a criterion for the TLS cipher suite selection. ssl_ciphers EECDH+AES128:RSA+AES128:EECDH+AES256:RSA+AES256:!MD5; ssl_prefer_server_ciphers on; ssl_protocols TLSv1.2; """) assert config['content'] == expected_configuration
def test_agent_cannot_be_configured(self, tls_versions, ciphers): """ Agent Admin Router configuration is not affected by changing Master Admin Router TLS version or TLS cipher suites configuration. """ config_path = '/etc/adminrouter-tls-agent.conf' new_arguments = {'adminrouter_tls_1_0_enabled': tls_versions[0], 'adminrouter_tls_1_1_enabled': tls_versions[1], 'adminrouter_tls_1_2_enabled': tls_versions[2], 'adminrouter_tls_cipher_suite': ciphers} arguments = make_arguments(new_arguments=new_arguments) generated = gen.generate(arguments=arguments) package = generated.templates['dcos-config.yaml']['package'] [config] = [item for item in package if item['path'] == config_path] expected_configuration = dedent( """\ # Note that Agent Admin Router only serves cluster-internal clients. Hence, # browser compatibility is not a criterion for the TLS cipher suite selection. ssl_ciphers EECDH+AES128:RSA+AES128:EECDH+AES256:RSA+AES256:!MD5; ssl_prefer_server_ciphers on; ssl_protocols TLSv1.2; """ ) assert config['content'] == expected_configuration
def validate_error_multikey(new_arguments, keys, message, unset=None): assert gen.validate(arguments=make_arguments(new_arguments)) == { 'status': 'errors', 'errors': {key: {'message': message} for key in keys}, 'unset': set() if unset is None else unset, }
def validate_ok(new_arguments): assert gen.validate(arguments=make_arguments(new_arguments)) == { 'status': 'ok', }
def validate_ok(new_arguments): assert gen.validate(arguments=make_arguments(new_arguments)) == { 'status': 'ok', }