【実践ガイド】HCP Terraform で S3 Private Module を作成する手順

## 概要

HCP Terraform（旧 Terraform Cloud）の Private Module Registry を使用することで、組織内で再利用可能な Terraform モジュールを安全に管理・共有できます。
本記事では、S3 バケットを構築する Private Module の作成に必要な対応の概要を、実際に動作するコードを用いて解説します。

## Private Module とは

Private Module は HCP Terraform の Organization 内でのみ使用できる Terraform モジュールです。
Public Registry のモジュールとは異なり、企業固有の要件やセキュリティポリシーを組み込んだカスタムモジュールを作成・管理できます。

### Public Registry との違い

- **アクセス制御**: 組織メンバーのみがアクセス可能
- **カスタマイズ性**: 企業固有の要件に完全対応
- **セキュリティ**: 内部情報を含むモジュールも安全に管理
- **ガバナンス**: 組織のベストプラクティスを強制可能

## 前提条件

- HCP Terraform アカウント
- Terraform CLI の基本知識
- AWS アカウントと IAM 権限

## GitHub リポジトリの用意

リポジトリの命名規則は下記に記載があります。

https://developer.hashicorp.com/terraform/cloud-docs/registry/publish-modules#preparing-a-module-repository

> Named terraform-\<PROVIDER\>-\<NAME\>

リポジトリ名の先頭が "terraform-" になっていないと、Terraform モジュールとして認識されないので注意が必要です。
今回は AWS Provider を使用するので、`<PROVIDER>` は "aws" にするのが適切です。
`<NAME>` は任意ですが、プライベートな S3 バケット用のモジュールなので "s3-private" にします。

## S3 Private Module の設計

### モジュール構成

```
terraform-aws-s3-private/
├── main.tf               # メインリソース定義
├── variables.tf          # 入力変数
├── outputs.tf            # 出力値
└── versions.tf           # プロバイダー要件
```

### variables.tf の実装

```hcl
variable "bucket_name" {
  description = "S3バケット名"
  type        = string

  validation {
    condition     = can(regex("^[a-z0-9][a-z0-9-]*[a-z0-9]$", var.bucket_name))
    error_message = "バケット名は小文字、数字、ハイフンのみ使用可能です。"
  }
}

variable "environment" {
  description = "環境名（dev, stg, prod）"
  type        = string

  validation {
    condition     = contains(["dev", "stg", "prod"], var.environment)
    error_message = "環境名は dev, stg, prod のいずれかを指定してください。"
  }
}

variable "tags" {
  description = "リソースタグ"
  type        = map(string)
  default     = {}
}
```

### main.tf の実装

```hcl
# S3バケット
resource "aws_s3_bucket" "this" {
  bucket = var.bucket_name

  tags = merge(
    var.tags,
    {
      Environment = var.environment
      ManagedBy   = "terraform"
      Module      = "terraform-aws-s3-private"
    }
  )
}

# パブリックアクセスブロック（セキュリティのため強制適用）
resource "aws_s3_bucket_public_access_block" "this" {
  bucket = aws_s3_bucket.this.id

  block_public_acls       = true
  block_public_policy     = true
  ignore_public_acls      = true
  restrict_public_buckets = true
}
```

このシンプルな構成では、S3 バケットの基本的な作成とセキュリティ強化のためのパブリックアクセスブロックのみを含めています。
バージョニングや暗号化などの追加機能は、必要に応じてモジュールを拡張する際に検討できます。

### outputs.tf の実装

```hcl
output "bucket_id" {
  description = "S3バケットID"
  value       = aws_s3_bucket.this.id
}

output "bucket_arn" {
  description = "S3バケットARN"
  value       = aws_s3_bucket.this.arn
}

output "bucket_domain_name" {
  description = "S3バケットドメイン名"
  value       = aws_s3_bucket.this.bucket_domain_name
}
```

### versions.tf の実装

```hcl
terraform {
  required_version = ">= 1.0"

  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
    }
  }
}
```

## HCP Terraform での Private Module 登録

### 1. Git リポジトリの準備

1. 上記のモジュールコードを GitHub リポジトリに配置
2. セマンティックバージョニングでタグを作成（例：v1.0.0）

### 2. HCP Terraform での設定

1. HCP Terraform コンソールにログイン
2. 「Registry」→「Modules」→「Publish」→「Module」を選択
3. VCS プロバイダーを選択（GitHub）
4. リポジトリを選択
5. publishing type に「Tag」を指定

### 3. モジュールの公開

- Git リポジトリにタグをプッシュすると自動的に HCP Terraform に同期
- バージョン管理が自動化される

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3769407/036a7d53-6554-4715-8ef2-07840f7e370b.png)


## Private Module の使用方法

### Terraform 設定での利用

```hcl
provider "aws" {
  region = "ap-northeast-1"
}

terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
    }
  }
}

variable "environment" {
  description = "Environment for the deployment (e.g., dev, staging, prod)"
  type        = string
  default     = "dev"
}

module "application_bucket" {
  source  = "app.terraform.io/your-org/s3-private/aws"
  version = "1.0.0"

  bucket_name = "myapp-${var.environment}-data"
  environment = var.environment

  tags = {
    Application = "myapp"
    Owner       = "platform-team"
  }
}
```

`your-org` はご利用の HCP Terraform の Organization 名に置き換えてください。
`bucket_name` 引数も実際に構築する場合はグローバルに重複しないよう調整が必要になります。

ローカルで `terraform init` する際は、対象の Organization に対して `terraform login` してください。
ログインしていない場合は Private Registry の参照ができず、下記のようなエラーが出ます。

```console
> terraform init -backend=false
Initializing modules...
╷
│ Error: Error accessing remote module registry
│
│   on main.tf line 15:
│    1: module "application_bucket" {
│
│ Failed to retrieve available versions for module "application_bucket" (main.tf:15) from app.terraform.io: error looking up module versions: 401 Unauthorized.
```

`terraform login` できていれば、下記のように `terraform plan` の実行が確認できるでしょう。

```console
> terraform plan

Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
  + create

Terraform will perform the following actions:

  # module.application_bucket.aws_s3_bucket.this will be created
  + resource "aws_s3_bucket" "this" {
      + acceleration_status         = (known after apply)
      + acl                         = (known after apply)
      + arn                         = (known after apply)
      + bucket                      = "myapp-dev-data"
      + bucket_domain_name          = (known after apply)
      + bucket_prefix               = (known after apply)
      + bucket_regional_domain_name = (known after apply)
      + force_destroy               = false
      + hosted_zone_id              = (known after apply)
      + id                          = (known after apply)
      + object_lock_enabled         = (known after apply)
      + policy                      = (known after apply)
      + region                      = (known after apply)
      + request_payer               = (known after apply)
      + tags                        = {
          + "Application" = "myapp"
          + "Environment" = "dev"
          + "ManagedBy"   = "terraform"
          + "Module"      = "terraform-aws-s3-private"
          + "Owner"       = "platform-team"
        }
      + tags_all                    = {
          + "Application" = "myapp"
          + "Environment" = "dev"
          + "ManagedBy"   = "terraform"
          + "Module"      = "terraform-aws-s3-private"
          + "Owner"       = "platform-team"
        }
      + website_domain              = (known after apply)
      + website_endpoint            = (known after apply)

      + cors_rule (known after apply)

      + grant (known after apply)

      + lifecycle_rule (known after apply)

      + logging (known after apply)

      + object_lock_configuration (known after apply)

      + replication_configuration (known after apply)

      + server_side_encryption_configuration (known after apply)

      + versioning (known after apply)

      + website (known after apply)
    }

  # module.application_bucket.aws_s3_bucket_public_access_block.this will be created
  + resource "aws_s3_bucket_public_access_block" "this" {
      + block_public_acls       = true
      + block_public_policy     = true
      + bucket                  = (known after apply)
      + id                      = (known after apply)
      + ignore_public_acls      = true
      + restrict_public_buckets = true
    }

Plan: 2 to add, 0 to change, 0 to destroy.

───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────

Note: You didn't use the -out option to save this plan, so Terraform can't guarantee to take exactly these actions if you run "terraform apply" now.
```

また、モジュール側の variable に validation ブロックの指定があるので、例えば不正なバケット名を指定すればエラーにしてくれます。

```console
> terraform plan   

Planning failed. Terraform encountered an error while generating this plan.

╷
│ Error: Invalid value for variable
│
│   on main.tf line 18, in module "application_bucket":
│   18:   bucket_name = "myapp-${var.environment}-@data"
│     ├────────────────
│     │ var.bucket_name is "myapp-dev-@data"
│
│ バケット名は小文字、数字、ハイフンのみ使用可能です。
│
│ This was checked by the validation rule at .terraform\modules\application_bucket\variables.tf:4,3-13.
```

## まとめ

HCP Terraform の Private Module を活用することで、組織内での Terraform コードの再利用性と保守性を大幅に向上させることができます。
本記事で紹介した S3 モジュールを参考に、組織固有の要件に合わせたモジュールを作成し、インフラストラクチャのコード化を推進してください。