github.com/akamai/AkamaiOPEN-edgegrid-golang/v2@v2.17.0/pkg/configdns/tsig.go (about)

     1  package dns
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"net/http"
     7  
     8  	validation "github.com/go-ozzo/ozzo-validation/v4"
     9  
    10  	"reflect"
    11  	"strings"
    12  	"sync"
    13  )
    14  
    15  var (
    16  	tsigWriteLock sync.Mutex
    17  )
    18  
    19  type (
    20  	// TSIGKeys contains operations available on TSIKeyG resource
    21  	// See: https://developer.akamai.com/api/cloud_security/edge_dns_zone_management/v2.html
    22  	TSIGKeys interface {
    23  		// NewTsigKey returns bare bones tsig key struct
    24  		NewTsigKey(context.Context, string) *TSIGKey
    25  		// NewTsigQueryString returns empty query string struct. No elements required.
    26  		NewTsigQueryString(context.Context) *TSIGQueryString
    27  		// ListTsigKeys lists the TSIG keys used by zones that you are allowed to manage
    28  		// See:
    29  		ListTsigKeys(context.Context, *TSIGQueryString) (*TSIGReportResponse, error)
    30  		// GetTsigKeyZones retrieves DNS Zones using tsig key
    31  		// See: https://developer.akamai.com/api/cloud_security/edge_dns_zone_management/v2.html#gettsigkeys
    32  		GetTsigKeyZones(context.Context, *TSIGKey) (*ZoneNameListResponse, error)
    33  		// GetTsigKeyAliases retrieves a DNS Zone's aliases
    34  		// See: https://developer.akamai.com/api/cloud_security/edge_dns_zone_management/v2.html#posttsigusedby
    35  		GetTsigKeyAliases(context.Context, string) (*ZoneNameListResponse, error)
    36  		// TsigKeyBulkUpdate updates Bulk Zones tsig key
    37  		// See: https://developer.akamai.com/api/cloud_security/edge_dns_zone_management/v2.html#posttsigbulkupdate
    38  		TsigKeyBulkUpdate(context.Context, *TSIGKeyBulkPost) error
    39  		// GetTsigKey retrieves a Tsig key for zone
    40  		// See:  https://developer.akamai.com/api/cloud_security/edge_dns_zone_management/v2.html#getzonekey
    41  		GetTsigKey(context.Context, string) (*TSIGKeyResponse, error)
    42  		// DeleteTsigKey deletes tsig key for zone
    43  		// See: https://developer.akamai.com/api/cloud_security/edge_dns_zone_management/v2.html#deletezonekey
    44  		DeleteTsigKey(context.Context, string) error
    45  		// UpdateTsigKey updates tsig key for zone
    46  		// See: https://developer.akamai.com/api/cloud_security/edge_dns_zone_management/v2.html#putzonekey
    47  		UpdateTsigKey(context.Context, *TSIGKey, string) error
    48  	}
    49  
    50  	// TSIGQueryString contains TSIG query parameters
    51  	TSIGQueryString struct {
    52  		ContractIds []string `json:"contractIds,omitempty"`
    53  		Search      string   `json:"search,omitempty"`
    54  		SortBy      []string `json:"sortBy,omitempty"`
    55  		Gid         int64    `json:"gid,omitempty"`
    56  	}
    57  
    58  	// TSIGKey contains TSIG key POST response
    59  	TSIGKey struct {
    60  		Name      string `json:"name"`
    61  		Algorithm string `json:"algorithm,omitempty"`
    62  		Secret    string `json:"secret,omitempty"`
    63  	}
    64  	// TSIGKeyResponse contains TSIG key GET response
    65  	TSIGKeyResponse struct {
    66  		TSIGKey
    67  		ZoneCount int64 `json:"zonesCount,omitempty"`
    68  	}
    69  
    70  	// TSIGKeyBulkPost contains TSIG key and a list of names of zones that should use the key. Used with update function.
    71  	TSIGKeyBulkPost struct {
    72  		Key   *TSIGKey `json:"key"`
    73  		Zones []string `json:"zones"`
    74  	}
    75  
    76  	// TSIGZoneAliases contains list of zone aliases
    77  	TSIGZoneAliases struct {
    78  		Aliases []string `json:"aliases"`
    79  	}
    80  
    81  	// TSIGReportMeta contains metadata for TSIGReport response
    82  	TSIGReportMeta struct {
    83  		TotalElements int64    `json:"totalElements"`
    84  		Search        string   `json:"search,omitempty"`
    85  		Contracts     []string `json:"contracts,omitempty"`
    86  		Gid           int64    `json:"gid,omitempty"`
    87  		SortBy        []string `json:"sortBy,omitempty"`
    88  	}
    89  
    90  	// TSIGReportResponse contains response with a list of the TSIG keys used by zones.
    91  	TSIGReportResponse struct {
    92  		Metadata *TSIGReportMeta    `json:"metadata"`
    93  		Keys     []*TSIGKeyResponse `json:"keys,omitempty"`
    94  	}
    95  )
    96  
    97  // Validate validates RecordBody
    98  func (key *TSIGKey) Validate() error {
    99  
   100  	return validation.Errors{
   101  		"Name":      validation.Validate(key.Name, validation.Required),
   102  		"Algorithm": validation.Validate(key.Algorithm, validation.Required),
   103  		"Secret":    validation.Validate(key.Secret, validation.Required),
   104  	}.Filter()
   105  }
   106  
   107  // Validate validates TSIGKeyBulkPost
   108  func (bulk *TSIGKeyBulkPost) Validate() error {
   109  	return validation.Errors{
   110  		"Key":   validation.Validate(bulk.Key, validation.Required),
   111  		"Zones": validation.Validate(bulk.Zones, validation.Required),
   112  	}.Filter()
   113  }
   114  
   115  // NewTsigKey returns bare bones tsig key struct
   116  func (p *dns) NewTsigKey(ctx context.Context, name string) *TSIGKey {
   117  
   118  	logger := p.Log(ctx)
   119  	logger.Debug("NewTsigKey")
   120  
   121  	key := &TSIGKey{Name: name}
   122  	return key
   123  }
   124  
   125  // NewTsigQueryString returns empty query string struct. No elements required.
   126  func (p *dns) NewTsigQueryString(ctx context.Context) *TSIGQueryString {
   127  
   128  	logger := p.Log(ctx)
   129  	logger.Debug("NewTsigQueryString")
   130  
   131  	tsigquerystring := &TSIGQueryString{}
   132  	return tsigquerystring
   133  }
   134  
   135  func constructTsigQueryString(tsigquerystring *TSIGQueryString) string {
   136  
   137  	queryString := ""
   138  	qsElems := reflect.ValueOf(tsigquerystring).Elem()
   139  	for i := 0; i < qsElems.NumField(); i++ {
   140  		varName := qsElems.Type().Field(i).Name
   141  		varValue := qsElems.Field(i).Interface()
   142  		keyVal := fmt.Sprint(varValue)
   143  		switch varName {
   144  		case "ContractIds":
   145  			contractList := ""
   146  			for j, id := range varValue.([]string) {
   147  				contractList += id
   148  				if j < len(varValue.([]string))-1 {
   149  					contractList += "%2C"
   150  				}
   151  			}
   152  			if len(varValue.([]string)) > 0 {
   153  				queryString += "contractIds=" + contractList
   154  			}
   155  		case "SortBy":
   156  			sortByList := ""
   157  			for j, sb := range varValue.([]string) {
   158  				sortByList += sb
   159  				if j < len(varValue.([]string))-1 {
   160  					sortByList += "%2C"
   161  				}
   162  			}
   163  			if len(varValue.([]string)) > 0 {
   164  				queryString += "sortBy=" + sortByList
   165  			}
   166  		case "Search":
   167  			if keyVal != "" {
   168  				queryString += "search=" + keyVal
   169  			}
   170  		case "Gid":
   171  			if varValue.(int64) != 0 {
   172  				queryString += "gid=" + keyVal
   173  			}
   174  		}
   175  		if i < qsElems.NumField()-1 {
   176  			queryString += "&"
   177  		}
   178  	}
   179  	queryString = strings.TrimRight(queryString, "&")
   180  	if len(queryString) > 0 {
   181  		return "?" + queryString
   182  	}
   183  	return ""
   184  }
   185  
   186  // List TSIG Keys
   187  func (p *dns) ListTsigKeys(ctx context.Context, tsigquerystring *TSIGQueryString) (*TSIGReportResponse, error) {
   188  
   189  	logger := p.Log(ctx)
   190  	logger.Debug("ListTsigKeys")
   191  
   192  	var tsigList TSIGReportResponse
   193  	getURL := fmt.Sprintf("/config-dns/v2/keys%s", constructTsigQueryString(tsigquerystring))
   194  	req, err := http.NewRequestWithContext(ctx, http.MethodGet, getURL, nil)
   195  	if err != nil {
   196  		return nil, fmt.Errorf("failed to create ListTsigKeyss request: %w", err)
   197  	}
   198  
   199  	resp, err := p.Exec(req, &tsigList)
   200  	if err != nil {
   201  		return nil, fmt.Errorf(" ListTsigKeys request failed: %w", err)
   202  	}
   203  
   204  	if resp.StatusCode != http.StatusOK {
   205  		return nil, p.Error(resp)
   206  	}
   207  
   208  	return &tsigList, nil
   209  
   210  }
   211  
   212  // GetTsigKeyZones retrieves DNS Zones using tsig key
   213  func (p *dns) GetTsigKeyZones(ctx context.Context, tsigKey *TSIGKey) (*ZoneNameListResponse, error) {
   214  
   215  	logger := p.Log(ctx)
   216  	logger.Debug("GetTsigKeyZones")
   217  
   218  	if err := tsigKey.Validate(); err != nil {
   219  		return nil, err
   220  	}
   221  
   222  	reqbody, err := convertStructToReqBody(tsigKey)
   223  	if err != nil {
   224  		return nil, fmt.Errorf("failed to generate request body: %w", err)
   225  	}
   226  
   227  	var zonesList ZoneNameListResponse
   228  	postURL := "/config-dns/v2/keys/used-by"
   229  	req, err := http.NewRequestWithContext(ctx, http.MethodPost, postURL, reqbody)
   230  	if err != nil {
   231  		return nil, fmt.Errorf("failed to create GetTsigKeyZones request: %w", err)
   232  	}
   233  
   234  	resp, err := p.Exec(req, &zonesList)
   235  	if err != nil {
   236  		return nil, fmt.Errorf("GetTsigKeyZones request failed: %w", err)
   237  	}
   238  
   239  	if resp.StatusCode != http.StatusOK {
   240  		return nil, p.Error(resp)
   241  	}
   242  
   243  	return &zonesList, nil
   244  }
   245  
   246  // GetTsigKeyAliases retrieves a DNS Zone's aliases
   247  //func GetZoneKeyAliases(zone string) (*TSIGZoneAliases, error) {
   248  //
   249  // There is a discrepency between the technical doc and API operation. API currently returns a zone name list.
   250  // TODO: Reconcile
   251  //
   252  func (p *dns) GetTsigKeyAliases(ctx context.Context, zone string) (*ZoneNameListResponse, error) {
   253  
   254  	logger := p.Log(ctx)
   255  	logger.Debug("GetTsigKeyAliases")
   256  
   257  	var zonesList ZoneNameListResponse
   258  	getURL := fmt.Sprintf("/config-dns/v2/zones/%s/key/used-by", zone)
   259  	req, err := http.NewRequestWithContext(ctx, http.MethodGet, getURL, nil)
   260  	if err != nil {
   261  		return nil, fmt.Errorf("failed to create GetTsigKeyAliases request: %w", err)
   262  	}
   263  
   264  	resp, err := p.Exec(req, &zonesList)
   265  	if err != nil {
   266  		return nil, fmt.Errorf("GetTsigKeyAliases request failed: %w", err)
   267  	}
   268  
   269  	if resp.StatusCode != http.StatusOK {
   270  		return nil, p.Error(resp)
   271  	}
   272  
   273  	return &zonesList, nil
   274  }
   275  
   276  //  TsigKeyBulkUpdate bulk tsig key update
   277  func (p *dns) TsigKeyBulkUpdate(ctx context.Context, tsigBulk *TSIGKeyBulkPost) error {
   278  
   279  	logger := p.Log(ctx)
   280  	logger.Debug("TsigKeyBulkUpdate")
   281  
   282  	if err := tsigBulk.Validate(); err != nil {
   283  		return err
   284  	}
   285  
   286  	reqbody, err := convertStructToReqBody(tsigBulk)
   287  	if err != nil {
   288  		return fmt.Errorf("failed to generate request body: %w", err)
   289  	}
   290  
   291  	postURL := "/config-dns/v2/keys/bulk-update"
   292  	req, err := http.NewRequestWithContext(ctx, http.MethodPost, postURL, reqbody)
   293  	if err != nil {
   294  		return fmt.Errorf("failed to create TsigKeyBulkUpdate request: %w", err)
   295  	}
   296  
   297  	resp, err := p.Exec(req, nil)
   298  	if err != nil {
   299  		return fmt.Errorf("TsigKeyBulkUpdate request failed: %w", err)
   300  	}
   301  
   302  	if resp.StatusCode != http.StatusNoContent {
   303  		return p.Error(resp)
   304  	}
   305  
   306  	return nil
   307  }
   308  
   309  // GetTsigKey retrieves a DNS Zone's key
   310  func (p *dns) GetTsigKey(ctx context.Context, zone string) (*TSIGKeyResponse, error) {
   311  
   312  	logger := p.Log(ctx)
   313  	logger.Debug("GetTsigKey")
   314  
   315  	var zonekey TSIGKeyResponse
   316  	getURL := fmt.Sprintf("/config-dns/v2/zones/%s/key", zone)
   317  	req, err := http.NewRequestWithContext(ctx, http.MethodGet, getURL, nil)
   318  	if err != nil {
   319  		return nil, fmt.Errorf("failed to create GetTsigKey request: %w", err)
   320  	}
   321  
   322  	resp, err := p.Exec(req, &zonekey)
   323  	if err != nil {
   324  		return nil, fmt.Errorf("GetTsigKey request failed: %w", err)
   325  	}
   326  
   327  	if resp.StatusCode != http.StatusOK {
   328  		return nil, p.Error(resp)
   329  	}
   330  
   331  	return &zonekey, nil
   332  }
   333  
   334  // DeleteTsigKey delete tsig key for zone
   335  func (p *dns) DeleteTsigKey(ctx context.Context, zone string) error {
   336  
   337  	logger := p.Log(ctx)
   338  	logger.Debug("DeleteTsigKey")
   339  
   340  	delURL := fmt.Sprintf("/config-dns/v2/zones/%s/key", zone)
   341  	req, err := http.NewRequestWithContext(ctx, http.MethodDelete, delURL, nil)
   342  	if err != nil {
   343  		return fmt.Errorf("failed to create DeleteTsigKey request: %w", err)
   344  	}
   345  
   346  	resp, err := p.Exec(req, nil)
   347  	if err != nil {
   348  		return fmt.Errorf("DeleteTsigKey request failed: %w", err)
   349  	}
   350  
   351  	if resp.StatusCode != http.StatusNoContent {
   352  		return p.Error(resp)
   353  	}
   354  
   355  	return nil
   356  }
   357  
   358  // UpdateTsigKey update tsig key for zone
   359  func (p *dns) UpdateTsigKey(ctx context.Context, tsigKey *TSIGKey, zone string) error {
   360  
   361  	logger := p.Log(ctx)
   362  	logger.Debug("UpdateTsigKey")
   363  
   364  	if err := tsigKey.Validate(); err != nil {
   365  		return err
   366  	}
   367  
   368  	reqbody, err := convertStructToReqBody(tsigKey)
   369  	if err != nil {
   370  		return fmt.Errorf("failed to generate request body: %w", err)
   371  	}
   372  
   373  	putURL := fmt.Sprintf("/config-dns/v2/zones/%s/key", zone)
   374  	req, err := http.NewRequestWithContext(ctx, http.MethodPut, putURL, reqbody)
   375  	if err != nil {
   376  		return fmt.Errorf("failed to create UpdateTsigKey request: %w", err)
   377  	}
   378  
   379  	resp, err := p.Exec(req, nil)
   380  	if err != nil {
   381  		return fmt.Errorf("UpdateTsigKey request failed: %w", err)
   382  	}
   383  
   384  	if resp.StatusCode != http.StatusNoContent {
   385  		return p.Error(resp)
   386  	}
   387  
   388  	return nil
   389  }