Skip to content

microsoftexpert/tf-mod-aws-signer

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

2 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

🟧 AWS Signer Terraform Module

A secure-by-default AWS Signer code-signing profile — finite signature validity, explicit (never-wildcard) cross-account grants, and optional Terraform-managed signing jobs, all from one module call. Built for the AWS provider v6.x.

Terraform aws module type resources


🧩 Overview

  • ✍️ Creates an AWS Signer signing profile (aws_signer_signing_profile) — the trusted-publisher configuration used to produce verifiable code/firmware/container-image signatures.
  • Finite signature validity by default ({ value = 1, type = "YEARS" }) — never relies on an unbounded/platform-default validity window.
  • 🎯 Platform selection is validated against a known, documented AWS Signer platform-ID allow-list (Lambda, IoT Device Management, FreeRTOS, container/OCI Notation) so typos and unsupported platforms fail at plan time.
  • 🔏 Optional bring-your-own signing certificate (signing_material) for platforms that require one (e.g. Notation-OCI-SHA384-ECDSA) — wired from tf-mod-aws-acm.
  • 🔐 Optional cross-account permissions (map(object), for_each) — every grant names an explicit AWS account ID or IAM principal ARN; wildcard ("*") principals are rejected outright.
  • 📦 Optional, narrowly-scoped Terraform-managed signing jobs for the rare static/infrastructure-provisioned signing use case — real signing workflows almost always belong in a CI/CD pipeline instead.

💡 Why it matters: code signing is how downstream consumers (Lambda, IoT OTA fleets, container runtimes) verify that an artifact came from a trusted publisher and hasn't been tampered with. This module makes the finite-validity, explicit-principal, never-wildcard posture the path of least resistance for a regulated FI's signing infrastructure.


❤️ Support this project

If these Terraform modules have been helpful to you or your organization, I'd appreciate your support in any of the following ways:

Whether it's a star, a professional connection, or a coffee, every gesture helps keep these modules actively maintained and continually improving. Thank you for being part of the community!


🗺️ Where this fits in the family

flowchart LR
 acm["tf-mod-aws-acm"]
 s3["tf-mod-aws-s3-bucket"]
 iam["tf-mod-aws-iam-role"]
 signer["tf-mod-aws-signer"]
 lambda["tf-mod-aws-lambda"]

 acm -->|"certificate_arn (optional)"| signer
 s3 -->|"bucket name (signing jobs)"| signer
 iam -->|"principal arn (permissions)"| signer
 signer -->|"profile arn"| lambda

 style signer fill:#FF9900,color:#fff
Loading

Signer is a near-foundation module: it consumes only an optional ACM certificate ARN and referenced S3 bucket names, and is consumed by the compute/containers/IoT layer (Lambda code-signing configs, OTA firmware pipelines, container image signing) that needs a verifiable, trusted publisher identity.


🧬 What this module builds

flowchart TD
 subgraph module["tf-mod-aws-signer"]
 profile["aws_signer_signing_profile.this<br/>(keystone)"]
 perm["aws_signer_signing_profile_permission.this<br/>(for_each, optional)"]
 job["aws_signer_signing_job.this<br/>(for_each, optional)"]
 end

 profile -->|profile_name| perm
 profile -->|profile_name| job

 style profile fill:#FF9900,color:#fff
Loading
Resource Role Created when
aws_signer_signing_profile.this Keystone signing profile Always
aws_signer_signing_profile_permission.this Cross-account permission statement (for_each) permissions non-empty
aws_signer_signing_job.this Terraform-managed signing job (for_each) signing_jobs non-empty

ℹ️ signing_jobs is deliberately the exception, not the pattern — real code-signing workflows call StartSigningJob per build artifact from a CI/CD pipeline, not from Terraform state. See Architecture Notes.


✅ Provider / Versions

Requirement Version
Terraform >= 1.12.0
hashicorp/aws >= 6.0, < 7.0

No configuration_aliases — AWS Signer is a single-Region service with no us-east-1 global-service coupling, so the module inherits the caller's default provider only.


🔑 Required IAM Permissions

The Terraform identity needs the following actions. Scope to the signing profile ARN where possible.

Action Required for Notes
signer:PutSigningProfile, signer:GetSigningProfile Profile lifecycle / read-back
signer:CancelSigningProfile Destroy Cancellation, not hard-delete — see Architecture Notes
signer:ListSigningProfiles, signer:ListTagsForResource Read-back / plan refresh
signer:TagResource, signer:UntagResource Tagging
signer:AddProfilePermission, signer:RemoveProfilePermission, signer:ListProfilePermissions Cross-account permissions (aws_signer_signing_profile_permission)
signer:StartSigningJob, signer:DescribeSigningJob Terraform-managed signing jobs (aws_signer_signing_job) Only needed if signing_jobs is used
acm:DescribeCertificate Validating a caller-supplied signing_material.certificate_arn Only when signing_material is set

ℹ️ No iam:PassRole is required by the Terraform identity — AWS Signer uses its own service principal (signer.amazonaws.com) to read the source object and write the signed object. The source/destination bucket policies (owned by tf-mod-aws-s3-bucket, out of scope here) must separately authorize that service principal for s3:GetObject/s3:GetObjectVersion/s3:PutObject.

⚠️ No service-linked role is auto-created by AWS Signer.


📋 AWS Prerequisites

  • Service: AWS Signer is enabled in every account and Region by default — no account-level opt-in required.
  • Service-linked role: none required.
  • Region constraints: Signer is a regional service — no us-east-1 global-service coupling (unlike CloudFront/WAFv2/ACM-for-CloudFront). Confirm Regional availability before use in GovCloud or newer Regions.
  • Platform support: confirm the account/Region actually supports the chosen platform_id — run aws signer list-signing-platforms for the authoritative, current list; the module's variable validation covers the platforms documented at authoring time (Lambda, IoT Device Management, FreeRTOS, Notation-OCI).
  • Source bucket versioning: aws_signer_signing_job requires the source S3 bucket to have versioning enabled — source.s3.version is a required argument.
  • Quotas: signing profiles, permission statements per profile, and concurrent signing jobs are subject to per-account/Region service quotas — check the Service Quotas console before large-scale rollout.

📁 Module Structure

tf-mod-aws-signer/
├── providers.tf # required_providers only — no provider {} block
├── variables.tf # name, platform_id, signature_validity_period, signing_material,
│ # signing_parameters, permissions, signing_jobs, tags
├── main.tf # aws_signer_signing_profile.this + permission + job (for_each)
├── outputs.tf # id, arn, name, version(_arn), status, revocation_record,
│ # permission_*, signing_job_*, tags_all
├── README.md # this file
└── SCOPE.md # in/out-of-scope, IAM, prerequisites, gotchas, design decisions

⚙️ Quick Start

Smallest working call — a Lambda code-signing profile with a finite (1-year) signature validity:

module "signer" {
  source = "git::https://github.com/microsoftexpert/tf-mod-aws-signer?ref=v1.0.0"

  name        = "app_lambda_signing"
  platform_id = "AWSLambda-SHA384-ECDSA"

  tags = {
    Environment = "prod"
    CostCenter  = "1234"
  }
}

# Wire the profile ARN into a Lambda code-signing config:
# allowed_publishers { signing_profile_version_arns = [module.signer.version_arn] }

⚠️ Always pin the source with ?ref=v1.0.0 — never a branch.


🔌 Cross-Module Contract

Consumes

Input Type Source module
signing_material.certificate_arn string (ACM ARN) tf-mod-aws-acm
permissions[*].principal string (IAM ARN or account ID) tf-mod-aws-iam-role / tf-mod-aws-iam-user
signing_jobs[*].source.s3.bucket / destination.s3.bucket string (bucket name) tf-mod-aws-s3-bucket

Signer is a near-foundation module — it consumes only an optional ACM cert ARN and referenced bucket names.

Emits

Output Description Consumed by
id Signing profile id (the profile name) Signer API references
arn Signing profile ARN — cross-resource reference type tf-mod-aws-lambda (code-signing config), IAM policies
name Signing profile name profile_name inputs on any sibling Signer resource
version / version_arn Current profile version / version-qualified ARN Version-pinned consumers (e.g. IAM signer:ProfileVersion conditions)
status Profile status (Active/Canceled) Drift/health monitoring
revocation_record Revocation metadata, if revoked Security monitoring
permission_ids / permission_statement_ids Maps of cross-account permission resource ids / statement ids Audit of granted cross-account access
signing_job_ids / signing_job_statuses / signed_object_locations Maps for any Terraform-managed signing jobs Release/build pipeline tracking
tags_all All tags incl. provider default_tags Governance / audit

📚 Example Library

1 · Minimal — Lambda code-signing profile
module "signer" {
  source = "git::https://github.com/microsoftexpert/tf-mod-aws-signer?ref=v1.0.0"

  name        = "app_lambda_signing"
  platform_id = "AWSLambda-SHA384-ECDSA"
}
2 · With tags (merges with provider default_tags)
provider "aws" {
  region = "us-east-1"
  default_tags {
    tags = {
      ManagedBy = "Terraform"
      Owner     = "platform-team"
    }
  }
}

module "signer" {
  source = "git::https://github.com/microsoftexpert/tf-mod-aws-signer?ref=v1.0.0"

  name        = "app_lambda_signing"
  platform_id = "AWSLambda-SHA384-ECDSA"

  # Resource tags MERGE with default_tags; resource tags win on key conflict.
  tags = {
    Environment = "prod"
    DataClass   = "internal"
  }
}

# module.signer.tags_all == { ManagedBy, Owner, Environment, DataClass }
3 · Custom signature validity (extend beyond the 1-year default)
module "signer" {
  source = "git::https://github.com/microsoftexpert/tf-mod-aws-signer?ref=v1.0.0"

  name        = "long_lived_signing"
  platform_id = "AWSLambda-SHA384-ECDSA"

  # Always finite — no "unlimited" option is exposed by this module.
  signature_validity_period = {
    value = 5
    type  = "YEARS"
  }
}
4 · IoT Device Management OTA firmware signing
module "iot_signer" {
  source = "git::https://github.com/microsoftexpert/tf-mod-aws-signer?ref=v1.0.0"

  name        = "iot_ota_signing"
  platform_id = "AWSIoTDeviceManagement-SHA256-ECDSA"

  signature_validity_period = {
    value = 6
    type  = "MONTHS"
  }
}
5 · FreeRTOS OTA firmware signing (generic platform)
module "freertos_signer" {
  source = "git::https://github.com/microsoftexpert/tf-mod-aws-signer?ref=v1.0.0"

  name        = "freertos_ota_signing"
  platform_id = "AmazonFreeRTOS-Default"
}
6 · Customer-managed signing certificate (Notation-OCI container image signing)
module "acm_cert" {
  source      = "git::https://github.com/microsoftexpert/tf-mod-aws-acm?ref=v1.0.0"
  domain_name = "signer.internal.example.com"
  #... validation config...
}

module "container_signer" {
  source = "git::https://github.com/microsoftexpert/tf-mod-aws-signer?ref=v1.0.0"

  name        = "oci_image_signing"
  platform_id = "Notation-OCI-SHA384-ECDSA"

  # Notation-OCI requires a bring-your-own signing certificate.
  signing_material = {
    certificate_arn = module.acm_cert.arn
  }
}
7 · Signing parameters (opaque platform-specific metadata)
module "signer" {
  source = "git::https://github.com/microsoftexpert/tf-mod-aws-signer?ref=v1.0.0"

  name        = "device_signing"
  platform_id = "AmazonFreeRTOS-TI-CC3220SF"

  signing_parameters = {
    certname = "/device/cert/path"
  }
}
8 · Cross-account permission for a CI/CD role (recommended pattern)
module "ci_role" {
  source             = "git::https://github.com/microsoftexpert/tf-mod-aws-iam-role?ref=v1.0.0"
  name               = "ci-signing-role"
  assume_role_policy = data.aws_iam_policy_document.ci_trust.json
}

module "signer" {
  source = "git::https://github.com/microsoftexpert/tf-mod-aws-signer?ref=v1.0.0"

  name        = "app_lambda_signing"
  platform_id = "AWSLambda-SHA384-ECDSA"

  permissions = {
    ci-pipeline = {
      action    = "signer:StartSigningJob"
      principal = module.ci_role.arn
    }
  }
}
9 · Cross-account permission scoped to a specific profile version
module "signer" {
  source = "git::https://github.com/microsoftexpert/tf-mod-aws-signer?ref=v1.0.0"

  name        = "prod_signing"
  platform_id = "AWSLambda-SHA384-ECDSA"

  permissions = {
    version-pinned = {
      action          = "signer:RevokeSignature"
      principal       = "111122223333"
      profile_version = module.signer.version # not literally self-referential in real use — pass a known prior version
    }
  }
}
10 · Multiple cross-account permissions (for_each pattern)
locals {
  signing_grantees = {
    ci-pipeline   = { action = "signer:StartSigningJob", principal = module.ci_role.arn }
    release-audit = { action = "signer:GetSigningProfile", principal = module.audit_role.arn }
  }
}

module "signer" {
  source = "git::https://github.com/microsoftexpert/tf-mod-aws-signer?ref=v1.0.0"

  name        = "app_lambda_signing"
  platform_id = "AWSLambda-SHA384-ECDSA"

  permissions = local.signing_grantees
}
11 · Static, Terraform-managed signing job (narrow exception case)
# Only for a deliberately static, infrastructure-provisioned artifact.
# Most signing belongs in a CI/CD pipeline — see Architecture Notes.
module "signer" {
  source = "git::https://github.com/microsoftexpert/tf-mod-aws-signer?ref=v1.0.0"

  name        = "release_pinned_signing"
  platform_id = "AWSLambda-SHA384-ECDSA"

  signing_jobs = {
    release-2026-07 = {
      source = {
        s3 = { bucket = "casey-build-artifacts", key = "app.zip", version = "abc123def456" }
      }
      destination = {
        s3 = { bucket = "casey-signed-artifacts", prefix = "signed/" }
      }
    }
  }
}
12 · Import an existing signing profile
import {
  to = module.signer.aws_signer_signing_profile.this
  id = "app_lambda_signing_DdW3Mk1foYL88fajut4mTVFGpuwfd4ACO6ANL0D1uIj7lrn8adK"
}

module "signer" {
  source = "git::https://github.com/microsoftexpert/tf-mod-aws-signer?ref=v1.0.0"

  name        = "app_lambda_signing"
  platform_id = "AWSLambda-SHA384-ECDSA"
}
13 · Secure-by-default opt-out — long signature validity (EXCEPTION)
# ⚠️ Weakens the baseline (long exposure window for a compromised signature).
# Document the exception and get sign-off before use.
module "signer" {
  source = "git::https://github.com/microsoftexpert/tf-mod-aws-signer?ref=v1.0.0"

  name        = "legacy_compat_signing"
  platform_id = "AWSLambda-SHA384-ECDSA"

  signature_validity_period = {
    value = 10
    type  = "YEARS" # still finite — no "unlimited" option exists in this module
  }
}
14 · End-to-end composition — IAM → Signer → Lambda code signing (mandatory finale)
# 1) Role that the CI/CD pipeline assumes to start signing jobs
module "ci_role" {
  source             = "git::https://github.com/microsoftexpert/tf-mod-aws-iam-role?ref=v1.0.0"
  name               = "ci-signing-role"
  assume_role_policy = data.aws_iam_policy_document.ci_trust.json
}

# 2) The signing profile — cross-account permission delegated to the CI role
module "signer" {
  source = "git::https://github.com/microsoftexpert/tf-mod-aws-signer?ref=v1.0.0"

  name        = "app_lambda_signing"
  platform_id = "AWSLambda-SHA384-ECDSA"

  permissions = {
    ci-pipeline = {
      action    = "signer:StartSigningJob"
      principal = module.ci_role.arn
    }
  }

  tags = { Environment = "prod", DataClass = "internal" }
}

# 3) Lambda function trusts signatures produced under this profile
resource "aws_lambda_code_signing_config" "this" {
  allowed_publishers {
    signing_profile_version_arns = [module.signer.version_arn]
  }

  policies {
    untrusted_artifact_on_deployment = "Enforce"
  }
}

module "app_lambda" {
  source                  = "git::https://github.com/microsoftexpert/tf-mod-aws-lambda?ref=v1.0.0"
  function_name           = "app"
  code_signing_config_arn = aws_lambda_code_signing_config.this.arn
  #... runtime, handler, package, role, etc....
}

📥 Inputs

ℹ️ High-level grouping below.

  • Core (FORCE-NEW): name (primary identity), platform_id.
  • Signature validity (FORCE-NEW): signature_validity_period — object { value, type }; defaults to finite { value = 1, type = "YEARS" }.
  • Signing certificate (FORCE-NEW): signing_material — nullable object { certificate_arn }; required only by bring-your-own-cert platforms (e.g. Notation-OCI).
  • Signing parameters (FORCE-NEW): signing_parameters — opaque map(string), platform-specific.
  • Cross-account permissions: permissionsmap(object) keyed by stable name; principal may never be "*".
  • Signing jobs (narrow use case): signing_jobsmap(object) keyed by stable name; see Architecture Notes before using.
  • Universal: tags. (No timeouts — none of the three Signer resources expose a configurable timeouts block.)

🧾 Outputs

  • id — the signing profile id (the profile name).
  • arn — the signing profile ARN (the canonical cross-resource reference).
  • name — the profile name.
  • version, version_arn — current profile version and version-qualified ARN.
  • status — profile status (e.g. Active, Canceled).
  • platform_display_name — human-readable platform name.
  • revocation_record — revocation metadata (empty unless the profile was revoked).
  • permission_ids, permission_statement_ids — maps keyed by var.permissions entry (empty when no permissions configured).
  • signing_job_ids, signing_job_statuses, signed_object_locations — maps keyed by var.signing_jobs entry (empty when no jobs configured).
  • tags_all — merged resource + provider default_tags.

No output in this module is sensitive = true — Signer does not emit secret material.


🧠 Architecture Notes

  • ARN / id formats.
  • Signing profile ARN: arn:aws:signer:<region>:<account>:/signing-profiles/<name>
  • Profile id: the profile name itself (same value as id).
  • Version ARN: the profile ARN suffixed with the version identifier — use to pin a consumer to an exact profile version.
  • Force-new / immutable fields. name, platform_id, signature_validity_period, and signing_material are all FORCE-NEW — changing any of them destroys and recreates the profile. Signing profile names are permanently retired once canceled — AWS Signer has no hard-delete, only cancellation, and a canceled name can never be reused in that account/Region.
  • signature_validity_period uses a value + type pair, not a duration string (type is one of DAYS/MONTHS/YEARS). This module defaults to a finite { value = 1, type = "YEARS" } rather than deferring to the platform's own default validity.
  • Signing jobs are typically pipeline-invoked, not Terraform-managed. Real code-signing workflows call StartSigningJob once per build artifact via CI/CD, the AWS CLI, or an SDK — that access pattern doesn't fit Terraform's declarative, converge-to-a-fixed-state model well. The signing_jobs input exists only for a single, static, infrastructure-provisioned signing job; a terraform apply re-runs the job if any input (source key/version, etc.) changes.
  • aws_signer_signing_profile_permission exports no additional attributes beyond the base id — there is no arn output for a permission statement.
  • tagstags_alldefault_tags. Only the signing profile is taggable — aws_signer_signing_job and aws_signer_signing_profile_permission are not. var.tags flows to the profile only; tags_all reflects the merge with provider default_tags (resource tags win on key conflict).
  • No us-east-1 global-resource coupling. Unlike CloudFront/WAFv2/ACM-for-CloudFront, AWS Signer is a regional service with no special-Region requirement.
  • Eventual consistency. A freshly created signing profile may take a moment to be resolvable by aws_signer_signing_profile_permission/aws_signer_signing_job in the same apply; Terraform's implicit dependency graph (via aws_signer_signing_profile.this.name) orders these correctly.

🧱 Design Principles

Secure-by-default posture — each hardened default and the exact variable that relaxes it:

Posture Hardened default Opt-out (variable)
Signature validity finite { value = 1, type = "YEARS" }, never unlimited supply a longer explicit signature_validity_period — always still finite; no "unlimited" option is exposed
Cross-account permission principal must be an explicit AWS account ID/ARN non-negotiable — wildcard ("*") principals are rejected by variable validation
Platform selection validated against a documented, known platform-ID allow-list extend the platform_id variable's validation list when a legitimately new AWS Signer platform is required
Signing job failures ignore_signing_job_failure = false (fail the apply loudly) set true per signing-job entry, only with a documented reason to inspect status/reason instead of failing outright

Additional principles:

  • One composite, one profile. The profile plus its optional cross-account permissions and optional static signing jobs come from a single call.
  • signing_jobs is the documented exception, not the pattern. Real signing workflows belong in a CI/CD pipeline invoking the Signer API per build, not in Terraform state — see Architecture Notes.
  • Optional features are absent unless configured. Permissions ({}) and signing jobs ({}) materialize only when supplied.

🚀 Runbook

terraform init -backend=false
terraform validate
terraform fmt -check

# plan/apply require valid AWS credentials (profile / SSO / OIDC) and a region:
terraform plan
terraform apply
terraform output

⚠️ Pin the module source with ?ref=v1.0.0 — never a branch.


🧪 Testing

  • terraform init -backend=false && terraform validate — offline schema validation.
  • terraform fmt -check — formatting gate.
  • terraform plan against a sandbox account — confirm the profile renders with the expected platform_id and signature_validity_period.
  • Verify permission_ids/permission_statement_ids populate correctly for each permissions entry, and confirm no plan ever accepts a "*" principal (the variable validation should reject it before plan even runs).
  • For signing_jobs: confirm signing_job_statuses shows Succeeded and signed_object_locations points at the expected destination key.

💬 Example Output

Apply complete! Resources: 1 added, 0 changed, 0 destroyed.

Outputs:

arn = "arn:aws:signer:us-east-1:111122223333:/signing-profiles/app_lambda_signing"
id = "app_lambda_signing"
name = "app_lambda_signing"
platform_display_name = "AWS Lambda"
status = "Active"
tags_all = {
 "DataClass" = "internal"
 "Environment" = "prod"
 "ManagedBy" = "Terraform"
}
version = "6QavYnEsxD3ir4DAAAAA"
version_arn = "arn:aws:signer:us-east-1:111122223333:/signing-profiles/app_lambda_signing/6QavYnEsxD3ir4DAAAAA"

🔍 Troubleshooting

  • Tag drift from default_tags overlap. A key set in both var.tags and provider default_tags shows the resource value in tags_all (resource wins). If a plan keeps re-adding a tag, it's defined in both places with different values — remove the duplicate.
  • Credential-chain failures. NoCredentialProviders / ExpiredToken on plan/apply means no valid AWS credentials — set AWS_PROFILE, refresh SSO, or confirm the OIDC role assumption succeeded in CI.
  • Region / availability errors. UnknownOperationException or similar for a platform in an unexpected Region usually means the chosen platform_id isn't available there — verify with aws signer list-signing-platforms in the target Region (no us-east-1 requirement, but not every platform is available everywhere).
  • IAM permission denials. A signer:PutSigningProfile/signer:StartSigningJob denial means the Terraform identity lacks the action — see Required IAM Permissions. Note that s3:GetObject/s3:PutObject denials on a signing job come from the bucket policy, not the Terraform identity's IAM policy — the signer.amazonaws.com service principal needs bucket-level access.
  • Profile name reuse fails. Attempting to recreate a profile with a previously canceled name errors — AWS Signer never allows reusing a retired profile name in the same account/Region. Pick a new name.
  • ValidationException on permissions[*].principal = "*". This module's variable validation rejects wildcard principals before the plan even reaches AWS — supply an explicit account ID or ARN.
  • Signing job "hangs" or times out. Signing jobs are asynchronous; check signing_job_statuses and, on failure with ignore_signing_job_failure = true, inspect the job's status_reason via the AWS Signer console/CLI (this module does not expose status_reason as an output since it is only meaningful on failure).
  • Destroy leaves the profile in Canceled, not gone. Signer has no hard-delete for profiles — terraform destroy cancels the profile; it remains visible (as canceled) in the console/API, and its name can never be reused.

🔗 Related Docs

  • Terraform Registry — aws_signer_signing_profile, aws_signer_signing_job, aws_signer_signing_profile_permission
  • AWS Signer Developer Guide — signing platforms, profile permissions, and revocation
  • AWS Lambda Developer Guide — code signing for Lambda functions and code-signing configs
  • AWS IoT / FreeRTOS Developer Guide — OTA firmware update code signing
  • module suite — tf-mod-aws-acm, tf-mod-aws-s3-bucket, tf-mod-aws-iam-role, tf-mod-aws-lambda

🧡 "Infrastructure as Code should be standardized, consistent, and secure."

Packages

 
 
 

Contributors

Languages