diff --git a/integrations/aws/manifest.yaml b/integrations/aws/manifest.yaml new file mode 100644 index 0000000..c152fdd --- /dev/null +++ b/integrations/aws/manifest.yaml @@ -0,0 +1,126 @@ +id: aws +name: AWS +version: 1.0.0 +description: "Amazon Web Services (EC2, IAM, STS) — cloud containment: describe instances and security groups, authorize/revoke security-group ingress rules, change an instance's security groups (isolate), stop instances, and deactivate a compromised IAM access key. AWS Signature V4 authentication; stdlib-only, no extra Python dependencies." +changelog: "1.0.0 — Initial release: EC2 describe instances/security-groups, authorize/revoke ingress, modify instance security groups, stop instances; IAM list/update access keys; STS caller identity." +category: cloud + +# Per-instance configuration. Requests are signed with AWS Signature V4. +# Use an IAM user/role access key with EC2 + IAM permissions. session_token is +# only needed for temporary (STS) credentials. +config_schema: + properties: + access_key_id: + type: string + description: "AWS access key ID" + secret_access_key: + type: string + description: "AWS secret access key" + x-soar-sensitive: true + region: + type: string + description: "Default AWS region (e.g. eu-west-1)" + default: "us-east-1" + session_token: + type: string + description: "Optional STS session token (for temporary credentials)" + x-soar-sensitive: true + required: + - access_key_id + - secret_access_key + +commands: + - id: describe_instances + name: aws-describe-instances + description: "Describe EC2 instances (optionally a single instance by ID)." + risk: read + inputs_schema: + properties: + instance_id: { type: string, description: "Optional instance ID to fetch a single instance" } + region: { type: string, description: "Region override" } + required: [] + outputs_schema: { properties: {} } + - id: describe_security_groups + name: aws-describe-security-groups + description: "Describe EC2 security groups (optionally a single group by ID)." + risk: read + inputs_schema: + properties: + group_id: { type: string, description: "Optional security group ID" } + region: { type: string, description: "Region override" } + required: [] + outputs_schema: { properties: {} } + - id: authorize_security_group_ingress + name: aws-authorize-security-group-ingress + description: "Add an inbound rule to a security group." + inputs_schema: + properties: + group_id: { type: string, description: "Security group ID" } + protocol: { type: string, description: "IP protocol (tcp, udp, icmp, or -1 for all)" } + from_port: { type: number, description: "Start port" } + to_port: { type: number, description: "End port" } + cidr: { type: string, description: "Source CIDR (e.g. 203.0.113.0/24)" } + region: { type: string, description: "Region override" } + required: [group_id, protocol, cidr] + outputs_schema: { properties: {} } + - id: revoke_security_group_ingress + name: aws-revoke-security-group-ingress + description: "Remove an inbound rule from a security group (containment)." + inputs_schema: + properties: + group_id: { type: string, description: "Security group ID" } + protocol: { type: string, description: "IP protocol (tcp, udp, icmp, or -1 for all)" } + from_port: { type: number, description: "Start port" } + to_port: { type: number, description: "End port" } + cidr: { type: string, description: "Source CIDR to revoke" } + region: { type: string, description: "Region override" } + required: [group_id, protocol, cidr] + outputs_schema: { properties: {} } + - id: modify_instance_security_groups + name: aws-modify-instance-security-groups + description: "Replace the security groups attached to an instance (e.g. move it to an isolation group)." + inputs_schema: + properties: + instance_id: { type: string, description: "Instance ID" } + group_ids: { type: string, description: "Comma-separated security group IDs to set" } + region: { type: string, description: "Region override" } + required: [instance_id, group_ids] + outputs_schema: { properties: {} } + - id: stop_instances + name: aws-stop-instances + description: "Stop one or more EC2 instances." + inputs_schema: + properties: + instance_ids: { type: string, description: "Comma-separated instance IDs" } + force: { type: boolean, description: "Force stop (default false)" } + region: { type: string, description: "Region override" } + required: [instance_ids] + outputs_schema: { properties: {} } + - id: list_access_keys + name: aws-list-access-keys + description: "List a user's IAM access keys." + risk: read + inputs_schema: + properties: + user_name: { type: string, description: "IAM user name (omit to use the calling user)" } + required: [] + outputs_schema: { properties: {} } + - id: update_access_key + name: aws-update-access-key + description: "Activate or deactivate an IAM access key (deactivate to contain a compromised key)." + inputs_schema: + properties: + access_key_id: { type: string, description: "The access key ID to update" } + status: { type: string, description: "Active or Inactive" } + user_name: { type: string, description: "IAM user name (omit to use the calling user)" } + required: [access_key_id, status] + outputs_schema: { properties: {} } + + - id: test_connection + name: aws-test-connection + description: "Verify credentials via STS GetCallerIdentity (used by the Test button)." + risk: read + inputs_schema: + properties: {} + required: [] + outputs_schema: { properties: {} } diff --git a/integrations/aws/scripts/authorize_security_group_ingress.py b/integrations/aws/scripts/authorize_security_group_ingress.py new file mode 100644 index 0000000..54eca01 --- /dev/null +++ b/integrations/aws/scripts/authorize_security_group_ingress.py @@ -0,0 +1,151 @@ +import json, os, sys, hmac, hashlib, datetime +import urllib.parse, urllib.request, urllib.error +import xml.etree.ElementTree as ET + + +def _cfg(): + return json.loads(os.environ.get("INTEGRATION_SECRETS", "{}")) + + +def _inputs(): + return json.loads(os.environ.get("INTEGRATION_INPUTS", "{}")) + + +def _sign_key(key, date_stamp, region, service): + def _h(k, m): + return hmac.new(k, m.encode("utf-8"), hashlib.sha256).digest() + k_date = _h(("AWS4" + key).encode("utf-8"), date_stamp) + k_region = _h(k_date, region) + k_service = _h(k_region, service) + return _h(k_service, "aws4_request") + + +def _strip_ns(tag): + return tag.split("}", 1)[1] if "}" in tag else tag + + +def _xml_to_dict(elem): + d = {} + children = list(elem) + if not children: + return (elem.text or "").strip() + for c in children: + tag = _strip_ns(c.tag) + val = _xml_to_dict(c) + if tag in d: + if not isinstance(d[tag], list): + d[tag] = [d[tag]] + d[tag].append(val) + else: + d[tag] = val + return d + + +def aws_query(service, host, region, action, version, params, cfg): + # params: dict of extra query params for this Action + body_params = {"Action": action, "Version": version} + body_params.update({k: str(v) for k, v in params.items() if v is not None}) + body = urllib.parse.urlencode(sorted(body_params.items())) + + access_key = str(cfg.get("access_key_id", "")) + secret_key = str(cfg.get("secret_access_key", "")) + session_token = cfg.get("session_token") or "" + + now = datetime.datetime.utcnow() + amz_date = now.strftime("%Y%m%dT%H%M%SZ") + date_stamp = now.strftime("%Y%m%d") + + method = "POST" + canonical_uri = "/" + canonical_querystring = "" + payload_hash = hashlib.sha256(body.encode("utf-8")).hexdigest() + canonical_headers = "content-type:application/x-www-form-urlencoded; charset=utf-8\n" \ + "host:" + host + "\n" \ + "x-amz-date:" + amz_date + "\n" + signed_headers = "content-type;host;x-amz-date" + if session_token: + canonical_headers += "x-amz-security-token:" + session_token + "\n" + signed_headers = "content-type;host;x-amz-date;x-amz-security-token" + canonical_request = "\n".join([method, canonical_uri, canonical_querystring, + canonical_headers, signed_headers, payload_hash]) + + algorithm = "AWS4-HMAC-SHA256" + credential_scope = date_stamp + "/" + region + "/" + service + "/aws4_request" + string_to_sign = "\n".join([algorithm, amz_date, credential_scope, + hashlib.sha256(canonical_request.encode("utf-8")).hexdigest()]) + signing_key = _sign_key(secret_key, date_stamp, region, service) + signature = hmac.new(signing_key, string_to_sign.encode("utf-8"), hashlib.sha256).hexdigest() + + authorization = (algorithm + " Credential=" + access_key + "/" + credential_scope + + ", SignedHeaders=" + signed_headers + ", Signature=" + signature) + headers = { + "Content-Type": "application/x-www-form-urlencoded; charset=utf-8", + "X-Amz-Date": amz_date, + "Authorization": authorization, + "Accept": "application/json", + } + if session_token: + headers["X-Amz-Security-Token"] = session_token + + req = urllib.request.Request("https://" + host + "/", data=body.encode("utf-8"), headers=headers, method="POST") + with urllib.request.urlopen(req, timeout=60) as r: + raw = r.read() + root = ET.fromstring(raw) + return {_strip_ns(root.tag): _xml_to_dict(root)} + + +def _region(cfg, inputs): + return (inputs.get("region") if inputs else None) or cfg.get("region") or "us-east-1" + + +def ec2(action, params, cfg, inputs): + region = _region(cfg, inputs) + return aws_query("ec2", "ec2." + region + ".amazonaws.com", region, action, "2016-11-15", params, cfg) + + +def iam(action, params, cfg): + return aws_query("iam", "iam.amazonaws.com", "us-east-1", action, "2010-05-08", params, cfg) + + +def sts(action, params, cfg): + return aws_query("sts", "sts.amazonaws.com", "us-east-1", action, "2011-06-15", params, cfg) + + +def _run(fn): + try: + print(json.dumps(fn(_cfg(), _inputs()))) + except urllib.error.HTTPError as e: + print(json.dumps({"error": "HTTP " + str(e.code), "detail": e.read().decode("utf-8", "replace")})) + sys.exit(1) + except Exception as e: + print(json.dumps({"error": str(e)})) + sys.exit(1) + + +def main(cfg, inputs): + group_id = inputs.get("group_id") + if not group_id: + raise Exception("group_id is required") + protocol = inputs.get("protocol") + if not protocol: + raise Exception("protocol is required") + cidr = inputs.get("cidr") + if not cidr: + raise Exception("cidr is required") + + params = { + "GroupId": group_id, + "IpPermissions.1.IpProtocol": protocol, + "IpPermissions.1.IpRanges.1.CidrIp": cidr, + } + from_port = inputs.get("from_port") + if from_port is not None and str(from_port).strip() != "": + params["IpPermissions.1.FromPort"] = int(from_port) + to_port = inputs.get("to_port") + if to_port is not None and str(to_port).strip() != "": + params["IpPermissions.1.ToPort"] = int(to_port) + + return ec2("AuthorizeSecurityGroupIngress", params, cfg, inputs) + + +_run(main) diff --git a/integrations/aws/scripts/describe_instances.py b/integrations/aws/scripts/describe_instances.py new file mode 100644 index 0000000..23dc970 --- /dev/null +++ b/integrations/aws/scripts/describe_instances.py @@ -0,0 +1,131 @@ +import json, os, sys, hmac, hashlib, datetime +import urllib.parse, urllib.request, urllib.error +import xml.etree.ElementTree as ET + + +def _cfg(): + return json.loads(os.environ.get("INTEGRATION_SECRETS", "{}")) + + +def _inputs(): + return json.loads(os.environ.get("INTEGRATION_INPUTS", "{}")) + + +def _sign_key(key, date_stamp, region, service): + def _h(k, m): + return hmac.new(k, m.encode("utf-8"), hashlib.sha256).digest() + k_date = _h(("AWS4" + key).encode("utf-8"), date_stamp) + k_region = _h(k_date, region) + k_service = _h(k_region, service) + return _h(k_service, "aws4_request") + + +def _strip_ns(tag): + return tag.split("}", 1)[1] if "}" in tag else tag + + +def _xml_to_dict(elem): + d = {} + children = list(elem) + if not children: + return (elem.text or "").strip() + for c in children: + tag = _strip_ns(c.tag) + val = _xml_to_dict(c) + if tag in d: + if not isinstance(d[tag], list): + d[tag] = [d[tag]] + d[tag].append(val) + else: + d[tag] = val + return d + + +def aws_query(service, host, region, action, version, params, cfg): + # params: dict of extra query params for this Action + body_params = {"Action": action, "Version": version} + body_params.update({k: str(v) for k, v in params.items() if v is not None}) + body = urllib.parse.urlencode(sorted(body_params.items())) + + access_key = str(cfg.get("access_key_id", "")) + secret_key = str(cfg.get("secret_access_key", "")) + session_token = cfg.get("session_token") or "" + + now = datetime.datetime.utcnow() + amz_date = now.strftime("%Y%m%dT%H%M%SZ") + date_stamp = now.strftime("%Y%m%d") + + method = "POST" + canonical_uri = "/" + canonical_querystring = "" + payload_hash = hashlib.sha256(body.encode("utf-8")).hexdigest() + canonical_headers = "content-type:application/x-www-form-urlencoded; charset=utf-8\n" \ + "host:" + host + "\n" \ + "x-amz-date:" + amz_date + "\n" + signed_headers = "content-type;host;x-amz-date" + if session_token: + canonical_headers += "x-amz-security-token:" + session_token + "\n" + signed_headers = "content-type;host;x-amz-date;x-amz-security-token" + canonical_request = "\n".join([method, canonical_uri, canonical_querystring, + canonical_headers, signed_headers, payload_hash]) + + algorithm = "AWS4-HMAC-SHA256" + credential_scope = date_stamp + "/" + region + "/" + service + "/aws4_request" + string_to_sign = "\n".join([algorithm, amz_date, credential_scope, + hashlib.sha256(canonical_request.encode("utf-8")).hexdigest()]) + signing_key = _sign_key(secret_key, date_stamp, region, service) + signature = hmac.new(signing_key, string_to_sign.encode("utf-8"), hashlib.sha256).hexdigest() + + authorization = (algorithm + " Credential=" + access_key + "/" + credential_scope + + ", SignedHeaders=" + signed_headers + ", Signature=" + signature) + headers = { + "Content-Type": "application/x-www-form-urlencoded; charset=utf-8", + "X-Amz-Date": amz_date, + "Authorization": authorization, + "Accept": "application/json", + } + if session_token: + headers["X-Amz-Security-Token"] = session_token + + req = urllib.request.Request("https://" + host + "/", data=body.encode("utf-8"), headers=headers, method="POST") + with urllib.request.urlopen(req, timeout=60) as r: + raw = r.read() + root = ET.fromstring(raw) + return {_strip_ns(root.tag): _xml_to_dict(root)} + + +def _region(cfg, inputs): + return (inputs.get("region") if inputs else None) or cfg.get("region") or "us-east-1" + + +def ec2(action, params, cfg, inputs): + region = _region(cfg, inputs) + return aws_query("ec2", "ec2." + region + ".amazonaws.com", region, action, "2016-11-15", params, cfg) + + +def iam(action, params, cfg): + return aws_query("iam", "iam.amazonaws.com", "us-east-1", action, "2010-05-08", params, cfg) + + +def sts(action, params, cfg): + return aws_query("sts", "sts.amazonaws.com", "us-east-1", action, "2011-06-15", params, cfg) + + +def _run(fn): + try: + print(json.dumps(fn(_cfg(), _inputs()))) + except urllib.error.HTTPError as e: + print(json.dumps({"error": "HTTP " + str(e.code), "detail": e.read().decode("utf-8", "replace")})) + sys.exit(1) + except Exception as e: + print(json.dumps({"error": str(e)})) + sys.exit(1) + + +def main(cfg, inputs): + instance_id = inputs.get("instance_id") + params = {"InstanceId.1": instance_id} if instance_id else {} + return ec2("DescribeInstances", params, cfg, inputs) + + +_run(main) diff --git a/integrations/aws/scripts/describe_security_groups.py b/integrations/aws/scripts/describe_security_groups.py new file mode 100644 index 0000000..aa3b4de --- /dev/null +++ b/integrations/aws/scripts/describe_security_groups.py @@ -0,0 +1,131 @@ +import json, os, sys, hmac, hashlib, datetime +import urllib.parse, urllib.request, urllib.error +import xml.etree.ElementTree as ET + + +def _cfg(): + return json.loads(os.environ.get("INTEGRATION_SECRETS", "{}")) + + +def _inputs(): + return json.loads(os.environ.get("INTEGRATION_INPUTS", "{}")) + + +def _sign_key(key, date_stamp, region, service): + def _h(k, m): + return hmac.new(k, m.encode("utf-8"), hashlib.sha256).digest() + k_date = _h(("AWS4" + key).encode("utf-8"), date_stamp) + k_region = _h(k_date, region) + k_service = _h(k_region, service) + return _h(k_service, "aws4_request") + + +def _strip_ns(tag): + return tag.split("}", 1)[1] if "}" in tag else tag + + +def _xml_to_dict(elem): + d = {} + children = list(elem) + if not children: + return (elem.text or "").strip() + for c in children: + tag = _strip_ns(c.tag) + val = _xml_to_dict(c) + if tag in d: + if not isinstance(d[tag], list): + d[tag] = [d[tag]] + d[tag].append(val) + else: + d[tag] = val + return d + + +def aws_query(service, host, region, action, version, params, cfg): + # params: dict of extra query params for this Action + body_params = {"Action": action, "Version": version} + body_params.update({k: str(v) for k, v in params.items() if v is not None}) + body = urllib.parse.urlencode(sorted(body_params.items())) + + access_key = str(cfg.get("access_key_id", "")) + secret_key = str(cfg.get("secret_access_key", "")) + session_token = cfg.get("session_token") or "" + + now = datetime.datetime.utcnow() + amz_date = now.strftime("%Y%m%dT%H%M%SZ") + date_stamp = now.strftime("%Y%m%d") + + method = "POST" + canonical_uri = "/" + canonical_querystring = "" + payload_hash = hashlib.sha256(body.encode("utf-8")).hexdigest() + canonical_headers = "content-type:application/x-www-form-urlencoded; charset=utf-8\n" \ + "host:" + host + "\n" \ + "x-amz-date:" + amz_date + "\n" + signed_headers = "content-type;host;x-amz-date" + if session_token: + canonical_headers += "x-amz-security-token:" + session_token + "\n" + signed_headers = "content-type;host;x-amz-date;x-amz-security-token" + canonical_request = "\n".join([method, canonical_uri, canonical_querystring, + canonical_headers, signed_headers, payload_hash]) + + algorithm = "AWS4-HMAC-SHA256" + credential_scope = date_stamp + "/" + region + "/" + service + "/aws4_request" + string_to_sign = "\n".join([algorithm, amz_date, credential_scope, + hashlib.sha256(canonical_request.encode("utf-8")).hexdigest()]) + signing_key = _sign_key(secret_key, date_stamp, region, service) + signature = hmac.new(signing_key, string_to_sign.encode("utf-8"), hashlib.sha256).hexdigest() + + authorization = (algorithm + " Credential=" + access_key + "/" + credential_scope + + ", SignedHeaders=" + signed_headers + ", Signature=" + signature) + headers = { + "Content-Type": "application/x-www-form-urlencoded; charset=utf-8", + "X-Amz-Date": amz_date, + "Authorization": authorization, + "Accept": "application/json", + } + if session_token: + headers["X-Amz-Security-Token"] = session_token + + req = urllib.request.Request("https://" + host + "/", data=body.encode("utf-8"), headers=headers, method="POST") + with urllib.request.urlopen(req, timeout=60) as r: + raw = r.read() + root = ET.fromstring(raw) + return {_strip_ns(root.tag): _xml_to_dict(root)} + + +def _region(cfg, inputs): + return (inputs.get("region") if inputs else None) or cfg.get("region") or "us-east-1" + + +def ec2(action, params, cfg, inputs): + region = _region(cfg, inputs) + return aws_query("ec2", "ec2." + region + ".amazonaws.com", region, action, "2016-11-15", params, cfg) + + +def iam(action, params, cfg): + return aws_query("iam", "iam.amazonaws.com", "us-east-1", action, "2010-05-08", params, cfg) + + +def sts(action, params, cfg): + return aws_query("sts", "sts.amazonaws.com", "us-east-1", action, "2011-06-15", params, cfg) + + +def _run(fn): + try: + print(json.dumps(fn(_cfg(), _inputs()))) + except urllib.error.HTTPError as e: + print(json.dumps({"error": "HTTP " + str(e.code), "detail": e.read().decode("utf-8", "replace")})) + sys.exit(1) + except Exception as e: + print(json.dumps({"error": str(e)})) + sys.exit(1) + + +def main(cfg, inputs): + group_id = inputs.get("group_id") + params = {"GroupId.1": group_id} if group_id else {} + return ec2("DescribeSecurityGroups", params, cfg, inputs) + + +_run(main) diff --git a/integrations/aws/scripts/list_access_keys.py b/integrations/aws/scripts/list_access_keys.py new file mode 100644 index 0000000..234ea5e --- /dev/null +++ b/integrations/aws/scripts/list_access_keys.py @@ -0,0 +1,131 @@ +import json, os, sys, hmac, hashlib, datetime +import urllib.parse, urllib.request, urllib.error +import xml.etree.ElementTree as ET + + +def _cfg(): + return json.loads(os.environ.get("INTEGRATION_SECRETS", "{}")) + + +def _inputs(): + return json.loads(os.environ.get("INTEGRATION_INPUTS", "{}")) + + +def _sign_key(key, date_stamp, region, service): + def _h(k, m): + return hmac.new(k, m.encode("utf-8"), hashlib.sha256).digest() + k_date = _h(("AWS4" + key).encode("utf-8"), date_stamp) + k_region = _h(k_date, region) + k_service = _h(k_region, service) + return _h(k_service, "aws4_request") + + +def _strip_ns(tag): + return tag.split("}", 1)[1] if "}" in tag else tag + + +def _xml_to_dict(elem): + d = {} + children = list(elem) + if not children: + return (elem.text or "").strip() + for c in children: + tag = _strip_ns(c.tag) + val = _xml_to_dict(c) + if tag in d: + if not isinstance(d[tag], list): + d[tag] = [d[tag]] + d[tag].append(val) + else: + d[tag] = val + return d + + +def aws_query(service, host, region, action, version, params, cfg): + # params: dict of extra query params for this Action + body_params = {"Action": action, "Version": version} + body_params.update({k: str(v) for k, v in params.items() if v is not None}) + body = urllib.parse.urlencode(sorted(body_params.items())) + + access_key = str(cfg.get("access_key_id", "")) + secret_key = str(cfg.get("secret_access_key", "")) + session_token = cfg.get("session_token") or "" + + now = datetime.datetime.utcnow() + amz_date = now.strftime("%Y%m%dT%H%M%SZ") + date_stamp = now.strftime("%Y%m%d") + + method = "POST" + canonical_uri = "/" + canonical_querystring = "" + payload_hash = hashlib.sha256(body.encode("utf-8")).hexdigest() + canonical_headers = "content-type:application/x-www-form-urlencoded; charset=utf-8\n" \ + "host:" + host + "\n" \ + "x-amz-date:" + amz_date + "\n" + signed_headers = "content-type;host;x-amz-date" + if session_token: + canonical_headers += "x-amz-security-token:" + session_token + "\n" + signed_headers = "content-type;host;x-amz-date;x-amz-security-token" + canonical_request = "\n".join([method, canonical_uri, canonical_querystring, + canonical_headers, signed_headers, payload_hash]) + + algorithm = "AWS4-HMAC-SHA256" + credential_scope = date_stamp + "/" + region + "/" + service + "/aws4_request" + string_to_sign = "\n".join([algorithm, amz_date, credential_scope, + hashlib.sha256(canonical_request.encode("utf-8")).hexdigest()]) + signing_key = _sign_key(secret_key, date_stamp, region, service) + signature = hmac.new(signing_key, string_to_sign.encode("utf-8"), hashlib.sha256).hexdigest() + + authorization = (algorithm + " Credential=" + access_key + "/" + credential_scope + + ", SignedHeaders=" + signed_headers + ", Signature=" + signature) + headers = { + "Content-Type": "application/x-www-form-urlencoded; charset=utf-8", + "X-Amz-Date": amz_date, + "Authorization": authorization, + "Accept": "application/json", + } + if session_token: + headers["X-Amz-Security-Token"] = session_token + + req = urllib.request.Request("https://" + host + "/", data=body.encode("utf-8"), headers=headers, method="POST") + with urllib.request.urlopen(req, timeout=60) as r: + raw = r.read() + root = ET.fromstring(raw) + return {_strip_ns(root.tag): _xml_to_dict(root)} + + +def _region(cfg, inputs): + return (inputs.get("region") if inputs else None) or cfg.get("region") or "us-east-1" + + +def ec2(action, params, cfg, inputs): + region = _region(cfg, inputs) + return aws_query("ec2", "ec2." + region + ".amazonaws.com", region, action, "2016-11-15", params, cfg) + + +def iam(action, params, cfg): + return aws_query("iam", "iam.amazonaws.com", "us-east-1", action, "2010-05-08", params, cfg) + + +def sts(action, params, cfg): + return aws_query("sts", "sts.amazonaws.com", "us-east-1", action, "2011-06-15", params, cfg) + + +def _run(fn): + try: + print(json.dumps(fn(_cfg(), _inputs()))) + except urllib.error.HTTPError as e: + print(json.dumps({"error": "HTTP " + str(e.code), "detail": e.read().decode("utf-8", "replace")})) + sys.exit(1) + except Exception as e: + print(json.dumps({"error": str(e)})) + sys.exit(1) + + +def main(cfg, inputs): + user_name = inputs.get("user_name") + params = {"UserName": user_name} if user_name else {} + return iam("ListAccessKeys", params, cfg) + + +_run(main) diff --git a/integrations/aws/scripts/modify_instance_security_groups.py b/integrations/aws/scripts/modify_instance_security_groups.py new file mode 100644 index 0000000..ce09ff3 --- /dev/null +++ b/integrations/aws/scripts/modify_instance_security_groups.py @@ -0,0 +1,143 @@ +import json, os, sys, hmac, hashlib, datetime +import urllib.parse, urllib.request, urllib.error +import xml.etree.ElementTree as ET + + +def _cfg(): + return json.loads(os.environ.get("INTEGRATION_SECRETS", "{}")) + + +def _inputs(): + return json.loads(os.environ.get("INTEGRATION_INPUTS", "{}")) + + +def _sign_key(key, date_stamp, region, service): + def _h(k, m): + return hmac.new(k, m.encode("utf-8"), hashlib.sha256).digest() + k_date = _h(("AWS4" + key).encode("utf-8"), date_stamp) + k_region = _h(k_date, region) + k_service = _h(k_region, service) + return _h(k_service, "aws4_request") + + +def _strip_ns(tag): + return tag.split("}", 1)[1] if "}" in tag else tag + + +def _xml_to_dict(elem): + d = {} + children = list(elem) + if not children: + return (elem.text or "").strip() + for c in children: + tag = _strip_ns(c.tag) + val = _xml_to_dict(c) + if tag in d: + if not isinstance(d[tag], list): + d[tag] = [d[tag]] + d[tag].append(val) + else: + d[tag] = val + return d + + +def aws_query(service, host, region, action, version, params, cfg): + # params: dict of extra query params for this Action + body_params = {"Action": action, "Version": version} + body_params.update({k: str(v) for k, v in params.items() if v is not None}) + body = urllib.parse.urlencode(sorted(body_params.items())) + + access_key = str(cfg.get("access_key_id", "")) + secret_key = str(cfg.get("secret_access_key", "")) + session_token = cfg.get("session_token") or "" + + now = datetime.datetime.utcnow() + amz_date = now.strftime("%Y%m%dT%H%M%SZ") + date_stamp = now.strftime("%Y%m%d") + + method = "POST" + canonical_uri = "/" + canonical_querystring = "" + payload_hash = hashlib.sha256(body.encode("utf-8")).hexdigest() + canonical_headers = "content-type:application/x-www-form-urlencoded; charset=utf-8\n" \ + "host:" + host + "\n" \ + "x-amz-date:" + amz_date + "\n" + signed_headers = "content-type;host;x-amz-date" + if session_token: + canonical_headers += "x-amz-security-token:" + session_token + "\n" + signed_headers = "content-type;host;x-amz-date;x-amz-security-token" + canonical_request = "\n".join([method, canonical_uri, canonical_querystring, + canonical_headers, signed_headers, payload_hash]) + + algorithm = "AWS4-HMAC-SHA256" + credential_scope = date_stamp + "/" + region + "/" + service + "/aws4_request" + string_to_sign = "\n".join([algorithm, amz_date, credential_scope, + hashlib.sha256(canonical_request.encode("utf-8")).hexdigest()]) + signing_key = _sign_key(secret_key, date_stamp, region, service) + signature = hmac.new(signing_key, string_to_sign.encode("utf-8"), hashlib.sha256).hexdigest() + + authorization = (algorithm + " Credential=" + access_key + "/" + credential_scope + + ", SignedHeaders=" + signed_headers + ", Signature=" + signature) + headers = { + "Content-Type": "application/x-www-form-urlencoded; charset=utf-8", + "X-Amz-Date": amz_date, + "Authorization": authorization, + "Accept": "application/json", + } + if session_token: + headers["X-Amz-Security-Token"] = session_token + + req = urllib.request.Request("https://" + host + "/", data=body.encode("utf-8"), headers=headers, method="POST") + with urllib.request.urlopen(req, timeout=60) as r: + raw = r.read() + root = ET.fromstring(raw) + return {_strip_ns(root.tag): _xml_to_dict(root)} + + +def _region(cfg, inputs): + return (inputs.get("region") if inputs else None) or cfg.get("region") or "us-east-1" + + +def ec2(action, params, cfg, inputs): + region = _region(cfg, inputs) + return aws_query("ec2", "ec2." + region + ".amazonaws.com", region, action, "2016-11-15", params, cfg) + + +def iam(action, params, cfg): + return aws_query("iam", "iam.amazonaws.com", "us-east-1", action, "2010-05-08", params, cfg) + + +def sts(action, params, cfg): + return aws_query("sts", "sts.amazonaws.com", "us-east-1", action, "2011-06-15", params, cfg) + + +def _run(fn): + try: + print(json.dumps(fn(_cfg(), _inputs()))) + except urllib.error.HTTPError as e: + print(json.dumps({"error": "HTTP " + str(e.code), "detail": e.read().decode("utf-8", "replace")})) + sys.exit(1) + except Exception as e: + print(json.dumps({"error": str(e)})) + sys.exit(1) + + +def main(cfg, inputs): + instance_id = inputs.get("instance_id") + if not instance_id: + raise Exception("instance_id is required") + group_ids_raw = inputs.get("group_ids") + if not group_ids_raw or not str(group_ids_raw).strip(): + raise Exception("group_ids is required") + group_ids = [s.strip() for s in str(group_ids_raw).split(",") if s.strip()] + if not group_ids: + raise Exception("group_ids is required") + + params = {"InstanceId": instance_id} + for i, gid in enumerate(group_ids, start=1): + params["GroupId.%d" % i] = gid + + return ec2("ModifyInstanceAttribute", params, cfg, inputs) + + +_run(main) diff --git a/integrations/aws/scripts/revoke_security_group_ingress.py b/integrations/aws/scripts/revoke_security_group_ingress.py new file mode 100644 index 0000000..9af870a --- /dev/null +++ b/integrations/aws/scripts/revoke_security_group_ingress.py @@ -0,0 +1,151 @@ +import json, os, sys, hmac, hashlib, datetime +import urllib.parse, urllib.request, urllib.error +import xml.etree.ElementTree as ET + + +def _cfg(): + return json.loads(os.environ.get("INTEGRATION_SECRETS", "{}")) + + +def _inputs(): + return json.loads(os.environ.get("INTEGRATION_INPUTS", "{}")) + + +def _sign_key(key, date_stamp, region, service): + def _h(k, m): + return hmac.new(k, m.encode("utf-8"), hashlib.sha256).digest() + k_date = _h(("AWS4" + key).encode("utf-8"), date_stamp) + k_region = _h(k_date, region) + k_service = _h(k_region, service) + return _h(k_service, "aws4_request") + + +def _strip_ns(tag): + return tag.split("}", 1)[1] if "}" in tag else tag + + +def _xml_to_dict(elem): + d = {} + children = list(elem) + if not children: + return (elem.text or "").strip() + for c in children: + tag = _strip_ns(c.tag) + val = _xml_to_dict(c) + if tag in d: + if not isinstance(d[tag], list): + d[tag] = [d[tag]] + d[tag].append(val) + else: + d[tag] = val + return d + + +def aws_query(service, host, region, action, version, params, cfg): + # params: dict of extra query params for this Action + body_params = {"Action": action, "Version": version} + body_params.update({k: str(v) for k, v in params.items() if v is not None}) + body = urllib.parse.urlencode(sorted(body_params.items())) + + access_key = str(cfg.get("access_key_id", "")) + secret_key = str(cfg.get("secret_access_key", "")) + session_token = cfg.get("session_token") or "" + + now = datetime.datetime.utcnow() + amz_date = now.strftime("%Y%m%dT%H%M%SZ") + date_stamp = now.strftime("%Y%m%d") + + method = "POST" + canonical_uri = "/" + canonical_querystring = "" + payload_hash = hashlib.sha256(body.encode("utf-8")).hexdigest() + canonical_headers = "content-type:application/x-www-form-urlencoded; charset=utf-8\n" \ + "host:" + host + "\n" \ + "x-amz-date:" + amz_date + "\n" + signed_headers = "content-type;host;x-amz-date" + if session_token: + canonical_headers += "x-amz-security-token:" + session_token + "\n" + signed_headers = "content-type;host;x-amz-date;x-amz-security-token" + canonical_request = "\n".join([method, canonical_uri, canonical_querystring, + canonical_headers, signed_headers, payload_hash]) + + algorithm = "AWS4-HMAC-SHA256" + credential_scope = date_stamp + "/" + region + "/" + service + "/aws4_request" + string_to_sign = "\n".join([algorithm, amz_date, credential_scope, + hashlib.sha256(canonical_request.encode("utf-8")).hexdigest()]) + signing_key = _sign_key(secret_key, date_stamp, region, service) + signature = hmac.new(signing_key, string_to_sign.encode("utf-8"), hashlib.sha256).hexdigest() + + authorization = (algorithm + " Credential=" + access_key + "/" + credential_scope + + ", SignedHeaders=" + signed_headers + ", Signature=" + signature) + headers = { + "Content-Type": "application/x-www-form-urlencoded; charset=utf-8", + "X-Amz-Date": amz_date, + "Authorization": authorization, + "Accept": "application/json", + } + if session_token: + headers["X-Amz-Security-Token"] = session_token + + req = urllib.request.Request("https://" + host + "/", data=body.encode("utf-8"), headers=headers, method="POST") + with urllib.request.urlopen(req, timeout=60) as r: + raw = r.read() + root = ET.fromstring(raw) + return {_strip_ns(root.tag): _xml_to_dict(root)} + + +def _region(cfg, inputs): + return (inputs.get("region") if inputs else None) or cfg.get("region") or "us-east-1" + + +def ec2(action, params, cfg, inputs): + region = _region(cfg, inputs) + return aws_query("ec2", "ec2." + region + ".amazonaws.com", region, action, "2016-11-15", params, cfg) + + +def iam(action, params, cfg): + return aws_query("iam", "iam.amazonaws.com", "us-east-1", action, "2010-05-08", params, cfg) + + +def sts(action, params, cfg): + return aws_query("sts", "sts.amazonaws.com", "us-east-1", action, "2011-06-15", params, cfg) + + +def _run(fn): + try: + print(json.dumps(fn(_cfg(), _inputs()))) + except urllib.error.HTTPError as e: + print(json.dumps({"error": "HTTP " + str(e.code), "detail": e.read().decode("utf-8", "replace")})) + sys.exit(1) + except Exception as e: + print(json.dumps({"error": str(e)})) + sys.exit(1) + + +def main(cfg, inputs): + group_id = inputs.get("group_id") + if not group_id: + raise Exception("group_id is required") + protocol = inputs.get("protocol") + if not protocol: + raise Exception("protocol is required") + cidr = inputs.get("cidr") + if not cidr: + raise Exception("cidr is required") + + params = { + "GroupId": group_id, + "IpPermissions.1.IpProtocol": protocol, + "IpPermissions.1.IpRanges.1.CidrIp": cidr, + } + from_port = inputs.get("from_port") + if from_port is not None and str(from_port).strip() != "": + params["IpPermissions.1.FromPort"] = int(from_port) + to_port = inputs.get("to_port") + if to_port is not None and str(to_port).strip() != "": + params["IpPermissions.1.ToPort"] = int(to_port) + + return ec2("RevokeSecurityGroupIngress", params, cfg, inputs) + + +_run(main) diff --git a/integrations/aws/scripts/stop_instances.py b/integrations/aws/scripts/stop_instances.py new file mode 100644 index 0000000..c4ed3bc --- /dev/null +++ b/integrations/aws/scripts/stop_instances.py @@ -0,0 +1,148 @@ +import json, os, sys, hmac, hashlib, datetime +import urllib.parse, urllib.request, urllib.error +import xml.etree.ElementTree as ET + + +def _cfg(): + return json.loads(os.environ.get("INTEGRATION_SECRETS", "{}")) + + +def _inputs(): + return json.loads(os.environ.get("INTEGRATION_INPUTS", "{}")) + + +def _sign_key(key, date_stamp, region, service): + def _h(k, m): + return hmac.new(k, m.encode("utf-8"), hashlib.sha256).digest() + k_date = _h(("AWS4" + key).encode("utf-8"), date_stamp) + k_region = _h(k_date, region) + k_service = _h(k_region, service) + return _h(k_service, "aws4_request") + + +def _strip_ns(tag): + return tag.split("}", 1)[1] if "}" in tag else tag + + +def _xml_to_dict(elem): + d = {} + children = list(elem) + if not children: + return (elem.text or "").strip() + for c in children: + tag = _strip_ns(c.tag) + val = _xml_to_dict(c) + if tag in d: + if not isinstance(d[tag], list): + d[tag] = [d[tag]] + d[tag].append(val) + else: + d[tag] = val + return d + + +def aws_query(service, host, region, action, version, params, cfg): + # params: dict of extra query params for this Action + body_params = {"Action": action, "Version": version} + body_params.update({k: str(v) for k, v in params.items() if v is not None}) + body = urllib.parse.urlencode(sorted(body_params.items())) + + access_key = str(cfg.get("access_key_id", "")) + secret_key = str(cfg.get("secret_access_key", "")) + session_token = cfg.get("session_token") or "" + + now = datetime.datetime.utcnow() + amz_date = now.strftime("%Y%m%dT%H%M%SZ") + date_stamp = now.strftime("%Y%m%d") + + method = "POST" + canonical_uri = "/" + canonical_querystring = "" + payload_hash = hashlib.sha256(body.encode("utf-8")).hexdigest() + canonical_headers = "content-type:application/x-www-form-urlencoded; charset=utf-8\n" \ + "host:" + host + "\n" \ + "x-amz-date:" + amz_date + "\n" + signed_headers = "content-type;host;x-amz-date" + if session_token: + canonical_headers += "x-amz-security-token:" + session_token + "\n" + signed_headers = "content-type;host;x-amz-date;x-amz-security-token" + canonical_request = "\n".join([method, canonical_uri, canonical_querystring, + canonical_headers, signed_headers, payload_hash]) + + algorithm = "AWS4-HMAC-SHA256" + credential_scope = date_stamp + "/" + region + "/" + service + "/aws4_request" + string_to_sign = "\n".join([algorithm, amz_date, credential_scope, + hashlib.sha256(canonical_request.encode("utf-8")).hexdigest()]) + signing_key = _sign_key(secret_key, date_stamp, region, service) + signature = hmac.new(signing_key, string_to_sign.encode("utf-8"), hashlib.sha256).hexdigest() + + authorization = (algorithm + " Credential=" + access_key + "/" + credential_scope + + ", SignedHeaders=" + signed_headers + ", Signature=" + signature) + headers = { + "Content-Type": "application/x-www-form-urlencoded; charset=utf-8", + "X-Amz-Date": amz_date, + "Authorization": authorization, + "Accept": "application/json", + } + if session_token: + headers["X-Amz-Security-Token"] = session_token + + req = urllib.request.Request("https://" + host + "/", data=body.encode("utf-8"), headers=headers, method="POST") + with urllib.request.urlopen(req, timeout=60) as r: + raw = r.read() + root = ET.fromstring(raw) + return {_strip_ns(root.tag): _xml_to_dict(root)} + + +def _region(cfg, inputs): + return (inputs.get("region") if inputs else None) or cfg.get("region") or "us-east-1" + + +def ec2(action, params, cfg, inputs): + region = _region(cfg, inputs) + return aws_query("ec2", "ec2." + region + ".amazonaws.com", region, action, "2016-11-15", params, cfg) + + +def iam(action, params, cfg): + return aws_query("iam", "iam.amazonaws.com", "us-east-1", action, "2010-05-08", params, cfg) + + +def sts(action, params, cfg): + return aws_query("sts", "sts.amazonaws.com", "us-east-1", action, "2011-06-15", params, cfg) + + +def _run(fn): + try: + print(json.dumps(fn(_cfg(), _inputs()))) + except urllib.error.HTTPError as e: + print(json.dumps({"error": "HTTP " + str(e.code), "detail": e.read().decode("utf-8", "replace")})) + sys.exit(1) + except Exception as e: + print(json.dumps({"error": str(e)})) + sys.exit(1) + + +def main(cfg, inputs): + instance_ids_raw = inputs.get("instance_ids") + if not instance_ids_raw or not str(instance_ids_raw).strip(): + raise Exception("instance_ids is required") + instance_ids = [s.strip() for s in str(instance_ids_raw).split(",") if s.strip()] + if not instance_ids: + raise Exception("instance_ids is required") + + force = inputs.get("force", False) + if isinstance(force, str): + force = force.strip().lower() in ("true", "1", "yes") + else: + force = bool(force) + + params = {} + for i, iid in enumerate(instance_ids, start=1): + params["InstanceId.%d" % i] = iid + if force: + params["Force"] = "true" + + return ec2("StopInstances", params, cfg, inputs) + + +_run(main) diff --git a/integrations/aws/scripts/test_connection.py b/integrations/aws/scripts/test_connection.py new file mode 100644 index 0000000..7bb8bd1 --- /dev/null +++ b/integrations/aws/scripts/test_connection.py @@ -0,0 +1,130 @@ +import json, os, sys, hmac, hashlib, datetime +import urllib.parse, urllib.request, urllib.error +import xml.etree.ElementTree as ET + + +def _cfg(): + return json.loads(os.environ.get("INTEGRATION_SECRETS", "{}")) + + +def _inputs(): + return json.loads(os.environ.get("INTEGRATION_INPUTS", "{}")) + + +def _sign_key(key, date_stamp, region, service): + def _h(k, m): + return hmac.new(k, m.encode("utf-8"), hashlib.sha256).digest() + k_date = _h(("AWS4" + key).encode("utf-8"), date_stamp) + k_region = _h(k_date, region) + k_service = _h(k_region, service) + return _h(k_service, "aws4_request") + + +def _strip_ns(tag): + return tag.split("}", 1)[1] if "}" in tag else tag + + +def _xml_to_dict(elem): + d = {} + children = list(elem) + if not children: + return (elem.text or "").strip() + for c in children: + tag = _strip_ns(c.tag) + val = _xml_to_dict(c) + if tag in d: + if not isinstance(d[tag], list): + d[tag] = [d[tag]] + d[tag].append(val) + else: + d[tag] = val + return d + + +def aws_query(service, host, region, action, version, params, cfg): + # params: dict of extra query params for this Action + body_params = {"Action": action, "Version": version} + body_params.update({k: str(v) for k, v in params.items() if v is not None}) + body = urllib.parse.urlencode(sorted(body_params.items())) + + access_key = str(cfg.get("access_key_id", "")) + secret_key = str(cfg.get("secret_access_key", "")) + session_token = cfg.get("session_token") or "" + + now = datetime.datetime.utcnow() + amz_date = now.strftime("%Y%m%dT%H%M%SZ") + date_stamp = now.strftime("%Y%m%d") + + method = "POST" + canonical_uri = "/" + canonical_querystring = "" + payload_hash = hashlib.sha256(body.encode("utf-8")).hexdigest() + canonical_headers = "content-type:application/x-www-form-urlencoded; charset=utf-8\n" \ + "host:" + host + "\n" \ + "x-amz-date:" + amz_date + "\n" + signed_headers = "content-type;host;x-amz-date" + if session_token: + canonical_headers += "x-amz-security-token:" + session_token + "\n" + signed_headers = "content-type;host;x-amz-date;x-amz-security-token" + canonical_request = "\n".join([method, canonical_uri, canonical_querystring, + canonical_headers, signed_headers, payload_hash]) + + algorithm = "AWS4-HMAC-SHA256" + credential_scope = date_stamp + "/" + region + "/" + service + "/aws4_request" + string_to_sign = "\n".join([algorithm, amz_date, credential_scope, + hashlib.sha256(canonical_request.encode("utf-8")).hexdigest()]) + signing_key = _sign_key(secret_key, date_stamp, region, service) + signature = hmac.new(signing_key, string_to_sign.encode("utf-8"), hashlib.sha256).hexdigest() + + authorization = (algorithm + " Credential=" + access_key + "/" + credential_scope + + ", SignedHeaders=" + signed_headers + ", Signature=" + signature) + headers = { + "Content-Type": "application/x-www-form-urlencoded; charset=utf-8", + "X-Amz-Date": amz_date, + "Authorization": authorization, + "Accept": "application/json", + } + if session_token: + headers["X-Amz-Security-Token"] = session_token + + req = urllib.request.Request("https://" + host + "/", data=body.encode("utf-8"), headers=headers, method="POST") + with urllib.request.urlopen(req, timeout=60) as r: + raw = r.read() + root = ET.fromstring(raw) + return {_strip_ns(root.tag): _xml_to_dict(root)} + + +def _region(cfg, inputs): + return (inputs.get("region") if inputs else None) or cfg.get("region") or "us-east-1" + + +def ec2(action, params, cfg, inputs): + region = _region(cfg, inputs) + return aws_query("ec2", "ec2." + region + ".amazonaws.com", region, action, "2016-11-15", params, cfg) + + +def iam(action, params, cfg): + return aws_query("iam", "iam.amazonaws.com", "us-east-1", action, "2010-05-08", params, cfg) + + +def sts(action, params, cfg): + return aws_query("sts", "sts.amazonaws.com", "us-east-1", action, "2011-06-15", params, cfg) + + +def _run(fn): + try: + print(json.dumps(fn(_cfg(), _inputs()))) + except urllib.error.HTTPError as e: + print(json.dumps({"error": "HTTP " + str(e.code), "detail": e.read().decode("utf-8", "replace")})) + sys.exit(1) + except Exception as e: + print(json.dumps({"error": str(e)})) + sys.exit(1) + + +def main(cfg, inputs): + identity = sts("GetCallerIdentity", {}, cfg) + return {"ok": True, "identity": identity} + + +_run(main) diff --git a/integrations/aws/scripts/update_access_key.py b/integrations/aws/scripts/update_access_key.py new file mode 100644 index 0000000..fd85f5b --- /dev/null +++ b/integrations/aws/scripts/update_access_key.py @@ -0,0 +1,143 @@ +import json, os, sys, hmac, hashlib, datetime +import urllib.parse, urllib.request, urllib.error +import xml.etree.ElementTree as ET + + +def _cfg(): + return json.loads(os.environ.get("INTEGRATION_SECRETS", "{}")) + + +def _inputs(): + return json.loads(os.environ.get("INTEGRATION_INPUTS", "{}")) + + +def _sign_key(key, date_stamp, region, service): + def _h(k, m): + return hmac.new(k, m.encode("utf-8"), hashlib.sha256).digest() + k_date = _h(("AWS4" + key).encode("utf-8"), date_stamp) + k_region = _h(k_date, region) + k_service = _h(k_region, service) + return _h(k_service, "aws4_request") + + +def _strip_ns(tag): + return tag.split("}", 1)[1] if "}" in tag else tag + + +def _xml_to_dict(elem): + d = {} + children = list(elem) + if not children: + return (elem.text or "").strip() + for c in children: + tag = _strip_ns(c.tag) + val = _xml_to_dict(c) + if tag in d: + if not isinstance(d[tag], list): + d[tag] = [d[tag]] + d[tag].append(val) + else: + d[tag] = val + return d + + +def aws_query(service, host, region, action, version, params, cfg): + # params: dict of extra query params for this Action + body_params = {"Action": action, "Version": version} + body_params.update({k: str(v) for k, v in params.items() if v is not None}) + body = urllib.parse.urlencode(sorted(body_params.items())) + + access_key = str(cfg.get("access_key_id", "")) + secret_key = str(cfg.get("secret_access_key", "")) + session_token = cfg.get("session_token") or "" + + now = datetime.datetime.utcnow() + amz_date = now.strftime("%Y%m%dT%H%M%SZ") + date_stamp = now.strftime("%Y%m%d") + + method = "POST" + canonical_uri = "/" + canonical_querystring = "" + payload_hash = hashlib.sha256(body.encode("utf-8")).hexdigest() + canonical_headers = "content-type:application/x-www-form-urlencoded; charset=utf-8\n" \ + "host:" + host + "\n" \ + "x-amz-date:" + amz_date + "\n" + signed_headers = "content-type;host;x-amz-date" + if session_token: + canonical_headers += "x-amz-security-token:" + session_token + "\n" + signed_headers = "content-type;host;x-amz-date;x-amz-security-token" + canonical_request = "\n".join([method, canonical_uri, canonical_querystring, + canonical_headers, signed_headers, payload_hash]) + + algorithm = "AWS4-HMAC-SHA256" + credential_scope = date_stamp + "/" + region + "/" + service + "/aws4_request" + string_to_sign = "\n".join([algorithm, amz_date, credential_scope, + hashlib.sha256(canonical_request.encode("utf-8")).hexdigest()]) + signing_key = _sign_key(secret_key, date_stamp, region, service) + signature = hmac.new(signing_key, string_to_sign.encode("utf-8"), hashlib.sha256).hexdigest() + + authorization = (algorithm + " Credential=" + access_key + "/" + credential_scope + + ", SignedHeaders=" + signed_headers + ", Signature=" + signature) + headers = { + "Content-Type": "application/x-www-form-urlencoded; charset=utf-8", + "X-Amz-Date": amz_date, + "Authorization": authorization, + "Accept": "application/json", + } + if session_token: + headers["X-Amz-Security-Token"] = session_token + + req = urllib.request.Request("https://" + host + "/", data=body.encode("utf-8"), headers=headers, method="POST") + with urllib.request.urlopen(req, timeout=60) as r: + raw = r.read() + root = ET.fromstring(raw) + return {_strip_ns(root.tag): _xml_to_dict(root)} + + +def _region(cfg, inputs): + return (inputs.get("region") if inputs else None) or cfg.get("region") or "us-east-1" + + +def ec2(action, params, cfg, inputs): + region = _region(cfg, inputs) + return aws_query("ec2", "ec2." + region + ".amazonaws.com", region, action, "2016-11-15", params, cfg) + + +def iam(action, params, cfg): + return aws_query("iam", "iam.amazonaws.com", "us-east-1", action, "2010-05-08", params, cfg) + + +def sts(action, params, cfg): + return aws_query("sts", "sts.amazonaws.com", "us-east-1", action, "2011-06-15", params, cfg) + + +def _run(fn): + try: + print(json.dumps(fn(_cfg(), _inputs()))) + except urllib.error.HTTPError as e: + print(json.dumps({"error": "HTTP " + str(e.code), "detail": e.read().decode("utf-8", "replace")})) + sys.exit(1) + except Exception as e: + print(json.dumps({"error": str(e)})) + sys.exit(1) + + +def main(cfg, inputs): + access_key_id = inputs.get("access_key_id") + if not access_key_id: + raise Exception("access_key_id is required") + status = inputs.get("status") + if not status: + raise Exception("status is required") + if status not in ("Active", "Inactive"): + raise Exception("status must be Active or Inactive") + + params = {"AccessKeyId": access_key_id, "Status": status} + user_name = inputs.get("user_name") + if user_name and str(user_name).strip(): + params["UserName"] = user_name + + return iam("UpdateAccessKey", params, cfg) + + +_run(main)