Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 36 additions & 2 deletions github/resource_github_actions_environment_secret.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ func resourceGithubActionsEnvironmentSecret() *schema.Resource {
ForceNew: true,
Sensitive: true,
Description: "Encrypted value of the secret using the GitHub public key in Base64 format.",
ConflictsWith: []string{"plaintext_value"},
ConflictsWith: []string{"plaintext_value", "plaintext_value_wo"},
ValidateDiagFunc: toDiagFunc(validation.StringIsBase64, "encrypted_value"),
},
"plaintext_value": {
Expand All @@ -55,7 +55,23 @@ func resourceGithubActionsEnvironmentSecret() *schema.Resource {
ForceNew: true,
Sensitive: true,
Description: "Plaintext value of the secret to be encrypted.",
ConflictsWith: []string{"encrypted_value"},
ConflictsWith: []string{"encrypted_value", "plaintext_value_wo"},
},
"plaintext_value_wo":{
Type: schema.TypeString,
Optional: true,
Sensitive: true,
WriteOnly: true,
Description: "Write-Only plaintext value of the secret to be encrypted.",
ConflictsWith: []string{"encrypted_value", "plaintext_value"},
RequiredWith: []string{"plaintext_value_wo_version"},
},
"plaintext_value_wo_version":{
Type: schema.TypeInt,
Optional: true,
ForceNew: true,
Description: "Write-Only plaintext value of the secret to be encrypted.",
RequiredWith: []string{"plaintext_value_wo"},
},
"created_at": {
Type: schema.TypeString,
Expand Down Expand Up @@ -93,6 +109,12 @@ func resourceGithubActionsEnvironmentSecretCreateOrUpdate(ctx context.Context, d

if encryptedText, ok := d.GetOk("encrypted_value"); ok {
encryptedValue = encryptedText.(string)
} else if plaintextValueWO, ok := d.GetOk("plaintext_value_wo"); ok {
encryptedBytes, err := encryptPlaintext(plaintextValueWO.(string), publicKey)
if err != nil {
return diag.FromErr(err)
}
encryptedValue = base64.StdEncoding.EncodeToString(encryptedBytes)
} else {
encryptedBytes, err := encryptPlaintext(plaintextValue, publicKey)
if err != nil {
Expand All @@ -101,6 +123,14 @@ func resourceGithubActionsEnvironmentSecretCreateOrUpdate(ctx context.Context, d
encryptedValue = base64.StdEncoding.EncodeToString(encryptedBytes)
}

// When using write-only plaintext and the resource already exists, only write to GitHub if the version changed.
if _, useWO := d.GetOk("plaintext_value_wo"); useWO && d.Id() != "" {
oldVer, newVer := d.GetChange("plaintext_value_wo_version")
if oldVer.(int) == newVer.(int) {
return resourceGithubActionsEnvironmentSecretRead(ctx, d, meta)
}
}

// Create an EncryptedSecret and encrypt the plaintext value into it
eSecret := &github.EncryptedSecret{
Name: secretName,
Expand Down Expand Up @@ -168,6 +198,9 @@ func resourceGithubActionsEnvironmentSecretRead(ctx context.Context, d *schema.R
if err = d.Set("plaintext_value", d.Get("plaintext_value")); err != nil {
return diag.FromErr(err)
}
if err = d.Set("plaintext_value_wo_version", d.Get("plaintext_value_wo_version")); err != nil {
return diag.FromErr(err)
}
if err = d.Set("created_at", secret.CreatedAt.String()); err != nil {
return diag.FromErr(err)
}
Expand All @@ -189,6 +222,7 @@ func resourceGithubActionsEnvironmentSecretRead(ctx context.Context, d *schema.R
log.Printf("[INFO] The environment secret %s has been externally updated in GitHub", d.Id())
_ = d.Set("encrypted_value", "")
_ = d.Set("plaintext_value", "")
_ = d.Set("plaintext_value_wo_version", "")
} else if !ok {
if err = d.Set("updated_at", secret.UpdatedAt.String()); err != nil {
return diag.FromErr(err)
Expand Down
97 changes: 74 additions & 23 deletions github/resource_github_actions_environment_secret_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,13 +35,22 @@ func TestAccGithubActionsEnvironmentSecret(t *testing.T) {
plaintext_value = "%s"
}

resource "github_actions_environment_secret" "plaintext_secret_wo" {
repository = github_repository.test.name
environment = github_repository_environment.test.environment
secret_name = "test_plaintext_secret_wo_name"
plaintext_value_wo = "%s"
plaintext_value_wo_version = 1234
}

resource "github_actions_environment_secret" "encrypted_secret" {
repository = github_repository.test.name
environment = github_repository_environment.test.environment
secret_name = "test_encrypted_secret_name"
encrypted_value = "%s"
}
`, repoName, secretValue, secretValue)

`, repoName, secretValue, secretValue, secretValue)

checks := map[string]resource.TestCheckFunc{
"before": resource.ComposeTestCheckFunc(
Expand All @@ -59,6 +68,12 @@ func TestAccGithubActionsEnvironmentSecret(t *testing.T) {
resource.TestCheckResourceAttrSet(
"github_actions_environment_secret.plaintext_secret", "updated_at",
),
resource.TestCheckNoResourceAttr(
"github_actions_environment_secret.plaintext_secret_wo", "plaintext_value_wo",
),
resource.TestCheckResourceAttr(
"github_actions_environment_secret.plaintext_secret_wo", "plaintext_value_wo_version", "1234",
),
),
"after": resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr(
Expand All @@ -75,6 +90,12 @@ func TestAccGithubActionsEnvironmentSecret(t *testing.T) {
resource.TestCheckResourceAttrSet(
"github_actions_environment_secret.plaintext_secret", "updated_at",
),
resource.TestCheckNoResourceAttr(
"github_actions_environment_secret.plaintext_secret_wo", "plaintext_value_wo",
),
resource.TestCheckResourceAttr(
"github_actions_environment_secret.plaintext_secret_wo", "plaintext_value_wo_version", "5678",
),
),
}

Expand All @@ -87,9 +108,9 @@ func TestAccGithubActionsEnvironmentSecret(t *testing.T) {
Check: checks["before"],
},
{
Config: strings.Replace(config,
Config: strings.Replace(strings.Replace(config,
secretValue,
updatedSecretValue, 2),
updatedSecretValue, 3), "1234", "5678", 1),
Check: checks["after"],
},
},
Expand All @@ -107,8 +128,8 @@ func TestAccGithubActionsEnvironmentSecret(t *testing.T) {
}

resource "github_repository_environment" "test" {
repository = github_repository.test.name
environment = "environment / test"
repository = github_repository.test.name
environment = "environment / test"
}

resource "github_actions_environment_secret" "plaintext_secret" {
Expand All @@ -118,13 +139,21 @@ func TestAccGithubActionsEnvironmentSecret(t *testing.T) {
plaintext_value = "%s"
}

resource "github_actions_environment_secret" "plaintext_secret_wo" {
repository = github_repository.test.name
environment = github_repository_environment.test.environment
secret_name = "test_plaintext_secret_wo_name"
plaintext_value_wo = "%s"
plaintext_value_wo_version = 1234
}

resource "github_actions_environment_secret" "encrypted_secret" {
repository = github_repository.test.name
environment = github_repository_environment.test.environment
secret_name = "test_encrypted_secret_name"
encrypted_value = "%s"
repository = github_repository.test.name
environment = github_repository_environment.test.environment
secret_name = "test_encrypted_secret_name"
encrypted_value = "%s"
}
`, repoName, secretValue, secretValue)
`, repoName, secretValue, secretValue, secretValue)

resource.Test(t, resource.TestCase{
PreCheck: func() { skipUnauthenticated(t) },
Expand Down Expand Up @@ -166,26 +195,38 @@ func TestAccGithubActionsEnvironmentSecretIgnoreChanges(t *testing.T) {
}

resource "github_repository_environment" "test" {
repository = github_repository.test.name
environment = "environment / test"
repository = github_repository.test.name
environment = "environment / test"
}

resource "github_actions_environment_secret" "plaintext_secret" {
repository = github_repository.test.name
environment = github_repository_environment.test.environment
secret_name = "test_plaintext_secret_name"
plaintext_value = "%s"
repository = github_repository.test.name
environment = github_repository_environment.test.environment
secret_name = "test_plaintext_secret_name"
plaintext_value = "%s"

lifecycle {
ignore_changes = [plaintext_value]
}
}

resource "github_actions_environment_secret" "plaintext_secret_wo" {
repository = github_repository.test.name
environment = github_repository_environment.test.environment
secret_name = "test_plaintext_secret_wo_name"
plaintext_value_wo = "%s"
plaintext_value_wo_version = %d

lifecycle {
ignore_changes = [plaintext_value_wo_version]
}
}

resource "github_actions_environment_secret" "encrypted_secret" {
repository = github_repository.test.name
environment = github_repository_environment.test.environment
secret_name = "test_encrypted_secret_name"
encrypted_value = "%s"
repository = github_repository.test.name
environment = github_repository_environment.test.environment
secret_name = "test_encrypted_secret_name"
encrypted_value = "%s"

lifecycle {
ignore_changes = [encrypted_value]
Expand All @@ -209,6 +250,9 @@ func TestAccGithubActionsEnvironmentSecretIgnoreChanges(t *testing.T) {
resource.TestCheckResourceAttrSet(
"github_actions_environment_secret.plaintext_secret", "updated_at",
),
resource.TestCheckResourceAttr(
"github_actions_environment_secret.plaintext_secret_wo", "plaintext_value_wo_version", "1234",
),
),
"after": resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr(
Expand All @@ -225,6 +269,9 @@ func TestAccGithubActionsEnvironmentSecretIgnoreChanges(t *testing.T) {
resource.TestCheckResourceAttrSet(
"github_actions_environment_secret.plaintext_secret", "updated_at",
),
resource.TestCheckResourceAttr(
"github_actions_environment_secret.plaintext_secret_wo", "plaintext_value_wo_version", "1234",
),
),
}

Expand All @@ -233,18 +280,18 @@ func TestAccGithubActionsEnvironmentSecretIgnoreChanges(t *testing.T) {
Providers: testAccProviders,
Steps: []resource.TestStep{
{
Config: fmt.Sprintf(configFmtStr, repoName, secretValue, secretValue),
Config: fmt.Sprintf(configFmtStr, repoName, secretValue, secretValue, 1234, secretValue),
Check: checks["before"],
},
{
Config: fmt.Sprintf(configFmtStr, repoName, secretValue, secretValue),
Config: fmt.Sprintf(configFmtStr, repoName, secretValue, secretValue, 5678, secretValue),
Check: checks["after"],
},
{
// In this case the values change in the config, but the lifecycle ignore_changes should
// not cause the actual values to be updated. This would also be the case when a secret
// is externally modified (when what is in state does not match what is given).
Config: fmt.Sprintf(configFmtStr, repoName, modifiedSecretValue, modifiedSecretValue),
Config: fmt.Sprintf(configFmtStr, repoName, modifiedSecretValue, modifiedSecretValue, 5678, modifiedSecretValue),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr(
"github_actions_environment_secret.plaintext_secret", "plaintext_value",
Expand All @@ -254,6 +301,10 @@ func TestAccGithubActionsEnvironmentSecretIgnoreChanges(t *testing.T) {
"github_actions_environment_secret.encrypted_secret", "encrypted_value",
secretValue, // Should still have the original value in state.
),
resource.TestCheckResourceAttr(
"github_actions_environment_secret.plaintext_secret_wo", "plaintext_value_wo_version",
"1234", // Should still have the original value in state.
),
),
},
},
Expand Down
15 changes: 10 additions & 5 deletions website/docs/r/actions_environment_secret.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,13 @@ 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 `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.

-> **Note:** Write-Only argument `plaintext_value_wo` is available to use in place of `plaintext_value`. Write-Only argumentss are supported in HashiCorp Terraform 1.11.0 and later. [Learn more](https://developer.hashicorp.com/terraform/language/manage-sensitive-data/ephemeral#write-only-arguments).

## Example Usage

```hcl
Expand Down Expand Up @@ -86,11 +89,13 @@ resource "github_actions_environment_secret" "example_secret" {
The following arguments are supported:


* `repository` - (Required) Name of the repository.
* `environment` - (Required) Name of the environment.
* `secret_name` - (Required) Name of the secret.
* `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.
* `repository` - (Required) Name of the repository.
* `environment` - (Required) Name of the environment.
* `secret_name` - (Required) Name of the secret.
* `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.
* `plaintext_value_wo` - (Optional, Write-Only) Plaintext value of the secret to be encrypted.
* `plaintext_value_wo_version` - (Optional) Used together with `plaintext_value_wo` to trigger an update. Increment this value when an update to `plaintext_value_wo` is required.

## Attributes Reference

Expand Down