github.com/akamai/AkamaiOPEN-edgegrid-golang/v8@v8.1.0/pkg/cps/enrollments.go (about) 1 package cps 2 3 import ( 4 "context" 5 "errors" 6 "fmt" 7 "net/http" 8 "net/url" 9 "strconv" 10 11 validation "github.com/go-ozzo/ozzo-validation/v4" 12 ) 13 14 type ( 15 // Enrollments is a CPS enrollments API interface 16 Enrollments interface { 17 // ListEnrollments fetches all enrollments with given contractId 18 // 19 // See https://techdocs.akamai.com/cps/reference/get-enrollments 20 ListEnrollments(context.Context, ListEnrollmentsRequest) (*ListEnrollmentsResponse, error) 21 22 // GetEnrollment fetches enrollment object with given ID 23 // 24 // See: https://techdocs.akamai.com/cps/reference/get-enrollment 25 GetEnrollment(context.Context, GetEnrollmentRequest) (*GetEnrollmentResponse, error) 26 27 // CreateEnrollment creates a new enrollment 28 // 29 // See: https://techdocs.akamai.com/cps/reference/post-enrollment 30 CreateEnrollment(context.Context, CreateEnrollmentRequest) (*CreateEnrollmentResponse, error) 31 32 // UpdateEnrollment updates a single enrollment entry with given ID 33 // 34 // See: https://techdocs.akamai.com/cps/reference/put-enrollment 35 UpdateEnrollment(context.Context, UpdateEnrollmentRequest) (*UpdateEnrollmentResponse, error) 36 37 // RemoveEnrollment removes an enrollment with given ID 38 // 39 // See: https://techdocs.akamai.com/cps/reference/delete-enrollment 40 RemoveEnrollment(context.Context, RemoveEnrollmentRequest) (*RemoveEnrollmentResponse, error) 41 } 42 43 // ListEnrollmentsResponse represents list of CPS enrollment objects under given contractId. It is used as a response body while fetching enrollments by contractId 44 ListEnrollmentsResponse struct { 45 Enrollments []Enrollment `json:"enrollments"` 46 } 47 48 // GetEnrollmentResponse contains response body from GetEnrollment operation 49 GetEnrollmentResponse Enrollment 50 51 // Enrollment represents a CPS enrollment object. It is used as a response body while fetching enrollment by ID and listing multiple enrollments 52 Enrollment struct { 53 ID int `json:"id"` 54 ProductionSlots []int `json:"productionSlots"` 55 StagingSlots []int `json:"stagingSlots"` 56 AssignedSlots []int `json:"assignedSlots"` 57 AdminContact *Contact `json:"adminContact"` 58 AutoRenewalStartTime string `json:"autoRenewalStartTime,omitempty"` 59 CertificateChainType string `json:"certificateChainType,omitempty"` 60 CertificateType string `json:"certificateType"` 61 ChangeManagement bool `json:"changeManagement"` 62 CSR *CSR `json:"csr"` 63 EnableMultiStackedCertificates bool `json:"enableMultiStackedCertificates"` 64 Location string `json:"location,omitempty"` 65 MaxAllowedSanNames int `json:"maxAllowedSanNames,omitempty"` 66 MaxAllowedWildcardSanNames int `json:"maxAllowedWildcardSanNames,omitempty"` 67 NetworkConfiguration *NetworkConfiguration `json:"networkConfiguration"` 68 Org *Org `json:"org"` 69 OrgID *int `json:"orgId"` 70 PendingChanges []PendingChange `json:"pendingChanges,omitempty"` 71 RA string `json:"ra"` 72 SignatureAlgorithm string `json:"signatureAlgorithm,omitempty"` 73 TechContact *Contact `json:"techContact"` 74 ThirdParty *ThirdParty `json:"thirdParty,omitempty"` 75 ValidationType string `json:"validationType"` 76 } 77 78 // Contact contains contact information 79 Contact struct { 80 AddressLineOne string `json:"addressLineOne,omitempty"` 81 AddressLineTwo string `json:"addressLineTwo,omitempty"` 82 City string `json:"city,omitempty"` 83 Country string `json:"country,omitempty"` 84 Email string `json:"email,omitempty"` 85 FirstName string `json:"firstName,omitempty"` 86 LastName string `json:"lastName,omitempty"` 87 OrganizationName string `json:"organizationName,omitempty"` 88 Phone string `json:"phone,omitempty"` 89 PostalCode string `json:"postalCode,omitempty"` 90 Region string `json:"region,omitempty"` 91 Title string `json:"title,omitempty"` 92 } 93 94 // CSR is a Certificate Signing Request object 95 CSR struct { 96 C string `json:"c,omitempty"` 97 CN string `json:"cn"` 98 L string `json:"l,omitempty"` 99 O string `json:"o,omitempty"` 100 OU string `json:"ou,omitempty"` 101 PreferredTrustChain string `json:"preferredTrustChain,omitempty"` 102 SANS []string `json:"sans,omitempty"` 103 ST string `json:"st,omitempty"` 104 } 105 106 // NetworkConfiguration contains settings that specify any network information and TLS Metadata you want CPS to use to push the completed certificate to the network 107 NetworkConfiguration struct { 108 ClientMutualAuthentication *ClientMutualAuthentication `json:"clientMutualAuthentication,omitempty"` 109 DisallowedTLSVersions []string `json:"disallowedTlsVersions,omitempty"` 110 DNSNameSettings *DNSNameSettings `json:"dnsNameSettings,omitempty"` 111 Geography string `json:"geography,omitempty"` 112 MustHaveCiphers string `json:"mustHaveCiphers,omitempty"` 113 OCSPStapling OCSPStapling `json:"ocspStapling,omitempty"` 114 PreferredCiphers string `json:"preferredCiphers,omitempty"` 115 QuicEnabled bool `json:"quicEnabled"` 116 SecureNetwork string `json:"secureNetwork,omitempty"` 117 SNIOnly bool `json:"sniOnly"` 118 } 119 120 // ClientMutualAuthentication specifies the trust chain that is used to verify client certificates and some configuration options 121 ClientMutualAuthentication struct { 122 AuthenticationOptions *AuthenticationOptions `json:"authenticationOptions,omitempty"` 123 SetID string `json:"setId,omitempty"` 124 } 125 126 // AuthenticationOptions contain the configuration options for the selected trust chain 127 AuthenticationOptions struct { 128 OCSP *OCSP `json:"ocsp,omitempty"` 129 SendCAListToClient *bool `json:"sendCaListToClient,omitempty"` 130 } 131 132 // OCSP specifies whether you want to enable ocsp stapling for client certificates 133 OCSP struct { 134 Enabled *bool `json:"enabled,omitempty"` 135 } 136 137 // DNSNameSettings contain DNS name setting in given network configuration 138 DNSNameSettings struct { 139 CloneDNSNames bool `json:"cloneDnsNames"` 140 DNSNames []string `json:"dnsNames,omitempty"` 141 } 142 143 // Org represents organization information 144 Org struct { 145 AddressLineOne string `json:"addressLineOne,omitempty"` 146 AddressLineTwo string `json:"addressLineTwo,omitempty"` 147 City string `json:"city,omitempty"` 148 Country string `json:"country,omitempty"` 149 Name string `json:"name,omitempty"` 150 Phone string `json:"phone,omitempty"` 151 PostalCode string `json:"postalCode,omitempty"` 152 Region string `json:"region,omitempty"` 153 } 154 155 // PendingChange represents pending change information 156 PendingChange struct { 157 ChangeType string `json:"changeType,omitempty"` 158 Location string `json:"location,omitempty"` 159 } 160 161 // ThirdParty specifies that you want to use a third party certificate 162 ThirdParty struct { 163 ExcludeSANS bool `json:"excludeSans"` 164 } 165 166 // ListEnrollmentsRequest contains Contract ID of enrollments that are to be fetched with ListEnrollments 167 ListEnrollmentsRequest struct { 168 ContractID string 169 } 170 171 // GetEnrollmentRequest contains ID of an enrollment that is to be fetched with GetEnrollment 172 GetEnrollmentRequest struct { 173 EnrollmentID int 174 } 175 176 // CreateEnrollmentRequest contains request body and path parameters used to create an enrollment 177 CreateEnrollmentRequest struct { 178 EnrollmentRequestBody 179 ContractID string 180 DeployNotAfter string 181 DeployNotBefore string 182 AllowDuplicateCN bool 183 } 184 185 // EnrollmentRequestBody represents request body parameters specific to the enrollment 186 EnrollmentRequestBody struct { 187 AdminContact *Contact `json:"adminContact"` 188 AutoRenewalStartTime string `json:"autoRenewalStartTime,omitempty"` 189 CertificateChainType string `json:"certificateChainType,omitempty"` 190 CertificateType string `json:"certificateType"` 191 ChangeManagement bool `json:"changeManagement"` 192 CSR *CSR `json:"csr"` 193 EnableMultiStackedCertificates bool `json:"enableMultiStackedCertificates"` 194 NetworkConfiguration *NetworkConfiguration `json:"networkConfiguration"` 195 Org *Org `json:"org"` 196 OrgID *int `json:"orgId,omitempty"` 197 RA string `json:"ra"` 198 SignatureAlgorithm string `json:"signatureAlgorithm,omitempty"` 199 TechContact *Contact `json:"techContact"` 200 ThirdParty *ThirdParty `json:"thirdParty,omitempty"` 201 ValidationType string `json:"validationType"` 202 } 203 204 // CreateEnrollmentResponse contains response body returned after successful enrollment creation 205 CreateEnrollmentResponse struct { 206 ID int 207 Enrollment string `json:"enrollment"` 208 Changes []string `json:"changes"` 209 } 210 211 // UpdateEnrollmentRequest contains request body and path parameters used to update an enrollment 212 UpdateEnrollmentRequest struct { 213 EnrollmentRequestBody 214 EnrollmentID int 215 AllowCancelPendingChanges *bool 216 AllowStagingBypass *bool 217 DeployNotAfter string 218 DeployNotBefore string 219 ForceRenewal *bool 220 RenewalDateCheckOverride *bool 221 } 222 223 // UpdateEnrollmentResponse contains response body returned after successful enrollment update 224 UpdateEnrollmentResponse struct { 225 ID int 226 Enrollment string `json:"enrollment"` 227 Changes []string `json:"changes"` 228 } 229 230 // RemoveEnrollmentRequest contains parameters necessary to send a RemoveEnrollment request 231 RemoveEnrollmentRequest struct { 232 EnrollmentID int 233 AllowCancelPendingChanges *bool 234 DeployNotAfter string 235 DeployNotBefore string 236 } 237 238 // RemoveEnrollmentResponse contains response body returned after successful enrollment deletion 239 RemoveEnrollmentResponse struct { 240 Enrollment string `json:"enrollment"` 241 Changes []string `json:"changes"` 242 } 243 244 // OCSPStapling is used to enable OCSP stapling for an enrollment 245 OCSPStapling string 246 ) 247 248 const ( 249 // OCSPStaplingOn parameter value 250 OCSPStaplingOn OCSPStapling = "on" 251 // OCSPStaplingOff parameter value 252 OCSPStaplingOff OCSPStapling = "off" 253 // OCSPStaplingNotSet parameter value 254 OCSPStaplingNotSet OCSPStapling = "not-set" 255 ) 256 257 // Validate performs validation on EnrollmentRequestBody 258 func (e EnrollmentRequestBody) Validate() error { 259 errs := validation.Errors{ 260 "adminContact": validation.Validate(e.AdminContact, validation.Required), 261 "certificateType": validation.Validate(e.CertificateType, validation.Required), 262 "csr": validation.Validate(e.CSR, validation.Required), 263 "networkConfiguration": validation.Validate(e.NetworkConfiguration, validation.Required), 264 "org": validation.Validate(e.Org, validation.Required), 265 "ra": validation.Validate(e.RA, validation.Required), 266 "techContact": validation.Validate(e.TechContact, validation.Required), 267 "validationType": validation.Validate(e.ValidationType, validation.Required), 268 "thirdParty": validation.Validate(e.ThirdParty), 269 } 270 271 if e.CSR != nil { 272 errs["csr.preferredTrustChain"] = validation.Validate(e.CSR.PreferredTrustChain, 273 validation.When(e.ValidationType != "dv", validation.Empty.Error("must be blank when 'validationType' is not 'dv'"))) 274 } 275 276 return errs.Filter() 277 } 278 279 // Validate performs validation on CSR 280 func (c CSR) Validate() error { 281 return validation.Errors{ 282 "cn": validation.Validate(c.CN, validation.Required), 283 }.Filter() 284 } 285 286 // Validate performs validation on NetworkConfiguration 287 func (n NetworkConfiguration) Validate() error { 288 return validation.Errors{ 289 "ocspStapling": validation.Validate(n.OCSPStapling, validation.In(OCSPStaplingOn, OCSPStaplingOff, OCSPStaplingNotSet)), 290 }.Filter() 291 } 292 293 // Validate performs validation on ListEnrollmentsRequest 294 func (e ListEnrollmentsRequest) Validate() error { 295 return validation.Errors{ 296 "contractId": validation.Validate(e.ContractID, validation.Required), 297 }.Filter() 298 } 299 300 // Validate performs validation on GetEnrollmentRequest 301 func (e GetEnrollmentRequest) Validate() error { 302 return validation.Errors{ 303 "enrollmentId": validation.Validate(e.EnrollmentID, validation.Required), 304 }.Filter() 305 } 306 307 // Validate performs validation on CreateEnrollmentRequest 308 func (e CreateEnrollmentRequest) Validate() error { 309 return validation.Errors{ 310 "enrollment": validation.Validate(e.EnrollmentRequestBody, validation.Required), 311 "contractId": validation.Validate(e.ContractID, validation.Required), 312 }.Filter() 313 } 314 315 // Validate performs validation on UpdateEnrollmentRequest 316 func (e UpdateEnrollmentRequest) Validate() error { 317 return validation.Errors{ 318 "enrollment": validation.Validate(e.EnrollmentRequestBody, validation.Required), 319 "enrollmentId": validation.Validate(e.EnrollmentID, validation.Required), 320 }.Filter() 321 } 322 323 // Validate performs validation on RemoveEnrollmentRequest 324 func (e RemoveEnrollmentRequest) Validate() error { 325 return validation.Errors{ 326 "enrollmentId": validation.Validate(e.EnrollmentID, validation.Required), 327 }.Filter() 328 } 329 330 var ( 331 // ErrListEnrollments is returned when ListEnrollments fails 332 ErrListEnrollments = errors.New("fetching enrollments") 333 // ErrGetEnrollment is returned when GetEnrollment fails 334 ErrGetEnrollment = errors.New("fetching enrollment") 335 // ErrCreateEnrollment is returned when CreateEnrollment fails 336 ErrCreateEnrollment = errors.New("create enrollment") 337 // ErrUpdateEnrollment is returned when UpdateEnrollment fails 338 ErrUpdateEnrollment = errors.New("update enrollment") 339 // ErrRemoveEnrollment is returned when RemoveEnrollment fails 340 ErrRemoveEnrollment = errors.New("remove enrollment") 341 ) 342 343 func (c *cps) ListEnrollments(ctx context.Context, params ListEnrollmentsRequest) (*ListEnrollmentsResponse, error) { 344 if err := params.Validate(); err != nil { 345 return nil, fmt.Errorf("%s: %w: %s", ErrListEnrollments, ErrStructValidation, err) 346 } 347 348 logger := c.Log(ctx) 349 logger.Debug("ListEnrollments") 350 351 uri := fmt.Sprintf("/cps/v2/enrollments?contractId=%s", params.ContractID) 352 353 req, err := http.NewRequestWithContext(ctx, http.MethodGet, uri, nil) 354 if err != nil { 355 return nil, fmt.Errorf("%w: failed to create request: %s", ErrListEnrollments, err) 356 } 357 req.Header.Set("Accept", "application/vnd.akamai.cps.enrollments.v11+json") 358 359 var result ListEnrollmentsResponse 360 361 resp, err := c.Exec(req, &result) 362 if err != nil { 363 return nil, fmt.Errorf("%w: request failed: %s", ErrListEnrollments, err) 364 } 365 366 if resp.StatusCode != http.StatusOK { 367 return nil, fmt.Errorf("%s: %w", ErrListEnrollments, c.Error(resp)) 368 } 369 370 return &result, nil 371 } 372 373 func (c *cps) GetEnrollment(ctx context.Context, params GetEnrollmentRequest) (*GetEnrollmentResponse, error) { 374 if err := params.Validate(); err != nil { 375 return nil, fmt.Errorf("%s: %w: %s", ErrGetEnrollment, ErrStructValidation, err) 376 } 377 378 logger := c.Log(ctx) 379 logger.Debug("GetEnrollment") 380 381 uri := fmt.Sprintf("/cps/v2/enrollments/%d", params.EnrollmentID) 382 383 req, err := http.NewRequestWithContext(ctx, http.MethodGet, uri, nil) 384 if err != nil { 385 return nil, fmt.Errorf("%w: failed to create request: %s", ErrGetEnrollment, err) 386 } 387 req.Header.Set("Accept", "application/vnd.akamai.cps.enrollment.v11+json") 388 389 var result GetEnrollmentResponse 390 391 resp, err := c.Exec(req, &result) 392 if err != nil { 393 return nil, fmt.Errorf("%w: request failed: %s", ErrGetEnrollment, err) 394 } 395 396 if resp.StatusCode != http.StatusOK { 397 return nil, fmt.Errorf("%s: %w", ErrGetEnrollment, c.Error(resp)) 398 } 399 400 return &result, nil 401 } 402 403 func (c *cps) CreateEnrollment(ctx context.Context, params CreateEnrollmentRequest) (*CreateEnrollmentResponse, error) { 404 if err := params.Validate(); err != nil { 405 return nil, fmt.Errorf("%s: %w: %s", ErrCreateEnrollment, ErrStructValidation, err) 406 } 407 408 logger := c.Log(ctx) 409 logger.Debug("CreateEnrollment") 410 411 uri, err := url.Parse(fmt.Sprintf("/cps/v2/enrollments?contractId=%s", params.ContractID)) 412 if err != nil { 413 return nil, fmt.Errorf("%w: parsing URL: %s", ErrCreateEnrollment, err) 414 } 415 query := uri.Query() 416 if params.DeployNotAfter != "" { 417 query.Add("deploy-not-after", params.DeployNotAfter) 418 } 419 if params.DeployNotBefore != "" { 420 query.Add("deploy-not-before", params.DeployNotBefore) 421 } 422 if params.AllowDuplicateCN { 423 query.Add("allow-duplicate-cn", "true") 424 } 425 uri.RawQuery = query.Encode() 426 req, err := http.NewRequestWithContext(ctx, http.MethodPost, uri.String(), nil) 427 if err != nil { 428 return nil, fmt.Errorf("%w: failed to create request: %s", ErrCreateEnrollment, err) 429 } 430 req.Header.Set("Accept", "application/vnd.akamai.cps.enrollment-status.v1+json") 431 req.Header.Set("Content-Type", "application/vnd.akamai.cps.enrollment.v11+json; charset=utf-8") 432 433 var result CreateEnrollmentResponse 434 435 resp, err := c.Exec(req, &result, params.EnrollmentRequestBody) 436 if err != nil { 437 return nil, fmt.Errorf("%w: request failed: %s", ErrCreateEnrollment, err) 438 } 439 440 if resp.StatusCode != http.StatusAccepted { 441 return nil, fmt.Errorf("%s: %w", ErrCreateEnrollment, c.Error(resp)) 442 } 443 id, err := GetIDFromLocation(result.Enrollment) 444 if err != nil { 445 return nil, fmt.Errorf("%s: %w: %s", ErrCreateEnrollment, ErrInvalidLocation, err) 446 } 447 result.ID = id 448 449 return &result, nil 450 } 451 452 func (c *cps) UpdateEnrollment(ctx context.Context, params UpdateEnrollmentRequest) (*UpdateEnrollmentResponse, error) { 453 if err := params.Validate(); err != nil { 454 return nil, fmt.Errorf("%s: %w: %s", ErrCreateEnrollment, ErrStructValidation, err) 455 } 456 457 logger := c.Log(ctx) 458 logger.Debug("UpdateEnrollment") 459 460 uri, err := url.Parse(fmt.Sprintf("/cps/v2/enrollments/%d", params.EnrollmentID)) 461 if err != nil { 462 return nil, fmt.Errorf("%w: parsing URL: %s", ErrUpdateEnrollment, err) 463 } 464 query := uri.Query() 465 if params.AllowCancelPendingChanges != nil { 466 query.Add("allow-cancel-pending-changes", strconv.FormatBool(*params.AllowCancelPendingChanges)) 467 } 468 if params.AllowStagingBypass != nil { 469 query.Add("allow-staging-bypass", strconv.FormatBool(*params.AllowStagingBypass)) 470 } 471 if params.DeployNotAfter != "" { 472 query.Add("deploy-not-after", params.DeployNotAfter) 473 } 474 if params.DeployNotBefore != "" { 475 query.Add("deploy-not-before", params.DeployNotBefore) 476 } 477 if params.ForceRenewal != nil { 478 query.Add("force-renewal", strconv.FormatBool(*params.ForceRenewal)) 479 } 480 if params.RenewalDateCheckOverride != nil { 481 query.Add("renewal-date-check-override", strconv.FormatBool(*params.RenewalDateCheckOverride)) 482 } 483 484 uri.RawQuery = query.Encode() 485 req, err := http.NewRequestWithContext(ctx, http.MethodPut, uri.String(), nil) 486 if err != nil { 487 return nil, fmt.Errorf("%w: failed to create request: %s", ErrUpdateEnrollment, err) 488 } 489 req.Header.Set("Accept", "application/vnd.akamai.cps.enrollment-status.v1+json") 490 req.Header.Set("Content-Type", "application/vnd.akamai.cps.enrollment.v11+json; charset=utf-8") 491 492 var result UpdateEnrollmentResponse 493 494 resp, err := c.Exec(req, &result, params.EnrollmentRequestBody) 495 if err != nil { 496 return nil, fmt.Errorf("%w: request failed: %s", ErrUpdateEnrollment, err) 497 } 498 499 if resp.StatusCode != http.StatusAccepted && resp.StatusCode != http.StatusOK { 500 return nil, fmt.Errorf("%s: %w", ErrUpdateEnrollment, c.Error(resp)) 501 } 502 id, err := GetIDFromLocation(result.Enrollment) 503 if err != nil { 504 return nil, fmt.Errorf("%s: %w: %s", ErrCreateEnrollment, ErrInvalidLocation, err) 505 } 506 result.ID = id 507 508 return &result, nil 509 } 510 511 func (c *cps) RemoveEnrollment(ctx context.Context, params RemoveEnrollmentRequest) (*RemoveEnrollmentResponse, error) { 512 if err := params.Validate(); err != nil { 513 return nil, fmt.Errorf("%s: %w: %s", ErrRemoveEnrollment, ErrStructValidation, err) 514 } 515 516 logger := c.Log(ctx) 517 logger.Debug("RemoveEnrollment") 518 519 uri, err := url.Parse(fmt.Sprintf("/cps/v2/enrollments/%d", params.EnrollmentID)) 520 if err != nil { 521 return nil, fmt.Errorf("%w: parsing URL: %s", ErrRemoveEnrollment, err) 522 } 523 query := uri.Query() 524 if params.AllowCancelPendingChanges != nil { 525 query.Add("allow-cancel-pending-changes", strconv.FormatBool(*params.AllowCancelPendingChanges)) 526 } 527 if params.DeployNotAfter != "" { 528 query.Add("deploy-not-after", params.DeployNotAfter) 529 } 530 if params.DeployNotBefore != "" { 531 query.Add("deploy-not-before", params.DeployNotBefore) 532 } 533 534 uri.RawQuery = query.Encode() 535 req, err := http.NewRequestWithContext(ctx, http.MethodDelete, uri.String(), nil) 536 if err != nil { 537 return nil, fmt.Errorf("%w: failed to create request: %s", ErrRemoveEnrollment, err) 538 } 539 req.Header.Set("Accept", "application/vnd.akamai.cps.enrollment-status.v1+json") 540 541 var result RemoveEnrollmentResponse 542 543 resp, err := c.Exec(req, &result) 544 if err != nil { 545 return nil, fmt.Errorf("%w: request failed: %s", ErrRemoveEnrollment, err) 546 } 547 548 if resp.StatusCode != http.StatusAccepted && resp.StatusCode != http.StatusOK { 549 return nil, fmt.Errorf("%s: %w", ErrRemoveEnrollment, c.Error(resp)) 550 } 551 552 return &result, nil 553 }