package dns import ( "context" "errors" "fmt" "net/http" "reflect" "strings" "github.com/akamai/AkamaiOPEN-edgegrid-golang/v11/pkg/edgegriderr" "github.com/akamai/AkamaiOPEN-edgegrid-golang/v11/pkg/session" validation "github.com/go-ozzo/ozzo-validation/v4" ) type ( // TSIGQueryString contains TSIG query parameters TSIGQueryString struct { ContractIDs []string `json:"contractIds,omitempty"` Search string `json:"search,omitempty"` SortBy []string `json:"sortBy,omitempty"` GID int64 `json:"gid,omitempty"` } // TSIGKey contains TSIG key POST response TSIGKey struct { Name string `json:"name"` Algorithm string `json:"algorithm,omitempty"` Secret string `json:"secret,omitempty"` } // TSIGKeyRequest contains request parameter TSIGKeyRequest struct { Zone string } // GetTSIGKeyRequest contains request parameters for GetTSIGKey GetTSIGKeyRequest TSIGKeyRequest // GetTSIGKeyResponse contains the response data from GetTSIGKey operation GetTSIGKeyResponse struct { TSIGKey ZoneCount int64 `json:"zonesCount,omitempty"` } // DeleteTSIGKeyRequest contains request parameters for DeleteTSIGKey DeleteTSIGKeyRequest TSIGKeyRequest // GetTSIGKeyAliasesRequest contains request parameters for GetTSIGKeyAliases GetTSIGKeyAliasesRequest TSIGKeyRequest // GetTSIGKeyAliasesResponse contains the response data from GetTSIGKeyAliases operation GetTSIGKeyAliasesResponse struct { Zones []string `json:"zones"` Aliases []string `json:"aliases"` } // TSIGKeyResponse contains TSIG key GET response TSIGKeyResponse struct { TSIGKey ZoneCount int64 `json:"zonesCount,omitempty"` } // TSIGKeyBulkPost contains TSIG key and a list of names of zones that should use the key. Used with update function. TSIGKeyBulkPost struct { Key *TSIGKey `json:"key"` Zones []string `json:"zones"` } // TSIGZoneAliases contains list of zone aliases TSIGZoneAliases struct { Aliases []string `json:"aliases"` } // TSIGReportMeta contains metadata for TSIGReport response TSIGReportMeta struct { TotalElements int64 `json:"totalElements"` Search string `json:"search,omitempty"` Contracts []string `json:"contracts,omitempty"` GID int64 `json:"gid,omitempty"` SortBy []string `json:"sortBy,omitempty"` } // TSIGReportResponse contains response with a list of the TSIG keys used by zones. TSIGReportResponse struct { Metadata *TSIGReportMeta `json:"metadata"` Keys []TSIGKeyResponse `json:"keys,omitempty"` } // UpdateTSIGKeyRequest contains request parameters for UpdateTSIGKey UpdateTSIGKeyRequest struct { TsigKey *TSIGKey Zone string } // UpdateTSIGKeyBulkRequest contains request parameters for UpdateTSIGKeyBulk UpdateTSIGKeyBulkRequest struct { TSIGKeyBulk *TSIGKeyBulkPost } // GetTSIGKeyZonesRequest contains request parameters for GetTSIGKeyZones GetTSIGKeyZonesRequest struct { TsigKey *TSIGKey } // GetTSIGKeyZonesResponse contains the response data from GetTSIGKeyZones operation GetTSIGKeyZonesResponse struct { Zones []string `json:"zones"` Aliases []string `json:"aliases"` } // ListTSIGKeysRequest contains request parameters for ListTSIGKeys ListTSIGKeysRequest struct { TsigQuery *TSIGQueryString } // ListTSIGKeysResponse contains the response data from ListTSIGKeys operation ListTSIGKeysResponse struct { Metadata *TSIGReportMeta `json:"metadata"` Keys []TSIGKeyResponse `json:"keys,omitempty"` } ) var ( // ErrGetTSIGKey is returned when GetTSIGKey fails ErrGetTSIGKey = errors.New("get tsig key") // ErrDeleteTSIGKey is returned when DeleteTSIGKey fails ErrDeleteTSIGKey = errors.New("delete tsig key") // ErrGetTSIGKeyAliases is returned when GetTSIGKeyAliases fails ErrGetTSIGKeyAliases = errors.New("get tsig key aliases") // ErrUpdateTSIGKey is returned when UpdateTSIGKey fails ErrUpdateTSIGKey = errors.New("updated tsig key") // ErrUpdateTSIGKeyBulk is returned when UpdateTSIGKeyBulk fails ErrUpdateTSIGKeyBulk = errors.New("update tsig key for multiple zones") // ErrGetTSIGKeyZones is returned when GetTSIGKeyZones fails ErrGetTSIGKeyZones = errors.New("list zones using tsig key") // ErrListTSIGKeys is returned when ListTSIGKeys fails ErrListTSIGKeys = errors.New("get a list of the tsig keys") ) // Validate validates GetTSIGKeyRequest func (r GetTSIGKeyRequest) Validate() error { return edgegriderr.ParseValidationErrors(validation.Errors{ "Zone": validation.Validate(r.Zone, validation.Required), }) } // Validate validates DeleteTSIGKeyRequest func (r DeleteTSIGKeyRequest) Validate() error { return edgegriderr.ParseValidationErrors(validation.Errors{ "Zone": validation.Validate(r.Zone, validation.Required), }) } // Validate validates GetTSIGKeyAliasesRequest func (r GetTSIGKeyAliasesRequest) Validate() error { return edgegriderr.ParseValidationErrors(validation.Errors{ "Zone": validation.Validate(r.Zone, validation.Required), }) } // Validate validates UpdateTSIGKeyRequest func (r UpdateTSIGKeyRequest) Validate() error { return edgegriderr.ParseValidationErrors(validation.Errors{ "Zone": validation.Validate(r.Zone, validation.Required), "TsigKey": validation.Validate(r.TsigKey), }) } // Validate validates UpdateTSIGKeyBulkRequest func (r UpdateTSIGKeyBulkRequest) Validate() error { return edgegriderr.ParseValidationErrors(validation.Errors{ "TSIGKeyBulk": validation.Validate(r.TSIGKeyBulk, validation.Required), }) } // Validate validates GetTSIGKeyZonesRequest func (r GetTSIGKeyZonesRequest) Validate() error { return edgegriderr.ParseValidationErrors(validation.Errors{ "TsigKey": validation.Validate(r.TsigKey, validation.Required), }) } // Validate validates TSIGKey func (key *TSIGKey) Validate() error { return validation.Errors{ "Name": validation.Validate(key.Name, validation.Required), "Algorithm": validation.Validate(key.Algorithm, validation.Required), "Secret": validation.Validate(key.Secret, validation.Required), }.Filter() } // Validate validates TSIGKeyBulkPost func (bulk *TSIGKeyBulkPost) Validate() error { return validation.Errors{ "Key": validation.Validate(bulk.Key, validation.Required), "Zones": validation.Validate(bulk.Zones, validation.Required), }.Filter() } func constructTSIGQueryString(tsigQueryString *TSIGQueryString) string { queryString := "" qsElems := reflect.ValueOf(tsigQueryString).Elem() for i := 0; i < qsElems.NumField(); i++ { varName := qsElems.Type().Field(i).Name varValue := qsElems.Field(i).Interface() keyVal := fmt.Sprint(varValue) switch varName { case "ContractIDs": contractList := "" for j, id := range varValue.([]string) { contractList += id if j < len(varValue.([]string))-1 { contractList += "%2C" } } if len(varValue.([]string)) > 0 { queryString += "contractIds=" + contractList } case "SortBy": sortByList := "" for j, sb := range varValue.([]string) { sortByList += sb if j < len(varValue.([]string))-1 { sortByList += "%2C" } } if len(varValue.([]string)) > 0 { queryString += "sortBy=" + sortByList } case "Search": if keyVal != "" { queryString += "search=" + keyVal } case "GID": if varValue.(int64) != 0 { queryString += "gid=" + keyVal } } if i < qsElems.NumField()-1 { queryString += "&" } } queryString = strings.TrimRight(queryString, "&") if len(queryString) > 0 { return "?" + queryString } return "" } func (d *dns) ListTSIGKeys(ctx context.Context, params ListTSIGKeysRequest) (*ListTSIGKeysResponse, error) { logger := d.Log(ctx) logger.Debug("ListTSIGKeys") getURL := fmt.Sprintf("/config-dns/v2/keys%s", constructTSIGQueryString(params.TsigQuery)) req, err := http.NewRequestWithContext(ctx, http.MethodGet, getURL, nil) if err != nil { return nil, fmt.Errorf("failed to create ListTsigKeyss request: %w", err) } var result ListTSIGKeysResponse resp, err := d.Exec(req, &result) if err != nil { return nil, fmt.Errorf(" ListTsigKeys request failed: %w", err) } defer session.CloseResponseBody(resp) if resp.StatusCode != http.StatusOK { return nil, d.Error(resp) } return &result, nil } func (d *dns) GetTSIGKeyZones(ctx context.Context, params GetTSIGKeyZonesRequest) (*GetTSIGKeyZonesResponse, error) { logger := d.Log(ctx) logger.Debug("GetTSIGKeyZones") if err := params.Validate(); err != nil { return nil, fmt.Errorf("%s: %w: %s", ErrGetTSIGKeyZones, ErrStructValidation, err) } reqBody, err := convertStructToReqBody(params.TsigKey) if err != nil { return nil, fmt.Errorf("failed to generate request body: %w", err) } postURL := "/config-dns/v2/keys/used-by" req, err := http.NewRequestWithContext(ctx, http.MethodPost, postURL, reqBody) if err != nil { return nil, fmt.Errorf("failed to create GetTsigKeyZones request: %w", err) } var result GetTSIGKeyZonesResponse resp, err := d.Exec(req, &result) if err != nil { return nil, fmt.Errorf("GetTsigKeyZones request failed: %w", err) } defer session.CloseResponseBody(resp) if resp.StatusCode != http.StatusOK { return nil, d.Error(resp) } return &result, nil } func (d *dns) GetTSIGKeyAliases(ctx context.Context, params GetTSIGKeyAliasesRequest) (*GetTSIGKeyAliasesResponse, error) { logger := d.Log(ctx) logger.Debug("GetTSIGKeyAliases") if err := params.Validate(); err != nil { return nil, fmt.Errorf("%s: %w: %s", ErrGetTSIGKeyAliases, ErrStructValidation, err) } getURL := fmt.Sprintf("/config-dns/v2/zones/%s/key/used-by", params.Zone) req, err := http.NewRequestWithContext(ctx, http.MethodGet, getURL, nil) if err != nil { return nil, fmt.Errorf("failed to create GetTsigKeyAliases request: %w", err) } var result GetTSIGKeyAliasesResponse resp, err := d.Exec(req, &result) if err != nil { return nil, fmt.Errorf("GetTsigKeyAliases request failed: %w", err) } defer session.CloseResponseBody(resp) if resp.StatusCode != http.StatusOK { return nil, d.Error(resp) } return &result, nil } func (d *dns) UpdateTSIGKeyBulk(ctx context.Context, params UpdateTSIGKeyBulkRequest) error { logger := d.Log(ctx) logger.Debug("TSIGKeyBulkUpdate") if err := params.Validate(); err != nil { return fmt.Errorf("%s: %w: %s", ErrUpdateTSIGKeyBulk, ErrStructValidation, err) } reqBody, err := convertStructToReqBody(params.TSIGKeyBulk) if err != nil { return fmt.Errorf("failed to generate request body: %w", err) } postURL := "/config-dns/v2/keys/bulk-update" req, err := http.NewRequestWithContext(ctx, http.MethodPost, postURL, reqBody) if err != nil { return fmt.Errorf("failed to create TsigKeyBulkUpdate request: %w", err) } resp, err := d.Exec(req, nil) if err != nil { return fmt.Errorf("TsigKeyBulkUpdate request failed: %w", err) } defer session.CloseResponseBody(resp) if resp.StatusCode != http.StatusNoContent { return d.Error(resp) } return nil } func (d *dns) GetTSIGKey(ctx context.Context, params GetTSIGKeyRequest) (*GetTSIGKeyResponse, error) { logger := d.Log(ctx) logger.Debug("GetTSIGKey") if err := params.Validate(); err != nil { return nil, fmt.Errorf("%s: %w: %s", ErrGetTSIGKey, ErrStructValidation, err) } getURL := fmt.Sprintf("/config-dns/v2/zones/%s/key", params.Zone) req, err := http.NewRequestWithContext(ctx, http.MethodGet, getURL, nil) if err != nil { return nil, fmt.Errorf("failed to create GetTsigKey request: %w", err) } var result GetTSIGKeyResponse resp, err := d.Exec(req, &result) if err != nil { return nil, fmt.Errorf("GetTsigKey request failed: %w", err) } defer session.CloseResponseBody(resp) if resp.StatusCode != http.StatusOK { return nil, d.Error(resp) } return &result, nil } func (d *dns) DeleteTSIGKey(ctx context.Context, params DeleteTSIGKeyRequest) error { logger := d.Log(ctx) logger.Debug("DeleteTSIGKey") if err := params.Validate(); err != nil { return fmt.Errorf("%s: %w: %s", ErrDeleteTSIGKey, ErrStructValidation, err) } delURL := fmt.Sprintf("/config-dns/v2/zones/%s/key", params.Zone) req, err := http.NewRequestWithContext(ctx, http.MethodDelete, delURL, nil) if err != nil { return fmt.Errorf("failed to create DeleteTsigKey request: %w", err) } resp, err := d.Exec(req, nil) if err != nil { return fmt.Errorf("DeleteTsigKey request failed: %w", err) } defer session.CloseResponseBody(resp) if resp.StatusCode != http.StatusNoContent { return d.Error(resp) } return nil } func (d *dns) UpdateTSIGKey(ctx context.Context, params UpdateTSIGKeyRequest) error { logger := d.Log(ctx) logger.Debug("UpdateTSIGKey") if err := params.Validate(); err != nil { return fmt.Errorf("%s: %w: %s", ErrUpdateTSIGKey, ErrStructValidation, err) } reqBody, err := convertStructToReqBody(params.TsigKey) if err != nil { return fmt.Errorf("failed to generate request body: %w", err) } putURL := fmt.Sprintf("/config-dns/v2/zones/%s/key", params.Zone) req, err := http.NewRequestWithContext(ctx, http.MethodPut, putURL, reqBody) if err != nil { return fmt.Errorf("failed to create UpdateTsigKey request: %w", err) } resp, err := d.Exec(req, nil) if err != nil { return fmt.Errorf("UpdateTsigKey request failed: %w", err) } defer session.CloseResponseBody(resp) if resp.StatusCode != http.StatusNoContent { return d.Error(resp) } return nil }