diff --git a/README.md b/README.md index f9ed6c561..2a24f4b2d 100644 --- a/README.md +++ b/README.md @@ -16,8 +16,8 @@ Terraform provider for [Netbox.](https://netbox.readthedocs.io/en/stable/) ## Compatibility with Netbox -Version 0.x.y => Netbox 2.8 -Version 1.x.y => Netbox 2.9 +Version 0.x.y => Netbox 2.8 +Version 1.x.y => Netbox 2.9 ## Building the provider @@ -78,19 +78,25 @@ $ make localinstall ## Using the provider -The definition of the provider is optional. -All the parameters could be setup by environment variables. +The definition of the provider is optional. +All the parameters could be setup by environment variables. ```hcl provider netbox { # Environment variable NETBOX_URL url = "127.0.0.1:8000" - + # Environment variable NETBOX_TOKEN token = "c07a2db4adb8b1e7f75e7c4369964e92f7680512" - + # Environment variable NETBOX_SCHEME scheme = "http" + + # Environment variable NETBOX_INSECURE + insecure = "true" + + # Environment variable NETBOX_PRIVATE_KEY_FILE + private_key_file = "/path/to/private/key" } ``` @@ -103,8 +109,8 @@ commits](https://www.conventionalcommits.org/en/v1.0.0-beta.2/) rules. ## Examples -You can find some examples in the examples folder. -Each example can be executed directly with command terraform init & terraform apply. +You can find some examples in the examples folder. +Each example can be executed directly with command terraform init & terraform apply. You can set different environment variables for your test: * NETBOX_URL to define the URL and the port (127.0.0.1:8000 by default) * NETBOX_TOKEN to define the TOKEN to access the application (empty by default) diff --git a/docs/index.md b/docs/index.md index 354e77e52..2b539b0b8 100644 --- a/docs/index.md +++ b/docs/index.md @@ -4,8 +4,8 @@ Terraform provider for [Netbox.](https://netbox.readthedocs.io/en/stable/) ## Compatibility with Netbox -Version 0.x.y => Netbox 2.8 -Version 1.x.y => Netbox 2.9 +Version 0.x.y => Netbox 2.8 +Version 1.x.y => Netbox 2.9 ## Example Usage @@ -22,6 +22,12 @@ provider netbox { # Environment variable NETBOX_SCHEME scheme = "http" + + # Environment variable NETBOX_INSECURE + insecure = "true" + + # Environment variable NETBOX_PRIVATE_KEY_FILE + private_key_file = "/path/to/private/key" } ``` @@ -32,3 +38,4 @@ provider netbox { * `token` or `NETBOX_TOKEN` environment variable to define the TOKEN to access the application (empty by default) * `scheme` or `NETBOX_SCHEME` environment variable to define the SCHEME of the URL (https by default) * `insecure` or `NETBOX_INSECURE` environment variable to skip or not the TLS certificat validation (false by default) +* `private_key_file` or `NETBOX_PRIVATE_KEY_FILE` environment variable to add a private key to work with encoded data like secrets (empty by default) diff --git a/netbox/provider.go b/netbox/provider.go index 2d83b8358..2748b5198 100644 --- a/netbox/provider.go +++ b/netbox/provider.go @@ -1,7 +1,9 @@ package netbox import ( + "crypto/tls" "fmt" + "net/http" runtimeclient "github.com/go-openapi/runtime/client" "github.com/go-openapi/strfmt" @@ -46,6 +48,12 @@ func Provider() *schema.Provider { DefaultFunc: schema.EnvDefaultFunc("NETBOX_INSECURE", false), Description: "Skip TLS certificate validation.", }, + "private_key_file": { + Type: schema.TypeString, + Optional: true, + DefaultFunc: schema.EnvDefaultFunc("NETBOX_PRIVATE_KEY_FILE", ""), + Description: "Private Key used for Secrets", + }, }, DataSourcesMap: map[string]*schema.Resource{ "netbox_dcim_platform": dataNetboxDcimPlatform(), @@ -149,17 +157,61 @@ func configureProvider(d *schema.ResourceData) (interface{}, error) { token := d.Get("token").(string) scheme := d.Get("scheme").(string) insecure := d.Get("insecure").(bool) - - defaultScheme := []string{scheme} + privateKeyFile := d.Get("private_key_file").(string) var options runtimeclient.TLSClientOptions options.InsecureSkipVerify = insecure + tlsConfig, _ := runtimeclient.TLSClientAuth(options) - clientWithTLSOptions, _ := runtimeclient.TLSClient(options) + headers := make(map[string]string) + + if privateKeyFile != "" { + privateKey := ReadRSAKey(privateKeyFile) + if privateKey == "" { + return nil, fmt.Errorf("Error reading the private key file.") + } + sessionKey := GetSessionKey(fmt.Sprintf("%s://%s", scheme, url), token, privateKey) + if sessionKey == "" { + return nil, fmt.Errorf("The provided private key is invalid.") + } + headers["X-Session-Key"] = sessionKey + } + // Create a custom client + // Override the default transport with a RoundTripper to inject dynamic headers + // Add TLSOptions + cli := &http.Client{ + Transport: &transport{ + headers: headers, + TLSClientConfig: tlsConfig, + }, + } - t := runtimeclient.NewWithClient(url, basepath, defaultScheme, clientWithTLSOptions) + defaultScheme := []string{scheme} + + t := runtimeclient.NewWithClient(url, basepath, defaultScheme, cli) t.DefaultAuthentication = runtimeclient.APIKeyAuth(authHeaderName, "header", fmt.Sprintf(authHeaderFormat, token)) return client.New(t, strfmt.Default), nil } + +type transport struct { + headers map[string]string + base http.RoundTripper + TLSClientConfig *tls.Config +} + +func (t *transport) RoundTrip(req *http.Request) (*http.Response, error) { + // Add headers to request + for k, v := range t.headers { + req.Header.Add(k, v) + } + base := t.base + if base == nil { + // init an http.Transport with TLSOptions + customTransport := http.DefaultTransport.(*http.Transport).Clone() + customTransport.TLSClientConfig = t.TLSClientConfig + base = customTransport + } + return base.RoundTrip(req) +} diff --git a/netbox/util.go b/netbox/util.go index bce78ced1..801feed43 100644 --- a/netbox/util.go +++ b/netbox/util.go @@ -1,7 +1,13 @@ package netbox import ( + "encoding/json" "fmt" + "io/ioutil" + "net/http" + "net/url" + "strings" + netboxclient "github.com/netbox-community/go-netbox/netbox/client" "github.com/netbox-community/go-netbox/netbox/client/virtualization" "github.com/netbox-community/go-netbox/netbox/models" @@ -210,3 +216,39 @@ func convertCustomFieldsFromTerraformToAPIUpdate(stateCustomFields, resourceCust return toReturn } + +func ReadRSAKey(filePath string) (res string) { + data, err := ioutil.ReadFile(filePath) + if err != nil { + return "" + } + return string(data) +} + +func GetSessionKey(destinationUrl string, token string, rsaKey string) string { + privateKey := rsaKey + data := url.Values{} + data.Set("private_key", privateKey) // set private key string + destinationUrl = destinationUrl + "/api/secrets/get-session-key/" + + req, err := http.NewRequest("POST", destinationUrl, strings.NewReader(data.Encode())) // URL-enconded payload + if err != nil { + // + return "" + } + + req.Header.Set("Authorization", "Token "+token) + req.Header.Add("Content-Type", "application/x-www-form-urlencoded") + req.Header.Set("Accept", "application/json; indent=4") + + resp, err := http.DefaultClient.Do(req) + if err != nil { + return "" + } + + content, err := ioutil.ReadAll(resp.Body) + m := make(map[string]string) + err = json.Unmarshal(content, &m) // convert json to map + + return m["session_key"] +}