Skip to content

Commit

Permalink
Reinvent permissions to ECR (#21)
Browse files Browse the repository at this point in the history
## What
* Grant permission to access ECR using ECR policy with principal that have access to it. Basically, let ECR describe who can access it, rather than each user/role listing the modules they can access

## Why
* To solve IAM limit problem (more scalable strategy and probably the way we should have done it from the get go)

## Breaking changes
* Variable `roles` replaced with `principals_full_access` or `principals_readonly_access` and expects list or role\user arns as value
* We removed policies that provide access to the registry. (`policy_login_name`, `policy_login_arn`, `policy_read_name`, `policy_read_arn`, `policy_write_name`, `policy_write_arn`).
So you do not need to attach the policies to IAM role\user. Please provide IAM role\user arn as variable `principals_full_access` or `principals_readonly_access` depend on what type of access to you need.

Example:


```
module "kops_ecr" {
  source       = "git::https://github.com/cloudposse/terraform-aws-ecr.git?ref=tags/0.2.11"
  name         = "${var.name}"
  namespace    = "${var.namespace}"
  stage        = "${var.stage}"
  use_fullname = "${var.use_fullname}"

  roles = [
    "${module.kops_metadata.masters_role_name}",
    "${module.kops_metadata.nodes_role_name}",
  ]
}

resource "aws_iam_policy_attachment" "login" {
  count      = "${signum(length(var.users))}"
  name       = "${module.label.id}"
  users      = ["${var.users}"]
  policy_arn = "${module.kops_ecr.policy_login_arn}"
}

```

now should be 

```
module "kops_ecr" {
  source       = "git::https://github.com/cloudposse/terraform-aws-ecr.git?ref=tags/0.3.0"
  name         = "${var.name}"
  namespace    = "${var.namespace}"
  stage        = "${var.stage}"
  use_fullname = "${var.use_fullname}"

  principals_readonly_access = [
    "${module.kops_metadata.masters_role_arn}",
    "${module.kops_metadata.nodes_role_arn}",
  ]

  principals_full_access =  [
    "${var.users_arns}"
  ]
}

```
  • Loading branch information
goruha authored Jan 29, 2019
1 parent c86a99e commit daf7796
Show file tree
Hide file tree
Showing 3 changed files with 117 additions and 164 deletions.
241 changes: 109 additions & 132 deletions main.tf
Original file line number Diff line number Diff line change
@@ -1,9 +1,60 @@
data "aws_iam_role" "default" {
count = "${signum(length(var.roles)) == 1 ? length(var.roles) : 0}"
name = "${element(var.roles, count.index)}"
locals {
principals_readonly_access_count = "${length(var.principals_readonly_access)}"
principals_readonly_access_non_empty = "${signum(length(var.principals_readonly_access))}"
principals_readonly_access_empty = "${signum(length(var.principals_readonly_access)) == 0 ? 1 : 0}"

principals_full_access_count = "${length(var.principals_full_access)}"
principals_full_access_non_empty = "${signum(length(var.principals_full_access))}"
principals_full_access_empty = "${signum(length(var.principals_full_access)) == 0 ? 1 : 0}"

principals_total_count = "${length(var.principals_readonly_access) + length(var.principals_full_access)}"
principals_total_non_empty = "${signum(length(var.principals_readonly_access) + length(var.principals_full_access))}"
principals_total_empty = "${signum(length(var.principals_readonly_access) + length(var.principals_full_access)) == 0 ? 1 : 0}"
}

module "label" {
source = "git::https://github.com/cloudposse/terraform-null-label.git?ref=tags/0.3.3"
namespace = "${var.namespace}"
stage = "${var.stage}"
name = "${var.name}"
delimiter = "${var.delimiter}"
attributes = "${var.attributes}"
tags = "${var.tags}"
}

resource "aws_ecr_repository" "default" {
name = "${var.use_fullname == "true" ? module.label.id : module.label.name}"
}

resource "aws_ecr_lifecycle_policy" "default" {
repository = "${aws_ecr_repository.default.name}"

policy = <<EOF
{
"rules": [{
"rulePriority": 1,
"description": "Rotate images when reach ${var.max_image_count} images stored",
"selection": {
"tagStatus": "tagged",
"tagPrefixList": ["${var.stage}"],
"countType": "imageCountMoreThan",
"countNumber": ${var.max_image_count}
},
"action": {
"type": "expire"
}
}]
}
EOF
}

## If roles are empty
## Create default role to provide full access.
## The role can be attached or assumed

data "aws_iam_policy_document" "assume_role" {
count = "${local.principals_total_empty}"

statement {
sid = "EC2AssumeRole"
effect = "Allow"
Expand All @@ -16,199 +67,125 @@ data "aws_iam_policy_document" "assume_role" {
}
}

data "aws_iam_policy_document" "login" {
statement {
sid = "ECRGetAuthorizationToken"
effect = "Allow"
actions = ["ecr:GetAuthorizationToken"]
resource "aws_iam_role" "default" {
count = "${local.principals_total_empty}"
name = "${module.label.id}"
assume_role_policy = "${data.aws_iam_policy_document.assume_role.json}"
}

resources = ["*"]
}
resource "aws_iam_instance_profile" "default" {
count = "${local.principals_total_empty}"
name = "${module.label.id}"
role = "${aws_iam_role.default.name}"
}

data "aws_iam_policy_document" "write" {
## Grant access to default role
data "aws_iam_policy_document" "default_ecr" {
count = "${local.principals_total_empty}"

statement {
sid = "ECRGetAuthorizationToken"
sid = "ECR"
effect = "Allow"

actions = [
"ecr:InitiateLayerUpload",
"ecr:UploadLayerPart",
"ecr:CompleteLayerUpload",
"ecr:PutImage",
]

resources = ["${aws_ecr_repository.default.arn}"]
}
}
principals = {
type = "AWS"

data "aws_iam_policy_document" "read" {
statement {
sid = "ECRGetAuthorizationToken"
effect = "Allow"
identifiers = [
"${aws_iam_role.default.arn}",
]
}

actions = [
"ecr:BatchCheckLayerAvailability",
"ecr:GetDownloadUrlForLayer",
"ecr:GetRepositoryPolicy",
"ecr:BatchGetImage",
"ecr:BatchCheckLayerAvailability",
"ecr:PutImage",
"ecr:InitiateLayerUpload",
"ecr:UploadLayerPart",
"ecr:CompleteLayerUpload",
"ecr:DescribeRepositories",
"ecr:ListImages",
"ecr:DescribeImages",
"ecr:BatchGetImage",
]

resources = ["${aws_ecr_repository.default.arn}"]
}
}

data "aws_iam_policy_document" "default_ecr" {
count = "${signum(length(var.roles)) == 1 ? 0 : 1}"
resource "aws_ecr_repository_policy" "default_ecr" {
count = "${local.principals_total_empty}"
repository = "${aws_ecr_repository.default.name}"
policy = "${data.aws_iam_policy_document.default_ecr.json}"
}

## If any roles provided
## Grant access to them

data "aws_iam_policy_document" "empty" {}

data "aws_iam_policy_document" "resource_readonly_access" {
statement {
sid = "ecr"
sid = "ReadonlyAccess"
effect = "Allow"

principals = {
type = "AWS"

identifiers = [
"${aws_iam_role.default.arn}",
"${var.principals_readonly_access}",
]
}

actions = [
"ecr:GetAuthorizationToken",
"ecr:GetDownloadUrlForLayer",
"ecr:BatchGetImage",
"ecr:BatchCheckLayerAvailability",
"ecr:PutImage",
"ecr:InitiateLayerUpload",
"ecr:UploadLayerPart",
"ecr:CompleteLayerUpload",
"ecr:GetDownloadUrlForLayer",
"ecr:GetRepositoryPolicy",
"ecr:DescribeRepositories",
"ecr:ListImages",
"ecr:DescribeImages",
"ecr:BatchGetImage",
]
}
}

data "aws_iam_policy_document" "resource" {
count = "${signum(length(var.roles))}"

data "aws_iam_policy_document" "resource_full_access" {
statement {
sid = "ecr"
sid = "FullAccess"
effect = "Allow"

principals = {
type = "AWS"

identifiers = [
"${data.aws_iam_role.default.*.arn}",
"${var.principals_full_access}",
]
}

actions = [
"ecr:GetAuthorizationToken",
"ecr:GetDownloadUrlForLayer",
"ecr:BatchGetImage",
"ecr:BatchCheckLayerAvailability",
"ecr:PutImage",
"ecr:InitiateLayerUpload",
"ecr:UploadLayerPart",
"ecr:CompleteLayerUpload",
"ecr:PutImage",
"ecr:BatchCheckLayerAvailability",
"ecr:GetDownloadUrlForLayer",
"ecr:GetRepositoryPolicy",
"ecr:DescribeRepositories",
"ecr:ListImages",
"ecr:DescribeImages",
"ecr:BatchGetImage",
]
}
}

module "label" {
source = "git::https://github.com/cloudposse/terraform-null-label.git?ref=tags/0.3.1"
namespace = "${var.namespace}"
stage = "${var.stage}"
name = "${var.name}"
delimiter = "${var.delimiter}"
attributes = "${var.attributes}"
tags = "${var.tags}"
}
data "aws_iam_policy_document" "resource" {
count = "${local.principals_total_non_empty}"

resource "aws_ecr_repository" "default" {
name = "${var.use_fullname == "true" ? module.label.id : module.label.name}"
source_json = "${local.principals_readonly_access_non_empty ? data.aws_iam_policy_document.resource_readonly_access.json : data.aws_iam_policy_document.empty.json}"
override_json = "${local.principals_full_access_non_empty ? data.aws_iam_policy_document.resource_full_access.json : data.aws_iam_policy_document.empty.json}"
}

resource "aws_ecr_repository_policy" "default" {
count = "${signum(length(var.roles))}"
count = "${local.principals_total_non_empty}"
repository = "${aws_ecr_repository.default.name}"
policy = "${data.aws_iam_policy_document.resource.json}"
}

resource "aws_ecr_repository_policy" "default_ecr" {
count = "${signum(length(var.roles)) == 1 ? 0 : 1}"
repository = "${aws_ecr_repository.default.name}"
policy = "${data.aws_iam_policy_document.default_ecr.json}"
}

resource "aws_iam_policy" "login" {
name = "${module.label.id}${var.delimiter}login"
description = "Allow IAM Users to call ecr:GetAuthorizationToken"
policy = "${data.aws_iam_policy_document.login.json}"
}

resource "aws_iam_policy" "read" {
name = "${module.label.id}${var.delimiter}read"
description = "Allow IAM Users to pull from ECR"
policy = "${data.aws_iam_policy_document.read.json}"
}

resource "aws_iam_policy" "write" {
name = "${module.label.id}${var.delimiter}write"
description = "Allow IAM Users to push into ECR"
policy = "${data.aws_iam_policy_document.write.json}"
}

resource "aws_iam_role" "default" {
count = "${signum(length(var.roles)) == 1 ? 0 : 1}"
name = "${module.label.id}"
assume_role_policy = "${data.aws_iam_policy_document.assume_role.json}"
}

resource "aws_iam_role_policy_attachment" "default_ecr" {
count = "${signum(length(var.roles)) == 1 ? 0 : 1}"
role = "${aws_iam_role.default.name}"
policy_arn = "${aws_iam_policy.login.arn}"
}

resource "aws_iam_role_policy_attachment" "default" {
count = "${signum(length(var.roles)) == 1 ? length(var.roles) : 0}"
role = "${element(var.roles, count.index)}"
policy_arn = "${aws_iam_policy.login.arn}"
}

resource "aws_iam_instance_profile" "default" {
count = "${signum(length(var.roles)) == 1 ? 0 : 1}"
name = "${module.label.id}"
role = "${aws_iam_role.default.name}"
}

resource "aws_ecr_lifecycle_policy" "default" {
repository = "${aws_ecr_repository.default.name}"

policy = <<EOF
{
"rules": [{
"rulePriority": 1,
"description": "Rotate images when reach ${var.max_image_count} images stored",
"selection": {
"tagStatus": "tagged",
"tagPrefixList": ["${var.stage}"],
"countType": "imageCountMoreThan",
"countNumber": ${var.max_image_count}
},
"action": {
"type": "expire"
}
}]
}
EOF
}
30 changes: 0 additions & 30 deletions output.tf
Original file line number Diff line number Diff line change
Expand Up @@ -22,33 +22,3 @@ output "role_arn" {
value = "${join("", aws_iam_role.default.*.arn)}"
description = "Assume Role ARN to get registry access"
}

output "policy_login_name" {
value = "${aws_iam_policy.login.name}"
description = "The IAM Policy name to be given access to login in ECR"
}

output "policy_login_arn" {
value = "${aws_iam_policy.login.arn}"
description = "The IAM Policy ARN to be given access to login in ECR"
}

output "policy_read_name" {
value = "${aws_iam_policy.read.name}"
description = "The IAM Policy name to be given access to pull images from ECR"
}

output "policy_read_arn" {
value = "${aws_iam_policy.read.arn}"
description = "The IAM Policy ARN to be given access to pull images from ECR"
}

output "policy_write_name" {
value = "${aws_iam_policy.write.name}"
description = "The IAM Policy name to be given access to push images to ECR"
}

output "policy_write_arn" {
value = "${aws_iam_policy.write.arn}"
description = "The IAM Policy ARN to be given access to push images to ECR"
}
10 changes: 8 additions & 2 deletions variables.tf
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,15 @@ variable "use_fullname" {
description = "Set 'true' to use `namespace-stage-name` for ecr repository name, else `name`"
}

variable "roles" {
variable "principals_full_access" {
type = "list"
description = "Principal IAM roles to provide with access to the ECR"
description = "Principal ARN to provide with full access to the ECR"
default = []
}

variable "principals_readonly_access" {
type = "list"
description = "Principal ARN to provide with readonly access to the ECR"
default = []
}

Expand Down

0 comments on commit daf7796

Please sign in to comment.