Skip to content

Commit ca9da21

Browse files
committed
Added integrity check to cf-remote remote installation
Ticket: ENT-13005 Signed-off-by: Victor Moene <victor.moene@northern.tech>
1 parent 82392ef commit ca9da21

5 files changed

Lines changed: 182 additions & 24 deletions

File tree

MANIFEST.in

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,5 @@ include cf_remote/nt-discovery.sh
22
include cf_remote/Vagrantfile
33
include cf_remote/default_provision.sh
44
include cf_remote/demo.sql
5+
include cf_remote/remote-download.sh
6+

cf_remote/nt-discovery.sh

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,8 @@ run_command "command -v apt" "APT" "Cannot find apt"
7474
run_command "command -v pkg" "PKG" "Cannot find pkg"
7575
run_command "command -v zypper" "ZYPPER" "Cannot find zypper"
7676
run_command "command -v curl" "CURL" "Cannot find curl"
77+
run_command "command -v wget" "WGET" "Cannot find wget"
78+
run_command "command -v sha256sum" "SHA256SUM" "Cannot find sha256sum"
7779

7880
# ip
7981

cf_remote/remote-download.sh

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
#!/bin/bash
2+
3+
set -e
4+
5+
# Arg parsing
6+
7+
INSECURE=0
8+
HAS_WGET=0
9+
CHECKSUM=""
10+
11+
while getopts c:IW option; do
12+
case "${option}" in
13+
I)
14+
INSECURE=1
15+
;;
16+
W)
17+
HAS_WGET=1
18+
;;
19+
c)
20+
CHECKSUM=$OPTARG
21+
;;
22+
*)
23+
echo "Usage: $0 [-I] [-c checksum] package_url"
24+
exit 1
25+
;;
26+
esac
27+
done
28+
29+
shift $((OPTIND - 1))
30+
31+
PACKAGE=$1
32+
if [ -z "$PACKAGE" ]; then
33+
echo "Usage: $0 [-I] [-c checksum] package_url"
34+
exit 1;
35+
fi
36+
37+
# temp file
38+
39+
tmpfile=$(mktemp)
40+
cleanup() {
41+
rm -f "$tmpfile"
42+
}
43+
trap cleanup EXIT QUIT TERM
44+
45+
# Download
46+
47+
if [ "$HAS_WGET" -eq 1 ]; then
48+
wget -nv -O "$tmpfile" "$PACKAGE"
49+
else
50+
curl --fail -sS -o "$tmpfile" "$PACKAGE"
51+
fi
52+
53+
# Checksum
54+
55+
filename="$(basename "$PACKAGE")"
56+
57+
if [ -n "$CHECKSUM" ]; then
58+
hash="$(sha256sum "$tmpfile" | awk '{print $1}')"
59+
60+
if [[ "$CHECKSUM" != "$hash" ]]; then
61+
if [ "$INSECURE" -eq 0 ]; then
62+
echo "Package '$PACKAGE' doesn't match the expected checksum '$CHECKSUM'. Run with --insecure to skip"
63+
exit 1
64+
fi
65+
echo "Package '$PACKAGE' doesn't match the expected checksum '$CHECKSUM'. Continuing due to insecure flag"
66+
fi
67+
fi
68+
69+
mv "$tmpfile" "$filename"
70+

cf_remote/remote.py

Lines changed: 107 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#!/usr/bin/env python3
22
import sys
33
import re
4+
45
from os.path import basename, dirname, join, exists
56
from collections import OrderedDict
67
from typing import Union
@@ -276,7 +277,17 @@ def get_info(host, *, users=None, connection=None):
276277
data["role"] = "hub" if discovery.get("NTD_CFHUB") else "client"
277278

278279
data["bin"] = {}
279-
for bin in ["dpkg", "rpm", "yum", "apt", "pkg", "zypper", "curl"]:
280+
for bin in [
281+
"dpkg",
282+
"rpm",
283+
"yum",
284+
"apt",
285+
"pkg",
286+
"zypper",
287+
"curl",
288+
"wget",
289+
"sha256sum",
290+
]:
280291
path = discovery.get("NTD_{}".format(bin.upper()))
281292
if path:
282293
data["bin"][bin] = path
@@ -432,7 +443,7 @@ def bootstrap_host(host_data, policy_server, *, connection=None, trust_server=Tr
432443
def _package_from_list(tags, extension, packages):
433444
artifacts = [Artifact(None, p) for p in packages]
434445
artifact = filter_artifacts(artifacts, tags, extension)[-1]
435-
return artifact.url
446+
return artifact.url, artifact
436447

437448

438449
def _package_from_releases(
@@ -445,7 +456,7 @@ def _package_from_releases(
445456
release = releases.pick_version(version)
446457
if release is None:
447458
print("Could not find a release for version {}".format(version))
448-
return None
459+
return None, None
449460

450461
release.init_download()
451462

@@ -455,7 +466,7 @@ def _package_from_releases(
455466
version, edition
456467
)
457468
)
458-
return None
469+
return None, None
459470

460471
artifacts = release.find(tags, extension)
461472
if not artifacts:
@@ -464,13 +475,16 @@ def _package_from_releases(
464475
"hub" if "hub" in tags else "client"
465476
)
466477
)
467-
return None
478+
return None, None
468479
artifact = artifacts[-1]
469480
if remote_download:
470-
return artifact.url
481+
return artifact.url, artifact
471482
else:
472-
return download_package(
473-
artifact.url, checksum=artifact.checksum, insecure=insecure
483+
return (
484+
download_package(
485+
artifact.url, checksum=artifact.checksum, insecure=insecure
486+
),
487+
artifact,
474488
)
475489

476490

@@ -514,13 +528,85 @@ def get_package_from_host_info(
514528
tags.extend(tag for tag in package_tags if tag != "msi")
515529

516530
if packages is None: # No command line argument given
517-
package = _package_from_releases(
531+
package, artifact = _package_from_releases(
518532
tags, extension, version, edition, remote_download, insecure
519533
)
520534
else:
521-
package = _package_from_list(tags, extension, packages)
535+
package, artifact = _package_from_list(tags, extension, packages)
536+
537+
return package, artifact
522538

523-
return package
539+
540+
def _remote_download(
541+
host, package, artifact, pkg_binary, insecure=False, connection=None
542+
):
543+
544+
if not pkg_binary:
545+
return None
546+
if "sha256sum" not in pkg_binary:
547+
if not insecure:
548+
log.error(
549+
"Cannot check file integrity. sha256sum is not installed on host. Run with --insecure to skip"
550+
)
551+
return None
552+
log.warning(
553+
"Cannot check file integrity. sha256sum is not installed on host. Continuing due to insecure flag"
554+
)
555+
556+
if "wget" in pkg_binary:
557+
has_wget = True
558+
elif "curl" in pkg_binary:
559+
has_wget = False
560+
else:
561+
log.error(
562+
"Cannot download remotely. wget and/or curl are not installed on host"
563+
)
564+
return None
565+
566+
if not (artifact and artifact.checksum):
567+
if not insecure:
568+
log.error(
569+
"Cannot check file integrity. No artifact associated with package '{}' found. Run with --insecure to skip".format(
570+
package
571+
)
572+
)
573+
return None
574+
log.warning(
575+
"Cannot check file integrity. No artifact associated with package '{}' found. Continuing due to insecure flag".format(
576+
package
577+
)
578+
)
579+
580+
cf_remote_dir = dirname(__file__)
581+
script_path = join(cf_remote_dir, "remote-download.sh")
582+
if not exists(script_path):
583+
sys.exit("%s does not exist" % script_path)
584+
scp(
585+
script_path,
586+
host,
587+
connection,
588+
hide=True,
589+
)
590+
591+
args = ""
592+
if insecure:
593+
args += "-I "
594+
if has_wget:
595+
args += "-W "
596+
if artifact:
597+
args += "-c {} ".format(artifact.checksum)
598+
args += package
599+
600+
ret = ssh_cmd(connection, "bash remote-download.sh {}".format(args), errors=True)
601+
602+
if ret is None:
603+
return None
604+
if insecure:
605+
log.warning(ret)
606+
607+
log.debug("Successfully remotely installed package on host")
608+
609+
return basename(package)
524610

525611

526612
@auto_connect
@@ -552,9 +638,10 @@ def install_host(
552638
elif packages and len(packages) == 1:
553639
package = packages[0]
554640

641+
artifact = None
555642
if not package:
556643
try:
557-
package = get_package_from_host_info(
644+
package, artifact = get_package_from_host_info(
558645
data.get("package_tags"),
559646
data.get("bin"),
560647
data.get("arch"),
@@ -574,19 +661,16 @@ def install_host(
574661
return 1
575662

576663
if remote_download:
577-
if ("bin" not in data) or ("curl" not in data["bin"]):
578-
log.error(
579-
"Couldn't download remotely. Curl is not installed on host '%s'" % host
580-
)
581-
return 1
582-
583-
print("Downloading '%s' on '%s' using curl" % (package, host))
584-
r = ssh_cmd(
585-
cmd="curl --fail -O {}".format(package), connection=connection, errors=True
664+
package = _remote_download(
665+
host,
666+
package,
667+
artifact,
668+
data.get("bin"),
669+
connection=connection,
670+
insecure=insecure,
586671
)
587-
if r is None:
672+
if package is None:
588673
return 1
589-
package = basename(package)
590674
elif not getattr(connection, "is_local", False):
591675
scp(package, host, connection=connection)
592676
package = basename(package)

cf_remote/utils.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -314,7 +314,7 @@ def parse_envfile(text):
314314
if not key:
315315
return error_and_none("Invalid env file format: Key missing")
316316

317-
if not re.fullmatch(r"([A-Z]+\_?)+", key):
317+
if not re.fullmatch(r"[A-Z][A-Z0-9]*(\_[A-Z0-9]+)*", key):
318318
return error_and_none("Invalid env file format: Invalid key")
319319

320320
if not (val.startswith('"') and val.endswith('"')):

0 commit comments

Comments
 (0)