Skip to content
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

[WIP] Implement secret manager datasource #99

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
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
99 changes: 99 additions & 0 deletions datasource/secretmanager/data.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
//go:generate packer-sdc struct-markdown
//go:generate packer-sdc mapstructure-to-hcl2 -type Config,DatasourceOutput

package secretmanager

import (
"context"
"errors"
"fmt"

"github.com/hashicorp/hcl/v2/hcldec"
"github.com/hashicorp/packer-plugin-sdk/hcl2helper"
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
"github.com/hashicorp/packer-plugin-sdk/template/config"

"github.com/zclconf/go-cty/cty"

secretmanager "cloud.google.com/go/secretmanager/apiv1"
"google.golang.org/api/option"
secretmanagerpb "google.golang.org/genproto/googleapis/cloud/secretmanager/v1"
)

type Config struct {
MockOption []interface{} `mapstructure:"mock" required:"false"`

// The project where the secret manager entry is stored.
Project string `mapstructure:"project" required:"true"`
// The name fo the secret manager entry.
Name string `mapstructure:"name" required:"true"`
// The secret manager version to fetch. Defaults to latest.
Version string `mapstructure:"version"`
}

type Datasource struct {
config Config
}

type DatasourceOutput struct {
// String payload of the secret manager version.
Payload string `mapstructure:"payload"`
// The crc32c checksum for the payload.
Checksum int64 `mapstructure:"checksum"`
}

func (d *Datasource) ConfigSpec() hcldec.ObjectSpec {
return d.config.FlatMapstructure().HCL2Spec()
}

func (d *Datasource) Configure(raws ...interface{}) error {
err := config.Decode(&d.config, nil, raws...)
if err != nil {
return err
}

var errs *packersdk.MultiError
if d.config.Version == "" {
d.config.Version = "latest"
}
if d.config.Project == "" {
errs = packersdk.MultiErrorAppend(errs, errors.New("a 'project' must be specified"))
}
if d.config.Name == "" {
errs = packersdk.MultiErrorAppend(errs, errors.New("a 'name' must be specified"))
}

if errs != nil && len(errs.Errors) > 0 {
return errs
}
return nil
}

func (d *Datasource) OutputSpec() hcldec.ObjectSpec {
return (&DatasourceOutput{}).FlatMapstructure().HCL2Spec()
}

func (d *Datasource) Execute() (cty.Value, error) {
opts := make([]option.ClientOption, len(d.config.MockOption))
for i, opt := range d.config.MockOption {
opts[i] = opt.(option.ClientOption)
}

client, err := secretmanager.NewClient(context.Background(), opts...)
if err != nil {
return cty.NullVal(cty.EmptyObject), err
}

secret, err := client.AccessSecretVersion(context.Background(), &secretmanagerpb.AccessSecretVersionRequest{
Name: fmt.Sprintf("projects/%s/secrets/%s/versions/%s", d.config.Project, d.config.Name, d.config.Version),
})
if err != nil {
return cty.NullVal(cty.EmptyObject), err
}

output := DatasourceOutput{
Payload: secret.GetPayload().String(),
Checksum: *secret.Payload.DataCrc32C,
}
return hcl2helper.HCL2ValueFromConfig(output, d.OutputSpec()), nil
}
62 changes: 62 additions & 0 deletions datasource/secretmanager/data.hcl2spec.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

103 changes: 103 additions & 0 deletions datasource/secretmanager/data_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
package secretmanager

import (
"context"
_ "embed"
"hash/crc32"
"net"
"testing"

"github.com/stretchr/testify/require"
"google.golang.org/api/option"
secretmanagerpb "google.golang.org/genproto/googleapis/cloud/secretmanager/v1"
"google.golang.org/grpc"
)

type SecretManagerMock struct {
secretmanagerpb.UnimplementedSecretManagerServiceServer
}

func (s *SecretManagerMock) Run() (string, error) {
l, err := net.Listen("tcp", "localhost:0")
if err != nil {
return "", err
}
gsrv := grpc.NewServer()
secretmanagerpb.RegisterSecretManagerServiceServer(gsrv, s)
go func() {
if err := gsrv.Serve(l); err != nil {
panic(err)
}
}()
return l.Addr().String(), nil
}

func (s *SecretManagerMock) AccessSecretVersion(ctx context.Context, req *secretmanagerpb.AccessSecretVersionRequest) (*secretmanagerpb.AccessSecretVersionResponse, error) {
payload := []byte("secret_content")
checksum := int64(crc32.Checksum(payload, crc32.IEEETable))

return &secretmanagerpb.AccessSecretVersionResponse{
Name: req.Name,
Payload: &secretmanagerpb.SecretPayload{
Data: payload,
DataCrc32C: &checksum,
},
}, nil
}

func TestSecretManagerDataSource_Mock(t *testing.T) {
m := &SecretManagerMock{}
addr, err := m.Run()
if err != nil {
t.Fatal(err)
}

type TestCase struct {
name string
project string
version string
shouldConfigErr bool
}

for name, testcase := range map[string]TestCase{
"empty": {
shouldConfigErr: true,
},
"empty_name": {
project: "prj-test",
shouldConfigErr: true,
},
"empty_project": {
name: "test",
shouldConfigErr: true,
},
"empty_version": {
name: "test",
project: "prj-test",
shouldConfigErr: false,
},
} {
testcase := testcase
t.Run(name, func(t *testing.T) {
d := &Datasource{
config: Config{
MockOption: []interface{}{
option.WithEndpoint(addr),
option.WithoutAuthentication(),
option.WithGRPCDialOption(grpc.WithInsecure()),
},
Name: testcase.name,
Project: testcase.project,
Version: testcase.version,
},
}
err := d.Configure()
require.Equal(t, testcase.shouldConfigErr, err != nil)

if err == nil {
_, err := d.Execute()
require.Nil(t, err)
}
})
}
}
25 changes: 25 additions & 0 deletions datasource/secretmanager/test-fixtures/template.pkr.hcl
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
data "google-compute-secret-manager" "test" {
project = "my-secret"
name = "my-project"
version = "1"
}

locals {
value = data.google-compute-secret-manage.test.value
}

source "null" "basic-example" {
communicator = "none"
}

build {
sources = [
"source.null.basic-example"
]

provisioner "shell-local" {
inline = [
"echo secret value: ${local.value}",
]
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<!-- Code generated from the comments of the Config struct in datasource/secretmanager/data.go; DO NOT EDIT MANUALLY -->

- `mock` ([]interface{}) - Mock Option

- `version` (string) - The secret manager version to fetch. Defaults to latest.

<!-- End of code generated from the comments of the Config struct in datasource/secretmanager/data.go; -->
7 changes: 7 additions & 0 deletions docs-partials/datasource/secretmanager/Config-required.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<!-- Code generated from the comments of the Config struct in datasource/secretmanager/data.go; DO NOT EDIT MANUALLY -->

- `project` (string) - The project where the secret manager entry is stored.

- `name` (string) - The name fo the secret manager entry.

<!-- End of code generated from the comments of the Config struct in datasource/secretmanager/data.go; -->
7 changes: 7 additions & 0 deletions docs-partials/datasource/secretmanager/DatasourceOutput.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<!-- Code generated from the comments of the DatasourceOutput struct in datasource/secretmanager/data.go; DO NOT EDIT MANUALLY -->

- `payload` (string) - String payload of the secret manager version.

- `checksum` (int64) - The crc32c checksum for the payload.

<!-- End of code generated from the comments of the DatasourceOutput struct in datasource/secretmanager/data.go; -->
28 changes: 16 additions & 12 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,24 @@ module github.com/hashicorp/packer-plugin-googlecompute
go 1.17

require (
cloud.google.com/go v0.94.0
cloud.google.com/go/compute v1.5.0
cloud.google.com/go/secretmanager v1.4.0
github.com/hashicorp/hcl/v2 v2.12.0
github.com/hashicorp/packer-plugin-sdk v0.2.13
github.com/hashicorp/vault/api v1.1.1
github.com/mitchellh/mapstructure v1.4.1
github.com/stretchr/testify v1.7.0
github.com/zclconf/go-cty v1.10.0
golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f
google.golang.org/api v0.56.0
golang.org/x/oauth2 v0.0.0-20220309155454-6242fa91716a
google.golang.org/api v0.74.0
google.golang.org/genproto v0.0.0-20220405205423-9d709892a2bf
google.golang.org/grpc v1.45.0
)

require (
cloud.google.com/go/storage v1.16.1 // indirect
cloud.google.com/go v0.100.2 // indirect
cloud.google.com/go/iam v0.3.0 // indirect
cloud.google.com/go/storage v1.18.2 // indirect
github.com/Azure/go-ntlmssp v0.0.0-20200615164410-66371956d46c // indirect
github.com/ChrisTrenkamp/goxpath v0.0.0-20210404020558-97928f7e12b6 // indirect
github.com/agext/levenshtein v1.2.3 // indirect
Expand All @@ -32,9 +37,9 @@ require (
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/golang/protobuf v1.5.2 // indirect
github.com/golang/snappy v0.0.4 // indirect
github.com/google/go-cmp v0.5.6 // indirect
github.com/google/go-cmp v0.5.7 // indirect
github.com/google/uuid v1.3.0 // indirect
github.com/googleapis/gax-go/v2 v2.1.0 // indirect
github.com/googleapis/gax-go/v2 v2.2.0 // indirect
github.com/hashicorp/consul/api v1.10.1 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
Expand Down Expand Up @@ -77,15 +82,14 @@ require (
github.com/ulikunitz/xz v0.5.10 // indirect
go.opencensus.io v0.23.0 // indirect
golang.org/x/crypto v0.0.0-20220427172511-eb4f295cb31f // indirect
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 // indirect
golang.org/x/sys v0.0.0-20211019181941-9d821ace8654 // indirect
golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b // indirect
golang.org/x/net v0.0.0-20220325170049-de3da57026de // indirect
golang.org/x/sys v0.0.0-20220328115105-d36c6a25d886 // indirect
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect
golang.org/x/text v0.3.7 // indirect
golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac // indirect
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/genproto v0.0.0-20210831024726-fe130286e0e2 // indirect
google.golang.org/grpc v1.40.0 // indirect
google.golang.org/protobuf v1.27.1 // indirect
google.golang.org/protobuf v1.28.0 // indirect
gopkg.in/square/go-jose.v2 v2.6.0 // indirect
gopkg.in/yaml.v2 v2.3.0 // indirect
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
Expand Down
Loading