1
0
Fork 0
mirror of https://github.com/imjasonh/terraform-provider-cosign-old synced 2026-07-19 07:07:57 +00:00

add resource update tests, force-new on sign, use OCI validators

Signed-off-by: Jason Hall <jason@chainguard.dev>
This commit is contained in:
Jason Hall 2023-04-26 16:27:27 -04:00
parent d2ec4379ae
commit 05e2331208
Failed to extract signature
13 changed files with 346 additions and 275 deletions

View file

@ -14,7 +14,7 @@ jobs:
ref: refs/pull/${{ github.event.pull_request.number }}/merge
- uses: actions/setup-go@v4
with:
go-version: '1.18'
go-version: 1.20.x
- run: go generate ./...
- name: git diff
run: |
@ -43,22 +43,11 @@ jobs:
ref: refs/pull/${{ github.event.pull_request.number }}/merge
- uses: actions/setup-go@v4
with:
go-version: '1.18'
- uses: imjasonh/setup-crane@v0.3
- uses: chainguard-dev/actions/setup-registry@main
with:
port: 5000
go-version: 1.20.x
- uses: hashicorp/setup-terraform@v2
with:
terraform_version: ${{ matrix.terraform }}
terraform_wrapper: false
- run: go mod download
- run: go build -v .
- run: |
crane copy --insecure cgr.dev/chainguard/static:latest-glibc localhost:5000/cosign-testing
export TEST_IMAGE=localhost:5000/cosign-testing@$(crane digest --insecure localhost:5000/cosign-testing)
TF_ACC=1 go test -v -cover ./internal/provider/
- run: TF_ACC=1 go test -v -cover ./internal/provider/

3
go.mod
View file

@ -2,7 +2,10 @@ module github.com/chainguard-dev/terraform-provider-cosign
go 1.19
replace github.com/chainguard-dev/terraform-provider-oci => ../terraform-provider-oci
require (
github.com/chainguard-dev/terraform-provider-oci v0.0.0-20230425204147-f48901367805
github.com/google/go-containerregistry v0.14.1-0.20230409045903-ed5c185df419
github.com/google/uuid v1.3.0
github.com/hashicorp/terraform-plugin-docs v0.14.1

View file

@ -1,89 +0,0 @@
package provider
import (
"fmt"
"os"
"regexp"
"testing"
"github.com/google/uuid"
"github.com/hashicorp/terraform-plugin-testing/helper/resource"
)
func TestAccResourceCosignAttest(t *testing.T) {
digest := os.Getenv("TEST_IMAGE")
url := "https://example.com/" + uuid.New().String()
value := uuid.New().String()
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
ProtoV6ProviderFactories: testAccProtoV6ProviderFactories,
Steps: []resource.TestStep{{
Config: fmt.Sprintf(`
resource "cosign_attest" "foo" {
image = %q
predicate_type = %q
predicate = jsonencode({
foo = %q
})
}
data "cosign_verify" "bar" {
image = cosign_attest.foo.attested_ref
policy = jsonencode({
apiVersion = "policy.sigstore.dev/v1beta1"
kind = "ClusterImagePolicy"
metadata = {
name = "attested-it"
}
spec = {
images = [{
glob = %q
}]
authorities = [{
keyless = {
url = "https://fulcio.sigstore.dev"
identities = [{
issuer = "https://token.actions.githubusercontent.com"
subject = "https://github.com/imjasonh/terraform-provider-cosign/.github/workflows/test.yml@refs/heads/main"
}]
}
attestations = [{
name = "must-have-attestation"
predicateType = %q
policy = {
type = "cue"
// When we do things in this style, we can use file("foo.cue") too!
data = <<EOF
predicateType: %q
predicate: {
foo: string
// Uncommenting this leads to a failure.
// foo: "bar"
foo: %q
}
EOF
}
}]
ctlog = {
url = "https://rekor.sigstore.dev"
}
}]
}
})
}
`, digest, url, value, digest, url, url, value),
Check: resource.ComposeTestCheckFunc(
resource.TestMatchResourceAttr(
"cosign_attest.foo", "image", regexp.MustCompile("^"+digest)),
resource.TestMatchResourceAttr(
"cosign_attest.foo", "attested_ref", regexp.MustCompile("^"+digest)),
// Check that it got attested!
resource.TestMatchResourceAttr(
"data.cosign_verify.bar", "verified_ref", regexp.MustCompile("^"+digest)),
),
}},
})
}

View file

@ -1,27 +0,0 @@
package provider
import (
"context"
"github.com/google/go-containerregistry/pkg/name"
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
)
type digestValidator struct{}
var _ validator.String = digestValidator{}
func (v digestValidator) Description(context.Context) string {
return "value must be a valid OCI digest"
}
func (v digestValidator) MarkdownDescription(ctx context.Context) string { return v.Description(ctx) }
func (v digestValidator) ValidateString(_ context.Context, req validator.StringRequest, resp *validator.StringResponse) {
if req.ConfigValue.IsNull() || req.ConfigValue.IsUnknown() {
return
}
val := req.ConfigValue.ValueString()
if _, err := name.NewDigest(val); err != nil {
resp.Diagnostics.AddError("Invalid OCI digest", err.Error())
}
}

View file

@ -1,27 +0,0 @@
package provider
import (
"context"
"encoding/json"
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
)
type jsonValidator struct{}
var _ validator.String = jsonValidator{}
func (v jsonValidator) Description(context.Context) string { return "value must be valid json" }
func (v jsonValidator) MarkdownDescription(ctx context.Context) string { return v.Description(ctx) }
func (v jsonValidator) ValidateString(_ context.Context, req validator.StringRequest, resp *validator.StringResponse) {
if req.ConfigValue.IsNull() || req.ConfigValue.IsUnknown() {
return
}
val := req.ConfigValue.ValueString()
var untyped interface{}
if err := json.Unmarshal([]byte(val), &untyped); err != nil {
resp.Diagnostics.AddError("Invalid json", err.Error())
}
}

View file

@ -1,25 +0,0 @@
package provider
import (
"context"
"github.com/google/go-containerregistry/pkg/name"
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
)
type refValidator struct{}
var _ validator.String = refValidator{}
func (v refValidator) Description(context.Context) string { return "value must be a valid OCI ref" }
func (v refValidator) MarkdownDescription(ctx context.Context) string { return v.Description(ctx) }
func (v refValidator) ValidateString(_ context.Context, req validator.StringRequest, resp *validator.StringResponse) {
if req.ConfigValue.IsNull() || req.ConfigValue.IsUnknown() {
return
}
val := req.ConfigValue.ValueString()
if _, err := name.ParseReference(val); err != nil {
resp.Diagnostics.AddError("Invalid image reference", err.Error())
}
}

View file

@ -6,6 +6,7 @@ import (
"fmt"
"os"
"github.com/chainguard-dev/terraform-provider-oci/pkg/validators"
"github.com/google/go-containerregistry/pkg/name"
"github.com/hashicorp/terraform-plugin-framework/path"
"github.com/hashicorp/terraform-plugin-framework/resource"
@ -57,19 +58,19 @@ func (r *AttestResource) Schema(ctx context.Context, req resource.SchemaRequest,
MarkdownDescription: "The digest of the container image to attest.",
Optional: false,
Required: true,
Validators: []validator.String{digestValidator{}},
Validators: []validator.String{validators.DigestValidator{}},
},
"predicate_type": schema.StringAttribute{
MarkdownDescription: "The in-toto predicate type of the claim being attested.",
Optional: false,
Required: true,
Validators: []validator.String{urlValidator{}},
Validators: []validator.String{validators.URLValidator{}},
},
"predicate": schema.StringAttribute{
MarkdownDescription: "The JSON body of the in-toto predicate's claim.",
Optional: false,
Required: true,
Validators: []validator.String{jsonValidator{}},
Validators: []validator.String{validators.JSONValidator{}},
},
"attested_ref": schema.StringAttribute{
MarkdownDescription: "This always matches the input digest, but is a convenience for composition.",

View file

@ -0,0 +1,187 @@
package provider
import (
"fmt"
"regexp"
"testing"
ocitesting "github.com/chainguard-dev/terraform-provider-oci/testing"
"github.com/google/go-containerregistry/pkg/v1/random"
"github.com/google/go-containerregistry/pkg/v1/remote"
"github.com/google/uuid"
"github.com/hashicorp/terraform-plugin-testing/helper/resource"
)
func TestAccResourceCosignAttest(t *testing.T) {
repo, cleanup := ocitesting.SetupRepository(t, "test")
defer cleanup()
// Push two images by digest.
img1, err := random.Image(1024, 1)
if err != nil {
t.Fatal(err)
}
dig1, err := img1.Digest()
if err != nil {
t.Fatal(err)
}
if err := remote.Write(repo.Digest(dig1.String()), img1); err != nil {
t.Fatal(err)
}
img2, err := random.Image(1024, 1)
if err != nil {
t.Fatal(err)
}
dig2, err := img2.Digest()
if err != nil {
t.Fatal(err)
}
if err := remote.Write(repo.Digest(dig2.String()), img2); err != nil {
t.Fatal(err)
}
url := "https://example.com/" + uuid.New().String()
value := uuid.New().String()
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
ProtoV6ProviderFactories: testAccProtoV6ProviderFactories,
Steps: []resource.TestStep{
// Attest and verify the first image.
{
Config: fmt.Sprintf(`
resource "cosign_attest" "foo" {
image = %q
predicate_type = %q
predicate = jsonencode({
foo = %q
})
}
data "cosign_verify" "bar" {
image = cosign_attest.foo.attested_ref
policy = jsonencode({
apiVersion = "policy.sigstore.dev/v1beta1"
kind = "ClusterImagePolicy"
metadata = {
name = "attested-it"
}
spec = {
images = [{
glob = %q
}]
authorities = [{
keyless = {
url = "https://fulcio.sigstore.dev"
identities = [{
issuer = "https://token.actions.githubusercontent.com"
subject = "https://github.com/imjasonh/terraform-provider-cosign/.github/workflows/test.yml@refs/heads/main"
}]
}
attestations = [{
name = "must-have-attestation"
predicateType = %q
policy = {
type = "cue"
// When we do things in this style, we can use file("foo.cue") too!
data = <<EOF
predicateType: %q
predicate: {
foo: string
// Uncommenting this leads to a failure.
// foo: "bar"
foo: %q
}
EOF
}
}]
ctlog = {
url = "https://rekor.sigstore.dev"
}
}]
}
})
}
`, dig1, url, value, dig1, url, url, value),
Check: resource.ComposeTestCheckFunc(
resource.TestMatchResourceAttr(
"cosign_attest.foo", "image", regexp.MustCompile("^"+dig1.String())),
resource.TestMatchResourceAttr(
"cosign_attest.foo", "attested_ref", regexp.MustCompile("^"+dig1.String())),
// Check that it got attested!
resource.TestMatchResourceAttr(
"data.cosign_verify.bar", "verified_ref", regexp.MustCompile("^"+dig1.String())),
),
},
// Update the resource to attest the second image, and verify it.
{
Config: fmt.Sprintf(`
resource "cosign_attest" "foo" {
image = %q
predicate_type = %q
predicate = jsonencode({
foo = %q
})
}
data "cosign_verify" "bar" {
image = cosign_attest.foo.attested_ref
policy = jsonencode({
apiVersion = "policy.sigstore.dev/v1beta1"
kind = "ClusterImagePolicy"
metadata = {
name = "attested-it"
}
spec = {
images = [{
glob = %q
}]
authorities = [{
keyless = {
url = "https://fulcio.sigstore.dev"
identities = [{
issuer = "https://token.actions.githubusercontent.com"
subject = "https://github.com/imjasonh/terraform-provider-cosign/.github/workflows/test.yml@refs/heads/main"
}]
}
attestations = [{
name = "must-have-attestation"
predicateType = %q
policy = {
type = "cue"
// When we do things in this style, we can use file("foo.cue") too!
data = <<EOF
predicateType: %q
predicate: {
foo: string
// Uncommenting this leads to a failure.
// foo: "bar"
foo: %q
}
EOF
}
}]
ctlog = {
url = "https://rekor.sigstore.dev"
}
}]
}
})
}
`, dig2, url, value, dig2, url, url, value),
Check: resource.ComposeTestCheckFunc(
resource.TestMatchResourceAttr(
"cosign_attest.foo", "image", regexp.MustCompile("^"+dig2.String())),
resource.TestMatchResourceAttr(
"cosign_attest.foo", "attested_ref", regexp.MustCompile("^"+dig2.String())),
// Check that it got attested!
resource.TestMatchResourceAttr(
"data.cosign_verify.bar", "verified_ref", regexp.MustCompile("^"+dig2.String())),
),
},
},
})
}

View file

@ -5,6 +5,7 @@ import (
"errors"
"fmt"
"github.com/chainguard-dev/terraform-provider-oci/pkg/validators"
"github.com/google/go-containerregistry/pkg/name"
"github.com/hashicorp/terraform-plugin-framework/path"
"github.com/hashicorp/terraform-plugin-framework/resource"
@ -54,7 +55,10 @@ func (r *SignResource) Schema(ctx context.Context, req resource.SchemaRequest, r
MarkdownDescription: "The digest of the container image to sign.",
Optional: false,
Required: true,
Validators: []validator.String{digestValidator{}},
Validators: []validator.String{validators.DigestValidator{}},
PlanModifiers: []planmodifier.String{
stringplanmodifier.RequiresReplace(),
},
},
"signed_ref": schema.StringAttribute{
MarkdownDescription: "This always matches the input digest, but is a convenience for composition.",

View file

@ -0,0 +1,142 @@
package provider
import (
"fmt"
"regexp"
"testing"
ocitesting "github.com/chainguard-dev/terraform-provider-oci/testing"
"github.com/google/go-containerregistry/pkg/v1/random"
"github.com/google/go-containerregistry/pkg/v1/remote"
"github.com/hashicorp/terraform-plugin-testing/helper/resource"
)
func TestAccResourceCosignSign(t *testing.T) {
repo, cleanup := ocitesting.SetupRepository(t, "test")
defer cleanup()
// Push two images by digest.
img1, err := random.Image(1024, 1)
if err != nil {
t.Fatal(err)
}
dig1, err := img1.Digest()
if err != nil {
t.Fatal(err)
}
ref1 := repo.Digest(dig1.String())
if err := remote.Write(ref1, img1); err != nil {
t.Fatal(err)
}
img2, err := random.Image(1024, 1)
if err != nil {
t.Fatal(err)
}
dig2, err := img2.Digest()
if err != nil {
t.Fatal(err)
}
ref2 := repo.Digest(dig2.String())
if err := remote.Write(ref2, img2); err != nil {
t.Fatal(err)
}
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
ProtoV6ProviderFactories: testAccProtoV6ProviderFactories,
Steps: []resource.TestStep{
// Sign and verify the first image.
{
Config: fmt.Sprintf(`
resource "cosign_sign" "foo" {
image = %q
}
data "cosign_verify" "bar" {
image = cosign_sign.foo.signed_ref
policy = jsonencode({
apiVersion = "policy.sigstore.dev/v1beta1"
kind = "ClusterImagePolicy"
metadata = {
name = "signed-it"
}
spec = {
images = [{
glob = %q
}]
authorities = [{
keyless = {
url = "https://fulcio.sigstore.dev"
identities = [{
issuer = "https://token.actions.githubusercontent.com"
subject = "https://github.com/imjasonh/terraform-provider-cosign/.github/workflows/test.yml@refs/heads/main"
}]
}
ctlog = {
url = "https://rekor.sigstore.dev"
}
}]
}
})
}
`, ref1, ref1),
Check: resource.ComposeTestCheckFunc(
resource.TestMatchResourceAttr(
"cosign_sign.foo", "image", regexp.MustCompile("^"+ref1.String())),
resource.TestMatchResourceAttr(
"cosign_sign.foo", "signed_ref", regexp.MustCompile("^"+ref1.String())),
// Check that it got signed!
resource.TestMatchResourceAttr(
"data.cosign_verify.bar", "verified_ref", regexp.MustCompile("^"+ref1.String())),
),
},
// Update the sign resource to sign the second image, and verify that.
{
Config: fmt.Sprintf(`
resource "cosign_sign" "foo" {
image = %q
}
data "cosign_verify" "bar" {
image = cosign_sign.foo.signed_ref
policy = jsonencode({
apiVersion = "policy.sigstore.dev/v1beta1"
kind = "ClusterImagePolicy"
metadata = {
name = "signed-it"
}
spec = {
images = [{
glob = %q
}]
authorities = [{
keyless = {
url = "https://fulcio.sigstore.dev"
identities = [{
issuer = "https://token.actions.githubusercontent.com"
subject = "https://github.com/imjasonh/terraform-provider-cosign/.github/workflows/test.yml@refs/heads/main"
}]
}
ctlog = {
url = "https://rekor.sigstore.dev"
}
}]
}
})
}
`, ref2, ref2),
Check: resource.ComposeTestCheckFunc(
resource.TestMatchResourceAttr(
"cosign_sign.foo", "image", regexp.MustCompile("^"+ref2.String())),
resource.TestMatchResourceAttr(
"cosign_sign.foo", "signed_ref", regexp.MustCompile("^"+ref2.String())),
// Check that it got signed!
resource.TestMatchResourceAttr(
"data.cosign_verify.bar", "verified_ref", regexp.MustCompile("^"+ref2.String())),
),
},
},
})
}

View file

@ -1,63 +0,0 @@
package provider
import (
"fmt"
"os"
"regexp"
"testing"
"github.com/hashicorp/terraform-plugin-testing/helper/resource"
)
func TestAccResourceCosignSign(t *testing.T) {
digest := os.Getenv("TEST_IMAGE")
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
ProtoV6ProviderFactories: testAccProtoV6ProviderFactories,
Steps: []resource.TestStep{{
Config: fmt.Sprintf(`
resource "cosign_sign" "foo" {
image = %q
}
data "cosign_verify" "bar" {
image = cosign_sign.foo.signed_ref
policy = jsonencode({
apiVersion = "policy.sigstore.dev/v1beta1"
kind = "ClusterImagePolicy"
metadata = {
name = "signed-it"
}
spec = {
images = [{
glob = %q
}]
authorities = [{
keyless = {
url = "https://fulcio.sigstore.dev"
identities = [{
issuer = "https://token.actions.githubusercontent.com"
subject = "https://github.com/imjasonh/terraform-provider-cosign/.github/workflows/test.yml@refs/heads/main"
}]
}
ctlog = {
url = "https://rekor.sigstore.dev"
}
}]
}
})
}
`, digest, digest),
Check: resource.ComposeTestCheckFunc(
resource.TestMatchResourceAttr(
"cosign_sign.foo", "image", regexp.MustCompile("^"+digest)),
resource.TestMatchResourceAttr(
"cosign_sign.foo", "signed_ref", regexp.MustCompile("^"+digest)),
// Check that it got signed!
resource.TestMatchResourceAttr(
"data.cosign_verify.bar", "verified_ref", regexp.MustCompile("^"+digest)),
),
}},
})
}

View file

@ -1,25 +0,0 @@
package provider
import (
"context"
"net/url"
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
)
type urlValidator struct{}
var _ validator.String = urlValidator{}
func (v urlValidator) Description(context.Context) string { return "value must be a valid URL" }
func (v urlValidator) MarkdownDescription(ctx context.Context) string { return v.Description(ctx) }
func (v urlValidator) ValidateString(_ context.Context, req validator.StringRequest, resp *validator.StringResponse) {
if req.ConfigValue.IsNull() || req.ConfigValue.IsUnknown() {
return
}
val := req.ConfigValue.ValueString()
if _, err := url.Parse(val); err != nil {
resp.Diagnostics.AddError("Invalid url", err.Error())
}
}

View file

@ -3,6 +3,7 @@ package provider
import (
"context"
"github.com/chainguard-dev/terraform-provider-oci/pkg/validators"
"github.com/google/go-containerregistry/pkg/authn"
"github.com/google/go-containerregistry/pkg/name"
"github.com/hashicorp/terraform-plugin-framework/datasource"
@ -43,7 +44,7 @@ func (d *VerifyDataSource) Schema(ctx context.Context, req datasource.SchemaRequ
"image": schema.StringAttribute{
MarkdownDescription: "The image tag or digest of the container image to verify.",
Required: true,
Validators: []validator.String{refValidator{}},
Validators: []validator.String{validators.RefValidator{}},
},
"policy": schema.StringAttribute{
MarkdownDescription: "The sigstore policy-controller policy to verify the image against.",