2023-11-28 11:30:11 -05:00
/ *
Copyright ( c ) Edgeless Systems GmbH
SPDX - License - Identifier : AGPL - 3.0 - only
* /
package provider
import (
"context"
"errors"
"fmt"
"net/http"
"github.com/edgelesssys/constellation/v2/internal/api/attestationconfigapi"
"github.com/edgelesssys/constellation/v2/internal/attestation/measurements"
"github.com/edgelesssys/constellation/v2/internal/attestation/variant"
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
"github.com/edgelesssys/constellation/v2/internal/sigstore"
2023-12-14 09:47:55 -05:00
"github.com/edgelesssys/constellation/v2/terraform-provider-constellation/internal/data"
2023-11-28 11:30:11 -05:00
"github.com/hashicorp/terraform-plugin-framework/datasource"
"github.com/hashicorp/terraform-plugin-framework/datasource/schema"
"github.com/hashicorp/terraform-plugin-framework/path"
"github.com/hashicorp/terraform-plugin-framework/types"
2023-12-18 04:15:54 -05:00
"github.com/hashicorp/terraform-plugin-framework/types/basetypes"
2023-11-28 11:30:11 -05:00
"github.com/hashicorp/terraform-plugin-log/tflog"
)
2023-12-20 09:56:48 -05:00
var (
// Ensure provider defined types fully satisfy framework interfaces.
_ datasource . DataSource = & AttestationDataSource { }
_ datasource . DataSourceWithValidateConfig = & AttestationDataSource { }
_ datasource . DataSourceWithConfigure = & AttestationDataSource { }
)
2023-11-28 11:30:11 -05:00
// NewAttestationDataSource creates a new attestation data source.
func NewAttestationDataSource ( ) datasource . DataSource {
return & AttestationDataSource { }
}
// AttestationDataSource defines the data source implementation.
type AttestationDataSource struct {
client * http . Client
fetcher attestationconfigapi . Fetcher
rekor * sigstore . Rekor
2023-12-14 09:47:55 -05:00
version string
2023-11-28 11:30:11 -05:00
}
// AttestationDataSourceModel describes the data source data model.
type AttestationDataSourceModel struct {
CSP types . String ` tfsdk:"csp" `
AttestationVariant types . String ` tfsdk:"attestation_variant" `
2023-12-18 04:15:54 -05:00
Image types . Object ` tfsdk:"image" `
2023-11-28 11:30:11 -05:00
MaaURL types . String ` tfsdk:"maa_url" `
2023-12-15 04:37:29 -05:00
Insecure types . Bool ` tfsdk:"insecure" `
2023-11-28 11:30:11 -05:00
Attestation types . Object ` tfsdk:"attestation" `
}
// Configure configures the data source.
2023-12-14 09:47:55 -05:00
func ( d * AttestationDataSource ) Configure ( _ context . Context , req datasource . ConfigureRequest , resp * datasource . ConfigureResponse ) {
// Prevent panic if the provider has not been configured. is necessary!
if req . ProviderData == nil {
return
}
providerData , ok := req . ProviderData . ( data . ProviderData )
if ! ok {
resp . Diagnostics . AddError (
"Unexpected Data Source Configure Type" ,
fmt . Sprintf ( "Expected data.ProviderData, got: %T. Please report this issue to the provider developers." , req . ProviderData ) ,
)
return
}
2023-12-18 08:21:19 -05:00
d . version = providerData . Version . String ( )
2023-12-14 09:47:55 -05:00
2023-11-28 11:30:11 -05:00
d . client = http . DefaultClient
d . fetcher = attestationconfigapi . NewFetcher ( )
rekor , err := sigstore . NewRekor ( )
if err != nil {
resp . Diagnostics . AddError ( "constructing rekor client" , err . Error ( ) )
return
}
d . rekor = rekor
}
// Metadata returns the metadata for the data source.
func ( d * AttestationDataSource ) Metadata ( _ context . Context , req datasource . MetadataRequest , resp * datasource . MetadataResponse ) {
resp . TypeName = req . ProviderTypeName + "_attestation"
}
// Schema returns the schema for the data source.
func ( d * AttestationDataSource ) Schema ( _ context . Context , _ datasource . SchemaRequest , resp * datasource . SchemaResponse ) {
resp . Schema = schema . Schema {
2023-12-18 04:15:54 -05:00
Description : "Data source to fetch an attestation configuration for a given cloud service provider, attestation variant, and OS image." ,
MarkdownDescription : "Data source to fetch an attestation configuration for a given cloud service provider, attestation variant, and OS image." ,
2023-11-28 11:30:11 -05:00
Attributes : map [ string ] schema . Attribute {
2023-12-18 04:15:54 -05:00
"csp" : newCSPAttributeSchema ( ) ,
"attestation_variant" : newAttestationVariantAttributeSchema ( attributeInput ) ,
"image" : newImageAttributeSchema ( attributeInput ) ,
2023-11-28 11:30:11 -05:00
"maa_url" : schema . StringAttribute {
2024-08-29 09:50:26 -04:00
MarkdownDescription : ` For Azure only , the URL of the Microsoft Azure Attestation service . The MAA ' s policy needs to be patched manually to work with Constellation OS images .
See the [ Constellation documentation ] ( https : //docs.edgeless.systems/constellation/workflows/terraform-provider#quick-setup) for more information.`,
Optional : true ,
2023-11-28 11:30:11 -05:00
} ,
2023-12-15 04:37:29 -05:00
"insecure" : schema . BoolAttribute {
MarkdownDescription : "DON'T USE IN PRODUCTION Skip the signature verification when fetching measurements for the image." ,
Optional : true ,
} ,
2023-12-18 04:15:54 -05:00
"attestation" : newAttestationConfigAttributeSchema ( attributeOutput ) ,
2023-11-28 11:30:11 -05:00
} ,
}
}
2023-12-14 09:47:55 -05:00
// ValidateConfig validates the configuration for the image data source.
func ( d * AttestationDataSource ) ValidateConfig ( ctx context . Context , req datasource . ValidateConfigRequest , resp * datasource . ValidateConfigResponse ) {
var data AttestationDataSourceModel
resp . Diagnostics . Append ( req . Config . Get ( ctx , & data ) ... )
if resp . Diagnostics . HasError ( ) {
return
}
if ! data . AttestationVariant . Equal ( types . StringValue ( "azure-sev-snp" ) ) && ! data . MaaURL . IsNull ( ) {
resp . Diagnostics . AddAttributeWarning (
path . Root ( "maa_url" ) ,
"MAA URL should only be set for Azure SEV-SNP" , "Only when attestation_variant is set to 'azure-sev-snp', 'maa_url' should be specified." ,
)
return
}
2024-08-29 09:50:26 -04:00
if ! data . MaaURL . IsNull ( ) {
resp . Diagnostics . AddAttributeWarning (
path . Root ( "maa_url" ) ,
"Ensure that the MAA's policy is patched" , "When MAA is used, please ensure the MAA's policy is patche properly for use within Constellation. See https://docs.edgeless.systems/constellation/workflows/terraform-provider#quick-setup for more information." ,
)
return
}
2023-12-14 09:47:55 -05:00
if data . AttestationVariant . Equal ( types . StringValue ( "azure-sev-snp" ) ) && data . MaaURL . IsNull ( ) {
2024-01-26 09:46:21 -05:00
tflog . Info ( ctx , "MAA URL not set, MAA fallback will be unavailable" )
2023-12-14 09:47:55 -05:00
}
}
2023-11-28 11:30:11 -05:00
// Read reads from the data source.
func ( d * AttestationDataSource ) Read ( ctx context . Context , req datasource . ReadRequest , resp * datasource . ReadResponse ) {
var data AttestationDataSourceModel
// Read Terraform configuration data into the model
resp . Diagnostics . Append ( req . Config . Get ( ctx , & data ) ... )
if resp . Diagnostics . HasError ( ) {
return
}
csp := cloudprovider . FromString ( data . CSP . ValueString ( ) )
if csp == cloudprovider . Unknown {
resp . Diagnostics . AddAttributeError (
path . Root ( "csp" ) ,
"Invalid CSP" ,
fmt . Sprintf ( "Invalid CSP: %s" , data . CSP . ValueString ( ) ) ,
)
return
}
attestationVariant , err := variant . FromString ( data . AttestationVariant . ValueString ( ) )
if err != nil {
resp . Diagnostics . AddAttributeError (
path . Root ( "attestation_variant" ) ,
"Invalid Attestation Variant" ,
2023-12-05 10:16:50 -05:00
fmt . Sprintf ( "Invalid attestation variant: %s" , data . AttestationVariant . ValueString ( ) ) ,
2023-11-28 11:30:11 -05:00
)
return
}
2023-12-05 10:16:50 -05:00
2023-12-15 04:37:29 -05:00
insecureFetch := data . Insecure . ValueBool ( )
2024-06-12 10:30:03 -04:00
latestVersions := attestationconfigapi . Entry { }
2024-06-12 04:24:16 -04:00
if attestationVariant . Equal ( variant . AWSSEVSNP { } ) ||
attestationVariant . Equal ( variant . AzureSEVSNP { } ) ||
attestationVariant . Equal ( variant . AzureTDX { } ) ||
2024-04-16 12:13:47 -04:00
attestationVariant . Equal ( variant . GCPSEVSNP { } ) {
2024-06-12 04:24:16 -04:00
latestVersions , err = d . fetcher . FetchLatestVersion ( ctx , attestationVariant )
2023-11-28 11:30:11 -05:00
if err != nil {
resp . Diagnostics . AddError ( "Fetching SNP Version numbers" , err . Error ( ) )
return
}
}
2024-06-12 04:24:16 -04:00
tfAttestation , err := convertToTfAttestation ( attestationVariant , latestVersions )
2023-12-05 10:16:50 -05:00
if err != nil {
2024-01-26 09:46:21 -05:00
resp . Diagnostics . AddError ( "Converting attestation" , err . Error ( ) )
2023-12-05 10:16:50 -05:00
}
2023-11-28 11:30:11 -05:00
verifyFetcher := measurements . NewVerifyFetcher ( sigstore . NewCosignVerifier , d . rekor , d . client )
2023-12-14 09:47:55 -05:00
2023-12-18 04:15:54 -05:00
// parse OS image version
var image imageAttribute
convertDiags := data . Image . As ( ctx , & image , basetypes . ObjectAsOptions { } )
resp . Diagnostics . Append ( convertDiags ... )
if resp . Diagnostics . HasError ( ) {
return
2023-12-14 09:47:55 -05:00
}
2023-12-18 04:15:54 -05:00
fetchedMeasurements , err := verifyFetcher . FetchAndVerifyMeasurements ( ctx , image . ShortPath ,
2023-12-15 04:37:29 -05:00
csp , attestationVariant , insecureFetch )
2023-11-28 11:30:11 -05:00
if err != nil {
var rekErr * measurements . RekorError
if errors . As ( err , & rekErr ) {
resp . Diagnostics . AddWarning ( "Ignoring Rekor related error" , err . Error ( ) )
} else {
resp . Diagnostics . AddError ( "fetching and verifying measurements" , err . Error ( ) )
return
}
}
2023-12-05 10:16:50 -05:00
tfAttestation . Measurements = convertToTfMeasurements ( fetchedMeasurements )
diags := resp . State . SetAttribute ( ctx , path . Root ( "attestation" ) , tfAttestation )
2023-11-28 11:30:11 -05:00
resp . Diagnostics . Append ( diags ... )
if resp . Diagnostics . HasError ( ) {
return
}
tflog . Trace ( ctx , "read constellation attestation data source" )
}