First working version
This commit is contained in:
parent
63ed6316bc
commit
d8eda81e0e
31 changed files with 3134 additions and 0 deletions
39
.gitignore
vendored
Normal file
39
.gitignore
vendored
Normal file
|
@ -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
|
3
CHANGELOG.md
Normal file
3
CHANGELOG.md
Normal file
|
@ -0,0 +1,3 @@
|
|||
## 0.1.0 (Unreleased)
|
||||
|
||||
FEATURES:
|
24
GNUmakefile
Normal file
24
GNUmakefile
Normal file
|
@ -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
|
13
META.d/_summary.yaml
Normal file
13
META.d/_summary.yaml
Normal file
|
@ -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
|
142
README.md
Normal file
142
README.md
Normal file
|
@ -0,0 +1,142 @@
|
|||
# Terraform Provider for Tor Bridges
|
||||
|
||||
[![][ci-badge]][ci]
|
||||
[](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 <abel@guardianproject.info>
|
||||
|
||||
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]:
|
80
docs/data-sources/obfs4_bridge_line.md
Normal file
80
docs/data-sources/obfs4_bridge_line.md
Normal file
|
@ -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 generated by tfplugindocs -->
|
||||
## 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
|
75
docs/index.md
Normal file
75
docs/index.md
Normal file
|
@ -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 generated by tfplugindocs -->
|
||||
## Schema
|
36
docs/resources/obfs4_state.md
Normal file
36
docs/resources/obfs4_state.md
Normal file
|
@ -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 generated by tfplugindocs -->
|
||||
## 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
|
24
docs/resources/relay_identity_ed25519.md
Normal file
24
docs/resources/relay_identity_ed25519.md
Normal file
|
@ -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 generated by tfplugindocs -->
|
||||
## 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
|
25
docs/resources/relay_identity_rsa.md
Normal file
25
docs/resources/relay_identity_rsa.md
Normal file
|
@ -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 generated by tfplugindocs -->
|
||||
## 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
|
29
examples/README.md
Normal file
29
examples/README.md
Normal file
|
@ -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.
|
48
examples/data-sources/tor_obfs4_bridge_line/data-source.tf
Normal file
48
examples/data-sources/tor_obfs4_bridge_line/data-source.tf
Normal file
|
@ -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
|
||||
}
|
56
examples/provider/provider.tf
Normal file
56
examples/provider/provider.tf
Normal file
|
@ -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
|
||||
}
|
72
go.mod
Normal file
72
go.mod
Normal file
|
@ -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
|
264
go.sum
Normal file
264
go.sum
Normal file
|
@ -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=
|
134
go.sum.broken
Normal file
134
go.sum.broken
Normal file
|
@ -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=
|
85
internal/provider/provider.go
Normal file
85
internal/provider/provider.go
Normal file
|
@ -0,0 +1,85 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
// Copyright (c) 2025 Abel Luck <abel@guardianproject.info>
|
||||
|
||||
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,
|
||||
}
|
||||
}
|
||||
}
|
25
internal/provider/provider_test.go
Normal file
25
internal/provider/provider_test.go
Normal file
|
@ -0,0 +1,25 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
// Copyright (c) 2025 Abel Luck <abel@guardianproject.info>
|
||||
|
||||
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.
|
||||
}
|
107
internal/provider/tor_obfs4_bridge_line_data_source.go
Normal file
107
internal/provider/tor_obfs4_bridge_line_data_source.go
Normal file
|
@ -0,0 +1,107 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
// Copyright (c) 2025 Abel Luck <abel@guardianproject.info>
|
||||
|
||||
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)...)
|
||||
}
|
197
internal/provider/tor_obfs4_bridge_line_data_source_test.go
Normal file
197
internal/provider/tor_obfs4_bridge_line_data_source_test.go
Normal file
|
@ -0,0 +1,197 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
// Copyright (c) 2025 Abel Luck <abel@guardianproject.info>
|
||||
|
||||
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
|
||||
}
|
||||
}
|
425
internal/provider/tor_obfs4_state_resource.go
Normal file
425
internal/provider/tor_obfs4_state_resource.go
Normal file
|
@ -0,0 +1,425 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
// Copyright (c) 2025 Abel Luck <abel@guardianproject.info>
|
||||
|
||||
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 := "<IP:PORT>"
|
||||
placeholderFingerprint := "<FINGERPRINT>"
|
||||
|
||||
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
|
||||
}
|
455
internal/provider/tor_obfs4_state_resource_test.go
Normal file
455
internal/provider/tor_obfs4_state_resource_test.go
Normal file
|
@ -0,0 +1,455 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
// Copyright (c) 2025 Abel Luck <abel@guardianproject.info>
|
||||
|
||||
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 <IP:PORT> <FINGERPRINT> 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 <IP:PORT> <FINGERPRINT> 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 <IP:PORT> <FINGERPRINT> 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 <IP:PORT> <FINGERPRINT> 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 <IP:PORT> <FINGERPRINT> 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 <IP:PORT> <FINGERPRINT> 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)
|
||||
}
|
188
internal/provider/tor_relay_identity_ed25519_resource.go
Normal file
188
internal/provider/tor_relay_identity_ed25519_resource.go
Normal file
|
@ -0,0 +1,188 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
// Copyright (c) 2025 Abel Luck <abel@guardianproject.info>
|
||||
|
||||
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)
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
// Copyright (c) 2025 Abel Luck <abel@guardianproject.info>
|
||||
|
||||
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" {}
|
||||
`
|
211
internal/provider/tor_relay_identity_rsa_resource.go
Normal file
211
internal/provider/tor_relay_identity_rsa_resource.go
Normal file
|
@ -0,0 +1,211 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
// Copyright (c) 2025 Abel Luck <abel@guardianproject.info>
|
||||
|
||||
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
|
||||
}
|
42
internal/provider/tor_relay_identity_rsa_resource_test.go
Normal file
42
internal/provider/tor_relay_identity_rsa_resource_test.go
Normal file
|
@ -0,0 +1,42 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
// Copyright (c) 2025 Abel Luck <abel@guardianproject.info>
|
||||
|
||||
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" {}
|
||||
`
|
51
main.go
Normal file
51
main.go
Normal file
|
@ -0,0 +1,51 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
// Copyright (c) 2025 Abel Luck <abel@guardianproject.info>
|
||||
|
||||
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())
|
||||
}
|
||||
}
|
6
terraform-registry-manifest.json
Normal file
6
terraform-registry-manifest.json
Normal file
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"version": 1,
|
||||
"metadata": {
|
||||
"protocol_versions": ["6.0"]
|
||||
}
|
||||
}
|
57
tools/go.mod
Normal file
57
tools/go.mod
Normal file
|
@ -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
|
||||
)
|
163
tools/go.sum
Normal file
163
tools/go.sum
Normal file
|
@ -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=
|
18
tools/tools.go
Normal file
18
tools/tools.go
Normal file
|
@ -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
|
Loading…
Add table
Add a link
Reference in a new issue