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