-
Notifications
You must be signed in to change notification settings - Fork 10
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
add data source for available keyless creds (#104)
Fixes #103 This adds a new data source that lists the OIDC providers available in the current environment, and a new required attribute for `cosign_sign` and `cosign_attest` for those providers. The intention is that sign/attest resources should be created for each available credential provider, instead of always creating them and having them just warn and no-op if credentials aren't available. This makes diffs less noisy when they're no-ops, since resources won't be created at all. Adding a new required attribute makes this a breaking API change; users will have to add `oidc_provider = "github-actions"` to get the previous behavior (or `"interactive"`, or `"filesystem"`, each of which had been implicitly used before) This also makes it easier to graft in new providers and multiple providers in the future. This change required some kind of gross hacks to test it, since it depends so heavily on the environment it's running in, and some paths are hard-coded in cosign, and since it should pass both locally and on actual GitHub Actions. In a future change we might be able to avoid some `t.Skip`s we've been using to run only on GHA, since we'll have more control over which providers are/aren't used, and how. --------- Signed-off-by: Jason Hall <[email protected]>
- Loading branch information
Showing
13 changed files
with
319 additions
and
48 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
--- | ||
# generated by https://github.com/hashicorp/terraform-plugin-docs | ||
page_title: "cosign_available_credentials Data Source - terraform-provider-cosign" | ||
subcategory: "" | ||
description: |- | ||
This produces a list of available keyless signing credentials. | ||
--- | ||
|
||
# cosign_available_credentials (Data Source) | ||
|
||
This produces a list of available keyless signing credentials. | ||
|
||
|
||
|
||
<!-- schema generated by tfplugindocs --> | ||
## Schema | ||
|
||
### Read-Only | ||
|
||
- `available` (Set of String) This contains the names of available keyless signing credentials. | ||
- `id` (String) This contains the hash of available keyless signing credentials. | ||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,135 @@ | ||
package provider | ||
|
||
import ( | ||
"context" | ||
"crypto/sha256" | ||
"fmt" | ||
"os" | ||
"sort" | ||
|
||
_ "github.com/chainguard-dev/terraform-provider-cosign/internal/provider/interactive" | ||
"github.com/hashicorp/terraform-plugin-framework/datasource" | ||
"github.com/hashicorp/terraform-plugin-framework/datasource/schema" | ||
"github.com/hashicorp/terraform-plugin-framework/diag" | ||
"github.com/hashicorp/terraform-plugin-framework/types" | ||
"github.com/hashicorp/terraform-plugin-framework/types/basetypes" | ||
"github.com/sigstore/cosign/v2/pkg/cosign/env" | ||
"github.com/sigstore/cosign/v2/pkg/providers/filesystem" | ||
_ "github.com/sigstore/cosign/v2/pkg/providers/github" | ||
) | ||
|
||
// Ensure provider defined types fully satisfy framework interfaces. | ||
var _ datasource.DataSource = &AvailableDataSource{} | ||
|
||
func NewAvailableDataSource() datasource.DataSource { | ||
return &AvailableDataSource{} | ||
} | ||
|
||
// ExampleDataSource defines the data source implementation. | ||
type AvailableDataSource struct { | ||
popts *ProviderOpts | ||
} | ||
|
||
// ExampleDataSourceModel describes the data source data model. | ||
type AvailableDataSourceModel struct { | ||
Id types.String `tfsdk:"id"` | ||
Available types.Set `tfsdk:"available"` | ||
} | ||
|
||
func (d *AvailableDataSource) Metadata(ctx context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { | ||
resp.TypeName = req.ProviderTypeName + "_available_credentials" | ||
} | ||
|
||
func (d *AvailableDataSource) Schema(ctx context.Context, req datasource.SchemaRequest, resp *datasource.SchemaResponse) { | ||
resp.Schema = schema.Schema{ | ||
MarkdownDescription: "This produces a list of available keyless signing credentials.", | ||
Attributes: map[string]schema.Attribute{ | ||
"id": schema.StringAttribute{ | ||
MarkdownDescription: "This contains the hash of available keyless signing credentials.", | ||
Computed: true, | ||
}, | ||
"available": schema.SetAttribute{ | ||
MarkdownDescription: "This contains the names of available keyless signing credentials.", | ||
Computed: true, | ||
ElementType: basetypes.StringType{}, | ||
}, | ||
}, | ||
} | ||
} | ||
|
||
func (d *AvailableDataSource) Configure(ctx context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) { | ||
// Prevent panic if the provider has not been configured. | ||
if req.ProviderData == nil { | ||
return | ||
} | ||
|
||
popts, ok := req.ProviderData.(*ProviderOpts) | ||
if !ok || popts == nil { | ||
resp.Diagnostics.AddError("Client Error", "invalid provider data") | ||
return | ||
} | ||
d.popts = popts | ||
} | ||
|
||
// Copied from "github.com/sigstore/cosign/v2/pkg/providers/filesystem" | ||
func gitHubAvailable() bool { | ||
if env.Getenv(env.VariableGitHubRequestToken) == "" { | ||
return false | ||
} | ||
if env.Getenv(env.VariableGitHubRequestURL) == "" { | ||
return false | ||
} | ||
return true | ||
} | ||
|
||
// Allow this path to be overridden for testing. | ||
var filesystemTokenPath = filesystem.FilesystemTokenPath | ||
|
||
// Copied from "github.com/sigstore/cosign/v2/pkg/providers/filesystem" | ||
func filesystemAvailable() bool { | ||
// If we can stat the file without error then this is enabled. | ||
_, err := os.Stat(filesystemTokenPath) | ||
return err == nil | ||
} | ||
|
||
func interactiveAvailable() bool { | ||
return os.Getenv("TF_COSIGN_LOCAL") != "" | ||
} | ||
|
||
func (d *AvailableDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { | ||
var data AvailableDataSourceModel | ||
resp.Diagnostics.Append(req.Config.Get(ctx, &data)...) | ||
if resp.Diagnostics.HasError() { | ||
return | ||
} | ||
|
||
var available []string | ||
if interactiveAvailable() { | ||
available = append(available, "interactive") | ||
} | ||
if filesystemAvailable() { | ||
available = append(available, "filesystem") | ||
} | ||
if gitHubAvailable() { | ||
available = append(available, "github-actions") | ||
} | ||
sort.Strings(available) | ||
|
||
h := sha256.New() | ||
for _, a := range available { | ||
fmt.Fprintln(h, a) | ||
} | ||
digest := fmt.Sprintf("%x", h.Sum(nil)) | ||
|
||
var diag diag.Diagnostics | ||
data.Available, diag = types.SetValueFrom(ctx, basetypes.StringType{}, available) | ||
if diag.HasError() { | ||
resp.Diagnostics.Append(diag...) | ||
return | ||
} | ||
|
||
data.Id = types.StringValue(digest) | ||
|
||
// Save data into Terraform state | ||
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) | ||
} |
109 changes: 109 additions & 0 deletions
109
internal/provider/data_source_available_credentials_test.go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,109 @@ | ||
package provider | ||
|
||
import ( | ||
"os" | ||
"path/filepath" | ||
"testing" | ||
|
||
"github.com/hashicorp/terraform-plugin-testing/helper/resource" | ||
"github.com/sigstore/cosign/v2/pkg/cosign/env" | ||
"github.com/sigstore/cosign/v2/pkg/providers/filesystem" | ||
) | ||
|
||
func TestAccAvailableCredentials(t *testing.T) { | ||
dir := t.TempDir() | ||
tmp, err := os.Create(filepath.Join(dir, "foo")) | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
defer tmp.Close() | ||
|
||
for _, c := range []struct { | ||
desc string | ||
pre, post func(t *testing.T) // pre- and post-test steps | ||
env map[string]string | ||
checks []resource.TestCheckFunc | ||
}{{ | ||
desc: "no env", | ||
env: nil, | ||
pre: func(t *testing.T) { | ||
// If this test is running on GHA, we will never have a scenario where we don't have some ambient credentials. | ||
if os.Getenv(string(env.VariableGitHubRequestToken)) != "" { | ||
t.Skip("Skipping no-env check since we're running on GitHub Actions") | ||
} | ||
}, | ||
checks: []resource.TestCheckFunc{ | ||
resource.TestCheckResourceAttr("data.cosign_available_credentials.available", "available.#", "0"), | ||
}, | ||
}, { | ||
desc: "github", | ||
env: map[string]string{ | ||
string(env.VariableGitHubRequestToken): "foo", | ||
string(env.VariableGitHubRequestURL): "bar", | ||
}, | ||
checks: []resource.TestCheckFunc{ | ||
resource.TestCheckResourceAttr("data.cosign_available_credentials.available", "available.#", "1"), | ||
resource.TestCheckResourceAttr("data.cosign_available_credentials.available", "available.0", "github-actions"), | ||
}, | ||
}, { | ||
desc: "filesystem and github", | ||
env: map[string]string{ | ||
string(env.VariableGitHubRequestToken): "foo", | ||
string(env.VariableGitHubRequestURL): "bar", | ||
}, | ||
pre: func(*testing.T) { filesystemTokenPath = tmp.Name() }, | ||
post: func(*testing.T) { filesystemTokenPath = filesystem.FilesystemTokenPath }, | ||
checks: []resource.TestCheckFunc{ | ||
resource.TestCheckResourceAttr("data.cosign_available_credentials.available", "available.#", "2"), | ||
resource.TestCheckResourceAttr("data.cosign_available_credentials.available", "available.0", "filesystem"), | ||
resource.TestCheckResourceAttr("data.cosign_available_credentials.available", "available.1", "github-actions"), | ||
}, | ||
}, { | ||
desc: "interactive and github", | ||
env: map[string]string{ | ||
string(env.VariableGitHubRequestToken): "foo", | ||
string(env.VariableGitHubRequestURL): "bar", | ||
"TF_COSIGN_LOCAL": "true", | ||
}, | ||
checks: []resource.TestCheckFunc{ | ||
resource.TestCheckResourceAttr("data.cosign_available_credentials.available", "available.#", "2"), | ||
resource.TestCheckResourceAttr("data.cosign_available_credentials.available", "available.0", "github-actions"), | ||
resource.TestCheckResourceAttr("data.cosign_available_credentials.available", "available.1", "interactive"), | ||
}, | ||
}, { | ||
desc: "all together now", | ||
env: map[string]string{ | ||
string(env.VariableGitHubRequestToken): "foo", | ||
string(env.VariableGitHubRequestURL): "bar", | ||
"TF_COSIGN_LOCAL": "true", | ||
}, | ||
pre: func(*testing.T) { filesystemTokenPath = tmp.Name() }, | ||
post: func(*testing.T) { filesystemTokenPath = filesystem.FilesystemTokenPath }, | ||
checks: []resource.TestCheckFunc{ | ||
resource.TestCheckResourceAttr("data.cosign_available_credentials.available", "available.#", "3"), | ||
resource.TestCheckResourceAttr("data.cosign_available_credentials.available", "available.0", "filesystem"), | ||
resource.TestCheckResourceAttr("data.cosign_available_credentials.available", "available.1", "github-actions"), | ||
resource.TestCheckResourceAttr("data.cosign_available_credentials.available", "available.2", "interactive"), | ||
}, | ||
}} { | ||
t.Run(c.desc, func(t *testing.T) { | ||
if c.pre != nil { | ||
c.pre(t) | ||
} | ||
if c.post != nil { | ||
defer c.post(t) | ||
} | ||
for k, v := range c.env { | ||
t.Setenv(k, v) | ||
} | ||
resource.Test(t, resource.TestCase{ | ||
PreCheck: func() { testAccPreCheck(t) }, | ||
ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, | ||
Steps: []resource.TestStep{{ | ||
Config: `data "cosign_available_credentials" "available" {}`, | ||
Check: resource.ComposeAggregateTestCheckFunc(c.checks...), | ||
}}, | ||
}) | ||
}) | ||
} | ||
} |
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.