changeset 3698:1d719d4ef18f

mod_aws_profile: New module for role-based access to AWS APIs
author Matthew Wild <mwild1@gmail.com>
date Tue, 08 Oct 2019 17:32:50 +0100
parents a07bd12fe554
children 1f68287138e3
files mod_aws_profile/README.markdown mod_aws_profile/mod_aws_profile.lua
diffstat 2 files changed, 96 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mod_aws_profile/README.markdown	Tue Oct 08 17:32:50 2019 +0100
@@ -0,0 +1,37 @@
+# Introduction
+
+This module adds support for reading AWS IAM access credentials from EC2 instance metadata,
+to allow Prosody modules to gain role-based access to AWS services.
+
+# Configuring
+
+``` {.lua}
+modules_enabled = {
+    "aws_profile";
+}
+```
+
+There is no other configuration.
+
+# Usage in other modules
+
+Other modules can import the credentials as a shared table:
+
+``` {.lua}
+local aws_credentials = module:shared("/*/aws_profile/credentials");
+do_something(aws_credentials.access_key, aws_credentials.secret_key);
+```
+
+Note that credentials are time-limited, and will change periodically. The
+shared table will automatically be updated. If you need to know when this
+happens, you can also hook the `'aws_profile/credentials-refreshed'` event:
+
+``` {.lua}
+module:hook_global("aws_profile/credentials-refreshed", function (new_credentials)
+  -- do something with new_credentials.access_key/secret_key
+end);
+```
+
+# Compatibility
+
+Meant for use with Prosody 0.11.x, may work in older versions.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mod_aws_profile/mod_aws_profile.lua	Tue Oct 08 17:32:50 2019 +0100
@@ -0,0 +1,59 @@
+local http = require "net.http";
+local json = require "util.json";
+local parse_timestamp = require "util.datetime".parse;
+
+module:set_global();
+
+local current_credentials = module:shared("/*/aws_profile/credentials");
+
+local function get_role_credentials(role_name, cb)
+	http.request("http://169.254.169.254/latest/meta-data/iam/security-credentials/"..role_name, nil, function (credentials_json)
+		local credentials = credentials_json and json.decode(credentials_json);
+		if not credentials or not (credentials.AccessKeyId and credentials.SecretAccessKey) then
+			module:log("warn", "Failed to fetch credentials for %q", role_name);
+			cb(nil);
+			return;
+		end
+		local expiry = parse_timestamp(credentials.Expiration);
+		local ttl = os.difftime(expiry, os.time());
+		cb({
+			access_key = credentials.AccessKeyId;
+			secret_key = credentials.SecretAccessKey;
+			ttl = ttl;
+			expiry = expiry;
+		});
+	end);
+end
+
+local function get_credentials(cb)
+	http.request("http://169.254.169.254/latest/meta-data/iam/security-credentials", nil, function (role_name)
+		role_name = role_name and role_name:match("%S+");
+		if not role_name then
+			module:log("warn", "Unable to discover role name");
+			cb(nil);
+			return;
+		end
+		get_role_credentials(role_name, cb);
+	end);
+end
+
+function refresh_credentials(force)
+	if not force and current_credentials.expiry and current_credentials.expiry - os.time() > 300 then
+		return;
+	end
+	get_credentials(function (credentials)
+		if not credentials then
+			module:log("warn", "Failed to refresh credentials!");
+			return;
+		end
+		current_credentials.access_key = credentials.access_key;
+		current_credentials.secret_key = credentials.secret_key;
+		current_credentials.expiry = credentials.expiry;
+		module:timer(credentials.ttl or 240, refresh_credentials);
+		module:fire_event("aws_profile/credentials-refreshed", current_credentials);
+	end);
+end
+
+function module.load()
+	refresh_credentials(true);
+end