diff --git a/.gitignore b/.gitignore index f0e5eea..23b12a9 100644 --- a/.gitignore +++ b/.gitignore @@ -39,9 +39,5 @@ CLAUDE.md extra .direnv .claude -e2e-tests/**/*tfstate* -e2e-tests/**/providers -e2e-tests/**/.terraform.lock.hcl -e2e-tests/**/torrc -e2e-tests/**/*.secret_family_key -e2e-tests/**/data +e2e-test/*tfstate* +e2e-test/.terraformrc diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 51541aa..a8d705a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -38,14 +38,6 @@ make lint # Run acceptance tests (creates real resources) make testacc - -# Run e2e tests -make clean build e2e/tor-family # Run the tor-family test with latest binary -make clean build e2e/obfs4 # Run the obfs4 e2e test with latest binary -make e2e # Run all e2e tests - -# Important: Always use 'make clean build' before e2e tests to ensure -# the test uses the latest provider binary ``` ## Project Structure @@ -57,7 +49,6 @@ make e2e # Run all e2e tests │ ├── provider/ # Provider configuration examples │ └── resources/ # Resource examples ├── docs/ # Generated documentation -├── e2e-tests/ # End-to-end tests ``` ## Security Considerations diff --git a/GNUmakefile b/GNUmakefile index 4a0b2eb..a705903 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -1,19 +1,13 @@ default: fmt lint install generate -dist-clean: +clean: go clean -cache -modcache -testcache -clean: - rm -f ./terraform-provider-tor ./provider.test - find e2e-tests -type f -name '*.tfstate*' -delete - find e2e-tests -type d -name '.terraform' -prune -exec rm -rf ./{} \; - find e2e-tests -type d -name 'providers' -prune -exec rm -rf ./{} \; - build: - go build + go build -v ./... install: build - go install + go install -v ./... lint: go mod tidy @@ -39,11 +33,16 @@ test: testacc: TF_ACC=1 go test -v -cover -timeout 120m ./... -e2e/%: - @echo "Executing end-to-end test: $*" - cd e2e-tests/$* && ./test.sh - -e2e: clean build e2e/obfs4 e2e/tor-family +e2e: + @echo "Running end-to-end test..." + cd e2e-test && \ + rm -rf .terraform/ .terraform.lock.hcl terraform.tfstate* && \ + ./setup-dev.sh && \ + TF_CLI_CONFIG_FILE=.terraformrc tofu plan && \ + TF_CLI_CONFIG_FILE=.terraformrc tofu apply -auto-approve && \ + echo "✓ E2E test passed! Cleaning up..." && \ + TF_CLI_CONFIG_FILE=.terraformrc tofu destroy -auto-approve && \ + echo "✓ E2E test completed successfully" ci: @echo "Running CI pipeline..." diff --git a/docs/resources/family_identity.md b/docs/resources/family_identity.md deleted file mode 100644 index 8fe89f0..0000000 --- a/docs/resources/family_identity.md +++ /dev/null @@ -1,51 +0,0 @@ ---- -# generated by https://github.com/hashicorp/terraform-plugin-docs -page_title: "tor_family_identity Resource - tor" -subcategory: "" -description: |- - Generates a Tor family identity key as described in proposal 321 (Happy Families). - References - https://community.torproject.org/relay/setup/post-install/family-ids/https://gitlab.torproject.org/tpo/core/torspec/-/blob/main/proposals/321-happy-families.md ---- - -# tor_family_identity (Resource) - -Generates a Tor family identity key as described in proposal 321 (Happy Families). - -### References -- https://community.torproject.org/relay/setup/post-install/family-ids/ -- https://gitlab.torproject.org/tpo/core/torspec/-/blob/main/proposals/321-happy-families.md - -## Example Usage - -```terraform -resource "tor_family_identity" "example" { - family_name = "MyFamily" -} - - -resource "local_sensitive_file" "family_key" { - content_base64 = tor_family_identity.example.secret_key - filename = "MyFamily.secret_family_key" -} - -resource "local_file" "torrc" { - filename = "./torrc" - content = < -## Schema - -### Required - -- `family_name` (String) Name of the family - -### Read-Only - -- `id` (String) Base64-encoded public key (as stored in public_family_id file) -- `secret_key` (String, Sensitive) Binary contents of the secret family key file (base64 encoded) diff --git a/e2e-tests/obfs4/README.md b/e2e-test/README.md similarity index 100% rename from e2e-tests/obfs4/README.md rename to e2e-test/README.md diff --git a/e2e-tests/obfs4/main.tf b/e2e-test/main.tf similarity index 87% rename from e2e-tests/obfs4/main.tf rename to e2e-test/main.tf index 08a3d97..8c3ecea 100644 --- a/e2e-tests/obfs4/main.tf +++ b/e2e-test/main.tf @@ -1,7 +1,7 @@ terraform { required_providers { tor = { - source = "guardianproject/tor" + source = "guardianproject/tor" version = "99.0.0" } } @@ -15,11 +15,6 @@ resource "tor_relay_identity_rsa" "bridge" {} # Generate Ed25519 identity key for the bridge resource "tor_relay_identity_ed25519" "bridge" {} -# Generate family identity for the bridge -resource "tor_family_identity" "bridge" { - family_name = "MyBridgeFamily" -} - # Generate obfs4 state using the identity keys resource "tor_obfs4_state" "bridge" { rsa_identity_private_key = tor_relay_identity_rsa.bridge.private_key_pem @@ -65,8 +60,3 @@ output "bridge_line" { description = "Complete bridge line for clients" value = data.tor_obfs4_bridge_line.bridge.bridge_line } - -output "family_id" { - description = "Family ID for the bridge" - value = tor_family_identity.bridge.id -} diff --git a/e2e-test/setup-dev.sh b/e2e-test/setup-dev.sh new file mode 100755 index 0000000..1b98732 --- /dev/null +++ b/e2e-test/setup-dev.sh @@ -0,0 +1,48 @@ +#!/usr/bin/env bash +set -e + +echo "Setting up development environment for terraform-provider-tor..." + +# Get the Go bin path +GOBIN=$(go env GOPATH)/bin +if [ -z "$GOBIN" ]; then + GOBIN=$(go env GOROOT)/bin +fi + +echo "Go bin path: $GOBIN" + +# Create local .terraformrc with dev overrides +TERRAFORMRC="$(pwd)/.terraformrc" +echo "Creating $TERRAFORMRC..." + +# Create local .terraformrc with dev overrides +cat > "$TERRAFORMRC" << EOF +provider_installation { + dev_overrides { + "guardianproject/tor" = "$GOBIN" + } + direct {} +} +EOF + +echo "✓ Created local $TERRAFORMRC with dev overrides" + +# Build and install the provider +echo "Building and installing provider..." +cd "$(dirname "$0")/.." + +# Build with proper naming for dev overrides +go build -o "$GOBIN/terraform-provider-tor_v99.0.0" + +echo "✓ Provider built and installed to $GOBIN/terraform-provider-tor_v99.0.0" + +echo "" +echo "Setup complete! You can now run:" +echo " cd e2e-test" +echo " ./tf plan" +echo " ./tf apply" +echo "" +echo "Or use the full command:" +echo " TF_CLI_CONFIG_FILE=.terraformrc tofu plan" +echo "" +echo "Note: Using local .terraformrc to avoid modifying your global configuration." diff --git a/e2e-tests/tor-family/tf b/e2e-test/tf similarity index 68% rename from e2e-tests/tor-family/tf rename to e2e-test/tf index e4fd76b..2df7ce0 100755 --- a/e2e-tests/tor-family/tf +++ b/e2e-test/tf @@ -1,4 +1,5 @@ #!/usr/bin/env bash # Wrapper script to run terraform with local config -export TF_CLI_CONFIG_FILE=terraformrc + +export TF_CLI_CONFIG_FILE=.terraformrc exec tofu "$@" diff --git a/e2e-tests/obfs4/terraformrc b/e2e-tests/obfs4/terraformrc deleted file mode 100644 index 553f6dd..0000000 --- a/e2e-tests/obfs4/terraformrc +++ /dev/null @@ -1,15 +0,0 @@ - provider_installation { - filesystem_mirror { - path = "./providers" - include = [ - "registry.terraform.io/guardianproject/*", - "registry.opentofu.org/guardianproject/*" - ] - } - direct { - exclude = [ - "registry.terraform.io/guardianproject/*", - "registry.opentofu.org/guardianproject/*" - ] - } - } diff --git a/e2e-tests/obfs4/test.sh b/e2e-tests/obfs4/test.sh deleted file mode 100755 index 5a94526..0000000 --- a/e2e-tests/obfs4/test.sh +++ /dev/null @@ -1,7 +0,0 @@ -#!/usr/bin/env sh -set -e -../setup.sh -rm -f terraform.tfstate* -./tf init -./tf plan -./tf apply -auto-approve diff --git a/e2e-tests/obfs4/tf b/e2e-tests/obfs4/tf deleted file mode 100755 index 1b3ad19..0000000 --- a/e2e-tests/obfs4/tf +++ /dev/null @@ -1,3 +0,0 @@ -#!/usr/bin/env bash -export TF_CLI_CONFIG_FILE=terraformrc -exec tofu "$@" diff --git a/e2e-tests/setup.sh b/e2e-tests/setup.sh deleted file mode 100755 index fdd911f..0000000 --- a/e2e-tests/setup.sh +++ /dev/null @@ -1,14 +0,0 @@ -#!/usr/bin/env bash -set -e -rm -f ./.terraform.lock.hcl - -mkdir -p providers/registry.terraform.io/guardianproject/tor/99.0.0/linux_amd64 -mkdir -p providers/registry.opentofu.org/guardianproject/tor/99.0.0/linux_amd64 -cp ../../terraform-provider-tor providers/registry.terraform.io/guardianproject/tor/99.0.0/linux_amd64 -cp ../../terraform-provider-tor providers/registry.opentofu.org/guardianproject/tor/99.0.0/linux_amd64 - -echo "" -echo "Setup complete! You can now run:" -echo " ./tf init" -echo " ./tf plan" -echo " ./tf apply" diff --git a/e2e-tests/tor-family/main.tf b/e2e-tests/tor-family/main.tf deleted file mode 100644 index e851a18..0000000 --- a/e2e-tests/tor-family/main.tf +++ /dev/null @@ -1,44 +0,0 @@ -terraform { - required_providers { - tor = { - source = "guardianproject/tor" - version = "99.0.0" - } - local = { - source = "hashicorp/local" - version = "2.5.3" - } - } -} - -provider "tor" {} - -resource "tor_family_identity" "this" { - family_name = "MyFamily" -} - -resource "local_sensitive_file" "family_key" { - content_base64 = tor_family_identity.this.secret_key - filename = "./data/keys/MyKey.secret_family_key" - file_permission = "0600" -} - -resource "local_file" "this" { - filename = "./torrc" - content = < -Nickname PickANickname -EOF -} - - -output "family_id" { - description = "Family ID for the bridge" - value = tor_family_identity.this.id -} diff --git a/e2e-tests/tor-family/terraformrc b/e2e-tests/tor-family/terraformrc deleted file mode 100644 index 553f6dd..0000000 --- a/e2e-tests/tor-family/terraformrc +++ /dev/null @@ -1,15 +0,0 @@ - provider_installation { - filesystem_mirror { - path = "./providers" - include = [ - "registry.terraform.io/guardianproject/*", - "registry.opentofu.org/guardianproject/*" - ] - } - direct { - exclude = [ - "registry.terraform.io/guardianproject/*", - "registry.opentofu.org/guardianproject/*" - ] - } - } diff --git a/e2e-tests/tor-family/test.sh b/e2e-tests/tor-family/test.sh deleted file mode 100755 index 5071b57..0000000 --- a/e2e-tests/tor-family/test.sh +++ /dev/null @@ -1,34 +0,0 @@ -#!/usr/bin/env sh -set -e -../setup.sh -rm -f terraform.tfstate* -./tf init -./tf plan -./tf apply -auto-approve - -set +e - -# Start tor and let it run for a few seconds -echo "Starting Tor to verify family key..." -timeout 5 tor -f ./torrc >tor.log 2>&1 -TOR_EXIT_CODE=$? - -set -e - -# Check if tor exited with an error (not due to timeout) -# timeout returns 124 when it kills the process -if [ $TOR_EXIT_CODE -ne 0 ] && [ $TOR_EXIT_CODE -ne 124 ]; then - echo "ERROR: Tor exited with error code $TOR_EXIT_CODE" - cat tor.log - exit 1 -fi - -# Check if tor started bootstrapping (indicates successful key loading) -if grep -q "Bootstrapped [0-9]" tor.log; then - echo "SUCCESS: Tor started bootstrapping with generated family key" - exit 0 -else - echo "ERROR: Tor did not start bootstrapping" - cat tor.log - exit 1 -fi diff --git a/examples/resources/tor_family_identity/resource.tf b/examples/resources/tor_family_identity/resource.tf deleted file mode 100644 index 3bccb40..0000000 --- a/examples/resources/tor_family_identity/resource.tf +++ /dev/null @@ -1,17 +0,0 @@ -resource "tor_family_identity" "example" { - family_name = "MyFamily" -} - - -resource "local_sensitive_file" "family_key" { - content_base64 = tor_family_identity.example.secret_key - filename = "MyFamily.secret_family_key" -} - -resource "local_file" "torrc" { - filename = "./torrc" - content = < - -package provider - -import ( - "context" - "crypto/rand" - "crypto/sha512" - "encoding/base64" - "fmt" - - "filippo.io/edwards25519" - "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 = &TorFamilyIdentityResource{} - -func NewTorFamilyIdentityResource() resource.Resource { - return &TorFamilyIdentityResource{} -} - -type TorFamilyIdentityResource struct{} - -type TorFamilyIdentityResourceModel struct { - Id types.String `tfsdk:"id"` - FamilyName types.String `tfsdk:"family_name"` - SecretKey types.String `tfsdk:"secret_key"` -} - -func (r *TorFamilyIdentityResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { - resp.TypeName = req.ProviderTypeName + "_family_identity" -} - -func (r *TorFamilyIdentityResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { - resp.Schema = schema.Schema{ - MarkdownDescription: `Generates a Tor family identity key as described in proposal 321 (Happy Families). - -### References -- https://community.torproject.org/relay/setup/post-install/family-ids/ -- https://gitlab.torproject.org/tpo/core/torspec/-/blob/main/proposals/321-happy-families.md - `, - - Attributes: map[string]schema.Attribute{ - "id": schema.StringAttribute{ - Computed: true, - MarkdownDescription: "Base64-encoded public key (as stored in public_family_id file)", - PlanModifiers: []planmodifier.String{ - stringplanmodifier.UseStateForUnknown(), - }, - }, - "family_name": schema.StringAttribute{ - Required: true, - MarkdownDescription: "Name of the family", - PlanModifiers: []planmodifier.String{ - stringplanmodifier.RequiresReplace(), - }, - }, - "secret_key": schema.StringAttribute{ - Computed: true, - Sensitive: true, - MarkdownDescription: "Binary contents of the secret family key file (base64 encoded)", - PlanModifiers: []planmodifier.String{ - stringplanmodifier.UseStateForUnknown(), - }, - }, - }, - } -} - -func (r *TorFamilyIdentityResource) Configure(ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) { -} - -// torExpandSeed mimics Tor's ed25519_extsk function -func torExpandSeed(seed []byte) []byte { - h := sha512.Sum512(seed) - - // Apply ed25519 bit manipulation to first 32 bytes - h[0] &= 248 - h[31] &= 127 - h[31] |= 64 - - return h[:] -} - -// torComputePublicKey mimics how Tor computes public key from expanded secret key -func torComputePublicKey(expandedSK []byte) ([]byte, error) { - // Tor uses only the first 32 bytes (the scalar) to compute public key - scalar := expandedSK[:32] - - var scalarBytes [32]byte - copy(scalarBytes[:], scalar) - - // The scalar is already clamped, so use SetBytesWithClamping - s, err := edwards25519.NewScalar().SetBytesWithClamping(scalarBytes[:]) - if err != nil { - return nil, fmt.Errorf("failed to create scalar: %v", err) - } - - publicKey := edwards25519.NewIdentityPoint().ScalarBaseMult(s) - - return publicKey.Bytes(), nil -} - -func (r *TorFamilyIdentityResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { - var data TorFamilyIdentityResourceModel - - resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...) - - if resp.Diagnostics.HasError() { - return - } - - seed := make([]byte, 32) - if _, err := rand.Read(seed); err != nil { - resp.Diagnostics.AddError("Failed to generate random seed", err.Error()) - return - } - - expandedSK := torExpandSeed(seed) - - publicKey, err := torComputePublicKey(expandedSK) - if err != nil { - resp.Diagnostics.AddError("Failed to compute public key", err.Error()) - return - } - - // Format secret key file content following Tor's tagged format - const FAMILY_KEY_FILE_TAG = "fmly-id" - header := fmt.Sprintf("== ed25519v1-secret: %s ==\x00", FAMILY_KEY_FILE_TAG) - secretKeyContent := append([]byte(header), expandedSK...) - - // Encode public key as base64 without padding (matching Tor's format) - publicKeyBase64 := base64.RawStdEncoding.EncodeToString(publicKey) - - data.Id = types.StringValue(publicKeyBase64) - data.SecretKey = types.StringValue(base64.StdEncoding.EncodeToString(secretKeyContent)) - - tflog.Trace(ctx, "created a family identity resource") - - resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) -} - -func (r *TorFamilyIdentityResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { - var data TorFamilyIdentityResourceModel - - resp.Diagnostics.Append(req.State.Get(ctx, &data)...) - - if resp.Diagnostics.HasError() { - return - } - - resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) -} - -func (r *TorFamilyIdentityResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { - resp.Diagnostics.AddError("Update not supported", "All changes to family identity require resource replacement") -} - -func (r *TorFamilyIdentityResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { -} diff --git a/internal/provider/tor_family_identity_resource_serialization_test.go b/internal/provider/tor_family_identity_resource_serialization_test.go deleted file mode 100644 index 328e450..0000000 --- a/internal/provider/tor_family_identity_resource_serialization_test.go +++ /dev/null @@ -1,80 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-or-later -// Copyright (C) 2025 Abel Luck - -package provider - -import ( - "encoding/base64" - "encoding/hex" - "testing" -) - -// Test data from a secret key generated by Tor version 0.4.9.2-alpha -// To reproduce: -// 1. Run: tor --keygen-family MyKey -// 2. This creates MyKey.secret_family_key and MyKey.public_family_id -// 3. Convert secret key to hex: xxd -p MyKey.secret_family_key | tr -d '\n' -const torGeneratedSecretKeyHex = "3d3d206564323535313976312d7365637265743a20666d6c792d6964203d3d00f84c620e227fcc5085eb538a29a11ac25abb052b6a36ddae008b307cca67fe792ecd73a67c0a7a28b2b747be2a59e5ef0c155e33217add6dac42dbc6f85a5162" -const torGeneratedPublicFamilyID = "xiHyFHgSU4/aaqPfRCrUk61jVBDQAv1hlHQd7lKIlI8" - -func TestTorFamilyIdentity_SerializationAgainstTorGenerated(t *testing.T) { - secretKeyBytes, err := hex.DecodeString(torGeneratedSecretKeyHex) - if err != nil { - t.Fatalf("Failed to decode secret key hex: %v", err) - } - - const expectedHeader = "== ed25519v1-secret: fmly-id ==\x00" - if len(secretKeyBytes) < len(expectedHeader) { - t.Fatalf("Secret key file too short: %d bytes", len(secretKeyBytes)) - } - - header := string(secretKeyBytes[:len(expectedHeader)]) - if header != expectedHeader { - t.Fatalf("Invalid header: got %q, want %q", header, expectedHeader) - } - - expandedSK := secretKeyBytes[len(expectedHeader):] - if len(expandedSK) != 64 { - t.Fatalf("Invalid expanded secret key length: got %d, want 64", len(expandedSK)) - } - - publicKey, err := torComputePublicKey(expandedSK) - if err != nil { - t.Fatalf("Failed to compute public key: %v", err) - } - - computedFamilyID := base64.RawStdEncoding.EncodeToString(publicKey) - - if computedFamilyID != torGeneratedPublicFamilyID { - t.Errorf("Family ID mismatch:\n computed: %s\n expected: %s", - computedFamilyID, torGeneratedPublicFamilyID) - } -} - -func TestTorFamilyIdentity_ExpandSeed(t *testing.T) { - // Test that our seed expansion produces the same result as Tor - // We'll need to reverse-engineer the seed from the expanded key - // This is a secondary test to verify our torExpandSeed function - secretKeyBytes, err := hex.DecodeString(torGeneratedSecretKeyHex) - if err != nil { - t.Fatalf("Failed to decode secret key hex: %v", err) - } - - const expectedHeader = "== ed25519v1-secret: fmly-id ==\x00" - expandedSK := secretKeyBytes[len(expectedHeader):] - - // Verify the key is properly clamped according to ed25519 standards - // For ed25519, the clamping is: - // - bits 0,1,2 of first byte cleared (make it a multiple of 8) - // - bit 6 of byte 31 set - // - bit 7 of byte 31 cleared - if expandedSK[0]&0x07 != 0 { - t.Errorf("First byte not properly clamped: %02x (bits 0,1,2 should be cleared)", expandedSK[0]) - } - if expandedSK[31]&0x40 == 0 { - t.Errorf("Byte 31 bit 6 should be set: %02x", expandedSK[31]) - } - if expandedSK[31]&0x80 != 0 { - t.Errorf("Byte 31 bit 7 should be cleared: %02x", expandedSK[31]) - } -} diff --git a/internal/provider/tor_family_identity_resource_test.go b/internal/provider/tor_family_identity_resource_test.go deleted file mode 100644 index 6e742b2..0000000 --- a/internal/provider/tor_family_identity_resource_test.go +++ /dev/null @@ -1,85 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-or-later -// Copyright (c) 2025 Abel Luck - -package provider - -import ( - "encoding/base64" - "fmt" - "regexp" - "strings" - "testing" - - "github.com/hashicorp/terraform-plugin-testing/helper/resource" - "github.com/hashicorp/terraform-plugin-testing/terraform" -) - -func TestAccTorFamilyIdentityResource(t *testing.T) { - resource.Test(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, - ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, - Steps: []resource.TestStep{ - { - Config: testAccTorFamilyIdentityResourceConfig("MyFamily"), - Check: resource.ComposeAggregateTestCheckFunc( - resource.TestCheckResourceAttr("tor_family_identity.test", "family_name", "MyFamily"), - resource.TestMatchResourceAttr("tor_family_identity.test", "id", regexp.MustCompile(`^[A-Za-z0-9+/]{43}$`)), - resource.TestCheckResourceAttrSet("tor_family_identity.test", "secret_key"), - testAccCheckTorFamilyIdentitySecretKeyFormat("tor_family_identity.test", "secret_key"), - ), - }, - }, - }) -} - -func testAccCheckTorFamilyIdentitySecretKeyFormat(resourceName, attributeName 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) - } - - secretKeyBase64 := rs.Primary.Attributes[attributeName] - if secretKeyBase64 == "" { - return fmt.Errorf("secret_key attribute is empty") - } - - secretKey, err := base64.StdEncoding.DecodeString(secretKeyBase64) - if err != nil { - return fmt.Errorf("Failed to decode secret_key: %v", err) - } - - if len(secretKey) != 96 { - return fmt.Errorf("secret_key has wrong size: expected 96 bytes, got %d", len(secretKey)) - } - - expectedHeader := "== ed25519v1-secret: fmly-id ==" - actualHeader := string(secretKey[:32]) - if !strings.HasPrefix(actualHeader, expectedHeader) { - return fmt.Errorf("secret_key has wrong header: expected %q, got %q", expectedHeader, actualHeader[:len(expectedHeader)]) - } - - if secretKey[31] != 0 { - return fmt.Errorf("secret_key header should end with null byte") - } - - id := rs.Primary.Attributes["id"] - if _, err := base64.RawStdEncoding.DecodeString(id); err != nil { - return fmt.Errorf("id should be a valid base64 string without padding: %s", id) - } - - if strings.HasSuffix(id, "=") { - return fmt.Errorf("id should not have padding characters: %s", id) - } - - return nil - } -} - -func testAccTorFamilyIdentityResourceConfig(familyName string) string { - return fmt.Sprintf(` -resource "tor_family_identity" "test" { - family_name = %[1]q -} -`, familyName) -}