diff --git a/github/resource_github_actions_organization_secret.go b/github/resource_github_actions_organization_secret.go index 3e55589bcb..4e23ae5f3e 100644 --- a/github/resource_github_actions_organization_secret.go +++ b/github/resource_github_actions_organization_secret.go @@ -9,6 +9,7 @@ import ( "github.com/google/go-github/v35/github" "github.com/hashicorp/terraform-plugin-sdk/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/helper/validation" ) func resourceGithubActionsOrganizationSecret() *schema.Resource { @@ -31,11 +32,20 @@ func resourceGithubActionsOrganizationSecret() *schema.Resource { ForceNew: true, ValidateFunc: validateSecretNameFunc, }, + "encrypted_value": { + Type: schema.TypeString, + ForceNew: true, + Optional: true, + Sensitive: true, + ConflictsWith: []string{"plaintext_value"}, + ValidateFunc: validation.StringIsBase64, + }, "plaintext_value": { - Type: schema.TypeString, - Required: true, - ForceNew: true, - Sensitive: true, + Type: schema.TypeString, + ForceNew: true, + Optional: true, + Sensitive: true, + ConflictsWith: []string{"encrypted_value"}, }, "visibility": { Type: schema.TypeString, @@ -70,6 +80,7 @@ func resourceGithubActionsOrganizationSecretCreateOrUpdate(d *schema.ResourceDat secretName := d.Get("secret_name").(string) plaintextValue := d.Get("plaintext_value").(string) + var encryptedValue string visibility := d.Get("visibility").(string) selectedRepositories, hasSelectedRepositories := d.GetOk("selected_repository_ids") @@ -95,9 +106,14 @@ func resourceGithubActionsOrganizationSecretCreateOrUpdate(d *schema.ResourceDat return err } - encryptedText, err := encryptPlaintext(plaintextValue, publicKey) - if err != nil { - return err + if encryptedText, ok := d.GetOk("encrypted_value"); ok { + encryptedValue = encryptedText.(string) + } else { + encryptedBytes, err := encryptPlaintext(plaintextValue, publicKey) + if err != nil { + return err + } + encryptedValue = base64.StdEncoding.EncodeToString(encryptedBytes) } // Create an EncryptedSecret and encrypt the plaintext value into it @@ -106,7 +122,7 @@ func resourceGithubActionsOrganizationSecretCreateOrUpdate(d *schema.ResourceDat KeyID: keyId, Visibility: visibility, SelectedRepositoryIDs: selectedRepositoryIDs, - EncryptedValue: base64.StdEncoding.EncodeToString(encryptedText), + EncryptedValue: encryptedValue, } _, err = client.Actions.CreateOrUpdateOrgSecret(ctx, owner, eSecret) @@ -136,6 +152,7 @@ func resourceGithubActionsOrganizationSecretRead(d *schema.ResourceData, meta in return err } + d.Set("encrypted_value", d.Get("encrypted_value")) d.Set("plaintext_value", d.Get("plaintext_value")) d.Set("created_at", secret.CreatedAt.String()) d.Set("visibility", secret.Visibility) diff --git a/github/resource_github_actions_organization_secret_test.go b/github/resource_github_actions_organization_secret_test.go index 059d255c8e..33dca385b4 100644 --- a/github/resource_github_actions_organization_secret_test.go +++ b/github/resource_github_actions_organization_secret_test.go @@ -14,36 +14,50 @@ func TestAccGithubActionsOrganizationSecret(t *testing.T) { updatedSecretValue := "updated_super_secret_value" config := fmt.Sprintf(` - resource "github_actions_organization_secret" "test_secret" { - secret_name = "test_secret_name" + resource "github_actions_organization_secret" "plaintext_secret" { + secret_name = "test_plaintext_secret" plaintext_value = "%s" visibility = "private" } - `, secretValue) + + resource "github_actions_organization_secret" "encrypted_secret" { + secret_name = "test_encrypted_secret" + encrypted_value = "%s" + visibility = "private" + } + `, secretValue, secretValue) checks := map[string]resource.TestCheckFunc{ "before": resource.ComposeTestCheckFunc( resource.TestCheckResourceAttr( - "github_actions_organization_secret.test_secret", "plaintext_value", + "github_actions_organization_secret.plaintext_secret", "plaintext_value", + secretValue, + ), + resource.TestCheckResourceAttr( + "github_actions_organization_secret.encrypted_secret", "encrypted_value", secretValue, ), resource.TestCheckResourceAttrSet( - "github_actions_organization_secret.test_secret", "created_at", + "github_actions_organization_secret.plaintext_secret", "created_at", ), resource.TestCheckResourceAttrSet( - "github_actions_organization_secret.test_secret", "updated_at", + "github_actions_organization_secret.plaintext_secret", "updated_at", ), ), "after": resource.ComposeTestCheckFunc( resource.TestCheckResourceAttr( - "github_actions_organization_secret.test_secret", "plaintext_value", + "github_actions_organization_secret.plaintext_secret", "plaintext_value", + updatedSecretValue, + ), + resource.TestCheckResourceAttr( + "github_actions_organization_secret.encrypted_secret", "encrypted_value", updatedSecretValue, ), resource.TestCheckResourceAttrSet( - "github_actions_organization_secret.test_secret", "created_at", + "github_actions_organization_secret.plaintext_secret", "created_at", ), resource.TestCheckResourceAttrSet( - "github_actions_organization_secret.test_secret", "updated_at", + "github_actions_organization_secret.plaintext_secret", "updated_at", ), ), } @@ -60,7 +74,7 @@ func TestAccGithubActionsOrganizationSecret(t *testing.T) { { Config: strings.Replace(config, secretValue, - updatedSecretValue, 1), + updatedSecretValue, 2), Check: checks["after"], }, }, @@ -81,15 +95,17 @@ func TestAccGithubActionsOrganizationSecret(t *testing.T) { }) t.Run("deletes secrets without error", func(t *testing.T) { - secretValue := "super_secret_value" + config := ` + resource "github_actions_organization_secret" "plaintext_secret" { + secret_name = "test_plaintext_secret" + visibility = "private" + } - config := fmt.Sprintf(` - resource "github_actions_organization_secret" "test_secret" { - secret_name = "test_secret_name" - plaintext_value = "%s" + resource "github_actions_organization_secret" "encrypted_secret" { + secret_name = "test_encrypted_secret" visibility = "private" } - `, secretValue) + ` testCase := func(t *testing.T, mode string) { resource.Test(t, resource.TestCase{ @@ -122,7 +138,7 @@ func TestAccGithubActionsOrganizationSecret(t *testing.T) { config := fmt.Sprintf(` resource "github_actions_organization_secret" "test_secret" { - secret_name = "test_secret_name" + secret_name = "test_plaintext_secret" plaintext_value = "%s" visibility = "private" } diff --git a/github/resource_github_actions_secret.go b/github/resource_github_actions_secret.go index c1ad5d784f..034734d48a 100644 --- a/github/resource_github_actions_secret.go +++ b/github/resource_github_actions_secret.go @@ -30,10 +30,19 @@ func resourceGithubActionsSecret() *schema.Resource { ForceNew: true, ValidateFunc: validateSecretNameFunc, }, + "encrypted_value": { + Type: schema.TypeString, + ForceNew: true, + Optional: true, + Sensitive: true, + ConflictsWith: []string{"plaintext_value"}, + }, "plaintext_value": { - Type: schema.TypeString, - Required: true, - Sensitive: true, + Type: schema.TypeString, + ForceNew: true, + Optional: true, + Sensitive: true, + ConflictsWith: []string{"encrypted_value"}, }, "created_at": { Type: schema.TypeString, @@ -55,22 +64,28 @@ func resourceGithubActionsSecretCreateOrUpdate(d *schema.ResourceData, meta inte repo := d.Get("repository").(string) secretName := d.Get("secret_name").(string) plaintextValue := d.Get("plaintext_value").(string) + var encryptedValue string keyId, publicKey, err := getPublicKeyDetails(owner, repo, meta) if err != nil { return err } - encryptedText, err := encryptPlaintext(plaintextValue, publicKey) - if err != nil { - return err + if encryptedText, ok := d.GetOk("encrypted_value"); ok { + encryptedValue = encryptedText.(string) + } else { + encryptedBytes, err := encryptPlaintext(plaintextValue, publicKey) + if err != nil { + return err + } + encryptedValue = base64.StdEncoding.EncodeToString(encryptedBytes) } // Create an EncryptedSecret and encrypt the plaintext value into it eSecret := &github.EncryptedSecret{ Name: secretName, KeyID: keyId, - EncryptedValue: base64.StdEncoding.EncodeToString(encryptedText), + EncryptedValue: encryptedValue, } _, err = client.Actions.CreateOrUpdateRepoSecret(ctx, owner, repo, eSecret) @@ -105,6 +120,7 @@ func resourceGithubActionsSecretRead(d *schema.ResourceData, meta interface{}) e return err } + d.Set("encrypted_value", d.Get("encrypted_value")) d.Set("plaintext_value", d.Get("plaintext_value")) d.Set("created_at", secret.CreatedAt.String()) diff --git a/github/resource_github_actions_secret_test.go b/github/resource_github_actions_secret_test.go index c072b743a7..8137182ba7 100644 --- a/github/resource_github_actions_secret_test.go +++ b/github/resource_github_actions_secret_test.go @@ -74,36 +74,50 @@ func TestAccGithubActionsSecret(t *testing.T) { name = "tf-acc-test-%s" } - resource "github_actions_secret" "test_secret" { + resource "github_actions_secret" "plaintext_secret" { repository = github_repository.test.name - secret_name = "test_secret_name" + secret_name = "test_plaintext_secret" plaintext_value = "%s" } - `, randomID, secretValue) + + resource "github_actions_secret" "encrypted_secret" { + repository = github_repository.test.name + secret_name = "test_encrypted_secret" + encrypted_value = "%s" + } + `, randomID, secretValue, secretValue) checks := map[string]resource.TestCheckFunc{ "before": resource.ComposeTestCheckFunc( resource.TestCheckResourceAttr( - "github_actions_secret.test_secret", "plaintext_value", + "github_actions_secret.plaintext_secret", "plaintext_value", + secretValue, + ), + resource.TestCheckResourceAttr( + "github_actions_secret.encrypted_secret", "encrypted_value", secretValue, ), resource.TestCheckResourceAttrSet( - "github_actions_secret.test_secret", "created_at", + "github_actions_secret.plaintext_secret", "created_at", ), resource.TestCheckResourceAttrSet( - "github_actions_secret.test_secret", "updated_at", + "github_actions_secret.plaintext_secret", "updated_at", ), ), "after": resource.ComposeTestCheckFunc( resource.TestCheckResourceAttr( - "github_actions_secret.test_secret", "plaintext_value", + "github_actions_secret.plaintext_secret", "plaintext_value", + updatedSecretValue, + ), + resource.TestCheckResourceAttr( + "github_actions_secret.encrypted_secret", "encrypted_value", updatedSecretValue, ), resource.TestCheckResourceAttrSet( - "github_actions_secret.test_secret", "created_at", + "github_actions_secret.plaintext_secret", "created_at", ), resource.TestCheckResourceAttrSet( - "github_actions_secret.test_secret", "updated_at", + "github_actions_secret.plaintext_secret", "updated_at", ), ), } @@ -120,7 +134,7 @@ func TestAccGithubActionsSecret(t *testing.T) { { Config: strings.Replace(config, secretValue, - updatedSecretValue, 1), + updatedSecretValue, 2), Check: checks["after"], }, }, @@ -138,24 +152,24 @@ func TestAccGithubActionsSecret(t *testing.T) { t.Run("with an organization account", func(t *testing.T) { testCase(t, organization) }) - }) t.Run("deletes secrets without error", func(t *testing.T) { - - secretValue := "super_secret_value" - config := fmt.Sprintf(` resource "github_repository" "test" { name = "tf-acc-test-%s" } - resource "github_actions_secret" "test_secret" { - repository = github_repository.test.name - secret_name = "test_secret_name" - plaintext_value = "%s" + resource "github_actions_secret" "plaintext_secret" { + repository = github_repository.test.name + secret_name = "test_plaintext_secret" + } + + resource "github_actions_secret" "encrypted_secret" { + repository = github_repository.test.name + secret_name = "test_encrypted_secret" } - `, randomID, secretValue) + `, randomID) testCase := func(t *testing.T, mode string) { resource.Test(t, resource.TestCase{ @@ -183,5 +197,4 @@ func TestAccGithubActionsSecret(t *testing.T) { }) }) - } diff --git a/website/docs/r/actions_organization_secret.html.markdown b/website/docs/r/actions_organization_secret.html.markdown index 63f0e5e08e..1f62725fdb 100644 --- a/website/docs/r/actions_organization_secret.html.markdown +++ b/website/docs/r/actions_organization_secret.html.markdown @@ -15,7 +15,7 @@ interoperable with [libsodium](https://libsodium.gitbook.io/doc/). Libsodium is For the purposes of security, the contents of the `plaintext_value` field have been marked as `sensitive` to Terraform, but it is important to note that **this does not hide it from state files**. You should treat state as sensitive always. -It is also advised that you do not store plaintext values in your code but rather populate the `plaintext_value` +It is also advised that you do not store plaintext values in your code but rather populate the `encrypted_value` using fields from a resource, data source or variable as, while encrypted in state, these will be easily accessible in your code. See below for an example of this abstraction. @@ -27,6 +27,12 @@ resource "github_actions_organization_secret" "example_secret" { visibility = "private" plaintext_value = var.some_secret_string } + +resource "github_actions_organization_secret" "example_secret" { + secret_name = "example_secret_name" + visibility = "private" + encrypted_value = var.some_encrypted_secret_string +} ``` ```hcl @@ -40,6 +46,13 @@ resource "github_actions_organization_secret" "example_secret" { plaintext_value = var.some_secret_string selected_repository_ids = [data.github_repository.repo.repo_id] } + +resource "github_actions_organization_secret" "example_secret" { + secret_name = "example_secret_name" + visibility = "selected" + encrypted_value = var.some_encrypted_secret_string + selected_repository_ids = [data.github_repository.repo.repo_id] +} ``` ## Argument Reference @@ -47,7 +60,8 @@ resource "github_actions_organization_secret" "example_secret" { The following arguments are supported: * `secret_name` - (Required) Name of the secret -* `plaintext_value` - (Required) Plaintext value of the secret to be encrypted +* `encrypted_value` - (Optional) Encrypted value of the secret using the Github public key in Base64 format. +* `plaintext_value` - (Optional) Plaintext value of the secret to be encrypted * `visiblity` - (Required) Configures the access that repositories have to the organization secret. Must be one of `all`, `private`, `selected`. `selected_repository_ids` is required if set to `selected`. * `selected_repository_ids` - (Optional) An array of repository ids that can access the organization secret. @@ -66,5 +80,4 @@ $ terraform import github_actions_organization_secret.test_secret test_secret_na ``` NOTE: the implementation is limited in that it won't fetch the value of the -`plaintext_value` field when importing. You may need to ignore changes for the -`plaintext_value` as a workaround. +`plaintext_value` or `encrypted_value` fields when importing. You may need to ignore changes for these as a workaround. diff --git a/website/docs/r/actions_secret.html.markdown b/website/docs/r/actions_secret.html.markdown index d4becd7239..dd4adca7b2 100644 --- a/website/docs/r/actions_secret.html.markdown +++ b/website/docs/r/actions_secret.html.markdown @@ -15,7 +15,7 @@ interoperable with [libsodium](https://libsodium.gitbook.io/doc/). Libsodium is For the purposes of security, the contents of the `plaintext_value` field have been marked as `sensitive` to Terraform, but it is important to note that **this does not hide it from state files**. You should treat state as sensitive always. -It is also advised that you do not store plaintext values in your code but rather populate the `plaintext_value` +It is also advised that you do not store plaintext values in your code but rather populate the `encrypted_value` using fields from a resource, data source or variable as, while encrypted in state, these will be easily accessible in your code. See below for an example of this abstraction. @@ -31,6 +31,12 @@ resource "github_actions_secret" "example_secret" { secret_name = "example_secret_name" plaintext_value = var.some_secret_string } + +resource "github_actions_secret" "example_secret" { + repository = "example_repository" + secret_name = "example_secret_name" + encrypted_value = var.some_encrypted_secret_string +} ``` ## Argument Reference @@ -39,7 +45,8 @@ The following arguments are supported: * `repository` - (Required) Name of the repository * `secret_name` - (Required) Name of the secret -* `plaintext_value` - (Required) Plaintext value of the secret to be encrypted +* `encrypted_value` - (Optional) Encrypted value of the secret using the Github public key in Base64 format. +* `plaintext_value` - (Optional) Plaintext value of the secret to be encrypted ## Attributes Reference