Skip to content

Commit

Permalink
Merge pull request #959 from amazeeio/577-api-disallow-duplicate-ssh-…
Browse files Browse the repository at this point in the history
…keys

API: Disallow duplicate ssh keys
  • Loading branch information
Schnitzel authored Mar 21, 2019
2 parents 9586eac + 941e82b commit bc94673
Show file tree
Hide file tree
Showing 9 changed files with 100 additions and 17 deletions.
4 changes: 4 additions & 0 deletions services/api-db/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
ARG IMAGE_REPO
FROM ${IMAGE_REPO:-lagoon}/mariadb

USER root
RUN apk add --no-cache openssh-keygen
USER mysql

ENV MARIADB_DATABASE=infrastructure \
MARIADB_USER=api \
MARIADB_PASSWORD=api \
Expand Down
11 changes: 6 additions & 5 deletions services/api-db/docker-entrypoint-initdb.d/00-tables.sql
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,12 @@ USE infrastructure;
-- Tables

CREATE TABLE IF NOT EXISTS ssh_key (
id int NOT NULL auto_increment PRIMARY KEY,
name varchar(100) NOT NULL,
key_value varchar(5000) NOT NULL,
key_type ENUM('ssh-rsa', 'ssh-ed25519') NOT NULL DEFAULT 'ssh-rsa',
created timestamp DEFAULT CURRENT_TIMESTAMP
id int NOT NULL auto_increment PRIMARY KEY,
name varchar(100) NOT NULL,
key_value varchar(5000) NOT NULL,
key_type ENUM('ssh-rsa', 'ssh-ed25519') NOT NULL DEFAULT 'ssh-rsa',
key_fingerprint char(51) NULL UNIQUE,
created timestamp DEFAULT CURRENT_TIMESTAMP
);

CREATE TABLE IF NOT EXISTS user (
Expand Down
19 changes: 19 additions & 0 deletions services/api-db/docker-entrypoint-initdb.d/01-migrations.sql
Original file line number Diff line number Diff line change
Expand Up @@ -588,6 +588,24 @@ CREATE OR REPLACE PROCEDURE
END;
$$

CREATE OR REPLACE PROCEDURE
add_key_fingerprint_to_ssh_key()

BEGIN
IF NOT EXISTS(
SELECT NULL
FROM INFORMATION_SCHEMA.COLUMNS
WHERE
table_name = 'ssh_key'
AND table_schema = 'infrastructure'
AND column_name = 'key_fingerprint'
) THEN
ALTER TABLE `ssh_key`
ADD `key_fingerprint` char(51) NULL UNIQUE;
END IF;
END;
$$

DELIMITER ;

CALL add_production_environment_to_project();
Expand Down Expand Up @@ -618,6 +636,7 @@ CALL add_default_value_to_task_status();
CALL add_scope_to_env_vars();
CALL add_deleted_to_environment_backup();
CALL convert_task_command_to_text();
CALL add_key_fingerprint_to_ssh_key();

-- Drop legacy SSH key procedures
DROP PROCEDURE IF EXISTS CreateProjectSshKey;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
#!/bin/bash

set -eu -o pipefail

# disable globbing
set -f;
# set field separator to NL (only)
IFS=$'\n';

DUPLICATE_SSHKEY_RECORDS=( $(mysql infrastructure --batch -sse "SELECT count(*) count, key_value FROM ssh_key GROUP BY key_value HAVING count > 1") );

if [ ${#DUPLICATE_SSHKEY_RECORDS[@]} -ne 0 ]; then
echo "====== FOUND DUPLICATE SSH KEYS IN LAGOON API DATABASE!"
for DUPLICATE_SSHKEY_RECORD in "${DUPLICATE_SSHKEY_RECORDS[@]}";
do
echo ""
echo $(awk '{print $2}' <<< "$DUPLICATE_SSHKEY_RECORD");
done;
echo ""
echo "====== PLEASE REMOVE DUPLICATED SSH KEYS AND RUN INITIALIZATION OF DB AGAIN"
#exit 1
fi

echo "=== Starting SSH KEY Fingerprint generation"

# get all ssh keys which have no fingerprint yet from api-db into a bash array
SSHKEY_RECORDS=( $(mysql infrastructure --batch -sse "SELECT id, key_type, key_value FROM ssh_key WHERE key_fingerprint is NULL") );

for SSHKEY_RECORD in "${SSHKEY_RECORDS[@]}";
do
RECORD_ID=$(awk '{print $1}' <<< "$SSHKEY_RECORD");
SSHKEY=$(awk '{print $2, $3}' <<< "$SSHKEY_RECORD");
FINGERPRINT=$(ssh-keygen -lE sha256 -f - <<< "$SSHKEY" | awk '{print $2}');
echo "Adding SSH Key Fingerprint for SSH KEY '$RECORD_ID': $FINGERPRINT"
mysql infrastructure -e "UPDATE ssh_key SET key_fingerprint = '$FINGERPRINT' WHERE id = $RECORD_ID";
done;

echo "=== Finished SSH KEY Fingerprint generation"
13 changes: 9 additions & 4 deletions services/api-db/rerun_initdb.sh
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
#!/bin/sh
#!/bin/bash

INITDB_DIR="/docker-entrypoint-initdb.d"

for sql_file in `ls $INITDB_DIR`; do mysql --verbose < "$INITDB_DIR/$sql_file" ; done
for f in `ls /docker-entrypoint-initdb.d/*`; do
case "$f" in
*.sh) echo "$0: running $f"; . "$f" ;;
*.sql) echo "$0: running $f"; cat $f| tee | mysql --verbose; echo ;;
*) echo "$0: ignoring $f" ;;
esac
echo
done
6 changes: 6 additions & 0 deletions services/api/src/resources/sshKey/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,12 @@ const validateSshKey = (key /* : string */) /* : boolean */ => {
}
};

const getSshKeyFingerprint = (key /* : string */) /* : string */ => {
const parsed = sshpk.parseKey(key, 'ssh');
return parsed.fingerprint('sha256', 'ssh').toString();
};

module.exports = {
validateSshKey,
getSshKeyFingerprint,
};
22 changes: 14 additions & 8 deletions services/api/src/resources/sshKey/resolvers.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
const R = require('ramda');
const sqlClient = require('../../clients/sqlClient');
const { isPatchEmpty, prepare, query } = require('../../util/db');
const { validateSshKey } = require('.');
const { validateSshKey, getSshKeyFingerprint } = require('.');
const Sql = require('./sql');

/* ::
Expand Down Expand Up @@ -66,8 +66,9 @@ const addSshKey = async (
{ credentials: { role, userId: credentialsUserId } },
) => {
const keyType = sshKeyTypeToString(unformattedKeyType);
const keyFormatted = formatSshKey({ keyType, keyValue });

if (!validateSshKey(formatSshKey({ keyType, keyValue }))) {
if (!validateSshKey(keyFormatted)) {
throw new Error('Invalid SSH key format! Please verify keyType + keyValue');
}

Expand All @@ -86,6 +87,7 @@ const addSshKey = async (
name,
keyValue,
keyType,
keyFingerprint: getSshKeyFingerprint(keyFormatted),
}),
);
await query(sqlClient, Sql.addSshKeyToUser({ sshKeyId: insertId, userId }));
Expand Down Expand Up @@ -125,16 +127,20 @@ const updateSshKey = async (
throw new Error('Input patch requires at least 1 attribute');
}

if (
(keyType || keyValue) &&
!validateSshKey(formatSshKey({ keyType, keyValue }))
) {
throw new Error('Invalid SSH key format! Please verify keyType + keyValue');
let keyFingerprint = null;
if ((keyType || keyValue)) {
const keyFormatted = formatSshKey({ keyType, keyValue });

if (!validateSshKey(keyFormatted)) {
throw new Error('Invalid SSH key format! Please verify keyType + keyValue');
}

keyFingerprint = getSshKeyFingerprint(keyFormatted);
}

await query(
sqlClient,
Sql.updateSshKey({ id, patch: { name, keyType, keyValue } }),
Sql.updateSshKey({ id, patch: { name, keyType, keyValue, keyFingerprint } }),
);
const rows = await query(sqlClient, Sql.selectSshKey(id));

Expand Down
3 changes: 3 additions & 0 deletions services/api/src/resources/sshKey/sql.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,11 +58,13 @@ const Sql /* : SqlObj */ = {
name,
keyValue,
keyType,
keyFingerprint,
} /* : {
id: number,
name: string,
keyValue: string,
keyType: string,
keyFingerprint: string,
} */,
) =>
knex('ssh_key')
Expand All @@ -71,6 +73,7 @@ const Sql /* : SqlObj */ = {
name,
key_value: keyValue,
key_type: keyType,
key_fingerprint: keyFingerprint,
})
.toString(),
addSshKeyToUser: (
Expand Down
1 change: 1 addition & 0 deletions services/api/src/typeDefs.js
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ const typeDefs = gql`
name: String
keyValue: String
keyType: String
keyFingerprint: String
created: String
}
Expand Down

0 comments on commit bc94673

Please sign in to comment.