diff --git a/k8s/tools/block-aws/Dockerfile b/k8s/tools/block-aws/Dockerfile index 7439fa2a..c61a3ff9 100644 --- a/k8s/tools/block-aws/Dockerfile +++ b/k8s/tools/block-aws/Dockerfile @@ -8,20 +8,26 @@ ENV PYTHONUNBUFFERED=1 ENV PIP_DISABLE_PIP_VERSION_CHECK=1 # add non-priviledged user -RUN adduser --uid 1000 --disabled-password --gecos '' --no-create-home webdev +RUN adduser --uid 1000 --disabled-password --gecos '' --no-create-home blocker # end from Dockerfile.footer WORKDIR /app # Install app -COPY block-aws.py /app/block-aws.py +COPY block_aws.py /app/block_aws.py COPY requirements.txt /app/requirements.txt RUN pip install --no-cache-dir -r requirements.txt -CMD ["python", "/app/block-aws.py"] +# Install kubectl +# to get latest version: "curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt" +ENV KUBECTL_VERSION="v1.10.1" +ADD https://storage.googleapis.com/kubernetes-release/release/${KUBECTL_VERSION}/bin/linux/amd64/kubectl /usr/local/bin/kubectl +RUN chmod +x /usr/local/bin/kubectl + +CMD ["python", "/app/block_aws.py"] # Change User -RUN chown webdev.webdev -R . -USER webdev +RUN chown blocker.blocker -R . +USER blocker diff --git a/k8s/tools/block-aws/block-aws-cron.yaml b/k8s/tools/block-aws/block-aws-cron.yaml index 66ba2061..42d3037a 100644 --- a/k8s/tools/block-aws/block-aws-cron.yaml +++ b/k8s/tools/block-aws/block-aws-cron.yaml @@ -59,7 +59,7 @@ spec: spec: containers: - name: block-aws-cron - image: quay.io/mozmar/blockaws:1f888cb + image: quay.io/mozmar/blockaws:0905680 env: - name: DMS_URL valueFrom: diff --git a/k8s/tools/block-aws/block-aws-networkpolicy.yaml b/k8s/tools/block-aws/block-aws-networkpolicy.yaml new file mode 100644 index 00000000..2ecbcb0d --- /dev/null +++ b/k8s/tools/block-aws/block-aws-networkpolicy.yaml @@ -0,0 +1,20 @@ +apiVersion: v1 +items: +- apiVersion: extensions/v1beta1 + kind: NetworkPolicy + metadata: + name: block-aws + spec: + egress: + - to: + - ipBlock: + cidr: 0.0.0.0/0 + except: + - 169.254.0.0/16 + podSelector: + matchExpressions: + - key: k8s-app + operator: DoesNotExist + policyTypes: + - Egress +kind: List diff --git a/k8s/tools/block-aws/block-aws.py b/k8s/tools/block-aws/block-aws.py deleted file mode 100755 index 84241701..00000000 --- a/k8s/tools/block-aws/block-aws.py +++ /dev/null @@ -1,65 +0,0 @@ -""" -This script iterates through all k8s namespaces and installs a NetworkPolicy -that blocks the AWS metadata service (169.254.0.0/16). Namespaces can -be whitelisted via the WHITELISTED_NAMESPACES list, with kube-system -being whitelisted by default (meaning we don't install this particular -network policy in kube-system, but others may be installed in kube-system -via some other method). -""" - -import os - -from kubernetes import client, config -import requests - -WHITELISTED_NAMESPACES = ['kube-system'] -AWS_NETWORK_POLICY_NAME = 'block-aws' - -config.load_incluster_config() - -v1 = client.CoreV1Api() -v1beta1 = client.ExtensionsV1beta1Api() -networkingv1 = client.NetworkingV1Api() - -namespace_response = v1.list_namespace() -for ns in namespace_response.items: - name = ns.metadata.name - print("-> ", name) - if name in WHITELISTED_NAMESPACES: - print("\tskipping, ns whitelisted") - continue - - ns_policy_response = v1beta1.list_namespaced_network_policy(name) - local_policies = [ - ns_policy.metadata.name for ns_policy in ns_policy_response.items] - if AWS_NETWORK_POLICY_NAME not in local_policies: - print("\tnamespace doesn't block AWS") - md = client.V1ObjectMeta(name=AWS_NETWORK_POLICY_NAME, namespace=name) - match_expression = client.V1LabelSelectorRequirement( - key='k8s-app', operator='DoesNotExist') - pod_selector = client.V1LabelSelector( - match_expressions=[match_expression]) - - ip_block = client.V1beta1IPBlock( - cidr='0.0.0.0/0', _except=['169.254.0.0/16']) - peer = client.V1beta1NetworkPolicyPeer(ip_block=ip_block) - egress = client.V1beta1NetworkPolicyEgressRule(to=[peer]) - spec = client.V1beta1NetworkPolicySpec( - pod_selector=pod_selector, - egress=[egress], - policy_types=['Egress']) - policy = client.V1beta1NetworkPolicy(metadata=md, spec=spec) - response = networkingv1.create_namespaced_network_policy(name, policy) - print( - "\tCreated {} in NS {}".format( - response.metadata.name, - response.metadata.namespace)) - else: - print("\tAWS already blocked") - -if 'DMS_URL' in os.environ: - print("Notifying DMS") - r = requests.get(os.environ['DMS_URL']) - print(r.status_code) -else: - print('DMS_URL not found, not notifying DMS') \ No newline at end of file diff --git a/k8s/tools/block-aws/block_aws.py b/k8s/tools/block-aws/block_aws.py new file mode 100755 index 00000000..55042e9e --- /dev/null +++ b/k8s/tools/block-aws/block_aws.py @@ -0,0 +1,113 @@ +""" +This script iterates through all k8s namespaces and installs a NetworkPolicy +that blocks the AWS metadata service (169.254.0.0/16). Namespaces can +be whitelisted via the WHITELISTED_NAMESPACES list, with kube-system +being whitelisted by default (meaning we don't install this particular +network policy in kube-system, but others may be installed in kube-system +via some other method). +""" + +from collections import defaultdict +import os + +from kubernetes import client, config +from munch import munchify +import requests +import sh +import yaml + +WHITELISTED_NAMESPACES = os.getenv('WHITELISTED_NAMESPACES', + 'kube-system').split(',') +AWS_NETWORK_POLICY_NAME = os.getenv('AWS_NETWORK_POLICY_NAME', 'block-aws') +POLICY_FILENAME = os.getenv('POLICY_FILENAME', 'block-aws-networkpolicy.yaml') +DMS_URL = os.getenv('DMS_URL') +USE_KUBECTL = os.getenv('USE_KUBECTL', True) +IN_CLUSTER = os.getenv('IN_CLUSTER', True) + +if IN_CLUSTER: + config.load_incluster_config() +else: + config.load_kube_config() + + +def kubemunch(*args): + kubectl = sh.kubectl.bake('-o', 'yaml') + munched = munchify(yaml.load(kubectl(args).stdout)) + if 'items' in munched.keys(): + # override items method + munched.items = munched['items'] + return munched + + +def namespace_network_policy_names(use_kubectl=USE_KUBECTL): + if use_kubectl: + policies = kubemunch('get', 'netpol', '--all-namespaces').items + else: + v1beta1 = client.ExtensionsV1beta1Api() + policies = v1beta1.list_network_policy_for_all_namespaces().items + + ns_policies = defaultdict(list) + for policy in policies: + ns_policies[policy.metadata.namespace].append(policy.metadata.name) + return ns_policies + + +def namespaces(use_kubectl=USE_KUBECTL): + if use_kubectl: + return [ns.metadata.name for ns in kubemunch('get', 'ns').items] + else: + v1 = client.CoreV1Api() + return [ns.metadata.name for ns in v1.list_namespace().items] + + +def create_policy(namespace, use_kubectl=USE_KUBECTL): + if use_kubectl: + response = kubemunch('create', '-n', namespace, '-f', POLICY_FILENAME) + else: + md = client.V1ObjectMeta(name=AWS_NETWORK_POLICY_NAME, + namespace=namespace) + match_expression = client.V1LabelSelectorRequirement( + key='k8s-app', operator='DoesNotExist') + pod_selector = client.V1LabelSelector( + match_expressions=[match_expression]) + + ip_block = client.V1beta1IPBlock( + cidr='0.0.0.0/0', _except=['169.254.0.0/16']) + peer = client.V1beta1NetworkPolicyPeer(ip_block=ip_block) + egress = client.V1beta1NetworkPolicyEgressRule(to=[peer]) + spec = client.V1beta1NetworkPolicySpec( + pod_selector=pod_selector, + egress=[egress], + policy_types=['Egress']) + policy = client.V1beta1NetworkPolicy(metadata=md, spec=spec) + networkingv1 = client.NetworkingV1Api() + response = networkingv1.create_namespaced_network_policy(namespace, + policy) + print("\tCreated {} in ns {}".format(response.metadata.name, + response.metadata.namespace)) + + +def ping_dms(): + if DMS_URL: + print("Notifying DMS") + r = requests.get(DMS_URL) + print(r.status_code) + else: + print('DMS_URL not found, not notifying DMS') + + +def main(): + ns_policies = namespace_network_policy_names() + for namespace in namespaces(): + print("-> ", namespace) + if namespace in WHITELISTED_NAMESPACES: + print("\tskipping, ns whitelisted") + elif AWS_NETWORK_POLICY_NAME not in ns_policies[namespace]: + create_policy(namespace) + else: + print("\tAWS already blocked") + ping_dms() + + +if __name__ == '__main__': + main() diff --git a/k8s/tools/block-aws/requirements.txt b/k8s/tools/block-aws/requirements.txt index 06b605d9..a71d7d65 100644 --- a/k8s/tools/block-aws/requirements.txt +++ b/k8s/tools/block-aws/requirements.txt @@ -1,2 +1,4 @@ kubernetes +munch requests +sh