Skip to content

Commit

Permalink
Merge pull request #362 from dmwgroup/gh-271-github-secrets
Browse files Browse the repository at this point in the history
Gh 271 GitHub secrets
  • Loading branch information
Jeremy Udit authored Mar 25, 2020
2 parents 081d542 + a1a5f63 commit e880ec6
Show file tree
Hide file tree
Showing 10 changed files with 485 additions and 9 deletions.
53 changes: 53 additions & 0 deletions github/data_source_github_actions_public_key.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package github

import (
"context"
"github.com/hashicorp/terraform-plugin-sdk/helper/schema"
"log"
)

func dataSourceGithubActionsPublicKey() *schema.Resource {
return &schema.Resource{
Read: dataSourceGithubActionsPublicKeyRead,

Schema: map[string]*schema.Schema{
"repository": {
Type: schema.TypeString,
Required: true,
},
"key_id": {
Type: schema.TypeString,
Optional: true,
},
"key": {
Type: schema.TypeString,
Optional: true,
},
},
}
}

func dataSourceGithubActionsPublicKeyRead(d *schema.ResourceData, meta interface{}) error {
err := checkOrganization(meta)
if err != nil {
return err
}

repository := d.Get("repository").(string)
owner := meta.(*Organization).name
log.Printf("[INFO] Refreshing GitHub Actions Public Key from: %s/%s", owner, repository)

client := meta.(*Organization).client
ctx := context.Background()

publicKey, _, err := client.Actions.GetPublicKey(ctx, owner, repository)
if err != nil {
return err
}

d.SetId(publicKey.GetKeyID())
d.Set("key_id", publicKey.GetKeyID())
d.Set("key", publicKey.GetKey())

return nil
}
53 changes: 53 additions & 0 deletions github/data_source_github_actions_public_key_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package github

import (
"fmt"
"os"
"regexp"
"testing"

"github.com/hashicorp/terraform-plugin-sdk/helper/resource"
)

func TestAccGithubActionsPublicKeyDataSource_noMatchReturnsError(t *testing.T) {
repo := "non-existent"
resource.ParallelTest(t, resource.TestCase{
PreCheck: func() {
testAccPreCheck(t)
},
Providers: testAccProviders,
Steps: []resource.TestStep{
{
Config: testAccCheckGithubActionsPublicKeyDataSourceConfig(repo),
ExpectError: regexp.MustCompile(`Not Found`),
},
},
})
}

func TestAccCheckGithubActionsPublicKeyDataSource_existing(t *testing.T) {
repo := os.Getenv("GITHUB_TEMPLATE_REPOSITORY")
resource.ParallelTest(t, resource.TestCase{
PreCheck: func() {
testAccPreCheck(t)
},
Providers: testAccProviders,
Steps: []resource.TestStep{
{
Config: testAccCheckGithubActionsPublicKeyDataSourceConfig(repo),
Check: resource.ComposeAggregateTestCheckFunc(
resource.TestCheckResourceAttrSet("data.github_actions_public_key.test_pk", "key"),
resource.TestCheckResourceAttrSet("data.github_actions_public_key.test_pk", "key_id"),
),
},
},
})
}

func testAccCheckGithubActionsPublicKeyDataSourceConfig(repo string) string {
return fmt.Sprintf(`
data "github_actions_public_key" "test_pk" {
repository = "%s"
}
`, repo)
}
16 changes: 9 additions & 7 deletions github/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ func Provider() terraform.ResourceProvider {
},

ResourcesMap: map[string]*schema.Resource{
"github_actions_secret": resourceGithubActionsSecret(),
"github_branch_protection": resourceGithubBranchProtection(),
"github_issue_label": resourceGithubIssueLabel(),
"github_membership": resourceGithubMembership(),
Expand All @@ -68,13 +69,14 @@ func Provider() terraform.ResourceProvider {
},

DataSourcesMap: map[string]*schema.Resource{
"github_collaborators": dataSourceGithubCollaborators(),
"github_ip_ranges": dataSourceGithubIpRanges(),
"github_release": dataSourceGithubRelease(),
"github_repositories": dataSourceGithubRepositories(),
"github_repository": dataSourceGithubRepository(),
"github_team": dataSourceGithubTeam(),
"github_user": dataSourceGithubUser(),
"github_collaborators": dataSourceGithubCollaborators(),
"github_ip_ranges": dataSourceGithubIpRanges(),
"github_release": dataSourceGithubRelease(),
"github_repositories": dataSourceGithubRepositories(),
"github_repository": dataSourceGithubRepository(),
"github_team": dataSourceGithubTeam(),
"github_user": dataSourceGithubUser(),
"github_actions_public_key": dataSourceGithubActionsPublicKey(),
},
}

Expand Down
169 changes: 169 additions & 0 deletions github/resource_github_actions_secret.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
package github

import (
"context"
"encoding/base64"
"fmt"
"github.com/google/go-github/v29/github"
"github.com/hashicorp/terraform-plugin-sdk/helper/schema"
"golang.org/x/crypto/nacl/box"
"log"
)

func resourceGithubActionsSecret() *schema.Resource {
return &schema.Resource{
Create: resourceGithubActionsSecretCreateOrUpdate,
Read: resourceGithubActionsSecretRead,
Update: resourceGithubActionsSecretCreateOrUpdate,
Delete: resourceGithubActionsSecretDelete,

Schema: map[string]*schema.Schema{
"repository": {
Type: schema.TypeString,
Required: true,
},
"secret_name": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"plaintext_value": {
Type: schema.TypeString,
Required: true,
Sensitive: true,
},
"created_at": {
Type: schema.TypeString,
Computed: true,
},
"updated_at": {
Type: schema.TypeString,
Computed: true,
},
},
}
}

func resourceGithubActionsSecretCreateOrUpdate(d *schema.ResourceData, meta interface{}) error {
err := checkOrganization(meta)
if err != nil {
return err
}

client := meta.(*Organization).client
owner := meta.(*Organization).name
ctx := context.Background()

repo := d.Get("repository").(string)
secretName := d.Get("secret_name").(string)
plaintextValue := d.Get("plaintext_value").(string)

keyId, publicKey, err := getPublicKeyDetails(owner, repo, meta)
if err != nil {
return err
}

encryptedText, err := encryptPlaintext(plaintextValue, publicKey)
if err != nil {
return err
}

// Create an EncryptedSecret and encrypt the plaintext value into it
eSecret := &github.EncryptedSecret{
Name: secretName,
KeyID: keyId,
EncryptedValue: base64.StdEncoding.EncodeToString(encryptedText),
}

_, err = client.Actions.CreateOrUpdateSecret(ctx, owner, repo, eSecret)
if err != nil {
return err
}

d.SetId(buildTwoPartID(&repo, &secretName))
return resourceGithubActionsSecretRead(d, meta)
}

func resourceGithubActionsSecretRead(d *schema.ResourceData, meta interface{}) error {
err := checkOrganization(meta)
if err != nil {
return err
}

client := meta.(*Organization).client
owner := meta.(*Organization).name
ctx := context.Background()

repoName, secretName, err := parseTwoPartID(d.Id(), "repository", "secret_name")
if err != nil {
return err
}

secret, _, err := client.Actions.GetSecret(ctx, owner, repoName, secretName)
if err != nil {
d.SetId("")
return err
}

d.Set("plaintext_value", d.Get("plaintext_value"))
d.Set("updated_at", secret.UpdatedAt.Format("default"))
d.Set("created_at", secret.CreatedAt.Format("default"))

return nil
}

func resourceGithubActionsSecretDelete(d *schema.ResourceData, meta interface{}) error {
err := checkOrganization(meta)
if err != nil {
return err
}

client := meta.(*Organization).client
orgName := meta.(*Organization).name
ctx := context.WithValue(context.Background(), ctxId, d.Id())

repoName, secretName, err := parseTwoPartID(d.Id(), "repository", "secret_name")
if err != nil {
return err
}

log.Printf("[DEBUG] Deleting secret: %s", d.Id())
_, err = client.Actions.DeleteSecret(ctx, orgName, repoName, secretName)

return err
}

func getPublicKeyDetails(owner, repository string, meta interface{}) (keyId, pkValue string, err error) {
client := meta.(*Organization).client
ctx := context.Background()

publicKey, _, err := client.Actions.GetPublicKey(ctx, owner, repository)
if err != nil {
return keyId, pkValue, err
}

return publicKey.GetKeyID(), publicKey.GetKey(), err
}

func encryptPlaintext(plaintext, publicKeyB64 string) ([]byte, error) {
publicKeyBytes, err := base64.StdEncoding.DecodeString(publicKeyB64)
if err != nil {
return nil, err
}

var publicKeyBytes32 [32]byte
copiedLen := copy(publicKeyBytes32[:], publicKeyBytes)
if copiedLen == 0 {
return nil, fmt.Errorf("could not convert publicKey to bytes")
}

plaintextBytes := []byte(plaintext)
var encryptedBytes []byte

cipherText, err := box.SealAnonymous(encryptedBytes, plaintextBytes, &publicKeyBytes32, nil)
if err != nil {
return nil, err
}

return cipherText, nil
}
Loading

0 comments on commit e880ec6

Please sign in to comment.