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 }