Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Amazon S3: buckets with IAM role for K8s service accounts #27

Merged
merged 10 commits into from
Jul 28, 2020
Merged
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
.vscode
.python-version
44 changes: 44 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -139,3 +139,47 @@ Celery
k8s_worker_image_pull_policy: "{{ k8s_container_image_pull_policy }}"
k8s_worker_image_tag: "{{ k8s_container_image_tag }}"
k8s_worker_resources: "{{ k8s_container_resources }}"


Amazon S3: IAM role for service accounts
````````````````````````````````````````

Web applications running on AWS typically use Amazon S3 for static and media
resources. ``caktus.django-k8s`` optionally supports enabling a Kubernetes
service account and associated IAM role that defines the access to public and
private S3 buckets. This provides similar functionality of
`EC2 instance profiles <https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_use_switch-role-ec2.html>`_
within Kubernetes namespaces. This
`AWS blog post <https://aws.amazon.com/blogs/opensource/introducing-fine-grained-iam-roles-service-accounts/>`_
also provides a good overview.

At a high level, the process is:

1. Create public and private S3 buckets
2. `Enable IAM roles for cluster service accounts <https://docs.aws.amazon.com/eks/latest/userguide/enable-iam-roles-for-service-accounts.html>`_
* Requirement: `eksctl <https://eksctl.io/introduction/#installation>`_ must be installed
3. `Create an IAM role with a trust relatinoship and S3 policy for a service account <https://docs.aws.amazon.com/eks/latest/userguide/create-service-account-iam-policy-and-role.html>`_
4. `Annotate the service account with the ARN of the IAM role <https://docs.aws.amazon.com/eks/latest/userguide/specify-service-account-role.html>`_

Required variables:

* ``k8s_s3_cluster_name``: name of EKS cluster in AWS

A separate playbook can be used to invoke this functionality:

.. code-block:: yaml

---
# file: deploy-s3.yaml

- hosts: k8s
vars:
ansible_connection: local
ansible_python_interpreter: "{{ ansible_playbook_python }}"
tasks:
- name: configure Amazon S3 buckets
import_role:
name: caktus.django-k8s
tasks_from: aws_s3

Run with: ``ansible-playbook deploy-s3.yml``.
8 changes: 8 additions & 0 deletions defaults/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,14 @@ k8s_collectstatic_command:
- -v
- "2"

k8s_s3_cluster_name: "" # name of EKS cluster in AWS
k8s_s3_region: "us-east-1"
k8s_s3_namespace: "{{ k8s_namespace }}"
k8s_s3_iam_role: "S3ServiceAccountRole-{{ k8s_s3_namespace }}"
k8s_s3_serviceaccount: "default"
k8s_s3_public_bucket: "{{ k8s_s3_namespace }}-assets"
k8s_s3_private_bucket: "{{ k8s_s3_namespace }}-private-assets"

k8s_templates:
- name: registry_secret.yaml.j2
state: "{{ k8s_dockerconfigjson | ternary('present', 'absent') }}"
Expand Down
107 changes: 107 additions & 0 deletions tasks/aws_s3.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
---

#
# Public S3 assets bucket
#

- name: "create public bucket {{ k8s_s3_public_bucket }}"
s3_bucket:
name: "{{ k8s_s3_public_bucket }}"
state: present
versioning: yes
region: "{{ k8s_s3_region }}"

#
# Private S3 assets bucket
#

- name: "create private bucket {{ k8s_s3_private_bucket }}"
s3_bucket:
name: "{{ k8s_s3_private_bucket }}"
state: present
versioning: yes
region: "{{ k8s_s3_region }}"
encryption: AES256

# Not available via Ansible module as of 7/2020
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bummer :( I've subscribed to the relevant github issue...

- name: "block all public access to {{ k8s_s3_private_bucket }}"
command:
argv:
- aws
- s3api
- put-public-access-block
- --bucket
- "{{ k8s_s3_private_bucket }}"
- --public-access-block-configuration
- BlockPublicAcls=true,IgnorePublicAcls=true,BlockPublicPolicy=true,RestrictPublicBuckets=true

#
# IAM OIDC identity provider and issuer
#

# https://docs.aws.amazon.com/eks/latest/userguide/enable-iam-roles-for-service-accounts.html
# Possibly replace in future with (to remove eksctl requirement):
# 1. https://docs.aws.amazon.com/cli/latest/reference/iam/create-open-id-connect-provider.html
# 2. https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_providers_create_oidc_verify-thumbprint.html
- name: create an IAM OIDC identity provider for the cluster
command: "eksctl utils associate-iam-oidc-provider --cluster {{ k8s_s3_cluster_name }} --approve"
register: associate_response
changed_when: "'created' in associate_response.stdout"

# Not available via Ansible module as of 7/2020
- name: describe cluster to obtain OIDC issuer
command: "aws eks describe-cluster --region {{ k8s_s3_region }} --name {{ k8s_s3_cluster_name }} --output json"
changed_when: false
register: cluster_query

- name: parse OIDC issuer from response
set_fact:
oidc_issuer: "{{ cluster_query.stdout | from_json | json_query('cluster.identity.oidc.issuer') | regex_replace('https://') }}"

#
# AWS Account ID
#

- name: get the current caller identity information
aws_caller_info:
register: caller_info

- name: parse AWS account ID
set_fact:
aws_account_id: "{{ caller_info.account }}"

#
# IAM Role
#

# https://docs.aws.amazon.com/eks/latest/userguide/create-service-account-iam-policy-and-role.html
- name: Create IAM role for K8s service account
iam_role:
name: "{{ k8s_s3_iam_role }}"
assume_role_policy_document: "{{ lookup('template', 's3/TrustPolicy.json.j2') }}"
description: IAM role for K8s service account

- name: Attach inline policy to user
iam_policy:
iam_type: role
iam_name: "{{ k8s_s3_iam_role }}"
policy_name: "EKSBucketPolicy"
state: present
policy_json: "{{ lookup( 'template', 's3/AssetManagementPolicy.json.j2') }}"

#
# Service Account
#

# https://docs.aws.amazon.com/eks/latest/userguide/specify-service-account-role.html
- name: "Associate IAM role with the service account in your cluster"
k8s:
state: present
definition:
apiVersion: v1
kind: ServiceAccount
metadata:
name: "{{ k8s_s3_serviceaccount }}"
namespace: "{{ k8s_s3_namespace }}"
annotations:
eks.amazonaws.com/role-arn: "arn:aws:iam::{{ aws_account_id }}:role/{{ k8s_s3_iam_role }}"
25 changes: 25 additions & 0 deletions templates/s3/AssetManagementPolicy.json.j2
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:ListBucket"
],
"Resource": [
"arn:aws:s3:::{{ k8s_s3_public_bucket }}",
"arn:aws:s3:::{{ k8s_s3_private_bucket }}"
]
},
{
"Effect": "Allow",
"Action": [
"s3:*"
],
"Resource": [
"arn:aws:s3:::{{ k8s_s3_public_bucket }}/*",
"arn:aws:s3:::{{ k8s_s3_private_bucket }}/*"
]
}
]
}
17 changes: 17 additions & 0 deletions templates/s3/TrustPolicy.json.j2
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Federated": "arn:aws:iam::{{ aws_account_id }}:oidc-provider/{{ oidc_issuer }}"
},
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
"StringEquals": {
"{{ oidc_issuer }}:sub": "system:serviceaccount:{{ k8s_s3_namespace }}:{{ k8s_s3_serviceaccount }}"
}
}
}
]
}
3 changes: 2 additions & 1 deletion templates/web.yaml.j2
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,8 @@ spec:
ports:
- containerPort: {{ container["port"] }}
resources: {{ container["resources"] | to_json }}
securityContext: {}
securityContext:
fsGroup: 2000
---
apiVersion: v1
kind: Service
Expand Down