Skip to content

Commit

Permalink
Merge pull request #2 from komminarlabs/tk/initial-setup
Browse files Browse the repository at this point in the history
Initial Setup
  • Loading branch information
thulasirajkomminar authored Apr 10, 2024
2 parents f0436ef + 026641f commit b506375
Show file tree
Hide file tree
Showing 8 changed files with 612 additions and 2 deletions.
143 changes: 143 additions & 0 deletions .github/workflows/terraform.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
---
name: Terraform

on:
pull_request:

permissions:
contents: write
pull-requests: write

env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

jobs:
fmt-lint-validate:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Setup Terraform
uses: hashicorp/setup-terraform@v2

- name: Setup Terraform Linters
uses: terraform-linters/setup-tflint@v4
with:
github_token: ${{ env.GITHUB_TOKEN }}

- name: Terraform Format
id: fmt
run: terraform fmt -check -recursive

- name: Terraform Init
id: init
run: terraform init

- name: Terraform Validate
id: validate
run: terraform validate -no-color

- name: Terraform Lint
id: lint
run: tflint --no-color --recursive --format compact

- uses: actions/github-script@v6
if: github.event_name == 'pull_request' || always()
with:
github-token: ${{ env.GITHUB_TOKEN }}
script: |
// 1. Retrieve existing bot comments for the PR
const { data: comments } = await github.rest.issues.listComments({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
})
const botComment = comments.find(comment => {
return comment.user.type === 'Bot' && comment.body.includes('Terraform Format and Style')
})
// 2. Prepare format of the comment
const output = `#### Terraform Format and Style 🖌\`${{ steps.fmt.outcome }}\`
#### Terraform Initialization ⚙️\`${{ steps.init.outcome }}\`
#### Terraform Lint 📖\`${{ steps.lint.outcome }}\`
#### Terraform Validation 🤖\`${{ steps.validate.outcome }}\`
<details><summary>Validation Output</summary>
\`\`\`\n
${{ steps.validate.outputs.stdout }}
\`\`\`
</details>`;
// 3. If we have a comment, update it, otherwise create a new one
if (botComment) {
github.rest.issues.updateComment({
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: botComment.id,
body: output
})
} else {
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: output
})
}
tfsec:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Terraform security scan
uses: aquasecurity/tfsec-action@v1.0.3
with:
github_token: ${{ env.GITHUB_TOKEN }}
soft_fail: false

- name: Terraform pr commenter
uses: aquasecurity/tfsec-pr-commenter-action@v1.3.1
with:
github_token: ${{ env.GITHUB_TOKEN }}
tfsec_args: --concise-output --force-all-dirs

checkov:
runs-on: ubuntu-latest
steps:
- name: Check out code
uses: actions/checkout@v4

- name: Run Checkov
uses: bridgecrewio/checkov-action@v12.2577.0
with:
container_user: 1000
directory: "/"
download_external_modules: false
framework: terraform
output_format: sarif
quiet: true
skip_check: "CKV_TF_1,CKV_AWS_108,CKV_AWS_109,CKV_AWS_111,CKV_AWS_356"
soft_fail: false

docs:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.ref }}

- name: Render terraform docs inside the README.md and push changes back to PR branch
uses: terraform-docs/gh-actions@v1.0.0
with:
args: --sort-by required
git-commit-message: "docs(readme): update module usage"
git-push: true
output-file: README.md
output-method: inject
working-dir: .
continue-on-error: true # added this to prevent a PR from a remote fork failing the workflow
72 changes: 70 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,70 @@
# terraform-aws-grafana
Terraform module to create and manage Grafana
# terraform-aws-managed-grafana
Terraform module to create and manage Amazon Managed Grafana

<!-- BEGIN_TF_DOCS -->
## Requirements

| Name | Version |
|------|---------|
| <a name="requirement_terraform"></a> [terraform](#requirement\_terraform) | >= 1.0 |

## Providers

| Name | Version |
|------|---------|
| <a name="provider_aws"></a> [aws](#provider\_aws) | n/a |

## Modules

No modules.

## Resources

| Name | Type |
|------|------|
| [aws_grafana_license_association.default](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/grafana_license_association) | resource |
| [aws_grafana_role_association.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/grafana_role_association) | resource |
| [aws_grafana_workspace.default](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/grafana_workspace) | resource |
| [aws_grafana_workspace_api_key.default](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/grafana_workspace_api_key) | resource |
| [aws_iam_policy.default](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy) | resource |
| [aws_iam_role.default](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role) | resource |
| [aws_iam_role_policy_attachment.data_sources](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource |
| [aws_iam_role_policy_attachment.default](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource |
| [aws_caller_identity.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/caller_identity) | data source |
| [aws_iam_policy_document.assume_policy](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source |
| [aws_iam_policy_document.default](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source |
| [aws_partition.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/partition) | data source |

## Inputs

| Name | Description | Type | Default | Required |
|------|-------------|------|---------|:--------:|
| <a name="input_description"></a> [description](#input\_description) | The workspace description | `string` | n/a | yes |
| <a name="input_name"></a> [name](#input\_name) | The Grafana workspace name | `string` | n/a | yes |
| <a name="input_tags"></a> [tags](#input\_tags) | A mapping of tags to assign to the resources | `map(string)` | n/a | yes |
| <a name="input_account_access_type"></a> [account\_access\_type](#input\_account\_access\_type) | The type of account access for the workspace. Valid values are `CURRENT_ACCOUNT` and `ORGANIZATION`. If ORGANIZATION is specified, then organizational\_units must also be present | `string` | `"CURRENT_ACCOUNT"` | no |
| <a name="input_authentication_providers"></a> [authentication\_providers](#input\_authentication\_providers) | The authentication providers for the workspace. Valid values are `AWS_SSO`, `SAML`, or both | `list(string)` | <pre>[<br> "AWS_SSO"<br>]</pre> | no |
| <a name="input_configuration"></a> [configuration](#input\_configuration) | The configuration string for the workspace that you create | `string` | `null` | no |
| <a name="input_data_sources"></a> [data\_sources](#input\_data\_sources) | The data sources for the workspace. Valid values are `AMAZON_OPENSEARCH_SERVICE`, `ATHENA`, `CLOUDWATCH`, `PROMETHEUS`, `REDSHIFT`, `SITEWISE`, `TIMESTREAM`, `XRAY` | `list(string)` | `[]` | no |
| <a name="input_grafana_version"></a> [grafana\_version](#input\_grafana\_version) | Specifies the version of Grafana to support in the new workspace. If not specified, the default version for the `aws_grafana_workspace` resource will be used. See `aws_grafana_workspace` documentation for available options. | `string` | `"8.4"` | no |
| <a name="input_iam_role_arn"></a> [iam\_role\_arn](#input\_iam\_role\_arn) | The arn of the IAM role to use for grafana workspace | `string` | `null` | no |
| <a name="input_license_type"></a> [license\_type](#input\_license\_type) | The type of license for the workspace license association. Valid values are `ENTERPRISE` and `ENTERPRISE_FREE_TRIAL` | `string` | `null` | no |
| <a name="input_network_access_control"></a> [network\_access\_control](#input\_network\_access\_control) | Configuration for network access to your workspace | <pre>object({<br> prefix_list_ids = list(string)<br> vpce_ids = list(string)<br> })</pre> | `null` | no |
| <a name="input_notification_destinations"></a> [notification\_destinations](#input\_notification\_destinations) | The notification destinations. If a data source is specified here, Amazon Managed Grafana will create IAM roles and permissions needed to use these destinations. Must be set to `SNS` | `list(string)` | <pre>[<br> "SNS"<br>]</pre> | no |
| <a name="input_organization_role_name"></a> [organization\_role\_name](#input\_organization\_role\_name) | The role name that the workspace uses to access resources through Amazon Organizations | `string` | `null` | no |
| <a name="input_organizational_units"></a> [organizational\_units](#input\_organizational\_units) | The Amazon Organizations organizational units that the workspace is authorized to use data sources from | `list(string)` | `[]` | no |
| <a name="input_permission_type"></a> [permission\_type](#input\_permission\_type) | The permission type of the workspace. If `SERVICE_MANAGED` is specified, the IAM roles and IAM policy attachments are generated automatically. If `CUSTOMER_MANAGED` is specified, the IAM roles and IAM policy attachments will not be created | `string` | `"SERVICE_MANAGED"` | no |
| <a name="input_role_association"></a> [role\_association](#input\_role\_association) | List of user/group IDs to assocaite to a role | <pre>list(object({<br> group_ids = optional(list(string))<br> role = string<br> user_ids = optional(list(string))<br> }))</pre> | `[]` | no |
| <a name="input_vpc_configuration"></a> [vpc\_configuration](#input\_vpc\_configuration) | The configuration settings for an Amazon VPC that contains data sources for your Grafana workspace to connect to | <pre>object({<br> security_group_ids = list(string)<br> subnet_ids = list(string)<br> })</pre> | `null` | no |
| <a name="input_workspace_api_key"></a> [workspace\_api\_key](#input\_workspace\_api\_key) | List of workspace API Key resources to create | <pre>list(object({<br> name = string<br> role = string<br> seconds_to_live = number<br> }))</pre> | `[]` | no |

## Outputs

| Name | Description |
|------|-------------|
| <a name="output_license_expiration"></a> [license\_expiration](#output\_license\_expiration) | If `license_type` is set to `ENTERPRISE`, this is the expiration date of the enterprise license |
| <a name="output_license_free_trial_expiration"></a> [license\_free\_trial\_expiration](#output\_license\_free\_trial\_expiration) | If `license_type` is set to `ENTERPRISE_FREE_TRIAL`, this is the expiration date of the free trial |
| <a name="output_workspace"></a> [workspace](#output\_workspace) | The Grafana workspace details |
| <a name="output_workspace_api_keys"></a> [workspace\_api\_keys](#output\_workspace\_api\_keys) | The workspace API keys created including their attributes |
| <a name="output_workspace_iam_role"></a> [workspace\_iam\_role](#output\_workspace\_iam\_role) | IAM role details of the Grafana workspace |
<!-- END_TF_DOCS -->
47 changes: 47 additions & 0 deletions examples/example.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
provider "aws" {
region = "eu-central-1"
}

module "example_grafana_workspace" {
source = "../"
name = "default-workspace"
description = "AWS Managed Grafana service example workspace"
account_access_type = "CURRENT_ACCOUNT"
authentication_providers = ["SAML"]
permission_type = "SERVICE_MANAGED"
data_sources = ["ATHENA", "TIMESTREAM", "XRAY"]

role_association = [
{
role = "ADMIN"
group_ids = ["*******"]
},
{
role = "EDITOR"
user_ids = ["*******"]
}
]

workspace_api_key = [
{
name = "admin"
role = "ADMIN"
seconds_to_live = 3600
},
{
name = "editor"
role = "EDITOR"
seconds_to_live = 3600
},
{
name = "viewer"
role = "VIEWER"
seconds_to_live = 3600
}
]

tags = {
Environment = "development"
Stack = "grafana"
}
}
111 changes: 111 additions & 0 deletions iam.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
locals {
create_iam_role = var.iam_role_arn == null ? true : false

iam_data_source_policies = {
ATHENA = "arn:${data.aws_partition.current.partition}:iam::aws:policy/service-role/AmazonGrafanaAthenaAccess"
CLOUDWATCH = "arn:${data.aws_partition.current.partition}:iam::aws:policy/service-role/AmazonGrafanaCloudWatchAccess"
REDSHIFT = "arn:${data.aws_partition.current.partition}:iam::aws:policy/service-role/AmazonGrafanaRedshiftAccess"
SITEWISE = "arn:${data.aws_partition.current.partition}:iam::aws:policy/service-role/AWSIoTSiteWiseReadOnlyAccess"
TIMESTREAM = "arn:${data.aws_partition.current.partition}:iam::aws:policy/AmazonTimestreamReadOnlyAccess"
XRAY = "arn:${data.aws_partition.current.partition}:iam::aws:policy/AWSXrayReadOnlyAccess"
}
}

data "aws_iam_policy_document" "assume_policy" {
count = local.create_iam_role ? 1 : 0

statement {
sid = "GrafanaAssume"
effect = "Allow"
actions = ["sts:AssumeRole"]

principals {
type = "Service"
identifiers = ["grafana.${data.aws_partition.current.dns_suffix}"]
}
}
}

data "aws_iam_policy_document" "default" {
count = local.create_iam_role ? 1 : 0

dynamic "statement" {
for_each = contains(var.data_sources, "AMAZON_OPENSEARCH_SERVICE") ? { create : true } : {}

content {
actions = [
"es:ESHttpGet",
"es:DescribeElasticsearchDomains",
"es:ListDomainNames",
]
resources = ["*"]
}
}

dynamic "statement" {
for_each = contains(var.data_sources, "AMAZON_OPENSEARCH_SERVICE") ? { create : true } : {}

content {
actions = ["es:ESHttpGet"]
resources = [
"arn:${data.aws_partition.current.partition}:es:*:*:domain/*/_msearch*",
"arn:${data.aws_partition.current.partition}:es:*:*:domain/*/_opendistro/_ppl",
]
}
}

dynamic "statement" {
for_each = contains(var.data_sources, "PROMETHEUS") ? { create : true } : {}

content {
actions = [
"aps:ListWorkspaces",
"aps:DescribeWorkspace",
"aps:QueryMetrics",
"aps:GetLabels",
"aps:GetSeries",
"aps:GetMetricMetadata",
]
resources = ["*"]
}
}

dynamic "statement" {
for_each = contains(var.notification_destinations, "SNS") ? { create : true } : {}

content {
actions = ["sns:Publish"]
resources = ["arn:${data.aws_partition.current.partition}:sns:*:${data.aws_caller_identity.current.account_id}:grafana*"]
}
}
}

resource "aws_iam_role" "default" {
count = local.create_iam_role ? 1 : 0

name = "GrafanaExecutionRole-${var.name}"
assume_role_policy = data.aws_iam_policy_document.assume_policy[0].json
tags = var.tags
}

resource "aws_iam_policy" "default" {
count = local.create_iam_role ? 1 : 0

name = "GrafanaExecutionRolePolicy-${var.name}"
policy = data.aws_iam_policy_document.default[0].json
tags = var.tags
}

resource "aws_iam_role_policy_attachment" "data_sources" {
for_each = { for i, v in var.data_sources : v => local.iam_data_source_policies[v] if local.create_iam_role }

role = aws_iam_role.default[0].name
policy_arn = each.value
}

resource "aws_iam_role_policy_attachment" "default" {
count = local.create_iam_role ? 1 : 0

role = aws_iam_role.default[0].name
policy_arn = aws_iam_policy.default[0].arn
}
Loading

0 comments on commit b506375

Please sign in to comment.