From da8032fe6160f18b8874641fcd622ec79b7b1e77 Mon Sep 17 00:00:00 2001 From: Joseph Schorr Date: Fri, 24 Mar 2017 16:39:28 -0400 Subject: [PATCH 1/4] Fix SSL custom certs installation file for bash shell scripting bug The missing quotes caused the script to fail with a bash error --- conf/init/certs_install.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/conf/init/certs_install.sh b/conf/init/certs_install.sh index 2ae929ac0..9f440d8a6 100755 --- a/conf/init/certs_install.sh +++ b/conf/init/certs_install.sh @@ -9,7 +9,7 @@ fi # Add extra trusted certificates (as a directory) if [ -d /conf/stack/extra_ca_certs ]; then - if test $(ls -A "/conf/stack/extra_ca_certs"); then + if test "$(ls -A "/conf/stack/extra_ca_certs")"; then echo "Installing extra certificates found in /conf/stack/extra_ca_certs directory" cp /conf/stack/extra_ca_certs/* /usr/local/share/ca-certificates/ cat /conf/stack/extra_ca_certs/* >> /venv/lib/python2.7/site-packages/requests/cacert.pem From e509eb4cbac5d297b7b4d2e8006abd7724409755 Mon Sep 17 00:00:00 2001 From: Joseph Schorr Date: Fri, 24 Mar 2017 17:00:51 -0400 Subject: [PATCH 2/4] Better custom cert handling in the superuser tool We now only allow certificates ending in .crt to be uploaded and we automatically install the certificate once it has been validated --- endpoints/api/superuser.py | 19 ++++++++++++++ .../config/config-certificates-field.html | 14 +++++++--- static/directives/file-upload-box.html | 4 ++- static/js/core-config-setup.js | 3 +++ static/js/directives/ui/file-upload-box.js | 10 +++++++ test/test_api_usage.py | 26 +++++++++---------- 6 files changed, 58 insertions(+), 18 deletions(-) diff --git a/endpoints/api/superuser.py b/endpoints/api/superuser.py index 9a1da3dea..953c30960 100644 --- a/endpoints/api/superuser.py +++ b/endpoints/api/superuser.py @@ -3,6 +3,7 @@ import logging import os import string +import subprocess import pathvalidate @@ -894,9 +895,27 @@ class SuperUserCustomCertificate(ApiResource): if not uploaded_file: abort(400) + # Save the certificate. certpath = pathvalidate.sanitize_filename(certpath) + if not certpath.endswith('.crt'): + abort(400) + cert_full_path = os.path.join(EXTRA_CA_DIRECTORY, certpath) config_provider.save_volume_file(cert_full_path, uploaded_file) + + # Validate the certificate. + try: + with config_provider.get_volume_file(cert_full_path) as f: + load_certificate(f.read()) + + # Call the update script to install the certificate immediately. + if not app.config['TESTING']: + subprocess.check_call(['/conf/init/certs_install.sh']) + except CertInvalidException: + pass + except IOError: + pass + return '', 204 abort(403) diff --git a/static/directives/config/config-certificates-field.html b/static/directives/config/config-certificates-field.html index 502475672..f20e4c459 100644 --- a/static/directives/config/config-certificates-field.html +++ b/static/directives/config/config-certificates-field.html @@ -12,6 +12,7 @@

Custom certificates are typically used in place of publicly signed certificates for corporate-internal services.

+

Please make sure that all custom names used for downstream services (such as Clair) are listed in the certificates below.

@@ -19,9 +20,10 @@
Upload certificates:
+ reset="resetUpload" + extensions="['.crt']">
@@ -33,7 +35,7 @@ Names Handled - + {{ certificate.path }}
@@ -62,7 +64,11 @@ -
+
+
+ Uploading, validating and updating certificate(s) +
+
No custom certificates found.
diff --git a/static/directives/file-upload-box.html b/static/directives/file-upload-box.html index f188e4191..65cdf9d6c 100644 --- a/static/directives/file-upload-box.html +++ b/static/directives/file-upload-box.html @@ -2,7 +2,9 @@
- +
-
+
Custom SSL Certificates
@@ -342,6 +342,16 @@
+ + + + - - - - From b017133cc6bc10cb39cdb287820bd1b67d1dfa04 Mon Sep 17 00:00:00 2001 From: Joseph Schorr Date: Fri, 24 Mar 2017 17:28:16 -0400 Subject: [PATCH 4/4] Make QSS validation errors more descriptive --- .../validators/test/test_validate_secscan.py | 11 +++++------ util/secscan/api.py | 15 +++++++++------ 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/util/config/validators/test/test_validate_secscan.py b/util/config/validators/test/test_validate_secscan.py index e47aa9bf5..0c484c250 100644 --- a/util/config/validators/test/test_validate_secscan.py +++ b/util/config/validators/test/test_validate_secscan.py @@ -11,26 +11,25 @@ def test_validate_noop(unvalidated_config, app): SecurityScannerValidator.validate(unvalidated_config, None, None) -@pytest.mark.parametrize('unvalidated_config, expected_error, error_message', [ +@pytest.mark.parametrize('unvalidated_config, expected_error', [ ({ 'TESTING': True, 'DISTRIBUTED_STORAGE_PREFERENCE': [], 'FEATURE_SECURITY_SCANNER': True, 'SECURITY_SCANNER_ENDPOINT': 'http://invalidhost', - }, Exception, 'Connection error when trying to connect to security scanner endpoint'), + }, Exception), ({ 'TESTING': True, 'DISTRIBUTED_STORAGE_PREFERENCE': [], 'FEATURE_SECURITY_SCANNER': True, 'SECURITY_SCANNER_ENDPOINT': 'http://fakesecurityscanner', - }, None, None), + }, None), ]) -def test_validate(unvalidated_config, expected_error, error_message, app): +def test_validate(unvalidated_config, expected_error, app): with fake_security_scanner(hostname='fakesecurityscanner'): if expected_error is not None: - with pytest.raises(expected_error) as ipe: + with pytest.raises(expected_error): SecurityScannerValidator.validate(unvalidated_config, None, None) - assert ipe.value.message == error_message else: SecurityScannerValidator.validate(unvalidated_config, None, None) diff --git a/util/secscan/api.py b/util/secscan/api.py index f90b3ba37..e53c61646 100644 --- a/util/secscan/api.py +++ b/util/secscan/api.py @@ -166,15 +166,18 @@ class SecurityScannerAPI(object): """ try: return self._call('GET', _API_METHOD_PING) - except requests.exceptions.Timeout: + except requests.exceptions.Timeout as tie: logger.exception('Timeout when trying to connect to security scanner endpoint') - raise Exception('Timeout when trying to connect to security scanner endpoint') - except requests.exceptions.ConnectionError: + msg = 'Timeout when trying to connect to security scanner endpoint: %s' % tie.message + raise Exception(msg) + except requests.exceptions.ConnectionError as ce: logger.exception('Connection error when trying to connect to security scanner endpoint') - raise Exception('Connection error when trying to connect to security scanner endpoint') - except (requests.exceptions.RequestException, ValueError): + msg = 'Connection error when trying to connect to security scanner endpoint: %s' % ce.message + raise Exception(msg) + except (requests.exceptions.RequestException, ValueError) as ve: logger.exception('Exception when trying to connect to security scanner endpoint') - raise Exception('Exception when trying to connect to security scanner endpoint') + msg = 'Exception when trying to connect to security scanner endpoint: %s' % ve + raise Exception(msg) def delete_layer(self, layer): """ Calls DELETE on the given layer in the security scanner, removing it from
Authentication Key: + +
+ The security scanning service requires an authorized service key to speak to Quay. Once setup, the key + can be managed in the Service Keys panel under the Super User Admin Panel. +
+
Security Scanner Endpoint: @@ -351,15 +361,8 @@
The HTTP URL at which the security scanner is running.
-
Authentication Key: - -
- The security scanning service requires an authorized service key to speak to Quay. Once setup, the key - can be managed in the Service Keys panel under the Super User Admin Panel. +
+ Is the security scanner behind a domain signed with a self-signed TLS certificate? If so, please make sure to register your SSL CA in the custom certificates panel above.