You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@trafficcontrol.apache.org by ra...@apache.org on 2021/05/21 03:36:32 UTC

[trafficcontrol] branch master updated: CIAB: Use postgres as the default traffic vault backend (#5857)

This is an automated email from the ASF dual-hosted git repository.

rawlin pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/trafficcontrol.git


The following commit(s) were added to refs/heads/master by this push:
     new deaf6fe  CIAB: Use postgres as the default traffic vault backend (#5857)
deaf6fe is described below

commit deaf6fe5bd983032669f1d2c4bc0bc0d80a83bd7
Author: Srijeet Chatterjee <30...@users.noreply.github.com>
AuthorDate: Thu May 20 21:36:14 2021 -0600

    CIAB: Use postgres as the default traffic vault backend (#5857)
    
    * wip
    
    * wip working postgres tv database
    
    * postinstall test
    
    * fix postinstall tests
    
    * uncomment add users
    
    * cdn.conf changes
    
    * more config changes
    
    * pass in conf items from env vbls
    
    * working ciab changes
    
    * print warning if no traffic vault backend
    
    * use postgres as default tv backend
    
    * fix indentation
    
    * remove extra code from docker images
    
    * code review fixes
    
    * code review changes
    
    * add aes
    
    * adding aes key
    
    * generate aes encryption key on the fly
    
    * move key generation out of dockerfile
    
    * print a warning instead of raising an exception if aes location is not present
    
    * changing name of aes file
    
    * add option to use riak in ciab
    
    * remove unneeded env var check
    
    * use context instead of opening file directly
    
    * leave tv stuff blank in cdn.conf
    
    * use openssl function
    
    * add extra line
    
    * code review
    
    * copy directly to conf/production
---
 infrastructure/cdn-in-a-box/traffic_ops/Dockerfile |  1 +
 infrastructure/cdn-in-a-box/traffic_ops/config.sh  | 44 +++++++++++-
 infrastructure/cdn-in-a-box/traffic_ops/run-go.sh  | 17 ++++-
 infrastructure/cdn-in-a-box/traffic_ops/tv.conf    | 10 +++
 infrastructure/cdn-in-a-box/variables.env          | 10 ++-
 traffic_ops/install/bin/_postinstall               | 80 +++++++++++++++++++---
 traffic_ops/install/bin/input.json                 | 38 ++++++++++
 traffic_ops/install/bin/postinstall.test.sh        | 45 ++++++++++++
 8 files changed, 228 insertions(+), 17 deletions(-)

diff --git a/infrastructure/cdn-in-a-box/traffic_ops/Dockerfile b/infrastructure/cdn-in-a-box/traffic_ops/Dockerfile
index 6f5d767..0d26f4c 100644
--- a/infrastructure/cdn-in-a-box/traffic_ops/Dockerfile
+++ b/infrastructure/cdn-in-a-box/traffic_ops/Dockerfile
@@ -131,6 +131,7 @@ RUN go get -u github.com/go-delve/delve/cmd/dlv
 
 FROM trafficops AS trafficops-debug
 COPY --from=get-delve /root/go/bin /usr/bin
+COPY infrastructure/cdn-in-a-box/traffic_ops/tv.conf conf/production/
 
 # Makes the default target skip the trafficops-debug stage
 FROM trafficops
diff --git a/infrastructure/cdn-in-a-box/traffic_ops/config.sh b/infrastructure/cdn-in-a-box/traffic_ops/config.sh
index c5bb745..c8161bd 100755
--- a/infrastructure/cdn-in-a-box/traffic_ops/config.sh
+++ b/infrastructure/cdn-in-a-box/traffic_ops/config.sh
@@ -32,9 +32,14 @@
 # TO_HOST
 # TO_PORT
 # TP_HOST
+# TV_DB_NAME
+# TV_DB_PORT
+# TV_DB_SERVER
+# TV_DB_USER
+# TV_DB_USER_PASS
 #
 # Check that env vars are set
-envvars=( DB_SERVER DB_PORT DB_ROOT_PASS DB_USER DB_USER_PASS ADMIN_USER ADMIN_PASS DOMAIN TO_HOST TO_PORT TP_HOST)
+envvars=( DB_SERVER DB_PORT DB_ROOT_PASS DB_USER DB_USER_PASS ADMIN_USER ADMIN_PASS DOMAIN TO_HOST TO_PORT TP_HOST TV_DB_NAME TV_DB_PORT TV_DB_SERVER TV_DB_USER TV_DB_USER_PASS)
 for v in $envvars; do
   if [[ -z "${!v}" ]]; then echo "$v is unset"; exit 1; fi
 done
@@ -76,6 +81,19 @@ cdn_conf=/opt/traffic_ops/app/conf/cdn.conf
     },
     "use_ims": true,
     "traffic_ops_golang" : {
+          "traffic_vault_backend": "$TV_BACKEND",
+          "traffic_vault_config": {
+            "dbname": "$TV_DB_NAME",
+            "hostname": "$TV_DB_SERVER.$INFRA_SUBDOMAIN.$TLD_DOMAIN",
+            "user": "$TV_DB_USER",
+            "password": "$TV_DB_USER_PASS",
+            "port": ${TV_DB_PORT:-5432},
+            "conn_max_lifetime_seconds": ${DEBUGGING_TIMEOUT:-60},
+            "max_connections": 500,
+            "max_idle_connections": 30,
+            "query_timeout_seconds": ${DEBUGGING_TIMEOUT:-60},
+            "aes_key_location": "$TV_AES_KEY_LOCATION"
+        },
         "proxy_timeout" : ${DEBUGGING_TIMEOUT:-60},
         "proxy_tls_timeout" : ${DEBUGGING_TIMEOUT:-60},
         "proxy_read_header_timeout" : ${DEBUGGING_TIMEOUT:-60},
@@ -150,6 +168,20 @@ echo "$(jq "$(<<'JQ_FILTER' envsubst
       "${DB_USER_PASS}"
     else . end))
   ) |
+  ."/opt/traffic_ops/app/conf/production/tv.conf"[] |= (
+    (select(.config_var == "dbname") |= with_entries(if .key | test("^[A-Z]") then .value =
+      "${TV_DB_NAME}"
+    else . end)) |
+    (select(.config_var == "hostname") |= with_entries(if .key | test("^[A-Z]") then .value =
+      "${DB_FQDN}"
+    else . end)) |
+    (select(.config_var == "user") |= with_entries(if .key | test("^[A-Z]") then .value =
+      "${TV_DB_USER}"
+    else . end)) |
+    (select(.config_var == "password") |= with_entries(if .key | test("^[A-Z]") then .value =
+      "${TV_DB_USER_PASS}"
+    else . end))
+  ) |
   ."/opt/traffic_ops/app/db/dbconf.yml"[] |= (
     (select(.config_var == "pgUser") |= with_entries(if .key | test("^[A-Z]") then .value =
       "${DB_USER}"
@@ -158,6 +190,14 @@ echo "$(jq "$(<<'JQ_FILTER' envsubst
       "${DB_USER_PASS}"
     else . end))
   ) |
+  ."/opt/traffic_ops/app/db/trafficvault/dbconf.yml"[] |= (
+    (select(.config_var == "pgUser") |= with_entries(if .key | test("^[A-Z]") then .value =
+      "${TV_DB_USER}"
+    else . end)) |
+    (select(.config_var == "pgPassword") |= with_entries(if .key | test("^[A-Z]") then .value =
+      "${TV_DB_USER_PASS}"
+    else . end))
+  ) |
   ."/opt/traffic_ops/install/data/json/openssl_configuration.json"[] |= (
     (select(.config_var == "genCert") |= with_entries(if .key | test("^[A-Z]") then .value =
       "no"
@@ -188,4 +228,4 @@ echo "$(jq "$(<<'JQ_FILTER' envsubst
 JQ_FILTER
 )" "$input_json")" >"$input_json"
 
-"${install_bin}/postinstall" -a --cfile "$input_json" -n --no-restart-to
+"${install_bin}/postinstall" --debug -a --cfile "$input_json" -n --no-restart-to
diff --git a/infrastructure/cdn-in-a-box/traffic_ops/run-go.sh b/infrastructure/cdn-in-a-box/traffic_ops/run-go.sh
index fc3e53c..2172775 100755
--- a/infrastructure/cdn-in-a-box/traffic_ops/run-go.sh
+++ b/infrastructure/cdn-in-a-box/traffic_ops/run-go.sh
@@ -30,16 +30,22 @@
 # ADMIN_USER
 # ADMIN_PASS
 # DOMAIN
+# TV_AES_KEY_LOCATION
+# TV_BACKEND
+# TV_DB_NAME
+# TV_DB_PORT
+# TV_DB_SERVER
+# TV_DB_USER
+# TV_DB_USER_PASS
 
 # TODO:  Unused -- should be removed?  TRAFFIC_VAULT_PASS
-
 # Setting the monitor shell option enables job control, which we need in order
 # to bring traffic_ops_golang back to the foreground.
 trap 'echo "Error on line ${LINENO} of ${0}"; exit 1' ERR
 set -o errexit -o monitor -o pipefail -o xtrace;
 
 # Check that env vars are set
-envvars=( DB_SERVER DB_PORT DB_ROOT_PASS DB_USER DB_USER_PASS ADMIN_USER ADMIN_PASS)
+envvars=( DB_SERVER DB_PORT DB_ROOT_PASS DB_USER DB_USER_PASS ADMIN_USER ADMIN_PASS TV_AES_KEY_LOCATION TV_DB_NAME TV_DB_PORT TV_DB_SERVER TV_DB_USER TV_DB_USER_PASS)
 for v in $envvars; do
 	if [[ -z $$v ]]; then
 		echo "$v is unset" >&2;
@@ -142,7 +148,12 @@ mkdir -p /var/log/traffic_ops
 touch "$TO_LOG_ERROR" "$TO_LOG_WARNING" "$TO_LOG_INFO" "$TO_LOG_DEBUG" "$TO_LOG_EVENT"
 tail -qf "$TO_LOG_ERROR" "$TO_LOG_WARNING" "$TO_LOG_INFO" "$TO_LOG_DEBUG" "$TO_LOG_EVENT" &
 
-traffic_ops_golang_command=(./bin/traffic_ops_golang -cfg "$CDNCONF" -dbcfg "$DBCONF" -riakcfg "$RIAKCONF");
+if [[ -z $TV_BACKEND ]]; then
+  traffic_ops_golang_command=(./bin/traffic_ops_golang -cfg "$CDNCONF" -dbcfg "$DBCONF" -riakcfg "$RIAKCONF");
+else
+  traffic_ops_golang_command=(./bin/traffic_ops_golang -cfg "$CDNCONF" -dbcfg "$DBCONF");
+fi;
+
 if [[ "$TO_DEBUG_ENABLE" == true ]]; then
 	traffic_ops_golang_command=(dlv '--accept-multiclient' '--continue' '--listen=:2345' '--headless=true' '--api-version=2' exec
 		"${traffic_ops_golang_command[0]}" -- "${traffic_ops_golang_command[@]:1}");
diff --git a/infrastructure/cdn-in-a-box/traffic_ops/tv.conf b/infrastructure/cdn-in-a-box/traffic_ops/tv.conf
new file mode 100644
index 0000000..dfebf50
--- /dev/null
+++ b/infrastructure/cdn-in-a-box/traffic_ops/tv.conf
@@ -0,0 +1,10 @@
+{
+	"description": "Local PostgreSQL database on port 5432",
+	"dbname": "traffic_vault",
+	"hostname": "localhost",
+	"user": "traffic_vault",
+	"password": "password",
+	"port": "5432",
+	"ssl": false,
+	"type": "Pg"
+}
diff --git a/infrastructure/cdn-in-a-box/variables.env b/infrastructure/cdn-in-a-box/variables.env
index 4047434..378140f 100644
--- a/infrastructure/cdn-in-a-box/variables.env
+++ b/infrastructure/cdn-in-a-box/variables.env
@@ -14,6 +14,9 @@
 # KIND, either express or implied.  See the License for the
 # specific language governing permissions and limitations
 # under the License.
+TV_AES_KEY_LOCATION=/opt/traffic_ops/app/aes.key
+# Unset TV_BACKEND to use riak as the traffic_vault backend
+TV_BACKEND=postgres
 TLD_DOMAIN=ciab.test
 INFRA_SUBDOMAIN=infra
 CDN_NAME=CDN-in-a-Box
@@ -38,10 +41,15 @@ X509_CA_PERSIST_DIR=/ca
 X509_CA_PERSIST_ENV_FILE=/ca/environment
 X509_CA_ENV_FILE=/shared/ssl/environment
 DB_NAME=traffic_ops
+TV_DB_NAME=traffic_vault
 DB_PORT=5432
 DB_SERVER=db
-DB_USER=traffic_ops
+TV_DB_SERVER=db
 DB_USER_PASS=twelve
+DB_USER=traffic_ops
+TV_DB_USER=traffic_vault
+TV_DB_USER_PASS=twelve
+TV_DB_PORT=5432
 DNS_SERVER=dns
 DBIC_TRACE=0
 ENROLLER_HOST=enroller
diff --git a/traffic_ops/install/bin/_postinstall b/traffic_ops/install/bin/_postinstall
index e073b9f..cb4aec2 100755
--- a/traffic_ops/install/bin/_postinstall
+++ b/traffic_ops/install/bin/_postinstall
@@ -70,6 +70,8 @@ from struct import unpack, pack
 # Paths for output configuration files
 DATABASE_CONF_FILE = "/opt/traffic_ops/app/conf/production/database.conf"
 DB_CONF_FILE = "/opt/traffic_ops/app/db/dbconf.yml"
+TV_DATABASE_CONF_FILE = "/opt/traffic_ops/app/conf/production/tv.conf"
+TV_DB_CONF_FILE = "/opt/traffic_ops/app/db/trafficvault/dbconf.yml"
 CDN_CONF_FILE = "/opt/traffic_ops/app/conf/cdn.conf"
 LDAP_CONF_FILE = "/opt/traffic_ops/app/conf/ldap.conf"
 USERS_CONF_FILE = "/opt/traffic_ops/install/data/json/users.json"
@@ -233,13 +235,23 @@ DEFAULTS = {
 		Question("Database server root (admin) user", "postgres", "pgUser"),
 		Question("Password for database server admin", "", "pgPassword", hidden=True)
 	],
+	TV_DB_CONF_FILE: [
+		Question("Database server root (admin) user", "postgres", "pgUser"),
+		Question("Password for database server admin", "", "pgPassword", hidden=True)
+	],
 	CDN_CONF_FILE: [
 		Question("Generate a new secret?", "yes", "genSecret"),
 		Question("Number of secrets to keep?", "1", "keepSecrets"),
 		Question("Port to serve on?", "443", "port"),
 		Question("Number of workers?", "12", "workers"),
 		Question("Traffic Ops url?", "http://localhost:3000", "base_url"),
-		Question("ldap.conf location?", "/opt/traffic_ops/app/conf/ldap.conf", "ldap_conf_location")
+		Question("ldap.conf location?", "/opt/traffic_ops/app/conf/ldap.conf", "ldap_conf_location"),
+		Question("Traffic Vault Database type", "Pg", "type"),
+		Question("Traffic Vault Database name", "traffic_vault", "dbname"),
+		Question("Traffic Vault Database server hostname IP or FQDN", "localhost", "hostname"),
+		Question("Traffic Vault Database port number", "5432", "port"),
+		Question("Traffic Vault database user", "traffic_vault", "user"),
+		Question("Password for Traffic Vault database user", "", "password", hidden=True)
 	],
 	LDAP_CONF_FILE:[
 		Question("Do you want to set up LDAP?", "no", "setupLdap"),
@@ -363,7 +375,6 @@ def generate_todb_conf(qstns, fname, auto, root, conf): # (list, str, bool, str,
 		print("production:", file=conf_file)
 		print("    driver:", driver, file=conf_file)
 		print("    open: {open_line} sslmode=disable".format(open_line=open_line), file=conf_file)
-
 	return todbconf
 
 def generate_ldap_conf(questions, fname, automatic, root): # type: (list[Question], str, bool, str) -> None
@@ -710,6 +721,26 @@ def unmarshal_config(dct): # type: (dict) -> dict[str, list[Question]]
 
 	return ret
 
+def write_encryption_key(aes_key_location): # type: (str) -> None
+	"""
+	Creates an AES encryption key for the postgres traffic vault backend
+
+	:param aes_key_location: Denotes the location of the aes encryption key file
+	:returns: None
+	"""
+	logging.info(aes_key_location)
+
+	args = (
+		"rand",
+		"-out",
+		aes_key_location,
+		"-base64",
+		"32"
+	)
+	if not exec_openssl("Generating an AES encryption key", *args):
+		logging.debug("AES key generation failed")
+		raise OSError("failed to generate AES key")
+
 def exec_openssl(description, *cmd_args): # type: (str, ...) -> bool
 	"""
 	Executes openssl with the supplied command-line arguments.
@@ -905,11 +936,12 @@ def random_word(length = 12): # type: (int) -> str
 	word_chars = string.ascii_letters + string.digits + '_'
 	return ''.join(random.choice(word_chars) for _ in range(length))
 
-def generate_cdn_conf(questions, fname, automatic, root): # type: (list[Question], str, bool, str) -> None
+def generate_cdn_conf(questions, fname, automatic, root): # type: (list[Question], str, bool, str) -> bool
 	"""
 	Generates some properties of a cdn.conf file based on the passed questions.
 
 	This modifies or writes the file 'fname' under the directory 'root'.
+	:returns: A boolean value denoting whether or not a postgres traffic vault backend is configured.
 	"""
 	cdn_conf = get_config(questions, fname, automatic)
 
@@ -996,6 +1028,8 @@ def generate_cdn_conf(questions, fname, automatic, root): # type: (list[Question
 	existing_conf["traffic_ops_golang"]["log_location_error"] = err_log
 	access_log = os.path.join(root, "var/log/traffic_ops/access.log")
 	existing_conf["traffic_ops_golang"]["log_location_event"] = access_log
+	traffic_vault_backend = "postgres"
+	traffic_vault_aes_encryption_location = "/opt/traffic_ops/app/aes.key"
 
 	if "hypnotoad" not in existing_conf or not isinstance(existing_conf["hypnotoad"], dict):
 		existing_conf["hypnotoad"]["workers"] = conf.num_workers
@@ -1004,6 +1038,19 @@ def generate_cdn_conf(questions, fname, automatic, root): # type: (list[Question
 		json.dump(existing_conf, conf_file, indent=indent)
 		print(file=conf_file)
 	logging.info("CDN configuration has been saved")
+	try:
+		traffic_vault_backend = existing_conf["traffic_ops_golang"]["traffic_vault_backend"]
+	except KeyError as e:
+		logging.warning("no traffic vault backend configured, using default postgres")
+
+	if traffic_vault_backend == "postgres":
+		try:
+			traffic_vault_aes_encryption_location = existing_conf["traffic_ops_golang"]["traffic_vault_config"]["aes_key_location"]
+			write_encryption_key(traffic_vault_aes_encryption_location)
+		except KeyError as e:
+			logging.warning("no traffic vault aes encryption key location specified, using default /opt/traffic_ops/app/aes.key")
+
+	return traffic_vault_backend == "postgres"
 
 def db_connection_string(dbconf): # type: (dict) -> str
 	"""
@@ -1042,7 +1089,7 @@ def exec_psql(conn_str, query, **args): # type: (str, str, dict) -> str
 	else:
 		return string.strip(output[0])
 
-def invoke_db_admin_pl(action, root): # type: (str, str) -> None
+def invoke_db_admin_pl(action, root, tv): # type: (str, str, bool) -> None
 	"""
 	Exectues admin with the given action, and looks for it from the given root directory.
 	"""
@@ -1051,6 +1098,8 @@ def invoke_db_admin_pl(action, root): # type: (str, str) -> None
 	# should be fixed at some point, IMO, but for now this works.
 	os.chdir(path)
 	cmd = [os.path.join(path, "db/admin"), "--env=production", action]
+	if tv:
+		cmd = [os.path.join(path, "db/admin"), "--trafficvault","--env=production", action]
 	proc = subprocess.Popen(
 		cmd,
 		stderr=subprocess.PIPE,
@@ -1063,7 +1112,7 @@ def invoke_db_admin_pl(action, root): # type: (str, str) -> None
 		raise OSError("Database {action} failed".format(action=action))
 	logging.info("Database %s succeeded", action)
 
-def setup_database_data(conn_str, user, param_conf, root): # type: (str, User, dict, str) -> None
+def setup_database_data(conn_str, user, param_conf, root, postgresTV): # type: (str, User, dict, str, bool) -> None
 	"""
 	Sets up all necessary initial database data using `/usr/bin/sql`
 	"""
@@ -1080,11 +1129,17 @@ def setup_database_data(conn_str, user, param_conf, root): # type: (str, User, d
 	if exec_psql(conn_str, tables_found_query) == "t":
 		logging.info("Found existing tables skipping table creation")
 	else:
-		invoke_db_admin_pl("load_schema", root)
+		invoke_db_admin_pl("load_schema", root, False)
+
+	invoke_db_admin_pl("migrate", root, False)
+	invoke_db_admin_pl("seed", root, False)
+	invoke_db_admin_pl("patch", root, False)
 
-	invoke_db_admin_pl("migrate", root)
-	invoke_db_admin_pl("seed", root)
-	invoke_db_admin_pl("patch", root)
+	if postgresTV:
+		invoke_db_admin_pl("create_user", root, True)
+		invoke_db_admin_pl("createdb", root, True)
+		invoke_db_admin_pl("load_schema", root, True)
+		invoke_db_admin_pl("migrate", root, True)
 
 	hashed_pass = hash_pass(user.password)
 	insert_admin_query = '''
@@ -1192,6 +1247,7 @@ no_database, # type: bool
 	Runs the main routine given the parsed arguments as input.
 	:rtype: int
 	"""
+	postgresTV = False
 	if debug:
 		logging.getLogger().setLevel(logging.DEBUG)
 	else:
@@ -1244,7 +1300,9 @@ no_database, # type: bool
 
 	try:
 		dbconf = generate_db_conf(user_input[DATABASE_CONF_FILE], DATABASE_CONF_FILE, automatic, root_dir)
+		tv_dbconf = generate_db_conf(user_input[TV_DATABASE_CONF_FILE], TV_DATABASE_CONF_FILE, automatic, root_dir)
 		todbconf = generate_todb_conf(user_input[DB_CONF_FILE], DB_CONF_FILE, automatic, root_dir, dbconf)
+		tv_todbconf = generate_todb_conf(user_input[TV_DB_CONF_FILE], TV_DB_CONF_FILE, automatic, root_dir, tv_dbconf)
 		generate_ldap_conf(user_input[LDAP_CONF_FILE], LDAP_CONF_FILE, automatic, root_dir)
 		admin_conf = generate_users_conf(
 		user_input[USERS_CONF_FILE],
@@ -1275,7 +1333,7 @@ no_database, # type: bool
 		return 1
 
 	try:
-		generate_cdn_conf(user_input[CDN_CONF_FILE], CDN_CONF_FILE, automatic, root_dir)
+		postgresTV = generate_cdn_conf(user_input[CDN_CONF_FILE], CDN_CONF_FILE, automatic, root_dir)
 	except OSError as e:
 		logging.critical("Generating cdn.conf: %s", e)
 		return 1
@@ -1305,7 +1363,7 @@ no_database, # type: bool
 			)
 
 		try:
-			setup_database_data(conn_str, admin_conf, paramconf, root_dir)
+			setup_database_data(conn_str, admin_conf, paramconf, root_dir, postgresTV)
 		except (subprocess.CalledProcessError, OSError) as e:
 			db_connect_failed()
 			return 1
diff --git a/traffic_ops/install/bin/input.json b/traffic_ops/install/bin/input.json
index 1fcfa1c..45709e5 100644
--- a/traffic_ops/install/bin/input.json
+++ b/traffic_ops/install/bin/input.json
@@ -67,6 +67,33 @@
       "hidden": "1"
     }
   ],
+  "/opt/traffic_ops/app/conf/production/tv.conf": [
+    {
+      "Database type": "Pg",
+      "config_var": "type"
+    },
+    {
+      "Database name": "traffic_ops_db",
+      "config_var": "dbname"
+    },
+    {
+      "Database server hostname IP or FQDN": "localhost",
+      "config_var": "hostname"
+    },
+    {
+      "Database port number": "5432",
+      "config_var": "port"
+    },
+    {
+      "Traffic Ops database user": "dbuser",
+      "config_var": "user"
+    },
+    {
+      "Traffic Ops database password": "dbpass",
+      "config_var": "password",
+      "hidden": "1"
+    }
+  ],
   "/opt/traffic_ops/app/db/dbconf.yml": [
     {
       "Database server root (admin) username": "dbuser",
@@ -78,6 +105,17 @@
       "hidden": "1"
     }
   ],
+  "/opt/traffic_ops/app/db/trafficvault/dbconf.yml": [
+    {
+      "Database server root (admin) username": "dbuser",
+      "config_var": "pgUser"
+    },
+    {
+      "Database server admin password": "dbpass",
+      "config_var": "pgPassword",
+      "hidden": "1"
+    }
+  ],
   "/opt/traffic_ops/install/data/json/openssl_configuration.json": [
     {
       "Do you want to generate a certificate?": "yes",
diff --git a/traffic_ops/install/bin/postinstall.test.sh b/traffic_ops/install/bin/postinstall.test.sh
index 024ec8d..e589b00 100755
--- a/traffic_ops/install/bin/postinstall.test.sh
+++ b/traffic_ops/install/bin/postinstall.test.sh
@@ -104,6 +104,7 @@ 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
 {
@@ -171,6 +172,38 @@ cat <<- EOF > "$ROOT_DIR/defaults.json"
 			"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/db/dbconf.yml": [
 		{
 			"Database server root (admin) user": "postgres",
@@ -183,6 +216,18 @@ cat <<- EOF > "$ROOT_DIR/defaults.json"
 			"hidden": true
 		}
 	],
+	"/opt/traffic_ops/app/db/trafficvault/dbconf.yml": [
+		{
+			"Database server root (admin) user": "postgres",
+			"config_var": "pgUser",
+			"hidden": false
+		},
+		{
+			"Password for database server admin": "${TO_PASSWORD}",
+			"config_var": "pgPassword",
+			"hidden": true
+		}
+	],
 	"/opt/traffic_ops/app/conf/cdn.conf": [
 		{
 			"Generate a new secret?": "yes",