diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..127b4dd --- /dev/null +++ b/.gitignore @@ -0,0 +1,39 @@ +*.dll +*.exe +.DS_Store +example.tf +terraform.tfplan +terraform.tfstate +bin/ +dist/ +modules-dev/ +/pkg/ +website/.vagrant +website/.bundle +website/build +website/node_modules +.vagrant/ +*.backup +./*.tfstate +.terraform/ +*.log +*.bak +*~ +.*.swp +.idea +*.iml +*.test +*.iml + +website/vendor + +# Test exclusions +!command/test-fixtures/**/*.tfstate +!command/test-fixtures/**/.terraform/ + +# Keep windows files with windows line endings +*.winfile eol=crlf +terraform-provider-tor +dev/ +CLAUDE.md +extra diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..b76e247 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,3 @@ +## 0.1.0 (Unreleased) + +FEATURES: diff --git a/GNUmakefile b/GNUmakefile new file mode 100644 index 0000000..e339a0a --- /dev/null +++ b/GNUmakefile @@ -0,0 +1,24 @@ +default: fmt lint install generate + +build: + go build -v ./... + +install: build + go install -v ./... + +lint: + golangci-lint run + +generate: + cd tools; go generate ./... + +fmt: + gofmt -s -w -e . + +test: + go test -v -cover -timeout=120s -parallel=10 ./... + +testacc: + TF_ACC=1 go test -v -cover -timeout 120m ./... + +.PHONY: fmt lint test testacc build install generate diff --git a/META.d/_summary.yaml b/META.d/_summary.yaml new file mode 100644 index 0000000..68c8ffd --- /dev/null +++ b/META.d/_summary.yaml @@ -0,0 +1,13 @@ +# Copyright (c) HashiCorp, Inc. + +--- +schema: 1.1 + +partition: tf-ecosystem + +summary: + owner: abel@guardianproject.info + description: | + Terraform/OpenTofu provider for managing Tor bridge infrastructure. + Generates RSA and Ed25519 relay identity keys and obfs4 state for stateless bridge deployments. + visibility: public diff --git a/README.md b/README.md new file mode 100644 index 0000000..a89eb78 --- /dev/null +++ b/README.md @@ -0,0 +1,142 @@ +# Terraform Provider for Tor Bridges + +[![][ci-badge]][ci] +[![Go Report Card](https://goreportcard.com/badge/github.com/guardianproject/terraform-provider-tor)](https://goreportcard.com/report/github.com/guardianproject/terraform-provider-tor) + +A Terraform/OpenTofu provider for managing obfs4 Tor bridge cryptographic identity and state. + +**Canonical Repository:** https://guardianproject.dev/ops/terraform-provider-tor + +## Overview + +This provider enables stateless deployment of obfs4 Tor bridges by +pre-generating all required cryptographic identity materials in +Terraform/OpenTofu. Instead of bridges generating new identity keys at startup +(which would change on each deployment), this provider manages the identity +lifecycle within your infrastructure-as-code workflow. + +**Why?*** + +When deploying obfs4 bridges at scale, maintaining consistent bridge identity +across VM upgrades and replacements is crucial. This provider solves that by: + +- Generating relay identity keys (RSA and Ed25519) +- Creating obfs4 state including certificates for bridge lines +- Providing complete bridge line generation for client distribution +- Enabling fully immutable bridge VMs that retain identity across deployments + +## Usage + +```hcl +terraform { + required_providers { + tor = { + source = "guardianproject/tor" + } + } +} + +provider "tor" {} + +# Generate relay identity keys +resource "tor_relay_identity_rsa" "bridge" {} + +resource "tor_relay_identity_ed25519" "bridge" {} + +# Generate obfs4 state using the identity keys +resource "tor_obfs4_state" "bridge" { + rsa_identity_private_key = tor_relay_identity_rsa.bridge.private_key_pem + ed25519_identity_private_key = tor_relay_identity_ed25519.bridge.private_key_pem + iat_mode = 1 +} + +# Generate bridge line for client distribution +data "tor_obfs4_bridge_line" "bridge" { + ip_address = "192.0.2.1" + port = 443 + identity_fingerprint_sha1 = tor_relay_identity_rsa.bridge.public_key_fingerprint_sha1 + obfs4_state_certificate = tor_obfs4_state.bridge.certificate + obfs4_state_iat_mode = tor_obfs4_state.bridge.iat_mode +} + +# Output bridge configuration for deployment +output "rsa_identity_pem" { + description = "RSA identity private key for bridge configuration" + value = tor_relay_identity_rsa.bridge.private_key_pem + sensitive = true +} + +output "ed25519_identity_pem" { + description = "Ed25519 identity private key for bridge configuration" + value = tor_relay_identity_ed25519.bridge.private_key_pem + sensitive = true +} + +output "obfs4_state_json" { + description = "Complete obfs4 state for bridge runtime" + value = tor_obfs4_state.bridge.state_json + sensitive = true +} + +output "bridge_line" { + description = "Complete bridge line for client use" + value = data.tor_obfs4_bridge_line.bridge.bridge_line +} +``` + +## Provider Options + +This provider requires no configuration options. + +## Documentation + +Complete documentation is available in the [docs/](docs/) directory: + +- [tor_relay_identity_rsa](docs/resources/relay_identity_rsa.md) +- [tor_relay_identity_ed25519](docs/resources/relay_identity_ed25519.md) +- [tor_obfs4_state](docs/resources/obfs4_state.md) +- [tor_obfs4_bridge_line (data source)](docs/data-sources/obfs4_bridge_line.md) + +## Requirements + +- Terraform >= 1.0 or OpenTofu >= 1.0 +- Go >= 1.23 (for development) + +## Versioning + +This provider follows [Semantic Versioning 2.0.0](https://semver.org/). See [CHANGELOG.md](CHANGELOG.md) for release history. + +## Maintenance + +This provider is actively maintained by [Guardian Project](https://guardianproject.info). + +### Issues + +For bug reports and feature requests, please use the [Issues][issues] page. + +### Security + +For security-related issues, please contact us through our [security policy][sec]. + +## Contributing + +We welcome contributions! Please see [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines on how to contribute to this project. + +## References + +- [lyrebird](https://gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/lyrebird) - the obfs4 Go implementation used by this provider +- [Tor Bridge Operations](https://community.torproject.org/relay/setup/bridge/) - Setting up Tor bridges +- [obfs4 Protocol Specification](https://gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/lyrebird/-/blob/main/doc/obfs4-spec.txt) + +## License + +Copyright © 2025 Abel Luck + +This project is licensed under the GNU General Public License v3.0 or later - see the [LICENSE](LICENSE) file for details. + +[repo]: https://guardianproject.dev/ops/terraform-provider-tor +[ci]: https://guardianproject.dev/ops/terraform-provider-tor/actions +[ci]: https://guardianproject.dev/ops/terraform-provider-tor/actions +[ci-badge]: https://guardianproject.dev/ops/terraform-provider-tor/badges/workflows/ci/badge.svg +[issues]: https://guardianproject.dev/ops/terraform-provider-tor/issues +[sec]: diff --git a/docs/data-sources/obfs4_bridge_line.md b/docs/data-sources/obfs4_bridge_line.md new file mode 100644 index 0000000..d75127e --- /dev/null +++ b/docs/data-sources/obfs4_bridge_line.md @@ -0,0 +1,80 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "tor_obfs4_bridge_line Data Source - tor" +subcategory: "" +description: |- + Generates a complete Tor bridge line using obfs4 state and network details +--- + +# tor_obfs4_bridge_line (Data Source) + +Generates a complete Tor bridge line using obfs4 state and network details + +## Example Usage + +```terraform +# Copyright (c) HashiCorp, Inc. + +terraform { + required_providers { + tor = { + source = "guardianproject/tor" + } + } +} + +provider "tor" {} + +# Example: Generate a bridge line from existing components +data "tor_obfs4_bridge_line" "example" { + ip_address = "192.0.2.1" + port = 443 + identity_fingerprint_sha1 = "1234567890abcdef1234567890abcdef12345678" + obfs4_state_certificate = "example-cert-value" + obfs4_state_iat_mode = 0 +} + +output "bridge_line" { + description = "Generated bridge line for clients" + value = data.tor_obfs4_bridge_line.example.bridge_line +} + +# Example: Complete workflow integration +resource "tor_relay_identity_rsa" "bridge" {} + +resource "tor_relay_identity_ed25519" "bridge" {} + +resource "tor_obfs4_state" "bridge" { + rsa_identity_private_key = tor_relay_identity_rsa.bridge.private_key_pem + ed25519_identity_private_key = tor_relay_identity_ed25519.bridge.private_key_pem +} + +data "tor_obfs4_bridge_line" "integrated" { + ip_address = "10.0.0.1" + port = 9001 + identity_fingerprint_sha1 = tor_relay_identity_rsa.bridge.public_key_fingerprint_sha1 + obfs4_state_certificate = tor_obfs4_state.bridge.certificate + obfs4_state_iat_mode = tor_obfs4_state.bridge.iat_mode +} + +output "integrated_bridge_line" { + description = "Bridge line from integrated workflow" + value = data.tor_obfs4_bridge_line.integrated.bridge_line +} +``` + + +## Schema + +### Required + +- `identity_fingerprint_sha1` (String) SHA1 fingerprint of the RSA identity key +- `ip_address` (String) Bridge IP address +- `obfs4_state_certificate` (String) Base64-encoded certificate from tor_obfs4_state resource +- `obfs4_state_iat_mode` (Number) Inter-Arrival Time mode from tor_obfs4_state resource +- `port` (Number) Bridge port number + +### Read-Only + +- `bridge_line` (String) Complete bridge line ready for client use +- `id` (String) Data source identifier diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 0000000..b70f61a --- /dev/null +++ b/docs/index.md @@ -0,0 +1,75 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "tor Provider" +subcategory: "" +description: |- + The Tor provider generates cryptographic identity materials for obfs4 Tor bridges, enabling stateless bridge deployments. +--- + +# tor Provider + +The Tor provider generates cryptographic identity materials for obfs4 Tor bridges, enabling stateless bridge deployments. + +## Example Usage + +```terraform +# Copyright (c) HashiCorp, Inc. + +terraform { + required_providers { + tor = { + source = "guardianproject/tor" + } + } +} + +provider "tor" {} + +# Generate relay identity keys +resource "tor_relay_identity_rsa" "bridge" {} + +resource "tor_relay_identity_ed25519" "bridge" {} + +# Generate obfs4 state using the identity keys +resource "tor_obfs4_state" "bridge" { + rsa_identity_private_key = tor_relay_identity_rsa.bridge.private_key_pem + ed25519_identity_private_key = tor_relay_identity_ed25519.bridge.private_key_pem + iat_mode = 1 +} + +# Generate bridge line for client distribution +data "tor_obfs4_bridge_line" "bridge" { + ip_address = "192.0.2.1" + port = 443 + identity_fingerprint_sha1 = tor_relay_identity_rsa.bridge.public_key_fingerprint_sha1 + obfs4_state_certificate = tor_obfs4_state.bridge.certificate + obfs4_state_iat_mode = tor_obfs4_state.bridge.iat_mode +} + +# Output bridge configuration for deployment +output "rsa_identity_pem" { + description = "RSA identity private key for bridge configuration" + value = tor_relay_identity_rsa.bridge.private_key_pem + sensitive = true +} + +output "ed25519_identity_pem" { + description = "Ed25519 identity private key for bridge configuration" + value = tor_relay_identity_ed25519.bridge.private_key_pem + sensitive = true +} + +output "obfs4_state_json" { + description = "Complete obfs4 state for bridge runtime" + value = tor_obfs4_state.bridge.state_json + sensitive = true +} + +output "bridge_line" { + description = "Complete bridge line for client use" + value = data.tor_obfs4_bridge_line.bridge.bridge_line +} +``` + + +## Schema diff --git a/docs/resources/obfs4_state.md b/docs/resources/obfs4_state.md new file mode 100644 index 0000000..5638af3 --- /dev/null +++ b/docs/resources/obfs4_state.md @@ -0,0 +1,36 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "tor_obfs4_state Resource - tor" +subcategory: "" +description: |- + Generates obfs4 state and certificate for Tor bridges using external relay identity keys +--- + +# tor_obfs4_state (Resource) + +Generates obfs4 state and certificate for Tor bridges using external relay identity keys + + + + +## Schema + +### Required + +- `ed25519_identity_private_key` (String, Sensitive) Ed25519 identity private key in PEM format (from tor_relay_identity_ed25519 resource) +- `rsa_identity_private_key` (String, Sensitive) RSA identity private key in PEM format (from tor_relay_identity_rsa resource) + +### Optional + +- `iat_mode` (Number) Inter-Arrival Time mode (0=none, 1=enabled, 2=paranoid) + +### Read-Only + +- `bridge_line` (String) Complete bridge line ready for client use (placeholder IP and fingerprint) +- `certificate` (String) Base64-encoded certificate for bridge lines +- `drbg_seed` (String, Sensitive) 24-byte DRBG seed in hex format +- `id` (String) Resource identifier +- `node_id` (String) 20-byte node ID in hex format +- `private_key` (String, Sensitive) 32-byte Curve25519 private key in hex format +- `public_key` (String) 32-byte Curve25519 public key in hex format +- `state_json` (String) Complete obfs4 state in JSON format diff --git a/docs/resources/relay_identity_ed25519.md b/docs/resources/relay_identity_ed25519.md new file mode 100644 index 0000000..5a8f006 --- /dev/null +++ b/docs/resources/relay_identity_ed25519.md @@ -0,0 +1,24 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "tor_relay_identity_ed25519 Resource - tor" +subcategory: "" +description: |- + Generates Ed25519 private key for Tor relay identity as required by the Tor specification. +--- + +# tor_relay_identity_ed25519 (Resource) + +Generates Ed25519 private key for Tor relay identity as required by the Tor specification. + + + + +## Schema + +### Read-Only + +- `algorithm` (String) Name of the algorithm used when generating the private key (always 'Ed25519') +- `id` (String) Unique identifier based on public key fingerprint +- `private_key_pem` (String, Sensitive) Private key data in PEM (RFC 1421) format +- `public_key_fingerprint_sha256` (String) SHA256 fingerprint of the public key in hex format +- `public_key_pem` (String) Public key data in PEM (RFC 1421) format diff --git a/docs/resources/relay_identity_rsa.md b/docs/resources/relay_identity_rsa.md new file mode 100644 index 0000000..eccbc72 --- /dev/null +++ b/docs/resources/relay_identity_rsa.md @@ -0,0 +1,25 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "tor_relay_identity_rsa Resource - tor" +subcategory: "" +description: |- + Generates 1024-bit RSA private key for Tor relay identity as required by the Tor specification. +--- + +# tor_relay_identity_rsa (Resource) + +Generates 1024-bit RSA private key for Tor relay identity as required by the Tor specification. + + + + +## Schema + +### Read-Only + +- `algorithm` (String) Name of the algorithm used when generating the private key (always 'RSA') +- `id` (String) Unique identifier based on public key fingerprint +- `private_key_pem` (String, Sensitive) Private key data in PEM (RFC 1421) format +- `public_key_fingerprint_sha1` (String) SHA1 fingerprint of the public key in hex format +- `public_key_fingerprint_sha256` (String) SHA256 fingerprint of the public key in hex format +- `public_key_pem` (String) Public key data in PEM (RFC 1421) format diff --git a/examples/README.md b/examples/README.md new file mode 100644 index 0000000..5aaf968 --- /dev/null +++ b/examples/README.md @@ -0,0 +1,29 @@ +# Terraform Provider for Tor Examples + +This directory contains example configurations for the Terraform provider for Tor bridges. + +## Examples + +### Complete Workflow +- `provider/provider.tf` - Complete bridge deployment example with all resources + +### Individual Resources +- `resources/tor_relay_identity_rsa/` - RSA identity key generation +- `resources/tor_relay_identity_ed25519/` - Ed25519 identity key generation +- `resources/tor_obfs4_state/` - obfs4 state generation + +### Data Sources +- `data-sources/tor_obfs4_bridge_line/` - Bridge line generation from components + +## Usage + +Each example can be run independently: + +```bash +cd examples/provider +terraform init +terraform plan +terraform apply +``` + +The complete workflow example in `provider/` demonstrates how all resources work together to create a fully configured bridge with generated bridge line for client distribution. \ No newline at end of file diff --git a/examples/data-sources/tor_obfs4_bridge_line/data-source.tf b/examples/data-sources/tor_obfs4_bridge_line/data-source.tf new file mode 100644 index 0000000..14e3efb --- /dev/null +++ b/examples/data-sources/tor_obfs4_bridge_line/data-source.tf @@ -0,0 +1,48 @@ +# Copyright (c) HashiCorp, Inc. + +terraform { + required_providers { + tor = { + source = "guardianproject/tor" + } + } +} + +provider "tor" {} + +# Example: Generate a bridge line from existing components +data "tor_obfs4_bridge_line" "example" { + ip_address = "192.0.2.1" + port = 443 + identity_fingerprint_sha1 = "1234567890abcdef1234567890abcdef12345678" + obfs4_state_certificate = "example-cert-value" + obfs4_state_iat_mode = 0 +} + +output "bridge_line" { + description = "Generated bridge line for clients" + value = data.tor_obfs4_bridge_line.example.bridge_line +} + +# Example: Complete workflow integration +resource "tor_relay_identity_rsa" "bridge" {} + +resource "tor_relay_identity_ed25519" "bridge" {} + +resource "tor_obfs4_state" "bridge" { + rsa_identity_private_key = tor_relay_identity_rsa.bridge.private_key_pem + ed25519_identity_private_key = tor_relay_identity_ed25519.bridge.private_key_pem +} + +data "tor_obfs4_bridge_line" "integrated" { + ip_address = "10.0.0.1" + port = 9001 + identity_fingerprint_sha1 = tor_relay_identity_rsa.bridge.public_key_fingerprint_sha1 + obfs4_state_certificate = tor_obfs4_state.bridge.certificate + obfs4_state_iat_mode = tor_obfs4_state.bridge.iat_mode +} + +output "integrated_bridge_line" { + description = "Bridge line from integrated workflow" + value = data.tor_obfs4_bridge_line.integrated.bridge_line +} \ No newline at end of file diff --git a/examples/provider/provider.tf b/examples/provider/provider.tf new file mode 100644 index 0000000..1d806f4 --- /dev/null +++ b/examples/provider/provider.tf @@ -0,0 +1,56 @@ +# Copyright (c) HashiCorp, Inc. + +terraform { + required_providers { + tor = { + source = "guardianproject/tor" + } + } +} + +provider "tor" {} + +# Generate relay identity keys +resource "tor_relay_identity_rsa" "bridge" {} + +resource "tor_relay_identity_ed25519" "bridge" {} + +# Generate obfs4 state using the identity keys +resource "tor_obfs4_state" "bridge" { + rsa_identity_private_key = tor_relay_identity_rsa.bridge.private_key_pem + ed25519_identity_private_key = tor_relay_identity_ed25519.bridge.private_key_pem + iat_mode = 1 +} + +# Generate bridge line for client distribution +data "tor_obfs4_bridge_line" "bridge" { + ip_address = "192.0.2.1" + port = 443 + identity_fingerprint_sha1 = tor_relay_identity_rsa.bridge.public_key_fingerprint_sha1 + obfs4_state_certificate = tor_obfs4_state.bridge.certificate + obfs4_state_iat_mode = tor_obfs4_state.bridge.iat_mode +} + +# Output bridge configuration for deployment +output "rsa_identity_pem" { + description = "RSA identity private key for bridge configuration" + value = tor_relay_identity_rsa.bridge.private_key_pem + sensitive = true +} + +output "ed25519_identity_pem" { + description = "Ed25519 identity private key for bridge configuration" + value = tor_relay_identity_ed25519.bridge.private_key_pem + sensitive = true +} + +output "obfs4_state_json" { + description = "Complete obfs4 state for bridge runtime" + value = tor_obfs4_state.bridge.state_json + sensitive = true +} + +output "bridge_line" { + description = "Complete bridge line for client use" + value = data.tor_obfs4_bridge_line.bridge.bridge_line +} \ No newline at end of file diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..87b5f11 --- /dev/null +++ b/go.mod @@ -0,0 +1,72 @@ +module terraform-provider-tor + +go 1.23.0 + +toolchain go1.24.2 + +require ( + github.com/hashicorp/terraform-plugin-framework v1.12.0 + github.com/hashicorp/terraform-plugin-go v0.24.0 + github.com/hashicorp/terraform-plugin-log v0.9.0 + github.com/hashicorp/terraform-plugin-testing v1.10.0 + gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/lyrebird v0.4.0 +) + +require ( + filippo.io/edwards25519 v1.1.0 // indirect + github.com/ProtonMail/go-crypto v1.1.0-alpha.2 // indirect + github.com/agext/levenshtein v1.2.3 // indirect + github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect + github.com/cloudflare/circl v1.5.0 // indirect + github.com/dchest/siphash v1.2.3 // indirect + github.com/fatih/color v1.16.0 // indirect + github.com/golang/protobuf v1.5.4 // indirect + github.com/google/go-cmp v0.7.0 // indirect + github.com/hashicorp/errwrap v1.1.0 // indirect + github.com/hashicorp/go-checkpoint v0.5.0 // indirect + github.com/hashicorp/go-cleanhttp v0.5.2 // indirect + github.com/hashicorp/go-cty v1.4.1-0.20200414143053-d3edf31b6320 // indirect + github.com/hashicorp/go-hclog v1.6.3 // indirect + github.com/hashicorp/go-multierror v1.1.1 // indirect + github.com/hashicorp/go-plugin v1.6.3 // indirect + github.com/hashicorp/go-retryablehttp v0.7.7 // indirect + github.com/hashicorp/go-uuid v1.0.3 // indirect + github.com/hashicorp/go-version v1.7.0 // indirect + github.com/hashicorp/hc-install v0.8.0 // indirect + github.com/hashicorp/hcl/v2 v2.21.0 // indirect + github.com/hashicorp/logutils v1.0.0 // indirect + github.com/hashicorp/terraform-exec v0.21.0 // indirect + github.com/hashicorp/terraform-json v0.22.1 // indirect + github.com/hashicorp/terraform-plugin-sdk/v2 v2.34.0 // indirect + github.com/hashicorp/terraform-registry-address v0.2.5 // indirect + github.com/hashicorp/terraform-svchost v0.1.1 // indirect + github.com/hashicorp/yamux v0.1.1 // indirect + github.com/kr/pretty v0.3.0 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mitchellh/copystructure v1.2.0 // indirect + github.com/mitchellh/go-testing-interface v1.14.1 // indirect + github.com/mitchellh/go-wordwrap v1.0.1 // indirect + github.com/mitchellh/mapstructure v1.5.0 // indirect + github.com/mitchellh/reflectwalk v1.0.2 // indirect + github.com/oklog/run v1.1.0 // indirect + github.com/rogpeppe/go-internal v1.12.0 // indirect + github.com/vmihailenco/msgpack v4.0.4+incompatible // indirect + github.com/vmihailenco/msgpack/v5 v5.4.1 // indirect + github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect + github.com/zclconf/go-cty v1.15.0 // indirect + gitlab.com/yawning/edwards25519-extra v0.0.0-20231005122941-2149dcafc266 // indirect + golang.org/x/crypto v0.37.0 // indirect + golang.org/x/mod v0.22.0 // indirect + golang.org/x/net v0.39.0 // indirect + golang.org/x/sync v0.13.0 // indirect + golang.org/x/sys v0.32.0 // indirect + golang.org/x/text v0.24.0 // indirect + golang.org/x/tools v0.29.0 // indirect + google.golang.org/appengine v1.6.8 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20250218202821-56aae31c358a // indirect + google.golang.org/grpc v1.72.1 // indirect + google.golang.org/protobuf v1.36.6 // indirect +) + +replace gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/lyrebird => ./extra/lyrebird diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..1a1ed7f --- /dev/null +++ b/go.sum @@ -0,0 +1,264 @@ +dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk= +dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= +filippo.io/edwards25519 v1.0.0/go.mod h1:N1IkdkCkiLB6tki+MYJoSx2JTY9NUlxZE7eHn5EwJns= +filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= +filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= +github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= +github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= +github.com/ProtonMail/go-crypto v1.1.0-alpha.2 h1:bkyFVUP+ROOARdgCiJzNQo2V2kiB97LyUpzH9P6Hrlg= +github.com/ProtonMail/go-crypto v1.1.0-alpha.2/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE= +github.com/agext/levenshtein v1.2.3 h1:YB2fHEn0UJagG8T1rrWknE3ZQzWM06O8AMAatNn7lmo= +github.com/agext/levenshtein v1.2.3/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558= +github.com/apparentlymart/go-textseg/v12 v12.0.0/go.mod h1:S/4uRK2UtaQttw1GenVJEynmyUenKwP++x/+DdGV/Ec= +github.com/apparentlymart/go-textseg/v15 v15.0.0 h1:uYvfpb3DyLSCGWnctWKGj857c6ew1u1fNQOlOtuGxQY= +github.com/apparentlymart/go-textseg/v15 v15.0.0/go.mod h1:K8XmNZdhEBkdlyDdvbmmsvpAG721bKi0joRfFdHIWJ4= +github.com/bufbuild/protocompile v0.4.0 h1:LbFKd2XowZvQ/kajzguUp2DC9UEIQhIq77fZZlaQsNA= +github.com/bufbuild/protocompile v0.4.0/go.mod h1:3v93+mbWn/v3xzN+31nwkJfrEpAUwp+BagBSZWx+TP8= +github.com/cloudflare/circl v1.5.0 h1:hxIWksrX6XN5a1L2TI/h53AGPhNHoUBo+TD1ms9+pys= +github.com/cloudflare/circl v1.5.0/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/cyphar/filepath-securejoin v0.2.4 h1:Ugdm7cg7i6ZK6x3xDF1oEu1nfkyfH53EtKeQYTC3kyg= +github.com/cyphar/filepath-securejoin v0.2.4/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dchest/siphash v1.2.3 h1:QXwFc8cFOR2dSa/gE6o/HokBMWtLUaNDVd+22aKHeEA= +github.com/dchest/siphash v1.2.3/go.mod h1:0NvQU092bT0ipiFN++/rXm69QG9tVxLAlQHIXMPAkHc= +github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= +github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= +github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= +github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= +github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= +github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI= +github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic= +github.com/go-git/go-billy/v5 v5.5.0 h1:yEY4yhzCDuMGSv83oGxiBotRzhwhNr8VZyphhiu+mTU= +github.com/go-git/go-billy/v5 v5.5.0/go.mod h1:hmexnoNsr2SJU1Ju67OaNz5ASJY3+sHgFRpCtpDCKow= +github.com/go-git/go-git/v5 v5.12.0 h1:7Md+ndsjrzZxbddRDZjF14qK+NN56sy6wkqaVrjZtys= +github.com/go-git/go-git/v5 v5.12.0/go.mod h1:FTM9VKtnI2m65hNI/TenDDDnUf2Q9FHnXYjuz9i5OEY= +github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= +github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/go-test/deep v1.0.3 h1:ZrJSEWsXzPOxaZnFteGEfooLba+ju3FYIbOrS+rQd68= +github.com/go-test/deep v1.0.3/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/protobuf v1.1.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= +github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-checkpoint v0.5.0 h1:MFYpPZCnQqQTE18jFwSII6eUQrD/oxMFp3mlgcqk5mU= +github.com/hashicorp/go-checkpoint v0.5.0/go.mod h1:7nfLNL10NsxqO4iWuW6tWW0HjZuDrwkBuEQsVcpCOgg= +github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= +github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= +github.com/hashicorp/go-cty v1.4.1-0.20200414143053-d3edf31b6320 h1:1/D3zfFHttUKaCaGKZ/dR2roBXv0vKbSCnssIldfQdI= +github.com/hashicorp/go-cty v1.4.1-0.20200414143053-d3edf31b6320/go.mod h1:EiZBMaudVLy8fmjf9Npq1dq9RalhveqZG5w/yz3mHWs= +github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k= +github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= +github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= +github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= +github.com/hashicorp/go-plugin v1.6.3 h1:xgHB+ZUSYeuJi96WtxEjzi23uh7YQpznjGh0U0UUrwg= +github.com/hashicorp/go-plugin v1.6.3/go.mod h1:MRobyh+Wc/nYy1V4KAXUiYfzxoYhs7V1mlH1Z7iY2h0= +github.com/hashicorp/go-retryablehttp v0.7.7 h1:C8hUCYzor8PIfXHa4UrZkU4VvK8o9ISHxT2Q8+VepXU= +github.com/hashicorp/go-retryablehttp v0.7.7/go.mod h1:pkQpWZeYWskR+D1tR2O5OcBFOxfA7DoAO6xtkuQnHTk= +github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8= +github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-version v1.7.0 h1:5tqGy27NaOTB8yJKUZELlFAS/LTKJkrmONwQKeRZfjY= +github.com/hashicorp/go-version v1.7.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/hashicorp/hc-install v0.8.0 h1:LdpZeXkZYMQhoKPCecJHlKvUkQFixN/nvyR1CdfOLjI= +github.com/hashicorp/hc-install v0.8.0/go.mod h1:+MwJYjDfCruSD/udvBmRB22Nlkwwkwf5sAB6uTIhSaU= +github.com/hashicorp/hcl/v2 v2.21.0 h1:lve4q/o/2rqwYOgUg3y3V2YPyD1/zkCLGjIV74Jit14= +github.com/hashicorp/hcl/v2 v2.21.0/go.mod h1:62ZYHrXgPoX8xBnzl8QzbWq4dyDsDtfCRgIq1rbJEvA= +github.com/hashicorp/logutils v1.0.0 h1:dLEQVugN8vlakKOUE3ihGLTZJRB4j+M2cdTm/ORI65Y= +github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= +github.com/hashicorp/terraform-exec v0.21.0 h1:uNkLAe95ey5Uux6KJdua6+cv8asgILFVWkd/RG0D2XQ= +github.com/hashicorp/terraform-exec v0.21.0/go.mod h1:1PPeMYou+KDUSSeRE9szMZ/oHf4fYUmB923Wzbq1ICg= +github.com/hashicorp/terraform-json v0.22.1 h1:xft84GZR0QzjPVWs4lRUwvTcPnegqlyS7orfb5Ltvec= +github.com/hashicorp/terraform-json v0.22.1/go.mod h1:JbWSQCLFSXFFhg42T7l9iJwdGXBYV8fmmD6o/ML4p3A= +github.com/hashicorp/terraform-plugin-framework v1.12.0 h1:7HKaueHPaikX5/7cbC1r9d1m12iYHY+FlNZEGxQ42CQ= +github.com/hashicorp/terraform-plugin-framework v1.12.0/go.mod h1:N/IOQ2uYjW60Jp39Cp3mw7I/OpC/GfZ0385R0YibmkE= +github.com/hashicorp/terraform-plugin-go v0.24.0 h1:2WpHhginCdVhFIrWHxDEg6RBn3YaWzR2o6qUeIEat2U= +github.com/hashicorp/terraform-plugin-go v0.24.0/go.mod h1:tUQ53lAsOyYSckFGEefGC5C8BAaO0ENqzFd3bQeuYQg= +github.com/hashicorp/terraform-plugin-log v0.9.0 h1:i7hOA+vdAItN1/7UrfBqBwvYPQ9TFvymaRGZED3FCV0= +github.com/hashicorp/terraform-plugin-log v0.9.0/go.mod h1:rKL8egZQ/eXSyDqzLUuwUYLVdlYeamldAHSxjUFADow= +github.com/hashicorp/terraform-plugin-sdk/v2 v2.34.0 h1:kJiWGx2kiQVo97Y5IOGR4EMcZ8DtMswHhUuFibsCQQE= +github.com/hashicorp/terraform-plugin-sdk/v2 v2.34.0/go.mod h1:sl/UoabMc37HA6ICVMmGO+/0wofkVIRxf+BMb/dnoIg= +github.com/hashicorp/terraform-plugin-testing v1.10.0 h1:2+tmRNhvnfE4Bs8rB6v58S/VpqzGC6RCh9Y8ujdn+aw= +github.com/hashicorp/terraform-plugin-testing v1.10.0/go.mod h1:iWRW3+loP33WMch2P/TEyCxxct/ZEcCGMquSLSCVsrc= +github.com/hashicorp/terraform-registry-address v0.2.5 h1:2GTftHqmUhVOeuu9CW3kwDkRe4pcBDq0uuK5VJngU1M= +github.com/hashicorp/terraform-registry-address v0.2.5/go.mod h1:PpzXWINwB5kuVS5CA7m1+eO2f1jKb5ZDIxrOPfpnGkg= +github.com/hashicorp/terraform-svchost v0.1.1 h1:EZZimZ1GxdqFRinZ1tpJwVxxt49xc/S52uzrw4x0jKQ= +github.com/hashicorp/terraform-svchost v0.1.1/go.mod h1:mNsjQfZyf/Jhz35v6/0LWcv26+X7JPS+buii2c9/ctc= +github.com/hashicorp/yamux v0.1.1 h1:yrQxtgseBDrq9Y652vSRDvsKCJKOUD+GzTS4Y0Y8pvE= +github.com/hashicorp/yamux v0.1.1/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ= +github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= +github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= +github.com/jhump/protoreflect v1.15.1 h1:HUMERORf3I3ZdX05WaQ6MIpd/NJ434hTp5YiKgfCL6c= +github.com/jhump/protoreflect v1.15.1/go.mod h1:jD/2GMKKE6OqX8qTjhADU1e6DShO+gavG9e0Q693nKo= +github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4= +github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= +github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= +github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= +github.com/mitchellh/go-testing-interface v1.14.1 h1:jrgshOhYAUVNMAJiKbEu7EqAwgJJ2JqpQmpLJOu07cU= +github.com/mitchellh/go-testing-interface v1.14.1/go.mod h1:gfgS7OtZj6MA4U1UrDRp04twqAjfvlZyCfX3sDjEym8= +github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0= +github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0= +github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= +github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= +github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= +github.com/oklog/run v1.1.0 h1:GEenZ1cK0+q0+wsJew9qUg/DyD8k3JzYsZAi5gYi2mA= +github.com/oklog/run v1.1.0/go.mod h1:sVPdnTZT1zYwAJeCMu2Th4T21pA3FPOQRfWjQlk7DVU= +github.com/pjbgf/sha1cd v0.3.0 h1:4D5XXmUUBUl/xQ6IjCkEAbqXskkq/4O7LmGn0AqMDs4= +github.com/pjbgf/sha1cd v0.3.0/go.mod h1:nZ1rrWOcGJ5uZgEEVL1VUM9iRQiZvWdbZjkKyFzPPsI= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= +github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= +github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= +github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8= +github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= +github.com/skeema/knownhosts v1.2.2 h1:Iug2P4fLmDw9f41PB6thxUkNUkJzB5i+1/exaj40L3A= +github.com/skeema/knownhosts v1.2.2/go.mod h1:xYbVRSPxqBZFrdmDyMmsOs+uX1UZC3nTN3ThzgDxUwo= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/vmihailenco/msgpack v3.3.3+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk= +github.com/vmihailenco/msgpack v4.0.4+incompatible h1:dSLoQfGFAo3F6OoNhwUmLwVgaUXK79GlxNBwueZn0xI= +github.com/vmihailenco/msgpack v4.0.4+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk= +github.com/vmihailenco/msgpack/v5 v5.4.1 h1:cQriyiUvjTwOHg8QZaPihLWeRAAVoCpE00IUPn0Bjt8= +github.com/vmihailenco/msgpack/v5 v5.4.1/go.mod h1:GaZTsDaehaPpQVyxrf5mtQlH+pc21PIudVV/E3rRQok= +github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g= +github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds= +github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM= +github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +github.com/zclconf/go-cty v1.15.0 h1:tTCRWxsexYUmtt/wVxgDClUe+uQusuI443uL6e+5sXQ= +github.com/zclconf/go-cty v1.15.0/go.mod h1:VvMs5i0vgZdhYawQNq5kePSpLAoz8u1xvZgrPIxfnZE= +github.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940 h1:4r45xpDWB6ZMSMNJFMOjqrGHynW3DIBuR2H9j0ug+Mo= +github.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940/go.mod h1:CmBdvvj3nqzfzJ6nTCIwDTPZ56aVGvDrmztiO5g3qrM= +gitlab.com/yawning/edwards25519-extra v0.0.0-20231005122941-2149dcafc266 h1:IvjshROr8z24+UCiOe/90cUWt3QDr8Rt+VkUjZsn+i0= +gitlab.com/yawning/edwards25519-extra v0.0.0-20231005122941-2149dcafc266/go.mod h1:K/3SQWdJL6udzwInHk1gaYaECYxMp9dDayniPq6gCSo= +go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= +go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= +go.opentelemetry.io/otel v1.34.0 h1:zRLXxLCgL1WyKsPVrgbSdMN4c0FMkDAskSTQP+0hdUY= +go.opentelemetry.io/otel v1.34.0/go.mod h1:OWFPOQ+h4G8xpyjgqo4SxJYdDQ/qmRH+wivy7zzx9oI= +go.opentelemetry.io/otel/metric v1.34.0 h1:+eTR3U0MyfWjRDhmFMxe2SsW64QrZ84AOhvqS7Y+PoQ= +go.opentelemetry.io/otel/metric v1.34.0/go.mod h1:CEDrp0fy2D0MvkXE+dPV7cMi8tWZwX3dmaIhwPOaqHE= +go.opentelemetry.io/otel/sdk v1.34.0 h1:95zS4k/2GOy069d321O8jWgYsW3MzVV+KuSPKp7Wr1A= +go.opentelemetry.io/otel/sdk v1.34.0/go.mod h1:0e/pNiaMAqaykJGKbi+tSjWfNNHMTxoC9qANsCzbyxU= +go.opentelemetry.io/otel/sdk/metric v1.34.0 h1:5CeK9ujjbFVL5c1PhLuStg1wxA7vQv7ce1EK0Gyvahk= +go.opentelemetry.io/otel/sdk/metric v1.34.0/go.mod h1:jQ/r8Ze28zRKoNRdkjCZxfs6YvBTG1+YIqyFVFYec5w= +go.opentelemetry.io/otel/trace v1.34.0 h1:+ouXS2V8Rd4hp4580a8q23bg0azF2nI8cqLYnC8mh/k= +go.opentelemetry.io/otel/trace v1.34.0/go.mod h1:Svm7lSjQD7kG7KJ/MUHPVXSDGz2OX4h0M2jHBhmSfRE= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= +golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE= +golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4= +golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/net v0.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY= +golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610= +golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20= +golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= +golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0= +golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/tools v0.29.0 h1:Xx0h3TtM9rzQpQuR4dKLrdglAmCEN5Oi+P74JdhdzXE= +golang.org/x/tools v0.29.0/go.mod h1:KMQVMRsVxU6nHCFXrBPhDB8XncLNLM0lIy/F14RP588= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM= +google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250218202821-56aae31c358a h1:51aaUVRocpvUOSQKM6Q7VuoaktNIaMCLuhZB6DKksq4= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250218202821-56aae31c358a/go.mod h1:uRxBH1mhmO8PGhU89cMcHaXKZqO+OfakD8QQO0oYwlQ= +google.golang.org/grpc v1.72.1 h1:HR03wO6eyZ7lknl75XlxABNVLLFc2PAb6mHlYh756mA= +google.golang.org/grpc v1.72.1/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= +google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= +gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/go.sum.broken b/go.sum.broken new file mode 100644 index 0000000..5093bb4 --- /dev/null +++ b/go.sum.broken @@ -0,0 +1,134 @@ +github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI= +github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= +github.com/Masterminds/semver/v3 v3.2.1 h1:RN9w6+7QoMeJVGyfmbcgs28Br8cvmnucEXnY0rYXWg0= +github.com/Masterminds/semver/v3 v3.2.1/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ= +github.com/Masterminds/sprig/v3 v3.2.3 h1:eL2fZNezLomi0uOLqjQoN6BfsDD+fyLtgbJMAj9n6YA= +github.com/Masterminds/sprig/v3 v3.2.3/go.mod h1:rXcFaZ2zZbLRJv/xSysmlgIM1u11eBaRMhvYXJNkGuM= +github.com/ProtonMail/go-crypto v1.0.0 h1:LRuvITjQWX+WIfr930YHG2HNfjR1uOfyf5vE0kC2U78= +github.com/ProtonMail/go-crypto v1.0.0/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0= +github.com/agext/levenshtein v1.2.3 h1:YB2fHEn0UJagG8T1rrWknE3ZQzWM06O8AMAatNn7lmo= +github.com/agext/levenshtein v1.2.3/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558= +github.com/apparentlymart/go-textseg/v15 v15.0.0 h1:uYvfpb3DyLSCGWnctWKGj857c6ew1u1fNQOlOtuGxQY= +github.com/apparentlymart/go-textseg/v15 v15.0.0/go.mod h1:K8XmNZdhEBkdlyDdvbmmsvpAG721bKi0joRfFdHIWJ4= +github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU= +github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= +github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRjRuY4O1FJmsbNOvSFE= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/hashicorp/cli v1.1.6 h1:KMHVPR7kZruGy7vbOn5W2M7PcOWJwkXaHLe8rjUV4dY= +github.com/hashicorp/cli v1.1.6/go.mod h1:6p7uk6XBr7g+W4hy7N7NYqYSBXPa5Z1C/VvQpPCMH9Y= +github.com/hashicorp/go-checkpoint v0.5.0 h1:MFYpPZCnQqQTE18jFwSII6eUQrD/oxMFp3mlgcqk5mU= +github.com/hashicorp/go-checkpoint v0.5.0/go.mod h1:7nfLNL10NsxqO4iWuW6tWW0HjZuDrwkBuEQsVcpCOgg= +github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= +github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= +github.com/hashicorp/go-cty v1.4.1-0.20200414143053-d3edf31b6320 h1:1/D3zfFHttUKaCaGKZ/dR2roBXv0vKbSCnssIldfQdI= +github.com/hashicorp/go-cty v1.4.1-0.20200414143053-d3edf31b6320/go.mod h1:EiZBMaudVLy8fmjf9Npq1dq9RalhveqZG5w/yz3mHWs= +github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k= +github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= +github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= +github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= +github.com/hashicorp/go-plugin v1.6.1 h1:P7MR2UP6gNKGPp+y7EZw2kOiq4IR9WiqLvp0XOsVdwI= +github.com/hashicorp/go-plugin v1.6.1/go.mod h1:XPHFku2tFo3o3QKFgSYo+cghcUhw1NA1hZyMK0PWAw0= +github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKdqZHlqU+s3zB+A/8bV3psmIos= +github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-version v1.7.0 h1:5tqGy27NaOTB8yJKUZELlFAS/LTKJkrmONwQKeRZfjY= +github.com/hashicorp/go-version v1.7.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/hashicorp/hc-install v0.8.0 h1:W5l9fjCIhG/QAImWR0LE4AOKiDUxGR3+V8SCMvQnb2Y= +github.com/hashicorp/hc-install v0.8.0/go.mod h1:vRJU/PIsZbJBT0DgpXzRH7ZQJgE8YlGV2/ZnbTsJgJ8= +github.com/hashicorp/hcl/v2 v2.21.0 h1:lve4q/o/2rqwYOgUg3y3V2YPyD2RtlUIQ/2UyfrxYtI= +github.com/hashicorp/hcl/v2 v2.21.0/go.mod h1:62ZYHrXgPoX8xBnzl8QzbWq4dyDsDtfCRgIq1rbJEvA= +github.com/hashicorp/logutils v1.0.0 h1:dLEQVugN8vlakKOUE3ihGLTZJRB4j+M2cdTm/ORI65Y= +github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= +github.com/hashicorp/terraform-exec v0.21.0 h1:uNkLAe95ey5Uux6KJdua6+cv8asgILFVWkd/RG0D2XQ= +github.com/hashicorp/terraform-exec v0.21.0/go.mod h1:1PPeMYou+KDUSSeRE9szMZ/oHf4fYUmB923Wzbq1ICg= +github.com/hashicorp/terraform-json v0.22.1 h1:xft84GZR0QzjPVWs4lRUwvTcPnegqlyS7orfb5Ltvec= +github.com/hashicorp/terraform-json v0.22.1/go.mod h1:JbWSQCLFSXFFhg42T7l9iJwdGXBYV8fmmD6o/ML4p3A= +github.com/hashicorp/terraform-plugin-docs v0.19.4 h1:CfI1r+Fb8jDjdADp5GTDPYB/LEU0CTJ0NkF/1RSSB8A= +github.com/hashicorp/terraform-plugin-docs v0.19.4/go.mod h1:DCF27O5RpcNm/p4v7WMTRmP1DQSA9ZMNj3ux6Nzx7MY= +github.com/hashicorp/terraform-plugin-framework v1.12.0 h1:CZBXAzKXNUCJXAz7my/4LgWDBbKIz+8IW7E6wlJFbOo= +github.com/hashicorp/terraform-plugin-framework v1.12.0/go.mod h1:k0btlrW8IMR7Gq+k2zJzqUK8MhLeR2RSrOGNw8qODmU= +github.com/hashicorp/terraform-plugin-go v0.23.0 h1:AALVuU1gD1kPb48aPQUjug9Ir/125t+AAurhqphJ2Co= +github.com/hashicorp/terraform-plugin-go v0.23.0/go.mod h1:1E3Cr9h2vMlahWMbsSEcNrOCxovCZhOOIXjFHbjc/lQ= +github.com/hashicorp/terraform-plugin-log v0.9.0 h1:i7hOA+vdAItN1/7UrfBqBwvYPQ9TFvymaRGZED3FCV0= +github.com/hashicorp/terraform-plugin-log v0.9.0/go.mod h1:rKL8egZQ/eXSyDqzLUuwUYLVdlYeamldAHSxjUFADow= +github.com/hashicorp/terraform-plugin-sdk/v2 v2.34.0 h1:kJoLKl3xJB54AtNsLvGZVDmzf+qzUlqOTe4jqp7L+ZU= +github.com/hashicorp/terraform-plugin-sdk/v2 v2.34.0/go.mod h1:sl/UbXl1zzUQupPCavdMdAQHGGODhSgVjrOF3Nd1UrA= +github.com/hashicorp/terraform-plugin-testing v1.10.0 h1:j/2S0ZM9m7QVm8b3t2B4PqT4PmQbxkM/CgJnr0YFGyk= +github.com/hashicorp/terraform-plugin-testing v1.10.0/go.mod h1:t7O9LSoYLyT88fT9jEX7sFcMSvKM7pU4pDsb6PrSdq8= +github.com/hashicorp/terraform-registry-address v0.2.3 h1:2TAiKJ1A3MAkZlH1YI/aTVcLZRu7JseiXNRHbOAyoTI= +github.com/hashicorp/terraform-registry-address v0.2.3/go.mod h1:lFHA76T8jfQteVfT7caREqguFrW3c4MFSPhZB7HHgUM= +github.com/hashicorp/terraform-svchost v0.1.1 h1:EZZimZ1GxdqFRinZ1tpJwVxxt49xc/S52uzrw4x0jKQ= +github.com/hashicorp/terraform-svchost v0.1.1/go.mod h1:mNsjQfZyf/Jhz35v6/0LWcv26+X7JPS+buii2c9/ctc= +github.com/hashicorp/yamux v0.1.1 h1:yrQxtgseBDrq9Y652vSRDvsKCJKOUD+GzTS4Y0Y8pvE= +github.com/hashicorp/yamux v0.1.1/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ= +github.com/huandu/xstrings v1.4.0 h1:D17IlohoQq4UcpqD7fDk80P7l+lwAmlFaBHgOipl2FU= +github.com/huandu/xstrings v1.4.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= +github.com/imdario/mergo v0.3.13 h1:lFzP57bqS/wsqKssCGmtLAb8A0wKjLGrve2q3PPVcBk= +github.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK2O4oXg= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mitchellh/cli v1.1.5 h1:OxRIeJXpAMztws/XHlN2vu6imG5Dpq+j61AzAX5fLng= +github.com/mitchellh/cli v1.1.5/go.mod h1:v8+iFts2sPIKUV1ltktPXMCC8fumSKFItNcD2cLtRR4= +github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= +github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= +github.com/mitchellh/go-testing-interface v1.14.1 h1:jrgshOhYAUVNMAJiKbEu7EqAwgJJ2JqpQmpLJOu07cU= +github.com/mitchellh/go-testing-interface v1.14.1/go.mod h1:gfgS7OtZj6MA4U1UrDRp04twqAjfvlZyCfX3sDjEym8= +github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0= +github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0= +github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= +github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxBiscop= +github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= +github.com/oklog/run v1.1.0 h1:GEenZ1cK0+q0+wsJew9qUg/DyD8k3JzYsZAi5gYi2mA= +github.com/oklog/run v1.1.0/go.mod h1:sVPdnTZT1zYwAJeCMu2Th4T21pA3FPOQRfWjQlk7DVU= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/posener/complete v1.2.3 h1:NP0eAhjcjImqslEwo/1hq7gpajME0fTLTezBKDqfXqo= +github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s= +github.com/russross/blackfriday v1.6.0 h1:KqfZb0pUVN2lYqZUYRddxF4OR8ZMURnJIG5Y3VRLtww= +github.com/russross/blackfriday v1.6.0/go.mod h1:ti0ldHuxg49ri4ksnFxlkCfN+hvslNlmVHqNRXXJNAY= +github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8= +github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWFro0E= +github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w= +github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGPiK/92rKx= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/vmihailenco/msgpack v4.0.4+incompatible h1:dSLoQfGFAo3F6OoNhwUmLwVgaUXK79GlxNBwueZn0xI= +github.com/vmihailenco/msgpack v4.0.4+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk= +github.com/vmihailenco/msgpack/v5 v5.4.1 h1:cQriyiUvjTwOHg8QZaPihLWeRAAVoCpE00IUPn0Bjt8= +github.com/vmihailenco/msgpack/v5 v5.4.1/go.mod h1:GaZTsDaehaPpQVyxrf5mtQlH+pc21PIudVV/E3rRQok= +github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g= +github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds= +github.com/zclconf/go-cty v1.15.0 h1:tTCRWxsexYUmtt/wVxgDClUe+uQusuI443uL6e+5sXQ= +github.com/zclconf/go-cty v1.15.0/go.mod h1:VvMs5i0vgZdhYawQNq5kePSpLAoz8u1xvZgrPIxfnZE= +gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/lyrebird v0.4.0 h1:NHnO+9dGWkXpSWwC8Y3xHu9PTLwDfKWYKQeNS1tPBR0= +gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/lyrebird v0.4.0/go.mod h1:Kxv+0sWNVD5VrFdIGQwE8VWLRtOg4g4lxr5IiMH74o= +golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= +golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= +golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA= +golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs= +golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= +golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= +golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg= +golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= +google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM= +google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240604185151-ef581f913117 h1:1GBuWVLM/KMVUv1t1En5Gs+gFZCNd360GGb4sSxtrhU= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240604185151-ef581f913117/go.mod h1:EfXuqaE1J41VCDicxHzUDm+8rk+7ZdXzHV0IhO/I6s0= +google.golang.org/grpc v1.66.0 h1:DibZuoBznOxbDQxRINckZcUvnCEvrW9pcWIE2yF9r1c= +google.golang.org/grpc v1.66.0/go.mod h1:s3/l6xSSCURdVfAnL+TqCNMyTDAGN6+lZeVxnZR128Y= +google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg= +google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= \ No newline at end of file diff --git a/internal/provider/provider.go b/internal/provider/provider.go new file mode 100644 index 0000000..beb9407 --- /dev/null +++ b/internal/provider/provider.go @@ -0,0 +1,85 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (c) 2025 Abel Luck + +package provider + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-framework/datasource" + "github.com/hashicorp/terraform-plugin-framework/function" + "github.com/hashicorp/terraform-plugin-framework/provider" + "github.com/hashicorp/terraform-plugin-framework/provider/schema" + "github.com/hashicorp/terraform-plugin-framework/resource" +) + +// Ensure ScaffoldingProvider satisfies various provider interfaces. +var _ provider.Provider = &ScaffoldingProvider{} +var _ provider.ProviderWithFunctions = &ScaffoldingProvider{} + +// ScaffoldingProvider defines the provider implementation. +type ScaffoldingProvider struct { + // version is set to the provider version on release, "dev" when the + // provider is built and ran locally, and "test" when running acceptance + // testing. + version string +} + +// ScaffoldingProviderModel describes the provider data model. +type ScaffoldingProviderModel struct { +} + +func (p *ScaffoldingProvider) Metadata(ctx context.Context, req provider.MetadataRequest, resp *provider.MetadataResponse) { + resp.TypeName = "tor" + resp.Version = p.version +} + +func (p *ScaffoldingProvider) Schema(ctx context.Context, req provider.SchemaRequest, resp *provider.SchemaResponse) { + resp.Schema = schema.Schema{ + MarkdownDescription: "The Tor provider generates cryptographic identity materials for obfs4 Tor bridges, enabling stateless bridge deployments.", + Attributes: map[string]schema.Attribute{}, + } +} + +func (p *ScaffoldingProvider) Configure(ctx context.Context, req provider.ConfigureRequest, resp *provider.ConfigureResponse) { + var data ScaffoldingProviderModel + + resp.Diagnostics.Append(req.Config.Get(ctx, &data)...) + + if resp.Diagnostics.HasError() { + return + } + + // No configuration needed for crypto generation provider + // Set nil for data sources and resources since no client is needed + resp.DataSourceData = nil + resp.ResourceData = nil +} + +func (p *ScaffoldingProvider) Resources(ctx context.Context) []func() resource.Resource { + return []func() resource.Resource{ + NewTorObfs4StateResource, + NewTorRelayIdentityRsaResource, + NewTorRelayIdentityEd25519Resource, + } +} + + +func (p *ScaffoldingProvider) DataSources(ctx context.Context) []func() datasource.DataSource { + return []func() datasource.DataSource{ + NewTorObfs4BridgeLineDataSource, + } +} + +func (p *ScaffoldingProvider) Functions(ctx context.Context) []func() function.Function { + return []func() function.Function{ + } +} + +func New(version string) func() provider.Provider { + return func() provider.Provider { + return &ScaffoldingProvider{ + version: version, + } + } +} \ No newline at end of file diff --git a/internal/provider/provider_test.go b/internal/provider/provider_test.go new file mode 100644 index 0000000..550db8b --- /dev/null +++ b/internal/provider/provider_test.go @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (c) 2025 Abel Luck + +package provider + +import ( + "testing" + + "github.com/hashicorp/terraform-plugin-framework/providerserver" + "github.com/hashicorp/terraform-plugin-go/tfprotov6" +) + +// testAccProtoV6ProviderFactories are used to instantiate a provider during +// acceptance testing. The factory function will be invoked for every Terraform +// CLI command executed to create a provider server to which the CLI can +// reattach. +var testAccProtoV6ProviderFactories = map[string]func() (tfprotov6.ProviderServer, error){ + "tor": providerserver.NewProtocol6WithError(New("test")()), +} + +func testAccPreCheck(t *testing.T) { + // You can add code here to run prior to any test case execution, for example assertions + // about the appropriate environment variables being set are common to see in a pre-check + // function. +} \ No newline at end of file diff --git a/internal/provider/tor_obfs4_bridge_line_data_source.go b/internal/provider/tor_obfs4_bridge_line_data_source.go new file mode 100644 index 0000000..18a125e --- /dev/null +++ b/internal/provider/tor_obfs4_bridge_line_data_source.go @@ -0,0 +1,107 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (c) 2025 Abel Luck + +package provider + +import ( + "context" + "fmt" + + "github.com/hashicorp/terraform-plugin-framework/datasource" + "github.com/hashicorp/terraform-plugin-framework/datasource/schema" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-log/tflog" +) + +// Ensure provider defined types fully satisfy framework interfaces. +var _ datasource.DataSource = &TorObfs4BridgeLineDataSource{} + +func NewTorObfs4BridgeLineDataSource() datasource.DataSource { + return &TorObfs4BridgeLineDataSource{} +} + +// TorObfs4BridgeLineDataSource defines the data source implementation. +type TorObfs4BridgeLineDataSource struct{} + +// TorObfs4BridgeLineDataSourceModel describes the data source data model. +type TorObfs4BridgeLineDataSourceModel struct { + Id types.String `tfsdk:"id"` + IpAddress types.String `tfsdk:"ip_address"` + Port types.Int64 `tfsdk:"port"` + IdentityFingerprintSha1 types.String `tfsdk:"identity_fingerprint_sha1"` + Obfs4StateCertificate types.String `tfsdk:"obfs4_state_certificate"` + Obfs4StateIatMode types.Int64 `tfsdk:"obfs4_state_iat_mode"` + BridgeLine types.String `tfsdk:"bridge_line"` +} + +func (d *TorObfs4BridgeLineDataSource) Metadata(ctx context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_obfs4_bridge_line" +} + +func (d *TorObfs4BridgeLineDataSource) Schema(ctx context.Context, req datasource.SchemaRequest, resp *datasource.SchemaResponse) { + resp.Schema = schema.Schema{ + MarkdownDescription: "Generates a complete Tor bridge line using obfs4 state and network details", + + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + Computed: true, + MarkdownDescription: "Data source identifier", + }, + "ip_address": schema.StringAttribute{ + Required: true, + MarkdownDescription: "Bridge IP address", + }, + "port": schema.Int64Attribute{ + Required: true, + MarkdownDescription: "Bridge port number", + }, + "identity_fingerprint_sha1": schema.StringAttribute{ + Required: true, + MarkdownDescription: "SHA1 fingerprint of the RSA identity key", + }, + "obfs4_state_certificate": schema.StringAttribute{ + Required: true, + MarkdownDescription: "Base64-encoded certificate from tor_obfs4_state resource", + }, + "obfs4_state_iat_mode": schema.Int64Attribute{ + Required: true, + MarkdownDescription: "Inter-Arrival Time mode from tor_obfs4_state resource", + }, + "bridge_line": schema.StringAttribute{ + Computed: true, + MarkdownDescription: "Complete bridge line ready for client use", + }, + }, + } +} + +func (d *TorObfs4BridgeLineDataSource) Configure(ctx context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) { + // No configuration needed for this data source +} + +func (d *TorObfs4BridgeLineDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { + var data TorObfs4BridgeLineDataSourceModel + + // Read Terraform configuration data into the model + resp.Diagnostics.Append(req.Config.Get(ctx, &data)...) + if resp.Diagnostics.HasError() { + return + } + + // Generate bridge line using the provided inputs + bridgeLine := fmt.Sprintf("Bridge obfs4 %s:%d %s cert=%s iat-mode=%d", + data.IpAddress.ValueString(), + data.Port.ValueInt64(), + data.IdentityFingerprintSha1.ValueString(), + data.Obfs4StateCertificate.ValueString(), + data.Obfs4StateIatMode.ValueInt64()) + + // Set computed values + data.Id = types.StringValue(fmt.Sprintf("bridge-%s-%d", data.IpAddress.ValueString(), data.Port.ValueInt64())) + data.BridgeLine = types.StringValue(bridgeLine) + + tflog.Trace(ctx, "read bridge line data source") + + // Save data into Terraform state + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} \ No newline at end of file diff --git a/internal/provider/tor_obfs4_bridge_line_data_source_test.go b/internal/provider/tor_obfs4_bridge_line_data_source_test.go new file mode 100644 index 0000000..e28a226 --- /dev/null +++ b/internal/provider/tor_obfs4_bridge_line_data_source_test.go @@ -0,0 +1,197 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (c) 2025 Abel Luck + +package provider + +import ( + "fmt" + "regexp" + "testing" + + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/terraform" +) + +func TestTorObfs4BridgeLineDataSource(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + // Read testing + { + Config: testTorObfs4BridgeLineDataSourceConfig(), + Check: resource.ComposeAggregateTestCheckFunc( + // Check that the data source was created + resource.TestCheckResourceAttrSet("data.tor_obfs4_bridge_line.test", "id"), + + // Check input values are preserved + resource.TestCheckResourceAttr("data.tor_obfs4_bridge_line.test", "ip_address", "192.0.2.1"), + resource.TestCheckResourceAttr("data.tor_obfs4_bridge_line.test", "port", "443"), + + // Check computed values are generated + resource.TestCheckResourceAttrSet("data.tor_obfs4_bridge_line.test", "bridge_line"), + + // Check bridge line format + resource.TestMatchResourceAttr("data.tor_obfs4_bridge_line.test", "bridge_line", + regexp.MustCompile(`^Bridge obfs4 192\.0\.2\.1:443 [0-9a-f]{40} cert=[A-Za-z0-9+/]+ iat-mode=[0-2]$`)), + + // Check that input values are used correctly + resource.TestCheckResourceAttrPair("data.tor_obfs4_bridge_line.test", "identity_fingerprint_sha1", "tor_relay_identity_rsa.test", "public_key_fingerprint_sha1"), + resource.TestCheckResourceAttrPair("data.tor_obfs4_bridge_line.test", "obfs4_state_certificate", "tor_obfs4_state.test", "certificate"), + resource.TestCheckResourceAttrPair("data.tor_obfs4_bridge_line.test", "obfs4_state_iat_mode", "tor_obfs4_state.test", "iat_mode"), + + // Validate bridge line consistency + testTorObfs4BridgeLineConsistency("data.tor_obfs4_bridge_line.test"), + ), + }, + }, + }) +} + +func TestTorObfs4BridgeLineDataSourceWithCustomIATMode(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + { + Config: testTorObfs4BridgeLineDataSourceConfigWithIATMode(2), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("data.tor_obfs4_bridge_line.test", "obfs4_state_iat_mode", "2"), + resource.TestMatchResourceAttr("data.tor_obfs4_bridge_line.test", "bridge_line", + regexp.MustCompile(`iat-mode=2$`)), + testTorObfs4BridgeLineConsistency("data.tor_obfs4_bridge_line.test"), + ), + }, + }, + }) +} + +func TestTorObfs4BridgeLineDataSourceMultiplePorts(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + { + Config: testTorObfs4BridgeLineDataSourceConfigMultiplePorts(), + Check: resource.ComposeAggregateTestCheckFunc( + // Check first bridge line + resource.TestCheckResourceAttr("data.tor_obfs4_bridge_line.test1", "ip_address", "192.0.2.1"), + resource.TestCheckResourceAttr("data.tor_obfs4_bridge_line.test1", "port", "443"), + resource.TestMatchResourceAttr("data.tor_obfs4_bridge_line.test1", "bridge_line", + regexp.MustCompile(`^Bridge obfs4 192\.0\.2\.1:443`)), + + // Check second bridge line (same obfs4_state, different port) + resource.TestCheckResourceAttr("data.tor_obfs4_bridge_line.test2", "ip_address", "192.0.2.1"), + resource.TestCheckResourceAttr("data.tor_obfs4_bridge_line.test2", "port", "80"), + resource.TestMatchResourceAttr("data.tor_obfs4_bridge_line.test2", "bridge_line", + regexp.MustCompile(`^Bridge obfs4 192\.0\.2\.1:80`)), + + // Verify both use the same certificate and fingerprint (same obfs4_state) + resource.TestCheckResourceAttrPair("data.tor_obfs4_bridge_line.test1", "obfs4_state_certificate", "data.tor_obfs4_bridge_line.test2", "obfs4_state_certificate"), + resource.TestCheckResourceAttrPair("data.tor_obfs4_bridge_line.test1", "identity_fingerprint_sha1", "data.tor_obfs4_bridge_line.test2", "identity_fingerprint_sha1"), + ), + }, + }, + }) +} + +// Test configuration functions +func testTorObfs4BridgeLineDataSourceConfig() string { + return ` +resource "tor_relay_identity_rsa" "test" {} + +resource "tor_relay_identity_ed25519" "test" {} + +resource "tor_obfs4_state" "test" { + rsa_identity_private_key = tor_relay_identity_rsa.test.private_key_pem + ed25519_identity_private_key = tor_relay_identity_ed25519.test.private_key_pem +} + +data "tor_obfs4_bridge_line" "test" { + ip_address = "192.0.2.1" + port = 443 + identity_fingerprint_sha1 = tor_relay_identity_rsa.test.public_key_fingerprint_sha1 + obfs4_state_certificate = tor_obfs4_state.test.certificate + obfs4_state_iat_mode = tor_obfs4_state.test.iat_mode +} +` +} + +func testTorObfs4BridgeLineDataSourceConfigWithIATMode(iatMode int) string { + return fmt.Sprintf(` +resource "tor_relay_identity_rsa" "test" {} + +resource "tor_relay_identity_ed25519" "test" {} + +resource "tor_obfs4_state" "test" { + rsa_identity_private_key = tor_relay_identity_rsa.test.private_key_pem + ed25519_identity_private_key = tor_relay_identity_ed25519.test.private_key_pem + iat_mode = %d +} + +data "tor_obfs4_bridge_line" "test" { + ip_address = "192.0.2.1" + port = 443 + identity_fingerprint_sha1 = tor_relay_identity_rsa.test.public_key_fingerprint_sha1 + obfs4_state_certificate = tor_obfs4_state.test.certificate + obfs4_state_iat_mode = tor_obfs4_state.test.iat_mode +} +`, iatMode) +} + +func testTorObfs4BridgeLineDataSourceConfigMultiplePorts() string { + return ` +resource "tor_relay_identity_rsa" "test" {} + +resource "tor_relay_identity_ed25519" "test" {} + +resource "tor_obfs4_state" "test" { + rsa_identity_private_key = tor_relay_identity_rsa.test.private_key_pem + ed25519_identity_private_key = tor_relay_identity_ed25519.test.private_key_pem +} + +data "tor_obfs4_bridge_line" "test1" { + ip_address = "192.0.2.1" + port = 443 + identity_fingerprint_sha1 = tor_relay_identity_rsa.test.public_key_fingerprint_sha1 + obfs4_state_certificate = tor_obfs4_state.test.certificate + obfs4_state_iat_mode = tor_obfs4_state.test.iat_mode +} + +data "tor_obfs4_bridge_line" "test2" { + ip_address = "192.0.2.1" + port = 80 + identity_fingerprint_sha1 = tor_relay_identity_rsa.test.public_key_fingerprint_sha1 + obfs4_state_certificate = tor_obfs4_state.test.certificate + obfs4_state_iat_mode = tor_obfs4_state.test.iat_mode +} +` +} + +// Custom test check functions +func testTorObfs4BridgeLineConsistency(resourceName string) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[resourceName] + if !ok { + return fmt.Errorf("Resource not found: %s", resourceName) + } + + ipAddress := rs.Primary.Attributes["ip_address"] + port := rs.Primary.Attributes["port"] + fingerprint := rs.Primary.Attributes["identity_fingerprint_sha1"] + certificate := rs.Primary.Attributes["obfs4_state_certificate"] + iatMode := rs.Primary.Attributes["obfs4_state_iat_mode"] + bridgeLine := rs.Primary.Attributes["bridge_line"] + + // Verify bridge line format matches expected pattern + expectedBridgeLine := fmt.Sprintf("Bridge obfs4 %s:%s %s cert=%s iat-mode=%s", + ipAddress, port, fingerprint, certificate, iatMode) + + if bridgeLine != expectedBridgeLine { + return fmt.Errorf("Bridge line doesn't match expected format.\nExpected: %s\nActual: %s", + expectedBridgeLine, bridgeLine) + } + + return nil + } +} \ No newline at end of file diff --git a/internal/provider/tor_obfs4_state_resource.go b/internal/provider/tor_obfs4_state_resource.go new file mode 100644 index 0000000..6cc97ab --- /dev/null +++ b/internal/provider/tor_obfs4_state_resource.go @@ -0,0 +1,425 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (c) 2025 Abel Luck + +package provider + +import ( + "context" + "crypto/rsa" + "crypto/sha1" + "crypto/x509" + "encoding/base64" + "encoding/hex" + "encoding/json" + "encoding/pem" + "fmt" + "strings" + + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/int64default" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-log/tflog" + + "gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/lyrebird/common/drbg" + "gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/lyrebird/common/ntor" +) + +// Ensure provider defined types fully satisfy framework interfaces. +var _ resource.Resource = &TorObfs4StateResource{} + +func NewTorObfs4StateResource() resource.Resource { + return &TorObfs4StateResource{} +} + +// TorObfs4StateResource defines the resource implementation. +type TorObfs4StateResource struct{} + +// TorObfs4StateResourceModel describes the resource data model. +type TorObfs4StateResourceModel struct { + Id types.String `tfsdk:"id"` + RsaIdentityPrivateKey types.String `tfsdk:"rsa_identity_private_key"` + Ed25519IdentityPrivateKey types.String `tfsdk:"ed25519_identity_private_key"` + NodeId types.String `tfsdk:"node_id"` + PrivateKey types.String `tfsdk:"private_key"` + PublicKey types.String `tfsdk:"public_key"` + DrbgSeed types.String `tfsdk:"drbg_seed"` + IatMode types.Int64 `tfsdk:"iat_mode"` + Certificate types.String `tfsdk:"certificate"` + StateJson types.String `tfsdk:"state_json"` + BridgeLine types.String `tfsdk:"bridge_line"` +} + +// obfs4StateJson represents the JSON structure for the state file +type obfs4StateJson struct { + NodeId string `json:"node-id"` + PrivateKey string `json:"private-key"` + PublicKey string `json:"public-key"` + DrbgSeed string `json:"drbg-seed"` + IatMode int `json:"iat-mode"` +} + +func (r *TorObfs4StateResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_obfs4_state" +} + +func (r *TorObfs4StateResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + MarkdownDescription: "Generates obfs4 state and certificate for Tor bridges using external relay identity keys", + + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + Computed: true, + MarkdownDescription: "Resource identifier", + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + "rsa_identity_private_key": schema.StringAttribute{ + Required: true, + Sensitive: true, + MarkdownDescription: "RSA identity private key in PEM format (from tor_relay_identity_rsa resource)", + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + "ed25519_identity_private_key": schema.StringAttribute{ + Required: true, + Sensitive: true, + MarkdownDescription: "Ed25519 identity private key in PEM format (from tor_relay_identity_ed25519 resource)", + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + "node_id": schema.StringAttribute{ + Computed: true, + MarkdownDescription: "20-byte node ID in hex format", + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + "private_key": schema.StringAttribute{ + Computed: true, + Sensitive: true, + MarkdownDescription: "32-byte Curve25519 private key in hex format", + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + "public_key": schema.StringAttribute{ + Computed: true, + MarkdownDescription: "32-byte Curve25519 public key in hex format", + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + "drbg_seed": schema.StringAttribute{ + Computed: true, + Sensitive: true, + MarkdownDescription: "24-byte DRBG seed in hex format", + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + "iat_mode": schema.Int64Attribute{ + Optional: true, + Computed: true, + Default: int64default.StaticInt64(0), + MarkdownDescription: "Inter-Arrival Time mode (0=none, 1=enabled, 2=paranoid)", + }, + "certificate": schema.StringAttribute{ + Computed: true, + MarkdownDescription: "Base64-encoded certificate for bridge lines", + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + "state_json": schema.StringAttribute{ + Computed: true, + MarkdownDescription: "Complete obfs4 state in JSON format", + }, + "bridge_line": schema.StringAttribute{ + Computed: true, + MarkdownDescription: "Complete bridge line ready for client use (placeholder IP and fingerprint)", + }, + }, + } +} + +func (r *TorObfs4StateResource) Configure(ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) { + // No configuration needed for this crypto generation resource +} + +func (r *TorObfs4StateResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + var data TorObfs4StateResourceModel + + // Read Terraform plan data into the model + resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...) + if resp.Diagnostics.HasError() { + return + } + + // Generate obfs4 state + err := r.generateObfs4State(ctx, &data) + if err != nil { + resp.Diagnostics.AddError("obfs4 State Generation Error", + fmt.Sprintf("Unable to generate obfs4 state: %s", err)) + return + } + + // Set ID to a hash of the public key for uniqueness + data.Id = types.StringValue(fmt.Sprintf("obfs4-%s", data.PublicKey.ValueString()[:16])) + + tflog.Trace(ctx, "created obfs4 state resource") + + // Save data into Terraform state + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} + +func (r *TorObfs4StateResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + var data TorObfs4StateResourceModel + + // Read Terraform prior state data into the model + resp.Diagnostics.Append(req.State.Get(ctx, &data)...) + if resp.Diagnostics.HasError() { + return + } + + // For this resource, the state is static once created, so no external API calls needed + // Just ensure the computed fields are still consistent + if !data.NodeId.IsNull() && !data.PublicKey.IsNull() { + // Regenerate certificate to ensure consistency + cert, err := r.generateCertificate(data.NodeId.ValueString(), data.PublicKey.ValueString()) + if err != nil { + resp.Diagnostics.AddError("Certificate Generation Error", + fmt.Sprintf("Unable to regenerate certificate: %s", err)) + return + } + data.Certificate = types.StringValue(cert) + + // Regenerate state JSON + stateJson, err := r.generateStateJson(&data) + if err != nil { + resp.Diagnostics.AddError("State JSON Generation Error", + fmt.Sprintf("Unable to regenerate state JSON: %s", err)) + return + } + data.StateJson = types.StringValue(stateJson) + + // Regenerate bridge line + bridgeLine := r.generateBridgeLine(data.Certificate.ValueString(), data.IatMode.ValueInt64()) + data.BridgeLine = types.StringValue(bridgeLine) + } + + // Save updated data into Terraform state + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} + +func (r *TorObfs4StateResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + var currentState TorObfs4StateResourceModel + var plannedState TorObfs4StateResourceModel + + // Read current state + resp.Diagnostics.Append(req.State.Get(ctx, ¤tState)...) + if resp.Diagnostics.HasError() { + return + } + + // Read planned state + resp.Diagnostics.Append(req.Plan.Get(ctx, &plannedState)...) + if resp.Diagnostics.HasError() { + return + } + + // Only iat_mode can be updated; preserve all crypto fields from current state + currentState.IatMode = plannedState.IatMode + + // Regenerate computed fields with new iat_mode + stateJson, err := r.generateStateJson(¤tState) + if err != nil { + resp.Diagnostics.AddError("State JSON Generation Error", + fmt.Sprintf("Unable to regenerate state JSON: %s", err)) + return + } + currentState.StateJson = types.StringValue(stateJson) + + // Regenerate bridge line with new iat_mode + bridgeLine := r.generateBridgeLine(currentState.Certificate.ValueString(), currentState.IatMode.ValueInt64()) + currentState.BridgeLine = types.StringValue(bridgeLine) + + tflog.Trace(ctx, "updated obfs4 state resource") + + // Save updated data into Terraform state + resp.Diagnostics.Append(resp.State.Set(ctx, ¤tState)...) +} + +func (r *TorObfs4StateResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + // No external resources to clean up - this is a stateless resource + tflog.Trace(ctx, "deleted obfs4 state resource") +} + + +// generateObfs4State generates all the obfs4 state fields +func (r *TorObfs4StateResource) generateObfs4State(ctx context.Context, data *TorObfs4StateResourceModel) error { + // Derive node ID from RSA identity key (required) + nodeId, err := r.deriveNodeIdFromRsaKey(data.RsaIdentityPrivateKey.ValueString()) + if err != nil { + return fmt.Errorf("failed to derive node ID from RSA key: %w", err) + } + data.NodeId = types.StringValue(nodeId.Hex()) + + // Generate Curve25519 keypair + keypair, err := ntor.NewKeypair(false) + if err != nil { + return fmt.Errorf("failed to generate keypair: %w", err) + } + data.PrivateKey = types.StringValue(keypair.Private().Hex()) + data.PublicKey = types.StringValue(keypair.Public().Hex()) + + // Generate DRBG seed + seed, err := drbg.NewSeed() + if err != nil { + return fmt.Errorf("failed to generate DRBG seed: %w", err) + } + data.DrbgSeed = types.StringValue(seed.Hex()) + + // Set default iat_mode if not specified + if data.IatMode.IsNull() { + data.IatMode = types.Int64Value(0) + } + + // Generate certificate + cert, err := r.generateCertificate(data.NodeId.ValueString(), data.PublicKey.ValueString()) + if err != nil { + return fmt.Errorf("failed to generate certificate: %w", err) + } + data.Certificate = types.StringValue(cert) + + // Generate state JSON + stateJson, err := r.generateStateJson(data) + if err != nil { + return fmt.Errorf("failed to generate state JSON: %w", err) + } + data.StateJson = types.StringValue(stateJson) + + // Generate bridge line + bridgeLine := r.generateBridgeLine(data.Certificate.ValueString(), data.IatMode.ValueInt64()) + data.BridgeLine = types.StringValue(bridgeLine) + + return nil +} + +// generateCertificate creates the base64-encoded certificate from node ID and public key +func (r *TorObfs4StateResource) generateCertificate(nodeIdHex, publicKeyHex string) (string, error) { + // Decode hex strings to bytes + nodeIdBytes, err := hex.DecodeString(nodeIdHex) + if err != nil { + return "", fmt.Errorf("failed to decode node ID: %w", err) + } + + publicKeyBytes, err := hex.DecodeString(publicKeyHex) + if err != nil { + return "", fmt.Errorf("failed to decode public key: %w", err) + } + + // Validate lengths + if len(nodeIdBytes) != 20 { + return "", fmt.Errorf("node ID must be 20 bytes, got %d", len(nodeIdBytes)) + } + if len(publicKeyBytes) != 32 { + return "", fmt.Errorf("public key must be 32 bytes, got %d", len(publicKeyBytes)) + } + + // Concatenate node ID + public key (52 bytes total) + certBytes := make([]byte, 52) + copy(certBytes[:20], nodeIdBytes) + copy(certBytes[20:], publicKeyBytes) + + // Base64 encode and remove padding + cert := base64.StdEncoding.EncodeToString(certBytes) + cert = strings.TrimSuffix(cert, "==") + + return cert, nil +} + +// generateStateJson creates the JSON representation of the obfs4 state +func (r *TorObfs4StateResource) generateStateJson(data *TorObfs4StateResourceModel) (string, error) { + state := obfs4StateJson{ + NodeId: data.NodeId.ValueString(), + PrivateKey: data.PrivateKey.ValueString(), + PublicKey: data.PublicKey.ValueString(), + DrbgSeed: data.DrbgSeed.ValueString(), + IatMode: int(data.IatMode.ValueInt64()), + } + + jsonBytes, err := json.Marshal(state) + if err != nil { + return "", fmt.Errorf("failed to marshal state JSON: %w", err) + } + + return string(jsonBytes), nil +} + +// generateBridgeLine creates a complete bridge line with placeholder IP and fingerprint +func (r *TorObfs4StateResource) generateBridgeLine(certificate string, iatMode int64) string { + // Use placeholder values that users can easily replace + placeholderIP := "" + placeholderFingerprint := "" + + return fmt.Sprintf("Bridge obfs4 %s %s cert=%s iat-mode=%d", + placeholderIP, placeholderFingerprint, certificate, iatMode) +} + +// deriveNodeIdFromRsaKey derives a 20-byte node ID from an RSA private key PEM +// This follows the Tor specification for relay identity +func (r *TorObfs4StateResource) deriveNodeIdFromRsaKey(rsaPrivateKeyPem string) (*ntor.NodeID, error) { + // Parse the PEM-encoded RSA private key + block, _ := pem.Decode([]byte(rsaPrivateKeyPem)) + if block == nil { + return nil, fmt.Errorf("failed to parse PEM block") + } + + var privateKey *rsa.PrivateKey + var err error + + // Try different RSA private key formats + if block.Type == "RSA PRIVATE KEY" { + privateKey, err = x509.ParsePKCS1PrivateKey(block.Bytes) + } else if block.Type == "PRIVATE KEY" { + parsedKey, err2 := x509.ParsePKCS8PrivateKey(block.Bytes) + if err2 == nil { + var ok bool + privateKey, ok = parsedKey.(*rsa.PrivateKey) + if !ok { + return nil, fmt.Errorf("parsed key is not an RSA private key") + } + } else { + err = err2 + } + } else { + return nil, fmt.Errorf("unsupported PEM block type: %s", block.Type) + } + + if err != nil { + return nil, fmt.Errorf("failed to parse RSA private key: %w", err) + } + + // Extract the public key and encode it + publicKeyBytes, err := x509.MarshalPKIXPublicKey(&privateKey.PublicKey) + if err != nil { + return nil, fmt.Errorf("failed to marshal public key: %w", err) + } + + // Generate SHA1 hash of public key (this is the relay fingerprint/node ID) + hash := sha1.Sum(publicKeyBytes) + + // Create node ID from the first 20 bytes (which is all of SHA1) + nodeId, err := ntor.NewNodeID(hash[:]) + if err != nil { + return nil, fmt.Errorf("failed to create node ID: %w", err) + } + + return nodeId, nil +} \ No newline at end of file diff --git a/internal/provider/tor_obfs4_state_resource_test.go b/internal/provider/tor_obfs4_state_resource_test.go new file mode 100644 index 0000000..b4c3a75 --- /dev/null +++ b/internal/provider/tor_obfs4_state_resource_test.go @@ -0,0 +1,455 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (c) 2025 Abel Luck + +package provider + +import ( + "encoding/base64" + "encoding/hex" + "encoding/json" + "fmt" + "regexp" + "testing" + + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/terraform" +) + +func TestTorObfs4StateResource(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + // Create and Read testing + { + Config: testTorObfs4StateResourceConfigWithIdentityKeys(), + Check: resource.ComposeAggregateTestCheckFunc( + // Check that the resource was created + resource.TestCheckResourceAttrSet("tor_obfs4_state.test", "id"), + + // Check that all fields are generated + resource.TestCheckResourceAttrSet("tor_obfs4_state.test", "node_id"), + resource.TestCheckResourceAttrSet("tor_obfs4_state.test", "private_key"), + resource.TestCheckResourceAttrSet("tor_obfs4_state.test", "public_key"), + resource.TestCheckResourceAttrSet("tor_obfs4_state.test", "drbg_seed"), + resource.TestCheckResourceAttrSet("tor_obfs4_state.test", "certificate"), + resource.TestCheckResourceAttrSet("tor_obfs4_state.test", "state_json"), + resource.TestCheckResourceAttrSet("tor_obfs4_state.test", "bridge_line"), + + // Check default iat_mode + resource.TestCheckResourceAttr("tor_obfs4_state.test", "iat_mode", "0"), + + // Check field lengths (hex-encoded) + resource.TestMatchResourceAttr("tor_obfs4_state.test", "node_id", regexp.MustCompile(`^[0-9a-f]{40}$`)), + resource.TestMatchResourceAttr("tor_obfs4_state.test", "private_key", regexp.MustCompile(`^[0-9a-f]{64}$`)), + resource.TestMatchResourceAttr("tor_obfs4_state.test", "public_key", regexp.MustCompile(`^[0-9a-f]{64}$`)), + resource.TestMatchResourceAttr("tor_obfs4_state.test", "drbg_seed", regexp.MustCompile(`^[0-9a-f]{48}$`)), + + // Check certificate format (base64 without padding) + resource.TestMatchResourceAttr("tor_obfs4_state.test", "certificate", regexp.MustCompile(`^[A-Za-z0-9+/]+$`)), + + // Check bridge_line format + resource.TestMatchResourceAttr("tor_obfs4_state.test", "bridge_line", regexp.MustCompile(`^Bridge obfs4 cert=[A-Za-z0-9+/]+ iat-mode=0$`)), + + // Check that the JSON is valid + testTorObfs4StateValidateJSON("tor_obfs4_state.test"), + + // Check that certificate is properly generated from node_id + public_key + testTorObfs4StateCertificateValidity("tor_obfs4_state.test"), + + // Check that bridge_line contains the correct certificate and iat_mode + testTorObfs4StateBridgeLineValidity("tor_obfs4_state.test"), + + // Verify the external identity keys are being used + resource.TestCheckResourceAttrPair("tor_obfs4_state.test", "rsa_identity_private_key", "tor_relay_identity_rsa.test", "private_key_pem"), + resource.TestCheckResourceAttrPair("tor_obfs4_state.test", "ed25519_identity_private_key", "tor_relay_identity_ed25519.test", "private_key_pem"), + ), + }, + // Update testing (iat_mode) + { + Config: testTorObfs4StateResourceConfigWithIATMode(1), + Check: resource.ComposeAggregateTestCheckFunc( + // Check that iat_mode was updated + resource.TestCheckResourceAttr("tor_obfs4_state.test", "iat_mode", "1"), + + // Check that crypto fields remain unchanged (immutable) + resource.TestCheckResourceAttrSet("tor_obfs4_state.test", "node_id"), + resource.TestCheckResourceAttrSet("tor_obfs4_state.test", "private_key"), + resource.TestCheckResourceAttrSet("tor_obfs4_state.test", "public_key"), + resource.TestCheckResourceAttrSet("tor_obfs4_state.test", "drbg_seed"), + resource.TestCheckResourceAttrSet("tor_obfs4_state.test", "certificate"), + + // Check that bridge_line reflects the updated iat_mode + resource.TestMatchResourceAttr("tor_obfs4_state.test", "bridge_line", regexp.MustCompile(`^Bridge obfs4 cert=[A-Za-z0-9+/]+ iat-mode=1$`)), + + // Check that state_json reflects the updated iat_mode + testTorObfs4StateValidateJSON("tor_obfs4_state.test"), + + // Check that bridge_line contains the correct certificate and iat_mode + testTorObfs4StateBridgeLineValidity("tor_obfs4_state.test"), + ), + }, + }, + }) +} + +func TestTorObfs4StateResourceWithCustomIATMode(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + { + Config: testTorObfs4StateResourceConfigWithIATMode(2), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("tor_obfs4_state.test", "iat_mode", "2"), + // Check that bridge_line reflects the custom iat_mode + resource.TestMatchResourceAttr("tor_obfs4_state.test", "bridge_line", regexp.MustCompile(`^Bridge obfs4 cert=[A-Za-z0-9+/]+ iat-mode=2$`)), + testTorObfs4StateValidateJSON("tor_obfs4_state.test"), + testTorObfs4StateBridgeLineValidity("tor_obfs4_state.test"), + ), + }, + }, + }) +} + +func TestTorObfs4StateResourceMultiple(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + { + Config: testTorObfs4StateResourceConfigMultiple(), + Check: resource.ComposeAggregateTestCheckFunc( + // Check first resource + resource.TestCheckResourceAttrSet("tor_obfs4_state.test1", "node_id"), + resource.TestCheckResourceAttrSet("tor_obfs4_state.test1", "certificate"), + resource.TestCheckResourceAttrSet("tor_obfs4_state.test1", "bridge_line"), + + // Check second resource + resource.TestCheckResourceAttrSet("tor_obfs4_state.test2", "node_id"), + resource.TestCheckResourceAttrSet("tor_obfs4_state.test2", "certificate"), + resource.TestCheckResourceAttrSet("tor_obfs4_state.test2", "bridge_line"), + + // Verify they have different keys (uniqueness) + testTorObfs4StateResourcesAreDifferent("tor_obfs4_state.test1", "tor_obfs4_state.test2"), + ), + }, + }, + }) +} + +// Unit tests for helper functions +func TestTorObfs4StateGenerateCertificate(t *testing.T) { + resource := &TorObfs4StateResource{} + + // Test with known values + nodeIdHex := "9be04232642c50ee2864c2724500870f5fce6d2d" + publicKeyHex := "45c9411955294f9b4306e4a65941ea546dee63e1c8a83dd984d1095c4a2f3911" + + cert, err := resource.generateCertificate(nodeIdHex, publicKeyHex) + if err != nil { + t.Fatalf("generateCertificate failed: %v", err) + } + + // Certificate should be base64 encoded 52 bytes (node_id + public_key) + if len(cert) != 70 { // 52 bytes base64 encoded without padding = 70 chars + t.Errorf("Expected certificate length 70, got %d", len(cert)) + } + + // Verify the certificate decodes correctly + decoded, err := base64.StdEncoding.DecodeString(cert + "==") // Add padding for decode + if err != nil { + t.Fatalf("Certificate is not valid base64: %v", err) + } + + if len(decoded) != 52 { + t.Errorf("Expected decoded certificate length 52, got %d", len(decoded)) + } + + // Verify node_id and public_key are correctly concatenated + nodeIdBytes, _ := hex.DecodeString(nodeIdHex) + publicKeyBytes, _ := hex.DecodeString(publicKeyHex) + + for i := 0; i < 20; i++ { + if decoded[i] != nodeIdBytes[i] { + t.Errorf("Node ID mismatch at byte %d", i) + } + } + + for i := 0; i < 32; i++ { + if decoded[20+i] != publicKeyBytes[i] { + t.Errorf("Public key mismatch at byte %d", i) + } + } +} + +func TestTorObfs4StateGenerateCertificateInvalidInput(t *testing.T) { + resource := &TorObfs4StateResource{} + + // Test with invalid node ID length + _, err := resource.generateCertificate("invalid", "45c9411955294f9b4306e4a65941ea546dee63e1c8a83dd984d1095c4a2f3911") + if err == nil { + t.Error("Expected error for invalid node ID length") + } + + // Test with invalid public key length + _, err = resource.generateCertificate("9be04232642c50ee2864c2724500870f5fce6d2d", "invalid") + if err == nil { + t.Error("Expected error for invalid public key length") + } + + // Test with non-hex input + _, err = resource.generateCertificate("zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz", "45c9411955294f9b4306e4a65941ea546dee63e1c8a83dd984d1095c4a2f3911") + if err == nil { + t.Error("Expected error for non-hex node ID") + } +} + +func TestTorObfs4StateGenerateBridgeLine(t *testing.T) { + resource := &TorObfs4StateResource{} + + // Test with known values + certificate := "Y+m2Eny/3H4JeW2RwRYGlNXpdYk8MXRbRFuv5AaTZfxnAUbhEnWCTmJM+VTMssTCYrxyag" + iatMode := int64(0) + + bridgeLine := resource.generateBridgeLine(certificate, iatMode) + + expectedBridgeLine := "Bridge obfs4 cert=Y+m2Eny/3H4JeW2RwRYGlNXpdYk8MXRbRFuv5AaTZfxnAUbhEnWCTmJM+VTMssTCYrxyag iat-mode=0" + + if bridgeLine != expectedBridgeLine { + t.Errorf("Expected bridge line:\n%s\nGot:\n%s", expectedBridgeLine, bridgeLine) + } + + // Test with different iat_mode + iatMode = int64(2) + bridgeLine = resource.generateBridgeLine(certificate, iatMode) + expectedBridgeLine = "Bridge obfs4 cert=Y+m2Eny/3H4JeW2RwRYGlNXpdYk8MXRbRFuv5AaTZfxnAUbhEnWCTmJM+VTMssTCYrxyag iat-mode=2" + + if bridgeLine != expectedBridgeLine { + t.Errorf("Expected bridge line with iat-mode=2:\n%s\nGot:\n%s", expectedBridgeLine, bridgeLine) + } +} + +func TestTorObfs4StateGenerateStateJson(t *testing.T) { + resource := &TorObfs4StateResource{} + + // Create test data + data := &TorObfs4StateResourceModel{ + NodeId: typeStringValue("9be04232642c50ee2864c2724500870f5fce6d2d"), + PrivateKey: typeStringValue("c858003d52fea6be52c6d7f8a3a44692fb3b9ad1bc3b7434871efd9acb8b1554"), + PublicKey: typeStringValue("45c9411955294f9b4306e4a65941ea546dee63e1c8a83dd984d1095c4a2f3911"), + DrbgSeed: typeStringValue("fae8faa79b74d08933251727aab6ca9627736a2387231b03"), + IatMode: typeInt64Value(0), + } + + jsonStr, err := resource.generateStateJson(data) + if err != nil { + t.Fatalf("generateStateJson failed: %v", err) + } + + // Parse the JSON to verify structure + var parsed obfs4StateJson + err = json.Unmarshal([]byte(jsonStr), &parsed) + if err != nil { + t.Fatalf("Generated JSON is invalid: %v", err) + } + + // Verify fields + if parsed.NodeId != "9be04232642c50ee2864c2724500870f5fce6d2d" { + t.Errorf("Node ID mismatch in JSON") + } + if parsed.PrivateKey != "c858003d52fea6be52c6d7f8a3a44692fb3b9ad1bc3b7434871efd9acb8b1554" { + t.Errorf("Private key mismatch in JSON") + } + if parsed.PublicKey != "45c9411955294f9b4306e4a65941ea546dee63e1c8a83dd984d1095c4a2f3911" { + t.Errorf("Public key mismatch in JSON") + } + if parsed.DrbgSeed != "fae8faa79b74d08933251727aab6ca9627736a2387231b03" { + t.Errorf("DRBG seed mismatch in JSON") + } + if parsed.IatMode != 0 { + t.Errorf("IAT mode mismatch in JSON") + } +} + +// Test configuration functions +func testTorObfs4StateResourceConfigWithIdentityKeys() string { + return ` +resource "tor_relay_identity_rsa" "test" {} + +resource "tor_relay_identity_ed25519" "test" {} + +resource "tor_obfs4_state" "test" { + rsa_identity_private_key = tor_relay_identity_rsa.test.private_key_pem + ed25519_identity_private_key = tor_relay_identity_ed25519.test.private_key_pem +} +` +} + +func testTorObfs4StateResourceConfigWithIATMode(iatMode int) string { + return fmt.Sprintf(` +resource "tor_relay_identity_rsa" "test" {} + +resource "tor_relay_identity_ed25519" "test" {} + +resource "tor_obfs4_state" "test" { + rsa_identity_private_key = tor_relay_identity_rsa.test.private_key_pem + ed25519_identity_private_key = tor_relay_identity_ed25519.test.private_key_pem + iat_mode = %d +} +`, iatMode) +} + +func testTorObfs4StateResourceConfigMultiple() string { + return ` +resource "tor_relay_identity_rsa" "test1" {} + +resource "tor_relay_identity_ed25519" "test1" {} + +resource "tor_relay_identity_rsa" "test2" {} + +resource "tor_relay_identity_ed25519" "test2" {} + +resource "tor_obfs4_state" "test1" { + rsa_identity_private_key = tor_relay_identity_rsa.test1.private_key_pem + ed25519_identity_private_key = tor_relay_identity_ed25519.test1.private_key_pem + iat_mode = 0 +} + +resource "tor_obfs4_state" "test2" { + rsa_identity_private_key = tor_relay_identity_rsa.test2.private_key_pem + ed25519_identity_private_key = tor_relay_identity_ed25519.test2.private_key_pem + iat_mode = 1 +} +` +} + +// Custom test check functions +func testTorObfs4StateValidateJSON(resourceName string) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[resourceName] + if !ok { + return fmt.Errorf("Resource not found: %s", resourceName) + } + + stateJsonStr := rs.Primary.Attributes["state_json"] + if stateJsonStr == "" { + return fmt.Errorf("state_json is empty") + } + + // Parse JSON to verify it's valid + var parsed obfs4StateJson + err := json.Unmarshal([]byte(stateJsonStr), &parsed) + if err != nil { + return fmt.Errorf("state_json is not valid JSON: %v", err) + } + + // Verify fields match attributes + if parsed.NodeId != rs.Primary.Attributes["node_id"] { + return fmt.Errorf("JSON node_id doesn't match attribute") + } + if parsed.PrivateKey != rs.Primary.Attributes["private_key"] { + return fmt.Errorf("JSON private_key doesn't match attribute") + } + if parsed.PublicKey != rs.Primary.Attributes["public_key"] { + return fmt.Errorf("JSON public_key doesn't match attribute") + } + if parsed.DrbgSeed != rs.Primary.Attributes["drbg_seed"] { + return fmt.Errorf("JSON drbg_seed doesn't match attribute") + } + + return nil + } +} + +func testTorObfs4StateCertificateValidity(resourceName string) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[resourceName] + if !ok { + return fmt.Errorf("Resource not found: %s", resourceName) + } + + nodeIdHex := rs.Primary.Attributes["node_id"] + publicKeyHex := rs.Primary.Attributes["public_key"] + certificate := rs.Primary.Attributes["certificate"] + + // Regenerate certificate and compare + resource := &TorObfs4StateResource{} + expectedCert, err := resource.generateCertificate(nodeIdHex, publicKeyHex) + if err != nil { + return fmt.Errorf("Failed to generate expected certificate: %v", err) + } + + if certificate != expectedCert { + return fmt.Errorf("Certificate doesn't match expected value") + } + + return nil + } +} + +func testTorObfs4StateBridgeLineValidity(resourceName string) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[resourceName] + if !ok { + return fmt.Errorf("Resource not found: %s", resourceName) + } + + certificate := rs.Primary.Attributes["certificate"] + iatMode := rs.Primary.Attributes["iat_mode"] + bridgeLine := rs.Primary.Attributes["bridge_line"] + + // Expected bridge line format + expectedBridgeLine := fmt.Sprintf("Bridge obfs4 cert=%s iat-mode=%s", certificate, iatMode) + + if bridgeLine != expectedBridgeLine { + return fmt.Errorf("Bridge line doesn't match expected format.\nExpected: %s\nActual: %s", expectedBridgeLine, bridgeLine) + } + + return nil + } +} + +func testTorObfs4StateResourcesAreDifferent(resource1, resource2 string) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs1, ok := s.RootModule().Resources[resource1] + if !ok { + return fmt.Errorf("Resource not found: %s", resource1) + } + + rs2, ok := s.RootModule().Resources[resource2] + if !ok { + return fmt.Errorf("Resource not found: %s", resource2) + } + + // Check that crypto fields are different + if rs1.Primary.Attributes["node_id"] == rs2.Primary.Attributes["node_id"] { + return fmt.Errorf("Resources have same node_id") + } + if rs1.Primary.Attributes["private_key"] == rs2.Primary.Attributes["private_key"] { + return fmt.Errorf("Resources have same private_key") + } + if rs1.Primary.Attributes["public_key"] == rs2.Primary.Attributes["public_key"] { + return fmt.Errorf("Resources have same public_key") + } + if rs1.Primary.Attributes["certificate"] == rs2.Primary.Attributes["certificate"] { + return fmt.Errorf("Resources have same certificate") + } + if rs1.Primary.Attributes["bridge_line"] == rs2.Primary.Attributes["bridge_line"] { + return fmt.Errorf("Resources have same bridge_line") + } + + return nil + } +} + + +// Helper functions for creating types.String and types.Int64 values in tests +func typeStringValue(value string) types.String { + return types.StringValue(value) +} + +func typeInt64Value(value int64) types.Int64 { + return types.Int64Value(value) +} \ No newline at end of file diff --git a/internal/provider/tor_relay_identity_ed25519_resource.go b/internal/provider/tor_relay_identity_ed25519_resource.go new file mode 100644 index 0000000..75b5769 --- /dev/null +++ b/internal/provider/tor_relay_identity_ed25519_resource.go @@ -0,0 +1,188 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (c) 2025 Abel Luck + +package provider + +import ( + "context" + "crypto/ed25519" + "crypto/rand" + "crypto/sha256" + "crypto/x509" + "encoding/pem" + "fmt" + + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-log/tflog" +) + +var _ resource.Resource = &TorRelayIdentityEd25519Resource{} + +func NewTorRelayIdentityEd25519Resource() resource.Resource { + return &TorRelayIdentityEd25519Resource{} +} + +type TorRelayIdentityEd25519Resource struct{} + +type TorRelayIdentityEd25519ResourceModel struct { + Id types.String `tfsdk:"id"` + Algorithm types.String `tfsdk:"algorithm"` + PrivateKeyPem types.String `tfsdk:"private_key_pem"` + PublicKeyPem types.String `tfsdk:"public_key_pem"` + PublicKeyFingerprintSha256 types.String `tfsdk:"public_key_fingerprint_sha256"` +} + +func (r *TorRelayIdentityEd25519Resource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_relay_identity_ed25519" +} + +func (r *TorRelayIdentityEd25519Resource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + MarkdownDescription: "Generates Ed25519 private key for Tor relay identity as required by the Tor specification.", + + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + Computed: true, + MarkdownDescription: "Unique identifier based on public key fingerprint", + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + "algorithm": schema.StringAttribute{ + Computed: true, + MarkdownDescription: "Name of the algorithm used when generating the private key (always 'Ed25519')", + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + "private_key_pem": schema.StringAttribute{ + Computed: true, + Sensitive: true, + MarkdownDescription: "Private key data in PEM (RFC 1421) format", + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + "public_key_pem": schema.StringAttribute{ + Computed: true, + MarkdownDescription: "Public key data in PEM (RFC 1421) format", + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + "public_key_fingerprint_sha256": schema.StringAttribute{ + Computed: true, + MarkdownDescription: "SHA256 fingerprint of the public key in hex format", + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + }, + } +} + +func (r *TorRelayIdentityEd25519Resource) Configure(ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) { +} + +func (r *TorRelayIdentityEd25519Resource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + var data TorRelayIdentityEd25519ResourceModel + + resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...) + if resp.Diagnostics.HasError() { + return + } + + // Set algorithm + data.Algorithm = types.StringValue("Ed25519") + + // Generate Ed25519 key pair + publicKey, privateKey, err := ed25519.GenerateKey(rand.Reader) + if err != nil { + resp.Diagnostics.AddError("Ed25519 Key Generation Error", + fmt.Sprintf("Unable to generate Ed25519 private key: %s", err)) + return + } + + // Encode private key as PEM + privateKeyPem, err := r.encodePrivateKeyPEM(privateKey) + if err != nil { + resp.Diagnostics.AddError("Private Key Encoding Error", + fmt.Sprintf("Unable to encode private key as PEM: %s", err)) + return + } + data.PrivateKeyPem = types.StringValue(privateKeyPem) + + // Encode public key as PEM + publicKeyPem, err := r.encodePublicKeyPEM(publicKey) + if err != nil { + resp.Diagnostics.AddError("Public Key Encoding Error", + fmt.Sprintf("Unable to encode public key as PEM: %s", err)) + return + } + data.PublicKeyPem = types.StringValue(publicKeyPem) + + // Generate SHA256 fingerprint + sha256Fingerprint := r.generateSha256Fingerprint(publicKey) + data.PublicKeyFingerprintSha256 = types.StringValue(sha256Fingerprint) + + // Generate ID from SHA256 fingerprint + data.Id = types.StringValue(fmt.Sprintf("ed25519-%s", sha256Fingerprint[:16])) + + tflog.Trace(ctx, "created tor relay identity Ed25519 resource") + + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} + +func (r *TorRelayIdentityEd25519Resource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + var data TorRelayIdentityEd25519ResourceModel + + resp.Diagnostics.Append(req.State.Get(ctx, &data)...) + if resp.Diagnostics.HasError() { + return + } + + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} + +func (r *TorRelayIdentityEd25519Resource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + resp.Diagnostics.AddError("Update Not Supported", + "Ed25519 relay identity keys are immutable. Any changes require resource replacement.") +} + +func (r *TorRelayIdentityEd25519Resource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + tflog.Trace(ctx, "deleted tor relay identity Ed25519 resource") +} + +func (r *TorRelayIdentityEd25519Resource) encodePrivateKeyPEM(privateKey ed25519.PrivateKey) (string, error) { + privateKeyBytes, err := x509.MarshalPKCS8PrivateKey(privateKey) + if err != nil { + return "", err + } + privateKeyPem := pem.EncodeToMemory(&pem.Block{ + Type: "PRIVATE KEY", + Bytes: privateKeyBytes, + }) + return string(privateKeyPem), nil +} + +func (r *TorRelayIdentityEd25519Resource) encodePublicKeyPEM(publicKey ed25519.PublicKey) (string, error) { + publicKeyBytes, err := x509.MarshalPKIXPublicKey(publicKey) + if err != nil { + return "", err + } + publicKeyPem := pem.EncodeToMemory(&pem.Block{ + Type: "PUBLIC KEY", + Bytes: publicKeyBytes, + }) + return string(publicKeyPem), nil +} + +func (r *TorRelayIdentityEd25519Resource) generateSha256Fingerprint(publicKey ed25519.PublicKey) string { + publicKeyBytes, _ := x509.MarshalPKIXPublicKey(publicKey) + sha256Sum := sha256.Sum256(publicKeyBytes) + return fmt.Sprintf("%x", sha256Sum) +} \ No newline at end of file diff --git a/internal/provider/tor_relay_identity_ed25519_resource_test.go b/internal/provider/tor_relay_identity_ed25519_resource_test.go new file mode 100644 index 0000000..846d20b --- /dev/null +++ b/internal/provider/tor_relay_identity_ed25519_resource_test.go @@ -0,0 +1,40 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (c) 2025 Abel Luck + +package provider + +import ( + "regexp" + "testing" + + "github.com/hashicorp/terraform-plugin-testing/helper/resource" +) + +func TestAccTorRelayIdentityEd25519Resource(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + // Create and Read testing + { + Config: testAccTorRelayIdentityEd25519ResourceConfig, + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("tor_relay_identity_ed25519.test", "algorithm", "Ed25519"), + resource.TestCheckResourceAttrSet("tor_relay_identity_ed25519.test", "id"), + resource.TestCheckResourceAttrSet("tor_relay_identity_ed25519.test", "private_key_pem"), + resource.TestCheckResourceAttrSet("tor_relay_identity_ed25519.test", "public_key_pem"), + resource.TestCheckResourceAttrSet("tor_relay_identity_ed25519.test", "public_key_fingerprint_sha256"), + // Verify PEM format + resource.TestMatchResourceAttr("tor_relay_identity_ed25519.test", "private_key_pem", regexp.MustCompile(`^-----BEGIN PRIVATE KEY-----`)), + resource.TestMatchResourceAttr("tor_relay_identity_ed25519.test", "public_key_pem", regexp.MustCompile(`^-----BEGIN PUBLIC KEY-----`)), + // Verify fingerprint format (64 hex characters for SHA256) + resource.TestMatchResourceAttr("tor_relay_identity_ed25519.test", "public_key_fingerprint_sha256", regexp.MustCompile(`^[0-9a-f]{64}$`)), + ), + }, + }, + }) +} + +const testAccTorRelayIdentityEd25519ResourceConfig = ` +resource "tor_relay_identity_ed25519" "test" {} +` \ No newline at end of file diff --git a/internal/provider/tor_relay_identity_rsa_resource.go b/internal/provider/tor_relay_identity_rsa_resource.go new file mode 100644 index 0000000..2e7be11 --- /dev/null +++ b/internal/provider/tor_relay_identity_rsa_resource.go @@ -0,0 +1,211 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (c) 2025 Abel Luck + +package provider + +import ( + "context" + "crypto/rand" + "crypto/rsa" + "crypto/sha1" + "crypto/sha256" + "crypto/x509" + "encoding/pem" + "fmt" + + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-log/tflog" +) + +var _ resource.Resource = &TorRelayIdentityRsaResource{} + +func NewTorRelayIdentityRsaResource() resource.Resource { + return &TorRelayIdentityRsaResource{} +} + +type TorRelayIdentityRsaResource struct{} + +type TorRelayIdentityRsaResourceModel struct { + Id types.String `tfsdk:"id"` + Algorithm types.String `tfsdk:"algorithm"` + PrivateKeyPem types.String `tfsdk:"private_key_pem"` + PublicKeyPem types.String `tfsdk:"public_key_pem"` + PublicKeyFingerprintSha1 types.String `tfsdk:"public_key_fingerprint_sha1"` + PublicKeyFingerprintSha256 types.String `tfsdk:"public_key_fingerprint_sha256"` +} + +func (r *TorRelayIdentityRsaResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_relay_identity_rsa" +} + +func (r *TorRelayIdentityRsaResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + MarkdownDescription: "Generates 1024-bit RSA private key for Tor relay identity as required by the Tor specification.", + + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + Computed: true, + MarkdownDescription: "Unique identifier based on public key fingerprint", + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + "algorithm": schema.StringAttribute{ + Computed: true, + MarkdownDescription: "Name of the algorithm used when generating the private key (always 'RSA')", + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + "private_key_pem": schema.StringAttribute{ + Computed: true, + Sensitive: true, + MarkdownDescription: "Private key data in PEM (RFC 1421) format", + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + "public_key_pem": schema.StringAttribute{ + Computed: true, + MarkdownDescription: "Public key data in PEM (RFC 1421) format", + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + "public_key_fingerprint_sha1": schema.StringAttribute{ + Computed: true, + MarkdownDescription: "SHA1 fingerprint of the public key in hex format", + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + "public_key_fingerprint_sha256": schema.StringAttribute{ + Computed: true, + MarkdownDescription: "SHA256 fingerprint of the public key in hex format", + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + }, + } +} + +func (r *TorRelayIdentityRsaResource) Configure(ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) { +} + +func (r *TorRelayIdentityRsaResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + var data TorRelayIdentityRsaResourceModel + + resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...) + if resp.Diagnostics.HasError() { + return + } + + // Set algorithm + data.Algorithm = types.StringValue("RSA") + + // Generate 1024-bit RSA private key as required by Tor specification + // See: https://spec.torproject.org/tor-spec/preliminaries.html#ciphers + privateKey, err := rsa.GenerateKey(rand.Reader, 1024) + if err != nil { + resp.Diagnostics.AddError("RSA Key Generation Error", + fmt.Sprintf("Unable to generate RSA private key: %s", err)) + return + } + + // Encode private key as PEM + privateKeyPem, err := r.encodePrivateKeyPEM(privateKey) + if err != nil { + resp.Diagnostics.AddError("Private Key Encoding Error", + fmt.Sprintf("Unable to encode private key as PEM: %s", err)) + return + } + data.PrivateKeyPem = types.StringValue(privateKeyPem) + + // Extract and encode public key + publicKey := &privateKey.PublicKey + publicKeyPem, err := r.encodePublicKeyPEM(publicKey) + if err != nil { + resp.Diagnostics.AddError("Public Key Encoding Error", + fmt.Sprintf("Unable to encode public key as PEM: %s", err)) + return + } + data.PublicKeyPem = types.StringValue(publicKeyPem) + + // Generate fingerprints + sha1Fingerprint, sha256Fingerprint, err := r.generateFingerprints(publicKey) + if err != nil { + resp.Diagnostics.AddError("Fingerprint Generation Error", + fmt.Sprintf("Unable to generate public key fingerprints: %s", err)) + return + } + data.PublicKeyFingerprintSha1 = types.StringValue(sha1Fingerprint) + data.PublicKeyFingerprintSha256 = types.StringValue(sha256Fingerprint) + + // Generate ID from SHA1 fingerprint + data.Id = types.StringValue(fmt.Sprintf("rsa-%s", sha1Fingerprint[:16])) + + tflog.Trace(ctx, "created tor relay identity RSA resource") + + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} + +func (r *TorRelayIdentityRsaResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + var data TorRelayIdentityRsaResourceModel + + resp.Diagnostics.Append(req.State.Get(ctx, &data)...) + if resp.Diagnostics.HasError() { + return + } + + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} + +func (r *TorRelayIdentityRsaResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + resp.Diagnostics.AddError("Update Not Supported", + "RSA relay identity keys are immutable. Any changes require resource replacement.") +} + +func (r *TorRelayIdentityRsaResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + tflog.Trace(ctx, "deleted tor relay identity RSA resource") +} + +func (r *TorRelayIdentityRsaResource) encodePrivateKeyPEM(privateKey *rsa.PrivateKey) (string, error) { + privateKeyBytes := x509.MarshalPKCS1PrivateKey(privateKey) + privateKeyPem := pem.EncodeToMemory(&pem.Block{ + Type: "RSA PRIVATE KEY", + Bytes: privateKeyBytes, + }) + return string(privateKeyPem), nil +} + +func (r *TorRelayIdentityRsaResource) encodePublicKeyPEM(publicKey *rsa.PublicKey) (string, error) { + publicKeyBytes, err := x509.MarshalPKIXPublicKey(publicKey) + if err != nil { + return "", err + } + publicKeyPem := pem.EncodeToMemory(&pem.Block{ + Type: "PUBLIC KEY", + Bytes: publicKeyBytes, + }) + return string(publicKeyPem), nil +} + +func (r *TorRelayIdentityRsaResource) generateFingerprints(publicKey *rsa.PublicKey) (string, string, error) { + publicKeyBytes, err := x509.MarshalPKIXPublicKey(publicKey) + if err != nil { + return "", "", err + } + + sha1Sum := sha1.Sum(publicKeyBytes) + sha256Sum := sha256.Sum256(publicKeyBytes) + + sha1Fingerprint := fmt.Sprintf("%x", sha1Sum) + sha256Fingerprint := fmt.Sprintf("%x", sha256Sum) + + return sha1Fingerprint, sha256Fingerprint, nil +} \ No newline at end of file diff --git a/internal/provider/tor_relay_identity_rsa_resource_test.go b/internal/provider/tor_relay_identity_rsa_resource_test.go new file mode 100644 index 0000000..d4d091b --- /dev/null +++ b/internal/provider/tor_relay_identity_rsa_resource_test.go @@ -0,0 +1,42 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (c) 2025 Abel Luck + +package provider + +import ( + "regexp" + "testing" + + "github.com/hashicorp/terraform-plugin-testing/helper/resource" +) + +func TestAccTorRelayIdentityRsaResource(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + // Create and Read testing + { + Config: testAccTorRelayIdentityRsaResourceConfig, + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("tor_relay_identity_rsa.test", "algorithm", "RSA"), + resource.TestCheckResourceAttrSet("tor_relay_identity_rsa.test", "id"), + resource.TestCheckResourceAttrSet("tor_relay_identity_rsa.test", "private_key_pem"), + resource.TestCheckResourceAttrSet("tor_relay_identity_rsa.test", "public_key_pem"), + resource.TestCheckResourceAttrSet("tor_relay_identity_rsa.test", "public_key_fingerprint_sha1"), + resource.TestCheckResourceAttrSet("tor_relay_identity_rsa.test", "public_key_fingerprint_sha256"), + // Verify PEM format + resource.TestMatchResourceAttr("tor_relay_identity_rsa.test", "private_key_pem", regexp.MustCompile(`^-----BEGIN RSA PRIVATE KEY-----`)), + resource.TestMatchResourceAttr("tor_relay_identity_rsa.test", "public_key_pem", regexp.MustCompile(`^-----BEGIN PUBLIC KEY-----`)), + // Verify fingerprint formats (hex strings) + resource.TestMatchResourceAttr("tor_relay_identity_rsa.test", "public_key_fingerprint_sha1", regexp.MustCompile(`^[0-9a-f]{40}$`)), + resource.TestMatchResourceAttr("tor_relay_identity_rsa.test", "public_key_fingerprint_sha256", regexp.MustCompile(`^[0-9a-f]{64}$`)), + ), + }, + }, + }) +} + +const testAccTorRelayIdentityRsaResourceConfig = ` +resource "tor_relay_identity_rsa" "test" {} +` \ No newline at end of file diff --git a/main.go b/main.go new file mode 100644 index 0000000..f330589 --- /dev/null +++ b/main.go @@ -0,0 +1,51 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (c) 2025 Abel Luck + +package main + +import ( + "context" + "flag" + "log" + + "github.com/hashicorp/terraform-plugin-framework/providerserver" + + "terraform-provider-tor/internal/provider" +) + +// Run "go generate" to format example terraform files and generate the docs for the registry/website + +// If you do not have terraform installed, you can remove the formatting command, but its suggested to +// ensure the documentation is formatted properly. +//go:generate terraform fmt -recursive ./examples/ + +// Run the docs generation tool, check its repository for more information on how it works and how docs +// can be customized. +//go:generate go run github.com/hashicorp/terraform-plugin-docs/cmd/tfplugindocs + +var ( + // these will be set by the goreleaser configuration + // to appropriate values for the compiled binary. + version string = "dev" + + // goreleaser can pass other information to the main package, such as the specific commit + // https://goreleaser.com/cookbooks/using-main.version/ +) + +func main() { + var debug bool + + flag.BoolVar(&debug, "debug", false, "set to true to run the provider with support for debuggers like delve") + flag.Parse() + + opts := providerserver.ServeOpts{ + Address: "registry.terraform.io/guardianproject/tor", + Debug: debug, + } + + err := providerserver.Serve(context.Background(), provider.New(version), opts) + + if err != nil { + log.Fatal(err.Error()) + } +} \ No newline at end of file diff --git a/terraform-registry-manifest.json b/terraform-registry-manifest.json new file mode 100644 index 0000000..fec2a56 --- /dev/null +++ b/terraform-registry-manifest.json @@ -0,0 +1,6 @@ +{ + "version": 1, + "metadata": { + "protocol_versions": ["6.0"] + } +} diff --git a/tools/go.mod b/tools/go.mod new file mode 100644 index 0000000..17f1d24 --- /dev/null +++ b/tools/go.mod @@ -0,0 +1,57 @@ +module tools + +go 1.23.7 + +require github.com/hashicorp/terraform-plugin-docs v0.21.0 + +require ( + dario.cat/mergo v1.0.1 // indirect + github.com/BurntSushi/toml v1.2.1 // indirect + github.com/Kunde21/markdownfmt/v3 v3.1.0 // indirect + github.com/Masterminds/goutils v1.1.1 // indirect + github.com/Masterminds/semver/v3 v3.3.0 // indirect + github.com/Masterminds/sprig/v3 v3.3.0 // indirect + github.com/ProtonMail/go-crypto v1.1.3 // indirect + github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect + github.com/armon/go-radix v1.0.0 // indirect + github.com/bgentry/speakeasy v0.1.0 // indirect + github.com/bmatcuk/doublestar/v4 v4.8.1 // indirect + github.com/cloudflare/circl v1.3.7 // indirect + github.com/fatih/color v1.16.0 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/hashicorp/cli v1.1.7 // indirect + github.com/hashicorp/errwrap v1.1.0 // indirect + github.com/hashicorp/go-checkpoint v0.5.0 // indirect + github.com/hashicorp/go-cleanhttp v0.5.2 // indirect + github.com/hashicorp/go-multierror v1.1.1 // indirect + github.com/hashicorp/go-retryablehttp v0.7.7 // indirect + github.com/hashicorp/go-uuid v1.0.3 // indirect + github.com/hashicorp/go-version v1.7.0 // indirect + github.com/hashicorp/hc-install v0.9.1 // indirect + github.com/hashicorp/terraform-exec v0.22.0 // indirect + github.com/hashicorp/terraform-json v0.24.0 // indirect + github.com/huandu/xstrings v1.5.0 // indirect + github.com/mattn/go-colorable v0.1.14 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mattn/go-runewidth v0.0.16 // indirect + github.com/mitchellh/copystructure v1.2.0 // indirect + github.com/mitchellh/reflectwalk v1.0.2 // indirect + github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect + github.com/posener/complete v1.2.3 // indirect + github.com/rivo/uniseg v0.4.7 // indirect + github.com/shopspring/decimal v1.4.0 // indirect + github.com/spf13/cast v1.7.0 // indirect + github.com/yuin/goldmark v1.7.8 // indirect + github.com/yuin/goldmark-meta v1.1.0 // indirect + github.com/zclconf/go-cty v1.16.2 // indirect + go.abhg.dev/goldmark/frontmatter v0.2.0 // indirect + golang.org/x/crypto v0.36.0 // indirect + golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df // indirect + golang.org/x/mod v0.22.0 // indirect + golang.org/x/net v0.38.0 // indirect + golang.org/x/sys v0.31.0 // indirect + golang.org/x/text v0.23.0 // indirect + gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/tools/go.sum b/tools/go.sum new file mode 100644 index 0000000..681077c --- /dev/null +++ b/tools/go.sum @@ -0,0 +1,163 @@ +dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s= +dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= +github.com/BurntSushi/toml v1.2.1 h1:9F2/+DoOYIOksmaJFPw1tGFy1eDnIJXg+UHjuD8lTak= +github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= +github.com/Kunde21/markdownfmt/v3 v3.1.0 h1:KiZu9LKs+wFFBQKhrZJrFZwtLnCCWJahL+S+E/3VnM0= +github.com/Kunde21/markdownfmt/v3 v3.1.0/go.mod h1:tPXN1RTyOzJwhfHoon9wUr4HGYmWgVxSQN6VBJDkrVc= +github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI= +github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= +github.com/Masterminds/semver/v3 v3.3.0 h1:B8LGeaivUe71a5qox1ICM/JLl0NqZSW5CHyL+hmvYS0= +github.com/Masterminds/semver/v3 v3.3.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= +github.com/Masterminds/sprig/v3 v3.3.0 h1:mQh0Yrg1XPo6vjYXgtf5OtijNAKJRNcTdOOGZe3tPhs= +github.com/Masterminds/sprig/v3 v3.3.0/go.mod h1:Zy1iXRYNqNLUolqCpL4uhk6SHUMAOSCzdgBfDb35Lz0= +github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= +github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= +github.com/ProtonMail/go-crypto v1.1.3 h1:nRBOetoydLeUb4nHajyO2bKqMLfWQ/ZPwkXqXxPxCFk= +github.com/ProtonMail/go-crypto v1.1.3/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE= +github.com/apparentlymart/go-textseg/v15 v15.0.0 h1:uYvfpb3DyLSCGWnctWKGj857c6ew1u1fNQOlOtuGxQY= +github.com/apparentlymart/go-textseg/v15 v15.0.0/go.mod h1:K8XmNZdhEBkdlyDdvbmmsvpAG721bKi0joRfFdHIWJ4= +github.com/armon/go-radix v1.0.0 h1:F4z6KzEeeQIMeLFa97iZU6vupzoecKdU5TX24SNppXI= +github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/bgentry/speakeasy v0.1.0 h1:ByYyxL9InA1OWqxJqqp2A5pYHUrCiAL6K3J+LKSsQkY= +github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= +github.com/bmatcuk/doublestar/v4 v4.8.1 h1:54Bopc5c2cAvhLRAzqOGCYHYyhcDHsFF4wWIR5wKP38= +github.com/bmatcuk/doublestar/v4 v4.8.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc= +github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU= +github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA= +github.com/cyphar/filepath-securejoin v0.2.5 h1:6iR5tXJ/e6tJZzzdMc1km3Sa7RRIVBKAK32O2s7AYfo= +github.com/cyphar/filepath-securejoin v0.2.5/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= +github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= +github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= +github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= +github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= +github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= +github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI= +github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic= +github.com/go-git/go-billy/v5 v5.6.0 h1:w2hPNtoehvJIxR00Vb4xX94qHQi/ApZfX+nBE2Cjio8= +github.com/go-git/go-billy/v5 v5.6.0/go.mod h1:sFDq7xD3fn3E0GOwUSZqHo9lrkmx8xJhA0ZrfvjBRGM= +github.com/go-git/go-git/v5 v5.13.0 h1:vLn5wlGIh/X78El6r3Jr+30W16Blk0CTcxTYcYPWi5E= +github.com/go-git/go-git/v5 v5.13.0/go.mod h1:Wjo7/JyVKtQgUNdXYXIepzWfJQkUEIGvkvVkiXRR/zw= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/hashicorp/cli v1.1.7 h1:/fZJ+hNdwfTSfsxMBa9WWMlfjUZbX8/LnUxgAd7lCVU= +github.com/hashicorp/cli v1.1.7/go.mod h1:e6Mfpga9OCT1vqzFuoGZiiF/KaG9CbUfO5s3ghU3YgU= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= +github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-checkpoint v0.5.0 h1:MFYpPZCnQqQTE18jFwSII6eUQrD/oxMFp3mlgcqk5mU= +github.com/hashicorp/go-checkpoint v0.5.0/go.mod h1:7nfLNL10NsxqO4iWuW6tWW0HjZuDrwkBuEQsVcpCOgg= +github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= +github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= +github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k= +github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= +github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= +github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= +github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= +github.com/hashicorp/go-retryablehttp v0.7.7 h1:C8hUCYzor8PIfXHa4UrZkU4VvK8o9ISHxT2Q8+VepXU= +github.com/hashicorp/go-retryablehttp v0.7.7/go.mod h1:pkQpWZeYWskR+D1tR2O5OcBFOxfA7DoAO6xtkuQnHTk= +github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8= +github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-version v1.7.0 h1:5tqGy27NaOTB8yJKUZELlFAS/LTKJkrmONwQKeRZfjY= +github.com/hashicorp/go-version v1.7.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/hashicorp/hc-install v0.9.1 h1:gkqTfE3vVbafGQo6VZXcy2v5yoz2bE0+nhZXruCuODQ= +github.com/hashicorp/hc-install v0.9.1/go.mod h1:pWWvN/IrfeBK4XPeXXYkL6EjMufHkCK5DvwxeLKuBf0= +github.com/hashicorp/terraform-exec v0.22.0 h1:G5+4Sz6jYZfRYUCg6eQgDsqTzkNXV+fP8l+uRmZHj64= +github.com/hashicorp/terraform-exec v0.22.0/go.mod h1:bjVbsncaeh8jVdhttWYZuBGj21FcYw6Ia/XfHcNO7lQ= +github.com/hashicorp/terraform-json v0.24.0 h1:rUiyF+x1kYawXeRth6fKFm/MdfBS6+lW4NbeATsYz8Q= +github.com/hashicorp/terraform-json v0.24.0/go.mod h1:Nfj5ubo9xbu9uiAoZVBsNOjvNKB66Oyrvtit74kC7ow= +github.com/hashicorp/terraform-plugin-docs v0.21.0 h1:yoyA/Y719z9WdFJAhpUkI1jRbKP/nteVNBaI3hW7iQ8= +github.com/hashicorp/terraform-plugin-docs v0.21.0/go.mod h1:J4Wott1J2XBKZPp/NkQv7LMShJYOcrqhQ2myXBcu64s= +github.com/huandu/xstrings v1.5.0 h1:2ag3IFq9ZDANvthTwTiqSSZLjDc+BedvHPAp5tJy2TI= +github.com/huandu/xstrings v1.5.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= +github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= +github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= +github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4= +github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= +github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= +github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= +github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= +github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= +github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= +github.com/pjbgf/sha1cd v0.3.0 h1:4D5XXmUUBUl/xQ6IjCkEAbqXskkq/4O7LmGn0AqMDs4= +github.com/pjbgf/sha1cd v0.3.0/go.mod h1:nZ1rrWOcGJ5uZgEEVL1VUM9iRQiZvWdbZjkKyFzPPsI= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/posener/complete v1.2.3 h1:NP0eAhjcjImqslEwo/1hq7gpajME0fTLTezBKDqfXqo= +github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s= +github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= +github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= +github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= +github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8= +github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= +github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k= +github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME= +github.com/skeema/knownhosts v1.3.0 h1:AM+y0rI04VksttfwjkSTNQorvGqmwATnvnAHpSgc0LY= +github.com/skeema/knownhosts v1.3.0/go.mod h1:sPINvnADmT/qYH1kfv+ePMmOBTH6Tbl7b5LvTDjFK7M= +github.com/spf13/cast v1.7.0 h1:ntdiHjuueXFgm5nzDRdOS4yfT43P5Fnud6DH50rz/7w= +github.com/spf13/cast v1.7.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= +github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM= +github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw= +github.com/yuin/goldmark v1.7.8 h1:iERMLn0/QJeHFhxSt3p6PeN9mGnvIKSpG9YYorDMnic= +github.com/yuin/goldmark v1.7.8/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E= +github.com/yuin/goldmark-meta v1.1.0 h1:pWw+JLHGZe8Rk0EGsMVssiNb/AaPMHfSRszZeUeiOUc= +github.com/yuin/goldmark-meta v1.1.0/go.mod h1:U4spWENafuA7Zyg+Lj5RqK/MF+ovMYtBvXi1lBb2VP0= +github.com/zclconf/go-cty v1.16.2 h1:LAJSwc3v81IRBZyUVQDUdZ7hs3SYs9jv0eZJDWHD/70= +github.com/zclconf/go-cty v1.16.2/go.mod h1:VvMs5i0vgZdhYawQNq5kePSpLAoz8u1xvZgrPIxfnZE= +go.abhg.dev/goldmark/frontmatter v0.2.0 h1:P8kPG0YkL12+aYk2yU3xHv4tcXzeVnN+gU0tJ5JnxRw= +go.abhg.dev/goldmark/frontmatter v0.2.0/go.mod h1:XqrEkZuM57djk7zrlRUB02x8I5J0px76YjkOzhB4YlU= +golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= +golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= +golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df h1:UA2aFVmmsIlefxMk29Dp2juaUSth8Pyn3Tq5Y5mJGME= +golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc= +golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4= +golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= +golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8= +golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= +golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw= +golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= +golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= +golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= +golang.org/x/tools v0.22.0 h1:gqSGLZqv+AI9lIQzniJ0nZDRG5GBPsSi+DRNHWNz6yA= +golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= +gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/tools/tools.go b/tools/tools.go new file mode 100644 index 0000000..84a3fec --- /dev/null +++ b/tools/tools.go @@ -0,0 +1,18 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +//go:build generate + +package tools + +import ( + _ "github.com/hashicorp/terraform-plugin-docs/cmd/tfplugindocs" +) + +// Format Terraform code for use in documentation. +// If you do not have Terraform installed, you can remove the formatting command, but it is suggested +// to ensure the documentation is formatted properly. +//go:generate terraform fmt -recursive ../examples/ + +// Generate documentation. +//go:generate go run github.com/hashicorp/terraform-plugin-docs/cmd/tfplugindocs generate --provider-dir .. -provider-name tor