tsig.go raw

   1  package dns
   2  
   3  import (
   4  	"context"
   5  	"errors"
   6  	"fmt"
   7  	"net/http"
   8  	"reflect"
   9  	"strings"
  10  
  11  	"github.com/akamai/AkamaiOPEN-edgegrid-golang/v11/pkg/edgegriderr"
  12  	"github.com/akamai/AkamaiOPEN-edgegrid-golang/v11/pkg/session"
  13  	validation "github.com/go-ozzo/ozzo-validation/v4"
  14  )
  15  
  16  type (
  17  	// TSIGQueryString contains TSIG query parameters
  18  	TSIGQueryString struct {
  19  		ContractIDs []string `json:"contractIds,omitempty"`
  20  		Search      string   `json:"search,omitempty"`
  21  		SortBy      []string `json:"sortBy,omitempty"`
  22  		GID         int64    `json:"gid,omitempty"`
  23  	}
  24  
  25  	// TSIGKey contains TSIG key POST response
  26  	TSIGKey struct {
  27  		Name      string `json:"name"`
  28  		Algorithm string `json:"algorithm,omitempty"`
  29  		Secret    string `json:"secret,omitempty"`
  30  	}
  31  
  32  	// TSIGKeyRequest contains request parameter
  33  	TSIGKeyRequest struct {
  34  		Zone string
  35  	}
  36  
  37  	// GetTSIGKeyRequest contains request parameters for GetTSIGKey
  38  	GetTSIGKeyRequest TSIGKeyRequest
  39  
  40  	// GetTSIGKeyResponse contains the response data from GetTSIGKey operation
  41  	GetTSIGKeyResponse struct {
  42  		TSIGKey
  43  		ZoneCount int64 `json:"zonesCount,omitempty"`
  44  	}
  45  
  46  	// DeleteTSIGKeyRequest contains request parameters for DeleteTSIGKey
  47  	DeleteTSIGKeyRequest TSIGKeyRequest
  48  
  49  	// GetTSIGKeyAliasesRequest contains request parameters for GetTSIGKeyAliases
  50  	GetTSIGKeyAliasesRequest TSIGKeyRequest
  51  
  52  	// GetTSIGKeyAliasesResponse contains the response data from GetTSIGKeyAliases operation
  53  	GetTSIGKeyAliasesResponse struct {
  54  		Zones   []string `json:"zones"`
  55  		Aliases []string `json:"aliases"`
  56  	}
  57  
  58  	// TSIGKeyResponse contains TSIG key GET response
  59  	TSIGKeyResponse struct {
  60  		TSIGKey
  61  		ZoneCount int64 `json:"zonesCount,omitempty"`
  62  	}
  63  
  64  	// TSIGKeyBulkPost contains TSIG key and a list of names of zones that should use the key. Used with update function.
  65  	TSIGKeyBulkPost struct {
  66  		Key   *TSIGKey `json:"key"`
  67  		Zones []string `json:"zones"`
  68  	}
  69  
  70  	// TSIGZoneAliases contains list of zone aliases
  71  	TSIGZoneAliases struct {
  72  		Aliases []string `json:"aliases"`
  73  	}
  74  
  75  	// TSIGReportMeta contains metadata for TSIGReport response
  76  	TSIGReportMeta struct {
  77  		TotalElements int64    `json:"totalElements"`
  78  		Search        string   `json:"search,omitempty"`
  79  		Contracts     []string `json:"contracts,omitempty"`
  80  		GID           int64    `json:"gid,omitempty"`
  81  		SortBy        []string `json:"sortBy,omitempty"`
  82  	}
  83  
  84  	// TSIGReportResponse contains response with a list of the TSIG keys used by zones.
  85  	TSIGReportResponse struct {
  86  		Metadata *TSIGReportMeta   `json:"metadata"`
  87  		Keys     []TSIGKeyResponse `json:"keys,omitempty"`
  88  	}
  89  
  90  	// UpdateTSIGKeyRequest contains request parameters for UpdateTSIGKey
  91  	UpdateTSIGKeyRequest struct {
  92  		TsigKey *TSIGKey
  93  		Zone    string
  94  	}
  95  
  96  	// UpdateTSIGKeyBulkRequest contains request parameters for UpdateTSIGKeyBulk
  97  	UpdateTSIGKeyBulkRequest struct {
  98  		TSIGKeyBulk *TSIGKeyBulkPost
  99  	}
 100  
 101  	// GetTSIGKeyZonesRequest contains request parameters for GetTSIGKeyZones
 102  	GetTSIGKeyZonesRequest struct {
 103  		TsigKey *TSIGKey
 104  	}
 105  
 106  	// GetTSIGKeyZonesResponse contains the response data from GetTSIGKeyZones operation
 107  	GetTSIGKeyZonesResponse struct {
 108  		Zones   []string `json:"zones"`
 109  		Aliases []string `json:"aliases"`
 110  	}
 111  
 112  	// ListTSIGKeysRequest contains request parameters for ListTSIGKeys
 113  	ListTSIGKeysRequest struct {
 114  		TsigQuery *TSIGQueryString
 115  	}
 116  
 117  	// ListTSIGKeysResponse contains the response data from ListTSIGKeys operation
 118  	ListTSIGKeysResponse struct {
 119  		Metadata *TSIGReportMeta   `json:"metadata"`
 120  		Keys     []TSIGKeyResponse `json:"keys,omitempty"`
 121  	}
 122  )
 123  
 124  var (
 125  	// ErrGetTSIGKey is returned when GetTSIGKey fails
 126  	ErrGetTSIGKey = errors.New("get tsig key")
 127  	// ErrDeleteTSIGKey is returned when DeleteTSIGKey fails
 128  	ErrDeleteTSIGKey = errors.New("delete tsig key")
 129  	// ErrGetTSIGKeyAliases is returned when GetTSIGKeyAliases fails
 130  	ErrGetTSIGKeyAliases = errors.New("get tsig key aliases")
 131  	// ErrUpdateTSIGKey is returned when UpdateTSIGKey fails
 132  	ErrUpdateTSIGKey = errors.New("updated tsig key")
 133  	// ErrUpdateTSIGKeyBulk is returned when UpdateTSIGKeyBulk fails
 134  	ErrUpdateTSIGKeyBulk = errors.New("update tsig key for multiple zones")
 135  	// ErrGetTSIGKeyZones is returned when GetTSIGKeyZones fails
 136  	ErrGetTSIGKeyZones = errors.New("list zones using tsig key")
 137  	// ErrListTSIGKeys is returned when ListTSIGKeys fails
 138  	ErrListTSIGKeys = errors.New("get a list of the tsig keys")
 139  )
 140  
 141  // Validate validates GetTSIGKeyRequest
 142  func (r GetTSIGKeyRequest) Validate() error {
 143  	return edgegriderr.ParseValidationErrors(validation.Errors{
 144  		"Zone": validation.Validate(r.Zone, validation.Required),
 145  	})
 146  }
 147  
 148  // Validate validates DeleteTSIGKeyRequest
 149  func (r DeleteTSIGKeyRequest) Validate() error {
 150  	return edgegriderr.ParseValidationErrors(validation.Errors{
 151  		"Zone": validation.Validate(r.Zone, validation.Required),
 152  	})
 153  }
 154  
 155  // Validate validates GetTSIGKeyAliasesRequest
 156  func (r GetTSIGKeyAliasesRequest) Validate() error {
 157  	return edgegriderr.ParseValidationErrors(validation.Errors{
 158  		"Zone": validation.Validate(r.Zone, validation.Required),
 159  	})
 160  }
 161  
 162  // Validate validates UpdateTSIGKeyRequest
 163  func (r UpdateTSIGKeyRequest) Validate() error {
 164  	return edgegriderr.ParseValidationErrors(validation.Errors{
 165  		"Zone":    validation.Validate(r.Zone, validation.Required),
 166  		"TsigKey": validation.Validate(r.TsigKey),
 167  	})
 168  }
 169  
 170  // Validate validates UpdateTSIGKeyBulkRequest
 171  func (r UpdateTSIGKeyBulkRequest) Validate() error {
 172  	return edgegriderr.ParseValidationErrors(validation.Errors{
 173  		"TSIGKeyBulk": validation.Validate(r.TSIGKeyBulk, validation.Required),
 174  	})
 175  }
 176  
 177  // Validate validates GetTSIGKeyZonesRequest
 178  func (r GetTSIGKeyZonesRequest) Validate() error {
 179  	return edgegriderr.ParseValidationErrors(validation.Errors{
 180  		"TsigKey": validation.Validate(r.TsigKey, validation.Required),
 181  	})
 182  }
 183  
 184  // Validate validates TSIGKey
 185  func (key *TSIGKey) Validate() error {
 186  	return validation.Errors{
 187  		"Name":      validation.Validate(key.Name, validation.Required),
 188  		"Algorithm": validation.Validate(key.Algorithm, validation.Required),
 189  		"Secret":    validation.Validate(key.Secret, validation.Required),
 190  	}.Filter()
 191  }
 192  
 193  // Validate validates TSIGKeyBulkPost
 194  func (bulk *TSIGKeyBulkPost) Validate() error {
 195  	return validation.Errors{
 196  		"Key":   validation.Validate(bulk.Key, validation.Required),
 197  		"Zones": validation.Validate(bulk.Zones, validation.Required),
 198  	}.Filter()
 199  }
 200  
 201  func constructTSIGQueryString(tsigQueryString *TSIGQueryString) string {
 202  	queryString := ""
 203  	qsElems := reflect.ValueOf(tsigQueryString).Elem()
 204  	for i := 0; i < qsElems.NumField(); i++ {
 205  		varName := qsElems.Type().Field(i).Name
 206  		varValue := qsElems.Field(i).Interface()
 207  		keyVal := fmt.Sprint(varValue)
 208  		switch varName {
 209  		case "ContractIDs":
 210  			contractList := ""
 211  			for j, id := range varValue.([]string) {
 212  				contractList += id
 213  				if j < len(varValue.([]string))-1 {
 214  					contractList += "%2C"
 215  				}
 216  			}
 217  			if len(varValue.([]string)) > 0 {
 218  				queryString += "contractIds=" + contractList
 219  			}
 220  		case "SortBy":
 221  			sortByList := ""
 222  			for j, sb := range varValue.([]string) {
 223  				sortByList += sb
 224  				if j < len(varValue.([]string))-1 {
 225  					sortByList += "%2C"
 226  				}
 227  			}
 228  			if len(varValue.([]string)) > 0 {
 229  				queryString += "sortBy=" + sortByList
 230  			}
 231  		case "Search":
 232  			if keyVal != "" {
 233  				queryString += "search=" + keyVal
 234  			}
 235  		case "GID":
 236  			if varValue.(int64) != 0 {
 237  				queryString += "gid=" + keyVal
 238  			}
 239  		}
 240  		if i < qsElems.NumField()-1 {
 241  			queryString += "&"
 242  		}
 243  	}
 244  	queryString = strings.TrimRight(queryString, "&")
 245  	if len(queryString) > 0 {
 246  		return "?" + queryString
 247  	}
 248  	return ""
 249  }
 250  
 251  func (d *dns) ListTSIGKeys(ctx context.Context, params ListTSIGKeysRequest) (*ListTSIGKeysResponse, error) {
 252  	logger := d.Log(ctx)
 253  	logger.Debug("ListTSIGKeys")
 254  
 255  	getURL := fmt.Sprintf("/config-dns/v2/keys%s", constructTSIGQueryString(params.TsigQuery))
 256  	req, err := http.NewRequestWithContext(ctx, http.MethodGet, getURL, nil)
 257  	if err != nil {
 258  		return nil, fmt.Errorf("failed to create ListTsigKeyss request: %w", err)
 259  	}
 260  
 261  	var result ListTSIGKeysResponse
 262  	resp, err := d.Exec(req, &result)
 263  	if err != nil {
 264  		return nil, fmt.Errorf(" ListTsigKeys request failed: %w", err)
 265  	}
 266  	defer session.CloseResponseBody(resp)
 267  
 268  	if resp.StatusCode != http.StatusOK {
 269  		return nil, d.Error(resp)
 270  	}
 271  
 272  	return &result, nil
 273  }
 274  
 275  func (d *dns) GetTSIGKeyZones(ctx context.Context, params GetTSIGKeyZonesRequest) (*GetTSIGKeyZonesResponse, error) {
 276  	logger := d.Log(ctx)
 277  	logger.Debug("GetTSIGKeyZones")
 278  
 279  	if err := params.Validate(); err != nil {
 280  		return nil, fmt.Errorf("%s: %w: %s", ErrGetTSIGKeyZones, ErrStructValidation, err)
 281  	}
 282  
 283  	reqBody, err := convertStructToReqBody(params.TsigKey)
 284  	if err != nil {
 285  		return nil, fmt.Errorf("failed to generate request body: %w", err)
 286  	}
 287  
 288  	postURL := "/config-dns/v2/keys/used-by"
 289  	req, err := http.NewRequestWithContext(ctx, http.MethodPost, postURL, reqBody)
 290  	if err != nil {
 291  		return nil, fmt.Errorf("failed to create GetTsigKeyZones request: %w", err)
 292  	}
 293  
 294  	var result GetTSIGKeyZonesResponse
 295  	resp, err := d.Exec(req, &result)
 296  	if err != nil {
 297  		return nil, fmt.Errorf("GetTsigKeyZones request failed: %w", err)
 298  	}
 299  	defer session.CloseResponseBody(resp)
 300  
 301  	if resp.StatusCode != http.StatusOK {
 302  		return nil, d.Error(resp)
 303  	}
 304  
 305  	return &result, nil
 306  }
 307  
 308  func (d *dns) GetTSIGKeyAliases(ctx context.Context, params GetTSIGKeyAliasesRequest) (*GetTSIGKeyAliasesResponse, error) {
 309  	logger := d.Log(ctx)
 310  	logger.Debug("GetTSIGKeyAliases")
 311  
 312  	if err := params.Validate(); err != nil {
 313  		return nil, fmt.Errorf("%s: %w: %s", ErrGetTSIGKeyAliases, ErrStructValidation, err)
 314  	}
 315  
 316  	getURL := fmt.Sprintf("/config-dns/v2/zones/%s/key/used-by", params.Zone)
 317  	req, err := http.NewRequestWithContext(ctx, http.MethodGet, getURL, nil)
 318  	if err != nil {
 319  		return nil, fmt.Errorf("failed to create GetTsigKeyAliases request: %w", err)
 320  	}
 321  
 322  	var result GetTSIGKeyAliasesResponse
 323  	resp, err := d.Exec(req, &result)
 324  	if err != nil {
 325  		return nil, fmt.Errorf("GetTsigKeyAliases request failed: %w", err)
 326  	}
 327  	defer session.CloseResponseBody(resp)
 328  
 329  	if resp.StatusCode != http.StatusOK {
 330  		return nil, d.Error(resp)
 331  	}
 332  
 333  	return &result, nil
 334  }
 335  
 336  func (d *dns) UpdateTSIGKeyBulk(ctx context.Context, params UpdateTSIGKeyBulkRequest) error {
 337  	logger := d.Log(ctx)
 338  	logger.Debug("TSIGKeyBulkUpdate")
 339  
 340  	if err := params.Validate(); err != nil {
 341  		return fmt.Errorf("%s: %w: %s", ErrUpdateTSIGKeyBulk, ErrStructValidation, err)
 342  	}
 343  
 344  	reqBody, err := convertStructToReqBody(params.TSIGKeyBulk)
 345  	if err != nil {
 346  		return fmt.Errorf("failed to generate request body: %w", err)
 347  	}
 348  
 349  	postURL := "/config-dns/v2/keys/bulk-update"
 350  	req, err := http.NewRequestWithContext(ctx, http.MethodPost, postURL, reqBody)
 351  	if err != nil {
 352  		return fmt.Errorf("failed to create TsigKeyBulkUpdate request: %w", err)
 353  	}
 354  
 355  	resp, err := d.Exec(req, nil)
 356  	if err != nil {
 357  		return fmt.Errorf("TsigKeyBulkUpdate request failed: %w", err)
 358  	}
 359  	defer session.CloseResponseBody(resp)
 360  
 361  	if resp.StatusCode != http.StatusNoContent {
 362  		return d.Error(resp)
 363  	}
 364  
 365  	return nil
 366  }
 367  
 368  func (d *dns) GetTSIGKey(ctx context.Context, params GetTSIGKeyRequest) (*GetTSIGKeyResponse, error) {
 369  	logger := d.Log(ctx)
 370  	logger.Debug("GetTSIGKey")
 371  
 372  	if err := params.Validate(); err != nil {
 373  		return nil, fmt.Errorf("%s: %w: %s", ErrGetTSIGKey, ErrStructValidation, err)
 374  	}
 375  
 376  	getURL := fmt.Sprintf("/config-dns/v2/zones/%s/key", params.Zone)
 377  	req, err := http.NewRequestWithContext(ctx, http.MethodGet, getURL, nil)
 378  	if err != nil {
 379  		return nil, fmt.Errorf("failed to create GetTsigKey request: %w", err)
 380  	}
 381  
 382  	var result GetTSIGKeyResponse
 383  	resp, err := d.Exec(req, &result)
 384  	if err != nil {
 385  		return nil, fmt.Errorf("GetTsigKey request failed: %w", err)
 386  	}
 387  	defer session.CloseResponseBody(resp)
 388  
 389  	if resp.StatusCode != http.StatusOK {
 390  		return nil, d.Error(resp)
 391  	}
 392  
 393  	return &result, nil
 394  }
 395  
 396  func (d *dns) DeleteTSIGKey(ctx context.Context, params DeleteTSIGKeyRequest) error {
 397  	logger := d.Log(ctx)
 398  	logger.Debug("DeleteTSIGKey")
 399  
 400  	if err := params.Validate(); err != nil {
 401  		return fmt.Errorf("%s: %w: %s", ErrDeleteTSIGKey, ErrStructValidation, err)
 402  	}
 403  
 404  	delURL := fmt.Sprintf("/config-dns/v2/zones/%s/key", params.Zone)
 405  	req, err := http.NewRequestWithContext(ctx, http.MethodDelete, delURL, nil)
 406  	if err != nil {
 407  		return fmt.Errorf("failed to create DeleteTsigKey request: %w", err)
 408  	}
 409  
 410  	resp, err := d.Exec(req, nil)
 411  	if err != nil {
 412  		return fmt.Errorf("DeleteTsigKey request failed: %w", err)
 413  	}
 414  	defer session.CloseResponseBody(resp)
 415  
 416  	if resp.StatusCode != http.StatusNoContent {
 417  		return d.Error(resp)
 418  	}
 419  
 420  	return nil
 421  }
 422  
 423  func (d *dns) UpdateTSIGKey(ctx context.Context, params UpdateTSIGKeyRequest) error {
 424  	logger := d.Log(ctx)
 425  	logger.Debug("UpdateTSIGKey")
 426  
 427  	if err := params.Validate(); err != nil {
 428  		return fmt.Errorf("%s: %w: %s", ErrUpdateTSIGKey, ErrStructValidation, err)
 429  	}
 430  
 431  	reqBody, err := convertStructToReqBody(params.TsigKey)
 432  	if err != nil {
 433  		return fmt.Errorf("failed to generate request body: %w", err)
 434  	}
 435  
 436  	putURL := fmt.Sprintf("/config-dns/v2/zones/%s/key", params.Zone)
 437  	req, err := http.NewRequestWithContext(ctx, http.MethodPut, putURL, reqBody)
 438  	if err != nil {
 439  		return fmt.Errorf("failed to create UpdateTsigKey request: %w", err)
 440  	}
 441  
 442  	resp, err := d.Exec(req, nil)
 443  	if err != nil {
 444  		return fmt.Errorf("UpdateTsigKey request failed: %w", err)
 445  	}
 446  	defer session.CloseResponseBody(resp)
 447  
 448  	if resp.StatusCode != http.StatusNoContent {
 449  		return d.Error(resp)
 450  	}
 451  
 452  	return nil
 453  }
 454