package netboxgo import ( "bytes" "context" "crypto/tls" "encoding/json" "fmt" "io" "io/ioutil" "log" "net/http" "net/url" "strings" "time" "github.com/pkg/errors" ) type DCIM struct { Devices *DevicesService DeviceRoles *DeviceRolesService DeviceTypes *DeviceTypesService InventoryItems *InventoryItemsService RearPorts *RearPortsService } type Virtualization struct { Clusters *ClustersService VirtualMachines *VirtualMachinesService } type IPAM struct { VLANs *VLANsService VRFs *VRFsService Prefixes *PrefixesService } type Secret struct { Secrets *SecretsService } type Tenancy struct { Tenants *TenantsService Sites *SitesService SiteGroups *SiteGroupsService } // Client struct is used to create a new NetBox endpoint type Client struct { DCIM *DCIM Tenancy *Tenancy IPAM *IPAM Virtualization *Virtualization Secret *Secret Tenants *TenantsService Sites *SitesService SiteGroups *SiteGroupsService VirtualMachines *VirtualMachinesService Clusters *ClustersService VLANs *VLANsService Secrets *SecretsService InventoryItems *InventoryItemsService Devices *DevicesService DeviceRoles *DeviceRolesService DeviceTypes *DeviceTypesService Interfaces *InterfacesService Prefixes *PrefixesService VRFs *VRFsService RearPorts *RearPortsService // baseURL is the URL used for the base URL of the API baseURL *url.URL // httpClient is used for HTTP requests httpClient *http.Client // Reuse a single struct instead of allocating one for each service on the heap. common service // UserAgent is used to set the UserAgent on the HTTP requests UserAgent string // Token is set for authentication of the API Token string // Debug enables verbose debugging messages to console. Debug bool // InsecureSkipVerify is used to selectively skip InsecureVerifications InsecureSkipVerify bool // SessionKey is used to read authentication data SessionKey string // Used by golang wasm FetchMode string } type service struct { client *Client } // NetBoxSessionKey sets the session key for secrets retrieval type NetBoxSessionKey struct { XSessionKey string `json:"session_key"` } // NewClient returns a new NetBox API client func NewClient(apiurl string, httpClient *http.Client) (*Client, error) { c := &Client{} api, err := url.Parse(apiurl) if err != nil { return c, fmt.Errorf("unable to parse url %s: %w", apiurl, err) } c.baseURL = api c.SetDebug(false) c.SetUserAgent("netboxgo/0.0.6") c.httpClient = httpClient if httpClient == nil { transport := &http.Transport{ TLSClientConfig: &tls.Config{ InsecureSkipVerify: false, CipherSuites: []uint16{ tls.TLS_AES_128_GCM_SHA256, tls.TLS_AES_256_GCM_SHA384, tls.TLS_CHACHA20_POLY1305_SHA256, }, MinVersion: tls.VersionTLS12, }, Proxy: http.ProxyFromEnvironment, } timeout := time.Duration(60 * time.Second) c.httpClient = &http.Client{ Transport: transport, Jar: nil, Timeout: timeout, } } c.common.client = c c.Clusters = (*ClustersService)(&c.common) c.Devices = (*DevicesService)(&c.common) c.DeviceRoles = (*DeviceRolesService)(&c.common) c.DeviceTypes = (*DeviceTypesService)(&c.common) c.Interfaces = (*InterfacesService)(&c.common) c.InventoryItems = (*InventoryItemsService)(&c.common) c.Prefixes = (*PrefixesService)(&c.common) c.RearPorts = (*RearPortsService)(&c.common) c.Tenants = (*TenantsService)(&c.common) c.Secrets = (*SecretsService)(&c.common) c.Sites = (*SitesService)(&c.common) c.SiteGroups = (*SiteGroupsService)(&c.common) c.VirtualMachines = (*VirtualMachinesService)(&c.common) c.VLANs = (*VLANsService)(&c.common) c.VRFs = (*VRFsService)(&c.common) return c, nil } // SetUserAgent is used to set the user agent for interaction with the API func (c *Client) SetUserAgent(u string) { c.UserAgent = u } // SetDebug is used to toggle debugging, set to true for debug func (c *Client) SetDebug(b bool) { c.Debug = b } // SetToken is used to set the http token bearer for interaction with the API func (c *Client) SetToken(token string) { c.Token = token } // FetchSessionKey fetches sessionkey func (n *Client) FetchSessionKey(privatekey string) error { form := url.Values{} form.Add("private_key", privatekey) query := form.Encode() transport := &http.Transport{ // #nosec XXX: FIXIT TLSClientConfig: &tls.Config{InsecureSkipVerify: n.InsecureSkipVerify}, } timeout := time.Duration(60 * time.Second) client := &http.Client{ Timeout: timeout, Transport: transport, } const secretsPath = "/secrets" request, err := http.NewRequest("POST", secretsPath+"/get-session-key/", strings.NewReader(query)) if err != nil { return err } request.Header.Add("Accept", "application/json") request.Header.Add("Content-Type", "application/x-www-form-urlencoded") if n.FetchMode != "" { request.Header.Add("js.fetch:mode", "no-cors") } request.Header.Add("Authorization", " Token "+n.Token) response, err := client.Do(request) if err != nil { return err } if response.StatusCode != http.StatusOK { return errors.Errorf("response was: %d should be %d\n%s\n", response.StatusCode, http.StatusOK, response.Header) } data, err := ioutil.ReadAll(response.Body) if err != nil { return err } err = response.Body.Close() if err != nil { return err } var sessionkey NetBoxSessionKey err = json.Unmarshal(data, &sessionkey) if err != nil { return err } n.SessionKey = sessionkey.XSessionKey return nil } // newRequest is used to make new request to endpoints func (c *Client) newRequest(ctx context.Context, method, path string, query string, body interface{}) (*http.Request, error) { var err error var buf io.ReadWriter u := &url.URL{ Scheme: c.baseURL.Scheme, Host: c.baseURL.Host, Path: c.baseURL.Path + path, RawQuery: query, } if body != nil { buf = new(bytes.Buffer) err = json.NewEncoder(buf).Encode(body) if err != nil { return nil, err } } readBuf, _ := json.Marshal(body) c.debugf("debug: method:%s url:%s body:%s", method, u.String(), string(readBuf)) req, err := http.NewRequestWithContext(ctx, method, u.String(), buf) if err != nil { return nil, err } if body != nil { req.Header.Set("Content-Type", "application/json") } if c.SessionKey != "" { req.Header.Add("X-Session-Key", c.SessionKey) } req.Header.Set("Accept", "application/json") if c.UserAgent != "" { req.Header.Set("User-Agent", c.UserAgent) } if c.Token != "" { req.Header.Add("Authorization", "Bearer "+c.Token) } if c.SessionKey != "" { req.Header.Add("X-Session-Key", c.SessionKey) } return req, nil } // do method is used to decode request response to proper struct func (c *Client) do(req *http.Request, v interface{}) (*http.Response, error) { var err error var resp *http.Response resp, err = c.httpClient.Do(req) if err != nil { return nil, fmt.Errorf("client unable to do request: %w", err) } if resp.StatusCode < 200 || resp.StatusCode > 299 { var data []byte data, err = io.ReadAll(resp.Body) if err != nil { return nil, fmt.Errorf("unable to read body response: %w", err) } return nil, fmt.Errorf("request failed: body=%s", string(data)) } if v != nil { err = json.NewDecoder(resp.Body).Decode(v) if err != nil { return resp, fmt.Errorf("unable to decode response body: %w", err) } } return resp, nil } // debugf is used as a formtter for debugging information func (c *Client) debugf(fmt string, v ...interface{}) { if c.Debug { log.Printf("cendot client: "+fmt, v...) } }