Skip to content

Commit

Permalink
Merge pull request #27 from caktus/aws-s3-iam-role
Browse files Browse the repository at this point in the history
Amazon S3: buckets with IAM role for K8s service accounts
  • Loading branch information
copelco authored Jul 28, 2020
2 parents 40f795b + 88614e6 commit 9783165
Show file tree
Hide file tree
Showing 7 changed files with 205 additions and 1 deletion.
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
- 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

0 comments on commit 9783165

Please sign in to comment.