From 05498e00b5b2ef07552354b34761f0e0d187710c Mon Sep 17 00:00:00 2001 From: Oliver Jarosch Date: Thu, 13 Aug 2026 09:49:10 +0200 Subject: [PATCH 01/12] feat: upgrade Terraform modules to v2 --- CHANGELOG.md | 14 + README.md | 348 ++---------------- aws_oidc/README.md | 15 + aws_oidc/main.tf | 19 +- aws_oidc/output.tf | 8 +- aws_oidc/provider.tf | 14 + aws_oidc/variables.tf | 45 ++- aws_rbac/README.md | 15 + aws_rbac/main.tf | 15 +- aws_rbac/outputs.tf | 4 + aws_rbac/provider.tf | 4 +- aws_rbac/variables.tf | 42 ++- aws_static/README.md | 13 + aws_static/main.tf | 8 +- aws_static/provider.tf | 6 +- aws_static/variables.tf | 9 +- azure_oidc/README.md | 17 + azure_oidc/main.tf | 14 +- azure_oidc/outputs.tf | 19 + azure_oidc/provider.tf | 8 +- azure_oidc/variables.tf | 63 +++- azure_static/README.md | 16 + azure_static/main.tf | 30 +- azure_static/provider.tf | 8 +- azure_static/variables.tf | 56 ++- gcp_oidc/README.md | 21 ++ gcp_oidc/main.tf | 85 +++-- gcp_oidc/outputs.tf | 14 + gcp_oidc/provider.tf | 8 +- gcp_oidc/variables.tf | 81 +++- main.tf | 74 ++-- provider.tf | 21 +- stackguardian_connector_cloud/README.md | 15 + stackguardian_connector_cloud/main.tf | 152 ++------ stackguardian_connector_cloud/outputs.tf | 18 +- stackguardian_connector_cloud/provider.tf | 6 +- stackguardian_connector_cloud/variables.tf | 117 +++--- stackguardian_connector_vcs/README.md | 18 + stackguardian_connector_vcs/main.tf | 41 +-- stackguardian_connector_vcs/outputs.tf | 4 +- stackguardian_connector_vcs/provider.tf | 6 +- stackguardian_connector_vcs/variables.tf | 72 ++-- stackguardian_role/README.md | 17 + stackguardian_role/main.tf | 3 +- stackguardian_role/provider.tf | 6 +- .../team_onboarding_permissions.tf | 60 +-- stackguardian_role/variables.tf | 35 +- stackguardian_role_assignment/README.md | 14 + stackguardian_role_assignment/main.tf | 6 +- stackguardian_role_assignment/outputs.tf | 6 +- stackguardian_role_assignment/provider.tf | 6 +- stackguardian_role_assignment/variables.tf | 32 +- stackguardian_workflow_group/README.md | 12 + stackguardian_workflow_group/provider.tf | 6 +- stackguardian_workflow_group/variables.tf | 15 +- terraform.tfvars.example | 154 ++++---- variables.tf | 225 ++++++----- 57 files changed, 1085 insertions(+), 1075 deletions(-) create mode 100644 aws_oidc/README.md create mode 100644 aws_oidc/provider.tf create mode 100644 aws_rbac/README.md create mode 100644 aws_rbac/outputs.tf create mode 100644 aws_static/README.md create mode 100644 azure_oidc/README.md create mode 100644 azure_oidc/outputs.tf create mode 100644 azure_static/README.md create mode 100644 gcp_oidc/README.md create mode 100644 gcp_oidc/outputs.tf create mode 100644 stackguardian_connector_cloud/README.md create mode 100644 stackguardian_connector_vcs/README.md create mode 100644 stackguardian_role/README.md create mode 100644 stackguardian_role_assignment/README.md create mode 100644 stackguardian_workflow_group/README.md diff --git a/CHANGELOG.md b/CHANGELOG.md index 80747ff..e3a86d6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [2.0.0] - 2026-08-12 + +### Breaking +- Pin Terraform Core to `1.5.7` and upgrade bounded provider ranges: StackGuardian 1.12, AWS 6, AzureRM 5, AzureAD 3.9, and Google 7. +- Replace legacy camelCase/hyphenated inputs, untyped connector objects, root static credential duplicates, and leaf StackGuardian API credential inputs with v2 flat snake_case contracts. +- Migrate roles to `stackguardian_rolev4` and assignments to `roles = [role_name]`. Existing role state requires the documented state remove/import procedure. +- Replace global AWS policy attachments and authoritative GCP service-account IAM policy management with narrowly scoped attachments/members. Existing state needs the documented import procedure before apply. + +### Changed +- Correct Azure connector settings to the provider's `arm_tenant_id`, `arm_subscription_id`, `arm_client_id`, and `arm_client_secret` schema. +- Use production StackGuardian OIDC issuer/audience defaults for AWS, Azure, and GCP; expose their configuration inputs. +- Retain configurable permissive defaults: AWS `ReadOnlyAccess`, Azure subscription `Contributor`, and GCP `roles/owner`. These defaults are documented as high-risk where applicable. +- Add validation, sensitive flags, finite Azure secret lifetime, typed VCS credentials, typed cloud connectors, and GCP same-type state moves. + ### Added - Comprehensive README.md with detailed documentation - Input validation for variables diff --git a/README.md b/README.md index 9cfaa69..fd792d1 100644 --- a/README.md +++ b/README.md @@ -1,327 +1,59 @@ -# StackGuardian Terraform Modules +# StackGuardian Terraform Modules v2 -A comprehensive collection of Terraform modules for onboarding and managing StackGuardian platform resources. This repository provides everything you need to set up team access, cloud connectors, workflow groups, and role-based access control (RBAC) for your StackGuardian organization. +Terraform modules for StackGuardian onboarding and cloud identity configuration. The root module creates workflow groups, cloud/VCS connectors, a role, and one assignment. `stackguardian_private_runner/` is explicitly outside the v2 upgrade scope. -## 🚀 Overview +## Prerequisites -StackGuardian is a cloud infrastructure management platform that helps organizations manage their Infrastructure as Code (IaC) deployments across multiple cloud providers. This Terraform module collection automates the setup of: +- Terraform **1.5.7** exactly. +- StackGuardian provider `>= 1.12.0, < 2.0.0`. +- AWS provider `>= 6.58.0, < 7.0.0`, AzureRM `>= 5.0.1, < 6.0.0`, AzureAD `>= 3.9.0, < 4.0.0`, and Google `>= 7.44.0, < 8.0.0` in their applicable modules. +- Credentials authorized to create the selected cloud identities. Azure management needs Microsoft Graph application-management and subscription role-assignment privileges. -- **Workflow Groups** - Organize deployments by environment (Dev, Test, Staging, Prod) -- **Cloud Connectors** - Secure connections to AWS, Azure, and GCP -- **VCS Connectors** - Integration with GitHub, GitLab, and Bitbucket -- **Roles & Permissions** - Custom roles with granular permissions -- **User/Group Management** - Assign roles to users and groups -- **OIDC Setup** - Optional OpenID Connect provider configuration +Use `terraform.tfvars.example` as a schema reference. Put secret values in `TF_VAR_*` variables or a secret manager, not version control. Static cloud credentials, generated AWS keys, and generated Azure secrets remain in Terraform state and plan artifacts, so secure both accordingly. -## 📋 Prerequisites +## V2 Inputs -- [Terraform](https://www.terraform.io/downloads.html) >= 1.0 -- StackGuardian account with API access -- Cloud provider accounts (AWS/Azure/GCP) if using cloud connectors -- VCS provider access tokens (GitHub/GitLab/Bitbucket) if using VCS connectors +| v1 input | v2 input | +| --- | --- | +| `api_key` | `stackguardian_api_key` | +| `org_name` | `stackguardian_org_name` | +| `user_or_group` | `subject` | +| `connector_type` | `kind` in `cloud_connectors` | +| `cloud_connector_name` | `name` in `cloud_connectors` | +| `aws_default_region` | `aws_region` | +| `armTenantId`, `armSubscriptionId`, `armClientId`, `armClientSecret` | `azure_tenant_id`, `azure_subscription_id`, `azure_client_id`, `azure_client_secret` | +| `role_arn`, `role_external_id` | `aws_role_arn`, `aws_external_id` | +| `aws_oidc.region`, `role_name`, `account_number`, `aws_policy` | `aws_region`, `iam_role_name`, `aws_account_id`, `policy_arn` | +| `aws_rbac.aws_role_name`, `aws_policy` | `iam_role_name`, `policy_arn` | +| `azure_static.AD_name` | `application_display_name` | +| `azure_oidc.sg_org_name` | `stackguardian_org_name` | +| `gcp_oidc.project`, `sg-org-id` | `project_id`, `stackguardian_org_id` | -## 🏗️ Module Architecture +Legacy aliases are intentionally unavailable. The root configures StackGuardian once; standalone StackGuardian leaf modules inherit provider configuration from their caller and do not accept API credentials. -``` -terraform-stackguardian-modules/ -├── main.tf # Root module orchestration -├── variables.tf # Input variables -├── provider.tf # Provider configurations -├── terraform.tfvars # Example configuration -├── stackguardian_workflow_group/ # Workflow group module -├── stackguardian_connector_cloud/ # Cloud connector module -├── stackguardian_connector_vcs/ # VCS connector module -├── stackguardian_role/ # Role management module -├── stackguardian_role_assignment/ # Role assignment module -├── aws_oidc/ # AWS OIDC setup module -├── aws_rbac/ # AWS RBAC setup module -├── azure_oidc/ # Azure OIDC setup module -└── gcp_oidc/ # GCP OIDC setup module -``` - -## 🚀 Quick Start +## Permissions And Defaults -### 1. Clone the Repository - -```bash -git clone -cd terraform-stackguardian-modules -``` +- `aws_static` needs IAM user/key permissions and stores a generated static key in state. +- `aws_rbac` and `aws_oidc` need IAM role/policy/OIDC permissions. `policy_arn` defaults to `ReadOnlyAccess`; override it for least privilege. RBAC keeps the two historical trusted StackGuardian accounts by default. +- `azure_static` and `azure_oidc` create an Entra application and assign `Contributor` at subscription scope by default. This is high privilege; use `role_definition_name` to reduce it. Static passwords expire after `8760h` by default. +- `gcp_oidc` needs service-account, workload-identity, and project IAM permissions. `project_role` defaults to high-privilege `roles/owner`; override it for production. Validate the configured issuer, audience, and exact `/orgs/` subject against a real StackGuardian token before applying. +- Cloud and VCS connector modules require access to create StackGuardian connectors. They reject missing, mismatched, or conflicting credentials. +- Role, assignment, and workflow-group modules require StackGuardian role-management permission. -### 2. Configure Variables +## Role v4 State Migration -Copy the example configuration and customize it for your organization: +The role resource changes from `stackguardian_role` to `stackguardian_rolev4`; this cannot use a `moved` block or `terraform state mv`. During a maintenance window with exclusive backend locking, back up state and record the existing permissions, then run: ```bash -cp terraform.tfvars.example terraform.tfvars -``` - -Edit `terraform.tfvars` with your StackGuardian credentials and desired configuration: - -```hcl -# StackGuardian Platform Credentials -api_key = "sgu-your-api-key-here" -org_name = "your-org-name" - -# Workflow Groups (environments) -workflow_groups = ["TeamX-Dev", "TeamX-Test", "TeamX-Staging", "TeamX-Prod"] - -# Cloud Connectors -cloud_connectors = [{ - name = "aws-connector-1" - connector_type = "AWS_RBAC" - role_arn = "arn:aws:iam::123456789012:role/StackGuardianRole" - aws_role_external_id = "your-org:random-string" -}] - -# VCS Connectors -vcs_connectors = { - vcs_github = { - kind = "GITHUB_COM" - name = "github-connector" - config = [{ - github_creds = { - githubCreds = "username:personal_access_token" - github_com_url = "https://api.github.com" - github_http_url = "https://github.com" - } - }] - } -} - -# Role Configuration -role_name = "TeamX-Role" -template_list = ["opentofu-aws-vpc"] - -# User Assignment -user_or_group = "user@example.com" -entity_type = "EMAIL" -``` - -### 3. Initialize and Apply - -```bash -# Initialize Terraform -terraform init - -# Plan the deployment +terraform state rm 'module.stackguardian_role.stackguardian_role.role' +terraform import 'module.stackguardian_role.stackguardian_rolev4.role' '' terraform plan - -# Apply the configuration -terraform apply ``` -## 📚 Module Documentation - -### Core Modules - -#### `stackguardian_workflow_group` -Creates workflow groups for organizing deployments by environment. - -**Inputs:** -- `workflow_group_name` - Name of the workflow group -- `api_key` - StackGuardian API key -- `org_name` - StackGuardian organization name - -**Outputs:** -- `workflow_groups` - Created workflow group name - -#### `stackguardian_connector_cloud` -Sets up cloud provider connectors with various authentication methods. - -**Supported Connector Types:** -- `AWS_STATIC` - AWS access key/secret -- `AWS_RBAC` - AWS role with external ID -- `AWS_OIDC` - AWS role with OIDC -- `AZURE_STATIC` - Azure service principal -- `AZURE_OIDC` - Azure with OIDC -- `GCP_STATIC` - GCP service account - -**Key Inputs:** -- `cloud_connector_name` - Name of the connector -- `connector_type` - Type of connector (see above) -- `role_arn` - AWS role ARN (for AWS connectors) -- `role_external_id` - External ID for AWS RBAC - -#### `stackguardian_connector_vcs` -Integrates with version control systems. - -**Supported VCS Types:** -- `GITHUB_COM` - GitHub.com -- `GITLAB_COM` - GitLab.com -- `BITBUCKET_ORG` - Bitbucket.org - -#### `stackguardian_role` -Creates custom roles with specific permissions. - -**Key Inputs:** -- `role_name` - Name of the role -- `cloud_connectors` - List of accessible cloud connectors -- `vcs_connectors` - List of accessible VCS connectors -- `workflow_groups` - List of accessible workflow groups -- `template_list` - List of accessible templates - -#### `stackguardian_role_assignment` -Assigns roles to users or groups. - -**Key Inputs:** -- `user_or_group` - User email or group identifier -- `entity_type` - Either "EMAIL" or "GROUP" -- `role_name` - Role to assign - -### Cloud Setup Modules - -#### `aws_oidc` -Creates AWS IAM OIDC provider and role for StackGuardian. - -#### `aws_rbac` -Sets up AWS IAM role with external ID for RBAC authentication. - -#### `azure_oidc` -Configures Azure AD application and service principal for OIDC. - -#### `gcp_oidc` -Sets up GCP workload identity federation for OIDC authentication. - -## 🔧 Configuration Examples - -### Multi-Environment Setup - -```hcl -workflow_groups = [ - "frontend-dev", - "frontend-staging", - "frontend-prod", - "backend-dev", - "backend-staging", - "backend-prod" -] -``` - -### Multiple Cloud Connectors - -```hcl -cloud_connectors = [ - { - name = "aws-dev" - connector_type = "AWS_RBAC" - role_arn = "arn:aws:iam::111111111111:role/StackGuardian-Dev" - aws_role_external_id = "myorg:dev-12345" - }, - { - name = "aws-prod" - connector_type = "AWS_RBAC" - role_arn = "arn:aws:iam::222222222222:role/StackGuardian-Prod" - aws_role_external_id = "myorg:prod-67890" - } -] -``` - -### Multiple VCS Connectors - -```hcl -vcs_connectors = { - vcs_github = { - kind = "GITHUB_COM" - name = "github-main" - config = [{ - github_creds = { - githubCreds = "username:personal_access_token" - github_com_url = "https://api.github.com" - github_http_url = "https://github.com" - } - }] - }, - vcs_gitlab = { - kind = "GITLAB_COM" - name = "gitlab-secondary" - config = [{ - gitlab_creds = { - gitlabCreds = "username:personal_access_token" - gitlabHttpUrl = "https://gitlab.com" - gitlabApiUrl = "https://gitlab.com/api/v4" - } - }] - } -} -``` - -## 🔐 Security Best Practices - -### API Key Management -- Store API keys in environment variables or secure secret management systems -- Never commit API keys to version control -- Use different API keys for different environments - -### Cloud Connector Security -- Use RBAC or OIDC instead of static credentials when possible -- Follow principle of least privilege for IAM roles -- Regularly rotate access keys and external IDs -- Use separate AWS accounts/Azure subscriptions for different environments - -### VCS Integration -- Use personal access tokens with minimal required scopes -- Regularly rotate VCS tokens -- Consider using organization-level tokens for team access - -## 🚨 Troubleshooting - -### Common Issues - -**Provider Authentication Errors** -```bash -Error: Invalid API key or organization name -``` -- Verify your `api_key` and `org_name` in terraform.tfvars -- Ensure the API key has sufficient permissions - -**Cloud Connector Failures** -```bash -Error: Unable to assume role -``` -- Check that the role ARN is correct -- Verify the external ID matches your StackGuardian organization -- Ensure the role trust policy allows StackGuardian to assume it - -**VCS Connector Issues** -```bash -Error: Invalid VCS credentials -``` -- Verify your VCS credentials format -- Check that tokens have required permissions -- Ensure URLs are correct for your VCS provider - -### Debug Mode -Enable Terraform debug logging: -```bash -export TF_LOG=DEBUG -terraform apply -``` - -## 🤝 Contributing - -1. Fork the repository -2. Create a feature branch (`git checkout -b feature/amazing-feature`) -3. Commit your changes (`git commit -m 'Add amazing feature'`) -4. Push to the branch (`git push origin feature/amazing-feature`) -5. Open a Pull Request - -## 📄 License - -This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details. - -## 🆘 Support - -- [StackGuardian Documentation](https://docs.stackguardian.io/) -- [StackGuardian Community](https://community.stackguardian.io/) -- [Terraform Provider Documentation](https://registry.terraform.io/providers/StackGuardian/stackguardian/latest/docs) - -## 🏷️ Version Compatibility +For standalone role usage, omit `module.stackguardian_role.`. Review the v4 `allowed_permissions`, apply the reviewed update, then run a second plan and test an allowed and denied path. Do not use `state mv`; roll back only by restoring the backed-up state/configuration. -| Module Version | StackGuardian Provider | Terraform Version | -|---------------|----------------------|------------------| -| 1.x.x | 1.1.0-rc5 | >= 1.0 | +Before replacing the former authoritative GCP IAM policy, add and import `google_service_account_iam_member.self_workload_identity`, then remove only the old policy state binding. Likewise, import the existing AWS role-policy attachment when converting from the legacy global attachment. Export existing IAM bindings first and verify the plan cannot remove unrelated principals. ---- +## Module Usage -**Made with ❤️ by the StackGuardian Community** +Each module has a short usage and outputs reference in its directory README. Run `terraform init -upgrade`, `terraform validate`, and a reviewed plan from the specific module directory. Lock files are deliberately not committed because callers initialize independently. diff --git a/aws_oidc/README.md b/aws_oidc/README.md new file mode 100644 index 0000000..bab0f43 --- /dev/null +++ b/aws_oidc/README.md @@ -0,0 +1,15 @@ +# AWS OIDC Identity + +Creates an AWS IAM OIDC provider, IAM role, and role-policy attachment. The caller needs IAM OIDC, role, and policy-attachment permissions. `policy_arn` defaults to `ReadOnlyAccess` and should be narrowed for production. + +```hcl +module "aws_oidc" { + source = "./aws_oidc" + aws_region = "eu-central-1" + aws_account_id = "123456789012" + iam_role_name = "StackGuardianOidcRole" + stackguardian_org_name = "example-org" +} +``` + +Outputs: `oidc_provider_arn`, `oidc_role_arn`. diff --git a/aws_oidc/main.tf b/aws_oidc/main.tf index d9e0802..2af9edc 100644 --- a/aws_oidc/main.tf +++ b/aws_oidc/main.tf @@ -1,28 +1,28 @@ # Step 1: Create an OpenID Connect provider in AWS IAM resource "aws_iam_openid_connect_provider" "oidc_provider" { - url = "https://api.app.stackguardian.io" # OIDC provider URL - client_id_list = ["https://api.app.stackguardian.io"] # OIDC client ID or the Audience id + url = var.oidc_issuer_url + client_id_list = [var.oidc_audience] thumbprint_list = [] } # Step 2: Create an IAM role that can be assumed by users authenticated through the OIDC provider resource "aws_iam_role" "oidc_role" { - name = var.role_name + name = var.iam_role_name assume_role_policy = jsonencode({ Version = "2012-10-17" Statement = [ { "Effect" : "Allow", "Principal" : { - "Federated" : "arn:aws:iam::${var.account_number}:oidc-provider/api.app.stackguardian.io" + "Federated" : aws_iam_openid_connect_provider.oidc_provider.arn }, "Action" : "sts:AssumeRoleWithWebIdentity", "Condition" : { "StringEquals" : { - "api.app.stackguardian.io:aud" = "https://api.app.stackguardian.io" + "api.app.stackguardian.io:aud" = var.oidc_audience }, "StringLike" : { - "api.app.stackguardian.io:sub" = "/orgs/${var.org_name}" + "api.app.stackguardian.io:sub" = "/orgs/${var.stackguardian_org_name}" } } } @@ -30,8 +30,7 @@ resource "aws_iam_role" "oidc_role" { }) } -resource "aws_iam_policy_attachment" "sg_role_policy" { - name = "${var.role_name}-policy" - policy_arn = var.aws_policy - roles = [aws_iam_role.oidc_role.name] +resource "aws_iam_role_policy_attachment" "sg_role_policy" { + role = aws_iam_role.oidc_role.name + policy_arn = var.policy_arn } diff --git a/aws_oidc/output.tf b/aws_oidc/output.tf index bc69d18..d40d0df 100644 --- a/aws_oidc/output.tf +++ b/aws_oidc/output.tf @@ -1,7 +1,9 @@ output "oidc_provider_arn" { - value = aws_iam_openid_connect_provider.oidc_provider.arn + description = "ARN of the AWS IAM OIDC provider." + value = aws_iam_openid_connect_provider.oidc_provider.arn } output "oidc_role_arn" { - value = aws_iam_role.oidc_role.arn -} \ No newline at end of file + description = "ARN of the AWS IAM role trusted by the OIDC provider." + value = aws_iam_role.oidc_role.arn +} diff --git a/aws_oidc/provider.tf b/aws_oidc/provider.tf new file mode 100644 index 0000000..e450e62 --- /dev/null +++ b/aws_oidc/provider.tf @@ -0,0 +1,14 @@ +terraform { + required_version = "= 1.5.7" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 6.58.0, < 7.0.0" + } + } +} + +provider "aws" { + region = var.aws_region +} diff --git a/aws_oidc/variables.tf b/aws_oidc/variables.tf index 40e6987..b52215a 100644 --- a/aws_oidc/variables.tf +++ b/aws_oidc/variables.tf @@ -1,24 +1,47 @@ -variable "region" { +variable "aws_region" { type = string - description = "the region for deploying the resources" + description = "AWS region used by the provider." + + validation { + condition = can(regex("^[a-z]{2}(-gov)?-[a-z]+-\\d$", var.aws_region)) + error_message = "aws_region must be a valid AWS region." + } } -variable "role_name" { +variable "iam_role_name" { type = string - description = "name of the aws role thats getting created" + description = "Name of the IAM role created for StackGuardian OIDC." } -variable "org_name" { +variable "stackguardian_org_name" { type = string - description = "the name of the StackGuardian Organization" + description = "StackGuardian organization name used in the OIDC subject." +} + +variable "aws_account_id" { + type = string + description = "AWS account ID retained as a validated configuration input." + + validation { + condition = can(regex("^\\d{12}$", var.aws_account_id)) + error_message = "aws_account_id must be a 12-digit AWS account ID." + } } -variable "account_number" { - type = number - description = "the value of the account number" +variable "policy_arn" { + type = string + description = "IAM policy attached to the OIDC role. ReadOnlyAccess is a permissive default." + default = "arn:aws:iam::aws:policy/ReadOnlyAccess" +} + +variable "oidc_issuer_url" { + type = string + description = "StackGuardian OIDC issuer URL." + default = "https://api.app.stackguardian.io" } -variable "aws_policy" { +variable "oidc_audience" { type = string - description = "arn of aws policy" + description = "OIDC audience accepted by the IAM role." + default = "https://api.app.stackguardian.io" } diff --git a/aws_rbac/README.md b/aws_rbac/README.md new file mode 100644 index 0000000..4d29d21 --- /dev/null +++ b/aws_rbac/README.md @@ -0,0 +1,15 @@ +# AWS RBAC Identity + +Creates an IAM role trusted by StackGuardian accounts. The caller needs IAM role and policy-attachment permissions. `policy_arn` defaults to `ReadOnlyAccess`; supply a least-privilege policy for production. + +```hcl +module "aws_rbac" { + source = "./aws_rbac" + iam_role_name = "StackGuardianRole" + role_external_id = "example-org:external-id" +} +``` + +The default `trusted_account_ids` preserves the two historical StackGuardian accounts and is configurable. + +Output: `iam_role_arn`. diff --git a/aws_rbac/main.tf b/aws_rbac/main.tf index e78c416..9441e5e 100644 --- a/aws_rbac/main.tf +++ b/aws_rbac/main.tf @@ -1,5 +1,5 @@ -resource "aws_iam_role" "sg-test-role" { - name = var.aws_role_name +resource "aws_iam_role" "sg_role" { + name = var.iam_role_name description = "StackGuardianIntegrationRole" assume_role_policy = jsonencode({ Version = "2012-10-17" @@ -7,9 +7,7 @@ resource "aws_iam_role" "sg-test-role" { { Effect = "Allow" Principal = { - AWS = ["arn:aws:iam::476299211833:root", - "arn:aws:iam::163602625436:root" - ] + AWS = [for account_id in var.trusted_account_ids : "arn:aws:iam::${account_id}:root"] } Action = "sts:AssumeRole" Condition = { @@ -22,8 +20,7 @@ resource "aws_iam_role" "sg-test-role" { }) } -resource "aws_iam_policy_attachment" "sg_role_policy" { - name = "${var.aws_role_name}-policy" - policy_arn = var.aws_policy - roles = [aws_iam_role.sg-test-role.name] +resource "aws_iam_role_policy_attachment" "sg_role_policy" { + role = aws_iam_role.sg_role.name + policy_arn = var.policy_arn } diff --git a/aws_rbac/outputs.tf b/aws_rbac/outputs.tf new file mode 100644 index 0000000..1da513f --- /dev/null +++ b/aws_rbac/outputs.tf @@ -0,0 +1,4 @@ +output "iam_role_arn" { + description = "ARN of the IAM role trusted by StackGuardian." + value = aws_iam_role.sg_role.arn +} diff --git a/aws_rbac/provider.tf b/aws_rbac/provider.tf index 57f6c66..e450e62 100644 --- a/aws_rbac/provider.tf +++ b/aws_rbac/provider.tf @@ -1,8 +1,10 @@ terraform { + required_version = "= 1.5.7" + required_providers { aws = { source = "hashicorp/aws" - version = "5.72.1" + version = ">= 6.58.0, < 7.0.0" } } } diff --git a/aws_rbac/variables.tf b/aws_rbac/variables.tf index 724dbee..f749f5e 100644 --- a/aws_rbac/variables.tf +++ b/aws_rbac/variables.tf @@ -1,22 +1,48 @@ variable "aws_region" { type = string + description = "AWS region used by the provider." default = "eu-central-1" - description = "AWS region" + + validation { + condition = can(regex("^[a-z]{2}(-gov)?-[a-z]+-\\d$", var.aws_region)) + error_message = "aws_region must be a valid AWS region." + } } -variable "aws_role_name" { +variable "iam_role_name" { type = string - description = "Name of the Role within AWS" + description = "Name of the IAM role created for StackGuardian." } variable "role_external_id" { - type = string - # Example 'my-stackguardian-org:abc12345' - description = "ExternalID for the Role in AWS - needs to start with the StackGuardian Organisation name." + type = string + description = "External ID required by the StackGuardian AWS RBAC connector." + sensitive = true + + validation { + condition = length(trimspace(var.role_external_id)) > 0 + error_message = "role_external_id must not be empty." + } } -variable "aws_policy" { +variable "policy_arn" { type = string - description = "ARN of the AWS Policy to be applied" + description = "IAM policy attached to the StackGuardian role. ReadOnlyAccess is a permissive default." default = "arn:aws:iam::aws:policy/ReadOnlyAccess" + + validation { + condition = can(regex("^arn:aws[a-z-]*:iam::(aws|\\d{12}):policy/.+$", var.policy_arn)) + error_message = "policy_arn must be a valid IAM policy ARN." + } +} + +variable "trusted_account_ids" { + type = list(string) + description = "AWS account IDs trusted to assume the role." + default = ["476299211833", "163602625436"] + + validation { + condition = length(var.trusted_account_ids) > 0 && alltrue([for account_id in var.trusted_account_ids : can(regex("^\\d{12}$", account_id))]) + error_message = "trusted_account_ids must contain one or more 12-digit AWS account IDs." + } } diff --git a/aws_static/README.md b/aws_static/README.md new file mode 100644 index 0000000..aa54d96 --- /dev/null +++ b/aws_static/README.md @@ -0,0 +1,13 @@ +# AWS Static Connector Identity + +Creates an IAM user and access key for an AWS static connector. The caller needs IAM user and access-key permissions; the generated secret is sensitive but remains in Terraform state. + +```hcl +module "aws_static" { + source = "./aws_static" + iam_user = "stackguardian-static" + aws_region = "eu-central-1" +} +``` + +Outputs: `access_key_id`, `secret_access_key` (sensitive). diff --git a/aws_static/main.tf b/aws_static/main.tf index fbf1409..20ab3bc 100644 --- a/aws_static/main.tf +++ b/aws_static/main.tf @@ -7,10 +7,12 @@ resource "aws_iam_access_key" "my_access_key" { } output "access_key_id" { - value = aws_iam_access_key.my_access_key.id + description = "Generated IAM access key ID." + value = aws_iam_access_key.my_access_key.id } output "secret_access_key" { - value = aws_iam_access_key.my_access_key.secret - sensitive = true # This will hide the secret in Terraform outputs + description = "Generated IAM secret access key. It is retained in Terraform state." + value = aws_iam_access_key.my_access_key.secret + sensitive = true # This will hide the secret in Terraform outputs } diff --git a/aws_static/provider.tf b/aws_static/provider.tf index 9d9e325..e450e62 100644 --- a/aws_static/provider.tf +++ b/aws_static/provider.tf @@ -1,12 +1,14 @@ terraform { + required_version = "= 1.5.7" + required_providers { aws = { source = "hashicorp/aws" - version = "5.72.1" + version = ">= 6.58.0, < 7.0.0" } } } provider "aws" { region = var.aws_region -} \ No newline at end of file +} diff --git a/aws_static/variables.tf b/aws_static/variables.tf index 9df860f..5ef17af 100644 --- a/aws_static/variables.tf +++ b/aws_static/variables.tf @@ -2,10 +2,15 @@ variable "aws_region" { type = string default = "eu-central-1" description = "AWS region" + + validation { + condition = can(regex("^[a-z]{2}(-gov)?-[a-z]+-\\d$", var.aws_region)) + error_message = "aws_region must be a valid AWS region." + } } variable "iam_user" { type = string default = "example" - description = "name of the iam user created" -} \ No newline at end of file + description = "Name of the IAM user created. Its generated static secret is retained in Terraform state." +} diff --git a/azure_oidc/README.md b/azure_oidc/README.md new file mode 100644 index 0000000..7d4704d --- /dev/null +++ b/azure_oidc/README.md @@ -0,0 +1,17 @@ +# Azure OIDC Identity + +Creates an Entra application, service principal, subscription assignment, and federated credential. The managing identity needs Microsoft Graph application-management plus subscription role-assignment privileges. `role_definition_name` defaults to high-privilege `Contributor`. + +```hcl +module "azure_oidc" { + source = "./azure_oidc" + subscription_id = "00000000-0000-0000-0000-000000000000" + tenant_id = "00000000-0000-0000-0000-000000000000" + client_id = "00000000-0000-0000-0000-000000000000" + client_secret = var.azure_management_secret + application_display_name = "stackguardian-oidc" + stackguardian_org_name = "example-org" +} +``` + +Outputs: `client_id`, `tenant_id`, `subscription_id`, `federated_credential_id`. diff --git a/azure_oidc/main.tf b/azure_oidc/main.tf index b7817f2..378f725 100644 --- a/azure_oidc/main.tf +++ b/azure_oidc/main.tf @@ -3,7 +3,7 @@ data "azurerm_subscription" "current" {} # Create Azure AD application resource "azuread_application" "app_registration" { - display_name = var.display_name + display_name = var.application_display_name owners = [data.azuread_client_config.current.object_id] } @@ -15,17 +15,17 @@ resource "azuread_service_principal" "sg_sp" { } # Assign Contributor role to the Service Principal at the subscription level -resource "azurerm_role_assignment" "example" { +resource "azurerm_role_assignment" "subscription" { principal_id = azuread_service_principal.sg_sp.object_id - role_definition_name = "Contributor" + role_definition_name = var.role_definition_name scope = data.azurerm_subscription.current.id } # Configure Workload Identity (Federated Credential) resource "azuread_application_federated_identity_credential" "sg_fed_id_creds" { application_id = azuread_application.app_registration.id - display_name = "sg-federated-identity" - audiences = ["https://api.app.stackguardian.io"] - issuer = "https://api.app.stackguardian.io" - subject = "/orgs/${var.sg_org_name}" + display_name = var.federated_credential_name + audiences = var.oidc_audiences + issuer = var.oidc_issuer + subject = coalesce(var.oidc_subject, "/orgs/${var.stackguardian_org_name}") } diff --git a/azure_oidc/outputs.tf b/azure_oidc/outputs.tf new file mode 100644 index 0000000..a7db426 --- /dev/null +++ b/azure_oidc/outputs.tf @@ -0,0 +1,19 @@ +output "client_id" { + description = "Client ID of the created Entra application." + value = azuread_application.app_registration.client_id +} + +output "tenant_id" { + description = "Tenant ID of the created Entra application." + value = var.tenant_id +} + +output "subscription_id" { + description = "Subscription scope of the role assignment." + value = var.subscription_id +} + +output "federated_credential_id" { + description = "ID of the StackGuardian federated identity credential." + value = azuread_application_federated_identity_credential.sg_fed_id_creds.id +} diff --git a/azure_oidc/provider.tf b/azure_oidc/provider.tf index 6b606c5..6eeb5a8 100644 --- a/azure_oidc/provider.tf +++ b/azure_oidc/provider.tf @@ -1,12 +1,14 @@ terraform { + required_version = "= 1.5.7" + required_providers { azurerm = { source = "hashicorp/azurerm" - version = "=4.6.0" + version = ">= 5.0.1, < 6.0.0" } azuread = { source = "hashicorp/azuread" - version = "=3.0.2" + version = ">= 3.9.0, < 4.0.0" } } } @@ -23,4 +25,4 @@ provider "azurerm" { provider "azuread" { tenant_id = var.tenant_id -} \ No newline at end of file +} diff --git a/azure_oidc/variables.tf b/azure_oidc/variables.tf index 97d8a16..09848a5 100644 --- a/azure_oidc/variables.tf +++ b/azure_oidc/variables.tf @@ -1,25 +1,70 @@ variable "subscription_id" { - type = string + type = string + description = "Azure subscription ID where the role assignment is created." + + validation { + condition = can(regex("^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$", var.subscription_id)) + error_message = "subscription_id must be a UUID." + } } variable "client_id" { - type = string + type = string + description = "Client ID Terraform uses to manage Azure resources." } variable "client_secret" { - type = string + type = string + description = "Client secret Terraform uses to manage Azure resources." + sensitive = true + + validation { + condition = length(trimspace(var.client_secret)) > 0 + error_message = "client_secret must not be empty." + } } variable "tenant_id" { - type = string + type = string + description = "Azure tenant ID." } -variable "display_name" { +variable "application_display_name" { type = string - description = "Display name for the Azure AD application" + description = "Display name for the Entra application." } -variable "sg_org_name" { +variable "stackguardian_org_name" { type = string - description = "StackGuardian organization name" -} \ No newline at end of file + description = "StackGuardian organization name used in the default OIDC subject." +} + +variable "role_definition_name" { + type = string + description = "Azure subscription role assigned to the created service principal. Contributor is a high-privilege default." + default = "Contributor" +} + +variable "oidc_issuer" { + type = string + description = "OIDC issuer URI." + default = "https://api.app.stackguardian.io" +} + +variable "oidc_audiences" { + type = list(string) + description = "OIDC audiences accepted by the federated credential." + default = ["https://api.app.stackguardian.io"] +} + +variable "oidc_subject" { + type = string + description = "OIDC subject accepted by the federated credential." + default = null +} + +variable "federated_credential_name" { + type = string + description = "Display name of the federated identity credential." + default = "sg-federated-identity" +} diff --git a/azure_static/README.md b/azure_static/README.md new file mode 100644 index 0000000..0c75044 --- /dev/null +++ b/azure_static/README.md @@ -0,0 +1,16 @@ +# Azure Static Identity + +Creates an Entra application, service principal, finite-lifetime client secret, and subscription assignment. The managing identity needs Microsoft Graph application-management plus subscription role-assignment privileges. `role_definition_name` defaults to high-privilege `Contributor`. + +```hcl +module "azure_static" { + source = "./azure_static" + subscription_id = "00000000-0000-0000-0000-000000000000" + tenant_id = "00000000-0000-0000-0000-000000000000" + client_id = "00000000-0000-0000-0000-000000000000" + client_secret = var.azure_management_secret + application_display_name = "stackguardian-static" +} +``` + +Outputs: `client_id`, `tenant_id`, `subscription_id`, `client_secret_value` (sensitive), `client_secret_id`. diff --git a/azure_static/main.tf b/azure_static/main.tf index e73f66e..af902a7 100644 --- a/azure_static/main.tf +++ b/azure_static/main.tf @@ -3,7 +3,7 @@ data "azurerm_subscription" "current" {} # Create Azure AD application resource "azuread_application" "app_registration" { - display_name = var.AD_name + display_name = var.application_display_name owners = [data.azuread_client_config.current.object_id] } @@ -15,29 +15,43 @@ resource "azuread_service_principal" "sg_sp" { } # Assign Contributor role to the Service Principal at the subscription level -resource "azurerm_role_assignment" "example" { +resource "azurerm_role_assignment" "subscription" { principal_id = azuread_service_principal.sg_sp.object_id - role_definition_name = "Contributor" + role_definition_name = var.role_definition_name scope = data.azurerm_subscription.current.id } # Step 3: Create a Client Secret for the Service Principal resource "azuread_service_principal_password" "client_secret" { service_principal_id = azuread_service_principal.sg_sp.id + end_date_relative = var.service_principal_password_end_date_relative } # Step 4: Output the Client Secret Value (ID will be available in the Service Principal) output "client_secret_value" { - value = azuread_service_principal_password.client_secret.value - sensitive = true + description = "Generated service principal secret. It is stored in Terraform state." + value = azuread_service_principal_password.client_secret.value + sensitive = true } # Step 5: Output the Client ID (Application ID) output "client_id" { - value = azuread_application.app_registration.client_id + description = "Client ID of the created Entra application." + value = azuread_application.app_registration.client_id +} + +output "tenant_id" { + description = "Tenant ID for the created service principal." + value = var.tenant_id +} + +output "subscription_id" { + description = "Subscription ID where the role assignment was created." + value = var.subscription_id } # Step 6: Output the Client Secret ID (from the service principal password) output "client_secret_id" { - value = azuread_service_principal_password.client_secret.id -} \ No newline at end of file + description = "ID of the generated service principal password." + value = azuread_service_principal_password.client_secret.id +} diff --git a/azure_static/provider.tf b/azure_static/provider.tf index 6b606c5..6eeb5a8 100644 --- a/azure_static/provider.tf +++ b/azure_static/provider.tf @@ -1,12 +1,14 @@ terraform { + required_version = "= 1.5.7" + required_providers { azurerm = { source = "hashicorp/azurerm" - version = "=4.6.0" + version = ">= 5.0.1, < 6.0.0" } azuread = { source = "hashicorp/azuread" - version = "=3.0.2" + version = ">= 3.9.0, < 4.0.0" } } } @@ -23,4 +25,4 @@ provider "azurerm" { provider "azuread" { tenant_id = var.tenant_id -} \ No newline at end of file +} diff --git a/azure_static/variables.tf b/azure_static/variables.tf index 7be88ef..ed59f0a 100644 --- a/azure_static/variables.tf +++ b/azure_static/variables.tf @@ -1,20 +1,62 @@ variable "subscription_id" { - type = string + type = string + description = "Azure subscription ID where the Contributor assignment is created." + + validation { + condition = can(regex("^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$", var.subscription_id)) + error_message = "subscription_id must be a UUID." + } } variable "client_id" { - type = string + type = string + description = "Client ID Terraform uses to manage Azure resources." + + validation { + condition = can(regex("^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$", var.client_id)) + error_message = "client_id must be a UUID." + } } variable "client_secret" { - type = string + type = string + description = "Client secret Terraform uses to manage Azure resources." + sensitive = true + + validation { + condition = length(trimspace(var.client_secret)) > 0 + error_message = "client_secret must not be empty." + } } variable "tenant_id" { - type = string + type = string + description = "Azure tenant ID." + + validation { + condition = can(regex("^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$", var.tenant_id)) + error_message = "tenant_id must be a UUID." + } } -variable "AD_name" { +variable "application_display_name" { type = string - description = "name of the azure active directory" -} \ No newline at end of file + description = "Display name for the Entra application." +} + +variable "role_definition_name" { + type = string + description = "Azure subscription role assigned to the created service principal. Contributor is a high-privilege default." + default = "Contributor" +} + +variable "service_principal_password_end_date_relative" { + type = string + description = "Finite lifetime for the generated service principal secret." + default = "8760h" + + validation { + condition = can(regex("^[1-9][0-9]*h$", var.service_principal_password_end_date_relative)) + error_message = "service_principal_password_end_date_relative must be a positive number of hours." + } +} diff --git a/gcp_oidc/README.md b/gcp_oidc/README.md new file mode 100644 index 0000000..fb0af8a --- /dev/null +++ b/gcp_oidc/README.md @@ -0,0 +1,21 @@ +# GCP OIDC Identity + +Creates a service account, workload identity pool/provider, and narrowly scoped IAM members. The caller needs service-account, workload-identity, and project-IAM permissions. `project_role` defaults to high-privilege `roles/owner`; override it for production. + +```hcl +module "gcp_oidc" { + source = "./gcp_oidc" + project_id = "example-project" + region = "europe-west3" + stackguardian_org_id = "example-org-id" + service_account_id = "stackguardian" + workload_identity_pool_id = "stackguardian" + workload_identity_pool_provider_id = "stackguardian-oidc" + workload_identity_pool_display_name = "StackGuardian" + project_role = "roles/viewer" +} +``` + +Validate issuer, audience, and the exact serialized subject with a real token before applying. Import the self-member binding before removing the previous authoritative IAM-policy state. + +Outputs: `service_account_email`, `workload_identity_pool_id`, `workload_identity_pool_provider_id`. diff --git a/gcp_oidc/main.tf b/gcp_oidc/main.tf index f0dfe23..6625eb7 100644 --- a/gcp_oidc/main.tf +++ b/gcp_oidc/main.tf @@ -1,64 +1,71 @@ -#StackGuardian OIDC Connector locals { - sg-org-id = var.sg-org-id + oidc_subject = coalesce(var.oidc_subject, "/orgs/${var.stackguardian_org_id}") } -# Create Service Account -resource "google_service_account" "sg-service-account" { +moved { + from = google_service_account.sg-service-account + to = google_service_account.stackguardian +} + +moved { + from = google_iam_workload_identity_pool.sg-pool + to = google_iam_workload_identity_pool.stackguardian +} + +moved { + from = google_iam_workload_identity_pool_provider.sg-oidc-connector-provider-x + to = google_iam_workload_identity_pool_provider.stackguardian +} + +moved { + from = google_service_account_iam_member.allow_federation_impersonation + to = google_service_account_iam_member.federation_impersonation +} + +moved { + from = google_project_iam_member.sg-service-account-iam + to = google_project_iam_member.stackguardian +} + +resource "google_service_account" "stackguardian" { account_id = var.service_account_id display_name = "StackGuardian Service Account" + description = "Service account used by StackGuardian workload identity federation." } - -resource "google_iam_workload_identity_pool" "sg-pool" { +resource "google_iam_workload_identity_pool" "stackguardian" { workload_identity_pool_id = var.workload_identity_pool_id } -resource "google_iam_workload_identity_pool_provider" "sg-oidc-connector-provider-x" { - workload_identity_pool_id = google_iam_workload_identity_pool.sg-pool.workload_identity_pool_id +resource "google_iam_workload_identity_pool_provider" "stackguardian" { + workload_identity_pool_id = google_iam_workload_identity_pool.stackguardian.workload_identity_pool_id workload_identity_pool_provider_id = var.workload_identity_pool_provider_id display_name = var.workload_identity_pool_display_name - description = "OIDC identity pool provider for StackGuardian Connector" - disabled = false + description = "OIDC identity pool provider for StackGuardian." attribute_mapping = { "google.subject" = "assertion.sub" } oidc { - allowed_audiences = ["https://testapi.qa.stackguardian.io"] # https://api.app.stackguardian.io - issuer_uri = "https://testapi.qa.stackguardian.io" + allowed_audiences = var.oidc_allowed_audiences + issuer_uri = var.oidc_issuer_uri } } - -# Allow Service Account Impersonation via Federation -resource "google_service_account_iam_member" "allow_federation_impersonation" { - service_account_id = google_service_account.sg-service-account.id +resource "google_service_account_iam_member" "federation_impersonation" { + service_account_id = google_service_account.stackguardian.id role = "roles/iam.workloadIdentityUser" - member = "principal://iam.googleapis.com/projects/${google_iam_workload_identity_pool.sg-pool.project}/locations/global/workloadIdentityPools/${google_iam_workload_identity_pool.sg-pool.workload_identity_pool_id}/subject//orgs/${local.sg-org-id}" + member = "principal://iam.googleapis.com/projects/${google_iam_workload_identity_pool.stackguardian.project}/locations/global/workloadIdentityPools/${google_iam_workload_identity_pool.stackguardian.workload_identity_pool_id}/subject/${local.oidc_subject}" } -# Assign Policy for Federated Identity -resource "google_service_account_iam_policy" "federated_identity" { - service_account_id = google_service_account.sg-service-account.name - - policy_data = < 0 + error_message = "stackguardian_org_id must not be empty." + } } -variable "sg-org-id" { - type = string +variable "oidc_subject" { + type = string + description = "Exact StackGuardian OIDC subject accepted by workload identity." + default = null + + validation { + condition = var.oidc_subject == null || can(regex("^/orgs/[^/\\s]+$", var.oidc_subject)) + error_message = "oidc_subject must use the /orgs/ format." + } } variable "service_account_id" { - type = string + type = string + description = "Service account ID." + + validation { + condition = can(regex("^[a-z][a-z0-9-]{4,28}[a-z0-9]$", var.service_account_id)) + error_message = "service_account_id must be 6-30 lowercase letters, digits, or hyphens." + } } variable "workload_identity_pool_id" { - type = string + type = string + description = "Workload identity pool ID." + + validation { + condition = can(regex("^[a-z][a-z0-9-]{3,31}$", var.workload_identity_pool_id)) + error_message = "workload_identity_pool_id must be 4-32 lowercase letters, digits, or hyphens." + } } variable "workload_identity_pool_provider_id" { - type = string + type = string + description = "Workload identity pool provider ID." + + validation { + condition = can(regex("^[a-z][a-z0-9-]{3,31}$", var.workload_identity_pool_provider_id)) + error_message = "workload_identity_pool_provider_id must be 4-32 lowercase letters, digits, or hyphens." + } } variable "workload_identity_pool_display_name" { - type = string -} \ No newline at end of file + type = string + description = "Display name of the workload identity pool provider." +} + +variable "oidc_issuer_uri" { + type = string + description = "StackGuardian OIDC issuer URI." + default = "https://api.app.stackguardian.io" +} + +variable "oidc_allowed_audiences" { + type = list(string) + description = "OIDC audiences allowed by workload identity." + default = ["https://api.app.stackguardian.io"] +} + +variable "project_role" { + type = string + description = "Project role granted to the StackGuardian service account. roles/owner is a high-privilege default." + default = "roles/owner" +} diff --git a/main.tf b/main.tf index 8560b41..b99fe90 100644 --- a/main.tf +++ b/main.tf @@ -1,69 +1,47 @@ -locals { - cloud_connectors_list = [for con in var.cloud_connectors : con.name] -} - -# ################################ -# # Create Stackguardian Workflow Group -# ################################ module "stackguardian_workflow_group" { for_each = toset(var.workflow_groups) source = "./stackguardian_workflow_group" - api_key = var.api_key - org_name = var.org_name - workflow_group_name = each.key + workflow_group_name = each.value } -# ################################ -# # Create Stackguardian cloud connector -# ################################ module "stackguardian_connector_cloud" { - for_each = { for c in var.cloud_connectors : c.name => c } - source = "./stackguardian_connector_cloud" - cloud_connector_name = each.key - connector_type = each.value.connector_type - role_arn = each.value.role_arn - role_external_id = each.value.aws_role_external_id - api_key = var.api_key - org_name = var.org_name + for_each = { + for connector in var.cloud_connectors : nonsensitive(connector.name) => connector + } + source = "./stackguardian_connector_cloud" + connector_name = each.value.name + connector_kind = each.value.kind + aws_access_key_id = try(each.value.aws_access_key_id, null) + aws_secret_access_key = try(each.value.aws_secret_access_key, null) + aws_region = try(each.value.aws_region, null) + azure_tenant_id = try(each.value.azure_tenant_id, null) + azure_subscription_id = try(each.value.azure_subscription_id, null) + azure_client_id = try(each.value.azure_client_id, null) + azure_client_secret = try(each.value.azure_client_secret, null) + aws_role_arn = try(each.value.aws_role_arn, null) + aws_external_id = try(each.value.aws_external_id, null) + gcp_config_file_content = try(each.value.gcp_config_file_content, null) } -################################ -# Create Stackguardian VCS Connector -################################ - - -module "vcs_connector" { +module "stackguardian_connector_vcs" { source = "./stackguardian_connector_vcs" vcs_connectors = var.vcs_connectors - api_key = var.api_key - org_name = var.org_name } - -################################ -# Create Stackguardian Role -################################ module "stackguardian_role" { source = "./stackguardian_role" - api_key = var.api_key - org_name = var.org_name + org_name = var.stackguardian_org_name role_name = var.role_name - cloud_connectors = [for con in var.cloud_connectors : con.name] - vcs_connectors = [for vcs in var.vcs_connectors : vcs.name] + cloud_connectors = [for connector in var.cloud_connectors : connector.name] + vcs_connectors = [for connector in values(var.vcs_connectors) : connector.name] workflow_groups = var.workflow_groups template_list = var.template_list - #depends_on = [ module.stackguardian_workflow_group, module.stackguardian_connector_cloud, module.stackguardian_connector_vcs ] } -# ################################ -# # Create Stackguardian role assignment -# ################################ module "stackguardian_role_assignment" { - source = "./stackguardian_role_assignment" - api_key = var.api_key - org_name = var.org_name - role_name = var.role_name - user_or_group = var.user_or_group - entity_type = var.entity_type - depends_on = [module.stackguardian_role] + source = "./stackguardian_role_assignment" + role_name = var.role_name + subject = var.subject + entity_type = var.entity_type + depends_on = [module.stackguardian_role] } diff --git a/provider.tf b/provider.tf index 8c6aae1..c56a6bd 100644 --- a/provider.tf +++ b/provider.tf @@ -1,26 +1,17 @@ terraform { + required_version = "= 1.5.7" + required_providers { stackguardian = { source = "StackGuardian/stackguardian" - version = "1.3.1" - } - aws = { - source = "hashicorp/aws" - version = "5.84.0" + version = ">= 1.12.0, < 2.0.0" } } } # StackGuardian provider configuration provider "stackguardian" { - api_key = var.api_key - org_name = var.org_name - api_uri = "https://api.app.stackguardian.io" -} - -# AWS provider configuration -/* -provider "aws" { - region = var.region + api_key = var.stackguardian_api_key + org_name = var.stackguardian_org_name + api_uri = var.stackguardian_api_uri } -*/ \ No newline at end of file diff --git a/stackguardian_connector_cloud/README.md b/stackguardian_connector_cloud/README.md new file mode 100644 index 0000000..9032aef --- /dev/null +++ b/stackguardian_connector_cloud/README.md @@ -0,0 +1,15 @@ +# StackGuardian Cloud Connector + +Creates one `AWS_STATIC`, `AWS_RBAC`, `AWS_OIDC`, `AZURE_STATIC`, `AZURE_OIDC`, or `GCP_OIDC` connector. Configure the StackGuardian provider in the calling root. Credential fields are typed, sensitive where secret, and persisted in Terraform state. + +```hcl +module "connector" { + source = "./stackguardian_connector_cloud" + connector_name = "aws-rbac" + connector_kind = "AWS_RBAC" + aws_role_arn = "arn:aws:iam::123456789012:role/StackGuardianRole" + aws_external_id = var.external_id +} +``` + +Outputs: `connector_name`, `connector_kind`, `connector_id`. diff --git a/stackguardian_connector_cloud/main.tf b/stackguardian_connector_cloud/main.tf index 27af7fc..20406ee 100644 --- a/stackguardian_connector_cloud/main.tf +++ b/stackguardian_connector_cloud/main.tf @@ -1,122 +1,48 @@ -check "aws_static_vars" { - assert { - condition = var.connector_type != "AWS_STATIC" || (var.aws_access_key_id != null && var.aws_secret_access_key != null && var.aws_default_region != null) - error_message = "Variables aws_access_key_id, aws_secret_access_key, and aws_default_region must be set when connector_type is AWS_STATIC." - } -} - -resource "stackguardian_connector" "sg_aws_static_connector" { - count = (var.connector_type == "AWS_STATIC") ? 1 : 0 - resource_name = var.cloud_connector_name - description = "Onboarding example of terraform-provider-stackguardian for AWSConnectorCloud" - settings = { - kind = var.connector_type, - config = [{ - aws_access_key_id = var.aws_access_key_id, - aws_secret_access_key = var.aws_secret_access_key, - aws_default_region = var.aws_default_region - }] - } -} - -check "aws_oidc_vars" { - assert { - condition = var.connector_type != "AWS_OIDC" || var.role_arn != null - error_message = "Variable role_arn must be set when connector_type is AWS_OIDC." - } -} - -resource "stackguardian_connector" "sg_aws_oidc_connector" { - count = (var.connector_type == "AWS_OIDC") ? 1 : 0 - resource_name = var.cloud_connector_name - description = "Onboarding an AWS Role with OIDC" - settings = { - kind = var.connector_type, - config = [{ - role_arn = var.role_arn - }] - } -} - -check "aws_rbac_vars" { - assert { - condition = var.connector_type != "AWS_RBAC" || (var.role_arn != null && var.role_external_id != null) - error_message = "Variables role_arn and role_external_id must be set when connector_type is AWS_RBAC." - } -} - -resource "stackguardian_connector" "sg_aws_rbac_connector" { - count = (var.connector_type == "AWS_RBAC") ? 1 : 0 - resource_name = var.cloud_connector_name - description = "Onboarding an AWS Role with RBAC" - settings = { - kind = var.connector_type, - config = [{ - role_arn = var.role_arn - external_id = var.role_external_id - duration_seconds = 3600 - }] - } -} - -check "azure_static_vars" { - assert { - condition = var.connector_type != "AZURE_STATIC" || (var.armTenantId != null && var.armSubscriptionId != null && var.armClientId != null && var.armClientSecret != null) - error_message = "Variables armTenantId, armSubscriptionId, armClientId, and armClientSecret must be set when connector_type is AZURE_STATIC." - } -} - -resource "stackguardian_connector" "sg_azure_static_connector" { - count = (var.connector_type == "AZURE_STATIC") ? 1 : 0 - resource_name = var.cloud_connector_name - description = "Onboarding example of terraform-provider-stackguardian for AzureConnectorCloud" - settings = { - kind = var.connector_type, - config = [{ - armTenantId = var.armTenantId, - armSubscriptionId = var.armSubscriptionId, - armClientId = var.armClientId, - armClientSecret = var.armClientSecret - }] - } -} - -check "azure_oidc_vars" { - assert { - condition = var.connector_type != "AZURE_OIDC" || (var.armTenantId != null && var.armSubscriptionId != null && var.armClientId != null) - error_message = "Variables armTenantId, armSubscriptionId, and armClientId must be set when connector_type is AZURE_OIDC." - } -} - -resource "stackguardian_connector" "sg_azure_oidc_connector" { - count = (var.connector_type == "AZURE_OIDC") ? 1 : 0 - resource_name = var.cloud_connector_name - description = "Onboarding example of terraform-provider-stackguardian for AzureConnectorCloud" - settings = { - kind = var.connector_type, - config = [{ - armTenantId = var.armTenantId, - armSubscriptionId = var.armSubscriptionId, - armClientId = var.armClientId, - }] - } +locals { + azure_identifiers_valid = alltrue([ + for value in [var.azure_tenant_id, var.azure_subscription_id, var.azure_client_id] : + value != null && can(regex("^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$", value)) + ]) } -check "gcp_oidc_vars" { +check "connector_credentials" { assert { - condition = var.connector_type != "GCP_OIDC" || var.gcp_config_file_content != null - error_message = "Variable gcp_config_file_content must be set when connector_type is GCP_OIDC." + condition = ( + (var.connector_kind == "AWS_STATIC" && var.aws_access_key_id != null && var.aws_secret_access_key != null && var.aws_region != null && var.aws_role_arn == null && var.azure_tenant_id == null && var.gcp_config_file_content == null) || + (var.connector_kind == "AWS_RBAC" && var.aws_role_arn != null && var.aws_external_id != null && var.aws_access_key_id == null && var.azure_tenant_id == null && var.gcp_config_file_content == null) || + (var.connector_kind == "AWS_OIDC" && var.aws_role_arn != null && var.aws_access_key_id == null && var.aws_external_id == null && var.azure_tenant_id == null && var.gcp_config_file_content == null) || + (var.connector_kind == "AZURE_STATIC" && local.azure_identifiers_valid && var.azure_client_secret != null && var.aws_access_key_id == null && var.aws_role_arn == null && var.gcp_config_file_content == null) || + (var.connector_kind == "AZURE_OIDC" && local.azure_identifiers_valid && var.azure_client_secret == null && var.aws_access_key_id == null && var.aws_role_arn == null && var.gcp_config_file_content == null) || + (var.connector_kind == "GCP_OIDC" && var.gcp_config_file_content != null && var.aws_access_key_id == null && var.aws_role_arn == null && var.azure_tenant_id == null) + ) + error_message = "Supply only the credential fields required by connector_kind." } } -resource "stackguardian_connector" "sg_gcp_oidc_connector" { - count = (var.connector_type == "GCP_OIDC") ? 1 : 0 - resource_name = var.cloud_connector_name - description = "Onboarding example of terraform-provider-stackguardian for AzureConnectorCloud" +resource "stackguardian_connector" "cloud" { + resource_name = var.connector_name + description = "StackGuardian ${var.connector_kind} cloud connector" settings = { - kind = var.connector_type, - config = [{ - gcp_config_file_content = var.gcp_config_file_content - }] + kind = var.connector_kind + config = [merge( + var.connector_kind == "AWS_STATIC" ? { + aws_access_key_id = var.aws_access_key_id + aws_secret_access_key = var.aws_secret_access_key + aws_default_region = var.aws_region + } : {}, + var.connector_kind == "AWS_RBAC" ? { + role_arn = var.aws_role_arn + external_id = var.aws_external_id + duration_seconds = 3600 + } : {}, + var.connector_kind == "AWS_OIDC" ? { role_arn = var.aws_role_arn } : {}, + contains(["AZURE_STATIC", "AZURE_OIDC"], var.connector_kind) ? { + arm_tenant_id = var.azure_tenant_id + arm_subscription_id = var.azure_subscription_id + arm_client_id = var.azure_client_id + } : {}, + var.connector_kind == "AZURE_STATIC" ? { arm_client_secret = var.azure_client_secret } : {}, + var.connector_kind == "GCP_OIDC" ? { gcp_config_file_content = var.gcp_config_file_content } : {} + )] } } diff --git a/stackguardian_connector_cloud/outputs.tf b/stackguardian_connector_cloud/outputs.tf index 9c8bf74..e51933e 100644 --- a/stackguardian_connector_cloud/outputs.tf +++ b/stackguardian_connector_cloud/outputs.tf @@ -1,8 +1,14 @@ -output "resource_name" { - description = "Cloud Connector Name created" - value = var.cloud_connector_name +output "connector_name" { + description = "Created cloud connector name." + value = stackguardian_connector.cloud.resource_name } -output "connector_type" { - description = "Cloud Connector Type created" - value = var.connector_type + +output "connector_kind" { + description = "Created cloud connector kind." + value = var.connector_kind +} + +output "connector_id" { + description = "Provider-computed cloud connector ID." + value = stackguardian_connector.cloud.id } diff --git a/stackguardian_connector_cloud/provider.tf b/stackguardian_connector_cloud/provider.tf index 65bfd24..b8f877f 100644 --- a/stackguardian_connector_cloud/provider.tf +++ b/stackguardian_connector_cloud/provider.tf @@ -1,8 +1,10 @@ terraform { + required_version = "= 1.5.7" + required_providers { stackguardian = { source = "StackGuardian/stackguardian" - version = "1.3.1" + version = ">= 1.12.0, < 2.0.0" } } -} \ No newline at end of file +} diff --git a/stackguardian_connector_cloud/variables.tf b/stackguardian_connector_cloud/variables.tf index ab92a41..3d5389f 100644 --- a/stackguardian_connector_cloud/variables.tf +++ b/stackguardian_connector_cloud/variables.tf @@ -1,109 +1,94 @@ -variable "api_key" { +variable "connector_name" { type = string - description = "Your organization's API key on the StackGuardian Platform" - sensitive = true -} - -variable "org_name" { - type = string - description = "Your organization name on StackGuardian Platform" -} + description = "Name of the cloud connector." -variable "connector_type" { - type = string - description = "type of connector. You can select anyone of the following AWS_STATIC, AWS_RBAC, AWS_OIDC, AZURE_STATIC, AZURE_OIDC, GCP_OIDC" validation { - condition = contains([ - "AWS_STATIC", - "AWS_OIDC", - "AWS_RBAC", - "AZURE_STATIC", - "AZURE_OIDC", - "GCP_OIDC", - ], var.connector_type) - error_message = "Variable connector_type must be one of AWS_STATIC, AWS_OIDC, AWS_RBAC, AZURE_STATIC, AZURE_OIDC, GCP_OIDC." + condition = can(regex("^[A-Za-z0-9][A-Za-z0-9 _.-]{0,99}$", var.connector_name)) + error_message = "connector_name must be 1-100 characters and start with a letter or number." } } -variable "cloud_connector_name" { +variable "connector_kind" { type = string - description = "Name of the Cloud connector" -} - + description = "Cloud connector kind." -################ -# AWS_STATIC Credentials -################ + validation { + condition = contains(["AWS_STATIC", "AWS_RBAC", "AWS_OIDC", "AZURE_STATIC", "AZURE_OIDC", "GCP_OIDC"], var.connector_kind) + error_message = "connector_kind must be AWS_STATIC, AWS_RBAC, AWS_OIDC, AZURE_STATIC, AZURE_OIDC, or GCP_OIDC." + } +} variable "aws_access_key_id" { type = string - description = "your AWS acoount access key" - default = null # optional + description = "AWS access key ID for an AWS_STATIC connector." + default = null + sensitive = true } variable "aws_secret_access_key" { type = string - description = "your AWS account secret access key" - default = null # optional + description = "AWS secret access key for an AWS_STATIC connector." + default = null sensitive = true } -variable "aws_default_region" { +variable "aws_region" { type = string - description = "any default region you want to set, for all your deployments" - default = null # optional -} + description = "AWS region for an AWS_STATIC connector." + default = null -################ -# AZURE_STATIC Credentials -################ + validation { + condition = var.aws_region == null || can(regex("^[a-z]{2}(-gov)?-[a-z]+-\\d$", var.aws_region)) + error_message = "aws_region must be a valid AWS region." + } +} -variable "armTenantId" { +variable "azure_tenant_id" { type = string - description = "your azure account tenant id" - default = null # optional + description = "Azure tenant ID for an Azure connector." + default = null } -variable "armSubscriptionId" { +variable "azure_subscription_id" { type = string - description = "your azure subscription id" - default = null # optional + description = "Azure subscription ID for an Azure connector." + default = null } -variable "armClientId" { +variable "azure_client_id" { type = string - description = "your azure client id" - default = null # optional + description = "Azure application client ID for an Azure connector." + default = null } -variable "armClientSecret" { +variable "azure_client_secret" { type = string - description = "your azure client secret" - default = null # optional + description = "Azure application secret for an AZURE_STATIC connector." + default = null sensitive = true } -################ -# AWS_OIDC Credentials + AWS_RBAC Credentials -################ -variable "role_arn" { +variable "aws_role_arn" { type = string - description = "arn of the aws oidc role" - default = null # optional + description = "AWS role ARN for AWS_RBAC or AWS_OIDC connectors." + default = null + + validation { + condition = var.aws_role_arn == null || can(regex("^arn:aws[a-z-]*:iam::\\d{12}:role/.+$", var.aws_role_arn)) + error_message = "aws_role_arn must be a valid IAM role ARN." + } } -###### for AWS_RBAC the externalID is also needed -variable "role_external_id" { +variable "aws_external_id" { type = string - description = "external id of the aws rbac role" - default = null # optional; ":" is recommended + description = "External ID for an AWS_RBAC connector." + default = null + sensitive = true } -################ -# GCP_OIDC Credentials + GCP_STATIC Credentials -################ variable "gcp_config_file_content" { type = string - description = "the gco config content gor the connector" - default = null # optional + description = "Google external-account configuration content for a GCP_OIDC connector." + default = null + sensitive = true } diff --git a/stackguardian_connector_vcs/README.md b/stackguardian_connector_vcs/README.md new file mode 100644 index 0000000..7a002eb --- /dev/null +++ b/stackguardian_connector_vcs/README.md @@ -0,0 +1,18 @@ +# StackGuardian VCS Connector + +Creates typed GitHub, GitLab, or Bitbucket connectors. Configure the StackGuardian provider in the calling root. VCS credentials are sensitive and retained in Terraform state. + +```hcl +module "vcs" { + source = "./stackguardian_connector_vcs" + vcs_connectors = { + github = { + name = "github" + kind = "GITHUB_COM" + github = { githubCreds = var.github_credential } + } + } +} +``` + +Output: `connector_vcs`. diff --git a/stackguardian_connector_vcs/main.tf b/stackguardian_connector_vcs/main.tf index 4dcd9b2..a07a27e 100644 --- a/stackguardian_connector_vcs/main.tf +++ b/stackguardian_connector_vcs/main.tf @@ -1,39 +1,16 @@ -# resource "stackguardian_connector" "sg_vcs_connector" { -# resource_name = var.stackguardian_connector_vcs_name -# description = "Onboarding example of terraform-provider-stackguardian for ConnectorVcs" -# settings = { -# kind = var.vcs_kind -# config = [{ -# gitlab_creds = var.gitlab_credentials -# }] -# } -# } - -resource "stackguardian_connector" "sg_vcs_connector" { +resource "stackguardian_connector" "vcs" { for_each = { - for key, value in var.vcs_connectors : - key => value if( - # Check if any credentials are provided for gitlab, github or bitbucket - ( - (lookup(value.config[0], "gitlab_creds", null) != null) || - (lookup(value.config[0], "github_creds", null) != null) || - (lookup(value.config[0], "bitbucket_creds", null) != null) - ) - ) + for key, connector in var.vcs_connectors : nonsensitive(key) => connector } resource_name = each.value.name - description = "Onboarding VCS connector" - + description = "StackGuardian ${each.value.kind} VCS connector" settings = { kind = each.value.kind - config = flatten([ - for config_item in each.value.config : { - # Dynamically handle different connector types and jsonencode here - gitlab_creds = lookup(config_item, "gitlab_creds", null) != null ? jsonencode(lookup(config_item, "gitlab_creds", null)) : null - github_creds = lookup(config_item, "github_creds", null) != null ? jsonencode(lookup(config_item, "github_creds", null)) : null - bitbucket_creds = lookup(config_item, "bitbucket_creds", null) != null ? jsonencode(lookup(config_item, "bitbucket_creds", null)) : null - } - ]) + config = [merge( + each.value.github != null ? { github_creds = jsonencode(each.value.github) } : {}, + each.value.gitlab != null ? { gitlab_creds = jsonencode(each.value.gitlab) } : {}, + each.value.bitbucket != null ? { bitbucket_creds = jsonencode(each.value.bitbucket) } : {} + )] } -} \ No newline at end of file +} diff --git a/stackguardian_connector_vcs/outputs.tf b/stackguardian_connector_vcs/outputs.tf index 802db5d..f9431e7 100644 --- a/stackguardian_connector_vcs/outputs.tf +++ b/stackguardian_connector_vcs/outputs.tf @@ -1,4 +1,4 @@ output "connector_vcs" { description = "Created VCS connector" - value = [for con in var.vcs_connectors : con.name] -} \ No newline at end of file + value = [for connector in values(var.vcs_connectors) : connector.name] +} diff --git a/stackguardian_connector_vcs/provider.tf b/stackguardian_connector_vcs/provider.tf index 65bfd24..b8f877f 100644 --- a/stackguardian_connector_vcs/provider.tf +++ b/stackguardian_connector_vcs/provider.tf @@ -1,8 +1,10 @@ terraform { + required_version = "= 1.5.7" + required_providers { stackguardian = { source = "StackGuardian/stackguardian" - version = "1.3.1" + version = ">= 1.12.0, < 2.0.0" } } -} \ No newline at end of file +} diff --git a/stackguardian_connector_vcs/variables.tf b/stackguardian_connector_vcs/variables.tf index a7c015a..305ec88 100644 --- a/stackguardian_connector_vcs/variables.tf +++ b/stackguardian_connector_vcs/variables.tf @@ -1,46 +1,32 @@ -variable "api_key" { - type = string - description = "API key to authenticate to StackGuardian" -} -variable "org_name" { - type = string - description = "Organisation name in StackGuardian platform" -} - variable "vcs_connectors" { - description = "A map of connectors and their respective configurations" - type = map(any) - default = { - vcs_gitlab = { - kind = "GITLAB_COM" - name = "gitlab-connector" - config = [{ - gitlab_creds = { - gitlabCreds = "gitlabuser:gitlab_pat", - gitlabHttpUrl = "https://gitlab.com", - gitlabApiUrl = "https://gitlab.com/api/v4" - } - }] - }, - vcs_github = { - name = "github-connector" - kind = "GITHUB_COM" - config = [{ - github_creds = { - githubCreds = "username:personal_access_token" - github_com_url = "https://api.github.com" - github_http_url = "https://github.com" - } - }] - }, - vcs_bitbucket = { - name = "bitbucket-connector" - kind = "BITBUCKET_ORG" - config = [{ - bitbucket_creds = { - bitbucket_creds = "" - } - }] - } + description = "Typed VCS connector configuration. Credentials remain in Terraform state." + sensitive = true + type = map(object({ + kind = string + name = string + github = optional(object({ + githubCreds = string + github_com_url = optional(string, "https://api.github.com") + github_http_url = optional(string, "https://github.com") + })) + gitlab = optional(object({ + gitlabCreds = string + gitlabHttpUrl = optional(string, "https://gitlab.com") + gitlabApiUrl = optional(string, "https://gitlab.com/api/v4") + })) + bitbucket = optional(object({ + bitbucket_creds = string + })) + })) + + validation { + condition = alltrue([for connector in values(var.vcs_connectors) : + length(trimspace(connector.name)) > 0 && ( + (connector.kind == "GITHUB_COM" && connector.github != null && connector.gitlab == null && connector.bitbucket == null && length(trimspace(connector.github.githubCreds)) > 0) || + (connector.kind == "GITLAB_COM" && connector.gitlab != null && connector.github == null && connector.bitbucket == null && length(trimspace(connector.gitlab.gitlabCreds)) > 0) || + (connector.kind == "BITBUCKET_ORG" && connector.bitbucket != null && connector.github == null && connector.gitlab == null && length(trimspace(connector.bitbucket.bitbucket_creds)) > 0) + ) + ]) + error_message = "Each VCS connector needs a non-empty, matching GitHub, GitLab, or Bitbucket credential object." } } diff --git a/stackguardian_role/README.md b/stackguardian_role/README.md new file mode 100644 index 0000000..47026c9 --- /dev/null +++ b/stackguardian_role/README.md @@ -0,0 +1,17 @@ +# StackGuardian Role v4 + +Creates a StackGuardian `rolev4` resource using workflow, connector, and template collections. Configure the StackGuardian provider in the calling root. Existing `stackguardian_role` state must be removed and imported at the v4 address; see the root README. + +```hcl +module "role" { + source = "./stackguardian_role" + org_name = "example-org" + role_name = "developer" + workflow_groups = ["engineering"] + cloud_connectors = ["aws-rbac"] + vcs_connectors = ["github"] + template_list = ["terraform-aws-vpc"] +} +``` + +Output: `role`. diff --git a/stackguardian_role/main.tf b/stackguardian_role/main.tf index 388ced1..82dc364 100644 --- a/stackguardian_role/main.tf +++ b/stackguardian_role/main.tf @@ -1,5 +1,4 @@ -resource "stackguardian_role" "role" { - +resource "stackguardian_rolev4" "role" { resource_name = var.role_name description = "Onboarding example of terraform-provider-stackguardian for Role Developer" tags = [ diff --git a/stackguardian_role/provider.tf b/stackguardian_role/provider.tf index 65bfd24..b8f877f 100644 --- a/stackguardian_role/provider.tf +++ b/stackguardian_role/provider.tf @@ -1,8 +1,10 @@ terraform { + required_version = "= 1.5.7" + required_providers { stackguardian = { source = "StackGuardian/stackguardian" - version = "1.3.1" + version = ">= 1.12.0, < 2.0.0" } } -} \ No newline at end of file +} diff --git a/stackguardian_role/team_onboarding_permissions.tf b/stackguardian_role/team_onboarding_permissions.tf index e9d29a5..c144b9d 100644 --- a/stackguardian_role/team_onboarding_permissions.tf +++ b/stackguardian_role/team_onboarding_permissions.tf @@ -3,7 +3,7 @@ locals { "GET/api/v1/orgs/${var.org_name}/wfgrps//wfs//wfruns//wfrunfacts//" : { "name" : "GetWorkflowRunFact", "paths" : { - "" : var.workflow_groups + "" : [join("|", var.workflow_groups)] "" : [ ".*" ], @@ -18,7 +18,7 @@ locals { "POST/api/v1/orgs/${var.org_name}/wfgrps//stacks//wfs//wfruns//resume/" : { "name" : "ResumeStackWorkflowRun", "paths" : { - "" : var.workflow_groups, + "" : [join("|", var.workflow_groups)], "" : [ ".*" ], @@ -33,13 +33,13 @@ locals { "PATCH/api/v1/orgs/${var.org_name}/wfgrps//" : { "name" : "UpdateWorkflowGroup", "paths" : { - "" : var.workflow_groups + "" : [join("|", var.workflow_groups)] } }, "GET/api/v1/orgs/${var.org_name}/wfgrps//stacks//wfs//listall_artifacts/" : { "name" : "ListStackWorkflowArtifacts", "paths" : { - "" : var.workflow_groups, + "" : [join("|", var.workflow_groups)], "" : [ ".*" ], @@ -51,13 +51,13 @@ locals { "POST/api/v1/orgs/${var.org_name}/wfgrps//wfgrps/" : { "name" : "CreateNestedWorkflowGroup", "paths" : { - "" : var.workflow_groups + "" : [join("|", var.workflow_groups)] } }, "POST/api/v1/orgs/${var.org_name}/wfgrps//wfs/" : { "name" : "CreateWorkflow", "paths" : { - "" : var.workflow_groups + "" : [join("|", var.workflow_groups)] } }, "DELETE/api/v1/orgs/${var.org_name}/wfgrps//wfs//wfruns//" : { @@ -66,7 +66,7 @@ locals { "" : [ ".*" ], - "" : var.workflow_groups, + "" : [join("|", var.workflow_groups)], "" : [ ".*" ] @@ -75,7 +75,7 @@ locals { "GET/api/v1/orgs/${var.org_name}/wfgrps//stacks//wfs//wfruns//" : { "name" : "GetStackWorkflowRun", "paths" : { - "" : var.workflow_groups, + "" : [join("|", var.workflow_groups)], "" : [ ".*" ], @@ -90,7 +90,7 @@ locals { "DELETE/api/v1/orgs/${var.org_name}/wfgrps//stacks//" : { "name" : "DeleteStack", "paths" : { - "" : var.workflow_groups, + "" : [join("|", var.workflow_groups)], "" : [ ".*" ] @@ -99,7 +99,7 @@ locals { "PATCH/api/v1/orgs/${var.org_name}/wfgrps//wfs//" : { "name" : "UpdateWorkflow", "paths" : { - "" : var.workflow_groups, + "" : [join("|", var.workflow_groups)], "" : [ ".*" ] @@ -108,7 +108,7 @@ locals { "POST/api/v1/orgs/${var.org_name}/wfgrps//stacks//stackruns/" : { "name" : "CreateStackRun", "paths" : { - "" : var.workflow_groups, + "" : [join("|", var.workflow_groups)], "" : [ ".*" ] @@ -117,7 +117,7 @@ locals { "GET/api/v1/orgs/${var.org_name}/wfgrps//wfs//listall_artifacts/" : { "name" : "ListWorkflowArtifacts", "paths" : { - "" : var.workflow_groups, + "" : [join("|", var.workflow_groups)], "" : [ ".*" ] @@ -126,7 +126,7 @@ locals { "PATCH/api/v1/orgs/${var.org_name}/wfgrps//stacks//wfs//" : { "name" : "UpdateStackWorkflow", "paths" : { - "" : var.workflow_groups, + "" : [join("|", var.workflow_groups)], "" : [ ".*" ], @@ -138,7 +138,7 @@ locals { "GET/api/v1/orgs/${var.org_name}/wfgrps//stacks//wfs//wfruns//wfrunfacts//" : { "name" : "GetStackWorkflowRunFact", "paths" : { - "" : var.workflow_groups, + "" : [join("|", var.workflow_groups)], "" : [ ".*" ], @@ -156,7 +156,7 @@ locals { "GET/api/v1/orgs/${var.org_name}/wfgrps//stacks//wfs//outputs/" : { "name" : "GetStackWorkflowOutputs", "paths" : { - "" : var.workflow_groups, + "" : [join("|", var.workflow_groups)], "" : [ ".*" ], @@ -168,7 +168,7 @@ locals { "GET/api/v1/orgs/${var.org_name}/wfgrps//stacks//wfs//wfruns//logs/" : { "name" : "GetStackWorkflowRunLogs", "paths" : { - "" : var.workflow_groups, + "" : [join("|", var.workflow_groups)], "" : [ ".*" ], @@ -186,7 +186,7 @@ locals { "" : [ ".*" ], - "" : var.workflow_groups + "" : [join("|", var.workflow_groups)] "" : [ ".*" ] @@ -195,7 +195,7 @@ locals { "POST/api/v1/orgs/${var.org_name}/wfgrps//wfs//wfruns/" : { "name" : "CreateWorkflowRun", "paths" : { - "" : var.workflow_groups + "" : [join("|", var.workflow_groups)] "" : [ ".*" ] @@ -204,7 +204,7 @@ locals { "DELETE/api/v1/orgs/${var.org_name}/wfgrps//stacks//wfs//" : { "name" : "DeleteStackWorkflow", "paths" : { - "" : var.workflow_groups + "" : [join("|", var.workflow_groups)] "" : [ ".*" ], @@ -216,7 +216,7 @@ locals { "GET/api/v1/orgs/${var.org_name}/wfgrps//stacks//wfs//" : { "name" : "GetStackWorkflow", "paths" : { - "" : var.workflow_groups + "" : [join("|", var.workflow_groups)] "" : [ ".*" ], @@ -228,7 +228,7 @@ locals { "GET/api/v1/orgs/${var.org_name}/wfgrps//stacks//" : { "name" : "GetStack", "paths" : { - "" : var.workflow_groups + "" : [join("|", var.workflow_groups)] "" : [ ".*" ] @@ -237,7 +237,7 @@ locals { "GET/api/v1/orgs/${var.org_name}/wfgrps//wfs//" : { "name" : "GetWorkflow", "paths" : { - "" : var.workflow_groups + "" : [join("|", var.workflow_groups)] "" : [ ".*" ] @@ -246,19 +246,19 @@ locals { "GET/api/v1/orgs/${var.org_name}/wfgrps//" : { "name" : "GetWorkflowGroup", "paths" : { - "" : var.workflow_groups + "" : [join("|", var.workflow_groups)] } }, "POST/api/v1/orgs/${var.org_name}/wfgrps//stacks/" : { "name" : "CreateStack", "paths" : { - "" : var.workflow_groups + "" : [join("|", var.workflow_groups)] } }, "GET/api/v1/orgs/${var.org_name}/wfgrps//wfs//outputs/" : { "name" : "GetWorkflowOutputs", "paths" : { - "" : var.workflow_groups + "" : [join("|", var.workflow_groups)] "" : [ ".*" ] @@ -267,7 +267,7 @@ locals { "DELETE/api/v1/orgs/${var.org_name}/wfgrps//wfs//" : { "name" : "DeleteWorkflow", "paths" : { - "" : var.workflow_groups + "" : [join("|", var.workflow_groups)] "" : [ ".*" ] @@ -279,7 +279,7 @@ locals { "" : [ ".*" ], - "" : var.workflow_groups + "" : [join("|", var.workflow_groups)] "" : [ ".*" ] @@ -291,7 +291,7 @@ locals { "" : [ ".*" ], - "" : var.workflow_groups + "" : [join("|", var.workflow_groups)] "" : [ ".*" ] @@ -309,7 +309,7 @@ locals { "" : [ ".*" ], - "