From 30e40e9c00a92c263f0dc4d5bf9f1f231a2b5f4d Mon Sep 17 00:00:00 2001 From: John Leslie Date: Sun, 25 Feb 2018 12:56:43 -0800 Subject: [PATCH 1/2] service.py: Add --auth-file parameter For usage of ectou-metadata behind a secured proxy server (i.e. for docker build --build-arg HTTP_PROXY) See: examples/mitmproxy-ectou-metadata.py --- ectou_metadata/service.py | 41 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/ectou_metadata/service.py b/ectou_metadata/service.py index 9ba3f34..fb7c150 100644 --- a/ectou_metadata/service.py +++ b/ectou_metadata/service.py @@ -7,6 +7,7 @@ import datetime import json import os +import base64 import boto3.session import botocore.session @@ -19,6 +20,21 @@ _conf_dir = None _credential_map = {} +_http_authorization = None + + +def _http_authorization(func): + def wrapper(): + if not _http_authorization: + return func() + + headers = bottle.request.headers + if 'Authorization' in headers and headers['Authorization'] == _http_authorization: + return func() + + return bottle.abort(401, "Unauthorized") + + return wrapper def _lookup_ip_role_arn(source_ip): @@ -64,21 +80,25 @@ def _index(items): @bottle.route("/latest/meta-data/iam") @bottle.route("/latest/meta-data/iam/security-credentials") @bottle.route("/latest/meta-data/placement") +@_http_authorization def slashify(): bottle.redirect(bottle.request.path + "/", 301) @bottle.route("/") +@_http_authorization def root(): return _index(["latest"]) @bottle.route("/latest/") +@_http_authorization def latest(): return _index(["meta-data"]) @bottle.route("/latest/meta-data/") +@_http_authorization def meta_data(): return _index(["ami-id", "iam/", @@ -91,16 +111,19 @@ def meta_data(): @bottle.route("/latest/meta-data/iam/") +@_http_authorization def iam(): return _index(["security-credentials/"]) @bottle.route("/latest/meta-data/iam/security-credentials/") +@_http_authorization def security_credentials(): return _index(["role-name"]) @bottle.route("/latest/meta-data/iam/security-credentials/role-name") +@_http_authorization def security_credentials_role_name(): role_arn = _get_role_arn() credentials = _credential_map.get(role_arn) @@ -142,58 +165,68 @@ def security_credentials_role_name(): @bottle.route("/latest/meta-data/instance-id") +@_http_authorization def instance_id(): bottle.response.content_type = 'text/plain' return "i-deadbeef" @bottle.route("/latest/meta-data/instance-type") +@_http_authorization def instance_type(): bottle.response.content_type = 'text/plain' return "m1.small" @bottle.route("/latest/meta-data/ami-id") +@_http_authorization def ami_id(): bottle.response.content_type = 'text/plain' return "ami-deadbeef" @bottle.route("/latest/meta-data/local-ipv4") +@_http_authorization def local_ipv4(): bottle.response.content_type = 'text/plain' return "127.0.0.1" @bottle.route("/latest/meta-data/placement/") +@_http_authorization def placement(): return _index(["availability-zone"]) @bottle.route("/latest/meta-data/placement/availability-zone") +@_http_authorization def availability_zone(): bottle.response.content_type = 'text/plain' return "us-east-1x" @bottle.route("/latest/meta-data/public-hostname") +@_http_authorization def public_hostname(): bottle.response.content_type = 'text/plain' return "localhost" @bottle.route("/latest/meta-data/public-ipv4") +@_http_authorization def public_ipv4(): bottle.response.content_type = 'text/plain' return "127.0.0.1" @bottle.route("/latest/dynamic/instance-identity") +@_http_authorization def instance_identity_index(slashes): bottle.response.content_type = 'text/plain' return 'document' @bottle.route("/latest/dynamic/instance-identitydocument") +@_http_authorization def instance_identity_document(slashes1, slashes2): bottle.response.content_type = 'text/plain' return '{"region": "us-east-1"}' @@ -207,6 +240,8 @@ def main(): parser.add_argument('--port', default=80) parser.add_argument('--role-arn', help="Default role ARN.") parser.add_argument('--conf-dir', help="Directory containing configuration files named by source ip.") + parser.add_argument('--auth-file', help=("Require Basic HTTP Authentication, with credentials specified in this" + " file (user:password). See: examples/mitmproxy-ectou-metadata.py")) args = parser.parse_args() global _role_arn @@ -216,6 +251,12 @@ def main(): _conf_dir = args.conf_dir app = bottle.default_app() + + if args.auth_file: + global _http_authorization + auth_user_password = open(args.auth_file).read().strip() + _http_authorization = "Basic {}".format(base64.b64encode(auth_user_password)) + app.run(host=args.host, port=args.port) From 40abc6517734050060cdd75a22581eff9b9881fc Mon Sep 17 00:00:00 2001 From: John Leslie Date: Sun, 25 Feb 2018 13:04:52 -0800 Subject: [PATCH 2/2] add mitmproxy-ectou-metadata.py: redirect traffic for 169.254.169.254 to a secured ectou-metadata instance --- examples/mitmproxy-ectou-metadata.py | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 examples/mitmproxy-ectou-metadata.py diff --git a/examples/mitmproxy-ectou-metadata.py b/examples/mitmproxy-ectou-metadata.py new file mode 100644 index 0000000..6fff109 --- /dev/null +++ b/examples/mitmproxy-ectou-metadata.py @@ -0,0 +1,24 @@ +# mitmproxy-ectou-metadata.py +# +# Example usage: mitmproxy -s mitmproxy-ectou-metadata.py --ignore-hosts '.+' --proxyauth "$(cat mitmproxy-auth-file.txt)" --listen-host 127.0.0.1 --listen-port 18080 +# - will require HTTP Proxy Authentication +# - forwards traffic for 169.254.169.254 to an ectou-metadata instance on 127.0.0.1:18081 +# - requests to ectou-metadata will be authenticated using credentials in ectou-auth-file.txt +# - May be used by docker build --build-arg="HTTP_PROXY=..." (see: https://github.com/moby/moby/pull/31584) + +import base64 +from mitmproxy import http + +ectou_metadata_host = "127.0.0.1" +ectou_metadata_port = 18081 +ectou_metadata_auth_file = 'ectou-auth-file.txt' + +_auth_user_password = open(ectou_metadata_auth_file).read().strip() +_http_authorization = "Basic {}".format(base64.b64encode(_auth_user_password.encode('ascii')).decode('ascii')) + + +def request(flow): + if flow.request.pretty_host == "169.254.169.254": + flow.request.host = ectou_metadata_host + flow.request.port = ectou_metadata_port + flow.request.headers.set_all("Authorization", [_http_authorization])