Skip to content

Commit 3dd89ca

Browse files
committed
Add warning if using old version
Perform a check in watchdog to see if newer version of amazon-efs-utils is available on yum or github. If so, log a warning to the watchdog log file. To disable this version check, we've added a config value, enable_version_check. If check fails, we silently fail. Also added Amazon Linux 2023 as supported distribution.
1 parent 550df10 commit 3dd89ca

File tree

5 files changed

+447
-28
lines changed

5 files changed

+447
-28
lines changed

README.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ The `efs-utils` package has been verified against the following Linux distributi
1010
|----------------------| ----- | --------- |
1111
| Amazon Linux 2017.09 | `rpm` | `upstart` |
1212
| Amazon Linux 2 | `rpm` | `systemd` |
13+
| Amazon Linux 2023 | `rpm` | `systemd` |
1314
| CentOS 7 | `rpm` | `systemd` |
1415
| CentOS 8 | `rpm` | `systemd` |
1516
| RHEL 7 | `rpm`| `systemd` |
@@ -72,6 +73,7 @@ The `efs-utils` package has been verified against the following MacOS distributi
7273
- [The way to access instance metadata](#the-way-to-access-instance-metadata)
7374
- [Use the assumed profile credentials for IAM](#use-the-assumed-profile-credentials-for-iam)
7475
- [Enabling FIPS Mode](#enabling-fips-mode)
76+
- [Disabling Version Check](#disabling-version-check)
7577
- [License Summary](#license-summary)
7678

7779

@@ -558,6 +560,18 @@ Threading:PTHREAD Sockets:POLL,IPv6 SSL:ENGINE,OCSP,FIPS Auth:LIBWRAP
558560

559561
For more information on how to configure OpenSSL with FIPS see the [OpenSSL FIPS README](https://github.com/openssl/openssl/blob/master/README-FIPS.md).
560562

563+
## Disabling Version Check
564+
By default, once an hour, the watchdog daemon service will check to see if a newer version of amazon-efs-utils is available on github or yum.
565+
You can disable this check by setting the `enable_version_check` field in `/etc/amazon/efs/efs-utils.conf` to `false`. For example,
566+
```bash
567+
sudo sed -i 's/enable_version_check = true/enable_version_check = false/' /etc/amazon/efs/efs-utils.conf
568+
```
569+
Or on MacOS:
570+
```bash
571+
VERSION=<efs-utils version, e.g. 1.34.1>
572+
sudo sed -i 's/enable_version_check = true/enable_version_check = false/' /usr/local/Cellar/amazon-efs-utils/${VERSION}/libexec/etc/amazon/efs/efs-utils.conf
573+
```
574+
561575
## License Summary
562576

563577
This code is made available under the MIT license.

dist/efs-utils.conf

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,10 @@ stunnel_health_check_enabled = true
8888
stunnel_health_check_interval_min = 5
8989
stunnel_health_check_command_timeout_sec = 30
9090

91+
# By default, once an hour, the watchdog process will check the latest version of amazon-efs-utils available
92+
# on yum and github. If we detect that the currently installed version is outdated, we'll log a warning.
93+
enable_version_check = true
94+
9195
[cloudwatch-log]
9296
# enabled = true
9397
log_group_name = /aws/efs/utils

src/mount_efs/__init__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1623,11 +1623,11 @@ def bootstrap_tls(
16231623
cert_details["privateKey"] = get_private_key_path()
16241624
cert_details["fsId"] = fs_id
16251625

1626-
start_watchdog(init_system)
1627-
16281626
if not os.path.exists(state_file_dir):
16291627
create_required_directory(config, state_file_dir)
16301628

1629+
start_watchdog(init_system)
1630+
16311631
verify_level = int(options.get("verify", DEFAULT_STUNNEL_VERIFY_LEVEL))
16321632
ocsp_enabled = is_ocsp_enabled(config, options)
16331633

src/watchdog/__init__.py

Lines changed: 254 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@
6363
CONFIG_SECTION = "mount-watchdog"
6464
MOUNT_CONFIG_SECTION = "mount"
6565
CLIENT_INFO_SECTION = "client-info"
66+
ENABLE_VERSION_CHECK = "enable_version_check"
6667
CLIENT_SOURCE_STR_LEN_LIMIT = 100
6768
DISABLE_FETCH_EC2_METADATA_TOKEN_ITEM = "disable_fetch_ec2_metadata_token"
6869
DEFAULT_UNKNOWN_VALUE = "unknown"
@@ -75,6 +76,7 @@
7576

7677
STATE_FILE_DIR = "/var/run/efs"
7778
STUNNEL_PID_FILE = "stunnel.pid"
79+
LAST_VERSION_CHECK_FILE = "last_version_check.json"
7880

7981
DEFAULT_NFS_PORT = "2049"
8082
PRIVATE_KEY_FILE = "/etc/amazon/efs/privateKey.pem"
@@ -182,6 +184,255 @@
182184
STUNNEL_INSTALLATION_MESSAGE = "Please install it following the instructions at: https://docs.aws.amazon.com/efs/latest/ug/using-amazon-efs-utils.html#upgrading-stunnel"
183185

184186

187+
def check_if_platform_is_mac():
188+
return sys.platform in MAC_OS_PLATFORM_LIST
189+
190+
191+
def get_system_release_version():
192+
# MacOS does not maintain paths /etc/os-release and /etc/sys-release
193+
if check_if_platform_is_mac():
194+
return platform.platform()
195+
196+
try:
197+
with open(SYSTEM_RELEASE_PATH) as f:
198+
return f.read().strip()
199+
except IOError:
200+
logging.debug("Unable to read %s", SYSTEM_RELEASE_PATH)
201+
202+
try:
203+
with open(OS_RELEASE_PATH) as f:
204+
for line in f:
205+
if "PRETTY_NAME" in line:
206+
return line.split("=")[1].strip()
207+
except IOError:
208+
logging.debug("Unable to read %s", OS_RELEASE_PATH)
209+
210+
return DEFAULT_UNKNOWN_VALUE
211+
212+
213+
class Version:
214+
"""This class is used for the version check logic. An instance of this class represents
215+
a semantic version following the format major.minor.patch. It is useful for comparing versions."""
216+
217+
def __init__(self, version_str):
218+
self.version_str = version_str
219+
220+
# The version list will have the format [major, minor, patch]
221+
self.version = version_str.split(".")
222+
if len(self.version) != 3:
223+
raise ValueError(
224+
"Version class must be instantiated with a version string that follows the format 'major.minor.patch'."
225+
)
226+
227+
def __validate_comparison_input(self, other):
228+
"""Assert that other has type Version"""
229+
if not isinstance(other, self.__class__):
230+
raise TypeError(
231+
"Version comparisons are only permitted between Version instances."
232+
)
233+
234+
def __lt__(self, other):
235+
"""returns True if self < other"""
236+
self.__validate_comparison_input(other)
237+
return (not self.__eq__(other)) and (not self.__gt__(other))
238+
239+
def __gt__(self, other):
240+
"""returns True if self > other"""
241+
self.__validate_comparison_input(other)
242+
return self.version > other.version
243+
244+
def __eq__(self, other):
245+
"""returns True if self == other"""
246+
self.__validate_comparison_input(other)
247+
return self.version == other.version
248+
249+
def __str__(self):
250+
return self.version_str
251+
252+
253+
class EFSUtilsVersionChecker:
254+
GITHUB_TIMEOUT_SEC = 0.300
255+
VERSION_CHECK_POLL_INTERVAL_SEC = 3600
256+
VERSION_CHECK_FILE_KEY = "time"
257+
GITHUB_TAG_API_URL = "https://api.github.com/repos/aws/efs-utils/tags"
258+
SHOULD_CHECK_AMZN_REPOS = "Amazon Linux" in get_system_release_version()
259+
260+
@staticmethod
261+
def get_latest_version_by_github():
262+
"""Queries the github api and returns a string with the latest tag (version) for efs-utils in the form of (e.g.) 1.34.5"""
263+
logging.debug("Querying github for the latest amazon-efs-utils version")
264+
with urlopen(
265+
EFSUtilsVersionChecker.GITHUB_TAG_API_URL,
266+
timeout=EFSUtilsVersionChecker.GITHUB_TIMEOUT_SEC,
267+
) as response:
268+
html = response.read()
269+
string = html.decode("utf-8")
270+
json_obj = json.loads(string)
271+
latest_version_dict = json_obj[0]
272+
latest_version_str = latest_version_dict["name"]
273+
logging.debug(
274+
"Latest amazon-efs-utils version found on github: "
275+
+ latest_version_str[1:]
276+
)
277+
return latest_version_str[1:]
278+
279+
@staticmethod
280+
def get_latest_version_by_yum():
281+
"""
282+
Queries yum and returns a string with the latest tag (version) for efs-utils in the form of (e.g.) 1.34.5.
283+
Returns an empty string if amazon-efs-utils is not available on Yum
284+
"""
285+
logging.debug("Querying yum for the latest amazon-efs-utils version")
286+
ps_yum = subprocess.Popen(
287+
["yum", "info", "amazon-efs-utils"],
288+
stdout=subprocess.PIPE,
289+
)
290+
ps_grep = subprocess.Popen(
291+
["grep", "Available Packages", "-A", "4"],
292+
stdin=ps_yum.stdout,
293+
stdout=subprocess.PIPE,
294+
)
295+
latest_version_str = subprocess.check_output(
296+
["awk", "/Version/ {printf $3}"], stdin=ps_grep.stdout
297+
).decode("utf-8")
298+
299+
if not latest_version_str:
300+
logging.debug("amazon-efs-utils was not found by yum")
301+
return ""
302+
303+
logging.debug(
304+
"Latest amazon-efs-utils version found on yum: " + latest_version_str
305+
)
306+
return latest_version_str
307+
308+
@staticmethod
309+
def warn_newer_version_available_yum(current_version, newer_version):
310+
message = (
311+
"We recommend you upgrade to the latest version of efs-utils by running 'yum update amazon-efs-utils'. "
312+
+ "Current version: "
313+
+ str(current_version)
314+
+ ". Latest version: "
315+
+ str(newer_version)
316+
)
317+
logging.warning(message)
318+
319+
@staticmethod
320+
def warn_newer_version_available_github(current_version, newer_version):
321+
message = (
322+
"We recommend you install the latest version of efs-utils from github. "
323+
+ "Current version: "
324+
+ str(current_version)
325+
+ ". Latest version: "
326+
+ str(newer_version)
327+
)
328+
logging.warning(message)
329+
330+
@staticmethod
331+
def get_last_version_check_time():
332+
"""
333+
Return the date that the last amazon-efs-utils version check was performed.
334+
Returns None if the version check file does not exist (indicates check hasn't happened yet).
335+
"""
336+
version_check_file = os.path.join(STATE_FILE_DIR, LAST_VERSION_CHECK_FILE)
337+
if not os.path.exists(version_check_file):
338+
return None
339+
340+
with open(version_check_file, "r") as f:
341+
data = json.load(f)
342+
if EFSUtilsVersionChecker.VERSION_CHECK_FILE_KEY not in data:
343+
return None
344+
last_version_check_time = datetime.strptime(
345+
data[EFSUtilsVersionChecker.VERSION_CHECK_FILE_KEY],
346+
CERT_DATETIME_FORMAT,
347+
)
348+
349+
return last_version_check_time
350+
351+
@staticmethod
352+
def version_check_ready():
353+
"""Inspect the last version check file and return true if the time since last version check
354+
is greater than VERSION_CHECK_POLL_INTERVAL"""
355+
last_version_check_time = EFSUtilsVersionChecker.get_last_version_check_time()
356+
if not last_version_check_time:
357+
return True
358+
359+
elapsed_seconds = (get_utc_now() - last_version_check_time).total_seconds()
360+
return elapsed_seconds >= EFSUtilsVersionChecker.VERSION_CHECK_POLL_INTERVAL_SEC
361+
362+
@staticmethod
363+
def update_version_check_file():
364+
"""Write current time into the version check file"""
365+
current_time_str = get_utc_now().strftime(CERT_DATETIME_FORMAT)
366+
dictionary = {
367+
EFSUtilsVersionChecker.VERSION_CHECK_FILE_KEY: current_time_str,
368+
}
369+
370+
if not os.path.exists(STATE_FILE_DIR):
371+
logging.warning(
372+
"update_version_check_file failed: "
373+
+ str(STATE_FILE_DIR)
374+
+ " does not exist."
375+
)
376+
return
377+
378+
with open(os.path.join(STATE_FILE_DIR, LAST_VERSION_CHECK_FILE), "w+") as f:
379+
json.dump(dictionary, f)
380+
381+
@staticmethod
382+
def check_if_using_old_version(current_version_string):
383+
"""Log a warning and print to console if newer version of amazon-efs-utils is available.
384+
The check will first query yum, and then if that fails,
385+
it will pull the latest tag from the github api.
386+
"""
387+
current_version = Version(current_version_string)
388+
389+
if EFSUtilsVersionChecker.SHOULD_CHECK_AMZN_REPOS:
390+
try:
391+
latest_yum_version = Version(
392+
EFSUtilsVersionChecker.get_latest_version_by_yum()
393+
)
394+
if latest_yum_version > current_version:
395+
EFSUtilsVersionChecker.warn_newer_version_available_yum(
396+
current_version, latest_yum_version
397+
)
398+
EFSUtilsVersionChecker.update_version_check_file()
399+
return
400+
except Exception as err:
401+
logging.debug(
402+
"Failed to query Yum for latest version of amazon-efs-utils. "
403+
+ str(err)
404+
)
405+
pass
406+
407+
try:
408+
latest_github_version = Version(
409+
EFSUtilsVersionChecker.get_latest_version_by_github()
410+
)
411+
if latest_github_version > current_version:
412+
EFSUtilsVersionChecker.warn_newer_version_available_github(
413+
current_version, latest_github_version
414+
)
415+
except Exception as err:
416+
logging.debug(
417+
"Failed to query Github for latest version of amazon-efs-utils. This is expected when Github is not reachable. "
418+
+ str(err)
419+
)
420+
pass
421+
422+
EFSUtilsVersionChecker.update_version_check_file()
423+
424+
@staticmethod
425+
def should_check_efs_utils_version(config):
426+
"""Returns True if a customer has enabled the amazon-efs-utils version check,
427+
and if the last version check occurred more than VERSION_CHECK_POLL_INTERVAL seconds ago."""
428+
version_check_enabled = get_boolean_config_item_value(
429+
config, CONFIG_SECTION, ENABLE_VERSION_CHECK, default_value=True
430+
)
431+
return (
432+
version_check_enabled and EFSUtilsVersionChecker.version_check_ready()
433+
)
434+
435+
185436
def fatal_error(user_message, log_message=None):
186437
if log_message is None:
187438
log_message = user_message
@@ -869,32 +1120,6 @@ def is_pid_running(pid):
8691120
return False
8701121

8711122

872-
def check_if_platform_is_mac():
873-
return sys.platform in MAC_OS_PLATFORM_LIST
874-
875-
876-
def get_system_release_version():
877-
# MacOS does not maintain paths /etc/os-release and /etc/sys-release
878-
if check_if_platform_is_mac():
879-
return platform.platform()
880-
881-
try:
882-
with open(SYSTEM_RELEASE_PATH) as f:
883-
return f.read().strip()
884-
except IOError:
885-
logging.debug("Unable to read %s", SYSTEM_RELEASE_PATH)
886-
887-
try:
888-
with open(OS_RELEASE_PATH) as f:
889-
for line in f:
890-
if "PRETTY_NAME" in line:
891-
return line.split("=")[1].strip()
892-
except IOError:
893-
logging.debug("Unable to read %s", OS_RELEASE_PATH)
894-
895-
return DEFAULT_UNKNOWN_VALUE
896-
897-
8981123
def find_command_path(command, install_method):
8991124
# If not running on macOS, use linux paths
9001125
if not check_if_platform_is_mac():
@@ -2149,6 +2374,9 @@ def main():
21492374
)
21502375
check_child_procs(child_procs)
21512376

2377+
if EFSUtilsVersionChecker.should_check_efs_utils_version(config):
2378+
EFSUtilsVersionChecker.check_if_using_old_version(VERSION)
2379+
21522380
time.sleep(poll_interval_sec)
21532381
else:
21542382
logging.info("amazon-efs-mount-watchdog is not enabled")

0 commit comments

Comments
 (0)