From 8db5134e141ea2a1e84fea0f0694125b8bde3f02 Mon Sep 17 00:00:00 2001 From: Patryk Kawa Date: Mon, 5 Feb 2024 20:16:51 +0100 Subject: [PATCH] Feature that allows loading cert/key/ca as strings, not only paths --- docs/index.md | 3 + internal/kmi/accounts.go | 23 ++++++- internal/provider/provider.go | 126 +++++++++++++++++++++++++++------- 3 files changed, 125 insertions(+), 27 deletions(-) diff --git a/docs/index.md b/docs/index.md index d900cd6..355886f 100644 --- a/docs/index.md +++ b/docs/index.md @@ -28,6 +28,9 @@ provider "kmi" { ### Optional - `akamai_ca` (String) +- `akamai_ca_path` (String) - `api_crt` (String) +- `api_crt_path` (String) - `api_key` (String, Sensitive) +- `api_key_path` (String, Sensitive) - `host` (String) diff --git a/internal/kmi/accounts.go b/internal/kmi/accounts.go index 39f1057..d283a81 100644 --- a/internal/kmi/accounts.go +++ b/internal/kmi/accounts.go @@ -19,7 +19,7 @@ type KMIRestClient struct { httpclient *http.Client } -func NewKMIRestClient(host string, apiKey string, apiCrt string, akamaiCA string) (*KMIRestClient, error) { +func NewKMIRestClientPath(host string, apiKey string, apiCrt string, akamaiCA string) (*KMIRestClient, error) { cert, err := tls.LoadX509KeyPair(apiCrt, apiKey) if err != nil { @@ -45,6 +45,27 @@ func NewKMIRestClient(host string, apiKey string, apiCrt string, akamaiCA string return &KMIRestClient{Host: host, ApiKey: apiKey, ApiCrt: apiCrt, AkamaiCA: akamaiCA, httpclient: client}, nil } +func NewKMIRestClient(host string, apiKey string, apiCrt string, akamaiCA string) (*KMIRestClient, error) { + + cert, err := tls.X509KeyPair([]byte(apiCrt), []byte(apiKey)) + if err != nil { + return nil, err + } + + caCertPool := x509.NewCertPool() + caCertPool.AppendCertsFromPEM([]byte(akamaiCA)) + + // Setup HTTPS client + tlsConfig := &tls.Config{ + Certificates: []tls.Certificate{cert}, + RootCAs: caCertPool, + } + transport := &http.Transport{TLSClientConfig: tlsConfig} + client := &http.Client{Transport: transport} + + return &KMIRestClient{Host: host, ApiKey: apiKey, ApiCrt: apiCrt, AkamaiCA: akamaiCA, httpclient: client}, nil +} + // GetAccountDetails returns the account details for the given account. func (client *KMIRestClient) GetAccountDetails(account string) (*Account, error) { diff --git a/internal/provider/provider.go b/internal/provider/provider.go index 20ab416..e94feed 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -59,16 +59,29 @@ func (p *kmiProvider) Schema(_ context.Context, _ provider.SchemaRequest, resp * "akamai_ca": schema.StringAttribute{ Optional: true, }, + "api_key_path": schema.StringAttribute{ + Optional: true, + Sensitive: true, + }, + "api_crt_path": schema.StringAttribute{ + Optional: true, + }, + "akamai_ca_path": schema.StringAttribute{ + Optional: true, + }, }, } } // kmiProviderModel maps provider schema data to a Go type. type kmiProviderModel struct { - Host types.String `tfsdk:"host"` - ApiKey types.String `tfsdk:"api_key"` - ApiCrt types.String `tfsdk:"api_crt"` - AkamaiCA types.String `tfsdk:"akamai_ca"` + Host types.String `tfsdk:"host"` + ApiKey types.String `tfsdk:"api_key"` + ApiCrt types.String `tfsdk:"api_crt"` + AkamaiCA types.String `tfsdk:"akamai_ca"` + ApiKeyPath types.String `tfsdk:"api_key_path"` + ApiCrtPath types.String `tfsdk:"api_crt_path"` + AkamaiCAPath types.String `tfsdk:"akamai_ca_path"` } // Configure prepares a kmi API client for data sources and resources. @@ -116,6 +129,33 @@ func (p *kmiProvider) Configure(ctx context.Context, req provider.ConfigureReque ) } + if config.AkamaiCAPath.IsUnknown() { + resp.Diagnostics.AddAttributeError( + path.Root("akamai_ca_path"), + "Unknown KMI akamai_ca_path", + "The provider cannot create the KMI API client as there is an unknown configuration value for the KMI_AKAMAI_CA_PATH. "+ + "Either target apply the source of the value first, set the value statically in the configuration, or use the KMI_AKAMAI_CA_PATH environment variable.", + ) + } + + if config.AkamaiCAPath.IsUnknown() { + resp.Diagnostics.AddAttributeError( + path.Root("api_crt_path"), + "Unknown KMI akamai_ca_path", + "The provider cannot create the KMI API client as there is an unknown configuration value for the KMI_API_CRT_PATH. "+ + "Either target apply the source of the value first, set the value statically in the configuration, or use the KMI_API_CRT_PATH environment variable.", + ) + } + + if config.AkamaiCAPath.IsUnknown() { + resp.Diagnostics.AddAttributeError( + path.Root("api_key_path"), + "Unknown KMI akamai_ca_path", + "The provider cannot create the KMI API client as there is an unknown configuration value for the KMI_API_KEY_PATH. "+ + "Either target apply the source of the value first, set the value statically in the configuration, or use the KMI_API_KEY_PATH environment variable.", + ) + } + if resp.Diagnostics.HasError() { return } @@ -124,6 +164,9 @@ func (p *kmiProvider) Configure(ctx context.Context, req provider.ConfigureReque apikey := os.Getenv("KMI_API_KEY") apicrt := os.Getenv("KMI_API_CRT") akamaica := os.Getenv("KMI_AKAMAI_CA") + apikeyPath := os.Getenv("KMI_API_KEY_PATH") + apicrtPath := os.Getenv("KMI_API_CRT_PATH") + akamaicaPath := os.Getenv("KMI_AKAMAI_CA_PATH") if !config.Host.IsNull() { host = config.Host.ValueString() @@ -141,6 +184,18 @@ func (p *kmiProvider) Configure(ctx context.Context, req provider.ConfigureReque akamaica = config.AkamaiCA.ValueString() } + if !config.AkamaiCAPath.IsNull() { + akamaicaPath = config.AkamaiCAPath.ValueString() + } + + if !config.ApiKeyPath.IsNull() { + apikeyPath = config.ApiKeyPath.ValueString() + } + + if !config.ApiCrtPath.IsNull() { + apicrtPath = config.ApiCrtPath.ValueString() + } + if host == "" { resp.Diagnostics.AddAttributeError( path.Root("host"), @@ -151,32 +206,32 @@ func (p *kmiProvider) Configure(ctx context.Context, req provider.ConfigureReque ) } - if apikey == "" { + if apikey == "" && apikeyPath == "" { resp.Diagnostics.AddAttributeError( path.Root("api_key"), "Missing KMI API Key", "The provider cannot create the KMI API client as there is a missing or empty value for the kmi API host. "+ - "Set the host value in the configuration or use the KMI_API_KEY environment variable. "+ + "Set the host value in the configuration or use the KMI_API_KEY/KMI_API_KEY_PATH environment variable. "+ "If either is already set, ensure the value is not empty.", ) } - if apicrt == "" { + if apicrt == "" && apicrtPath == "" { resp.Diagnostics.AddAttributeError( path.Root("api_crt"), "Missing KMI API Certificate", "The provider cannot create the KMI API client as there is a missing or empty value for the kmi API host. "+ - "Set the host value in the configuration or use the KMI_API_CRT environment variable. "+ + "Set the host value in the configuration or use the KMI_API_CRT/KMI_API_CRT_PATH environment variable. "+ "If either is already set, ensure the value is not empty.", ) } - if akamaica == "" { + if akamaica == "" && akamaicaPath == "" { resp.Diagnostics.AddAttributeError( path.Root("akamai_ca"), "Missing KMI Certificate Authority", "The provider cannot create the KMI API client as there is a missing or empty value for the kmi API host. "+ - "Set the host value in the configuration or use the KMI_AKAMAI_CA environment variable. "+ + "Set the host value in the configuration or use the KMI_AKAMAI_CA/KMI_AKAMAI_CA_PATH environment variable. "+ "If either is already set, ensure the value is not empty.", ) } @@ -189,27 +244,46 @@ func (p *kmiProvider) Configure(ctx context.Context, req provider.ConfigureReque ctx = tflog.SetField(ctx, "kmi_key", apikey) ctx = tflog.SetField(ctx, "kmi_crt", apicrt) ctx = tflog.SetField(ctx, "kmi_ca", akamaica) + ctx = tflog.SetField(ctx, "kmi_key_path", apikeyPath) + ctx = tflog.SetField(ctx, "kmi_crt_path", apicrtPath) + ctx = tflog.SetField(ctx, "kmi_ca_path", akamaicaPath) tflog.Debug(ctx, "Creating KMI client") - client, err := kmi.NewKMIRestClient(host, apikey, apicrt, akamaica) - tflog.Info(ctx, "Configured KMI client", map[string]any{"success": true}) - - if err != nil { - resp.Diagnostics.AddError( - "Unable to Create kmi API Client", - "An unexpected error occurred when creating the kmi API client. "+ - "If the error is not clear, please contact the provider developers.\n\n"+ - "kmi Client Error: "+err.Error(), - ) - return - } + // setting a path variable takes a precedence over having it configured with the string + if apikeyPath != "" { + client, err := kmi.NewKMIRestClientPath(host, apikeyPath, apicrtPath, akamaicaPath) + tflog.Info(ctx, "Configured KMI client", map[string]any{"success": true}) + + if err != nil { + resp.Diagnostics.AddError( + "Unable to Create kmi API Client", + "An unexpected error occurred when creating the kmi API client. "+ + "If the error is not clear, please contact the provider developers.\n\n"+ + "kmi Client Error: "+err.Error(), + ) + return + } - // Make the kmi client available during DataSource and Resource - // type Configure methods. - resp.DataSourceData = client - resp.ResourceData = client + resp.DataSourceData = client + resp.ResourceData = client + } else { + client, err := kmi.NewKMIRestClient(host, apikey, apicrt, akamaica) + tflog.Info(ctx, "Configured KMI client", map[string]any{"success": true}) + + if err != nil { + resp.Diagnostics.AddError( + "Unable to Create kmi API Client", + "An unexpected error occurred when creating the kmi API client. "+ + "If the error is not clear, please contact the provider developers.\n\n"+ + "kmi Client Error: "+err.Error(), + ) + return + } + resp.DataSourceData = client + resp.ResourceData = client + } } // DataSources defines the data sources implemented in the provider.