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