-
Notifications
You must be signed in to change notification settings - Fork 764
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Gh 271 GitHub secrets #362
Changes from 3 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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 | ||
} |
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) | ||
} |
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 | ||
} | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You should set the Id here using |
||
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 | ||
} | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. How come there are no There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I wasn't aware that the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think I would store the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sounds like a solid plan - I will also include |
||
d.Set("plaintext_value", d.Get("plaintext_value")) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not 100% sure but I think is not required, as it already should exist in state. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I am also not sure on this point. I left it there such that it was obvious (and definite) it would be set. I don't see any issue with leaving this line in? |
||
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 | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shall we add
ForceNew: true
here?