| #!/usr/bin/env bash |
| # Licensed to the Apache Software Foundation (ASF) under one |
| # or more contributor license agreements. See the NOTICE file |
| # distributed with this work for additional information |
| # regarding copyright ownership. The ASF licenses this file |
| # to you 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. |
| set -e; |
| |
| cd "$(dirname "${BASH_SOURCE[0]}")"; |
| readonly MY_DIR="$(pwd)"; |
| |
| help_string="$(<<-'HELP_STRING' cat |
| Usage: ./postinstall.test.h [ |
| -2 Set Python version to 2 |
| -3 Set Python version to 3 |
| -b Explicitly set the path to the Python binary as this value |
| -h, ? Print this help text and exit |
| -s Do not test Python 2 after testing Python 3 |
| HELP_STRING |
| )" |
| |
| while getopts :23hsb: opt; do |
| case "$opt" in |
| 2) python_version=2;; |
| 3) python_version=3;; |
| b) python_bin="$OPTARG";; |
| h) echo "$help_string" && exit;; |
| s) skip_python2=true;; |
| ?) echo "$help_string" && exit;; |
| *) echo "Invalid flag received: ${OPTARG}" >&2 && echo "$help_string" && exit 1;; |
| esac; |
| done; |
| |
| python_version="${python_version:-3}"; |
| python_bin="${python_bin:-/usr/bin/python${python_version}}"; |
| |
| if [[ ! -x "$python_bin" && "$python_version" -ge 3 ]]; then |
| echo "Python 3.6+ is required to run - or test - _postinstall.py" >&2; |
| exit 1; |
| elif [[ ! -x "$python_bin" && "$python_version" == 2 ]]; then |
| echo "Python ${python_version} is required to run - or test - _postinstall.py against Python 2" >&2; |
| fi |
| |
| readonly TO_PASSWORD=twelve; |
| readonly ROOT_DIR="$(mktemp -d)"; |
| |
| trap 'rm -rf $ROOT_DIR' EXIT; |
| |
| "$python_bin" <<EOF; |
| from __future__ import print_function |
| import importlib |
| import sys |
| from os.path import dirname, join |
| from _postinstall import Scrypt |
| |
| passwd = '${TO_PASSWORD}' |
| n = 2 ** 10 |
| r_val = 1 |
| p_val = 1 |
| dklen = 2 ** 4 |
| salt = bytearray([196, 187, 115, 30, 109, 244, 168, 124, 70, 67, 229, 123, 156, 3, 138, 243, 234, 79, 79, 31, 67, 239, 249, 177, 237, 240, 201, 216, 81, 116, 186, 172, 153, 99, 240, 184, 186, 0, 119, 34, 165, 220, 3, 201, 104, 13, 13, 189, 135, 76, 160, 6, 206, 154, 124, 78, 112, 243, 132, 30, 48, 223, 224, 28]) |
| scrypt = Scrypt(password=passwd.encode(), salt=salt, cost_factor=n, block_size_factor=r_val, |
| parallelization_factor=p_val, key_length=dklen) |
| |
| expected_block = [82000861, 2203666842, 4001293736, 627876473, 3101038348, 376175724, 2967675936, 3143524608, 1069098580, 1894075103, 3699786793, 3537442772, 3575269184, 2926196224, 913960627, 2079499993] |
| actual_block = [2245251288, 1072667772, 4071019211, 2090053191, 2877361598, 1101440729, 1502049634, 3905719376, 3112080378, 1388114151, 3517514506, 1152690600, 2085938056, 2696735995, 3835186347, 283826820] |
| scrypt.salsa20(actual_block) |
| if expected_block != actual_block: |
| print('Expected {expected_block} for salsa20 result, got {actual_block}'.format(expected_block=expected_block, actual_block=actual_block), file=sys.stderr) |
| exit(1) |
| |
| input_block = [1923378, 729355550, 2408212191, 579221939, 681409774, 1765430015, 3846256959, 831940078, 1480976199, 2878095125, 4245323720, 2776886825, 3332759976, 3497079966, 3107631655, 3763839506, 1283955177, 2851514107, 1743501900, 1888209181, 3387403441, 2898469985, 3685946334, 2122268467, 2234902587, 2934192414, 2528543680, 3247696936, 4144265372, 1687923239, 1573958329, 422403479] |
| expected_block = [54040099, 3246390556, 3905565410, 4170358448, 2569315507, 3679433373, 2964493607, 3621375783, 318358481, 2014381982, 3240374105, 3569092356, 3150068788, 569153936, 2099549087, 2807540417, 2384835523, 4053238240, 1126008925, 1477842924, 1740405559, 1762470512, 2159908599, 1049875013, 2630682622, 1368095319, 1753173294, 3987760372, 3175003396, 1324304335, 775131569, 2728051478] |
| actual_block = scrypt.block_mix(input_block) |
| if expected_block != actual_block: |
| print('Expected {expected_block} for block mix result, got {actual_block}'.format(expected_block=expected_block, actual_block=actual_block), file=sys.stderr) |
| exit(1) |
| |
| expected_digest = bytearray([86, 124, 148, 28, 117, 181, 239, 64, 228, 6, 247, 83, 210, 88, 43, 144]) |
| actual_digest = scrypt.derive() |
| if expected_digest != actual_digest: |
| print('Expected {expected_digest} for scrypt, got {actual_digest}'.format(expected_digest=expected_digest, actual_digest=actual_digest), file=sys.stderr) |
| exit(1) |
| EOF |
| |
| mkdir -p "$ROOT_DIR/etc/pki/tls/certs"; |
| mkdir "$ROOT_DIR/etc/pki/tls/private"; |
| mkdir -p "$ROOT_DIR/opt/traffic_ops/app/public/routing"; |
| mkdir "$ROOT_DIR/opt/traffic_ops/app/db"; |
| mkdir "$ROOT_DIR/opt/traffic_ops/app/db/trafficvault"; |
| mkdir -p "$ROOT_DIR/opt/traffic_ops/app/conf/production"; |
| cat > "$ROOT_DIR/opt/traffic_ops/app/conf/cdn.conf" <<EOF |
| { |
| "hypnotoad": { |
| "listen": [ |
| "https://[::]:60443?cert=$ROOT_DIR/etc/pki/tls/certs/localhost.crt&key=$ROOT_DIR/etc/pki/tls/private/localhost.key" |
| ] |
| } |
| } |
| EOF |
| |
| "$python_bin" <<TESTS 2>/dev/null | tee -a "${ROOT_DIR}/stdout"; |
| from __future__ import print_function |
| import subprocess |
| import sys |
| import _postinstall |
| from os.path import dirname, join |
| |
| download_tool = '/does/not/exist' |
| root = '${ROOT_DIR}' |
| |
| _postinstall.exec_psql('N/A', 'N/A', '--version') |
| TESTS |
| |
| mkdir -p "$ROOT_DIR/opt/traffic_ops/install/data/json"; |
| mkdir "$ROOT_DIR/opt/traffic_ops/install/bin"; |
| |
| # defaults.json is used as input into the `--cfile` option of _postinstall.py |
| # for testing purposes |
| cat <<- EOF > "$ROOT_DIR/defaults.json" |
| { |
| "/opt/traffic_ops/app/conf/production/database.conf": [ |
| { |
| "Database type": "Pg", |
| "config_var": "type", |
| "hidden": false |
| }, |
| { |
| "Database name": "traffic_ops", |
| "config_var": "dbname", |
| "hidden": false |
| }, |
| { |
| "Database server hostname IP or FQDN": "localhost", |
| "config_var": "hostname", |
| "hidden": false |
| }, |
| { |
| "Database port number": "5432", |
| "config_var": "port", |
| "hidden": false |
| }, |
| { |
| "Traffic Ops database user": "traffic_ops", |
| "config_var": "user", |
| "hidden": false |
| }, |
| { |
| "Password for Traffic Ops database user": "${TO_PASSWORD}", |
| "config_var": "password", |
| "hidden": true |
| } |
| ], |
| "/opt/traffic_ops/app/conf/production/tv.conf": [ |
| { |
| "Database type": "Pg", |
| "config_var": "type", |
| "hidden": false |
| }, |
| { |
| "Database name": "traffic_vault", |
| "config_var": "dbname", |
| "hidden": false |
| }, |
| { |
| "Database server hostname IP or FQDN": "localhost", |
| "config_var": "hostname", |
| "hidden": false |
| }, |
| { |
| "Database port number": "5432", |
| "config_var": "port", |
| "hidden": false |
| }, |
| { |
| "Traffic Ops database user": "traffic_vault", |
| "config_var": "user", |
| "hidden": false |
| }, |
| { |
| "Password for Traffic Ops database user": "${TO_PASSWORD}", |
| "config_var": "password", |
| "hidden": true |
| } |
| ], |
| "/opt/traffic_ops/app/conf/cdn.conf": [ |
| { |
| "Generate a new secret?": "yes", |
| "config_var": "genSecret", |
| "hidden": false |
| }, |
| { |
| "Number of secrets to keep?": "1", |
| "config_var": "keepSecrets", |
| "hidden": false |
| }, |
| { |
| "Port to serve on?": "443", |
| "config_var": "port", |
| "hidden": false |
| }, |
| { |
| "Number of workers?": "12", |
| "config_var": "workers", |
| "hidden": false |
| }, |
| { |
| "Traffic Ops url?": "http://localhost:3000", |
| "config_var": "base_url", |
| "hidden": false |
| }, |
| { |
| "ldap.conf location?": "/opt/traffic_ops/app/conf/ldap.conf", |
| "config_var": "ldap_conf_location", |
| "hidden": false |
| } |
| ], |
| "/opt/traffic_ops/app/conf/ldap.conf": [ |
| { |
| "Do you want to set up LDAP?": "yes", |
| "config_var": "setupLdap", |
| "hidden": false |
| }, |
| { |
| "LDAP server hostname": "ldaps://ad.cdn.site:3269", |
| "config_var": "host", |
| "hidden": false |
| }, |
| { |
| "LDAP Admin DN": "contact@cdn.site", |
| "config_var": "admin_dn", |
| "hidden": false |
| }, |
| { |
| "LDAP Admin Password": "${TO_PASSWORD}", |
| "config_var": "admin_pass", |
| "hidden": true |
| }, |
| { |
| "LDAP Search Base": "dc=cdn,dc=site", |
| "config_var": "search_base", |
| "hidden": false |
| }, |
| { |
| "LDAP Search Query": "(&(objectCategory=person)(objectClass=user)(sAMAccountName=%s))", |
| "config_var": "search_query", |
| "hidden": false |
| }, |
| { |
| "LDAP Skip TLS verify": "True", |
| "config_var": "insecure", |
| "hidden": false |
| }, |
| { |
| "LDAP Timeout Seconds": "120", |
| "config_var": "ldap_timeout_secs", |
| "hidden": false |
| } |
| ], |
| "/opt/traffic_ops/install/data/json/users.json": [ |
| { |
| "Administration username for Traffic Ops": "admin", |
| "config_var": "tmAdminUser", |
| "hidden": false |
| }, |
| { |
| "Password for the admin user": "twelve", |
| "config_var": "tmAdminPw", |
| "hidden": true |
| } |
| ], |
| "/opt/traffic_ops/install/data/profiles/": [ |
| { |
| "Add custom profiles?": "no", |
| "config_var": "custom_profiles", |
| "hidden": false |
| } |
| ], |
| "/opt/traffic_ops/install/data/json/openssl_configuration.json": [ |
| { |
| "Do you want to generate a certificate?": "yes", |
| "config_var": "genCert", |
| "hidden": false |
| }, |
| { |
| "Country Name (2 letter code)": "US", |
| "config_var": "country", |
| "hidden": false |
| }, |
| { |
| "State or Province Name (full name)": "Colorado", |
| "config_var": "state", |
| "hidden": false |
| }, |
| { |
| "Locality Name (eg, city)": "Denver", |
| "config_var": "locality", |
| "hidden": false |
| }, |
| { |
| "Organization Name (eg, company)": "Comcast", |
| "config_var": "company", |
| "hidden": false |
| }, |
| { |
| "Organizational Unit Name (eg, section)": "Viper", |
| "config_var": "org_unit", |
| "hidden": false |
| }, |
| { |
| "Common Name (eg, your name or your server's hostname)": "cdn", |
| "config_var": "common_name", |
| "hidden": false |
| }, |
| { |
| "RSA Passphrase": "testquest", |
| "config_var": "rsaPassword", |
| "hidden": true |
| } |
| ], |
| "/opt/traffic_ops/install/data/json/profiles.json": [ |
| { |
| "Traffic Ops url": "https://localhost", |
| "config_var": "tm.url", |
| "hidden": false |
| }, |
| { |
| "Human-readable CDN Name. (No whitespace, please)": "kabletown_cdn", |
| "config_var": "cdn_name", |
| "hidden": false |
| }, |
| { |
| "DNS sub-domain for which your CDN is authoritative": "cdn1.kabletown.net", |
| "config_var": "dns_subdomain", |
| "hidden": false |
| } |
| ] |
| } |
| EOF |
| |
| "$python_bin" "$MY_DIR/_postinstall.py" --no-root --root-directory="$ROOT_DIR" --no-restart-to --no-database --ops-user="$(whoami)" --ops-group="$(id -gn)" --automatic --cfile="$ROOT_DIR/defaults.json" --debug > >(tee -a "$ROOT_DIR/stdout") 2> >(tee -a "$ROOT_DIR/stderr" >&2); |
| |
| if grep -q 'ERROR' $ROOT_DIR/stdout; then |
| echo "Errors found in script logs" >&2; |
| cat "$ROOT_DIR/stdout" "$ROOT_DIR/stderr"; |
| exit 1; |
| fi |
| |
| readonly USERS_JSON_FILE="$ROOT_DIR/opt/traffic_ops/install/data/json/users.json"; |
| |
| "$python_bin" <<EOF |
| from __future__ import print_function |
| import json |
| import sys |
| |
| if sys.version_info.major < 3: |
| str = unicode |
| |
| try: |
| with open('$USERS_JSON_FILE') as fd: |
| users_json = json.load(fd) |
| except Exception as e: |
| print('Error loading users.json file:', e, file=sys.stderr) |
| exit(1) |
| |
| if not isinstance(users_json, dict) or len(users_json) != 2 or 'username' not in users_json or 'password' not in users_json: |
| print('Malformed users.json file - not an object or incorrect keys', file=sys.stderr) |
| exit(1) |
| |
| username = users_json['username'] |
| if not isinstance(username, str): |
| print('Username is not a string in users.json:', username, file=sys.stderr) |
| exit(1) |
| |
| if username != 'admin': |
| print('Incorrect username in users.json, expected: admin, got:', username, file=sys.stderr) |
| exit(1) |
| |
| password = users_json['password'] |
| if not isinstance(password, str): |
| print('Password is not a string in users.json:', password, file=sys.stderr) |
| exit(1) |
| |
| if not password.startswith('SCRYPT:16384:8:1:') or len(password.split(':')) != 6: |
| print('Malformed password field in users.json:', password, file=sys.stderr) |
| exit(1) |
| |
| exit(0) |
| EOF |
| |
| readonly POST_INSTALL_JSON="$ROOT_DIR/opt/traffic_ops/install/data/json/post_install.json"; |
| if [[ "$(cat $POST_INSTALL_JSON)" != "{}" ]]; then |
| echo "Incorrect post_install.json, expected: {}, got: $(cat $POST_INSTALL_JSON)" >&2; |
| exit 1; |
| fi |
| |
| readonly PROFILES_JSON_EXPECTED="{ |
| \"cdn_name\": \"kabletown_cdn\", |
| \"dns_subdomain\": \"cdn1.kabletown.net\", |
| \"tm.url\": \"https://localhost\" |
| }"; |
| |
| readonly PROFILES_JSON_ACTUAL="$(<"$ROOT_DIR/opt/traffic_ops/install/data/json/profiles.json" jq -S --tab .)"; |
| if [[ "$PROFILES_JSON_ACTUAL" != "$PROFILES_JSON_EXPECTED" ]]; then |
| echo "Incorrect profiles.json, expected: $PROFILES_JSON_EXPECTED, got: $PROFILES_JSON_ACTUAL" >&2; |
| exit 1; |
| fi |
| |
| readonly DB_CONF_EXPECTED="production: |
| driver: postgres |
| open: host=localhost port=5432 user=traffic_ops password=twelve dbname=traffic_ops sslmode=disable"; |
| |
| readonly DB_CONF_ACTUAL="$(cat $ROOT_DIR/opt/traffic_ops/app/db/dbconf.yml)"; |
| if [[ "$DB_CONF_ACTUAL" != "$DB_CONF_EXPECTED" ]]; then |
| echo "Incorrect dbconf.yml, expected:" >&2; |
| echo "$DB_CONF_EXPECTED" >&2; |
| echo "got:" >&2; |
| echo "$DB_CONF_ACTUAL" >&2; |
| exit 1; |
| fi |
| |
| "$python_bin" <<EOF |
| from __future__ import print_function |
| import json |
| import string |
| import sys |
| |
| if sys.version_info.major < 3: |
| str = unicode |
| |
| try: |
| with(open('$ROOT_DIR/opt/traffic_ops/app/conf/cdn.conf')) as fd: |
| conf = json.load(fd) |
| except Exception as e: |
| print('Error loading cdn.conf file:', e, file=sys.stderr) |
| exit(1) |
| |
| if not isinstance(conf, dict) or len(conf) != 4 or 'hypnotoad' not in conf or 'secrets' not in conf or 'to' not in conf or 'traffic_ops_golang' not in conf: |
| print('Malformed cdn.conf file - not an object or missing keys', file=sys.stderr) |
| exit(1) |
| |
| if not isinstance(conf['hypnotoad'], dict) or len(conf['hypnotoad']) != 1 or 'listen' not in conf['hypnotoad'] or not isinstance(conf['hypnotoad']['listen'], list) or len(conf['hypnotoad']['listen']) != 1 or not isinstance(conf['hypnotoad']['listen'][0], str): |
| print('Malformed hypnotoad object in cdn.conf:', conf['hypnotoad'], file=sys.stderr) |
| exit(1) |
| |
| listen = 'https://[::]:60443?cert=$ROOT_DIR/etc/pki/tls/certs/localhost.crt&key=$ROOT_DIR/etc/pki/tls/private/localhost.key' |
| if conf['hypnotoad']['listen'][0] != listen: |
| print('Incorrect hypnotoad.listen[0] in cdn.conf, expected:', listen, 'got:', conf['hypnotoad']['listen'][0], file=sys.stderr) |
| exit(1) |
| |
| if not isinstance(conf['secrets'], list) or len(conf['secrets']) != 1 or not isinstance(conf['secrets'][0], str): |
| print('Malformed secrets object in cdn.conf:', conf['secrets'], file=sys.stderr) |
| exit(1) |
| |
| if len(conf['secrets'][0]) != 12 or any(True for x in conf['secrets'][0] if x not in string.ascii_letters + string.digits + '_'): |
| print('Incorrect secret in cdn.conf, expected 12 word characters, got:', conf['secrets'][0], file=sys.stderr) |
| exit(1) |
| |
| if not isinstance(conf['to'], dict) or 'base_url' not in conf['to'] or len(conf['to']) != 1 or not isinstance(conf['to']['base_url'], str): |
| print('Malformed to object in cdn.conf:', conf['to']) |
| exit(1) |
| |
| if conf['to']['base_url'] != 'http://localhost:3000': |
| print('Incorrect to.base_url in cdn.conf, expected: http://localhost:3000, got:', conf['to']['base_url'], file=sys.stderr) |
| exit(1) |
| |
| if not isinstance(conf['traffic_ops_golang'], dict) or len(conf['traffic_ops_golang']) != 3 or 'port' not in conf['traffic_ops_golang'] or 'log_location_error' not in conf['traffic_ops_golang'] or 'log_location_event' not in conf['traffic_ops_golang']: |
| print('Malformed traffic_ops_golang object in cdn.conf:', conf['traffic_ops_golang'], sys.stderr) |
| exit(1) |
| |
| if conf['traffic_ops_golang']['port'] != '443': |
| print('Incorrect traffic_ops_golang.port, expected: 443, got:', conf['traffic_ops_golang']['port'], file=sys.stderr) |
| exit(1) |
| |
| if conf['traffic_ops_golang']['log_location_error'] != '$ROOT_DIR/var/log/traffic_ops/error.log': |
| print('Incorrect traffic_ops_golang.log_location_error in cdn.conf, expected: $ROOT_DIR/var/log/traffic_ops/error.log, got:', conf['traffic_ops_golang']['log_location_error'], file=sys.stderr) |
| exit(1) |
| |
| if conf['traffic_ops_golang']['log_location_event'] != '$ROOT_DIR/var/log/traffic_ops/access.log': |
| print('Incorrect traffic_ops_golang.log_location_event in cdn.conf, expected: $ROOT_DIR/var/log/traffic_ops/access.log, got:', conf['traffic_ops_golang']['log_location_event'], file=sys.stderr) |
| exit(1) |
| |
| exit(0) |
| EOF |
| |
| readonly DATABASE_CONF_EXPECTED='{ |
| "dbname": "traffic_ops", |
| "description": "Pg database on localhost:5432", |
| "hostname": "localhost", |
| "password": "twelve", |
| "port": "5432", |
| "type": "Pg", |
| "user": "traffic_ops" |
| }'; |
| |
| readonly DATABASE_CONF_ACTUAL="$(<"$ROOT_DIR/opt/traffic_ops/app/conf/production/database.conf" jq -S --tab .)"; |
| if [[ "$DATABASE_CONF_ACTUAL" != "$DATABASE_CONF_EXPECTED" ]]; then |
| echo "Incorrect database.conf, expected: $DATABASE_CONF_EXPECTED, got $DATABASE_CONF_ACTUAL" >&2; |
| exit 1; |
| fi |
| |
| readonly CSR_FILE="$ROOT_DIR/etc/pki/tls/certs/localhost.csr"; |
| readonly CSR_FILE_TYPE="$(file $CSR_FILE)"; |
| if [[ "$CSR_FILE_TYPE" != "$CSR_FILE: PEM certificate request" ]]; then |
| echo "Incorrect csr file, expected a PEM certificate request, got: $CSR_FILE_TYPE" >&2; |
| exit 1; |
| fi |
| |
| readonly CERT_FILE="$ROOT_DIR/etc/pki/tls/certs/localhost.crt"; |
| readonly CERT_FILE_TYPE="$(file $CERT_FILE)"; |
| if [[ "$CERT_FILE_TYPE" != "$CERT_FILE: PEM certificate" ]]; then |
| echo "Incorrect cert file, expected a PEM certificate, got: $CERT_FILE_TYPE" >&2; |
| exit 1; |
| fi |
| |
| readonly KEY_FILE="$ROOT_DIR/etc/pki/tls/private/localhost.key"; |
| readonly KEY_FILE_TYPE="$(file $KEY_FILE)"; |
| if [[ "$KEY_FILE_TYPE" != "$KEY_FILE: PEM RSA private key" ]]; then |
| echo "Incorrect key file, expected PEM RSA private key, got: $KEY_FILE_TYPE" >&2; |
| exit 1; |
| fi |
| |
| if [[ "$python_version" != 2 && -z "$skip_python2" ]]; then |
| exec "${MY_DIR}/$(basename "${BASH_SOURCE[0]}")" -2; |
| fi; |